Developer guide · SDK v1

Build a native UNBSE plugin

Create a 64-bit Windows UE4SS C++ mod that registers with the UNBSE host and uses only the versioned capabilities it needs.

Current runtime:

UNBSE 0.14.5 supports Steam 1.512.105.0 only. Check runtime identity before selecting version-specific addresses or behavior.

Step 1

Include the SDK

Add the five public SDK headers to your project. Begin with UNBSEAddonHostV1.h; it also includes the script-service declarations. Query the other services only when your feature needs them.

HeaderUse
UNBSEAddonHostV1.hRegistration, capabilities, ownership, and retirement
UNBSEMessagingV1.hIn-process messages and lifecycle events
UNBSEScriptServiceV1.hNative functions exposed to supported script runtimes
UNBSERuntimeInfoV1.hLoaded executable and foundation identity
UNBSERelocationV1.hOwner-checked executable RVA resolution

Step 2

Find and register with the host

Locate UNBSE_QueryAddonHostV1 among modules already loaded in the game process, request ABI version 1, then submit a descriptor. Use a stable namespaced add-on ID and declare every effect your add-on can perform. The declaration records effects; it is not a security sandbox.

Minimal registration C++
#include <UNBSEAddonHostV1.h>
#include <cstdint>
#include <cstring>

bool RegisterAddon(UNBSEQueryAddonHostV1Function query,
                   UNBSEAddonHostV1& host,
                   std::uint32_t& owner)
{
    host = {};
    host.structSize = sizeof(host);
    if (!query || !query(UNBSE_ADDON_HOST_ABI_VERSION, &host) ||
        host.apiVersion != UNBSE_ADDON_HOST_ABI_VERSION ||
        host.structSize < sizeof(host) ||
        !host.registerAddon || !host.retireAddon) {
        return false;
    }

    UNBSEAddonDescriptorV1 descriptor{};
    descriptor.structSize = sizeof(descriptor);
    descriptor.apiVersion = UNBSE_ADDON_HOST_ABI_VERSION;
    descriptor.declaredEffects = UNBSE_ADDON_EFFECT_RUNTIME_READ;
    descriptor.requiredHostCapabilities =
        UNBSE_ADDON_HOST_CAPABILITY_RUNTIME_INFO_V1;
    std::memcpy(descriptor.addonId, "example.weather", 16);
    std::memcpy(descriptor.addonVersion, "1.0.0", 6);

    UNBSEAddonRegistrationV1 registration{};
    registration.structSize = sizeof(registration);
    registration.apiVersion = UNBSE_ADDON_HOST_ABI_VERSION;
    const auto result = host.registerAddon(&descriptor, &registration);
    if (result != UNBSE_ADDON_RESULT_OK) {
        return false;
    }

    if (registration.missingHostCapabilities != 0) {
        host.retireAddon(registration.ownerHandle,
                         UNBSE_SCRIPT_DEFAULT_DEADLINE_MS);
        return false;
    }

    owner = registration.ownerHandle;
    return true;
}

Keep the host table and owner handle for the add-on lifetime. Validate missingHostCapabilities before using optional features, and log both numeric results and resultName when diagnosing failures.

Step 3

Query only the services you use

Each service has its own query export and ABI version. Zero the destination, initialize structSize, request the matching version, and verify every returned function pointer before calling it.

GoalQueryFirst check
Publish script functionshost.queryScriptServicegetCapabilities()
Exchange add-on messagesUNBSE_QueryMessagingV1Listener and message limits
Read the loaded runtimeUNBSE_QueryRuntimeInfoV1Identity flags and exact game version
Resolve a known RVAUNBSE_QueryRelocationV1Runtime identity, then bounds result
Capabilities are conditional.

A successful script-service query does not promise that every VM or specialized integration is available. Test the matching capability bit and provide a clean disabled path for unsupported hosts.

See the complete native SDK reference for every query signature, service-table function, result code, structure size, capability value, and current limit.

Step 4

Retire cleanly

Stop starting new work, then retire service callbacks and the owner before your DLL unloads. Use retireOwner for registered script functions and retireAddon for the add-on. Treat message payloads, invocation arguments, and other call pointers as borrowed for the synchronous call unless a header explicitly says otherwise.

Handle timeouts as real failures.

Do not unload code that the host may still call. Log the result and keep the module loaded when retirement cannot complete safely.

Step 5

Package and declare compatibility

Ship the DLL as an enabled UE4SS C++ mod with this layout. The empty enabled.txt marker tells UE4SS to load the mod.

ue4ss/Mods/<YourPlugin>/
├── enabled.txt
└── dlls/
    └── main.dll

Do not put a native UNBSE add-on in OBSE/Plugins; that directory is for plugins using the OBSE64 compatibility ABI. In your release notes, state the exact UNBSE and game versions you tested and describe optional features that disable themselves when capabilities are unavailable.

Next steps

Reference