nmarshall commited on
Commit
63e6366
·
1 Parent(s): 529b124

Trace simulation timing error exits and authentication announcements

Browse files
docs/FINDINGS.md CHANGED
@@ -195,3 +195,10 @@ Exact native fork/source revision, runtime resource lookup, legacy launch enviro
195
  **F-034 — Observed: bounded copy and error-service chain.** QVM helper 69338 calls trap 102 with capacity minus one and explicitly NUL-terminates the final destination byte. Null source/nonpositive capacity route through wrappers to game trap 1 and native Com_Error mode 1, not a print-only service. The native handler's full exit/escalation behavior remains open. [DEF-001](research/DEFERRED_AUTH.md).
196
 
197
  **F-035 — Observed: deferred-drop sentinel/timestamp and strict delay.** Eight direct constant-offset references show field 3588 changing from sentinel 1 to current time in the begin callback, then being consumed by the frame callback only for state 2, positive pending value and signed elapsed > 100. Synthetic instruction-slice tests verify 100/101 boundaries and wrap; actual runtime timing, aliases, state names and reentrancy remain open. [DEF-002/003](research/DEFERRED_AUTH.md).
 
 
 
 
 
 
 
 
195
  **F-034 — Observed: bounded copy and error-service chain.** QVM helper 69338 calls trap 102 with capacity minus one and explicitly NUL-terminates the final destination byte. Null source/nonpositive capacity route through wrappers to game trap 1 and native Com_Error mode 1, not a print-only service. The native handler's full exit/escalation behavior remains open. [DEF-001](research/DEFERRED_AUTH.md).
196
 
197
  **F-035 — Observed: deferred-drop sentinel/timestamp and strict delay.** Eight direct constant-offset references show field 3588 changing from sentinel 1 to current time in the begin callback, then being consumed by the frame callback only for state 2, positive pending value and signed elapsed > 100. Synthetic instruction-slice tests verify 100/101 boundaries and wrap; actual runtime timing, aliases, state names and reentrancy remain open. [DEF-002/003](research/DEFERRED_AUTH.md).
198
+
199
+
200
+ **F-036 — Observed/inferred: simulation milliseconds and restart guard.** Both Linux ELF64 server loops derive ticks from 1000/sv_fps, adjust by timescale and pass accumulated game time to VM command 8. This establishes simulation milliseconds for the deferred-drop > 100 comparison. A native time guard branches to shutdown/map command construction above 0x70000000; synthetic wrap cases are not evidence of normal runtime reachability. [CTL-001](research/ENGINE_CONTROL.md).
201
+
202
+ **F-037 — Observed: error frame-abort versus process-exit paths.** Linux mode-1 Com_Error ordinarily shuts down/disconnects and longjmps to the frame boundary. Build-script, close-spaced repeated errors and recursion can select process-exit paths through Sys_Error/_exit(1). Complete cleanup/platform parity remain open. [CTL-002](research/ENGINE_CONTROL.md).
203
+
204
+ **F-038 — Observed: authentication announcement helper.** The begin callback's field-3200 gate sets a one-shot value before invoking helper 165352, whose formats and trap-17 calls announce authentication/account status through print/chat commands. Recipient/mode/state-update semantics and permission rules are not yet specified. [CTL-003](research/ENGINE_CONTROL.md).
docs/RESEARCH_LEDGER.md CHANGED
@@ -88,3 +88,8 @@ Completed RE-218–220 with `analyze_native_coverage.py` and `analyze_auth_drop.
88
  ## 2026-10-11 — Deferred authentication drops
89
 
90
  Completed static milestones RE-222–224: copy-count/final-NUL behavior, invalid-input error-service chain, eight direct pending-field references and sentinel/timestamp transitions, strict signed elapsed > 100 consumer. `analyze_deferred_auth.py` emits source-hashed functions/native cases and bounded synthetic-memory traces; six tests check branch/copy boundaries and reject unsupported slices. Findings F-034/035 and DEF-001–003 distinguish these checks from runtime fixtures. An initial print-path interpretation of the diagnostic wrapper was corrected after tracing function 2319 through trap 1 to native Com_Error(1, ...). No game process or full QVM runtime was launched; tests only evaluate selected decoded instructions without entering callees.
 
 
 
 
 
 
88
  ## 2026-10-11 — Deferred authentication drops
89
 
90
  Completed static milestones RE-222–224: copy-count/final-NUL behavior, invalid-input error-service chain, eight direct pending-field references and sentinel/timestamp transitions, strict signed elapsed > 100 consumer. `analyze_deferred_auth.py` emits source-hashed functions/native cases and bounded synthetic-memory traces; six tests check branch/copy boundaries and reject unsupported slices. Findings F-034/035 and DEF-001–003 distinguish these checks from runtime fixtures. An initial print-path interpretation of the diagnostic wrapper was corrected after tracing function 2319 through trap 1 to native Com_Error(1, ...). No game process or full QVM runtime was launched; tests only evaluate selected decoded instructions without entering callees.
91
+
92
+
93
+ ## 2026-10-11 — Engine timing, errors and announcements
94
+
95
+ Completed static milestones RE-227–229. `analyze_engine_control.py` retains ten native instruction graphs, timing/error windows, the full QVM announcement helper and synthetic gate traces. CTL-001–003/F-036–038 establish simulation millisecond units, native time guard, ordinary mode-1 frame abort and escalation/process exit paths, and one-shot authentication status announcements. Six tests verify syntactic CFG edge handling, native service targets, native tick/VM-command connection and synthetic gate behavior. Computed native jumps and call continuations remain explicitly provisional; no full game/VM execution or runtime conformance is claimed.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -264,5 +264,10 @@ Purpose: turn the supplied installation into an evidence-backed specification of
264
  - [x] RE-222 Recover helper 69338 copy count, terminator write and invalid-input diagnostic chain through game trap 1/native Com_Error. **Evidence:** DEF-001, complete helper bodies and both ELF64 cases; capacity 1/2/128/256 and invalid-input slice tests pass.
265
  - [x] RE-223 Collect all explicit constant-3588 references and associate sentinel initialization, connection reset, begin-time replacement and frame-time reads with source-hashed function partitions. **Evidence:** DEF-002; eight references across four functions; alias/computed-offset coverage remains open.
266
  - [x] RE-224 Recover and verify the deferred drop consumer's strict > 100 signed 32-bit elapsed-time branch and argument forwarding. **Evidence:** DEF-003 and synthetic traces; delay boundaries, state gates, pending values and signed wrap tested without entering callees.
267
- - [ ] RE-225 Trace native Com_Error mode 1 through ordinary, repeated and recursive error paths on every platform; define formatting, shutdown, nonlocal exit and Rust error-boundary behavior.
268
- - [ ] RE-226 Establish game timestamp units and wrap/reset behavior; verify deferred-drop sentinel collisions, stored negative timestamps and connection resets with deterministic runtime traces.
 
 
 
 
 
 
264
  - [x] RE-222 Recover helper 69338 copy count, terminator write and invalid-input diagnostic chain through game trap 1/native Com_Error. **Evidence:** DEF-001, complete helper bodies and both ELF64 cases; capacity 1/2/128/256 and invalid-input slice tests pass.
265
  - [x] RE-223 Collect all explicit constant-3588 references and associate sentinel initialization, connection reset, begin-time replacement and frame-time reads with source-hashed function partitions. **Evidence:** DEF-002; eight references across four functions; alias/computed-offset coverage remains open.
266
  - [x] RE-224 Recover and verify the deferred drop consumer's strict > 100 signed 32-bit elapsed-time branch and argument forwarding. **Evidence:** DEF-003 and synthetic traces; delay boundaries, state gates, pending values and signed wrap tested without entering callees.
267
+ - [ ] RE-225 Trace native Com_Error mode 1 through ordinary, repeated and recursive error paths on every platform; define formatting, shutdown, nonlocal exit and Rust error-boundary behavior. **Progress 2026-10-11:** Linux ELF64 mode-1 nonlocal exit, mode-zero process exit, build-script override, recursive path and signed 99 ms repeat window recorded ([CTL-002](research/ENGINE_CONTROL.md)); full cleanup, fixtures and other platforms remain open.
268
+ - [ ] RE-226 Establish game timestamp units and wrap/reset behavior; verify deferred-drop sentinel collisions, stored negative timestamps and connection resets with deterministic runtime traces. **Progress 2026-10-11:** Native server tick calculation establishes simulation milliseconds and a > 0x70000000 time restart guard ([CTL-001](research/ENGINE_CONTROL.md)); reset ordering/runtime behavior remain open.
269
+ - [x] RE-227 Recover Linux ELF64 server tick units, timescale calculation, game-time argument to VM command 8 and the native time-wrap guard. **Evidence:** CTL-001, both SV_Frame graphs/windows and Sys_Milliseconds bodies. Full scheduling/reset/runtime behavior remains RE-226.
270
+ - [x] RE-228 Recover ordinary Linux mode-1 error shutdown/frame-abort path and build-script, repeated-error and recursion escalation branches. **Evidence:** CTL-002; both Com_Error, Com_Frame and Sys_Error graphs. Full helper cleanup and other platforms remain RE-225.
271
+ - [x] RE-229 Identify begin callback field-3200 one-shot gate and helper 165352 as an authentication status announcement formatter/service caller. **Evidence:** CTL-003, helper body/string operands and five synthetic gate cases; recipient/state-update semantics remain open.
272
+ - [ ] RE-230 Recover authentication announcement mode/recipient/role formatting branches and every field-3200 reset or alias write; compare UI output with deterministic runtime fixtures.
273
+ - [ ] RE-231 Trace native event-loop elapsed-time production, timescale/pause/catch-up/map-restart paths and connect/begin/frame callback ordering on every platform.
docs/research/DEFERRED_AUTH.md CHANGED
@@ -40,3 +40,5 @@ The begin callback also uses record field 3200 as a one-shot gate for helper 165
40
  Six tests cover strict delay boundaries, state/pending/wrap gates, sentinel replacement under the authentication-enabled gate, copy count/NUL positions, invalid-input diagnostic calls, native error targets, and fail-closed unknown reads/opcodes/control-flow bounds. The evaluator intentionally rejects full function prologues and arbitrary callees.
41
 
42
  RE-222–224 complete these static deliverables. RE-221 remains open for runtime fixtures, reentrancy, authoritative field/state naming, alias coverage and complete state-transition behavior. Follow the formatting helper and native error handler, test buffer contents and repeated drops in an isolated reference harness, and compare Windows/macOS service behavior before implementing compatibility policy in Rust.
 
 
 
40
  Six tests cover strict delay boundaries, state/pending/wrap gates, sentinel replacement under the authentication-enabled gate, copy count/NUL positions, invalid-input diagnostic calls, native error targets, and fail-closed unknown reads/opcodes/control-flow bounds. The evaluator intentionally rejects full function prologues and arbitrary callees.
41
 
42
  RE-222–224 complete these static deliverables. RE-221 remains open for runtime fixtures, reentrancy, authoritative field/state naming, alias coverage and complete state-transition behavior. Follow the formatting helper and native error handler, test buffer contents and repeated drops in an isolated reference harness, and compare Windows/macOS service behavior before implementing compatibility policy in Rust.
43
+
44
+ Follow-up: [CTL-001–003](ENGINE_CONTROL.md) establishes simulation millisecond units, a native pre-wrap restart guard, ordinary Linux error frame-abort semantics and the field-3200 status-announcement helper. Earlier unit/error/helper unknowns above are refined for these specific Linux/static boundaries; runtime and platform tasks remain open.
docs/research/ENGINE_CONTROL.md ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Simulation clock, error exits and authentication announcements
2
+
3
+ Static pass, 2026-10-11. Reproduce with `python3 scripts/analyze_engine_control.py`; validate with `python3 scripts/test_engine_control.py`. [Generated evidence](engine-control.json) contains ten instruction graphs across the two Linux ELF64 engines, selected timing/error instruction windows, the QVM announcement helper and synthetic gate traces. No original game process or complete VM was launched.
4
+
5
+ ## CTL-001: server simulation time
6
+
7
+ Both engines' `SV_Frame` functions compute an integer base tick **1000 / sv_fps**, convert it to floating point, multiply by `com_timescale`, then truncate to an integer. A nonpositive result follows a correction path that assigns one. Each simulation step advances the server game-time field at `sv + 0x50850`, places that new value in `%edx`, and invokes `VM_Call(gvm, 8, game_time)`. The QVM frame callback copies its incoming value to global address 34992532, which supplies the deferred-drop comparison.
8
+
9
+ This establishes millisecond simulation units through the FPS-based tick calculation. The earlier strict elapsed > 100 condition therefore means **more than 100 simulation milliseconds**, checked only when the game frame callback runs. Its wall-clock latency depends on tick scheduling, timescale, pauses and callback order; it is not an independently scheduled 100 ms network timer.
10
+
11
+ Dedicated VM transfer: `0x412a12`; client-engine equivalent: `0x47a992`. Both occur inside the simulation catch-up loop. `Sys_Milliseconds` independently uses `gettimeofday`, a seconds base, multiplication by 1000 and a compiler reciprocal sequence for microseconds divided by 1000, returning a 32-bit result. This documents the host timing primitive; every event-loop adjustment between host time and frame elapsed time remains untraced.
12
+
13
+ The server loop also tests `svs + 0x4` against **0x70000000** and has a greater-than branch to `SV_Shutdown` followed by map-command construction. It additionally guards snapshot-counter space. Thus prior synthetic signed-wrap examples describe instruction arithmetic, not proof that a normal uninterrupted server reaches the same wrap state. Exact restart/reset ordering and any bypasses still need fixtures.
14
+
15
+ ## CTL-002: native error mode 1
16
+
17
+ The game error trap invokes `Com_Error` with mode 1. Static branch inspection establishes the following paths in both Linux ELF64 images:
18
+
19
+ | Condition/path | Observed operations |
20
+ | --- | --- |
21
+ | Ordinary mode 1, with no escalation or recursion | Format the diagnostic; update an error cvar; print the error; call `SV_Shutdown`; call `CL_Disconnect(1)` and `CL_FlushMemory`; clear loaded-pak state and the error-entered flag; call `longjmp` with value -1. |
22
+ | Positive `com_buildScript` value | Replace the requested mode with zero before dispatch. |
23
+ | Signed time since previous error ≤ 99 ms | Increment the retained error counter. If the incremented counter is greater than three, replace the mode with zero. Elapsed > 99 resets the counter instead. |
24
+ | Error-entered flag already nonzero | Call `Sys_Error` with the recursive-error diagnostic. |
25
+ | Mode-zero shutdown path | Shut down client/server, close log/journal files where present and call `Sys_Error`. |
26
+
27
+ `Com_Frame` establishes `_setjmp` using the frame-abort buffer and branches out when its result is nonzero. `Com_Error` targets the shared buffer through `longjmp`. Consequently the ordinary error path abandons the current frame after shutdown; it is not a normal return to the copy helper. The provided `Sys_Error` functions terminate through `_exit(1)` after diagnostic/terminal cleanup paths. The graph collector retains potential instruction continuation after calls, so nonreturn behavior must be interpreted using these service semantics, not graph reachability alone.
28
+
29
+ The repeat counter's exact state is important: do not simplify the condition to “the fourth error always exits.” The previous timestamp, counter reset, build-script setting and recursion state alter that outcome. Formatting uses a native `vsprintf` dependency in these bodies; a Rust error boundary will need explicit message ownership, bounded formatting policy and ordered shutdown/frame-abort behavior rather than a direct nonlocal-jump translation.
30
+
31
+ Windows/macOS parity, repeated-error runtime sequences, subsystem cleanup and all mode values remain RE-225. This pass specifies the observed Linux mode-1 boundary and escalation structure only.
32
+
33
+ ## CTL-003: begin callback announcement gate
34
+
35
+ The QVM begin slice at instructions 174979–175026 checks positive global 35017616, handles the pending sentinel/timestamp, and then checks field 3200. With no pending value at least one and field 3200 equal to zero, it sets field 3200 to one and invokes helper **165352** with `(client_record_pointer, 1, 0)`. Disabled/nonpositive global, existing announcement flag or pending drop suppress this call. Synthetic-memory traces verify these gates and forwarded arguments.
36
+
37
+ Helper 165352 formats authentication/account information, including no-account/disabled/banned/error text, account roles, login, notoriety and level. Its second argument controls a private-message prefix; its body formats print/chat command strings and invokes trap 17 through four observed call sites. These operations identify an authentication status announcement helper; the earlier generic admission/authentication interpretation is narrowed by the actual body.
38
+
39
+ The third argument and all formatting branches, output-recipient selection, role values, announcement-flag reset/alias writes and interaction with authentication updates remain open. Observing role names in strings does not establish the permission system or server trust policy. The positive-global gate's complete semantics likewise remain provisional.
40
+
41
+ ## Tooling and verification limits
42
+
43
+ The control-flow collector records decoded instruction addresses, direct conditional/unconditional edges, return edges, call targets and possible call continuations. Computed jumps remain explicitly unresolved. Tests verify malformed entries/duplicate addresses, branch/call/return edges, unresolved jumps, every local real-graph edge, native shutdown/error/time imports and synthetic announcement gates. These are syntactic graphs and local static checks, not full native decompilation or runtime reference traces.
44
+
45
+ RE-227–229 record the completed static evidence milestones. Follow up complete frame scheduling/reset behavior, error mode parity and announcement state/output semantics before implementing their Rust counterparts.
docs/research/README.md CHANGED
@@ -14,6 +14,8 @@ The [native metadata follow-up](native/METADATA.md) recovers SDL/client debug-ma
14
 
15
  [Deferred authentication analysis](DEFERRED_AUTH.md) traces message-copy/error services and the sentinel/timestamp state branches, with bounded synthetic-memory instruction checks distinct from runtime conformance.
16
 
 
 
17
  ## Reproduce
18
 
19
  From the repository root, with the verified source installation and extracted assets present:
@@ -25,10 +27,11 @@ python3 scripts/test_vm_extensions.py
25
  python3 scripts/test_native_metadata.py
26
  python3 scripts/test_native_followup.py
27
  python3 scripts/test_deferred_auth.py
 
28
  python3 scripts/storage_policy.py check --require-setup
29
  ```
30
 
31
- The pipeline runs `fetch_analysis_references.py`, `analyze_qvm.py`, `analyze_native.py`, `analyze_vm_abi.py`, `analyze_native_dispatch.py`, `analyze_vm_extensions.py`, `analyze_native_metadata.py`, `analyze_native_coverage.py`, `analyze_auth_drop.py`, and `analyze_deferred_auth.py` in that order. Python 3.9+ and **LLVM objdump** with x86 ELF/COFF/Mach-O support are required. This pass used Apple LLVM 17.0.0 on macOS arm64; it disassembled x86 images without running them. Upstream references are downloaded only if absent, checked against the committed SHA-256 manifest, and cached under `.cache/research/upstream/`. A clone also needs `git lfs pull` to obtain the derived artifacts/extracted members.
32
 
33
  Generated JSON/JSONL and compressed evidence are regenerated in place; keep prose notes in Markdown. Rerunning the four analysis stages reproduced every generated report and gzip artifact byte-for-byte on the same toolchain. gzip timestamps and stored filenames are fixed. Different objdump versions can produce different disassembly text, so tool version and uncompressed evidence hashes are recorded. Reports are UTF-8 normal Git text, with individual files below the storage policy's 2 MiB limit.
34
 
 
14
 
15
  [Deferred authentication analysis](DEFERRED_AUTH.md) traces message-copy/error services and the sentinel/timestamp state branches, with bounded synthetic-memory instruction checks distinct from runtime conformance.
16
 
17
+ [Engine control findings](ENGINE_CONTROL.md) link native simulation timing, frame-abort/process-exit paths and the QVM authentication announcement gate to address-keyed instruction graphs.
18
+
19
  ## Reproduce
20
 
21
  From the repository root, with the verified source installation and extracted assets present:
 
27
  python3 scripts/test_native_metadata.py
28
  python3 scripts/test_native_followup.py
29
  python3 scripts/test_deferred_auth.py
30
+ python3 scripts/test_engine_control.py
31
  python3 scripts/storage_policy.py check --require-setup
32
  ```
33
 
34
+ The pipeline runs `fetch_analysis_references.py`, `analyze_qvm.py`, `analyze_native.py`, `analyze_vm_abi.py`, `analyze_native_dispatch.py`, `analyze_vm_extensions.py`, `analyze_native_metadata.py`, `analyze_native_coverage.py`, `analyze_auth_drop.py`, `analyze_deferred_auth.py`, and `analyze_engine_control.py` in that order. Python 3.9+ and **LLVM objdump** with x86 ELF/COFF/Mach-O support are required. This pass used Apple LLVM 17.0.0 on macOS arm64; it disassembled x86 images without running them. Upstream references are downloaded only if absent, checked against the committed SHA-256 manifest, and cached under `.cache/research/upstream/`. A clone also needs `git lfs pull` to obtain the derived artifacts/extracted members.
35
 
36
  Generated JSON/JSONL and compressed evidence are regenerated in place; keep prose notes in Markdown. Rerunning the four analysis stages reproduced every generated report and gzip artifact byte-for-byte on the same toolchain. gzip timestamps and stored filenames are fixed. Different objdump versions can produce different disassembly text, so tool version and uncompressed evidence hashes are recorded. Reports are UTF-8 normal Git text, with individual files below the storage policy's 2 MiB limit.
37
 
docs/research/engine-control.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_binaries.py CHANGED
@@ -11,7 +11,7 @@ def main():
11
  for script in ('fetch_analysis_references.py', 'analyze_qvm.py', 'analyze_native.py',
12
  'analyze_vm_abi.py', 'analyze_native_dispatch.py', 'analyze_vm_extensions.py',
13
  'analyze_native_metadata.py', 'analyze_native_coverage.py', 'analyze_auth_drop.py',
14
- 'analyze_deferred_auth.py'):
15
  subprocess.run([sys.executable, str(Path('scripts')/script)], check=True)
16
 
17
 
 
11
  for script in ('fetch_analysis_references.py', 'analyze_qvm.py', 'analyze_native.py',
12
  'analyze_vm_abi.py', 'analyze_native_dispatch.py', 'analyze_vm_extensions.py',
13
  'analyze_native_metadata.py', 'analyze_native_coverage.py', 'analyze_auth_drop.py',
14
+ 'analyze_deferred_auth.py', 'analyze_engine_control.py'):
15
  subprocess.run([sys.executable, str(Path('scripts')/script)], check=True)
16
 
17
 
scripts/analyze_engine_control.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Selected native control-flow evidence for simulation timing and errors."""
3
+ import gzip
4
+ import json
5
+ from pathlib import Path
6
+ import re
7
+ from analyze_qvm import decode, write_json
8
+ from analyze_vm_extensions import verified, native_functions, command_callers
9
+ from analyze_deferred_auth import partition_function, trace_slice
10
+
11
+
12
+ def control_flow(function):
13
+ instructions = []
14
+ for line in function['lines']:
15
+ m = re.match(r'\s*([0-9a-f]+):[^\t]*\t(\w+)\s*(.*)', line)
16
+ if m:
17
+ instructions.append({'address': int(m[1], 16), 'opcode': m[2], 'operands': m[3], 'line': line.strip()})
18
+ if not instructions or instructions[0]['address'] != function['address']:
19
+ raise ValueError('missing function entry instruction')
20
+ addresses = {i['address'] for i in instructions}
21
+ if len(addresses) != len(instructions):
22
+ raise ValueError('duplicate instruction addresses')
23
+ edges, calls = [], []
24
+ for index, ins in enumerate(instructions):
25
+ op, operands, address = ins['opcode'], ins['operands'], ins['address']
26
+ next_address = instructions[index+1]['address'] if index+1 < len(instructions) else None
27
+ target = re.match(r'0x([0-9a-f]+)', operands)
28
+ target = int(target[1], 16) if target else None
29
+ name = re.search(r'<([^>]+)>', operands)
30
+ if op.startswith('call'):
31
+ calls.append({'address': address, 'target': target, 'label': name[1] if name else None})
32
+ if op.startswith(('j', 'loop')):
33
+ edges.append({'from': address, 'to': target, 'kind': 'unconditional' if op in ('jmp', 'jmpq') else 'conditional_taken',
34
+ 'target_scope': 'unresolved_indirect' if target is None else 'local' if target in addresses else 'external'})
35
+ if op in ('jmp', 'jmpq'):
36
+ continue
37
+ if op.startswith('ret'):
38
+ edges.append({'from': address, 'to': None, 'kind': 'return', 'target_scope': 'external'})
39
+ continue
40
+ if next_address is not None:
41
+ edges.append({'from': address, 'to': next_address,
42
+ 'kind': 'possible_call_continuation' if op.startswith('call') else 'fallthrough', 'target_scope': 'local'})
43
+ return {'entry': function['address'], 'instructions': instructions, 'edges': edges, 'calls': calls,
44
+ 'limitations': 'Syntactic instruction graph, not a reachability proof. Computed jumps remain unresolved; '
45
+ 'calls retain possible continuation even for known nonreturning external services.'}
46
+
47
+
48
+ def windows(function, patterns, before=4, after=8):
49
+ ranges = set()
50
+ lines = function['lines']
51
+ for i, line in enumerate(lines):
52
+ if any(re.search(pattern, line) for pattern in patterns):
53
+ ranges.update(range(max(0, i-before), min(len(lines), i+after)))
54
+ return [lines[i].strip() for i in sorted(ranges)]
55
+
56
+
57
+ def main():
58
+ output = {'native': {}, 'limitations': 'Static Linux ELF64 evidence. No original game process is launched. '
59
+ 'Instruction windows and control-flow edges are evidence rather than complete decompilation or platform parity.'}
60
+ names = ['Com_Error', 'Com_Frame', 'SV_Frame', 'Sys_Milliseconds', 'Sys_Error']
61
+ for label in ('linux--Quake3-UrT-Ded.x86_64', 'linux--Quake3-UrT.x86_64'):
62
+ report = json.loads(Path(f'docs/research/native/{label}.json').read_text())
63
+ verified((Path('.cache/UrbanTerror43')/report['source']['path']).read_bytes(), report['source']['sha256'])
64
+ artifact = report['artifacts']['disassembly.txt']
65
+ text = verified(gzip.open(artifact['path'], 'rb').read(), artifact['uncompressed_sha256']).decode()
66
+ functions = native_functions(text)
67
+ output['native'][label] = {'source': report['source'], 'disassembly_sha256': artifact['uncompressed_sha256'],
68
+ 'graphs': {name: control_flow(functions[name]) for name in names},
69
+ 'frame_command_candidates': [r for r in command_callers({'SV_Frame': functions['SV_Frame']}) if r['command_candidate'] == 8],
70
+ 'clock_windows': windows(functions['SV_Frame'], [r'<sv_fps>', r'\$0x3e8', r'<VM_Call>', r'\$0x70000000']),
71
+ 'error_windows': windows(functions['Com_Error'], [r'\$0x63', r'<errorCount', r'<com_buildScript>', r'<com_errorEntered>', r'longjmp', r'<Sys_Error>'])}
72
+ report = json.loads(Path('docs/research/qvm/qagame.json').read_text())
73
+ data = verified(Path(report['source']['member']['output']).read_bytes(), report['sha256'])
74
+ header, instructions = decode(data)
75
+ helper = partition_function(instructions, 165352, data, header)
76
+ synthetic = []
77
+ record = 0x10000
78
+ for enabled, pending, announced in [(1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1000, 0), (0, 0, 0)]:
79
+ model = trace_slice(instructions, 174979, 175027,
80
+ {35017616: enabled, record+3588: pending, record+3200: announced, 34992532: 1234}, {24: record})
81
+ synthetic.append({'positive_auth_gate': enabled, 'pending': pending, 'announcement_flag': announced,
82
+ 'result_flag': model['memory'][record+3200], 'calls': model['calls'], 'trace': model['trace']})
83
+ output['qvm'] = {'source': report['source'], 'sha256': report['sha256'], 'announcement_helper': helper,
84
+ 'begin_slice': instructions[174979:175027], 'synthetic_gate_traces': synthetic}
85
+ write_json(Path('docs/research/engine-control.json'), output)
86
+ print('Recovered ten native instruction graphs, clock/error windows and auth announcement gate')
87
+
88
+
89
+ if __name__ == '__main__':
90
+ main()
scripts/test_engine_control.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Control-flow edges and actual engine service/gate evidence checks."""
3
+ import json
4
+ from pathlib import Path
5
+ import unittest
6
+ from analyze_engine_control import control_flow
7
+
8
+
9
+ class EngineControlTests(unittest.TestCase):
10
+ def test_conditional_call_return_edges(self):
11
+ f = {'address': 1, 'lines': [' 1: aa\tje\t0x4', ' 2: aa\tcallq\t0x10 <external>',
12
+ ' 3: aa\tjmp\t0x4', ' 4: c3\tretq']}
13
+ graph = control_flow(f)
14
+ self.assertEqual(len(graph['edges']), 5)
15
+ self.assertIn({'from': 2, 'to': 3, 'kind': 'possible_call_continuation', 'target_scope': 'local'}, graph['edges'])
16
+ self.assertEqual(graph['calls'][0]['target'], 16)
17
+
18
+ def test_indirect_jump_is_not_invented(self):
19
+ graph = control_flow({'address': 1, 'lines': [' 1: aa\tjmpq\t*%rax', ' 2: aa\tnop']})
20
+ self.assertEqual(graph['edges'], [{'from': 1, 'to': None, 'kind': 'unconditional', 'target_scope': 'unresolved_indirect'}])
21
+
22
+ def test_bad_entry_and_duplicate_addresses_rejected(self):
23
+ for lines in [[' 2: aa\tnop'], [' 1: aa\tnop', ' 1: aa\tretq']]:
24
+ with self.assertRaises(ValueError):
25
+ control_flow({'address': 1, 'lines': lines})
26
+
27
+ def test_real_graphs_and_error_exit_services(self):
28
+ report = json.loads(Path('docs/research/engine-control.json').read_text())
29
+ for n in report['native'].values():
30
+ for graph in n['graphs'].values():
31
+ addresses = {i['address'] for i in graph['instructions']}
32
+ self.assertIn(graph['entry'], addresses)
33
+ for e in graph['edges']:
34
+ self.assertIn(e['from'], addresses)
35
+ if e['target_scope'] == 'local':
36
+ self.assertIn(e['to'], addresses)
37
+ errors = {c['label'] for c in n['graphs']['Com_Error']['calls']}
38
+ self.assertTrue({'longjmp@plt', 'Sys_Error', 'SV_Shutdown'} <= errors)
39
+ self.assertIn('_exit@plt', {c['label'] for c in n['graphs']['Sys_Error']['calls']})
40
+ self.assertIn('_setjmp@plt', {c['label'] for c in n['graphs']['Com_Frame']['calls']})
41
+ self.assertIn('gettimeofday@plt', {c['label'] for c in n['graphs']['Sys_Milliseconds']['calls']})
42
+
43
+ def test_actual_announcement_gates_and_literals(self):
44
+ q = json.loads(Path('docs/research/engine-control.json').read_text())['qvm']
45
+ self.assertEqual([len(r['calls']) for r in q['synthetic_gate_traces']], [1, 0, 0, 0, 0])
46
+ self.assertEqual(q['synthetic_gate_traces'][0]['calls'][0]['target'], 165352)
47
+ self.assertEqual(q['synthetic_gate_traces'][0]['calls'][0]['args'], {'8': 0x10000, '12': 1, '16': 0})
48
+ self.assertEqual(q['synthetic_gate_traces'][0]['result_flag'], 1)
49
+ literals = {r['text'] for r in q['announcement_helper']['literal_argument_candidates']}
50
+ self.assertIn('print "%s"', literals)
51
+ self.assertIn('chat "%s"', literals)
52
+ self.assertIn(' - ^7no account', literals)
53
+
54
+ def test_real_clock_tick_and_vm_command_connection(self):
55
+ for n in json.loads(Path('docs/research/engine-control.json').read_text())['native'].values():
56
+ self.assertEqual(len(n['frame_command_candidates']), 1)
57
+ self.assertEqual(n['frame_command_candidates'][0]['function'], 'SV_Frame')
58
+ lines = '\n'.join(i['line'] for i in n['graphs']['SV_Frame']['instructions'])
59
+ self.assertIn('<sv_fps>', lines)
60
+ self.assertIn('<com_timescale>', lines)
61
+ self.assertIn('$0x3e8', lines)
62
+ self.assertIn('idivl', lines)
63
+ self.assertIn('$0x70000000', lines)
64
+
65
+
66
+ if __name__ == '__main__':
67
+ unittest.main()