Oyunlar için bot koruması

Oyuncular birkaç resme tıklar, botlar takılır. Token'ları oyun sunucunuz kendisi imzalar ve doğrular; kuralları değiştirdiğinizde sunucunuzda hiçbir şey değişmez.

Görünüm ve kurulum

Deneyin

Örnek bir soru. Soruya uyan bütün hayvanları seçin, sonra İleri'ye basın. Burada web widget'ının masaüstü ve mobil tarayıcıdaki hali görünüyor.

Nasıl çalışır

  1. 1
    Oyun sunucusu→Oyun client'ı

    Oyuncu saldırır ya da balık tutar. Sunucu işlemi durdurur ve client'a sizin gizli anahtarınızla imzalı bir token verir: hangi oyuncu, ne zaman ve tek kullanımlık bir kod.

  2. 2
    Oyun client'ı→Captcha servisi

    Client bu token'ı servise gönderip bir soru ister. Servis imzayı kontrol eder, böylece sadece sizin sunucunuzdan gelen isteklere soru verilir.

  3. 3
    Captcha servisi→Oyun client'ı

    Servis bir resim, bir soru ve bir soru kimliği gönderir.

  4. 4
    Oyun client'ı→Captcha servisi

    Oyuncu uyan hayvanları seçer. Client tıklamaları soru kimliğiyle birlikte gönderir.

  5. 5
    Captcha servisi→Oyun client'ı

    Doğruysa servis, sizin gizli anahtarınızla imzalı ve aynı tek kullanımlık kodu taşıyan bir geçiş token'ı gönderir. Yanlışsa oyuncu birkaç saniye bekler, client aynı token'la tekrar soru ister.

  6. 6
    Oyun client'ı→Oyun sunucusu

    Client geçiş token'ını sunucuya gönderir. Sunucu imzayı gizli anahtarınızla, tek kullanımlık kodu da kendi verdiğiyle karşılaştırır ve işleme izin verir. Sunucu servise hiç bağlanmaz.

Kontrol sizde

  • Captcha'yı oyun içinden GM komutuyla açıp kapatın, sunucuyu yeniden başlatmadan
  • Seviyeye göre ne sıklıkla sorulacağını ayarlayın ya da belirli bir seviyeden sonra hiç sormayın
  • Panelden sunucu başına kullanımı görün: son 1 saat ve 24 saatteki sorular, geçen ve kalanlar, bekleyen oyuncular
  • Sorular oyuncunun dilinde, varsayılan İngilizce

Metin2 örnek kurulum

Kaynakta zaten bulunan Crypto++ ve rapidjson kullanılır, ek kütüphane gerekmez.

Dosyaları panelinizden indirin

Bu sayfadaki dosyalar şablondur. Paneldeki kopyalarda API adresiniz, site kimliğiniz ve gizli anahtarınız hazır yazılıdır; her sunucu kendi dosyalarını alır. Sunucu dosyalarını kimseyle paylaşmayın: İçindeki gizli anahtara sahip olan herkes sunucunuz için geçerli captcha token'ı üretebilir. Client dosyalarında sadece API adresi bulunur.

Client (arayüz)

  1. 1

    Panelinizden indirdiğiniz thick_captcha.js dosyasını sayfanıza ekleyin.

    thick_captcha.js

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

    Oyun sunucunuzdan gelen token ile açın, geçiş token'ını sunucuya geri gönderin.

    <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>

Oyun sunucusu

  1. 1

    Sistemi bu define ile açın. Satırı silerseniz sistem tamamen kapanır.

    common/service.h (CommonDefines.h)

    #define ENABLE_THICK_CAPTCHA
  2. 2

    Panelinizden indirdiğiniz thick_captcha.h ve thick_captcha.cpp dosyalarını game/src içine kopyalayın, .cpp dosyasını Makefile'a ekleyin.

    game/src/Makefile

    CPPFILE = ... thick_captcha.cpp
  3. 3

    main.cpp içinde yöneticiyi oluşturun.

    game/src/main.cpp

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

    CONFIG_PATH değerini tüm core'lar aynı dosyayı gösterecek şekilde ayarlayın. GM komutları ayarları bu dosyaya kaydeder, her core dosyayı dakikada bir yeniden okur; böylece bir kanalda yapılan değişiklik tüm kanallara ulaşır ve yeniden başlatmada kaybolmaz. Dosya ilk açılışta otomatik oluşturulur.

    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

    Girişten sonraki ilk canavar/metin saldırısında ve ilk balık tutmada, sonra oyuncunun seviyesine ait süre dolunca (varsayılan 30 dakika, thick_captcha_interval ile ayarlanır) tekrar sorulur. Oyunculara yapılan saldırılarda (savaş, lonca savaşı, PvP) ve boşta duran oyunculara hiç sorulmaz.

    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

    Oyuncu çıkış yapınca kayıtları temizleyin. Bekleyen bir captcha, oyuncu tekrar girdikten sonraki ilk saldırıda veya balık tutmada yeniden gönderilir.

    game/src/char.cpp

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

    Oyuncu komutlarını ve GM komutlarını ekleyin. thick_captcha_ask test captcha'sı gönderir, thick_captcha_toggle sistemi açıp kapatır, thick_captcha_difficulty 1-10 zorluğu ayarlar, thick_captcha_interval <seviye> <dakika> o seviyeden itibaren ne sıklıkla sorulacağını ayarlar (0 = hiç sorulmaz, del <seviye> kuralı siler, parametresiz yazılınca kuralları listeler). Değişiklikler config dosyasına kaydedilir ve bir dakika içinde tüm kanallara ulaşır.

    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

    Başka bir eylemi korumak için aynı kontrolü oraya ekleyin.

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