Skip to content

Architecture

CAPIE has four layers. Each one depends only on the layer below it, and the bottom layer, the game itself, is never modified.

Three layers: mod scripts, CAPIE, the unmodified game. Pulses show requests going down and names coming back up.
Mods call CAPIE through script; CAPIE works inside the running game.
 Dependent mods (Rename Anything, others)
   call SDK effects and triggers, ship the indicator snippet
 CAPIE script SDK (plain HOI4 script, versioned file names)
   capie_v1_* triggers, effects, scripted loc, loc keys
   fallback branches, authority checks, cooldowns, validation
 CAPIE modules (native)
   text transport | name layers | UI refresh
   status loc keys | version record for multiplayer | console commands
 CAPIE core (native)
   game build check (SHA-256) | signature table | hook manager
   input validation | capability and version registry | logging
 Unmodified HOI4 (hoi4.exe and steam_api64.dll untouched)

Core

The core decides whether CAPIE may run at all.

  • Game build check. CAPIE computes the SHA-256 of the running hoi4.exe with Windows bcrypt and compares it with the pinned build. Works today
  • Pattern check. Every place CAPIE hooks must match a byte pattern exactly once, at the expected address, before any hook is switched on. Works today
  • Hook manager. CAPIE uses MinHook. It enables all hooks or none, and it keeps trampolines until the process exits, because a call may still be running through one. Works today
  • Signature table. Every signature and pin is one row of a table in the source, checked by one matcher and by an offline test against the game file. Works today A signed table per supported game build is Planned.
  • Capability and API level. capie_core.dll exports a capability value that is non-zero only after a successful start, and every annotated command carries the API level as capie_cap. Works today

This is the pattern check of the first country module, from before CAPIE; the current matcher in plugin/capie/src/core/signatures.hpp applies the same rules to a table of signatures:

plugin/src/country.cpp
struct HookSpec { size_t rva; std::vector<int> pattern; void* replacement; void** original; };

bool unique_signature(const unsigned char* base, const IMAGE_SECTION_HEADER* text,
                      const HookSpec& hook) {
    const auto* start = base + text->VirtualAddress;
    const size_t size = text->Misc.VirtualSize;
    size_t matches = 0;
    for (size_t off = 0; off + hook.pattern.size() <= size; ++off) {
        bool match = true;
        for (size_t i = 0; i < hook.pattern.size(); ++i) {
            if (hook.pattern[i] >= 0 && start[off+i] != hook.pattern[i]) { match = false; break; }
        }
        if (match) {
            if (static_cast<size_t>(text->VirtualAddress) + off != hook.rva || ++matches > 1) return false;
        }
    }
    return matches == 1;
}

A pattern entry of -1 is a wildcard for bytes that change when the game is relinked, such as relative addresses. A second match anywhere in the code section, or a match at the wrong address, fails the check.

Start-up checks: known build, every pattern fits, CAPIE active. Planned opt-in verification for unknown builds. Any failure: the game runs without CAPIE.
Start-up is all or nothing.

Start-up errors are numbered, so a log line tells you which check stopped CAPIE:

Code Meaning
0 Active
10 Unsupported hoi4.exe hash
11 Wrong executable format or machine type
12 Code section not found
20 A pattern did not match exactly once at its address
30, 31, 32 MinHook could not initialise, create or enable the hooks

Modules

Module What it does State
Name layers Puts a country's custom name and adjective in place while the game rebuilds its name caches, without touching the cosmetic tag or flag (Name layers); still reads the older RN1\|Name\|Adjective\| cosmetic tag Works today on a developer build; faction names In progress
Status loc keys Overrides reserved localisation keys when they are drawn; proven with RN_PLUGIN_STATUS Works today for the mechanism; CAPIE_* keys built, need an in-game test
Text transport Copies the text of a known native text field into temporary records of the player's own Apply command Works today for Rename Anything's fields and for grammar editors of other mods
Capability records Adds the API level to known commands and removes script-set copies Works today as rn_native_cap and capie_cap
UI refresh Redraws the leader's name in the open Politics panel after a successful rename Works today
Version record Adds the CAPIE version (capie_ver) to every annotated command, for the multiplayer check Built, needs a two-machine test
Console commands change_nation_name, reset_nation_name and capie_cmd in the game's console Works today in developer builds only

Modules are compiled into two DLLs, not loaded at run time: capie_core.dll (names, status, capability) and capie_editor.dll (editors, refresh). The launcher's hashes of the two files cover all native code.

One rename, end to end

The player clicks Apply; CAPIE annotates the player's own command; the effect runs on every client; the SDK applies or takes the fallback branch.
Variant B: CAPIE adds data to a command; script decides what happens.
  1. The player opens an editor and types into the game's own text field (editBoxType).
  2. On Apply or Enter, the game builds the scripted GUI command. CAPIE copies the field's text into temporary records of that command, as character codes, and adds the capability record.
  3. The game runs the command's effect. The script checks the capability record, checks authority, decodes and validates the text, then calls a vanilla setter. For a country it writes the name layer: country variables with the character codes of the name and adjective.
  4. When the game rebuilds the country's cached names, CAPIE puts the layer in place of the tag the names are built from. Without CAPIE the game builds the names as usual and shows the cosmetic tag or base name.

Principles

  • Minimal intrusion: hooks only where a script cannot do the job, each forwarding to the original exactly once.
  • Fail closed: an unknown build, an ambiguous pattern, bad input or a missing capability means CAPIE does nothing and the script takes its fallback branch.
  • Full passthrough: anything CAPIE does not recognise exactly goes to the game unchanged.
  • No network, no files: CAPIE opens no sockets and does no file input or output on behalf of scripts.
  • Script is the source of truth: names live in the save through vanilla mechanisms. CAPIE keeps no sidecar files and no state between sessions.
  • Visible degradation: when CAPIE is missing or too old, the player sees it.