Packets and hooks

Intercepting, modifying and sending packets, filters, raw hooks and protocol versions.

Reading packets

mod.hook(name, version, [options], callback) registers a handler and returns a hook handle. name is the exact packet name, such as S_LOAD_TOPO; version is the .def definition version, not the game patch. C_ indicates a client-originated packet, S_ a server-originated packet.

mod.hook('S_LOAD_TOPO', 3, (event) => {
    mod.log(`Zone: ${event.zone}`);
});

The .def file determines the properties of event. For example, int32 zone, vec3 loc and bool quick produce matching object properties. IDs such as gameId use BigInt; converting them to Number can lose precision.

Modification and blocking

Callback result Behavior
undefined Forward without saving object changes
true Serialize the modified object and remove any previous block
false Silence the packet
mod.hook('S_CHAT', 3, (event) => {
    if (event.message === 'blocked example') return false;
    if (event.message === 'replace example') {
        event.message = 'Replacement text';
        return true;
    }
});

Handlers run synchronously. An async function returns a Promise instead of true/false, so it cannot decide whether to modify or block the current packet.

Sending

mod.send(name, version, data) determines the destination from the name: C to the server, S/I to the client, accounting for the TTB_ prefix. mod.toClient and mod.toServer specify the destination explicitly and accept either (name, version, data) or a complete Buffer including the header. mod.send(buffer) is unsupported.

mod.send('S_CHAT', 3, {
    channel: 21,
    name: 'Example',
    message: 'Hello!'
});

The parser provides defaults for omitted fields, but defaults do not guarantee valid game semantics. Supply meaningful fields according to the definition. Generated packets pass through hooks with fake: true. Sending returns true when handed to the connection, or false if not sent or silenced; this is not a server acknowledgement.

Ordering and filters

mod.hook('S_LOAD_TOPO', 3, {
    order: 10,
    filter: { fake: false, incoming: true, modified: null, silenced: false }
}, (event) => {
    mod.log(event.zone);
});

Lower order runs first; the default is 0. A filter value of true requires the flag, false excludes it, and null ignores it.

Filter Default Meaning of true
fake false Generated by the proxy
incoming null Destined for the client
modified null Changed by an earlier hook
silenced false Blocked by an earlier hook

Flags are available as event.$fake, $incoming, $modified and $silenced. If you receive fake packets, avoid generating the same packet repeatedly and creating an infinite loop. Silencing does not stop iteration: later matching hooks may inspect and restore the packet.

Raw and event hooks

'raw' passes (code, data, incoming, fake), where data is a decrypted Buffer including the four-byte header. This distribution passes a copy: return the modified Buffer to preserve changes. Returning false silences the packet; true removes a block but does not preserve edits to the copy. This differs from the older tera-network-proxy README.

const observer = mod.hook('*', 'raw', (code, data, incoming) => {
    mod.log(`${incoming ? 'S' : 'C'} opcode=${code}, bytes=${data.length}`);
});
// Stop observing when no longer needed:
mod.unhook(observer);

'event' only signals that a packet occurred, without parsing or callback arguments. Returning false silences it. Example: mod.hook('S_LOGIN', 'event', () => mod.log('Login packet')). A '*' hook is normally used with 'raw' or 'event'.

One-shot hooks and errors

mod.hookOnce(...) unregisters before the first callback invocation. mod.unhook(handle) removes a specific hook; do not modify its handle. Module hooks are removed on unload.

mod.tryHook(...) returns null on registration failure. mod.tryHookOnce(...) does the same, but an invalid final argument still throws. Neither method catches errors thrown later by callbacks. mod.trySend(...) returns false on a synchronous send error; use ordinary send with try/catch and mod.error for diagnostics.

Versions

'*' selects the latest available definition; it does not unify incompatible layouts. This distribution accepts one version per hook call, not an array of versions. For multiple patches, select a verified version using mod.majorPatchVersion or register a suitable definition after checking availability. Do not hide a broken mandatory hook behind tryHook.

Sources: doc/mod/hooks.md, bin/mod.js, node_modules/tera-network-proxy/lib/connection/dispatch.js. See networking for the traffic pipeline.