REDnode is a Node-based API for scripting Cyberpunk 2077, built on the same injection points that RED4ext, Cyber Engine Tweaks and redscript already use. Asynchronous mods run in a normal Node process. A small sandboxed tier runs inside the game for the few cases that must change a value mid-call.
fs, require and a live event loop.Checked against the install on this machine. Two independent injection points are already in use, so a new framework can attach to either.
| Component | How it attaches | Seen on this install |
|---|---|---|
| Ultimate ASI Loader | Proxy version.dll that loads every .asi in bin/x64/plugins/. | v6.0.0. Loads cyber_engine_tweaks.asi. |
| RED4ext | Proxy winmm.dll loads red4ext/RED4ext.dll, which loads red4ext/plugins/*/*.dll. | v1.30.0. Plugins: ArchiveXL, Codeware, RedData, mod_settings, input_loader. |
| Cyber Engine Tweaks | In-process Lua runtime with reflection access to game types and an ImGui overlay. | Closest precedent for an embedded scripting runtime. |
| redscript | Compiles .reds files into the game's script bytecode at launch. Not a runtime embed. | Many mods under r6/scripts/. |
| REDmod | Official CDPR deploy tool for archives, tweaks, scripts and audio. No injection. | redMod.exe present, mods/ empty. |
.reds files. Mod authors already expect that packaging shape.| Plan | Approach | Strengths | Weaknesses |
|---|---|---|---|
| A | Embed Node in a RED4ext plugin or .asi. | Closest to CET. JS runs inside hooks as plain function calls. | 30 to 80 MB runtime. Event loop and threads inside a DX12 game process. Hard to keep stable. |
| B | Thin native RED4ext plugin plus a separate Node process over IPC. | Crash isolation. Normal npm tooling. Easy hot reload. Small native surface to maintain. | No synchronous access from hooks. Round-trip latency on every call. |
| C | Embed QuickJS only. | About 1 MB. Fast start. Easy to sandbox. | Not Node. No npm ecosystem. |
A hook runs inside the game's own function call, on the game thread, and the game waits for it to return. If the logic lives in another process, the hook must block on a round trip. That costs roughly 50 to 200 µs when healthy and much more when Node is busy. A 60 fps frame has about 16 ms, and hooks such as damage or per-frame ticks can fire thousands of times a frame. If the Node process crashes or hangs, the game thread blocks until a timeout.
| Pattern | Plan B |
|---|---|
| Notify: "the player took damage" | Works. The hook enqueues an event and returns. |
| Query: "what is the player's health?" | Works. Node awaits the answer. |
| Command: "set health to 100" | Works. Queued and applied on the game thread next tick. |
| Intercept: change a value before the call continues | Does not work well. Needs a synchronous answer while the game waits. |
Keep Plan B as the main path. Add a small in-process tier for interceptors only. A mod can ship either tier or both.
await, no I/O, no require.| Engine | Size | Notes |
|---|---|---|
| Full Node / V8 | 30 to 80 MB | Brings back every problem of Plan A. Not recommended for interceptors. |
| Bare V8 isolate | 10 to 30 MB | Same JS dialect as Node. Fast. No Node APIs. Heavier to ship. |
| QuickJS | about 1 MB | Recommended. Small, easy to sandbox and to time-limit. TypeScript is compiled ahead of time. Slower than V8, which is fine for tiny functions. |
Tier 1 shares TypeScript types with Tier 2 but is not Node. The documentation must say so plainly.
For the simplest cases, such as "multiply incoming damage by X", offer a no-code rule that the native plugin applies directly in the hook. It needs no JS engine and no IPC, and it covers a large share of real mods.
export default defineInterceptor({ target: "PlayerPuppet.OnDamageReceived", run(ctx) { ctx.args.damage *= ctx.params.multiplier; // pushed from Tier 2 } });
import { game } from "rednode"; game.on("PlayerPuppet.OnDamageReceived", async (e) => { await db.log(e); // fine, the game is not waiting game.player.setHealth(100); // queued, runs next tick }); const hp = await game.player.getHealth(); // async query interceptor("damage-scale").set("multiplier", 0.5);
CName, Vector4, handles, arrays) to and from JSON-like values.A buggy interceptor must never crash or freeze the game.
Each call is time-limited. On overrun, return to default game behavior.
Catch exceptions, fall back to defaults, and auto-disable an interceptor after repeated failures.
Define priority and chaining when several mods hook the same function.
Validate every write to ctx.args against the real parameter types before it reaches the game.
Read-only accessors and shared-memory reads only. No arbitrary game calls from a hook, to avoid reentrancy bugs.
Swap Tier 1 code between frames. Tier 2 reloads freely.
| Risk | Impact | Mitigation |
|---|---|---|
| Game patch breaks hooks | Mods stop working until the plugin updates. | Use the Address Library and RED4ext hooking. Never hard-code offsets. |
| Conflicts with other plugins | Double hooks, crashes. | Use RED4ext's hook API. Do not add a third loader. |
| Thread-safety bugs | Random crashes. | All game access goes through the game-thread queue. |
| IPC latency or stalls | Stutter. | Async by default. Fast-path rules and shared memory for hot paths. |
| Tier 1 scope creep | Maintenance load. | Build it last, only for hooks modders actually need. |
| Modding terms | Distribution limits. | Check CD Projekt and Nexus Mods terms before publishing. Single-player only. |
Read the RED4ext plugin entry points and the imports and exports of an installed plugin. Inspect how cyber_engine_tweaks.asi hooks and resolves addresses.
A RED4ext plugin that logs the RTTI class count. Confirms build, load and logging on the current game version.
Expose one function over a named pipe and call it from Node. Then events, queries, commands and generated types.
Native fast-path rules applied inside hooks with no IPC. Gives synchronous behavior with no engine.
Add the sandboxed runner, time limits, ordering and fault handling. Only after P2 and P3 show which hooks need it.
red4ext-rs (Audioware is a Rust precedent)?red4ext/plugins layout or its own folder?