urt-rs / scripts /analyze_chat_items_time.py
nmarshall's picture
Recover packed chat item fields and countdown branches
71a2cf7
Raw History Blame Contribute Delete
4.84 kB
#!/usr/bin/env python3
"""Recover packed chat ammo/item arguments and bounded timer substitutions."""
import json
import struct
from pathlib import Path
from analyze_deferred_auth import partition_function, trace_slice
from analyze_game_cvars import vm_string
from analyze_qvm import decode,write_json
from analyze_vm_extensions import verified
def name_candidates(data,h,count=30):
"""Read an explicitly sampled domain; this does not establish table length."""
if not 0<=count<=256:raise ValueError('sample outside low-byte domain')
rows=[]
for index in range(count):
address=4020+432*index
if address+4>h['data_length']:raise ValueError('sample word outside initialized data')
if h['data_offset']+address+4>len(data):raise ValueError('truncated sampled word')
pointer=struct.unpack_from('<I',data,h['data_offset']+address)[0]
rows.append({'index':index,'word_address':address,'pointer':pointer,
'name':vm_string(data,h,pointer) if pointer else None})
return rows
def main():
q=json.loads(Path('docs/research/qvm/qagame.json').read_text())
data=verified(Path(q['source']['member']['output']).read_bytes(),q['sha256']);h,ins=decode(data)
c=json.loads(Path('docs/research/game-cvars.json').read_text())
if c['sha256']!=q['sha256']:raise ValueError('cvar provenance changed')
timelimit=next(r for r in c['registry']['rows'] if r['mirror_address']+12==34088124)
if timelimit['name']!='timelimit':raise ValueError('timer cvar identity changed')
rows=name_candidates(data,h)
entity,record=0x10000,0x20000
ammo=[]
for key,start in [('clips',185415),('bullets',185502)]:
for packed in [0,0x01020304,0xfffe001d,-1]:
for state,override in [(0,0),(3,0),(4,0),(0,1)]:
t=trace_slice(ins,start,186431,{entity+520:record,record+144:2,
record+312+4*2:packed,record+4:state,record+4324:override},{1144:entity})
ammo.append({'key':key,'selected_word_index':2,'packed_word':packed,'raw_state':state,
'override':override,'trace':t})
weapons=[]
for row in rows:
index=row['index'];packed=0xaabbcc00|index
for state in [0,3]:
t=trace_slice(ins,185589,186431,{entity+520:record,record+144:1,record+316:packed,
record+4:state,record+4324:0,row['word_address']:row['pointer']},{1144:entity})
weapons.append({'low_byte':index,'packed_word':packed,'raw_state':state,'trace':t})
game=[]
for minutes,now,start in [(20,1000,0),(0,0,0),(-1,0,0),(1,0,0),(1,59999,0),(1,60000,0),
(1,60001,0),(1,1000,2000),(35792,0,0),(1,2147483640,-2147483640)]:
t=trace_slice(ins,185781,186431,{34088124:minutes,34992532:now,34992544:start},{})
game.append({'timelimit_integer':minutes,'now':now,'start':start,'trace':t})
round_time=[]
for gt in range(0,12):
for delta in [-1,0,1,59999,60000,61000]:
t=trace_slice(ins,185850,186431,{34090788:gt,34992548:100000+delta,34992532:100000},{})
round_time.append({'gametype':gt,'deadline_minus_now':delta,'trace':t})
item=[]
for gt in [0,7,8,9]:
t=trace_slice(ins,185706,186431,{34090788:gt},{})
item.append({'gametype':gt,'trace':t})
random_tail=[]
for value in [0,1,2,3,-1]:
t=trace_slice(ins,185742,186431,{}, {68:value})
random_tail.append({'supplied_helper_36717_return':value,'trace':t})
addresses=[78068,78066,78042,78037,77972,77959,77936,78014,78009,78000,77991]
out={'source':q['source'],'sha256':q['sha256'],'timelimit_registry':timelimit,
'sampled_name_words':rows,'sample_domain':{'first':0,'last':29,'complete_table':False},
'ammo_traces':ammo,'weapon_traces':weapons,'gametime_traces':game,'roundtime_traces':round_time,
'gameitem_traces':item,'gameitem_bit_traces':random_tail,
'strings':{str(a):vm_string(data,h,a) for a in addresses},
'lookup_function':partition_function(ins,185212,data,h),
'limitations':'Static file-backed samples and bounded synthetic slices, not original runtime/full '
'QVM execution. CALLs have zero placeholder returns; helper-36717 bit-tail inputs are supplied, '
'not RNG outputs. Formatter calls are arguments, not text. The name-word sample does not establish '
'table extent, record layout or legal weapon IDs. Packed-field producers, timer global writers, '
'cvar update/wrap behavior, item ownership and platform parity remain open.'}
write_json(Path('docs/research/chat-items-time.json'),out)
print(f'Recovered {len(ammo)} ammo, {len(weapons)} weapon and {len(game)+len(round_time)} timer slices')
if __name__=='__main__':main()