1511 lines
193 KiB
C++
1511 lines
193 KiB
C++
// GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1).
|
|
// Source: sots-re ghidra/addresses.json @ 612bfe6, 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>
|
|
|
|
namespace sots::addr {
|
|
constexpr uint32_t IMAGE_BASE = 0x00400000;
|
|
|
|
// thiscall bool (Application* this, AppStartup* startup) /* ONE stack arg, RET 4, returns AL; EDX unused. startup = {HINSTANCE hInst; std::vector<std::string> args; ...; int nShow; const char* wndClass; int iconId} built in WinMain */ [verified]
|
|
constexpr uint32_t Mars_Application_Initialize = 0x004a0e50;
|
|
// thiscall void (Application* this) /* no stack args, plain RET, EDX not an input, EAX = last PumpMessages result (ignore) */ [verified]
|
|
constexpr uint32_t Mars_Application_Run = 0x0049f5b0;
|
|
// cdecl void (int coreIndex) [unverified]
|
|
constexpr uint32_t Process_PinAffinity = 0x0049ee70;
|
|
// thiscall GlobalConst* (GlobalConst* this, void* storage, const char* key, ParseFn parser, const char* file) // RET 0x10 [verified]
|
|
constexpr uint32_t CfgVar_RegisterKey = 0x004b76a0;
|
|
// thiscall bool (DemoApp* this) /* no stack args; returns 0 when quitting (this+0x1b2), else 1 */ [verified]
|
|
constexpr uint32_t DemoApp_OnTick = 0x0049a640;
|
|
// thiscall void (StrategyClient* this) [unverified]
|
|
constexpr uint32_t StrategyClient_EndTurn = 0x00383be0;
|
|
// thiscall void (StrategyServer* this) [unverified]
|
|
constexpr uint32_t StrategyServer_BeginProcessTurn = 0x003d98e0;
|
|
// thiscall void (StrategyServer* this) [unverified]
|
|
constexpr uint32_t StrategyServer_ProcessTurn = 0x003dc6c0;
|
|
// thiscall void (ServerPlayer* this, Budget* out, bool projected) /* Budget = { int slot[22]; std::vector<{Tech* node, int points}> researchAlloc (3 words @+0x58); int overBudget @+0x64 }. The array is 22 ints, not 25: the three words after slot 21 are the allocation vector and grow by one 8-byte element exactly when the player has a research target. Slots: 0 Sav, 1 systemIncome+, 2 trade, 3 shipCarriedPop, 4 secondaryManager, 5 savingsInterest, 6 bonusIncome, 7 systemIncome-, 8 maintenance, 9 researchMoneyKept (written only when ResT is set), 10 debtInterest, 11 construction, 12 expenses, 13 researchMoneyGiven, 14 savingsGiven, 15 available, 16 researchMoney, 17 researchPoints, 18 TRA, 19 researchPointsGiven, 20 TRP, 21 totalResearchPoints */ [verified-by-trace]
|
|
constexpr uint32_t ServerPlayer_ComputeBudget = 0x00463030;
|
|
// thiscall void (TechTree* this, Mars::RNG* rng, std::vector<{TechDef* target, int points}>* alloc, int* overbudget) /* args confirmed at the single call site: rng = StrategyServer+0x16c object, passed as the RNG *object* and re-based to &mt with lea ecx,[rng+4] before each NextFloat. Loop: node = this->nodes[*(int*)entry.target]; lo/hi from Cost x 50/150 /100 (32-bit imul, signed /100); spend = min(points, hi-progress) with NO clamp at 0; *overbudget += points-spend; odds and roll are both narrowed to float32 before the compare; decay loop hits every state-2 node with progress != 0; the trailing unlock-event loop makes no RNG draw and writes no node */ [verified]
|
|
constexpr uint32_t TechTree_ProcessResearch = 0x001876c0;
|
|
// thiscall void (ServerSystem* this /*+8 IStreamable*/, Stream* s) [verified-by-save]
|
|
constexpr uint32_t ServerSystem_Read = 0x0035d4b0;
|
|
// thiscall void (ServerSystem* this /*+8*/, Stream* s) [verified-by-save]
|
|
constexpr uint32_t ServerSystem_Write = 0x00349630;
|
|
// thiscall void (ServerPlayer* this /*+0x3a0*/, Stream* s) [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_Read = 0x004804d0;
|
|
// thiscall void (ServerPlayer* this /*+0x3a0*/, Stream* s) [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_Write = 0x004563e0;
|
|
// thiscall void (StarFleet* this /*+8*/, Stream* s) [verified-by-save]
|
|
constexpr uint32_t StarFleet_Read = 0x00302470;
|
|
// thiscall void (StarFleet* this /*+8*/, Stream* s) [verified-by-save]
|
|
constexpr uint32_t StarFleet_Write = 0x00301070;
|
|
// thiscall void (StarShip* this /*+8*/, Stream* s) [verified-by-save]
|
|
constexpr uint32_t StarShip_Read = 0x00453fa0;
|
|
// thiscall void (StarShip* this /*+8*/, Stream* s) [verified-by-save]
|
|
constexpr uint32_t StarShip_Write = 0x004291f0;
|
|
// thiscall void (StrategyServer* this, Stream* s) [verified-by-save]
|
|
constexpr uint32_t StrategyServer_Read = 0x003d27a0;
|
|
// thiscall void (StrategyServer* this, Stream* s) [verified-by-save]
|
|
constexpr uint32_t StrategyServer_Write = 0x0039fa70;
|
|
// thiscall void (Stream* this, const char* tag, NetworkObject* obj) [unverified]
|
|
constexpr uint32_t Stream_WriteNetworkObjectId = 0x00416490;
|
|
// thiscall void (uint32_t* mtBlock) // ECX = &mt = RNG object + 4 (NOT the object). N=624 M=397 MATRIX_A 0x9908b0df, 227/396 split; ends with left(&mt+0x9c4) = 624 and next(&mt+0x9c0) = &mt[0]. RET 0 [verified]
|
|
constexpr uint32_t RNG_Twist = 0x00026e00;
|
|
// thiscall RNG* (RNG* this, uint32_t seed) // RET 4; writes vftable 0x009e9aec, seeds from &mt = this+4: mt[0]=seed, mt[i]=0x6c078965*(mt[i-1]^(mt[i-1]>>30))+i for i=1..623, then Twist(&mt). OBJECT layout: {vftable @+0; uint32 mt[624] @+4 .. +0x9c3; uint32* next @+0x9c4; int left @+0x9c8} = 0x9cc bytes. Beware: NextFloat/NextInt/Twist take &mt, so *their* this+0x9c0/+0x9c4 are next/left [verified]
|
|
constexpr uint32_t RNG_Seed = 0x0009fdf0;
|
|
// thiscall float (uint32_t* mtBlock) // ECX = &mt = RNG object + 4. Lazy twist (if left==0 Twist()), y = *next++, left--, standard MT tempering, then value = (double)(uint32)y * 1/(2^32-1) -- the multiplier at 0x009e61b0 is 0x3df0000000001000 = 1/4294967295.0, NOT 2^-32; the unsigned fix-up at 0x009e61b8 is +2^32 after a signed fild. Left in st(0) at the current x87 precision and narrowed by the caller; range [0,1] INCLUSIVE [verified]
|
|
constexpr uint32_t RNG_NextFloat = 0x0007d830;
|
|
// thiscall uint32_t (uint32_t* mtBlock, const uint32_t* n) // ECX = &mt; the bound is passed BY POINTER, RET 4. mask = smallest 2^k-1 >= *n via the or/shift cascade; draws (lazy twist + tempering) until (y & mask) <= *n -- note the loop condition is `ja`, so the result is uniform on [0, *n] INCLUSIVE, not [0, n) [verified]
|
|
constexpr uint32_t RNG_NextInt = 0x000271c0;
|
|
// offset sizeof(Mars::RNG) -- {vftable @+0; uint32 mt[624] @+4; uint32* next @+0x9c4; int left @+0x9c8} [verified]
|
|
constexpr uint32_t RNG_size = 0x000009cc;
|
|
// offset uint32 mt[624]; also the pointer Twist/NextFloat/NextInt receive in ECX [verified]
|
|
constexpr uint32_t RNG_off_State = 0x00000004;
|
|
// offset uint32* next; always &mt[624 - left], and recomputed from left on load, so `left` alone pins the stream position [verified]
|
|
constexpr uint32_t RNG_off_Next = 0x000009c4;
|
|
// offset int left; words still unread in the current block [verified]
|
|
constexpr uint32_t RNG_off_Left = 0x000009c8;
|
|
// offset int tech id -- the index into TechTree_off_Nodes [verified]
|
|
constexpr uint32_t TechDef_off_TechId = 0x00000000;
|
|
// offset sizeof({TechDef* target, int points}) -- the element of the vector ProcessResearch walks [verified]
|
|
constexpr uint32_t ResearchAlloc_stride = 0x00000008;
|
|
// cdecl void (void) /* sots.ini [Modules] Mount<N> -> gobio::Init */ [verified]
|
|
constexpr uint32_t Mars_Application_MountModules = 0x004a0a20;
|
|
// cdecl void (int* count, const char*** table) /* count 3 @0x00b2d514, table @0x00a35e98 */ [verified]
|
|
constexpr uint32_t Game_GetDefaultMounts = 0x004991c0;
|
|
// thiscall GlobalConst* (GlobalConst* this, void* storage, const char* key, GlobalConstParseFn parser, const char* file) /* RET 0x10; static-init stub call. GlobalConst 0x10 bytes: {+0 void* storage (int/float/float[4]), +4 const char* key, +8 parser, +c const char* file} */ [verified]
|
|
constexpr uint32_t GlobalConst_ctor = 0x004b76a0;
|
|
// cdecl void (GlobalConst* g) [verified]
|
|
constexpr uint32_t GlobalConst_Register = 0x004b7610;
|
|
// cdecl void (int* storage, const char* text) [verified]
|
|
constexpr uint32_t GlobalConst_ParseInt = 0x004b7000;
|
|
// cdecl void (float* storage, const char* text) [verified]
|
|
constexpr uint32_t GlobalConst_ParseFloat = 0x004b7020;
|
|
// cdecl void (float* storage, const char* text) /* sscanf(%f) into *storage, then *storage = (float)((double)*storage * *(double*)0x00af5210) */ [verified]
|
|
constexpr uint32_t GlobalConst_ParseFloatScaled = 0x004b70e0;
|
|
// cdecl void (float rgba[4], const char* text) /* '%d %d %d %d', defaults 255, /255, clamp 0..1 */ [verified]
|
|
constexpr uint32_t GlobalConst_ParseColour = 0x004b7110;
|
|
// cdecl void (int rect[4], const char* text) /* '%d %d %d %d' (x y w h); 2 keys (COMBATSETUP_CARD_*_RECT); layout inferred from the M1 trace, int vs float unconfirmed */ [verified-by-trace]
|
|
constexpr uint32_t GlobalConst_ParseRect = 0x004b7040;
|
|
// cdecl void (float xyz[3], const char* text) /* '%f %f %f'; 5 keys (HIVER_SPAWN_*_GATE_HEIGHT / _SHIP_OFFSET ...); inferred from the M1 trace */ [verified-by-trace]
|
|
constexpr uint32_t GlobalConst_ParseVec3 = 0x004b7080;
|
|
// cdecl void (std::string* storage, const char* text) /* *storage = text; MSVC std::string is 0x1c bytes {+0 char buf[16] | char* ptr when capacity > 15, +0x10 _Mysize, +0x14 _Myres, +0x18 _Alval (empty allocator, never read/written)} -- see std_string_sizeof; 146 keys (*_NAME, *_SOUND, *_TEXTURENAME, DERELICT_SECTION_nn ...); seen in the M1 trace */ [verified-by-trace]
|
|
constexpr uint32_t GlobalConst_ParseString = 0x004b7670;
|
|
// cdecl void (void) /* called once from Application::Initialize; sets g_GlobalConstsLoaded */ [verified]
|
|
constexpr uint32_t GlobalConsts_LoadAll = 0x004b76d0;
|
|
// cdecl void (const char* file, GlobalConstMap* consts) /* consts = std::map<const char*,GlobalConst*,stricmp> of the keys registered for this file; MSVC2010 layout: map {+4 head node, +8 size}; node {+0 left, +4 parent, +8 right, +0xc const char* key, +0x10 GlobalConst*, +0x14 color, +0x15 isnil}; head: isnil=1, parent=root. Consumed keys are erased from the map. Hook this for the file-level read */ [verified]
|
|
constexpr uint32_t GlobalConsts_LoadFile = 0x004b73c0;
|
|
// data bool [verified]
|
|
constexpr uint32_t g_GlobalConstsLoaded = 0x0072d740;
|
|
// data std::map<const char*,GlobalConst*,stricmp_less> /* +4 (0x00b2d748) = header node pointer */ [verified]
|
|
constexpr uint32_t g_GlobalConstRegistry = 0x0072d744;
|
|
// data double /* pi/180 as a DOUBLE (0x3f91df46a2529d39); GlobalConst_ParseFloatScaled does float word *= this in x87 and stores float */ [verified]
|
|
constexpr uint32_t g_GlobalConstFloatScale = 0x006f5210;
|
|
// thiscall Script* (Script* this) /* sizeof(Script) = 0x28 */ [verified]
|
|
constexpr uint32_t Script_ctor = 0x004cd700;
|
|
// thiscall void (Script* this) [verified]
|
|
constexpr uint32_t Script_dtor = 0x004cd720;
|
|
// thiscall bool (Script* this, const char* path) /* gobio::ReadFile into this->buf */ [verified]
|
|
constexpr uint32_t Script_Open = 0x004cd7d0;
|
|
// thiscall void (Script* this) [verified]
|
|
constexpr uint32_t Script_Close = 0x004cd6c0;
|
|
// thiscall int (Script* this, char* out, uint outMax) /* 0 token, 1 no buffer/past end, 2 reached end */ [verified]
|
|
constexpr uint32_t Script_ReadToken = 0x004cd2f0;
|
|
// thiscall int (Script* this, ScriptToken* tok) /* tok: {int type; char key[1024]; char value[1024]}; type 1 kv, 2 block open, 3 '}' */ [verified]
|
|
constexpr uint32_t Script_Next = 0x004cd3e0;
|
|
// thiscall int (Script* this, int depth) /* 0 ok, nonzero EOF */ [verified]
|
|
constexpr uint32_t Script_SkipBlock = 0x004cd4b0;
|
|
// custom /* EAX=cursor, ECX="edFlag, EDX=out; stack: end, outEnd, &len — internal, do not hook */ [verified]
|
|
constexpr uint32_t Script_ScanToken = 0x004cd1f0;
|
|
// cdecl void (IConfigCallback* cb, const char* path, const char* blockName, bool* found) /* cb->vft[1](Script*) per matching block */ [verified]
|
|
constexpr uint32_t ConfigParser_ParseFile = 0x004cd820;
|
|
// fastcall int (IBuffer* obj) /* ECX=obj; --refcount, vft[0](1) at 0 */ [verified]
|
|
constexpr uint32_t RefCounted_Release = 0x004a7230;
|
|
// thiscall void (WeaponDictionary* this) /* reads Weapons/_weapons.txt manifest */ [verified]
|
|
constexpr uint32_t WeaponDictionary_Init = 0x0019a4c0;
|
|
// thiscall void (WeaponDictionary* this, const char* path, int id, WeaponDef** out) [verified]
|
|
constexpr uint32_t WeaponDictionary_LoadWeapon = 0x0019a230;
|
|
// thiscall WeaponDef* (WeaponDictionary* this, const char* name) /* _stricmp over def->name @+0x40 */ [verified]
|
|
constexpr uint32_t WeaponDictionary_FindByName = 0x00190a10;
|
|
// thiscall WeaponDef* (WeaponDictionary* this, const char* name) /* 'Weapon not found: "%s" - Was it added to the index file?' */ [verified]
|
|
constexpr uint32_t WeaponDictionary_FindByNameSorted = 0x00191e30;
|
|
// thiscall WeaponDef* (WeaponDef* this, int index, int id, const char* path) /* sizeof 0x278 */ [verified]
|
|
constexpr uint32_t WeaponDef_ctor = 0x00198a20;
|
|
// thiscall bool (WeaponDef* this, Script* script, int* rc, TechTree* tree) [verified]
|
|
constexpr uint32_t WeaponDef_ParseScript = 0x00199070;
|
|
// thiscall SectionDictionary* (SectionDictionary* this, TechTree* tree) /* reads Species/<Race>/_shipsections.txt for species 0..6 */ [verified]
|
|
constexpr uint32_t SectionDictionary_ctor = 0x00176f40;
|
|
// thiscall SectionDef* (SectionDictionary* this, const char* path, int species, int id) [verified]
|
|
constexpr uint32_t SectionDictionary_LoadSection = 0x00176cd0;
|
|
// thiscall SectionDef* (SectionDef* this, int index, int species, int id) /* sizeof 0x3d8 */ [verified]
|
|
constexpr uint32_t SectionDef_ctor = 0x00174020;
|
|
// thiscall bool (SectionDef* this, Script* script, int* rc, TechTree* tree) [verified]
|
|
constexpr uint32_t SectionDef_ParseScript = 0x001744e0;
|
|
// cdecl const char* (uint species) [verified]
|
|
constexpr uint32_t Species_GetDirName = 0x00145ec0;
|
|
// thiscall MasterTechTree* (MasterTechTree* this) /* TechTree/MasterTechList.tech */ [verified]
|
|
constexpr uint32_t MasterTechTree_ctor = 0x0018b870;
|
|
// thiscall void (MasterTechTree* this, const char* path, int flag) [verified]
|
|
constexpr uint32_t MasterTechTree_LoadTechFile = 0x0018b770;
|
|
// thiscall void (MasterTechTree* this, Script* script, int flag) /* one tech{} block */ [verified]
|
|
constexpr uint32_t MasterTechTree_ParseTech = 0x0018b050;
|
|
// thiscall void (DemoApp* this) /* MasterTechTree -> +0x110, WeaponDictionary -> +0x118, SectionDictionary -> +0x114 */ [verified]
|
|
constexpr uint32_t DemoApp_LoadGameData = 0x00499280;
|
|
// thiscall void (DemoApp* this) /* once, from OnTick when this+0x10c is set */ [verified]
|
|
constexpr uint32_t DemoApp_EnsureGameData = 0x00499870;
|
|
// cdecl void (std::vector<std::string>* mountPaths) [verified]
|
|
constexpr uint32_t gobio_Init = 0x004d7010;
|
|
// thiscall FileSystemSet* (FileSystemSet* this, std::vector<std::string>* mounts) [verified]
|
|
constexpr uint32_t gobio_FileSystemSet_ctor = 0x004d6d60;
|
|
// thiscall bool (FileSystemSet* this, const char* path) [verified]
|
|
constexpr uint32_t gobio_FileSystemSet_Open = 0x004d5060;
|
|
// thiscall void (FileSystemSet* this, const char* pattern, std::vector<std::string>* out) [verified]
|
|
constexpr uint32_t gobio_FileSystemSet_ListFiles = 0x004d7090;
|
|
// cdecl bool (const char* path, IBuffer** out) /* whole file by relative path; THE hook point for M4 */ [verified]
|
|
constexpr uint32_t gobio_ReadFile = 0x004d5140;
|
|
// cdecl bool (const char* path) [verified]
|
|
constexpr uint32_t gobio_Open = 0x004d50d0;
|
|
// cdecl void (void) [verified]
|
|
constexpr uint32_t gobio_Close = 0x004d4d20;
|
|
// cdecl uint (void) [verified]
|
|
constexpr uint32_t gobio_Size = 0x004d4d50;
|
|
// cdecl uint (void* dst, uint n) [verified]
|
|
constexpr uint32_t gobio_Read = 0x004d4d80;
|
|
// cdecl bool (const char* path) [verified]
|
|
constexpr uint32_t gobio_Exists = 0x004d50f0;
|
|
// cdecl void (const char* pattern, std::vector<std::string>* out) [verified]
|
|
constexpr uint32_t gobio_ListFiles = 0x004d71d0;
|
|
// custom /* size in EBX, IBuffer** out on stack — internal, do not hook */ [verified]
|
|
constexpr uint32_t gobio_Buffer_Create = 0x004d4c90;
|
|
// thiscall void* (IBuffer* this) /* vft[1] and vft[2]; = this+8 */ [verified]
|
|
constexpr uint32_t gobio_Buffer_Data = 0x00282d50;
|
|
// thiscall uint (IBuffer* this) /* vft[3]; = this+0xc */ [verified]
|
|
constexpr uint32_t gobio_Buffer_Size = 0x004d4c80;
|
|
// thiscall NativeFileSystem* (NativeFileSystem* this, const char* root) /* sizeof 0x28; vtable 0x00a398ac */ [verified]
|
|
constexpr uint32_t gobio_NativeFileSystem_ctor = 0x004d57a0;
|
|
// thiscall bool (NativeFileSystem* this, const char* path) /* vft[1] fopen(root+path,'rb') */ [verified]
|
|
constexpr uint32_t gobio_NativeFileSystem_Open = 0x004d4ef0;
|
|
// thiscall uint (NativeFileSystem* this, void* dst, uint n) /* vft[4] */ [verified]
|
|
constexpr uint32_t gobio_NativeFileSystem_Read = 0x004d4c30;
|
|
// thiscall ZipFileSystem* (ZipFileSystem* this, const char* zipPath) /* sizeof 0x34; vtable 0x00a39934 */ [verified]
|
|
constexpr uint32_t gobio_ZipFileSystem_ctor = 0x004d6c40;
|
|
// thiscall bool (ZipFileSystem* this, const char* path) /* vft[1]; case- and slash-insensitive */ [verified]
|
|
constexpr uint32_t gobio_ZipFileSystem_Open = 0x004d56a0;
|
|
// thiscall uint (ZipFileSystem* this, void* dst, uint n) /* vft[4] */ [verified]
|
|
constexpr uint32_t gobio_ZipFileSystem_Read = 0x004d4b40;
|
|
// data FileSystemSet* /* -> 0x00b2e274 after gobio::Init */ [verified]
|
|
constexpr uint32_t g_gobio = 0x0072e270;
|
|
// data const char*[3] /* used when sots.ini has no [Modules] Mount<N>; contains sots_local_en.gob, sots.gob */ [verified]
|
|
constexpr uint32_t g_DefaultMountTable = 0x00635e98;
|
|
// cdecl Stream* (const char* path) /* sniffs 'TXT' magic, returns a TextFileStream (0x420 bytes) opened for read */ [verified]
|
|
constexpr uint32_t Stream_OpenFile = 0x004cfcb0;
|
|
// thiscall TextFileStream* (TextFileStream* this) /* vtable 0x00a428c0 */ [verified]
|
|
constexpr uint32_t TextFileStream_ctor = 0x0051e460;
|
|
// thiscall bool (TextFileStream* this, const char* path, int mode /*1*/) [verified]
|
|
constexpr uint32_t TextFileStream_Open = 0x004cfb90;
|
|
// thiscall uint (Stream* this, char* out, int max) [verified]
|
|
constexpr uint32_t FileStream_ReadLine = 0x004cfa30;
|
|
// thiscall const char* (TextFileStream* this, const char* line, const char* label) /* NULL on END/mismatch */ [verified]
|
|
constexpr uint32_t TextFileStream_MatchLabel = 0x0051e760;
|
|
// thiscall bool (TextFileStream* this, const char* label, char* out, int outMax) /* vft[1] */ [verified]
|
|
constexpr uint32_t TextFileStream_ReadString = 0x0051ec90;
|
|
// thiscall bool (TextFileStream* this, const char* label, bool* out) /* vft[2] */ [verified]
|
|
constexpr uint32_t TextFileStream_ReadBool = 0x0051eba0;
|
|
// thiscall bool (TextFileStream* this, const char* label, float* out) /* vft[3] */ [verified]
|
|
constexpr uint32_t TextFileStream_ReadFloat = 0x0051eb30;
|
|
// thiscall bool (TextFileStream* this, const char* label, int* out, int dflt) /* vft[4] */ [verified]
|
|
constexpr uint32_t TextFileStream_ReadInt = 0x0051eab0;
|
|
// thiscall void (TextFileStream* this, const char* label, IStreamable* obj /*NULL=skip*/) /* vft[5]; BEGIN..END */ [verified]
|
|
constexpr uint32_t TextFileStream_ReadNested = 0x0051e8e0;
|
|
// thiscall void (ParticleSystem* this, Stream* s) /* .effect body */ [verified]
|
|
constexpr uint32_t ParticleSystem_Read = 0x004db650;
|
|
// thiscall void (ParticleSystem* this, Stream* s) [verified]
|
|
constexpr uint32_t ParticleSystem_Write = 0x004dad60;
|
|
// thiscall void* (EffectDictionary* this, const char* name) /* 'effects/' + name; Stream::OpenFile; 0x1c0-byte effect */ [unverified]
|
|
constexpr uint32_t EffectDictionary_Load = 0x004b42b0;
|
|
// cdecl void (Stream* s, const char* label, std::string* out, int flag) /* wrapper over vft[1] */ [verified]
|
|
constexpr uint32_t Stream_ReadString = 0x004b9d90;
|
|
// cdecl void (Stream* s, const char* label, int* out, int dflt) /* wrapper over vft[4] */ [verified]
|
|
constexpr uint32_t Stream_ReadInt = 0x004b9d20;
|
|
// cdecl void (Stream* s, const char* label, bool* out) /* wrapper over vft[2] */ [verified]
|
|
constexpr uint32_t Stream_ReadBool = 0x004b9c00;
|
|
// cdecl void (Stream* s, const char* label, float* out, int flag) /* wrapper over vft[3] */ [verified]
|
|
constexpr uint32_t Stream_ReadFloat = 0x004b9bc0;
|
|
// thiscall void (ServerPlayer* this, TechDef* def, bool silent) /* vft slot 4 of 0x00a327a4; hard-coded tech effects */ [verified]
|
|
constexpr uint32_t ServerPlayer_OnTechResearched = 0x00491790;
|
|
// thiscall bool (MasterTechTree* this, TechDef* def, int techId) /* techId = 10000+index into g_TechIdNames; 0xc5 = none */ [verified]
|
|
constexpr uint32_t MasterTechTree_IsTech = 0x0017d5d0;
|
|
// thiscall TechDef* (MasterTechTree* this, int techId) [verified]
|
|
constexpr uint32_t MasterTechTree_GetTechDef = 0x0017d610;
|
|
// thiscall bool (TechTree* this, int techId) [verified]
|
|
constexpr uint32_t TechTree_HasResearched = 0x0017d810;
|
|
// thiscall int (TechTree* this, TechNode* node) // RET 4. node==0 -> INT_MAX; node->costRP(+0x18)==INT_MAX -> INT_MAX; costRP<=0 or this->owner(+0xc)==0 or mult<=0 -> 0; else max(1, _ftol2((float)TechCostMult(owner, node->def) * costRP)). Reads only (no RNG, no writes), so a reimplementation may call it on a scratch tree whose +0xc is the real owner [verified]
|
|
constexpr uint32_t TechTree_Cost = 0x0017da00;
|
|
// thiscall float (ServerPlayer* this, TechDef* def) // 1.0 - 0.25 per applicable species research-bonus tech owned; read-only [verified]
|
|
constexpr uint32_t ServerPlayer_TechCostMult = 0x0040db50;
|
|
// thiscall void (TechTree* this, TechDef* def, int flags) // state 4 + turn/order stamps + owner callback + child unlock cascade. Makes no direct RNG draw; the owner callback is not audited, so a compare that runs it is out of scope [verified]
|
|
constexpr uint32_t TechTree_SetResearched = 0x00181e10;
|
|
// constant flags bit 1 of SetResearched(def, flags): skip the PrereqsMet test and complete unconditionally. TechTree::ProcessResearch passes flags = 2 [verified]
|
|
constexpr uint32_t TechTree_SetResearched_flag_Force = 0x00000002;
|
|
// constant flags bit 2 of SetResearched(def, flags): the owner callback is invoked as vft+0x10(def, (flags>>2)&1), so this bit IS OnTechResearched's `silent` argument. ProcessResearch passes flags = 2, so silent = FALSE and the completion event IS posted [verified]
|
|
constexpr uint32_t TechTree_SetResearched_flag_Silent = 0x00000004;
|
|
// offset ServerPlayer* owner (0 for a tree with no player) [verified]
|
|
constexpr uint32_t TechTree_off_Owner = 0x0000000c;
|
|
// offset std::vector<TechNode*> indexed by tech id (MSVC2010: 3 words {first@+0x10, last@+0x14, end@+0x18}); entries may be NULL [verified]
|
|
constexpr uint32_t TechTree_off_Nodes = 0x00000010;
|
|
// offset int completion-order counter; SetResearched stamps node+0x28 from it and post-increments it. Harness-audit row 9 [verified]
|
|
constexpr uint32_t TechTree_off_OrderCounter = 0x00000020;
|
|
// offset sizeof(TechNode) -- the ctor's operator new argument [verified]
|
|
constexpr uint32_t TechNode_size = 0x00000034;
|
|
// offset TechDef* def; *(int*)def is the tech id used to index TechTree_off_Nodes [verified]
|
|
constexpr uint32_t TechNode_off_Def = 0x00000000;
|
|
// offset int state (0 hidden, 1 parent researched, 2 available, 3 available AND selected as the current research target, 4 researched); ctor writes 0. The decay sweep tests == 2, so a state-3 node never decays -- confirmed by the B3 live trace [verified]
|
|
constexpr uint32_t TechNode_off_State = 0x00000014;
|
|
// offset int costRP, INT_MAX = no researched parent yet; ctor writes 0x7fffffff [verified]
|
|
constexpr uint32_t TechNode_off_CostRP = 0x00000018;
|
|
// offset int progress in RP; the only node word ProcessResearch itself writes besides the flag [verified]
|
|
constexpr uint32_t TechNode_off_Progress = 0x0000001c;
|
|
// offset int turnAvailable; SetResearched's second sweep stamps it with the server ModCount ONLY when it currently reads -1 (a sticky first-availability stamp). EVENT_TECHS_UNLOCKED collects nodes with state==2 && turnAvailable==currentTurn [verified]
|
|
constexpr uint32_t TechNode_off_TurnAvailable = 0x00000020;
|
|
// offset int turnResearched; SetResearched writes the server ModCount [verified]
|
|
constexpr uint32_t TechNode_off_TurnResearched = 0x00000024;
|
|
// offset int order; SetResearched writes the tree's completion-order counter, then bumps it [verified]
|
|
constexpr uint32_t TechNode_off_Order = 0x00000028;
|
|
// offset std::vector<TechEdge*> children (3 words at +0x04/+0x08/+0x0c) [verified]
|
|
constexpr uint32_t TechNode_off_Children = 0x00000004;
|
|
// offset int RP cost carried by the edge; SetResearched sets child.costRP = min(child.costRP, edge.costRP) [verified]
|
|
constexpr uint32_t TechEdge_off_CostRP = 0x0000001c;
|
|
// offset TechDef* the edge's child tech; SetResearched indexes tree->nodes by childDef->[0] [verified]
|
|
constexpr uint32_t TechEdge_off_ChildDef = 0x00000040;
|
|
// offset int flag (1 default from the ctor, 0 completed below 80% of cost, 2 over-budget event raised) [verified]
|
|
constexpr uint32_t TechNode_off_Flag = 0x0000002c;
|
|
// thiscall void (MasterTechTree* this) /* fills TechDef*[196] at this+0 from g_TechIdNames */ [verified]
|
|
constexpr uint32_t MasterTechTree_ResolveTechIds = 0x00181c10;
|
|
// cdecl int (std::string* name) /* membership in g_CombatTechNames (116) */ [verified]
|
|
constexpr uint32_t IsCombatTechName = 0x002965c0;
|
|
// fastcall void (ServerPlayer* this) /* flags[7] at +0x348 from SpeciesDef xnc tech ids */ [verified]
|
|
constexpr uint32_t ServerPlayer_RebuildSpeciesTechFlags = 0x0042bf10;
|
|
// thiscall void (ServerPlayer* this, TechDef* def) [verified]
|
|
constexpr uint32_t ServerPlayer_ApplyAITechBonus = 0x0040e330;
|
|
// thiscall void (ServerPlayer* this, bool on) [verified]
|
|
constexpr uint32_t ServerPlayer_SetAIBenefit = 0x004186b0;
|
|
// cdecl bool (TechDef* def, uint* maskOut) [verified]
|
|
constexpr uint32_t TechDef_GetPlagueCureMask = 0x00137240;
|
|
// cdecl uint* (uint out[2], TechTree* tree) /* EBX-passed tree in caller; tables 0x00adf378 / 0x00adf478 */ [unverified]
|
|
constexpr uint32_t ComputeTechBitmasks = 0x000d7960;
|
|
// cdecl SpeciesDef* (uint species) /* 0x00b10a00 + i*0x184 */ [verified]
|
|
constexpr uint32_t SpeciesDef_Get = 0x00145cc0;
|
|
// cdecl void (void) [verified]
|
|
constexpr uint32_t SpeciesDef_InitTable = 0x001453a0;
|
|
// fastcall void (ServerPlayer* this) [verified]
|
|
constexpr uint32_t ServerPlayer_UpdateBankruptcyLimits = 0x00418600;
|
|
// thiscall void (ServerPlayer* this, int level) [verified]
|
|
constexpr uint32_t ServerPlayer_SetBankruptcyState = 0x0040e260;
|
|
// fastcall int (ServerPlayer* this) [verified]
|
|
constexpr uint32_t ServerPlayer_BankruptcyLevel = 0x0040db10;
|
|
// cdecl double (double suit, double ideal, double tol) /* clamp01(1-|suit-ideal|/(tol+0.1)) */ [verified]
|
|
constexpr uint32_t ServerSystem_HazardMod = 0x00347ae0;
|
|
// thiscall double (ServerSystem* this, double tradePoints) /* RET 8 */ [verified]
|
|
constexpr uint32_t ServerSystem_TradePointsToMoney = 0x003505b0;
|
|
// thiscall double (ServerSystem* this, int species) [verified]
|
|
constexpr uint32_t ServerSystem_CalcSuitMod = 0x003484d0;
|
|
// fastcall void (ServerSystem* this) [verified]
|
|
constexpr uint32_t ServerSystem_AccrueSystemBonus = 0x0034d4f0;
|
|
// thiscall int (ServerSystem* this, float frac) [verified]
|
|
constexpr uint32_t ServerSystem_SystemBonusPopTarget = 0x0034b5a0;
|
|
// data struct { const char* name; int x; }[196] /* TechId = 10000+i */ [verified]
|
|
constexpr uint32_t g_TechIdNames = 0x005ff9e4;
|
|
// data const char*[116] [verified]
|
|
constexpr uint32_t g_CombatTechNames = 0x00619718;
|
|
// data SpeciesDef[7] (0x184 B each, .bss, filled by SpeciesDef_InitTable) [verified]
|
|
constexpr uint32_t g_SpeciesDefTable = 0x00710a00;
|
|
// data struct { int techId; uint bit; }[32] [verified]
|
|
constexpr uint32_t g_TechBitmaskTableA = 0x006df378;
|
|
// data struct { int techId; uint bit; }[29] [verified]
|
|
constexpr uint32_t g_TechBitmaskTableB = 0x006df478;
|
|
// data struct { int techId; float rebellionOdds; float bonus; }[6] /* rows: CCC_AI/CCC_AIAdmin/CCC_AIFac odds 0.1f bonus 0.5f, CCC_AIFRCON odds 0.2f bonus 0, CCC_AIVrus/CCC_AISlv odds 0 bonus 0 */ [verified]
|
|
constexpr uint32_t g_AITechValueTable = 0x00617888;
|
|
// data int (config storage; PTR slot 0x00aedfe0) [verified]
|
|
constexpr uint32_t g_PERGATETRAFFIC_DRV_TpGate = 0x00723e2c;
|
|
// data int (config storage; PTR slot 0x00aedfe4) [verified]
|
|
constexpr uint32_t g_PERGATETRAFFIC_DRV_GatAmp = 0x00723e30;
|
|
// offset int PlyrIdx [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_PlyrIdx = 0x00000028;
|
|
// offset std::vector<ServerSystem*> owned systems (MSVC2010 release layout: 3 words {first@+0, last@+4, end@+8}, handle ids, 4 B stride); save tag NumOwn/OwnId [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_OwnedSystems = 0x00000030;
|
|
// offset int Species (0 Human .. 6 Morrigi) [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_Species = 0x0000005c;
|
|
// offset float ResRate (research share of available money, 0..1) [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_ResRate = 0x000000bc;
|
|
// offset float ResMod [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_ResMod = 0x000000c0;
|
|
// offset float ResScl [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_ResScl = 0x000000c4;
|
|
// offset float TRM (timed research multiplier bonuses) [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_TRM = 0x000000d0;
|
|
// offset int TRA (per-turn research-point contribution) [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_TRA = 0x000000d4;
|
|
// offset int TRP (per-turn research-point contribution) [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_TRP = 0x000000d8;
|
|
// offset bool Elim [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_Elim = 0x000000f8;
|
|
// offset bool isAI /* gate ComputeBudget reads for the construction slot and for picking the AI difficulty-mod row */ [verified]
|
|
constexpr uint32_t ServerPlayer_off_IsAI = 0x000000f9;
|
|
// offset bool NPC [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_NPC = 0x000000fb;
|
|
// offset bool RebAI [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_RebAI = 0x000000fc;
|
|
// offset int Maint (raw fleet upkeep, before the difficulty divisor) [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_Maint = 0x0000015c;
|
|
// offset float shrm (shared research modifier) [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_shrm = 0x00000160;
|
|
// offset std::vector<ExpenseEntry> expense sliders (3 words; entry 16 B {int xid, int xmin, int xmax, float xper}); save tag Nexp [verified-by-save]
|
|
constexpr uint32_t ServerPlayer_off_Nexp = 0x00000204;
|
|
// offset float income multiplier from the game-setup handicap block; ComputeBudget bonus slot = ftol((this - 1.0) x net) [verified]
|
|
constexpr uint32_t ServerPlayer_off_SetupIncomeMult = 0x00000228;
|
|
// offset float research multiplier from the game-setup handicap block; a factor of the research-points slot [verified]
|
|
constexpr uint32_t ServerPlayer_off_SetupResearchMult = 0x0000022c;
|
|
// offset int Sav (treasury) [verified-by-save]
|
|
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. 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;
|
|
// offset std::vector<PlayerAid> (3 words; entry 0x18 B, {+0x8 int researchPercent, +0xc int researchActive, +0x10 int savings, +0x14 int savingsActive}) [verified]
|
|
constexpr uint32_t ServerPlayer_off_Aid = 0x00000310;
|
|
// offset float SuitTol -- raised by the two adaptation techs; also caps the hazard money cost [verified]
|
|
constexpr uint32_t ServerPlayer_off_SuitTol = 0x000000b4;
|
|
// offset float MaxOH -- max over-harvest slider, raised by a float32 max() against 0.1f [verified]
|
|
constexpr uint32_t ServerPlayer_off_MaxOH = 0x000000b8;
|
|
// offset TechTree* the player's own tech tree; its +4 is the MasterTechTree the IsTech chain uses [verified]
|
|
constexpr uint32_t ServerPlayer_off_TechTree = 0x000000f4;
|
|
// offset bool AIBn -- AI benefit active; gates ApplyAITechBonus and flips the bonus sign [verified]
|
|
constexpr uint32_t ServerPlayer_off_AIBn = 0x000000fe;
|
|
// offset bool CnTrd -- trade routes allowed (set by CCC_FtlEcon unless RebAI) [verified]
|
|
constexpr uint32_t ServerPlayer_off_CnTrd = 0x000000ff;
|
|
// offset bool CnRad -- commerce raiding allowed (CCC_ComRaid) [verified]
|
|
constexpr uint32_t ServerPlayer_off_CnRad = 0x00000100;
|
|
// offset bool CnVItl -- may view other empires' intel (CCC_DatCor) [verified]
|
|
constexpr uint32_t ServerPlayer_off_CnVItl = 0x00000101;
|
|
// offset bool hgs -- gravitic-syncing drive researched (DRV_GrvSyn); client-synced only [verified]
|
|
constexpr uint32_t ServerPlayer_off_hgs = 0x00000102;
|
|
// offset bool hadvs -- advanced sensors (CCC_AdvSens) [verified]
|
|
constexpr uint32_t ServerPlayer_off_hadvs = 0x00000103;
|
|
// offset bool harcc -- arcologies (IND_ArcCon) [verified]
|
|
constexpr uint32_t ServerPlayer_off_harcc = 0x00000104;
|
|
// offset float pddm -- multiplied by 0.25 by IND_HrdStrct [verified]
|
|
constexpr uint32_t ServerPlayer_off_pddm = 0x00000108;
|
|
// offset float ConMod[3] -- construction cost per hull class; techs subtract from all three, IND_OrbDry only from [1] and [2] [verified]
|
|
constexpr uint32_t ServerPlayer_off_ConMod = 0x0000010c;
|
|
// offset float SavMod[3] -- IND_OrbFound subtracts 0.05f from all three [verified]
|
|
constexpr uint32_t ServerPlayer_off_SavMod = 0x00000118;
|
|
// offset float OutMod -- industrial output multiplier; several techs add, IND_HrdStrct multiplies by 0.9f [verified]
|
|
constexpr uint32_t ServerPlayer_off_OutMod = 0x00000124;
|
|
// offset float PopMod [verified]
|
|
constexpr uint32_t ServerPlayer_off_PopMod = 0x00000130;
|
|
// offset float TerraMod [verified]
|
|
constexpr uint32_t ServerPlayer_off_TerraMod = 0x00000134;
|
|
// offset bool AMine -- asteroid mining (IND_AstMine) [verified]
|
|
constexpr uint32_t ServerPlayer_off_AMine = 0x00000138;
|
|
// offset float MinRate -- mining rate; IND_MsMine adds 1.0 [verified]
|
|
constexpr uint32_t ServerPlayer_off_MinRate = 0x00000140;
|
|
// offset int PrGtTrf -- per-gate traffic capacity; the two gate techs raise it with a SIGNED INTEGER max against a config int, not a float compare [verified]
|
|
constexpr uint32_t ServerPlayer_off_PrGtTrf = 0x00000148;
|
|
// offset float CstR, CstE, CstT at +0x150/+0x154/+0x158 -- set to 10.0f / 2.0f / 1.0f (FLD1) by DRV_FarCast [verified]
|
|
constexpr uint32_t ServerPlayer_off_CstR = 0x00000150;
|
|
// offset uint32 design-option mask A; +0x194 is mask B. Rewritten wholesale by ComputeTechBitmasks on every completion; not serialised [verified]
|
|
constexpr uint32_t ServerPlayer_off_TechMaskA = 0x00000190;
|
|
// offset uint32 sticky 'level-1 translation researched for species sp' bit mask, one bit per species, NPC (4) excluded. Second pass of RebuildSpeciesTechFlags; only ever ORed [verified]
|
|
constexpr uint32_t ServerPlayer_off_TranslationKnown = 0x000001a4;
|
|
// offset uint32 HasVac; +0x28c HasImm. Both get |= the plague-cure mask of the completing tech [verified]
|
|
constexpr uint32_t ServerPlayer_off_HasVac = 0x00000288;
|
|
// offset uint32 NPTrk -- bit per species whose node-space traffic is visible; set by that species' node-track tech [verified]
|
|
constexpr uint32_t ServerPlayer_off_NPTrk = 0x00000290;
|
|
// offset int (*)[3] -- pointer to a separately allocated 3-word node-bore parameter block, NULL while no bore drive is researched (the updater allocates and frees it) [verified]
|
|
constexpr uint32_t ServerPlayer_off_NodeBore = 0x00000308;
|
|
// offset bool cdp -- set once CCC_SpyBm and IND_SlvgTech are both researched; the test is in the completion tail, not in either tech's branch [verified]
|
|
constexpr uint32_t ServerPlayer_off_CaptureDesigns = 0x00000330;
|
|
// offset uint32 flags[7] xenotech bits per target species, followed by a count word 7 at +0x364 (0x20 bytes assigned as a unit) [verified]
|
|
constexpr uint32_t ServerPlayer_off_SpeciesTechFlags = 0x00000348;
|
|
// offset bool -- a pending plague-cure roll; run and cleared when the completing tech is the current research target [verified]
|
|
constexpr uint32_t ServerPlayer_off_ResearchRollPending = 0x000003b4;
|
|
// offset AIRebellion* -- non-null while an AI rebellion object exists; the two AI techs notify it [verified]
|
|
constexpr uint32_t ServerPlayer_off_AIRebellion = 0x000003b8;
|
|
// offset MasterTechTree* -- the `this` MasterTechTree::IsTech / GetTechDef are called on [verified]
|
|
constexpr uint32_t TechTree_off_Master = 0x00000004;
|
|
// fastcall void (ServerPlayer* this) -- re-selects the node-bore parameters from the researched set and stores them in the +0x308 block, allocating it on first use and freeing it when no bore drive is researched [verified]
|
|
constexpr uint32_t ServerPlayer_UpdateNodeBoreParams = 0x004182c0;
|
|
// cdecl bool (int out[3], ServerPlayer* p) -- highest researched bore drive wins: DRV_RAD {95,60,5}, else DRV_REND {65,35,4}, else DRV_RIP {45,15,3}; false (and out left at {INT_MAX,INT_MAX,0}) when none. No species gate [verified]
|
|
constexpr uint32_t SelectNodeBoreParams = 0x002e18e0;
|
|
// custom int (TechDef* def in EBX) -- index of the species whose SpeciesDef+0x74 node-track tech this def is, or -1 [verified]
|
|
constexpr uint32_t ServerPlayer_SpeciesOfTranslationTech = 0x0040e410;
|
|
// cdecl struct {int techId; float rebellionOdds; float bonus;}* (TechDef* def) -- linear search of g_AITechValueTable, NULL when the def is none of them [verified]
|
|
constexpr uint32_t AITechRow = 0x00290f70;
|
|
// data int -- number of rows in g_AITechValueTable (6) [verified]
|
|
constexpr uint32_t g_AITechValueCount = 0x006ea2ec;
|
|
// offset void* -- the StrategyServer subobject pointer; StrategyServer = *(void**)(player+8) - 4. Its +0x16c is the Mars::RNG* the strategic sim draws from, and its +8 the event manager [verified]
|
|
constexpr uint32_t ServerPlayer_off_ServerLink = 0x00000008;
|
|
// offset Mars::RNG* -- the strategic generator (same object TechTree::ProcessResearch is handed) [verified]
|
|
constexpr uint32_t StrategyServer_off_RNG = 0x0000016c;
|
|
// fastcall void (ServerPlayer* this) -- odds = ResearchEventOdds(this, this->ResT); draws ONE NextFloat from StrategyServer's generator (entered at rng+4), unconditionally, then fires ServerPlayer_OnResearchRollSucceeded (0x00889d60) when odds > roll (fcompp + test ah,0x41, so equality also skips). THAT ONE WORD IS THE COST OF REACHING THE BRANCH, NOT OF A FIRED ROLL: the plague path inside draws a SECOND word (NextInt) to pick an owned system and posts EVENT_PLAGUE_OUTBREAK; the rebellion path allocates an AIRebellion at ServerPlayer+0x3b8 and cancels the research with no further draw. So a fired roll costs one or two words. Called from OnTechResearched when the completing def is the current research target and the pending-roll byte at +0x3b4 is set. This is the extra RNG draw B3 observed on a completion [verified]
|
|
constexpr uint32_t ServerPlayer_RollResearchEvent = 0x0048df20;
|
|
// thiscall float (ServerPlayer* this, TechDef* def) -- 0 when def is null; a plague-family path via 0x00535480, else the AI-rebellion path via AITechRow (odds column) gated on !NPC. Read-only, makes no draw [verified]
|
|
constexpr uint32_t ServerPlayer_ResearchEventOdds = 0x00420380;
|
|
// thiscall void (ServerSystem* this) /* NO stack arguments -- plain RET, nothing reads [ebp+8]; Ghidra's decompile shows a spurious second parameter. Body order: unowned Infra decay -> ApplyInfraBonus -> ApplyPopBonus -> independent pop drift -> IsStable/ntdev -> AccrueSystemBonus -> ProcessPlague -> ProcessBuildQueue -> imperial growth -> civilian growth -> AdjustResources(-out[2]) -> TRes=0 -> RefuelInOrbit(1) -> Bats2 tick -> rcex tick -> haltv[0..2]=false -> ProcessSlaves -> ProcessRebellion -> addiction sweep. Consumes no RNG itself; ProcessPlague / civilian growth / ProcessSlaves are draw-free to depth 1, and ProcessRebellion is the only consumer */ [verified]
|
|
constexpr uint32_t ServerSystem_ProcessTurn = 0x003598e0;
|
|
// thiscall bool (StrategyServer* this, StarFleet* fleet, float dt) /* RET 8; dt is a 4-byte float pushed with fstp DWORD [esp]; returns AL, true only when the fleet ends exactly on a waypoint destination at the deepest recursion level. range = MinRange(fleet, +0.05f) -- ADDED, not subtracted; out of range with MinRange(0)==0 zeroes the RANGE, not the step; move = min(min(range, step), distance) with no floor at 0; recursion iff fraction < (double)0.9999f with dt' = float32((1-fraction)*dt) */ [verified]
|
|
constexpr uint32_t StrategyServer_MoveFleet = 0x003d9ee0;
|
|
// thiscall void (StrategyServer* this) /* no stack args, plain RET. Pursuit schedule, not a departing/in-transit split: classify every fleet whose current waypoint targets a fleet by owner relation (0 = pursuer, else follower); pass1 prey dt 0.5, pass2 pursuers dt 0.5 (an arrival retires the pair), pass3 remaining prey dt 0.5, pass4 everything unscheduled dt 1.0 (an uncaught pursuer gets 0.5), pass5 followers dt 1.0. Then FPdpos, gate traffic, OnFleetArrived, and clears flag 0x100 on every fleet. Makes no RNG draw of its own */ [verified]
|
|
constexpr uint32_t StrategyServer_ProcessFleetMovement = 0x003da9a0;
|
|
// thiscall float (StarFleet* this, float bias) /* RET 4; min over ships of ship->Range seeded with FLT_MAX (an empty fleet is unconstrained), then + bias, narrowed to float32. No ship is skipped -- range-exempt tankers still clamp the fleet */ [verified]
|
|
constexpr uint32_t StarFleet_MinRange = 0x002ff6a0;
|
|
// thiscall bool (StarFleet* this, uint32 mask) /* (flags & mask) == mask, flags at fleet+0x10c */ [verified]
|
|
constexpr uint32_t StarFleet_HasAllFlags = 0x002fe1d0;
|
|
// thiscall void (StarFleet* this, uint32 mask, bool on) /* the only two writers of fleet+0x10c in the image */ [verified]
|
|
constexpr uint32_t StarFleet_SetFlag = 0x002fe1a0;
|
|
// thiscall void* (StarFleet* this) /* no args; NULL when the waypoint vector is empty or the front waypoint's target id does not resolve in the entity hash at fleet->galaxy(+0x10)+0x80 */ [verified]
|
|
constexpr uint32_t StarFleet_ResolveWaypoint = 0x00301390;
|
|
// cdecl bool (int waypointType) /* 7-entry jump table: true only for 4 and 5; false default */ [verified]
|
|
constexpr uint32_t IsGateTransitWaypoint = 0x0016e6e0;
|
|
// cdecl bool (int waypointType) /* 7-entry jump table: true only for 3. NOTE the node-LINE case of the movement switch is type 2, which this does NOT accept */ [verified]
|
|
constexpr uint32_t IsNodeWaypoint = 0x0016e720;
|
|
// thiscall void (StrategyServer* this, StarFleet* fleet, void* dest) /* waypoint type 5. v = float32(NextFloat() * player->CstE); arrives iff !(v > player->CstT) (equality arrives); on a miss the fleet is placed at dest + randomUnitVector * v -- scattered AROUND the destination by v, not advanced a fraction along the vector -- which costs a second raw draw. 1 draw on success, 2 on a miss */ [verified]
|
|
constexpr uint32_t ProbabilisticJump = 0x003b6700;
|
|
// cdecl void (Vector3* posOut, bool* arrivedOut, float nodespeed, float dt, vector<StarSystem*>* systems, const Vector3* from, const Vector3* to) /* builds the stutter segments, reverses them and pops from the back (so ascending by start); plain nodespeed between spheres, a constant per-segment speed inside one; arrived = (time < dt) || |along - length| < FLT_EPSILON, and on arrival the destination is copied verbatim */ [verified]
|
|
constexpr uint32_t NodeLine_Step = 0x00305510;
|
|
// cdecl void (vector<StutterSegment>* out, vector<StarSystem*>* systems, const Vector3* from, const Vector3* to) /* chord parameters scaled to world distance and clamped to [0, length]; a chord with |start-end| <= 0.01f is dropped; std::sort ascending by start; then ONE forward pass over adjacent pairs sets BOTH boundaries of an overlap to end_i + 0.5*(end_i - start_{i+1}) -- the mirror of the midpoint, pushed forward past both chords. Nothing is dropped or clipped back, so a swallowed chord comes out inverted */ [verified]
|
|
constexpr uint32_t NodeLine_BuildStutterSegments = 0x00305280;
|
|
// cdecl bool (float* tNear, float* tFar, const Sphere4* s, const Vector3* from, const Vector3* to) /* wraps the quadratic solver 0x008a62c0; rejects a sphere the segment does not reach and reports an open end as -FLT_MAX / +FLT_MAX */ [verified]
|
|
constexpr uint32_t SegmentSphereIntersect = 0x004a64f0;
|
|
// cdecl float (const Vector3* p, const Vector3* a, const Vector3* b) /* projection parameter clamped to [0,1]. NOTE 0x008c8eb0 is a varargs formatter, not this */ [verified]
|
|
constexpr uint32_t DistPointToSegment = 0x004e8eb0;
|
|
// thiscall int (ServerSystem* this, ServerPlayer* p, int flag) /* RET 8 -- (player, flag), NOT (species, groupType). Species is derived from p->Species and the group type is hard-coded to 0, which makes the cross-species and INDSYS branches dead in this specialisation. Returns the int64 result clamped to [0, INT32_MAX], low dword only */ [verified]
|
|
constexpr uint32_t ServerSystem_MaxPop = 0x0034ab20;
|
|
// thiscall int64 (ServerSystem* this, int groupType, int species, ServerPlayer* p, float* suitOverride) /* RET 0x10 */ [verified]
|
|
constexpr uint32_t ServerSystem_MaxPopGeneric = 0x0034a4a0;
|
|
// thiscall bool (ServerSystem* this) /* false without an owner, with Infra < 1.0, or with Suit != IdealSuit */ [verified]
|
|
constexpr uint32_t ServerSystem_IsStable = 0x0034ad90;
|
|
// thiscall void (ServerSystem* this) /* returns early unless ibon > 0 and Infra < 1; resets ntdev to 0 when the system is not the owner's home system; applied = min(ibon, float32(1 - Infra)); Infra becomes EXACTLY 1.0f when applied == the remainder, else float32(Infra + applied); ibon -= applied */ [verified]
|
|
constexpr uint32_t ServerSystem_ApplyInfraBonus = 0x00346780;
|
|
// thiscall void (ServerSystem* this) /* nothing unless pbon > 0; an unowned system drops the whole pool; returns when Pop >= MaxPop; resets ntdev for a non-home system; Pop += min(cap - Pop, pbon); pbon -= same. Pop and pbon are int32 */ [verified]
|
|
constexpr uint32_t ServerSystem_ApplyPopBonus = 0x0034b510;
|
|
// thiscall void (ServerSystem* this, float delta) /* no-op once Infra >= 1; clamps to 1 and re-normalises the system's own output rates when it lands there */ [verified]
|
|
constexpr uint32_t ServerSystem_ApplyInfraDelta = 0x00348270;
|
|
// thiscall void (ServerSystem* this, float delta) /* clamps at the ideal from whichever side it approached, so suitability never overshoots; re-normalises the rates when it lands exactly on the ideal */ [verified]
|
|
constexpr uint32_t ServerSystem_ApplySuitDelta = 0x003481c0;
|
|
// thiscall void (ServerSystem* this, int out[12], float rates[7]) /* RET 8. `out` is 12 dwords of MIXED type: 0 total (truncated), 1 strip-mined resources, 2 resources consumed, 3 money, 7 gross construction, 8 points to the queue, 9 points SPENT on repairs, 10 float32 infra delta, 11 float32 suitability delta. `rates` is copied to the stack first, so the caller's struct is untouched. NOT side-effect free: it repairs damaged ships in orbit via 0x00751590(points, estimateOnly=0) */ [verified]
|
|
constexpr uint32_t ServerSystem_ComputeOutputFromRates = 0x00351bb0;
|
|
// cdecl void (float rates[7], ServerSystem* sys, float* pinned) /* `pinned` defaults to &rates[0] (trade) and every call site passes NULL or that. Suppress terraform at the ideal (exact ==) and infra when float32(ibon+Infra) >= 1; zero any channel <= (double)1e-4f; clamp ONLY the pinned channel to [0,1]; sum the other three in float32; an exactly-zero sum seeds them with 1e-4f (honouring the suppressions); each becomes (r/sum)*(1-pinned) */ [verified]
|
|
constexpr uint32_t ServerSystem_NormaliseOutputRates = 0x00347390;
|
|
// thiscall void (ServerSystem* this, int* resourcesConsumedOut) /* computes the turn's output vector, writes out[2] (resources consumed) through the pointer, applies out[11] via ApplySuitDelta and out[10] via ApplyInfraDelta, and feeds out[8] to BuildQueue::ProcessTurn. Increments TnsOH while SRoh > 0 && out[1] > 0 */ [verified]
|
|
constexpr uint32_t ServerSystem_ProcessBuildQueue = 0x00352500;
|
|
// thiscall int (BuildQueue* this, ServerSystem* sys, int points) /* RET 8; `points` is BY VALUE and the leftover is the RETURN value. An order needing more than what is left absorbs everything and stops the pass; a design that costs money asks this->vft[9](sys, (int64)cost) and a refusal SKIPS that order and continues rather than stopping. Removal is a separate sweep afterwards that unlinks every order with conleft <= 0 */ [verified]
|
|
constexpr uint32_t BuildQueue_ProcessTurn = 0x00490d50;
|
|
// thiscall void (ServerSystem* this, int delta, int mode) /* RET 8; ProcessTurn passes -out[2] with mode 0 when the owner has AMine, else mode 3. Mode 3 takes it all from Res; mode 0 splits it proportionally over Res / ARes2 / MRes */ [verified]
|
|
constexpr uint32_t ServerSystem_AdjustResources = 0x00345f30;
|
|
// thiscall int64 (ServerSystem* this, int groupType, int species) /* RET 8; 0 without an owner or when haltv[groupType] is set */ [verified]
|
|
constexpr uint32_t ServerSystem_PopGrowthDelta = 0x00348100;
|
|
// thiscall float (ServerPlayer* p, int groupType, int species, float suit, float factor) /* the growth curve. base = 1 - clamp01(min(|ideal - clamp(suit,0,20)|, SuitTol) / SuitTol) -- there is NO pop/capacity term anywhere; g = clamp01(pow(base, clamp(POPULATION_GROWTH_EXP, 0.01f, 1000))) then x MOD x PopMod x factor x groupdef[+4], each gated on a strict > 0 and each stored back to a float32 */ [verified]
|
|
constexpr uint32_t PopGrowthFraction = 0x00136fb0;
|
|
// thiscall float (ServerSystem* this, int species) /* RET 4; 1.0 when unowned. mod = (bit0 ? 0.8f : 1) - 0.2*bit1 - 0.2*bit2, no clamp; rate = ((|Ideal-Suit| x BYHAZARD + DEATH_RATE) + SRs x BYOUTPUT) x mod, every step narrowed to float32 */ [verified]
|
|
constexpr uint32_t ServerSystem_SlaveDeathRate = 0x0034b110;
|
|
// thiscall void (ServerSystem* this) /* the worst plague's rate is ADDED to the death rate; SLAVES_MIN/MAX_DEATHS are each disabled by ANY negative value; deaths are clamped into [0, adjusted slave count]. Consumes no RNG */ [verified]
|
|
constexpr uint32_t ServerSystem_ProcessSlaves = 0x003537b0;
|
|
// thiscall void (ServerSystem* this) /* consumes no RNG, at depth 1 */ [verified]
|
|
constexpr uint32_t ServerSystem_ProcessPlague = 0x00356a90;
|
|
// thiscall void (ServerSystem* this) /* the ONLY RNG consumer in a colony turn, and its draw count is data-dependent: one RandChance per iteration of a 64-bit rebel counter (0x0074fbe0), a short-circuiting per-species roll loop (0x00753c60), one outcome roll (0x00756350) and one 0.2f continuation roll */ [verified]
|
|
constexpr uint32_t ServerSystem_ProcessRebellion = 0x003583b0;
|
|
// thiscall void (ServerSystem* this) /* consumes no RNG, at depth 1 */ [verified]
|
|
constexpr uint32_t ServerSystem_GrowCivilianPops = 0x00354220;
|
|
// stdcall bool (RNG* this /*ecx, the object*/, float p) /* p <= 0 -> false and p >= 1 -> true, both WITHOUT a draw; otherwise exactly one NextFloat and `r < p` */ [verified]
|
|
constexpr uint32_t RNG_Chance = 0x004e6dd0;
|
|
// thiscall void (ServerPlayer* this, MoraleEvent* ev, ServerSystem* sys) /* the actual apply. 0x00752a10 is only the MoraleEvent constructor; the per-species delta is carried in ev->deltas[species] (int[7] at ev+0x18), not as an argument */ [verified]
|
|
constexpr uint32_t ServerPlayer_AddMoraleEvent = 0x00439d60;
|
|
// thiscall MoraleEvent* (MoraleEvent* this) /* 0x50 bytes: {vptr, ints (+0x10 = event id), vptr2 @+0x14, int deltas[7] @+0x18, std::string name @+0x34} */ [verified]
|
|
constexpr uint32_t MoraleEvent_ctor = 0x00352a10;
|
|
// offset StrategyServer* owner (the RAW base; the RNG accessor uses owner-4) [verified]
|
|
constexpr uint32_t ServerSystem_off_Owner = 0x00000010;
|
|
// offset int Idx [verified]
|
|
constexpr uint32_t ServerSystem_off_Idx = 0x0000005c;
|
|
// offset int Size (1..10); the capacity base is Size * 100000000 as an exact int64 product [verified]
|
|
constexpr uint32_t ServerSystem_off_Size = 0x00000060;
|
|
// offset float Suit [verified]
|
|
constexpr uint32_t ServerSystem_off_Suit = 0x00000064;
|
|
// offset int Res [verified]
|
|
constexpr uint32_t ServerSystem_off_Res = 0x00000068;
|
|
// offset int ARes2 [verified]
|
|
constexpr uint32_t ServerSystem_off_ARes2 = 0x0000006c;
|
|
// offset int MRes [verified]
|
|
constexpr uint32_t ServerSystem_off_MRes = 0x00000070;
|
|
// offset int TRes -- zeroed every turn by ProcessTurn [verified]
|
|
constexpr uint32_t ServerSystem_off_TRes = 0x00000074;
|
|
// offset bool haltv[3] -- growth halt per group; cleared every turn by ProcessTurn [verified]
|
|
constexpr uint32_t ServerSystem_off_haltv = 0x00000078;
|
|
// offset float OutMod [verified]
|
|
constexpr uint32_t ServerSystem_off_OutMod = 0x0000007c;
|
|
// offset int TAcq (turn acquired) [verified]
|
|
constexpr uint32_t ServerSystem_off_TAcq = 0x00000080;
|
|
// offset StarSystem::OutputRates (0x1c): float SRt, SRsc, SRtf, SRi, SRoh, SRs; int SRnr [verified]
|
|
constexpr uint32_t ServerSystem_off_Rates = 0x00000088;
|
|
// offset float SRoh -- the over-harvest slider (Rates + 0x10) [verified]
|
|
constexpr uint32_t ServerSystem_off_SRoh = 0x00000098;
|
|
// offset BuildQueue* [verified]
|
|
constexpr uint32_t ServerSystem_off_BuildQueue = 0x000000a4;
|
|
// offset std::string Name (0x1c, MSVC SSO; capacity word at +0xbc) [verified]
|
|
constexpr uint32_t ServerSystem_off_Name = 0x000000a8;
|
|
// offset bool Abdn [verified]
|
|
constexpr uint32_t ServerSystem_off_Abdn = 0x000000c4;
|
|
// offset int64 Bats2 -- player i's 4-bit battle-recent counter at bits [4i, 4i+4), i < 15 [verified]
|
|
constexpr uint32_t ServerSystem_off_Bats2 = 0x000000f0;
|
|
// offset int64 rcex -- the same shape for the explored/recon-recent counter [verified]
|
|
constexpr uint32_t ServerSystem_off_Rcex = 0x000000f8;
|
|
// offset ServerPlayer* PID (null = unowned) [verified]
|
|
constexpr uint32_t ServerSystem_off_PID = 0x00000100;
|
|
// offset Morale cm (0x20: vptr + int[7]) [verified]
|
|
constexpr uint32_t ServerSystem_off_Morale = 0x0000011c;
|
|
// offset std::vector<StarFleet*> [verified]
|
|
constexpr uint32_t ServerSystem_off_Fleets = 0x0000016c;
|
|
// offset int Pop (imperial) [verified]
|
|
constexpr uint32_t ServerSystem_off_Pop = 0x0000018c;
|
|
// offset float Infra [verified]
|
|
constexpr uint32_t ServerSystem_off_Infra = 0x00000190;
|
|
// offset int pbon -- the pending population bonus (int32, not a float) [verified]
|
|
constexpr uint32_t ServerSystem_off_pbon = 0x00000194;
|
|
// offset float ibon -- the pending infrastructure bonus [verified]
|
|
constexpr uint32_t ServerSystem_off_ibon = 0x00000198;
|
|
// offset Population Pop2 (civilians): {vptr, vector<PopulationGroup> @+4}, 24-byte entries {?, int type @+4, int species @+8, int64 count @+0x10} [verified]
|
|
constexpr uint32_t ServerSystem_off_Pop2 = 0x000001a0;
|
|
// offset Population pbon2 -- the civilian analogue of pbon [verified]
|
|
constexpr uint32_t ServerSystem_off_pbon2 = 0x000001b4;
|
|
// offset IndependenceInfo* indi (int indsp at +4) [verified]
|
|
constexpr uint32_t ServerSystem_off_Indi = 0x000001c8;
|
|
// offset int rbfl -- rebelling-species bitmask [verified]
|
|
constexpr uint32_t ServerSystem_off_rbfl = 0x000001dc;
|
|
// offset int adt[7] -- per-species addiction start turn; 0 means never addicted [verified]
|
|
constexpr uint32_t ServerSystem_off_adt = 0x000001e4;
|
|
// offset uint32 -- bit i is cleared when player i's Bats2 counter reaches 0 [verified]
|
|
constexpr uint32_t ServerSystem_off_BatsMask = 0x000002a0;
|
|
// offset uint32 -- the same for rcex [verified]
|
|
constexpr uint32_t ServerSystem_off_RcexMask = 0x000002a4;
|
|
// offset int TnsOH (turns over-harvesting) [verified]
|
|
constexpr uint32_t ServerSystem_off_TnsOH = 0x000002b8;
|
|
// offset int ntdev -- turns developing; ++ when IsStable, reset to 0 otherwise AND by either bonus-apply helper on a non-home system. This is the second SYSTEMBONUS_MINTURNS gate, not rbtn [verified]
|
|
constexpr uint32_t ServerSystem_off_ntdev = 0x000002c4;
|
|
// offset int rbtn [verified]
|
|
constexpr uint32_t ServerSystem_off_rbtn = 0x000002cc;
|
|
// offset int ModCount (the turn counter), relative to the RAW server base a ServerSystem's +0x10 points at [verified]
|
|
constexpr uint32_t StrategyServer_off_ModCount = 0x00000008;
|
|
// offset std::vector<ServerPlayer*> (begin @+0x50, end @+0x54); numPlayers = (end-begin)>>2 /* TWO BASES: every StrategyServer_off_* here is relative to the RAW base a ServerSystem's owner word (+0x10) points at. The class's own methods receive a base FOUR BYTES LOWER in ECX (0x007437f0 does owner-4), so a hook on MoveFleet or ProcessFleetMovement must add 4 to `this` before applying these */ [verified]
|
|
constexpr uint32_t StrategyServer_off_Players = 0x00000050;
|
|
// offset std::vector<StarFleet*> (begin @+0x60, end @+0x64) -- ALL fleets of ALL players, one flat global list. B4 live: the earlier 0x64 was the Ghidra-base number transcribed as a raw-base one; it made ProcessFleetMovement enumerate the vector's spare capacity instead of its elements [verified]
|
|
constexpr uint32_t StrategyServer_off_Fleets = 0x00000060;
|
|
// offset 16-bucket hash of entity id -> object, hashed by (key & 0xF); 0x008b9240 is the lookup [verified]
|
|
constexpr uint32_t StrategyServer_off_EntityHash = 0x00000080;
|
|
// offset Mars::RNG* relative to the RAW base (== the -4-adjusted base's +0x16c) [verified]
|
|
constexpr uint32_t StrategyServer_off_RNGPtr = 0x00000168;
|
|
// offset std::set<pair<FleetId,LocationId>> cleared at the head of ProcessFleetMovement [verified]
|
|
constexpr uint32_t StrategyServer_off_ArrivedSet = 0x00000200;
|
|
// offset std::set<pair<FleetId,LocationId>> that MoveFleet fills for fleets still in motion; OnFleetArrived takes the set difference [verified]
|
|
constexpr uint32_t StrategyServer_off_InMotionSet = 0x00000210;
|
|
// offset int id (also the entity-hash key) [verified]
|
|
constexpr uint32_t StarFleet_off_Id = 0x00000004;
|
|
// offset the object whose +0x80 holds the entity hash the waypoint target is resolved in [verified]
|
|
constexpr uint32_t StarFleet_off_Galaxy = 0x00000010;
|
|
// offset Vector3 Pos [verified]
|
|
constexpr uint32_t StarFleet_off_Pos = 0x00000018;
|
|
// offset Vector3 PrvPos -- set to the ENTRY position when the fleet moved [verified]
|
|
constexpr uint32_t StarFleet_off_PrvPos = 0x0000004c;
|
|
// offset ServerPlayer* owner [verified]
|
|
constexpr uint32_t StarFleet_off_PID = 0x00000058;
|
|
// offset Location*; kind at Location+0x14 (0 system, 1 fleet, 2 point) [verified]
|
|
constexpr uint32_t StarFleet_off_Location = 0x000000a0;
|
|
// offset std::vector<StarShip*> (begin @+0xa4, end @+0xa8) [verified]
|
|
constexpr uint32_t StarFleet_off_Ships = 0x000000a4;
|
|
// offset int16 -- SIGNED, summed into the owner's gate traffic [verified]
|
|
constexpr uint32_t StarFleet_off_GateTraffic = 0x000000c0;
|
|
// offset std::vector<Waypoint> _Myfirst (proxy @+0xc4, last @+0xcc, end @+0xd0) [verified]
|
|
constexpr uint32_t StarFleet_off_Waypoints = 0x000000c8;
|
|
// offset float FPsp2 -- the fleet's strategic speed [verified]
|
|
constexpr uint32_t StarFleet_off_Speed = 0x000000d8;
|
|
// offset Vector3 -- set after every pass to the current waypoint target's position. The notes called this FPogn2; by the FlightPlan layout it is FPdpos [verified]
|
|
constexpr uint32_t StarFleet_off_DestPos = 0x000000ec;
|
|
// offset uint32; bit 0x100 = held this turn (cleared for every fleet at the end of ProcessFleetMovement), bit 0x2 cleared in the destination pass, bit 0x1 set by MoveFleet when the fleet moved [verified]
|
|
constexpr uint32_t StarFleet_off_Flags = 0x0000010c;
|
|
// offset sizeof(Waypoint) -- pinned by the divide-by-28 reciprocal multiply in the iterator arithmetic [verified]
|
|
constexpr uint32_t Waypoint_stride = 0x0000001c;
|
|
// offset int -- the destination entity id [verified]
|
|
constexpr uint32_t Waypoint_off_Target = 0x00000004;
|
|
// offset int -- 2 node line, 3 node route, 4 gate teleport, 5 probabilistic jump, otherwise a straight run [verified]
|
|
constexpr uint32_t Waypoint_off_Type = 0x00000008;
|
|
// offset uint64 flag pair; bit 0x1000 exempts the ship from movement fuel and marks it a tanker [verified]
|
|
constexpr uint32_t StarShip_off_Flags = 0x00000018;
|
|
// offset float Range -- remaining strategic range [verified]
|
|
constexpr uint32_t StarShip_off_Range = 0x00000020;
|
|
// offset float -- the cap the refuel helper clamps Range to [verified]
|
|
constexpr uint32_t StarShip_off_MaxRange = 0x00000078;
|
|
// offset ServerSystem* -- the bonus-apply helpers skip the ntdev reset for this system [verified]
|
|
constexpr uint32_t ServerPlayer_off_HomeSystem = 0x0000002c;
|
|
// offset int GTraf -- assigned (not accumulated) once per turn [verified]
|
|
constexpr uint32_t ServerPlayer_off_GateTraffic = 0x0000014c;
|
|
// offset float CstE -- the probabilistic jump's efficiency [verified]
|
|
constexpr uint32_t ServerPlayer_off_CstE = 0x00000154;
|
|
// offset float CstT -- the probabilistic jump's threshold [verified]
|
|
constexpr uint32_t ServerPlayer_off_CstT = 0x00000158;
|
|
// data int** -- the GlobalConst pointer slot; the storage word is *slot [verified]
|
|
constexpr uint32_t g_ptr_SYSTEMBONUS_MINTURNS = 0x006eca10;
|
|
// data float** -- pointer slot [verified]
|
|
constexpr uint32_t g_ptr_SYSTEMBONUS_POPBONUS = 0x006eca18;
|
|
// data float** -- pointer slot [verified]
|
|
constexpr uint32_t g_ptr_SYSTEMBONUS_INFRABONUS = 0x006eca20;
|
|
// data float** -- pointer slot [verified]
|
|
constexpr uint32_t g_ptr_SYSTEMBONUS_POPBONUS_INC = 0x006eca38;
|
|
// data float** -- pointer slot [verified]
|
|
constexpr uint32_t g_ptr_SYSTEMBONUS_INFRABONUS_INC = 0x006eca40;
|
|
// data int** -- pointer slot (image default 10) [verified]
|
|
constexpr uint32_t g_ptr_ADDICTION_PHASE2_START = 0x006eca58;
|
|
// data int** -- pointer slot (image default 15) [verified]
|
|
constexpr uint32_t g_ptr_ADDICTION_PHASE3_START = 0x006eca60;
|
|
// data float -- 0.05f, a hard-coded literal in .data, NOT a GlobalConst; both ServerSystem::ProcessTurn calls to the independent pop-drift helper pass it [verified]
|
|
constexpr uint32_t g_IndependentPopDriftRate = 0x006eca80;
|
|
// data float** -- pointer slot; storage 0x00b212cc (shipped value 2) [verified]
|
|
constexpr uint32_t g_ptr_STUTTER_SYSTEM_INFLUENCE_RADIUS = 0x006ebc44;
|
|
// data float** -- pointer slot; storage 0x00b212d0 (shipped value 0.33) [verified]
|
|
constexpr uint32_t g_ptr_STUTTER_MIN_SPEED = 0x006ebc48;
|
|
// data float** -- pointer slot; storage 0x00b212d4 (shipped value 0.33). NOTE min == max in the shipped data, so the stutter ramp collapses to a constant 0.33x inside any influence sphere [verified]
|
|
constexpr uint32_t g_ptr_STUTTER_MAX_SPEED = 0x006ebc4c;
|
|
// thiscall EventStorage* (ServerPlayer* this) /* whole body: lea eax,[ecx+0x29c]; ret. No stack args, plain RET */ [verified]
|
|
constexpr uint32_t ServerPlayer_GetEventStorage = 0x0040db00;
|
|
// thiscall void* (ServerPlayer* this) /* eax = [this+8] ? [this+8]-4 : 0. StrategyServer primary base; turn = *(int*)(result+0x0c) */ [verified]
|
|
constexpr uint32_t ServerPlayer_GetServer = 0x0040e320;
|
|
// thiscall int (EventStorage* this, std::string summary /*BY VALUE 0x1c -> EvDsc*/, std::string message /*BY VALUE 0x1c -> EvMsg*/, void* obj, Vector3* pos, int turn, const char* img, int act) RET 0x4c. Returns the event id (a duplicate's id if one already exists in the turn bucket). Callee frees both by-value string buffers. img==NULL -> "". act==0 && obj==NULL && pos==NULL -> stored EvAct becomes 2. EvLoc = obj ? obj[+4] : 0; EvPos = obj ? obj[+0x18..0x20] : pos ? *pos : FLT_MAX triple. 161 call sites in 113 functions: this is the whole simulation's event API [verified]
|
|
constexpr uint32_t EventStorage_PostEvent = 0x004862b0;
|
|
// thiscall TurnEvents* (EventStorage* this, int turn) RET 4. Linear scan with NO early exit, so it returns the LAST bucket whose EvTurn == turn; otherwise appends a new bucket (ctor 0x00884cb0, vtable 0x00a0f07c) and sets its EvTurn [verified]
|
|
constexpr uint32_t EventStorage_GetOrCreateTurnBucket = 0x00485380;
|
|
// thiscall PlayerEvent* (EventStorage* this, TurnEvents* bucket, PlayerEvent* candidate) RET 8. NULL bucket -> 0. Match requires EvAct, EvLoc, all three EvPos floats (fucompp), EvMsg and EvImg to be equal. EvDsc is NOT compared [verified]
|
|
constexpr uint32_t EventStorage_FindDuplicate = 0x00425d40;
|
|
// thiscall void (EventStorage* this, int turn) RET 4. Cutoff = turn - 0x32 (50), a code constant. Shifts from the LAST bucket of the leading run with EvTurn < cutoff, so it erases n-1 of n leading stale buckets: one stale bucket always survives and a single leading stale bucket is never removed [verified]
|
|
constexpr uint32_t EventStorage_PruneOldTurns = 0x00479eb0;
|
|
// thiscall void (EventStorage* this, IStreamable* s) RET 4. Order: EvNxID (int, tag 0x00a2bd90), then nested collection "Events" (tag 0x00a2bda0, descriptor vtable 0x00a2da8c) over this+4 [verified]
|
|
constexpr uint32_t EventStorage_Read = 0x00425cc0;
|
|
// thiscall void (TurnEvents* this, IStreamable* s) RET 4. Order: EvTurn (int, tag 0x00a2bd98), then nested collection "Events" (descriptor vtable 0x00a2da7c) over this+8 [verified]
|
|
constexpr uint32_t TurnEvents_Write = 0x00425bb0;
|
|
// thiscall void (TurnEvents* this, IStreamable* s) RET 4. Same field order as TurnEvents_Write [verified]
|
|
constexpr uint32_t TurnEvents_Read = 0x00425c40;
|
|
// thiscall PlayerEvent* (PlayerEvent* this) RET 0. vptr=0x00a21958; EvEID=EvLoc=EvAct=EvCID=0; the three std::strings = "" (0x009e100c); EvPos = the Vector3 global at 0x00af0dc8 = {FLT_MAX, FLT_MAX, FLT_MAX} (0x7f7fffff x3, NOT infinity) [verified]
|
|
constexpr uint32_t PlayerEvent_ctor = 0x0044ee30;
|
|
// thiscall void (PlayerEvent* this, IStreamable* s) RET 4. vftable slot 1. Field order on the wire: EvEID(+4) EvDsc(+8) EvMsg(+0x24) EvImg(+0x50) EvLoc(+0x40) EvPos(+0x44) EvAct(+0x6c) EvCID(+0x70) [verified]
|
|
constexpr uint32_t PlayerEvent_Serialize = 0x00425970;
|
|
// thiscall call site: EventStorage::PostEvent for EVENT_RESEARCH_OVERBUDGET. Guard at 0x005879e9: !wasDone && nowDone && owner, inside the completion-roll-FAILED branch (chance < draw). Sets TechNode.flag(+0x2c)=2 at 0x00587ba3. EvAct=1, obj=NULL, pos=NULL [verified]
|
|
constexpr uint32_t ProcessResearch_PostEventOverbudget = 0x00187b97;
|
|
// thiscall call site: EventStorage::PostEvent for EVENT_TECHS_UNLOCKED, once after the per-node loop, if any node has state==2 and turnAvailable==currentTurn. EvAct=1, obj=NULL, pos=NULL [verified]
|
|
constexpr uint32_t ProcessResearch_PostEventTechsUnlocked = 0x00187ff4;
|
|
// thiscall call site: EventStorage::PostEvent for EVENT_RESEARCH_COMPLETE / _UNDERBUDGET. Guarded by !silent ([ebp+0xc]==0). Message is _snprintf'd (0x008c8eb0) into a 0x100-byte buffer, so >255 chars truncate. EvAct=1 [verified]
|
|
constexpr uint32_t OnTechResearched_PostEventComplete = 0x004919b5;
|
|
// thiscall call site: EventStorage::PostEvent for EVENT_TEMPERANCE. Guarded by !silent AND by the 'a system was cured' local at [ebp-0x189]. Pushed act=0 with obj=pos=NULL, so the STORED EvAct is 2 [verified]
|
|
constexpr uint32_t OnTechResearched_PostEventTemperance = 0x00492427;
|
|
// thiscall call site: EventStorage::PostEvent for EVENT_NO_RESEARCH. Condition at 0x0089162a: ResT(+0x294)==NULL && ListAvailableTechs(0x00584e50, turn, INT_MAX, 1) returned empty && TechTree 0x0057da90 != 0. EvAct=1 [verified]
|
|
constexpr uint32_t ServerPlayer_ProcessTurn_PostEventNoResearch = 0x0049168c;
|
|
// data const char* "Research completed at %d of %d (%.1f%%). (Odds: %.2f, Roll: %.2f)\n" -- log line on the completion-roll-SUCCEEDED branch, 0x00587977 [verified]
|
|
constexpr uint32_t TechTree_ProcessResearch_LogFormat = 0x006007a8;
|
|
// offset EventStorage ServerPlayer::Events -- embedded, size 0x1c. Confirmed by ServerPlayer_GetEventStorage [verified]
|
|
constexpr uint32_t ServerPlayer_off_Events = 0x0000029c;
|
|
// offset std::vector<TurnEvents> _Myfirst (element stride 0x18; _Mylast +0x08, _Myend +0x0c, _Alval +0x10) [verified]
|
|
constexpr uint32_t EventStorage_off_Events = 0x00000004;
|
|
// offset int EvNxID -- next event id. Starts at 0; PostEvent promotes 0->1 on the first post, then post-increments. ServerPlayer+0x2b0 [verified]
|
|
constexpr uint32_t EventStorage_off_EvNxID = 0x00000014;
|
|
// offset sizeof(EventStorage) [verified]
|
|
constexpr uint32_t EventStorage_sizeof = 0x0000001c;
|
|
// offset int EvTurn (after the vptr at +0) [verified]
|
|
constexpr uint32_t TurnEvents_off_EvTurn = 0x00000004;
|
|
// offset std::vector<PlayerEvent> _Myfirst (stride 0x74; _Mylast +0x0c, _Myend +0x10, _Alval +0x14) [verified]
|
|
constexpr uint32_t TurnEvents_off_Events = 0x00000008;
|
|
// offset sizeof(TurnEvents); the outer vector's stride, from the /24 divide at 0x008853b3 [verified]
|
|
constexpr uint32_t TurnEvents_sizeof = 0x00000018;
|
|
// offset int EvEID (after the vptr at +0) [verified]
|
|
constexpr uint32_t PlayerEvent_off_EvEID = 0x00000004;
|
|
// offset std::string EvDsc (summary / title), 0x1c bytes [verified]
|
|
constexpr uint32_t PlayerEvent_off_EvDsc = 0x00000008;
|
|
// offset std::string EvMsg (body), 0x1c bytes [verified]
|
|
constexpr uint32_t PlayerEvent_off_EvMsg = 0x00000024;
|
|
// offset int EvLoc (object id, or 0) [verified]
|
|
constexpr uint32_t PlayerEvent_off_EvLoc = 0x00000040;
|
|
// offset float[3] EvPos (default FLT_MAX x3) [verified]
|
|
constexpr uint32_t PlayerEvent_off_EvPos = 0x00000044;
|
|
// offset std::string EvImg (event-type name, e.g. "EVENT_RESEARCH_OVERBUDGET"), 0x1c bytes [verified]
|
|
constexpr uint32_t PlayerEvent_off_EvImg = 0x00000050;
|
|
// offset int EvAct [verified]
|
|
constexpr uint32_t PlayerEvent_off_EvAct = 0x0000006c;
|
|
// offset int EvCID -- ctor sets 0 and PostEvent never writes it [verified]
|
|
constexpr uint32_t PlayerEvent_off_EvCID = 0x00000070;
|
|
// offset sizeof(PlayerEvent) = 116. Two independent confirmations: the /116 divide at 0x00825d5f and PostEvent's 'mov [_Mylast-0x70], id' writing EvEID at element+4 [verified]
|
|
constexpr uint32_t PlayerEvent_sizeof = 0x00000074;
|
|
// offset prune cutoff = turn - 50; a code constant at 0x00879ec3, not config [verified]
|
|
constexpr uint32_t EventStorage_PruneWindowTurns = 0x00000032;
|
|
// data void** -- PlayerEvent vftable. slot0 dtor 0x007694d0, slot1 Serialize 0x00825970, slot2 0x00825ab0. RTTI locator 0x00a80a7c [verified]
|
|
constexpr uint32_t g_vft_PlayerEvent = 0x00621958;
|
|
// data void** -- TurnEvents vftable (bucket ctor 0x00884cb0); slot0 is the virtual dtor the pruner calls [verified]
|
|
constexpr uint32_t g_vft_TurnEvents = 0x0060f07c;
|
|
// data const char[12][8] -- stride 8: EvEID EvNxID EvTurn Events EvPos EvLoc EvMsg EvImg EvDsc EvCID EvAct sasc [verified]
|
|
constexpr uint32_t g_EventTagTable = 0x0062bd88;
|
|
// data float[3] = {0x7f7fffff, 0x7f7fffff, 0x7f7fffff} = FLT_MAX. PlayerEvent's default EvPos. NOT infinity [verified]
|
|
constexpr uint32_t g_Vector3_Invalid = 0x006f0dc8;
|
|
// data const char* "EVENT_RESEARCH_OVERBUDGET" -- EvImg pushed at 0x00587b24 [verified]
|
|
constexpr uint32_t g_str_EVENT_RESEARCH_OVERBUDGET = 0x0060078c;
|
|
// data const char* "EVENT_RESEARCH_COMPLETE" -- EvImg for progress/cost >= 0.8 [verified]
|
|
constexpr uint32_t g_str_EVENT_RESEARCH_COMPLETE = 0x00633368;
|
|
// data const char* "EVENT_RESEARCH_UNDERBUDGET" -- EvImg for progress/cost < 0.8 [verified]
|
|
constexpr uint32_t g_str_EVENT_RESEARCH_UNDERBUDGET = 0x00633380;
|
|
// data const char* "EVENT_TEMPERANCE" -- EvImg at 0x008923ae; posted with act=0 so the stored EvAct is 2 [verified]
|
|
constexpr uint32_t g_str_EVENT_TEMPERANCE = 0x00633340;
|
|
// data const char* "EVENT_TECHS_UNLOCKED" (length 0x14 pushed at 0x00587eb6) [verified]
|
|
constexpr uint32_t g_str_EVENT_TECHS_UNLOCKED = 0x00600774;
|
|
// data const char* "EVENT_NO_RESEARCH" -- EvImg at 0x0089168c [verified]
|
|
constexpr uint32_t g_str_EVENT_NO_RESEARCH = 0x0063332c;
|
|
// data const char** -> slot 0x00ae48e0; Strings.csv key EVENTSUM_RESEARCH_OVERBUDGET = "Research Over Budget" [verified]
|
|
constexpr uint32_t g_ptr_EVENTSUM_RESEARCH_OVERBUDGET = 0x006e48e4;
|
|
// data const char** -> slot 0x00ae48e8; key EVENTMSG_RESEARCH_OVERBUDGET = "Research for %s has gone overbudget." [verified]
|
|
constexpr uint32_t g_ptr_EVENTMSG_RESEARCH_OVERBUDGET = 0x006e48ec;
|
|
// data const char** -> slot 0x00ae48f0; key EVENTSUM_UNLOCKEDTECHS = "New Technologies Available" [verified]
|
|
constexpr uint32_t g_ptr_EVENTSUM_UNLOCKEDTECHS = 0x006e48f4;
|
|
// data const char** -> slot 0x00ae48f8; key EVENTMSG_UNLOCKEDTECHS [verified]
|
|
constexpr uint32_t g_ptr_EVENTMSG_UNLOCKEDTECHS = 0x006e48fc;
|
|
// data const char** -> slot 0x00af09e8; key EVENTSUM_RESEARCH_COMPLETE = "Research Complete" [verified]
|
|
constexpr uint32_t g_ptr_EVENTSUM_RESEARCH_COMPLETE = 0x006f09ec;
|
|
// data const char** -> slot 0x00af09f0; key EVENTMSG_RESEARCH_COMPLETE = "Tech %s has been acquired" [verified]
|
|
constexpr uint32_t g_ptr_EVENTMSG_RESEARCH_COMPLETE = 0x006f09f4;
|
|
// data const char** -> slot 0x00af09f8; key EVENTSUM_RESEARCH_UNDERBUDGET = "Research Breakthrough!" [verified]
|
|
constexpr uint32_t g_ptr_EVENTSUM_RESEARCH_UNDERBUDGET = 0x006f09fc;
|
|
// data const char** -> slot 0x00af0a00; key EVENTMSG_RESEARCH_UNDERBUDGET [verified]
|
|
constexpr uint32_t g_ptr_EVENTMSG_RESEARCH_UNDERBUDGET = 0x006f0a04;
|
|
// data const char** -> slot 0x00af0a88; key EVENTSUM_ADDICTION_TEMPERENCE (shipped misspelling) [verified]
|
|
constexpr uint32_t g_ptr_EVENTSUM_ADDICTION_TEMPERENCE = 0x006f0a8c;
|
|
// data const char** -> slot 0x00af0a90; key EVENTMSG_ADDICTION_TEMPERENCE [verified]
|
|
constexpr uint32_t g_ptr_EVENTMSG_ADDICTION_TEMPERENCE = 0x006f0a94;
|
|
// data double 0.800000011920929 = (double)0.8f. progress/cost >= this -> EVENT_RESEARCH_COMPLETE, else EVENT_RESEARCH_UNDERBUDGET; the same constant gates TechNode.flag=0 in ProcessResearch [verified]
|
|
constexpr uint32_t g_dbl_ResearchUnderbudgetThreshold = 0x005e20c8;
|
|
// data double 0.0 -- the research completion draw is NextFloat()*(1.0-this)+this, so it is a plain NextFloat() [verified]
|
|
constexpr uint32_t g_dbl_ResearchRollBias = 0x005e1e68;
|
|
// cdecl float (Vec3* out, const Vec3* in) /* normalises in-place-capable (MoveFleet passes the same pointer twice) and RETURNS THE LENGTH. FOUR float32 narrowings in this order: sumsq = float32(x*x+y*y+z*z) (products/adds stay in the x87 53-bit registers, only the sum is stored to a dword and reloaded); len = float32(sqrt(sumsq)); inv = float32(1.0/len) -- a RECIPROCAL, stored to a dword and reloaded, then MULTIPLIED through, NOT three divides; out.c = float32(in.c * inv). Zero branch: !(len > 2^-23, the float at 0x009e1ef8) => out = {0,0,0} and it returns with an EMPTY x87 stack, i.e. no return value at all (original bug, only reachable on a zero-length vector). 123 callers. */ [verified]
|
|
constexpr uint32_t Mars_Vec3_Normalize = 0x00022520;
|
|
// cdecl float (const Vec3* v) /* float32(sqrt(float32(x*x+y*y+z*z))) -- the same two narrowings as the first half of Mars_Vec3_Normalize */ [verified]
|
|
constexpr uint32_t Mars_Vec3_Length = 0x000224b0;
|
|
// cdecl float (const Vec3* v) /* float32(x*x+y*y+z*z), one narrowing on the sum */ [verified]
|
|
constexpr uint32_t Mars_Vec3_LengthSquared = 0x000224f0;
|
|
// data const float = 0x34000000 = 2^-23 = 1.1920928955078125e-07 /* Mars_Vec3_Normalize zeroes the direction when !(len > this) */ [verified]
|
|
constexpr uint32_t Mars_Vec3_NormaliseEpsilon = 0x005e1ef8;
|
|
// site site inside StrategyServer::MoveFleet /* the straight-run leg. Each delta dest.c - fleet.pos.c is computed on the x87 stack and STORED BACK TO A FLOAT32 SLOT before Mars_Vec3_Normalize(&v, &v) is called on it in place; that single call returns the leg DISTANCE and leaves the float32 unit direction in the same slots. The distance is never recomputed. */ [verified]
|
|
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. 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;
|
|
// offset std::string (save tag `otch`, the tech name) at ObservedTech+0x0c, 0x1c bytes, spanning +0x0c..+0x27. MSVC layout relative to the string object: _Bx[16] @+0x00, _Mysize @+0x10, _Myres @+0x14, _Alval @+0x18 -- i.e. ObservedTech+0x1c is the length, +0x20 the capacity, +0x24 the empty-allocator word (never read or written by anything). Evidence: ObservedTech_ctor 0x008562a0 writes [elem+0x0c]=0, [elem+0x1c]=0, [elem+0x20]=0xf; RecordObservedTech's search reads [elem+0x1c] as the length and calls compare with this=elem+0x0c; the post-append assign uses lea ecx,[_Mylast-0x20]. CORRECTION (lane S 2026-09-08): an earlier revision called this string 0x18 bytes and listed +0x24 as an unaccounted data field. It is not one -- three independent whole-object enumerations skip +0x24 entirely: ObservedTech_ctor 0x008562a0, ObservedTech_copy_ctor 0x0079a184, and ObservedTech_Write 0x00817cf0 (which serialises +0x04,+0x06,+0x08,+0x0c,+0x28 and nothing else). sizeof(std::string)=0x1c holds binary-wide; see std_string_sizeof. [verified]
|
|
constexpr uint32_t ObservedTech_off_Name = 0x0000000c;
|
|
// thiscall void (this, ServerPlayer* observer, ?, std::string* techName) -- appends to observer->otch. Linear-searches observer->otch (ServerPlayer+0x274) with stride 0x2c comparing each element's name string; if not found, default-constructs an ObservedTech on the stack (0x008562a0) and push_backs it (0x007b7320 @0x007ba288), then writes otnF (+0x04) and otnL (+0x06), both 16-bit, from the same source word param_1+0xc -- i.e. first-observed turn == last-observed turn on the first sighting, which is what the tag names now confirm. DIRECT CALLEE of ServerPlayer::OnTechResearched 0x00891790; also called from 0x007be228, 0x007be4e1, 0x007be535. This is the append lane P could not find. DE-DUPLICATING: appends only when no existing element carries that tech name, so a reimplementation must not naively push_back on re-observation. [verified]
|
|
constexpr uint32_t RecordObservedTech = 0x003ba1a0;
|
|
// thiscall void (std::vector<ObservedTech>* this, ObservedTech* value) RET 4. Stride 0x2c. Calls vector_44B_grow 0x007b5820 when _Mylast==_Myend -- the realloc that moves all three vector words. Exactly one caller (RecordObservedTech), so this instantiation is not COMDAT-ambiguous. [verified]
|
|
constexpr uint32_t vector_ObservedTech_push_back = 0x003b7320;
|
|
// thiscall std::vector<ObservedTech>& operator=(const std::vector<ObservedTech>&) RET 4. Both callers pass ServerPlayer+0x274 (0x00878091 in the settings copy-out, 0x00892507 in the copy-in). [verified]
|
|
constexpr uint32_t vector_ObservedTech_assign = 0x00472380;
|
|
// thiscall Game::ObservedTech* (ObservedTech* this) -- default ctor. Writes exactly: vptr 0x00a2439c at +0x00; dword 0 at +0x04 (otnF+otnL zeroed together); BYTE 0 at +0x08 (`mov [esi+0x8],bl`, 0x008562f4 -- odet is a bool, not an int); the empty string at +0x0c (_Buf[0]=0, _Mysize=0, _Myres=0xf, then assign("") 0x00425550); dword 0 at +0x28 (owith). It never touches +0x24 -- that word is the string's _Alval. [verified]
|
|
constexpr uint32_t ObservedTech_ctor = 0x004562a0;
|
|
// thiscall void Game::ObservedTech::Write(Mars::Stream* s) RET 4. Vftable 0x00a2439c slot [2]. Serialises the whole object, in order and with nothing else: `otnF` = movzx word [this+0x04] via stream vft+0x24 (int); `otnL` = movzx word [this+0x06] via vft+0x24; `odet` = WriteBool 0x008b9c20 (&this[+0x08]); `otch` = WriteString 0x008b9d70 (&this[+0x0c]); `owith` = [this+0x28] via vft+0x24. THIS IS THE MEMBER MAP: there is no field at +0x24. Tag pointers 0x00a2b38c/0x00a2b384/0x00a2b36c/0x00a2b3e8/0x00a2b364. [verified]
|
|
constexpr uint32_t ObservedTech_Write = 0x00417cf0;
|
|
// thiscall void Game::ObservedTech::Read(Mars::Stream* s) RET 4. Vftable 0x00a2439c slot [1]. Mirror of ObservedTech_Write: `otnF`/`otnL` through stream vft+0x10 (int-by-ref) stored back as 16-bit (`mov word [ebx],ax`) at +0x04/+0x06, `odet` = ReadBool 0x008b9c00 (&this[+0x08]), `otch` = ReadString 0x008b9d90 (&this[+0x0c]), `owith` at +0x28 (reached as `add edi,0x28`). [verified]
|
|
constexpr uint32_t ObservedTech_Read = 0x00417c40;
|
|
// thiscall ObservedTech* (ObservedTech* this, int flags) RET 4. Vftable 0x00a2439c slot [0], scalar deleting dtor. Inlines ~basic_string on the name at +0x0c (`cmp [esi+0x20],0x10` -> operator delete [esi+0x0c], then _Tidy: [esi+0x20]=0xf, [esi+0x1c]=0, byte [esi+0x0c]=0), restores the base vptr 0x009e22bc, and frees `this` when flags&1. The string is the ONLY member needing destruction -- confirming there is no second string or owned pointer in the element. [verified]
|
|
constexpr uint32_t ObservedTech_dtor = 0x00393610;
|
|
// site site inside the vector<ObservedTech> uninitialised-copy helper FUN_0079a150(&_Alval, dest, src). The inlined copy constructor writes vptr 0x00a2439c, `mov word [dst+0x04],[src+0x04]`, `mov word [dst+0x06],[src+0x06]`, `mov byte [dst+0x08],[src+0x08]`, the string at +0x0c (via basic_string::assign 0x00425430), and `mov [dst+0x28],[src+0x28]`. Nothing is copied at +0x24 -- second independent proof that +0x24 belongs to the 0x1c string, not to a data member. [verified]
|
|
constexpr uint32_t ObservedTech_copy_ctor = 0x0039a184;
|
|
// offset uint16 otnF -- turn the tech was first observed. Widened to int on disk by stream vft+0x24. Written together with otnL from one source word at RecordObservedTech 0x007ba2b0. [verified]
|
|
constexpr uint32_t ObservedTech_off_TurnFirst = 0x00000004;
|
|
// offset uint16 otnL -- turn the tech was last observed. Widened to int on disk. Written at RecordObservedTech 0x007ba2c6 from the same source word as otnF. [verified]
|
|
constexpr uint32_t ObservedTech_off_TurnLast = 0x00000006;
|
|
// offset bool odet -- ONE BYTE (ctor `mov [esi+0x8],bl` 0x008562f4; copy-ctor `mov byte` 0x0079a1a0), serialised through WriteBool/ReadBool, so on disk it is 1 byte + 3 NUL joint padding. Note the save reader types `odet` as int; for a 4-character tag a bool item and an int item are the same 12 bytes and the value is identical, so that is benign, not a parse error. [verified]
|
|
constexpr uint32_t ObservedTech_off_Detected = 0x00000008;
|
|
// offset int owith -- last member of the element; serialised through stream vft+0x24 with tag 0x00a2b364. +0x28..+0x2b is the tail of the 0x2c-byte element, which is why sizeof is 0x2c with no padding slack. [verified]
|
|
constexpr uint32_t ObservedTech_off_With = 0x00000028;
|
|
// thiscall void Game::ObservedWeapon::Write(Mars::Stream* s) RET 4. Byte-for-byte the same shape as ObservedTech_Write with tag `owep` (0x00a2b3f0) in place of `otch`: otnF u16 @+0x04, otnL u16 @+0x06, odet bool @+0x08, owep std::string (0x1c) @+0x0c, owith int @+0x28. Reader is 0x00817b10. Independent second instance of the same 0x2c element shape. [verified]
|
|
constexpr uint32_t ObservedWeapon_Write = 0x00417bc0;
|
|
// constant sizeof(std::basic_string<char>) = 0x1c (28) binary-wide, ONE layout only: _Bx (16-byte SSO union, char[16] or char*) @+0x00, _Mysize @+0x10, _Myres @+0x14, _Alval (empty allocator) @+0x18. The allocator word is trailing, never read and never written -- the same allocator-last shape as this build's std::vector {_Myfirst,_Mylast,_Myend,_Alval} = 0x10. Evidence: (a) Stream::WriteString 0x008b9d76 `cmp [str+0x14],0x10` / `mov eax,[str]` and basic_string::assign 0x00425550 (_Mysize +0x10, _Myres +0x14) fix the field offsets; (b) the vector<std::string> scan loop FUN_00699bd0 advances `add esi,0x1c` (0x00699c29) while doing the SSO test at [esi+0x14]/[esi] -- element base == string base, so the stride IS the sizeof; (c) PostEvent 0x008862b0 takes two by-value strings at [ebp+0x08] and [ebp+0x24] and RET 0x4c = 2*0x1c + 5*4; (d) a whole-binary sweep of the Stream string helpers recovered 65 std::string members off a non-stack base across all serializers -- ZERO have any sibling member inside (N, N+0x1c), and 51 of the 52 that have a next member have it at exactly N+0x1c (the one exception, StrategyServer KeyPath @+0x134, is +0x20 on the Write side and +0x1c on the Read side, i.e. the writer skips a member). There is no 0x18 instantiation, no empty-base variant and no game-local string class. [verified]
|
|
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;
|
|
// thiscall void (Game::StrategyAIAgent::Streamable* this, Mars::Stream* s) // the body of every `Player.<id>.AIAgent` CD block. 36 wire items, NO conditionals: the only `if` the decompiler shows around `lnat` is an inlined std::vector destructor whose operator delete is marked noreturn, and both paths converge at 0x006c72e8. The agent object is *(this+4) [verified]
|
|
constexpr uint32_t Game_StrategyAIAgent_Streamable_Write = 0x002c6f00;
|
|
// thiscall bool (Game::StrategyAIAgent::Streamable* this, Mars::Stream* s) [verified]
|
|
constexpr uint32_t Game_StrategyAIAgent_Streamable_Read = 0x002c8fd0;
|
|
// thiscall void (Game::StrategyAIAgent::DesignNameGen* this, Mars::Stream* s) // `dnnc` = (this+8 - this+4)/0x24, then dnnc x { dnid: StreamableHelper<ShipSectionID> at elem+0, dnnm: WriteString at elem+8 }. Element stride 0x24 = ShipSectionID(8) + Mars::String(0x1c) [verified]
|
|
constexpr uint32_t Game_StrategyAIAgent_DesignNameGen_Write = 0x002c77a0;
|
|
// thiscall bool (Game::StrategyAIAgent::DesignNameGen* this, Mars::Stream* s) [verified]
|
|
constexpr uint32_t Game_StrategyAIAgent_DesignNameGen_Read = 0x002ccc70;
|
|
// thiscall void (Game::AISituation* this, Mars::Stream* s) // AISitSecs = AIWeightMap<ShipSectionID> at this+0, AISitWeapFams = AIWeightMap<StreamableEnum<WeaponFamilyID>> at this+0x10. sizeof >= 0x20 [verified]
|
|
constexpr uint32_t Game_AISituation_Write = 0x0029a220;
|
|
// thiscall bool (Game::AISituation* this, Mars::Stream* s) [verified]
|
|
constexpr uint32_t Game_AISituation_Read = 0x0029a160;
|
|
// thiscall void (Game::AISystem* this, Mars::Stream* s) // writes ONE `AISysSit` frame and nothing else, and the AISituation it writes is constructed empty on the stack -- `this` is never read. Every AISys body on disk is therefore two empty weight maps regardless of game state [verified]
|
|
constexpr uint32_t Game_AISystem_Write = 0x002bda30;
|
|
// thiscall bool (Game::AISystem* this, Mars::Stream* s) [verified]
|
|
constexpr uint32_t Game_AISystem_Read = 0x002bd960;
|
|
// thiscall void (Game::AttribMap* this, Mars::Stream* s) // count = *(this+8) (the tree's _Mysize; _Myhead is at this+4), then a red-black walk writing two NULL-named strings per node -- key at node+0x0c, value at node+0x28, _Isnil at node+0x45. So AttribMap is a std::map<Mars::String, Mars::String> [verified]
|
|
constexpr uint32_t Game_AttribMap_Write = 0x0019ee60;
|
|
// thiscall bool (Game::AttribMap* this, Mars::Stream* s) [verified]
|
|
constexpr uint32_t Game_AttribMap_Read = 0x001a8a60;
|
|
// thiscall void (Game::AIWeightMap<Game::ShipSectionID>* this, Mars::Stream* s) // NULL-named count = span/0xc, then n x { NULL-named StreamableHelper<ShipSectionID> frame at elem+0, NULL-named float at elem+8 } [verified]
|
|
constexpr uint32_t Game_AIWeightMap_ShipSectionID_Write = 0x00296b40;
|
|
// thiscall bool (Game::AIWeightMap<Game::ShipSectionID>* this, Mars::Stream* s) [verified]
|
|
constexpr uint32_t Game_AIWeightMap_ShipSectionID_Read = 0x002bff00;
|
|
// thiscall void (Game::AIWeightMap<Mars::StreamableEnum<unsigned int>>* this, Mars::Stream* s) // NULL-named count = span>>3, then n x { NULL-named StreamableHelper<StreamableEnum<uint>> frame at elem+0, NULL-named float at elem+4 } [verified]
|
|
constexpr uint32_t Game_AIWeightMap_StreamableEnumUInt_Write = 0x00296c50;
|
|
// thiscall bool (Game::AIWeightMap<Mars::StreamableEnum<unsigned int>>* this, Mars::Stream* s) [verified]
|
|
constexpr uint32_t Game_AIWeightMap_StreamableEnumUInt_Read = 0x002b57c0;
|
|
// thiscall void (Game::AIWeightMap<Mars::StreamableEnum<Game::AIPurposeID>>* this, Mars::Stream* s) // same wire shape as the uint instantiation; `AITurnPris` [verified]
|
|
constexpr uint32_t Game_AIWeightMap_AIPurposeID_Write = 0x0029b590;
|
|
// thiscall bool (Game::AIWeightMap<Mars::StreamableEnum<Game::AIPurposeID>>* this, Mars::Stream* s) [verified]
|
|
constexpr uint32_t Game_AIWeightMap_AIPurposeID_Read = 0x002c01a0;
|
|
// thiscall void (Game::AIWeightMap<Mars::StreamableEnum<Game::WeaponFamilyID>>* this, Mars::Stream* s) // same wire shape as the uint instantiation; `AISitWeapFams` [verified]
|
|
constexpr uint32_t Game_AIWeightMap_WeaponFamilyID_Write = 0x0029b4c0;
|
|
// thiscall bool (Game::AIWeightMap<Mars::StreamableEnum<Game::WeaponFamilyID>>* this, Mars::Stream* s) [verified]
|
|
constexpr uint32_t Game_AIWeightMap_WeaponFamilyID_Read = 0x002bffe0;
|
|
// thiscall void (Game::ShipSectionID* this, Mars::Stream* s) // two NULL-named ints, at this+0 and this+4 [verified]
|
|
constexpr uint32_t Game_ShipSectionID_Write = 0x0016ed50;
|
|
// thiscall bool (Game::ShipSectionID* this, Mars::Stream* s) [verified]
|
|
constexpr uint32_t Game_ShipSectionID_Read = 0x0016ed10;
|
|
// data void* [3] // { 0x004f7230 scalar-deleting dtor, 0x00697ae0 Read, 0x0054b6e0 Write } [verified]
|
|
constexpr uint32_t Mars_StreamableHelper_StreamableEnumUInt_vftable = 0x005e24b8;
|
|
// thiscall void (Mars::StreamableHelper<Mars::StreamableEnum<T>>* this, Mars::Stream* s) // writes exactly ONE NULL-named int. So a StreamableEnum member is a FRAME on the wire whose body is a single int -- not a bare int. This is what makes a VectorHelper<StreamableEnum<T>> a framed array of one-int frames [verified]
|
|
constexpr uint32_t Mars_StreamableEnum_Write = 0x0014b6e0;
|
|
// thiscall void (Mars::StreamableHelper<Mars::StreamableEnum<T>>* this, Mars::Stream* s) // ReadInt through stream slot +0x10; stores only when the read succeeds [verified]
|
|
constexpr uint32_t Mars_StreamableEnum_Read = 0x00297ae0;
|
|
// data void* [3] // { 0x004f7230 dtor, 0x00431610 Read, 0x0042f7f0 Write } [verified]
|
|
constexpr uint32_t Mars_VectorHelper_StreamableEnumUInt_vftable = 0x005e2438;
|
|
// thiscall void (Mars::VectorHelper<Mars::StreamableEnum<unsigned int>>* this, Mars::Stream* s) // NULL-named count = span>>2, then n NULL-named FRAMES each written by Mars_StreamableEnum_Write. Backs `nalat`, `SysMem` and `mts`, all of which have count 0 in every save we hold [verified]
|
|
constexpr uint32_t Mars_VectorHelper_StreamableEnumUInt_Write = 0x0002f7f0;
|
|
// data void* [3] // { 0x004f7230 dtor, 0x006b5600 Read, 0x0069b310 Write }; backs `prs2` [verified]
|
|
constexpr uint32_t Mars_VectorHelper_AIPlayerRequestStamp_vftable = 0x0061a700;
|
|
// thiscall void (Mars::VectorHelper<Game::AIPlayerRequestStamp>* this, Mars::Stream* s) // NULL-named count = span>>3, then n NULL-named frames written by Game_AIPlayerRequestStamp_Write. Element stride 8 [verified]
|
|
constexpr uint32_t Mars_VectorHelper_AIPlayerRequestStamp_Write = 0x0029b310;
|
|
// data void* [3] // { 0x004f7230 dtor, 0x006953b0 Read, 0x00695400 Write } [verified]
|
|
constexpr uint32_t Mars_StreamableHelper_AIPlayerRequestStamp_vftable = 0x0061a730;
|
|
// thiscall void (Mars::StreamableHelper<Game::AIPlayerRequestStamp>* this, Mars::Stream* s) // two named ints: `pid` at +0 and `trn` at +4. Game::AIPlayerRequestStamp is a POD with no RTTI class of its own, reached only through this specialised helper, so tools/serializers.py reports 'no serializer' for it and it has no entry in the generated wire table [verified]
|
|
constexpr uint32_t Game_AIPlayerRequestStamp_Write = 0x00295400;
|
|
// thiscall void (StrategyServer* this /*base S*/, std::vector<EncounterResults>* results) // RET 4. THE SECOND TURN DRIVER. Reached from exactly one caller: StrategyHost::OnMessage 0x00784640 at 0x00784d07, on the SNMAllCombatDone message, with this = host->+0x54 and results = msg+4. 36 phases, whole 1587-byte body read from the instruction stream. Base is S (NOT S+4): Players at [S+0x54/0x58], ServerTradeManager at [S+0x158], SVScriptObject at [S+0x1b4], encounters at [S+0x1e8]. STRAIGHT-LINE past 0x007d96bf -- every jcc from there on is a per-player loop bound or one of three null tests on S+0x1b4. Order: ++S->+0x8 / arity check / first contact over all ordered combatant pairs / sighting announce / battle tally / diplomacy stats / ApplyEncounterResult per encounter + resupply / encounters.clear() / script(8) / AIRebellion(1) / ProcessNodeSpaceTravel / node-line decay (RNG) / colony-loss drain / two morale passes / ProcessBankruptcy / colonizer resolve / PlayerView rebuild / warnings / infra-terra drain / script(0x14,0x15) / survey+stats / FUN_0078a7c0 / eight ServerTradeManager vtable calls / upkeep / sensors / script(0x1c) / view refresh / node-line sightings / intercept aborts / comm masks / UpdateBankruptcyLimits per player / incoming warnings / two more vtable calls / observed designs / player reports / turn records [verified]
|
|
constexpr uint32_t StrategyServer_OnAllCombatDone_Tail = 0x003d92a0;
|
|
// site site, phase 7: S->encounters.clear(). The bytes are `if (_Myfirst != _Mylast) { newEnd = FUN_007c5780(_Mylast,_Mylast,_Myfirst,c); FUN_00679c80(newEnd,_Mylast,&vec+0xc,c); _Mylast = newEnd; }`, MSVC's vector::erase(begin,end). FUN_007c5780 is std::_Uninit_move over 0x74-byte Encounters and is handed the EMPTY range [_Mylast,_Mylast), so it copies nothing and returns _Myfirst; FUN_00679c80 is std::_Destroy_range. THE IDENTICAL FOUR-ARGUMENT SHAPE appears at 0x007cd147/0x007cd15b inside FUN_007cd100 (vector<Encounter>::operator= taking the empty-source path), which is what identifies it. NO PREDICATE, NO FILTER: every encounter is erased. The `if` is the empty-vector guard erase always carries and both arms converge at 0x007d96bf [verified]
|
|
constexpr uint32_t StrategyServer_OnAllCombatDone_Tail_ClearEncounters = 0x003d9690;
|
|
// site site in FUN_007ae010 (phase 11 of OnAllCombatDone_Tail): `mov ecx,[esi+0x16c]; fld dword [0x009e2ea0] /*0.5f*/; push ecx; fstp [esp]; call 0x008e6dd0` = Mars::RNG::Chance(0.5f) on the strategic generator at S+0x16c. Chance early-outs WITHOUT a draw at p<=0 and p>=1 but takes neither at 0.5f, so this is EXACTLY ONE NextFloat PER EXPIRED NODE LINE PER TURN. State-dependent draw count, in the combat-done tail, BEFORE the autosave. Every RNG account in the repo assumes the strategic generator advances only inside StrategyServer::ProcessTurn; it also advances here, and again inside the combat resolver FUN_007d5af0 (RNG_NextInt on the node-cannon path, RNG_Twist + RNG_NextInt on the salvage path) [verified]
|
|
constexpr uint32_t StrategyServer_OnAllCombatDone_Tail_NodeDecayRoll = 0x003ae095;
|
|
// thiscall void (StrategyHost* this /*the global at 0x00b29f98*/, std::string* outName, bool endTurn) // THE AUTOSAVE. Exactly two call sites: SendEndTurn 0x007839d7 with endTurn=1 -> (Autosave EndTurn).sav, the PRE-turn state; StrategyHost::OnMessage 0x00784e59 with endTurn=0 -> (Autosave).sav, the POST-turn state. Body: null-check this->+0x4 (the strat game) -> log "Can't autosave- Strat game doesn't exist."; build FOUR paths as _snprintf(buf,0x3ff,"%s/%s.%s", dir, name, ext) with dir=FUN_007a05a0(game) ("SavedGames") and ext=FUN_007a0620(game) ("sav") and the four localized names registered at 0x009bed00..0x009bed7f (SOTS_GAME_AUTOSAVE @0xaf092c, _AUTOSAVEBACKUP @0xaf0934, _ENDTURN_AUTOSAVE @0xaf093c, _ENDTURN_AUTOSAVEBACKUP @0xaf0944); if (!IsSinglePlayerHost()) remove both ENDTURN files; pick (cur,bak) by endTurn; mkdir(dir); ROTATE remove(bak)+rename(cur,bak) ONLY WHEN endTurn==0 (the flag byte at [ebp-0x14a1] is set to 1 and the je at 0x00895266 SKIPS the store of 0 when the arg is zero); gate on (this->flags & 4) && this->+0x4; DETACH each player's connection at pl->+0x12c via conn->vft[0x14] and reattach via conn->vft[0x18] after; call SaveGame_WriteFile(this->+0x4, curPath, 1, &agentNames) at 0x0089595d [verified]
|
|
constexpr uint32_t StrategyHost_Autosave = 0x00495210;
|
|
// cdecl bool () // whole 27-byte body: `g = *(void**)0x00b2d540; net = g->+0x148; return net != 0 && net->+0x4 == 0;`. Gates SendEndTurn's pre-turn autosave and the AI-agent sidecar branch inside StrategyHost::Autosave; its NEGATION gates the deletion of the ENDTURN autosave pair (so the pre-turn autosave is a single-player-only feature) [verified]
|
|
constexpr uint32_t StrategyHost_IsSinglePlayerHost = 0x00415fb0;
|
|
// thiscall bool (void* this) // `return this->+0x148 != 0;`. Called on the global at 0x00b2d540 from StrategyHost::OnMessage 0x00784e3f -- this is the gate on the POST-turn autosave [verified]
|
|
constexpr uint32_t StrategyHost_HasNetworkSession = 0x00498af0;
|
|
// cdecl bool (void* game, const char* path, bool write, std::vector<AgentName>* agentNames) // the save-file ROOT that verify/save-reader/save_reader.py already models (its comment at line 694 names this address). Opens the stream with OpenSaveStream(path,&stream,write); with write=1 that is operator new(0x118) + ctor 0x008d10c0 + FUN_008d1090(path,"wb"), the gzip writer. Then writes four named top-level sections through stream->vft[0x28](tag,&ref): "Summary", "CreateParams", "Sim", "CDT"; then one "CD" record (tag at 0x00a2b9d4) per entry of the 0x20-stride agentNames vector whose +0x1c is non-null. NOTHING TIME-, NAME- OR MACHINE-DEPENDENT ENTERS THE PAYLOAD: the file NAME is built by StrategyHost::Autosave and never reaches here [verified]
|
|
constexpr uint32_t SaveGame_WriteFile = 0x00477070;
|
|
// cdecl bool (const char* path, Stream** out, bool write) // write -> operator new(0x118), ctor 0x008d10c0, open FUN_008d1090(path, "wb" @0x009e150c); read -> OpenFile(path, @0x00a2ec2c). Returns *out != 0. The "wb" is the gzip container the determinism note measured as header-deterministic (MTIME 0, XFL 0, OS 11) [verified]
|
|
constexpr uint32_t OpenSaveStream = 0x00416510;
|
|
// thiscall void (StrategyServer* this) // 122 BYTES, AND IT GENERATES NOTHING. (1) FUN_007c5610(&scratch, S->+0x304, S->+0x308) with ecx = &S->+0x304 -- erase-to-empty of the vector<SETurnResults> OUTBOX at S+0x304. (2) if (S->+0x244 != S->+0x248) S->+0x248 = S->+0x244 -- clear of a 0xc-stride vector; THE COPY LOOP AT 0x007dc680 IS DEAD CODE, `cmp edx,edx; je` at 0x007dc676 is unconditionally taken. (3) FUN_00792a20(S) prunes two intrusive lists at S+0x2d8 and S+0x2e4. (4) if (S->+0x128 & 4) BuildTurnEvents(S) -- normally FALSE. One caller: StrategyHost::OnMessage 0x00784e34 [verified]
|
|
constexpr uint32_t StrategyServer_GenerateTurnEvents = 0x003dc640;
|
|
// note MISNAMED. BuildTurnEvents 0x007db780 is NOT a per-turn turn-event builder: it is the FULL-STATE RESYNC PUSH for setup / load / rejoin. Its entire 3701-byte body is under `if (this->+0x12c != 0)`, and both that descriptor and bit 2 of +0x128 are set in exactly one place in the image -- FUN_007bd1b0 at 0x007bd204/0x007bd23a. It references NO EVENT_* string at all; its only string immediates are "vector<T> too long" and "StrategyServer: OnEvent() called, but no callback function specified." It sends SEResetMap (0x29), SEAddPlayer (0x01), SEInitTrade (0x2a), SETurnEvents (0x28), SESyncDesign (0x19) and calls SynchronizePlayer. It CALLS FUN_0081b390 (the previous-turn snapshot) at 0x007dbc7c to ESTABLISH the baseline and never diffs against it. findings/control-flow/turn-spine.md reads as if this were a per-turn diff step -- it is not [verified]
|
|
constexpr uint32_t StrategyServer_BuildTurnEvents_isResync = 0x003db780;
|
|
// offset StrategyServer+0x2f4 (S frame) = std::vector<Game::SETurnResults> ACCUMULATOR, stride 0x11c, one record per player indexed by PlyrIdx. Written during the turn by (at least) ApplyEncounterResult 0x007d8f9e, FUN_007ae010 0x007ae286/0x007ae3ce, FUN_007a4ff0 0x007a516f, FUN_007a4700, FUN_007b9df0, ProcessAid (3 sites) and ApplyEncounterResults itself -- found by a whole-image scan for `imul r32,r32,0x11c` / `add r32,0x11c` at real instruction boundaries. ApplyEncounterResults' tail (0x007d4fa0-0x007d505f) destroys S+0x304, SWAPS the two vector headers so this turn's accumulation becomes the outbox, then resize(0)+resize(nPlayers) here for the next turn [verified]
|
|
constexpr uint32_t StrategyServer_off_TurnResultsPending = 0x000002f4;
|
|
// offset StrategyServer+0x304 (S frame) = std::vector<Game::SETurnResults> OUTBOX, stride 0x11c, filled by the swap in ApplyEncounterResults' tail. Read by SynchronizePlayer 0x007c865f: `if (size() == Players.size()) { r = base + i*0x11c; r->+0x20 = S->+0x1fc; OnEventCallback(netId, 0x25, r); r->+0x20 = 0; }`. Cleared by GenerateTurnEvents' first statement. SETurnResults is strategy-event id 0x25, unicast per player, and is NOT serialized -- its vtable 0x00a24b00 has no Read/Write pair and it appears in no save schema [verified]
|
|
constexpr uint32_t StrategyServer_off_TurnResultsOutbox = 0x00000304;
|
|
// thiscall void (SETurnResults* this) // the DEFAULT CONSTRUCTOR of Game::SETurnResults (Ghidra calls it Create; it is not a factory). sizeof == 0x11c, enumerated five ways: the 0x11c stride and its reciprocal 0xe6c2b449/sar 8 in vector<SETurnResults>::resize 0x007cd2a0, the `add esi,0x11c` in _Ufill 0x007c5850, the accessor 0x00788cb0 (base[PlyrIdx*0x11c]), the operator new[] in 0x0078b0c0, and this ctor closing at +0x118 (the _Alval of a vector member at +0x10c). Layout: +0x00 vptr; +0x04 bool; +0x08 EMBEDDED Game::EventStorage::TurnEvents (vptr +0x08, int EvTurn +0x0c, vector<Event> +0x10/+0x14/+0x18, _Alval +0x1c); +0x20 int stamped by SynchronizePlayer from S+0x1fc and cleared after; +0x24 vector<ClientEncounterResults> (what ApplyEncounterResult publishes into); +0x34 byte with two bit-flags; strings at +0x38/+0x54/+0xa4/+0xc0/+0xdc; list at +0x70; vectors at +0x80/+0x90/+0xfc/+0x10c; two bools at +0xa0/+0xa1; int at +0xf8 [verified]
|
|
constexpr uint32_t SETurnResults_ctor = 0x003a7ae0;
|
|
// thiscall void (std::vector<SETurnResults>* this, int n) // MISNAMED as DispatchTurnResults: it dispatches nothing. std::vector<Game::SETurnResults>::resize(n) -- shrink to _Erase 0x007c5610, grow to _Reserve 0x007cb340 + _Ufill 0x007c5850. Likewise 0x007c5850 ("SendTurnResultsToPlayers") is _Ufill: per element default-construct a stack temp with 0x007a7ae0, copy-construct into the destination with 0x007c24d0, destroy the temp with 0x0079ac10, dest += 0x11c. The ONLY send of an SETurnResults in the image is SynchronizePlayer 0x007c86d1 (push 0x25) [verified]
|
|
constexpr uint32_t vector_SETurnResults_resize = 0x003cd2a0;
|
|
// note THE GHIDRA SYMBOL NAMES ON 0x00825bb0 / 0x00825c40 ARE SWAPPED, and so is EventStorage_Read 0x00825cc0 (which is the Write). 0x00825c40 is the WRITE: it calls 0x008b9d50, which invokes stream vtable slot +0x24 and pushes the MEMBER'S VALUE -- identical in shape to the golden Game::ObservedTech::Write 0x00817cf0. 0x00825bb0 is the READ: it calls 0x008b9d20, which invokes slot +0x10 and passes a stack scratch as a DESTINATION. objects/layouts.json and objects/streams.json already have the direction right (write 0x825c40, read 0x825bb0); the Ghidra names and findings/subsystems/events.md repeat the swap. Wire schema of Game::EventStorage::TurnEvents, in order: "EvTurn" by WriteInt (FOUR BYTES ON THE WIRE, default -1) at this+0x04; then "Events" through slot +0x28 as a framed counted array of Game::EventStorage::Event bound via Mars::VectorHelper<Game::EventStorage::Event> (vtable 0x00a2da7c) at this+0x08. sizeof == 0x18 by enumeration four ways: serializer span (0x08+0x10), the SETurnResults default ctor (subobject 0x08..0x1f, next member at +0x20), its copy ctor, and the 0x18 container stride in EventStorage::FindTurnBucket 0x00811f70 [verified]
|
|
constexpr uint32_t TurnEvents_serializer_direction = 0x00425c40;
|
|
// cdecl void* () // whole 10-byte body: `mov ecx,0x00b29f98; jmp 0x005f6450` and 0x005f6450 is `mov eax,[ecx+4]; ret`, i.e. `return *(void**)0x00b29f9c`. 0x00b29f98 is the SAME global StrategyHost::Autosave takes as its `this`, and +0x4 is the same strat-game pointer it null-checks and hands to SaveGame_WriteFile. GetGame()+0x84 is the game's global handle map. 670 xrefs [verified]
|
|
constexpr uint32_t GetGame = 0x00178050;
|
|
// thiscall void* (HandleMap* this, uint id) // `if (!id) return 0; slot = id & 0xF; if (slot >= (this->+0xc - this->+0x8)/0x14) return 0; b = this->+0x8 + slot*0x14; lower_bound(b, &it, &id); return it == b->+0x4 ? 0 : *(void**)(it + 0x10);`. A 16-BUCKET stdext::hash_map<uint32 handle, Object*>: vector<Bucket> at +0x8/+0xc/+0x10 with 0x14-byte stride, bucket index = id & 0xF, each bucket a red-black tree whose head is at bucket+0x4 (node layout _Left@0 _Parent@4 _Right@8 key@0xc value@0x10 _Color@0x14 _Isnil@0x15). NOTE the `this` at the call site is &bucketVector, i.e. map+0x84 on the game root, not the map object [verified]
|
|
constexpr uint32_t HandleMap_Resolve = 0x004b9240;
|
|
// thiscall void (ServerPlayer* this, ServerPlayer* other) // `if (other) this->HasEnc(+0x1a8) |= 1 << other->PlyrIdx(+0x28);`. Called twice symmetrically per ordered combatant pair in OnAllCombatDone_Tail phase 2 [verified]
|
|
constexpr uint32_t ServerPlayer_MarkPlayerEncountered = 0x0040df10;
|
|
// thiscall void (ServerPlayer* this, uint species) // `if (species < 7 && species != 4) this->HasDiscCl(+0x1a4) |= 1 << species;`. Species index 4 is permanently excluded. Called in OnAllCombatDone_Tail phase 2 as MarkSpeciesDiscovered(other->Species(+0x5c)) [verified]
|
|
constexpr uint32_t ServerPlayer_MarkSpeciesDiscovered = 0x0040dee0;
|
|
// cdecl int (uint typeMask) // A RANKER OVER GROUPS, NOT A FILTER OF ONE. Clears bit 0 (Standard); if the mask hits the boss group {SystemKiller 7, PuppetMaster 8, Locust 14, 21} the mask is RESTRICTED to that group; otherwise the ambient groups {Swarm 3, Derelict 4, Monitor 5, SlaversRefuel 9, CrowRuins 17}, {CrowsNest 12, GravTrap 13} and {GasCloud 11, Meteor 2, Pirate 6, TradeRaiders 18, 20, 23} are each dropped IF ANYTHING ELSE REMAINS. Returns the index of the lowest surviving set bit in [0,0x18), else 0. The four group masks are lazily built once into 0x00b0e96c..0x00b0e988. FUN_004f4970 is the id->name switch (Standard/VonNeumann/Meteor/Swarm/Derelict/Monitor/Pirate/SystemKiller/PuppetMaster/SlaversRefuel/SwarmQueen/GasCloud/CrowsNest/GravTrap/Locust/Berserker/CrowDefenders/CrowRuins/TradeRaiders), which fixes the return type as an EncounterType enum [verified]
|
|
constexpr uint32_t PickDominantEncounterType = 0x000f4c40;
|
|
// thiscall void (StrategyServer* this, Node* node /*= enc->+0xc*/, int encType /*= PickDominantEncounterType(enc->+0x38)*/) // GHIDRA'S DECOMPILE OF THIS FUNCTION IS UNUSABLE -- 19 'removing unreachable block' warnings delete the entire event-posting body; read it as instructions. mask = 0; if (!FUN_00788cd0(node, encType, &mask)) return; -- that gate is true only for VonNeumann(1), Meteor(2), Pirate(6), GasCloud(0xb), Berserker(0xf), 0x17, and fills mask with the players who can see it. Then per set player it composes the event key as the LITERAL "EVENT_" (0x00a24bf8, length 6) CONCATENATED WITH THE TYPE NAME -- EVENT_PIRATE, EVENT_TRADERAIDERS, ... -- and posts through ServerPlayer_GetEventStorage + EventStorage_PostEvent. Finally push_backs a 0x10-byte {system, encType, turn, turn+1} record into the vector at S+0x2c8/+0x2cc/+0x2d0. No RNG [verified]
|
|
constexpr uint32_t StrategyServer_AnnounceEncounterSighting = 0x003a9db0;
|
|
// thiscall void (StrategyServer* this, std::vector<Encounter>* encounters, std::vector<EncounterResults>* results) // two passes over ServerPlayer+0x230 vector<DiplomacyStats>, no RNG, no events. PASS A (dead homeworld): if the battle was at a player's own HomeSys(+0x2c), was a real battle (result->+0x4 == 0), had planet stats (result->+0x10c != 0) and the INT64 at result+0x120 is <= 0, then every participant that actually fought them gets deadhome(+0x20)++. PASS B (treaty betrayal): for every ordered pair with GetRelation < 1, a treaty slot signed within the last 3 turns and not yet betrayed since signing (`last != -1 && turn-last < 3 && (bty == -1 || bty < last)`), where the other side actually fought -- bty++ and lastXbty = turn, independently for NAP (+0x8/+0xa/+0xe), alliance (+0x10/+0x12/+0x16) and ceasefire (+0x18/+0x1a/+0x1e) [verified]
|
|
constexpr uint32_t StrategyServer_UpdateDiplomacyStatsFromCombat = 0x00389d00;
|
|
// thiscall void (StrategyServer* this, Encounter* enc, EncounterResults* res) // phase 6 of OnAllCombatDone_Tail. Dispatch on three result bytes: res->+0x4 != 0 makes the WHOLE FUNCTION A NO-OP (that flag means 'no battle happened', which is exactly what phase 3's sighting arm keys on); res->+0x6 != 0 -> FUN_007a06a0 (posts EVENT_PEACEFUL_ENCOUNTER); res->+0x7 != 0 -> FUN_007d3eb0 (posts EVENT_SYSTEM_SURRENDERED); otherwise the full path -- stamp StarShip+0x5c = turn on every participating ship; set the pairwise HasEng(+0x1ac) engagement bits; if enc->+0x3c stamp ServerPlayer+0x3d4 = turn; build a ~0xea0-byte combat report and run THE REAL RESOLVER FUN_007d5af0 (7499 B, UNREAD); append a Game::CombatReport to the std::list at S+0x1fc; fire the script hook vt[0x10](7,..)/vt[0x30](..). ALL branches then run a publication tail that push_backs a 0x30-byte Game::ClientEncounterResults into *(S+0x2f4) + PlyrIdx*0x11c + 0x24. DRAWS RNG through its subtree: FUN_007d5af0 -> FUN_007bb530 -> RNG_NextInt (node cannon), and -> FUN_007a7f30 -> RNG_Twist plus -> FUN_007a0540 -> FUN_00852d30 -> RNG_NextInt (salvage / back-engineering) [mapped]
|
|
constexpr uint32_t StrategyServer_ApplyEncounterResult = 0x003d8920;
|
|
// thiscall void (Node* this /*= enc->+0xc, the encounter's system*/, byte mask) // `if (!this->+0x100 /*owner*/) return; for each fleet at the node (vt[8] count, vt[0x10] get): if (owner->GetRelation(fleet->PID(+0x58)) == 3) for each ship in fleet->NShips(+0xa4/+0xa8) StarShip::RefreshFromDesign(ship, mask);`. FUN_00854680(ship,1) copies ship->+0x20 = design->+0xe8 and ship->+0x6c = design->+0xd8 then runs five recompute helpers -- a repair/refuel/stat refresh, not a movement step. strategic-turn-internals.md line 320 already calls it RefuelInOrbit(1); called from OnAllCombatDone_Tail phase 6 with mask = 1 [verified]
|
|
constexpr uint32_t Node_ResupplyAlliedFleets = 0x003463f0;
|
|
// thiscall void (StrategyServer* this) // the LAST call of OnAllCombatDone_Tail (0x007d98ba), and also called from LoadGame 0x007ddc40 -- so the per-player turn record is rebuilt at the end of every turn AND on load, and never has to survive a save round-trip. Per player, rec = P->+0x3d8: rec+0x0c (32) = P->Sav(+0x284) - P->PvSav(+0x188); rec+0x14 (32) = P->Sav; rec+0x18 (16, mov WORD) = (P->+0x34 - P->+0x30)>>2 owned systems; rec+0x28 (16) = completed-tech count from FUN_0057d980 over the tree's +0x10/+0x14 with state == 4; rec+0x20/+0x24 (int64, cdq/add/adc) = SUM over owned systems of (sys->+0x194 + sys->+0x18c) total population; rec+0x2a/+0x2c/+0x2e (16) = ship counts by hull size 0/1/2 for designs WITHOUT flag 0x400; rec+0x30/+0x32/+0x34 (16) = the same for designs WITH flag 0x400. The census comes from FUN_00818a50(P, int[8]) whose slots [0] and [1] (the grand totals) are computed and DISCARDED. Second loop: FUN_00894260(S->+0x200, i, S->+0xc, rec) archives the record by turn; the archive's copy-assign FUN_008712a0 deliberately does NOT copy +0x1c [verified]
|
|
constexpr uint32_t StrategyServer_FinalizeTurnRecords = 0x0038a0e0;
|
|
// thiscall int (ServerSystem* sys) // float mods[7] = {1.0f,0,..}; FUN_00747390(mods, sys, 0); float out[12] = {0}; ServerSystem::ComputeOutputRates(sys, out, mods); return max(*(int*)&out[3], 0). Slot 3 is the money/income rate. THE max() IS A `jg` -- the clamp at zero is what makes a loss-making colony contribute nothing to the bankruptcy limits rather than reducing them, which formula-gaps.md Q1 did not say. Only caller: ServerPlayer::UpdateBankruptcyLimits [verified]
|
|
constexpr uint32_t ServerSystem_ComputeMaxIncome = 0x003521c0;
|
|
// note Game::SNMAllCombatDone RTTI vtable, four slots (0x0079e590, 0x0082a100, 0x0082a170, 0x0079e500 -- the middle pair are the network Read/Write). Layout by enumeration from the two stack constructors and from every offset OnAllCombatDone_Tail reads: `struct SNMAllCombatDone { void* vptr; std::vector<EncounterResults> results; }`, 0x10 bytes -- which is why the handler passes msg+4 and not msg. Three construction sites: RunCombatRound 0x007cc847 (stack), the combat server FUN_007cfd00+0x541 = 0x007d0241 (stack; sends it to every player whose +0x44 is 4 or 5, then sets combatServer->+0x60 = 9; NOTE Ghidra sizes FUN_007cfd00 at 384 B but its real body runs to the ret at 0x007d02b9), and the deserialization factory 0x008663b0 (operator new(0x14) -- 4 bytes larger than the enumerated size, UNEXPLAINED) [verified]
|
|
constexpr uint32_t SNMAllCombatDone_layout = 0x00624758;
|
|
// data const float 0.5f -- the progress-ratio threshold in ServerPlayer::ProcessTurn's `ResT != NULL && ResErrRoll != 0 && CONST < progressRatio` gate (events.md §3, window 0x008915ec-0x00891624). Read out of dumps/sots.exe: .rdata bytes at 0x00a2c788 are 00 00 00 3f (float 0.5); the following word 0x00a2c78c is float 100.0, so this is a float32, NOT the double an 8-byte read would suggest (that reads as 5.28e13). CONSEQUENCE, measured on the live game: ResErrRoll survives into ProcessResearch only while progress/cost <= 0.5 at the START of the turn, so the OnTechResearched draw (0x0088df20) can fire only when a single turn supplies more than half the target tech's remaining cost. See the board's `research_roll_pending save` row [verified]
|
|
constexpr uint32_t ResearchRollProgressThreshold = 0x0062c788;
|
|
// thiscall void (ServerPlayer* this, float dt) // RET 4. The per-player turn driver, called once per player from StrategyServer::ProcessTurn's player loop. THE dt ARGUMENT IS NEVER READ: the whole 1086-byte body contains zero [ebp+N] references (mechanical check over the full instruction decode), so a reimplementation may ignore it. Order: ComputeBudget -> Sav = SatAdd(Sav, net) -> record aid given -> ProcessSpecialProjects -> (ResT ? RollResearchAccident/ProcessResearch) -> research refund -> zero TRM/TRA/TRP -> RebAI decay -> timed-bonus sweep -> ResearchRollPending site -> EVENT_NO_RESEARCH -> PruneRaidTargets [verified]
|
|
constexpr uint32_t ServerPlayer_ProcessTurn = 0x00491340;
|
|
// site site in ServerPlayer::ProcessTurn: `if (this->ResT(+0x294) != 0) { if (!RollResearchAccident(&budget)) TechTree::ProcessResearch(this->TechTree(+0xf4), rng, &budget.researchAlloc, &overBudget); }`. Argument order read off the push order at 0x00891496-0x008914a5: pushes are (edx=&overBudget), (ecx=&allocVector), (eax=rng), so left-to-right the args are (RNG*, vector*, int*). The RNG is `*(ServerPlayer+8 - 4 + 0x16c)`. `overBudget` is a FRESH STACK LOCAL at [ebp-0x14], NOT Budget+0x64 [verified]
|
|
constexpr uint32_t ServerPlayer_ProcessTurn_ResearchCall = 0x00491461;
|
|
// site site in ServerPlayer::ProcessTurn, immediately after ProcessResearch returns: `if (overBudget > 0 && budget[17] > 0) Sav = SatAdd(Sav, ftol( (double)min(overBudget, budget[17]) / budget[17] * budget[16] ))`. budget[17] = researchPoints, budget[16] = researchMoney. Unspent research points are refunded to savings at the turn's own points-per-credit rate. PREVIOUSLY UNMODELLED - it is not in strategic-turn-internals.md §1.2. The min is selected by `cmp ecx,eax; lea eax,[ebp-0x18]; jl; lea eax,[ebp-0x10]`, then `fild; fidiv budget[17]; fimul budget[16]; call _ftol2` [verified]
|
|
constexpr uint32_t ServerPlayer_ProcessTurn_ResearchRefund = 0x004914aa;
|
|
// site site in ServerPlayer::ProcessTurn, AFTER ProcessResearch (ProcessTurn+0x2ac vs +0x165): `if (ResT(+0x294) && ResErrRoll(+0x3b4) && 0.5f < TechTree::ResearchProgressRatio(tree, ResT)) { RollResearchEvent(this); ResErrRoll = false; }`. THE CLEAR IS INSIDE THE BRANCH - when the ratio test fails the flag is left set, which is how it survives to the next turn. The threshold is a strict `<` (MSVC `fld 0.5f; fcomp ratio; fnstsw; test ah,5; jp skip`), constant 0x00a2c788 = 0.5f. This is the site campaign/board.md describes as running BEFORE ProcessResearch; it runs after [verified]
|
|
constexpr uint32_t ServerPlayer_ProcessTurn_ResearchRollBlock = 0x004915ec;
|
|
// thiscall void (ServerPlayer* this) // `mov byte [ecx+0x3b4], 1; ret` - the only ARMING write of ResErrRoll in the image other than the constructor. Referenced ONLY from the vtable word at 0x00a327bc (slot 6 of the 0x00a327a4 ServerPlayer vptr), so every caller is an indirect `call [vft+0x18]`. find-vtable-callers returns no genuine caller: all 18 hits are other classes' slot at byte offset 0x70 and pass arguments. WHO RE-ARMS THE FLAG DURING PLAY IS UNRESOLVED [verified]
|
|
constexpr uint32_t ServerPlayer_SetResearchRollPending = 0x0040e300;
|
|
// thiscall void (ServerPlayer* this) // `mov byte [ecx+0x3b4], 0; ret`. Vtable word 0x00a327c0, the slot after SetResearchRollPending [verified]
|
|
constexpr uint32_t ServerPlayer_ClearResearchRollPending = 0x0040e310;
|
|
// site site in the ServerPlayer constructor: `mov WORD PTR [esi+0x3b4], 1` - a two-byte store that sets ResErrRoll(+0x3b4) = 1 and +0x3b5 = 0. THE FLAG IS BORN TRUE. Consistent with (but not proof of) the hypothesis that ResErrRoll is armed once at construction and consumed once, never re-armed except through the vtable pair [verified]
|
|
constexpr uint32_t ServerPlayer_ctor_ResearchRollPendingInit = 0x00480474;
|
|
// thiscall float (TechTree* this, TechDef* def) // RET 4, result in st(0). node = this->nodes(+0x10)[def->id(+0x0)]; if (!node) return 0.0f; return (float)((double)node->progress(+0x1c) / (double)TechTree::Cost(node)). Whole 59-byte body read. The divide is done in double and narrowed once on the fstp. Called from exactly one place: the ResearchRollPending block in ServerPlayer::ProcessTurn [verified]
|
|
constexpr uint32_t TechTree_ResearchProgressRatio = 0x0017e950;
|
|
// thiscall TechDef* (TechTree* this) // plain RET. Walks this->nodes(+0x10/+0x14) in index order; for each non-null node resolves node->def(+0x0) and re-indexes nodes by that def's id (the same self-resolving idiom lane E corrected in ProcessResearch), and returns the FIRST def whose node state(+0x14) == 2 (available). NULL when none. Used only as the second half of the EVENT_NO_RESEARCH test [verified]
|
|
constexpr uint32_t TechTree_FindFirstAvailableTech = 0x0017da90;
|
|
// thiscall void (TechTree* this, std::vector<ResearchedTechRecord>* out, int minTurn, int maxTurn, bool sort) // RET 0x10. Clears *out, walks this->master(+0x4)->defs(+0x24/+0x28); SKIPS any def whose name (std::string at def+0x4) tail from its first '_' matches "_Root" case-insensitively; requires nodes[def->id]->state(+0x14) == 4 (researched) and minTurn <= node->turnResearched(+0x24) <= maxTurn; pushes {TechDef*, turnResearched, node->flag(+0x2c)} (12-byte record). When `sort` is true it finally calls 0x00582cd0(first, last, count, this). ServerPlayer::ProcessTurn calls it with (minTurn = server ModCount, maxTurn = INT_MAX, sort = true), i.e. "techs this player completed on THIS turn". strategic-turn-internals.md §1.2 step 9 described it as a list of AVAILABLE techs; it is the opposite [verified]
|
|
constexpr uint32_t TechTree_CollectResearchedTechs = 0x00184e50;
|
|
// field sizeof(ResearchedTechRecord) = { TechDef* def; int turnResearched; int flag } - the element TechTree::CollectResearchedTechs pushes. Stride confirmed by the vector-clear loop at 0x00584e75 (three dword copies, `add eax,0xc`) [verified]
|
|
constexpr uint32_t ResearchedTechRecord_stride = 0x0000000c;
|
|
// cdecl int (int a, int b) // saturating add. Overflow-checked: b>0 and a+b<=a -> +2000000000 (0x77359400); b<0 and a+b>=a -> -2000000000 (0x88ca6c00); otherwise clamp(a+b, -2000000000, +2000000000). Whole 94-byte body read. Used for every write to ServerPlayer::Sav in ProcessTurn (the net apply and the research refund) [verified]
|
|
constexpr uint32_t ServerPlayer_SatAdd = 0x00417990;
|
|
// cdecl int (ServerPlayer* p, int available) // the AI branch of ProcessTurn phase 4: a human player uses budget[11], which ComputeBudget fills only when p->IsAI(+0xf9)==0, and an AI recomputes it here. Body not read by lane T; the call shape (two pushes, `add esp,8`) is verified [mapped]
|
|
constexpr uint32_t ServerPlayer_ConstructionSpend = 0x00417f90;
|
|
// thiscall void (ServerPlayer* this, int constructionSpend, int available) // TWO stack arguments, pushed as (edi = budget[15] available) then (eax = construction spend), so the callee sees (spend, available). turn-spine.md called this "income/savings"; strategic-turn-internals.md §1.1 already corrected it to special projects. Body not read by lane T [mapped]
|
|
constexpr uint32_t ServerPlayer_ProcessSpecialProjects = 0x00440fe0;
|
|
// thiscall void (ServerPlayer* this) // the unconditional tail of ServerPlayer::ProcessTurn. Sweeps the vector<RaidTargets> at +0x338/+0x33c (0x20 stride) forward, erasing an element when: its inner range rec[0xc]==rec[0x10] (empty); or ModCount - rec[0x1c] > 20; or the entity lookup 0x008b9240(server EntityHash, rec[0x4]) fails; or the resolved object's +0x90 mask lacks bit (1 << this->PlyrIdx(+0x28)). Erase shifts the tail down 0x20 and calls the trailing element's vft[0] scalar destructor. Whole 239-byte body read [verified]
|
|
constexpr uint32_t ServerPlayer_PruneRaidTargets = 0x00463cf0;
|
|
// thiscall void (ServerTradeManager* this) // StrategyServer::ProcessTurn phase 2, called on StrategyServer+0x158 (S frame) / +0x154 (raw frame). 1494 bytes. Freighter -> route allocation; the formulas are in strategic-turn-internals.md §1.4 and have never been run against the game [mapped]
|
|
constexpr uint32_t ServerTradeManager_ProcessTurn = 0x0046b300;
|
|
// thiscall void (StrategyServer* this) // StrategyServer::ProcessTurn phase 3, 192 bytes. Every owned system whose owner has CnTrd registers NumTradeRoutesSupported routes [mapped]
|
|
constexpr uint32_t StrategyServer_RegisterTradeSystems = 0x003adc80;
|
|
// thiscall void (StrategyServer* this) // StrategyServer::ProcessTurn phase 28, 185 bytes. For every player, for every species index 0..6 except 4 and the player's own: if the species is known (0x0080de60) and SpeciesDef::Get(sp)->+0x78 is a tech id != 0xc5 whose def resolves and is not already researched, make it visible via 0x00586200(def, 0). The "you have met this race, its racial tech appears in your tree" rule. Draw-free, event-free, three callees [verified]
|
|
constexpr uint32_t StrategyServer_UnlockMetSpeciesTechs = 0x0038aa70;
|
|
// site site in StrategyServer::ProcessTurn: the per-system `ServerSystem::ProcessTurn` loop. `mov ecx,[Systems._Myfirst + i*4]; call 0x007598e0` - NO stack arguments and no returned value, confirming B4's correction that the decompile's `void* stream` parameter is a Ghidra guess. Loop bound recomputed every iteration as `(Systems._Mylast - _Myfirst) >> 2` [verified]
|
|
constexpr uint32_t StrategyServer_ProcessTurn_SystemLoop = 0x003dc9c8;
|
|
// site site in StrategyServer::ProcessTurn: the per-player `ServerPlayer::ProcessTurn` loop. `fld DWORD [ebp+8]; mov ecx,player; push ecx; fstp DWORD [esp]; call 0x00891340` - the driver's own float dt is forwarded as the callee's single stack argument (the `push ecx` only reserves the slot). Loop bound recomputed every iteration as `(Players._Mylast - _Myfirst) >> 2` [verified]
|
|
constexpr uint32_t StrategyServer_ProcessTurn_PlayerLoop = 0x003dca00;
|
|
// site site in StrategyServer::ProcessTurn: the first instruction of the end-of-turn tail (ProcessAid, ProcessSpecialProjects, ProcessSurrenders, the per-player 0x00818530 sweep, the two SVScriptObject hook pairs, 0x0086a8d0, 0x0078ab30, 0x00799380, UnlockMetSpeciesTechs, the per-system 0x00743ec0 sweep, 0x007b4c00 and the team/Status sweep). IT IS UNCONDITIONAL. turn-spine.md §2.4 step 11 says the tail is deferred when encounters are pending; that came from reading the INLINED std::vector DESTRUCTOR at 0x007dcb15 as a branch - `je 0x7dcb38` skips only the `operator delete`, and both arms converge here. There is no branch on the encounter snapshot anywhere in this function [verified]
|
|
constexpr uint32_t StrategyServer_ProcessTurn_EndOfTurnTail = 0x003dcb38;
|
|
// field THE TWO BASES, stated once. Let S = the `this` StrategyServer::ProcessTurn receives, which is also what ServerPlayer::GetServer (0x0080e320) returns. Then ServerPlayer+0x8 holds S+4, and EVERY `StrategyServer_off_*` entry in addresses.json except `StrategyServer_off_RNG` is expressed in the S+4 frame (Players 0x50 = S+0x54, Fleets 0x60 = S+0x64, EntityHash 0x80 = S+0x84, RNGPtr 0x168 = S+0x16c). `StrategyServer_off_RNG = 0x16c` is the same word in the S frame. Convert S-frame -> stored frame by subtracting 4. Reading the wrong base yields an empty Players vector and zero declared regions [verified]
|
|
constexpr uint32_t StrategyServer_base_delta = 0x00000004;
|
|
// field int, S+4 frame (== S+0x8). Incremented by the FIRST instruction of StrategyServer::ProcessTurn (`inc [esi+8]` @0x007dc6f0). This is a SECOND per-turn counter, distinct from StrategyServer_off_ModCount (0x8 in this frame == S+0xc), which BeginProcessTurn increments and which the research code stamps and reads. Both advance once per turn in different functions. Nobody has named this one [verified]
|
|
constexpr uint32_t StrategyServer_off_PhaseCounter = 0x00000004;
|
|
// field std::vector<ServerSystem*> (begin @+0x40, end @+0x44) in the S+4 frame, i.e. S+0x44/S+0x48. Iterated three times per turn: the morale/abandon pre-pass, the ServerSystem::ProcessTurn loop, and the tail's 0x00743ec0 sweep [verified]
|
|
constexpr uint32_t StrategyServer_off_Systems = 0x00000040;
|
|
// field ServerTradeManager* in the S+4 frame (S+0x158). The `this` for ServerTradeManager::ProcessTurn, phase 2 of the turn [verified]
|
|
constexpr uint32_t StrategyServer_off_TradeManager = 0x00000154;
|
|
// field SVScriptObject* in the S+4 frame (S+0x1b4), null in a normal game. The end-of-turn tail calls two pairs on it when non-null: vft[0x10](6, 0) then vft[0x2c](), and later vft[0x10](0x1c, 0) then vft[0x78]() [verified]
|
|
constexpr uint32_t StrategyServer_off_ScriptObject = 0x000001b0;
|
|
// field std::vector<TeamRecord> in the S+4 frame (S+0x1e8), element stride 0x74. Rebuilt each turn by 0x007d7f70(this, &teamRecords) and then swept: for every member of every record, if (player->IsAI(+0xf9)==0 || player->+0xfa != 0) player->Status(+0x164) = 1. Strides verified from the signed-division magics: 0x8d3dcb09 with `sar 6` is /0x74, 0x78787879 with `sar 5` is /0x44 [verified]
|
|
constexpr uint32_t StrategyServer_off_TeamRecords = 0x000001e4;
|
|
// field sizeof(TeamRecord), the element of StrategyServer's team vector at +0x1e4 (S+4 frame) [verified]
|
|
constexpr uint32_t TeamRecord_stride = 0x00000074;
|
|
// field std::vector<TeamMember> inside a TeamRecord (begin @+0x28, end @+0x2c), element stride 0x44, first word of each element a ServerPlayer* [verified]
|
|
constexpr uint32_t TeamRecord_off_Members = 0x00000028;
|
|
// field sizeof(TeamMember). Only its first word (ServerPlayer*) is read by the turn's Status sweep; the other 0x40 bytes are unread by that pass [verified]
|
|
constexpr uint32_t TeamMember_stride = 0x00000044;
|
|
// field void* to a per-player per-turn record. StrategyServer::ProcessTurn's player pre-pass writes rec->+0x8 = (1 << playerSlot) and ORs in the player's alliance mask AL(+0x16c) when ALid(+0x168) != -1; ServerPlayer::ProcessTurn then writes rec->+0x10 = budget[2] (trade income). NOTE the bit index is the player's POSITION in the server's Players vector, not PlyrIdx [verified]
|
|
constexpr uint32_t ServerPlayer_off_TurnRecord = 0x000003d8;
|
|
// field int, written by ServerPlayer::ProcessTurn from budget[19] (research points given away as aid). Not serialized. strategic-turn-internals.md §1.2 step 2 calls this a per-turn accumulator that is ZEROED; it is ASSIGNED [verified]
|
|
constexpr uint32_t ServerPlayer_off_ResearchAidGiven = 0x000000c8;
|
|
// field int, written by ServerPlayer::ProcessTurn from budget[14] (savings given away as aid). Not serialized [verified]
|
|
constexpr uint32_t ServerPlayer_off_SavingsAidGiven = 0x000000cc;
|
|
// field float RebOutMod. When RebAI(+0xfc) is set, ServerPlayer::ProcessTurn does RebOutMod = clamp(RebOutMod - 0.04f, 1.0f, 2.0f) each turn. The three constants are image floats: 0x00a17870 = 0.04f, 0x00a17868 = 1.0f, 0x00a1786c = 2.0f [verified]
|
|
constexpr uint32_t ServerPlayer_off_RebOutMod = 0x00000128;
|
|
// field int Status (serialized). 1 = playing, 4 = turn done (SNMSetPlayerStatus). Set back to 1 by the end-of-turn team sweep for every member with (IsAI==0 || +0xfa != 0) [verified]
|
|
constexpr uint32_t ServerPlayer_off_Status = 0x00000164;
|
|
// field int ALid, the first word of the PlayerAlliances block {ALid, AL, NA, CF} at +0x168. -1 means no alliance; the turn's per-player pre-pass tests it before OR-ing AL into the turn record's visibility mask [verified]
|
|
constexpr uint32_t ServerPlayer_off_AllianceId = 0x00000168;
|
|
// field int AL, the alliance member bitmask at PlayerAlliances+4. OR-ed into (player->+0x3d8)->+0x8 once per turn when ALid != -1 [verified]
|
|
constexpr uint32_t ServerPlayer_off_AllianceMask = 0x0000016c;
|
|
// field std::vector<RaidTarget> (begin @+0x338, end @+0x33c), element stride 0x20, save tags `rdtc` + n x `rdt`. Pruned every turn by ServerPlayer_PruneRaidTargets. Fields the prune reads: +0x4 entity id, +0xc/+0x10 an inner range, +0x1c the turn the record was created [verified]
|
|
constexpr uint32_t ServerPlayer_off_RaidTargets = 0x00000338;
|
|
// field sizeof(RaidTarget), the element of ServerPlayer+0x338. Confirmed by the prune's `add edi,0x20` step and its `sar ecx,5` count [verified]
|
|
constexpr uint32_t RaidTarget_stride = 0x00000020;
|
|
// field std::vector<TimedResearchBonus> (begin @+0x3a4, end @+0x3a8), element {float PRm; int PRBt}, save tags `NumPR` + n x (`PRm`,`PRBt`). Swept once per turn by ServerPlayer::ProcessTurn: `for (i = count-1; i >= 0; --i) { TRM += e[i].PRm; if (--e[i].PRBt <= 0) erase(i); }`. THE ITERATION IS DESCENDING - float addition is not associative, so the order is load-bearing for bit-exactness [verified]
|
|
constexpr uint32_t ServerPlayer_off_TimedResearchBonuses = 0x000003a4;
|
|
// field sizeof(TimedResearchBonus) = { float PRm; int PRBt }. Confirmed by `sar edi,3` on the byte count and `[ecx+edi*8]` addressing [verified]
|
|
constexpr uint32_t TimedResearchBonus_stride = 0x00000008;
|
|
// thiscall bool (ServerPlayer* this) // the branch RollResearchEvent takes when odds > roll -- the one the campaign has never observed firing. Splits on the CURRENT RESEARCH TARGET: (a) ResT is one of five hard-coded plague-cure TechDefs (ids 0x273c..0x2740, resolved by 0x00535480) -> 0x00889bb0, which draws ONE FURTHER Mars::RNG::NextInt to pick a random element of this->OwnId (+0x30/+0x34) and posts EVENT_PLAGUE_OUTBREAK naming it; (b) ResT is in the aggression tech table (0x00690f70) AND this->+0x3b5 is set -> 0x008206a0, which allocates a Game::AIRebellionImpl into +0x3b8 if absent and then CANCELS the current research (progress reset 0x0057e7c0, state back to 2 via 0x0057e8d0, vtable hook, ResT = NULL), with no further RNG; (c) otherwise nothing. SO A FIRED ROLL COSTS 1 OR 2 RNG WORDS, not 1 -- every existing coverage note says 'exactly one NextFloat', which is the cost of REACHING here [mapped]
|
|
constexpr uint32_t ServerPlayer_OnResearchRollSucceeded = 0x00489d60;
|
|
// field bool `cta` -- the AI-rebellion gate of the succeeded-roll branch (0x00889d60 path b). The ServerPlayer constructor writes it in the SAME two-byte store that arms ResErrRoll: `mov WORD [esi+0x3b4], 1` sets +0x3b4 = 1 and +0x3b5 = 0. The two research-roll flags are adjacent and initialised together [mapped]
|
|
constexpr uint32_t ServerPlayer_off_RebellionRollArmed = 0x000003b5;
|
|
// thiscall void (StrategyServer* this, std::vector<int>* actionTypes, bool force) // the ship-action dispatcher, run THREE TIMES per turn with different type sets -- instruction-verified at the three call sites 0x007dc92b, 0x007dc952, 0x007dcb0c. Walks the queued-ship-action list at S+0x78/0x7c and dispatches each action whose type is in `actionTypes` through a 12-entry function-pointer table indexed by type (targets include 0x00789500, the ship-borne BuildQueue::ProcessTurn wrapper); with `force` it validates (0x0083cbb0) and cancels (0x00849280) instead. Sets: {0..14} minus {2} before movement, {2} after movement, {0..14} with force at the end. Type 2 is the action that requires the fleet to have arrived [mapped]
|
|
constexpr uint32_t StrategyServer_ProcessShipActions = 0x003b9b90;
|
|
// cdecl std::vector<int>* (std::vector<int>* out) // fills *out with the fifteen ship-action type ids 0..14 and returns it. IT IS NOT AN ENCOUNTER SNAPSHOT -- turn-spine.md §2.4 steps 4 and 11 read it as one, which is where the 'the tail is deferred when encounters exist' error came from. Encounter detection is 0x007d7f70 at the very end of the turn [mapped]
|
|
constexpr uint32_t StrategyServer_BuildShipActionTypeList = 0x00394ad0;
|
|
// thiscall void (StrategyServer_secondBase* this) // phase 0, called on S+4. Per system copies current words into shadow words (+0x20c/0x210/0x214 <- +0x68/0x6c/0x70, +0x150.. <- +0x120.., +0xc9 <- +0xc8) via 0x00753530; per player +0x188 <- Sav(+0x284) and +0x18c <- +0x138. Also called from BuildTurnEvents, so it is the baseline 'changed since last turn' is diffed against. Draw-free, event-free [mapped]
|
|
constexpr uint32_t StrategyServer_SnapshotPreviousTurn = 0x0041b390;
|
|
// cdecl void (StrategyServer* server) // the end-of-turn fog-of-war update, timed with QueryPerformanceCounter and logged as "Sensors: %f seconds to update sensors for all players.". Wraps 0x0086a6d0: per player x per system (predicate 0x00850cf0) and per player x per fleet (predicate 0x0081e630), packing 2-bit visibility results into system+0x24 and fleet+0x54 for up to 15 players. Deterministic, no events [mapped]
|
|
constexpr uint32_t StrategyServer_UpdateSensors = 0x0046a8d0;
|
|
// thiscall void (ServerPlayer* this, bool flag) // no-ops unless this->AIRebellion(+0x3b8) is non-null; steps it (0x006d1fd0(flag)), and if it signals completion (0x00690ff0) calls its vft[0](1) and nulls +0x3b8. Called per player with flag=0 from StrategyServer::ProcessTurn's tail and with flag=1 from the deferred combat tail [mapped]
|
|
constexpr uint32_t StrategyServer_StepAIRebellion = 0x00418530;
|
|
// thiscall void (StrategyServer* this, std::vector<TeamRecord>* out) // the LAST phase of the turn: per system with combatants present (0x0078cb10), builds the pairwise 0x74-byte team records for fleet pairs whose relation (0x0080e050) is war, into StrategyServer+0x1e4 (S+4 frame). The turn's Status-back-to-playing sweep then walks the records it produced [mapped]
|
|
constexpr uint32_t StrategyServer_DetectEncounters = 0x003d7f70;
|
|
// data const float = 0.5f. The ONLY consumer is the ResearchRollPending block in ServerPlayer::ProcessTurn: the roll fires when 0.5f < progress/Cost, strictly. Not a registered config key - it is an image literal [verified]
|
|
constexpr uint32_t g_flt_ResearchRollProgressThreshold = 0x0062c788;
|
|
// data const float = 0.04f, subtracted from RebOutMod each turn for a RebAI player [verified]
|
|
constexpr uint32_t g_flt_RebOutModDecay = 0x00617870;
|
|
// data const float = 1.0f, the lower clamp of RebOutMod [verified]
|
|
constexpr uint32_t g_flt_RebOutModMin = 0x00617868;
|
|
// data const float = 2.0f, the upper clamp of RebOutMod [verified]
|
|
constexpr uint32_t g_flt_RebOutModMax = 0x0061786c;
|
|
// thiscall bool (TechTree* this, TechPrereqs* prereqs /* = TechDef + 0x88 */) // RET 4. An AND over groups, each group an OR over techs: a group is satisfied by any listed tech whose node exists in this->nodes AND whose state (+0x14) is 4. Zero groups -> TRUE (the function returns satisfied==total with both 0); a group with ZERO entries -> FALSE and the whole test fails, because the inner loop cannot break and the outer one then exits with that group uncounted. Reads only. Called from SetResearched twice: the unforced completion gate on the argument def, and the availability sweep on each node's self-resolved def [verified]
|
|
constexpr uint32_t TechTree_PrereqsMet = 0x0017d8e0;
|
|
// field TechPrereqs prereqs -- the block TechTree::PrereqsMet is called on. Two MSVC vectors back to back: the flat entry array at +0x00 and the group array at +0x10. SetResearched passes `def + 0x88` at both call sites [verified]
|
|
constexpr uint32_t TechDef_off_Prereqs = 0x00000088;
|
|
// field std::vector<TechPrereqEntry> at TechPrereqs+0x00; only its _Myfirst is read (`*param_2`), because the groups carry the bounds. Element stride 8, the entry's TechDef* at +0x00; the second word is never read by PrereqsMet [verified]
|
|
constexpr uint32_t TechPrereqs_off_Entries = 0x00000000;
|
|
// field std::vector<TechPrereqGroup> at TechPrereqs+0x10 (_Myfirst +0x10, _Mylast +0x14). Group count = (last - first) >> 3. Each group is {int start; int count} indexing the entry array: the OR-set is entries[start .. start+count) [verified]
|
|
constexpr uint32_t TechPrereqs_off_Groups = 0x00000010;
|
|
// field sizeof(TechPrereqEntry) -- the flat prerequisite entry, {TechDef* def; int unread} [verified]
|
|
constexpr uint32_t TechPrereqs_entry_stride = 0x00000008;
|
|
// field sizeof(TechPrereqGroup) -- {int start; int count} into the entry array [verified]
|
|
constexpr uint32_t TechPrereqs_group_stride = 0x00000008;
|
|
// field BYTE. Non-zero excludes the node from SetResearched's availability sweep entirely (`if ((char)def[0x2c] != 0) continue`, i.e. def+0xb0) -- it can still be completed by an explicit SetResearched and its cost/state are still lowered by the parent-edge sweep, but nothing ever moves it to state 2 and it never stamps turnAvailable, so it can never raise EVENT_TECHS_UNLOCKED. THE WRITE SITE WAS NOT READ: the name records what the byte does, not where it comes from. The tech-file keyword `unlock_explicitly` is the obvious candidate and matches the behaviour exactly, but MasterTechTree::ParseTech 0x0058b050 shows no reference to 0xb0 in its decompilation, so the link is a hypothesis and not a fact [verified]
|
|
constexpr uint32_t TechDef_off_NoAutoAvailable = 0x000000b0;
|
|
// field std::string name (0x1c bytes, _Mysize at +0x50, _Myres at +0x54: >= 0x10 selects the heap pointer). The substitution for every research event's %s, and the key RecordObservedTech de-duplicates the observed-tech vector on [verified]
|
|
constexpr uint32_t TechDef_off_Name = 0x00000040;
|
|
// constant flags bit 3 of SetResearched(def, flags): after the call, and after each recursive call from the availability sweep, run the refresh helper 0x00585ef0. Also enables the `node slot is NULL` re-lookup path at the head of the function (0x00580e30). No research-path call site sets it -- ProcessResearch passes 2 -- so neither behaviour is modelled [verified]
|
|
constexpr uint32_t TechTree_SetResearched_flag_Refresh = 0x00000008;
|
|
// site site inside TechTree::ProcessResearch: the tail loop that collects the newly available nodes for EVENT_TECHS_UNLOCKED. Runs only when tree->owner != 0, after the per-node loop AND after the decay sweep. Collects every node n with n != NULL, n->def != NULL, p = tree->nodes[n->def->techId] != NULL, p->state (+0x14) == 2, and n->turnAvailable (+0x20) == the owner's ModCount. Posts once if the collected vector is non-empty. NOTE the asymmetry: the state test is on the SELF-RESOLVED node p, the turn test on the iterated node n [verified]
|
|
constexpr uint32_t TechTree_ProcessResearch_TechsUnlockedCollector = 0x00187cc3;
|
|
// site site at the very head of ServerPlayer::OnTechResearched: RecordObservedTech is the FIRST statement, called unconditionally on every completion -- before the ResT/roll block and before the !silent event post. It de-duplicates by tech name, so the observed-tech vector grows by one 0x2c element per completion of a tech not already observed and by nothing otherwise [verified]
|
|
constexpr uint32_t ServerPlayer_OnTechResearched_RecordObservedTech = 0x00491790;
|
|
// site site in ServerPlayer::OnTechResearched, second statement: `if (this->ResT(+0x294) == def) { if (this->ResearchRollPending(+0x3b4)) RollResearchEvent(this); this->ResearchRollPending = 0; this->ResT = 0; }`. RollResearchEvent (0x0088df20) draws ONE NextFloat unconditionally and then enters ServerPlayer_OnResearchRollSucceeded (0x00889d60) only when roll < ResearchEventOdds -- the odds are 0 for every tech outside the plague and AI-rebellion families, so that branch is normally dead. CORRECTED BY LANE K 2026-09-08: that one word is the cost of REACHING the branch, not of a fired roll -- the plague path draws a SECOND word (NextInt) and posts EVENT_PLAGUE_OUTBREAK, the rebellion path cancels the research. A fired roll costs one or two words. This is the extra RNG a completion consumes, and clearing ResT means a second completion in the same pass consumes none [verified]
|
|
constexpr uint32_t ServerPlayer_OnTechResearched_ResearchRollBlock = 0x00491790;
|
|
// cdecl Game::SVScriptObject* (int encID) // The EncObj factory. `dec eax; cmp eax,0x16; ja <null>; jmp dword [eax*4 + 0x0052bf60]` -- a 23-entry dword jump table indexed by encID-1. Live ids: 1 VonNeumann, 3 Swarm, 4 Derelict, 5 Monitor, 7 SystemKiller, 8 PuppetMaster, 9 SlaversRefuel, 10 SwarmQueen, 14 Locust, 17 CrowRuins, 20 Refugees, 21 Ortgay. Ids 2, 6, 11-13, 15, 16, 18, 19, 22, 23 and everything outside 1..23 return NULL. Class names read off the vftable store in each ctor [verified]
|
|
constexpr uint32_t SVScriptObject_FactoryByEncID = 0x0012bf00;
|
|
// data void* [23] // the jump table SVScriptObject_FactoryByEncID indexes with encID-1 [verified]
|
|
constexpr uint32_t SVScriptObject_EncIDJumpTable = 0x0012bf60;
|
|
// thiscall Game::SVScriptObject* (Game::SVSOSots* this, int encID) // scans the loaded (id, obj) pair vector at this+0x0C..0x10 first and only calls SVScriptObject_FactoryByEncID on a miss [verified]
|
|
constexpr uint32_t SVSOSots_GetOrCreateEncObj = 0x001a4450;
|
|
// cdecl Game::SVScriptObject* (const char* xscn) // the xsc factory: a flat four-way _stricmp chain, EXHAUSTIVE. "traps" -> Game::SVSOTraps (ctor 0x0052cbb0), "crowdefs" -> Game::SVSOCrowDefenders (0x0052b3c0), "indsys" -> Game::SVSOIndependentSystems (0x0075b530), "gmtrigger" -> Game::SVSOGrandMenaceTrigger (0x004f5630). Falls through with "Error creating extra script %s." [verified]
|
|
constexpr uint32_t SVScriptObject_FactoryByScenarioName = 0x001a7050;
|
|
// thiscall void (Game::SVSOSots* this) // registers exactly traps, crowdefs, indsys and -- only when ScnObj == NULL -- gmtrigger. Corroborates that the four scenario names are the whole set [verified]
|
|
constexpr uint32_t SVSOSots_SeedNewGameScripts = 0x001a7d70;
|
|
// thiscall void (Game::SVSOSots* this, Mars::IStream* s) // slot 2 of vftable 0x00A063C4. Emits ScnID, ScnObj (only when non-NULL), numx x (xscn, xsc), NEncObjs x (EncID, EncObj) [verified]
|
|
constexpr uint32_t SVSOSots_Write = 0x0019ddf0;
|
|
// thiscall void (Game::SVSOSots* this, Mars::IStream* s) // slot 1. Accepts two tags Write never emits -- NPCPlr (int, first) and hastraps (bool) with a following traps object -- read-only backward compatibility; a writer that omits them is correct [verified]
|
|
constexpr uint32_t SVSOSots_Read = 0x001a7a40;
|
|
// data Game::SVSOSots vftable (slot0 dtor, slot1 Read 0x005a7a40, slot2 Write 0x0059ddf0) [verified]
|
|
constexpr uint32_t SVSOSots_vftable = 0x006063c4;
|
|
// data Game::SVSOIndependentSystems vftable. slot0 dtor 0x0075afb0; slot1 Read AND slot2 Write are both 0x005f8ac0, the shared `ret 4` no-op stub -- so the "indsys" frame is genuinely empty on disk and the class's 0x1c4-byte body is runtime-only state [verified]
|
|
constexpr uint32_t SVSOIndependentSystems_vftable = 0x00620314;
|
|
// thiscall void (void*, Mars::IStream*) // `C2 04 00` -- a bare RET 4 shared as the inherited Read/Write for classes that serialize nothing [verified]
|
|
constexpr uint32_t Streamable_NoOpStub = 0x001f8ac0;
|
|
// thiscall void (Game::SVSOCrowDefenders* this, Mars::IStream* s) // sys; ndsys count then a loop writing dsys; ndes count then a loop writing des; drad. NOTE: `dsys` is INSIDE the ndsys loop -- objects/layouts.json records it as a plain member, which is wrong, and no save can settle it because both counts are 0 everywhere [verified]
|
|
constexpr uint32_t SVSOCrowDefenders_Write = 0x000f8c90;
|
|
// thiscall void (Game::SVSOMonitor* this, Mars::IStream* s) // calls SVSODerelict::Write (0x004fc2b0) as its first act -- Monitor derives from Derelict, which is why its tag run starts NDsn/DsnID/Dwght + NAsg/Eflt/Esys before nt/scnm/spwt/rsmd/dsgn [verified]
|
|
constexpr uint32_t SVSOMonitor_Write = 0x000fd810;
|
|
// thiscall void (Game::SVSODerelict* this, Mars::IStream* s) // NDsn count then a loop of (DsnID, Dwght); NAsg count then a loop of (Eflt, Esys). Two fields per iteration in each, confirmed by the 8-byte element strides [verified]
|
|
constexpr uint32_t SVSODerelict_Write = 0x000fc2b0;
|
|
|
|
} // namespace sots::addr
|