#!/usr/bin/env python3 """Bounded authentication selector, command-role and internal-mode evidence.""" import json from pathlib import Path from analyze_auth_announcements import recover_table 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 byte_string(address, value): return {address+i: b for i, b in enumerate(value+b'\0')} def cleaned_name(ins, value): address = 0x10000 t = trace_slice(ins, 70242, 70383, {}, {44: address}, limit=10000, byte_memory=byte_string(address, value)) result = [] for at in range(address, address+len(value)+1): b = t['byte_memory'][at] if not b: break result.append(b) return {'input_hex': value.hex(), 'output_hex': bytes(result).hex(), 'trace': t} def substring_trace(ins, haystack, needle): # The length-call results are supplied explicitly; the byte comparison runs. empty = not haystack or not needle or len(needle)>len(haystack) return trace_slice(ins, 69746, 69760 if empty else 69910, {}, {24: len(needle), 28: len(haystack), 72: 0x10000, 76: 0x20000}, limit=10000, byte_memory={**byte_string(0x10000, haystack), **byte_string(0x20000, needle)}) def main(): qvm = json.loads(Path('docs/research/qvm/qagame.json').read_text()) data = verified(Path(qvm['source']['member']['output']).read_bytes(), qvm['sha256']) h, ins = decode(data) rows = json.loads(Path('docs/research/game-cvars.json').read_text()) if rows['sha256'] != qvm['sha256']: raise ValueError('registry provenance mismatch') by_name = {r['name']: r for r in rows['registry']['rows']} for name, address, offset in [('auth_enable',35003268,12),('dedicated',34090492,12), ('auth_groups',35001792,16),('auth_notoriety',35002084,12), ('auth_cmds_friend',35001200,16),('auth_cmds_member',35000904,16), ('auth_cmds_referee',35000608,16)]: if by_name[name]['mirror_address']+offset != address: raise ValueError('cvar mirror identity mismatch') classify = [] for text in [b'',b'0',b'01',b'63',b'123',b'-1',b'+1',b' 1',b'1 ',b'1x',b'Player']: digit_only = all(48 <= x <= 57 for x in text) trace = trace_slice(ins, 253562, 253605 if digit_only else 253669, {}, {376: 0x10000}, limit=2048, byte_memory=byte_string(0x10000,text)) classify.append({'input_hex':text.hex(),'digit_only':digit_only,'trace':trace}) slots = [] base = 0x30000 for slot in [-1,0,3,4]: valid = 0 <= slot < 4 t = trace_slice(ins,253612,253641 if valid else 253638,{34992524:4},{360:slot}) slots.append({'slot':slot,'maxclients':4,'trace':t}) connection = [] for state in [-1,0,1,2]: slot=3;record=base+slot*6336 t=trace_slice(ins,253641,253666 if state==0 else 253875, {34992496:base,record+468:state},{352:slot}) returned = None if state==0 else trace_slice(ins,253875,253889,{34992496:base},{88:record}) connection.append({'state_raw':state,'slot':slot,'trace':t,'success_tail':returned}) substrings=[] for haystack,needle in [(b'AlphaBravo',b'BRAVO'),(b'AlphaBravo',b'phab'),(b'Alpha',b'Gamma'), (b'bigtext say',b'ay'),(b'bigtext say',b'say map'),(b'public',b'PUBLIC'), (b'',b'a'),(b'abc',b''),(b'ab',b'abc')]: substrings.append({'haystack_hex':haystack.hex(),'needle_hex':needle.hex(), 'trace':substring_trace(ins,haystack,needle)}) table=recover_table(data,h,ins,29196,2,8,167097,167279) table_memory={r['vm_address']:r['target_instruction'] for r in table['rows']} role_targets={r['value']:r['target_instruction'] for r in table['rows']} roles=[] for role in [-1,0,1,2,3,4,5,6,7,8,9]: target=role_targets.get(role,167275) t=trace_slice(ins,167148,target,{**table_memory,0x20000+520:0x30000,0x30000+3688:role},{68:0x20000}) roles.append({'role':role,'target':target,'trace':t}) permission_gates=[] for mode,entity,command,state,role in [(0,0x20000,b'whois',2,2),(1,0,b'whois',2,2), (1,0x20000,b'',2,2),(1,0x20000,b'whois',1,2),(1,0x20000,b'whois',2,1), (1,0x20000,b'whois',2,2),(3,0x20000,b'whois',2,8)]: stop = 167103 if mode<1 else 167131 if not entity or not command or state!=2 else 167145 if role<2 else 167148 t=trace_slice(ins,167098,stop,{35017616:mode,entity+520:0x30000,0x30000+468:state,0x30000+3688:role}, {68:entity,72:0x40000},byte_memory=byte_string(0x40000,command)) permission_gates.append({'mode':mode,'entity':entity,'command_hex':command.hex(),'state_raw':state, 'role':role,'reaches_role_switch':stop==167148,'trace':t}) routes=[] for route,start,stop,locals_map in [('client',189074,189083,{3348:0x20000,3120:0x40000}), ('console',263048,263055,{})]: routes.append({'route':route,'trace':trace_slice(ins,start,stop,{},locals_map)}) modes=[] for groups,notoriety in [(0,0),(1,0),(0,5),(1,5),(0,-1)]: modes.append({'groups_length':groups,'notoriety':notoriety, 'trace':trace_slice(ins,308,389,{35002084:notoriety},{80:groups})}) disabled=[] for cause,start,memory,locals_map in [ ('enable_not_one',172,{35003268:0},{}),('bad_engine',201,{}, {68:0}), ('not_dedicated_two',224,{34090492:1},{}),('no_features_tail',279,{}, {76:0}), ('unresolved_address',469,{35017620:0},{})]: disabled.append({'cause':cause,'trace':trace_slice(ins,start,838,memory,locals_map)}) refs=[];callers=[];entry=None for i in ins: at=i['index'] if i['opcode']=='ENTER':entry=at if i['opcode']=='CONST' and i['operand']==35017616: refs.append({'instruction':at,'entry':entry,'window':ins[max(entry,at-4):at+7]}) if i['opcode']=='CALL' and at and ins[at-1]['opcode']=='CONST' and ins[at-1]['operand']==1264: callers.append({'instruction':at,'entry':entry,'window':ins[at-22:at+3]}) output={'source':qvm['source'],'sha256':qvm['sha256'], 'functions':{str(e):partition_function(ins,e,data,h) for e in [171,253561,69723,70241,167097]}, 'digit_classification_traces':classify,'slot_bound_traces':slots,'slot_connection_traces':connection, 'clean_name_traces':[cleaned_name(ins,s) for s in [b'^1Alpha^7',b'A^^B',b'A^xB',b'A\x01\x1f\x7f\x80B']], 'substring_traces':substrings,'permission_role_table':table,'permission_role_traces':roles, 'permission_gate_traces':permission_gates, 'query_direct_callers':callers,'dispatch_traces':routes, 'dispatch_windows':{'client_prefix':ins[188019:188059], 'client_precheck':ins[188059:188105],'client_match':ins[189059:189085], 'console_match':ins[263034:263059],'console_argv':ins[262012:262040]}, 'mode_selection_traces':modes,'disabled_mode_traces':disabled,'mode_literal_references':refs, 'strings':{str(at):vm_string(data,h,at) for at in [77268,87076,38124,38397,38385,38139,38107,38095, 38081,38073,38060,38053,37794,67226,91861,91825,91796,91759,91747]}, 'limitations':'Static supplied qagame plus bounded instruction slices, not a complete QVM/runtime. ' 'All CALLs are recorded and return zero placeholders. Integer parser and substring length-call results ' 'are supplied fixture inputs; selected slices exclude preceding conditions. No complete selector, ' 'permission lifecycle, engine registration/config timing or network/runtime conformance claim.'} write_json(Path('docs/research/auth-queries.json'),output) print(f'Recovered selector helpers, role table, {len(callers)} query callers and {len(refs)} internal-mode references') if __name__=='__main__': main()