"""Recover the original GalaxyMapControls graph without running the client.
The frozen pointer tables are decoded, rather than treating adjacent strings as
bindings. Native InputChord 913450/8D70E0 independently proves the required-on
and required-off lists; GalaxyMapCamera 3B36F10/3B507D0 proves their consumers.
"""
from pathlib import Path
import argparse
import hashlib
import json
import struct
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--verify', action='store_true')
args = parser.parse_args()
directory = Path('artifacts/galaxy-query-research/palette-resources')
key = '86ec03fba621526059fc6812faca6f10adcb63cb'
f = (directory / (key + '.fres')).read_bytes()
b = (directory / (key + '.indexed1')).read_bytes()
np = struct.unpack_from('<I', f, 0x34)[0]
nb = struct.unpack_from('<I', f, 0x38)[0] - struct.unpack_from('<I', f, 0x40)[0]
nr = struct.unpack_from('<I', f, 0x20)[0]
cursor = total = 0
while total < np:
kind, count = struct.unpack_from('<HH', b, cursor)
if kind not in (2, 3, 4):
raise ValueError('Unexpected original frozen pool group')
cursor += 4
total += count
if total != np:
raise ValueError('Invalid original pool count')
pools = [struct.unpack_from('<8I', b, cursor + i * 32) for i in range(np)]
cursor += np * 32
buffers = [struct.unpack_from('<IIHHIQQ', b, cursor + i * 32) for i in range(nb)]
cursor += nb * 32 + sum(x[3] for x in buffers) * 8
roots = [struct.unpack_from('<4I', b, cursor + i * 16) for i in range(nr)]
cursor += nr * 16
refs = {}
while cursor + 16 <= len(b):
p, o, q, v = struct.unpack_from('<4I', b, cursor)
if p >= np or q >= np or o >= pools[p][2] or v >= pools[q][2]:
break
if (p, o) in refs:
raise ValueError('Duplicate original frozen reference')
refs[p, o] = q, v
cursor += 16
if b[cursor:cursor + 16] != bytes.fromhex('0000000000000000ffbf7f3f08040201'):
raise ValueError('Original frozen delimiter changed')
start = cursor + 16
def at(pointer):
p, o = pointer
if p >= np or o < 0 or o >= pools[p][2]:
raise ValueError('Original pointer exceeds pool')
return start + pools[p][3] + o
def label(pointer):
pos = at(pointer)
return b[pos:b.index(0, pos, pos + 256)].decode('ascii')
name_pos = f.index(b'galaxymapcontrols\0')
name_entry = f.find(struct.pack('<I', name_pos - 144), 43000, 50000)
if name_entry < 0:
raise ValueError('Original named resource entry not found')
resource_id, flags = struct.unpack_from('<II', f, name_entry + 4)
if (resource_id, flags) != (0xE800A1BD, 0x10004):
raise ValueError('Original GalaxyMapControls resource binding changed')
root = next(row for row in roots if row[:2] == (resource_id, 0x42B3EE19))
rp, ro = root[2:]
count = struct.unpack_from('<I', b, at((rp, ro)) + 8)[0]
if count != 100:
raise ValueError('Original control graph node count changed')
table = refs[rp, ro]
nodes = []
for index in range(count):
p, o = refs[table[0], table[1] + index * 8]
# Final semaphore entries use a different layout and are not mouse axes.
if (p, o + 16) not in refs:
continue
name = label(refs[p, o + 16])
if not (name.startswith('Mouse') or name in (
'CamPitch', 'CamYaw', 'CamCrane', 'CameraCraning', 'TranslateX', 'TranslateY')):
continue
row = dict(index=index, pointer=[p, o], name=name,
serializedKind=struct.unpack_from('<I', b, at((p, o)) + 8)[0])
if (p, o + 24) in refs:
row['source'] = label(refs[p, o + 24])
if p == 494:
for offset, field in ((32, 'requiredOn'), (48, 'requiredOff')):
length = struct.unpack_from('<I', b, at((p, o)) + offset + 8)[0]
ptr = refs.get((p, o + offset))
if length > 8 or (length and ptr is None):
raise ValueError('Invalid original InputChord conditions')
row[field] = [label(refs[ptr[0], ptr[1] + i * 8]) for i in range(length)]
if name in ('MouseXAxisScaled', 'MouseYAxisScaled', 'MouseZAxisScaled'):
row['factor'] = struct.unpack_from('<f', b, at((p, o)) + 36)[0]
nodes.append(row)
def require(name, source, on=None, off=None):
matches = [x for x in nodes if x['name'] == name and x.get('source') == source
and (on is None or x.get('requiredOn') == on)
and (off is None or x.get('requiredOff') == off)]
if len(matches) != 1:
raise ValueError('Original mouse binding changed: ' + name)
require('MouseLeft', 'UI_Mouse_Button0')
require('MouseCrane', 'UI_Mouse_Button1')
require('MouseRotate', 'MouseLeft', [], ['MouseIsOverGUI'])
require('MouseTranslate', 'MouseRotate', ['MouseCrane'], ['IsInSystemMap'])
require('MouseCameraCraning', 'MouseCrane', [], ['MouseRotate'])
require('MouseCamPitch', 'MouseYAxisScaled', ['MouseRotate'], ['MouseTranslate'])
require('MouseCamYaw', 'MouseXAxisScaled', ['MouseRotate'], ['MouseTranslate', 'MouseIsOverGUI'])
require('MouseCamYaw', 'MouseXAxisScaled', ['MouseCrane'], ['MouseTranslate', 'MouseIsOverGUI'])
require('MouseCamCrane', 'MouseYAxisScaled', ['MouseCrane'], ['MouseTranslate', 'MouseIsOverGUI'])
require('MouseTranslateX', 'MouseXAxisInverted', ['MouseTranslate'], [])
require('MouseTranslateY', 'MouseYAxisScaled', ['MouseTranslate'], [])
require('MouseTranslate', 'UI_Mouse_Button0', ['IsInSystemMap'], ['PlanetaryViewOn', 'MouseIsOverGUI'])
result = dict(
sourceResource=key,
sourceSha256='29a90189d0bb238c88d09dcc0f15ff751ea29aa208b1a90894ef3b47f8942e5f',
frozenSha256=hashlib.sha256(b).hexdigest(), fresSha256=hashlib.sha256(f).hexdigest(),
pinnedGameSha256='6891f70a021fa9f3a0e00458c284f48d1226e68ae7fc02ad48081aed47857e93',
gameProcessAccess=False, gameCalls=0, networkRequests=0,
resourceName='galaxymapcontrols', resourceId=hex(resource_id),
root=list(root), count=count, nodes=nodes,
nativeEvidence={
'inputChordBind': '913450: config+10 output, +18 source, +20/+28 required-on, +30/+38 required-off',
'inputChordEvaluate': '8D70E0: AND all +24 active bits of on inputs and AND !active of off inputs before forwarding source',
'inputChordCsmBinding': '87800 -> 5F32440 = 512CBF8 -> 8D70E0; init 913450 uses 5F32448',
'invert': '87890 -> 5F326E0 = 512CBE0 -> 8D6F20: source float XOR sign bit',
'cameraBindings': '3B36F10 component base+8 -> CamPitch/CamYaw/CameraCraning/CamCrane/CamDolly/TranslateX/TranslateY',
'cameraUpdate': '3B507D0 camera base+70: +410 pitch, +418 yaw, +428 crane, +438 translateX, +440 translateY',
},
interpretation={
'leftOnly': {'x': 'yaw', 'y': 'pitch'},
'rightOnly': {'x': 'yaw', 'y': 'world height/crane'},
'leftAndRight': {'x': 'horizontal-plane translation from inverted mouse X', 'y': 'horizontal-plane translation from mouse Y'},
'systemMap': 'separate Button0 translation branch; must not replace Galaxy rules',
'scope': 'original frozen control graph plus pinned original native consumers; no live cursor/acceleration validation',
})
output = Path('artifacts/galaxy-query-research/power-outer-sources/original-galaxy-mouse-controls.json')
encoded = json.dumps(result, ensure_ascii=False, indent=2) + '\n'
if args.verify:
if output.read_text(encoding='utf-8') != encoded:
raise ValueError('Recorded original mouse evidence does not match current local resource')
else:
output.write_text(encoded, encoding='utf-8')
print(json.dumps(dict(verified=args.verify, nodes=len(nodes), count=count,
gameProcessAccess=False, output=str(output))))
"""Recover the original GalaxyMapControls graph without running the client.
The frozen pointer tables are decoded, rather than treating adjacent strings as
bindings. Native InputChord 913450/8D70E0 independently proves the required-on
and required-off lists; GalaxyMapCamera 3B36F10/3B507D0 proves their consumers.
"""
from pathlib import Path
import argparse
import hashlib
import json
import struct
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--verify', action='store_true')
args = parser.parse_args()
directory = Path('artifacts/galaxy-query-research/palette-resources')
key = '86ec03fba621526059fc6812faca6f10adcb63cb'
f = (directory / (key + '.fres')).read_bytes()
b = (directory / (key + '.indexed1')).read_bytes()
np = struct.unpack_from('<I', f, 0x34)[0]
nb = struct.unpack_from('<I', f, 0x38)[0] - struct.unpack_from('<I', f, 0x40)[0]
nr = struct.unpack_from('<I', f, 0x20)[0]
cursor = total = 0
while total < np:
kind, count = struct.unpack_from('<HH', b, cursor)
if kind not in (2, 3, 4):
raise ValueError('Unexpected original frozen pool group')
cursor += 4
total += count
if total != np:
raise ValueError('Invalid original pool count')
pools = [struct.unpack_from('<8I', b, cursor + i * 32) for i in range(np)]
cursor += np * 32
buffers = [struct.unpack_from('<IIHHIQQ', b, cursor + i * 32) for i in range(nb)]
cursor += nb * 32 + sum(x[3] for x in buffers) * 8
roots = [struct.unpack_from('<4I', b, cursor + i * 16) for i in range(nr)]
cursor += nr * 16
refs = {}
while cursor + 16 <= len(b):
p, o, q, v = struct.unpack_from('<4I', b, cursor)
if p >= np or q >= np or o >= pools[p][2] or v >= pools[q][2]:
break
if (p, o) in refs:
raise ValueError('Duplicate original frozen reference')
refs[p, o] = q, v
cursor += 16
if b[cursor:cursor + 16] != bytes.fromhex('0000000000000000ffbf7f3f08040201'):
raise ValueError('Original frozen delimiter changed')
start = cursor + 16
def at(pointer):
p, o = pointer
if p >= np or o < 0 or o >= pools[p][2]:
raise ValueError('Original pointer exceeds pool')
return start + pools[p][3] + o
def label(pointer):
pos = at(pointer)
return b[pos:b.index(0, pos, pos + 256)].decode('ascii')
name_pos = f.index(b'galaxymapcontrols\0')
name_entry = f.find(struct.pack('<I', name_pos - 144), 43000, 50000)
if name_entry < 0:
raise ValueError('Original named resource entry not found')
resource_id, flags = struct.unpack_from('<II', f, name_entry + 4)
if (resource_id, flags) != (0xE800A1BD, 0x10004):
raise ValueError('Original GalaxyMapControls resource binding changed')
root = next(row for row in roots if row[:2] == (resource_id, 0x42B3EE19))
rp, ro = root[2:]
count = struct.unpack_from('<I', b, at((rp, ro)) + 8)[0]
if count != 100:
raise ValueError('Original control graph node count changed')
table = refs[rp, ro]
nodes = []
for index in range(count):
p, o = refs[table[0], table[1] + index * 8]
# Final semaphore entries use a different layout and are not mouse axes.
if (p, o + 16) not in refs:
continue
name = label(refs[p, o + 16])
if not (name.startswith('Mouse') or name in (
'CamPitch', 'CamYaw', 'CamCrane', 'CameraCraning', 'TranslateX', 'TranslateY')):
continue
row = dict(index=index, pointer=[p, o], name=name,
serializedKind=struct.unpack_from('<I', b, at((p, o)) + 8)[0])
if (p, o + 24) in refs:
row['source'] = label(refs[p, o + 24])
if p == 494:
for offset, field in ((32, 'requiredOn'), (48, 'requiredOff')):
length = struct.unpack_from('<I', b, at((p, o)) + offset + 8)[0]
ptr = refs.get((p, o + offset))
if length > 8 or (length and ptr is None):
raise ValueError('Invalid original InputChord conditions')
row[field] = [label(refs[ptr[0], ptr[1] + i * 8]) for i in range(length)]
if name in ('MouseXAxisScaled', 'MouseYAxisScaled', 'MouseZAxisScaled'):
row['factor'] = struct.unpack_from('<f', b, at((p, o)) + 36)[0]
nodes.append(row)
def require(name, source, on=None, off=None):
matches = [x for x in nodes if x['name'] == name and x.get('source') == source
and (on is None or x.get('requiredOn') == on)
and (off is None or x.get('requiredOff') == off)]
if len(matches) != 1:
raise ValueError('Original mouse binding changed: ' + name)
require('MouseLeft', 'UI_Mouse_Button0')
require('MouseCrane', 'UI_Mouse_Button1')
require('MouseRotate', 'MouseLeft', [], ['MouseIsOverGUI'])
require('MouseTranslate', 'MouseRotate', ['MouseCrane'], ['IsInSystemMap'])
require('MouseCameraCraning', 'MouseCrane', [], ['MouseRotate'])
require('MouseCamPitch', 'MouseYAxisScaled', ['MouseRotate'], ['MouseTranslate'])
require('MouseCamYaw', 'MouseXAxisScaled', ['MouseRotate'], ['MouseTranslate', 'MouseIsOverGUI'])
require('MouseCamYaw', 'MouseXAxisScaled', ['MouseCrane'], ['MouseTranslate', 'MouseIsOverGUI'])
require('MouseCamCrane', 'MouseYAxisScaled', ['MouseCrane'], ['MouseTranslate', 'MouseIsOverGUI'])
require('MouseTranslateX', 'MouseXAxisInverted', ['MouseTranslate'], [])
require('MouseTranslateY', 'MouseYAxisScaled', ['MouseTranslate'], [])
require('MouseTranslate', 'UI_Mouse_Button0', ['IsInSystemMap'], ['PlanetaryViewOn', 'MouseIsOverGUI'])
result = dict(
sourceResource=key,
sourceSha256='29a90189d0bb238c88d09dcc0f15ff751ea29aa208b1a90894ef3b47f8942e5f',
frozenSha256=hashlib.sha256(b).hexdigest(), fresSha256=hashlib.sha256(f).hexdigest(),
pinnedGameSha256='6891f70a021fa9f3a0e00458c284f48d1226e68ae7fc02ad48081aed47857e93',
gameProcessAccess=False, gameCalls=0, networkRequests=0,
resourceName='galaxymapcontrols', resourceId=hex(resource_id),
root=list(root), count=count, nodes=nodes,
nativeEvidence={
'inputChordBind': '913450: config+10 output, +18 source, +20/+28 required-on, +30/+38 required-off',
'inputChordEvaluate': '8D70E0: AND all +24 active bits of on inputs and AND !active of off inputs before forwarding source',
'inputChordCsmBinding': '87800 -> 5F32440 = 512CBF8 -> 8D70E0; init 913450 uses 5F32448',
'invert': '87890 -> 5F326E0 = 512CBE0 -> 8D6F20: source float XOR sign bit',
'cameraBindings': '3B36F10 component base+8 -> CamPitch/CamYaw/CameraCraning/CamCrane/CamDolly/TranslateX/TranslateY',
'cameraUpdate': '3B507D0 camera base+70: +410 pitch, +418 yaw, +428 crane, +438 translateX, +440 translateY',
},
interpretation={
'leftOnly': {'x': 'yaw', 'y': 'pitch'},
'rightOnly': {'x': 'yaw', 'y': 'world height/crane'},
'leftAndRight': {'x': 'horizontal-plane translation from inverted mouse X', 'y': 'horizontal-plane translation from mouse Y'},
'systemMap': 'separate Button0 translation branch; must not replace Galaxy rules',
'scope': 'original frozen control graph plus pinned original native consumers; no live cursor/acceleration validation',
})
output = Path('artifacts/galaxy-query-research/power-outer-sources/original-galaxy-mouse-controls.json')
encoded = json.dumps(result, ensure_ascii=False, indent=2) + '\n'
if args.verify:
if output.read_text(encoding='utf-8') != encoded:
raise ValueError('Recorded original mouse evidence does not match current local resource')
else:
output.write_text(encoded, encoding='utf-8')
print(json.dumps(dict(verified=args.verify, nodes=len(nodes), count=count,
gameProcessAccess=False, output=str(output))))