76 lines
4.6 KiB
Markdown
76 lines
4.6 KiB
Markdown
# Research callback: fresh instruction-stream findings
|
|
|
|
Lead: openai/gpt-6-astra, 2026-09-09. Static RE, not live replacement verification.
|
|
Original binary: dumps/sots.exe, SHA-256
|
|
`970b7de729956a53094c7eb98aba4270aee98e2fed5daf0d39e290013c90c841` (matches recorded fingerprint).
|
|
Fresh ReVa reads were cross-checked against local GNU objdump on that executable. No Ghidra edits.
|
|
Raw/materialized captures: verify/results/research-callback/.
|
|
|
|
## Callback order is observable, not interchangeable
|
|
|
|
OnTechResearched starts at 0x00891790. The binary establishes this order:
|
|
|
|
1. Call RecordObservedTech at **0x008917dc**, passing the technology and the player twice;
|
|
`ecx` is formed from the server reference at player+8 minus four (when non-null).
|
|
2. Compare completing technology to player+0x294. If matched, optionally call the pending-roll
|
|
handler at 0x0088df20, then clear player+0x3b4 and player+0x294.
|
|
3. Unless silent, format and post the completion event at **0x008919b5**. Its text branches
|
|
use the research progress-ratio result before applying economic effects.
|
|
4. Execute the technology-specific scalar effects and common completion tail.
|
|
|
|
Thus the observed-record operation precedes target clearing, and the completion event precedes
|
|
economic effect writes. A replacement should preserve this order; the earlier conceptual pipeline
|
|
diagram was not an instruction-order specification.
|
|
|
|
The two records also use DIFFERENT strings. RecordObservedTech compares/copies the internal
|
|
technology key from **TechDef+4**. The completion message fetches **TechDef+0x40**, with its
|
|
capacity checked at +0x54 (0x0089180d..0x00891832), before formatting. This is the display-name
|
|
surface seen in original event prose, not the internal key. Reusing `IND_Waldo` as event text
|
|
would be wrong even if the observed-tech name were correct.
|
|
|
|
## W1 IND_Waldo scalar path
|
|
|
|
`MasterTechTree::IsTech` is tested with immediate **10001 / 0x2711** at 0x00891a05.
|
|
The true branch reaches 0x00891a7f: subtract the qword constant at 0x009e2318 from construction
|
|
modifiers +0x10c/+0x110/+0x114, store each as float32; add the qword constant at 0x009ed188 to
|
|
output modifier +0x124 and store float32. Raw literals:
|
|
|
|
- 0x009e2318: `00 00 00 a0 99 99 b9 3f` = double-widened float32 0.1.
|
|
- 0x009ed188: `00 00 00 40 33 33 c3 3f` = double-widened float32 0.15.
|
|
|
|
The common tail invokes bitmask rebuilding, the node-bore updater and species flags. W1's four
|
|
numeric writes do not justify skipping the common tail or patching later bankruptcy/repair values.
|
|
|
|
## Decompiler hazard: missing post-delete control flow
|
|
|
|
Fresh ReVa decompilation marks operator_delete at 0x00924faa as non-returning. This truncates
|
|
real logic: UpdateNodeBoreParams is reported as ending at 0x00818365, immediately after its delete
|
|
call. Local executable disassembly shows:
|
|
|
|
- 0x00818361: call 0x00924faa;
|
|
- 0x00818366: caller stack cleanup;
|
|
- **0x00818369: write zero to player+0x308**;
|
|
- **0x00818382: return**.
|
|
|
|
Its positive branch allocates **12 bytes** through 0x00924fb6 if the pointer is absent, constructs
|
|
the object through 0x006e18c0, stores the pointer, and copies three words from selected parameters.
|
|
It is not a read-only helper. Both allocation/free helpers are import thunks. Fresh PE import
|
|
inspection pins them to **MSVCR100.dll**: 0x00924faa jumps through IAT 0x009dd1c8 to
|
|
`??3@YAXPAX@Z` (scalar operator delete); 0x00924fb6 jumps through IAT 0x009dd1d0 to
|
|
`??2@YAPAXI@Z` (scalar operator new). The import descriptor's first thunk is RVA 0x005dd15c;
|
|
entries 27 and 29 resolve those slots. See pe-headers-and-imports.txt and allocation-import-thunks.txt.
|
|
This provides a concrete original-runtime allocation boundary; it does not justify substituting
|
|
the MinGW runtime allocator for memory the original's destructors will free.
|
|
|
|
The same false non-return annotation appears after long-string temporary cleanup in the main
|
|
callback. At 0x008919d1 the binary calls delete, then falls through to effect processing at
|
|
0x008919d9; the decompiler labels that branch non-returning. Therefore decompiled absence of a
|
|
write/return after deletion cannot be evidence that the original lacks it.
|
|
|
|
## Astra decision / next RE action
|
|
|
|
Continue this read-only RE slice with raw instructions as authority on affected lifetime paths.
|
|
Do not change the shared database during concurrent analysis. Retain the flawed decompilation
|
|
as evidence of the limitation. No replacement or allocator-safety claim follows from these facts.
|
|
Next: verify the analyst's ObservedTech/event ownership recovery against the binary, then issue
|
|
the smallest concrete implementation handoff. The allocator import family is now pinned statically.
|