File size: 4,837 Bytes
71a2cf7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/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()