Download scripts/analyze_cvar_changes.py from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 7.22 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/8fb42db136c756d4a8a8cc79dbf1dfb2bf24cc2a/scripts/analyze_cvar_changes.py
- Command line
-
hf download hf://nmarshall/urt-rs@8fb42db136c756d4a8a8cc79dbf1dfb2bf24cc2a/scripts/analyze_cvar_changes.py
-
curl -L -o analyze_cvar_changes.py https://huggingface.co/nmarshall/urt-rs/resolve/8fb42db136c756d4a8a8cc79dbf1dfb2bf24cc2a/scripts/analyze_cvar_changes.py
7.22 kB
| #!/usr/bin/env python3 | |
| """Recover bounded qagame cvar change paths without executing any callees.""" | |
| import json | |
| 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 direct_registrations(ins, data, header): | |
| """Index adjacent trap-3 calls; immediate argument candidates are syntactic.""" | |
| result, entry = [], None | |
| for i in ins: | |
| at = i['index'] | |
| if i['opcode'] == 'ENTER': | |
| entry = at | |
| if i['opcode'] != 'CALL' or at == 0 or ins[at-1]['opcode'] != 'CONST' or ins[at-1]['operand'] != -4: | |
| continue | |
| start = max(entry, at-28) | |
| previous_calls = [j for j in range(start, at) if ins[j]['opcode'] == 'CALL'] | |
| if previous_calls: | |
| start = previous_calls[-1]+1 | |
| candidates = {} | |
| for j in range(start, at-1): | |
| if ins[j]['opcode'] == 'CONST' and ins[j+1]['opcode'] == 'ARG': | |
| arg, value = ins[j+1]['operand'], ins[j]['operand'] | |
| item = {'instruction': j, 'value': value} | |
| if arg in (12, 16) and 0 <= value < header['data_length']+header['literal_length']: | |
| item['string'] = vm_string(data, header, value) | |
| candidates[str(arg)] = item | |
| result.append({'function_entry': entry, 'call_instruction': at, | |
| 'immediate_argument_candidates': candidates, 'window': ins[start:at+2]}) | |
| return result | |
| def main(): | |
| source = json.loads(Path('docs/research/qvm/qagame.json').read_text()) | |
| data = verified(Path(source['source']['member']['output']).read_bytes(), source['sha256']) | |
| header, ins = decode(data) | |
| registry = json.loads(Path('docs/research/game-cvars.json').read_text()) | |
| if registry['sha256'] != source['sha256']: | |
| raise ValueError('registry provenance mismatch') | |
| rows = registry['registry']['rows'] | |
| for name, address in [('g_gametype', 34090788), ('g_stratTime', 34077764)]: | |
| if len([r for r in rows if r['name'] == name and r['mirror_address']+12 == address]) != 1: | |
| raise ValueError('strategy setting mirror identity changed') | |
| row, mirror = 0x10000, 0x20000 | |
| traces = {} | |
| # Each fixture follows actual instructions, recording rather than entering calls. | |
| traces['cache_changed'] = trace_slice(ins, 2974, 2997, {row: mirror, row+16: 7, mirror+4: 8}, {20: row}) | |
| traces['cache_unchanged'] = trace_slice(ins, 2974, 3834, {row: mirror, row+16: 8, mirror+4: 8}, {20: row}) | |
| traces['null_mirror'] = trace_slice(ins, 2962, 3834, {row: 0}, {20: row}) | |
| traces['update_call'] = trace_slice(ins, 2962, 2974, {row: mirror}, {20: row}) | |
| traces['notifications_disabled'] = trace_slice(ins, 2997, 3123, {row+20: 0}, {20: row}) | |
| notifications = [] | |
| for comparison, flags in [(0, 0), (1, 0), (1, 32), (1, 33)]: | |
| model = trace_slice(ins, 3018, 3123, {row: mirror, row+4: 0x30000, row+12: flags, mirror+8: 0x3f800000}, | |
| {20: row, 40: comparison}) | |
| notifications.append({'name_comparison_result': comparison, 'raw_flags': flags, 'trace': model}) | |
| word24 = [] | |
| for value in (0, 1, -1): | |
| for phase, start, stop, pointer_local, flag_local in [('register', 2764, 2774, 24, 32), | |
| ('update', 3123, 3133, 20, 28)]: | |
| model = trace_slice(ins, start, stop, {row+24: value}, {pointer_local: row, flag_local: 0}) | |
| word24.append({'phase': phase, 'word24': value, 'flag_local': flag_local, 'trace': model}) | |
| settings = [] | |
| for name, address, start, stop, values in [ | |
| ('g_bombPlantTime', 34080428, 3219, 3260, [-1, 0, 2, 3, 5]), | |
| ('g_funstuff', 34063556, 3585, 3630, [-1, 0, 1, 2]), | |
| ('g_inactivity', 34085460, 3648, 3668, [-1, 0, 1, 9, 10])]: | |
| match = [r for r in rows if r['name'] == name and r['mirror_address']+12 == address] | |
| if len(match) != 1: | |
| raise ValueError('setting mirror identity changed') | |
| for value in values: | |
| settings.append({'name': name, 'input': value, 'integer_address': address, | |
| 'trace': trace_slice(ins, start, stop, {address: value}, {})}) | |
| for gametype in (0, 4, 8): | |
| for value in (-1, 0, 1, 2, 10, 11): | |
| settings.append({'name': 'g_stratTime', 'input': value, 'gametype_raw': gametype, | |
| 'integer_address': 34077764, | |
| 'trace': trace_slice(ins, 3777, 3834, {34077764: value, 34090788: gametype}, {})}) | |
| strings = {str(at): vm_string(data, header, at) for at in | |
| [36187, 36145, 36111, 39360, 36382, 36358, 36345, 37632, 35998, 38919, 38546, 39625, 38431, | |
| 39367, 39400, 39982, 39731, 39293]} | |
| sites = direct_registrations(ins, data, header) | |
| constant_traces = [] | |
| for site in sites: | |
| at = site['call_instruction'] | |
| start = at-9 | |
| sequence = ins[start:at+2] | |
| expected = ['CONST', 'ARG']*4+['CONST', 'CALL', 'POP'] | |
| if [i['opcode'] for i in sequence] != expected or [sequence[j]['operand'] for j in (1, 3, 5, 7)] != [8, 12, 16, 20]: | |
| continue | |
| constant_traces.append({'call_instruction': at, 'trace': trace_slice(ins, start, at+2, {}, {})}) | |
| output = {'source': source['source'], 'sha256': source['sha256'], | |
| 'row_word20_nonzero_names': [r['name'] for r in rows if r['word20_initial']], | |
| 'row_word24_nonzero_names': [r['name'] for r in rows if r['word24_initial']], | |
| 'cache_traces': traces, 'notification_traces': notifications, 'word24_traces': word24, | |
| 'setting_traces': settings, 'strings': strings, | |
| 'direct_registration_sites': sites, 'constant_registration_traces': constant_traces, | |
| 'post_table_registration_trace': trace_slice(ins, 2791, 2813, {}, {}), | |
| 'registration_wrapper_trace': trace_slice(ins, 16095, 16124, {}, | |
| {32: 0x20000, 36: 0x30000, 40: 0x40000, 44: 80}), | |
| 'support_functions': {str(e): partition_function(ins, e, data, header) for e in [2589, 16094, 34364, 69386]}, | |
| 'word24_call_windows': {str(at): ins[at-5:at+3] for at in [2818, 3873]}, | |
| 'limitations': 'Static supplied qagame and bounded synthetic memory only. Calls are not entered; ' | |
| 'zero call results are placeholders, including formatted command pointers. Notification comparison ' | |
| 'results are explicit fixture inputs. Setting slices exclude surrounding name/change gates. Immediate ' | |
| 'registration argument candidates are syntactic, not proven CFG argument flow; computed calls excluded. ' | |
| 'Trap cvar-set calls do not mutate synthetic mirrors. No effective runtime/config/permissions claim.'} | |
| write_json(Path('docs/research/cvar-changes.json'), output) | |
| print(f'Recovered {len(output["direct_registration_sites"])} direct registration sites and {len(settings)} setting fixtures') | |
| if __name__ == '__main__': | |
| main() | |