Bot-Schutz für Spiele
Spieler klicken ein paar Bilder an. Bots bleiben hängen. Dein Gameserver signiert und prüft jedes Token selbst, deshalb ändert sich bei dir nichts, wenn du die Regeln anpasst.
Vorschau & EinrichtungProbier es aus
Eine Beispielfrage. Wähle alle passenden Tiere und drücke dann Weiter. Zu sehen ist das Web-Widget im Desktop- und im mobilen Browser.
So funktioniert es
- 1Gameserver→Spiel-Client
Ein Spieler greift an oder angelt. Der Server hält die Aktion an und gibt dem Client ein mit deinem geheimen Schlüssel signiertes Token: welcher Spieler, wann und ein Einmalcode.
- 2Spiel-Client→Captcha-Dienst
Der Client sendet dieses Token an den Dienst und fragt nach einer Frage. Der Dienst prüft die Signatur, damit nur Anfragen von deinem Server Fragen bekommen.
- 3Captcha-Dienst→Spiel-Client
Der Dienst sendet ein Bild, eine Frage und eine Fragen-ID.
- 4Spiel-Client→Captcha-Dienst
Der Spieler wählt die passenden Tiere. Der Client sendet die Klicks zusammen mit der Fragen-ID.
- 5Captcha-Dienst→Spiel-Client
Richtig: Der Dienst sendet ein mit deinem geheimen Schlüssel signiertes Pass-Token mit demselben Einmalcode. Falsch: Der Spieler wartet ein paar Sekunden, dann fragt der Client mit demselben Token erneut.
- 6Spiel-Client→Gameserver
Der Client sendet das Pass-Token an den Server. Der Server prüft die Signatur mit deinem geheimen Schlüssel und ob der Einmalcode von ihm stammt, dann erlaubt er die Aktion. Der Server ruft den Dienst nie auf.
Was Sie steuern
- Captchas im Spiel per GM-Befehl ein- und ausschalten, ohne den Server neu zu starten
- Festlegen, wie oft Spieler je nach Level gefragt werden, oder ab einem Level gar nicht mehr
- Nutzung pro Server im Panel sehen: Fragen der letzten Stunde und des letzten Tages, bestanden und nicht bestanden, wartende Spieler
- Fragen in der Sprache des Spielers, standardmäßig Englisch
Metin2-Beispiel
Nutzt Crypto++ und rapidjson, die bereits im Source enthalten sind, also keine zusätzliche Bibliothek.
Lade die Dateien aus deinem Panel
Die Dateien auf dieser Seite sind Vorlagen. Die Kopien in deinem Panel enthalten bereits deine API-Adresse, Site-ID und dein Secret, jeder Server bekommt also eigene Dateien. Halte die Server-Dateien geheim: Wer das Secret darin hat, kann gültige Captcha-Tokens für deinen Server erzeugen. Die Client-Dateien enthalten nur die API-Adresse.
Client (UI)
- 1
Binde thick_captcha.js aus deinem Panel in deine Seite ein.
thick_captcha.js
<script src="thick_captcha.js"></script> - 2
Öffne es mit dem Token deines Gameservers und schicke das Pass-Token an den Server zurück.
<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>
Gameserver
- 1
Schalte das System mit diesem Define ein. Ohne die Zeile ist es komplett aus.
common/service.h (CommonDefines.h)
#define ENABLE_THICK_CAPTCHA - 2
Kopiere thick_captcha.h und thick_captcha.cpp aus deinem Panel nach game/src und trage die .cpp im Makefile ein.
game/src/Makefile
CPPFILE = ... thick_captcha.cpp - 3
Erstelle den Manager in 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
Setze CONFIG_PATH so, dass alle Cores auf dieselbe Datei zeigen. GM-Befehle speichern ihre Einstellungen dort und jeder Core liest die Datei einmal pro Minute neu, so erreicht eine Änderung auf einem Channel alle Channels und übersteht Neustarts. Die Datei wird beim ersten Start angelegt.
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
Gefragt wird beim ersten Angriff auf ein Monster oder einen Metin und beim ersten Angeln nach dem Login, danach wieder nach dem Intervall für das Level des Spielers (standardmäßig 30 Minuten, einstellbar mit thick_captcha_interval). Angriffe auf Spieler (Kriege, Gildenkriege, PvP) und untätige Spieler lösen nie eine Frage aus.
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
Räume beim Ausloggen des Spielers auf. Ein offenes Captcha wird nach dem erneuten Einloggen beim nächsten Angriff oder Angeln wieder gesendet.
game/src/char.cpp
// CHARACTER::Disconnect #ifdef ENABLE_THICK_CAPTCHA CThickCaptchaManager::instance().OnLogout(this); #endif - 7
Füge die Spielerbefehle und die GM-Befehle hinzu. thick_captcha_ask sendet ein Test-Captcha, thick_captcha_toggle schaltet das System ein oder aus, thick_captcha_difficulty 1-10 setzt die Schwierigkeit, thick_captcha_interval <Level> <Minuten> legt fest, wie oft Spieler ab diesem Level gefragt werden (0 = nie, del <Level> löscht eine Regel, ohne Argumente werden die Regeln angezeigt). Änderungen werden in der Config-Datei gespeichert und erreichen alle Channels innerhalb einer Minute.
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
Um eine weitere Aktion zu schützen, füge dort dieselbe Prüfung ein.
#ifdef ENABLE_THICK_CAPTCHA if (!CThickCaptchaManager::instance().CheckAction(ch)) return; #endif