Recover team leader and location lookup paths
Browse files- docs/FINDINGS.md +6 -0
- docs/PROVISIONAL_TODO.md +1 -1
- docs/RESEARCH_LEDGER.md +4 -0
- docs/REVERSE_ENGINEERING_TODO.md +5 -0
- docs/research/CHAT_EXPANSION.md +2 -0
- docs/research/CHAT_LOCATIONS.md +48 -0
- docs/research/README.md +4 -1
- docs/research/chat-locations.json +0 -0
- scripts/analyze_binaries.py +1 -1
- scripts/analyze_chat_locations.py +86 -0
- scripts/test_chat_locations.py +85 -0
docs/FINDINGS.md
CHANGED
|
@@ -270,3 +270,9 @@ Bounded qagame paths recover bomb-plant/inactivity correction requests, direct f
|
|
| 270 |
**F-065 — Observed sampled item names:** weapon-name words use 4020+432*low_byte; sampled indices 0–29 include duplicate None strings and a NULL pointer at 18. This does not establish legal IDs or complete table extent. Game-item types 7/8 select flag/bomb; other paths branch on helper-36717's least-significant bit with RNG semantics unresolved. [ITEM-002](research/CHAT_ITEMS_TIME.md).
|
| 271 |
|
| 272 |
**F-066 — Observed countdown predicates:** gametime uses registered timelimit integer times 60000 minus elapsed; nonpositive results request no timelimit even at expiry. Roundtime requires positive remaining and types 4/5/8/10. Boundary/wrap traces are synthetic; timer writers, active cvar bounds, output and original-runtime conformance remain open. [ITEM-003](research/CHAT_ITEMS_TIME.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
**F-065 — Observed sampled item names:** weapon-name words use 4020+432*low_byte; sampled indices 0–29 include duplicate None strings and a NULL pointer at 18. This does not establish legal IDs or complete table extent. Game-item types 7/8 select flag/bomb; other paths branch on helper-36717's least-significant bit with RNG semantics unresolved. [ITEM-002](research/CHAT_ITEMS_TIME.md).
|
| 271 |
|
| 272 |
**F-066 — Observed countdown predicates:** gametime uses registered timelimit integer times 60000 minus elapsed; nonpositive results request no timelimit even at expiry. Roundtime requires positive remaining and types 4/5/8/10. Boundary/wrap traces are synthetic; timer writers, active cvar bounds, output and original-runtime conformance remain open. [ITEM-003](research/CHAT_ITEMS_TIME.md).
|
| 273 |
+
|
| 274 |
+
**F-067 — Observed leader selection:** helper 169397 returns the first nonzero-state record with matching team and nonzero leader flag, or -1. The leader substitution uses a stride-880 record-pointer/name address chain on success; isolated failure skips its formatter. Role/state lifecycle, caller default composition and runtime output remain open. [LOC-001](research/CHAT_LOCATIONS.md).
|
| 275 |
+
|
| 276 |
+
**F-068 — Observed cached location contract:** nonzero object field 848 returns only when signed now < pointed-field 596; equality misses. Wrapper 268637 returns zero without formatting on NULL, otherwise formats pointed-field 592 and returns one. Cache writers, source validity, caller failure handling and signed-wrap conformance remain open. [LOC-002](research/CHAT_LOCATIONS.md).
|
| 277 |
+
|
| 278 |
+
**F-069 — Observed location list/search structure:** guarded registration publishes unknown at configstring 640, numbers qualifying objects and inserts reverse-order links through *(object+524)+24. Static lookup retains first trigger_location in a 40/40/52 query box, then distance <= best plus visibility fallback. Geometry/engine services, raw tag identity, list lifecycle and runtime/platform parity remain unverified. [LOC-003](research/CHAT_LOCATIONS.md).
|
docs/PROVISIONAL_TODO.md
CHANGED
|
@@ -33,7 +33,7 @@ For implementation items, the minimum done condition is a documented contract, a
|
|
| 33 |
|
| 34 |
### First research batch
|
| 35 |
|
| 36 |
-
Progress: [RE-232–234 / TIME-001–003](research/FRAME_TIMING.md) now cover Linux ELF64 elapsed modification, event sources and pause predicates. The [RE-237–239 follow-up](research/EVENT_QUEUES.md) resolves the pause-field address/bot chain and adds queue/journal evidence. The [RE-241–243 announcement pass](research/AUTH_ANNOUNCEMENTS.md) recovers label tables and recipient paths; The [RE-245–247 cvar pass](research/GAME_CVARS.md) adds all 126 main registration rows and resolves auth_verbosity/callers. The [RE-249–251 change pass](research/CVAR_CHANGES.md) adds cache/notification behavior, four numeric correction branches and direct registration coverage. The [RE-253–255 query pass](research/AUTH_QUERIES.md) adds selector helpers, command-role routing and internal authentication modes. The [RE-257–259 bounds/entry pass](research/SELECTOR_BOUNDS.md) adds integer parsing, selector storage/count evidence and native/QVM/chat route connections. The [RE-261–263 chat pass](research/CHAT_PATHS.md) adds native command metadata/filter evidence and bounded message/recipient paths, including the documented argument-mapping correction. The [RE-265–267 delivery pass](research/CHAT_DELIVERY.md) adds bounded dead-chat predicates, formatter argument routing and echo/fanout calls. The [RE-269–271 expansion pass](research/CHAT_EXPANSION.md) adds marker states, twelve key comparisons and health/momo branches. The [RE-273–275 item/time pass](research/CHAT_ITEMS_TIME.md) adds packed ammunition fields, sampled names and countdown branches. PROV-002 remains open for full output/reset coverage. PROV-001 remains open for complete scheduling and platform coverage.
|
| 37 |
|
| 38 |
Start with the open questions raised by the current binary evidence. These tasks depend on the linked reports, not on a working rewrite.
|
| 39 |
|
|
|
|
| 33 |
|
| 34 |
### First research batch
|
| 35 |
|
| 36 |
+
Progress: [RE-232–234 / TIME-001–003](research/FRAME_TIMING.md) now cover Linux ELF64 elapsed modification, event sources and pause predicates. The [RE-237–239 follow-up](research/EVENT_QUEUES.md) resolves the pause-field address/bot chain and adds queue/journal evidence. The [RE-241–243 announcement pass](research/AUTH_ANNOUNCEMENTS.md) recovers label tables and recipient paths; The [RE-245–247 cvar pass](research/GAME_CVARS.md) adds all 126 main registration rows and resolves auth_verbosity/callers. The [RE-249–251 change pass](research/CVAR_CHANGES.md) adds cache/notification behavior, four numeric correction branches and direct registration coverage. The [RE-253–255 query pass](research/AUTH_QUERIES.md) adds selector helpers, command-role routing and internal authentication modes. The [RE-257–259 bounds/entry pass](research/SELECTOR_BOUNDS.md) adds integer parsing, selector storage/count evidence and native/QVM/chat route connections. The [RE-261–263 chat pass](research/CHAT_PATHS.md) adds native command metadata/filter evidence and bounded message/recipient paths, including the documented argument-mapping correction. The [RE-265–267 delivery pass](research/CHAT_DELIVERY.md) adds bounded dead-chat predicates, formatter argument routing and echo/fanout calls. The [RE-269–271 expansion pass](research/CHAT_EXPANSION.md) adds marker states, twelve key comparisons and health/momo branches. The [RE-273–275 item/time pass](research/CHAT_ITEMS_TIME.md) adds packed ammunition fields, sampled names and countdown branches. The [RE-277–279 location pass](research/CHAT_LOCATIONS.md) adds leader selection, cache/format boundaries and location-list/spatial evidence. PROV-002 remains open for full output/reset coverage. PROV-001 remains open for complete scheduling and platform coverage.
|
| 37 |
|
| 38 |
Start with the open questions raised by the current binary evidence. These tasks depend on the linked reports, not on a working rewrite.
|
| 39 |
|
docs/RESEARCH_LEDGER.md
CHANGED
|
@@ -137,3 +137,7 @@ Completed bounded milestones RE-269–271 with `analyze_chat_expansion.py`: pars
|
|
| 137 |
## 2026-10-11 — Packed chat item fields and countdowns
|
| 138 |
|
| 139 |
Completed bounded milestones RE-273–275 with `analyze_chat_items_time.py`: 32 ammo/60 weapon/82 timer slices, thirty file-backed name-word samples and nine game-item mode/bit paths. ITEM-001–003/F-064–066 retain NULL/duplicate samples, explicit table-extent uncertainty, cvar identity and expired-countdown fallback distinctions. Added restricted signed/unsigned right shifts and truncation-based signed remainder to the bounded evaluator. Eight tests check arithmetic failure domains, byte masks, sampled pointers/bounds, death overrides and countdown/type boundaries. RE-272 remains open; RE-276 tracks full producer/table/timer ownership and formatter/runtime/platform behavior. No original process/full QVM or recorded callee executes.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
## 2026-10-11 — Packed chat item fields and countdowns
|
| 138 |
|
| 139 |
Completed bounded milestones RE-273–275 with `analyze_chat_items_time.py`: 32 ammo/60 weapon/82 timer slices, thirty file-backed name-word samples and nine game-item mode/bit paths. ITEM-001–003/F-064–066 retain NULL/duplicate samples, explicit table-extent uncertainty, cvar identity and expired-countdown fallback distinctions. Added restricted signed/unsigned right shifts and truncation-based signed remainder to the bounded evaluator. Eight tests check arithmetic failure domains, byte masks, sampled pointers/bounds, death overrides and countdown/type boundaries. RE-272 remains open; RE-276 tracks full producer/table/timer ownership and formatter/runtime/platform behavior. No original process/full QVM or recorded callee executes.
|
| 140 |
+
|
| 141 |
+
## 2026-10-11 — Team leader and location lookup paths
|
| 142 |
+
|
| 143 |
+
Completed RE-277–279 bounded/static milestones with `analyze_chat_locations.py`: six leader scans, three name chains, five cache boundaries, two wrapper returns, four registration paths, literal-reference windows and file-backed box extents. LOC-001–003/F-067–069 separate executed synthetic integer slices from retained floating-point/engine spatial code and distinguish upstream ABI candidates from native proof. Seven tests check first-match/nonzero semantics, strict expiry, failure/success formatting requests, guard/filter/numbering/reverse links and static spatial constants/branches. RE-272 remains open; RE-280 tracks writers/ownership/reset/geometry/client/runtime/platform composition. No original process/full QVM or recorded callee executes.
|
docs/REVERSE_ENGINEERING_TODO.md
CHANGED
|
@@ -327,3 +327,8 @@ Purpose: turn the supplied installation into an evidence-backed specification of
|
|
| 327 |
- [x] RE-274 Recover weapon-name pointer expression, explicitly sampled indices 0–29 including NULL/duplicate entries, and game-item mode/bit branches ([ITEM-002](research/CHAT_ITEMS_TIME.md)).
|
| 328 |
- [x] RE-275 Connect gametime to registered timelimit and recover bounded game/round countdown arithmetic, fallback literals and game-type gates ([ITEM-003](research/CHAT_ITEMS_TIME.md)).
|
| 329 |
- [ ] RE-276 Recover complete packed-state producers/selector bounds, stride-432 table layout/extent/legal IDs/consumers, helper-36717 semantics, timer globals/cvar lifecycle and full formatter/original-runtime/platform fixtures.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
- [x] RE-274 Recover weapon-name pointer expression, explicitly sampled indices 0–29 including NULL/duplicate entries, and game-item mode/bit branches ([ITEM-002](research/CHAT_ITEMS_TIME.md)).
|
| 328 |
- [x] RE-275 Connect gametime to registered timelimit and recover bounded game/round countdown arithmetic, fallback literals and game-type gates ([ITEM-003](research/CHAT_ITEMS_TIME.md)).
|
| 329 |
- [ ] RE-276 Recover complete packed-state producers/selector bounds, stride-432 table layout/extent/legal IDs/consumers, helper-36717 semantics, timer globals/cvar lifecycle and full formatter/original-runtime/platform fixtures.
|
| 330 |
+
|
| 331 |
+
- [x] RE-277 Recover bounded first-match team-leader predicates, missing result and isolated leader-name address chain ([LOC-001](research/CHAT_LOCATIONS.md)).
|
| 332 |
+
- [x] RE-278 Recover strict location cache-expiry boundaries and isolated NULL/nonzero formatter-wrapper returns ([LOC-002](research/CHAT_LOCATIONS.md)).
|
| 333 |
+
- [x] RE-279 Recover bounded location registration/filter/numbering/reverse-link paths and retain static box/distance/visibility search evidence ([LOC-003](research/CHAT_LOCATIONS.md)).
|
| 334 |
+
- [ ] RE-280 Recover leader/cache/state writers, count/slot bounds, location class-tag derivation/list ownership/reset, caller failure/default behavior, full float/box/PVS search, configstring client consumption and original-runtime/platform fixtures.
|
docs/research/CHAT_EXPANSION.md
CHANGED
|
@@ -50,3 +50,5 @@ The `momo` match clears shared buffer byte zero and reads sender-record pointer
|
|
| 50 |
RE-269–271 record parser-state, key-comparison and health/momo milestones. RE-268 remains open for the complete expansion/output contract; RE-272 tracks full capacity-150 copy and append bounds, prefix consumption and buffer aliases, item/ammunition tables, timer arithmetic and cvar identities, location/leader/crosshair callees, momo object writers/consumers and original-runtime/platform fixtures. A Rust implementation will need owned-buffer semantics, exact marker/control-byte behavior and distinct field predicates, backed by completed output specifications before claiming parity.
|
| 51 |
|
| 52 |
The [ITEM-001–003 follow-up](CHAT_ITEMS_TIME.md) recovers bounded ammo/weapon/game-item/timer paths and thirty sampled name words. Full table, producer and timing/output contracts remain open under RE-272/276.
|
|
|
|
|
|
|
|
|
| 50 |
RE-269–271 record parser-state, key-comparison and health/momo milestones. RE-268 remains open for the complete expansion/output contract; RE-272 tracks full capacity-150 copy and append bounds, prefix consumption and buffer aliases, item/ammunition tables, timer arithmetic and cvar identities, location/leader/crosshair callees, momo object writers/consumers and original-runtime/platform fixtures. A Rust implementation will need owned-buffer semantics, exact marker/control-byte behavior and distinct field predicates, backed by completed output specifications before claiming parity.
|
| 51 |
|
| 52 |
The [ITEM-001–003 follow-up](CHAT_ITEMS_TIME.md) recovers bounded ammo/weapon/game-item/timer paths and thirty sampled name words. Full table, producer and timing/output contracts remain open under RE-272/276.
|
| 53 |
+
|
| 54 |
+
The [LOC-001–003 follow-up](CHAT_LOCATIONS.md) recovers leader scan/name paths, location cache/format boundaries and list-building/static spatial evidence. Full caller/output/geometry behavior remains open under RE-272/280.
|
docs/research/CHAT_LOCATIONS.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Team-leader and location lookup paths
|
| 2 |
+
|
| 3 |
+
Static pass, 2026-10-11. Reproduce with `python3 scripts/analyze_chat_locations.py`; check with `python3 scripts/test_chat_locations.py`. [Generated evidence](chat-locations.json) retains verified qagame partitions, leader/cache/format/list traces, literal reference windows and file-backed spatial constants. Original processes/full QVMs do not execute. Recorded callees have zero placeholder returns; isolated leader/location returns are supplied inputs. Floating-point geometry, box/visibility engine services and real formatting are not evaluated. This extends the [substitution key inventory](CHAT_EXPANSION.md).
|
| 4 |
+
|
| 5 |
+
## LOC-001: first matching team leader
|
| 6 |
+
|
| 7 |
+
Helper **169397** scans indices from zero while index is signed less than count global **34992524**. Its record address is **global 34992496 + index * 6336**. It returns the first index with all three raw predicates:
|
| 8 |
+
|
| 9 |
+
- Record offset **468 !=0**.
|
| 10 |
+
- Record offset **3096 == requested team**.
|
| 11 |
+
- Record offset **3124 !=0**.
|
| 12 |
+
|
| 13 |
+
If no record matches, it returns **-1**. Zero/negative synthetic counts make no record reads. Both record states 1 and 2 can match; this is broader than delivery's required recipient state 2. A negative leader-flag word also satisfies the nonzero test. The fixtures cover skipped disconnected/wrong-team/unflagged records and multiple matches to verify first-match selection, without assigning complete state/role lifecycles.
|
| 14 |
+
|
| 15 |
+
The `leader` substitution passes sender-record field 3096 into this helper. A supplied -1 result skips the name formatter, retaining the lookup's previously requested `unknown` default. Other observed results load the record pointer at **34091888 + slot * 880**, add **504**, and request `%s` into shared buffer 35738436 with capacity 150. Slot 0/2 slices verify that address chain. The helper call, initial default and formatting are not composed/executed here, so no actual no-leader screen text or bounds invariant is established.
|
| 16 |
+
|
| 17 |
+
Literal-reference windows show helper 169397 in multiple containing functions, including chat lookup 185212. They do not cover computed calls, aliased references or every flag writer.
|
| 18 |
+
|
| 19 |
+
## LOC-002: cached location and formatting wrapper
|
| 20 |
+
|
| 21 |
+
Helper **268248** first reads object offset **848**. A nonzero pointer returns immediately only if signed global time **34992532 < pointed-object field 596**. NULL, equality or later time proceeds to spatial lookup. Synthetic cases cover those boundaries and a signed-wrap comparison; they do not establish a safe wrap-aware expiry contract. No cache refresh/store appears in the retained function, so the producers of fields 848/596 remain essential follow-ups.
|
| 22 |
+
|
| 23 |
+
Wrapper **268637** calls 268248 and returns zero if lookup returns NULL. That failure path makes no formatting call and no direct buffer write in its slice. A nonzero lookup result loads its word at offset **592**, requests formatter 70386 with caller buffer/capacity and template `%s`, then returns **1**. The isolated success fixture uses capacity 150, text pointer 0x60000 and destination 0x70000. Success here is a return convention, not proof of a valid source string or successful bounded formatting.
|
| 24 |
+
|
| 25 |
+
The `location` substitution forwards sender/shared buffer/capacity 150 to this wrapper. Crosshair branches and team-chat prefix construction also reference it. Failure handling/default text and output preservation must be recovered for every caller before a Rust API contract is complete.
|
| 26 |
+
|
| 27 |
+
## LOC-003: registration, list order and static spatial search
|
| 28 |
+
|
| 29 |
+
List-builder **264976** is guarded by nonzero global **34999636**. Its unguarded path sets that guard to 1, clears head/count globals **34999640/34999644**, and requests trap **18** (encoded -19) to set configstring **640** to literal **`unknown`**. It visits stride-880 objects beginning at **34091368** while its signed index is below entity count global **34992508**.
|
| 30 |
+
|
| 31 |
+
Each object qualifies only if offset **536** is nonzero and raw word **540 ==8489281**. This pass does not assign the raw tag a complete class/hash identity. A qualifying object:
|
| 32 |
+
|
| 33 |
+
1. Increments global 34999644 and stores that one-based number at object offset **684**.
|
| 34 |
+
2. Requests configstring **640 + number** from its string pointer at object offset **592**.
|
| 35 |
+
3. Writes the old list head through ***(object + 524) +24**, then makes the object the new head.
|
| 36 |
+
|
| 37 |
+
The three-object fixture publishes indices 641/642/643 in scan order and links head object 2 -> object 1 -> object 0 -> NULL. Filtering fixtures independently exclude zero field 536 and wrong field 540. The guarded path performs no reset/publication. Guard reset, entity-count capacity, NULL field-524 handling, configstring bounds, map restart and linked-object ownership remain open.
|
| 38 |
+
|
| 39 |
+
The retained spatial helper shows two search stages after a cache miss:
|
| 40 |
+
|
| 41 |
+
- It copies position words at object offsets **488/492/496**, forms a query box by subtracting/adding file-backed **40.0/40.0/52.0** extents at VM addresses 31128/31132/31136, and calls encoded service **-33** with four arguments including local result array and maximum **1024**. Its returned indices become `34091368 + index * 880`. It compares object offset 536 against **`trigger_location`** through ASCII comparator 69386 and returns the first match in the supplied engine order.
|
| 42 |
+
- If that stage finds no match, it traverses head 34999640 via the same `*(object+524)+24` links. The code calculates squared coordinate distance, starting with raw float bits **1296039936** (201326592.0) as the limit. Candidate distance **<= current best** reaches encoded service **-27** with two position pointers; a nonzero return updates both best distance and selected object. Equal-distance candidates therefore may replace an earlier selected object. End-of-list returns the selected pointer, initially NULL.
|
| 43 |
+
|
| 44 |
+
The [pinned ABI candidate report](qvm/qagame.abi-candidates.json), checked against the same qagame hash, labels service numbers 32/26 as **G_ENTITIES_IN_BOX/G_IN_PVS** and 18 as **G_SET_CONFIGSTRING**. Those are upstream candidates, not new proof of all shipped native argument/layout semantics. Float execution, engine ordering, portals, collision bounds and native platform correspondence remain unverified. The synthetic list and cache traces do not execute either spatial stage.
|
| 45 |
+
|
| 46 |
+
## Remaining work and Rust dependencies
|
| 47 |
+
|
| 48 |
+
RE-277–279 record these bounded/static milestones; RE-272 remains open for full substitution behavior. RE-280 tracks leader-flag/state writers, count/slot invariants, caller failure/default behavior, cache writers/expiry/reset, raw class tag derivation, linked-field ownership, full floating-point/box/PVS selection, configstring clients and original-runtime/platform fixtures. Rust location lookup needs explicit cache/ordering/tie rules and ownership backed by that evidence; a generic nearest-point search would not reproduce the observed two-stage control flow by itself.
|
docs/research/README.md
CHANGED
|
@@ -39,10 +39,11 @@ python3 scripts/test_chat_paths.py
|
|
| 39 |
python3 scripts/test_chat_delivery.py
|
| 40 |
python3 scripts/test_chat_expansion.py
|
| 41 |
python3 scripts/test_chat_items_time.py
|
|
|
|
| 42 |
python3 scripts/storage_policy.py check --require-setup
|
| 43 |
```
|
| 44 |
|
| 45 |
-
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`, `analyze_engine_control.py`, `analyze_frame_timing.py`, `analyze_event_queues.py`, `analyze_auth_announcements.py`, `analyze_game_cvars.py`, `analyze_cvar_changes.py`, `analyze_auth_queries.py`, `analyze_selector_bounds.py`, `analyze_chat_paths.py`, `analyze_chat_delivery.py`, `analyze_chat_expansion.py`, and `
|
| 46 |
|
| 47 |
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.
|
| 48 |
|
|
@@ -88,3 +89,5 @@ The extension collector adds five focused tests for native tail calls, command-r
|
|
| 88 |
[Radio/chat expansion states and keys](CHAT_EXPANSION.md) adds RE-269–271: marker and length branches, all twelve comparison keys, health labels and momo side-effect writes. Reproduce with `python3 scripts/analyze_chat_expansion.py`; check with `python3 scripts/test_chat_expansion.py`. Full composed parser output, buffer bounds and the remaining substitution bodies stay open.
|
| 89 |
|
| 90 |
[Chat ammunition, item names and countdowns](CHAT_ITEMS_TIME.md) adds RE-273–275: packed byte extraction, thirty sampled name words/game-item branches and timer/cvar/gate evidence. Reproduce with `python3 scripts/analyze_chat_items_time.py`; check with `python3 scripts/test_chat_items_time.py`. Complete item tables/producers and timing/output/runtime contracts remain open.
|
|
|
|
|
|
|
|
|
| 39 |
python3 scripts/test_chat_delivery.py
|
| 40 |
python3 scripts/test_chat_expansion.py
|
| 41 |
python3 scripts/test_chat_items_time.py
|
| 42 |
+
python3 scripts/test_chat_locations.py
|
| 43 |
python3 scripts/storage_policy.py check --require-setup
|
| 44 |
```
|
| 45 |
|
| 46 |
+
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`, `analyze_engine_control.py`, `analyze_frame_timing.py`, `analyze_event_queues.py`, `analyze_auth_announcements.py`, `analyze_game_cvars.py`, `analyze_cvar_changes.py`, `analyze_auth_queries.py`, `analyze_selector_bounds.py`, `analyze_chat_paths.py`, `analyze_chat_delivery.py`, `analyze_chat_expansion.py`, `analyze_chat_items_time.py`, and `analyze_chat_locations.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.
|
| 47 |
|
| 48 |
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.
|
| 49 |
|
|
|
|
| 89 |
[Radio/chat expansion states and keys](CHAT_EXPANSION.md) adds RE-269–271: marker and length branches, all twelve comparison keys, health labels and momo side-effect writes. Reproduce with `python3 scripts/analyze_chat_expansion.py`; check with `python3 scripts/test_chat_expansion.py`. Full composed parser output, buffer bounds and the remaining substitution bodies stay open.
|
| 90 |
|
| 91 |
[Chat ammunition, item names and countdowns](CHAT_ITEMS_TIME.md) adds RE-273–275: packed byte extraction, thirty sampled name words/game-item branches and timer/cvar/gate evidence. Reproduce with `python3 scripts/analyze_chat_items_time.py`; check with `python3 scripts/test_chat_items_time.py`. Complete item tables/producers and timing/output/runtime contracts remain open.
|
| 92 |
+
|
| 93 |
+
[Team-leader and location lookup paths](CHAT_LOCATIONS.md) adds RE-277–279: first-match leader scan, strict cache/format boundaries, location registration/list order and retained spatial control flow/constants. Reproduce with `python3 scripts/analyze_chat_locations.py`; check with `python3 scripts/test_chat_locations.py`. Geometry/services, ownership, caller output and runtime/platform contracts remain open.
|
docs/research/chat-locations.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
scripts/analyze_binaries.py
CHANGED
|
@@ -14,7 +14,7 @@ def main():
|
|
| 14 |
'analyze_deferred_auth.py', 'analyze_engine_control.py', 'analyze_frame_timing.py',
|
| 15 |
'analyze_event_queues.py', 'analyze_auth_announcements.py', 'analyze_game_cvars.py',
|
| 16 |
'analyze_cvar_changes.py', 'analyze_auth_queries.py', 'analyze_selector_bounds.py',
|
| 17 |
-
'analyze_chat_paths.py', 'analyze_chat_delivery.py', 'analyze_chat_expansion.py', 'analyze_chat_items_time.py'):
|
| 18 |
subprocess.run([sys.executable, str(Path('scripts')/script)], check=True)
|
| 19 |
|
| 20 |
|
|
|
|
| 14 |
'analyze_deferred_auth.py', 'analyze_engine_control.py', 'analyze_frame_timing.py',
|
| 15 |
'analyze_event_queues.py', 'analyze_auth_announcements.py', 'analyze_game_cvars.py',
|
| 16 |
'analyze_cvar_changes.py', 'analyze_auth_queries.py', 'analyze_selector_bounds.py',
|
| 17 |
+
'analyze_chat_paths.py', 'analyze_chat_delivery.py', 'analyze_chat_expansion.py', 'analyze_chat_items_time.py', 'analyze_chat_locations.py'):
|
| 18 |
subprocess.run([sys.executable, str(Path('scripts')/script)], check=True)
|
| 19 |
|
| 20 |
|
scripts/analyze_chat_locations.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Recover leader scan, cached location formatting and location-list registration."""
|
| 3 |
+
import json
|
| 4 |
+
import struct
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from analyze_deferred_auth import partition_function, trace_slice
|
| 7 |
+
from analyze_game_cvars import vm_string
|
| 8 |
+
from analyze_qvm import decode,write_json
|
| 9 |
+
from analyze_vm_extensions import verified
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def main():
|
| 13 |
+
q=json.loads(Path('docs/research/qvm/qagame.json').read_text())
|
| 14 |
+
d=verified(Path(q['source']['member']['output']).read_bytes(),q['sha256']);h,ins=decode(d)
|
| 15 |
+
leader=[]
|
| 16 |
+
cases=[(0,[]),(-1,[]),(3,[(0,1,1),(1,2,1),(2,1,1)]),
|
| 17 |
+
(3,[(1,1,1),(2,1,1),(2,1,1)]),(3,[(2,1,0),(2,1,0),(2,2,1)]),
|
| 18 |
+
(3,[(1,1,-1),(0,1,1),(2,1,1)])]
|
| 19 |
+
for count,records in cases:
|
| 20 |
+
memory={34992496:0x10000,34992524:count}
|
| 21 |
+
for i,(state,team,flag) in enumerate(records):
|
| 22 |
+
a=0x10000+i*6336
|
| 23 |
+
memory.update({a+468:state,a+3096:team,a+3124:flag})
|
| 24 |
+
t=trace_slice(ins,169398,169460,memory,{20:1},halt_on_leave=True,capture_stack=True,limit=1024)
|
| 25 |
+
leader.append({'count':count,'record_state_team_flag':records,'requested_team':1,'trace':t})
|
| 26 |
+
leader_format=[]
|
| 27 |
+
for slot in [-1,0,2]:
|
| 28 |
+
memory={} if slot==-1 else {34091888+slot*880:0x50000}
|
| 29 |
+
t=trace_slice(ins,185947,186431,memory,{76:slot})
|
| 30 |
+
leader_format.append({'supplied_leader_return':slot,'trace':t})
|
| 31 |
+
cache=[]
|
| 32 |
+
obj,location=0x10000,0x20000
|
| 33 |
+
for pointer,now,expiry in [(0,1000,0),(location,999,1000),(location,1000,1000),
|
| 34 |
+
(location,1001,1000),(location,-2147483640,2147483640)]:
|
| 35 |
+
m={obj+848:pointer,34992532:now}
|
| 36 |
+
if pointer:m[pointer+596]=expiry
|
| 37 |
+
t=trace_slice(ins,268249,268279,m,{4236:obj},halt_on_leave=True,capture_stack=True)
|
| 38 |
+
cache.append({'pointer':pointer,'now':now,'expiry':expiry,'trace':t})
|
| 39 |
+
format_traces=[]
|
| 40 |
+
for result in [0,location]:
|
| 41 |
+
t=trace_slice(ins,268649,268676,{} if not result else {location+592:0x60000},
|
| 42 |
+
{24:result,44:0x70000,48:150},halt_on_leave=True,capture_stack=True)
|
| 43 |
+
format_traces.append({'supplied_lookup_return':result,'trace':t})
|
| 44 |
+
lists=[]
|
| 45 |
+
for guarded,count,fields in [(1,3,[]),(0,0,[]),
|
| 46 |
+
(0,3,[(1,8489281),(0,8489281),(1,0)]),
|
| 47 |
+
(0,3,[(1,8489281),(1,8489281),(1,8489281)])]:
|
| 48 |
+
m={34999636:guarded,34992508:count}
|
| 49 |
+
for i,(name_present,raw_tag) in enumerate(fields):
|
| 50 |
+
obj=34091368+i*880
|
| 51 |
+
m.update({obj+536:0x80000 if name_present else 0,obj+540:raw_tag,
|
| 52 |
+
obj+524:0x90000+i*64,obj+592:0xa0000+i*64})
|
| 53 |
+
t=trace_slice(ins,264977,265088,m,{36:34091368},limit=2048)
|
| 54 |
+
lists.append({'guard':guarded,'count':count,'name_present_raw_tag':fields,'trace':t})
|
| 55 |
+
references={}
|
| 56 |
+
for value in [169397,268248,268637,34999640]:
|
| 57 |
+
references[str(value)]=[]
|
| 58 |
+
for j,x in enumerate(ins):
|
| 59 |
+
if x['opcode']=='CONST' and x['operand']==value:
|
| 60 |
+
entry=next(k for k in range(j,-1,-1) if ins[k]['opcode']=='ENTER')
|
| 61 |
+
references[str(value)].append({'instruction':j,'containing_entry':entry,
|
| 62 |
+
'window':ins[max(entry,j-4):j+7]})
|
| 63 |
+
abi=json.loads(Path('docs/research/qvm/qagame.abi-candidates.json').read_text())
|
| 64 |
+
if abi['sha256']!=q['sha256']:raise ValueError('ABI provenance changed')
|
| 65 |
+
services=[r for r in abi['syscalls'] if r['number'] in [18,26,32]]
|
| 66 |
+
extent=[]
|
| 67 |
+
for address in [31128,31132,31136]:
|
| 68 |
+
if address+4>h['data_length']:raise ValueError('box extent outside initialized data')
|
| 69 |
+
offset=h['data_offset']+address
|
| 70 |
+
extent.append({'vm_address':address,'raw_bits':struct.unpack_from('<I',d,offset)[0],
|
| 71 |
+
'float_value':struct.unpack_from('<f',d,offset)[0]})
|
| 72 |
+
out={'box_extent_constants':extent,'abi_reference_revision':abi['reference_revision'],'service_candidates':services,'source':q['source'],'sha256':q['sha256'],
|
| 73 |
+
'functions':{str(e):partition_function(ins,e,d,h) for e in [169397,264976,268248,268637]},
|
| 74 |
+
'leader_scan_traces':leader,'leader_format_traces':leader_format,'cache_traces':cache,
|
| 75 |
+
'location_format_traces':format_traces,'registration_traces':lists,'literal_references':references,
|
| 76 |
+
'strings':{str(a):vm_string(d,h,a) for a in [93224,93227,92460,78042]},
|
| 77 |
+
'limitations':'Static partitions and bounded synthetic traces, not original runtime/full QVM '
|
| 78 |
+
'execution. Callees are recorded with zero placeholder returns; isolated lookup/leader returns are '
|
| 79 |
+
'supplied. Floating-point distance/geometry and engine box/visibility calls are retained but not '
|
| 80 |
+
'evaluated. Field types, cache writers, list lifecycle/ownership, real formatting and all-platform '
|
| 81 |
+
'behavior remain open. Literal scans exclude aliases/computed references.'}
|
| 82 |
+
write_json(Path('docs/research/chat-locations.json'),out)
|
| 83 |
+
print('Recovered leader selection, cached location formatting and list-building traces')
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
if __name__=='__main__':main()
|
scripts/test_chat_locations.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Check leader/cache/list boundaries and static spatial branch evidence."""
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import unittest
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class ChatLocationTests(unittest.TestCase):
|
| 9 |
+
@classmethod
|
| 10 |
+
def setUpClass(cls):
|
| 11 |
+
cls.r=json.loads(Path('docs/research/chat-locations.json').read_text())
|
| 12 |
+
cls.ins={i['index']:i for f in cls.r['functions'].values() for i in f['instructions']}
|
| 13 |
+
|
| 14 |
+
def test_first_nonzero_state_matching_flagged_leader(self):
|
| 15 |
+
rows=self.r['leader_scan_traces']
|
| 16 |
+
self.assertEqual([x['trace']['stack'] for x in rows],[[-1],[-1],[2],[0],[-1],[0]])
|
| 17 |
+
for x in rows:
|
| 18 |
+
t=x['trace'];self.assertEqual(t['calls'],[])
|
| 19 |
+
expected=next((i for i,(state,team,flag) in enumerate(x['record_state_team_flag'])
|
| 20 |
+
if state!=0 and team==x['requested_team'] and flag!=0),-1)
|
| 21 |
+
self.assertEqual(t['stack'],[expected])
|
| 22 |
+
self.assertNotIn(169430,rows[2]['trace']['trace'][:10])
|
| 23 |
+
self.assertEqual(self.ins[169405]['operand'],6336)
|
| 24 |
+
|
| 25 |
+
def test_leader_format_failure_and_name_pointer(self):
|
| 26 |
+
rows=self.r['leader_format_traces'];self.assertEqual(rows[0]['trace']['calls'],[])
|
| 27 |
+
for x in rows[1:]:
|
| 28 |
+
calls=x['trace']['calls'];self.assertEqual(len(calls),1)
|
| 29 |
+
self.assertEqual(calls[0]['target'],70386)
|
| 30 |
+
self.assertEqual(calls[0]['args'],{'8':35738436,'12':150,'16':78042,'20':0x50000+504})
|
| 31 |
+
|
| 32 |
+
def test_cache_expiry_strictness_and_signed_comparison(self):
|
| 33 |
+
rows=self.r['cache_traces']
|
| 34 |
+
self.assertEqual([x['trace']['stack'] for x in rows],[[],[0x20000],[],[],[0x20000]])
|
| 35 |
+
self.assertEqual([268279==self.ins[x['trace']['trace'][-1]]['operand'] for x in rows],
|
| 36 |
+
[True,False,True,True,False])
|
| 37 |
+
for x in rows:self.assertEqual(x['trace']['calls'],[])
|
| 38 |
+
|
| 39 |
+
def test_location_format_success_and_null_failure(self):
|
| 40 |
+
a,b=self.r['location_format_traces']
|
| 41 |
+
self.assertEqual(a['trace']['calls'],[]);self.assertEqual(a['trace']['stack'],[0])
|
| 42 |
+
self.assertEqual(b['trace']['stack'],[1])
|
| 43 |
+
self.assertEqual(b['trace']['calls'][0]['args'],{'8':0x70000,'12':150,'16':93224,'20':0x60000})
|
| 44 |
+
self.assertEqual(self.r['strings']['93224'],'%s')
|
| 45 |
+
|
| 46 |
+
def test_registration_guard_and_empty_reset(self):
|
| 47 |
+
a,b=self.r['registration_traces'][:2]
|
| 48 |
+
self.assertEqual(a['trace']['calls'],[])
|
| 49 |
+
self.assertNotIn('34999640',a['trace']['memory'])
|
| 50 |
+
self.assertEqual(b['trace']['memory']['34999640'],0)
|
| 51 |
+
self.assertEqual(b['trace']['memory']['34999644'],0)
|
| 52 |
+
self.assertEqual(b['trace']['memory']['34999636'],1)
|
| 53 |
+
self.assertEqual(b['trace']['calls'][0]['args'],{'8':640,'12':92460})
|
| 54 |
+
self.assertEqual(self.r['strings']['92460'],'unknown')
|
| 55 |
+
|
| 56 |
+
def test_registration_filter_numbering_and_reverse_links(self):
|
| 57 |
+
one,all_rows=self.r['registration_traces'][2:]
|
| 58 |
+
self.assertEqual(one['trace']['memory']['34999644'],1)
|
| 59 |
+
self.assertEqual(one['trace']['memory']['34999640'],34091368)
|
| 60 |
+
t=all_rows['trace'];self.assertEqual(t['memory']['34999644'],3)
|
| 61 |
+
self.assertEqual(t['memory']['34999640'],34091368+2*880)
|
| 62 |
+
self.assertEqual([c['args']['8'] for c in t['calls']],[640,641,642,643])
|
| 63 |
+
self.assertTrue(all(c['target']==-19 for c in t['calls']))
|
| 64 |
+
for i in range(3):
|
| 65 |
+
self.assertEqual(t['memory'][str(34091368+i*880+684)],i+1)
|
| 66 |
+
self.assertEqual(t['memory'][str(0x90000+i*64+24)],0 if i==0 else 34091368+(i-1)*880)
|
| 67 |
+
|
| 68 |
+
def test_spatial_function_constants_and_branch_order(self):
|
| 69 |
+
# Static evidence only: the float/collision callees are not evaluated.
|
| 70 |
+
self.assertEqual(self.ins[268283]['operand'],1296039936)
|
| 71 |
+
self.assertEqual([x['float_value'] for x in self.r['box_extent_constants']],[40.0,40.0,52.0])
|
| 72 |
+
self.assertEqual([x['raw_bits'] for x in self.r['box_extent_constants']],[0x42200000,0x42200000,0x42500000])
|
| 73 |
+
self.assertEqual((self.ins[268368]['operand'],self.ins[268365]['operand']),(-33,1024))
|
| 74 |
+
self.assertEqual(self.r['strings']['93227'],'trigger_location')
|
| 75 |
+
self.assertEqual(self.ins[268590]['opcode'],'LEF')
|
| 76 |
+
self.assertEqual(self.ins[268590]['operand'],268593)
|
| 77 |
+
self.assertEqual(self.ins[268601]['operand'],-27)
|
| 78 |
+
self.assertEqual(self.ins[268607]['operand'],268610)
|
| 79 |
+
self.assertEqual(self.ins[268621]['operand'],524)
|
| 80 |
+
self.assertEqual(self.ins[268624]['operand'],24)
|
| 81 |
+
candidates={x['number']:x['upstream_name_candidate'] for x in self.r['service_candidates']}
|
| 82 |
+
self.assertEqual(candidates,{18:'G_SET_CONFIGSTRING',26:'G_IN_PVS',32:'G_ENTITIES_IN_BOX'})
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
if __name__=='__main__':unittest.main()
|