lane D: automated struct recovery from the IStreamable serializers
Every serializable class carries an enumeration of its own fields -- its
Write(Stream&), walking the members in order with a 4-char tag. This decodes
that idiom mechanically for the whole binary in 0.35 s.
Validation first (tools/serializers.py validate), against answers the campaign
already had before the tool existed:
A 305/307 field offsets+kinds exact across 17 classes, 0 WRONG, vs
struct-recovery.md 1-4 and observedtech-append.md
B sizeof from the container-stride divides: ObservedTech 0x2c, MoraleEvent
0x50, PlayerReport 0x30, DiplomacyStats 0x24 -- all matching
C 22 of save_reader.py's shapes, tag order identical (Sys 78 tags,
Player 104, CreateParams 25, Ship 22): 22 agree, 0 disagree
D Read/Write cross-check on every class: 437/437 field offsets agree
At scale: 386 classes with a Write, 1,682 member fields.
verified 87 (542 fields) | clean 77 (328) | unnamed 176 (471)
partial 31 (341) | empty 15
58 classes with a sizeof corroborated by a second line of evidence
(45 container stride, 13 enumeration meeting the embedding bound); the rest
report a lower bound and say so.
Four things each worth 10-170 classes: the RTTI class hierarchy descriptor as
the only honest "is this an IStreamable" test (a 3-slot vftable also matches
TacAISquadRule_* and the row parsers); mod=0 memory operands, which x86disp.py
cannot index and which hide every field at offset 0; the member->id pointer
idiom behind every handle field; and sub-writers, both base-class and private
(StrategyServer's six id lists live in FUN_00794cd0).
Failure classes are enumerated in the finding -- 176 anonymous-tag classes are
a hard limit on names but not on layout, and the other 64 are bounded
mechanical fixes. Two fields lost to a value assembled across a branch were
left unrecovered rather than patched with an unverifiable heuristic.
Write-back: 288 structures + 328 labels into Ghidra (0 failures), +201
addresses.json entries, header regenerated with tools/gen_addresses.py.
Note: ghidra/addresses.json also carries lane V's already-written live
confirmation text on ObservedTech_sizeof and ServerPlayer_off_ObservedTechs --
their edit, swept in only because we share the file.
This commit is contained in:
parent
795aa471d4
commit
d7ea0a048c
11 changed files with 45572 additions and 5 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -12,3 +12,5 @@ captures/
|
|||
*.lock
|
||||
__pycache__/
|
||||
*.pyc
|
||||
# local ghidra write-back progress (machine-local, resumable)
|
||||
objects/.ghidra_pushed
|
||||
|
|
|
|||
248
findings/objects/serializer-struct-recovery.md
Normal file
248
findings/objects/serializer-struct-recovery.md
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
# Automated struct recovery from the `Mars::IStreamable` serializers
|
||||
|
||||
Lane D, 2026-09-08. Static/host only — VM140 was held by lane V and the game was never run.
|
||||
|
||||
The campaign had ~1,600 classes mapped by name and a few dozen by layout. This round makes the
|
||||
layout step mechanical: every serializable class in the exe carries an **enumeration** of its own
|
||||
fields — its `Write(Stream&)`, which walks the members in order with a 4-char name tag — and a
|
||||
decoder for that idiom turns "read one class in an afternoon" into "read them all in 0.35 s".
|
||||
|
||||
Tools: `tools/rtti_map.py` (RTTI → vftables), `tools/serializers.py` (the recovery),
|
||||
`tools/serializers_golden.py` (the regression set), `tools/serializers_ghidra.py` (write-back).
|
||||
Outputs: `objects/layouts.json`, `objects/layouts.md`, `objects/layouts.h`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Validation first
|
||||
|
||||
`uv run python3 tools/serializers.py validate` runs three checks, all against answers this
|
||||
campaign already had **before** the tool existed. Nothing below §1 is claimed until this passes.
|
||||
|
||||
### A — field offsets and kinds, vs `struct-recovery.md` §1–§4 and `observedtech-append.md`
|
||||
|
||||
```
|
||||
[OK ] Game::DiplomacyStats 14/14 exact
|
||||
[OK ] Game::FlightPlan 6/6 exact
|
||||
[OK ] Game::FlightPlan::Waypoint 3/3 exact
|
||||
[OK ] Game::IndependenceInfo 5/5 exact
|
||||
[OK ] Game::MoraleEvent 6/6 exact
|
||||
[OK ] Game::NodeRoute 3/3 exact
|
||||
[OK ] Game::ObservedTech 5/5 exact
|
||||
[OK ] Game::ObservedWeapon 5/5 exact
|
||||
[OK ] Game::PlayerReport 11/11 exact
|
||||
[OK ] Game::PopulationGroup 3/3 exact
|
||||
[MISS] Game::ServerPlayer 101/103 exact 2 missing
|
||||
[OK ] Game::ServerSystem 79/79 exact
|
||||
[OK ] Game::ShipBuildOrder 5/5 exact
|
||||
[OK ] Game::StarFleet 16/16 exact
|
||||
[OK ] Game::StarShip 23/23 exact
|
||||
[OK ] Game::StarSystem::OutputRates 7/7 exact
|
||||
[OK ] Game::StarSystem::PlayerView 13/13 exact
|
||||
|
||||
fields: 305/307 exact, 2 not recovered, 0 WRONG
|
||||
```
|
||||
|
||||
**Zero wrong.** Every offset, every width, every tag the tool emits for these 17 classes matches the
|
||||
hand-recovered tables — including the ones that were traps: `ObservedTech` `+0x24` is *not* reported
|
||||
as a field (it is the string's trailing `_Alval`), `Bats2`/`rcex` come out as int64 and not int,
|
||||
`TShn`/`ETS`/the whole `DiplomacyStats` block come out int16, `ltis` comes out int, `pswd` comes out
|
||||
string, `TRM`/`CstR`/`CstE`/`CstT`/`shrm`/`RefCap`/`RepCap`/`PlayerView.Infra` come out float.
|
||||
|
||||
The two misses are one failure class, described in §4: a value assembled **across a branch**
|
||||
(`ResTNm` writes `tech ? tech->name : ""`; `tnc` writes `max(member, 1)`). A linear pass sees only
|
||||
the last branch's expression. Neither was papered over with a heuristic.
|
||||
|
||||
### B — `sizeof`, from the container-stride divides
|
||||
|
||||
```
|
||||
[OK ] Game::DiplomacyStats got 0x24 [OK ] Game::ObservedWeapon got 0x2c
|
||||
[OK ] Game::MoraleEvent got 0x50 [OK ] Game::PlayerReport got 0x30
|
||||
[OK ] Game::ObservedTech got 0x2c
|
||||
```
|
||||
|
||||
`sizeof(ObservedTech) = 0x2c` — the value lane X pinned three ways — falls out mechanically, from a
|
||||
completely different place: `VectorHelper<ObservedTech>::Write` divides the vector's byte span by
|
||||
the element stride, and MSVC's magic-number sequence for that divide is invertible
|
||||
(`M = ceil(2^(32+k)/s)`). `PopulationGroup` and `OutputRates` are `[--]` — they have no
|
||||
`VectorHelper`, so this oracle is silent on them rather than guessing.
|
||||
|
||||
### C — on-disk tag order, vs `save_reader.py`
|
||||
|
||||
22 of `save_reader.py`'s shapes name the exact `::Write` that produces them. For every one, the
|
||||
tool's recovered tag sequence contains the oracle's tag sequence **in the same relative order**:
|
||||
|
||||
```
|
||||
22 shapes agree, 0 disagree
|
||||
```
|
||||
|
||||
That includes `Sys` (78 tags), `Player` (104), `CreateParams` (25), `Ship` (22), `Slot` (18),
|
||||
`DipStat` (14), `PlayerView` (14). `save_reader.py` parses the three real saves `--strict` clean
|
||||
with 36 tests and lane C's `state_checksum.py` proves its coverage by re-serialising byte-for-byte,
|
||||
so this is disassembly and the save oracle agreeing independently, at scale.
|
||||
|
||||
### D — Read/Write cross-check, run on every class
|
||||
|
||||
The campaign's own standard ("the Read functions agree on every member address"), automated: the
|
||||
class's `Read` is extracted the same way and its member offsets compared to `Write`'s, matched by
|
||||
tag name.
|
||||
|
||||
```
|
||||
Read/Write cross-check : 437/437 fields agree (0 classes with any disagreement)
|
||||
```
|
||||
|
||||
Two subtleties had to be handled or this check manufactures false alarms, and both are worth
|
||||
carrying: a tag used **twice** in one class (`ServerPlayer` writes both an `int Team` at `0xac` and
|
||||
a `PlayerAlliances Team` at `0x168`) cannot be matched by name, and an **anonymous** tag (`"."`,
|
||||
the writer passed NULL) carries no identity at all, so positional matching turns one side's
|
||||
under-recovery into a cascade of fake conflicts. Both are excluded, and the classes affected are
|
||||
reported as not-cross-checkable rather than as agreeing.
|
||||
|
||||
---
|
||||
|
||||
## 2. How it works
|
||||
|
||||
Five things had to be got right; each of the last four was worth 10–170 classes on its own.
|
||||
|
||||
**The idiom.** Writer side, one shape under two calling conventions:
|
||||
|
||||
```
|
||||
push 0xff ; default-value argument
|
||||
<member expression> ; lea r,[this+D] | mov r,[this+D] | movzx r,word [this+D]
|
||||
push r
|
||||
push <tag> ; -> .rdata "otch", "pswd", ... (or 0 -> "." on disk)
|
||||
push <stream> / mov ecx,<stream>
|
||||
call WriteString/WriteBool/… | call [[stream]+slot]
|
||||
```
|
||||
|
||||
The push **immediately before the tag push** is always the member. Stream vftable slots, read off
|
||||
the wrappers rather than assumed: `+0x18` string, `+0x1c` bool, `+0x20` float, `+0x24` int,
|
||||
`+0x28` nested, `+0x30` raw bytes (`n = 8` → int64).
|
||||
|
||||
**Sizes come from the kind, never from the offsets touched** — lane S's rule. `bool` 1, `int16` 2,
|
||||
int/float/enum/handle 4, `int64` 8, `std::string` **0x1c**, `std::vector` **0x10**, both
|
||||
*allocator-last* in this build's STL and therefore invisible to any touch-scan.
|
||||
|
||||
**(a) RTTI gives the owner and the this-adjustment.** `tools/rtti_map.py` walks
|
||||
type-descriptor ← `COL.pTypeDescriptor` ← `vftable[-1]` for the whole image: 1,924 type
|
||||
descriptors (exactly the inventory's count), 2,172 COLs, 2,172 vftables. It reproduces the known
|
||||
anchors exactly — `ObservedTech` vftable `0x00a2439c` / COL `0x00a81c78`; `ServerSystem`'s
|
||||
IStreamable vftable `0x00a2043c` at COL offset **+8** with Read `0x0075d4b0` / Write `0x00749630`.
|
||||
`COL.offset` is what turns a decompiled offset into an absolute one, and for `ServerPlayer` that is
|
||||
`+0x3a0`, so most of its members are at *negative* decompiled offsets.
|
||||
|
||||
**(b) The class hierarchy descriptor is the only honest membership test.** A 3-slot vftable is
|
||||
*not* a serializer test: `TacAISquadRule_*`, the CSV row parsers and ~100 other classes have three
|
||||
virtuals. Walking `COL → ClassHierarchyDescriptor → base class array` and requiring
|
||||
`Mars::IStreamable` in the base list removes them all. This dropped a 116-class "empty" bucket to
|
||||
11 — i.e. it removed 105 classes that were never serializers, instead of reporting them as failures.
|
||||
|
||||
**(c) `mod=0` memory operands.** `tools/x86disp.py` indexes operands that carry a *displacement*, so
|
||||
`mov eax,[edi]` — a member at offset **0** — is invisible to it. For a plain struct with no vptr,
|
||||
offset 0 is a real field: `StarSystem::OutputRates.SRt` is exactly that, and so is every
|
||||
`handle`/element read through a bare iterator. Synthesising the mod=0 case recovered `SRt` and every
|
||||
`Flt`/`GF`/`SnF`/`MnF` element in `ServerSystem`.
|
||||
|
||||
**(d) The pointer-member idiom.** `HomeSys`, `PlrID`, `DesID`, `FltID`, `SrnTo` are written as
|
||||
`member->id`, so the pushed value is a load off a *pointer that came from* a member, three to six
|
||||
instructions and a conditional branch earlier. Remembering where a register's pointer came from
|
||||
(and reading the base's this-ness **before** retiring the destination — `mov edi,[edi+0x1c0]`
|
||||
overwrites the this-register with the member it is reading) recovered those five plus
|
||||
`ServerSystem::indi`.
|
||||
|
||||
**(e) Sub-writers.** Two kinds, both invisible without special handling. A **base-class Write**
|
||||
(`StarMapNode::Write` inside `ServerSystem::Write`, giving `Pos`) and a **private sub-writer of the
|
||||
same class** (`StrategyServer` writes its six id lists in `FUN_00794cd0`, so `PlayerIDs`,
|
||||
`DesignIDs`, `SystemIDs`, `FleetIDs`, `ShipIDs`, `TradeIDs` are simply absent without it — that was
|
||||
the last disagreement in check C). Both are spliced in at the call site, which preserves disk order.
|
||||
A third kind, a **tag-forwarding** writer `f(stream, name, member)` that passes the caller's name
|
||||
through to a stream primitive, is *discovered* (by spotting a tag push that is `[ebp+0xc]`) rather
|
||||
than listed by hand, because a hand list is exactly what silently under-reports on 1,600 classes.
|
||||
|
||||
**Containers.** A count argument is never a member; it is `(_Mylast - _Myfirst)/stride`. Recovering
|
||||
the subtraction gives the container's offset (`NumFlts` → the `vector` at `0x16c`, `NVO` → the map
|
||||
`_Mysize` at `0x278`), and inverting the divide gives the element stride — which is `sizeof(T)`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Results at scale
|
||||
|
||||
`uv run python3 tools/serializers.py all` — 0.35 s for the whole binary.
|
||||
|
||||
| tier | classes | member fields | meaning |
|
||||
|---|---|---|---|
|
||||
| **verified** | 87 | 542 | every field resolved and sized, and `Read` agrees on every comparable offset |
|
||||
| **clean** | 77 | 328 | every field resolved and sized; no `Read`-side comparison available |
|
||||
| **unnamed** | 176 | 471 | offsets and types resolved, but the writer passes a NULL name so the fields have no on-disk names (combat commands, network messages) |
|
||||
| **partial** | 31 | 341 | see §4 |
|
||||
| **empty** | 15 | 0 | the class's `Write` writes no tagged field |
|
||||
| **total** | **386** | **1,682** | |
|
||||
|
||||
* **58 classes have a `sizeof` corroborated by a second, independent line of evidence** — 45 from a
|
||||
`VectorHelper<T>` element-stride divide, 13 where the field enumeration's lower bound meets the
|
||||
upper bound implied by every embedding of that type in another class. For the rest the tool
|
||||
reports a **lower bound** and says so: a serializer enumerates *serialised* members, so a
|
||||
non-streamed cache or back-pointer at the tail is invisible to it. That is the honest limit of
|
||||
this method and it is stated everywhere the number appears.
|
||||
* Coverage of the population: 368 non-template classes have `Mars::IStreamable` in their RTTI base
|
||||
list; 348 of them are covered here, plus 38 POD types (`Vector3`, `OutputRates`, …) that have no
|
||||
vftable of their own and are reachable only through a specialised `StreamableHelper` thunk
|
||||
(`push ebp; mov ebp,esp; mov ecx,[ecx+8]; pop ebp; jmp writer`).
|
||||
* This is roughly a **10× increase** in classes with a known layout, and it is reproducible from the
|
||||
exe in under a second.
|
||||
|
||||
---
|
||||
|
||||
## 4. Failure classes — what defeats the tool, and whether it scales
|
||||
|
||||
This is the part that says whether the remaining classes are reachable.
|
||||
|
||||
| # | failure | classes | why it happens | fixable? |
|
||||
|---|---|---|---|---|
|
||||
| 1 | **anonymous tags** | 176 | the writer passes a NULL name, so the field is `"."` on disk. Offsets and types are still recovered; only the *names* are missing, and the Read cross-check cannot run | not by this method — the names do not exist in the binary. These are combat commands and network messages, where the reader is positional |
|
||||
| 2 | **nested member of unknown size** | 19 | an inline sub-object whose own type has neither a `VectorHelper` stride nor a tight embedding bound (`TechTree`, `ShipRecords`, `FleetLayout`) | yes — one constructor or `operator new` size each |
|
||||
| 3 | **container elements only** | 9 | everything the serializer writes is a loop body over a `std::map`/`std::list` node, so the offsets are within the node, not the object (`BuildQueue`, `SpyReport`'s four lists) | partly — needs a node-layout model for the map/list |
|
||||
| 4 | **untyped sub-writer** | 3 | a discovered tag-forwarding writer whose argument type could not be classified, so the member's offset is known but its size is not | yes, per sub-writer |
|
||||
| 5 | **value built across a branch** | 2 fields | `tech ? tech->name : ""`, `max(member, 1)`. The pass is linear, so it sees the last branch only | yes, with a basic-block-aware pass — deliberately **not** patched with a backward-search heuristic, because there is no oracle for the 1,600 classes on which a heuristic would silently be wrong |
|
||||
| 6 | **`std::map` / `std::list` members** | — | recovered as their `_Mysize` int, not as the container. `ServerSystem::NVO` comes out as `int @0x278`; the map itself starts at `0x274` | yes, and cheaply: this build's `std::map` is 0x10 bytes with head@0/size@4, from `NVO@0x274` → `NVE@0x284` |
|
||||
| 7 | **runtime-built tag** | ~1 | `Mars::ParticleSystem::Write` makes six write-side calls and not one tag is a compile-time constant | no |
|
||||
| 8 | **stub `Write`** | 15 | e.g. `Game::ServerTradeManager`'s Read and Write are both the inherited no-op `0x924fb0`; `Game::ShipDesign::Write` makes no stream call at all. These classes are not streamed through `IStreamable` and something else persists them | n/a — a finding, not a failure |
|
||||
|
||||
**Does it generalise?** Yes, with one caveat that matters. Categories 2, 3, 4, 5 and 6 are 64
|
||||
classes and every one of them is a bounded, mechanical fix — none is a research problem. Category 1
|
||||
(176 classes, 46% of the population) is a hard limit, but a *soft* one: those layouts are complete
|
||||
in offsets and types, and only the names are missing, so a reimplementation can use them as-is.
|
||||
Category 7 is genuinely out of reach and is one class.
|
||||
|
||||
The caveat: **this method covers only what the class serialises.** `ServerSystem` is `>= 0x2d8` by
|
||||
enumeration and the campaign's hand table agrees, but neither can see a non-streamed member past the
|
||||
last serialised one. Every `sizeof` in the output is therefore labelled with how it was obtained,
|
||||
and the 58 corroborated ones are the only ones that should be treated as closed.
|
||||
|
||||
---
|
||||
|
||||
## 5. Write-back
|
||||
|
||||
* **`objects/layouts.json`** — the full machine-readable result (fields, offsets, kinds, tags,
|
||||
nested types, container strides, grade, why, Read/Write agreement).
|
||||
* **`objects/layouts.md`** — the same as tables, one section per class.
|
||||
* **`objects/layouts.h`** — 288 C structs, gaps made explicit as padding, `Mars_string` (0x1c) and
|
||||
`Mars_vector` (0x10) declared once with the allocator word in place.
|
||||
* **Ghidra** — `tools/serializers_ghidra.py structs` pushes every struct through
|
||||
`parse-c-structure` (dependencies first, resumable) — **288 pushed, 0 failed** — and `… labels`
|
||||
names the `Write`/`Read` entry points (**328 labels, 0 failed**). `Game_ObservedTech` comes back from Ghidra as `size: 44`.
|
||||
* **`ghidra/addresses.json`** — +201 entries: `<Class>_Write` / `<Class>_Read` for all 87
|
||||
verified-tier classes, and `sizeof_<Class>` for every corroborated size, each carrying its
|
||||
Read/Write agreement count in the prototype comment. Header regenerated with
|
||||
`tools/gen_addresses.py` (never hand-edited).
|
||||
|
||||
## 6. Open items for the next lane
|
||||
|
||||
1. **`Game::ShipDesign::Write` (`0x8747a0`) makes no stream call.** `ShipDesign` is an important
|
||||
class and its IStreamable slots are inert — worth knowing how designs actually persist
|
||||
(`ShipDesignDef` is already verified separately, which may be the whole answer).
|
||||
2. **`std::map`/`std::list` node layouts** would close failure classes 3 and 6 — ~15 classes.
|
||||
3. **The 19 unsized nested types** need one size each; a constructor or `operator new` argument
|
||||
scan would do it in bulk and would also give sizes for the non-streamed tail.
|
||||
4. `Game::SVSOJewelsOfTheCrown` writes the tag `JEWELLOCATIONID` **twice**, at `+0x8` and `+0x10`.
|
||||
Not an error in the tool (the Write really does emit it twice) but it means the on-disk record
|
||||
has a duplicated name — a save reader matching that shape by name will get the wrong field.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
// GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1).
|
||||
// Source: sots-re ghidra/addresses.json @ d523d27, generated 2026-09-08 by tools/gen_addresses.py
|
||||
// Source: sots-re ghidra/addresses.json @ 795aa47, generated 2026-09-08 by tools/gen_addresses.py
|
||||
// Runtime address = (uintptr_t)GetModuleHandle(NULL) + RVA (the exe is ASLR-relocated).
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
|
@ -369,7 +369,7 @@ constexpr uint32_t ServerPlayer_off_SetupResearchMult = 0x0000022c;
|
|||
constexpr uint32_t ServerPlayer_off_Sav = 0x00000284;
|
||||
// offset Tech* current research target (ResT); NULL = none [verified-by-save]
|
||||
constexpr uint32_t ServerPlayer_off_ResearchTarget = 0x00000294;
|
||||
// offset std::vector<ObservedTech> otch -- 3 words {_Myfirst@0x274,_Mylast@0x278,_Myend@0x27c}; save tag otch. sizeof(ObservedTech) = 0x2c (44), PINNED three ways: magic divide 0x2e8ba2e9 sar 3 (=/44) in vector_ObservedTech_assign 0x0087239f, imul reg,reg,0x2c at 0x0087243a / 0x007b735b, and the linear search stride `add edi,0x2c` at 0x007ba257. Append site = RecordObservedTech+0xdf (0x007ba27f): lea ecx,[player+0x274]; call vector_ObservedTech_push_back 0x007b7320. All three words move because push_back reallocs through vector_44B_grow 0x007b5820. [verified]
|
||||
// offset std::vector<ObservedTech> otch -- 3 words {_Myfirst@0x274,_Mylast@0x278,_Myend@0x27c}; save tag otch. sizeof(ObservedTech) = 0x2c (44), PINNED three ways: magic divide 0x2e8ba2e9 sar 3 (=/44) in vector_ObservedTech_assign 0x0087239f, imul reg,reg,0x2c at 0x0087243a / 0x007b735b, and the linear search stride `add edi,0x2c` at 0x007ba257. Append site = RecordObservedTech+0xdf (0x007ba27f): lea ecx,[player+0x274]; call vector_ObservedTech_push_back 0x007b7320. All three words move because push_back reallocs through vector_44B_grow 0x007b5820. LIVE CONFIRMATION (lane V, 2026-09-08, VM140, build eventlive-dd38117-20260908T0916Z): the ProcessResearch `observed_techs` Result region measured the vector's byte span growing by exactly 44 on each of the two tech-completion calls of the five-End-Turn run from ref-turn2 (call 3: 440 -> 484; call 9: 484 -> 528) -- an independent behavioural confirmation of the static pin. Both non-completing players' spans were 880 = 20 x 44 and never moved. [verified]
|
||||
constexpr uint32_t ServerPlayer_off_ObservedTechs = 0x00000274;
|
||||
// offset float IncMod [verified-by-save]
|
||||
constexpr uint32_t ServerPlayer_off_IncMod = 0x0000030c;
|
||||
|
|
@ -799,7 +799,7 @@ constexpr uint32_t Mars_Vec3_NormaliseEpsilon = 0x005e1ef8;
|
|||
constexpr uint32_t StrategyServer_MoveFleet_straight_leg = 0x003da0f2;
|
||||
// site site inside StrategyServer::MoveFleet /* the move != distance branch: exactly two roundings per component, tmp.c = float32(dir.c * move) then pos.c = float32(pos.c + tmp.c). `move` is reloaded from a float32 slot. The sibling branch (move == distance, an EXACT float compare) copies the destination's three words verbatim with mov, so an arrival never steps onto its destination. */ [verified]
|
||||
constexpr uint32_t StrategyServer_MoveFleet_position_update = 0x003da2ac;
|
||||
// constant sizeof(Game::ObservedTech) = 0x2c (44 bytes). Confirmed independently by (a) the compiler's magic division by 44 (mov eax,0x2e8ba2e9; imul; sar edx,3) at 0x0087239f and 0x007b7339, (b) imul reg,reg,0x2c at 0x0087243a, 0x00872468, 0x007b735b, and (c) the iterator advance `add edi,0x2c` in RecordObservedTech's linear search at 0x007ba257. FULL MEMBER MAP, from ObservedTech_Write 0x00817cf0 / ObservedTech_Read 0x00817c40 (lane S 2026-09-08): +0x00 vptr 0x00a2439c; +0x04 uint16 otnF; +0x06 uint16 otnL; +0x08 bool odet (1 byte, +3 pad); +0x0c std::string otch (0x1c, so _Mysize at +0x1c, _Myres at +0x20, _Alval at +0x24); +0x28 int owith. 4 + 2 + 2 + 4 + 0x1c + 4 = 0x2c exactly, no padding slack and no unaccounted field. [verified]
|
||||
// constant sizeof(Game::ObservedTech) = 0x2c (44 bytes). Confirmed independently by (a) the compiler's magic division by 44 (mov eax,0x2e8ba2e9; imul; sar edx,3) at 0x0087239f and 0x007b7339, (b) imul reg,reg,0x2c at 0x0087243a, 0x00872468, 0x007b735b, and (c) the iterator advance `add edi,0x2c` in RecordObservedTech's linear search at 0x007ba257. FULL MEMBER MAP, from ObservedTech_Write 0x00817cf0 / ObservedTech_Read 0x00817c40 (lane S 2026-09-08): +0x00 vptr 0x00a2439c; +0x04 uint16 otnF; +0x06 uint16 otnL; +0x08 bool odet (1 byte, +3 pad); +0x0c std::string otch (0x1c, so _Mysize at +0x1c, _Myres at +0x20, _Alval at +0x24); +0x28 int owith. 4 + 2 + 2 + 4 + 0x1c + 4 = 0x2c exactly, no padding slack and no unaccounted field. LIVE CONFIRMATION (lane V, 2026-09-08, VM140, build eventlive-dd38117-20260908T0916Z): the ProcessResearch `observed_techs` Result region measured the vector's byte span growing by exactly 44 on each of the two tech-completion calls of the five-End-Turn run from ref-turn2 (call 3: 440 -> 484; call 9: 484 -> 528) -- an independent behavioural confirmation of the static pin. Both non-completing players' spans were 880 = 20 x 44 and never moved. [verified]
|
||||
constexpr uint32_t ObservedTech_sizeof = 0x0000002c;
|
||||
// data Game::ObservedTech vftable. RTTI COL 0x00a81c78 -> type descriptor 0x00aeede4 = '.?AVObservedTech@Game@@'. Written to element+0x00 by ObservedTech_ctor 0x008562a0 -- so ObservedTech is polymorphic and its first word is the vptr, not a data field. [verified]
|
||||
constexpr uint32_t ObservedTech_vftable = 0x0062439c;
|
||||
|
|
@ -835,5 +835,407 @@ constexpr uint32_t ObservedWeapon_Write = 0x00417bc0;
|
|||
constexpr uint32_t std_string_sizeof = 0x0000001c;
|
||||
// thiscall vector<T>::_Reserve/grow for a 44-byte element type. Shared with FUN_0086dec0, so it may be a COMDAT-folded body -- do NOT assume it is ObservedTech-specific. [mapped]
|
||||
constexpr uint32_t vector_44B_grow = 0x003b5820;
|
||||
// thiscall void (Game_AIAutoPeaceRun* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a199b4, COL offset +0x0; 3 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_AIAutoPeaceRun_Write = 0x00292cf0;
|
||||
// thiscall void (Game_AIAutoPeaceRun* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a199b4, COL offset +0x0; 3 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_AIAutoPeaceRun_Read = 0x00292c90;
|
||||
// layout sizeof(Game::AIAutoPeaceRun) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_AIAutoPeaceRun = 0x00000010;
|
||||
// thiscall void (Game_BackEngProject* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a3185c, COL offset +0x0; 6 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_BackEngProject_Write = 0x00453390;
|
||||
// thiscall void (Game_BackEngProject* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a3185c, COL offset +0x0; 6 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_BackEngProject_Read = 0x00453280;
|
||||
// thiscall void (Game_CombatPlayerReport* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a0623c, COL offset +0x0; 11 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CombatPlayerReport_Write = 0x0019d6d0;
|
||||
// thiscall void (Game_CombatPlayerReport* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a0623c, COL offset +0x0; 11 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CombatPlayerReport_Read = 0x0019d360;
|
||||
// layout sizeof(Game::CombatPlayerReport) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_CombatPlayerReport = 0x00000054;
|
||||
// thiscall void (Game_CombatPlayerStats* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009e4b74, COL offset +0x0; 5 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CombatPlayerStats_Write = 0x00059140;
|
||||
// thiscall void (Game_CombatPlayerStats* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009e4b74, COL offset +0x0; 5 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CombatPlayerStats_Read = 0x00059070;
|
||||
// layout sizeof(Game::CombatPlayerStats) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_CombatPlayerStats = 0x00000024;
|
||||
// thiscall void (Game_CombatReport* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a0624c, COL offset +0x0; 14 member fields; 6/6 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CombatReport_Write = 0x0019db30;
|
||||
// thiscall void (Game_CombatReport* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a0624c, COL offset +0x0; 14 member fields; 6/6 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CombatReport_Read = 0x0019d8f0;
|
||||
// thiscall void (Game_CombatShipReport* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a06064, COL offset +0x0; 12 member fields; 7/7 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CombatShipReport_Write = 0x0019b960;
|
||||
// thiscall void (Game_CombatShipReport* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a06064, COL offset +0x0; 12 member fields; 7/7 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CombatShipReport_Read = 0x0019c650;
|
||||
// layout sizeof(Game::CombatShipReport) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_CombatShipReport = 0x00000050;
|
||||
// thiscall void (Game_CombatWeaponReport* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a06074, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CombatWeaponReport_Write = 0x0019bb70;
|
||||
// thiscall void (Game_CombatWeaponReport* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a06074, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CombatWeaponReport_Read = 0x0019bad0;
|
||||
// layout sizeof(Game::CombatWeaponReport) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_CombatWeaponReport = 0x00000030;
|
||||
// thiscall void (Game_CommDeclareShareSystemNotes* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a3157c, COL offset +0x0; 8 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CommDeclareShareSystemNotes_Write = 0x00410da0;
|
||||
// thiscall void (Game_CommDeclareShareSystemNotes* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a3157c, COL offset +0x0; 8 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CommDeclareShareSystemNotes_Read = 0x00410d50;
|
||||
// thiscall void (Game_CommonPlague* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f8b50, COL offset +0x0; 6 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CommonPlague_Write = 0x001360e0;
|
||||
// thiscall void (Game_CommonPlague* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f8b50, COL offset +0x0; 6 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_CommonPlague_Read = 0x001373e0;
|
||||
// thiscall void (Game_DefenceFleetAssignment* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009e3c48, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_DefenceFleetAssignment_Write = 0x004119d0;
|
||||
// thiscall void (Game_DefenceFleetAssignment* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009e3c48, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_DefenceFleetAssignment_Read = 0x00432330;
|
||||
// thiscall void (Game_DiplomacyStats* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a21430, COL offset +0x0; 14 member fields; 13/13 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_DiplomacyStats_Write = 0x00418cb0;
|
||||
// thiscall void (Game_DiplomacyStats* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a21430, COL offset +0x0; 14 member fields; 13/13 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_DiplomacyStats_Read = 0x00418b80;
|
||||
// layout sizeof(Game::DiplomacyStats) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_DiplomacyStats = 0x00000024;
|
||||
// thiscall void (Game_EventStorage* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a313c8, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_EventStorage_Write = 0x00425cc0;
|
||||
// thiscall void (Game_EventStorage* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a313c8, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_EventStorage_Read = 0x004850f0;
|
||||
// thiscall void (Game_EventStorage_Event* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a21958, COL offset +0x0; 8 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_EventStorage_Event_Write = 0x00425ab0;
|
||||
// thiscall void (Game_EventStorage_Event* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a21958, COL offset +0x0; 8 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_EventStorage_Event_Read = 0x00425970;
|
||||
// thiscall void (Game_EventStorage_TurnEvents* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a0f07c, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_EventStorage_TurnEvents_Write = 0x00425c40;
|
||||
// thiscall void (Game_EventStorage_TurnEvents* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a0f07c, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_EventStorage_TurnEvents_Read = 0x00425bb0;
|
||||
// layout sizeof(Game::EventStorage::TurnEvents) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_EventStorage_TurnEvents = 0x00000018;
|
||||
// thiscall void (Game_FleetNameGenerator* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a32760, COL offset +0x0; 1 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_FleetNameGenerator_Write = 0x00417460;
|
||||
// thiscall void (Game_FleetNameGenerator* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a32760, COL offset +0x0; 1 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_FleetNameGenerator_Read = 0x00417440;
|
||||
// thiscall void (Game_FlightPlan* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a1d50c, COL offset +0x0; 6 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_FlightPlan_Write = 0x00300f60;
|
||||
// thiscall void (Game_FlightPlan* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a1d50c, COL offset +0x0; 6 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_FlightPlan_Read = 0x00304c70;
|
||||
// layout sizeof(Game::FlightPlan) -- enumeration meets embedding [verified]
|
||||
constexpr uint32_t sizeof_Game_FlightPlan = 0x00000038;
|
||||
// thiscall void (Game_IndependenceInfo* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a2005c, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_IndependenceInfo_Write = 0x00348ee0;
|
||||
// thiscall void (Game_IndependenceInfo* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a2005c, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_IndependenceInfo_Read = 0x00348df0;
|
||||
// thiscall void (Game_JewelsProject* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fa438, COL offset +0x0; 4 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_JewelsProject_Write = 0x00148910;
|
||||
// thiscall void (Game_JewelsProject* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fa438, COL offset +0x0; 4 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_JewelsProject_Read = 0x001488d0;
|
||||
// thiscall void (Game_MonitorProject* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a3180c, COL offset +0x0; 7 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_MonitorProject_Write = 0x004287d0;
|
||||
// thiscall void (Game_MonitorProject* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a3180c, COL offset +0x0; 7 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_MonitorProject_Read = 0x00428740;
|
||||
// thiscall void (Game_Morale* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a1f7c8, COL offset +0x0; 1 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_Morale_Write = 0x00344ea0;
|
||||
// thiscall void (Game_Morale* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a1f7c8, COL offset +0x0; 1 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_Morale_Read = 0x00344dd0;
|
||||
// thiscall void (Game_MoraleEvent* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a2003c, COL offset +0x0; 6 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_MoraleEvent_Write = 0x003491b0;
|
||||
// thiscall void (Game_MoraleEvent* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a2003c, COL offset +0x0; 6 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_MoraleEvent_Read = 0x003490b0;
|
||||
// layout sizeof(Game::MoraleEvent) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_MoraleEvent = 0x00000050;
|
||||
// thiscall void (Game_NodePath* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a1cb50, COL offset +0x0; 11 member fields; 6/6 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_NodePath_Write = 0x002e1fd0;
|
||||
// thiscall void (Game_NodePath* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a1cb50, COL offset +0x0; 11 member fields; 6/6 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_NodePath_Read = 0x002e1e90;
|
||||
// layout sizeof(Game::NodePath) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_NodePath = 0x00000030;
|
||||
// thiscall void (Game_ObservedTech* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a2439c, COL offset +0x0; 5 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ObservedTech_Write = 0x00417cf0;
|
||||
// thiscall void (Game_ObservedTech* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a2439c, COL offset +0x0; 5 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ObservedTech_Read = 0x00417c40;
|
||||
// layout sizeof(Game::ObservedTech) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_ObservedTech = 0x0000002c;
|
||||
// thiscall void (Game_ObservedWeapon* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a2438c, COL offset +0x0; 5 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ObservedWeapon_Write = 0x00417bc0;
|
||||
// thiscall void (Game_ObservedWeapon* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a2438c, COL offset +0x0; 5 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ObservedWeapon_Read = 0x00417b10;
|
||||
// layout sizeof(Game::ObservedWeapon) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_ObservedWeapon = 0x0000002c;
|
||||
// thiscall void (Game_PlayerAid* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a286dc, COL offset +0x0; 5 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_PlayerAid_Write = 0x00418dc0;
|
||||
// thiscall void (Game_PlayerAid* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a286dc, COL offset +0x0; 5 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_PlayerAid_Read = 0x004207b0;
|
||||
// layout sizeof(Game::PlayerAid) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_PlayerAid = 0x00000018;
|
||||
// thiscall void (Game_PlayerNotes* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a21948, COL offset +0x0; 3 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_PlayerNotes_Write = 0x004132b0;
|
||||
// thiscall void (Game_PlayerNotes* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a21948, COL offset +0x0; 3 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_PlayerNotes_Read = 0x00413250;
|
||||
// thiscall void (Game_PlayerReport* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a21440, COL offset +0x0; 11 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_PlayerReport_Write = 0x00417480;
|
||||
// thiscall void (Game_PlayerReport* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a21440, COL offset +0x0; 11 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_PlayerReport_Read = 0x004200a0;
|
||||
// layout sizeof(Game::PlayerReport) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_PlayerReport = 0x00000030;
|
||||
// thiscall void (Game_PopulationGroup* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f8d50, COL offset +0x0; 3 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_PopulationGroup_Write = 0x00136af0;
|
||||
// thiscall void (Game_PopulationGroup* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f8d50, COL offset +0x0; 3 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_PopulationGroup_Read = 0x00136a80;
|
||||
// thiscall void (Game_RaidTargets* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a21968, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_RaidTargets_Write = 0x00418ec0;
|
||||
// thiscall void (Game_RaidTargets* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a21968, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_RaidTargets_Read = 0x0043a090;
|
||||
// thiscall void (Game_SVSOAntiquarians* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fab94, COL offset +0x0; 3 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOAntiquarians_Write = 0x0014bb40;
|
||||
// thiscall void (Game_SVSOAntiquarians* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fab94, COL offset +0x0; 3 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOAntiquarians_Read = 0x00151e90;
|
||||
// thiscall void (Game_SVSOCrowDefenders* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f5904, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOCrowDefenders_Write = 0x000f8c90;
|
||||
// thiscall void (Game_SVSOCrowDefenders* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f5904, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOCrowDefenders_Read = 0x0010fe90;
|
||||
// thiscall void (Game_SVSOLandGrab* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fa92c, COL offset +0x0; 2 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOLandGrab_Write = 0x00148f70;
|
||||
// thiscall void (Game_SVSOLandGrab* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fa92c, COL offset +0x0; 2 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOLandGrab_Read = 0x0014a9e0;
|
||||
// thiscall void (Game_SVSOLocust* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f56bc, COL offset +0x0; 5 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOLocust_Write = 0x00108ef0;
|
||||
// thiscall void (Game_SVSOLocust* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f56bc, COL offset +0x0; 5 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOLocust_Read = 0x0010d380;
|
||||
// thiscall void (Game_SVSOOrtgay* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f0e94, COL offset +0x0; 9 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOOrtgay_Write = 0x00109510;
|
||||
// thiscall void (Game_SVSOOrtgay* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f0e94, COL offset +0x0; 9 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOOrtgay_Read = 0x00109390;
|
||||
// thiscall void (Game_SVSOProgressionWars* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fb34c, COL offset +0x0; 10 member fields; 8/8 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOProgressionWars_Write = 0x0014cdb0;
|
||||
// thiscall void (Game_SVSOProgressionWars* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fb34c, COL offset +0x0; 10 member fields; 8/8 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOProgressionWars_Read = 0x00153490;
|
||||
// thiscall void (Game_SVSOPuppetMaster* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f3dcc, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOPuppetMaster_Write = 0x00109f80;
|
||||
// thiscall void (Game_SVSOPuppetMaster* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f3dcc, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOPuppetMaster_Read = 0x00111b80;
|
||||
// thiscall void (Game_SVSORefugees* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f575c, COL offset +0x0; 3 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSORefugees_Write = 0x00109640;
|
||||
// thiscall void (Game_SVSORefugees* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f575c, COL offset +0x0; 3 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSORefugees_Read = 0x00126420;
|
||||
// thiscall void (Game_SVSORefugees_PlayerStatus* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f11cc, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSORefugees_PlayerStatus_Write = 0x000fbf50;
|
||||
// thiscall void (Game_SVSORefugees_PlayerStatus* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f11cc, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSORefugees_PlayerStatus_Read = 0x000fbeb0;
|
||||
// thiscall void (Game_SVSOSlaversRefuel* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f4834, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOSlaversRefuel_Write = 0x000fdf80;
|
||||
// thiscall void (Game_SVSOSlaversRefuel* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f4834, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOSlaversRefuel_Read = 0x00120b00;
|
||||
// thiscall void (Game_SVSOSwarm_Infestation* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f1720, COL offset +0x0; 6 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOSwarm_Infestation_Write = 0x000fe0e0;
|
||||
// thiscall void (Game_SVSOSwarm_Infestation* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f1720, COL offset +0x0; 6 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOSwarm_Infestation_Read = 0x00104bd0;
|
||||
// thiscall void (Game_SVSOSwarmQueen_HiveInfo* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f1a68, COL offset +0x0; 3 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOSwarmQueen_HiveInfo_Write = 0x000fe730;
|
||||
// thiscall void (Game_SVSOSwarmQueen_HiveInfo* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f1a68, COL offset +0x0; 3 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOSwarmQueen_HiveInfo_Read = 0x000fe6f0;
|
||||
// layout sizeof(Game::SVSOSwarmQueen::HiveInfo) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_SVSOSwarmQueen_HiveInfo = 0x00000010;
|
||||
// thiscall void (Game_SVSOSystemKiller* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f3e6c, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOSystemKiller_Write = 0x0010af20;
|
||||
// thiscall void (Game_SVSOSystemKiller* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f3e6c, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOSystemKiller_Read = 0x00111f90;
|
||||
// thiscall void (Game_SVSOUpstartApes* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fb4cc, COL offset +0x0; 4 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_Write = 0x0014d1b0;
|
||||
// thiscall void (Game_SVSOUpstartApes* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fb4cc, COL offset +0x0; 4 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_Read = 0x00163850;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_DEMANDS_SHIPS* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fb290, COL offset +0x0; 6 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_DEMANDS_SHIPS_Write = 0x0014cfe0;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_DEMANDS_SHIPS* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fb290, COL offset +0x0; 6 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_DEMANDS_SHIPS_Read = 0x0014cef0;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_DEMANDS_TRADE_GOODS* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fa564, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_DEMANDS_TRADE_GOODS_Write = 0x00148ce0;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_DEMANDS_TRADE_GOODS* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fa564, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_DEMANDS_TRADE_GOODS_Read = 0x00148c90;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_DEMANDS_VICTORY* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fa534, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_DEMANDS_VICTORY_Write = 0x00148ce0;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_DEMANDS_VICTORY* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fa534, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_DEMANDS_VICTORY_Read = 0x00148c90;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_ENERGY_WEAPONS_DECADENT* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fa504, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_ENERGY_WEAPONS_DECADENT_Write = 0x00148ce0;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_ENERGY_WEAPONS_DECADENT* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fa504, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_ENERGY_WEAPONS_DECADENT_Read = 0x00148c90;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_FEARS_YOUR_POWER* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fa4d4, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_FEARS_YOUR_POWER_Write = 0x00148ce0;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_FEARS_YOUR_POWER* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fa4d4, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_FEARS_YOUR_POWER_Read = 0x00148c90;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_GIFTS* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fa54c, COL offset +0x0; 2 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_GIFTS_Write = 0x00148c50;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_GIFTS* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fa54c, COL offset +0x0; 2 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_GIFTS_Read = 0x00148c10;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_MUST_EXPAND* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fa51c, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_MUST_EXPAND_Write = 0x00148bb0;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_MUST_EXPAND* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fa51c, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_MUST_EXPAND_Read = 0x00148b50;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_REQUIRES_CASH* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fa4ec, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_REQUIRES_CASH_Write = 0x00148ce0;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_REQUIRES_CASH* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fa4ec, COL offset +0x0; 3 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_REQUIRES_CASH_Read = 0x00148c90;
|
||||
// thiscall void (Game_SVSOVonNeumann* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f59cc, COL offset +0x0; 24 member fields; 14/14 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOVonNeumann_Write = 0x0012b8d0;
|
||||
// thiscall void (Game_SVSOVonNeumann* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f59cc, COL offset +0x0; 24 member fields; 14/14 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOVonNeumann_Read = 0x0010e130;
|
||||
// thiscall void (Game_SVSOVonNeumann_DefeatRecord* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f1c9c, COL offset +0x0; 5 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOVonNeumann_DefeatRecord_Write = 0x000ff7f0;
|
||||
// thiscall void (Game_SVSOVonNeumann_DefeatRecord* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f1c9c, COL offset +0x0; 5 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOVonNeumann_DefeatRecord_Read = 0x001051f0;
|
||||
// layout sizeof(Game::SVSOVonNeumann::DefeatRecord) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_SVSOVonNeumann_DefeatRecord = 0x00000018;
|
||||
// thiscall void (Game_ServerNodeGraph* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a1cc4c, COL offset +0x44; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ServerNodeGraph_Write = 0x002e3530;
|
||||
// thiscall void (Game_ServerNodeGraph* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a1cc4c, COL offset +0x44; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ServerNodeGraph_Read = 0x002e61e0;
|
||||
// thiscall void (Game_ServerSpyManager* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a30728, COL offset +0x4; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ServerSpyManager_Write = 0x00428a40;
|
||||
// thiscall void (Game_ServerSpyManager* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a30728, COL offset +0x4; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ServerSpyManager_Read = 0x004381b0;
|
||||
// thiscall void (Game_ServerTradeManagerImpl* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a31b64, COL offset +0x4c; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ServerTradeManagerImpl_Write = 0x0042cb60;
|
||||
// thiscall void (Game_ServerTradeManagerImpl* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a31b64, COL offset +0x4c; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ServerTradeManagerImpl_Read = 0x00458a10;
|
||||
// thiscall void (Game_ServerTradeSector* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a31b30, COL offset +0x8; 12 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ServerTradeSector_Write = 0x0042c8c0;
|
||||
// thiscall void (Game_ServerTradeSector* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a31b30, COL offset +0x8; 12 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ServerTradeSector_Read = 0x00458290;
|
||||
// thiscall void (Game_ServerTradeSector_FreighterWarning* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a2d858, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ServerTradeSector_FreighterWarning_Write = 0x004197d0;
|
||||
// thiscall void (Game_ServerTradeSector_FreighterWarning* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a2d858, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ServerTradeSector_FreighterWarning_Read = 0x00419780;
|
||||
// layout sizeof(Game::ServerTradeSector::FreighterWarning) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_ServerTradeSector_FreighterWarning = 0x0000000c;
|
||||
// thiscall void (Game_ShipBuildOrder* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a0c160, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ShipBuildOrder_Write = 0x00413800;
|
||||
// thiscall void (Game_ShipBuildOrder* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a0c160, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ShipBuildOrder_Read = 0x00413770;
|
||||
// thiscall void (Game_ShipBuildOrderDef* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a0ad08, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ShipBuildOrderDef_Write = 0x00413800;
|
||||
// thiscall void (Game_ShipBuildOrderDef* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a0ad08, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ShipBuildOrderDef_Read = 0x00413770;
|
||||
// layout sizeof(Game::ShipBuildOrderDef) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_ShipBuildOrderDef = 0x00000018;
|
||||
// thiscall void (Game_ShipRecords* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a3144c, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ShipRecords_Write = 0x004176a0;
|
||||
// thiscall void (Game_ShipRecords* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a3144c, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ShipRecords_Read = 0x004560d0;
|
||||
// thiscall void (Game_SpecialProject* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a21938, COL offset +0x0; 6 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpecialProject_Write = 0x00414760;
|
||||
// thiscall void (Game_SpecialProject* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a21938, COL offset +0x0; 6 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpecialProject_Read = 0x004146c0;
|
||||
// layout sizeof(Game::SpecialProject) -- enumeration meets embedding [verified]
|
||||
constexpr uint32_t sizeof_Game_SpecialProject = 0x00000034;
|
||||
// thiscall void (Game_SpyCraft* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a30718, COL offset +0x0; 13 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpyCraft_Write = 0x00414940;
|
||||
// thiscall void (Game_SpyCraft* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a30718, COL offset +0x0; 13 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpyCraft_Read = 0x00437dc0;
|
||||
// thiscall void (Game_SpyReportDefences* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a313e8, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpyReportDefences_Write = 0x00428ba0;
|
||||
// thiscall void (Game_SpyReportDefences* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a313e8, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpyReportDefences_Read = 0x00428b10;
|
||||
// thiscall void (Game_SpyReportTechTree* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a318b4, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpyReportTechTree_Write = 0x00414ba0;
|
||||
// thiscall void (Game_SpyReportTechTree* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a318b4, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpyReportTechTree_Read = 0x00414b40;
|
||||
// thiscall void (Game_SpyReportTrade* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a313f8, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpyReportTrade_Write = 0x00428cf0;
|
||||
// thiscall void (Game_SpyReportTrade* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a313f8, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpyReportTrade_Read = 0x00428c20;
|
||||
// thiscall void (Game_SpyReportTradeRoute* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a3079c, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpyReportTradeRoute_Write = 0x00414aa0;
|
||||
// thiscall void (Game_SpyReportTradeRoute* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a3079c, COL offset +0x0; 4 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SpyReportTradeRoute_Read = 0x00438980;
|
||||
// layout sizeof(Game::SpyReportTradeRoute) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_SpyReportTradeRoute = 0x00000020;
|
||||
// thiscall void (Game_StrategyGameCreateParams* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a25574, COL offset +0x0; 25 member fields; 19/19 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StrategyGameCreateParams_Write = 0x0042ae40;
|
||||
// thiscall void (Game_StrategyGameCreateParams* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a25574, COL offset +0x0; 25 member fields; 19/19 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StrategyGameCreateParams_Read = 0x00432cc0;
|
||||
// thiscall void (Game_StrategyGameInfo* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a07ba0, COL offset +0x0; 13 member fields; 10/10 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StrategyGameInfo_Write = 0x00429960;
|
||||
// thiscall void (Game_StrategyGameInfo* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a07ba0, COL offset +0x0; 13 member fields; 10/10 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StrategyGameInfo_Read = 0x00475cb0;
|
||||
// thiscall void (Game_StrategyPlayerInfo* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a077d8, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StrategyPlayerInfo_Write = 0x004298d0;
|
||||
// thiscall void (Game_StrategyPlayerInfo* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a077d8, COL offset +0x0; 2 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StrategyPlayerInfo_Read = 0x00429820;
|
||||
// layout sizeof(Game::StrategyPlayerInfo) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_StrategyPlayerInfo = 0x000000c4;
|
||||
// thiscall void (Game_StrategyServer* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a26084, COL offset +0x0; 38 member fields; 18/18 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StrategyServer_Write = 0x0039fa70;
|
||||
// thiscall void (Game_StrategyServer* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a26084, COL offset +0x0; 38 member fields; 18/18 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StrategyServer_Read = 0x003d27a0;
|
||||
// thiscall void (Game_StrategyTimerParams* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a07334, COL offset +0x0; 4 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StrategyTimerParams_Write = 0x004173e0;
|
||||
// thiscall void (Game_StrategyTimerParams* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a07334, COL offset +0x0; 4 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StrategyTimerParams_Read = 0x00420040;
|
||||
// thiscall void (Game_TacReport* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009e4600, COL offset +0x0; 17 member fields; 13/13 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_TacReport_Write = 0x00058e60;
|
||||
// thiscall void (Game_TacReport* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009e4600, COL offset +0x0; 17 member fields; 13/13 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_TacReport_Read = 0x00058c00;
|
||||
// thiscall void (Game_TacReportEvents* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009e45f0, COL offset +0x0; 7 member fields; 7/7 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_TacReportEvents_Write = 0x000565d0;
|
||||
// thiscall void (Game_TacReportEvents* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009e45f0, COL offset +0x0; 7 member fields; 7/7 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_TacReportEvents_Read = 0x00056540;
|
||||
// layout sizeof(Game::TacReportEvents) -- enumeration meets embedding [verified]
|
||||
constexpr uint32_t sizeof_Game_TacReportEvents = 0x00000020;
|
||||
// thiscall void (Game_TechOfferProject* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a31884, COL offset +0x0; 9 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_TechOfferProject_Write = 0x004535c0;
|
||||
// thiscall void (Game_TechOfferProject* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a31884, COL offset +0x0; 9 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_TechOfferProject_Read = 0x00453530;
|
||||
// thiscall void (Game_TechProject* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a31834, COL offset +0x0; 6 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_TechProject_Write = 0x00453390;
|
||||
// thiscall void (Game_TechProject* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a31834, COL offset +0x0; 6 member fields; 4/4 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_TechProject_Read = 0x00453280;
|
||||
// thiscall void (Game_TradeRoute* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a2d848, COL offset +0x0; 8 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_TradeRoute_Write = 0x00419650;
|
||||
// thiscall void (Game_TradeRoute* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a2d848, COL offset +0x0; 8 member fields; 5/5 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_TradeRoute_Read = 0x004208d0;
|
||||
// thiscall void (Game_ZuulInfestation* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f8cb0, COL offset +0x0; 5 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ZuulInfestation_Write = 0x001361e0;
|
||||
// thiscall void (Game_ZuulInfestation* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f8cb0, COL offset +0x0; 5 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ZuulInfestation_Read = 0x00136160;
|
||||
// thiscall void (Game_AICombatReport* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a1a6f0, COL offset +0x0; 4 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_AICombatReport_Write = 0x002986e0;
|
||||
// thiscall void (Game_AICombatReport* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a1a6f0, COL offset +0x0; 4 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_AICombatReport_Read = 0x0029c6d0;
|
||||
// thiscall void (Game_FieldTemplate_Point* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a05df8, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_FieldTemplate_Point_Write = 0x0019bfa0;
|
||||
// thiscall void (Game_FieldTemplate_Point* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a05df8, COL offset +0x0; 5 member fields; 3/3 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_FieldTemplate_Point_Read = 0x0019bf10;
|
||||
// layout sizeof(Game::FieldTemplate::Point) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_FieldTemplate_Point = 0x00000014;
|
||||
// thiscall void (Game_LocustFleetLogistics* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009f1f00, COL offset +0x0; 4 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_LocustFleetLogistics_Write = 0x00108e50;
|
||||
// thiscall void (Game_LocustFleetLogistics* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009f1f00, COL offset +0x0; 4 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_LocustFleetLogistics_Read = 0x00108d60;
|
||||
// thiscall void (Game_Objective* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a2e0cc, COL offset +0x0; 3 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_Objective_Write = 0x004505b0;
|
||||
// thiscall void (Game_Objective* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a2e0cc, COL offset +0x0; 3 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_Objective_Read = 0x00450430;
|
||||
// layout sizeof(Game::Objective) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_Objective = 0x00000044;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_DEMANDS_SHIPS_ShipRequest* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fa614, COL offset +0x0; 1 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_DEMANDS_SHIPS_ShipRequest_Write = 0x0015d120;
|
||||
// thiscall void (Game_SVSOUpstartApes_EO_DEMANDS_SHIPS_ShipRequest* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fa614, COL offset +0x0; 1 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SVSOUpstartApes_EO_DEMANDS_SHIPS_ShipRequest_Read = 0x0015d040;
|
||||
// layout sizeof(Game::SVSOUpstartApes_EO_DEMANDS_SHIPS::ShipRequest) -- container stride [verified]
|
||||
constexpr uint32_t sizeof_Game_SVSOUpstartApes_EO_DEMANDS_SHIPS_ShipRequest = 0x00000020;
|
||||
// thiscall void (Game_ShipDesignDef_LegacyWeaponDef* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a2df7c, COL offset +0x0; 1 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ShipDesignDef_LegacyWeaponDef_Write = 0x00413950;
|
||||
// thiscall void (Game_ShipDesignDef_LegacyWeaponDef* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a2df7c, COL offset +0x0; 1 member fields; 1/1 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ShipDesignDef_LegacyWeaponDef_Read = 0x00446260;
|
||||
// thiscall void (Game_ShipWeapon* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x009fdc8c, COL offset +0x0; 3 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ShipWeapon_Write = 0x0016f450;
|
||||
// thiscall void (Game_ShipWeapon* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x009fdc8c, COL offset +0x0; 3 member fields; 2/2 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_ShipWeapon_Read = 0x00171130;
|
||||
// thiscall void (Game_SlotDef* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a2db6c, COL offset +0x0; 18 member fields; 15/15 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SlotDef_Write = 0x004276d0;
|
||||
// thiscall void (Game_SlotDef* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a2db6c, COL offset +0x0; 18 member fields; 15/15 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_SlotDef_Read = 0x00432790;
|
||||
// layout sizeof(Game::SlotDef) -- enumeration meets embedding [verified]
|
||||
constexpr uint32_t sizeof_Game_SlotDef = 0x000000bc;
|
||||
// thiscall void (Game_StarSystem_OutputRates* this, Mars::Stream* s) /* IStreamable slot 2, vftable 0x00a1f884, COL offset +0x0; 7 member fields; 7/7 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StarSystem_OutputRates_Write = 0x00345190;
|
||||
// thiscall void (Game_StarSystem_OutputRates* this, Mars::Stream* s) /* IStreamable slot 1, vftable 0x00a1f884, COL offset +0x0; 7 member fields; 7/7 field offsets agree between Read and Write */ [verified]
|
||||
constexpr uint32_t Game_StarSystem_OutputRates_Read = 0x003472a0;
|
||||
// layout sizeof(Game::StarSystem::OutputRates) -- enumeration meets embedding [verified]
|
||||
constexpr uint32_t sizeof_Game_StarSystem_OutputRates = 0x0000001c;
|
||||
|
||||
} // namespace sots::addr
|
||||
|
|
|
|||
2224
objects/layouts.h
Normal file
2224
objects/layouts.h
Normal file
File diff suppressed because it is too large
Load diff
34628
objects/layouts.json
Normal file
34628
objects/layouts.json
Normal file
File diff suppressed because it is too large
Load diff
4342
objects/layouts.md
Normal file
4342
objects/layouts.md
Normal file
File diff suppressed because it is too large
Load diff
261
tools/rtti_map.py
Normal file
261
tools/rtti_map.py
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
#!/usr/bin/env python3
|
||||
"""MSVC RTTI walker for the SOTS1 exe: type descriptors -> COLs -> vftables.
|
||||
|
||||
Why this exists
|
||||
---------------
|
||||
`findings/objects/00-inventory.md` has 1,924 type-descriptor *names* but no
|
||||
addresses, so a recovered function can be named "a serializer" but not "*whose*
|
||||
serializer". Every serializable class in this binary reaches its `Read`/`Write`
|
||||
through an `IStreamable` sub-vftable, and MSVC puts a Complete Object Locator at
|
||||
`vftable[-1]`. Walking
|
||||
|
||||
type descriptor <- COL.pTypeDescriptor <- vftable[-1]
|
||||
|
||||
gives, for every vftable in the image: the owning class name, the sub-object
|
||||
offset (`COL.offset` -- the this-adjustment that turns a decompiled offset into
|
||||
an absolute member offset), and the slot functions.
|
||||
|
||||
Structures (32-bit MSVC):
|
||||
TypeDescriptor { void* pVFTable; void* spare; char name[]; } name at +8
|
||||
COL { u32 signature; u32 offset; u32 cdOffset;
|
||||
TypeDescriptor* pTypeDescriptor;
|
||||
ClassHierarchyDescriptor* pClassDescriptor; } 20 bytes
|
||||
vftable[-1] = COL*
|
||||
|
||||
Usage:
|
||||
uv run python3 tools/rtti_map.py build # -> dumps/rtti.json
|
||||
uv run python3 tools/rtti_map.py show ObservedTech
|
||||
uv run python3 tools/rtti_map.py vftable 0x00a2439c
|
||||
uv run python3 tools/rtti_map.py stats
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
REPO = os.path.dirname(HERE)
|
||||
EXE = os.path.join(REPO, "dumps", "sots.exe")
|
||||
FUNCS = os.path.join(REPO, "dumps", "functions.json")
|
||||
RTTI = os.path.join(REPO, "dumps", "rtti.json")
|
||||
|
||||
|
||||
# ----------------------------------------------------------------- PE image
|
||||
class Image:
|
||||
"""Whole-image VA reader (all sections, not just executable ones)."""
|
||||
|
||||
def __init__(self, path=EXE):
|
||||
data = open(path, "rb").read()
|
||||
pe = struct.unpack_from("<I", data, 0x3C)[0]
|
||||
assert data[pe:pe + 4] == b"PE\0\0", "not a PE"
|
||||
nsec = struct.unpack_from("<H", data, pe + 6)[0]
|
||||
optsz = struct.unpack_from("<H", data, pe + 20)[0]
|
||||
self.base = struct.unpack_from("<I", data, pe + 24 + 28)[0]
|
||||
self.secs = [] # (va, size, bytes, name, exec)
|
||||
off = pe + 24 + optsz
|
||||
for k in range(nsec):
|
||||
s = off + k * 40
|
||||
name = data[s:s + 8].rstrip(b"\0").decode("latin1")
|
||||
vsize, vaddr, rsize, raddr = struct.unpack_from("<IIII", data, s + 8)
|
||||
chars = struct.unpack_from("<I", data, s + 36)[0]
|
||||
n = min(vsize, rsize) if vsize else rsize
|
||||
self.secs.append((self.base + vaddr, n, data[raddr:raddr + n],
|
||||
name, bool(chars & 0x20000000)))
|
||||
self.lo = min(s[0] for s in self.secs)
|
||||
self.hi = max(s[0] + s[1] for s in self.secs)
|
||||
self.text = next(s for s in self.secs if s[4])
|
||||
|
||||
def find(self, va):
|
||||
for sva, n, buf, name, ex in self.secs:
|
||||
if sva <= va < sva + n:
|
||||
return buf, va - sva, name
|
||||
return None, 0, None
|
||||
|
||||
def u32(self, va):
|
||||
buf, o, _ = self.find(va)
|
||||
if buf is None or o + 4 > len(buf):
|
||||
return None
|
||||
return struct.unpack_from("<I", buf, o)[0]
|
||||
|
||||
def i32(self, va):
|
||||
buf, o, _ = self.find(va)
|
||||
if buf is None or o + 4 > len(buf):
|
||||
return None
|
||||
return struct.unpack_from("<i", buf, o)[0]
|
||||
|
||||
def cstr(self, va, maxn=512):
|
||||
buf, o, _ = self.find(va)
|
||||
if buf is None:
|
||||
return None
|
||||
e = buf.find(b"\0", o, o + maxn)
|
||||
if e < 0:
|
||||
return None
|
||||
try:
|
||||
return buf[o:e].decode("latin1")
|
||||
except UnicodeDecodeError:
|
||||
return None
|
||||
|
||||
def in_image(self, va):
|
||||
return va is not None and self.lo <= va < self.hi
|
||||
|
||||
def in_text(self, va):
|
||||
return va is not None and self.text[0] <= va < self.text[0] + self.text[1]
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ demangle
|
||||
def demangle(mangled: str) -> str:
|
||||
"""`.?AVObservedTech@Game@@` -> `Game::ObservedTech`.
|
||||
|
||||
Templates keep their raw inner text (`?$StreamableHelper@...`) because a
|
||||
full MSVC template demangler is not needed here -- only a stable key.
|
||||
"""
|
||||
m = mangled
|
||||
for p in (".?AV", ".?AU", ".?AW", ".?AT"):
|
||||
if m.startswith(p):
|
||||
m = m[len(p):]
|
||||
break
|
||||
if m.endswith("@@"):
|
||||
m = m[:-2]
|
||||
# A template argument list can itself contain '@'; split only the trailing
|
||||
# namespace chain, which is everything after the outermost template body.
|
||||
if m.startswith("?$"):
|
||||
return m # template: keep raw
|
||||
parts = m.split("@")
|
||||
return "::".join(reversed([p for p in parts if p]))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- build
|
||||
def build():
|
||||
img = Image()
|
||||
funcs = json.load(open(FUNCS))
|
||||
fstarts = {int(k, 16) for k in funcs}
|
||||
fnames = {int(k, 16): v[0] for k, v in funcs.items()}
|
||||
|
||||
# 1. type descriptors: the mangled name lives at TD+8
|
||||
tds = {} # td_va -> mangled
|
||||
pat = re.compile(rb"\.\?A[VUWT][\x20-\x7e]{0,300}?@@\x00")
|
||||
for sva, n, buf, name, ex in img.secs:
|
||||
if ex:
|
||||
continue
|
||||
for m in pat.finditer(buf):
|
||||
nva = sva + m.start()
|
||||
td = nva - 8
|
||||
if img.in_image(td):
|
||||
tds[td] = m.group()[:-1].decode("latin1")
|
||||
|
||||
# 2. COLs: any aligned dword equal to a TD address, at COL+0xC
|
||||
cols = {} # col_va -> dict
|
||||
for sva, n, buf, name, ex in img.secs:
|
||||
if ex:
|
||||
continue
|
||||
for o in range(0, n - 3, 4):
|
||||
v = struct.unpack_from("<I", buf, o)[0]
|
||||
if v not in tds:
|
||||
continue
|
||||
col = sva + o - 0xC
|
||||
sig = img.u32(col)
|
||||
if sig not in (0, 1):
|
||||
continue
|
||||
off = img.u32(col + 4)
|
||||
cd = img.u32(col + 8)
|
||||
chd = img.u32(col + 0x10)
|
||||
if off is None or off > 0x10000 or cd is None or cd > 0x10000:
|
||||
continue
|
||||
if not img.in_image(chd):
|
||||
continue
|
||||
# Class Hierarchy Descriptor -> base class list. This is the only
|
||||
# exact test for "is this class an IStreamable?": a 3-slot vftable
|
||||
# on its own also matches TacAISquadRule_*, the row parsers and any
|
||||
# other class that happens to have three virtuals.
|
||||
bases = []
|
||||
nb = img.u32(chd + 8)
|
||||
arr = img.u32(chd + 0xC)
|
||||
if nb and nb < 64 and img.in_image(arr):
|
||||
for b in range(nb):
|
||||
bcd = img.u32(arr + 4 * b)
|
||||
if not img.in_image(bcd):
|
||||
break
|
||||
btd = img.u32(bcd)
|
||||
if btd in tds:
|
||||
bases.append(demangle(tds[btd]))
|
||||
cols[col] = {"td": v, "offset": off, "cdOffset": cd,
|
||||
"mangled": tds[v], "name": demangle(tds[v]),
|
||||
"bases": bases}
|
||||
|
||||
# 3. vftables: any aligned dword equal to a COL address; table starts at +4
|
||||
vfts = {}
|
||||
for sva, n, buf, name, ex in img.secs:
|
||||
if ex:
|
||||
continue
|
||||
for o in range(0, n - 3, 4):
|
||||
v = struct.unpack_from("<I", buf, o)[0]
|
||||
if v not in cols:
|
||||
continue
|
||||
vf = sva + o + 4
|
||||
slots = []
|
||||
a = vf
|
||||
while True:
|
||||
p = img.u32(a)
|
||||
if p is None or p not in fstarts:
|
||||
break
|
||||
slots.append(p)
|
||||
a += 4
|
||||
if len(slots) > 400:
|
||||
break
|
||||
if not slots:
|
||||
continue
|
||||
c = cols[v]
|
||||
vfts[vf] = {"col": v, "class": c["name"], "mangled": c["mangled"],
|
||||
"offset": c["offset"], "slots": slots,
|
||||
"bases": c["bases"],
|
||||
"slotNames": [fnames.get(s, "") for s in slots]}
|
||||
|
||||
out = {"typeDescriptors": {hex(k): v for k, v in tds.items()},
|
||||
"cols": {hex(k): v for k, v in cols.items()},
|
||||
"vftables": {hex(k): v for k, v in vfts.items()}}
|
||||
with open(RTTI, "w") as fh:
|
||||
json.dump(out, fh)
|
||||
print(f"type descriptors : {len(tds)}")
|
||||
print(f"COLs : {len(cols)}")
|
||||
print(f"vftables : {len(vfts)}")
|
||||
ns = {}
|
||||
for v in vfts.values():
|
||||
ns[v["class"].split("::")[0]] = ns.get(v["class"].split("::")[0], 0) + 1
|
||||
print("vftables by top-level namespace:",
|
||||
", ".join(f"{k}={v}" for k, v in
|
||||
sorted(ns.items(), key=lambda x: -x[1])[:6]))
|
||||
|
||||
|
||||
def load():
|
||||
with open(RTTI) as fh:
|
||||
r = json.load(fh)
|
||||
return ({int(k, 16): v for k, v in r["typeDescriptors"].items()},
|
||||
{int(k, 16): v for k, v in r["cols"].items()},
|
||||
{int(k, 16): v for k, v in r["vftables"].items()})
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2 or sys.argv[1] == "build":
|
||||
return build()
|
||||
cmd = sys.argv[1]
|
||||
tds, cols, vfts = load()
|
||||
if cmd == "show":
|
||||
pat = sys.argv[2]
|
||||
for va, v in sorted(vfts.items()):
|
||||
if pat.lower() in v["class"].lower():
|
||||
print(f"vftable 0x{va:08x} COL 0x{v['col']:08x} "
|
||||
f"offset +0x{v['offset']:x} {v['class']}")
|
||||
for i, (s, nm) in enumerate(zip(v["slots"], v["slotNames"])):
|
||||
print(f" [{i}] 0x{s:08x} {nm}")
|
||||
elif cmd == "vftable":
|
||||
va = int(sys.argv[2], 0)
|
||||
v = vfts.get(va)
|
||||
print(json.dumps(v, indent=2) if v else "not a vftable")
|
||||
elif cmd == "stats":
|
||||
print(f"tds={len(tds)} cols={len(cols)} vftables={len(vfts)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main() or 0)
|
||||
1425
tools/serializers.py
Normal file
1425
tools/serializers.py
Normal file
File diff suppressed because it is too large
Load diff
152
tools/serializers_ghidra.py
Normal file
152
tools/serializers_ghidra.py
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Push the recovered layouts into Ghidra so later lanes inherit them.
|
||||
|
||||
Reads `objects/layouts.json` + `objects/layouts.h` (produced by
|
||||
`tools/serializers.py all`) and, through `tools/reva_call.py`:
|
||||
|
||||
* `parse-c-structure` for every emittable struct, dependencies first
|
||||
* `create-label` for every serializer `Write` / `Read` entry point
|
||||
|
||||
Idempotent: re-running replaces the structures and re-applies the labels.
|
||||
Progress is written to `objects/.ghidra_pushed` so an interrupted run resumes.
|
||||
|
||||
uv run python3 tools/serializers_ghidra.py structs
|
||||
uv run python3 tools/serializers_ghidra.py labels
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
REPO = os.path.dirname(HERE)
|
||||
OUT = os.path.join(REPO, "objects")
|
||||
PROG = "/Sword of the Stars.exe"
|
||||
STATE = os.path.join(OUT, ".ghidra_pushed")
|
||||
|
||||
|
||||
def reva(tool, payload):
|
||||
r = subprocess.run(
|
||||
["uv", "run", "python3", os.path.join(HERE, "reva_call.py"), tool,
|
||||
json.dumps(payload)],
|
||||
cwd=REPO, capture_output=True, text=True, timeout=180)
|
||||
return r.returncode, (r.stdout or r.stderr).strip()
|
||||
|
||||
|
||||
def done_set():
|
||||
if not os.path.exists(STATE):
|
||||
return set()
|
||||
return set(open(STATE).read().split("\n"))
|
||||
|
||||
|
||||
def mark(key):
|
||||
with open(STATE, "a") as fh:
|
||||
fh.write(key + "\n")
|
||||
|
||||
|
||||
def structs():
|
||||
text = open(os.path.join(OUT, "layouts.h")).read()
|
||||
defs = re.findall(r"struct \w+ \{.*?\};", text, re.S)
|
||||
have = done_set()
|
||||
ok = fail = skip = 0
|
||||
for d in defs:
|
||||
name = d.split()[1]
|
||||
key = "struct:" + name
|
||||
if key in have:
|
||||
skip += 1
|
||||
continue
|
||||
rc, out = reva("parse-c-structure",
|
||||
{"programPath": PROG, "cDefinition": d})
|
||||
if rc == 0 and '"message"' in out:
|
||||
ok += 1
|
||||
mark(key)
|
||||
else:
|
||||
fail += 1
|
||||
print(f" FAIL {name}: {out[:160]}")
|
||||
if (ok + fail) % 25 == 0:
|
||||
print(f" {ok} ok, {fail} failed, {skip} already there")
|
||||
print(f"structures: {ok} pushed, {fail} failed, {skip} skipped")
|
||||
|
||||
|
||||
def labels():
|
||||
lay = json.load(open(os.path.join(OUT, "layouts.json")))
|
||||
have = done_set()
|
||||
ok = fail = skip = 0
|
||||
for c, L in sorted(lay.items()):
|
||||
if L["grade"] not in ("verified", "clean"):
|
||||
continue
|
||||
nm = c.replace("::", "_")
|
||||
for kind in ("write", "read"):
|
||||
va = L.get(kind)
|
||||
if not va:
|
||||
continue
|
||||
label = f"{nm}_{kind.capitalize()}"
|
||||
key = f"label:{label}:{va:x}"
|
||||
if key in have:
|
||||
skip += 1
|
||||
continue
|
||||
rc, out = reva("create-label",
|
||||
{"programPath": PROG, "labelName": label,
|
||||
"address": f"0x{va:08x}"})
|
||||
if rc == 0:
|
||||
ok += 1
|
||||
mark(key)
|
||||
else:
|
||||
fail += 1
|
||||
print(f" FAIL {label}: {out[:120]}")
|
||||
print(f"labels: {ok} created, {fail} failed, {skip} skipped")
|
||||
|
||||
|
||||
def addresses():
|
||||
"""Merge the verified-tier serializers and corroborated sizeofs into
|
||||
ghidra/addresses.json (then run tools/gen_addresses.py -- never hand-edit
|
||||
the header)."""
|
||||
path = os.path.join(REPO, "ghidra", "addresses.json")
|
||||
j = json.load(open(path))
|
||||
base = int(j["image_base"], 16)
|
||||
have = {e["name"] for e in j["entries"]}
|
||||
lay = json.load(open(os.path.join(OUT, "layouts.json")))
|
||||
added = 0
|
||||
for c, L in sorted(lay.items()):
|
||||
if L["grade"] != "verified":
|
||||
continue
|
||||
nm = c.replace("::", "_")
|
||||
cr = f"{L['read_agree']}/{L['read_comparable']} field offsets agree " \
|
||||
f"between Read and Write"
|
||||
for kind in ("write", "read"):
|
||||
va = L.get(kind)
|
||||
name = f"{nm}_{kind.capitalize()}"
|
||||
if not va or name in have or not (base <= va < base + 0x1000000):
|
||||
continue
|
||||
j["entries"].append({
|
||||
"name": name, "addr": f"0x{va:08x}", "convention": "thiscall",
|
||||
"prototype": f"void ({nm}* this, Mars::Stream* s) "
|
||||
f"/* IStreamable slot {2 if kind == 'write' else 1}"
|
||||
f", vftable 0x{L['vftable']:08x}, COL offset "
|
||||
f"+0x{L['col_offset']:x}; {len(L['fields'])} "
|
||||
f"member fields; {cr} */",
|
||||
"status": "verified",
|
||||
"source": "findings/objects/serializer-struct-recovery.md"})
|
||||
have.add(name)
|
||||
added += 1
|
||||
if L["sizeof"]:
|
||||
name = f"sizeof_{nm}"
|
||||
if name not in have:
|
||||
j["entries"].append({
|
||||
"name": name, "offset": f"0x{L['sizeof']:x}",
|
||||
"convention": "layout",
|
||||
"prototype": f"sizeof({c}) -- {L['sizeof_by']}",
|
||||
"status": "verified",
|
||||
"source": "findings/objects/serializer-struct-recovery.md"})
|
||||
have.add(name)
|
||||
added += 1
|
||||
with open(path, "w") as fh:
|
||||
json.dump(j, fh, indent=1, ensure_ascii=False)
|
||||
fh.write("\n")
|
||||
print(f"addresses.json: +{added} entries, {len(j['entries'])} total")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cmd = sys.argv[1] if len(sys.argv) > 1 else "structs"
|
||||
{"structs": structs, "labels": labels, "addresses": addresses}[cmd]()
|
||||
275
tools/serializers_golden.py
Normal file
275
tools/serializers_golden.py
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Golden layouts for `tools/serializers.py --validate`.
|
||||
|
||||
Transcribed by hand from the layouts this campaign had already recovered and
|
||||
cross-checked against real saves *before* this tool existed:
|
||||
|
||||
* `findings/objects/struct-recovery.md` sections 1-4 (member tables read out
|
||||
of the decompiled `Write`/`Read` pairs, COL offsets from RTTI)
|
||||
* `findings/subsystems/observedtech-append.md` section 4 + 9 (`ObservedTech`,
|
||||
`sizeof` pinned three independent ways)
|
||||
* `verify/save-reader/SAVE_FORMAT.md` / `save_reader.py` (on-disk tag order,
|
||||
`--strict` clean against three real saves, 36 tests)
|
||||
|
||||
Offsets are **absolute** (object base), i.e. the decompiled `this`-relative
|
||||
offset plus the IStreamable COL offset. Kinds use the tool's vocabulary.
|
||||
Nothing here is derived from the tool: it is the independent answer the tool
|
||||
has to reproduce.
|
||||
"""
|
||||
|
||||
# tag, absolute offset, kind
|
||||
GOLDEN = {
|
||||
"Game::ObservedTech": {
|
||||
"write": 0x00817CF0, "sizeof": 0x2C,
|
||||
"fields": [("otnF", 0x04, "int16"), ("otnL", 0x06, "int16"),
|
||||
("odet", 0x08, "bool"), ("otch", 0x0C, "string"),
|
||||
("owith", 0x28, "int")],
|
||||
},
|
||||
"Game::ObservedWeapon": {
|
||||
"write": 0x00817BC0, "sizeof": 0x2C,
|
||||
"fields": [("otnF", 0x04, "int16"), ("otnL", 0x06, "int16"),
|
||||
("odet", 0x08, "bool"), ("owep", 0x0C, "string"),
|
||||
("owith", 0x28, "int")],
|
||||
},
|
||||
"Game::StarSystem::OutputRates": {
|
||||
"write": 0x00745190, "sizeof": 0x1C,
|
||||
"fields": [("SRt", 0x00, "float"), ("SRsc", 0x04, "float"),
|
||||
("SRtf", 0x08, "float"), ("SRi", 0x0C, "float"),
|
||||
("SRoh", 0x10, "float"), ("SRs", 0x14, "float"),
|
||||
("SRnr", 0x18, "int")],
|
||||
},
|
||||
"Game::ServerSystem": {
|
||||
"write": 0x00749630, "sizeof": None,
|
||||
"fields": [
|
||||
("Pos", 0x18, "object"),
|
||||
("R", 0x4C, "float"), ("G", 0x50, "float"), ("B", 0x54, "float"),
|
||||
("A", 0x58, "float"), ("Idx", 0x5C, "int"), ("Size", 0x60, "int"),
|
||||
("Suit", 0x64, "float"), ("Res", 0x68, "int"),
|
||||
("ARes2", 0x6C, "int"), ("MRes", 0x70, "int"),
|
||||
("TRes", 0x74, "int"), ("haltv", 0x78, "bool"),
|
||||
("OutMod", 0x7C, "float"), ("TAcq", 0x80, "int"),
|
||||
("TFAcq", 0x84, "int"), ("Rts", 0x88, "object"),
|
||||
("BQ", 0xA4, "object"), ("Name", 0xA8, "string"),
|
||||
("Abdn", 0xC4, "bool"), ("Dstyd", 0xC5, "bool"),
|
||||
("vnh", 0xC6, "bool"), ("vnd", 0xC7, "bool"),
|
||||
("vnex3", 0xC8, "bool"), ("vnpex3", 0xC9, "bool"),
|
||||
("VFlags", 0xCC, "int"), ("EFlags", 0xD0, "int"),
|
||||
("AFlags", 0xD4, "int"), ("FFlags", 0xD8, "int"),
|
||||
("GFlags", 0xDC, "int"), ("MnRFlags", 0xE0, "int"),
|
||||
("RfRFlags", 0xE4, "int"), ("ClkFlags", 0xE8, "int"),
|
||||
("Bats2", 0xF0, "int64"), ("rcex", 0xF8, "int64"),
|
||||
("PID", 0x100, "handle"), ("dcs", 0x104, "object"),
|
||||
("dsu", 0x118, "float"), ("cm", 0x11C, "object"),
|
||||
("cme2", 0x13C, "vector"), ("PvCM", 0x14C, "object"),
|
||||
("NumFlts", 0x16C, "vector"), ("RepCur", 0x17C, "float"),
|
||||
("RepMax", 0x180, "float"), ("EggScio", 0x184, "int"),
|
||||
("NoRebAI", 0x188, "bool"), ("PvNoRebAI", 0x189, "bool"),
|
||||
("Pop", 0x18C, "int"), ("Infra", 0x190, "float"),
|
||||
("pbon", 0x194, "int"), ("ibon", 0x198, "float"),
|
||||
("TerrFl", 0x19C, "int"), ("Pop2", 0x1A0, "object"),
|
||||
("pbon2", 0x1B4, "object"), ("indi", 0x1C8, "object"),
|
||||
("spies2", 0x1CC, "vector"), ("rbfl", 0x1DC, "int"),
|
||||
("hsrg", 0x1E0, "bool"), ("adt", 0x1E4, "int"),
|
||||
("PvPop", 0x200, "int"), ("PvInfra", 0x204, "float"),
|
||||
("PvSuit", 0x208, "float"), ("PvRes", 0x20C, "int"),
|
||||
("PvARes2", 0x210, "int"), ("PvMRes", 0x214, "int"),
|
||||
("PvPop2", 0x218, "object"), ("DefF", 0x238, "handle"),
|
||||
("DefSF", 0x23C, "handle"), ("NumGFs", 0x240, "vector"),
|
||||
("NumSnF", 0x250, "vector"), ("NumMnF", 0x260, "vector"),
|
||||
("NumPlgs2", 0x2A8, "vector"), ("TnsOH", 0x2B8, "int"),
|
||||
("TDst", 0x2BC, "int"), ("ntdev", 0x2C4, "int"),
|
||||
("ltis", 0x2C8, "int"), ("rbtn", 0x2CC, "int"),
|
||||
("rbfr", 0x2D0, "int"), ("rbwn", 0x2D4, "int"),
|
||||
],
|
||||
},
|
||||
"Game::StarSystem::PlayerView": {
|
||||
"write": 0x007492D0, "sizeof": None,
|
||||
"fields": [("VTrn", 0x08, "int"), ("Pop", 0x0C, "int"),
|
||||
("Pop2", 0x10, "object"), ("Infra", 0x24, "float"),
|
||||
("Suit", 0x28, "float"), ("Res", 0x2C, "int"),
|
||||
("ARes2", 0x30, "int"), ("MRes", 0x34, "int"),
|
||||
("NoRebAI", 0x38, "bool"), ("pbon", 0x3C, "int"),
|
||||
("pbon2", 0x40, "object"), ("ibon", 0x54, "float"),
|
||||
("TerrFl", 0x58, "int")],
|
||||
},
|
||||
"Game::PopulationGroup": {
|
||||
"write": 0x00536AF0, "sizeof": 0x18,
|
||||
"fields": [("PopT", 0x04, "int"), ("PopS", 0x08, "int"),
|
||||
("PopC", 0x10, "int64")],
|
||||
},
|
||||
"Game::IndependenceInfo": {
|
||||
"write": 0x00748EE0, "sizeof": 0x70,
|
||||
"fields": [("indsp", 0x04, "int"), ("indcl", 0x08, "object"),
|
||||
("indnm", 0x1C, "string"), ("indav", 0x38, "string"),
|
||||
("indba", 0x54, "string")],
|
||||
},
|
||||
"Game::MoraleEvent": {
|
||||
"write": 0x007491B0, "sizeof": 0x50,
|
||||
"fields": [("mid", 0x04, "int"), ("mtr", 0x08, "int"),
|
||||
("mn", 0x0C, "int"), ("mtp", 0x10, "int"),
|
||||
("mfx", 0x14, "object"), ("mdsc", 0x34, "string")],
|
||||
},
|
||||
"Game::ShipBuildOrder": {
|
||||
"write": 0x00813800, "sizeof": None,
|
||||
"fields": [("desID", 0x04, "int"), ("con", 0x08, "int"),
|
||||
("sav", 0x0C, "int"), ("conleft", 0x10, "int"),
|
||||
("ordID", 0x14, "int")],
|
||||
},
|
||||
"Game::DiplomacyStats": {
|
||||
"write": 0x00818CB0, "sizeof": 0x24,
|
||||
"fields": [("other", 0x04, "int"),
|
||||
("lastnap", 0x08, "int16"), ("lastnapbty", 0x0A, "int16"),
|
||||
("bknnap", 0x0C, "int16"), ("btynap", 0x0E, "int16"),
|
||||
("lastally", 0x10, "int16"), ("lastallybty", 0x12, "int16"),
|
||||
("bknally", 0x14, "int16"), ("btyally", 0x16, "int16"),
|
||||
("lastcf", 0x18, "int16"), ("lastcfbty", 0x1A, "int16"),
|
||||
("bkncf", 0x1C, "int16"), ("btycf", 0x1E, "int16"),
|
||||
("deadhome", 0x20, "int16")],
|
||||
},
|
||||
"Game::PlayerReport": {
|
||||
"write": 0x00817480, "sizeof": 0x30,
|
||||
"fields": [("oid", 0x04, "int"), ("pid", 0x08, "int"),
|
||||
("flds", 0x0C, "int"), ("sav", 0x10, "int"),
|
||||
("home", 0x14, "int"), ("ncol", 0x18, "int"),
|
||||
("mpwr", 0x1C, "int"), ("mcls", 0x20, "int"),
|
||||
("mmsl", 0x24, "int"), ("nshp", 0x28, "int"),
|
||||
("nsat", 0x2C, "int")],
|
||||
},
|
||||
"Game::FlightPlan": {
|
||||
"write": 0x00700F60, "sizeof": 0x38,
|
||||
"fields": [("wpts", 0x04, "vector"), ("FPsp2", 0x14, "float"),
|
||||
("FPeta2", 0x18, "int"), ("FPogn2", 0x1C, "object"),
|
||||
("FPdpos", 0x28, "object"), ("pnd", 0x34, "int")],
|
||||
},
|
||||
"Game::FlightPlan::Waypoint": {
|
||||
"write": 0x00700ED0, "sizeof": None,
|
||||
"fields": [("Wpt", 0x04, "int"), ("Tp", 0x08, "int"),
|
||||
("nrt", 0x0C, "object")],
|
||||
},
|
||||
"Game::NodeRoute": {
|
||||
"write": 0x006E22E0, "sizeof": None,
|
||||
"fields": [("nrp", 0x04, "int"), ("nrf", 0x08, "int"),
|
||||
("nrt", 0x0C, "int")],
|
||||
},
|
||||
"Game::StarFleet": {
|
||||
"write": 0x00701070, "sizeof": None,
|
||||
"fields": [("Pos", 0x18, "object"), ("PrvPos", 0x4C, "object"),
|
||||
("PID", 0x58, "handle"), ("FtName", 0x5C, "string"),
|
||||
("Perm", 0x78, "bool"), ("Lay", 0x7C, "object"),
|
||||
("LocID", 0xA0, "handle"), ("NShips", 0xA4, "vector"),
|
||||
("FPlan", 0xC4, "object"), ("FtTrans", 0xFC, "int"),
|
||||
("FtOrig", 0x100, "object"), ("FtFlg", 0x10C, "int"),
|
||||
("Ftae", 0x110, "int"), ("Ftpae", 0x114, "int"),
|
||||
("FtEnc", 0x118, "int"), ("FtMS", 0x11C, "int")],
|
||||
},
|
||||
"Game::StarShip": {
|
||||
"write": 0x008291F0, "sizeof": None,
|
||||
"fields": [("PlrID", 0x10, "handle"), ("DesID", 0x14, "int"),
|
||||
("Range", 0x20, "float"), ("Health", 0x24, "object"),
|
||||
("MineCap", 0x34, "int"), ("NTH", 0x38, "vector"),
|
||||
("Plg", 0x48, "int"), ("Act", 0x4C, "int"),
|
||||
("Dep", 0x50, "bool"), ("Atq", 0x51, "bool"),
|
||||
("LCT", 0x5C, "int"), ("tsd", 0x60, "int"),
|
||||
("FltID", 0x64, "handle"), ("ConCap", 0x68, "int"),
|
||||
("RefCap", 0x6C, "float"), ("RepCap", 0x70, "float"),
|
||||
("EncID", 0x7C, "int"), ("PrisH", 0x80, "object"),
|
||||
("BQ2", 0x98, "object"), ("pop", 0x9C, "object"),
|
||||
("ppop", 0xA0, "object"), ("atsp", 0xA8, "int"),
|
||||
("tblt", 0xAC, "int")],
|
||||
},
|
||||
"Game::ServerPlayer": {
|
||||
"write": 0x008563E0, "sizeof": None,
|
||||
"fields": [
|
||||
("PlyrIdx", 0x28, "int"), ("HomeSys", 0x2C, "handle"),
|
||||
("NumOwn", 0x30, "vector"), ("PlryName", 0x40, "string"),
|
||||
("Species", 0x5C, "int"), ("ClrID", 0x60, "object"),
|
||||
("Bdg", 0x74, "string"), ("Avt", 0x90, "string"),
|
||||
("Team", 0xAC, "int"), ("IdealSuit", 0xB0, "float"),
|
||||
("SuitTol", 0xB4, "float"), ("MaxOH", 0xB8, "float"),
|
||||
("ResRate", 0xBC, "float"), ("ResMod", 0xC0, "float"),
|
||||
("ResScl", 0xC4, "float"), ("TRM", 0xD0, "float"),
|
||||
("TRA", 0xD4, "int"), ("TRP", 0xD8, "int"),
|
||||
("NumDes", 0xE4, "vector"), ("TechTree", 0xF4, "object"),
|
||||
("Elim", 0xF8, "bool"), ("NPC", 0xFB, "bool"),
|
||||
("RebAI", 0xFC, "bool"), ("ReqCL", 0xFD, "bool"),
|
||||
("AIBn", 0xFE, "bool"), ("CnTrd", 0xFF, "bool"),
|
||||
("CnRad", 0x100, "bool"), ("CnVItl", 0x101, "bool"),
|
||||
("hgs", 0x102, "bool"), ("hadvs", 0x103, "bool"),
|
||||
("harcc", 0x104, "bool"), ("pddm", 0x108, "float"),
|
||||
("OutMod", 0x124, "float"), ("RebOutMod", 0x128, "float"),
|
||||
("ScOutMod", 0x12C, "float"), ("PopMod", 0x130, "float"),
|
||||
("TerraMod", 0x134, "float"), ("AMine", 0x138, "bool"),
|
||||
("MinPure", 0x13C, "float"), ("MinRate", 0x140, "float"),
|
||||
("NGts", 0x144, "int"), ("PrGtTrf", 0x148, "int"),
|
||||
("GTraf", 0x14C, "int"), ("CstR", 0x150, "float"),
|
||||
("CstE", 0x154, "float"), ("CstT", 0x158, "float"),
|
||||
("Maint", 0x15C, "int"), ("shrm", 0x160, "float"),
|
||||
("Status", 0x164, "int"), ("Team", 0x168, "object"),
|
||||
("NumLeg", 0x178, "vector"), ("PvSav", 0x188, "int"),
|
||||
("PvMA", 0x18C, "bool"), ("HasDisc", 0x19C, "int"),
|
||||
("HasDiscSp", 0x1A0, "int"), ("HasDiscCl", 0x1A4, "int"),
|
||||
("HasEnc", 0x1A8, "int"), ("HasEng", 0x1AC, "int"),
|
||||
("ShipRecs", 0x1B0, "object"), ("Ojvs", 0x1F4, "vector"),
|
||||
("Nexp", 0x204, "vector"), ("NWeapXcl", 0x214, "vector"),
|
||||
("dipstats", 0x230, "vector"), ("comms", 0x240, "object"),
|
||||
("preps", 0x244, "vector"), ("odes", 0x254, "vector"),
|
||||
("owep", 0x264, "vector"), ("otch", 0x274, "vector"),
|
||||
("Sav", 0x284, "int"), ("HasImm", 0x288, "int"),
|
||||
("HasVac", 0x28C, "int"), ("NPTrk", 0x290, "int"),
|
||||
("ResTNm", 0x294, "object"), ("FNG", 0x298, "object"),
|
||||
("Events", 0x29C, "object"), ("BnkWrn", 0x2C4, "int"),
|
||||
("BnkTrn", 0x2C8, "int"), ("BnkEl", 0x2CC, "int"),
|
||||
("BnkPr", 0x2D0, "int"), ("plcy", 0x2D8, "int"),
|
||||
("pswd", 0x2DC, "string"), ("Srn", 0x2F8, "bool"),
|
||||
("SrnTo", 0x2FC, "handle"), ("lboid", 0x300, "int"),
|
||||
("lcid2", 0x304, "int"), ("IncMod", 0x30C, "float"),
|
||||
("aid", 0x310, "vector"), ("ndeflay", 0x320, "vector"),
|
||||
("cdp", 0x330, "bool"), ("spy2", 0x334, "object"),
|
||||
("rdtc", 0x338, "vector"), ("aidf", 0x368, "int"),
|
||||
("civr", 0x370, "object"), ("tnc", 0x39C, "int"),
|
||||
("NumPR", 0x3A4, "vector"), ("ResErrRoll", 0x3B4, "bool"),
|
||||
("cta", 0x3B5, "bool"), ("AIR", 0x3B8, "object"),
|
||||
("AIEnf", 0x3BC, "object"), ("NSprj", 0x3C0, "vector"),
|
||||
("NextPrjID", 0x3D0, "int"), ("lret", 0x3D4, "int"),
|
||||
("nmeid", 0x3DC, "int"),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# sizeof values the campaign pinned independently of any serializer walk
|
||||
GOLDEN_SIZEOF = {
|
||||
"Game::ObservedTech": 0x2C, # magic divide + imul + search stride
|
||||
"Game::ObservedWeapon": 0x2C,
|
||||
"Game::PopulationGroup": 0x18, # Population's own vector stride
|
||||
"Game::MoraleEvent": 0x50,
|
||||
"Game::PlayerReport": 0x30,
|
||||
"Game::DiplomacyStats": 0x24,
|
||||
"Game::StarSystem::OutputRates": 0x1C,
|
||||
}
|
||||
|
||||
# on-disk tag order, from save_reader.py shapes -> the serializer that writes it
|
||||
DISK_ORDER = {
|
||||
0x00829960: "Summary", # StrategyGameInfo::Write
|
||||
0x008276D0: "Slot", # SlotDef::Write
|
||||
0x0082AE40: "CreateParams", # StrategyGameCreateParams::Write
|
||||
0x0082AD40: "Scrp", # StrategyScriptParams::Write
|
||||
0x0056EC00: "PrisonerHold",
|
||||
0x008189A0: "SystemEvent",
|
||||
0x0082C290: "PlayerTurnStats",
|
||||
0x0082C4A0: "PlayerTurnHistory",
|
||||
0x0082C5A0: "TurnStats",
|
||||
0x0079FA70: "Sim", # StrategyServer::Write
|
||||
0x00749630: "Sys", # ServerSystem::Write
|
||||
0x008563E0: "Player", # ServerPlayer::Write
|
||||
0x00701070: "Flt", # StarFleet::Write
|
||||
0x008291F0: "Ship", # StarShip::Write
|
||||
0x00700F60: "FlightPlan",
|
||||
0x00817CF0: "Otch",
|
||||
0x00817BC0: "Owep",
|
||||
0x00745190: "Rts",
|
||||
0x00748EE0: "IndependenceInfo",
|
||||
0x00813800: "BuildOrder",
|
||||
0x00817480: "Prep",
|
||||
0x00818CB0: "DipStat",
|
||||
0x007492D0: "PlayerView",
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue