sots-engine/src/shim/hooks/global_consts.h

62 lines
3.3 KiB
C++

// Hook descriptor for Mars::GlobalConsts::LoadFile(const char* file, GlobalConstMap* consts)
// -- the file-level read of the flat KEY/value constant tables (M1).
//
// Side-effect model: the map handed to LoadFile lists every constant registered for that
// file (key -> {storage word, parser, file}). The hook walks the map before the original
// runs and declares one region per constant, named by its key, as wide as its parser's
// type (int / float / scaled float = 4 bytes, colour = float[4] = 16 bytes), with a struct
// describer so a diff names the field ("v", or "r"/"g"/"b"/"a"). In compare mode `ours`
// (game::config::apply over the same file bytes, read through the game's own gobio) fills
// the scratch copies of those words; the tracer diffs them against the original's.
//
// The map itself is a temporary of LoadAll (the original erases consumed keys from it); it
// is not declared as a region and `ours` never touches it.
//
// LoadFile is cdecl with a verified prototype, so the Hook<> template applies directly.
#pragma once
#include <cstdint>
#include <tuple>
#include <vector>
#include "shim/trace/hook.h"
namespace shim::hooks {
struct GlobalConstsLoadFileHook {
static constexpr const char* name = "Mars::GlobalConsts::LoadFile";
static constexpr trace::CallConv conv = trace::CallConv::Cdecl;
using Ret = void;
using Args = std::tuple<const char*, void*>;
static void describe_args(std::vector<trace::Tv>& out, const char* file, void* consts);
static void regions(std::vector<trace::Region>& out, const char* file, void* consts);
static Args rebind(trace::Scratch& s, const char* file, void* consts);
static void ours(const char* file, void* consts);
static trace::HookPolicy policy() { return trace::HookPolicy{}; }
static void coverage(trace::Coverage& c) {
c.unmodelled("erases each consumed key from the caller's std::map",
trace::Risk::Medium,
"the map is a LoadAll temporary; declaring a red-black tree as a region is "
"not possible before the call. First-occurrence-wins is reproduced in "
"game::config::apply instead, so the *effect* is modelled, the container is "
"not",
"LoadAll's post-state would have to be hooked to see it");
c.unmodelled("writes three kinds of line to the game log (unrecognised key, applied "
"key, expected-but-not-found)",
trace::Risk::Low, "log text is not part of the simulation state");
c.unmodelled("opens the file through the VFS and allocates/releases a refcounted buffer",
trace::Risk::Low,
"ours performs the same two calls, so allocation behaviour matches by "
"construction rather than by comparison");
c.unmodelled("String slots assign through the engine's own std::string, leaking one heap "
"block per long string in compare mode",
trace::Risk::Low, "start-up only; documented in docs/M1.md");
}
};
// Process facts the hook needs: the exe's load address (parser identification, the scale
// constant, gobio::ReadFile) and a line logger (shim.log). Call once before installing.
void init_global_consts(std::uintptr_t exe_base, void (*log_line)(const char* line));
} // namespace shim::hooks