diff options
author | Ibrahem Hossam <hemagx2@gmail.com> | 2016-04-23 17:29:59 +0200 |
---|---|---|
committer | Ibrahem Hossam <hemagx2@gmail.com> | 2016-04-23 17:29:59 +0200 |
commit | 242c506676d3a8a42547992241dda7331f982d81 (patch) | |
tree | b42a95009620c3ba71329ac57b30c22d9af848a2 | |
parent | 2dc732fe12f9748a2601f06ea8e4c2848de4b6f1 (diff) | |
parent | 654a8c90afe0671323b75bb2d718912a0abf6ac7 (diff) | |
download | hercules-242c506676d3a8a42547992241dda7331f982d81.tar.gz hercules-242c506676d3a8a42547992241dda7331f982d81.tar.bz2 hercules-242c506676d3a8a42547992241dda7331f982d81.tar.xz hercules-242c506676d3a8a42547992241dda7331f982d81.zip |
Merge pull request #1263 from HerculesWS/chatmessage_terminator
-rw-r--r-- | src/common/HPMDataCheck.h | 2 | ||||
-rw-r--r-- | src/map/clif.c | 519 | ||||
-rw-r--r-- | src/map/clif.h | 3 | ||||
-rw-r--r-- | src/map/packets_struct.h | 13 | ||||
-rw-r--r-- | src/map/pc.c | 84 | ||||
-rw-r--r-- | src/map/pc.h | 5 | ||||
-rw-r--r-- | src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc | 20 | ||||
-rw-r--r-- | src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc | 5 | ||||
-rw-r--r-- | src/plugins/HPMHooking/HPMHooking_map.Hooks.inc | 104 |
9 files changed, 457 insertions, 298 deletions
diff --git a/src/common/HPMDataCheck.h b/src/common/HPMDataCheck.h index e5f482686..86fcb671d 100644 --- a/src/common/HPMDataCheck.h +++ b/src/common/HPMDataCheck.h @@ -532,6 +532,7 @@ HPExport const struct s_HPMDataCheck HPMDataCheck[] = { { "packet_bgqueue_revoke_req", sizeof(struct packet_bgqueue_revoke_req), SERVER_TYPE_MAP }, { "packet_bgqueue_update_info", sizeof(struct packet_bgqueue_update_info), SERVER_TYPE_MAP }, { "packet_cart_additem_ack", sizeof(struct packet_cart_additem_ack), SERVER_TYPE_MAP }, + { "packet_chat_message", sizeof(struct packet_chat_message), SERVER_TYPE_MAP }, { "packet_damage", sizeof(struct packet_damage), SERVER_TYPE_MAP }, { "packet_dropflooritem", sizeof(struct packet_dropflooritem), SERVER_TYPE_MAP }, { "packet_equip_item", sizeof(struct packet_equip_item), SERVER_TYPE_MAP }, @@ -574,6 +575,7 @@ HPExport const struct s_HPMDataCheck HPMDataCheck[] = { { "packet_unequipitem_ack", sizeof(struct packet_unequipitem_ack), SERVER_TYPE_MAP }, { "packet_unit_walking", sizeof(struct packet_unit_walking), SERVER_TYPE_MAP }, { "packet_viewequip_ack", sizeof(struct packet_viewequip_ack), SERVER_TYPE_MAP }, + { "packet_whisper_message", sizeof(struct packet_whisper_message), SERVER_TYPE_MAP }, { "packet_wis_end", sizeof(struct packet_wis_end), SERVER_TYPE_MAP }, #else #define MAP_PACKETS_STRUCT_H diff --git a/src/map/clif.c b/src/map/clif.c index e1b4be8f4..2dea386be 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -8867,105 +8867,137 @@ void clif_msgtable_skill(struct map_session_data* sd, uint16 skill_id, int msg_i WFIFOSET(fd, packet_len(0x7e6)); } -/// Validates one global/guild/party/whisper message packet and tries to recognize its components. -/// Returns true if the packet was parsed successfully. -/// Formats: 0 - <packet id>.w <packet len>.w (<name> : <message>).?B 00 -/// 1 - <packet id>.w <packet len>.w <name>.24B <message>.?B 00 -bool clif_process_message(struct map_session_data *sd, int format, const char **name_, size_t *namelen_, const char **message_, size_t *messagelen_) -{ - const char *text, *name, *message; - unsigned int packetlen, textlen; - size_t namelen, messagelen; - int fd = sd->fd; +/** + * Validates and processes a global/guild/party message packet. + * + * @param[in] sd The source character. + * @param[in] packet The packet data. + * @param[out] out_buf The output buffer (must be a valid buffer), that will + * be filled with "Name : Message". + * @param[in] out_buflen The size of out_buf (including the NUL terminator). + * @return a pointer to the "Message" part of out_buf. + * @retval NULL if the validation failed, the messages was a command or the + * character can't send chat messages. out_buf shan't be used. + */ +const char *clif_process_chat_message(struct map_session_data *sd, const struct packet_chat_message *packet, char *out_buf, int out_buflen) +{ + const char *srcname = NULL, *srcmessage = NULL, *message = NULL; + int textlen = 0, namelen = 0, messagelen = 0; - nullpo_retr(false, sd); - nullpo_retr(false, name_); - nullpo_retr(false, namelen_); - nullpo_retr(false, message_); - nullpo_retr(false, messagelen_); - - *name_ = NULL; - *namelen_ = 0; - *message_ = NULL; - *messagelen_ = 0; - - packetlen = RFIFOW(fd,2); - // basic structure checks - if (packetlen < 4 + 1) { + nullpo_ret(sd); + nullpo_ret(packet); + nullpo_ret(out_buf); + + if (packet->packet_len < 4 + 1) { // 4-byte header and at least an empty string is expected - ShowWarning("clif_process_message: Received malformed packet from player '%s' (no message data)!\n", sd->status.name); - return false; + ShowWarning("clif_process_chat_message: Received malformed packet from player '%s' (no message data)!\n", sd->status.name); + return NULL; } - text = RFIFOP(fd,4); - textlen = packetlen - 4; +#if PACKETVER >= 20151001 + // Packet doesn't include a NUL terminator + textlen = packet->packet_len - 4; +#else // PACKETVER < 20151001 + // Packet includes a NUL terminator + textlen = packet->packet_len - 4 - 1; +#endif // PACKETVER > 20151001 - // process <name> part of the packet - if( format == 0 ) - {// name and message are separated by ' : ' - // validate name - name = text; - namelen = strnlen(sd->status.name, NAME_LENGTH-1); // name length (w/o zero byte) + // name and message are separated by ' : ' + srcname = packet->message; + namelen = (int)strnlen(sd->status.name, NAME_LENGTH-1); // name length (w/o zero byte) - if( strncmp(name, sd->status.name, namelen) || // the text must start with the speaker's name - name[namelen] != ' ' || name[namelen+1] != ':' || name[namelen+2] != ' ' ) // followed by ' : ' - { - //Hacked message, or infamous "client desynchronize" issue where they pick one char while loading another. - ShowWarning("clif_process_message: Player '%s' sent a message using an incorrect name! Forcing a relog...\n", sd->status.name); - sockt->eof(fd); // Just kick them out to correct it. - return false; - } + if (strncmp(srcname, sd->status.name, namelen) != 0 // the text must start with the speaker's name + || srcname[namelen] != ' ' || srcname[namelen+1] != ':' || srcname[namelen+2] != ' ' // followed by ' : ' + ) { + //Hacked message, or infamous "client desynchronize" issue where they pick one char while loading another. + ShowWarning("clif_process_chat_message: Player '%s' sent a message using an incorrect name! Forcing a relog...\n", sd->status.name); + sockt->eof(sd->fd); // Just kick them out to correct it. + return NULL; + } + + srcmessage = packet->message + namelen + 3; // <name> " : " <message> + messagelen = textlen - namelen - 3; - message = name + namelen + 3; - messagelen = textlen - namelen - 3; // this should be the message length (w/ zero byte included) + if (messagelen >= CHAT_SIZE_MAX || textlen >= out_buflen) { + // messages mustn't be too long + // Normally you can only enter CHATBOX_SIZE-1 letters into the chat box, but Frost Joke / Dazzler's text can be longer. + // Also, the physical size of strings that use multibyte encoding can go multiple times over the chatbox capacity. + // Neither the official client nor server place any restriction on the length of the data in the packet, + // but we'll only allow reasonably long strings here. This also makes sure that they fit into the `chatlog` table. + ShowWarning("clif_process_chat_message: Player '%s' sent a message too long ('%.*s')!\n", sd->status.name, CHATBOX_SIZE-1, srcmessage); + return NULL; } - else - {// name has fixed width - if( textlen < NAME_LENGTH + 1 ) - { - ShowWarning("clif_process_message: Received malformed packet from player '%s' (packet length is incorrect)!\n", sd->status.name); - return false; - } - // validate name - name = text; - namelen = strnlen(name, NAME_LENGTH-1); // name length (w/o zero byte) + safestrncpy(out_buf, packet->message, textlen+1); // [!] packet->message is not necessarily NUL terminated + message = out_buf + namelen + 3; - if (name[namelen] != '\0') { - // only restriction is that the name must be zero-terminated - ShowWarning("clif_process_message: Player '%s' sent an unterminated name!\n", sd->status.name); - return false; - } + if (!pc->process_chat_message(sd, message)) + return NULL; + return message; +} - message = name + NAME_LENGTH; - messagelen = textlen - NAME_LENGTH; // this should be the message length (w/ zero byte included) - } +/** + * Validates and processes a whisper message packet. + * + * @param[in] sd The source character. + * @param[in] packet The packet data. + * @param[out] out_name The parsed target name buffer (must be a valid + * buffer of size NAME_LENGTH). + * @param[out] out_message The output message buffer (must be a valid buffer). + * @param[in] out_messagelen The size of out_message. + * @retval true if the validation succeeded and the message is a chat message. + * @retval false if the validation failed, the messages was a command or the + * character can't send chat messages. out_name and out_message + * shan't be used. + */ +bool clif_process_whisper_message(struct map_session_data *sd, const struct packet_whisper_message *packet, char *out_name, char *out_message, int out_messagelen) +{ + int namelen = 0, messagelen = 0; - if (messagelen != strnlen(message, messagelen)+1) { - // the declared length must match real length - ShowWarning("clif_process_message: Received malformed packet from player '%s' (length is incorrect)!\n", sd->status.name); + nullpo_retr(false, sd); + nullpo_retr(false, packet); + nullpo_retr(false, out_name); + nullpo_retr(false, out_message); + + if (packet->packet_len < NAME_LENGTH + 4 + 1) { + // 4-byte header and at least an empty string is expected + ShowWarning("clif_process_whisper_message: Received malformed packet from player '%s' (packet length is incorrect)!\n", sd->status.name); return false; } - // verify <message> part of the packet - if (message[messagelen-1] != '\0') { - // message must be zero-terminated - ShowWarning("clif_process_message: Player '%s' sent an unterminated message string!\n", sd->status.name); + + // validate name + namelen = (int)strnlen(packet->name, NAME_LENGTH-1); // name length (w/o zero byte) + + if (packet->name[namelen] != '\0') { + // only restriction is that the name must be zero-terminated + ShowWarning("clif_process_whisper_message: Player '%s' sent an unterminated name!\n", sd->status.name); return false; } - if (messagelen > CHAT_SIZE_MAX-1) { + +#if PACKETVER >= 20151001 + // Packet doesn't include a NUL terminator + messagelen = packet->packet_len - NAME_LENGTH - 4; +#else // PACKETVER < 20151001 + // Packet includes a NUL terminator + messagelen = packet->packet_len - NAME_LENGTH - 4 - 1; +#endif // PACKETVER > 20151001 + + if (messagelen >= CHAT_SIZE_MAX || messagelen >= out_messagelen) { // messages mustn't be too long // Normally you can only enter CHATBOX_SIZE-1 letters into the chat box, but Frost Joke / Dazzler's text can be longer. // Also, the physical size of strings that use multibyte encoding can go multiple times over the chatbox capacity. // Neither the official client nor server place any restriction on the length of the data in the packet, // but we'll only allow reasonably long strings here. This also makes sure that they fit into the `chatlog` table. - ShowWarning("clif_process_message: Player '%s' sent a message too long ('%.*s')!\n", sd->status.name, CHAT_SIZE_MAX-1, message); + ShowWarning("clif_process_whisper_message: Player '%s' sent a message too long ('%.*s')!\n", sd->status.name, CHAT_SIZE_MAX-1, packet->message); return false; } - *name_ = name; - *namelen_ = namelen; - *message_ = message; - *messagelen_ = messagelen; + safestrncpy(out_name, packet->name, namelen+1); // [!] packet->name is not NUL terminated + safestrncpy(out_message, packet->message, messagelen+1); // [!] packet->message is not necessarily NUL terminated + + if (!pc->process_chat_message(sd, out_message)) + return false; + return true; } @@ -9712,144 +9744,110 @@ int clif_undisguise_timer(int tid, int64 tick, int id, intptr_t data) { return 0; } -void clif_parse_GlobalMessage(int fd, struct map_session_data* sd) __attribute__((nonnull (2))); -/// Validates and processes global messages -/// 008c <packet len>.W <text>.?B (<name> : <message>) 00 (CZ_REQUEST_CHAT) -/// There are various variants of this packet. -void clif_parse_GlobalMessage(int fd, struct map_session_data* sd) +/** + * Validates and processed global messages. + * + * There are various variants of this packet. + * + * @code + * 008c <packet len>.W <text>.?B (<name> : <message>) 00 (CZ_REQUEST_CHAT) + * @endcode + * + * @param fd The incoming file descriptor. + * @param sd The related character. + */ +void clif_parse_GlobalMessage(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); +void clif_parse_GlobalMessage(int fd, struct map_session_data *sd) { - const char *text = RFIFOP(fd,4); - size_t textlen = RFIFOW(fd,2) - 4; + const struct packet_chat_message *packet = NULL; + char full_message[CHAT_SIZE_MAX + NAME_LENGTH + 3 + 1]; + const char *message = NULL; + bool is_fakename = false; + int outlen = 0; - const char *name = NULL, *message = NULL; - char *fakename = NULL; - size_t namelen, messagelen; - - bool is_fake; + nullpo_retv(sd); - // validate packet and retrieve name and message - if( !clif->process_message(sd, 0, &name, &namelen, &message, &messagelen) ) + packet = RP2PTR(fd); + message = clif->process_chat_message(sd, packet, full_message, sizeof full_message); + if (message == NULL) return; - if( atcommand->exec(fd, sd, message, true) ) - return; + pc->check_supernovice_call(sd, message); - if( !pc->can_talk(sd) ) + if (sd->gcbind != NULL) { + channel->send(sd->gcbind, sd, message); return; - - if( battle_config.min_chat_delay ) { //[Skotlex] - if (DIFF_TICK(sd->cantalk_tick, timer->gettick()) > 0) - return; - sd->cantalk_tick = timer->gettick() + battle_config.min_chat_delay; } - if( (sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE ) { - unsigned int next = pc->nextbaseexp(sd); - if( next == 0 ) next = pc->thisbaseexp(sd); - if( next ) { // 0%, 10%, 20%, ... - int percent = (int)( ( (float)sd->status.base_exp/(float)next )*1000. ); - if( (battle_config.snovice_call_type || percent) && ( percent%100 ) == 0 ) {// 10.0%, 20.0%, ..., 90.0% - switch (sd->state.snovice_call_flag) { - case 0: - if( strstr(message, msg_txt(1479)) ) // "Dear angel, can you hear my voice?" - sd->state.snovice_call_flag = 1; - break; - case 1: { - char buf[256]; - snprintf(buf, 256, msg_txt(1480), sd->status.name); - if( strstr(message, buf) ) // "I am %s Super Novice~" - sd->state.snovice_call_flag = 2; - } - break; - case 2: - if( strstr(message, msg_txt(1481)) ) // "Help me out~ Please~ T_T" - sd->state.snovice_call_flag = 3; - break; - case 3: - sc_start(NULL,&sd->bl, status->skill2sc(MO_EXPLOSIONSPIRITS), 100, 17, skill->get_time(MO_EXPLOSIONSPIRITS, 5)); //Lv17-> +50 critical (noted by Poki) [Skotlex] - clif->skill_nodamage(&sd->bl, &sd->bl, MO_EXPLOSIONSPIRITS, 5, 1); // prayer always shows successful Lv5 cast and disregards noskill restrictions - sd->state.snovice_call_flag = 0; - break; - } - } - } + if (sd->fakename[0] != '\0') { + is_fakename = true; + outlen = (int)strlen(sd->fakename) + (int)strlen(message) + 3 + 1; + } else { + outlen = (int)strlen(full_message) + 1; } - pc->update_idle_time(sd, BCIDLE_CHAT); - - if( sd->gcbind ) { - channel->send(sd->gcbind,sd,message); - return; - } else if ( sd->fontcolor && !sd->chatID ) { - char mout[200]; - unsigned char mylen = 1; + if (sd->fontcolor != 0 && sd->chatID == 0) { uint32 color = 0; - if( sd->disguise == -1 ) { + if (sd->disguise == -1) { sd->fontcolor_tid = timer->add(timer->gettick()+5000, clif->undisguise_timer, sd->bl.id, 0); pc->disguise(sd,sd->status.class_); - if( pc_isdead(sd) ) + if (pc_isdead(sd)) clif->clearunit_single(-sd->bl.id, CLR_DEAD, sd->fd); - if( unit->is_walking(&sd->bl) ) + if (unit->is_walking(&sd->bl)) clif->move(&sd->ud); - } else if ( sd->disguise == sd->status.class_ && sd->fontcolor_tid != INVALID_TIMER ) { + } else if (sd->disguise == sd->status.class_ && sd->fontcolor_tid != INVALID_TIMER) { const struct TimerData *td; - if( (td = timer->get(sd->fontcolor_tid)) ) { + if ((td = timer->get(sd->fontcolor_tid)) != NULL) timer->settick(sd->fontcolor_tid, td->tick+5000); - } } - mylen += snprintf(mout, 200, "%s : %s",sd->fakename[0]?sd->fakename:sd->status.name,message); - color = channel->config->colors[sd->fontcolor - 1]; - WFIFOHEAD(fd,mylen + 12); + WFIFOHEAD(fd, outlen + 12); WFIFOW(fd,0) = 0x2C1; - WFIFOW(fd,2) = mylen + 12; + WFIFOW(fd,2) = outlen + 12; WFIFOL(fd,4) = sd->bl.id; WFIFOL(fd,8) = RGB2BGR(color); - safestrncpy(WFIFOP(fd,12), mout, mylen); + if (is_fakename) + safesnprintf(WFIFOP(fd, 12), outlen, "%s : %s", sd->fakename, message); + else + safestrncpy(WFIFOP(fd, 12), full_message, outlen); clif->send(WFIFOP(fd,0), WFIFOW(fd,2), &sd->bl, AREA_WOS); WFIFOL(fd,4) = -sd->bl.id; - WFIFOSET(fd, mylen + 12); + WFIFOSET(fd, outlen + 12); return; } - /** - * Fake Name Design by FatalEror (bug report #9) - **/ - if( ( is_fake = ( sd->fakename[0] ) ) ) { - fakename = (char*) aMalloc(strlen(sd->fakename)+messagelen+3); - strcpy(fakename, sd->fakename); - strcat(fakename, " : "); - strcat(fakename, message); - textlen = strlen(fakename) + 1; + { + // send message to others + void *buf = aMalloc(8 + outlen); + WBUFW(buf, 0) = 0x8d; + WBUFW(buf, 2) = 8 + outlen; + WBUFL(buf, 4) = sd->bl.id; + if (is_fakename) + safesnprintf(WBUFP(buf, 8), outlen, "%s : %s", sd->fakename, message); + else + safestrncpy(WBUFP(buf, 8), full_message, outlen); + //FIXME: chat has range of 9 only + clif->send(buf, WBUFW(buf, 2), &sd->bl, sd->chatID ? CHAT_WOS : AREA_CHAT_WOC); + aFree(buf); } - // send message to others (using the send buffer for temp. storage) - WFIFOHEAD(fd, 8 + textlen); - WFIFOW(fd,0) = 0x8d; - WFIFOW(fd,2) = 8 + textlen; - WFIFOL(fd,4) = sd->bl.id; - safestrncpy(WFIFOP(fd,8), is_fake ? fakename : text, textlen); - //FIXME: chat has range of 9 only - clif->send(WFIFOP(fd,0), WFIFOW(fd,2), &sd->bl, sd->chatID ? CHAT_WOS : AREA_CHAT_WOC); // send back message to the speaker - if( is_fake ) { - WFIFOW(fd,0) = 0x8e; - WFIFOW(fd,2) = textlen + 4; - safestrncpy(WFIFOP(fd,4), fakename, textlen); - aFree(fakename); - } else { - memcpy(WFIFOP(fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); - WFIFOW(fd,0) = 0x8e; - } + WFIFOHEAD(fd, 4 + outlen); + WFIFOW(fd, 0) = 0x8e; + WFIFOW(fd, 2) = 4 + outlen; + if (is_fakename) + safesnprintf(WFIFOP(fd, 4), outlen, "%s : %s", sd->fakename, message); + else + safestrncpy(WFIFOP(fd, 4), full_message, outlen); WFIFOSET(fd, WFIFOW(fd,2)); // Chat logging type 'O' / Global Chat logs->chat(LOG_CHAT_GLOBAL, 0, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, NULL, message); // trigger listening npcs - map->foreachinrange(npc_chat->sub, &sd->bl, AREA_SIZE, BL_NPC, text, textlen, &sd->bl); + map->foreachinrange(npc_chat->sub, &sd->bl, AREA_SIZE, BL_NPC, full_message, strlen(full_message), &sd->bl); } void clif_parse_MapMove(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); @@ -10122,37 +10120,28 @@ void clif_parse_Restart(int fd, struct map_session_data *sd) { } } -void clif_parse_WisMessage(int fd, struct map_session_data* sd) __attribute__((nonnull (2))); -/// Validates and processes whispered messages (CZ_WHISPER). -/// 0096 <packet len>.W <nick>.24B <message>.?B +/** + * Validates and processes whispered messages (CZ_WHISPER). + * + * @code + * 0096 <packet len>.W <nick>.24B <message>.?B + * @endcode + * + * @param fd The incoming file descriptor. + * @param sd The related character. + */ +void clif_parse_WisMessage(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); void clif_parse_WisMessage(int fd, struct map_session_data* sd) { struct map_session_data* dstsd; int i; - const char *target, *message; - size_t namelen, messagelen; - - // validate packet and retrieve name and message - if( !clif->process_message(sd, 1, &target, &namelen, &message, &messagelen) ) - return; - - if ( atcommand->exec(fd, sd, message, true) ) - return; + char target[NAME_LENGTH], message[CHAT_SIZE_MAX + 1]; + const struct packet_whisper_message *packet = RP2PTR(fd); - // Statuses that prevent the player from whispering - if( !pc->can_talk(sd) ) + if (!clif->process_whisper_message(sd, packet, target, message, sizeof message)) return; - if (battle_config.min_chat_delay) { //[Skotlex] - if (DIFF_TICK(sd->cantalk_tick, timer->gettick()) > 0) { - return; - } - sd->cantalk_tick = timer->gettick() + battle_config.min_chat_delay; - } - - pc->update_idle_time(sd, BCIDLE_CHAT); - // Chat logging type 'W' / Whisper logs->chat(LOG_CHAT_WHISPER, 0, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, target, message); @@ -10222,7 +10211,7 @@ void clif_parse_WisMessage(int fd, struct map_session_data* sd) // if there are 'Test' player on an other map-server and 'test' player on this map-server, // and if we ask for 'Test', we must not contact 'test' player // so, we send information to inter-server, which is the only one which decide (and copy correct name). - intif->wis_message(sd, target, message, messagelen); + intif->wis_message(sd, target, message, strlen(message)); return; } @@ -10256,7 +10245,7 @@ void clif_parse_WisMessage(int fd, struct map_session_data* sd) clif->wis_end(fd, 0); // 0: success to send wisper // Normal message - clif->wis_message(dstsd->fd, sd->status.name, message, messagelen); + clif->wis_message(dstsd->fd, sd->status.name, message, strlen(message)); } void clif_parse_Broadcast(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); @@ -11952,36 +11941,26 @@ void clif_parse_PartyChangeOption(int fd, struct map_session_data *sd) #endif } -void clif_parse_PartyMessage(int fd, struct map_session_data* sd) __attribute__((nonnull (2))); -/// Validates and processes party messages (CZ_REQUEST_CHAT_PARTY). -/// 0108 <packet len>.W <text>.?B (<name> : <message>) 00 -void clif_parse_PartyMessage(int fd, struct map_session_data* sd) +/** + * Validates and processes party messages (CZ_REQUEST_CHAT_PARTY). + * + * @code + * 0108 <packet len>.W <text>.?B (<name> : <message>) 00 + * @endcode + * + * @param fd The incoming file descriptor. + * @param sd The related character. + */ +void clif_parse_PartyMessage(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); +void clif_parse_PartyMessage(int fd, struct map_session_data *sd) { - const char *text = RFIFOP(fd,4); - int textlen = RFIFOW(fd,2) - 4; - - const char *name, *message; - size_t namelen, messagelen; - - // validate packet and retrieve name and message - if( !clif->process_message(sd, 0, &name, &namelen, &message, &messagelen) ) - return; + const struct packet_chat_message *packet = RP2PTR(fd); + char message[CHAT_SIZE_MAX + NAME_LENGTH + 3 + 1]; - if( atcommand->exec(fd, sd, message, true) ) + if (clif->process_chat_message(sd, packet, message, sizeof message) == NULL) return; - if( !pc->can_talk(sd) ) - return; - - if (battle_config.min_chat_delay) { - if (DIFF_TICK(sd->cantalk_tick, timer->gettick()) > 0) - return; - sd->cantalk_tick = timer->gettick() + battle_config.min_chat_delay; - } - - pc->update_idle_time(sd, BCIDLE_CHAT); - - party->send_message(sd, text, textlen); + party->send_message(sd, message, (int)strlen(message)); } void clif_parse_PartyChangeLeader(int fd, struct map_session_data* sd) __attribute__((nonnull (2))); @@ -13060,39 +13039,29 @@ void clif_parse_GuildExpulsion(int fd,struct map_session_data *sd) { guild->expulsion(sd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOP(fd,14)); } -void clif_parse_GuildMessage(int fd, struct map_session_data* sd) __attribute__((nonnull (2))); -/// Validates and processes guild messages (CZ_GUILD_CHAT). -/// 017e <packet len>.W <text>.?B (<name> : <message>) 00 -void clif_parse_GuildMessage(int fd, struct map_session_data* sd) +/** + * Validates and processes guild messages (CZ_GUILD_CHAT). + * + * @code + * 017e <packet len>.W <text>.?B (<name> : <message>) 00 + * @endcode + * + * @param fd The incoming file descriptor. + * @param sd The related character. + */ +void clif_parse_GuildMessage(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); +void clif_parse_GuildMessage(int fd, struct map_session_data *sd) { - const char *text = RFIFOP(fd,4); - int textlen = RFIFOW(fd,2) - 4; - - const char *name, *message; - size_t namelen, messagelen; - - // validate packet and retrieve name and message - if( !clif->process_message(sd, 0, &name, &namelen, &message, &messagelen) ) - return; + const struct packet_chat_message *packet = RP2PTR(fd); + char message[CHAT_SIZE_MAX + NAME_LENGTH + 3 + 1]; - if( atcommand->exec(fd, sd, message, true) ) + if (clif->process_chat_message(sd, packet, message, sizeof message) == NULL) return; - if( !pc->can_talk(sd) ) - return; - - if (battle_config.min_chat_delay) { - if (DIFF_TICK(sd->cantalk_tick, timer->gettick()) > 0) - return; - sd->cantalk_tick = timer->gettick() + battle_config.min_chat_delay; - } - - pc->update_idle_time(sd, BCIDLE_CHAT); - - if( sd->bg_id ) - bg->send_message(sd, text, textlen); + if (sd->bg_id) + bg->send_message(sd, message, (int)strlen(message)); else - guild->send_message(sd, text, textlen); + guild->send_message(sd, message, (int)strlen(message)); } void clif_parse_GuildRequestAlliance(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); @@ -16174,35 +16143,26 @@ void clif_bg_message(struct battleground_data *bgd, int src_id, const char *name aFree(buf); } -void clif_parse_BattleChat(int fd, struct map_session_data* sd) __attribute__((nonnull (2))); -/// Validates and processes battlechat messages [pakpil] (CZ_BATTLEFIELD_CHAT). -/// 0x2db <packet len>.W <text>.?B (<name> : <message>) 00 -void clif_parse_BattleChat(int fd, struct map_session_data* sd) +/** + * Validates and processes battlechat messages [pakpil] (CZ_BATTLEFIELD_CHAT). + * + * @code + * 0x2db <packet len>.W <text>.?B (<name> : <message>) 00 + * @endcode + * + * @param fd The incoming file descriptor. + * @param sd The related character. + */ +void clif_parse_BattleChat(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); +void clif_parse_BattleChat(int fd, struct map_session_data *sd) { - const char *text = RFIFOP(fd,4); - int textlen = RFIFOW(fd,2) - 4; - - const char *name, *message; - size_t namelen, messagelen; + const struct packet_chat_message *packet = RP2PTR(fd); + char message[CHAT_SIZE_MAX + NAME_LENGTH + 3 + 1]; - if( !clif->process_message(sd, 0, &name, &namelen, &message, &messagelen) ) + if (clif->process_chat_message(sd, packet, message, sizeof message) == NULL) return; - if( atcommand->exec(fd, sd, message, true) ) - return; - - if( !pc->can_talk(sd) ) - return; - - if( battle_config.min_chat_delay ) { - if( DIFF_TICK(sd->cantalk_tick, timer->gettick()) > 0 ) - return; - sd->cantalk_tick = timer->gettick() + battle_config.min_chat_delay; - } - - pc->update_idle_time(sd, BCIDLE_CHAT); - - bg->send_message(sd, text, textlen); + bg->send_message(sd, message, (int)strlen(message)); } /// Notifies client of a battleground score change (ZC_BATTLEFIELD_NOTIFY_POINT). @@ -19365,7 +19325,8 @@ void clif_defaults(void) { clif->message = clif_displaymessage; clif->messageln = clif_displaymessage2; clif->messages = clif_displaymessage_sprintf; - clif->process_message = clif_process_message; + clif->process_chat_message = clif_process_chat_message; + clif->process_whisper_message = clif_process_whisper_message; clif->wisexin = clif_wisexin; clif->wisall = clif_wisall; clif->PMIgnoreList = clif_PMIgnoreList; diff --git a/src/map/clif.h b/src/map/clif.h index f930e4ca1..6d6c368f2 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -856,7 +856,8 @@ struct clif_interface { void (*messageln) (const int fd, const char* mes); /* message+s(printf) */ void (*messages) (const int fd, const char *mes, ...) __attribute__((format(printf, 2, 3))); - bool (*process_message) (struct map_session_data *sd, int format, const char **name_, size_t *namelen_, const char **message_, size_t *messagelen_); + const char *(*process_chat_message) (struct map_session_data *sd, const struct packet_chat_message *packet, char *out_buf, int out_buflen); + bool (*process_whisper_message) (struct map_session_data *sd, const struct packet_whisper_message *packet, char *out_name, char *out_message, int out_messagelen); void (*wisexin) (struct map_session_data *sd,int type,int flag); void (*wisall) (struct map_session_data *sd,int type,int flag); void (*PMIgnoreList) (struct map_session_data* sd); diff --git a/src/map/packets_struct.h b/src/map/packets_struct.h index 25b20cddf..888d893b6 100644 --- a/src/map/packets_struct.h +++ b/src/map/packets_struct.h @@ -1215,6 +1215,19 @@ struct packet_quest_list_header { //struct packet_quest_list_info list[]; // Variable-length } __attribute__((packed)); +struct packet_chat_message { + uint16 packet_id; + int16 packet_len; + char message[]; +} __attribute__((packed)); + +struct packet_whisper_message { + uint16 packet_id; + int16 packet_len; + char name[NAME_LENGTH]; + char message[]; +} __attribute__((packed)); + #if !defined(sun) && (!defined(__NETBSD__) || __NetBSD_Version__ >= 600000000) // NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute #pragma pack(pop) #endif // not NetBSD < 6 / Solaris diff --git a/src/map/pc.c b/src/map/pc.c index 57b2fe19a..8fe9ddd6e 100644 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -11516,6 +11516,87 @@ int pc_have_magnifier(struct map_session_data *sd) return n; } +/** + * Verifies a chat message, searching for atcommands, checking if the sender + * character can chat, and updating the idle timer. + * + * @param sd The sender character. + * @param message The message text. + * @return Whether the message is a valid chat message. + */ +bool pc_process_chat_message(struct map_session_data *sd, const char *message) +{ + if (atcommand->exec(sd->fd, sd, message, true)) { + return false; + } + + if (!pc->can_talk(sd)) { + return false; + } + + if (battle_config.min_chat_delay != 0) { + if (DIFF_TICK(sd->cantalk_tick, timer->gettick()) > 0) { + return false; + } + sd->cantalk_tick = timer->gettick() + battle_config.min_chat_delay; + } + + pc->update_idle_time(sd, BCIDLE_CHAT); + + return true; +} + +/** + * Checks a chat message, scanning for the Super Novice prayer sequence. + * + * If a match is found, the angel is invoked or the counter is incremented as + * appropriate. + * + * @param sd The sender character. + * @param message The message text. + */ +void pc_check_supernovice_call(struct map_session_data *sd, const char *message) +{ + unsigned int next = pc->nextbaseexp(sd); + int percent = 0; + + if ((sd->class_&MAPID_UPPERMASK) != MAPID_SUPER_NOVICE) + return; + if (next == 0) + next = pc->thisbaseexp(sd); + if (next == 0) + return; + + // 0%, 10%, 20%, ... + percent = (int)( ( (float)sd->status.base_exp/(float)next )*1000. ); + if ((battle_config.snovice_call_type != 0 || percent != 0) && (percent%100) == 0) { + // 10.0%, 20.0%, ..., 90.0% + switch (sd->state.snovice_call_flag) { + case 0: + if (strstr(message, msg_txt(1479))) // "Dear angel, can you hear my voice?" + sd->state.snovice_call_flag = 1; + break; + case 1: + { + char buf[256]; + snprintf(buf, 256, msg_txt(1480), sd->status.name); + if (strstr(message, buf)) // "I am %s Super Novice~" + sd->state.snovice_call_flag = 2; + } + break; + case 2: + if (strstr(message, msg_txt(1481))) // "Help me out~ Please~ T_T" + sd->state.snovice_call_flag = 3; + break; + case 3: + sc_start(NULL, &sd->bl, status->skill2sc(MO_EXPLOSIONSPIRITS), 100, 17, skill->get_time(MO_EXPLOSIONSPIRITS, 5)); //Lv17-> +50 critical (noted by Poki) [Skotlex] + clif->skill_nodamage(&sd->bl, &sd->bl, MO_EXPLOSIONSPIRITS, 5, 1); // prayer always shows successful Lv5 cast and disregards noskill restrictions + sd->state.snovice_call_flag = 0; + break; + } + } +} + void do_final_pc(void) { db_destroy(pc->itemcd_db); pc->at_db->destroy(pc->at_db,pc->autotrade_final); @@ -11871,6 +11952,9 @@ void pc_defaults(void) { pc->db_checkid = pc_db_checkid; pc->validate_levels = pc_validate_levels; + pc->check_supernovice_call = pc_check_supernovice_call; + pc->process_chat_message = pc_process_chat_message; + /** * Autotrade persistency [Ind/Hercules <3] **/ diff --git a/src/map/pc.h b/src/map/pc.h index b648b7113..5d35fd1cc 100644 --- a/src/map/pc.h +++ b/src/map/pc.h @@ -1089,8 +1089,11 @@ END_ZEROED_BLOCK; /* End */ int (*check_job_name) (const char *name); void (*update_idle_time) (struct map_session_data* sd, enum e_battle_config_idletime type); - + int (*have_magnifier) (struct map_session_data *sd); + + bool (*process_chat_message) (struct map_session_data *sd, const char *message); + void (*check_supernovice_call) (struct map_session_data *sd, const char *message); }; #ifdef HERCULES_CORE diff --git a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc index a11e964c2..a1efd6f16 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc @@ -984,8 +984,10 @@ struct { struct HPMHookPoint *HP_clif_message_post; struct HPMHookPoint *HP_clif_messageln_pre; struct HPMHookPoint *HP_clif_messageln_post; - struct HPMHookPoint *HP_clif_process_message_pre; - struct HPMHookPoint *HP_clif_process_message_post; + struct HPMHookPoint *HP_clif_process_chat_message_pre; + struct HPMHookPoint *HP_clif_process_chat_message_post; + struct HPMHookPoint *HP_clif_process_whisper_message_pre; + struct HPMHookPoint *HP_clif_process_whisper_message_post; struct HPMHookPoint *HP_clif_wisexin_pre; struct HPMHookPoint *HP_clif_wisexin_post; struct HPMHookPoint *HP_clif_wisall_pre; @@ -4338,6 +4340,10 @@ struct { struct HPMHookPoint *HP_pc_db_checkid_post; struct HPMHookPoint *HP_pc_validate_levels_pre; struct HPMHookPoint *HP_pc_validate_levels_post; + struct HPMHookPoint *HP_pc_process_chat_message_pre; + struct HPMHookPoint *HP_pc_process_chat_message_post; + struct HPMHookPoint *HP_pc_check_supernovice_call_pre; + struct HPMHookPoint *HP_pc_check_supernovice_call_post; struct HPMHookPoint *HP_pc_autotrade_load_pre; struct HPMHookPoint *HP_pc_autotrade_load_post; struct HPMHookPoint *HP_pc_autotrade_update_pre; @@ -6867,8 +6873,10 @@ struct { int HP_clif_message_post; int HP_clif_messageln_pre; int HP_clif_messageln_post; - int HP_clif_process_message_pre; - int HP_clif_process_message_post; + int HP_clif_process_chat_message_pre; + int HP_clif_process_chat_message_post; + int HP_clif_process_whisper_message_pre; + int HP_clif_process_whisper_message_post; int HP_clif_wisexin_pre; int HP_clif_wisexin_post; int HP_clif_wisall_pre; @@ -10221,6 +10229,10 @@ struct { int HP_pc_db_checkid_post; int HP_pc_validate_levels_pre; int HP_pc_validate_levels_post; + int HP_pc_process_chat_message_pre; + int HP_pc_process_chat_message_post; + int HP_pc_check_supernovice_call_pre; + int HP_pc_check_supernovice_call_post; int HP_pc_autotrade_load_pre; int HP_pc_autotrade_load_post; int HP_pc_autotrade_update_pre; diff --git a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc index 8d9752849..2b29d14bb 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc @@ -512,7 +512,8 @@ struct HookingPointData HookingPoints[] = { { HP_POP(clif->msgtable_skill, HP_clif_msgtable_skill) }, { HP_POP(clif->message, HP_clif_message) }, { HP_POP(clif->messageln, HP_clif_messageln) }, - { HP_POP(clif->process_message, HP_clif_process_message) }, + { HP_POP(clif->process_chat_message, HP_clif_process_chat_message) }, + { HP_POP(clif->process_whisper_message, HP_clif_process_whisper_message) }, { HP_POP(clif->wisexin, HP_clif_wisexin) }, { HP_POP(clif->wisall, HP_clif_wisall) }, { HP_POP(clif->PMIgnoreList, HP_clif_PMIgnoreList) }, @@ -2219,6 +2220,8 @@ struct HookingPointData HookingPoints[] = { { HP_POP(pc->expire_check, HP_pc_expire_check) }, { HP_POP(pc->db_checkid, HP_pc_db_checkid) }, { HP_POP(pc->validate_levels, HP_pc_validate_levels) }, + { HP_POP(pc->process_chat_message, HP_pc_process_chat_message) }, + { HP_POP(pc->check_supernovice_call, HP_pc_check_supernovice_call) }, { HP_POP(pc->autotrade_load, HP_pc_autotrade_load) }, { HP_POP(pc->autotrade_update, HP_pc_autotrade_update) }, { HP_POP(pc->autotrade_start, HP_pc_autotrade_start) }, diff --git a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc index fa7b192c6..d867c9ed0 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc @@ -12820,15 +12820,42 @@ void HP_clif_messageln(const int fd, const char *mes) { } return; } -bool HP_clif_process_message(struct map_session_data *sd, int format, const char **name_, size_t *namelen_, const char **message_, size_t *messagelen_) { +const char* HP_clif_process_chat_message(struct map_session_data *sd, const struct packet_chat_message *packet, char *out_buf, int out_buflen) { + int hIndex = 0; + const char* retVal___ = NULL; + if( HPMHooks.count.HP_clif_process_chat_message_pre ) { + const char* (*preHookFunc) (struct map_session_data *sd, const struct packet_chat_message *packet, char *out_buf, int *out_buflen); + *HPMforce_return = false; + for(hIndex = 0; hIndex < HPMHooks.count.HP_clif_process_chat_message_pre; hIndex++ ) { + preHookFunc = HPMHooks.list.HP_clif_process_chat_message_pre[hIndex].func; + retVal___ = preHookFunc(sd, packet, out_buf, &out_buflen); + } + if( *HPMforce_return ) { + *HPMforce_return = false; + return retVal___; + } + } + { + retVal___ = HPMHooks.source.clif.process_chat_message(sd, packet, out_buf, out_buflen); + } + if( HPMHooks.count.HP_clif_process_chat_message_post ) { + const char* (*postHookFunc) (const char* retVal___, struct map_session_data *sd, const struct packet_chat_message *packet, char *out_buf, int *out_buflen); + for(hIndex = 0; hIndex < HPMHooks.count.HP_clif_process_chat_message_post; hIndex++ ) { + postHookFunc = HPMHooks.list.HP_clif_process_chat_message_post[hIndex].func; + retVal___ = postHookFunc(retVal___, sd, packet, out_buf, &out_buflen); + } + } + return retVal___; +} +bool HP_clif_process_whisper_message(struct map_session_data *sd, const struct packet_whisper_message *packet, char *out_name, char *out_message, int out_messagelen) { int hIndex = 0; bool retVal___ = false; - if( HPMHooks.count.HP_clif_process_message_pre ) { - bool (*preHookFunc) (struct map_session_data *sd, int *format, const char **name_, size_t *namelen_, const char **message_, size_t *messagelen_); + if( HPMHooks.count.HP_clif_process_whisper_message_pre ) { + bool (*preHookFunc) (struct map_session_data *sd, const struct packet_whisper_message *packet, char *out_name, char *out_message, int *out_messagelen); *HPMforce_return = false; - for(hIndex = 0; hIndex < HPMHooks.count.HP_clif_process_message_pre; hIndex++ ) { - preHookFunc = HPMHooks.list.HP_clif_process_message_pre[hIndex].func; - retVal___ = preHookFunc(sd, &format, name_, namelen_, message_, messagelen_); + for(hIndex = 0; hIndex < HPMHooks.count.HP_clif_process_whisper_message_pre; hIndex++ ) { + preHookFunc = HPMHooks.list.HP_clif_process_whisper_message_pre[hIndex].func; + retVal___ = preHookFunc(sd, packet, out_name, out_message, &out_messagelen); } if( *HPMforce_return ) { *HPMforce_return = false; @@ -12836,13 +12863,13 @@ bool HP_clif_process_message(struct map_session_data *sd, int format, const char } } { - retVal___ = HPMHooks.source.clif.process_message(sd, format, name_, namelen_, message_, messagelen_); + retVal___ = HPMHooks.source.clif.process_whisper_message(sd, packet, out_name, out_message, out_messagelen); } - if( HPMHooks.count.HP_clif_process_message_post ) { - bool (*postHookFunc) (bool retVal___, struct map_session_data *sd, int *format, const char **name_, size_t *namelen_, const char **message_, size_t *messagelen_); - for(hIndex = 0; hIndex < HPMHooks.count.HP_clif_process_message_post; hIndex++ ) { - postHookFunc = HPMHooks.list.HP_clif_process_message_post[hIndex].func; - retVal___ = postHookFunc(retVal___, sd, &format, name_, namelen_, message_, messagelen_); + if( HPMHooks.count.HP_clif_process_whisper_message_post ) { + bool (*postHookFunc) (bool retVal___, struct map_session_data *sd, const struct packet_whisper_message *packet, char *out_name, char *out_message, int *out_messagelen); + for(hIndex = 0; hIndex < HPMHooks.count.HP_clif_process_whisper_message_post; hIndex++ ) { + postHookFunc = HPMHooks.list.HP_clif_process_whisper_message_post[hIndex].func; + retVal___ = postHookFunc(retVal___, sd, packet, out_name, out_message, &out_messagelen); } } return retVal___; @@ -57836,6 +57863,59 @@ void HP_pc_validate_levels(void) { } return; } +bool HP_pc_process_chat_message(struct map_session_data *sd, const char *message) { + int hIndex = 0; + bool retVal___ = false; + if( HPMHooks.count.HP_pc_process_chat_message_pre ) { + bool (*preHookFunc) (struct map_session_data *sd, const char *message); + *HPMforce_return = false; + for(hIndex = 0; hIndex < HPMHooks.count.HP_pc_process_chat_message_pre; hIndex++ ) { + preHookFunc = HPMHooks.list.HP_pc_process_chat_message_pre[hIndex].func; + retVal___ = preHookFunc(sd, message); + } + if( *HPMforce_return ) { + *HPMforce_return = false; + return retVal___; + } + } + { + retVal___ = HPMHooks.source.pc.process_chat_message(sd, message); + } + if( HPMHooks.count.HP_pc_process_chat_message_post ) { + bool (*postHookFunc) (bool retVal___, struct map_session_data *sd, const char *message); + for(hIndex = 0; hIndex < HPMHooks.count.HP_pc_process_chat_message_post; hIndex++ ) { + postHookFunc = HPMHooks.list.HP_pc_process_chat_message_post[hIndex].func; + retVal___ = postHookFunc(retVal___, sd, message); + } + } + return retVal___; +} +void HP_pc_check_supernovice_call(struct map_session_data *sd, const char *message) { + int hIndex = 0; + if( HPMHooks.count.HP_pc_check_supernovice_call_pre ) { + void (*preHookFunc) (struct map_session_data *sd, const char *message); + *HPMforce_return = false; + for(hIndex = 0; hIndex < HPMHooks.count.HP_pc_check_supernovice_call_pre; hIndex++ ) { + preHookFunc = HPMHooks.list.HP_pc_check_supernovice_call_pre[hIndex].func; + preHookFunc(sd, message); + } + if( *HPMforce_return ) { + *HPMforce_return = false; + return; + } + } + { + HPMHooks.source.pc.check_supernovice_call(sd, message); + } + if( HPMHooks.count.HP_pc_check_supernovice_call_post ) { + void (*postHookFunc) (struct map_session_data *sd, const char *message); + for(hIndex = 0; hIndex < HPMHooks.count.HP_pc_check_supernovice_call_post; hIndex++ ) { + postHookFunc = HPMHooks.list.HP_pc_check_supernovice_call_post[hIndex].func; + postHookFunc(sd, message); + } + } + return; +} void HP_pc_autotrade_load(void) { int hIndex = 0; if( HPMHooks.count.HP_pc_autotrade_load_pre ) { |