// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL // For more information, see LICENCE in the main folder #include "char_clif.hpp" #include #include #include "../common/malloc.hpp" #include "../common/mapindex.hpp" #include "../common/mmo.hpp" #include "../common/random.hpp" #include "../common/showmsg.hpp" #include "../common/socket.hpp" #include "../common/sql.hpp" #include "../common/strlib.hpp" #include "../common/timer.hpp" #include "../common/utils.hpp" #include "char.hpp" #include "char_logif.hpp" #include "char_mapif.hpp" #include "inter.hpp" #include "packets.hpp" #if PACKETVER_SUPPORTS_PINCODE bool pincode_allowed( char* pincode ); #endif //------------------------------------------------ //Add On system //------------------------------------------------ // reason // 0: success // 1: failed void chclif_moveCharSlotReply( int fd, struct char_session_data* sd, unsigned short index, short reason ){ WFIFOHEAD(fd,8); WFIFOW(fd,0) = HEADER_HC_ACK_CHANGE_CHARACTER_SLOT; WFIFOW(fd,2) = 8; WFIFOW(fd,4) = reason; WFIFOW(fd,6) = sd->char_moves[index]; WFIFOSET(fd,8); } /* * Client is requesting to move a charslot */ int chclif_parse_moveCharSlot( int fd, struct char_session_data* sd){ FIFOSD_CHECK(8); uint16 from, to; from = RFIFOW(fd,2); to = RFIFOW(fd,4); //Cnt = RFIFOW(fd,6); //how many time we have left to change (client.. lol we don't trust him) RFIFOSKIP(fd,8); // Have we changed to often or is it disabled? if( (charserv_config.charmove_config.char_move_enabled)==0 || ( (charserv_config.charmove_config.char_moves_unlimited)==0 && sd->char_moves[from] <= 0 ) ){ chclif_moveCharSlotReply( fd, sd, from, 1 ); return 1; } // We don't even have a character on the chosen slot? if( sd->found_char[from] <= 0 || to >= sd->char_slots ){ chclif_moveCharSlotReply( fd, sd, from, 1 ); return 1; } if( sd->found_char[to] > 0 ){ // We want to move to a used position if( charserv_config.charmove_config.char_movetoused ){ // TODO: check if the target is in deletion process // Admin is friendly and uses triangle exchange if( SQL_ERROR == Sql_QueryStr(sql_handle, "START TRANSACTION") || SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `char_num`='%d' WHERE `char_id` = '%d'",schema_config.char_db, to, sd->found_char[from] ) || SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `char_num`='%d' WHERE `char_id` = '%d'", schema_config.char_db, from, sd->found_char[to] ) || SQL_ERROR == Sql_QueryStr(sql_handle, "COMMIT") ){ chclif_moveCharSlotReply( fd, sd, from, 1 ); Sql_ShowDebug(sql_handle); Sql_QueryStr(sql_handle,"ROLLBACK"); return 1; } }else{ // Admin doesn't allow us to chclif_moveCharSlotReply( fd, sd, from, 1 ); return 1; } }else if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `char_num`='%d' WHERE `char_id`='%d'", schema_config.char_db, to, sd->found_char[from] ) ){ Sql_ShowDebug(sql_handle); chclif_moveCharSlotReply( fd, sd, from, 1 ); return 1; } if( (charserv_config.charmove_config.char_moves_unlimited)==0 ){ sd->char_moves[from]--; Sql_Query(sql_handle, "UPDATE `%s` SET `moves`='%d' WHERE `char_id`='%d'", schema_config.char_db, sd->char_moves[from], sd->found_char[from] ); } // We successfully moved the char - time to notify the client chclif_moveCharSlotReply( fd, sd, from, 0 ); chclif_mmo_char_send(fd, sd); return 1; } #if PACKETVER_SUPPORTS_PINCODE /* pincode_sendstate transmist the pincode state to client * S 08b9 .L .L .W (HC_SECOND_PASSWD_LOGIN) * state : * 0 = disabled / pin is correct * 1 = ask for pin - client sends 0x8b8 * 2 = create new pin - client sends 0x8ba * 3 = pin must be changed - client 0x8be * 4 = create new pin - client sends 0x8ba * 5 = client shows msgstr(1896) * 6 = client shows msgstr(1897) Unable to use your KSSN number * 7 = char select window shows a button - client sends 0x8c5 * 8 = pincode was incorrect */ void chclif_pincode_sendstate( int fd, struct char_session_data* sd, enum pincode_state state ){ WFIFOHEAD(fd, 12); WFIFOW(fd, 0) = 0x8b9; WFIFOL(fd, 2) = sd->pincode_seed = rnd() % 0xFFFF; WFIFOL(fd, 6) = sd->account_id; WFIFOW(fd,10) = state; WFIFOSET(fd,12); } /* * Client just entering charserv from login, send him pincode confirmation */ int chclif_parse_reqpincode_window(int fd, struct char_session_data* sd){ FIFOSD_CHECK(6); if( charserv_config.pincode_config.pincode_enabled && RFIFOL(fd,2) == sd->account_id ){ if( strlen( sd->pincode ) <= 0 ){ chclif_pincode_sendstate( fd, sd, PINCODE_NEW ); }else{ chclif_pincode_sendstate( fd, sd, PINCODE_ASK ); } } RFIFOSKIP(fd,6); return 1; } /* * Client as anwsered pincode questionning, checking if valid anwser */ int chclif_parse_pincode_check( int fd, struct char_session_data* sd ){ FIFOSD_CHECK(10); char pin[PINCODE_LENGTH+1]; if( charserv_config.pincode_config.pincode_enabled==0 || RFIFOL(fd,2) != sd->account_id ) return 1; memset(pin,0,PINCODE_LENGTH+1); strncpy((char*)pin, RFIFOCP(fd, 6), PINCODE_LENGTH); RFIFOSKIP(fd,10); char_pincode_decrypt(sd->pincode_seed, pin ); if( char_pincode_compare( fd, sd, pin ) ){ chclif_pincode_sendstate( fd, sd, PINCODE_PASSED ); } return 1; } /* * Helper function to check if a new pincode contains illegal characters or combinations */ bool pincode_allowed( char* pincode ){ int i; char c, n, compare[PINCODE_LENGTH+1]; memset( compare, 0, PINCODE_LENGTH+1); // Sanity check for bots to prevent errors for( i = 0; i < PINCODE_LENGTH; i++ ){ c = pincode[i]; if( c < '0' || c > '9' ){ return false; } } // Is it forbidden to use only the same character? if( !charserv_config.pincode_config.pincode_allow_repeated ){ c = pincode[0]; // Check if the first character equals the rest of the input for( i = 0; i < PINCODE_LENGTH; i++ ){ compare[i] = c; } if( strncmp( pincode, compare, PINCODE_LENGTH + 1 ) == 0 ){ return false; } } // Is it forbidden to use a sequential combination of numbers? if( !charserv_config.pincode_config.pincode_allow_sequential ){ c = pincode[0]; // Check if it is an ascending sequence for( i = 0; i < PINCODE_LENGTH; i++ ){ n = c + i; if( n > '9' ){ compare[i] = '0' + ( n - '9' ) - 1; }else{ compare[i] = n; } } if( strncmp( pincode, compare, PINCODE_LENGTH + 1 ) == 0 ){ return false; } // Check if it is an descending sequence for( i = 0; i < PINCODE_LENGTH; i++ ){ n = c - i; if( n < '0' ){ compare[i] = '9' - ( '0' - n ) + 1; }else{ compare[i] = n; } } if( strncmp( pincode, compare, PINCODE_LENGTH + 1 ) == 0 ){ return false; } } return true; } /* * Client request to change pincode */ int chclif_parse_pincode_change( int fd, struct char_session_data* sd ){ FIFOSD_CHECK(14); if( charserv_config.pincode_config.pincode_enabled==0 || RFIFOL(fd,2) != sd->account_id ) return 1; else { char oldpin[PINCODE_LENGTH+1]; char newpin[PINCODE_LENGTH+1]; memset(oldpin,0,PINCODE_LENGTH+1); memset(newpin,0,PINCODE_LENGTH+1); strncpy(oldpin, RFIFOCP(fd,6), PINCODE_LENGTH); strncpy(newpin, RFIFOCP(fd,10), PINCODE_LENGTH); RFIFOSKIP(fd,14); char_pincode_decrypt(sd->pincode_seed,oldpin); if( !char_pincode_compare( fd, sd, oldpin ) ) return 1; char_pincode_decrypt(sd->pincode_seed,newpin); if( pincode_allowed(newpin) ){ chlogif_pincode_notifyLoginPinUpdate( sd->account_id, newpin ); strncpy(sd->pincode, newpin, sizeof(newpin)); ShowInfo("Pincode changed for AID: %d\n", sd->account_id); chclif_pincode_sendstate( fd, sd, PINCODE_PASSED ); }else{ chclif_pincode_sendstate( fd, sd, PINCODE_ILLEGAL ); } } return 1; } /* * activate PIN system and set first PIN */ int chclif_parse_pincode_setnew( int fd, struct char_session_data* sd ){ FIFOSD_CHECK(10); if( charserv_config.pincode_config.pincode_enabled==0 || RFIFOL(fd,2) != sd->account_id ) return 1; else { char newpin[PINCODE_LENGTH+1]; memset(newpin,0,PINCODE_LENGTH+1); strncpy( newpin, RFIFOCP(fd,6), PINCODE_LENGTH ); RFIFOSKIP(fd,10); char_pincode_decrypt( sd->pincode_seed, newpin ); if( pincode_allowed(newpin) ){ chlogif_pincode_notifyLoginPinUpdate( sd->account_id, newpin ); strncpy( sd->pincode, newpin, sizeof( newpin ) ); chclif_pincode_sendstate( fd, sd, PINCODE_PASSED ); }else{ chclif_pincode_sendstate( fd, sd, PINCODE_ILLEGAL ); } } return 1; } #endif //---------------------------------------- // Tell client how many pages, kRO sends 17 (Yommy) //---------------------------------------- void chclif_charlist_notify( int fd, struct char_session_data* sd ){ // This is needed on RE clients from october 2015 onwards #if defined(PACKETVER_RE) && PACKETVER >= 20151001 && PACKETVER < 20180103 WFIFOHEAD(fd, 10); WFIFOW(fd, 0) = 0x9a0; // pages to req / send them all in 1 until mmo_chars_fromsql can split them up WFIFOL(fd, 2) = (sd->char_slots>3)?sd->char_slots/3:1; //int TotalCnt (nb page to load) WFIFOL(fd, 6) = sd->char_slots; WFIFOSET(fd,10); #else WFIFOHEAD(fd, 6); WFIFOW(fd, 0) = 0x9a0; // pages to req / send them all in 1 until mmo_chars_fromsql can split them up WFIFOL(fd, 2) = (sd->char_slots>3)?sd->char_slots/3:1; //int TotalCnt (nb page to load) WFIFOSET(fd,6); #endif } //---------------------------------------- // Function to send characters to a player //---------------------------------------- int chclif_mmo_send006b(int fd, struct char_session_data* sd){ int j, offset; #if PACKETVER >= 20100413 offset = 3; #else offset = 0; #endif if (charserv_config.save_log) ShowInfo("Loading Char Data (" CL_BOLD "%d" CL_RESET ")\n",sd->account_id); j = 24 + offset; // offset WFIFOHEAD(fd,j + MAX_CHARS*MAX_CHAR_BUF); WFIFOW(fd,0) = 0x6b; #if PACKETVER >= 20100413 WFIFOB(fd,4) = MAX_CHARS; // Max slots. WFIFOB(fd,5) = MIN_CHARS; // Available slots. (PremiumStartSlot) WFIFOB(fd,6) = MIN_CHARS+sd->chars_vip; // Premium slots. (Any existent chars past sd->char_slots but within MAX_CHARS will show a 'Premium Service' in red) #endif memset(WFIFOP(fd,4 + offset), 0, 20); // unknown bytes j+=char_mmo_chars_fromsql(sd, WFIFOP(fd,j)); WFIFOW(fd,2) = j; // packet len WFIFOSET(fd,j); return 0; } //---------------------------------------- // Notify client about charselect window data [Ind] //---------------------------------------- void chclif_mmo_send082d(int fd, struct char_session_data* sd) { if (charserv_config.save_log) ShowInfo("Loading Char Data (" CL_BOLD "%d" CL_RESET ")\n",sd->account_id); WFIFOHEAD(fd,29); WFIFOW(fd,0) = 0x82d; WFIFOW(fd,2) = 29; WFIFOB(fd,4) = MIN_CHARS; // normal_slot WFIFOB(fd,5) = sd->chars_vip; // premium_slot WFIFOB(fd,6) = sd->chars_billing; // billing_slot WFIFOB(fd,7) = sd->char_slots; // producible_slot WFIFOB(fd,8) = MAX_CHARS; // valid_slot memset(WFIFOP(fd,9), 0, 20); // unused bytes WFIFOSET(fd,29); } void chclif_mmo_send099d(int fd, struct char_session_data *sd) { uint8 count = 0; WFIFOHEAD(fd,4 + (MAX_CHARS*MAX_CHAR_BUF)); WFIFOW(fd,0) = HEADER_HC_ACK_CHARINFO_PER_PAGE; WFIFOW(fd,2) = char_mmo_chars_fromsql(sd, WFIFOP(fd,4), &count) + 4; WFIFOSET(fd,WFIFOW(fd,2)); // This is something special Gravity came up with. // The client triggers some finalization code only if count is != 3. if( count == 3 ){ WFIFOHEAD(fd,4); WFIFOW(fd,0) = HEADER_HC_ACK_CHARINFO_PER_PAGE; WFIFOW(fd,2) = 4; WFIFOSET(fd,4); } } /* * Function to choose wich kind of charlist to send to client depending on his version */ void chclif_mmo_char_send(int fd, struct char_session_data* sd){ #if PACKETVER >= 20130000 chclif_mmo_send082d(fd, sd); chclif_mmo_send006b(fd, sd); chclif_charlist_notify(fd, sd); #else chclif_mmo_send006b(fd,sd); //@FIXME dump from kro doesn't show 6b transmission #endif #if PACKETVER >= 20060819 chclif_block_character(fd,sd); #endif } /* * Transmit auth result to client * .B () * result : * 1 : Server closed * 2 : Someone has already logged in with this id * 8 : already online */ void chclif_send_auth_result(int fd,char result){ WFIFOHEAD(fd,3); WFIFOW(fd,0) = 0x81; WFIFOB(fd,2) = result; WFIFOSET(fd,3); } /// @param result /// 0 (0x718): An unknown error has occurred. /// 1: none/success /// 3 (0x719): A database error occurred. /// 4 (0x71a): To delete a character you must withdraw from the guild. /// 5 (0x71b): To delete a character you must withdraw from the party. /// Any (0x718): An unknown error has occurred. /// HC: <0828>.W .L .L .L void chclif_char_delete2_ack(int fd, uint32 char_id, uint32 result, time_t delete_date) { WFIFOHEAD(fd,14); WFIFOW(fd,0) = 0x828; WFIFOL(fd,2) = char_id; WFIFOL(fd,6) = result; #if PACKETVER_CHAR_DELETEDATE WFIFOL(fd,10) = TOL(delete_date-time(NULL)); #else WFIFOL(fd,10) = TOL(delete_date); #endif WFIFOSET(fd,14); } /// @param result /// 0 (0x718): An unknown error has occurred. /// 1: none/success /// 2 (0x71c): Due to system settings can not be deleted. /// 3 (0x719): A database error occurred. /// 4 (0x71d): Deleting not yet possible time. /// 5 (0x71e): Date of birth do not match. /// 6 Name does not match. /// 7 Character Deletion has failed because you have entered an incorrect e-mail address. /// Any (0x718): An unknown error has occurred. /// HC: <082a>.W .L .L void chclif_char_delete2_accept_ack(int fd, uint32 char_id, uint32 result) { #if PACKETVER >= 20130000 if(result == 1 ){ chclif_mmo_char_send(fd, (char_session_data*)session[fd]->session_data); } #endif WFIFOHEAD(fd,10); WFIFOW(fd,0) = 0x82a; WFIFOL(fd,2) = char_id; WFIFOL(fd,6) = result; WFIFOSET(fd,10); } /// @param result /// 1 (0x718): none/success, (if char id not in deletion process): An unknown error has occurred. /// 2 (0x719): A database error occurred. /// Any (0x718): An unknown error has occurred. /// HC: <082c>.W .L .L void chclif_char_delete2_cancel_ack(int fd, uint32 char_id, uint32 result) { WFIFOHEAD(fd,10); WFIFOW(fd,0) = 0x82c; WFIFOL(fd,2) = char_id; WFIFOL(fd,6) = result; WFIFOSET(fd,10); } // CH: <0827>.W .L int chclif_parse_char_delete2_req(int fd, struct char_session_data* sd) { FIFOSD_CHECK(6) { uint32 char_id, i, guild_id, party_id; char* data; time_t delete_date; char_id = RFIFOL(fd,2); RFIFOSKIP(fd,6); ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == char_id ); if( i == MAX_CHARS ) {// character not found chclif_char_delete2_ack(fd, char_id, 3, 0); return 1; } if( SQL_SUCCESS != Sql_Query(sql_handle, "SELECT `delete_date`,`party_id`,`guild_id` FROM `%s` WHERE `char_id`='%d'", schema_config.char_db, char_id) || SQL_SUCCESS != Sql_NextRow(sql_handle) ) { Sql_ShowDebug(sql_handle); chclif_char_delete2_ack(fd, char_id, 3, 0); return 1; } Sql_GetData(sql_handle, 0, &data, NULL); delete_date = strtoul(data, NULL, 10); Sql_GetData(sql_handle, 1, &data, NULL); party_id = strtoul(data, NULL, 10); Sql_GetData(sql_handle, 2, &data, NULL); guild_id = strtoul(data, NULL, 10); if( delete_date ) {// character already queued for deletion chclif_char_delete2_ack(fd, char_id, 0, 0); return 1; } if (charserv_config.char_config.char_del_restriction&CHAR_DEL_RESTRICT_GUILD && guild_id) // character is in guild { chclif_char_delete2_ack(fd, char_id, 4, 0); return 1; } if (charserv_config.char_config.char_del_restriction&CHAR_DEL_RESTRICT_PARTY && party_id) // character is in party { chclif_char_delete2_ack(fd, char_id, 5, 0); return 1; } // success delete_date = time(NULL)+(charserv_config.char_config.char_del_delay); if( SQL_SUCCESS != Sql_Query(sql_handle, "UPDATE `%s` SET `delete_date`='%lu' WHERE `char_id`='%d'", schema_config.char_db, (unsigned long)delete_date, char_id) ) { Sql_ShowDebug(sql_handle); chclif_char_delete2_ack(fd, char_id, 3, 0); return 1; } chclif_char_delete2_ack(fd, char_id, 1, delete_date); } return 1; } /** * Check char deletion code * @param sd * @param delcode E-mail or birthdate * @param flag Delete flag * @return true:Success, false:Failure **/ bool chclif_delchar_check(struct char_session_data *sd, char *delcode, uint8 flag) { // E-Mail check if (flag&CHAR_DEL_EMAIL && ( !stricmp(delcode, sd->email) || //email does not match or ( !stricmp("a@a.com", sd->email) && //it is default email and !strcmp("", delcode) //user sent an empty email ))) { ShowInfo("" CL_RED "Char Deleted" CL_RESET " " CL_GREEN "(E-Mail)" CL_RESET ".\n"); return true; } // Birthdate (YYMMDD) if (flag&CHAR_DEL_BIRTHDATE && ( !strcmp(sd->birthdate+2, delcode) || // +2 to cut off the century ( !strcmp("",sd->birthdate) && // it is default birthdate and !strcmp("",delcode) // user sent an empty birthdate ))) { ShowInfo("" CL_RED "Char Deleted" CL_RESET " " CL_GREEN "(Birthdate)" CL_RESET ".\n"); return true; } return false; } // CH: <0829>.W .L .6B int chclif_parse_char_delete2_accept(int fd, struct char_session_data* sd) { FIFOSD_CHECK(12) { char birthdate[8+1]; uint32 char_id; char_id = RFIFOL(fd,2); ShowInfo(CL_RED "Request Char Deletion: " CL_GREEN "%d (%d)" CL_RESET "\n", sd->account_id, char_id); // construct "YY-MM-DD" birthdate[0] = RFIFOB(fd,6); birthdate[1] = RFIFOB(fd,7); birthdate[2] = '-'; birthdate[3] = RFIFOB(fd,8); birthdate[4] = RFIFOB(fd,9); birthdate[5] = '-'; birthdate[6] = RFIFOB(fd,10); birthdate[7] = RFIFOB(fd,11); birthdate[8] = 0; RFIFOSKIP(fd,12); // Only check for birthdate if (!chclif_delchar_check(sd, birthdate, CHAR_DEL_BIRTHDATE)) { chclif_char_delete2_accept_ack(fd, char_id, 5); return 1; } switch( char_delete(sd,char_id) ){ // success case CHAR_DELETE_OK: chclif_char_delete2_accept_ack(fd, char_id, 1); break; // data error case CHAR_DELETE_DATABASE: // character not found case CHAR_DELETE_NOTFOUND: chclif_char_delete2_accept_ack(fd, char_id, 3); break; // in a party case CHAR_DELETE_PARTY: // in a guild case CHAR_DELETE_GUILD: // character level config restriction case CHAR_DELETE_BASELEVEL: chclif_char_delete2_accept_ack(fd, char_id, 2); break; // not queued or delay not yet passed case CHAR_DELETE_TIME: chclif_char_delete2_accept_ack(fd, char_id, 4); break; } } return 1; } // CH: <082b>.W .L int chclif_parse_char_delete2_cancel(int fd, struct char_session_data* sd) { FIFOSD_CHECK(6); uint32 char_id, i; char_id = RFIFOL(fd,2); RFIFOSKIP(fd,6); ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == char_id ); if( i == MAX_CHARS ) {// character not found chclif_char_delete2_cancel_ack(fd, char_id, 2); return 1; } // there is no need to check, whether or not the character was // queued for deletion, as the client prints an error message by // itself, if it was not the case (@see char_delete2_cancel_ack) if( SQL_SUCCESS != Sql_Query(sql_handle, "UPDATE `%s` SET `delete_date`='0' WHERE `char_id`='%d'", schema_config.char_db, char_id) ) { Sql_ShowDebug(sql_handle); chclif_char_delete2_cancel_ack(fd, char_id, 2); return 1; } chclif_char_delete2_cancel_ack(fd, char_id, 1); return 1; } /* * Register a new mapserver into that char-serv * charserv can handle a MAX_SERVERS mapservs */ int chclif_parse_maplogin(int fd){ if (RFIFOREST(fd) < 60) return 0; else { int i; char* l_user = RFIFOCP(fd,2); char* l_pass = RFIFOCP(fd,26); l_user[23] = '\0'; l_pass[23] = '\0'; ARR_FIND( 0, ARRAYLENGTH(map_server), i, map_server[i].fd <= 0 ); if( !global_core->is_running() || i == ARRAYLENGTH(map_server) || strcmp(l_user, charserv_config.userid) != 0 || strcmp(l_pass, charserv_config.passwd) != 0 ) { chmapif_connectack(fd, 3); //fail } else { chmapif_connectack(fd, 0); //success map_server[i].fd = fd; map_server[i].ip = ntohl(RFIFOL(fd,54)); map_server[i].port = ntohs(RFIFOW(fd,58)); map_server[i].users = 0; map_server[i].map = {}; session[fd]->func_parse = chmapif_parse; session[fd]->flag.server = 1; realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); chmapif_init(fd); } RFIFOSKIP(fd,60); } return 0; } // 0065 .L .L .L .W .B int chclif_parse_reqtoconnect(int fd, struct char_session_data* sd,uint32 ipl){ if( RFIFOREST(fd) < 17 ) // request to connect return 0; else { struct auth_node* node; DBMap *auth_db = char_get_authdb(); uint32 account_id = RFIFOL(fd,2); uint32 login_id1 = RFIFOL(fd,6); uint32 login_id2 = RFIFOL(fd,10); int sex = RFIFOB(fd,16); RFIFOSKIP(fd,17); ShowInfo("request connect - account_id:%d/login_id1:%d/login_id2:%d\n", account_id, login_id1, login_id2); if (sd) { //Received again auth packet for already authentified account?? Discard it. //TODO: Perhaps log this as a hack attempt? //TODO: and perhaps send back a reply? ShowInfo("Already registered break\n"); return 1; } CREATE(session[fd]->session_data, struct char_session_data, 1); sd = (struct char_session_data*)session[fd]->session_data; sd->account_id = account_id; sd->login_id1 = login_id1; sd->login_id2 = login_id2; sd->sex = sex; sd->auth = false; // not authed yet // send back account_id WFIFOHEAD(fd,4); WFIFOL(fd,0) = account_id; WFIFOSET(fd,4); if( !global_core->is_running() ){ chclif_reject(fd, 0); // rejected from server return 1; } // search authentification node = (struct auth_node*)idb_get(auth_db, account_id); if( node != NULL && node->account_id == account_id && node->login_id1 == login_id1 && node->login_id2 == login_id2 /*&& node->ip == ipl*/ ) {// authentication found (coming from map server) idb_remove(auth_db, account_id); char_auth_ok(fd, sd); } else {// authentication not found (coming from login server) if (session_isValid(login_fd)) { // don't send request if no login-server WFIFOHEAD(login_fd,23); WFIFOW(login_fd,0) = 0x2712; // ask login-server to authentify an account WFIFOL(login_fd,2) = sd->account_id; WFIFOL(login_fd,6) = sd->login_id1; WFIFOL(login_fd,10) = sd->login_id2; WFIFOB(login_fd,14) = sd->sex; WFIFOL(login_fd,15) = htonl(ipl); WFIFOL(login_fd,19) = fd; WFIFOSET(login_fd,23); } else { // if no login-server, we must refuse connection chclif_reject(fd, 0); // rejected from server } } } return 1; } //struct PACKET_CH_CHARLIST_REQ { 0x0 short PacketType} int chclif_parse_req_charlist(int fd, struct char_session_data* sd){ FIFOSD_CHECK(2); RFIFOSKIP(fd,2); chclif_mmo_send099d(fd,sd); return 1; } //Send player to map void chclif_send_map_data( int fd, struct mmo_charstatus *cd, uint32 ipl, int map_server_index ){ #if PACKETVER >= 20170315 int cmd = 0xAC5; int size = 156; #else int cmd = 0x71; int size = 28; #endif WFIFOHEAD(fd,size); WFIFOW(fd,0) = cmd; WFIFOL(fd,2) = cd->char_id; mapindex_getmapname_ext(mapindex_id2name(cd->last_point.map), WFIFOCP(fd,6)); uint32 subnet_map_ip = char_lan_subnetcheck(ipl); // Advanced subnet check [LuzZza] WFIFOL(fd,22) = htonl((subnet_map_ip) ? subnet_map_ip : map_server[map_server_index].ip); WFIFOW(fd,26) = ntows(htons(map_server[map_server_index].port)); // [!] LE byte order here [!] #if PACKETVER >= 20170315 memset(WFIFOP(fd, 28), 0, 128); // Unknown #endif WFIFOSET(fd,size); } int chclif_parse_charselect(int fd, struct char_session_data* sd,uint32 ipl){ FIFOSD_CHECK(3) { struct mmo_charstatus char_dat; struct mmo_charstatus *cd; char* data; uint32 char_id; struct auth_node* node; int i, map_fd, server_id; DBMap *auth_db = char_get_authdb(); DBMap *char_db_ = char_get_chardb(); int slot = RFIFOB(fd,2); RFIFOSKIP(fd,3); ARR_FIND( 0, ARRAYLENGTH(map_server), server_id, session_isValid(map_server[server_id].fd) && !map_server[server_id].map.empty() ); // Map-server not available, tell the client to wait (client wont close, char select will respawn) if (server_id == ARRAYLENGTH(map_server)) { WFIFOHEAD(fd, 24); WFIFOW(fd, 0) = 0x840; WFIFOW(fd, 2) = 24; strncpy(WFIFOCP(fd, 4), "0", 20); // we can't send it empty (otherwise the list will pop up) WFIFOSET(fd, 24); return 1; } // Check if the character exists and is not scheduled for deletion if ( SQL_SUCCESS != Sql_Query(sql_handle, "SELECT `char_id` FROM `%s` WHERE `account_id`='%d' AND `char_num`='%d' AND `delete_date` = 0", schema_config.char_db, sd->account_id, slot) || SQL_SUCCESS != Sql_NextRow(sql_handle) || SQL_SUCCESS != Sql_GetData(sql_handle, 0, &data, NULL) ) { //Not found?? May be forged packet. Sql_ShowDebug(sql_handle); Sql_FreeResult(sql_handle); chclif_reject(fd, 0); // rejected from server return 1; } char_id = atoi(data); Sql_FreeResult(sql_handle); // Prevent select a char while retrieving guild bound items if (sd->flag&1) { chclif_reject(fd, 0); // rejected from server return 1; } /* client doesn't let it get to this point if you're banned, so its a forged packet */ if( sd->found_char[slot] == char_id && sd->unban_time[slot] > time(NULL) ) { chclif_reject(fd, 0); // rejected from server return 1; } /* set char as online prior to loading its data so 3rd party applications will realise the sql data is not reliable */ char_set_char_online(-2,char_id,sd->account_id); if( !char_mmo_char_fromsql(char_id, &char_dat, true) ) { /* failed? set it back offline */ char_set_char_offline(char_id, sd->account_id); /* failed to load something. REJECT! */ chclif_reject(fd, 0); /* jump off this boat */ return 1; } //Have to switch over to the DB instance otherwise data won't propagate [Kevin] cd = (struct mmo_charstatus *)idb_get(char_db_, char_id); if (charserv_config.log_char) { char esc_name[NAME_LENGTH*2+1]; Sql_EscapeStringLen(sql_handle, esc_name, char_dat.name, strnlen(char_dat.name, NAME_LENGTH)); if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`time`, `account_id`,`char_num`,`name`) VALUES (NOW(), '%d', '%d', '%s')", schema_config.charlog_db, sd->account_id, slot, esc_name) ) Sql_ShowDebug(sql_handle); } ShowInfo("Selected char: (Account %d: %d - %s)\n", sd->account_id, slot, char_dat.name); // searching map server i = char_search_mapserver(cd->last_point.map, -1, -1); // if map is not found, we check major cities if (i < 0 || !cd->last_point.map) { unsigned short j; //First check that there's actually a map server online. ARR_FIND( 0, ARRAYLENGTH(map_server), j, session_isValid(map_server[j].fd) && !map_server[j].map.empty() ); if (j == ARRAYLENGTH(map_server)) { ShowInfo("Connection Closed. No map servers available.\n"); chclif_send_auth_result(fd,1); // 01 = Server closed return 1; } if ((i = char_search_mapserver((j=mapindex_name2id(MAP_PRONTERA)),-1,-1)) >= 0) { cd->last_point.x = 273; cd->last_point.y = 354; } else if ((i = char_search_mapserver((j=mapindex_name2id(MAP_GEFFEN)),-1,-1)) >= 0) { cd->last_point.x = 120; cd->last_point.y = 100; } else if ((i = char_search_mapserver((j=mapindex_name2id(MAP_MORROC)),-1,-1)) >= 0) { cd->last_point.x = 160; cd->last_point.y = 94; } else if ((i = char_search_mapserver((j=mapindex_name2id(MAP_ALBERTA)),-1,-1)) >= 0) { cd->last_point.x = 116; cd->last_point.y = 57; } else if ((i = char_search_mapserver((j=mapindex_name2id(MAP_PAYON)),-1,-1)) >= 0) { cd->last_point.x = 87; cd->last_point.y = 117; } else if ((i = char_search_mapserver((j=mapindex_name2id(MAP_IZLUDE)),-1,-1)) >= 0) { cd->last_point.x = 94; cd->last_point.y = 103; } else { ShowInfo("Connection Closed. No map server available that has a major city, and unable to find map-server for '%s'.\n", mapindex_id2name(cd->last_point.map)); chclif_send_auth_result(fd,1); // 01 = Server closed return 1; } ShowWarning("Unable to find map-server for '%s', sending to major city '%s'.\n", mapindex_id2name(cd->last_point.map), mapindex_id2name(j)); cd->last_point.map = j; } //Send NEW auth packet [Kevin] //FIXME: is this case even possible? [ultramage] if ((map_fd = map_server[i].fd) < 1 || session[map_fd] == NULL) { ShowError("parse_char: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", map_fd, i); map_server[i].fd = -1; memset(&map_server[i], 0, sizeof(struct mmo_map_server)); chclif_send_auth_result(fd,1); //Send server closed. return 1; } chclif_send_map_data( fd, cd, ipl, i ); // create temporary auth entry CREATE(node, struct auth_node, 1); node->account_id = sd->account_id; node->char_id = cd->char_id; node->login_id1 = sd->login_id1; node->login_id2 = sd->login_id2; node->sex = sd->sex; node->expiration_time = sd->expiration_time; node->group_id = sd->group_id; node->ip = ipl; idb_put(auth_db, sd->account_id, node); } return 1; } // S 0970 .24B .B .W .W // S 0067 .24B .B .B .B .B .B .B .B .W .W // S 0a39 .24B .B .W .W .W .(W or 2 B's)??? .B int chclif_parse_createnewchar(int fd, struct char_session_data* sd,int cmd){ if (cmd == 0xa39) FIFOSD_CHECK(36) //>=20151001 else if (cmd == 0x970) FIFOSD_CHECK(31) //>=20120307 else if (cmd == 0x67) FIFOSD_CHECK(37) else return 0; int char_id; if( (charserv_config.char_new)==0 ) //turn character creation on/off [Kevin] char_id = -2; else { char name[NAME_LENGTH]; int str, agi, vit, int_, dex, luk; int slot; int hair_color; int hair_style; short start_job; int sex; #if PACKETVER >= 20151001 // Sent values safestrncpy( name, RFIFOCP( fd, 2 ), NAME_LENGTH ); slot = RFIFOB( fd, 26 ); hair_color = RFIFOW( fd, 27 ); hair_style = RFIFOW( fd, 29 ); start_job = RFIFOW( fd, 31 ); // Unknown RFIFOW( fd, 32 ) sex = RFIFOB( fd, 35 ); // Default values str = 1; agi = 1; vit = 1; int_ = 1; dex = 1; luk = 1; RFIFOSKIP( fd, 36 ); #elif PACKETVER >= 20120307 // Sent values safestrncpy( name, RFIFOCP( fd, 2 ), NAME_LENGTH ); slot = RFIFOB( fd, 26 ); hair_color = RFIFOW( fd, 27 ); hair_style = RFIFOW( fd, 29 ); // Default values str = 1; agi = 1; vit = 1; int_ = 1; dex = 1; luk = 1; start_job = JOB_NOVICE; sex = sd->sex; RFIFOSKIP( fd, 31 ); #else // Sent values safestrncpy( name, RFIFOCP( fd, 2 ), NAME_LENGTH ); str = RFIFOB( fd, 26 ); agi = RFIFOB( fd, 27 ); vit = RFIFOB( fd, 28 ); int_ = RFIFOB( fd, 29 ); dex = RFIFOB( fd, 30 ); luk = RFIFOB( fd, 31 ); slot = RFIFOB( fd, 32 ); hair_color = RFIFOW( fd, 33 ); hair_style = RFIFOW( fd, 35 ); // Default values start_job = JOB_NOVICE; sex = sd->sex; RFIFOSKIP( fd, 37 ); #endif char_id = char_make_new_char( sd, name, str, agi, vit, int_, dex, luk, slot, hair_color, hair_style, start_job, sex ); } if (char_id < 0) { // deny character creation WFIFOHEAD(fd,3); WFIFOW(fd,0) = 0x6e; switch (char_id) { // 'Charname already exists' (-1) case -1: WFIFOB(fd,2) = 0x00; break; // 'Char creation denied' (-2) case -2: WFIFOB(fd,2) = 0xFF; break; // 'You are underaged' (-3) case -3: WFIFOB(fd,2) = 0x01; break; // 'You are not elegible to open the Character Slot' (-4) case -4: WFIFOB(fd,2) = 0x03; break; /* Unused: 0x02 = Symbols in Character Names are forbidden [Ind]*/ } WFIFOSET(fd,3); } else { int len; // retrieve data struct mmo_charstatus char_dat; char_mmo_char_fromsql(char_id, &char_dat, false); //Only the short data is needed. // send to player WFIFOHEAD(fd,2+MAX_CHAR_BUF); WFIFOW(fd,0) = HEADER_HC_ACCEPT_MAKECHAR; len = 2 + char_mmo_char_tobuf(WFIFOP(fd,2), &char_dat); WFIFOSET(fd,len); // add new entry to the chars list sd->found_char[char_dat.slot] = char_id; } return 1; } /** * Inform client that his deletion request was refused * 0x70 B HC_REFUSE_DELETECHAR * @param fd * @param ErrorCode * 00 = Incorrect Email address * 01 = Invalid Slot * 02 = In a party or guild */ void chclif_refuse_delchar(int fd, uint8 errCode){ WFIFOHEAD(fd,3); WFIFOW(fd,0) = 0x70; WFIFOB(fd,2) = errCode; WFIFOSET(fd,3); } int chclif_parse_delchar(int fd,struct char_session_data* sd, int cmd){ if (cmd == 0x68) FIFOSD_CHECK(46) else if (cmd == 0x1fb) FIFOSD_CHECK(56) else return 0; { char email[40]; uint32 cid = RFIFOL(fd,2); ShowInfo(CL_RED "Request Char Deletion: " CL_GREEN "%u (%u)" CL_RESET "\n", sd->account_id, cid); memcpy(email, RFIFOP(fd,6), 40); RFIFOSKIP(fd,( cmd == 0x68) ? 46 : 56); if (!chclif_delchar_check(sd, email, charserv_config.char_config.char_del_option)) { chclif_refuse_delchar(fd,0); // 00 = Incorrect Email address return 1; } /* Delete character */ switch( char_delete(sd,cid) ){ case CHAR_DELETE_OK: break; case CHAR_DELETE_DATABASE: case CHAR_DELETE_BASELEVEL: case CHAR_DELETE_TIME: chclif_refuse_delchar(fd, 0); return 1; case CHAR_DELETE_NOTFOUND: chclif_refuse_delchar(fd, 1); return 1; case CHAR_DELETE_GUILD: case CHAR_DELETE_PARTY: chclif_refuse_delchar(fd, 2); return 1; } /* Char successfully deleted.*/ WFIFOHEAD(fd,2); WFIFOW(fd,0) = 0x6f; WFIFOSET(fd,2); } return 1; } // R 0187 .l int chclif_parse_keepalive(int fd){ if (RFIFOREST(fd) < 6) return 0; //int aid = RFIFOL(fd,2); RFIFOSKIP(fd,6); return 1; } // Tells the client if the name was accepted or not // 028e .W (HC_ACK_IS_VALID_CHARNAME) // result: // 0 = name is not OK // 1 = name is OK void chclif_reqrename_response( int fd, struct char_session_data* sd, bool name_valid ){ WFIFOHEAD(fd, 4); WFIFOW(fd, 0) = 0x28e; WFIFOW(fd, 2) = name_valid; WFIFOSET(fd, 4); } // Request for checking the new name on character renaming // 028d .l .l .24B (CH_REQ_IS_VALID_CHARNAME) int chclif_parse_reqrename( int fd, struct char_session_data* sd ){ FIFOSD_CHECK(34); int i, cid, aid; char name[NAME_LENGTH]; char esc_name[NAME_LENGTH*2+1]; aid = RFIFOL(fd,2); cid = RFIFOL(fd,6); safestrncpy(name, RFIFOCP(fd,10), NAME_LENGTH); RFIFOSKIP(fd,34); if( aid != sd->account_id ) return 1; ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == cid ); if( i == MAX_CHARS ) return 1; normalize_name(name,TRIM_CHARS); Sql_EscapeStringLen(sql_handle, esc_name, name, strnlen(name, NAME_LENGTH)); if( !char_check_char_name(name,esc_name) ) { i = 1; safestrncpy(sd->new_name, name, NAME_LENGTH); } else i = 0; chclif_reqrename_response(fd, sd, i > 0); return 1; } TIMER_FUNC(charblock_timer){ struct char_session_data* sd=NULL; int i=0; ARR_FIND( 0, fd_max, i, session[i] && (sd = (struct char_session_data*)session[i]->session_data) && sd->account_id == id); if(sd == NULL || sd->charblock_timer==INVALID_TIMER) //has disconected or was required to stop return 0; if (sd->charblock_timer != tid){ sd->charblock_timer = INVALID_TIMER; return 0; } chclif_block_character(i,sd); return 0; } /* * 0x20d .W 24B (HC_BLOCK_CHARACTER) * L 20B (TAG_CHARACTER_BLOCK_INFO) */ void chclif_block_character( int fd, struct char_session_data* sd){ int i=0, j=0, len=4; time_t now = time(NULL); WFIFOHEAD(fd, 4+MAX_CHARS*24); WFIFOW(fd, 0) = 0x20d; for(i=0; ifound_char[i] == -1) continue; if(sd->unban_time[i]){ if( sd->unban_time[i] > now ) { char szExpireDate[21]; WFIFOL(fd, 4+j*24) = sd->found_char[i]; timestamp2string(szExpireDate, 20, sd->unban_time[i], "%Y-%m-%d %H:%M:%S"); memcpy(WFIFOP(fd,8+j*24),szExpireDate,20); } else { WFIFOL(fd, 4+j*24) = 0; sd->unban_time[i] = 0; if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `unban_time`='0' WHERE `char_id`='%d' LIMIT 1", schema_config.char_db, sd->found_char[i]) ) Sql_ShowDebug(sql_handle); } len+=24; j++; //pkt list idx } } WFIFOW(fd, 2) = len; //packet len WFIFOSET(fd,len); ARR_FIND(0, MAX_CHARS, i, sd->unban_time[i] > now); //sd->charslot only have productible char if(i < MAX_CHARS ){ sd->charblock_timer = add_timer( gettick() + 10000, // each 10s resend that list charblock_timer, sd->account_id, 0); } } // Sends the response to a rename request to the client. // 0290 .W (HC_ACK_CHANGE_CHARNAME) // 08fd .L (HC_ACK_CHANGE_CHARACTERNAME) // result: // 0: Successful // 1: This character's name has already been changed. You cannot change a character's name more than once. // 2: User information is not correct. // 3: You have failed to change this character's name. // 4: Another user is using this character name, so please select another one. // 5: In order to change the character name, you must leave the guild. // 6: In order to change the character name, you must leave the party. // 7: Length exceeds the maximum size of the character name you want to change. // 8: Name contains invalid characters. Character name change failed. // 9: The name change is prohibited. Character name change failed. // 10: Character name change failed, due an unknown error. void chclif_rename_response(int fd, struct char_session_data* sd, int16 response) { #if PACKETVER >= 20111101 WFIFOHEAD(fd, 6); WFIFOW(fd, 0) = 0x8fd; WFIFOL(fd, 2) = response; WFIFOSET(fd, 6); #else WFIFOHEAD(fd, 4); WFIFOW(fd, 0) = 0x290; WFIFOW(fd, 2) = response; WFIFOSET(fd, 4); #endif } // Request to change a character name // 028f .L (CH_REQ_CHANGE_CHARNAME) // 08fc .L .24B (CH_REQ_CHANGE_CHARACTERNAME) int chclif_parse_ackrename(int fd, struct char_session_data* sd){ #if PACKETVER >= 20111101 FIFOSD_CHECK(30) { int i, cid; char name[NAME_LENGTH], esc_name[NAME_LENGTH * 2 + 1]; cid = RFIFOL(fd, 2); safestrncpy(name, RFIFOCP(fd, 6), NAME_LENGTH); RFIFOSKIP(fd, 30); ARR_FIND(0, MAX_CHARS, i, sd->found_char[i] == cid); if (i == MAX_CHARS) return 1; normalize_name(name, TRIM_CHARS); Sql_EscapeStringLen(sql_handle, esc_name, name, strnlen(name, NAME_LENGTH)); safestrncpy(sd->new_name, name, NAME_LENGTH); // Start the renaming process i = char_rename_char_sql(sd, cid); chclif_rename_response(fd, sd, i); // If the renaming was successful, we need to resend the characters if( i == 0 ) chclif_mmo_char_send(fd, sd); return 1; } #else FIFOSD_CHECK(6) { int i; int cid = RFIFOL(fd,2); RFIFOSKIP(fd,6); ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == cid ); if( i == MAX_CHARS ) return 1; i = char_rename_char_sql(sd, cid); chclif_rename_response(fd, sd, i); } return 1; #endif } int chclif_ack_captcha(int fd){ WFIFOHEAD(fd,5); WFIFOW(fd,0) = 0x7e9; WFIFOW(fd,2) = 5; WFIFOB(fd,4) = 1; WFIFOSET(fd,5); return 1; } // R 06C B HEADER_HC_REFUSE_ENTER void chclif_reject(int fd, uint8 errCode){ WFIFOHEAD(fd,3); WFIFOW(fd,0) = 0x6c; WFIFOB(fd,2) = errCode;// rejected from server WFIFOSET(fd,3); } // R 07e5 .w .l int chclif_parse_reqcaptcha(int fd){ //FIFOSD_CHECK(8) RFIFOSKIP(fd,8); chclif_ack_captcha(fd); return 1; } // R 07e7 .w .l .b10 .b14 int chclif_parse_chkcaptcha(int fd){ //FIFOSD_CHECK(32) RFIFOSKIP(fd,32); chclif_ack_captcha(fd); return 1; } /** * Entry point from client to char-serv * function that check incoming command then split it to correct handler. * @param fd: file descriptor to parse, (link to client) */ int chclif_parse(int fd) { struct char_session_data* sd = (struct char_session_data*)session[fd]->session_data; uint32 ipl = session[fd]->client_addr; // disconnect any player if no login-server. if(login_fd < 0) set_eof(fd); if(session[fd]->flag.eof) { if( sd != NULL && sd->auth ) { // already authed client DBMap *online_char_db = char_get_onlinedb(); struct online_char_data* data = (struct online_char_data*)idb_get(online_char_db, sd->account_id); if( data != NULL && data->fd == fd) data->fd = -1; if( data == NULL || data->server == -1) //If it is not in any server, send it offline. [Skotlex] char_set_char_offline(-1,sd->account_id); } do_close(fd); return 0; } while( RFIFOREST(fd) >= 2 ) { int next = 1; unsigned short cmd; cmd = RFIFOW(fd,0); switch( cmd ) { case 0x65: next=chclif_parse_reqtoconnect(fd,sd,ipl); break; // char select case 0x66: next=chclif_parse_charselect(fd,sd,ipl); break; // createnewchar case 0xa39: case 0x970: case 0x67: next=chclif_parse_createnewchar(fd,sd,cmd); break; // delete char case 0x68: next=chclif_parse_delchar(fd,sd,cmd); break; // case 0x1fb: next=chclif_parse_delchar(fd,sd,cmd); break; // 2004-04-19aSakexe+ langtype 12 char deletion packet // client keep-alive packet (every 12 seconds) case 0x187: next=chclif_parse_keepalive(fd); break; // char rename case 0x28d: next=chclif_parse_reqrename(fd,sd); break; //Check new desired name case 0x28f: next=chclif_parse_ackrename(fd,sd); break; //Confirm change name. case 0x8fc: next=chclif_parse_ackrename(fd,sd); break; //Rename request without confirmation // captcha case 0x7e5: next=chclif_parse_reqcaptcha(fd); break; // captcha code request (not implemented) case 0x7e7: next=chclif_parse_chkcaptcha(fd); break; // captcha code check (not implemented) // deletion timer request case 0x827: next=chclif_parse_char_delete2_req(fd, sd); break; // deletion accept request case 0x829: next=chclif_parse_char_delete2_accept(fd, sd); break; // deletion cancel request case 0x82b: next=chclif_parse_char_delete2_cancel(fd, sd); break; // login as map-server case 0x2af8: chclif_parse_maplogin(fd); return 0; // avoid processing of followup packets here #if PACKETVER_SUPPORTS_PINCODE //pincode case 0x8b8: next=chclif_parse_pincode_check( fd, sd ); break; // checks the entered pin case 0x8c5: next=chclif_parse_reqpincode_window(fd,sd); break; // request for PIN window case 0x8be: next=chclif_parse_pincode_change( fd, sd ); break; // pincode change request case 0x8ba: next=chclif_parse_pincode_setnew( fd, sd ); break; // activate PIN system and set first PIN #endif // character movement request case 0x8d4: next=chclif_parse_moveCharSlot(fd,sd); break; case 0x9a1: next=chclif_parse_req_charlist(fd,sd); break; // unknown packet received default: ShowError("parse_char: Received unknown packet " CL_WHITE "0x%x" CL_RESET " from ip '" CL_WHITE "%s" CL_RESET "'! Disconnecting!\n", RFIFOW(fd,0), ip2str(ipl, NULL)); set_eof(fd); return 0; } if (next == 0) return 0; // avoid processing of followup packets (prev was probably incomplete) } RFIFOFLUSH(fd); return 0; }