// 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 #include #include #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; static void describe_args(std::vector& out, const char* file, void* consts); static void regions(std::vector& 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