From 2d381dad91ee0c8a3daa91e3273429a41e5c897a Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Thu, 13 Nov 2025 06:03:16 -0800 Subject: [PATCH 1/7] feat: Adds anti-botting system for resource harvesting --- Projects/UOContent/Engines/Harvest/Fishing.cs | 11 ++ .../Engines/Harvest/Lumberjacking.cs | 11 ++ Projects/UOContent/Engines/Harvest/Mining.cs | 11 ++ .../Systems/AntiBotSystem/AntiBotGump.cs | 62 ++++++++ .../Systems/AntiBotSystem/AntiBotSystem.cs | 132 ++++++++++++++++++ 5 files changed, 227 insertions(+) create mode 100644 Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs create mode 100644 Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs diff --git a/Projects/UOContent/Engines/Harvest/Fishing.cs b/Projects/UOContent/Engines/Harvest/Fishing.cs index df46fe9fc..99de3d11d 100644 --- a/Projects/UOContent/Engines/Harvest/Fishing.cs +++ b/Projects/UOContent/Engines/Harvest/Fishing.cs @@ -492,6 +492,17 @@ namespace Server.Engines.Harvest return false; } + if (Utility.Random(100) < 1) + { + if (!AntiBotSystem.CheckPlayer(from, () => + { + from.Target = new HarvestTarget(tool, this); + })) + { + return false; // antibot challenge sent + } + } + from.SendLocalizedMessage(500974); // What water do you want to fish in? return true; } diff --git a/Projects/UOContent/Engines/Harvest/Lumberjacking.cs b/Projects/UOContent/Engines/Harvest/Lumberjacking.cs index 9640f68d8..95f4c0fa1 100644 --- a/Projects/UOContent/Engines/Harvest/Lumberjacking.cs +++ b/Projects/UOContent/Engines/Harvest/Lumberjacking.cs @@ -185,6 +185,17 @@ namespace Server.Engines.Harvest { from.RevealingAction(); } + + if (Utility.Random(100) < 1) + { + if (!AntiBotSystem.CheckPlayer(from, () => + { + from.Target = new HarvestTarget(tool, this); + })) + { + // antibot challenge sent + } + } } public static void Initialize() diff --git a/Projects/UOContent/Engines/Harvest/Mining.cs b/Projects/UOContent/Engines/Harvest/Mining.cs index dd7f5c747..60c734343 100644 --- a/Projects/UOContent/Engines/Harvest/Mining.cs +++ b/Projects/UOContent/Engines/Harvest/Mining.cs @@ -444,6 +444,17 @@ namespace Server.Engines.Harvest return false; } + if (Utility.Random(100) < 1) + { + if (!AntiBotSystem.CheckPlayer(from, () => + { + from.Target = new HarvestTarget(tool, this); + })) + { + // antibot challenge sent + } + } + from.SendLocalizedMessage(503033); // Where do you wish to dig? return true; } diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs new file mode 100644 index 000000000..cfe607894 --- /dev/null +++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs @@ -0,0 +1,62 @@ +using Server.Gumps; +using Server.Network; + +namespace Server.Engines.AntiBot +{ + public class AntiBotGump : Gump + { + public readonly Mobile _mobile; + public readonly int _code; + + public AntiBotGump(Mobile mobile, int code) : base(150, 150) + { + _mobile = mobile; + _code = code; + + Closable = false; + Disposable = false; + Draggable = true; + Resizable = false; + + AddPage(0); + AddBackground(0, 0, 350, 220, 9270); + + AddHtml(20, 20, 310, 25, "
Anti-Bot Verification
", false, false); + AddHtml(20, 50, 310, 40, $"
Please enter the following number:
", false, false); + AddHtml(20, 55, 310, 40, $"

{code}
", false, false); + AddHtml(20, 100, 310, 50, $"You have 2 minutes to input the correct number.
Incorrect numbers, cancellations, or timeouts will disconnect you from the server.", false, false); + + AddBackground(20, 160, 200, 25, 3000); + AddTextEntry(25, 165, 190, 20, 0, 0, ""); + + AddButton(230, 160, 4005, 4007, 1, GumpButtonType.Reply, 0); // Submit + AddButton(290, 160, 4017, 4019, 0, GumpButtonType.Reply, 0); // Cancel + + AddHtml(230, 190, 40, 20, "
Submit
", false, false); + AddHtml(290, 190, 40, 20, "
Cancel
", false, false); + } + + public override void OnResponse(NetState sender, in RelayInfo info) + { + var from = sender?.Mobile; + if (from == null) + { + return; + } + + bool cancelled = info.ButtonID == 0; + int enteredCode = 0; + + if (!cancelled) + { + var textEntry = info.GetTextEntry(0); + if (textEntry != null && !int.TryParse(textEntry.Trim(), out enteredCode)) + { + enteredCode = -1; // Invalid input - will cause disconnect + } + } + + AntiBotSystem.ProcessResponse(from, enteredCode, cancelled); + } + } +} diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs new file mode 100644 index 000000000..b36af735a --- /dev/null +++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using Server.Gumps; +using Server.Mobiles; + +namespace Server.Engines.AntiBot +{ + public static class AntiBotSystem + { + private class AntiBotChallenge + { + public int Code { get; set; } + public DateTime ChallengeExpiry { get; set; } + public Action SuccessCallback { get; set; } + public Timer TimeoutTimer { get; set; } + } + + private static readonly Dictionary _activeChallenges = new(); + + public static bool Enabled { get; set; } = true; + public static int MaxAttempts { get; set; } = 1; + public static TimeSpan ChallengeTimeout { get; set; } = TimeSpan.FromMinutes(2); + + public static bool CheckPlayer(Mobile from, Action onSuccess) + { + if (!Enabled || from is not PlayerMobile) + { + return true; + } + + lock (_activeChallenges) + { + CleanupExpiredChallenges(); + + if (_activeChallenges.TryGetValue(from, out var existing)) + { + from.CloseGump(); + from.SendGump(new AntiBotGump(from, existing.Code)); + return false; + } + + var challenge = new AntiBotChallenge + { + Code = Utility.RandomMinMax(1000, 9999), + ChallengeExpiry = Core.Now.Add(ChallengeTimeout), + SuccessCallback = onSuccess + }; + + challenge.TimeoutTimer = Timer.DelayCall(ChallengeTimeout, () => + { + lock (_activeChallenges) + { + if (_activeChallenges.ContainsKey(from)) + { + _activeChallenges.Remove(from); + from.SendMessage("Anti-Bot: Verification timed out. Disconnecting..."); + from.NetState?.Disconnect("Anti-Bot: Verification failed by timing out."); + } + } + }); + + _activeChallenges[from] = challenge; + from.CloseGump(); + from.SendGump(new AntiBotGump(from, challenge.Code)); + return false; + } + } + + internal static void ProcessResponse(Mobile from, int enteredCode, bool cancelled) + { + lock (_activeChallenges) + { + if (!_activeChallenges.TryGetValue(from, out var challenge)) + { + return; + } + + challenge.TimeoutTimer?.Stop(); + _activeChallenges.Remove(from); + + if (cancelled) + { + from.SendMessage("Anti-Bot: Verification cancelled. Disconnecting..."); + from.NetState?.Disconnect("Anti-Bot: Verification failed by cancellation."); + return; + } + + if (enteredCode == challenge.Code) + { + from.SendMessage("Anti-Bot: Verification successful!!!"); + } + else + { + from.SendMessage("Anti-Bot: Incorrect number. Disconnecting..."); + from.NetState?.Disconnect("Anti-Bot: Verification failed by incorrect number."); + } + } + } + + private static void CleanupExpiredChallenges() + { + var now = Core.Now; + var toRemove = new List(); + + foreach (var kvp in _activeChallenges) + { + if (kvp.Value.ChallengeExpiry < now) + { + kvp.Value.TimeoutTimer?.Stop(); + toRemove.Add(kvp.Key); + } + } + + foreach (var mobile in toRemove) + { + _activeChallenges.Remove(mobile); + } + } + + public static void CancelChallenge(Mobile from) + { + lock (_activeChallenges) + { + if (_activeChallenges.TryGetValue(from, out var challenge)) + { + challenge.TimeoutTimer?.Stop(); + _activeChallenges.Remove(from); + } + } + } + } +} \ No newline at end of file From 534a7f11c7e2739610dc181bdea21c95dcbfc0da Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Thu, 13 Nov 2025 06:11:53 -0800 Subject: [PATCH 2/7] adds namespace --- Projects/UOContent/Engines/Harvest/Fishing.cs | 1 + Projects/UOContent/Engines/Harvest/Lumberjacking.cs | 1 + Projects/UOContent/Engines/Harvest/Mining.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/Projects/UOContent/Engines/Harvest/Fishing.cs b/Projects/UOContent/Engines/Harvest/Fishing.cs index 99de3d11d..920f8e3bd 100644 --- a/Projects/UOContent/Engines/Harvest/Fishing.cs +++ b/Projects/UOContent/Engines/Harvest/Fishing.cs @@ -3,6 +3,7 @@ using Server.Engines.Quests.Collector; using Server.Items; using Server.Mobiles; using Server.Spells; +using Server.Engines.AntiBot; namespace Server.Engines.Harvest { diff --git a/Projects/UOContent/Engines/Harvest/Lumberjacking.cs b/Projects/UOContent/Engines/Harvest/Lumberjacking.cs index 95f4c0fa1..b850d07dd 100644 --- a/Projects/UOContent/Engines/Harvest/Lumberjacking.cs +++ b/Projects/UOContent/Engines/Harvest/Lumberjacking.cs @@ -1,6 +1,7 @@ using System; using Server.Items; using Server.Targeting; +using Server.Engines.AntiBot; namespace Server.Engines.Harvest { diff --git a/Projects/UOContent/Engines/Harvest/Mining.cs b/Projects/UOContent/Engines/Harvest/Mining.cs index 60c734343..33970951d 100644 --- a/Projects/UOContent/Engines/Harvest/Mining.cs +++ b/Projects/UOContent/Engines/Harvest/Mining.cs @@ -2,6 +2,7 @@ using System; using Server.Items; using Server.Mobiles; using Server.Targeting; +using Server.Engines.AntiBot; namespace Server.Engines.Harvest { From 535a194c885872720c5d31b4553d4754c1aa8bda Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Thu, 13 Nov 2025 06:13:01 -0800 Subject: [PATCH 3/7] adds copyright --- .../Systems/AntiBotSystem/AntiBotGump.cs | 15 +++++++++++++++ .../Systems/AntiBotSystem/AntiBotSystem.cs | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs index cfe607894..f7d397c8d 100644 --- a/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs +++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs @@ -1,3 +1,18 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AntiBotGump.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + using Server.Gumps; using Server.Network; diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs index b36af735a..e9ff316cb 100644 --- a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs +++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs @@ -1,3 +1,18 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AntiBotSystem.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + using System; using System.Collections.Generic; using Server.Gumps; From 316f25d9af6034ad6db5363dce716885d90650ca Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Thu, 13 Nov 2025 07:12:19 -0800 Subject: [PATCH 4/7] removes locks --- .../Systems/AntiBotSystem/AntiBotSystem.cs | 108 ++++++++---------- 1 file changed, 48 insertions(+), 60 deletions(-) diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs index e9ff316cb..fbf1dff15 100644 --- a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs +++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs @@ -43,72 +43,63 @@ namespace Server.Engines.AntiBot return true; } - lock (_activeChallenges) + CleanupExpiredChallenges(); + + if (_activeChallenges.TryGetValue(from, out var existing)) { - CleanupExpiredChallenges(); - - if (_activeChallenges.TryGetValue(from, out var existing)) - { - from.CloseGump(); - from.SendGump(new AntiBotGump(from, existing.Code)); - return false; - } - - var challenge = new AntiBotChallenge - { - Code = Utility.RandomMinMax(1000, 9999), - ChallengeExpiry = Core.Now.Add(ChallengeTimeout), - SuccessCallback = onSuccess - }; - - challenge.TimeoutTimer = Timer.DelayCall(ChallengeTimeout, () => - { - lock (_activeChallenges) - { - if (_activeChallenges.ContainsKey(from)) - { - _activeChallenges.Remove(from); - from.SendMessage("Anti-Bot: Verification timed out. Disconnecting..."); - from.NetState?.Disconnect("Anti-Bot: Verification failed by timing out."); - } - } - }); - - _activeChallenges[from] = challenge; from.CloseGump(); - from.SendGump(new AntiBotGump(from, challenge.Code)); + from.SendGump(new AntiBotGump(from, existing.Code)); return false; } + + var challenge = new AntiBotChallenge + { + Code = Utility.RandomMinMax(1000, 9999), + ChallengeExpiry = Core.Now.Add(ChallengeTimeout), + SuccessCallback = onSuccess + }; + + challenge.TimeoutTimer = Timer.DelayCall(ChallengeTimeout, () => + { + if (_activeChallenges.ContainsKey(from)) + { + _activeChallenges.Remove(from); + from.SendMessage("Anti-Bot: Verification timed out. Disconnecting..."); + from.NetState?.Disconnect("Anti-Bot: Verification failed by timing out."); + } + }); + + _activeChallenges[from] = challenge; + from.CloseGump(); + from.SendGump(new AntiBotGump(from, challenge.Code)); + return false; } internal static void ProcessResponse(Mobile from, int enteredCode, bool cancelled) { - lock (_activeChallenges) + if (!_activeChallenges.TryGetValue(from, out var challenge)) { - if (!_activeChallenges.TryGetValue(from, out var challenge)) - { - return; - } + return; + } - challenge.TimeoutTimer?.Stop(); - _activeChallenges.Remove(from); + challenge.TimeoutTimer?.Stop(); + _activeChallenges.Remove(from); - if (cancelled) - { - from.SendMessage("Anti-Bot: Verification cancelled. Disconnecting..."); - from.NetState?.Disconnect("Anti-Bot: Verification failed by cancellation."); - return; - } + if (cancelled) + { + from.SendMessage("Anti-Bot: Verification cancelled. Disconnecting..."); + from.NetState?.Disconnect("Anti-Bot: Verification failed by cancellation."); + return; + } - if (enteredCode == challenge.Code) - { - from.SendMessage("Anti-Bot: Verification successful!!!"); - } - else - { - from.SendMessage("Anti-Bot: Incorrect number. Disconnecting..."); - from.NetState?.Disconnect("Anti-Bot: Verification failed by incorrect number."); - } + if (enteredCode == challenge.Code) + { + from.SendMessage("Anti-Bot: Verification successful!!!"); + } + else + { + from.SendMessage("Anti-Bot: Incorrect number. Disconnecting..."); + from.NetState?.Disconnect("Anti-Bot: Verification failed by incorrect number."); } } @@ -134,13 +125,10 @@ namespace Server.Engines.AntiBot public static void CancelChallenge(Mobile from) { - lock (_activeChallenges) + if (_activeChallenges.TryGetValue(from, out var challenge)) { - if (_activeChallenges.TryGetValue(from, out var challenge)) - { - challenge.TimeoutTimer?.Stop(); - _activeChallenges.Remove(from); - } + challenge.TimeoutTimer?.Stop(); + _activeChallenges.Remove(from); } } } From da2ac1f26853f33b8f3091988a149d5cd473f8e4 Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:28:50 -0800 Subject: [PATCH 5/7] adds gump for cloudflare turnstile --- .../AntiBotSystem/AntiBotTurnstileGump.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 Projects/UOContent/Systems/AntiBotSystem/AntiBotTurnstileGump.cs diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotTurnstileGump.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotTurnstileGump.cs new file mode 100644 index 000000000..22cc4c3ab --- /dev/null +++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotTurnstileGump.cs @@ -0,0 +1,85 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AntiBotTurnstileGump.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using Server.Gumps; +using Server.Network; +using System.Diagnostics; + +namespace Server.Engines.AntiBot +{ + public class AntiBotTurnstileGump : Gump + { + private readonly Mobile _from; + private readonly string _challengeId; + + public AntiBotTurnstileGump(Mobile from, string challengeId) : base(150, 150) + { + _from = from; + _challengeId = challengeId; + + Closable = false; + Disposable = false; + Draggable = true; + Resizable = false; + + AddPage(0); + AddBackground(0, 0, 350, 220, 9270); + + AddHtml(20, 20, 310, 25, "
Anti-Bot Verification
", false, false); + AddHtml(20, 50, 310, 40, "
Please verify by web browser:
", false, false); + AddHtml(20, 75, 310, 40, $"
{AntiBotSystem.VerificationUrl}?id={challengeId}
", false, false); + AddHtml(20, 100, 310, 50, "You have 5 minutes to complete verification.
Cancelling or timing out will disconnect you from the server.", false, false); + + AddButton(30, 170, 4005, 4007, 1, GumpButtonType.Reply, 0); // Open Browser + AddHtml(60, 173, 100, 20, "
Open Browser
", false, false); + + AddButton(230, 170, 4017, 4019, 0, GumpButtonType.Reply, 0); // Cancel + AddHtml(260, 173, 60, 20, "
Cancel
", false, false); + } + + public override void OnResponse(NetState sender, in RelayInfo info) + { + var from = sender?.Mobile; + if (from == null) + { + return; + } + + switch (info.ButtonID) + { + case 1: // Open Browser + try + { + var url = $"{AntiBotSystem.VerificationUrl}?id={_challengeId}"; + Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); + _from.SendMessage("Browser opened. Complete verification and return to game."); + _from.SendGump(this); // Keep gump open + } + catch + { + _from.SendMessage("Could not open browser. Please visit the verification URL manually."); + _from.SendGump(this); // Keep gump open + } + break; + + case 0: // Cancel + AntiBotSystem.CancelChallenge(_from); + _from.SendMessage("Anti-Bot: Verification cancelled. Disconnecting..."); + _from.NetState?.Disconnect("Anti-Bot: Verification cancelled."); + break; + } + } + } +} \ No newline at end of file From ac028c7b56bdd6ad139ffcd9e63c320c54379ea2 Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:30:25 -0800 Subject: [PATCH 6/7] adds cloudflare turnstile verification --- .../Systems/AntiBotSystem/AntiBotSystem.cs | 122 ++++++++++++++++-- 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs index fbf1dff15..c2224d105 100644 --- a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs +++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs @@ -15,6 +15,9 @@ using System; using System.Collections.Generic; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; using Server.Gumps; using Server.Mobiles; @@ -24,17 +27,34 @@ namespace Server.Engines.AntiBot { private class AntiBotChallenge { - public int Code { get; set; } + public string ChallengeId { get; set; } public DateTime ChallengeExpiry { get; set; } public Action SuccessCallback { get; set; } public Timer TimeoutTimer { get; set; } + public bool UseTurnstile { get; set; } + public int FallbackCode { get; set; } } private static readonly Dictionary _activeChallenges = new(); + private static readonly HttpClient _httpClient = new(); + // enable or disable the entire anti-bot verification system public static bool Enabled { get; set; } = true; - public static int MaxAttempts { get; set; } = 1; - public static TimeSpan ChallengeTimeout { get; set; } = TimeSpan.FromMinutes(2); + + // if set to false (default) = uses a number matching verification + // if set to true = uses Cloudflare's Turnstile verification + public static bool UseTurnstile { get; set; } = false; + + // Cloudflare Turnstile + // secret key from your Cloudflare account (https://dash.cloudflare.com/login) + public static string TurnstileSecretKey { get; set; } = "YOUR_SECRET_KEY"; + + // the base URL where the widget is hosted (must support HTTPS) + // view the docs here: https://developers.cloudflare.com/turnstile/ + public static string VerificationUrl { get; set; } = "https://yourwebserver.com/verify"; + + // timeout before disconnecting the user (applies to both Turnstile and number match verification) + public static TimeSpan ChallengeTimeout { get; set; } = TimeSpan.FromMinutes(5); public static bool CheckPlayer(Mobile from, Action onSuccess) { @@ -45,18 +65,19 @@ namespace Server.Engines.AntiBot CleanupExpiredChallenges(); - if (_activeChallenges.TryGetValue(from, out var existing)) + if (_activeChallenges.ContainsKey(from)) { - from.CloseGump(); - from.SendGump(new AntiBotGump(from, existing.Code)); return false; } + var challengeId = Guid.NewGuid().ToString("N")[..8]; var challenge = new AntiBotChallenge { - Code = Utility.RandomMinMax(1000, 9999), + ChallengeId = challengeId, ChallengeExpiry = Core.Now.Add(ChallengeTimeout), - SuccessCallback = onSuccess + SuccessCallback = onSuccess, + UseTurnstile = UseTurnstile, + FallbackCode = Utility.RandomMinMax(1000, 9999) }; challenge.TimeoutTimer = Timer.DelayCall(ChallengeTimeout, () => @@ -70,11 +91,40 @@ namespace Server.Engines.AntiBot }); _activeChallenges[from] = challenge; - from.CloseGump(); - from.SendGump(new AntiBotGump(from, challenge.Code)); + + if (UseTurnstile) + { + from.CloseGump(); + from.SendGump(new AntiBotTurnstileGump(from, challengeId)); + } + else + { + from.CloseGump(); + from.SendGump(new AntiBotGump(from, challenge.FallbackCode)); + } + return false; } + public static async Task VerifyTurnstileToken(string token) + { + var formData = new List> + { + new("secret", TurnstileSecretKey), + new("response", token) + }; + + var response = await _httpClient.PostAsync( + "https://challenges.cloudflare.com/turnstile/v0/siteverify", + new FormUrlEncodedContent(formData) + ); + + var jsonResponse = await response.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(jsonResponse); + + return result?.Success == true; + } + internal static void ProcessResponse(Mobile from, int enteredCode, bool cancelled) { if (!_activeChallenges.TryGetValue(from, out var challenge)) @@ -92,9 +142,10 @@ namespace Server.Engines.AntiBot return; } - if (enteredCode == challenge.Code) + if (enteredCode == challenge.FallbackCode) { - from.SendMessage("Anti-Bot: Verification successful!!!"); + from.SendMessage("Anti-Bot: Verification successful!"); + challenge.SuccessCallback?.Invoke(); } else { @@ -103,6 +154,48 @@ namespace Server.Engines.AntiBot } } + internal static async void ProcessTurnstileResponse(Mobile from, string token) + { + if (!_activeChallenges.TryGetValue(from, out var challenge)) + { + return; + } + + challenge.TimeoutTimer?.Stop(); + _activeChallenges.Remove(from); + + var isValid = await VerifyTurnstileToken(token); + + if (isValid) + { + from.SendMessage("Anti-Bot: Verification successful!"); + challenge.SuccessCallback?.Invoke(); + } + else + { + from.SendMessage("Anti-Bot: Verification failed. Disconnecting..."); + from.NetState?.Disconnect("Anti-Bot: Verification failed."); + } + } + + public static void ProcessTurnstileVerification(string challengeId, string token) + { + Mobile targetMobile = null; + foreach (var kvp in _activeChallenges) + { + if (kvp.Value.ChallengeId == challengeId) + { + targetMobile = kvp.Key; + break; + } + } + + if (targetMobile != null) + { + ProcessTurnstileResponse(targetMobile, token); + } + } + private static void CleanupExpiredChallenges() { var now = Core.Now; @@ -131,5 +224,10 @@ namespace Server.Engines.AntiBot _activeChallenges.Remove(from); } } + + private class TurnstileResponse + { + public bool Success { get; set; } + } } } \ No newline at end of file From 9bdc255ae5ccc085d5c97dc746e5005699ec2617 Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:31:45 -0800 Subject: [PATCH 7/7] adjusts antibot gump --- .../UOContent/Systems/AntiBotSystem/AntiBotGump.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs index f7d397c8d..559692a39 100644 --- a/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs +++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs @@ -37,17 +37,17 @@ namespace Server.Engines.AntiBot AddBackground(0, 0, 350, 220, 9270); AddHtml(20, 20, 310, 25, "
Anti-Bot Verification
", false, false); - AddHtml(20, 50, 310, 40, $"
Please enter the following number:
", false, false); + AddHtml(20, 50, 310, 40, "
Please enter the following number:
", false, false); AddHtml(20, 55, 310, 40, $"

{code}
", false, false); - AddHtml(20, 100, 310, 50, $"You have 2 minutes to input the correct number.
Incorrect numbers, cancellations, or timeouts will disconnect you from the server.", false, false); + AddHtml(20, 100, 310, 50, "You have 5 minutes to input the correct number.
Incorrect numbers, cancellations, or timeouts will disconnect you from the server.", false, false); AddBackground(20, 160, 200, 25, 3000); AddTextEntry(25, 165, 190, 20, 0, 0, ""); - + AddButton(230, 160, 4005, 4007, 1, GumpButtonType.Reply, 0); // Submit - AddButton(290, 160, 4017, 4019, 0, GumpButtonType.Reply, 0); // Cancel - AddHtml(230, 190, 40, 20, "
Submit
", false, false); + + AddButton(290, 160, 4017, 4019, 0, GumpButtonType.Reply, 0); // Cancel AddHtml(290, 190, 40, 20, "
Cancel
", false, false); } @@ -67,7 +67,7 @@ namespace Server.Engines.AntiBot var textEntry = info.GetTextEntry(0); if (textEntry != null && !int.TryParse(textEntry.Trim(), out enteredCode)) { - enteredCode = -1; // Invalid input - will cause disconnect + enteredCode = -1; } }