Protecție anti-bot pentru jocuri

Jucătorii dau clic pe câteva imagini. Boții se blochează. Serverul tău de joc semnează și verifică singur fiecare token, așa că nu schimbi nimic pe server când ajustezi regulile.

Previzualizare și instalare

Încearcă

O întrebare de exemplu. Alege toate animalele potrivite, apoi apasă Înainte. Aici se vede widgetul web într-un browser desktop și unul mobil.

Cum funcționează

  1. 1
    Server de joc→Client de joc

    Un jucător atacă sau pescuiește. Serverul oprește acțiunea și îi dă clientului un token semnat cu cheia ta secretă: ce jucător, când și un cod de unică folosință.

  2. 2
    Client de joc→Serviciu captcha

    Clientul trimite acest token serviciului și cere o întrebare. Serviciul verifică semnătura, deci doar cererile de la serverul tău primesc întrebări.

  3. 3
    Serviciu captcha→Client de joc

    Serviciul trimite o imagine, o întrebare și un id al întrebării.

  4. 4
    Client de joc→Serviciu captcha

    Jucătorul alege animalele potrivite. Clientul trimite clicurile împreună cu id-ul întrebării.

  5. 5
    Serviciu captcha→Client de joc

    Corect: serviciul trimite un token de trecere semnat cu cheia ta secretă, cu același cod de unică folosință. Greșit: jucătorul așteaptă câteva secunde, apoi clientul cere din nou cu același token.

  6. 6
    Client de joc→Server de joc

    Clientul trimite tokenul de trecere serverului. Serverul verifică semnătura cu cheia ta secretă și că codul de unică folosință este cel emis de el, apoi permite acțiunea. Serverul nu contactează niciodată serviciul.

Ce controlați

  • Porniți sau opriți captcha din joc cu o comandă GM, fără să reporniți serverul
  • Setați cât de des sunt întrebați jucătorii în funcție de nivel sau deloc de la un anumit nivel
  • Vedeți utilizarea pe server în panou: întrebările din ultima oră și ultima zi, trecute și picate, jucători care așteaptă
  • Întrebări în limba jucătorului, implicit engleză

Exemplu de instalare Metin2

Folosește Crypto++ și rapidjson care există deja în sursă, deci nu e nevoie de altă bibliotecă.

Descarcă fișierele din panou

Fișierele de pe această pagină sunt șabloane. Copiile din panoul tău conțin deja adresa API, ID-ul site-ului și secretul, deci fiecare server primește propriile fișiere. Păstrează fișierele de server private: oricine are secretul din ele poate crea tokenuri captcha valide pentru serverul tău. Fișierele de client conțin doar adresa API.

Client (interfață)

  1. 1

    Adaugă în pagină thick_captcha.js din panoul tău.

    thick_captcha.js

    <script src="thick_captcha.js"></script>
  2. 2

    Deschide-l cu tokenul de la serverul de joc și trimite înapoi tokenul de trecere.

    <div id="captcha"></div>
    <script>
      ThickCaptcha.open(document.getElementById("captcha"), {
        token: REQUEST_TOKEN_FROM_GAME_SERVER,
        lang: navigator.language,
        onPass: function (passToken) {
          sendToGameServer("/thick_captcha " + passToken);
        },
        onRenew: function () {
          return askGameServerForNewToken();
        },
      });
    </script>

Server de joc

  1. 1

    Pornește sistemul cu acest define. Fără linie, sistemul e oprit complet.

    common/service.h (CommonDefines.h)

    #define ENABLE_THICK_CAPTCHA
  2. 2

    Copiază thick_captcha.h și thick_captcha.cpp din panou în game/src și adaugă .cpp în Makefile.

    game/src/Makefile

    CPPFILE = ... thick_captcha.cpp
  3. 3

    Creează managerul în main.cpp.

    game/src/main.cpp

    #ifdef ENABLE_THICK_CAPTCHA
    #include "thick_captcha.h"
    #endif
    
    #ifdef ENABLE_THICK_CAPTCHA
    	CThickCaptchaManager thick_captcha_manager;
    #endif
  4. 4

    Setează CONFIG_PATH astfel încât toate core-urile să indice același fișier. Comenzile GM își salvează setările acolo și fiecare core recitește fișierul o dată pe minut, așa că o schimbare făcută pe un canal ajunge pe toate canalele și rămâne după repornire. Fișierul este creat la prima pornire.

    game/src/thick_captcha.cpp, thick_captcha.txt

    const char*	CONFIG_PATH = "../../thick_captcha.txt";
    
    // thick_captcha.txt
    enabled 1
    difficulty 1
    interval 1 30
    interval 75 60
    interval 120 0
  5. 5

    Se cere la primul atac asupra unui monstru sau metin și la primul pescuit după autentificare, apoi din nou după intervalul pentru nivelul jucătorului (implicit 30 de minute, setat cu thick_captcha_interval). Atacurile asupra jucătorilor (războaie, războaie de breaslă, PvP) și jucătorii inactivi nu declanșează niciodată întrebarea.

    game/src/char_battle.cpp, char_skill.cpp, char.cpp

    // CHARACTER::Attack, after battle_is_attackable
    #ifdef ENABLE_THICK_CAPTCHA
    	if (IsPC() && !CThickCaptchaManager::instance().CheckAttack(this, pkVictim))
    		return false;
    #endif
    
    // CHARACTER::ComputeSkill, at the top
    #ifdef ENABLE_THICK_CAPTCHA
    	if (IsPC() && pkVictim && !CThickCaptchaManager::instance().CheckAttack(this, pkVictim))
    		return BATTLE_NONE;
    #endif
    
    // CHARACTER::fishing, after the "if (m_pkFishingEvent) { fishing_take(); return; }" block
    #ifdef ENABLE_THICK_CAPTCHA
    	if (!CThickCaptchaManager::instance().CheckAction(this))
    		return;
    #endif
  6. 6

    Curăță datele când jucătorul se deconectează. Un captcha în așteptare este trimis din nou la următorul atac sau pescuit după reconectare.

    game/src/char.cpp

    // CHARACTER::Disconnect
    #ifdef ENABLE_THICK_CAPTCHA
    	CThickCaptchaManager::instance().OnLogout(this);
    #endif
  7. 7

    Adaugă comenzile pentru jucători și comenzile GM. thick_captcha_ask trimite un captcha de test, thick_captcha_toggle pornește sau oprește sistemul, thick_captcha_difficulty 1-10 setează dificultatea, thick_captcha_interval <nivel> <minute> stabilește cât de des sunt întrebați jucătorii de la acel nivel (0 = niciodată, del <nivel> șterge o regulă, fără argumente afișează regulile). Modificările se salvează în fișierul de configurare și ajung pe toate canalele într-un minut.

    game/src/cmd.cpp, cmd_general.cpp

    #ifdef ENABLE_THICK_CAPTCHA
    ACMD(do_thick_captcha);
    ACMD(do_thick_captcha_renew);
    ACMD(do_thick_captcha_ask);
    ACMD(do_thick_captcha_toggle);
    ACMD(do_thick_captcha_difficulty);
    ACMD(do_thick_captcha_interval);
    #endif
    
    #ifdef ENABLE_THICK_CAPTCHA
    	{ "thick_captcha",       do_thick_captcha,       0, POS_DEAD, GM_PLAYER     },
    	{ "thick_captcha_renew", do_thick_captcha_renew, 0, POS_DEAD, GM_PLAYER     },
    	{ "thick_captcha_ask",   do_thick_captcha_ask,   0, POS_DEAD, GM_LOW_WIZARD },
    	{ "thick_captcha_toggle",     do_thick_captcha_toggle,     0, POS_DEAD, GM_IMPLEMENTOR },
    	{ "thick_captcha_difficulty", do_thick_captcha_difficulty, 0, POS_DEAD, GM_IMPLEMENTOR },
    	{ "thick_captcha_interval",   do_thick_captcha_interval,   0, POS_DEAD, GM_IMPLEMENTOR },
    #endif
    
    #ifdef ENABLE_THICK_CAPTCHA
    ACMD(do_thick_captcha)
    {
    	while (*argument == ' ')
    		++argument;
    	if (!CThickCaptchaManager::instance().Verify(ch, argument))
    		ch->ChatPacket(CHAT_TYPE_COMMAND, "ThickCaptchaFailed");
    }
    
    ACMD(do_thick_captcha_renew)
    {
    	CThickCaptchaManager::instance().Renew(ch);
    }
    
    ACMD(do_thick_captcha_ask)
    {
    	char arg1[256];
    	one_argument(argument, arg1, sizeof(arg1));
    	LPCHARACTER tch = *arg1 ? CHARACTER_MANAGER::instance().FindPC(arg1) : ch;
    	if (tch)
    		CThickCaptchaManager::instance().Request(tch);
    }
    
    ACMD(do_thick_captcha_toggle)
    {
    	CThickCaptchaManager& mgr = CThickCaptchaManager::instance();
    	mgr.SetEnabled(!mgr.IsEnabled());
    	ch->ChatPacket(CHAT_TYPE_INFO, "Thick Captcha: %s", mgr.IsEnabled() ? "on" : "off");
    }
    
    ACMD(do_thick_captcha_difficulty)
    {
    	char arg1[256];
    	one_argument(argument, arg1, sizeof(arg1));
    	CThickCaptchaManager& mgr = CThickCaptchaManager::instance();
    	if (*arg1 && isdigit(*arg1))
    		mgr.SetDifficulty(atoi(arg1));
    	ch->ChatPacket(CHAT_TYPE_INFO, "Thick Captcha difficulty: %d (1-10)", mgr.GetDifficulty());
    }
    
    ACMD(do_thick_captcha_interval)
    {
    	char arg1[256], arg2[256];
    	two_arguments(argument, arg1, sizeof(arg1), arg2, sizeof(arg2));
    	CThickCaptchaManager& mgr = CThickCaptchaManager::instance();
    
    	if (!strcmp(arg1, "del") && isdigit(*arg2))
    		mgr.RemoveInterval(atoi(arg2));
    	else if (isdigit(*arg1) && isdigit(*arg2))
    		mgr.SetInterval(atoi(arg1), atoi(arg2));
    
    	for (const auto& rule : mgr.GetIntervals())
    	{
    		if (rule.second)
    			ch->ChatPacket(CHAT_TYPE_INFO, "Thick Captcha level %d+: every %d min", rule.first, rule.second);
    		else
    			ch->ChatPacket(CHAT_TYPE_INFO, "Thick Captcha level %d+: off", rule.first);
    	}
    }
    #endif
  8. 8

    Pentru a proteja altă acțiune, adaugă aceeași verificare acolo.

    #ifdef ENABLE_THICK_CAPTCHA
    	if (!CThickCaptchaManager::instance().CheckAction(ch))
    		return;
    #endif