sots-engine/src/shim/hooks/tech_effect_fields.cpp

338 lines
13 KiB
C++

#include "shim/hooks/tech_effect_fields.h"
#include <cstring>
#include <vector>
#include "generated/sots_addresses.h"
namespace shim::hooks::techfx {
using trace::Tv;
namespace tv = trace::tv;
namespace fx = sots::effects;
namespace {
namespace A = sots::addr;
std::int32_t i32_at(const void* p, std::size_t off) {
std::int32_t v = 0;
std::memcpy(&v, static_cast<const char*>(p) + off, sizeof v);
return v;
}
std::uint32_t u32_at(const void* p, std::size_t off) {
std::uint32_t v = 0;
std::memcpy(&v, static_cast<const char*>(p) + off, sizeof v);
return v;
}
float f32_at(const void* p, std::size_t off) {
float v = 0;
std::memcpy(&v, static_cast<const char*>(p) + off, sizeof v);
return v;
}
void* ptr_at(const void* p, std::size_t off) {
void* v = nullptr;
std::memcpy(&v, static_cast<const char*>(p) + off, sizeof v);
return v;
}
std::uint8_t u8_at(const void* p, std::size_t off) {
return static_cast<const std::uint8_t*>(p)[off];
}
void put_i32(void* p, std::size_t off, std::int32_t v) { std::memcpy(static_cast<char*>(p) + off, &v, sizeof v); }
void put_u32(void* p, std::size_t off, std::uint32_t v) { std::memcpy(static_cast<char*>(p) + off, &v, sizeof v); }
void put_f32(void* p, std::size_t off, float v) { std::memcpy(static_cast<char*>(p) + off, &v, sizeof v); }
void put_u8(void* p, std::size_t off, std::uint8_t v) { static_cast<std::uint8_t*>(p)[off] = v; }
void put_ptr(void* p, std::size_t off, void* v) { std::memcpy(static_cast<char*>(p) + off, &v, sizeof v); }
// One describer per field group, so a divergence names the field.
Tv describe_suit(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
s.add("suit_tol", tv::f32(f32_at(p, 0)));
s.add("max_overharvest", tv::f32(f32_at(p, 4)));
return s;
}
Tv describe_res_mod(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
s.add("res_mod", tv::f32(f32_at(p, 0)));
return s;
}
Tv describe_abilities(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
s.add("reb_ai", tv::boolean(u8_at(p, 0x00) != 0));
s.add("unk_fd", tv::u8(u8_at(p, 0x01)));
s.add("ai_benefit", tv::boolean(u8_at(p, 0x02) != 0));
s.add("trade_allowed", tv::boolean(u8_at(p, 0x03) != 0));
s.add("commerce_raiding", tv::boolean(u8_at(p, 0x04) != 0));
s.add("view_intel", tv::boolean(u8_at(p, 0x05) != 0));
s.add("grav_synth", tv::boolean(u8_at(p, 0x06) != 0));
s.add("advanced_sensors", tv::boolean(u8_at(p, 0x07) != 0));
s.add("arcology", tv::boolean(u8_at(p, 0x08) != 0));
s.add("unk_105", tv::u8(u8_at(p, 0x09)));
s.add("unk_106", tv::u8(u8_at(p, 0x0a)));
s.add("unk_107", tv::u8(u8_at(p, 0x0b)));
return s;
}
// +0x108 .. +0x15c. The five words this module does not model are emitted as raw u32 so a
// surprise write by the original shows up as a real divergence rather than going unseen.
Tv describe_modifiers(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
s.add("pddm", tv::f32(f32_at(p, 0x00)));
std::vector<Tv> con;
for (int i = 0; i < 3; ++i) con.push_back(tv::f32(f32_at(p, 0x04 + 4u * i)));
s.add("con_mod", tv::list(std::move(con)));
std::vector<Tv> sav;
for (int i = 0; i < 3; ++i) sav.push_back(tv::f32(f32_at(p, 0x10 + 4u * i)));
s.add("sav_mod", tv::list(std::move(sav)));
s.add("out_mod", tv::f32(f32_at(p, 0x1c)));
s.add("unk_128", tv::u32(u32_at(p, 0x20)));
s.add("unk_12c", tv::u32(u32_at(p, 0x24)));
s.add("pop_mod", tv::f32(f32_at(p, 0x28)));
s.add("terra_mod", tv::f32(f32_at(p, 0x2c)));
s.add("asteroid_mining", tv::boolean(u8_at(p, 0x30) != 0));
s.add("unk_139_13b", tv::u32(u32_at(p, 0x30) >> 8));
s.add("unk_13c", tv::u32(u32_at(p, 0x34)));
s.add("min_rate", tv::f32(f32_at(p, 0x38)));
s.add("unk_144", tv::u32(u32_at(p, 0x3c)));
s.add("per_gate_traffic", tv::i32(i32_at(p, 0x40))); // an int, not a float
s.add("unk_14c", tv::u32(u32_at(p, 0x44)));
s.add("cast_range", tv::f32(f32_at(p, 0x48)));
s.add("cast_efficiency", tv::f32(f32_at(p, 0x4c)));
s.add("cast_threshold", tv::f32(f32_at(p, 0x50)));
return s;
}
Tv describe_masks(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
s.add("design_options_a", tv::u32(u32_at(p, 0)));
s.add("design_options_b", tv::u32(u32_at(p, 4)));
return s;
}
Tv describe_translation(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
s.add("translation_known", tv::u32(u32_at(p, 0)));
return s;
}
Tv describe_vaccines(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
s.add("has_vaccine", tv::u32(u32_at(p, 0)));
s.add("has_immunity", tv::u32(u32_at(p, 4)));
s.add("node_track_mask", tv::u32(u32_at(p, 8)));
return s;
}
Tv describe_research_target(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
// A heap pointer, so only "still set" / "cleared" is meaningful across two runs.
s.add("research_target", tv::ptr(ptr_at(p, 0)));
s.add("research_target_set", tv::boolean(ptr_at(p, 0) != nullptr));
return s;
}
Tv describe_nodebore_ptr(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
// Pointer values are ignored by the default policy; `present` is the fact that matters,
// and it is deliberately NOT compared (see the header): `ours` cannot allocate the
// block, so a first bore-drive completion would diverge here for the wrong reason.
s.add("node_bore_block", tv::ptr(ptr_at(p, 0)));
return s;
}
Tv describe_inc_mod(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
s.add("inc_mod", tv::f32(f32_at(p, 0)));
return s;
}
Tv describe_capture(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
s.add("capture_designs", tv::boolean(u8_at(p, 0) != 0));
s.add("unk_331_333", tv::u32(u32_at(p, 0) >> 8));
return s;
}
Tv describe_species_flags(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
std::vector<Tv> f;
for (int i = 0; i < sots::sim::kSpeciesCount; ++i) f.push_back(tv::u32(u32_at(p, 4u * i)));
s.add("species_flags", tv::list(std::move(f)));
s.add("count", tv::u32(u32_at(p, 0x1c)));
return s;
}
Tv describe_roll(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
s.add("research_roll_pending", tv::boolean(u8_at(p, 0) != 0));
s.add("unk_3b5_3b7", tv::u32(u32_at(p, 0) >> 8));
s.add("ai_rebellion", tv::ptr(ptr_at(p, 4)));
return s;
}
Tv describe_nodebore(const void* p, std::size_t, unsigned) {
Tv s = tv::struct_();
std::vector<Tv> v;
for (int i = 0; i < 3; ++i) v.push_back(tv::i32(i32_at(p, 4u * i)));
s.add("node_bore_params", tv::list(std::move(v)));
return s;
}
} // namespace
const RegionDef kRegions[kRegionCount] = {
{"suit", A::ServerPlayer_off_SuitTol, 8, &describe_suit},
{"res_mod", A::ServerPlayer_off_ResMod, 4, &describe_res_mod},
{"abilities", A::ServerPlayer_off_RebAI, 12, &describe_abilities},
{"modifiers", A::ServerPlayer_off_pddm, 0x54, &describe_modifiers},
{"design_masks", A::ServerPlayer_off_TechMaskA, 8, &describe_masks},
{"translation", A::ServerPlayer_off_TranslationKnown, 4, &describe_translation},
{"vaccines", A::ServerPlayer_off_HasVac, 12, &describe_vaccines},
{"research_target", A::ServerPlayer_off_ResearchTarget, 4, &describe_research_target},
{"node_bore_ptr", A::ServerPlayer_off_NodeBore, 4, &describe_nodebore_ptr},
{"inc_mod", A::ServerPlayer_off_IncMod, 4, &describe_inc_mod},
{"capture_designs", A::ServerPlayer_off_CaptureDesigns, 4, &describe_capture},
{"species_flags", A::ServerPlayer_off_SpeciesTechFlags, 0x20, &describe_species_flags},
{"roll", A::ServerPlayer_off_ResearchRollPending, 8, &describe_roll},
{"node_bore", 0, 12, &describe_nodebore}, // base is *(player + off_NodeBore)
};
std::size_t PlayerSpan() { return A::ServerPlayer_off_ResearchRollPending + 8; }
Views Views::OverPlayer(void* player) {
Views v;
for (int i = 0; i < kRegionCount; ++i) {
v.base[i] = i == R_NODEBORE ? ptr_at(player, A::ServerPlayer_off_NodeBore)
: static_cast<char*>(player) + kRegions[i].off;
}
return v;
}
// Seed our model from the pre-call field values. Everything the callback can write comes
// from the region copies; species (never written) comes from the live object.
fx::PlayerEconomyState ReadPlayerState(const Views& v_, sots::sim::Species species) {
fx::PlayerEconomyState s;
s.species = species;
const void* suit = v_.base[R_SUIT];
s.suitTol = f32_at(suit, 0);
s.maxOverharvest = f32_at(suit, 4);
s.resMod = f32_at(v_.base[R_RESMOD], 0);
s.incMod = f32_at(v_.base[R_INCMOD], 0);
const void* ab = v_.base[R_ABILITIES];
s.rebelAI = u8_at(ab, 0x00) != 0;
s.aiBenefit = u8_at(ab, 0x02) != 0;
s.flags[static_cast<int>(fx::PlayerFlag::TradeAllowed)] = u8_at(ab, 0x03) != 0;
s.flags[static_cast<int>(fx::PlayerFlag::CommerceRaiding)] = u8_at(ab, 0x04) != 0;
s.flags[static_cast<int>(fx::PlayerFlag::ViewIntel)] = u8_at(ab, 0x05) != 0;
s.flags[static_cast<int>(fx::PlayerFlag::GravSynth)] = u8_at(ab, 0x06) != 0;
s.flags[static_cast<int>(fx::PlayerFlag::AdvancedSensors)] = u8_at(ab, 0x07) != 0;
s.flags[static_cast<int>(fx::PlayerFlag::Arcology)] = u8_at(ab, 0x08) != 0;
const void* m = v_.base[R_MODIFIERS];
s.defenceDamageMod = f32_at(m, 0x00);
for (int i = 0; i < 3; ++i) s.conMod[i] = f32_at(m, 0x04 + 4u * i);
for (int i = 0; i < 3; ++i) s.savMod[i] = f32_at(m, 0x10 + 4u * i);
s.outMod = f32_at(m, 0x1c);
s.popMod = f32_at(m, 0x28);
s.terraMod = f32_at(m, 0x2c);
s.flags[static_cast<int>(fx::PlayerFlag::AsteroidMining)] = u8_at(m, 0x30) != 0;
s.miningRate = f32_at(m, 0x38);
s.perGateTraffic = i32_at(m, 0x40);
s.castRange = f32_at(m, 0x48);
s.castEfficiency = f32_at(m, 0x4c);
s.castThreshold = f32_at(m, 0x50);
const void* v = v_.base[R_VACCINES];
s.hasVaccine = u32_at(v, 0);
s.hasImmunity = u32_at(v, 4);
s.nodeTrackMask = u32_at(v, 8);
s.translationKnownMask = u32_at(v_.base[R_TRANSLATION], 0);
s.flags[static_cast<int>(fx::PlayerFlag::CaptureDesigns)] =
u8_at(v_.base[R_CAPTURE], 0) != 0;
// Present exactly when the block exists: in compare mode that is the declared region,
// in replace mode the live pointer.
if (const void* nb = v_.base[R_NODEBORE]) {
s.hasNodeBoreParams = true;
for (int i = 0; i < 3; ++i) s.nodeBoreParams[i] = i32_at(nb, 4u * i);
}
return s;
}
void WritePlayerState(const Views& v_, const fx::PlayerEconomyState& s) {
void* suit = v_.base[R_SUIT];
put_f32(suit, 0, s.suitTol);
put_f32(suit, 4, s.maxOverharvest);
put_f32(v_.base[R_RESMOD], 0, s.resMod);
put_f32(v_.base[R_INCMOD], 0, s.incMod);
void* ab = v_.base[R_ABILITIES];
put_u8(ab, 0x02, s.aiBenefit ? 1 : 0);
put_u8(ab, 0x03, s.Flag(fx::PlayerFlag::TradeAllowed) ? 1 : 0);
put_u8(ab, 0x04, s.Flag(fx::PlayerFlag::CommerceRaiding) ? 1 : 0);
put_u8(ab, 0x05, s.Flag(fx::PlayerFlag::ViewIntel) ? 1 : 0);
put_u8(ab, 0x06, s.Flag(fx::PlayerFlag::GravSynth) ? 1 : 0);
put_u8(ab, 0x07, s.Flag(fx::PlayerFlag::AdvancedSensors) ? 1 : 0);
put_u8(ab, 0x08, s.Flag(fx::PlayerFlag::Arcology) ? 1 : 0);
void* m = v_.base[R_MODIFIERS];
put_f32(m, 0x00, s.defenceDamageMod);
for (int i = 0; i < 3; ++i) put_f32(m, 0x04 + 4u * i, s.conMod[i]);
for (int i = 0; i < 3; ++i) put_f32(m, 0x10 + 4u * i, s.savMod[i]);
put_f32(m, 0x1c, s.outMod);
put_f32(m, 0x28, s.popMod);
put_f32(m, 0x2c, s.terraMod);
put_u8(m, 0x30, s.Flag(fx::PlayerFlag::AsteroidMining) ? 1 : 0);
put_f32(m, 0x38, s.miningRate);
put_i32(m, 0x40, s.perGateTraffic);
put_f32(m, 0x48, s.castRange);
put_f32(m, 0x4c, s.castEfficiency);
put_f32(m, 0x50, s.castThreshold);
void* v = v_.base[R_VACCINES];
put_u32(v, 0, s.hasVaccine);
put_u32(v, 4, s.hasImmunity);
put_u32(v, 8, s.nodeTrackMask);
put_u32(v_.base[R_TRANSLATION], 0, s.translationKnownMask);
put_u8(v_.base[R_CAPTURE], 0, s.Flag(fx::PlayerFlag::CaptureDesigns) ? 1 : 0);
void* sf = v_.base[R_SPECIES_FLAGS];
for (int i = 0; i < sots::sim::kSpeciesCount; ++i) put_u32(sf, 4u * i, s.speciesFlags[i]);
put_u32(sf, 0x1c, static_cast<std::uint32_t>(sots::sim::kSpeciesCount));
// The block is only writable where it already exists; see the header.
void* nb = v_.base[R_NODEBORE];
if (nb && s.hasNodeBoreParams) {
for (int i = 0; i < 3; ++i) put_i32(nb, 4u * i, s.nodeBoreParams[i]);
}
}
bool ClearResearchTargetIfMatched(const Views& v_, const void* def) {
void* rt = v_.base[R_RESEARCH_TARGET];
void* roll = v_.base[R_ROLL];
if (!rt || ptr_at(rt, 0) != def) return false;
const bool pending = roll && u8_at(roll, 0) != 0;
if (roll) put_u8(roll, 0, 0);
put_ptr(rt, 0, nullptr); // a pointer-sized zero: 4 bytes on the i386 target
return pending;
}
void WriteDesignOptionMasks(const Views& v_, std::uint32_t a, std::uint32_t b) {
void* mk = v_.base[R_MASKS];
if (!mk) return;
put_u32(mk, 0, a);
put_u32(mk, 4, b);
}
} // namespace shim::hooks::techfx