96 lines
5.6 KiB
C++
96 lines
5.6 KiB
C++
// Hook descriptor for the per-system colony turn (B4):
|
|
//
|
|
// Game::ServerSystem::ProcessTurn(this)
|
|
//
|
|
// A verified `__thiscall` with **no stack arguments** and no return value, so it goes through
|
|
// Hook<> with CallConv::Thiscall. It is called once per system per turn from
|
|
// StrategyServer::ProcessTurn -- about 28 calls on the reference save's End Turn.
|
|
//
|
|
// Why it is worth a compare even though it is a dispatcher: the words the function body writes
|
|
// itself are a real, self-contained slice of a colony turn (the unowned infrastructure decay,
|
|
// both bonus pools, the long-stability accrual, the turn's resource reset, the growth-halt
|
|
// clear and the two per-player countdown sweeps), and they are exactly the ones our
|
|
// `sim::ProcessColonyTurn` models. Everything the callees write is left undeclared, so the
|
|
// harness never compares it.
|
|
//
|
|
// The generator is a declared region as well. A colony turn's only RNG consumer is
|
|
// ProcessRebellion (ProcessPlague, the civilian growth pass and ProcessSlaves were each swept
|
|
// to call depth one and are draw-free), so on a system with no rebellion the post-call state
|
|
// must be **identical** -- and any movement of it names the system whose rebellion fired.
|
|
// COVERAGE STATEMENT (for meta.hooks[H].coverage once HookPolicy carries the field; the
|
|
// harness audit lane owns that struct change, so this lives as text until it lands).
|
|
// state: "partial"
|
|
// unmodelled:
|
|
// - what: "the whole plague pass (ProcessPlague), imperial and civilian population growth,
|
|
// the resource debit (AdjustResources), the in-orbit refuel, ProcessSlaves and
|
|
// ProcessRebellion"
|
|
// risk: high mitigation: "none - those fields are not declared regions at all"
|
|
// - what: "the addiction sweep's morale events: ours computes the {species, event id,
|
|
// delta} list but nothing applies it, so the system's Morale int[7] and its
|
|
// morale-event vector are never written or compared"
|
|
// risk: high why: "this is exactly B3's failure mode - a list append outside every
|
|
// declared region" mitigation: "guard:morale (not yet declared)"
|
|
// - what: "the independent-colony imperial<->civilian pop drift (0x007514f0), called
|
|
// between the bonus pools and the stability check"
|
|
// risk: medium mitigation: "none"
|
|
// - what: "TnsOH, the build queue's own list, and every ship the queue creates"
|
|
// risk: medium mitigation: "none"
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
#include "shim/trace/hook.h"
|
|
|
|
namespace shim::hooks {
|
|
|
|
struct ServerSystemProcessTurnHook {
|
|
static constexpr const char* name = "Game::ServerSystem::ProcessTurn";
|
|
static constexpr trace::CallConv conv = trace::CallConv::Thiscall;
|
|
using Ret = void;
|
|
using Args = std::tuple<void*>; // this (ServerSystem*)
|
|
|
|
static void describe_args(std::vector<trace::Tv>& out, void* self);
|
|
static void regions(std::vector<trace::Region>& out, void* self);
|
|
static Args rebind(trace::Scratch& s, void* self);
|
|
static void ours(void* self);
|
|
static trace::HookPolicy policy() { return trace::HookPolicy{}; }
|
|
static void coverage(trace::Coverage& c) {
|
|
c.unmodelled("the addiction sweep raises MoraleEvents, which are constructed and appended "
|
|
"to the system's capped morale history",
|
|
trace::Risk::High,
|
|
"the same class of write as B3's defect. sim::ProcessColonyTurn does compute "
|
|
"the morale events (ColonyTurnResult), but the hook never emits them: "
|
|
"DescribeMoraleEvents is dead code, so they are neither compared nor logged",
|
|
"guard:system");
|
|
c.unmodelled("every callee: the plague pass, imperial and civilian growth, the resource "
|
|
"debit, in-orbit refuel, slaves, rebellion and the build queue",
|
|
trace::Risk::High,
|
|
"declared input boundary -- ProcessTurn is a dispatcher and only the words "
|
|
"it writes itself are modelled. The callees raise EVENT_SLAVES_DEAD, "
|
|
"EVENT_SYSTEM_REBELLION_CONTINUES, the plague events and SEBuildCompleted, "
|
|
"create ships and bump per-player ShipRecords counters",
|
|
"guard:system covers the system object only, not the other objects");
|
|
c.unmodelled("ApplyInfraBonus / ApplyPopBonus read the owner's home-system id, and the "
|
|
"build queue writes the owning ServerPlayer",
|
|
trace::Risk::Medium,
|
|
"writes through a pointer to another object; no region reaches the player");
|
|
c.unmodelled("ProcessRebellion is the pass's only RNG consumer and its draw count is "
|
|
"data-dependent",
|
|
trace::Risk::Low,
|
|
"the generator IS a declared region, so a moved post-state is visible and "
|
|
"names the system whose rebellion fired -- it is reported, not modelled");
|
|
c.unmodelled("replace mode is refused for this hook",
|
|
trace::Risk::Medium,
|
|
"our side models the dispatcher's own writes and none of the callees, so a "
|
|
"replace run would silently skip a colony's whole turn. There is therefore "
|
|
"no oracle layer behind the compare for this hook");
|
|
}
|
|
};
|
|
|
|
// Process facts the hook needs (exe base for the RVAs, a line logger). Call once before
|
|
// installing.
|
|
void init_colony_turn(std::uintptr_t exe_base, void (*log_line)(const char* line));
|
|
|
|
} // namespace shim::hooks
|