From ee2dcf816ef6960c94785eff36326cfe9d1b59b9 Mon Sep 17 00:00:00 2001 From: Aleos Date: Tue, 13 Dec 2022 15:44:42 -0500 Subject: [PATCH] New macro detection features (#7359) * Adds battle config macro_detection_punishment to ban or jail characters. * Adds battle config macro_detection_punishment_time to adjust the duration of ban/jail for players who fail the captcha challenges. * Adds script command macro_detector which invokes the captcha challenge. * General cleanups to the jail functions to remove duplicate code. * General cleanups to the macro punishment calls to remove duplicate code. * Ending SC_JAILED will now properly remove the jail status rather than trying to reset the timer to 1 second resolving any possibility of players getting stuck. Thanks to @Lemongrass3110! --- conf/battle/client.conf | 11 +++++ doc/script_commands.txt | 22 +++++++++ src/map/atcommand.cpp | 38 +++------------ src/map/battle.cpp | 2 + src/map/battle.hpp | 2 + src/map/clif.cpp | 2 +- src/map/pc.cpp | 102 +++++++++++++++++++++++++++++++++------- src/map/pc.hpp | 5 +- src/map/script.cpp | 21 +++++++++ src/map/status.cpp | 4 -- 10 files changed, 154 insertions(+), 55 deletions(-) diff --git a/conf/battle/client.conf b/conf/battle/client.conf index 5381b0489e..d07ea8b65b 100644 --- a/conf/battle/client.conf +++ b/conf/battle/client.conf @@ -158,3 +158,14 @@ macro_detection_retry: 3 // Amount of time in milliseconds before the macro detection will fail and the user will be banned. // Official: 60000 macro_detection_timeout: 60000 + +// Macro Detector punishment type +// 0 - Ban +// 1 - Jail +// Official: 0 +macro_detection_punishment: 0 + +// Macro Detector punishment duration +// Amount of time in minutes that the punishment type is active for. Use 0 for infinite. +// Official: 0 +macro_detection_punishment_time: 0 diff --git a/doc/script_commands.txt b/doc/script_commands.txt index 82e4a74b16..a6d48a2c0b 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -6579,6 +6579,28 @@ Examples: --------------------------------------- +macro_detector({}); +macro_detector({""}); + +This command will display the captcha UI challenge onto the invoking character or the given /. + +Example: + // Use 'getareaunits' to gather an area of players to test. + // Build an int array of the account IDs. + .@num = getareaunits(BL_PC, "prontera", 150, 150, 160, 160, .@array[0]); + + mes "The number of Players in Prontera in between 150x150 and 160x160 is " + .@num + " ."; + mes "Players to challenge:"; + freeloop(1); // If the list is too big + for(.@i = 0; .@i < getarraysize(.@array); .@i++) { + mes (.@i + 1) + " " + convertpcinfo(.@array[.@i], CPC_NAME); + macro_detector .@array[.@i]; + } + freeloop(0); + end; + +--------------------------------------- + ================================== |5.- Mob / NPC -related commands.| ================================== diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp index 4675bb9ed1..e24bd4ef62 100644 --- a/src/map/atcommand.cpp +++ b/src/map/atcommand.cpp @@ -5218,8 +5218,7 @@ ACMD_FUNC(servertime) ACMD_FUNC(jail) { struct map_session_data *pl_sd; - int x, y; - unsigned short m_index; + nullpo_retr(-1, sd); memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); @@ -5244,21 +5243,7 @@ ACMD_FUNC(jail) return -1; } - switch(rnd() % 2) { //Jail Locations - case 0: - m_index = mapindex_name2id(MAP_JAIL); - x = 24; - y = 75; - break; - default: - m_index = mapindex_name2id(MAP_JAIL); - x = 49; - y = 75; - break; - } - - //Duration of INT_MAX to specify infinity. - sc_start4(NULL,&pl_sd->bl,SC_JAILED,100,INT_MAX,m_index,x,y,1000); + pc_jail(*pl_sd); clif_displaymessage(pl_sd->fd, msg_txt(sd,117)); // GM has send you in jails. clif_displaymessage(fd, msg_txt(sd,118)); // Player warped in jails. return 0; @@ -5296,7 +5281,7 @@ ACMD_FUNC(unjail) } //Reset jail time to 1 sec. - sc_start(NULL,&pl_sd->bl,SC_JAILED,100,1,1000); + pc_jail(*pl_sd, 0); clif_displaymessage(pl_sd->fd, msg_txt(sd,120)); // A GM has discharged you from jail. clif_displaymessage(fd, msg_txt(sd,121)); // Player unjailed. return 0; @@ -5305,8 +5290,8 @@ ACMD_FUNC(unjail) ACMD_FUNC(jailfor) { struct map_session_data *pl_sd = NULL; char * modif_p; - int jailtime = 0,x,y; - short m_index = 0; + int jailtime = 0; + nullpo_retr(-1, sd); memset(atcmd_output, '\0', sizeof(atcmd_output)); @@ -5363,19 +5348,8 @@ ACMD_FUNC(jailfor) { return -1; } - // Jail locations, add more as you wish. - switch(rnd()%2) { - case 1: // Jail #1 - m_index = mapindex_name2id(MAP_JAIL); - x = 49; y = 75; - break; - default: // Default Jail - m_index = mapindex_name2id(MAP_JAIL); - x = 24; y = 75; - break; - } + pc_jail(*pl_sd, jailtime); - sc_start4(NULL,&pl_sd->bl,SC_JAILED,100,jailtime,m_index,x,y,jailtime?60000:1000); //jailtime = 0: Time was reset to 0. Wait 1 second to warp player out (since it's done in status_change_timer). return 0; } diff --git a/src/map/battle.cpp b/src/map/battle.cpp index 041a981ef4..fa54cfa151 100644 --- a/src/map/battle.cpp +++ b/src/map/battle.cpp @@ -10271,6 +10271,8 @@ static const struct _battle_data { { "break_mob_equip", &battle_config.break_mob_equip, 0, 0, 1, }, { "macro_detection_retry", &battle_config.macro_detection_retry, 3, 1, INT_MAX, }, { "macro_detection_timeout", &battle_config.macro_detection_timeout, 60000, 0, INT_MAX, }, + { "macro_detection_punishment", &battle_config.macro_detection_punishment, 0, 0, 1, }, + { "macro_detection_punishment_time", &battle_config.macro_detection_punishment_time, 0, 0, INT_MAX, }, { "feature.dynamicnpc_timeout", &battle_config.feature_dynamicnpc_timeout, 1000, 60000, INT_MAX, }, { "feature.dynamicnpc_rangex", &battle_config.feature_dynamicnpc_rangex, 2, 0, INT_MAX, }, diff --git a/src/map/battle.hpp b/src/map/battle.hpp index d416c622be..0ead94920d 100644 --- a/src/map/battle.hpp +++ b/src/map/battle.hpp @@ -714,6 +714,8 @@ struct Battle_Config int break_mob_equip; int macro_detection_retry; int macro_detection_timeout; + int macro_detection_punishment; + int macro_detection_punishment_time; int feature_dynamicnpc_timeout; int feature_dynamicnpc_rangex; diff --git a/src/map/clif.cpp b/src/map/clif.cpp index ff51b80536..ade4ba1762 100644 --- a/src/map/clif.cpp +++ b/src/map/clif.cpp @@ -24776,7 +24776,7 @@ void clif_parse_macro_reporter_ack(int fd, map_session_data *sd) { return; } - pc_macro_reporter_process(*sd, *tsd); + pc_macro_reporter_process(*tsd, sd->status.account_id); clif_macro_reporter_status(*sd, MCR_MONITORING); #endif } diff --git a/src/map/pc.cpp b/src/map/pc.cpp index 2cd6815274..96ebd0a297 100755 --- a/src/map/pc.cpp +++ b/src/map/pc.cpp @@ -15235,6 +15235,76 @@ void pc_attendance_claim_reward( struct map_session_data* sd ){ clif_attendence_response( sd, attendance_counter ); } +/** + * Send a player to jail and determine the location to send in jail. + * @param sd: Player data + * @param duration: Duration in minutes (default INT_MAX = infinite) + */ +void pc_jail(map_session_data &sd, int32 duration) { + uint16 m_index = mapindex_name2id(MAP_JAIL); + int16 x, y; + + switch (rnd() % 2) { // Jail Locations + case 0: // Jail #1 + x = 49; + y = 75; + break; + default: // Default Jail + x = 24; + y = 75; + break; + } + + duration = i32max(0, duration); // Can't be less than 0 seconds. + + // If duration > 0 then triggered via jailfor which checks every minute. + // If duration == INT_MAX then triggered via jail for infinite duration. + // If duration == 0 then triggered via unjail and end status. + if (duration > 0) + sc_start4(nullptr, &sd.bl, SC_JAILED, 100, duration, m_index, x, y, 60000); + else + status_change_end(&sd.bl, SC_JAILED); +} + +/** + * Determine the punishment type when failing macro checks. + * @param sd: Player data + * @param stype: Macro detection status type (for banning) + */ +static void pc_macro_punishment(map_session_data &sd, e_macro_detect_status stype) { + int32 duration = 0; + + // Determine if there's a unique duration + if (battle_config.macro_detection_punishment_time > 0) { + char time[13]; + + safesnprintf(time, 13, "+%dnm", battle_config.macro_detection_punishment_time); + duration = static_cast(solve_time(time)); + } + + // Delete the timer + if (sd.macro_detect.timer != INVALID_TIMER) + delete_timer(sd.macro_detect.timer, pc_macro_detector_timeout); + + // Clear the macro detect data + sd.macro_detect = {}; + sd.macro_detect.timer = INVALID_TIMER; + + // Unblock all actions for the player + sd.state.block_action &= ~PCBLOCK_ALL; + sd.state.block_action &= ~PCBLOCK_IMMUNE; + + if (battle_config.macro_detection_punishment == 0) { // Ban + clif_macro_detector_status(sd, stype); + chrif_req_login_operation(sd.macro_detect.reporter_aid, sd.status.name, (duration == 0 ? CHRIF_OP_LOGIN_BLOCK : CHRIF_OP_LOGIN_BAN), duration, 0, 0); + } else { // Jail + // Send success to close the window without closing the client + clif_macro_detector_status(sd, MCD_GOOD); + + pc_jail(sd, (duration == 0 ? INT_MAX : duration / 60)); + } +} + /** * Save a captcha image to memory via /macro_register. * @param sd: Player data @@ -15320,9 +15390,8 @@ TIMER_FUNC(pc_macro_detector_timeout) { sd->macro_detect.retry -= 1; if (sd->macro_detect.retry == 0) { - // All attempts have been exhausted block the user - clif_macro_detector_status(*sd, MCD_TIMEOUT); - chrif_req_login_operation(sd->macro_detect.reporter_aid, sd->status.name, CHRIF_OP_LOGIN_BLOCK, 0, 0, 0); + // All attempts have been exhausted, punish the user + pc_macro_punishment(*sd, MCD_TIMEOUT); } else { // Update the client clif_macro_detector_request_show(*sd); @@ -15373,10 +15442,9 @@ void pc_macro_detector_process_answer(map_session_data &sd, char captcha_answer[ // Deduct an answering attempt sd.macro_detect.retry -= 1; - // All attempts have been exhausted block the user + // All attempts have been exhausted, punish the user if (sd.macro_detect.retry <= 0) { - clif_macro_detector_status(sd, MCD_INCORRECT); - chrif_req_login_operation(sd.macro_detect.reporter_aid, sd.status.name, CHRIF_OP_LOGIN_BLOCK, 0, 0, 0); + pc_macro_punishment(sd, MCD_INCORRECT); return; } @@ -15399,9 +15467,9 @@ void pc_macro_detector_disconnect(map_session_data &sd) { sd.macro_detect.timer = INVALID_TIMER; } - // If the player disconnects before clearing the challenge the account is banned. + // If the player disconnects before clearing the challenge the player is punished. if (sd.macro_detect.retry != 0) - chrif_req_login_operation(sd.macro_detect.reporter_aid, sd.status.name, CHRIF_OP_LOGIN_BLOCK, 0, 0, 0); + pc_macro_punishment(sd, MCD_TIMEOUT); } /** @@ -15438,10 +15506,10 @@ void pc_macro_reporter_area_select(map_session_data &sd, const int16 x, const in /** * Send out captcha check to player. - * @param ssd: Source player data - * @param tsd: Target player data + * @param sd: Target player data + * @param reporter_account_id: Account ID of reporter */ -void pc_macro_reporter_process(map_session_data &ssd, map_session_data &tsd) { +void pc_macro_reporter_process(map_session_data &sd, int32 reporter_account_id) { if (captcha_db.empty()) return; @@ -15449,18 +15517,18 @@ void pc_macro_reporter_process(map_session_data &ssd, map_session_data &tsd) { const std::shared_ptr cd = captcha_db.random(); // Set macro detection data. - tsd.macro_detect.cd = cd; - tsd.macro_detect.reporter_aid = ssd.status.account_id; - tsd.macro_detect.retry = battle_config.macro_detection_retry; + sd.macro_detect.cd = cd; + sd.macro_detect.reporter_aid = reporter_account_id; + sd.macro_detect.retry = battle_config.macro_detection_retry; // Block all actions for the target player. - tsd.state.block_action |= (PCBLOCK_ALL | PCBLOCK_IMMUNE); + sd.state.block_action |= (PCBLOCK_ALL | PCBLOCK_IMMUNE); // Open macro detect client side. - clif_macro_detector_request(tsd); + clif_macro_detector_request(sd); // Start the timeout timer. - tsd.macro_detect.timer = add_timer(gettick() + battle_config.macro_detection_timeout, pc_macro_detector_timeout, tsd.bl.id, 0); + sd.macro_detect.timer = add_timer(gettick() + battle_config.macro_detection_timeout, pc_macro_detector_timeout, sd.bl.id, 0); } /** diff --git a/src/map/pc.hpp b/src/map/pc.hpp index b497a3c10c..23d69d7de1 100644 --- a/src/map/pc.hpp +++ b/src/map/pc.hpp @@ -1722,17 +1722,20 @@ bool pc_attendance_enabled(); int32 pc_attendance_counter( struct map_session_data* sd ); void pc_attendance_claim_reward( struct map_session_data* sd ); +void pc_jail(map_session_data &sd, int32 duration = INT_MAX); + // Captcha Register void pc_macro_captcha_register(map_session_data &sd, uint16 image_size, char captcha_answer[CAPTCHA_ANSWER_SIZE]); void pc_macro_captcha_register_upload(map_session_data & sd, uint16 upload_size, char *upload_data); // Macro Detector +TIMER_FUNC(pc_macro_detector_timeout); void pc_macro_detector_process_answer(map_session_data &sd, char captcha_answer[CAPTCHA_ANSWER_SIZE]); void pc_macro_detector_disconnect(map_session_data &sd); // Macro Reporter void pc_macro_reporter_area_select(map_session_data &sd, const int16 x, const int16 y, const int8 radius); -void pc_macro_reporter_process(map_session_data &ssd, map_session_data &tsd); +void pc_macro_reporter_process(map_session_data &sd, int32 reporter_account_id = -1); #ifdef MAP_GENERATOR void pc_reputation_generate(); diff --git a/src/map/script.cpp b/src/map/script.cpp index 323310eb1e..4fea520062 100644 --- a/src/map/script.cpp +++ b/src/map/script.cpp @@ -26829,6 +26829,25 @@ BUILDIN_FUNC(isdead) { return SCRIPT_CMD_SUCCESS; } +BUILDIN_FUNC(macro_detector) { + map_session_data *sd; + + if (script_hasdata(st, 2) && script_isstring(st, 2)) { // Character Name + if (!script_nick2sd(2, sd)) { + return SCRIPT_CMD_FAILURE; + } + } else { // Account ID + if (!script_accid2sd(2, sd)) { + return SCRIPT_CMD_FAILURE; + } + } + + // Reporter Account ID as -1 for server. + pc_macro_reporter_process(*sd); + + return SCRIPT_CMD_SUCCESS; +} + #include "../custom/script.inc" // declarations that were supposed to be exported from npc_chat.cpp @@ -27582,6 +27601,8 @@ struct script_function buildin_func[] = { BUILDIN_DEF(getfame, "?"), BUILDIN_DEF(getfamerank, "?"), BUILDIN_DEF(isdead, "?"), + BUILDIN_DEF(macro_detector, "?"), + #include "../custom/script_def.inc" {NULL,NULL,NULL}, diff --git a/src/map/status.cpp b/src/map/status.cpp index 2c8338efc7..78feb15c4d 100644 --- a/src/map/status.cpp +++ b/src/map/status.cpp @@ -11257,7 +11257,6 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty break; case SC_JAILED: // Val1 is duration in minutes. Use INT_MAX to specify 'unlimited' time. - tick = val1>0?1000:250; if (sd) { if (sd->mapindex != val2) { int pos = (bl->x&0xFFFF)|(bl->y<<16), // Current Coordinates @@ -13275,9 +13274,6 @@ int status_change_end(struct block_list* bl, enum sc_type type, int tid) } break; case SC_JAILED: - if(tid == INVALID_TIMER) - break; - // Natural expiration. if(sd && sd->mapindex == sce->val2) pc_setpos(sd,(unsigned short)sce->val3,sce->val4&0xFFFF, sce->val4>>16, CLR_TELEPORT); break; // Guess hes not in jail :P