Ochrona przed botami dla gier
Gracze klikają kilka obrazków. Boty się zacinają. Twój serwer gry sam podpisuje i sprawdza każdy token, więc zmiana zasad nie wymaga aktualizacji serwera.
Podgląd i instalacjaWypróbuj
Przykładowe pytanie. Zaznacz wszystkie pasujące zwierzęta i naciśnij Dalej.
Jak to działa
- 1Serwer gry→Klient gry
Gracz atakuje lub łowi. Serwer wstrzymuje akcję i daje klientowi token podpisany twoim tajnym kluczem: który gracz, kiedy i jednorazowy kod.
- 2Klient gry→Usługa captcha
Klient wysyła ten token do usługi i prosi o pytanie. Usługa sprawdza podpis, więc pytania dostają tylko żądania z twojego serwera.
- 3Usługa captcha→Klient gry
Usługa wysyła obraz, pytanie i identyfikator pytania.
- 4Klient gry→Usługa captcha
Gracz wybiera pasujące zwierzęta. Klient wysyła kliknięcia razem z identyfikatorem pytania.
- 5Usługa captcha→Klient gry
Dobrze: usługa wysyła token przepustki podpisany twoim tajnym kluczem, z tym samym jednorazowym kodem. Źle: gracz czeka kilka sekund, a klient pyta ponownie tym samym tokenem.
- 6Klient gry→Serwer gry
Klient wysyła token przepustki do serwera. Serwer sprawdza podpis twoim tajnym kluczem i czy jednorazowy kod pochodzi od niego, po czym pozwala na akcję. Serwer nigdy nie łączy się z usługą.
Sterowane z panelu
- Włączaj i wyłączaj ochronę bez aktualizacji serwera
- Ustal, ile zadań gracz musi rozwiązać w danym przedziale poziomów
- Wyłącz sprawdzanie powyżej wybranego poziomu
- Pytania w języku gracza, domyślnie po angielsku
Przykładowa instalacja Metin2
Korzysta z Crypto++ i rapidjson, które są już w źródłach, więc nie potrzeba dodatkowej biblioteki.
Pobierz pliki z panelu
Pliki na tej stronie to szablony. Kopie w panelu mają już wpisany adres API, ID strony i sekret, więc każdy serwer dostaje własne pliki. Nie udostępniaj plików serwera: każdy, kto ma zapisany w nich sekret, może tworzyć ważne tokeny captcha dla twojego serwera. Pliki klienta zawierają tylko adres API.
Klient (interfejs)
- 1
Dodaj do strony thick_captcha.js z panelu.
thick_captcha.js
<script src="thick_captcha.js"></script> - 2
Otwórz go z tokenem z serwera gry i odeślij token przejścia do serwera.
<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>
Serwer gry
- 1
Włącz system tym define. Bez tej linii system jest całkowicie wyłączony.
common/service.h (CommonDefines.h)
#define ENABLE_THICK_CAPTCHA - 2
Skopiuj thick_captcha.h i thick_captcha.cpp z panelu do game/src i dodaj .cpp do Makefile.
game/src/Makefile
CPPFILE = ... thick_captcha.cpp - 3
Utwórz menedżera w 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
Ustaw CONFIG_PATH tak, aby wszystkie core wskazywały ten sam plik. Komendy GM zapisują tam ustawienia, a każdy core wczytuje plik ponownie raz na minutę, więc zmiana na jednym kanale trafia na wszystkie kanały i przetrwa restart. Plik tworzy się przy pierwszym uruchomieniu.
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
Pytanie pojawia się przy pierwszym ataku na potwora lub metin i pierwszym łowieniu po zalogowaniu, a potem po upływie czasu dla poziomu gracza (domyślnie 30 minut, ustawiane przez thick_captcha_interval). Ataki na graczy (wojny, wojny gildii, PvP) i bezczynni gracze nigdy nie wywołują pytania.
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
Wyczyść dane, gdy gracz się rozłącza. Oczekująca captcha zostanie wysłana ponownie przy następnym ataku lub łowieniu po ponownym zalogowaniu.
game/src/char.cpp
// CHARACTER::Disconnect #ifdef ENABLE_THICK_CAPTCHA CThickCaptchaManager::instance().OnLogout(this); #endif - 7
Dodaj komendy gracza i komendy GM. thick_captcha_ask wysyła testową captcha, thick_captcha_toggle włącza lub wyłącza system, thick_captcha_difficulty 1-10 ustawia trudność, thick_captcha_interval <poziom> <minuty> ustala, jak często pytani są gracze od tego poziomu (0 = nigdy, del <poziom> usuwa regułę, bez argumentów wyświetla reguły). Zmiany zapisują się w pliku konfiguracyjnym i w ciągu minuty trafiają na wszystkie kanały.
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
Aby chronić inną akcję, dodaj tam to samo sprawdzenie.
#ifdef ENABLE_THICK_CAPTCHA if (!CThickCaptchaManager::instance().CheckAction(ch)) return; #endif