Adds support for macro detection (#7315)
* Adds the official client macro detection system. * Includes the ability to load imagery at server boot. * See doc/captcha_db.txt for more information! Thanks to @Asheraf and @Lemongrass3110! Co-authored-by: Lemongrass3110 <lemongrass@kstp.at>
This commit is contained in:
parent
750c7d72de
commit
d7bf5ebb58
@ -148,3 +148,13 @@ show_skill_scale: yes
|
||||
// Note: Enabling this is known to cause problems on clients that make use of REST API calls.
|
||||
// Official: no
|
||||
drop_connection_on_quit: no
|
||||
|
||||
// Macro Detector retries
|
||||
// Number of times someone can fail the macro detection before being banned.
|
||||
// Official: 3 (minimum: 1)
|
||||
macro_detection_retry: 3
|
||||
|
||||
// Macro Detector timeout
|
||||
// Amount of time in milliseconds before the macro detection will fail and the user will be banned.
|
||||
// Official: 60000
|
||||
macro_detection_timeout: 60000
|
||||
|
@ -216,6 +216,7 @@ Body:
|
||||
hack_info: true
|
||||
any_warp: true
|
||||
view_hpmeter: true
|
||||
macro_detect: true
|
||||
- Id: 99
|
||||
Name: Admin
|
||||
Level: 99
|
||||
@ -238,6 +239,7 @@ Body:
|
||||
item_unconditional: false
|
||||
bypass_stat_onclone: true
|
||||
bypass_max_stat: true
|
||||
macro_register: true
|
||||
#all_permission: true
|
||||
|
||||
Footer:
|
||||
|
37
db/captcha_db.yml
Normal file
37
db/captcha_db.yml
Normal file
@ -0,0 +1,37 @@
|
||||
# This file is a part of rAthena.
|
||||
# Copyright(C) 2022 rAthena Development Team
|
||||
# https://rathena.org - https://github.com/rathena
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
###########################################################################
|
||||
# Captcha Database Table
|
||||
###########################################################################
|
||||
#
|
||||
# Captcha Database Settings
|
||||
#
|
||||
###########################################################################
|
||||
# - Id Index value.
|
||||
# Filename Name of the BMP image file (with location).
|
||||
# Answer Correct answer for the captcha (case-sensitive).
|
||||
# Bonus Bonus Script ran on success. (Default: Level 10 Blessing and Increase Agility)
|
||||
###########################################################################
|
||||
|
||||
Header:
|
||||
Type: CAPTCHA_DB
|
||||
Version: 1
|
||||
|
||||
Footer:
|
||||
Imports:
|
||||
- Path: db/import/captcha_db.yml
|
33
db/import-tmpl/captcha_db.yml
Normal file
33
db/import-tmpl/captcha_db.yml
Normal file
@ -0,0 +1,33 @@
|
||||
# This file is a part of rAthena.
|
||||
# Copyright(C) 2022 rAthena Development Team
|
||||
# https://rathena.org - https://github.com/rathena
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
###########################################################################
|
||||
# Captcha Database Table
|
||||
###########################################################################
|
||||
#
|
||||
# Captcha Database Settings
|
||||
#
|
||||
###########################################################################
|
||||
# - Id Index value.
|
||||
# Filename Name of the BMP image file (with location).
|
||||
# Answer Correct answer for the captcha (case-sensitive).
|
||||
# Bonus Bonus Script ran on success. (Default: Level 10 Blessing and Increase Agility)
|
||||
###########################################################################
|
||||
|
||||
Header:
|
||||
Type: CAPTCHA_DB
|
||||
Version: 1
|
44
doc/captcha_db.txt
Normal file
44
doc/captcha_db.txt
Normal file
@ -0,0 +1,44 @@
|
||||
//===== rAthena Documentation ================================
|
||||
//= Captcha Database Structure
|
||||
//===== By: ==================================================
|
||||
//= rAthena Dev Team
|
||||
//===== Last Updated: ========================================
|
||||
//= 20220920
|
||||
//===== Description: =========================================
|
||||
//= Explanation of the captcha_db.yml file and structure.
|
||||
//============================================================
|
||||
|
||||
---------------------------------------
|
||||
|
||||
Id: Unique ID.
|
||||
|
||||
---------------------------------------
|
||||
|
||||
Filename: Name of the BMP image file (with location).
|
||||
The path of the file can be different for each captcha image, but it's best practice to keep them in the same directory.
|
||||
|
||||
Example:
|
||||
Filename: db/import/captcha/rathena.bmp
|
||||
|
||||
---------------------------------------
|
||||
|
||||
Answer: Correct answer for the captcha (case-sensitive).
|
||||
|
||||
---------------------------------------
|
||||
|
||||
Bonus: NPC script that is ran when a captcha is successfully answered. Accepts all forms of script constants, variables, as well as the
|
||||
unique player variable @captcha_retries. This variable can be used within the Bonus script to get the remaining retries a player
|
||||
has. Coupled with the script command 'getbattleflag()' this could be used to assign different bonuses based on success rate.
|
||||
|
||||
Example:
|
||||
# Give level 10 Blessing for 20 minutes with no failures, else give for 30 seconds.
|
||||
Bonus: >
|
||||
if (@captcha_retries == getbattleflag("macro_detection_retry")) {
|
||||
# Player solved it on first try
|
||||
specialeffect2 EF_BLESSING;
|
||||
sc_start SC_BLESSING,1200000,10;
|
||||
} else {
|
||||
# Player needed more than one try
|
||||
specialeffect2 EF_BLESSING;
|
||||
sc_start SC_BLESSING,30000,10;
|
||||
}
|
@ -212,3 +212,15 @@ Allow to bypass the maximum stat parameter (at conf/player.conf) to
|
||||
maximum value 32,767.
|
||||
|
||||
---------------------------------------
|
||||
|
||||
*macro_detect
|
||||
|
||||
Allows player to use the client command /macro_detector.
|
||||
|
||||
---------------------------------------
|
||||
|
||||
*macro_register
|
||||
|
||||
Allows player to use the client commands /maco_register (used to add new captcha) and /macro_preview (used to preview captcha by ID).
|
||||
|
||||
---------------------------------------
|
||||
|
@ -3268,7 +3268,7 @@ ACMD_FUNC(recall) {
|
||||
|
||||
if ( pc_get_group_level(sd) < pc_get_group_level(pl_sd) )
|
||||
{
|
||||
clif_displaymessage(fd, msg_txt(sd,81)); // Your GM level doesn't authorize you to preform this action on the specified player.
|
||||
clif_displaymessage(fd, msg_txt(sd,81)); // Your GM level doesn't authorize you to perform this action on the specified player.
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -10270,6 +10270,8 @@ static const struct _battle_data {
|
||||
{ "feature.barter", &battle_config.feature_barter, 1, 0, 1, },
|
||||
{ "feature.barter_extended", &battle_config.feature_barter_extended, 1, 0, 1, },
|
||||
{ "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, },
|
||||
|
||||
#include "../custom/battle_config_init.inc"
|
||||
};
|
||||
|
@ -711,6 +711,8 @@ struct Battle_Config
|
||||
int feature_barter;
|
||||
int feature_barter_extended;
|
||||
int break_mob_equip;
|
||||
int macro_detection_retry;
|
||||
int macro_detection_timeout;
|
||||
|
||||
#include "../custom/battle_config_struct.inc"
|
||||
};
|
||||
|
267
src/map/clif.cpp
267
src/map/clif.cpp
@ -21552,6 +21552,14 @@ void clif_parse_open_ui( int fd, struct map_session_data* sd ){
|
||||
clif_msg_color( sd, MSG_ATTENDANCE_DISABLED, color_table[COLOR_RED] );
|
||||
}
|
||||
break;
|
||||
#if PACKETVER >= 20160316
|
||||
case IN_UI_MACRO_REGISTER:
|
||||
clif_ui_open(*sd, OUT_UI_CAPTCHA, 0);
|
||||
break;
|
||||
case IN_UI_MACRO_DETECTOR:
|
||||
clif_ui_open(*sd, OUT_UI_MACRO, 0);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -24628,6 +24636,265 @@ void clif_broadcast_refine_result(map_session_data& sd, t_itemid itemId, int8 le
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_parse_captcha_register(int fd, map_session_data *sd) {
|
||||
#if PACKETVER >= 20160316
|
||||
nullpo_retv(sd);
|
||||
|
||||
if (!pc_has_permission(sd, PC_PERM_MACRO_REGISTER)) {
|
||||
clif_displaymessage(sd->fd, msg_txt(sd, 246)); // Your GM level doesn't authorize you to perform this action.
|
||||
return;
|
||||
}
|
||||
|
||||
PACKET_CZ_REQ_UPLOAD_MACRO_DETECTOR *p = (PACKET_CZ_REQ_UPLOAD_MACRO_DETECTOR *)RFIFOP(fd, 0);
|
||||
|
||||
pc_macro_captcha_register(*sd, p->imageSize, p->answer);
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_captcha_upload_request(map_session_data &sd) {
|
||||
#if PACKETVER >= 20160330
|
||||
PACKET_ZC_ACK_UPLOAD_MACRO_DETECTOR p = {};
|
||||
|
||||
p.PacketType = HEADER_ZC_ACK_UPLOAD_MACRO_DETECTOR;
|
||||
safestrncpy(p.captchaKey, "", sizeof(p.captchaKey));
|
||||
if (sd.captcha_upload.cd != nullptr) {
|
||||
p.captchaFlag = 0;
|
||||
} else {
|
||||
p.captchaFlag = 1;
|
||||
}
|
||||
|
||||
clif_send(&p, sizeof(p), &sd.bl, SELF);
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_parse_captcha_upload(int fd, map_session_data *sd) {
|
||||
#if PACKETVER >= 20160316
|
||||
nullpo_retv(sd);
|
||||
|
||||
if (!pc_has_permission(sd, PC_PERM_MACRO_REGISTER)) {
|
||||
clif_displaymessage(sd->fd, msg_txt(sd, 246)); // Your GM level doesn't authorize you to perform this action.
|
||||
return;
|
||||
}
|
||||
|
||||
PACKET_CZ_UPLOAD_MACRO_DETECTOR_CAPTCHA *p = (PACKET_CZ_UPLOAD_MACRO_DETECTOR_CAPTCHA *)RFIFOP(fd, 0);
|
||||
int16 upload_size = p->PacketLength - sizeof(PACKET_CZ_UPLOAD_MACRO_DETECTOR_CAPTCHA);
|
||||
|
||||
if (upload_size < 1 || upload_size > MAX_CAPTCHA_CHUNK_SIZE)
|
||||
return;
|
||||
|
||||
if (sd->captcha_upload.upload_size + upload_size > sd->captcha_upload.cd->image_size)
|
||||
return;
|
||||
|
||||
pc_macro_captcha_register_upload(*sd, upload_size, p->imageData);
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_captcha_upload_end(map_session_data &sd) {
|
||||
#if PACKETVER >= 20160330
|
||||
PACKET_ZC_COMPLETE_UPLOAD_MACRO_DETECTOR_CAPTCHA p = {};
|
||||
|
||||
p.PacketType = HEADER_ZC_COMPLETE_UPLOAD_MACRO_DETECTOR_CAPTCHA;
|
||||
|
||||
clif_send(&p, sizeof(p), &sd.bl, SELF);
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_parse_captcha_preview_request(int fd, map_session_data *sd) {
|
||||
#if PACKETVER >= 20160323
|
||||
nullpo_retv(sd);
|
||||
|
||||
if (!(pc_has_permission(sd, PC_PERM_MACRO_REGISTER) && pc_has_permission(sd, PC_PERM_MACRO_DETECT))) {
|
||||
clif_displaymessage(sd->fd, msg_txt(sd, 246)); // Your GM level doesn't authorize you to perform this action.
|
||||
return;
|
||||
}
|
||||
|
||||
PACKET_CZ_REQ_PREVIEW_MACRO_DETECTOR *p = (PACKET_CZ_REQ_PREVIEW_MACRO_DETECTOR *)RFIFOP(fd, 0);
|
||||
|
||||
clif_captcha_preview_response(*sd, captcha_db.find(p->captchaID));
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_captcha_preview_response(map_session_data &sd, std::shared_ptr<s_captcha_data> cd) {
|
||||
#if PACKETVER >= 20160330
|
||||
PACKET_ZC_ACK_PREVIEW_MACRO_DETECTOR p = {};
|
||||
|
||||
p.PacketType = HEADER_ZC_ACK_PREVIEW_MACRO_DETECTOR;
|
||||
safestrncpy(p.captchaKey, "", sizeof(p.captchaKey));
|
||||
if (cd == nullptr) {
|
||||
p.captchaFlag = 1;
|
||||
p.imageSize = 0;
|
||||
} else {
|
||||
p.captchaFlag = 0;
|
||||
p.imageSize = cd->image_size;
|
||||
}
|
||||
|
||||
clif_send(&p, sizeof(p), &sd.bl, SELF);
|
||||
|
||||
if (cd != nullptr) {
|
||||
for (uint16 offset = 0; offset < cd->image_size;) {
|
||||
uint16 chunk_size = min(cd->image_size - offset, MAX_CAPTCHA_CHUNK_SIZE);
|
||||
PACKET_ZC_PREVIEW_MACRO_DETECTOR_CAPTCHA *p2 = (PACKET_ZC_PREVIEW_MACRO_DETECTOR_CAPTCHA *)packet_buffer;
|
||||
|
||||
p2->PacketType = HEADER_ZC_PREVIEW_MACRO_DETECTOR_CAPTCHA;
|
||||
p2->PacketLength = (int16)(sizeof(PACKET_ZC_PREVIEW_MACRO_DETECTOR_CAPTCHA) + chunk_size);
|
||||
safestrncpy(p2->captchaKey, p.captchaKey, sizeof(p2->captchaKey));
|
||||
memcpy(p2->imageData, &cd->image_data[offset], chunk_size);
|
||||
|
||||
clif_send(p2, p2->PacketLength, &sd.bl, SELF);
|
||||
|
||||
offset += chunk_size;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_macro_detector_request(map_session_data &sd) {
|
||||
#if PACKETVER >= 20160330
|
||||
std::shared_ptr<s_captcha_data> cd = sd.macro_detect.cd;
|
||||
|
||||
if (cd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send preview initialization request to the client.
|
||||
PACKET_ZC_APPLY_MACRO_DETECTOR p = {};
|
||||
|
||||
p.PacketType = HEADER_ZC_APPLY_MACRO_DETECTOR;
|
||||
p.imageSize = cd->image_size;
|
||||
safestrncpy(p.captchaKey, "", sizeof(p.captchaKey));
|
||||
|
||||
clif_send(&p, sizeof(p), &sd.bl, SELF);
|
||||
|
||||
for (uint16 offset = 0; offset < cd->image_size;) {
|
||||
uint16 chunk_size = min(cd->image_size - offset, MAX_CAPTCHA_CHUNK_SIZE);
|
||||
PACKET_ZC_APPLY_MACRO_DETECTOR_CAPTCHA *p2 = (PACKET_ZC_APPLY_MACRO_DETECTOR_CAPTCHA *)packet_buffer;
|
||||
|
||||
p2->PacketType = HEADER_ZC_APPLY_MACRO_DETECTOR_CAPTCHA;
|
||||
p2->PacketLength = (int16)(sizeof(PACKET_ZC_APPLY_MACRO_DETECTOR_CAPTCHA) + chunk_size);
|
||||
safestrncpy(p2->captchaKey, p.captchaKey, sizeof(p2->captchaKey));
|
||||
memcpy(p2->imageData, &cd->image_data[offset], chunk_size);
|
||||
|
||||
clif_send(p2, p2->PacketLength, &sd.bl, SELF);
|
||||
|
||||
offset += chunk_size;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_macro_detector_request_show(map_session_data &sd) {
|
||||
#if PACKETVER >= 20160330
|
||||
PACKET_ZC_REQ_ANSWER_MACRO_DETECTOR p = {};
|
||||
|
||||
p.PacketType = HEADER_ZC_REQ_ANSWER_MACRO_DETECTOR;
|
||||
p.retryCount = sd.macro_detect.retry;
|
||||
p.timeout = battle_config.macro_detection_timeout;
|
||||
|
||||
clif_send(&p, sizeof(p), &sd.bl, SELF);
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_parse_macro_detector_download_ack(int fd, map_session_data *sd) {
|
||||
#if PACKETVER >= 20160316
|
||||
nullpo_retv(sd);
|
||||
|
||||
if (sd->macro_detect.retry != 0) {
|
||||
//PACKET_CZ_COMPLETE_APPLY_MACRO_DETECTOR_CAPTCHA *p = (PACKET_CZ_COMPLETE_APPLY_MACRO_DETECTOR_CAPTCHA *)RFIFOP(fd, 0);
|
||||
|
||||
clif_macro_detector_request_show(*sd);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_parse_macro_detector_answer(int fd, map_session_data *sd) {
|
||||
#if PACKETVER >= 20160316
|
||||
nullpo_retv(sd);
|
||||
|
||||
PACKET_CZ_ACK_ANSWER_MACRO_DETECTOR *p = (PACKET_CZ_ACK_ANSWER_MACRO_DETECTOR *)RFIFOP(fd, 0);
|
||||
|
||||
pc_macro_detector_process_answer(*sd, p->answer);
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_macro_detector_status(map_session_data &sd, e_macro_detect_status stype) {
|
||||
#if PACKETVER >= 20160330
|
||||
PACKET_ZC_CLOSE_MACRO_DETECTOR p = {};
|
||||
|
||||
p.PacketType = HEADER_ZC_CLOSE_MACRO_DETECTOR;
|
||||
p.status = stype;
|
||||
|
||||
clif_send(&p, sizeof(p), &sd.bl, SELF);
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_parse_macro_reporter_select(int fd, map_session_data *sd) {
|
||||
#if PACKETVER >= 20160330
|
||||
nullpo_retv(sd);
|
||||
|
||||
if (!pc_has_permission(sd, PC_PERM_MACRO_DETECT)) {
|
||||
clif_displaymessage(sd->fd, msg_txt(sd, 246)); // Your GM level doesn't authorize you to perform this action.
|
||||
return;
|
||||
}
|
||||
|
||||
PACKET_CZ_REQ_PLAYER_AID_IN_RANGE *p = (PACKET_CZ_REQ_PLAYER_AID_IN_RANGE *)RFIFOP(fd, 0);
|
||||
|
||||
pc_macro_reporter_area_select(*sd, p->xPos, p->yPos, p->RadiusRange);
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_macro_reporter_select(map_session_data &sd, const std::vector<uint32> &aid_list) {
|
||||
#if PACKETVER >= 20160330
|
||||
PACKET_ZC_ACK_PLAYER_AID_IN_RANGE *p = (PACKET_ZC_ACK_PLAYER_AID_IN_RANGE *)packet_buffer;
|
||||
|
||||
p->PacketType = HEADER_ZC_ACK_PLAYER_AID_IN_RANGE;
|
||||
p->PacketLength = static_cast<int16>(sizeof(PACKET_ZC_ACK_PLAYER_AID_IN_RANGE) + sizeof(uint32) * aid_list.size());
|
||||
for (size_t i = 0; i < aid_list.size(); i++)
|
||||
p->AID[i] = aid_list[static_cast<int32>(i)];
|
||||
|
||||
clif_send(p, p->PacketLength, &sd.bl, SELF);
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_parse_macro_reporter_ack(int fd, map_session_data *sd) {
|
||||
#if PACKETVER >= 20160316
|
||||
nullpo_retv(sd);
|
||||
|
||||
if (!pc_has_permission(sd, PC_PERM_MACRO_DETECT)) {
|
||||
clif_displaymessage(sd->fd, msg_txt(sd, 246)); // Your GM level doesn't authorize you to perform this action.
|
||||
return;
|
||||
}
|
||||
|
||||
PACKET_CZ_REQ_APPLY_MACRO_DETECTOR *p = (PACKET_CZ_REQ_APPLY_MACRO_DETECTOR *)RFIFOP(fd, 0);
|
||||
map_session_data *tsd = map_id2sd(p->AID);
|
||||
|
||||
if (tsd == nullptr) {
|
||||
clif_displaymessage(fd, msg_txt(sd, 3)); // Character not found.
|
||||
return;
|
||||
}
|
||||
if (tsd->macro_detect.retry != 0) {
|
||||
clif_macro_reporter_status(*sd, MCR_INPROGRESS);
|
||||
return;
|
||||
}
|
||||
if (captcha_db.empty()) {
|
||||
clif_macro_reporter_status(*sd, MCR_NO_DATA);
|
||||
return;
|
||||
}
|
||||
|
||||
pc_macro_reporter_process(*sd, *tsd);
|
||||
clif_macro_reporter_status(*sd, MCR_MONITORING);
|
||||
#endif
|
||||
}
|
||||
|
||||
void clif_macro_reporter_status(map_session_data &sd, e_macro_report_status stype) {
|
||||
#if PACKETVER >= 20160330
|
||||
PACKET_ZC_ACK_APPLY_MACRO_DETECTOR p = {};
|
||||
|
||||
p.PacketType = HEADER_ZC_ACK_APPLY_MACRO_DETECTOR;
|
||||
p.status = stype;
|
||||
|
||||
clif_send(&p, sizeof(p), &sd.bl, SELF);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*==========================================
|
||||
* Main client packet processing function
|
||||
*------------------------------------------*/
|
||||
|
@ -47,6 +47,9 @@ enum e_bg_queue_apply_ack : uint16;
|
||||
enum e_instance_notify : uint8;
|
||||
struct s_laphine_synthesis;
|
||||
struct s_laphine_upgrade;
|
||||
struct s_captcha_data;
|
||||
enum e_macro_detect_status : uint8;
|
||||
enum e_macro_report_status : uint8;
|
||||
|
||||
enum e_PacketDBVersion { // packet DB
|
||||
MIN_PACKET_DB = 0x064,
|
||||
@ -1157,12 +1160,16 @@ void clif_achievement_reward_ack(int fd, unsigned char result, int ach_id);
|
||||
|
||||
/// Attendance System
|
||||
enum in_ui_type : int8 {
|
||||
IN_UI_MACRO_REGISTER = 2,
|
||||
IN_UI_MACRO_DETECTOR,
|
||||
IN_UI_ATTENDANCE = 5
|
||||
};
|
||||
|
||||
enum out_ui_type : int8 {
|
||||
OUT_UI_BANK = 0,
|
||||
OUT_UI_STYLIST,
|
||||
OUT_UI_CAPTCHA,
|
||||
OUT_UI_MACRO,
|
||||
OUT_UI_QUEST = 6,
|
||||
OUT_UI_ATTENDANCE,
|
||||
OUT_UI_ENCHANTGRADE,
|
||||
@ -1217,4 +1224,20 @@ void clif_enchantingshadow_spirit(unit_data &ud);
|
||||
|
||||
void clif_broadcast_refine_result(struct map_session_data& sd, t_itemid itemId, int8 level, bool success);
|
||||
|
||||
// Captcha Register
|
||||
void clif_captcha_upload_request(map_session_data &sd);
|
||||
void clif_captcha_upload_end(map_session_data &sd);
|
||||
|
||||
// Captcha Preview
|
||||
void clif_captcha_preview_response(map_session_data &sd, std::shared_ptr<s_captcha_data> cd);
|
||||
|
||||
// Macro Detector
|
||||
void clif_macro_detector_request(map_session_data &sd);
|
||||
void clif_macro_detector_request_show(map_session_data &sd);
|
||||
void clif_macro_detector_status(map_session_data &sd, e_macro_detect_status stype);
|
||||
|
||||
// Macro Reporter
|
||||
void clif_macro_reporter_select(map_session_data &sd, const std::vector<uint32> &aid_list);
|
||||
void clif_macro_reporter_status(map_session_data &sd, e_macro_report_status stype);
|
||||
|
||||
#endif /* CLIF_HPP */
|
||||
|
@ -2246,6 +2246,20 @@
|
||||
packet(0x0A51,34);
|
||||
#endif
|
||||
|
||||
#if PACKETVER >= 20160316
|
||||
parseable_packet(HEADER_CZ_REQ_UPLOAD_MACRO_DETECTOR, sizeof(PACKET_CZ_REQ_UPLOAD_MACRO_DETECTOR), clif_parse_captcha_register, 0);
|
||||
parseable_packet(HEADER_CZ_UPLOAD_MACRO_DETECTOR_CAPTCHA, -1, clif_parse_captcha_upload, 0);
|
||||
parseable_packet(HEADER_CZ_COMPLETE_APPLY_MACRO_DETECTOR_CAPTCHA, sizeof(PACKET_CZ_COMPLETE_APPLY_MACRO_DETECTOR_CAPTCHA), clif_parse_macro_detector_download_ack, 0);
|
||||
parseable_packet(HEADER_CZ_ACK_ANSWER_MACRO_DETECTOR, sizeof(PACKET_CZ_ACK_ANSWER_MACRO_DETECTOR), clif_parse_macro_detector_answer, 0);
|
||||
parseable_packet(HEADER_CZ_REQ_APPLY_MACRO_DETECTOR, sizeof(PACKET_CZ_REQ_APPLY_MACRO_DETECTOR), clif_parse_macro_reporter_ack, 0);
|
||||
#endif
|
||||
#if PACKETVER >= 20160323
|
||||
parseable_packet(HEADER_CZ_REQ_PREVIEW_MACRO_DETECTOR, sizeof(PACKET_CZ_REQ_PREVIEW_MACRO_DETECTOR), clif_parse_captcha_preview_request, 0);
|
||||
#endif
|
||||
#if PACKETVER >= 20160330
|
||||
parseable_packet(HEADER_CZ_REQ_PLAYER_AID_IN_RANGE, sizeof(PACKET_CZ_REQ_PLAYER_AID_IN_RANGE), clif_parse_macro_reporter_select, 0);
|
||||
#endif
|
||||
|
||||
// 2016-03-30aRagexe
|
||||
#if PACKETVER >= 20160330
|
||||
parseable_packet(0x0A6E,-1,clif_parse_Mail_send,2,4,28,52,60,62,64,68); // CZ_REQ_WRITE_MAIL2
|
||||
|
@ -320,6 +320,7 @@
|
||||
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\attendance.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\attendance.yml')" />
|
||||
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\attr_fix.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\attr_fix.yml')" />
|
||||
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\battleground_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\battleground_db.yml')" />
|
||||
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\captcha_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\captcha_db.yml')" />
|
||||
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\castle_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\castle_db.yml')" />
|
||||
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\const.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\const.yml')" />
|
||||
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\create_arrow_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\create_arrow_db.yml')" />
|
||||
|
@ -2172,6 +2172,7 @@ int map_quit(struct map_session_data *sd) {
|
||||
pc_makesavestatus(sd);
|
||||
pc_clean_skilltree(sd);
|
||||
pc_crimson_marker_clear(sd);
|
||||
pc_macro_detector_disconnect(*sd);
|
||||
chrif_save(sd, CSAVE_QUIT|CSAVE_INVENTORY|CSAVE_CART);
|
||||
unit_free_pc(sd);
|
||||
return 0;
|
||||
|
@ -5000,7 +5000,7 @@ DEFINE_PACKET_HEADER(ZC_MYGUILD_BASIC_INFO, 0x014c);
|
||||
struct PACKET_CZ_REQ_UPLOAD_MACRO_DETECTOR {
|
||||
int16 PacketType;
|
||||
char answer[16];
|
||||
int16 imageSize;
|
||||
uint16 imageSize;
|
||||
} __attribute__((packed));
|
||||
DEFINE_PACKET_HEADER(CZ_REQ_UPLOAD_MACRO_DETECTOR, 0x0a52);
|
||||
#endif
|
||||
@ -5050,7 +5050,7 @@ DEFINE_PACKET_HEADER(ZC_ACK_APPLY_MACRO_DETECTOR, 0x0a57);
|
||||
#if PACKETVER >= 20160330
|
||||
struct PACKET_ZC_APPLY_MACRO_DETECTOR {
|
||||
int16 PacketType;
|
||||
int16 imageSize;
|
||||
uint16 imageSize;
|
||||
char captchaKey[4];
|
||||
} __attribute__((packed));
|
||||
DEFINE_PACKET_HEADER(ZC_APPLY_MACRO_DETECTOR, 0x0a58);
|
||||
@ -5110,7 +5110,7 @@ DEFINE_PACKET_HEADER(CZ_REQ_PREVIEW_MACRO_DETECTOR, 0x0a69);
|
||||
struct PACKET_ZC_ACK_PREVIEW_MACRO_DETECTOR {
|
||||
int16 PacketType;
|
||||
int captchaFlag;
|
||||
int16 imageSize;
|
||||
uint16 imageSize;
|
||||
char captchaKey[4];
|
||||
} __attribute__((packed));
|
||||
DEFINE_PACKET_HEADER(ZC_ACK_PREVIEW_MACRO_DETECTOR, 0x0a6a);
|
||||
|
@ -418,7 +418,7 @@ int party_invite(struct map_session_data *sd,struct map_session_data *tsd)
|
||||
|
||||
// confirm whether the account has the ability to invite before checking the player
|
||||
if( !pc_has_permission(sd, PC_PERM_PARTY) || (tsd && !pc_has_permission(tsd, PC_PERM_PARTY)) ) {
|
||||
clif_displaymessage(sd->fd, msg_txt(sd,81)); // "Your GM level doesn't authorize you to preform this action on the specified player."
|
||||
clif_displaymessage(sd->fd, msg_txt(sd,81)); // "Your GM level doesn't authorize you to perform this action on the specified player."
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
353
src/map/pc.cpp
353
src/map/pc.cpp
@ -12,6 +12,7 @@
|
||||
#include "../common/core.hpp" // get_svn_revision()
|
||||
#include "../common/database.hpp"
|
||||
#include "../common/ers.hpp" // ers_destroy
|
||||
#include "../common/grfio.hpp"
|
||||
#include "../common/malloc.hpp"
|
||||
#include "../common/mmo.hpp" //NAME_LENGTH
|
||||
#include "../common/nullpo.hpp"
|
||||
@ -61,6 +62,9 @@ using namespace rathena;
|
||||
|
||||
JobDatabase job_db;
|
||||
|
||||
CaptchaDatabase captcha_db;
|
||||
const char *macro_allowed_answer_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||
|
||||
int pc_split_atoui(char* str, unsigned int* val, char sep, int max);
|
||||
static inline bool pc_attendance_rewarded_today( struct map_session_data* sd );
|
||||
|
||||
@ -1747,6 +1751,7 @@ bool pc_authok(struct map_session_data *sd, uint32 login_id2, time_t expiration_
|
||||
sd->autotrade_tid = INVALID_TIMER;
|
||||
sd->respawn_tid = INVALID_TIMER;
|
||||
sd->tid_queue_active = INVALID_TIMER;
|
||||
sd->macro_detect.timer = INVALID_TIMER;
|
||||
|
||||
sd->skill_keep_using.tid = INVALID_TIMER;
|
||||
sd->skill_keep_using.skill_id = 0;
|
||||
@ -14989,6 +14994,351 @@ void pc_attendance_claim_reward( struct map_session_data* sd ){
|
||||
clif_attendence_response( sd, attendance_counter );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a captcha image to memory via /macro_register.
|
||||
* @param sd: Player data
|
||||
* @param image_size: Captcha image size
|
||||
* @param captcha_answer: Answer to captcha
|
||||
*/
|
||||
void pc_macro_captcha_register(map_session_data &sd, uint16 image_size, char captcha_answer[CAPTCHA_ANSWER_SIZE]) {
|
||||
nullpo_retv(captcha_answer);
|
||||
|
||||
sd.captcha_upload.cd = nullptr;
|
||||
sd.captcha_upload.upload_size = 0;
|
||||
|
||||
if (strlen(captcha_answer) < 4 || image_size == 0 || image_size > CAPTCHA_BMP_SIZE) {
|
||||
clif_captcha_upload_request(sd); // Notify client of failure.
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<s_captcha_data> cd = std::make_shared<s_captcha_data>();
|
||||
sd.captcha_upload.cd = cd;
|
||||
|
||||
cd->image_size = image_size;
|
||||
safestrncpy(cd->captcha_answer, captcha_answer, sizeof(cd->captcha_answer));
|
||||
memset(cd->image_data, 0, sizeof(cd->image_data));
|
||||
|
||||
// Request the image data from the client.
|
||||
clif_captcha_upload_request(sd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save captcha image to server.
|
||||
* @param sd: Player data
|
||||
* @param captcha_key: Captcha ID
|
||||
* @param upload_size: Captcha size
|
||||
* @param upload_data: Image data
|
||||
*/
|
||||
void pc_macro_captcha_register_upload(map_session_data &sd, uint16 upload_size, char *upload_data) {
|
||||
nullpo_retv(upload_data);
|
||||
|
||||
memcpy(&sd.captcha_upload.cd->image_data[sd.captcha_upload.upload_size], upload_data, upload_size);
|
||||
sd.captcha_upload.upload_size += upload_size;
|
||||
|
||||
// Notify that the image finished uploading.
|
||||
if (sd.captcha_upload.upload_size == sd.captcha_upload.cd->image_size) {
|
||||
// Tell the client that the upload was finished
|
||||
clif_captcha_upload_end(sd);
|
||||
|
||||
// Look for a free key
|
||||
uint16 index;
|
||||
|
||||
for (index = 0; index < UINT16_MAX; index++) {
|
||||
if (!captcha_db.exists(index)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == UINT16_MAX) {
|
||||
// no free key found...
|
||||
sd.captcha_upload.cd = nullptr;
|
||||
sd.captcha_upload.upload_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
captcha_db.put(index, sd.captcha_upload.cd);
|
||||
sd.captcha_upload.cd = nullptr;
|
||||
sd.captcha_upload.upload_size = 0;
|
||||
|
||||
// TODO: write YAML and BMP file?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Timer attached to target player with attempts to confirm captcha.
|
||||
*/
|
||||
TIMER_FUNC(pc_macro_detector_timeout) {
|
||||
map_session_data *sd = map_id2sd(id);
|
||||
|
||||
nullpo_ret(sd);
|
||||
|
||||
// Remove the current timer
|
||||
sd->macro_detect.timer = INVALID_TIMER;
|
||||
|
||||
// Deduct an answering attempt
|
||||
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);
|
||||
} else {
|
||||
// Update the client
|
||||
clif_macro_detector_request_show(*sd);
|
||||
|
||||
// Start a new timer
|
||||
sd->macro_detect.timer = add_timer(gettick() + battle_config.macro_detection_timeout, pc_macro_detector_timeout, sd->bl.id, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check player's captcha answer.
|
||||
* @param sd: Player data
|
||||
* @param captcha_answer: Captcha answer entered by player
|
||||
*/
|
||||
void pc_macro_detector_process_answer(map_session_data &sd, char captcha_answer[CAPTCHA_ANSWER_SIZE]) {
|
||||
nullpo_retv(captcha_answer);
|
||||
|
||||
const std::shared_ptr<s_captcha_data> cd = sd.macro_detect.cd;
|
||||
|
||||
// Has no captcha request
|
||||
if (cd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Correct answer
|
||||
if (strcmp(captcha_answer, cd->captcha_answer) == 0) {
|
||||
// Delete the 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;
|
||||
|
||||
// Assign temporary macro variable to check failures
|
||||
pc_setreg(&sd, add_str("@captcha_retries"), battle_config.macro_detection_retry - sd.macro_detect.retry);
|
||||
|
||||
// Grant bonuses via script
|
||||
run_script(cd->bonus_script, 0, sd.bl.id, fake_nd->bl.id);
|
||||
|
||||
// Notify the client
|
||||
clif_macro_detector_status(sd, MCD_GOOD);
|
||||
} else {
|
||||
// Deduct an answering attempt
|
||||
sd.macro_detect.retry -= 1;
|
||||
|
||||
// All attempts have been exhausted block 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);
|
||||
return;
|
||||
}
|
||||
|
||||
// Incorrect response, update the client
|
||||
clif_macro_detector_request_show(sd);
|
||||
|
||||
// Reset the timer
|
||||
addtick_timer(sd.macro_detect.timer, gettick() + battle_config.macro_detection_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a player tries to log out during a captcha check.
|
||||
* @param sd: Player data
|
||||
*/
|
||||
void pc_macro_detector_disconnect(map_session_data &sd) {
|
||||
// Delete the timeout timer
|
||||
if (sd.macro_detect.timer != INVALID_TIMER) {
|
||||
delete_timer(sd.macro_detect.timer, pc_macro_detector_timeout);
|
||||
sd.macro_detect.timer = INVALID_TIMER;
|
||||
}
|
||||
|
||||
// If the player disconnects before clearing the challenge the account is banned.
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a list of players from an area select via /macro_detector.
|
||||
*/
|
||||
int pc_macro_reporter_area_select_sub(block_list *bl, va_list ap) {
|
||||
nullpo_retr(0, bl);
|
||||
|
||||
if (bl->type != BL_PC)
|
||||
return 0;
|
||||
|
||||
std::vector<uint32> *aid_list = va_arg(ap, std::vector<uint32> *);
|
||||
|
||||
nullpo_ret(aid_list);
|
||||
|
||||
aid_list->push_back(bl->id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Area select via /macro_detector.
|
||||
* @param sd: Player data
|
||||
* @param x: X location
|
||||
* @param y: Y location
|
||||
* @param radius: Area
|
||||
*/
|
||||
void pc_macro_reporter_area_select(map_session_data &sd, const int16 x, const int16 y, const int8 radius) {
|
||||
std::vector<uint32> aid_list;
|
||||
|
||||
map_foreachinarea(pc_macro_reporter_area_select_sub, sd.bl.m, x - radius, y - radius, x + radius, y + radius, BL_PC, &aid_list);
|
||||
|
||||
clif_macro_reporter_select(sd, aid_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send out captcha check to player.
|
||||
* @param ssd: Source player data
|
||||
* @param tsd: Target player data
|
||||
*/
|
||||
void pc_macro_reporter_process(map_session_data &ssd, map_session_data &tsd) {
|
||||
if (captcha_db.empty())
|
||||
return;
|
||||
|
||||
// Pick a random image from the database.
|
||||
const std::shared_ptr<s_captcha_data> 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;
|
||||
|
||||
// Block all actions for the target player.
|
||||
tsd.state.block_action |= (PCBLOCK_ALL | PCBLOCK_IMMUNE);
|
||||
|
||||
// Open macro detect client side.
|
||||
clif_macro_detector_request(tsd);
|
||||
|
||||
// Start the timeout timer.
|
||||
tsd.macro_detect.timer = add_timer(gettick() + battle_config.macro_detection_timeout, pc_macro_detector_timeout, tsd.bl.id, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a BMP image to memory.
|
||||
* @param filepath: Image file location
|
||||
* @param cd: Captcha data
|
||||
*/
|
||||
bool pc_macro_read_captcha_db_loadbmp(const std::string &filepath, std::shared_ptr<s_captcha_data> cd) {
|
||||
if (cd == nullptr)
|
||||
return false;
|
||||
|
||||
FILE *fp = fopen(filepath.c_str(), "rb");
|
||||
|
||||
if (fp == nullptr) {
|
||||
ShowError("%s: Failed to open file \"%s\"\n", __func__, filepath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load the file data and verify magic
|
||||
char bmp_data[CAPTCHA_BMP_SIZE];
|
||||
|
||||
if (fread(bmp_data, CAPTCHA_BMP_SIZE, 1, fp) != 1) {
|
||||
ShowError("%s: Failed to read data from \"%s\"\n", __func__, filepath.c_str());
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if (bmp_data[0] != 'B' || bmp_data[1] != 'M') {
|
||||
ShowError("%s: Invalid BMP file header given at \"%s\"\n", __func__, filepath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compress the data into the destination
|
||||
unsigned long com_size = sizeof(cd->image_data);
|
||||
|
||||
encode_zip(cd->image_data, &com_size, bmp_data, CAPTCHA_BMP_SIZE);
|
||||
cd->image_size = static_cast<int16>(com_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string CaptchaDatabase::getDefaultLocation() {
|
||||
return std::string(db_path) + "/captcha_db.yml";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and parses an entry from the captcha_db.
|
||||
* @param node: YAML node containing the entry.
|
||||
* @return count of successfully parsed rows
|
||||
*/
|
||||
uint64 CaptchaDatabase::parseBodyNode(const ryml::NodeRef &node) {
|
||||
uint16 index;
|
||||
|
||||
if (!this->asUInt16(node, "Id", index))
|
||||
return 0;
|
||||
|
||||
std::shared_ptr<s_captcha_data> cd = captcha_db.find(index);
|
||||
bool exists = cd != nullptr;
|
||||
|
||||
if (!exists) {
|
||||
if (!this->nodesExist(node, { "Filename", "Answer" }))
|
||||
return 0;
|
||||
|
||||
cd = std::make_shared<s_captcha_data>();
|
||||
cd->index = index;
|
||||
}
|
||||
|
||||
if (this->nodeExists(node, "Filename")) {
|
||||
std::string filename;
|
||||
|
||||
if (!this->asString(node, "Filename", filename))
|
||||
return 0;
|
||||
|
||||
if (!pc_macro_read_captcha_db_loadbmp(filename, cd)) {
|
||||
this->invalidWarning(node["Filename"], "Failed to parse BMP image, skipping...\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->nodeExists(node, "Answer")) {
|
||||
std::string answer;
|
||||
|
||||
if (!this->asString(node, "Answer", answer))
|
||||
return 0;
|
||||
|
||||
if (answer.length() < 4 || answer.length() > CAPTCHA_ANSWER_SIZE) {
|
||||
this->invalidWarning(node["Answer"], "The captcha answer must be between 4~%d characters, skipping...", CAPTCHA_ANSWER_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
safestrncpy(cd->captcha_answer, answer.c_str(), sizeof(cd->captcha_answer));
|
||||
}
|
||||
|
||||
if (this->nodeExists(node, "Bonus")) {
|
||||
std::string script;
|
||||
|
||||
if (!this->asString(node, "Bonus", script)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cd->bonus_script) {
|
||||
script_free_code(cd->bonus_script);
|
||||
cd->bonus_script = nullptr;
|
||||
}
|
||||
|
||||
cd->bonus_script = parse_script(script.c_str(), this->getCurrentFile().c_str(), this->getLineNumber(node["Bonus"]), SCRIPT_IGNORE_EXTERNAL_BRACKETS);
|
||||
} else {
|
||||
if (!exists)
|
||||
cd->bonus_script = parse_script("specialeffect2 EF_BLESSING; sc_start SC_BLESSING,600000,10; specialeffect2 EF_INCAGILITY; sc_start SC_INCREASEAGI,600000,10;", "macro_script", 0, SCRIPT_IGNORE_EXTERNAL_BRACKETS);
|
||||
}
|
||||
|
||||
if (!exists)
|
||||
captcha_db.put(index, cd);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*==========================================
|
||||
* pc Init/Terminate
|
||||
*------------------------------------------*/
|
||||
@ -15003,6 +15353,7 @@ void do_final_pc(void) {
|
||||
attendance_db.clear();
|
||||
reputation_db.clear();
|
||||
penalty_db.clear();
|
||||
captcha_db.clear();
|
||||
}
|
||||
|
||||
void do_init_pc(void) {
|
||||
@ -15013,6 +15364,7 @@ void do_init_pc(void) {
|
||||
pc_read_motd(); // Read MOTD [Valaris]
|
||||
attendance_db.load();
|
||||
reputation_db.load();
|
||||
captcha_db.load();
|
||||
|
||||
add_timer_func_list(pc_invincible_timer, "pc_invincible_timer");
|
||||
add_timer_func_list(pc_eventtimer, "pc_eventtimer");
|
||||
@ -15027,6 +15379,7 @@ void do_init_pc(void) {
|
||||
add_timer_func_list(pc_expiration_timer, "pc_expiration_timer");
|
||||
add_timer_func_list(pc_autotrade_timer, "pc_autotrade_timer");
|
||||
add_timer_func_list(pc_on_expire_active, "pc_on_expire_active");
|
||||
add_timer_func_list(pc_macro_detector_timeout, "pc_macro_detector_timeout");
|
||||
|
||||
add_timer(gettick() + autosave_interval, pc_autosave, 0, 0);
|
||||
|
||||
|
@ -120,6 +120,60 @@ enum e_additem_result : uint8 {
|
||||
ADDITEM_STACKLIMIT
|
||||
};
|
||||
|
||||
#ifndef CAPTCHA_ANSWER_SIZE
|
||||
#define CAPTCHA_ANSWER_SIZE 16
|
||||
#endif
|
||||
#ifndef CAPTCHA_BMP_SIZE
|
||||
#define CAPTCHA_BMP_SIZE (2 + 52 + (3 * 220 * 90)) // sizeof("BM") + sizeof(BITMAPV2INFOHEADER) + 24bits 220x90 BMP
|
||||
#endif
|
||||
#ifndef MAX_CAPTCHA_CHUNK_SIZE
|
||||
#define MAX_CAPTCHA_CHUNK_SIZE 1024
|
||||
#endif
|
||||
|
||||
struct s_captcha_data {
|
||||
uint16 index;
|
||||
uint16 image_size;
|
||||
char image_data[CAPTCHA_BMP_SIZE];
|
||||
char captcha_answer[CAPTCHA_ANSWER_SIZE];
|
||||
script_code *bonus_script;
|
||||
|
||||
~s_captcha_data() {
|
||||
if (this->bonus_script)
|
||||
script_free_code(this->bonus_script);
|
||||
}
|
||||
};
|
||||
|
||||
struct s_macro_detect {
|
||||
std::shared_ptr<s_captcha_data> cd;
|
||||
int32 reporter_aid;
|
||||
int32 retry;
|
||||
int32 timer;
|
||||
};
|
||||
|
||||
enum e_macro_detect_status : uint8 {
|
||||
MCD_TIMEOUT = 0,
|
||||
MCD_INCORRECT = 1,
|
||||
MCD_GOOD = 2,
|
||||
};
|
||||
|
||||
enum e_macro_report_status : uint8 {
|
||||
MCR_MONITORING = 0,
|
||||
MCR_NO_DATA = 1,
|
||||
MCR_INPROGRESS = 2,
|
||||
};
|
||||
|
||||
class CaptchaDatabase : public TypesafeYamlDatabase<int16, s_captcha_data> {
|
||||
public:
|
||||
CaptchaDatabase() : TypesafeYamlDatabase("CAPTCHA_DB", 1) {
|
||||
|
||||
}
|
||||
|
||||
const std::string getDefaultLocation() override;
|
||||
uint64 parseBodyNode(const ryml::NodeRef &node) override;
|
||||
};
|
||||
|
||||
extern CaptchaDatabase captcha_db;
|
||||
|
||||
struct skill_cooldown_entry {
|
||||
unsigned short skill_id;
|
||||
int timer;
|
||||
@ -869,6 +923,13 @@ struct map_session_data {
|
||||
uint16 level;
|
||||
int target;
|
||||
} skill_keep_using;
|
||||
|
||||
struct {
|
||||
std::shared_ptr<s_captcha_data> cd;
|
||||
uint16 upload_size;
|
||||
} captcha_upload;
|
||||
|
||||
s_macro_detect macro_detect;
|
||||
};
|
||||
|
||||
extern struct eri *pc_sc_display_ers; /// Player's SC display table
|
||||
@ -1632,4 +1693,16 @@ bool pc_attendance_enabled();
|
||||
int32 pc_attendance_counter( struct map_session_data* sd );
|
||||
void pc_attendance_claim_reward( struct map_session_data* sd );
|
||||
|
||||
// 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
|
||||
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);
|
||||
|
||||
#endif /* PC_HPP */
|
||||
|
@ -48,6 +48,8 @@ enum e_pc_permission : uint32 {
|
||||
PC_PERM_BYPASS_STAT_ONCLONE,
|
||||
PC_PERM_BYPASS_MAX_STAT,
|
||||
PC_PERM_ATTENDANCE,
|
||||
PC_PERM_MACRO_DETECT,
|
||||
PC_PERM_MACRO_REGISTER,
|
||||
//.. add other here
|
||||
PC_PERM_MAX,
|
||||
};
|
||||
@ -84,6 +86,8 @@ static const struct s_pcg_permission_name {
|
||||
{ "bypass_stat_onclone",PC_PERM_BYPASS_STAT_ONCLONE },
|
||||
{ "bypass_max_stat",PC_PERM_BYPASS_MAX_STAT },
|
||||
{ "attendance",PC_PERM_ATTENDANCE },
|
||||
{ "macro_detect",PC_PERM_MACRO_DETECT },
|
||||
{ "macro_register",PC_PERM_MACRO_REGISTER },
|
||||
};
|
||||
|
||||
struct s_player_group{
|
||||
|
Loading…
x
Reference in New Issue
Block a user