FumoEngine
FumoEngine is Fumoware’s embedded JavaScript engine. It runs ECMAScript modules through QuickJS-NG and gives every installed package its own runtime, context, globals, module graph, and console stream.
All builds discover packages from:
%APPDATA%\FumowareV2\scripts
Safe mode is the default. It applies per-package runtime limits and does not
expose process memory or native calls. Insecure mode is opt-in and enables
fumo:ffi only for packages whose manifest also requests the ffi
capability.
Insecure mode is not a sandbox. An FFI script runs native code inside the game process and can crash it, corrupt memory, or compromise the current Windows user.
Current implementation
The current engine includes:
- Manifest-based package discovery
- One QuickJS runtime per package
- ES modules and package-relative imports
- Global and module-based console logging
- Promise startup and shutdown handling
- Execution deadlines
- Per-package enable and disable controls
- Source editing, saving, and reload from the menu
- A bounded JavaScript console window
- Optional Win64 FFI and typed native memory access
Other modules described in the original architecture document remain planned until they appear in this reference.
Getting started
Create a package directory:
%APPDATA%\FumowareV2\scripts\hello-fumo\
Add manifest.json:
{
"id": "example.hello-fumo",
"name": "Hello Fumo",
"version": "1.0.0",
"apiVersion": 1,
"entry": "index.js",
"capabilities": ["console"]
}
Add index.js:
import { log } from "fumo:console";
log("Hello from FumoEngine");
export function shutdown() {
console.log("Hello Fumo unloaded");
}
Open Fumoware’s Scripts tab and press R to rediscover packages. New
packages appear disabled; turn the package on once to evaluate it. That choice
is remembered across game restarts. Output appears in the JavaScript console.
Module imports
Package-relative imports are supported:
import { value } from "./helper.js";
Imports cannot leave the package directory. Arbitrary bare module names and
QuickJS’s std and os modules are unavailable.
Packages and manifests
Every immediate child directory under the script root is treated as a
possible package. A package must contain manifest.json.
Manifest fields
| Field | Type | Required | Meaning |
|---|---|---|---|
id | string | yes | Stable, unique package identifier. |
name | string | yes | Name shown in the Scripts workspace. |
version | string | yes | Semantic package version. |
apiVersion | integer | yes | FumoEngine API version. Currently 1. |
entry | string | yes | Package-relative .js or .mjs entry file. |
description | string | no | Human-readable package summary. |
author | string | no | Package author. |
capabilities | string[] | yes | Host features requested by the package. |
dependencies | object | no | Package IDs mapped to semantic version ranges. |
assets | string[] | no | Files decoded and cached before entry evaluation. |
http.hosts | string[] | no | Exact or wildcard hosts allowed for HTTP. |
Paths are canonicalized before loading. Symlinked package directories, absolute paths, and paths escaping the package root are rejected.
Capabilities
Capabilities declare what a package intends to use. Common capabilities are
console, events, draw, trace, input.write, assets, sound,
http, and ffi.
FFI requires both a manifest request:
{
"capabilities": ["console", "ffi"]
}
and Settings > Script Engine > Insecure scripting.
Dependencies
Dependencies load before their consumers. Supported ranges include exact
versions, *, caret and tilde ranges, and whitespace-separated comparators:
{
"dependencies": {
"author.common-library": "^1.2.0",
"author.protocol": ">=2.0.0 <3.0.0"
}
}
Missing, disabled, incompatible, and cyclic dependencies keep the consumer unloaded and put a specific reason in its Scripts workspace status.
Assets and HTTP
Every file used through fumo:assets or fumo:sound must be declared:
{
"assets": [
"images/marker.png",
"sounds/notify.wav",
"data/presets.json"
],
"http": {
"hosts": ["api.example.com", "*.static.example.com"]
}
}
The runtime preloads declared assets before entry evaluation. Safe mode only permits HTTPS.
Template catalog
Copy a template directory into
%APPDATA%\FumowareV2\scripts, change its package ID when building
from a starter, press R in the Scripts workspace, and enable it.
New packages start disabled.
Download every template as a ZIP
Archive: 21 KiB | SHA-256: 33FF2310BEAB64DF6F87E36EDD67F23DBB55358F2825BD7346C76022D4C238EB
Starter templates
| Folder | Package | Version | API | Capabilities | Description |
|---|---|---|---|---|---|
basic | My FumoJS Scriptexample.my-script | 1.0.0 | 1 | console, events, draw | Starter package using native controls and persistent drawing. |
http-assets | HTTP and Assets Starterexample.http-assets | 1.0.0 | 1 | console, assets, http | Starter package for preloaded data and allowlisted HTTPS. |
Conformance templates
| Folder | Package | Version | API | Capabilities | Description |
|---|---|---|---|---|---|
01-core-console | Core console conformancetest.fumojs.core-console | 1.0.0 | 1 | console | Core console conformance |
02-promises | Promise conformancetest.fumojs.promises | 1.0.0 | 1 | console | Promise conformance |
03-isolation-a | Isolation writertest.fumojs.isolation-a | 1.0.0 | 1 | console | Isolation writer |
04-isolation-b | Isolation readertest.fumojs.isolation-b | 1.0.0 | 1 | console | Isolation reader |
05-events-draw | Events and drawingtest.fumojs.events-draw | 1.0.0 | 1 | console | Events and drawing |
06-extended-draw | Extended drawingtest.fumojs.extended-draw | 1.0.0 | 1 | console, draw | Extended drawing |
07-native-controls | Native controlstest.fumojs.native-controls | 1.0.0 | 1 | console, ui | Native controls |
08-trace-command | Trace and command lifetimetest.fumojs.trace-command | 1.0.0 | 1 | console, events, trace, input.write | Trace and command lifetime |
09-assets | Cached assetstest.fumojs.assets | 1.0.0 | 1 | console, assets | Cached assets |
10-http-policy | HTTP policytest.fumojs.http-policy | 1.0.0 | 1 | console, http | HTTP policy |
11-dependency-base | Dependency basetest.fumojs.dependency-base | 1.4.0 | 1 | console | Dependency base |
12-dependency-consumer | Dependency consumertest.fumojs.dependency-consumer | 1.0.0 | 1 | console | Dependency consumer |
13-texture-sound | Texture and sound cachetest.fumojs.texture-sound | 1.0.0 | 1 | console, events, draw, ui, assets, sound | Texture and sound cache |
14-command-readonly | Command Read-only Testtest.fumojs.command-readonly | 1.0.0 | 1 | none | Verifies command writes require input.write. |
15-trace-quota | Trace Quota Testtest.fumojs.trace-quota | 1.0.0 | 1 | trace | Verifies the per-pump penetration quota. |
16-http-credentials | HTTP Credential Testtest.fumojs.http-credentials | 1.0.0 | 1 | http | Verifies URL credentials are rejected before a request starts. |
20-render-persistence | Render Persistence Testtest.fumojs.render-persistence | 1.0.0 | 1 | draw | Draws once so persistent frame buffering can be checked visually. |
21-http-live | HTTP Live Testtest.fumojs.http-live | 1.0.0 | 1 | http | Optional live HTTPS promise and JSON response test. |
Expected rejection templates
These packages are intentionally incompatible or unsafe and should remain unloaded with a specific status or console error.
| Folder | Package | Version | API | Capabilities | Description |
|---|---|---|---|---|---|
17-dependency-mismatch | Dependency Mismatch Testtest.fumojs.dependency-mismatch | 1.0.0 | 1 | none | Must remain unavailable because the installed base is version 1.x. |
18-dependency-cycle-a | Dependency Cycle Atest.fumojs.dependency-cycle-a | 1.0.0 | 1 | none | First half of a dependency cycle rejection test. |
19-dependency-cycle-b | Dependency Cycle Btest.fumojs.dependency-cycle-b | 1.0.0 | 1 | none | Second half of a dependency cycle rejection test. |
90-budget-interrupt | Expected rejection - execution budgettest.fumojs.budget-interrupt | 1.0.0 | 1 | console | Expected rejection - execution budget |
91-memory-limit | Expected rejection - memory limittest.fumojs.memory-limit | 1.0.0 | 1 | console | Expected rejection - memory limit |
92-bare-import-denied | Expected rejection - bare importtest.fumojs.bare-import-denied | 1.0.0 | 1 | console | Expected rejection - bare import |
93-path-traversal-denied | Expected rejection - path traversaltest.fumojs.path-traversal-denied | 1.0.0 | 1 | console | Expected rejection - path traversal |
94-invalid-manifest | Expected rejection - invalid manifesttest.fumojs.invalid-manifest | 1.0.0 | 999 | console | Expected rejection - invalid manifest |
99-ffi-denied | Expected rejection - insecure FFI unavailabletest.fumojs.ffi-denied | 1.0.0 | 1 | console, ffi | Expected rejection - insecure FFI unavailable |
Total templates: 29
Scripts workspace
The Scripts tab contains an installed-package rail and a source editor.
Toolbar controls:
| Control | Action |
|---|---|
R | Rediscover and reload all packages. |
S | Save the selected entry file and reload its package. |
T | Open the JavaScript console window. |
The toggle beside each package uses the same toggle control as the rest of
Fumoware. Disabling a package calls its exported shutdown() function,
destroys its context, and destroys its runtime. Enabling it creates a fresh
runtime and evaluates the entry module again.
New packages do not automatically run when the DLL is injected. Toggle state
is stored immediately in %APPDATA%\FumowareV2\script-states.cfg and restored
on the next game launch or injection.
The source editor currently edits the package entry file and enforces a 1 MiB source-file limit. Line numbers follow the editor’s vertical scroll.
The JavaScript console retains the newest 1,000 entries. It includes package output, unhandled promise rejections, load failures, engine lifecycle messages, and contained native faults.
API reference
FumoEngine APIs are exposed as globals or built-in ES modules.
Currently implemented:
| API | Availability |
|---|---|
Global console | Every loaded package |
fumo:console | Every loaded package |
fumo:events | Every loaded package |
fumo:draw | Every loaded package |
fumo:ui | Every loaded package |
fumo:game | Every loaded package |
createMove command | Read every package; writes need input.write |
fumo:trace | Manifest trace capability |
fumo:assets | Declared package assets |
fumo:sound | Manifest sound capability |
fumo:http | Manifest http capability and host allowlist |
Global timers / fumo:timers | Every loaded package |
fumo:storage | Every loaded package |
fumo:ffi | Insecure mode plus manifest ffi capability |
An import fails during module linking when its requirements are not met.
console
Every package receives its own console object.
console.log("ready");
console.warn("slow operation");
console.error("operation failed");
Arguments are converted to strings and joined with spaces. Entries are tagged with the package ID and routed to both Fumoware’s debug log and the JavaScript console window.
Methods
interface Console {
log(...values: unknown[]): void;
warn(...values: unknown[]): void;
error(...values: unknown[]): void;
}
fumo:console
The console module provides named functions and the shared package console object.
import {
console,
log,
warn,
error
} from "fumo:console";
log("package loaded");
warn("example warning");
error("example error");
The named functions behave identically to the matching global console methods.
fumo:events
fumo:events owns persistent callbacks. Callbacks execute on FumoEngine’s
serialized script lane: createMove is pumped by the command hook and
render is pumped by the view-render frame-start hook, never from Present.
import { events } from "fumo:events";
const unsubscribe = events.on("render", ({ displaySize, deltaTime }) => {
console.log(displaySize.width, deltaTime);
});
events.once("mapLoad", ({ map }) => {
console.log("Loaded", map);
});
events.on() and events.once() return an unsubscribe function. events.off
accepts a numeric subscription ID for low-level integrations, although normal
scripts should retain and call the returned function.
Implemented host events:
| Event | Payload |
|---|---|
load | undefined |
shutdown | undefined |
createMove | { displaySize, deltaTime } |
render | { displaySize, deltaTime } |
mapLoad | { map, value } |
mapUnload | { map, value } |
menuOpenChanged | { value: "true" | "false" } |
configLoaded | { value } |
frameStage, gameEvent, and input are reserved subscription names. Their
native bridges are not emitted yet.
Each callback has an 8 ms hard execution deadline. Exceptions are written to the JavaScript console and do not unwind through the game hook.
fumo:draw
Drawing records native commands during render. The completed command list
is immutable and remains visible until that script submits a newer frame,
which avoids flicker when the JavaScript lane skips a Present.
import { events } from "fumo:events";
import { draw, Color } from "fumo:draw";
events.on("render", ({ displaySize }) => {
const accent = Color.rgba(45, 170, 255, 220);
draw.line(12, 12, displaySize.width - 12, 12, accent, 2);
draw.rectFilled(24, 32, 140, 42, Color.rgba(10, 14, 20, 180), 5);
draw.text(36, 44, "FumoEngine", Color.white, 14, true);
});
Implemented primitives:
line, rect, rectFilled
circle, circleFilled
text
polyline, polygon, polygonFilled
triangle, triangleFilled
rectGradient
pushClipRect, popClipRect
image
Point collections accept [[x, y], ...], {x, y} objects, or a flat
coordinate array. draw.image accepts only a texture handle loaded by the
same package.
draw.worldToScreen({x, y, z}) and draw.worldToScreen(x, y, z) return a
copied {x, y} or null when projection is unavailable or behind the view.
Colors use packed 0xRRGGBBAA values. Color.rgba(r, g, b, a) creates one;
Color.white and Color.black are predefined.
Draw calls outside a render callback throw. Per-frame command and point
limits prevent one package from exhausting the renderer.
fumo:ui
Scripts register native controls at startup. Fumoware owns their widgets, values, RGB animation, and config serialization.
import { ui } from "fumo:ui";
ui.collapsible({
id: "advanced",
label: "Advanced",
collapsed: false
});
const enabled = ui.checkbox({
id: "enabled",
label: "Enabled",
default: true,
group: "advanced"
});
const mode = ui.combo({
id: "mode",
label: "Mode",
options: ["Quiet", "Detailed"],
default: 0,
group: "advanced",
visibleWhen: { id: "enabled", value: true }
});
Controls:
checkbox,sliderFloat,sliderInt, andcolorcomboandmultiSelecthotkeyandtextInputbuttonwithonClicklabelandseparatorcollapsible
All controls expose a live value property. Common options are id, label,
group, visibleWhen, and disabledWhen. A color may opt into native RGB
cycling with rgbCycle: true.
Control IDs are namespaced by package and may contain letters, numbers, _,
-, and .. Values are serialized as
script_value/<package>/<control> config assignments. Before another config
loads, controls reset to their declared defaults so state cannot leak between
profiles.
fumo:game
fumo:game returns copied, immutable data. Safe scripts never receive entity,
controller, scene-node, or bone pointers.
import { game } from "fumo:game";
const state = game.snapshot();
if (state.inGame) {
for (const player of state.players) {
console.log(player.id, player.name, player.health, player.origin);
}
}
Methods:
game.snapshot()returns the full current snapshot.game.players()returns the player array.game.localPlayer()returns copied local team/origin data ornull.
Player fields currently include:
id, generation, index, name, team, health, armor, distance,
enemy, visible, flashed, scoped, defusing, hasBomb, origin
The generation increments when the map or in-game state changes. An id
combines that generation with the entity index, so a script can reject cached
references after transitions.
The current player list is sourced from Fumoware’s validated visual snapshot. It contains players that reached that snapshot during the latest render data build; it is not a raw full entity-system iterator.
Commands
The createMove event contains a short-lived command:
import { events } from "fumo:events";
events.on("createMove", ({ command }) => {
const angles = command.getViewAngles();
if (angles) {
command.setViewAngles({ ...angles, y: angles.y + 5 });
}
});
Writing requires the input.write capability. Reads are allowed without it.
Pitch, yaw, and roll are normalized and clamped natively.
The object expires immediately after that callback returns. It is already invalid in a Promise continuation, timer, another event callback, or a later command:
events.once("createMove", ({ command }) => {
Promise.resolve().then(() => {
console.log(command.isValid()); // false
});
});
Copy values you need; never retain the command object.
fumo:trace
Trace calls require the trace capability.
import { trace } from "fumo:trace";
const from = { x: 0, y: 0, z: 64 };
const to = { x: 256, y: 0, z: 64 };
const ray = trace.ray({ from, to });
const hull = trace.hull({
from,
to,
mins: { x: -16, y: -16, z: 0 },
maxs: { x: 16, y: 16, z: 72 }
});
const visible = trace.visibility({ from, to });
const smoked = trace.throughSmoke({ from, to });
Ray and hull results are copied objects containing hit, world,
startSolid, fraction, start, end, normal, and entityIndex.
No engine pointer is exposed.
Penetration estimates accept copied weapon values:
const result = trace.penetration({
from,
to,
damage: 40,
penetration: 2,
range: 8192,
rangeModifier: 0.98,
maximumPenetrations: 4
});
The runtime allows 128 regular trace/smoke calls and eight penetration estimates per owner-lane pump. Counters reset for the next pump.
Assets and sound
Declare package-relative assets in manifest.json. They are validated and
decoded before the entry module evaluates, so first use does not stall a
frame.
import { assets } from "fumo:assets";
import { sound } from "fumo:sound";
const settings = assets.loadJson("data/settings.json");
const text = assets.loadText("data/message.txt");
const marker = assets.loadTexture("images/marker.png");
const notify = sound.load("sounds/notify.wav");
Supported resources:
- PNG, JPEG, WebP when a Windows WIC WebP codec is available, and BMP
- PCM, float, and extensible WAV
- JSON and text-like files
Textures produce stable package-owned handles. GPU views are created lazily,
released on device loss, and recreated from the decoded cache. Sounds are
played from cached memory with sound.play(handle).
Assets are size- and dimension-limited, canonicalized inside the package, and removed when the package unloads. A package cannot use another package’s handle.
fumo:http
HTTP requires the http capability and a manifest host allowlist:
{
"capabilities": ["http"],
"http": { "hosts": ["api.example.com"] }
}
import { http } from "fumo:http";
const response = await http.get("https://api.example.com/state", {
timeoutMs: 5000,
maximumBytes: 262144,
accept: "application/json"
});
console.log(response.status, response.json);
request, get, and post return Promises. Supported methods are GET, POST,
PUT, PATCH, and DELETE. Responses contain ok, status, body,
contentType, and parsed json when applicable.
Safe mode enforces HTTPS. Hosts must match an exact declaration or a declared
*.domain suffix. URLs with embedded credentials are rejected. Scripts
cannot set authorization or cookie headers and WinHTTP does not reuse loader
credentials or cookies.
Limits:
- 256 KiB request body
- 250 to 15,000 ms timeout
- up to 4 MiB response
- four concurrent and 1,024 lifetime requests per runtime
All pending requests are cancelled and joined when the script unloads.
Timers
Standard timer globals are installed in every runtime:
const timeout = setTimeout(() => console.log("once"), 250);
const interval = setInterval(() => console.log("tick"), 1000);
clearTimeout(timeout);
clearInterval(interval);
The equivalent module API is:
import { timers } from "fumo:timers";
const id = timers.setTimeout(callback, 250);
timers.clear(id);
Timers execute on the same owner lane as events and are checked at the next
script pump. They do not create threads and do not enter QuickJS from
Present. Delays are clamped to ten minutes, repeating timers have a minimum
1 ms delay, and one pump processes at most 64 due callbacks per package.
fumo:storage
Storage is a package-scoped JSON object:
import { storage } from "fumo:storage";
const position = storage.get("position", { x: 40, y: 60 });
storage.set("position", position);
storage.delete("old-value");
Methods return ordinary values, so using await is also valid.
Properties:
- Keys contain 1–64 printable characters.
- Values must be JSON-compatible.
- The total package quota is 256 KiB.
- Data is isolated under
%APPDATA%\FumowareV2\script-data\<package-id>\storage.json. set,delete, andclearonly mutate memory during play.- Dirty storage flushes through an atomic replace during script reload, disable, or Fumoware unload.
No arbitrary path is accepted and safe storage never exposes filesystem handles.
fumo:ffi
fumo:ffi provides intentionally unsafe Win64 native access.
It is available only when:
- Settings → Script Engine → Insecure scripting is enabled.
- The package manifest includes the
fficapability. - The package is reloaded after the setting changes.
import { ffi, NativeType } from "fumo:ffi";
Native types
const NativeType = {
void: "void",
bool: "bool",
int8: "int8",
uint8: "uint8",
int16: "int16",
uint16: "uint16",
int32: "int32",
uint32: "uint32",
int64: "int64",
uint64: "uint64",
pointer: "pointer",
cstring: "cstring",
float32: "float32",
float64: "float64"
};
float32 and float64 are currently supported by read and write, not
by bound function signatures. Bound calls accept at most eight integer,
pointer, or C-string arguments.
Loading and binding
const kernel32 = ffi.openModule("kernel32.dll");
const getCurrentProcessId = kernel32.bind("GetCurrentProcessId", {
abi: "win64",
returns: NativeType.uint32,
args: []
});
console.log("PID", getCurrentProcessId());
ffi.module(name) is an alias for ffi.openModule(name). Existing modules
are reused through GetModuleHandle; otherwise the engine calls
LoadLibrary.
An opened module has:
interface NativeModule {
readonly name: string;
readonly base: bigint;
bind(name: string, signature?: NativeSignature): NativeFunction;
}
interface NativeSignature {
abi?: "win64";
returns?: NativeType;
args?: NativeType[];
}
Pointer and 64-bit return values are JavaScript bigint values. Native calls
are surrounded by a Windows exception boundary, but not every native failure
is recoverable.
Allocating memory
const block = ffi.alloc(64);
try {
ffi.write(block, NativeType.uint32, 0x12345678);
console.log(ffi.read(block, NativeType.uint32));
} finally {
ffi.free(block);
}
alloc uses read/write VirtualAlloc pages. Package-owned allocations are
released automatically when the package unloads. free accepts only an
address returned by that package’s ffi.alloc.
Reading and writing memory
ffi.read(address: bigint, type: NativeType, maxLength?: number): unknown;
ffi.write(address: bigint, type: NativeType, value: unknown): void;
read and write accept arbitrary process addresses. cstring reads stop
at the first null byte and default to a 4 KiB maximum. The optional maximum
is clamped to 1 MiB. C-string writes are not currently supported.
Invalid memory accesses normally throw a JavaScript error containing the Windows exception code.
Warning
Signatures are not verified. Calling an address with the wrong argument or return types can corrupt registers, the stack, heap state, or game state. Only enable insecure mode for code you fully trust.
TypeScript declarations
The maintained declaration file is
types/fumo.d.ts. Add its path to your editor project
for autocomplete while keeping runtime source as JavaScript modules.
Example jsconfig.json:
{
"compilerOptions": {
"checkJs": true,
"target": "ES2023"
},
"include": [
"./**/*.js",
"C:/path/to/fumoengine/types/fumo.d.ts"
]
}
Starter packages and conformance examples live in:
fumoengine/templates/
See the template catalog for the generated complete list.
Copy a starter into %APPDATA%\FumowareV2\scripts, assign a unique package ID,
and press R in the Scripts workspace. It is discovered in the off state
until you explicitly enable it.
Runtime behavior
Each package runs in a separate QuickJS-NG runtime and context.
Safe mode
Safe mode applies:
- 32 MiB QuickJS heap limit per package
- 512 KiB QuickJS stack limit per package
- 50 ms startup execution deadline
- 10 ms shutdown execution deadline
- Blocking operations disabled
fumo:ffiunavailable
Insecure mode
Insecure mode changes runtime creation:
- No QuickJS heap limit is installed
- No QuickJS maximum stack size is installed
- Blocking operations are allowed
fumo:ffimay load for packages requesting the capability
Execution deadlines and module source-size validation remain active. The setting causes packages to reload so existing runtimes cannot retain the previous security profile.
Module evaluation
Entry files are compiled as ES modules. Top-level promises are pumped during
startup and must settle before the startup budget expires. An exported
shutdown function is called when the package is disabled, reloaded, or the
engine stops.
Unhandled promise rejections are written to the package console.
Enabled state
Newly discovered packages start disabled. The Scripts workspace writes every toggle immediately to:
%APPDATA%\FumowareV2\script-states.cfg
This state is independent of Fumoware configs and is restored on the next injection. It is flushed again during a normal engine shutdown.
Host object lifetime
Game data and trace results are copied values. A UserCommand is valid only
while its own createMove callback is running. It expires before Promise jobs
or microtasks execute, so retaining one cannot mutate a later command.
Async work
HTTP runs on cancellable native workers and settles Promises on the script owner lane. Unloading a package cancels and joins its requests before destroying QuickJS.
Security model
Safe mode and insecure mode have different trust assumptions.
Safe mode
Safe mode is designed for host-provided APIs that exchange copied values, opaque handles, and bounded resources. It does not expose raw native addresses.
Package isolation prevents ordinary JavaScript objects and globals from being shared across runtimes. It does not turn an injected module into an operating-system security boundary.
Insecure mode
Insecure mode trusts the complete package, including every relative module it imports. FFI can:
- Read or overwrite process memory
- Load DLLs
- Invoke exported native functions
- Block the game thread
- Bypass future safe host-API validation
- Crash the process despite exception containment
The global toggle is persisted in Fumoware configs. Loading a config without the setting returns the engine to safe mode, preventing insecure state from leaking between configs.
Do not enable insecure mode for downloaded or obfuscated packages.