// Copyright (c) Hercules Dev Team, licensed under GNU GPL.
// See the LICENSE file
// Portions Copyright (c) Athena Dev Teams
#define HERCULES_CORE
#include "inter.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "char.h"
#include "geoip.h"
#include "int_auction.h"
#include "int_elemental.h"
#include "int_guild.h"
#include "int_homun.h"
#include "int_mail.h"
#include "int_mercenary.h"
#include "int_party.h"
#include "int_pet.h"
#include "int_quest.h"
#include "int_storage.h"
#include "mapif.h"
#include "../common/cbasetypes.h"
#include "../common/db.h"
#include "../common/malloc.h"
#include "../common/mmo.h"
#include "../common/showmsg.h"
#include "../common/socket.h"
#include "../common/strlib.h"
#include "../common/timer.h"
#define WISDATA_TTL (60*1000) // Wis data Time To Live (60 seconds)
#define WISDELLIST_MAX 256 // Number of elements in the list Delete data Wis
struct inter_interface inter_s;
int char_server_port = 3306;
char char_server_ip[32] = "127.0.0.1";
char char_server_id[32] = "ragnarok";
char char_server_pw[100] = "ragnarok";
char char_server_db[32] = "ragnarok";
char default_codepage[32] = ""; //Feature by irmin.
unsigned int party_share_level = 10;
// recv. packet list
int inter_recv_packet_length[] = {
-1,-1, 7,-1, -1,13,36, (2 + 4 + 4 + 4 + NAME_LENGTH), 0, 0, 0, 0, 0, 0, 0, 0, // 3000-
6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, // 3010-
-1,10,-1,14, 14,19, 6,-1, 14,14, 0, 0, 0, 0, 0, 0, // 3020- Party
-1, 6,-1,-1, 55,19, 6,-1, 14,-1,-1,-1, 18,19,186,-1, // 3030-
-1, 9, 0, 0, 0, 0, 0, 0, 7, 6,10,10, 10,-1, 0, 0, // 3040-
-1,-1,10,10, 0,-1,12, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3050- Auction System [Zephyrus], Item Bound [Mhalicot]
6,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3060- Quest system [Kevin] [Inkfish]
-1,10, 6,-1, 0, 0, 0, 0, 0, 0, 0, 0, -1,10, 6,-1, // 3070- Mercenary packets [Zephyrus], Elemental packets [pakpil]
48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3080-
-1,10,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3090- Homunculus packets [albator]
};
struct WisData {
int id, fd, count, len;
int64 tick;
unsigned char src[24], dst[24], msg[512];
};
static DBMap* wis_db = NULL; // int wis_id -> struct WisData*
static int wis_dellist[WISDELLIST_MAX], wis_delnum;
#define MAX_JOB_NAMES 150
static char* msg_table[MAX_JOB_NAMES]; // messages 550 ~ 699 are job names
const char* inter_msg_txt(int msg_number) {
msg_number -= 550;
if (msg_number >= 0 && msg_number < MAX_JOB_NAMES &&
msg_table[msg_number] != NULL && msg_table[msg_number][0] != '\0')
return msg_table[msg_number];
return "Unknown";
}
/**
* Reads Message Data.
*
* This is a modified version of the mapserver's inter->msg_config_read to
* only read messages with IDs between 550 and 550+MAX_JOB_NAMES.
*
* @param[in] cfg_name configuration filename to read.
* @param[in] allow_override whether to allow duplicate message IDs to override the original value.
* @return success state.
*/
bool inter_msg_config_read(const char *cfg_name, bool allow_override)
{
int msg_number;
char line[1024], w1[1024], w2[1024];
FILE *fp;
static int called = 1;
if ((fp = fopen(cfg_name, "r")) == NULL) {
ShowError("Messages file not found: %s\n", cfg_name);
return 1;
}
if ((--called) == 0)
memset(msg_table, 0, sizeof(msg_table[0]) * MAX_JOB_NAMES);
while(fgets(line, sizeof(line), fp) ) {
if (line[0] == '/' && line[1] == '/')
continue;
if (sscanf(line, "%1023[^:]: %1023[^\r\n]", w1, w2) != 2)
continue;
if (strcmpi(w1, "import") == 0)
inter->msg_config_read(w2, true);
else {
msg_number = atoi(w1);
if( msg_number < 550 || msg_number > (550+MAX_JOB_NAMES) )
continue;
msg_number -= 550;
if (msg_number >= 0 && msg_number < MAX_JOB_NAMES) {
if (msg_table[msg_number] != NULL) {
if (!allow_override) {
ShowError("Duplicate message: ID '%d' was already used for '%s'. Message '%s' will be ignored.\n",
msg_number, w2, msg_table[msg_number]);
continue;
}
aFree(msg_table[msg_number]);
}
msg_table[msg_number] = (char *)aMalloc((strlen(w2) + 1)*sizeof (char));
strcpy(msg_table[msg_number],w2);
}
}
}
fclose(fp);
return 0;
}
/*==========================================
* Cleanup Message Data
*------------------------------------------*/
void inter_do_final_msg(void)
{
int i;
for (i = 0; i < MAX_JOB_NAMES; i++)
aFree(msg_table[i]);
}
/* from pc.c due to @accinfo. any ideas to replace this crap are more than welcome. */
const char* inter_job_name(int class_)
{
switch (class_) {
case JOB_NOVICE: // 550
case JOB_SWORDMAN: // 551
case JOB_MAGE: // 552
case JOB_ARCHER: // 553
case JOB_ACOLYTE: // 554
case JOB_MERCHANT: // 555
case JOB_THIEF: // 556
return inter->msg_txt(550 - JOB_NOVICE+class_);
case JOB_KNIGHT: // 557
case JOB_PRIEST: // 558
case JOB_WIZARD: // 559
case JOB_BLACKSMITH: // 560
case JOB_HUNTER: // 561
case JOB_ASSASSIN: // 562
return inter->msg_txt(557 - JOB_KNIGHT+class_);
case JOB_KNIGHT2:
return inter->msg_txt(557);
case JOB_CRUSADER: // 563
case JOB_MONK: // 564
case JOB_SAGE: // 565
case JOB_ROGUE: // 566
case JOB_ALCHEMIST: // 567
case JOB_BARD: // 568
case JOB_DANCER: // 569
return inter->msg_txt(563 - JOB_CRUSADER+class_);
case JOB_CRUSADER2:
return inter->msg_txt(563);
case JOB_WEDDING: // 570
case JOB_SUPER_NOVICE: // 571
case JOB_GUNSLINGER: // 572
case JOB_NINJA: // 573
case JOB_XMAS: // 574
return inter->msg_txt(570 - JOB_WEDDING+class_);
case JOB_SUMMER:
return inter->msg_txt(621);
case JOB_NOVICE_HIGH: // 575
case JOB_SWORDMAN_HIGH: // 576
case JOB_MAGE_HIGH: // 577
case JOB_ARCHER_HIGH: // 578
case JOB_ACOLYTE_HIGH: // 579
case JOB_MERCHANT_HIGH: // 580
case JOB_THIEF_HIGH: // 581
return inter->msg_txt(575 - JOB_NOVICE_HIGH+class_);
case JOB_LORD_KNIGHT: // 582
case JOB_HIGH_PRIEST: // 583
case JOB_HIGH_WIZARD: // 584
case JOB_WHITESMITH: // 585
case JOB_SNIPER: // 586
case JOB_ASSASSIN_CROSS: // 587
return inter->msg_txt(582 - JOB_LORD_KNIGHT+class_);
case JOB_LORD_KNIGHT2:
return inter->msg_txt(582);
case JOB_PALADIN: // 588
case JOB_CHAMPION: // 589
case JOB_PROFESSOR: // 590
case JOB_STALKER: // 591
case JOB_CREATOR: // 592
case JOB_CLOWN: // 593
case JOB_GYPSY: // 594
return inter->msg_txt(588 - JOB_PALADIN + class_);
case JOB_PALADIN2:
return inter->msg_txt(588);
case JOB_BABY: // 595
case JOB_BABY_SWORDMAN: // 596
case JOB_BABY_MAGE: // 597
case JOB_BABY_ARCHER: // 598
case JOB_BABY_ACOLYTE: // 599
case JOB_BABY_MERCHANT: // 600
case JOB_BABY_THIEF: // 601
return inter->msg_txt(595 - JOB_BABY + class_);
case JOB_BABY_KNIGHT: // 602
case JOB_BABY_PRIEST: // 603
case JOB_BABY_WIZARD: // 604
case JOB_BABY_BLACKSMITH: // 605
case JOB_BABY_HUNTER: // 606
case JOB_BABY_ASSASSIN: // 607
return inter->msg_txt(602 - JOB_BABY_KNIGHT + class_);
case JOB_BABY_KNIGHT2:
return inter->msg_txt(602);
case JOB_BABY_CRUSADER: // 608
case JOB_BABY_MONK: // 609
case JOB_BABY_SAGE: // 610
case JOB_BABY_ROGUE: // 611
case JOB_BABY_ALCHEMIST: // 612
case JOB_BABY_BARD: // 613
case JOB_BABY_DANCER: // 614
return inter->msg_txt(608 - JOB_BABY_CRUSADER + class_);
case JOB_BABY_CRUSADER2:
return inter->msg_txt(608);
case JOB_SUPER_BABY:
return inter->msg_txt(615);
case JOB_TAEKWON:
return inter->msg_txt(616);
case JOB_STAR_GLADIATOR:
case JOB_STAR_GLADIATOR2:
return inter->msg_txt(617);
case JOB_SOUL_LINKER:
return inter->msg_txt(618);
case JOB_GANGSI: // 622
case JOB_DEATH_KNIGHT: // 623
case JOB_DARK_COLLECTOR: // 624
return inter->msg_txt(622 - JOB_GANGSI+class_);
case JOB_RUNE_KNIGHT: // 625
case JOB_WARLOCK: // 626
case JOB_RANGER: // 627
case JOB_ARCH_BISHOP: // 628
case JOB_MECHANIC: // 629
case JOB_GUILLOTINE_CROSS: // 630
return inter->msg_txt(625 - JOB_RUNE_KNIGHT+class_);
case JOB_RUNE_KNIGHT_T: // 656
case JOB_WARLOCK_T: // 657
case JOB_RANGER_T: // 658
case JOB_ARCH_BISHOP_T: // 659
case JOB_MECHANIC_T: // 660
case JOB_GUILLOTINE_CROSS_T: // 661
return inter->msg_txt(656 - JOB_RUNE_KNIGHT_T+class_);
case JOB_ROYAL_GUARD: // 631
case JOB_SORCERER: // 632
case JOB_MINSTREL: // 633
case JOB_WANDERER: // 634
case JOB_SURA: // 635
case JOB_GENETIC: // 636
case JOB_SHADOW_CHASER: // 637
return inter->msg_txt(631 - JOB_ROYAL_GUARD+class_);
case JOB_ROYAL_GUARD_T: // 662
case JOB_SORCERER_T: // 663
case JOB_MINSTREL_T: // 664
case JOB_WANDERER_T: // 665
case JOB_SURA_T: // 666
case JOB_GENETIC_T: // 667
case JOB_SHADOW_CHASER_T: // 668
return inter->msg_txt(662 - JOB_ROYAL_GUARD_T+class_);
case JOB_RUNE_KNIGHT2:
return inter->msg_txt(625);
case JOB_RUNE_KNIGHT_T2:
return inter->msg_txt(656);
case JOB_ROYAL_GUARD2:
return inter->msg_txt(631);
case JOB_ROYAL_GUARD_T2:
return inter->msg_txt(662);
case JOB_RANGER2:
return inter->msg_txt(627);
case JOB_RANGER_T2:
return inter->msg_txt(658);
case JOB_MECHANIC2:
return inter->msg_txt(629);
case JOB_MECHANIC_T2:
return inter->msg_txt(660);
case JOB_BABY_RUNE: // 638
case JOB_BABY_WARLOCK: // 639
case JOB_BABY_RANGER: // 640
case JOB_BABY_BISHOP: // 641
case JOB_BABY_MECHANIC: // 642
case JOB_BABY_CROSS: // 643
case JOB_BABY_GUARD: // 644
case JOB_BABY_SORCERER: // 645
case JOB_BABY_MINSTREL: // 646
case JOB_BABY_WANDERER: // 647
case JOB_BABY_SURA: // 648
case JOB_BABY_GENETIC: // 649
case JOB_BABY_CHASER: // 650
return inter->msg_txt(638 - JOB_BABY_RUNE+class_);
case JOB_BABY_RUNE2:
return inter->msg_txt(638);
case JOB_BABY_GUARD2:
return inter->msg_txt(644);
case JOB_BABY_RANGER2:
return inter->msg_txt(640);
case JOB_BABY_MECHANIC2:
return inter->msg_txt(642);
case JOB_SUPER_NOVICE_E: // 651
case JOB_SUPER_BABY_E: // 652
return inter->msg_txt(651 - JOB_SUPER_NOVICE_E+class_);
case JOB_KAGEROU: // 653
case JOB_OBORO: // 654
return inter->msg_txt(653 - JOB_KAGEROU+class_);
case JOB_REBELLION:
return inter->msg_txt(655);
default:
return inter->msg_txt(620); // "Unknown Job"
}
}
/**
* Argument-list version of inter_msg_to_fd
* @see inter_msg_to_fd
*/
void inter_vmsg_to_fd(int fd, int u_fd, int aid, char* msg, va_list ap)
{
char msg_out[512];
va_list apcopy;
int len = 1;/* yes we start at 1 */
va_copy(apcopy, ap);
len += vsnprintf(msg_out, 512, msg, apcopy);
va_end(apcopy);
WFIFOHEAD(fd,12 + len);
WFIFOW(fd,0) = 0x3807;
WFIFOW(fd,2) = 12 + (unsigned short)len;
WFIFOL(fd,4) = u_fd;
WFIFOL(fd,8) = aid;
safestrncpy((char*)WFIFOP(fd,12), msg_out, len);
WFIFOSET(fd,12 + len);
return;
}
/**
* Sends a message to map server (fd) to a user (u_fd) although we use fd we
* keep aid for safe-check.
* @param fd Mapserver's fd
* @param u_fd Recipient's fd
* @param aid Recipient's expected for sanity checks on the mapserver
* @param msg Message format string
* @param ... Additional parameters for (v)sprinf
*/
void inter_msg_to_fd(int fd, int u_fd, int aid, char *msg, ...) __attribute__((format(printf, 4, 5)));
void inter_msg_to_fd(int fd, int u_fd, int aid, char *msg, ...)
{
va_list ap;
va_start(ap,msg);
inter->vmsg_to_fd(fd, u_fd, aid, msg, ap);
va_end(ap);
}
/* [Dekamaster/Nightroad] */
void mapif_parse_accinfo(int fd)
{
int u_fd = RFIFOL(fd,2), aid = RFIFOL(fd,6), castergroup = RFIFOL(fd,10);
char query[NAME_LENGTH], query_esq[NAME_LENGTH*2+1];
int account_id;
char *data;
safestrncpy(query, (char*) RFIFOP(fd,14), NAME_LENGTH);
SQL->EscapeString(inter->sql_handle, query_esq, query);
account_id = atoi(query);
if (account_id < START_ACCOUNT_NUM) {
// is string
if ( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `account_id`,`name`,`class`,`base_level`,`job_level`,`online` FROM `%s` WHERE `name` LIKE '%s' LIMIT 10", char_db, query_esq)
|| SQL->NumRows(inter->sql_handle) == 0 ) {
if( SQL->NumRows(inter->sql_handle) == 0 ) {
inter->msg_to_fd(fd, u_fd, aid, "No matches were found for your criteria, '%s'",query);
} else {
Sql_ShowDebug(inter->sql_handle);
inter->msg_to_fd(fd, u_fd, aid, "An error occurred, bother your admin about it.");
}
SQL->FreeResult(inter->sql_handle);
return;
} else {
if( SQL->NumRows(inter->sql_handle) == 1 ) {//we found a perfect match
SQL->NextRow(inter->sql_handle);
SQL->GetData(inter->sql_handle, 0, &data, NULL); account_id = atoi(data);
SQL->FreeResult(inter->sql_handle);
} else {// more than one, listing... [Dekamaster/Nightroad]
inter->msg_to_fd(fd, u_fd, aid, "Your query returned the following %d results, please be more specific...",(int)SQL->NumRows(inter->sql_handle));
while ( SQL_SUCCESS == SQL->NextRow(inter->sql_handle) ) {
int class_;
short base_level, job_level, online;
char name[NAME_LENGTH];
SQL->GetData(inter->sql_handle, 0, &data, NULL); account_id = atoi(data);
SQL->GetData(inter->sql_handle, 1, &data, NULL); safestrncpy(name, data, sizeof(name));
SQL->GetData(inter->sql_handle, 2, &data, NULL); class_ = atoi(data);
SQL->GetData(inter->sql_handle, 3, &data, NULL); base_level = atoi(data);
SQL->GetData(inter->sql_handle, 4, &data, NULL); job_level = atoi(data);
SQL->GetData(inter->sql_handle, 5, &data, NULL); online = atoi(data);
inter->msg_to_fd(fd, u_fd, aid, "[AID: %d] %s | %s | Level: %d/%d | %s", account_id, name, inter->job_name(class_), base_level, job_level, online?"Online":"Offline");
}
SQL->FreeResult(inter->sql_handle);
return;
}
}
}
/* it will only get here if we have a single match */
/* and we will send packet with account id to login server asking for account info */
if( account_id ) {
mapif->on_parse_accinfo(account_id, u_fd, aid, castergroup, fd);
}
return;
}
void mapif_parse_accinfo2(bool success, int map_fd, int u_fd, int u_aid, int account_id, const char *userid, const char *user_pass,
const char *email, const char *last_ip, const char *lastlogin, const char *pin_code, const char *birthdate,
int group_id, int logincount, int state)
{
if (map_fd <= 0 || !session_isActive(map_fd))
return; // check if we have a valid fd
if (!success) {
inter->msg_to_fd(map_fd, u_fd, u_aid, "No account with ID '%d' was found.", account_id);
return;
}
inter->msg_to_fd(map_fd, u_fd, u_aid, "-- Account %d --", account_id);
inter->msg_to_fd(map_fd, u_fd, u_aid, "User: %s | GM Group: %d | State: %d", userid, group_id, state);
if (user_pass && *user_pass != '\0') { /* password is only received if your gm level is greater than the one you're searching for */
if (pin_code && *pin_code != '\0')
inter->msg_to_fd(map_fd, u_fd, u_aid, "Password: %s (PIN:%s)", user_pass, pin_code);
else
inter->msg_to_fd(map_fd, u_fd, u_aid, "Password: %s", user_pass );
}
inter->msg_to_fd(map_fd, u_fd, u_aid, "Account e-mail: %s | Birthdate: %s", email, birthdate);
inter->msg_to_fd(map_fd, u_fd, u_aid, "Last IP: %s (%s)", last_ip, geoip->getcountry(str2ip(last_ip)));
inter->msg_to_fd(map_fd, u_fd, u_aid, "This user has logged %d times, the last time were at %s", logincount, lastlogin);
inter->msg_to_fd(map_fd, u_fd, u_aid, "-- Character Details --");
if ( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `char_id`, `name`, `char_num`, `class`, `base_level`, `job_level`, `online` "
"FROM `%s` WHERE `account_id` = '%d' ORDER BY `char_num` LIMIT %d", char_db, account_id, MAX_CHARS)
|| SQL->NumRows(inter->sql_handle) == 0 ) {
if (SQL->NumRows(inter->sql_handle) == 0) {
inter->msg_to_fd(map_fd, u_fd, u_aid, "This account doesn't have characters.");
} else {
inter->msg_to_fd(map_fd, u_fd, u_aid, "An error occurred, bother your admin about it.");
Sql_ShowDebug(inter->sql_handle);
}
} else {
while ( SQL_SUCCESS == SQL->NextRow(inter->sql_handle) ) {
char *data;
int char_id, class_;
short char_num, base_level, job_level, online;
char name[NAME_LENGTH];
SQL->GetData(inter->sql_handle, 0, &data, NULL); char_id = atoi(data);
SQL->GetData(inter->sql_handle, 1, &data, NULL); safestrncpy(name, data, sizeof(name));
SQL->GetData(inter->sql_handle, 2, &data, NULL); char_num = atoi(data);
SQL->GetData(inter->sql_handle, 3, &data, NULL); class_ = atoi(data);
SQL->GetData(inter->sql_handle, 4, &data, NULL); base_level = atoi(data);
SQL->GetData(inter->sql_handle, 5, &data, NULL); job_level = atoi(data);
SQL->GetData(inter->sql_handle, 6, &data, NULL); online = atoi(data);
inter->msg_to_fd(map_fd, u_fd, u_aid, "[Slot/CID: %d/%d] %s | %s | Level: %d/%d | %s", char_num, char_id, name, inter->job_name(class_), base_level, job_level, online?"On":"Off");
}
}
SQL->FreeResult(inter->sql_handle);
return;
}
/**
* Handles save reg data from map server and distributes accordingly.
*
* @param val either str or int, depending on type
* @param type false when int, true otherwise
**/
void inter_savereg(int account_id, int char_id, const char *key, unsigned int index, intptr_t val, bool is_string)
{
/* to login server we go! */
if( key[0] == '#' && key[1] == '#' ) {/* global account reg */
if( session_isValid(chr->login_fd) )
chr->global_accreg_to_login_add(key,index,val,is_string);
else {
ShowError("Login server unavailable, cant perform update on '%s' variable for AID:%d CID:%d\n",key,account_id,char_id);
}
} else if ( key[0] == '#' ) {/* local account reg */
if( is_string ) {
if( val ) {
if( SQL_ERROR == SQL->Query(inter->sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%s')", acc_reg_str_db, account_id, key, index, (char*)val) )
Sql_ShowDebug(inter->sql_handle);
} else {
if( SQL_ERROR == SQL->Query(inter->sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", acc_reg_str_db, account_id, key, index) )
Sql_ShowDebug(inter->sql_handle);
}
} else {
if( val ) {
if( SQL_ERROR == SQL->Query(inter->sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%d')", acc_reg_num_db, account_id, key, index, (int)val) )
Sql_ShowDebug(inter->sql_handle);
} else {
if( SQL_ERROR == SQL->Query(inter->sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", acc_reg_num_db, account_id, key, index) )
Sql_ShowDebug(inter->sql_handle);
}
}
} else { /* char reg */
if( is_string ) {
if( val ) {
if( SQL_ERROR == SQL->Query(inter->sql_handle, "REPLACE INTO `%s` (`char_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%s')", char_reg_str_db, char_id, key, index, (char*)val) )
Sql_ShowDebug(inter->sql_handle);
} else {
if( SQL_ERROR == SQL->Query(inter->sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", char_reg_str_db, char_id, key, index) )
Sql_ShowDebug(inter->sql_handle);
}
} else {
if( val ) {
if( SQL_ERROR == SQL->Query(inter->sql_handle, "REPLACE INTO `%s` (`char_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%d')", char_reg_num_db, char_id, key, index, (int)val) )
Sql_ShowDebug(inter->sql_handle);
} else {
if( SQL_ERROR == SQL->Query(inter->sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", char_reg_num_db, char_id, key, index) )
Sql_ShowDebug(inter->sql_handle);
}
}
}
}
// Load account_reg from sql (type=2)
int inter_accreg_fromsql(int account_id,int char_id, int fd, int type)
{
char* data;
size_t len;
unsigned int plen = 0;
switch( type ) {
case 3: //char reg
if( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `char_id`='%d'", char_reg_str_db, char_id) )
Sql_ShowDebug(inter->sql_handle);
break;
case 2: //account reg
if( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", acc_reg_str_db, account_id) )
Sql_ShowDebug(inter->sql_handle);
break;
case 1: //account2 reg
ShowError("inter->accreg_fromsql: Char server shouldn't handle type 1 registry values (##). That is the login server's work!\n");
return 0;
default:
ShowError("inter->accreg_fromsql: Invalid type %d\n", type);
return 0;
}
WFIFOHEAD(fd, 60000 + 300);
WFIFOW(fd, 0) = 0x3804;
/* 0x2 = length, set prior to being sent */
WFIFOL(fd, 4) = account_id;
WFIFOL(fd, 8) = char_id;
WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */
WFIFOB(fd, 13) = 1;/* is string type */
WFIFOW(fd, 14) = 0;/* count */
plen = 16;
/**
* Vessel!
*
* str type
* { keyLength(B), key(<keyLength>), index(L), valLength(B), val(<valLength>) }
**/
while ( SQL_SUCCESS == SQL->NextRow(inter->sql_handle) ) {
SQL->GetData(inter->sql_handle, 0, &data, NULL);
len = strlen(data)+1;
WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 32 */
plen += 1;
safestrncpy((char*)WFIFOP(fd,plen), data, len);
plen += len;
SQL->GetData(inter->sql_handle, 1, &data, NULL);
WFIFOL(fd, plen) = (unsigned int)atol(data);
plen += 4;
SQL->GetData(inter->sql_handle, 2, &data, NULL);
len = strlen(data)+1;
WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 254 */
plen += 1;
safestrncpy((char*)WFIFOP(fd,plen), data, len);
plen += len;
WFIFOW(fd, 14) += 1;
if( plen > 60000 ) {
WFIFOW(fd, 2) = plen;
WFIFOSET(fd, plen);
/* prepare follow up */
WFIFOHEAD(fd, 60000 + 300);
WFIFOW(fd, 0) = 0x3804;
/* 0x2 = length, set prior to being sent */
WFIFOL(fd, 4) = account_id;
WFIFOL(fd, 8) = char_id;
WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */
WFIFOB(fd, 13) = 1;/* is string type */
WFIFOW(fd, 14) = 0;/* count */
plen = 16;
}
}
/* mark & go. */
WFIFOW(fd, 2) = plen;
WFIFOSET(fd, plen);
SQL->FreeResult(inter->sql_handle);
switch( type ) {
case 3: //char reg
if( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `char_id`='%d'", char_reg_num_db, char_id) )
Sql_ShowDebug(inter->sql_handle);
break;
case 2: //account reg
if( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", acc_reg_num_db, account_id) )
Sql_ShowDebug(inter->sql_handle);
break;
case 1: //account2 reg
ShowError("inter->accreg_fromsql: Char server shouldn't handle type 1 registry values (##). That is the login server's work!\n");
return 0;
}
WFIFOHEAD(fd, 60000 + 300);
WFIFOW(fd, 0) = 0x3804;
/* 0x2 = length, set prior to being sent */
WFIFOL(fd, 4) = account_id;
WFIFOL(fd, 8) = char_id;
WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */
WFIFOB(fd, 13) = 0;/* is int type */
WFIFOW(fd, 14) = 0;/* count */
plen = 16;
/**
* Vessel!
*
* int type
* { keyLength(B), key(<keyLength>), index(L), value(L) }
**/
while ( SQL_SUCCESS == SQL->NextRow(inter->sql_handle) ) {
SQL->GetData(inter->sql_handle, 0, &data, NULL);
len = strlen(data)+1;
WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 32 */
plen += 1;
safestrncpy((char*)WFIFOP(fd,plen), data, len);
plen += len;
SQL->GetData(inter->sql_handle, 1, &data, NULL);
WFIFOL(fd, plen) = (unsigned int)atol(data);
plen += 4;
SQL->GetData(inter->sql_handle, 2, &data, NULL);
WFIFOL(fd, plen) = atoi(data);
plen += 4;
WFIFOW(fd, 14) += 1;
if( plen > 60000 ) {
WFIFOW(fd, 2) = plen;
WFIFOSET(fd, plen);
/* prepare follow up */
WFIFOHEAD(fd, 60000 + 300);
WFIFOW(fd, 0) = 0x3804;
/* 0x2 = length, set prior to being sent */
WFIFOL(fd, 4) = account_id;
WFIFOL(fd, 8) = char_id;
WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */
WFIFOB(fd, 13) = 0;/* is int type */
WFIFOW(fd, 14) = 0;/* count */
plen = 16;
}
}
/* mark as complete & go. */
WFIFOB(fd, 12) = type;
WFIFOW(fd, 2) = plen;
WFIFOSET(fd, plen);
SQL->FreeResult(inter->sql_handle);
return 1;
}
/*==========================================
* read config file
*------------------------------------------*/
static int inter_config_read(const char* cfgName)
{
int i;
char line[1024], w1[1024], w2[1024];
FILE* fp;
fp = fopen(cfgName, "r");
if(fp == NULL) {
ShowError("File not found: %s\n", cfgName);
return 1;
}
while (fgets(line, sizeof(line), fp)) {
i = sscanf(line, "%1023[^:]: %1023[^\r\n]", w1, w2);
if(i != 2)
continue;
if(!strcmpi(w1,"char_server_ip")) {
strcpy(char_server_ip,w2);
} else
if(!strcmpi(w1,"char_server_port")) {
char_server_port = atoi(w2);
} else
if(!strcmpi(w1,"char_server_id")) {
strcpy(char_server_id,w2);
} else
if(!strcmpi(w1,"char_server_pw")) {
strcpy(char_server_pw,w2);
} else
if(!strcmpi(w1,"char_server_db")) {
strcpy(char_server_db,w2);
} else
if(!strcmpi(w1,"default_codepage")) {
strcpy(default_codepage,w2);
}
else if(!strcmpi(w1,"party_share_level"))
party_share_level = atoi(w2);
else if(!strcmpi(w1,"log_inter"))
log_inter = atoi(w2);
else if(!strcmpi(w1,"import"))
inter->config_read(w2);
}
fclose(fp);
ShowInfo ("Done reading %s.\n", cfgName);
return 0;
}
/**
* Save interlog into sql (arglist version)
* @see inter_log
*/
int inter_vlog(char* fmt, va_list ap)
{
char str[255];
char esc_str[sizeof(str)*2+1];// escaped str
va_list apcopy;
va_copy(apcopy, ap);
vsnprintf(str, sizeof(str), fmt, apcopy);
va_end(apcopy);
SQL->EscapeStringLen(inter->sql_handle, esc_str, str, strnlen(str, sizeof(str)));
if( SQL_ERROR == SQL->Query(inter->sql_handle, "INSERT INTO `%s` (`time`, `log`) VALUES (NOW(), '%s')", interlog_db, esc_str) )
Sql_ShowDebug(inter->sql_handle);
return 0;
}
/**
* Save interlog into sql
* @param fmt Message's format string
* @param ... Additional (printf-like) arguments
* @return Always 0 // FIXME
*/
int inter_log(char* fmt, ...)
{
va_list ap;
int ret;
va_start(ap,fmt);
ret = inter->vlog(fmt, ap);
va_end(ap);
return ret;
}
// initialize
int inter_init_sql(const char *file)
{
//int i;
inter->config_read(file);
//DB connection initialized
inter->sql_handle = SQL->Malloc();
ShowInfo("Connect Character DB server.... (Character Server)\n");
if( SQL_ERROR == SQL->Connect(inter->sql_handle, char_server_id, char_server_pw, char_server_ip, (uint16)char_server_port, char_server_db) )
{
Sql_ShowDebug(inter->sql_handle);
SQL->Free(inter->sql_handle);
exit(EXIT_FAILURE);
}
if( *default_codepage ) {
if( SQL_ERROR == SQL->SetEncoding(inter->sql_handle, default_codepage) )
Sql_ShowDebug(inter->sql_handle);
}
wis_db = idb_alloc(DB_OPT_RELEASE_DATA);
inter_guild->sql_init();
inter_storage->sql_init();
inter_party->sql_init();
inter_pet->sql_init();
inter_homunculus->sql_init();
inter_mercenary->sql_init();
inter_elemental->sql_init();
inter_mail->sql_init();
inter_auction->sql_init();
geoip->init();
inter->msg_config_read("conf/messages.conf", false);
return 0;
}
// finalize
void inter_final(void)
{
wis_db->destroy(wis_db, NULL);
inter_guild->sql_final();
inter_storage->sql_final();
inter_party->sql_final();
inter_pet->sql_final();
inter_homunculus->sql_final();
inter_mercenary->sql_final();
inter_elemental->sql_final();
inter_mail->sql_final();
inter_auction->sql_final();
geoip->final(true);
inter->do_final_msg();
return;
}
int inter_mapif_init(int fd)
{
return 0;
}
//--------------------------------------------------------
// broadcast sending
int mapif_broadcast(unsigned char *mes, int len, unsigned int fontColor, short fontType, short fontSize, short fontAlign, short fontY, int sfd)
{
unsigned char *buf = (unsigned char*)aMalloc((len)*sizeof(unsigned char));
WBUFW(buf,0) = 0x3800;
WBUFW(buf,2) = len;
WBUFL(buf,4) = fontColor;
WBUFW(buf,8) = fontType;
WBUFW(buf,10) = fontSize;
WBUFW(buf,12) = fontAlign;
WBUFW(buf,14) = fontY;
memcpy(WBUFP(buf,16), mes, len - 16);
mapif->sendallwos(sfd, buf, len);
if (buf)
aFree(buf);
return 0;
}
// Wis sending
int mapif_wis_message(struct WisData *wd)
{
unsigned char buf[2048];
if (wd->len > 2047-56) wd->len = 2047-56; //Force it to fit to avoid crashes. [Skotlex]
WBUFW(buf, 0) = 0x3801;
WBUFW(buf, 2) = 56 +wd->len;
WBUFL(buf, 4) = wd->id;
memcpy(WBUFP(buf, 8), wd->src, NAME_LENGTH);
memcpy(WBUFP(buf,32), wd->dst, NAME_LENGTH);
memcpy(WBUFP(buf,56), wd->msg, wd->len);
wd->count = mapif->sendall(buf,WBUFW(buf,2));
return 0;
}
void mapif_wis_response(int fd, unsigned char *src, int flag)
{
unsigned char buf[27];
WBUFW(buf, 0)=0x3802;
memcpy(WBUFP(buf, 2),src,24);
WBUFB(buf,26)=flag;
mapif->send(fd,buf,27);
}
// Wis sending result
int mapif_wis_end(struct WisData *wd, int flag)
{
mapif->wis_response(wd->fd, wd->src, flag);
return 0;
}
#if 0
// Account registry transfer to map-server
static void mapif_account_reg(int fd, unsigned char *src)
{
WBUFW(src,0)=0x3804; //NOTE: writing to RFIFO
mapif->sendallwos(fd, src, WBUFW(src,2));
}
#endif // 0
// Send the requested account_reg
int mapif_account_reg_reply(int fd,int account_id,int char_id, int type)
{
inter->accreg_fromsql(account_id,char_id,fd,type);
return 0;
}
//Request to kick char from a certain map server. [Skotlex]
int mapif_disconnectplayer(int fd, int account_id, int char_id, int reason)
{
if (fd >= 0)
{
WFIFOHEAD(fd,7);
WFIFOW(fd,0) = 0x2b1f;
WFIFOL(fd,2) = account_id;
WFIFOB(fd,6) = reason;
WFIFOSET(fd,7);
return 0;
}
return -1;
}
//--------------------------------------------------------
/**
* Existence check of WISP data
* @see DBApply
*/
int inter_check_ttl_wisdata_sub(DBKey key, DBData *data, va_list ap)
{
int64 tick;
struct WisData *wd = DB->data2ptr(data);
tick = va_arg(ap, int64);
if (DIFF_TICK(tick, wd->tick) > WISDATA_TTL && wis_delnum < WISDELLIST_MAX)
wis_dellist[wis_delnum++] = wd->id;
return 0;
}
int inter_check_ttl_wisdata(void)
{
int64 tick = timer->gettick();
int i;
do {
wis_delnum = 0;
wis_db->foreach(wis_db, inter->check_ttl_wisdata_sub, tick);
for(i = 0; i < wis_delnum; i++) {
struct WisData *wd = (struct WisData*)idb_get(wis_db, wis_dellist[i]);
ShowWarning("inter: wis data id=%d time out : from %s to %s\n", wd->id, wd->src, wd->dst);
// removed. not send information after a timeout. Just no answer for the player
//mapif->wis_end(wd, 1); // flag: 0: success to send whisper, 1: target character is not logged in?, 2: ignored by target
idb_remove(wis_db, wd->id);
}
} while(wis_delnum >= WISDELLIST_MAX);
return 0;
}
//--------------------------------------------------------
// broadcast sending
int mapif_parse_broadcast(int fd)
{
mapif->broadcast(RFIFOP(fd,16), RFIFOW(fd,2), RFIFOL(fd,4), RFIFOW(fd,8), RFIFOW(fd,10), RFIFOW(fd,12), RFIFOW(fd,14), fd);
return 0;
}
// Wisp/page request to send
int mapif_parse_WisRequest(int fd)
{
struct WisData* wd;
static int wisid = 0;
char name[NAME_LENGTH];
char esc_name[NAME_LENGTH*2+1];// escaped name
char* data;
size_t len;
if ( fd <= 0 ) {return 0;} // check if we have a valid fd
if (RFIFOW(fd,2)-52 >= sizeof(wd->msg)) {
ShowWarning("inter: Wis message size too long.\n");
return 0;
} else if (RFIFOW(fd,2)-52 <= 0) { // normally, impossible, but who knows...
ShowError("inter: Wis message doesn't exist.\n");
return 0;
}
safestrncpy(name, (char*)RFIFOP(fd,28), NAME_LENGTH); //Received name may be too large and not contain \0! [Skotlex]
SQL->EscapeStringLen(inter->sql_handle, esc_name, name, strnlen(name, NAME_LENGTH));
if( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `name` FROM `%s` WHERE `name`='%s'", char_db, esc_name) )
Sql_ShowDebug(inter->sql_handle);
// search if character exists before to ask all map-servers
if( SQL_SUCCESS != SQL->NextRow(inter->sql_handle) )
{
mapif->wis_response(fd, RFIFOP(fd, 4), 1);
}
else
{// Character exists. So, ask all map-servers
// to be sure of the correct name, rewrite it
SQL->GetData(inter->sql_handle, 0, &data, &len);
memset(name, 0, NAME_LENGTH);
memcpy(name, data, min(len, NAME_LENGTH));
// if source is destination, don't ask other servers.
if( strncmp((const char*)RFIFOP(fd,4), name, NAME_LENGTH) == 0 )
{
mapif->wis_response(fd, RFIFOP(fd, 4), 1);
}
else
{
CREATE(wd, struct WisData, 1);
// Whether the failure of previous wisp/page transmission (timeout)
inter->check_ttl_wisdata();
wd->id = ++wisid;
wd->fd = fd;
wd->len= RFIFOW(fd,2)-52;
memcpy(wd->src, RFIFOP(fd, 4), NAME_LENGTH);
memcpy(wd->dst, RFIFOP(fd,28), NAME_LENGTH);
memcpy(wd->msg, RFIFOP(fd,52), wd->len);
wd->tick = timer->gettick();
idb_put(wis_db, wd->id, wd);
mapif->wis_message(wd);
}
}
SQL->FreeResult(inter->sql_handle);
return 0;
}
// Wisp/page transmission result
int mapif_parse_WisReply(int fd)
{
int id, flag;
struct WisData *wd;
id = RFIFOL(fd,2);
flag = RFIFOB(fd,6);
wd = (struct WisData*)idb_get(wis_db, id);
if (wd == NULL)
return 0; // This wisp was probably suppress before, because it was timeout of because of target was found on another map-server
if ((--wd->count) <= 0 || flag != 1) {
mapif->wis_end(wd, flag); // flag: 0: success to send whisper, 1: target character is not logged in?, 2: ignored by target
idb_remove(wis_db, id);
}
return 0;
}
// Received wisp message from map-server for ALL gm (just copy the message and resends it to ALL map-servers)
int mapif_parse_WisToGM(int fd)
{
unsigned char buf[2048]; // 0x3003/0x3803 <packet_len>.w <wispname>.24B <min_gm_level>.w <message>.?B
memcpy(WBUFP(buf,0), RFIFOP(fd,0), RFIFOW(fd,2));
WBUFW(buf, 0) = 0x3803;
mapif->sendall(buf, RFIFOW(fd,2));
return 0;
}
// Save account_reg into sql (type=2)
int mapif_parse_Registry(int fd)
{
int account_id = RFIFOL(fd, 4), char_id = RFIFOL(fd, 8), count = RFIFOW(fd, 12);
if( count ) {
int cursor = 14, i;
char key[32], sval[254];
unsigned int index;
bool isLoginActive = session_isActive(chr->login_fd);
if( isLoginActive )
chr->global_accreg_to_login_start(account_id,char_id);
for(i = 0; i < count; i++) {
safestrncpy(key, (char*)RFIFOP(fd, cursor + 1), RFIFOB(fd, cursor));
cursor += RFIFOB(fd, cursor) + 1;
index = RFIFOL(fd, cursor);
cursor += 4;
switch (RFIFOB(fd, cursor++)) {
/* int */
case 0:
inter->savereg(account_id,char_id,key,index,RFIFOL(fd, cursor),false);
cursor += 4;
break;
case 1:
inter->savereg(account_id,char_id,key,index,0,false);
break;
/* str */
case 2:
safestrncpy(sval, (char*)RFIFOP(fd, cursor + 1), RFIFOB(fd, cursor));
cursor += RFIFOB(fd, cursor) + 1;
inter->savereg(account_id,char_id,key,index,(intptr_t)sval,true);
break;
case 3:
inter->savereg(account_id,char_id,key,index,0,true);
break;
default:
ShowError("mapif->parse_Registry: unknown type %d\n",RFIFOB(fd, cursor - 1));
return 1;
}
}
if (isLoginActive)
chr->global_accreg_to_login_send();
}
return 0;
}
// Request the value of all registries.
int mapif_parse_RegistryRequest(int fd)
{
//Load Char Registry
if (RFIFOB(fd,12)) mapif->account_reg_reply(fd,RFIFOL(fd,2),RFIFOL(fd,6),3);
//Load Account Registry
if (RFIFOB(fd,11)) mapif->account_reg_reply(fd,RFIFOL(fd,2),RFIFOL(fd,6),2);
//Ask Login Server for Account2 values.
if (RFIFOB(fd,10)) chr->request_accreg2(RFIFOL(fd,2),RFIFOL(fd,6));
return 1;
}
void mapif_namechange_ack(int fd, int account_id, int char_id, int type, int flag, const char *const name)
{
WFIFOHEAD(fd, NAME_LENGTH+13);
WFIFOW(fd, 0) = 0x3806;
WFIFOL(fd, 2) = account_id;
WFIFOL(fd, 6) = char_id;
WFIFOB(fd,10) = type;
WFIFOB(fd,11) = flag;
memcpy(WFIFOP(fd, 12), name, NAME_LENGTH);
WFIFOSET(fd, NAME_LENGTH+13);
}
int mapif_parse_NameChangeRequest(int fd)
{
int account_id, char_id, type;
char* name;
int i;
account_id = RFIFOL(fd,2);
char_id = RFIFOL(fd,6);
type = RFIFOB(fd,10);
name = (char*)RFIFOP(fd,11);
// Check Authorized letters/symbols in the name
if (char_name_option == 1) { // only letters/symbols in char_name_letters are authorized
for (i = 0; i < NAME_LENGTH && name[i]; i++)
if (strchr(char_name_letters, name[i]) == NULL) {
mapif->namechange_ack(fd, account_id, char_id, type, 0, name);
return 0;
}
} else if (char_name_option == 2) { // letters/symbols in char_name_letters are forbidden
for (i = 0; i < NAME_LENGTH && name[i]; i++)
if (strchr(char_name_letters, name[i]) != NULL) {
mapif->namechange_ack(fd, account_id, char_id, type, 0, name);
return 0;
}
}
//TODO: type holds the type of object to rename.
//If it were a player, it needs to have the guild information and db information
//updated here, because changing it on the map won't make it be saved [Skotlex]
//name allowed.
mapif->namechange_ack(fd, account_id, char_id, type, 1, name);
return 0;
}
//--------------------------------------------------------
/// Returns the length of the next complete packet to process,
/// or 0 if no complete packet exists in the queue.
///
/// @param length The minimum allowed length, or -1 for dynamic lookup
int inter_check_length(int fd, int length)
{
if( length == -1 )
{// variable-length packet
if( RFIFOREST(fd) < 4 )
return 0;
length = RFIFOW(fd,2);
}
if( (int)RFIFOREST(fd) < length )
return 0;
return length;
}
int inter_parse_frommap(int fd)
{
int cmd;
int len = 0;
cmd = RFIFOW(fd,0);
// Check is valid packet entry
if(cmd < 0x3000 || cmd >= 0x3000 + ARRAYLENGTH(inter_recv_packet_length) || inter_recv_packet_length[cmd - 0x3000] == 0)
return 0;
// Check packet length
if((len = inter->check_length(fd, inter_recv_packet_length[cmd - 0x3000])) == 0)
return 2;
switch(cmd) {
case 0x3000: mapif->parse_broadcast(fd); break;
case 0x3001: mapif->parse_WisRequest(fd); break;
case 0x3002: mapif->parse_WisReply(fd); break;
case 0x3003: mapif->parse_WisToGM(fd); break;
case 0x3004: mapif->parse_Registry(fd); break;
case 0x3005: mapif->parse_RegistryRequest(fd); break;
case 0x3006: mapif->parse_NameChangeRequest(fd); break;
case 0x3007: mapif->parse_accinfo(fd); break;
/* 0x3008 is used by the report stuff */
default:
if( inter_party->parse_frommap(fd)
|| inter_guild->parse_frommap(fd)
|| inter_storage->parse_frommap(fd)
|| inter_pet->parse_frommap(fd)
|| inter_homunculus->parse_frommap(fd)
|| inter_mercenary->parse_frommap(fd)
|| inter_elemental->parse_frommap(fd)
|| inter_mail->parse_frommap(fd)
|| inter_auction->parse_frommap(fd)
|| inter_quest->parse_frommap(fd)
)
break;
else
return 0;
}
RFIFOSKIP(fd, len);
return 1;
}
void inter_defaults(void)
{
inter = &inter_s;
inter->sql_handle = NULL;
inter->msg_txt = inter_msg_txt;
inter->msg_config_read = inter_msg_config_read;
inter->do_final_msg = inter_do_final_msg;
inter->job_name = inter_job_name;
inter->vmsg_to_fd = inter_vmsg_to_fd;
inter->msg_to_fd = inter_msg_to_fd;
inter->savereg = inter_savereg;
inter->accreg_fromsql = inter_accreg_fromsql;
inter->config_read = inter_config_read;
inter->vlog = inter_vlog;
inter->log = inter_log;
inter->init_sql = inter_init_sql;
inter->mapif_init = inter_mapif_init;
inter->check_ttl_wisdata_sub = inter_check_ttl_wisdata_sub;
inter->check_ttl_wisdata = inter->check_ttl_wisdata;
inter->check_length = inter_check_length;
inter->parse_frommap = inter_parse_frommap;
inter->final = inter_final;
}