Documentation 9.6.24
Événements et triggers
Les événements que Tosun AntiCheat émet pour que vos scripts les écoutent, les événements que vous pouvez déclencher pour l'intégration, et les listes de config qui associent vos propres événements à des exemptions, des vérifications de clé, des limites de débit et des pièges. Les événements internes de l'anticheat sont protégés et ne doivent pas être appelés.
Vue d'ensemble#
FiveM a des événements locaux et des événements réseau. Un événement local reste d'un seul côté du jeu : TriggerEvent le déclenche et AddEventHandler le reçoit. Un événement réseau traverse le réseau : le serveur l'envoie avec TriggerClientEvent, un client l'envoie avec TriggerServerEvent, et le côté qui le reçoit doit l'enregistrer avec RegisterNetEvent. Tosun AntiCheat utilise les deux types, sur le serveur et sur le client.
Cette page liste les événements que l'anticheat émet pour que vous les écoutiez, les événements que vous pouvez déclencher pour intégrer vos scripts, et les listes de config (ts.DetectionExempt, ts.ProtectedEvents, ts.EventLimiter, ts.triggerList) qui transforment vos propres noms d'événements en exemptions, vérifications de clé, limites de débit ou pièges.
L'anticheat enregistre aussi de nombreux événements internes pour sa propre poignée de main à la connexion, l'échange de clés, Shield, le menu admin et les actions du panel web. Ils sont protégés par des nonces, des clés, des signatures, des limites de débit ou des vérifications du staff, et certains sont des pièges. Ils ne font pas partie de la surface d'intégration : ne les déclenchez pas, n'y enregistrez pas de gestionnaires et ne réutilisez pas leurs noms. Utilisez plutôt les exports.
| Type | Déclencher avec | Recevoir avec | Qui peut le déclencher |
|---|---|---|---|
| Événement local serveur | TriggerEvent | AddEventHandler | N'importe quelle ressource serveur. |
| Événement local client | TriggerEvent | AddEventHandler | N'importe quel script client dans le jeu de ce joueur, code de triche injecté compris. |
| Événement réseau, client vers serveur | TriggerServerEvent | RegisterNetEvent + AddEventHandler | N'importe quel joueur connecté, avec n'importe quels arguments. |
| Événement réseau, serveur vers client | TriggerClientEvent | RegisterNetEvent + AddEventHandler | N'importe quelle ressource serveur. Un script client peut aussi déclencher le même nom en local avec TriggerEvent. |
Tâches d'intégration courantes#
Choisissez l'événement ou le réglage qui correspond à ce que vous voulez faire. Chacun est détaillé plus bas sur cette page.
| Objectif | À utiliser |
|---|---|
| Réagir quand une détection aboutit à un ban, une expulsion ou une ligne de log | ts_anticheat:playerBanned |
| Réagir uniquement quand un ban a réellement été prononcé | ts_anticheat:banCommitted |
| Réagir à un unban | ts_anticheat:playerUnbanned |
| Reproduire le flux de logs en direct | ts_anticheat:newLog |
| Rafraîchir votre cache quand l'anticheat réapplique les réglages | tosun-ac:settingsReloaded / ts_anticheat:configUpdated |
| Appliquer un renommage de personnage du panel sur un framework personnalisé | tosun-ac:renameCharacter |
| Activer les vérifications client après un spawn personnalisé | playerSpawned |
| Rafraîchir le panel après une sélection de personnage personnalisée | tosun-ac:panel:characterReady |
| Faire revérifier le statut staff par le client | ts_anticheat:adminStateChanged |
| Exempter un joueur côté client depuis votre script serveur | tosun-ac:bridge:clientExempt |
| Exempter automatiquement les joueurs quand votre événement de menu ou d'action se déclenche | ts.DetectionExempt |
| Exiger une clé par joueur sur votre propre événement client vers serveur | ts.ProtectedEvents + <name>:safe |
Événements serveur émis par l'anticheat#
Ce sont des événements locaux serveur. Écoutez-les avec AddEventHandler dans n'importe quel script serveur. Ne les déclenchez pas vous-même ; voir les sections sécurité plus bas.
| Événement | Quand il se déclenche | Données |
|---|---|---|
ts_anticheat:playerBanned | À chaque issue de détection : BAN, KICK et LOG. Il se déclenche avant l'expulsion, l'insertion du ban et les logs. Il ne se déclenche pas pour les joueurs ignorés parce qu'ils sont staff ou en liste blanche. | table data : playerId (chaîne), playerName, reason, banID, side ('server'), punishment ('BAN', 'KICK' ou 'LOG') |
ts_anticheat:banCommitted | Issues BAN uniquement (détections et exports ban et punish), juste après la mise en file de l'insertion de la ligne de ban et avant le webhook du panel et la déconnexion du joueur. | playerId (nombre), table row : banID, playerName, reason, license, steam, discord, ip, HWID, HWID2 à HWID5, config (clé de config de la détection) |
ts_anticheat:playerUnbanned | Après que l'export unban, la commande /ts unban ou le menu en jeu a retiré un ban. Les unbans faits dans le panel web ne le déclenchent pas. | banID (la valeur passée à l'export unban, inchangée) |
ts_anticheat:newLog | Chaque ligne de log en direct : détections, lignes de log client, actions du menu admin et certaines lignes internes de l'anticheat. | table payload : src (libellé de catégorie, pas un id de joueur), event (texte du message), status ('Info' par défaut), playerName, time (os.time()) |
tosun-ac:settingsReloaded | Après qu'une synchro a réappliqué les réglages. Cela arrive au démarrage et à presque chaque cycle de synchro, pas seulement quand quelque chose a changé. | aucune |
ts_anticheat:shield:screenshot_saved | Une capture d'écran Shield demandée est arrivée et a passé les vérifications. | src (nombre), b64 (image en base64, de 64 à 2 500 000 caractères) |
ts_anticheat:shield:ban_video_saved | La vidéo de gameplay d'un joueur expulsé ou banni a été envoyée dans les 120 s. | src (nombre), videoUrl, reason, config (reason et config viennent du client) |
<name>:safe | Un appel d'un événement listé dans ts.ProtectedEvents a passé les vérifications de clé et de données. | src (nombre, id d'un joueur en ligne), puis les arguments d'origine après la clé |
tosun-ac:renameCharacter | Un renommage de personnage depuis le panel web sur un framework autre que qb, qbcore, qbox, qbx ou esx. | citizenid (chaîne), firstName (premier mot), lastName (reste du nom, peut valoir '') |
Événements client émis par l'anticheat#
Ces événements arrivent aux scripts client. ts_anticheat:configUpdated est un événement local client ; écoutez-le avec AddEventHandler. ts_anticheat:receiveEventKey est un événement réseau venant du serveur ; enregistrez-le avec RegisterNetEvent dans votre script client.
| Événement | Type | Quand il se déclenche | Données |
|---|---|---|---|
ts_anticheat:configUpdated | Événement local client | Après que le client a appliqué les réglages reçus du serveur et réanalysé ses hooks DetectionExempt. Comptez environ une fois par intervalle de synchro. | aucune |
ts_anticheat:receiveEventKey | Événement réseau, serveur vers client | En réponse à ts_anticheat:requestEventKey (votre demande ou celle de l'anticheat lui-même), et chaque fois que le serveur attribue une nouvelle clé au joueur. Il arrive après la poignée de main de connexion. | key (chaîne, la clé SafeEvents du joueur) |
Exemple d'écoute#
Un script serveur qui réagit aux expulsions et aux bans, et un script client qui vide un cache quand l'anticheat réapplique les réglages. Chaque écouteur serveur vérifie d'abord que c'est bien l'anticheat qui a déclenché l'événement.
-- server.lua (votre ressource)
local AC = 'tosun-ac' -- nom de la ressource (dossier) de l'anticheat
AddEventHandler('ts_anticheat:playerBanned', function(d)
if GetInvokingResource() ~= AC then return end -- ignorer les appels falsifiés
if d.punishment == 'LOG' then return end
print(('[AC] %s %s: %s'):format(d.punishment, d.playerName, d.reason))
end)
AddEventHandler('ts_anticheat:banCommitted', function(playerId, row)
if GetInvokingResource() ~= AC then return end
print('AC ban', row.banID, row.license, row.reason)
end)
-- client.lua (votre ressource)
local myCache
AddEventHandler('ts_anticheat:configUpdated', function()
myCache = nil
end)playerBanned en détail#
Le nom est historique. ts_anticheat:playerBanned se déclenche pour chaque issue de détection, pas seulement pour les bans. Filtrez sur data.punishment.
data.playerId est une chaîne. Convertissez-la avec tonumber avant de l'utiliser comme id serveur.
L'événement se déclenche avant l'action. L'annonce dans le chat, les logs, DropPlayer et l'insertion du ban s'exécutent après votre gestionnaire.
Il ne se déclenche pas quand la cible est hors ligne, quand le joueur est ignoré parce qu'il est staff (le staff est ignoré sauf si ts.AdminBypassDetections vaut false ou si ts.Debug est actif), ni quand le joueur est dans la liste blanche temporaire ou la liste blanche du panel.
Un banID est généré pour chaque issue, mais stocké uniquement pour BAN. Un KICK visant un membre du staff est annulé alors que l'événement s'est déjà déclenché.
Tant que l'écran de ban (editable/server/sv_banscreen.lua) est affiché à un joueur, la plupart des détections suivantes pour ce joueur sont absorbées et aucun événement ne se déclenche pour elles.
banCommitted en détail#
ts_anticheat:banCommitted est le meilleur point d'accroche pour « un ban a réellement été prononcé ». Utilisez playerBanned pour les expulsions et les lignes de log.
Il se déclenche pour chaque issue BAN du circuit de sanction de l'anticheat : détections et exports ban et punish. Les bans hors ligne (exports offlineBan et offlineBanByLicense) et les bans faits dans le panel web ne le déclenchent pas.
L'insertion de la ligne de ban est mise en file avec MySQL.Async.execute sans être attendue : la ligne peut donc ne pas encore être en base quand votre gestionnaire s'exécute.
Les identifiants de la ligne ont leur préfixe retiré (license:, license2:, steam:, discord:, ip:). Un identifiant ou un jeton manquant vaut le texte anglais 'Not found' ; une ip manquante vaut 'Hidden'. HWID à HWID5 sont les jetons bruts du joueur.
newLog et settingsReloaded en détail#
Ces deux événements se déclenchent souvent. Gardez leurs gestionnaires légers.
ts_anticheat:newLog : le champ src est un libellé de catégorie, pas un id de joueur. Les détections utilisent 'DETECTION' avec le statut BAN, KICK ou LOG. Les lignes de log client utilisent une étiquette et un statut fixes : 'AntiCheat' avec 'Tespit' (« Detection » codé en dur en turc), 'AC-Grace' avec 'Info', et 'AC-FakeTrigger' avec 'Warning'. Les entrées du menu admin utilisent normalement le nom de l'admin avec le statut BAN, KICK, INFO ou SUCCESS. Quelques lignes internes utilisent d'autres catégories.
tosun-ac:settingsReloaded : l'anticheat ne compare pas les anciennes et nouvelles valeurs ; l'événement se déclenche donc après la synchro de démarrage et à la plupart des cycles de synchro, toutes les ts.ServerPerf.configSyncIntervalSec secondes (120 dans la config fournie, 60 si la clé est absente, minimum 20). Lisez-le comme « les réglages ont été réappliqués », pas « les réglages ont changé ».
Après chacune de ces synchros, le serveur envoie aussi les réglages à tous les clients, et chaque client déclenche alors ts_anticheat:configUpdated.
Si une autre ressource serveur déclenche tosun-ac:settingsReloaded, l'anticheat lance un rafraîchissement complet des permissions staff pour chaque joueur en ligne.
Événements de capture d'écran et de vidéo Shield#
Ces deux événements nécessitent Shield (ts.shield.enabled différent de false ; il est actif dans la config fournie).
ts_anticheat:shield:screenshot_saved : demandez une capture d'écran avec l'export serveur RequestShieldScreenshot(src). L'image doit arriver dans les 60 s suivant la demande, et l'anticheat en accepte au plus une toutes les 20 s par joueur. Le panel web peut aussi demander des captures, et celles-ci déclenchent également l'événement.
ts_anticheat:shield:ban_video_saved : nécessite ts.shield.banGameplayVideo différent de false (actif dans la config fournie). La fenêtre d'envoi s'ouvre pendant 120 s après un KICK ou un BAN, et l'hôte de l'URL de la vidéo doit correspondre au panel.
reason et config dans ban_video_saved viennent du client et ne sont ni vérifiés en type ni tronqués. Traitez-les comme du texte non fiable.
local AC = 'tosun-ac'
-- serveur : demander une capture d'écran au jeu du joueur
exports[AC]:RequestShieldScreenshot(src)
AddEventHandler('ts_anticheat:shield:screenshot_saved', function(src, b64)
if GetInvokingResource() ~= AC then return end
-- b64 est une chaîne d'image en base64
end)Renommage de personnage sur un framework personnalisé#
Le panel web peut renommer un personnage. Pour qb, qbcore, qbox et qbx, l'anticheat met à jour players.charinfo ; pour esx, il met à jour users.firstname et users.lastname. Pour toute autre valeur de ts.Framework.framework, il déclenche tosun-ac:renameCharacter afin que vous appliquiez le renommage vous-même.
L'anticheat signale 'event_emitted_unknown_fw' au panel, qu'un écouteur existe ou non.
Le nouveau nom est débarrassé des caractères de contrôle, limité à 64 caractères et doit faire au moins 3 caractères. firstName est le premier mot ; lastName est le reste et peut être vide.
L'exemple utilise l'appel MySQL.update d'oxmysql et une table characters inventée. Adaptez la requête à votre schéma.
AddEventHandler('tosun-ac:renameCharacter', function(cid, first, last)
if GetInvokingResource() ~= 'tosun-ac' then return end
MySQL.update('UPDATE characters SET first = ?, last = ? WHERE cid = ?', { first, last, cid })
end)Événements que vous pouvez déclencher#
Voici les événements de l'anticheat destinés à être déclenchés par vos scripts. Les événements de mort et de réanimation des frameworks et vos événements ts.DetectionExempt sont traités dans leurs propres sections.
| Événement | Déclencher depuis | Effet | Limites |
|---|---|---|---|
tosun-ac:panel:characterReady | Client, avec TriggerServerEvent. | Rafraîchit les données du personnage du joueur dans la liste des joueurs en ligne du panel. | Un appel toutes les 20 s par joueur. |
ts_anticheat:locale:setSelf | Client, avec TriggerServerEvent et un code de langue. | Règle la langue des messages serveur de l'anticheat pour ce joueur uniquement. Affiche une ligne dans la console du serveur. | Aucune limite de débit. Le code doit faire 2 ou 3 lettres minuscules et un fichier locales/<lang>.json doit exister. |
ts_anticheat:requestEventKey | Client, avec TriggerServerEvent. | Renvoie la clé SafeEvents du joueur via ts_anticheat:receiveEventKey. | Un appel toutes les 3 s par joueur. La clé est envoyée uniquement au joueur qui la demande. |
playerSpawned | Client, avec TriggerEvent (local). | Active la plupart des vérifications client et démarre la fenêtre de grâce du spawn. | Toutes les autres ressources de ce client le reçoivent aussi. |
ts_anticheat:adminStateChanged | Serveur, avec TriggerClientEvent vers un joueur. | Le client redemande au serveur son vrai statut staff. | Le serveur répond au plus une fois par seconde et par joueur. |
tosun-ac:bridge:clientExempt | Serveur, avec TriggerClientEvent ; nécessite le fichier bridge dans votre ressource. | Exemption côté client pour ce joueur. | 500 à 300000 ms. |
Spawn personnalisé : playerSpawned#
La plupart des vérifications client restent inactives tant que l'anticheat n'a pas vu l'événement local playerSpawned : regen, godmode, téléportation, invisibilité, freecam, armes, OCR, natives, coordonnées et godmode des véhicules. spawnmanager le déclenche normalement.
Si votre système de spawn ou de multipersonnage n'utilise pas spawnmanager, déclenchez playerSpawned en local une fois le joueur dans le monde, ou appelez l'export client SetSpawned(true).
playerSpawned démarre aussi la fenêtre de grâce du spawn de ts.spawnGraceSecondsMs. La valeur fournie est 500, que le code relève à son minimum de 5000 ms ; sans la clé, le code utilise 25000. Une détection dans cette fenêtre n'est pas annulée. Elle ajoute seulement une ligne de log 'AC-Grace'.
playerSpawned efface aussi l'état de mort de l'anticheat et marque une téléportation client.
La liste ts.DetectionExempt.clientEvents fournie contient playerSpawned : chaque playerSpawned démarre donc aussi une exemption client de defaultDurationMs (60 s).
SetSpawned(false) prend effet au plus une fois toutes les 5 minutes, et les vérifications se réactivent après environ 30 s de mouvement visible.
-- client, après votre propre logique de spawn
TriggerEvent('playerSpawned')
-- ou, sans les effets de bord sur les autres ressources
exports['tosun-ac']:SetSpawned(true)Multipersonnage personnalisé : characterReady#
Sur le serveur, l'anticheat écoute QBCore:Server:OnPlayerLoaded, QBCore:Server:PlayerLoaded, esx:playerLoaded et qbx_core:server:playerLoaded. Chacun pousse immédiatement le personnage chargé dans la liste des joueurs en ligne du panel. Un script de multipersonnage personnalisé qui n'en déclenche aucun doit déclencher tosun-ac:panel:characterReady depuis le client après la sélection du personnage.
Chaque appel écrit la ligne du joueur en base et, quand l'URL et la clé du panel sont définies, envoie une requête HTTPS au panel. Le serveur accepte donc un appel toutes les 20 s par joueur et ignore les autres.
-- client, une fois la sélection du personnage terminée
TriggerServerEvent('tosun-ac:panel:characterReady')Langue du joueur : locale:setSelf#
Un client peut choisir pour lui-même la langue des messages serveur de l'anticheat. En cas de succès, le serveur affiche une ligne dans la console du serveur.
Cela ne change que les messages serveur envoyés à ce joueur. La langue côté client n'est pas modifiée.
Le client de l'anticheat lui-même n'envoie pas cet événement. Les alternatives côté serveur sont l'export setPlayerLocale et la commande ts_setlang.
-- client
TriggerServerEvent('ts_anticheat:locale:setSelf', 'en')Changements de staff : adminStateChanged#
Après que votre script admin a accordé ou retiré des droits staff, demandez au client du joueur de revérifier son statut staff. L'événement ne peut pas changer le statut à lui seul : le client envoie un nouveau nonce au serveur, et seule la réponse du serveur décide.
Rien dans l'anticheat n'envoie cet événement. Le client revérifie aussi de lui-même toutes les 20 s : l'appel ne fait que supprimer ce délai.
Une triche qui le déclenche en local n'y gagne rien ; le serveur répond à la vérification au plus une fois par seconde et par joueur.
-- serveur, après modification des droits staff de src
TriggerClientEvent('ts_anticheat:adminStateChanged', src)Événement bridge : tosun-ac:bridge:clientExempt#
Le fichier bridge client enregistre cet événement réseau dans votre propre ressource. Votre script serveur l'envoie à un joueur, et le bridge appelle l'export client SetExempt de l'anticheat pour ce joueur.
durationMs est un nombre en ms ou un nom de préréglage, quelle que soit la casse : SHORT, MEDIUM, LONG ou XL (30, 60, 120 ou 180 s). Les valeurs sont bornées entre 500 et 300000 ms. Sans valeur, la valeur par défaut de la convar s'applique (60000).
La clé d'exemption est 'bridge:' suivi de tag, ou du nom de votre ressource quand tag vaut nil.
Il s'agit uniquement d'une exemption côté client. Les vérifications serveur continuent.
L'anticheat n'envoie jamais cet événement. Si plusieurs de vos ressources incluent le fichier bridge, un seul TriggerClientEvent s'exécute une fois dans chacune d'elles.
- Ajoutez client_script '@tosun-ac/bridge/tosun_ac_client.lua' au fxmanifest.lua de votre ressource.
- Facultatif, dans server.cfg : si vous avez renommé le dossier de l'anticheat, ajoutez setr tosun_ac_resource avec le nouveau nom (tosun-ac par défaut). Pour changer la durée par défaut, ajoutez setr tosun_ac_bridge_client_ms avec une valeur en ms (60000 par défaut).
- Depuis votre script serveur, envoyez TriggerClientEvent('tosun-ac:bridge:clientExempt', src, durationMs, tag).
-- serveur : exempter le joueur 2 minutes pendant qu'un menu de barbier est ouvert
TriggerClientEvent('tosun-ac:bridge:clientExempt', src, 'LONG', 'barber')Événements de mort et de réanimation des frameworks#
Le client écoute ces événements pour savoir quand un joueur est mort ou à terre. Tant que cet état est actif, les vérifications client de pic de régénération, godmode, téléportation, invisibilité, mode spectateur et plusieurs autres sont ignorées. Les morts natives sont aussi détectées via l'événement de dégâts gameEventTriggered.
Depuis la 9.5.9, l’anticheat met fin à cet état de lui-même dès que le joueur est de nouveau clairement actif.
Un script d'ambulance personnalisé peut déclencher l'un de ces noms en local pour aligner l'état de l'anticheat. Ce sont des noms de framework partagés : toutes les autres ressources de ce client qui les écoutent s'exécutent aussi (par exemple les écrans de mort ESX).
| Événement | Type | Effet |
|---|---|---|
hospital:client:isDead | Réseau | À terre ; démarre la fenêtre post-mort de 25 s. |
hospital:client:SetDeathState | Réseau, argument booléen | true : à terre et fenêtre post-mort de 25 s. false : réanimé et fenêtre post-réanimation de 10 s. |
hospital:client:SetLaststand | Réseau, argument booléen | true : à terre et fenêtre post-mort de 25 s (false est ignoré). Également une exemption automatique de 45 s. |
qb-medical:client:OnDeath | Réseau | À terre ; démarre la fenêtre post-mort de 25 s. |
esx:onPlayerDeath | Réseau | À terre ; démarre la fenêtre post-mort de 25 s. Également une exemption automatique de 45 s. |
baseevents:onPlayerDied | Local (réseau tant qu'autoExempt est actif) | Démarre la fenêtre post-mort de 25 s. Également une exemption automatique de 45 s. |
baseevents:onPlayerKilled | Local uniquement | Démarre la fenêtre post-mort de 25 s. |
hospital:client:Revive | Réseau | Réanimé ; démarre la fenêtre post-réanimation de 10 s. |
qb-ambulancejob:client:RevivePlayer | Réseau | Réanimé ; démarre la fenêtre post-réanimation de 10 s. |
qb-medical:client:OnRevive | Réseau | Réanimé ; démarre la fenêtre post-réanimation de 10 s. |
esx_ambulancejob:revive | Réseau | Réanimé ; démarre la fenêtre post-réanimation de 10 s. |
esx_basicneeds:onRevive | Réseau | Réanimé ; démarre la fenêtre post-réanimation de 10 s. |
playerSpawned | Local | Efface l'état à terre et laisse environ 17 s de la fenêtre post-mort. |
-- ambulance personnalisée (client), après votre propre logique de réanimation
TriggerEvent('esx_ambulancejob:revive')Événements d'exemption automatique (autoExempt)#
Avec ts.autoExempt.enabled (true dans la config fournie), le client écoute des événements de framework courants et accorde une courte exemption client via l'export SetExempt. Les détections client pendant l'exemption ne sont pas sanctionnées ; elles sont signalées comme lignes LOG marquées '[exempt]'. Réglez ts.autoExempt.enabled = false pour désactiver ce comportement.
Le commentaire d'en-tête de client/anticheat_auto_exempt.lua indique que ces événements ne peuvent pas être déclenchés à distance. C'est faux : ils sont enregistrés avec RegisterNetEvent, donc le serveur peut aussi les déclencher. L'effet reste une simple exemption client.
| Situation | Durée | Événements |
|---|---|---|
| Menus d'apparence | 180 s | illenium-appearance:client:openMenu, appearance:client:openMenu, fivem-appearance:client:openMenu, ox_appearance:openMenu |
| Menus de vêtements | 180 s | qb-clothing:client:openMenu, qb-clothes:client:openMenu, rcore_clothing:openClothingShop, clothing:client:openMenu |
| Menus de skin et de barbier ESX | 180 s | esx_skin:openSaveableMenu, esx_skin:openRestrictedMenu, skinchanger:model:loaded, tgg_barber:open, barbershop:client:open |
| Sélection du personnage | 30 s | qb-multicharacter:client:chooseChar, qb-multicharacter:client:closeNUIdefault, esx_multicharacter:SetupCharacters, esx:restoreLoadout, ox:playerLoaded, qbx_core:client:playerLoggedOut |
| Mort et laststand (QB, ESX) | 45 s | esx_ambulancejob:setDeathStatus, esx:onPlayerDeath, hospital:client:SetLaststand, hospital:client:OnPlayerLaststand, qb-ambulancejob:client:playerDead |
| Mort (autres scripts) | 45 s | wasabi_ambulance:clientDeath, ars_ambulancejob:client:dead, baseevents:onPlayerDied, baseevents:onPlayerWasted |
| Personnage chargé | 15 s | QBCore:Client:OnPlayerLoaded, qbx_core:client:playerLoaded, esx:playerLoaded, ox:playerLoaded |
| Démarrage de l'anticheat sur le client | 60 s | onClientResourceStart |
Porte de connexion : événements de chargement du framework#
Avec ts.NetworkJoin.waitForFrameworkLoaded (true dans la config fournie) et le framework qb, qbox ou esx, le client attend un événement de chargement du framework avant de terminer sa connexion à l'anticheat. Cela retarde le démarrage de toute la protection client pour ce joueur. Les autres valeurs de framework sautent cette attente.
Après minReadyDelayMs (15 s dans la config fournie) et la fin de l'écran de chargement, le client attend l'un des événements de chargement, ou LocalPlayer.state.isLoggedIn (qb, qbox) ou ESX.PlayerLoaded (esx), jusqu'à 180 s au total. Il attend ensuite postFrameworkSettleMs (5 s dans la config fournie) et les collisions avant de se déclarer prêt.
| Framework | Événements de chargement | Événements de réinitialisation |
|---|---|---|
qb | QBCore:Client:OnPlayerLoaded | QBCore:Client:OnPlayerUnload |
qbox | QBCore:Client:OnPlayerLoaded, qbx_core:client:playerLoggedIn, qbx_multicharacter:client:chooseChar | QBCore:Client:OnPlayerUnload |
esx | esx:playerLoaded | esx:onPlayerLogout |
Événements protégés (SafeEvents)#
ts.ProtectedEvents fait vérifier par l'anticheat une clé par joueur sur vos propres événements client vers serveur. Un appel qui passe est redéclenché sur le serveur comme événement local '<name>:safe', avec l'id du joueur en premier. La liste fournie contient seulement 'test:event'.
TriggerSafeServerEvent n'existe que dans le client de l'anticheat lui-même et n'est pas exporté : les autres ressources doivent donc utiliser le schéma receiveEventKey ci-dessus. Le client de l'anticheat demande la clé toutes les 5 s jusqu'à ce qu'elle arrive (12 tentatives au maximum).
Le serveur répond au plus à une demande de clé toutes les 3 s par joueur, et les demandes de l'anticheat lui-même comptent aussi. Si aucune clé n'arrive, redemandez-la après quelques secondes. Gardez votre gestionnaire enregistré et envoyez toujours la dernière clé reçue.
- Ajoutez le nom de votre événement à ts.ProtectedEvents dans configs/anticheat_config.lua. La liste est lue une seule fois au démarrage : redémarrez l'anticheat après chaque modification.
- Dans votre script client, enregistrez ts_anticheat:receiveEventKey pour stocker la clé, puis envoyez ts_anticheat:requestEventKey.
- Envoyez votre événement avec la clé comme premier argument : TriggerServerEvent(name, key, ...).
- Dans votre script serveur, gérez '<name>:safe' avec AddEventHandler. Le premier argument est l'id serveur du joueur, suivi de vos arguments.
-- configs/anticheat_config.lua
ts.ProtectedEvents = { ['my-shop:buy'] = true }
-- client (votre ressource)
local key
RegisterNetEvent('ts_anticheat:receiveEventKey', function(k) key = k end)
TriggerServerEvent('ts_anticheat:requestEventKey')
-- plus tard, une fois la clé reçue :
TriggerServerEvent('my-shop:buy', key, 'bread')
-- serveur (votre ressource)
AddEventHandler('my-shop:buy:safe', function(src, item)
if GetInvokingResource() ~= 'tosun-ac' then return end
-- vérifiez ici l'argent, le stock et les permissions
end)Ce que SafeEvents vérifie et ne vérifie pas#
SafeEvents ne valide que le transport. Il ne sanctionne jamais personne et ne remplace pas les vérifications de votre propre gestionnaire.
- Chaque appel client compte d'abord dans ts.SafeEventRateLimit par fenêtre de ts.SafeEventRateWindow secondes (30 appels par 10 s par défaut), même sans clé.
- Si le joueur n'a pas encore de clé, l'appel est abandonné en silence.
- Une clé erronée, la limite de débit, plus de ts.SafeEventMaxArgs arguments (64 par défaut) ou des données invalides font abandonner l'appel et écrivent une ligne de log en base par joueur, événement et fenêtre.
- Les appels venant du serveur (source 0) sont ignorés et ne déclenchent aucun événement ':safe'.
- L'événement réseau brut atteint quand même tout gestionnaire que votre propre ressource a enregistré avec RegisterNetEvent. Seul le miroir ':safe' est filtré.
- N'utilisez jamais RegisterNetEvent sur le nom ':safe' ; cela permettrait aux clients de l'appeler directement.
- SafeEvents est une couche supplémentaire : votre propre gestionnaire doit toujours vérifier les permissions, les soldes et la propriété.
- N'ajoutez pas d'événements de framework (esx, qb) à la liste. Leurs appelants n'envoient pas de clé : SafeEvents rejette donc ces appels, écrit des lignes de log et ne déclenche jamais ':safe'. Les gestionnaires du framework lui-même s'exécutent quand même.
- Le commentaire en turc de configs/anticheat_config.lua indique que les appels sans clé provoquent un faux ban. Dans cette version, ils sont seulement abandonnés.
ts.DetectionExempt : associer vos événements à des exemptions#
ts.DetectionExempt transforme vos propres noms d'événements en exemptions de détection temporaires. Utilisez-le pour les menus et les actions serveur qui modifient légitimement l'apparence, la position ou l'état d'un joueur : vêtements, barbiers, logements, garages, sélection de personnage et réanimations. Les listes sont lues dans configs/anticheat_config.lua ; les réglages du panel ne les modifient pas. ts.MenuDetectionExempt est un alias conservé pour les anciennes configs.
| Clé | Type | Valeur fournie | Effet |
|---|---|---|---|
enabled | booléen | true | false désactive tous les hooks DetectionExempt. |
defaultDurationMs | nombre (ms) | 60000 | Durée d'exemption pour les entrées sans durationMs propre. Les entrées serveur sont bornées entre 1000 et 120000 ms, les entrées client entre 1000 et 300000 ms. |
budgetMsPer10Min | nombre (ms) | 240000 | Temps d'exemption souple maximal qu'un joueur peut cumuler via serverEvents en 10 minutes. |
clientEvents | liste de chaînes | 153 entrées (142 noms uniques) | Événements réseau client. Quand l'un se déclenche, les vérifications client du joueur sont exemptées. |
localEvents | liste de chaînes | 11 entrées | Événements locaux client (TriggerEvent sur le client uniquement). Même effet que clientEvents. |
serverEvents | liste de chaînes ou de tables | 34 entrées | Événements réseau client vers serveur. Quand un joueur en déclenche un, ce joueur reçoit une exemption souple. |
DetectionExempt.serverEvents#
L'anticheat enregistre chaque nom listé avec RegisterNetEvent. Quand un joueur le déclenche, ce joueur reçoit une exemption souple : les vérifications de mouvement côté serveur et les vérifications serveur de godmode, d'invisibilité, de noclip et de caméra sont assouplies, et les vérifications client du joueur sont exemptées via un state bag. Ce n'est jamais une immunité contre les sanctions.
Une entrée est une chaîne ou une table : { event = 'name', durationMs = 60000, resource = 'owner-resource', always = true }. durationMs est borné entre 1000 et 120000 ms.
Le hook n'est installé que tant que la ressource propriétaire est démarrée ou en cours de démarrage. Le propriétaire est le préfixe de l'événement avant le premier deux-points ; qb-clothes correspond à qb-clothing et hospital à qb-ambulancejob. Définissez resource quand le préfixe diffère du nom de la ressource. always = true installe le hook même quand le propriétaire ne tourne pas.
Les hooks sont installés 2,5 s après le démarrage de l'anticheat, puis 1 s après le démarrage de toute autre ressource.
Les joueurs peuvent déclencher ces événements eux-mêmes ; c'est pourquoi chaque joueur a un budget (budgetMsPer10Min). Quand un joueur l'a épuisé, l'anticheat écrit un avertissement dans la console et une ligne de log en base, et ignore les demandes suivantes de ce joueur pour le reste des 10 minutes.
Lancez tosunac_integration dans la console du serveur pour voir combien d'événements d'exemption serveur sont accrochés. Elle ne fonctionne que depuis la console du serveur, et la sortie y est affichée.
DetectionExempt.clientEvents et localEvents#
Les noms de clientEvents sont enregistrés avec RegisterNetEvent sur le client. Quand l'un se déclenche, depuis le serveur ou via un TriggerEvent local, le client est exempté pendant defaultDurationMs.
clientEvents n'accepte que des chaînes. Les entrées de type table, y compris celles avec durationMs, sont ignorées en silence sur le client.
Pendant l'exemption, les détections client ne sont pas sanctionnées. Elles sont signalées comme lignes LOG marquées '[exempt]', au plus une fois par minute et par vérification, afin que les abus restent visibles dans le panel. Les vérifications serveur ne sont pas affectées.
localEvents fonctionne de la même façon mais n'utilise que AddEventHandler : il ne réagit donc qu'à TriggerEvent sur le client.
Le client analyse les deux listes 3,5 s après son chargement, puis à chaque synchro des réglages.
Un événement d'un script que vous n'utilisez pas ne se déclenche jamais : des noms en trop dans la liste ne font donc aucun mal. La liste clientEvents fournie répète 11 noms ; ces doublons sont sans effet.
Exemple DetectionExempt#
Ajoutez vos noms aux listes existantes dans configs/anticheat_config.lua, puis redémarrez l'anticheat. Remplacer toute la table supprime les entrées fournies. L'exemple ne montre que les nouvelles entrées.
Préférez les exports quand vous maîtrisez le code : l'export client SetExempt(true, ms, reason), l'export serveur SetExempt(src, ms, reason) depuis une ressource de confiance, et l'export serveur MarkTeleport(src, ms) juste avant de téléporter un joueur.
ts.DetectionExempt = {
enabled = true,
defaultDurationMs = 60000,
budgetMsPer10Min = 240000,
-- événements réseau client : chaînes uniquement
clientEvents = {
-- ...entrées fournies...
'my-clothing:client:openMenu',
},
-- événements locaux client (TriggerEvent sur le client)
localEvents = {
-- ...entrées fournies...
'my-menu:opened',
},
-- événements client vers serveur : exemption souple pour l'émetteur
serverEvents = {
-- ...entrées fournies...
'my-clothing:server:saveOutfit',
{ event = 'my-housing:server:enter', durationMs = 60000 },
{ event = 'myhouse:server:enter', resource = 'my-housing' },
{ event = 'my-garage:server:takeOut', durationMs = 45000, always = true },
},
}ts.EventLimiter : limites de débit des événements réseau#
ts.EventLimiter associe des noms d'événements à un nombre maximal d'appels par joueur sur 5 s. L'anticheat enregistre chaque nom avec RegisterServerEvent au chargement et compte les appels par joueur ; tous les compteurs sont remis à zéro toutes les 5 s. Votre propre gestionnaire de l'événement s'exécute quand même.
Quand un compteur atteint sa limite, l'anticheat tente une détection avec la config ts.EventLimiter (server_event_spam, KICK par défaut).
La liste fournie compte 25 entrées : test:event, esx:getSharedObject, des événements bancaires et d'argent QBCore, revive, spawn de véhicule, inventaire, prison et menottes, et HCheat:TempDisableDetection.
Les noms et les limites sont lus au chargement. La table ac_event_limits de la base remplace ts.EventLimiter à la synchro mais ne modifie pas les gestionnaires enregistrés : redémarrez l'anticheat après avoir modifié l'un ou l'autre.
ts.EventLimiter = {
-- ...entrées fournies...
['my-shop:buy'] = 10,
}Événements pièges : n'utilisez jamais ces noms#
Certains événements existent uniquement pour attraper les triches. Suivez ces règles pour que vos propres scripts n'en déclenchent jamais aucun.
- L'anticheat enregistre des événements pièges que seules les triches déclenchent. Ne les déclenchez jamais et ne réutilisez jamais leurs noms.
- Pour voir les noms des pièges sur votre propre serveur, lancez /ts evtlist (la liste s'affiche dans la console du serveur). Pour qu'un événement ne soit plus traité comme un piège, utilisez /ts evtwhitelist <eventName>. Voir la rubrique Commandes.
- Préfixez vos propres événements avec le nom de votre ressource, par exemple myresource:doThing, pour qu'ils n'entrent jamais en conflit avec un piège.
Garde-fou de débit des événements sonores#
L'anticheat enregistre ces événements sonores comme événements réseau dans sa propre ressource et les compte par joueur. Au-delà de ts.premiumGuard.soundPerSec (6 par seconde par défaut), il applique soundAction ('log' par défaut). Il n'annule l'événement que si soundCancel = true (false par défaut). Les gestionnaires de votre ressource sonore s'exécutent quand même si cette ressource a enregistré l'événement elle-même.
Le garde-fou ne fonctionne que tant que ts.premiumGuard.enabled et ts.premiumGuard.soundGuard valent true (les deux à true dans la config fournie). Gardez soundCancel à false tant que vous n'avez pas vérifié que vos scripts sonores restent sous la limite.
| Événement | Ressource |
|---|---|
InteractSound_SV:PlayOnAll | InteractSound |
InteractSound_SV:PlayWithinDistance | InteractSound |
InteractSound_SV:PlayOnOne | InteractSound |
xsound:stateSound | xsound |
xsound:server:play | xsound |
xsound:server:playUrl | xsound |
xsound:server:playUrlPos | xsound |
Événements que l'anticheat envoie à d'autres ressources#
Après des actions du staff, du panel ou des détections, l'anticheat déclenche des événements qui appartiennent à d'autres ressources. Si vous utilisez l'une de ces ressources, attendez-vous à ces appels.
La météo et l'heure n'utilisent que la première ressource de synchro démarrée, dans cet ordre : qb-weather, Renewed-Weathersync (export uniquement), cd_easytime, wd_weather, weathersync, vSync, qb-weathersync. Si aucune n'est démarrée, l'anticheat règle la météo et l'heure directement sur chaque client.
hospital:client:Revive et esx_ambulancejob:revive sont aussi écoutés par l'anticheat (voir Événements de mort et de réanimation des frameworks) : ces réanimations ouvrent donc aussi la fenêtre post-réanimation de 10 s.
| Événement | Côté | Envoyé quand |
|---|---|---|
qb-weather:server:RequestStateChange | Local serveur | Changement de météo ou d'heure quand qb-weather n'a pas d'export setWeather ou setTime. Pour l'heure, la valeur est une table { hour, minute }. |
cd_easytime:setWeather, cd_easytime:setTime | Local serveur | Changement de météo ou d'heure avec cd_easytime. |
<res>:setWeather, <res>:setTime | Local serveur | Changement de météo ou d'heure avec wd_weather, weathersync, vSync ou qb-weathersync quand cette ressource n'a pas d'export correspondant. |
wasabi_ambulance:revive | Local client | Réanimation par le staff ou le panel quand wasabi_ambulance est démarré. |
hospital:client:Revive | Local client | Réanimation par le staff ou le panel avec qb-ambulancejob ou Starter-Hospital. |
esx_ambulancejob:revive | Local client | Réanimation par le staff ou le panel avec esx_ambulancejob. |
<res>:revive, <res>:client:Revive, <res>:client:revive | Local client | Réanimation par le staff ou le panel dans les autres cas : la première ressource démarrée parmi ambulance, qb-ambulancejob, cd_ambulance et ars_ambulancejob reçoit les trois noms. Une résurrection native suit si le joueur est encore mort. |
QBCore:Notify | Local client ; serveur vers client | Chaque soin effectué par l'anticheat, même sans qb-core (sa vérification de qb-core vaut toujours true). Aussi les actions d'argent, d'inventaire et de notification du panel. |
esx:showNotification, QBCore:Player:SetPlayerData, inventory:client:ItemBox | Serveur vers client | Actions d'argent, d'inventaire et de notification du panel, et dons d'armes sous forme d'objet. |
chat:addMessage | Serveur vers client ; local client | Messages du chat et annonces du panel. Avec ts.chatMessages = true (false par défaut), chaque issue de détection, LOG compris, est envoyée à tous les joueurs en turc codé en dur (editable/server/sv_editme.lua). |
chat:addSuggestion | Local client | Ajoute la suggestion /unspectate au chargement du fichier du menu admin. |
qb-phone:client:CustomNotification, qs-smartphone:client:sendNotification, lb-phone:notification, yseries:notification, gks-phone:client:notification, roadphone:notify | Serveur vers client | Alertes du staff sur les expulsions et les bans, et exports phoneNotify et phoneNotifyAdmins. Seul le premier téléphone démarré les reçoit (npwd utilise un export ; le chat si aucun). Nécessite ts.AdminMenu.enable = true. |
Sécurité : qui peut déclencher quel événement#
Seul l'anticheat peut déclencher les événements qu'il émet. Les autres ressources ne doivent pas les déclencher. Le tableau indique qui peut déclencher chaque événement.
| Événement | Qui peut le déclencher |
|---|---|
ts_anticheat:playerBanned | Seulement l'anticheat. |
ts_anticheat:banCommitted | Seulement l'anticheat. |
ts_anticheat:newLog | Seulement l'anticheat. |
ts_anticheat:shield:ban_video_saved | Seulement l'anticheat. |
tosun-ac:settingsReloaded | Seulement l'anticheat. |
ts_anticheat:configUpdated | Seulement l'anticheat. |
ts_anticheat:adminStateChanged | N'importe quelle ressource serveur. |
tosun-ac:panel:characterReady | N'importe quel client. Limité à un appel toutes les 20 s par joueur. |
ts_anticheat:locale:setSelf | N'importe quel client. N'affecte que l'appelant. |
ts_anticheat:requestEventKey | N'importe quel client. La clé est envoyée uniquement au joueur qui la demande. |
tosun-ac:bridge:clientExempt | Vos scripts serveur. |
Sécurité : vérifiez GetInvokingResource#
N'importe quelle ressource serveur peut déclencher un événement local serveur. Dans vos écouteurs, vérifiez que c'est bien l'anticheat qui l'a déclenché avant d'agir. L'écouteur de l'écran de ban de l'anticheat lui-même utilise une vérification similaire.
Appliquez cette vérification à playerBanned, banCommitted, playerUnbanned, newLog, settingsReloaded, aux événements Shield, à renameCharacter et à vos gestionnaires '<name>:safe'.
Ne déclenchez jamais les événements de l'anticheat depuis vos propres ressources, même pour tester. Plusieurs écouteurs intégrés ne vérifient pas l'appelant.
AddEventHandler('ts_anticheat:banCommitted', function(playerId, row)
if GetInvokingResource() ~= 'tosun-ac' then return end -- nom de votre dossier s'il a été renommé
-- vous pouvez agir sur row ici en toute sécurité
end)Sécurité : conception où le serveur fait autorité#
Tout ce qu'un client peut déclencher est traité comme un indice, pas comme une autorité. Appliquez la même règle dans vos propres gestionnaires.
- Les exemptions client (clientEvents, localEvents, autoExempt, l'événement bridge et l'export client SetExempt) ne changent jamais l'application côté serveur. Tant qu'elles sont actives, les détections client sont journalisées comme '[exempt]' au lieu d'être sanctionnées.
- Les exemptions serverEvents sont souples : sur le serveur, elles n'assouplissent que les vérifications de mouvement et de visibilité (les sanctions pour armes, argent, dégâts, abus d'événements, injection et crash s'appliquent toujours), et chaque joueur est plafonné par budgetMsPer10Min.
- Le statut staff est décidé par la réponse du serveur protégée par nonce. Pousser un indicateur staff vers un client ne peut pas accorder les droits admin.
- La clé SafeEvents ne valide que le transport. Votre gestionnaire ':safe' doit vérifier les permissions, les soldes et la propriété sur le serveur.
- Une triche peut déclencher en local les événements de mort des frameworks. L'indicateur à terre s'efface après environ 20 s de jeu clairement actif.
- Pour exempter un joueur côté serveur, utilisez les exports : SetExempt(src, ms, reason) depuis une ressource de confiance, ou MarkTeleport(src, ms) avant une téléportation.
Événements internes#
En plus des événements de cette page, l'anticheat enregistre des événements internes pour sa poignée de main de connexion, l'échange de clés, Shield, le menu admin, les actions du panel web et une ressource compagnon. Ils sont protégés par des nonces, des clés, des signatures, des limites de débit ou des vérifications du staff, et plusieurs sont des pièges qui sanctionnent l'appelant. Ils ne sont volontairement pas documentés ici.
- Ne déclenchez pas les événements internes, ni depuis le serveur ni depuis un client.
- N'y enregistrez pas de gestionnaires et ne réutilisez pas leurs noms.
- Ne déclenchez aucun événement dont le nom commence par ts_anticheat: ou tosun-ac:, sauf si cette page l'indique comme déclenchable.
- Utilisez les exports pour les actions : par exemple punish, MarkTeleport, freezePlayer, setPlayerHealth, RequestShieldScreenshot et quarantine.
Problèmes connus dans la 9.6.15#
Ces comportements sont présents dans le code livré. Tenez-en compte.
- tosun-ac:settingsReloaded et ts_anticheat:configUpdated se déclenchent à presque chaque cycle de synchro, pas seulement quand les réglages changent.
- ts_anticheat:playerBanned se déclenche pour KICK et LOG en plus de BAN.
- L'alerte téléphone du staff pour les logs en direct attend le statut 'TESPİT' ou 'Detection' dans ts_anticheat:newLog, mais rien n'émet ces valeurs : cette alerte ne se déclenche donc jamais.
- Chaque soin effectué par l'anticheat déclenche QBCore:Notify sur le client, même sur les serveurs sans qb-core.
- ts.quarantine.autoQuarantine n'a aucun effet : sa seule entrée est un événement interne que rien n'émet. Utilisez plutôt l'export quarantine.
- Plusieurs commentaires de configs/anticheat_config.lua sont en turc et certains sont obsolètes : les appels sans clé de ts.ProtectedEvents sont abandonnés, pas bannis, et ts.EventLimiter n'expulse pas.
Éviter les analyses inutiles après redémarrage#
Chaque démarrage vérifie les empreintes SHA256 du contenu. Les ressources dont les fichiers et règles sont inchangés réutilisent les résultats authentifiés du KVP serveur au lieu de répéter les analyses coûteuses de signatures et événements. Les changements de fichiers, exceptions ou règles déclenchent une nouvelle analyse. Les empreintes nécessitent encore des lectures ; aucune valeur resmon fixe n’est garantie.
- Conservez le cache KVP pendant une mise à jour normale ; les entrées corrompues ou invérifiables sont réanalysées.
- Le catalogue initial collecte les noms d’événements littéraux Lua/JS. Les noms dynamiques, fichiers illisibles ou escrow et DLL ne sont pas certifiés entièrement analysés.
- L’installation automatique manuelle attend l’analyse de sécurité. Les ressources incomplètes, suspectes, modifiées ou expirées ne sont pas approuvées automatiquement.
- Testez la connexion des joueurs, le jeu normal et les actions administrateur sur un serveur de test ; évaluez resmon avec le nombre de joueurs et la charge du framework.