Skip to content

Client SDK

The SDK is a small file you add to your own resources. It numbers the network events your players send, so that Bastrel can tell a call made by your resource from a call made by a cheat program that talks to the server on its own. It is a speed bump, not a proof: code injected into one of your running resources calls the same functions as the resource and is not stopped by it. The real protection stays on the server (registerNetEvent: schema, rate, rights, context; see netevents.md).

Bastrel never edits your files. You decide, resource by resource.

In the fxmanifest.lua of each resource whose client triggers one of those events (the resource that calls TriggerServerEvent, which is not always the one that registers the event with exports.bastrel:registerNetEvent):

shared_scripts {
'@bastrel/sdk/import.lua', -- FIRST
'config.lua',
-- your other scripts
}

Put it first in the shared_scripts: a script loaded before it that keeps TriggerServerEvent in a local variable would call the plain function and send without SDK data. A single shared_script '@bastrel/sdk/import.lua' line works too. The file is loaded on the client and, because it is a shared_script, also on the server of your resource, where it does nothing. If it is loaded twice in the same resource, the second copy does nothing.

Bastrel checks the manifest: it gives a number only to a resource whose fxmanifest.lua really lists @bastrel/sdk/import.lua as a shared_script or a client_script.

Only the events you register through registerNetEvent are numbered. Events registered with a plain RegisterNetEvent, callbacks (registerCallback) and every other call go out untouched, so a handler that counts its arguments is never affected. Your handler never sees the SDK data either: Bastrel removes it before calling you.

  • One hello per resource when it starts on the client, to ask for a number to start counting from.
  • Then, for an announced event only, one extra last argument: a table with the counter, the name of the resource and the client time. The client time is only written to the log when a detection is made; it is never used to judge.
  • One bastrel:sdk:stopped message when Bastrel’s client script stops on the player’s machine (the client event onClientResourceStop with the resource bastrel), carrying the name of your resource. It is sent plain, without the counter. It is the only fact the SDK reports about Bastrel; the server decides what it means (restart of Bastrel, normal disconnection, or a stopped client) and answers with a probe before judging: see heartbeat.md, BAS-HB-002. It needs the resource folder to be named bastrel, as the manifest line already does.

The SDK never grants anything (no teleport, no state): only the server API can. It holds no secret, no threshold and no rule: every file sent to players is public, and a CI test checks that none of them contains a threshold, a weight, a webhook, a convar name or a detection code.

An announced call is judged last, after the rate, size, schema, ACL and context checks (a refusal there comes first). BAS-NE-005 is raised when:

  • the call has no SDK data (a cheat program that calls the event directly), outside the grace periods below;
  • the counter is replayed, too old or jumps ahead (replay or forgery); a latent event that arrives a little late is fine;
  • the SDK data names a resource Bastrel gave no number to;
  • a hello comes without a reason (not the first one of the resource, not after the resource restarted), for a resource that is not started or whose manifest does not list the SDK, or too often.

The number of a call must be a whole number from 1 to 2^53: anything else is a malformed SDK data (BAS-NE-005).

Every counter is per player and per resource: each equipped resource has its own, and ensure or restart of a resource starts a new one.

BAS-NE-005 never blocks a call and never sanctions on its own. It is a faint clue (weight 2). The call goes on to your handler if the other checks passed.

  • netevents.sdk.announceGraceMs (5000): an event registered less than this ago is accepted without SDK data (the list reaches the players a moment later).
  • netevents.sdk.joinGraceMs (30000): a player is not judged on missing SDK data right after joining (his SDK needs a round trip first). This is only a delay: it never grants a teleport or a state.

If you forget the SDK in one resource, all its honest players would look suspicious. So BAS-NE-005 is off the score and in observe (nothing is applied, only logged) until you say it is ready:

  1. Add the SDK to every resource whose client triggers an event registered through registerNetEvent.
  2. Run the server and read the log. Bastrel writes a finding BAS-CV-107 when most calls to an event, from several different players, carry no SDK data. The finding names the event and the resource that registered it (registeredBy); the resource whose client makes the calls is unknown to the server, so look at who triggers that event in your client scripts: that resource probably lacks the SDK. It is only an alert to you: it changes no detection and no score.
  3. When no BAS-CV-107 is left, set sdk = { calibrated = true } in config/whitelists.lua. From then on BAS-NE-005 counts in the score, as a faint clue.

There is no automatic switch. A cheater could not be allowed to turn a detection off by making many calls without the SDK, so only you can turn it on.

  • It only slows down a cheat program that creates its own runtime. It does not stop code injected into a resource that is running, nor a cheat that reads the counter in the client and uses it.
  • It does not cover events that are not announced (plain RegisterNetEvent, callbacks, an event registered with requireSdk = false).
  • When a resource stops, Bastrel tells the players’ SDKs to stop numbering its events; they start again when the resource registers them again.
  • Honeypot event names (when you define some) are never sent to players.
  • A resource whose name is longer than 64 bytes sends no hello (the server would refuse it), so its events carry no SDK data and are not announced to the SDK (never judged for a missing envelope; one netevents.sdk_name_too_long line per resource tells you; a call from such a resource to an event of ANOTHER resource still counts as a missing envelope, which the server cannot fix without a forgeable mark): keep resource names at 64 bytes or less. Its stop message is cut to 64 bytes (never in the middle of a character), so it never counts as a forged message (BAS-NE-001).
  • A cheat program that sends its own hello before the real SDK of a resource, in the first seconds of a connection, can take the number of that resource: the real SDK then gets none, and its calls will show as BAS-NE-005 while it stays in observe. This is the reason for the calibration step.
  • TriggerLatentServerEvent: the SDK follows the documented form TriggerLatentServerEvent(eventName, bps, ...). That a shared_script can replace these two functions for the other scripts of the same resource has to be confirmed in game (this test is still to be done).

config/modules.lua, block netevents.sdk (integers, except coverageMissingRatio, a number; an invalid value takes its default, with a warning):

Key Default Meaning
seqTolerance 8 Counter values below the latest one that are still accepted once (1 to 32).
seqJumpFactor 4 A step above the latest value of more than seqTolerance x this is a jump (1 to 16); the counter then restarts from it, one detection and no cascade.
helloMax / helloPerMs 3 / 60000 hello messages accepted per player and per resource.
helloPlayerMax / helloPlayerPerMs 100 / 60000 hello messages accepted per player, whatever the resource (checked first; 1 to 1000 per 1000 to 3600000 ms). Keep helloPlayerMax above the number of resources that load the SDK: each sends one hello per start on a client, and a player past the limit gets no number for the next resources until he reconnects. Bastrel warns at startup when the count reaches it.
announceGraceMs 5000 See above (0 to 600000).
joinGraceMs 30000 See above (0 to 600000).
coverageMinCalls 20 Calls needed before BAS-CV-107 is raised for an event.
coverageMinPlayers 3 Distinct players needed: one player alone can never raise it.
coverageReportMs 3600000 At most one BAS-CV-107 per event and report window.
coverageWindowMs 3600000 The calls are counted per window of this length, not since the start (1000 to 86400000).
coverageMissingRatio 0.5 A number from 0.1 to 0.99: BAS-CV-107 needs more than this share of the calls of the window to carry no SDK data.

config/whitelists.lua: sdk = { calibrated = false } (see above). The weight of BAS-NE-005 is in scoring.weights (2).