Addresses versus runtime wrappers
The address layer prevents every mod from rediscovering the same RVA. It does not make a raw native call safe by itself.
Inspect catalog evidence
#include <RPS/Addresses/Catalog.h>
using namespace RPS::Addresses;
const SymbolRecord& entry = record(Symbol::Physics_SetBodyVelocityDeferred);
const auto name = entry.name;
const auto rva = entry.rva;
const auto kind = entry.kind;
const auto source = entry.source;
const auto evidence = entry.evidence;
const auto runtime = entry.runtime;
Every record contains:
- a typed symbol name;
- an RVA, never an absolute process address;
- symbol kind: function, data, vtable, callsite, hook site, or patch site;
- subsystem and source project;
- in-game-proven evidence classification;
- the exact FO4VR runtime version.
String tools can use find(name) or findByRva(rva). Runtime integrations
should prefer the typed Symbol enum.
Prefer a runtime wrapper
This direct resolution:
const auto address = module.resolve(
RPS::Addresses::Symbol::Physics_SetBodyVelocityDeferred);
only proves that the symbol's RVA fits inside the supported module image. It does not prove that your world is current, the body exists, the current thread owns a write epoch, the vectors are finite, the call is legal in this phase, or the postcondition succeeded.
The corresponding wrapper owns those checks:
RPS::Runtime::Physics::WorldWriteGuard write{module, hknpWorld};
RPS::Runtime::Physics::Api physics{module, hknpWorld};
const bool changed = physics.setVelocityDeferred(
write,
bodyId,
linearVelocity,
angularVelocity);
Use the first form only when no wrapper exists and your component explicitly owns the missing lifetime, synchronization, and validation boundary.
Resolve only after executable detection
const auto module = RPS::Runtime::RuntimeModule::detect();
if (!module) {
return false;
}
using Function = void (*)(
void*,
std::uint32_t,
const float*,
const float*);
const Function function = module.resolveFunction<Function>(
RPS::Addresses::Symbol::Physics_SetBodyVelocityDeferred);
if (!function) {
return false;
}
The function type and calling contract must come from the RPS source/evidence for that exact symbol. Do not infer a signature from the symbol name or a flat Fallout 4 declaration.
Verify identity before hooks and patches
RuntimeModule::matches() compares a masked byte pattern at a cataloged symbol.
Hooks::inspectExecutableSite() combines runtime resolution, symbol-kind
checking, executable memory validation, and entry-pattern comparison.
Direct-call preflight additionally proves that the site starts with E8, its
relative target decodes safely, and the target matches the expected native
function:
#include <RPS/Runtime/HookPatch.h>
using namespace RPS::Runtime::Hooks;
const SiteInspection site = inspectDirectCall(
callsiteAddress,
expectedNativeTarget);
if (!site) {
return false;
}
Patch a related group transactionally:
std::array<DirectCallPatch, 2> patches{
DirectCallPatch{siteA, expectedA, replacementA},
DirectCallPatch{siteB, expectedB, replacementB},
};
std::array<std::uintptr_t, 2> originals{};
const auto patched = patchDirectCallsTransactional(patches, originals);
if (!patched) {
reportPatchFailure(patched.status, patched.failedIndex);
return false;
}
The transaction preflights the group, rejects duplicate sites, captures original targets, commits the writes, and attempts rollback after a partial failure.
What HookPatch does not provide
It does not:
- suspend or quiesce threads;
- allocate executable trampolines;
- decode arbitrary overwritten instruction sequences;
- chain multiple hook owners;
- own callback or original-function storage;
- wait for in-flight callbacks at shutdown;
- decide when it is safe to restore a site.
Direct E8 rewriting is not instruction-stream atomic. One explicit component
must own the complete install/use/uninstall lifecycle and prevent other threads
from executing the site while it is being rewritten.
Catalog-only does not mean stable callable API
Some evidence is intentionally catalog-only because a general ownership boundary is not complete. Examples include animation preharvest loading, passive clip telemetry hooks, physics contact subscription, global physics-step listeners, actor path request submission, character physics hooks, global collision matrix ownership, and the collision-audio global.
Using those symbols requires a consumer design that supplies process-global ownership, registration/unregistration, callback quiescence, generation protection, and deterministic restore. Do not present a catalog record alone as a supported high-level feature.
Withheld paths
The stable runtime intentionally excludes:
- direct ragdoll constraint/pivot mutation;
- live world-from-model compensation;
- the single-witness graph active-index field;
- custom six-axis constraint shellcode/vtable construction;
- weak legacy REL-ID-only candidates.
Those exclusions prevent an address collection from becoming a false promise of safe multi-consumer behavior.