Ir para o guia

Documentação 9.6.24

Eventos e triggers

Os eventos que o Tosun AntiCheat emite para os seus scripts escutarem, os eventos que você pode disparar para integrar e as listas da config que mapeiam os seus próprios eventos para isenções, verificações de chave, limites de taxa e armadilhas. Os eventos internos do anticheat são protegidos e não devem ser chamados.

Visão geral#

O FiveM tem eventos locais e eventos de rede. Um evento local fica de um lado só do jogo: TriggerEvent dispara e AddEventHandler recebe. Um evento de rede atravessa a rede: o servidor envia com TriggerClientEvent, um cliente envia com TriggerServerEvent, e o lado que recebe precisa registrá-lo com RegisterNetEvent. O Tosun AntiCheat usa os dois tipos, no servidor e no cliente.

Esta página lista os eventos que o anticheat emite para você escutar, os eventos que você pode disparar para integrar os seus scripts e as listas da config (ts.DetectionExempt, ts.ProtectedEvents, ts.EventLimiter, ts.triggerList) que transformam os nomes dos seus próprios eventos em isenções, verificações de chave, limites de taxa ou armadilhas.

O anticheat também registra muitos eventos internos para o seu próprio handshake de entrada, troca de chaves, Shield, menu de admin e ações do painel web. Eles são protegidos por nonces, chaves, assinaturas, limites de taxa ou verificações de staff, e alguns deles são armadilhas. Eles não fazem parte da superfície de integração: não os dispare, não registre handlers neles e não reutilize os nomes deles. Use os exports.

TipoDisparar comReceber comQuem pode disparar
Evento local do servidorTriggerEventAddEventHandlerQualquer resource do servidor.
Evento local do clienteTriggerEventAddEventHandlerQualquer script de cliente no jogo daquele jogador, inclusive código de cheat injetado.
Evento de rede, do cliente para o servidorTriggerServerEventRegisterNetEvent + AddEventHandlerQualquer jogador conectado, com quaisquer argumentos.
Evento de rede, do servidor para o clienteTriggerClientEventRegisterNetEvent + AddEventHandlerQualquer resource do servidor. Um script de cliente também pode disparar o mesmo nome localmente com TriggerEvent.

Tarefas comuns de integração#

Escolha o evento ou a configuração que corresponde ao que você quer fazer. Cada um é descrito em detalhes mais abaixo nesta página.

ObjetivoUse
Reagir quando uma detecção termina em ban, kick ou linha de logts_anticheat:playerBanned
Reagir só quando um ban foi realmente aplicadots_anticheat:banCommitted
Reagir a um unbants_anticheat:playerUnbanned
Espelhar o feed de logs ao vivots_anticheat:newLog
Atualizar o seu cache quando o anticheat reaplica as configuraçõestosun-ac:settingsReloaded / ts_anticheat:configUpdated
Aplicar uma renomeação de personagem feita no painel em um framework própriotosun-ac:renameCharacter
Ligar as verificações do cliente depois de um spawn próprioplayerSpawned
Atualizar o painel depois de uma seleção de personagem própriatosun-ac:panel:characterReady
Fazer o cliente verificar de novo o status de staffts_anticheat:adminStateChanged
Isentar um jogador no cliente a partir do seu script de servidortosun-ac:bridge:clientExempt
Isentar jogadores automaticamente quando o evento do seu menu ou ação disparats.DetectionExempt
Exigir uma chave por jogador no seu próprio evento do cliente para o servidorts.ProtectedEvents + <name>:safe

Eventos de servidor que o anticheat emite#

Estes são eventos locais do servidor. Escute-os com AddEventHandler em qualquer script de servidor. Não os dispare você mesmo; veja as seções de segurança abaixo.

EventoQuando disparaPayload
ts_anticheat:playerBannedTodo resultado de detecção: BAN, KICK e LOG. Dispara antes do kick, da inserção do ban e dos logs. Não dispara para jogadores ignorados por serem staff ou estarem na whitelist.tabela data: playerId (string), playerName, reason, banID, side ('server'), punishment ('BAN', 'KICK' ou 'LOG')
ts_anticheat:banCommittedSó resultados BAN (detecções e os exports de ban e punish), logo depois que a inserção da linha do ban entra na fila e antes do webhook do painel e do drop.playerId (number), tabela row: banID, playerName, reason, license, steam, discord, ip, HWID, HWID2 a HWID5, config (chave de config da detecção)
ts_anticheat:playerUnbannedDepois que o export unban, o comando /ts unban ou o menu no jogo removeu um ban. Unbans feitos pelo painel web não disparam este evento.banID (o valor passado para o export unban, sem alteração)
ts_anticheat:newLogToda linha de log ao vivo: detecções, linhas de log do cliente, ações do menu de admin e algumas linhas internas do anticheat.tabela payload: src (rótulo de categoria, não um id de jogador), event (texto da mensagem), status (padrão 'Info'), playerName, time (os.time())
tosun-ac:settingsReloadedDepois que uma sincronização reaplicou as configurações. Isso acontece na inicialização e em quase todo ciclo de sincronização, não só quando algo mudou.nenhum
ts_anticheat:shield:screenshot_savedUma captura de tela do Shield solicitada chegou e passou nas verificações.src (number), b64 (imagem em base64, de 64 a 2.500.000 caracteres)
ts_anticheat:shield:ban_video_savedO vídeo de gameplay de um jogador expulso ou banido foi enviado dentro de 120 s.src (number), videoUrl, reason, config (reason e config vêm do cliente)
<name>:safeUma chamada de um evento listado em ts.ProtectedEvents passou nas verificações de chave e de payload.src (number, id de jogador online), depois os argumentos originais que vêm após a chave
tosun-ac:renameCharacterUma renomeação de personagem pelo painel web em um framework diferente de qb, qbcore, qbox, qbx ou esx.citizenid (string), firstName (primeira palavra), lastName (o resto do nome, pode ser '')

Eventos de cliente que o anticheat emite#

Estes eventos chegam aos scripts de cliente. ts_anticheat:configUpdated é um evento local do cliente; escute com AddEventHandler. ts_anticheat:receiveEventKey é um evento de rede vindo do servidor; registre com RegisterNetEvent no seu script de cliente.

EventoTipoQuando disparaPayload
ts_anticheat:configUpdatedEvento local do clienteDepois que o cliente aplicou as configurações vindas do servidor e varreu de novo os seus hooks de DetectionExempt. Espere que dispare mais ou menos uma vez por intervalo de sincronização.nenhum
ts_anticheat:receiveEventKeyEvento de rede, do servidor para o clienteEm resposta a ts_anticheat:requestEventKey (a sua requisição ou a do próprio anticheat), e sempre que o servidor emite uma nova chave para o jogador. Chega depois do handshake de entrada.key (string, a chave SafeEvents do jogador)

Exemplo de listener#

Um script de servidor que reage a kicks e bans, e um script de cliente que descarta um cache quando o anticheat reaplica as configurações. Cada listener do servidor primeiro verifica se foi o anticheat que disparou o evento.

-- server.lua (seu resource)
local AC = 'tosun-ac' -- nome do resource (pasta) do anticheat

AddEventHandler('ts_anticheat:playerBanned', function(d)
  if GetInvokingResource() ~= AC then return end -- ignora chamadas forjadas
  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 (seu resource)
local myCache
AddEventHandler('ts_anticheat:configUpdated', function()
  myCache = nil
end)

playerBanned em detalhes#

O nome é histórico. ts_anticheat:playerBanned dispara para todo resultado de detecção, não só para bans. Filtre por data.punishment.

data.playerId é uma string. Converta com tonumber antes de usar como id de servidor.

O evento dispara antes da ação. O anúncio no chat, os logs, o DropPlayer e a inserção do ban rodam depois do seu handler.

Não dispara quando o alvo está offline, quando o jogador é ignorado por ser staff (a staff é ignorada a menos que ts.AdminBypassDetections seja false ou ts.Debug esteja ligado), ou quando o jogador está na whitelist temporária ou na whitelist do painel.

Um banID é gerado para todo resultado, mas só é salvo para BAN. Um KICK de um membro da staff é cancelado depois que o evento já disparou.

Enquanto a tela de ban (editable/server/sv_banscreen.lua) está sendo mostrada para um jogador, a maioria das detecções seguintes desse jogador é descartada e nenhum evento dispara para elas.

banCommitted em detalhes#

ts_anticheat:banCommitted é o melhor hook para 'um ban foi realmente aplicado'. Use playerBanned para kicks e linhas de log.

Dispara para todo resultado BAN do pipeline de punição do anticheat: detecções e os exports de ban e punish. Bans offline (os exports offlineBan e offlineBanByLicense) e bans feitos no painel web não disparam este evento.

A inserção da linha do ban entra na fila com MySQL.Async.execute e não é aguardada, então a linha pode ainda não estar no banco de dados quando o seu handler roda.

Os identificadores na linha vêm sem o prefixo (license:, license2:, steam:, discord:, ip:). Um identificador ou token ausente vem como o texto em inglês 'Not found'; um ip ausente vem como 'Hidden'. HWID a HWID5 são tokens brutos do jogador.

newLog e settingsReloaded em detalhes#

Os dois eventos disparam com frequência. Mantenha os handlers leves.

ts_anticheat:newLog: o campo src é um rótulo de categoria, não um id de jogador. As detecções usam 'DETECTION' com status BAN, KICK ou LOG. As linhas de log do cliente usam uma tag e um status fixos: 'AntiCheat' com 'Tespit' (turco fixo no código para 'Detection'), 'AC-Grace' com 'Info' e 'AC-FakeTrigger' com 'Warning'. As entradas do menu de admin normalmente usam o nome do admin com status BAN, KICK, INFO ou SUCCESS. Algumas linhas internas usam outras categorias.

tosun-ac:settingsReloaded: o anticheat não compara os valores antigos com os novos, então o evento dispara depois da sincronização inicial e na maioria dos ciclos de sincronização, a cada ts.ServerPerf.configSyncIntervalSec segundos (120 na config padrão, 60 se a chave estiver ausente, mínimo 20). Entenda como 'as configurações foram reaplicadas', não 'as configurações mudaram'.

Depois de cada sincronização dessas, o servidor também envia as configurações para todos os clientes, e cada cliente então dispara ts_anticheat:configUpdated.

Se outro resource do servidor disparar tosun-ac:settingsReloaded, o anticheat faz uma atualização completa das permissões de staff de todos os jogadores online.

Eventos de captura de tela e vídeo do Shield#

Os dois eventos exigem o Shield (ts.shield.enabled diferente de false; ele vem ligado na config padrão).

ts_anticheat:shield:screenshot_saved: solicite uma captura de tela com o export de servidor RequestShieldScreenshot(src). A imagem precisa chegar em até 60 s depois da solicitação, e o anticheat aceita no máximo uma a cada 20 s por jogador. O painel web também pode solicitar capturas de tela, e elas também disparam o evento.

ts_anticheat:shield:ban_video_saved: exige ts.shield.banGameplayVideo diferente de false (ligado na config padrão). A janela de upload fica aberta por 120 s depois de um KICK ou BAN, e o host da URL do vídeo precisa ser o mesmo do painel.

reason e config em ban_video_saved vêm do cliente e não têm o tipo verificado nem são truncados. Trate-os como texto não confiável.

local AC = 'tosun-ac'

-- servidor: pede uma captura de tela ao jogo do jogador
exports[AC]:RequestShieldScreenshot(src)

AddEventHandler('ts_anticheat:shield:screenshot_saved', function(src, b64)
  if GetInvokingResource() ~= AC then return end
  -- b64 é uma string de imagem em base64
end)

Renomear personagem em um framework próprio#

O painel web pode renomear um personagem. Para qb, qbcore, qbox e qbx, o anticheat atualiza players.charinfo; para esx, atualiza users.firstname e users.lastname. Para qualquer outro valor de ts.Framework.framework, ele dispara tosun-ac:renameCharacter para que você mesmo aplique a renomeação.

O anticheat informa 'event_emitted_unknown_fw' ao painel, exista ou não um listener.

O novo nome tem os caracteres de controle removidos, é limitado a 64 caracteres e precisa ter pelo menos 3 caracteres. firstName é a primeira palavra; lastName é o resto e pode ser vazio.

O exemplo usa a chamada MySQL.update do oxmysql e uma tabela characters inventada. Adapte a query ao seu schema.

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)

Eventos que você pode disparar#

Estes são os eventos do anticheat feitos para serem disparados pelos seus scripts. Os eventos de morte e revive dos frameworks e os seus eventos de ts.DetectionExempt têm seções próprias.

EventoDisparar deEfeitoLimites
tosun-ac:panel:characterReadyCliente, com TriggerServerEvent.Atualiza os dados do personagem do jogador na lista de jogadores online do painel.Uma chamada a cada 20 s por jogador.
ts_anticheat:locale:setSelfCliente, com TriggerServerEvent e um código de idioma.Define o idioma das mensagens de servidor do anticheat só para aquele jogador. Imprime uma linha no console do servidor.Sem limite de taxa. O código precisa ter 2 ou 3 letras minúsculas e um arquivo locales/<lang>.json existente.
ts_anticheat:requestEventKeyCliente, com TriggerServerEvent.Envia de volta a chave SafeEvents do jogador por ts_anticheat:receiveEventKey.Uma chamada a cada 3 s por jogador. A chave vai só para o jogador que pediu.
playerSpawnedCliente, com TriggerEvent (local).Liga a maioria das verificações do cliente e inicia a janela de tolerância do spawn.Todos os outros resources naquele cliente também recebem.
ts_anticheat:adminStateChangedServidor, com TriggerClientEvent para um jogador.O cliente pergunta de novo ao servidor qual é o seu status real de staff.O servidor responde no máximo uma vez por segundo por jogador.
tosun-ac:bridge:clientExemptServidor, com TriggerClientEvent; exige o arquivo de bridge no seu resource.Isenção do lado do cliente para aquele jogador.500 a 300000 ms.

Spawn próprio: playerSpawned#

A maioria das verificações do cliente fica desligada até o anticheat ver o evento local playerSpawned: regen, godmode, teleporte, invisibilidade, freecam, armas, OCR, natives, coordenadas e godmode de veículo. O spawnmanager normalmente dispara esse evento.

Se o seu sistema de spawn ou de multicharacter não usa o spawnmanager, dispare playerSpawned localmente assim que o jogador estiver no mundo, ou chame o export de cliente SetSpawned(true).

playerSpawned também inicia a janela de tolerância do spawn de ts.spawnGraceSecondsMs. O valor padrão é 500, que o código eleva para o mínimo de 5000 ms; sem a chave, o código usa 25000. Uma detecção dentro da janela não é cancelada. Ela só adiciona uma linha de log 'AC-Grace'.

playerSpawned também limpa o estado de morte do anticheat e marca um teleporte do cliente.

A lista padrão ts.DetectionExempt.clientEvents contém playerSpawned, então cada playerSpawned também inicia uma isenção de cliente de defaultDurationMs (60 s).

SetSpawned(false) tem efeito no máximo uma vez a cada 5 minutos, e as verificações voltam a ligar depois de cerca de 30 s de movimento visível.

-- cliente, depois da sua própria lógica de spawn
TriggerEvent('playerSpawned')

-- ou, sem os efeitos colaterais nos outros resources
exports['tosun-ac']:SetSpawned(true)

Multicharacter próprio: characterReady#

No servidor, o anticheat escuta QBCore:Server:OnPlayerLoaded, QBCore:Server:PlayerLoaded, esx:playerLoaded e qbx_core:server:playerLoaded. Cada um envia na hora o personagem carregado para a lista de jogadores online do painel. Um script de multicharacter próprio que não dispara nenhum deles deve disparar tosun-ac:panel:characterReady a partir do cliente depois da seleção de personagem.

Cada chamada grava a linha do jogador no banco de dados e, quando a URL e a chave do painel estão definidas, envia uma requisição HTTPS ao painel. Por isso o servidor aceita uma chamada a cada 20 s por jogador e ignora o resto.

-- cliente, depois que a seleção de personagem terminou
TriggerServerEvent('tosun-ac:panel:characterReady')

Idioma do jogador: locale:setSelf#

Um cliente pode escolher, para si mesmo, o idioma das mensagens de servidor do anticheat. Se der certo, o servidor imprime uma linha no console do servidor.

Muda só as mensagens de servidor enviadas para aquele jogador. Não muda o idioma do lado do cliente.

O próprio cliente do anticheat não envia este evento. As alternativas do lado do servidor são o export setPlayerLocale e o comando ts_setlang.

-- cliente
TriggerServerEvent('ts_anticheat:locale:setSelf', 'en')

Mudanças de staff: adminStateChanged#

Depois que o seu script de admin concede ou remove direitos de staff, peça ao cliente do jogador para verificar de novo o seu status de staff. O evento não muda o status sozinho: o cliente envia um nonce novo ao servidor, e só a resposta do servidor decide.

Nada no anticheat envia este evento. O cliente também verifica de novo por conta própria a cada 20 s, então a chamada só elimina essa espera.

Um cheat que dispara o evento localmente não ganha nada; o servidor responde à verificação no máximo uma vez por segundo por jogador.

-- servidor, depois de mudar os direitos de staff de src
TriggerClientEvent('ts_anticheat:adminStateChanged', src)

Evento de bridge: tosun-ac:bridge:clientExempt#

O arquivo de bridge do cliente registra este evento de rede dentro do seu próprio resource. O seu script de servidor envia o evento para um jogador, e a bridge chama o export de cliente SetExempt do anticheat para aquele jogador.

durationMs é um número em ms ou o nome de um preset em maiúsculas ou minúsculas: SHORT, MEDIUM, LONG ou XL (30, 60, 120 ou 180 s). Os valores ficam limitados entre 500 e 300000 ms. Sem valor, vale o padrão da convar (60000).

A chave da isenção é 'bridge:' seguido de tag, ou do nome do seu resource quando tag é nil.

Esta é só uma isenção do lado do cliente. As verificações do servidor continuam rodando.

O anticheat nunca envia este evento. Se vários dos seus resources incluírem o arquivo de bridge, um único TriggerClientEvent roda uma vez em cada um deles.

  1. Adicione client_script '@tosun-ac/bridge/tosun_ac_client.lua' ao fxmanifest.lua do seu resource.
  2. Opcional, no server.cfg: se você renomeou a pasta do anticheat, adicione setr tosun_ac_resource com o novo nome (padrão tosun-ac). Para mudar a duração padrão, adicione setr tosun_ac_bridge_client_ms com um valor em ms (padrão 60000).
  3. No seu script de servidor, envie TriggerClientEvent('tosun-ac:bridge:clientExempt', src, durationMs, tag).
-- servidor: isenta o jogador por 2 minutos enquanto o menu da barbearia está aberto
TriggerClientEvent('tosun-ac:bridge:clientExempt', src, 'LONG', 'barber')

Eventos de morte e revive dos frameworks#

O cliente escuta estes eventos para saber quando um jogador está morto ou caído. Enquanto esse estado está ativo, as verificações de pico de regen, godmode, teleporte, invisibilidade, spectate e várias outras do cliente são puladas. Mortes nativas também são captadas pelo evento de dano gameEventTriggered.

Desde a 9.5.9, o anticheat encerra esse estado sozinho assim que o jogador volta a estar claramente ativo.

Um script de ambulância próprio pode disparar um destes nomes localmente para alinhar o estado do anticheat. São nomes compartilhados dos frameworks, então todos os outros resources naquele cliente que os escutam também rodam (por exemplo, telas de morte do ESX).

EventoTipoEfeito
hospital:client:isDeadDe redeCaído; inicia a janela pós-morte de 25 s.
hospital:client:SetDeathStateDe rede, argumento booleantrue: caído e janela pós-morte de 25 s. false: revivido e janela pós-revive de 10 s.
hospital:client:SetLaststandDe rede, argumento booleantrue: caído e janela pós-morte de 25 s (false é ignorado). Também uma isenção automática de 45 s.
qb-medical:client:OnDeathDe redeCaído; inicia a janela pós-morte de 25 s.
esx:onPlayerDeathDe redeCaído; inicia a janela pós-morte de 25 s. Também uma isenção automática de 45 s.
baseevents:onPlayerDiedLocal (de rede enquanto o autoExempt está ligado)Inicia a janela pós-morte de 25 s. Também uma isenção automática de 45 s.
baseevents:onPlayerKilledSó localInicia a janela pós-morte de 25 s.
hospital:client:ReviveDe redeRevivido; inicia a janela pós-revive de 10 s.
qb-ambulancejob:client:RevivePlayerDe redeRevivido; inicia a janela pós-revive de 10 s.
qb-medical:client:OnReviveDe redeRevivido; inicia a janela pós-revive de 10 s.
esx_ambulancejob:reviveDe redeRevivido; inicia a janela pós-revive de 10 s.
esx_basicneeds:onReviveDe redeRevivido; inicia a janela pós-revive de 10 s.
playerSpawnedLocalLimpa o estado de caído e deixa cerca de 17 s da janela pós-morte.
-- ambulância própria (cliente), depois da sua própria lógica de revive
TriggerEvent('esx_ambulancejob:revive')

Eventos de isenção automática (autoExempt)#

Com ts.autoExempt.enabled (true na config padrão), o cliente escuta eventos comuns dos frameworks e concede uma isenção curta de cliente pelo export SetExempt. As detecções do cliente durante a isenção não são punidas; elas são reportadas como linhas LOG marcadas com '[exempt]'. Defina ts.autoExempt.enabled = false para desligar isso.

O comentário de cabeçalho em client/anticheat_auto_exempt.lua diz que estes eventos não podem ser disparados remotamente. Isso está errado: eles são registrados com RegisterNetEvent, então o servidor também pode dispará-los. O efeito continua sendo só uma isenção de cliente.

SituaçãoDuraçãoEventos
Menus de aparência180 sillenium-appearance:client:openMenu, appearance:client:openMenu, fivem-appearance:client:openMenu, ox_appearance:openMenu
Menus de roupas180 sqb-clothing:client:openMenu, qb-clothes:client:openMenu, rcore_clothing:openClothingShop, clothing:client:openMenu
Menus de skin e de barbearia do ESX180 sesx_skin:openSaveableMenu, esx_skin:openRestrictedMenu, skinchanger:model:loaded, tgg_barber:open, barbershop:client:open
Seleção de personagem30 sqb-multicharacter:client:chooseChar, qb-multicharacter:client:closeNUIdefault, esx_multicharacter:SetupCharacters, esx:restoreLoadout, ox:playerLoaded, qbx_core:client:playerLoggedOut
Morte e laststand (QB, ESX)45 sesx_ambulancejob:setDeathStatus, esx:onPlayerDeath, hospital:client:SetLaststand, hospital:client:OnPlayerLaststand, qb-ambulancejob:client:playerDead
Morte (outros scripts)45 swasabi_ambulance:clientDeath, ars_ambulancejob:client:dead, baseevents:onPlayerDied, baseevents:onPlayerWasted
Personagem carregado15 sQBCore:Client:OnPlayerLoaded, qbx_core:client:playerLoaded, esx:playerLoaded, ox:playerLoaded
Início do anticheat no cliente60 sonClientResourceStart

Portão de entrada: eventos de framework carregado#

Com ts.NetworkJoin.waitForFrameworkLoaded (true na config padrão) e o framework qb, qbox ou esx, o cliente espera um evento de framework carregado antes de terminar a entrada no anticheat. Isso atrasa o início de toda a proteção do cliente para aquele jogador. Outros valores de framework pulam a espera.

Depois de minReadyDelayMs (15 s na config padrão) e do fim da tela de carregamento, o cliente espera um dos eventos de carregado, ou LocalPlayer.state.isLoggedIn (qb, qbox) ou ESX.PlayerLoaded (esx), por até 180 s no total. Depois espera postFrameworkSettleMs (5 s na config padrão) e a colisão antes de informar que está pronto.

FrameworkEventos de carregadoEventos de reset
qbQBCore:Client:OnPlayerLoadedQBCore:Client:OnPlayerUnload
qboxQBCore:Client:OnPlayerLoaded, qbx_core:client:playerLoggedIn, qbx_multicharacter:client:chooseCharQBCore:Client:OnPlayerUnload
esxesx:playerLoadedesx:onPlayerLogout

Eventos protegidos (SafeEvents)#

ts.ProtectedEvents faz o anticheat verificar uma chave por jogador nos seus próprios eventos do cliente para o servidor. Uma chamada que passa é disparada de novo no servidor como o evento local '<name>:safe', com o id do jogador primeiro. A lista padrão contém só 'test:event'.

TriggerSafeServerEvent existe só dentro do próprio cliente do anticheat e não é exportado, então outros resources precisam usar o padrão com receiveEventKey mostrado acima. O cliente do anticheat pede a chave a cada 5 s até ela chegar (no máximo 12 tentativas).

O servidor responde no máximo a uma requisição de chave a cada 3 s por jogador, e as requisições do próprio anticheat também contam. Se nenhuma chave chegar, peça de novo depois de alguns segundos. Mantenha o seu handler registrado e sempre envie a chave mais recente que recebeu.

  1. Adicione o nome do seu evento a ts.ProtectedEvents em configs/anticheat_config.lua. A lista é lida uma vez no início, então reinicie o anticheat depois de mudanças.
  2. No seu script de cliente, registre ts_anticheat:receiveEventKey para guardar a chave e depois envie ts_anticheat:requestEventKey.
  3. Envie o seu evento com a chave como primeiro argumento: TriggerServerEvent(name, key, ...).
  4. No seu script de servidor, trate '<name>:safe' com AddEventHandler. O primeiro argumento é o id de servidor do jogador, seguido dos seus argumentos.
-- configs/anticheat_config.lua
ts.ProtectedEvents = { ['my-shop:buy'] = true }

-- cliente (seu resource)
local key
RegisterNetEvent('ts_anticheat:receiveEventKey', function(k) key = k end)
TriggerServerEvent('ts_anticheat:requestEventKey')
-- depois, quando a chave estiver definida:
TriggerServerEvent('my-shop:buy', key, 'bread')

-- servidor (seu resource)
AddEventHandler('my-shop:buy:safe', function(src, item)
  if GetInvokingResource() ~= 'tosun-ac' then return end
  -- verifique dinheiro, estoque e permissões aqui
end)

O que o SafeEvents verifica e o que não verifica#

O SafeEvents valida só o transporte. Ele nunca pune ninguém e não substitui as verificações do seu próprio handler.

  • Toda chamada de cliente primeiro conta para ts.SafeEventRateLimit a cada ts.SafeEventRateWindow segundos (30 chamadas a cada 10 s por padrão), mesmo sem chave.
  • Se o jogador ainda não tem chave, a chamada é descartada em silêncio.
  • Uma chave errada, o limite de taxa, mais de ts.SafeEventMaxArgs argumentos (64 por padrão) ou um payload inválido descartam a chamada e gravam uma linha de log no banco de dados por jogador, evento e janela.
  • Chamadas vindas do servidor (source 0) são ignoradas e não disparam nenhum evento ':safe'.
  • O evento de rede bruto continua chegando a qualquer handler que o seu próprio resource registrou com RegisterNetEvent. Só o espelho ':safe' passa pelo filtro.
  • Nunca use RegisterNetEvent no nome ':safe'; isso deixaria os clientes chamá-lo diretamente.
  • O SafeEvents é uma camada extra. O seu próprio handler ainda precisa verificar permissões, saldos e posse.
  • Não adicione eventos de framework (esx, qb) à lista. Quem os chama não envia chave, então o SafeEvents rejeita essas chamadas, grava linhas de log e nunca dispara ':safe'. Os handlers do próprio framework continuam rodando.
  • O comentário em turco em configs/anticheat_config.lua diz que chamadas sem chave causam um ban falso. Nesta versão elas só são descartadas.

ts.DetectionExempt: mapeie os seus eventos para isenções#

ts.DetectionExempt transforma os nomes dos seus próprios eventos em isenções temporárias de detecção. Use para menus e ações de servidor que mudam de forma legítima a aparência, a posição ou o estado de um jogador: roupas, barbearias, casas, garagens, seleção de personagem e revives. As listas são lidas de configs/anticheat_config.lua; as configurações do painel não as alteram. ts.MenuDetectionExempt é um alias mantido para configs antigas.

TeclaTipoValor padrãoEfeito
enabledbooleantruefalse desliga todos os hooks de DetectionExempt.
defaultDurationMsnumber (ms)60000Duração da isenção para entradas sem durationMs próprio. Entradas de servidor ficam limitadas entre 1000 e 120000 ms; entradas de cliente, entre 1000 e 300000 ms.
budgetMsPer10Minnumber (ms)240000Tempo máximo de isenção leve que um jogador pode acumular com serverEvents em 10 minutos.
clientEventslista de strings153 entradas (142 nomes únicos)Eventos de rede do cliente. Quando um dispara, as verificações de cliente do jogador ficam isentas.
localEventslista de strings11 entradasEventos locais do cliente (TriggerEvent só no cliente). Mesmo efeito de clientEvents.
serverEventslista de strings ou tabelas34 entradasEventos de rede do cliente para o servidor. Quando um jogador dispara um, esse jogador recebe uma isenção leve.

DetectionExempt.serverEvents#

O anticheat registra cada nome da lista com RegisterNetEvent. Quando um jogador o dispara, esse jogador recebe uma isenção leve: as verificações de movimento do servidor e as verificações de godmode, invisibilidade, noclip e câmera do lado do servidor ficam mais tolerantes, e as verificações de cliente do jogador ficam isentas por meio de um state bag. Nunca é imunidade contra punição.

Uma entrada é uma string ou uma tabela: { event = 'name', durationMs = 60000, resource = 'owner-resource', always = true }. durationMs fica limitado entre 1000 e 120000 ms.

O hook só é instalado enquanto o resource dono está iniciado ou iniciando. O dono é o prefixo do evento antes dos primeiros dois-pontos; qb-clothes aponta para qb-clothing e hospital aponta para qb-ambulancejob. Defina resource quando o prefixo for diferente do nome do resource. always = true instala o hook mesmo quando o dono não está rodando.

Os hooks são instalados 2,5 s depois que o anticheat inicia e de novo 1 s depois que qualquer outro resource inicia.

Os jogadores podem disparar estes eventos por conta própria, e é por isso que cada jogador tem um orçamento (budgetMsPer10Min). Quando um jogador esgota o orçamento, o anticheat grava um aviso no console e uma linha de log no banco de dados, e ignora novos pedidos desse jogador pelo resto dos 10 minutos.

Rode tosunac_integration no console do servidor para ver quantos eventos de isenção do servidor estão com hook. Funciona só no console do servidor, e a saída vai para lá.

DetectionExempt.clientEvents e localEvents#

Os nomes de clientEvents são registrados com RegisterNetEvent no cliente. Quando um dispara, pelo servidor ou por um TriggerEvent local, o cliente fica isento por defaultDurationMs.

clientEvents aceita só strings. Entradas em tabela, inclusive as que têm durationMs, são ignoradas em silêncio no cliente.

Durante a isenção, as detecções do cliente não são punidas. Elas são reportadas como linhas LOG marcadas com '[exempt]', no máximo uma vez por minuto por verificação, para que abusos continuem visíveis no painel. As verificações do servidor não são afetadas.

localEvents funciona da mesma forma, mas usa só AddEventHandler, então reage apenas a TriggerEvent no cliente.

O cliente varre as duas listas 3,5 s depois de carregar e de novo a cada sincronização de configurações.

Um evento de um script que você não roda nunca dispara, então nomes extras na lista não fazem mal. A lista padrão de clientEvents repete 11 nomes; os duplicados são inofensivos.

Exemplo de DetectionExempt#

Adicione os seus nomes às listas existentes em configs/anticheat_config.lua e depois reinicie o anticheat. Substituir a tabela inteira remove as entradas padrão. O exemplo mostra só as entradas novas.

Prefira os exports quando o código é seu: o export de cliente SetExempt(true, ms, reason), o export de servidor SetExempt(src, ms, reason) a partir de um resource confiável e o export de servidor MarkTeleport(src, ms) logo antes de teleportar um jogador.

ts.DetectionExempt = {
  enabled           = true,
  defaultDurationMs = 60000,
  budgetMsPer10Min  = 240000,

  -- eventos de rede do cliente: só strings
  clientEvents = {
    -- ...entradas padrão...
    'my-clothing:client:openMenu',
  },

  -- eventos locais do cliente (TriggerEvent no cliente)
  localEvents = {
    -- ...entradas padrão...
    'my-menu:opened',
  },

  -- eventos do cliente para o servidor: isenção leve para quem envia
  serverEvents = {
    -- ...entradas padrão...
    '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 taxa para eventos de rede#

ts.EventLimiter mapeia nomes de eventos para um número máximo de chamadas por jogador em 5 s. O anticheat registra cada nome com RegisterServerEvent no carregamento e conta as chamadas por jogador; todos os contadores zeram a cada 5 s. O seu próprio handler do evento continua rodando.

Quando uma contagem chega ao limite, o anticheat tenta uma detecção com a config ts.EventLimiter (server_event_spam, KICK por padrão).

A lista padrão tem 25 entradas: test:event, esx:getSharedObject, eventos de banco e de dinheiro do QBCore, revive, spawn de veículo, inventário, eventos de prisão e de algemas, e HCheat:TempDisableDetection.

Os nomes e limites são lidos no carregamento. A tabela de banco de dados ac_event_limits substitui ts.EventLimiter na sincronização, mas não muda os handlers registrados, então reinicie o anticheat depois de editar qualquer um dos dois.

ts.EventLimiter = {
  -- ...entradas padrão...
  ['my-shop:buy'] = 10,
}

Eventos armadilha: nunca use estes nomes#

Alguns eventos existem só para pegar cheats. Siga estas regras para que os seus scripts nunca disparem nenhum deles.

  • O anticheat registra eventos armadilha que só cheats disparam. Nunca os dispare e nunca reutilize os nomes deles.
  • Para ver os nomes das armadilhas no seu próprio servidor, rode /ts evtlist (a lista sai no console do servidor). Para deixar de tratar um evento como armadilha, use /ts evtwhitelist <eventName>. Veja o tópico Comandos.
  • Dê aos seus próprios eventos o nome do seu resource como prefixo, por exemplo myresource:doThing, para que nunca colidam com uma armadilha.

Limite de taxa dos eventos de som#

O anticheat registra estes eventos de som como eventos de rede no seu próprio resource e os conta por jogador. Acima de ts.premiumGuard.soundPerSec (6 por segundo por padrão), aplica soundAction ('log' por padrão). Só cancela o evento quando soundCancel = true (false por padrão). Os handlers do seu resource de som continuam rodando quando esse resource registrou o evento por conta própria.

A proteção só roda enquanto ts.premiumGuard.enabled e ts.premiumGuard.soundGuard forem true (os dois são true na config padrão). Mantenha soundCancel em false até confirmar que os seus scripts de som ficam abaixo do limite.

EventoResource
InteractSound_SV:PlayOnAllInteractSound
InteractSound_SV:PlayWithinDistanceInteractSound
InteractSound_SV:PlayOnOneInteractSound
xsound:stateSoundxsound
xsound:server:playxsound
xsound:server:playUrlxsound
xsound:server:playUrlPosxsound

Eventos que o anticheat envia para outros resources#

Depois de ações da staff, do painel ou de detecções, o anticheat dispara eventos que pertencem a outros resources. Se você roda um desses resources, espere essas chamadas.

Clima e horário usam só o primeiro resource de sincronização iniciado, nesta ordem: qb-weather, Renewed-Weathersync (só export), cd_easytime, wd_weather, weathersync, vSync, qb-weathersync. Se nenhum estiver iniciado, o anticheat define o clima e o horário diretamente em cada cliente.

hospital:client:Revive e esx_ambulancejob:revive também são listeners do anticheat (veja Eventos de morte e revive dos frameworks), então esses revives também abrem a janela pós-revive de 10 s.

EventoLadoEnviado quando
qb-weather:server:RequestStateChangeLocal do servidorMudança de clima ou de horário quando o qb-weather não tem export setWeather ou setTime. Para o horário, o valor é uma tabela { hour, minute }.
cd_easytime:setWeather, cd_easytime:setTimeLocal do servidorMudança de clima ou de horário com o cd_easytime.
<res>:setWeather, <res>:setTimeLocal do servidorMudança de clima ou de horário com wd_weather, weathersync, vSync ou qb-weathersync quando esse resource não tem um export correspondente.
wasabi_ambulance:reviveLocal do clienteRevive pela staff ou pelo painel quando o wasabi_ambulance está iniciado.
hospital:client:ReviveLocal do clienteRevive pela staff ou pelo painel com qb-ambulancejob ou Starter-Hospital.
esx_ambulancejob:reviveLocal do clienteRevive pela staff ou pelo painel com esx_ambulancejob.
<res>:revive, <res>:client:Revive, <res>:client:reviveLocal do clienteRevive pela staff ou pelo painel nos demais casos: o primeiro iniciado entre ambulance, qb-ambulancejob, cd_ambulance e ars_ambulancejob recebe os três nomes. Um resurrect nativo vem em seguida se o jogador ainda estiver morto.
QBCore:NotifyLocal do cliente; do servidor para o clienteToda cura que o anticheat faz, mesmo sem qb-core (a verificação de qb-core dele é sempre true). Também ações de dinheiro, inventário e notificação do painel.
esx:showNotification, QBCore:Player:SetPlayerData, inventory:client:ItemBoxDo servidor para o clienteAções de dinheiro, inventário e notificação do painel, e entregas de arma como item.
chat:addMessageDo servidor para o cliente; local do clienteMensagens de chat e anúncios do painel. Com ts.chatMessages = true (padrão false), todo resultado de detecção, inclusive LOG, vai para todos os jogadores em turco fixo no código (editable/server/sv_editme.lua).
chat:addSuggestionLocal do clienteAdiciona a sugestão de /unspectate quando o arquivo do menu de admin carrega.
qb-phone:client:CustomNotification, qs-smartphone:client:sendNotification, lb-phone:notification, yseries:notification, gks-phone:client:notification, roadphone:notifyDo servidor para o clienteAlertas para a staff em kicks e bans, e os exports phoneNotify e phoneNotifyAdmins. Só o primeiro celular iniciado recebe (o npwd usa um export; chat se não houver nenhum). Exige ts.AdminMenu.enable = true.

Segurança: quem pode disparar cada evento#

Só o anticheat pode disparar os eventos que ele emite. Outros resources não devem dispará-los. A tabela mostra quem pode disparar cada evento.

EventoQuem pode disparar
ts_anticheat:playerBannedSó o anticheat.
ts_anticheat:banCommittedSó o anticheat.
ts_anticheat:newLogSó o anticheat.
ts_anticheat:shield:ban_video_savedSó o anticheat.
tosun-ac:settingsReloadedSó o anticheat.
ts_anticheat:configUpdatedSó o anticheat.
ts_anticheat:adminStateChangedQualquer resource do servidor.
tosun-ac:panel:characterReadyQualquer cliente. Limitado a uma chamada a cada 20 s por jogador.
ts_anticheat:locale:setSelfQualquer cliente. Afeta só quem chamou.
ts_anticheat:requestEventKeyQualquer cliente. A chave vai só para o jogador que pediu.
tosun-ac:bridge:clientExemptOs seus scripts de servidor.

Segurança: verifique GetInvokingResource#

Qualquer resource do servidor pode disparar um evento local do servidor. Nos seus listeners, confira se foi o anticheat que disparou antes de agir. O próprio listener da tela de ban do anticheat usa uma verificação parecida.

Aplique a verificação em playerBanned, banCommitted, playerUnbanned, newLog, settingsReloaded, nos eventos do Shield, em renameCharacter e nos seus handlers '<name>:safe'.

Nunca dispare os eventos do anticheat a partir dos seus próprios resources, nem para testar. Vários listeners embutidos não verificam quem chamou.

AddEventHandler('ts_anticheat:banCommitted', function(playerId, row)
  if GetInvokingResource() ~= 'tosun-ac' then return end -- o nome da sua pasta, se renomeou
  -- aqui é seguro agir sobre row
end)

Segurança: design com autoridade do servidor#

Tudo o que um cliente pode disparar é tratado como uma indicação, não como autoridade. Mantenha a mesma regra nos seus próprios handlers.

  • As isenções de cliente (clientEvents, localEvents, autoExempt, o evento de bridge e o export de cliente SetExempt) nunca mudam a aplicação das regras no servidor. Enquanto estão ativas, as detecções do cliente são registradas como '[exempt]' em vez de punidas.
  • As isenções de serverEvents são leves: no servidor elas só deixam mais tolerantes as verificações de movimento e de visibilidade (as punições de arma, dinheiro, dano, abuso de eventos, injeção e crash continuam valendo), e cada jogador fica limitado por budgetMsPer10Min.
  • O status de staff é decidido pela resposta do servidor, protegida por nonce. Enviar uma marca de staff para um cliente não concede admin.
  • A chave SafeEvents só valida o transporte. O seu handler ':safe' precisa verificar permissões, saldos e posse no servidor.
  • Um cheat pode disparar eventos de morte de framework localmente. A marca de caído é limpa depois de cerca de 20 s de jogo claramente ativo.
  • Para isentar um jogador pelo lado do servidor, use os exports: SetExempt(src, ms, reason) a partir de um resource confiável, ou MarkTeleport(src, ms) antes de um teleporte.

Eventos internos#

Além dos eventos desta página, o anticheat registra eventos internos para o seu handshake de entrada, troca de chaves, Shield, menu de admin, ações do painel web e um resource complementar. Eles são protegidos por nonces, chaves, assinaturas, limites de taxa ou verificações de staff, e vários deles são armadilhas que punem quem chama. Eles não são documentados aqui de propósito.

  • Não dispare eventos internos, nem pelo servidor nem por um cliente.
  • Não registre handlers neles e não reutilize os nomes deles.
  • Não dispare nenhum evento cujo nome comece com ts_anticheat: ou tosun-ac:, a menos que esta página o liste como um que você pode disparar.
  • Use os exports para ações: por exemplo punish, MarkTeleport, freezePlayer, setPlayerHealth, RequestShieldScreenshot e quarantine.

Problemas conhecidos na 9.6.15#

Estes comportamentos estão no código distribuído. Planeje-se em torno deles.

  • tosun-ac:settingsReloaded e ts_anticheat:configUpdated disparam em quase todo ciclo de sincronização, não só quando as configurações mudam.
  • ts_anticheat:playerBanned dispara para KICK e LOG, além de BAN.
  • O alerta de celular para a staff nos logs ao vivo escuta o status 'TESPİT' ou 'Detection' em ts_anticheat:newLog, mas nada emite nenhum dos dois valores, então esse alerta nunca dispara.
  • Toda cura que o anticheat faz dispara QBCore:Notify no cliente, mesmo em servidores sem qb-core.
  • ts.quarantine.autoQuarantine não tem efeito: a única entrada dele é um evento interno que nada emite. Use o export quarantine.
  • Vários comentários em configs/anticheat_config.lua estão em turco e alguns estão desatualizados: chamadas sem chave de ts.ProtectedEvents são descartadas, não banidas, e ts.EventLimiter não expulsa.

Evitar análise desnecessária após reiniciar#

Cada arranque verifica impressões SHA256 do conteúdo. Recursos com arquivos e regras inalterados reutilizam resultados autenticados no KVP do servidor, evitando repetir análises dispendiosas de assinaturas e eventos. Mudanças em arquivos, exceções ou regras invalidam a análise correspondente. As impressões ainda leem arquivos; não há garantia de um valor resmon fixo.

  1. Mantenha o cache KVP nas atualizações normais; entradas corrompidas ou não verificáveis são analisadas novamente.
  2. O catálogo inicial reúne nomes literais de eventos Lua/JS. Nomes dinâmicos, arquivos ilegíveis ou escrow e DLL não são certificados como totalmente analisados.
  3. A configuração automática manual aguarda a análise de segurança. Recursos incompletos, suspeitos, alterados ou com tempo esgotado não são aprovados automaticamente.
  4. Teste entrada de jogadores, jogo normal e ações administrativas num servidor de teste; avalie resmon com o número de jogadores e a carga do framework.