Skip to main content

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.