90 lines
5.3 KiB
C++
90 lines
5.3 KiB
C++
// Hook descriptor for the hard-coded tech-effect callback (B2):
|
|
//
|
|
// Game::ServerPlayer::OnTechResearched(this, TechDef* def, bool silent)
|
|
//
|
|
// A verified __thiscall in sots_addresses.h (vft slot 4), so it goes through Hook<> with
|
|
// CallConv::Thiscall. TechTree::SetResearched calls it every time a tech is marked
|
|
// researched -- from the per-turn research pass (B3's hook), from the Zuul boarding-pod
|
|
// grant this very function makes, and from scenario grants.
|
|
//
|
|
// What it does, and therefore what this hook has to reproduce: an if/else-if chain over
|
|
// tech ids 10000..10031 (at most one branch runs) that writes the player's economy
|
|
// modifiers, followed by a tail that runs on *every* completion -- the plague-cure mask,
|
|
// the two design-option masks, the node-bore parameters, the per-species xenotech flag
|
|
// words and their sticky translation mask, the Zuul boarding-pod grant, the
|
|
// capture-designs pair test, and the temperance sweep.
|
|
//
|
|
// Region model: one region per field group of the player, each with a struct describer,
|
|
// so a divergence names the field (`side.modifiers.after.v.out_mod`) instead of a byte
|
|
// offset in one opaque blob. The regions do not cover the whole object, so `ours` reads
|
|
// every field the call can modify out of the scratch copies and everything else (species,
|
|
// RebAI, the tech tree) from the live player, where "live" and "before" are the same.
|
|
//
|
|
// What `ours` deliberately does NOT do, because compare mode must not touch live game
|
|
// state or consume randomness:
|
|
// * the completion events (EVENT_RESEARCH_COMPLETE / _UNDERBUDGET / _TEMPERANCE);
|
|
// * the pending plague-cure roll (it would draw from the real generator) -- the two
|
|
// words it guards are still cleared, and the record says whether it would have fired;
|
|
// * the writes to *other* objects: the owned systems' AI flag, the arcology civilian-cap
|
|
// re-evaluation, the addiction cure, and the plague clear on systems and ships;
|
|
// * TechTree::SetResearched for the Zuul boarding pods (it would mutate the tree).
|
|
// Each of those is reported in the record's `ours` fields instead, so a trace still shows
|
|
// what our layer decided even where the compare cannot check it.
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
#include "shim/trace/hook.h"
|
|
|
|
namespace shim::hooks {
|
|
|
|
struct ServerPlayerOnTechResearchedHook {
|
|
static constexpr const char* name = "Game::ServerPlayer::OnTechResearched";
|
|
static constexpr trace::CallConv conv = trace::CallConv::Thiscall;
|
|
using Ret = void;
|
|
// this (ServerPlayer*), def (TechDef*), silent
|
|
using Args = std::tuple<void*, void*, bool>;
|
|
|
|
static void describe_args(std::vector<trace::Tv>& out, void* self, void* def, bool silent);
|
|
static void regions(std::vector<trace::Region>& out, void* self, void* def, bool silent);
|
|
static Args rebind(trace::Scratch& s, void* self, void* def, bool silent);
|
|
static void ours(void* self, void* def, bool silent);
|
|
static trace::HookPolicy policy() { return trace::HookPolicy{}; }
|
|
static void coverage(trace::Coverage& c) {
|
|
c.unmodelled("posts EVENT_RESEARCH_COMPLETE / _UNDERBUDGET / _TEMPERANCE on the owner's "
|
|
"EventStorage when !silent",
|
|
trace::Risk::High,
|
|
"the same class of write as B3's defect, and this hook has no replace-mode "
|
|
"oracle that could catch it: gotcha 4 in docs/B2.md says a changed save hash "
|
|
"on a completion turn is expected and therefore not a finding",
|
|
"guard:player (EventStorage is inline at ServerPlayer+0x29c)");
|
|
c.unmodelled("writes every owned system's AI flag (CCC_AIVrus / CCC_AISlv), re-evaluates "
|
|
"the arcology civilian cap, cures addiction and clears plague across systems "
|
|
"AND ships",
|
|
trace::Risk::High,
|
|
"writes through pointers to other objects; compare mode must not touch live "
|
|
"state, and no region reaches them");
|
|
c.unmodelled("the pending plague-cure roll (ServerPlayer::RollResearchEvent)",
|
|
trace::Risk::High,
|
|
"it draws exactly one word from the strategic generator unconditionally; "
|
|
"running it in compare mode would consume real randomness. The two words it "
|
|
"guards are still cleared and the record says whether it would have fired",
|
|
"this is the extra draw B3 observed on a completion");
|
|
c.unmodelled("TechTree::SetResearched for the Zuul boarding-pod grant",
|
|
trace::Risk::Medium, "it would mutate the live tree, and it recurses");
|
|
c.unmodelled("allocates or frees the node-bore block at ServerPlayer+0x308",
|
|
trace::Risk::Medium,
|
|
"ours has no allocator the game's runtime could free, so replace mode calls "
|
|
"the game's own updater -- which means replace mode never exercises our "
|
|
"node-bore selection at all",
|
|
"region:node_bore, declared only when the block already exists");
|
|
}
|
|
};
|
|
|
|
// Process facts the hook needs (exe base for the RVAs, a line logger). Call once before
|
|
// installing.
|
|
void init_tech_effects(std::uintptr_t exe_base, void (*log_line)(const char* line));
|
|
|
|
} // namespace shim::hooks
|