From 620e60eebce2c1f35c5c9a82f6ca365b316587f5 Mon Sep 17 00:00:00 2001 From: Valaris Date: Sun, 29 Jan 2006 16:10:48 +0000 Subject: AS OF SVN REV. 5901, WE ARE NOW USING TRUNK. ALL UNTESTED BUGFIXES/FEATURES GO INTO TRUNK. IF YOU HAVE A WORKING AND TESTED BUGFIX PUT IT INTO STABLE AS WELL AS TRUNK. EVERYTHING ELSE GOES INTO TRUNK AND WILL BE MERGED INTO STABLE BY VALARIS AND WIZPUTER. -- VALARIS git-svn-id: https://rathena.svn.sourceforge.net/svnroot/rathena/trunk@5094 54d463be-8e91-2dee-dedb-b68131a5f0ec --- src/char/Makefile | 30 + src/char/char.c | 4161 ++++++++++++ src/char/char.h | 45 + src/char/int_guild.c | 1530 +++++ src/char/int_guild.h | 19 + src/char/int_party.c | 654 ++ src/char/int_party.h | 18 + src/char/int_pet.c | 380 ++ src/char/int_pet.h | 16 + src/char/int_status.c | 187 + src/char/int_status.h | 24 + src/char/int_storage.c | 476 ++ src/char/int_storage.h | 19 + src/char/inter.c | 656 ++ src/char/inter.h | 28 + src/char_sql/Makefile | 27 + src/char_sql/char.c | 4307 ++++++++++++ src/char_sql/char.h | 103 + src/char_sql/int_guild.c | 1914 ++++++ src/char_sql/int_guild.h | 18 + src/char_sql/int_party.c | 865 +++ src/char_sql/int_party.h | 14 + src/char_sql/int_pet.c | 351 + src/char_sql/int_pet.h | 16 + src/char_sql/int_storage.c | 364 + src/char_sql/int_storage.h | 17 + src/char_sql/inter.c | 789 +++ src/char_sql/inter.h | 58 + src/char_sql/itemdb.c | 229 + src/char_sql/itemdb.h | 38 + src/char_sql/make.sh | 10 + src/common/Makefile | 56 + src/common/cbasetypes.h | 253 + src/common/core.c | 269 + src/common/core.h | 30 + src/common/db.c | 2344 +++++++ src/common/db.h | 734 ++ src/common/ers.c | 532 ++ src/common/ers.h | 193 + src/common/graph.c | 318 + src/common/graph.h | 27 + src/common/grfio.c | 1146 ++++ src/common/grfio.h | 21 + src/common/lock.c | 71 + src/common/lock.h | 11 + src/common/malloc.c | 715 ++ src/common/malloc.h | 153 + src/common/mapindex.c | 130 + src/common/mapindex.h | 37 + src/common/mmo.h | 403 ++ src/common/nullpo.c | 94 + src/common/nullpo.h | 237 + src/common/plugin.h | 40 + src/common/plugins.c | 367 + src/common/plugins.h | 61 + src/common/showmsg.c | 219 + src/common/showmsg.h | 88 + src/common/socket.c | 1390 ++++ src/common/socket.h | 189 + src/common/strlib.c | 133 + src/common/strlib.h | 17 + src/common/timer.c | 429 ++ src/common/timer.h | 60 + src/common/utils.c | 384 ++ src/common/utils.h | 52 + src/common/version.h | 30 + src/ladmin/Makefile | 17 + src/ladmin/ladmin.c | 4410 ++++++++++++ src/ladmin/ladmin.h | 13 + src/ladmin/md5calc.c | 239 + src/ladmin/md5calc.h | 10 + src/login/Makefile | 25 + src/login/login.c | 4153 ++++++++++++ src/login/login.h | 44 + src/login/md5calc.c | 236 + src/login/md5calc.h | 7 + src/login_sql/Makefile | 22 + src/login_sql/login.c | 2256 +++++++ src/login_sql/login.h | 57 + src/login_sql/make.sh | 6 + src/login_sql/md5calc.c | 239 + src/login_sql/md5calc.h | 10 + src/map/Makefile | 99 + src/map/Makefile.win32 | 99 + src/map/atcommand.c | 10039 +++++++++++++++++++++++++++ src/map/atcommand.h | 319 + src/map/battle.c | 4412 ++++++++++++ src/map/battle.h | 428 ++ src/map/charcommand.c | 1794 +++++ src/map/charcommand.h | 68 + src/map/charsave.c | 516 ++ src/map/charsave.h | 16 + src/map/chat.c | 371 + src/map/chat.h | 22 + src/map/chrif.c | 1572 +++++ src/map/chrif.h | 54 + src/map/clif.c | 12146 +++++++++++++++++++++++++++++++++ src/map/clif.h | 343 + src/map/date.c | 72 + src/map/date.h | 17 + src/map/guild.c | 1976 ++++++ src/map/guild.h | 96 + src/map/intif.c | 1401 ++++ src/map/intif.h | 61 + src/map/itemdb.c | 1103 +++ src/map/itemdb.h | 106 + src/map/log.c | 956 +++ src/map/log.h | 47 + src/map/mail.c | 361 + src/map/mail.h | 12 + src/map/map.c | 3952 +++++++++++ src/map/map.h | 1409 ++++ src/map/mercenary.c | 11 + src/map/mercenary.h | 11 + src/map/mob.c | 5020 ++++++++++++++ src/map/mob.h | 173 + src/map/npc.c | 2826 ++++++++ src/map/npc.h | 71 + src/map/npc_chat.c | 519 ++ src/map/party.c | 742 ++ src/map/party.h | 47 + src/map/path.c | 483 ++ src/map/pc.c | 8305 +++++++++++++++++++++++ src/map/pc.h | 251 + src/map/pcre.h | 258 + src/map/pet.c | 1973 ++++++ src/map/pet.h | 73 + src/map/script.c | 10736 +++++++++++++++++++++++++++++ src/map/script.h | 73 + src/map/skill.c | 12348 ++++++++++++++++++++++++++++++++++ src/map/skill.h | 879 +++ src/map/status.c | 6038 +++++++++++++++++ src/map/status.h | 529 ++ src/map/storage.c | 693 ++ src/map/storage.h | 45 + src/map/trade.c | 569 ++ src/map/trade.h | 15 + src/map/vending.c | 261 + src/map/vending.h | 14 + src/mysql/config-win.h | 434 ++ src/mysql/m_ctype.h | 478 ++ src/mysql/my_alloc.h | 52 + src/mysql/my_dbug.h | 101 + src/mysql/my_global.h | 1285 ++++ src/mysql/my_list.h | 46 + src/mysql/my_pthread.h | 711 ++ src/mysql/my_sys.h | 904 +++ src/mysql/mysql-5.0.16 | 0 src/mysql/mysql.h | 839 +++ src/mysql/mysql_com.h | 444 ++ src/mysql/mysql_time.h | 56 + src/mysql/mysql_version.h | 29 + src/mysql/raid.h | 159 + src/mysql/typelib.h | 34 + src/plugins/Makefile | 46 + src/plugins/gui.c | 101 + src/plugins/gui.txt | 15 + src/plugins/httpd.c | 751 +++ src/plugins/httpd.h | 107 + src/plugins/httpd.txt | 20 + src/plugins/pid.c | 54 + src/plugins/sample.c | 77 + src/plugins/sig.c | 211 + src/plugins/upnp.txt | 31 + src/tool/Makefile | 10 + src/tool/adduser.c | 99 + src/tool/convert.c | 299 + src/txt-converter/Makefile | 15 + src/txt-converter/char-converter.c | 1360 ++++ src/txt-converter/login-converter.c | 227 + src/webserver/Makefile | 20 + src/webserver/doc/API.txt | 50 + src/webserver/doc/README | 11 + src/webserver/generate.c | 38 + src/webserver/htmlstyle.c | 51 + src/webserver/logs.c | 8 + src/webserver/main.c | 142 + src/webserver/pages/about.c | 6 + src/webserver/pages/notdone.c | 5 + src/webserver/pages/sample.c | 24 + src/webserver/parse.c | 135 + src/zlib/Makefile | 21 + src/zlib/crypt.h | 132 + src/zlib/ioapi.c | 177 + src/zlib/ioapi.h | 75 + src/zlib/iowin32.c | 270 + src/zlib/iowin32.h | 21 + src/zlib/unzip.c | 1602 +++++ src/zlib/unzip.h | 354 + src/zlib/zconf.h | 332 + src/zlib/zlib-1.2.3 | Bin 0 -> 58 bytes src/zlib/zlib.h | 1357 ++++ 192 files changed, 150996 insertions(+) create mode 100644 src/char/Makefile create mode 100644 src/char/char.c create mode 100644 src/char/char.h create mode 100644 src/char/int_guild.c create mode 100644 src/char/int_guild.h create mode 100644 src/char/int_party.c create mode 100644 src/char/int_party.h create mode 100644 src/char/int_pet.c create mode 100644 src/char/int_pet.h create mode 100644 src/char/int_status.c create mode 100644 src/char/int_status.h create mode 100644 src/char/int_storage.c create mode 100644 src/char/int_storage.h create mode 100644 src/char/inter.c create mode 100644 src/char/inter.h create mode 100644 src/char_sql/Makefile create mode 100644 src/char_sql/char.c create mode 100644 src/char_sql/char.h create mode 100644 src/char_sql/int_guild.c create mode 100644 src/char_sql/int_guild.h create mode 100644 src/char_sql/int_party.c create mode 100644 src/char_sql/int_party.h create mode 100644 src/char_sql/int_pet.c create mode 100644 src/char_sql/int_pet.h create mode 100644 src/char_sql/int_storage.c create mode 100644 src/char_sql/int_storage.h create mode 100644 src/char_sql/inter.c create mode 100644 src/char_sql/inter.h create mode 100644 src/char_sql/itemdb.c create mode 100644 src/char_sql/itemdb.h create mode 100644 src/char_sql/make.sh create mode 100644 src/common/Makefile create mode 100644 src/common/cbasetypes.h create mode 100644 src/common/core.c create mode 100644 src/common/core.h create mode 100644 src/common/db.c create mode 100644 src/common/db.h create mode 100644 src/common/ers.c create mode 100644 src/common/ers.h create mode 100644 src/common/graph.c create mode 100644 src/common/graph.h create mode 100644 src/common/grfio.c create mode 100644 src/common/grfio.h create mode 100644 src/common/lock.c create mode 100644 src/common/lock.h create mode 100644 src/common/malloc.c create mode 100644 src/common/malloc.h create mode 100644 src/common/mapindex.c create mode 100644 src/common/mapindex.h create mode 100644 src/common/mmo.h create mode 100644 src/common/nullpo.c create mode 100644 src/common/nullpo.h create mode 100644 src/common/plugin.h create mode 100644 src/common/plugins.c create mode 100644 src/common/plugins.h create mode 100644 src/common/showmsg.c create mode 100644 src/common/showmsg.h create mode 100644 src/common/socket.c create mode 100644 src/common/socket.h create mode 100644 src/common/strlib.c create mode 100644 src/common/strlib.h create mode 100644 src/common/timer.c create mode 100644 src/common/timer.h create mode 100644 src/common/utils.c create mode 100644 src/common/utils.h create mode 100644 src/common/version.h create mode 100644 src/ladmin/Makefile create mode 100644 src/ladmin/ladmin.c create mode 100644 src/ladmin/ladmin.h create mode 100644 src/ladmin/md5calc.c create mode 100644 src/ladmin/md5calc.h create mode 100644 src/login/Makefile create mode 100644 src/login/login.c create mode 100644 src/login/login.h create mode 100644 src/login/md5calc.c create mode 100644 src/login/md5calc.h create mode 100644 src/login_sql/Makefile create mode 100644 src/login_sql/login.c create mode 100644 src/login_sql/login.h create mode 100644 src/login_sql/make.sh create mode 100644 src/login_sql/md5calc.c create mode 100644 src/login_sql/md5calc.h create mode 100644 src/map/Makefile create mode 100644 src/map/Makefile.win32 create mode 100644 src/map/atcommand.c create mode 100644 src/map/atcommand.h create mode 100644 src/map/battle.c create mode 100644 src/map/battle.h create mode 100644 src/map/charcommand.c create mode 100644 src/map/charcommand.h create mode 100644 src/map/charsave.c create mode 100644 src/map/charsave.h create mode 100644 src/map/chat.c create mode 100644 src/map/chat.h create mode 100644 src/map/chrif.c create mode 100644 src/map/chrif.h create mode 100644 src/map/clif.c create mode 100644 src/map/clif.h create mode 100644 src/map/date.c create mode 100644 src/map/date.h create mode 100644 src/map/guild.c create mode 100644 src/map/guild.h create mode 100644 src/map/intif.c create mode 100644 src/map/intif.h create mode 100644 src/map/itemdb.c create mode 100644 src/map/itemdb.h create mode 100644 src/map/log.c create mode 100644 src/map/log.h create mode 100644 src/map/mail.c create mode 100644 src/map/mail.h create mode 100644 src/map/map.c create mode 100644 src/map/map.h create mode 100644 src/map/mercenary.c create mode 100644 src/map/mercenary.h create mode 100644 src/map/mob.c create mode 100644 src/map/mob.h create mode 100644 src/map/npc.c create mode 100644 src/map/npc.h create mode 100644 src/map/npc_chat.c create mode 100644 src/map/party.c create mode 100644 src/map/party.h create mode 100644 src/map/path.c create mode 100644 src/map/pc.c create mode 100644 src/map/pc.h create mode 100644 src/map/pcre.h create mode 100644 src/map/pet.c create mode 100644 src/map/pet.h create mode 100644 src/map/script.c create mode 100644 src/map/script.h create mode 100644 src/map/skill.c create mode 100644 src/map/skill.h create mode 100644 src/map/status.c create mode 100644 src/map/status.h create mode 100644 src/map/storage.c create mode 100644 src/map/storage.h create mode 100644 src/map/trade.c create mode 100644 src/map/trade.h create mode 100644 src/map/vending.c create mode 100644 src/map/vending.h create mode 100644 src/mysql/config-win.h create mode 100644 src/mysql/m_ctype.h create mode 100644 src/mysql/my_alloc.h create mode 100644 src/mysql/my_dbug.h create mode 100644 src/mysql/my_global.h create mode 100644 src/mysql/my_list.h create mode 100644 src/mysql/my_pthread.h create mode 100644 src/mysql/my_sys.h create mode 100644 src/mysql/mysql-5.0.16 create mode 100644 src/mysql/mysql.h create mode 100644 src/mysql/mysql_com.h create mode 100644 src/mysql/mysql_time.h create mode 100644 src/mysql/mysql_version.h create mode 100644 src/mysql/raid.h create mode 100644 src/mysql/typelib.h create mode 100644 src/plugins/Makefile create mode 100644 src/plugins/gui.c create mode 100644 src/plugins/gui.txt create mode 100644 src/plugins/httpd.c create mode 100644 src/plugins/httpd.h create mode 100644 src/plugins/httpd.txt create mode 100644 src/plugins/pid.c create mode 100644 src/plugins/sample.c create mode 100644 src/plugins/sig.c create mode 100644 src/plugins/upnp.txt create mode 100644 src/tool/Makefile create mode 100644 src/tool/adduser.c create mode 100644 src/tool/convert.c create mode 100644 src/txt-converter/Makefile create mode 100644 src/txt-converter/char-converter.c create mode 100644 src/txt-converter/login-converter.c create mode 100644 src/webserver/Makefile create mode 100644 src/webserver/doc/API.txt create mode 100644 src/webserver/doc/README create mode 100644 src/webserver/generate.c create mode 100644 src/webserver/htmlstyle.c create mode 100644 src/webserver/logs.c create mode 100644 src/webserver/main.c create mode 100644 src/webserver/pages/about.c create mode 100644 src/webserver/pages/notdone.c create mode 100644 src/webserver/pages/sample.c create mode 100644 src/webserver/parse.c create mode 100644 src/zlib/Makefile create mode 100644 src/zlib/crypt.h create mode 100644 src/zlib/ioapi.c create mode 100644 src/zlib/ioapi.h create mode 100644 src/zlib/iowin32.c create mode 100644 src/zlib/iowin32.h create mode 100644 src/zlib/unzip.c create mode 100644 src/zlib/unzip.h create mode 100644 src/zlib/zconf.h create mode 100644 src/zlib/zlib-1.2.3 create mode 100644 src/zlib/zlib.h (limited to 'src') diff --git a/src/char/Makefile b/src/char/Makefile new file mode 100644 index 000000000..ff58f8ed5 --- /dev/null +++ b/src/char/Makefile @@ -0,0 +1,30 @@ +all txt: char-server + +COMMON_OBJ = ../common/obj/core.o ../common/obj/socket.o ../common/obj/timer.o \ + ../common/obj/db.o ../common/obj/plugins.o ../common/obj/lock.o \ + ../common/obj/malloc.o ../common/obj/showmsg.o ../common/obj/utils.o \ + ../common/obj/strlib.o ../common/obj/graph.o ../common/obj/grfio.o \ + ../common/obj/mapindex.o ../common/obj/ers.o ../zlib/unz.o +COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h \ + ../common/version.h ../common/db.h ../common/plugins.h ../common/lock.h \ + ../common/malloc.h ../common/showmsg.h ../common/utils.h ../common/strlib.h \ + ../common/graph.h ../common/grfio.h ../common/mapindex.h + +%.o: %.c + $(COMPILE.c) -DTXT_ONLY $(OUTPUT_OPTION) $< + +char-server: char.o inter.o int_party.o int_guild.o int_status.o int_storage.o int_pet.o $(COMMON_OBJ) + $(CC) -o ../../$@ $> $(LIB_S) + +clean: + rm -f *.o ../../char-server + +# DO NOT DELETE + +char.o: char.c char.h inter.h int_pet.h $(COMMON_H) ../common/version.h +inter.o: inter.c inter.h int_party.h int_guild.h int_storage.h int_pet.h char.h $(COMMON_H) +int_party.o: int_party.c int_party.h inter.h char.h $(COMMON_H) +int_guild.o: int_guild.c int_guild.h int_storage.h inter.h char.h $(COMMON_H) +int_storage.o: int_storage.c int_storage.h int_guild.h inter.h char.h $(COMMON_H) +int_status.o: int_status.c int_status.h char.h $(COMMON_H) +int_pet.o: int_pet.c int_pet.h inter.h char.h $(COMMON_H) diff --git a/src/char/char.c b/src/char/char.c new file mode 100644 index 000000000..ef77102b9 --- /dev/null +++ b/src/char/char.c @@ -0,0 +1,4161 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include + +#ifdef _WIN32 +#include +typedef long in_addr_t; +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include "../common/strlib.h" +#include "../common/core.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/mmo.h" +#include "../common/db.h" +#include "../common/version.h" +#include "../common/lock.h" +#include "../common/showmsg.h" +#include "../common/malloc.h" + +#include "char.h" +#include "inter.h" +#include "int_pet.h" +#include "int_guild.h" +#include "int_party.h" +#include "int_storage.h" +#ifdef ENABLE_SC_SAVING +#include "int_status.h" +#endif + +struct mmo_map_server server[MAX_MAP_SERVERS]; +int server_fd[MAX_MAP_SERVERS]; + +int login_fd, char_fd; +char userid[24]; +char passwd[24]; +char server_name[20]; +char wisp_server_name[NAME_LENGTH] = "Server"; +int login_ip_set_ = 0; +char login_ip_str[16]; +in_addr_t login_ip; +int login_port = 6900; +int char_ip_set_ = 0; +char char_ip_str[16]; +int bind_ip_set_ = 0; +char bind_ip_str[16]; +in_addr_t char_ip; +int char_port = 6121; +int char_maintenance; +int char_new; +int char_new_display; +int email_creation = 0; // disabled by default +char char_txt[1024]="save/athena.txt"; +char backup_txt[1024]="save/backup.txt"; //By zanetheinsane +char friends_txt[1024]="save/friends.txt"; // davidsiaw +char backup_txt_flag = 0; // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. => option By [Yor] +char unknown_char_name[1024] = "Unknown"; +char char_log_filename[1024] = "log/char.log"; +char db_path[1024]="db"; +//Added for lan support +char lan_map_ip[128]; +int subneti[4]; +int subnetmaski[4]; +int name_ignoring_case = 0; // Allow or not identical name for characters but with a different case by [Yor] +int char_name_option = 0; // Option to know which letters/symbols are authorised in the name of a character (0: all, 1: only those in char_name_letters, 2: all EXCEPT those in char_name_letters) by [Yor] +//The following are characters that are trimmed regardless because they cause confusion and problems on the servers. [Skotlex] +#define TRIM_CHARS "\032\t\n " +char char_name_letters[1024] = ""; // list of letters/symbols authorised (or not) in a character name. by [Yor] + +int log_char = 1; // loggin char or not [devil] +int log_inter = 1; // loggin inter or not [devil] + +struct char_session_data{ + int account_id, login_id1, login_id2, sex; + int found_char[9]; + char email[40]; // e-mail (default: a@a.com) by [Yor] + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id, char_id, login_id1, login_id2, ip, char_pos, delflag, sex; + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos = 0; + +int check_ip_flag = 1; // It's to check IP of a player between char-server and other servers (part of anti-hacking system) +static int online_check = 1; //If one, it won't let players connect when their account is already registered online and will send the relevant map server a kick user request. [Skotlex] + +int char_id_count = START_CHAR_NUM; +struct character_data { + struct mmo_charstatus status; + int global_num; + struct global_reg global[GLOBAL_REG_NUM]; +} *char_dat; + +int char_num, char_max; +int max_connect_user = 0; +int gm_allow_level = 99; +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int save_log = 1; +int start_zeny = 500; +int start_weapon = 1201; +int start_armor = 2301; + +// Initial position (it's possible to set it in conf file) +struct point start_point = { 0, 53, 111}; + +struct gm_account *gm_account = NULL; +int GM_num = 0; + +// online players by [Yor] +char online_txt_filename[1024] = "online.txt"; +char online_html_filename[1024] = "online.html"; +int online_sorting_option = 0; // sorting option to display online players in online files +int online_display_option = 1; // display options: to know which columns must be displayed +int online_refresh_html = 20; // refresh time (in sec) of the html file in the explorer +int online_gm_display_min_level = 20; // minimum GM level to display 'GM' when we want to display it + +//These are used to aid the map server in identifying valid clients. [Skotlex] +static int max_account_id = DEFAULT_MAX_ACCOUNT_ID, max_char_id = DEFAULT_MAX_CHAR_ID; + +struct online_char_data { + int account_id; + int char_id; + short server; + unsigned waiting_disconnect :1; +}; + +struct dbt *online_char_db; //Holds all online characters. + +time_t update_online; // to update online files when we receiving information from a server (not less than 8 seconds) + +int console = 0; + +//------------------------------ +// Writing function of logs file +//------------------------------ +int char_log(char *fmt, ...) { + if(log_char) + { + FILE *logfp; + va_list ap; + time_t raw_time; + char tmpstr[2048]; + + va_start(ap, fmt); + + logfp = fopen(char_log_filename, "a"); + if (logfp) { + if (fmt[0] == '\0') // jump a line if no message + fprintf(logfp, RETCODE); + else { + time(&raw_time); + strftime(tmpstr, 24, "%d-%m-%Y %H:%M:%S", localtime(&raw_time)); + sprintf(tmpstr + 19, ": %s", fmt); + vfprintf(logfp, tmpstr, ap); + } + fclose(logfp); + } + va_end(ap); + } + return 0; +} + +//---------------------------------------------------------------------- +// Determine if an account (id) is a GM account +// and returns its level (or 0 if it isn't a GM account or if not found) +//---------------------------------------------------------------------- +int isGM(int account_id) { + int i; + + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == account_id) + return gm_account[i].level; + return 0; +} + +//---------------------------------------------- +// Search an character id +// (return character index or -1 (if not found)) +// If exact character name is not found, +// the function checks without case sensitive +// and returns index if only 1 character is found +// and similar to the searched name. +//---------------------------------------------- +int search_character_index(char* character_name) { + int i, quantity, index; + + quantity = 0; + index = -1; + for(i = 0; i < char_num; i++) { + // Without case sensitive check (increase the number of similar character names found) + if (stricmp(char_dat[i].status.name, character_name) == 0) { + // Strict comparison (if found, we finish the function immediatly with correct value) + if (strcmp(char_dat[i].status.name, character_name) == 0) + return i; + quantity++; + index = i; + } + } + // Here, the exact character name is not found + // We return the found index of a similar account ONLY if there is 1 similar character + if (quantity == 1) + return index; + + // Exact character name is not found and 0 or more than 1 similar characters have been found ==> we say not found + return -1; +} + +//------------------------------------- +// Return character name with the index +//------------------------------------- +char * search_character_name(int index) { + + if (index >= 0 && index < char_num) + return char_dat[index].status.name; + + return unknown_char_name; +} + +static void * create_online_char_data(DBKey key, va_list args) { + struct online_char_data* character; + character = aCalloc(1, sizeof(struct online_char_data)); + character->account_id = key.i; + character->char_id = -1; + character->server = -1; + return character; +} + +static int chardb_waiting_disconnect(int tid, unsigned int tick, int id, int data); + +//------------------------------------------------- +// Set Character online/offline [Wizputer] +//------------------------------------------------- + +void set_char_online(int map_id, int char_id, int account_id) { + struct online_char_data* character; + + if ( char_id != 99 && (max_account_id < account_id || max_char_id < char_id)) + { //Notify map-server of the new max IDs [Skotlex] + if (account_id > max_account_id) + max_account_id = account_id; + if (char_id > max_char_id) + max_char_id = char_id; + mapif_send_maxid(max_account_id, max_char_id); + } + character = idb_ensure(online_char_db, account_id, create_online_char_data); + if (online_check && character->char_id != -1 && character->server > -1 && character->server != map_id) + { + ShowNotice("set_char_online: Character %d:%d marked in map server %d, but map server %d claims to have (%d:%d) online!\n", + character->account_id, character->char_id, character->server, map_id, account_id, char_id); + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + } + character->waiting_disconnect = 0; + character->char_id = (char_id==99)?-1:char_id; + character->server = (char_id==99)?-1:map_id; + + if (login_fd <= 0 || session[login_fd]->eof) + return; + WFIFOHEAD(login_fd, 6); + WFIFOW(login_fd,0) = 0x272b; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); + + //printf ("set online\n"); +} +void set_char_offline(int char_id, int account_id) { + struct online_char_data* character; + + if ((character = idb_get(online_char_db, account_id)) != NULL) + { //We don't free yet to avoid aCalloc/aFree spamming during char change. [Skotlex] + character->char_id = -1; + character->server = -1; + character->waiting_disconnect = 0; + } + if (login_fd <= 0 || session[login_fd]->eof) + return; + WFIFOHEAD(login_fd, 6); + WFIFOW(login_fd,0) = 0x272c; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); + +} + +static int char_db_setoffline(DBKey key, void* data, va_list ap) { + struct online_char_data* character = (struct online_char_data*)data; + int server = va_arg(ap, int); + if (server == -1) { + character->char_id = -1; + character->server = -1; + character->waiting_disconnect = 0; + } else if (character->server == server) + character->server = -2; //In some map server that we aren't connected to. + return 0; +} + +void set_all_offline(void) { + online_char_db->foreach(online_char_db,char_db_setoffline,-1); + if (login_fd <= 0 || session[login_fd]->eof) + return; + WFIFOHEAD(login_fd, 6); + WFIFOW(login_fd,0) = 0x272c; + WFIFOL(login_fd,2) = 99; + WFIFOSET(login_fd,6); + + //printf ("set all offline\n"); +} + +/*--------------------------------------------------- + Make a data line for friends list + --------------------------------------------------*/ + +int mmo_friends_list_data_str(char *str, struct mmo_charstatus *p) { + int i; + char *str_p = str; + str_p += sprintf(str_p, "%d", p->char_id); + + for (i=0;ifriends[i].account_id > 0 && p->friends[i].char_id > 0 && p->friends[i].name[0]) + str_p += sprintf(str_p, ",%d,%d,%s", p->friends[i].account_id, p->friends[i].char_id, p->friends[i].name); + else + str_p += sprintf(str_p,",,,"); + } + + str_p += '\0'; + + return 0; +} + +//------------------------------------------------- +// Function to create the character line (for save) +//------------------------------------------------- +int mmo_char_tostr(char *str, struct mmo_charstatus *p, struct global_reg *reg, int reg_num) { + int i,j; + char *str_p = str; + + // on multi-map server, sometimes it's posssible that last_point become void. (reason???) We check that to not lost character at restart. + if (!p->last_point.map) { + p->last_point.map = mapindex_name2id(MAP_PRONTERA); + p->last_point.x = 273; + p->last_point.y = 354; + } + + str_p += sprintf(str_p, "%d\t%d,%d\t%s\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%s,%d,%d\t%s,%d,%d,%d,%d,%d,%d,%d\t", + p->char_id, p->account_id, p->char_num, p->name, // + p->class_, p->base_level, p->job_level, + p->base_exp, p->job_exp, p->zeny, + p->hp, p->max_hp, p->sp, p->max_sp, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, + p->status_point, p->skill_point, + p->option, p->karma, p->manner, // + p->party_id, p->guild_id, p->pet_id, + p->hair, p->hair_color, p->clothes_color, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, + mapindex_id2name(p->last_point.map), p->last_point.x, p->last_point.y, // + mapindex_id2name(p->save_point.map), p->save_point.x, p->save_point.y, + p->partner_id,p->father,p->mother,p->child,p->fame); + for(i = 0; i < 10; i++) + if (p->memo_point[i].map) { + str_p += sprintf(str_p, "%s,%d,%d", mapindex_id2name(p->memo_point[i].map), p->memo_point[i].x, p->memo_point[i].y); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_INVENTORY; i++) + if (p->inventory[i].nameid) { + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d", + p->inventory[i].id,p->inventory[i].nameid,p->inventory[i].amount,p->inventory[i].equip, + p->inventory[i].identify,p->inventory[i].refine,p->inventory[i].attribute); + for(j=0; jinventory[i].card[j]); + str_p += sprintf(str_p," "); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_CART; i++) + if (p->cart[i].nameid) { + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d", + p->cart[i].id,p->cart[i].nameid,p->cart[i].amount,p->cart[i].equip, + p->cart[i].identify,p->cart[i].refine,p->cart[i].attribute); + for(j=0; jcart[i].card[j]); + str_p += sprintf(str_p," "); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_SKILL; i++) + if (p->skill[i].id && p->skill[i].flag != 1) { + str_p += sprintf(str_p, "%d,%d ", p->skill[i].id, (p->skill[i].flag == 0) ? p->skill[i].lv : p->skill[i].flag-2); + } + *(str_p++) = '\t'; + + for(i = 0; i < reg_num; i++) + if (reg[i].str[0]) + str_p += sprintf(str_p, "%s,%s ", reg[i].str, reg[i].value); + *(str_p++) = '\t'; + + *str_p = '\0'; + return 0; +} + +//------------------------------------------------------------------------- +// Function to set the character from the line (at read of characters file) +//------------------------------------------------------------------------- +int mmo_char_fromstr(char *str, struct mmo_charstatus *p, struct global_reg *reg, int *reg_num) { + char tmp_str[3][128]; //To avoid deleting chars with too long names. + int tmp_int[256]; + int set, next, len, i, j; + + // initilialise character + memset(p, '\0', sizeof(struct mmo_charstatus)); + + // If it's not char structure of version 1488 and after + if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + tmp_str[1], &tmp_int[35], &tmp_int[36], + tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39], + &tmp_int[40], &tmp_int[41], &tmp_int[42], &tmp_int[43], &next)) != 47) + { + tmp_int[43] = 0; + // If it's not char structure of version 1363 and after + if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + tmp_str[1], &tmp_int[35], &tmp_int[36], // + tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39], + &tmp_int[40], &tmp_int[41], &tmp_int[42], &next)) != 46) + { + tmp_int[40] = 0; // father + tmp_int[41] = 0; // mother + tmp_int[42] = 0; // child + // If it's not char structure of version 1008 and before 1363 + if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + tmp_str[1], &tmp_int[35], &tmp_int[36], // + tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39], &next)) != 43) + { + tmp_int[39] = 0; // partner id + // If not char structure from version 384 to 1007 + if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%127[^,],%d,%d\t%127[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + tmp_str[1], &tmp_int[35], &tmp_int[36], // + tmp_str[2], &tmp_int[37], &tmp_int[38], &next)) != 42) + { + // It's char structure of a version before 384 + tmp_int[26] = 0; // pet id + set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%127[^,],%d,%d\t%127[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], // + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + tmp_str[1], &tmp_int[35], &tmp_int[36], // + tmp_str[2], &tmp_int[37], &tmp_int[38], &next); + set += 2; + //printf("char: old char data ver.1\n"); + // Char structure of version 1007 or older + } else { + set++; + //printf("char: old char data ver.2\n"); + } + // Char structure of version 1008+ + } else { + set += 3; + //printf("char: new char data ver.3\n"); + } + // Char structture of version 1363+ + } else { + set++; + //printf("char: new char data ver.4\n"); + } + // Char structure of version 1488+ + } else { + //printf("char: new char data ver.5\n"); + } + if (set != 47) + return 0; + + memcpy(p->name, tmp_str[0], NAME_LENGTH-1); //Overflow protection [Skotlex] + p->char_id = tmp_int[0]; + p->account_id = tmp_int[1]; + p->char_num = tmp_int[2]; + p->class_ = tmp_int[3]; + p->base_level = tmp_int[4]; + p->job_level = tmp_int[5]; + p->base_exp = tmp_int[6]; + p->job_exp = tmp_int[7]; + p->zeny = tmp_int[8]; + p->hp = tmp_int[9]; + p->max_hp = tmp_int[10]; + p->sp = tmp_int[11]; + p->max_sp = tmp_int[12]; + p->str = tmp_int[13]; + p->agi = tmp_int[14]; + p->vit = tmp_int[15]; + p->int_ = tmp_int[16]; + p->dex = tmp_int[17]; + p->luk = tmp_int[18]; + p->status_point = tmp_int[19]; + p->skill_point = tmp_int[20]; + p->option = tmp_int[21]; + p->karma = tmp_int[22]; + p->manner = tmp_int[23]; + p->party_id = tmp_int[24]; + p->guild_id = tmp_int[25]; + p->pet_id = tmp_int[26]; + p->hair = tmp_int[27]; + p->hair_color = tmp_int[28]; + p->clothes_color = tmp_int[29]; + p->weapon = tmp_int[30]; + p->shield = tmp_int[31]; + p->head_top = tmp_int[32]; + p->head_mid = tmp_int[33]; + p->head_bottom = tmp_int[34]; + p->last_point.map = mapindex_name2id(tmp_str[1]); + p->last_point.x = tmp_int[35]; + p->last_point.y = tmp_int[36]; + p->save_point.map = mapindex_name2id(tmp_str[2]); + p->save_point.x = tmp_int[37]; + p->save_point.y = tmp_int[38]; + p->partner_id = tmp_int[39]; + p->father = tmp_int[40]; + p->mother = tmp_int[41]; + p->child = tmp_int[42]; + p->fame = tmp_int[43]; + + // Some checks + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.char_id == p->char_id) { + ShowError(CL_RED"mmmo_auth_init: a character has an identical id to another.\n"); + ShowError(" character id #%d -> new character not readed.\n", p->char_id); + ShowError(" Character saved in log file."CL_RESET"\n"); + return -1; + } else if (strcmp(char_dat[i].status.name, p->name) == 0) { + ShowError(CL_RED"mmmo_auth_init: a character name already exists.\n"); + ShowError(" character name '%s' -> new character not read.\n", p->name); + ShowError(" Character saved in log file."CL_RESET"\n"); + return -2; + } + } + + if (strcmpi(wisp_server_name, p->name) == 0) { + ShowWarning("mmo_auth_init: ******WARNING: character name has wisp server name.\n"); + ShowWarning(" Character name '%s' = wisp server name '%s'.\n", p->name, wisp_server_name); + ShowWarning(" Character readed. Suggestion: change the wisp server name.\n"); + char_log("mmo_auth_init: ******WARNING: character name has wisp server name: Character name '%s' = wisp server name '%s'." RETCODE, + p->name, wisp_server_name); + } + + if (str[next] == '\n' || str[next] == '\r') + return 1; // 新規データ + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str+next, "%[^,],%d,%d%n", tmp_str[0], &tmp_int[0], &tmp_int[1], &len) != 3) + return -3; + p->memo_point[i].map = mapindex_name2id(tmp_str[0]); + p->memo_point[i].x = tmp_int[0]; + p->memo_point[i].y = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str[0], &len) == 8) + { + p->inventory[i].id = tmp_int[0]; + p->inventory[i].nameid = tmp_int[1]; + p->inventory[i].amount = tmp_int[2]; + p->inventory[i].equip = tmp_int[3]; + p->inventory[i].identify = tmp_int[4]; + p->inventory[i].refine = tmp_int[5]; + p->inventory[i].attribute = tmp_int[6]; + + for(j = 0; j < MAX_SLOTS && tmp_str[0] && sscanf(tmp_str[0], ",%d%[0-9,-]",&tmp_int[0], tmp_str[0]) > 0; j++) + p->inventory[i].card[j] = tmp_int[0]; + + next += len; + if (str[next] == ' ') + next++; + } else // invalid structure + return -4; + } + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str[0], &len) == 8) + { + p->cart[i].id = tmp_int[0]; + p->cart[i].nameid = tmp_int[1]; + p->cart[i].amount = tmp_int[2]; + p->cart[i].equip = tmp_int[3]; + p->cart[i].identify = tmp_int[4]; + p->cart[i].refine = tmp_int[5]; + p->cart[i].attribute = tmp_int[6]; + + for(j = 0; j < MAX_SLOTS && tmp_str && sscanf(tmp_str[0], ",%d%[0-9,-]",&tmp_int[0], tmp_str[0]) > 0; j++) + p->cart[i].card[j] = tmp_int[0]; + + next += len; + if (str[next] == ' ') + next++; + } else // invalid structure + return -5; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d%n", &tmp_int[0], &tmp_int[1], &len) != 2) + return -6; + p->skill[tmp_int[0]].id = tmp_int[0]; + p->skill[tmp_int[0]].lv = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t' && str[next] != '\n' && str[next] != '\r'; i++) { // global_reg実装以前のathena.txt互換のため一応'\n'チェック + if (sscanf(str + next, "%[^,],%[^ ] %n", reg[i].str, reg[i].value, &len) != 2) { + // because some scripts are not correct, the str can be "". So, we must check that. + // If it's, we must not refuse the character, but just this REG value. + // Character line will have something like: nov_2nd_cos,9 ,9 nov_1_2_cos_c,1 (here, ,9 is not good) + if (str[next] == ',' && sscanf(str + next, ",%[^ ] %n", reg[i].value, &len) == 1) + i--; + else + return -7; + } + next += len; + if (str[next] == ' ') + next++; + } + *reg_num = i; + + return 1; +} +//--------------------------------- +// Function to read friend list +//--------------------------------- + +int parse_friend_txt(struct mmo_charstatus *p) +{ + char line[1024], temp[1024]; + int pos = 0, count = 0, next; + int i,len; + FILE *fp; + + // Open the file and look for the ID + fp = fopen(friends_txt, "r"); + + if(fp == NULL) + return -1; + + while(fgets(line, sizeof(line)-1, fp)) { + + if(line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%d%n",&i, &pos) < 1 || i != p->char_id) + continue; //Not this line... + //Read friends + len = strlen(line); + next = pos; + for (count = 0; next < len && count < MAX_FRIENDS; count++) + { //Read friends. + if (sscanf(line+next, ",%d,%d,%23[^,]%n",&p->friends[count].account_id,&p->friends[count].char_id, p->friends[count].name, &pos) < 3) + { //Invalid friend? + memset(&p->friends[count], 0, sizeof(p->friends[count])); + break; + } + next+=pos; + //What IF the name contains a comma? while the next field is not a + //number, we assume it belongs to the current name. [Skotlex] + //NOTE: Of course, this will fail if someone sets their name to something like + //Bob,2005 but... meh, it's the problem of parsing a text file (encasing it in " + //won't do as quotes are also valid name chars!) + while(next < len && sscanf(line+next, ",%23[^,]%n", temp, &len) > 0) + { + if (atoi(temp)) + { //We read the next friend, just continue. + break; + } else { //Append the name. + next+=len; + if (strlen(p->friends[count].name) + strlen(temp) +1 < NAME_LENGTH) + { + strcat(p->friends[count].name, ","); + strcat(p->friends[count].name, temp); + } + } + } //End Guess Block + } //Friend's for. + break; //Finished reading. + } + /* + //Character names must not exceed the 23+\0 limit. [Skotlex] + sscanf(line, "%d,%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23s",&cid, + &temp[0],p->friend_name[0], + &temp[1],p->friend_name[1], + &temp[2],p->friend_name[2], + &temp[3],p->friend_name[3], + &temp[4],p->friend_name[4], + &temp[5],p->friend_name[5], + &temp[6],p->friend_name[6], + &temp[7],p->friend_name[7], + &temp[8],p->friend_name[8], + &temp[9],p->friend_name[9], + &temp[10],p->friend_name[10], + &temp[11],p->friend_name[11], + &temp[12],p->friend_name[12], + &temp[13],p->friend_name[13], + &temp[14],p->friend_name[14], + &temp[15],p->friend_name[15], + &temp[16],p->friend_name[16], + &temp[17],p->friend_name[17], + &temp[18],p->friend_name[18], + &temp[19],p->friend_name[19]); + if (cid == p->char_id) + break; + } + // No register of friends list + if (cid == 0) { + fclose(fp); + return 0; + } + + // Fill in the list + + for (i=0; ifriend_id[i] = temp[i]; +*/ + fclose(fp); + return count; +} + +//--------------------------------- +// Function to read characters file +//--------------------------------- +int mmo_char_init(void) { + char line[65536]; + int ret, line_count; + FILE *fp; + + char_max = 256; + char_dat = (struct character_data*)aCalloc(sizeof(struct character_data) * 256, 1); + if (!char_dat) { + ShowFatalError("out of memory: mmo_char_init (calloc of char_dat).\n"); + exit(1); + } + char_num = 0; + + fp = fopen(char_txt, "r"); + + if (fp == NULL) { + ShowError("Characters file not found: %s.\n", char_txt); + char_log("Characters file not found: %s." RETCODE, char_txt); + char_log("Id for the next created character: %d." RETCODE, char_id_count); + return 0; + } + + line_count = 0; + while(fgets(line, sizeof(line)-1, fp)) { + int i, j; + line_count++; + + if (line[0] == '/' && line[1] == '/') + continue; + line[sizeof(line)-1] = '\0'; + + j = 0; + if (sscanf(line, "%d\t%%newid%%%n", &i, &j) == 1 && j > 0) { + if (char_id_count < i) + char_id_count = i; + continue; + } + + if (char_num >= char_max) { + char_max += 256; + char_dat = (struct character_data*)aRealloc(char_dat, sizeof(struct character_data) * char_max); + if (!char_dat) { + ShowFatalError("Out of memory: mmo_char_init (realloc of char_dat).\n"); + char_log("Out of memory: mmo_char_init (realloc of char_dat)." RETCODE); + exit(1); + } + } + + ret = mmo_char_fromstr(line, &char_dat[char_num].status, char_dat[char_num].global, &char_dat[char_num].global_num); + + // Initialize friends list + parse_friend_txt(&char_dat[char_num].status); // Grab friends for the character + + if (ret > 0) { // negative value or zero for errors + if (char_dat[char_num].status.char_id >= char_id_count) + char_id_count = char_dat[char_num].status.char_id + 1; + char_num++; + } else { + ShowError("mmo_char_init: in characters file, unable to read the line #%d.\n", line_count); + ShowError(" -> Character saved in log file.\n"); + switch (ret) { + case -1: + char_log("Duplicate character id in the next character line (character not readed):" RETCODE); + break; + case -2: + char_log("Duplicate character name in the next character line (character not readed):" RETCODE); + break; + case -3: + char_log("Invalid memo point structure in the next character line (character not readed):" RETCODE); + break; + case -4: + char_log("Invalid inventory item structure in the next character line (character not readed):" RETCODE); + break; + case -5: + char_log("Invalid cart item structure in the next character line (character not readed):" RETCODE); + break; + case -6: + char_log("Invalid skill structure in the next character line (character not readed):" RETCODE); + break; + case -7: + char_log("Invalid register structure in the next character line (character not readed):" RETCODE); + break; + default: // 0 + char_log("Unabled to get a character in the next line - Basic structure of line (before inventory) is incorrect (character not readed):" RETCODE); + break; + } + char_log("%s", line); + } + } + fclose(fp); + + if (char_num == 0) { + ShowNotice("mmo_char_init: No character found in %s.\n", char_txt); + char_log("mmo_char_init: No character found in %s." RETCODE, char_txt); + } else if (char_num == 1) { + ShowStatus("mmo_char_init: 1 character read in %s.\n", char_txt); + char_log("mmo_char_init: 1 character read in %s." RETCODE, char_txt); + } else { + ShowStatus("mmo_char_init: %d characters read in %s.\n", char_num, char_txt); + char_log("mmo_char_init: %d characters read in %s." RETCODE, char_num, char_txt); + } + + char_log("Id for the next created character: %d." RETCODE, char_id_count); + + return 0; +} + +//--------------------------------------------------------- +// Function to save characters in files (speed up by [Yor]) +//--------------------------------------------------------- +void mmo_char_sync(void) { + char line[65536],f_line[1024]; + int i, j, k; + int lock; + FILE *fp,*f_fp; + //int *id = (int *) aMalloc(sizeof(int) * char_num); + CREATE_BUFFER(id, int, char_num); + + // Sorting before save (by [Yor]) + for(i = 0; i < char_num; i++) { + id[i] = i; + for(j = 0; j < i; j++) { + if ((char_dat[i].status.account_id < char_dat[id[j]].status.account_id) || + // if same account id, we sort by slot. + (char_dat[i].status.account_id == char_dat[id[j]].status.account_id && + char_dat[i].status.char_num < char_dat[id[j]].status.char_num)) { + for(k = i; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[i] + break; + } + } + } + + // Data save + fp = lock_fopen(char_txt, &lock); + if (fp == NULL) { + ShowWarning("Server can't not save characters.\n"); + char_log("WARNING: Server can't not save characters." RETCODE); + } else { + for(i = 0; i < char_num; i++) { + // create only once the line, and save it in the 2 files (it's speeder than repeat twice the loop and create twice the line) + mmo_char_tostr(line, &char_dat[id[i]].status, char_dat[id[i]].global, char_dat[id[i]].global_num); // use of sorted index + fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%" RETCODE, char_id_count); + lock_fclose(fp, char_txt, &lock); + } + + // Data save (backup) + if (backup_txt_flag) { // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. => option By [Yor] + fp = lock_fopen(backup_txt, &lock); + if (fp == NULL) { + ShowWarning("Server can't not create backup of characters file.\n"); + char_log("WARNING: Server can't not create backup of characters file." RETCODE); + //aFree(id); // free up the memory before leaving -.- [Ajarn] + DELETE_BUFFER(id); + return; + } + for(i = 0; i < char_num; i++) { + // create only once the line, and save it in the 2 files (it's speeder than repeat twice the loop and create twice the line) + mmo_char_tostr(line, &char_dat[id[i]].status,char_dat[id[i]].global, char_dat[id[i]].global_num); // use of sorted index + fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%" RETCODE, char_id_count); + lock_fclose(fp, backup_txt, &lock); + } + + // Friends List data save (davidsiaw) + f_fp = lock_fopen(friends_txt, &lock); + for(i = 0; i < char_num; i++) { + mmo_friends_list_data_str(f_line, &char_dat[id[i]].status); + fprintf(f_fp, "%s" RETCODE, f_line); + } + + lock_fclose(f_fp, friends_txt, &lock); + + //aFree(id); + DELETE_BUFFER(id); + + return; +} + +//---------------------------------------------------- +// Function to save (in a periodic way) datas in files +//---------------------------------------------------- +int mmo_char_sync_timer(int tid, unsigned int tick, int id, int data) { + if (save_log) + ShowInfo("Saving all files...\n"); + mmo_char_sync(); + inter_save(); + return 0; +} + +//----------------------------------- +// Function to create a new character +//----------------------------------- +int make_new_char(int fd, unsigned char *dat) { + int i; + struct char_session_data *sd; + char name[NAME_LENGTH]; + + sd = (struct char_session_data*)session[fd]->session_data; + + // remove control characters from the name + strncpy(name, dat, NAME_LENGTH); + name[NAME_LENGTH-1] = '\0'; //Trunc name to max possible value (23) + + trim(name,TRIM_CHARS); //Trim character name. [Skotlex] + + //check name != main chat nick [LuzZza] + if(strcmpi(name, main_chat_nick) == 0) { + char_log("Create char failed (%d): this nick (%s) reserved for mainchat messages." RETCODE, + sd->account_id, name); + return -1; + } + + if (remove_control_chars((unsigned char *)name)) { + char_log("Make new char error (control char received in the name): (connection #%d, account: %d)." RETCODE, + fd, sd->account_id); + return -1; + } + + // check lenght of character name + if (strlen(name) < 4) { + char_log("Make new char error (character name too small): (connection #%d, account: %d, name: '%s')." RETCODE, + fd, sd->account_id, dat); + return -1; + } + + // Check Authorised letters/symbols in the name of the character + if (char_name_option == 1) { // only letters/symbols in char_name_letters are authorised + for (i = 0; i < NAME_LENGTH && name[i]; i++) + if (strchr(char_name_letters, name[i]) == NULL) { + char_log("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c." RETCODE, + fd, sd->account_id, name, name[i]); + return -1; + } + } 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) { + char_log("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c." RETCODE, + fd, sd->account_id, dat, dat[i]); + return -1; + } + } // else, all letters/symbols are authorised (except control char removed before) + + if (dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29] != 5*6 || // stats + dat[30] >= 9 || // slots (dat[30] can not be negativ) + dat[33] <= 0 || dat[33] >= 24 || // hair style + dat[31] >= 9) { // hair color (dat[31] can not be negativ) + char_log("Make new char error (invalid values): (connection #%d, account: %d) slot %d, name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d" RETCODE, + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + + // check individual stat value + for(i = 24; i <= 29; i++) { + if (dat[i] < 1 || dat[i] > 9) { + char_log("Make new char error (invalid stat value: not between 1 to 9): (connection #%d, account: %d) slot %d, name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d" RETCODE, + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + } // now we know that every stat has proper value but we have to check if str/int agi/luk vit/dex pairs are correct + + if( ((dat[24]+dat[27]) > 10) || ((dat[25]+dat[29]) > 10) || ((dat[26]+dat[28]) > 10) ) { + if (log_char) { + char_log("Make new char error (invalid stat value): (connection #%d, account: %d) slot %d, name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d" RETCODE, + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + } // now when we have passed all stat checks + + for(i = 0; i < char_num; i++) { + if ((name_ignoring_case != 0 && strncmp(char_dat[i].status.name, name, NAME_LENGTH) == 0) || + (name_ignoring_case == 0 && strncmpi(char_dat[i].status.name, name, NAME_LENGTH) == 0)) { + char_log("Make new char error (name already exists): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], dat, char_dat[i].status.name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + if (char_dat[i].status.account_id == sd->account_id && char_dat[i].status.char_num == dat[30]) { + char_log("Make new char error (slot already used): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], dat, char_dat[i].status.name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + } + + if (strcmp(wisp_server_name, name) == 0) { + char_log("Make new char error (name used is wisp name for server): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], name, char_dat[i].status.name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + + if (char_num >= char_max) { + char_max += 256; + char_dat = (struct character_data*)aRealloc(char_dat, sizeof(struct character_data) * char_max); + if (!char_dat) { + ShowFatalError("Out of memory: make_new_char (realloc of char_dat).\n"); + char_log("Out of memory: make_new_char (realloc of char_dat)." RETCODE); + exit(1); + } + } + + char_log("Creation of New Character: (connection #%d, account: %d) slot %d, character Name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + + memset(&char_dat[i], 0, sizeof(struct character_data)); + + char_dat[i].status.char_id = char_id_count++; + char_dat[i].status.account_id = sd->account_id; + char_dat[i].status.char_num = dat[30]; + strcpy(char_dat[i].status.name,name); + char_dat[i].status.class_ = 0; + char_dat[i].status.base_level = 1; + char_dat[i].status.job_level = 1; + char_dat[i].status.base_exp = 0; + char_dat[i].status.job_exp = 0; + char_dat[i].status.zeny = start_zeny; + char_dat[i].status.str = dat[24]; + char_dat[i].status.agi = dat[25]; + char_dat[i].status.vit = dat[26]; + char_dat[i].status.int_ = dat[27]; + char_dat[i].status.dex = dat[28]; + char_dat[i].status.luk = dat[29]; + char_dat[i].status.max_hp = 40 * (100 + char_dat[i].status.vit) / 100; + char_dat[i].status.max_sp = 11 * (100 + char_dat[i].status.int_) / 100; + char_dat[i].status.hp = char_dat[i].status.max_hp; + char_dat[i].status.sp = char_dat[i].status.max_sp; + char_dat[i].status.status_point = 0; + char_dat[i].status.skill_point = 0; + char_dat[i].status.option = 0; + char_dat[i].status.karma = 0; + char_dat[i].status.manner = 0; + char_dat[i].status.party_id = 0; + char_dat[i].status.guild_id = 0; + char_dat[i].status.hair = dat[33]; + char_dat[i].status.hair_color = dat[31]; + char_dat[i].status.clothes_color = 0; + char_dat[i].status.inventory[0].nameid = start_weapon; // Knife + char_dat[i].status.inventory[0].amount = 1; + char_dat[i].status.inventory[0].equip = 0x02; + char_dat[i].status.inventory[0].identify = 1; + char_dat[i].status.inventory[1].nameid = start_armor; // Cotton Shirt + char_dat[i].status.inventory[1].amount = 1; + char_dat[i].status.inventory[1].equip = 0x10; + char_dat[i].status.inventory[1].identify = 1; + char_dat[i].status.weapon = 1; + char_dat[i].status.shield = 0; + char_dat[i].status.head_top = 0; + char_dat[i].status.head_mid = 0; + char_dat[i].status.head_bottom = 0; + memcpy(&char_dat[i].status.last_point, &start_point, sizeof(start_point)); + memcpy(&char_dat[i].status.save_point, &start_point, sizeof(start_point)); + char_num++; + + mmo_char_sync(); + return i; +} + +//---------------------------------------------------- +// This function return the name of the job (by [Yor]) +//---------------------------------------------------- +char * job_name(int class_) { + switch (class_) { + case 0: return "Novice"; + case 1: return "Swordsman"; + case 2: return "Mage"; + case 3: return "Archer"; + case 4: return "Acolyte"; + case 5: return "Merchant"; + case 6: return "Thief"; + case 7: return "Knight"; + case 8: return "Priest"; + case 9: return "Wizard"; + case 10: return "Blacksmith"; + case 11: return "Hunter"; + case 12: return "Assassin"; + case 13: return "Knight 2"; + case 14: return "Crusader"; + case 15: return "Monk"; + case 16: return "Sage"; + case 17: return "Rogue"; + case 18: return "Alchemist"; + case 19: return "Bard"; + case 20: return "Dancer"; + case 21: return "Crusader 2"; + case 22: return "Wedding"; + case 23: return "Super Novice"; + case 4001: return "Novice High"; + case 4002: return "Swordsman High"; + case 4003: return "Mage High"; + case 4004: return "Archer High"; + case 4005: return "Acolyte High"; + case 4006: return "Merchant High"; + case 4007: return "Thief High"; + case 4008: return "Lord Knight"; + case 4009: return "High Priest"; + case 4010: return "High Wizard"; + case 4011: return "Whitesmith"; + case 4012: return "Sniper"; + case 4013: return "Assassin Cross"; + case 4014: return "Peko Knight"; + case 4015: return "Paladin"; + case 4016: return "Champion"; + case 4017: return "Professor"; + case 4018: return "Stalker"; + case 4019: return "Creator"; + case 4020: return "Clown"; + case 4021: return "Gypsy"; + case 4022: return "Peko Paladin"; + case 4023: return "Baby Novice"; + case 4024: return "Baby Swordsman"; + case 4025: return "Baby Mage"; + case 4026: return "Baby Archer"; + case 4027: return "Baby Acolyte"; + case 4028: return "Baby Merchant"; + case 4029: return "Baby Thief"; + case 4030: return "Baby Knight"; + case 4031: return "Baby Priest"; + case 4032: return "Baby Wizard"; + case 4033: return "Baby Blacksmith"; + case 4034: return "Baby Hunter"; + case 4035: return "Baby Assassin"; + case 4036: return "Baby Peco Knight"; + case 4037: return "Baby Crusader"; + case 4038: return "Baby Monk"; + case 4039: return "Baby Sage"; + case 4040: return "Baby Rogue"; + case 4041: return "Baby Alchemist"; + case 4042: return "Baby Bard"; + case 4043: return "Baby Dancer"; + case 4044: return "Baby Peco Crusader"; + case 4045: return "Super Baby"; + } + return "Unknown Job"; +} + +static int create_online_files_sub(DBKey key, void* data, va_list va) +{ + struct online_char_data *character; + int* players; + int *id; + int j,k,l; + character = (struct online_char_data*) data; + players = va_arg(va, int*); + id = va_arg(va, int*); + + // check if map-server is online + if (character->server == -1 || character->char_id == -1) { //Character not currently online. + return -1; + } + + j = character->server; + if (server_fd[j] < 0) { + server[j].users = 0; + if (kick_on_disconnect) + { + character->char_id = -1; + character->server = -1; + } + return -1; + } + // search position of character in char_dat and sort online characters. + for(j = 0; j < char_num; j++) { + if (char_dat[j].status.char_id != character->char_id) + continue; + id[*players] = j; + // use sorting option + switch (online_sorting_option) { + case 1: // by name (without case sensitive) + for(k = 0; k < *players; k++) + if (stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0 || + // if same name, we sort with case sensitive. + (stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) == 0 && + strcmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) { + for(l = *players; l > k; l--) + id[l] = id[l-1]; + id[k] = j; // id[*players] + break; + } + break; + case 2: // by zeny + for(k = 0; k < *players; k++) + if (char_dat[j].status.zeny < char_dat[id[k]].status.zeny || + // if same number of zenys, we sort by name. + (char_dat[j].status.zeny == char_dat[id[k]].status.zeny && + stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) { + for(l = *players; l > k; l--) + id[l] = id[l-1]; + id[k] = j; // id[*players] + break; + } + break; + case 3: // by base level + for(k = 0; k < *players; k++) + if (char_dat[j].status.base_level < char_dat[id[k]].status.base_level || + // if same base level, we sort by base exp. + (char_dat[j].status.base_level == char_dat[id[k]].status.base_level && + char_dat[j].status.base_exp < char_dat[id[k]].status.base_exp)) { + for(l = *players; l > k; l--) + id[l] = id[l-1]; + id[k] = j; // id[*players] + break; + } + break; + case 4: // by job (and job level) + for(k = 0; k < *players; k++) + if (char_dat[j].status.class_ < char_dat[id[k]].status.class_ || + // if same job, we sort by job level. + (char_dat[j].status.class_ == char_dat[id[k]].status.class_ && + char_dat[j].status.job_level < char_dat[id[k]].status.job_level) || + // if same job and job level, we sort by job exp. + (char_dat[j].status.class_ == char_dat[id[k]].status.class_ && + char_dat[j].status.job_level == char_dat[id[k]].status.job_level && + char_dat[j].status.job_exp < char_dat[id[k]].status.job_exp)) { + for(l = *players; l > k; l--) + id[l] = id[l-1]; + id[k] = j; // id[*players] + break; + } + break; + case 5: // by location map name + { + const char *map1, *map2; + map1 = mapindex_id2name(char_dat[j].status.last_point.map); + + for(k = 0; k < *players; k++) { + map2 = mapindex_id2name(char_dat[id[k]].status.last_point.map); + if (!map1 || !map2 || //Avoid sorting if either one failed to resolve. + stricmp(map1, map2) < 0 || + // if same map name, we sort by name. + (stricmp(map1, map2) == 0 && + stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) { + for(l = *players; l > k; l--) + id[l] = id[l-1]; + id[k] = j; // id[*players] + break; + } + } + } + break; + default: // 0 or invalid value: no sorting + break; + } + (*players)++; + break; + } + return 0; +} +//------------------------------------------------------------- +// Function to create the online files (txt and html). by [Yor] +//------------------------------------------------------------- +void create_online_files(void) { + unsigned int k, j; // for loop with strlen comparing + int i, l; // for loops + int players; // count the number of players + FILE *fp; // for the txt file + FILE *fp2; // for the html file + char temp[256]; // to prepare what we must display + time_t time_server; // for number of seconds + struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ... + int id[4096]; + + if (online_display_option == 0) // we display nothing, so return + return; + + // Get number of online players, id of each online players, and verify if a server is offline + players = 0; + online_char_db->foreach(online_char_db, create_online_files_sub, &players, &id); + + // write files + fp = fopen(online_txt_filename, "w"); + if (fp != NULL) { + fp2 = fopen(online_html_filename, "w"); + if (fp2 != NULL) { + // get time + time(&time_server); // get time in seconds since 1/1/1970 + datetime = localtime(&time_server); // convert seconds in structure + strftime(temp, sizeof(temp), "%d %b %Y %X", datetime); // like sprintf, but only for date/time (05 dec 2003 15:12:52) + // write heading + fprintf(fp2, "\n"); + fprintf(fp2, " \n", online_refresh_html); // update on client explorer every x seconds + fprintf(fp2, " \n"); + fprintf(fp2, " Online Players on %s\n", server_name); + fprintf(fp2, " \n"); + fprintf(fp2, " \n"); + fprintf(fp2, "

Online Players on %s (%s):

\n", server_name, temp); + fprintf(fp, "Online Players on %s (%s):\n", server_name, temp); + fprintf(fp, "\n"); + + for (i = 0; i < players; i++) { + // if it's the first player + if (i == 0) { + j = 0; // count the number of characters for the txt version and to set the separate line + fprintf(fp2, " \n"); + fprintf(fp2, " \n"); + if ((online_display_option & 1) || (online_display_option & 64)) { + fprintf(fp2, " \n"); + if (online_display_option & 64) { + fprintf(fp, "Name "); // 30 + j += 30; + } else { + fprintf(fp, "Name "); // 25 + j += 25; + } + } + if ((online_display_option & 6) == 6) { + fprintf(fp2, " \n"); + fprintf(fp, "Job Levels "); // 27 + j += 27; + } else if (online_display_option & 2) { + fprintf(fp2, " \n"); + fprintf(fp, "Job "); // 19 + j += 19; + } else if (online_display_option & 4) { + fprintf(fp2, " \n"); + fprintf(fp, " Levels "); // 8 + j += 8; + } + if (online_display_option & 24) { // 8 or 16 + fprintf(fp2, " \n"); + if (online_display_option & 16) { + fprintf(fp, "Location ( x , y ) "); // 23 + j += 23; + } else { + fprintf(fp, "Location "); // 13 + j += 13; + } + } + if (online_display_option & 32) { + fprintf(fp2, " \n"); + fprintf(fp, " Zenys "); // 16 + j += 16; + } + fprintf(fp2, " \n"); + fprintf(fp, "\n"); + for (k = 0; k < j; k++) + fprintf(fp, "-"); + fprintf(fp, "\n"); + } + fprintf(fp2, " \n"); + // get id of the character (more speed) + j = id[i]; + // displaying the character name + if ((online_display_option & 1) || (online_display_option & 64)) { // without/with 'GM' display + strcpy(temp, char_dat[j].status.name); + l = isGM(char_dat[j].status.account_id); + if (online_display_option & 64) { + if (l >= online_gm_display_min_level) + fprintf(fp, "%-24s (GM) ", temp); + else + fprintf(fp, "%-24s ", temp); + } else + fprintf(fp, "%-24s ", temp); + // name of the character in the html (no < >, because that create problem in html code) + fprintf(fp2, " \n"); + } + // displaying of the job + if (online_display_option & 6) { + char * jobname = job_name(char_dat[j].status.class_); + if ((online_display_option & 6) == 6) { + fprintf(fp2, " \n", jobname, char_dat[j].status.base_level, char_dat[j].status.job_level); + fprintf(fp, "%-18s %3d/%3d ", jobname, char_dat[j].status.base_level, char_dat[j].status.job_level); + } else if (online_display_option & 2) { + fprintf(fp2, " \n", jobname); + fprintf(fp, "%-18s ", jobname); + } else if (online_display_option & 4) { + fprintf(fp2, " \n", char_dat[j].status.base_level, char_dat[j].status.job_level); + fprintf(fp, "%3d/%3d ", char_dat[j].status.base_level, char_dat[j].status.job_level); + } + } + // displaying of the map + if (online_display_option & 24) { // 8 or 16 + // prepare map name + memcpy(temp, mapindex_id2name(char_dat[j].status.last_point.map), MAP_NAME_LENGTH); + temp[MAP_NAME_LENGTH] = '\0'; + if (strstr(temp, ".gat") != NULL) { + temp[strstr(temp, ".gat") - temp] = 0; // suppress the '.gat' + } + // write map name + if (online_display_option & 16) { // map-name AND coordinates + fprintf(fp2, " \n", temp, char_dat[j].status.last_point.x, char_dat[j].status.last_point.y); + fprintf(fp, "%-12s (%3d,%3d) ", temp, char_dat[j].status.last_point.x, char_dat[j].status.last_point.y); + } else { + fprintf(fp2, " \n", temp); + fprintf(fp, "%-12s ", temp); + } + } + // displaying nimber of zenys + if (online_display_option & 32) { + // write number of zenys + if (char_dat[j].status.zeny == 0) { // if no zeny + fprintf(fp2, " \n"); + fprintf(fp, " no zeny "); + } else { + fprintf(fp2, " \n", char_dat[j].status.zeny); + fprintf(fp, "%13d z ", char_dat[j].status.zeny); + } + } + fprintf(fp, "\n"); + fprintf(fp2, " \n"); + } + // If we display at least 1 player + if (players > 0) { + fprintf(fp2, "
NameJob (levels)JobLevelsLocationzenys
"); + if ((online_display_option & 64) && l >= online_gm_display_min_level) + fprintf(fp2, ""); + for (k = 0; k < strlen(temp); k++) { + switch(temp[k]) { + case '<': // < + fprintf(fp2, "<"); + break; + case '>': // > + fprintf(fp2, ">"); + break; + default: + fprintf(fp2, "%c", temp[k]); + break; + }; + } + if ((online_display_option & 64) && l >= online_gm_display_min_level) + fprintf(fp2, " (GM)"); + fprintf(fp2, "%s %d/%d%s%d/%d%s (%d, %d)%sno zeny%d z
\n"); + fprintf(fp, "\n"); + } + + // Displaying number of online players + if (players == 0) { + fprintf(fp2, "

No user is online.

\n"); + fprintf(fp, "No user is online.\n"); + } else if (players == 1) { + fprintf(fp2, "

%d user is online.

\n", players); + fprintf(fp, "%d user is online.\n", players); + } else { + fprintf(fp2, "

%d users are online.

\n", players); + fprintf(fp, "%d users are online.\n", players); + } + fprintf(fp2, " \n"); + fprintf(fp2, "\n"); + fclose(fp2); + } + fclose(fp); + } + + return; +} + +//--------------------------------------------------------------------- +// This function return the number of online players in all map-servers +//--------------------------------------------------------------------- +int count_users(void) { + int i, users; + + users = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) + users += server[i].users; + + return users; +} + +//---------------------------------------- +// Function to send characters to a player +//---------------------------------------- +int mmo_char_send006b(int fd, struct char_session_data *sd) { + int i, j, found_num; + struct mmo_charstatus *p; +//#ifdef NEW_006b + const int offset = 24; +//#else +// const int offset = 4; +//#endif + + set_char_online(0, 99,sd->account_id); + + found_num = 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.account_id == sd->account_id) { + sd->found_char[found_num] = i; + found_num++; + if (found_num == 9) + break; + } + } + for(i = found_num; i < 9; i++) + sd->found_char[i] = -1; + + WFIFOHEAD(fd, offset + found_num * 106); + memset(WFIFOP(fd,0), 0, offset + found_num * 106); + WFIFOW(fd,0) = 0x6b; + WFIFOW(fd,2) = offset + found_num * 106; + + for(i = 0; i < found_num; i++) { + p = &char_dat[sd->found_char[i]].status; + j = offset + (i * 106); // increase speed of code + + WFIFOL(fd,j) = p->char_id; + WFIFOL(fd,j+4) = p->base_exp; + WFIFOL(fd,j+8) = p->zeny; + WFIFOL(fd,j+12) = p->job_exp; + WFIFOL(fd,j+16) = p->job_level; + + WFIFOL(fd,j+20) = 0; + WFIFOL(fd,j+24) = 0; + WFIFOL(fd,j+28) = p->option; + + WFIFOL(fd,j+32) = p->karma; + WFIFOL(fd,j+36) = p->manner; + + WFIFOW(fd,j+40) = p->status_point; + WFIFOW(fd,j+42) = (p->hp > 0x7fff) ? 0x7fff : p->hp; + WFIFOW(fd,j+44) = (p->max_hp > 0x7fff) ? 0x7fff : p->max_hp; + WFIFOW(fd,j+46) = (p->sp > 0x7fff) ? 0x7fff : p->sp; + WFIFOW(fd,j+48) = (p->max_sp > 0x7fff) ? 0x7fff : p->max_sp; + WFIFOW(fd,j+50) = DEFAULT_WALK_SPEED; // p->speed; + WFIFOW(fd,j+52) = p->class_; + WFIFOW(fd,j+54) = p->hair; + + // pecopeco knights/crusaders crash fix + if (p->class_ == 13 || p->class_ == 21 || + p->class_ == 4014 || p->class_ == 4022 || + p->class_ == 4036 || p->class_ == 4044) + WFIFOW(fd,j+56) = 0; + else WFIFOW(fd,j+56) = p->weapon; + + WFIFOW(fd,j+58) = p->base_level; + WFIFOW(fd,j+60) = p->skill_point; + WFIFOW(fd,j+62) = p->head_bottom; + WFIFOW(fd,j+64) = p->shield; + WFIFOW(fd,j+66) = p->head_top; + WFIFOW(fd,j+68) = p->head_mid; + WFIFOW(fd,j+70) = p->hair_color; + WFIFOW(fd,j+72) = p->clothes_color; + + memcpy(WFIFOP(fd,j+74), p->name, NAME_LENGTH); + + WFIFOB(fd,j+98) = (p->str > 255) ? 255 : p->str; + WFIFOB(fd,j+99) = (p->agi > 255) ? 255 : p->agi; + WFIFOB(fd,j+100) = (p->vit > 255) ? 255 : p->vit; + WFIFOB(fd,j+101) = (p->int_ > 255) ? 255 : p->int_; + WFIFOB(fd,j+102) = (p->dex > 255) ? 255 : p->dex; + WFIFOB(fd,j+103) = (p->luk > 255) ? 255 : p->luk; + WFIFOB(fd,j+104) = p->char_num; + } + + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +// 離婚(char削除時に使用) +int char_divorce(struct mmo_charstatus *cs) { + if (cs == NULL) + return 0; + + if (cs->partner_id > 0){ + int i, j; + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.char_id == cs->partner_id && char_dat[i].status.partner_id == cs->char_id) { + cs->partner_id = 0; + char_dat[i].status.partner_id = 0; + for(j = 0; j < MAX_INVENTORY; j++) + if (char_dat[i].status.inventory[j].nameid == WEDDING_RING_M || char_dat[i].status.inventory[j].nameid == WEDDING_RING_F) + memset(&char_dat[i].status.inventory[j], 0, sizeof(char_dat[i].status.inventory[0])); + if (cs->inventory[j].nameid == WEDDING_RING_M || cs->inventory[j].nameid == WEDDING_RING_F) + memset(&cs->inventory[j], 0, sizeof(cs->inventory[0])); + return 0; + } + } + } + return 0; +} + +int char_married(int pl1,int pl2) { + return (char_dat[pl1].status.char_id == char_dat[pl2].status.partner_id && char_dat[pl2].status.char_id == char_dat[pl1].status.partner_id); +} + +int char_child(int parent_id, int child_id) { + return (char_dat[parent_id].status.child == char_dat[child_id].status.char_id && + ((char_dat[parent_id].status.char_id == char_dat[child_id].status.father) || + (char_dat[parent_id].status.char_id == char_dat[child_id].status.mother))); +} + +//------------------------------------------------------------ +// E-mail check: return 0 (not correct) or 1 (valid). by [Yor] +//------------------------------------------------------------ +int e_mail_check(char *email) { + char ch; + char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//---------------------------------------------------------------------- +// Force disconnection of an online player (with account value) by [Yor] +//---------------------------------------------------------------------- +int disconnect_player(int accound_id) { + int i; + struct char_session_data *sd; + + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) { + if (sd->account_id == accound_id) { + session[i]->eof = 1; + return 1; + } + } + } + + return 0; +} + +// キャラ削除に伴うデータ削除 +static int char_delete(struct mmo_charstatus *cs) { + int j; + + // ペット削除 + if (cs->pet_id) + inter_pet_delete(cs->pet_id); + for (j = 0; j < MAX_INVENTORY; j++) + if (cs->inventory[j].card[0] == (short)0xff00) + inter_pet_delete(MakeDWord(cs->inventory[j].card[1],cs->inventory[j].card[2])); + for (j = 0; j < MAX_CART; j++) + if (cs->cart[j].card[0] == (short)0xff00) + inter_pet_delete( MakeDWord(cs->cart[j].card[1],cs->cart[j].card[2]) ); + // ギルド脱退 + if (cs->guild_id) + inter_guild_leave(cs->guild_id, cs->account_id, cs->char_id); + // パーティー脱退 + if (cs->party_id) + inter_party_leave(cs->party_id, cs->account_id, cs->char_id); + // 離婚 + if (cs->partner_id){ + // 離婚情報をmapに通知 + unsigned char buf[10]; + WBUFW(buf,0) = 0x2b12; + WBUFL(buf,2) = cs->char_id; + WBUFL(buf,6) = cs->partner_id; + mapif_sendall(buf,10); + // 離婚 + char_divorce(cs); + } + return 0; +} + +int parse_tologin(int fd) { + int i; + struct char_session_data *sd; + RFIFOHEAD(fd); + + // only login-server can have an access to here. + // so, if it isn't the login-server, we disconnect the session (fd != login_fd). + if (fd != login_fd) + session[fd]->eof = 1; + if(session[fd]->eof) { + if (fd == login_fd) { + ShowWarning("Connection to login-server lost (connection #%d).\n", fd); + login_fd = -1; + } + do_close(fd); + return 0; + } + + sd = (struct char_session_data*)session[fd]->session_data; + + while(RFIFOREST(fd) >= 2 && !session[fd]->eof) { +// printf("parse_tologin: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { + case 0x2711: + if (RFIFOREST(fd) < 3) + return 0; + if (RFIFOB(fd,2)) { +// printf("connect login server error : %d\n", RFIFOB(fd,2)); + ShowError("Can not connect to the login-server.\n"); + ShowError("The server communication passwords (default s1/p1) are probably invalid.\n"); + ShowInfo("Also, please make sure your accounts file (default: accounts.txt) has those values present.\n"); + ShowInfo("The communication passwords can be changed in map_athena.conf and char_athena.conf\n"); + exit(1); + } else { + ShowStatus("Connected to login-server (connection #%d).\n", fd); + if (kick_on_disconnect) + set_all_offline(); + // if no map-server already connected, display a message... + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0 && server[i].map[0]) // if map-server online and at least 1 map + break; + if (i == MAX_MAP_SERVERS) + ShowStatus("Awaiting maps from map-server.\n"); + } + RFIFOSKIP(fd,3); + break; + + case 0x2713: + if (RFIFOREST(fd) < 51) + return 0; +// printf("parse_tologin 2713 : %d\n", RFIFOB(fd,6)); + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = (struct char_session_data*)session[i]->session_data) && sd->account_id == RFIFOL(fd,2)) { + if (RFIFOB(fd,6) != 0) { + WFIFOHEAD(i, 3); + WFIFOW(i,0) = 0x6c; + WFIFOB(i,2) = 0x42; + WFIFOSET(i,3); + } else if (max_connect_user == 0 || count_users() < max_connect_user) { +// if (max_connect_user == 0) +// printf("max_connect_user (unlimited) -> accepted.\n"); +// else +// printf("count_users(): %d < max_connect_user (%d) -> accepted.\n", count_users(), max_connect_user); + memcpy(sd->email, RFIFOP(fd, 7), 40); + if (e_mail_check(sd->email) == 0) + strncpy(sd->email, "a@a.com", 40); // default e-mail + sd->connect_until_time = (time_t)RFIFOL(fd,47); + // send characters to player + mmo_char_send006b(i, sd); + } else if(isGM(sd->account_id) >= gm_allow_level) { + sd->connect_until_time = (time_t)RFIFOL(fd,47); + // send characters to player + mmo_char_send006b(i, sd); + } else { + // refuse connection: too much online players +// printf("count_users(): %d < max_connect_use (%d) -> fail...\n", count_users(), max_connect_user); + WFIFOHEAD(fd, 3); + WFIFOW(i,0) = 0x6c; + WFIFOW(i,2) = 0; + WFIFOSET(i,3); + } + break; + } + } + RFIFOSKIP(fd,51); + break; + + // Receiving of an e-mail/time limit from the login-server (answer of a request because a player comes back from map-server to char-server) by [Yor] + case 0x2717: + if (RFIFOREST(fd) < 50) + return 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) { + if (sd->account_id == RFIFOL(fd,2)) { + memcpy(sd->email, RFIFOP(fd,6), 40); + if (e_mail_check(sd->email) == 0) + strncpy(sd->email, "a@a.com", 40); // default e-mail + sd->connect_until_time = (time_t)RFIFOL(fd,46); + break; + } + } + } + RFIFOSKIP(fd,50); + break; + + // login-server alive packet + case 0x2718: + if (RFIFOREST(fd) < 2) + return 0; + RFIFOSKIP(fd,2); + break; + + // Receiving authentification from Freya-type login server (to avoid char->login->char) + case 0x2719: + if (RFIFOREST(fd) < 18) + return 0; + // to conserv a maximum of authentification, search if account is already authentified and replace it + // that will reduce multiple connection too + for(i = 0; i < AUTH_FIFO_SIZE; i++) + if (auth_fifo[i].account_id == RFIFOL(fd,2)) + break; + // if not found, use next value + if (i == AUTH_FIFO_SIZE) { + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + i = auth_fifo_pos; + auth_fifo_pos++; + } + auth_fifo[i].account_id = RFIFOL(fd,2); + auth_fifo[i].char_id = 0; + auth_fifo[i].login_id1 = RFIFOL(fd,6); + auth_fifo[i].login_id2 = RFIFOL(fd,10); + auth_fifo[i].delflag = 2; // 0: auth_fifo canceled/void, 2: auth_fifo received from login/map server in memory, 1: connection authentified + auth_fifo[i].char_pos = 0; + auth_fifo[i].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[i].ip = RFIFOL(fd,14); + RFIFOSKIP(fd,18); + break; + + case 0x2721: // gm reply + if (RFIFOREST(fd) < 10) + return 0; + { + unsigned char buf[10]; + WBUFW(buf,0) = 0x2b0b; + WBUFL(buf,2) = RFIFOL(fd,2); // account + WBUFL(buf,6) = RFIFOL(fd,6); // GM level + mapif_sendall(buf,10); +// printf("parse_tologin: To become GM answer: char -> map.\n"); + } + RFIFOSKIP(fd,10); + break; + + case 0x2723: // changesex reply (modified by [Yor]) + if (RFIFOREST(fd) < 7) + return 0; + { + int acc, sex, i, j; + unsigned char buf[7]; + acc = RFIFOL(fd,2); + sex = RFIFOB(fd,6); + RFIFOSKIP(fd, 7); + if (acc > 0) { + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == acc) + auth_fifo[i].sex = sex; + } + for (i = 0; i < char_num; i++) { + if (char_dat[i].status.account_id == acc) { + int jobclass = char_dat[i].status.class_; + char_dat[i].status.sex = sex; + if (jobclass == 19 || jobclass == 20 || + jobclass == 4020 || jobclass == 4021 || + jobclass == 4042 || jobclass == 4043) { + // job modification + if (jobclass == 19 || jobclass == 20) { + char_dat[i].status.class_ = (sex) ? 19 : 20; + } else if (jobclass == 4020 || jobclass == 4021) { + char_dat[i].status.class_ = (sex) ? 4020 : 4021; + } else if (jobclass == 4042 || jobclass == 4043) { + char_dat[i].status.class_ = (sex) ? 4042 : 4043; + } + // remove specifical skills of classes 19, 4020 and 4042 + for(j = 315; j <= 322; j++) { + if (char_dat[i].status.skill[j].id > 0 && !char_dat[i].status.skill[j].flag) { + char_dat[i].status.skill_point += char_dat[i].status.skill[j].lv; + char_dat[i].status.skill[j].id = 0; + char_dat[i].status.skill[j].lv = 0; + } + } + // remove specifical skills of classes 20, 4021 and 4043 + for(j = 323; j <= 330; j++) { + if (char_dat[i].status.skill[j].id > 0 && !char_dat[i].status.skill[j].flag) { + char_dat[i].status.skill_point += char_dat[i].status.skill[j].lv; + char_dat[i].status.skill[j].id = 0; + char_dat[i].status.skill[j].lv = 0; + } + } + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + for (j = 0; j < MAX_INVENTORY; j++) { + if (char_dat[i].status.inventory[j].nameid && char_dat[i].status.inventory[j].equip) + char_dat[i].status.inventory[j].equip = 0; + } + char_dat[i].status.weapon = 0; + char_dat[i].status.shield = 0; + char_dat[i].status.head_top = 0; + char_dat[i].status.head_mid = 0; + char_dat[i].status.head_bottom = 0; + + if (char_dat[i].status.guild_id) //If there is a guild, update the guild_member data [Skotlex] + inter_guild_sex_changed(char_dat[i].status.guild_id, acc, char_dat[i].status.char_id, sex); + } + } + // disconnect player if online on char-server + disconnect_player(acc); + } + WBUFW(buf,0) = 0x2b0d; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + mapif_sendall(buf, 7); + } + break; + + case 0x2726: // Request to send a broadcast message (no answer) + if (RFIFOREST(fd) < 8 || RFIFOREST(fd) < (8 + RFIFOL(fd,4))) + return 0; + if (RFIFOL(fd,4) < 1) + char_log("Receiving a message for broadcast, but message is void." RETCODE); + else { + // at least 1 map-server + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) + break; + if (i == MAX_MAP_SERVERS) + char_log("'ladmin': Receiving a message for broadcast, but no map-server is online." RETCODE); + else { + unsigned char buf[128]; + char message[4096]; // +1 to add a null terminated if not exist in the packet + int lp; + char *p; + memset(message, '\0', sizeof(message)); + memcpy(message, RFIFOP(fd,8), RFIFOL(fd,4)); + message[sizeof(message)-1] = '\0'; + remove_control_chars((unsigned char *)message); + // remove all first spaces + p = message; + while(p[0] == ' ') + p++; + // if message is only composed of spaces + if (p[0] == '\0') + char_log("Receiving a message for broadcast, but message is only a lot of spaces." RETCODE); + // else send message to all map-servers + else { + if (RFIFOW(fd,2) == 0) { + char_log("'ladmin': Receiving a message for broadcast (message (in yellow): %s)" RETCODE, + message); + lp = 4; + } else { + char_log("'ladmin': Receiving a message for broadcast (message (in blue): %s)" RETCODE, + message); + lp = 8; + } + // split message to max 80 char + while(p[0] != '\0') { // if not finish + if (p[0] == ' ') // jump if first char is a space + p++; + else { + char split[80]; + char* last_space; + sscanf(p, "%79[^\t]", split); // max 79 char, any char (\t is control char and control char was removed before) + split[sizeof(split)-1] = '\0'; // last char always \0 + if ((last_space = strrchr(split, ' ')) != NULL) { // searching space from end of the string + last_space[0] = '\0'; // replace it by NULL to have correct length of split + p++; // to jump the new NULL + } + p += strlen(split); + // send broadcast to all map-servers + WBUFW(buf,0) = 0x3800; + WBUFW(buf,2) = lp + strlen(split) + 1; + WBUFL(buf,4) = 0x65756c62; // only write if in blue (lp = 8) + memcpy(WBUFP(buf,lp), split, strlen(split) + 1); + mapif_sendall(buf, WBUFW(buf,2)); + } + } + } + } + } + RFIFOSKIP(fd,8 + RFIFOL(fd,4)); + break; + + // account_reg2変更通知 + case 0x2729: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { //Receive account_reg2 registry, forward to map servers. + unsigned char buf[ACCOUNT_REG2_NUM*(256+32+2)+16]; + memcpy(buf,RFIFOP(fd,0), RFIFOW(fd,2)); +// WBUFW(buf,0) = 0x2b11; + WBUFW(buf,0) = 0x3804; //Map server can now receive all kinds of reg values with the same packet. [Skotlex] + mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd, RFIFOW(fd,2)); + } + break; + + // Account deletion notification (from login-server) + case 0x2730: + if (RFIFOREST(fd) < 6) + return 0; + // Deletion of all characters of the account + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.account_id == RFIFOL(fd,2)) { + char_delete(&char_dat[i].status); + if (i < char_num - 1) { + memcpy(&char_dat[i], &char_dat[char_num-1], sizeof(struct character_data)); + // if moved character owns to deleted account, check again it's character + if (char_dat[i].status.account_id == RFIFOL(fd,2)) { + i--; + // Correct moved character reference in the character's owner by [Yor] + } else { + int j, k; + struct char_session_data *sd2; + for (j = 0; j < fd_max; j++) { + if (session[j] && (sd2 = (struct char_session_data*)session[j]->session_data) && + sd2->account_id == char_dat[char_num-1].status.account_id) { + for (k = 0; k < 9; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = i; + break; + } + } + break; + } + } + } + } + char_num--; + } + } + // Deletion of the storage + inter_storage_delete(RFIFOL(fd,2)); + // send to all map-servers to disconnect the player + { + unsigned char buf[6]; + WBUFW(buf,0) = 0x2b13; + WBUFL(buf,2) = RFIFOL(fd,2); + mapif_sendall(buf, 6); + } + // disconnect player if online on char-server + disconnect_player(RFIFOL(fd,2)); + RFIFOSKIP(fd,6); + break; + + // State change of account/ban notification (from login-server) by [Yor] + case 0x2731: + if (RFIFOREST(fd) < 11) + return 0; + // send to all map-servers to disconnect the player + { + unsigned char buf[11]; + WBUFW(buf,0) = 0x2b14; + WBUFL(buf,2) = RFIFOL(fd,2); + WBUFB(buf,6) = RFIFOB(fd,6); // 0: change of statut, 1: ban + WBUFL(buf,7) = RFIFOL(fd,7); // status or final date of a banishment + mapif_sendall(buf, 11); + } + // disconnect player if online on char-server + disconnect_player(RFIFOL(fd,2)); + RFIFOSKIP(fd,11); + break; + + // Receiving GM acounts info from login-server (by [Yor]) + case 0x2732: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + unsigned char buf[32000]; + if (gm_account != NULL) + aFree(gm_account); + gm_account = (struct gm_account*)aCalloc(sizeof(struct gm_account) * ((RFIFOW(fd,2) - 4) / 5), 1); + GM_num = 0; + for (i = 4; i < RFIFOW(fd,2); i = i + 5) { + gm_account[GM_num].account_id = RFIFOL(fd,i); + gm_account[GM_num].level = (int)RFIFOB(fd,i+4); + //printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } + ShowStatus("From login-server: receiving information of %d GM accounts.\n", GM_num); + char_log("From login-server: receiving information of %d GM accounts." RETCODE, GM_num); + create_online_files(); // update online players files (perhaps some online players change of GM level) + // send new gm acccounts level to map-servers + memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x2b15; + mapif_sendall(buf, RFIFOW(fd,2)); + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // Receive GM accounts [Freya login server packet by Yor] + case 0x2733: + // add test here to remember that the login-server is Freya-type + // sprintf (login_server_type, "Freya"); + if (RFIFOREST(fd) < 7) + return 0; + { + unsigned char buf[32000]; + int new_level = 0; + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == RFIFOL(fd,2)) { + if (gm_account[i].level != (int)RFIFOB(fd,6)) { + gm_account[i].level = (int)RFIFOB(fd,6); + new_level = 1; + } + break; + } + // if not found, add it + if (i == GM_num) { + // limited to 4000, because we send information to char-servers (more than 4000 GM accounts???) + // int (id) + int (level) = 8 bytes * 4000 = 32k (limit of packets in windows) + if (((int)RFIFOB(fd,6)) > 0 && GM_num < 4000) { + if (GM_num == 0) { + gm_account = (struct gm_account*)aMalloc(sizeof(struct gm_account)); + } else { + gm_account = (struct gm_account*)aRealloc(gm_account, sizeof(struct gm_account) * (GM_num + 1)); + } + gm_account[GM_num].account_id = RFIFOL(fd,2); + gm_account[GM_num].level = (int)RFIFOB(fd,6); + new_level = 1; + GM_num++; + if (GM_num >= 4000) { + ShowWarning("4000 GM accounts found. Next GM accounts are not readed.\n"); + char_log("***WARNING: 4000 GM accounts found. Next GM accounts are not readed." RETCODE); + } + } + } + if (new_level == 1) { + int len; + ShowStatus("From login-server: receiving GM account information (%d: level %d).\n", RFIFOL(fd,2), (int)RFIFOB(fd,6)); + char_log("From login-server: receiving a GM account information (%d: level %d)." RETCODE, RFIFOL(fd,2), (int)RFIFOB(fd,6)); + //create_online_files(); // not change online file for only 1 player (in next timer, that will be done + // send gm acccounts level to map-servers + len = 4; + WBUFW(buf,0) = 0x2b15; + + for(i = 0; i < GM_num; i++) { + WBUFL(buf, len) = gm_account[i].account_id; + WBUFB(buf, len+4) = (unsigned char)gm_account[i].level; + len += 5; + } + WBUFW(buf, 2) = len; + mapif_sendall(buf, len); + } + } + RFIFOSKIP(fd,7); + break; + + //Login server request to kick a character out. [Skotlex] + case 0x2734: + if (RFIFOREST(fd) < 6) + return 0; + { + struct online_char_data* character; + int aid = RFIFOL(fd,2); + if ((character = idb_get(online_char_db, aid)) != NULL) + { //Kick out this player. + if (character->server > -1) + { //Kick it from the map server it is on. + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + if (!character->waiting_disconnect) + add_timer(gettick()+15000, chardb_waiting_disconnect, character->account_id, 0); + character->waiting_disconnect = 1; + } else { //Manual kick from char server. + struct char_session_data *tsd; + int i; + for(i = 0; i < fd_max; i++) { + if (session[i] && (tsd = (struct char_session_data*)session[i]->session_data) && tsd->account_id == aid) + { + WFIFOHEAD(fd, 3); + WFIFOW(i,0) = 0x81; + WFIFOB(i,2) = 2; + WFIFOSET(i,3); + break; + } + } + if (i == fd_max) //Shouldn't happen, but just in case. + set_char_offline(99, aid); + } + } + RFIFOSKIP(fd,6); + } + break; + default: + ShowWarning("Unknown packet 0x%04x received from login-server, disconnecting.\n", RFIFOW(fd,0)); + session[fd]->eof = 1; + return 0; + } + } + RFIFOFLUSH(fd); + + return 0; +} + +int request_accreg2(int account_id, int char_id) { + if (login_fd > 0) { + WFIFOW(login_fd, 0) = 0x272e; + WFIFOL(login_fd, 2) = account_id; + WFIFOL(login_fd, 6) = char_id; + WFIFOSET(login_fd, 10); + return 1; + } + return 0; +} + +//Send packet forward to login-server for account saving +int save_accreg2(unsigned char* buf, int len) { + if (login_fd > 0) { + WFIFOHEAD(login_fd, len+4); + memcpy(WFIFOP(login_fd,4), buf, len); + WFIFOW(login_fd,0) = 0x2728; + WFIFOW(login_fd,2) = len+4; + WFIFOSET(login_fd,len+4); + return 1; + } + return 0; +} + +//Receive Registry information for a character. +int char_parse_Registry(int account_id, int char_id, unsigned char *buf, int buf_len) { + int i,j,p,len; + for (i = 0; i < char_num; i++) { + if (char_dat[i].status.account_id == account_id && char_dat[i].status.char_id == char_id) + break; + } + if(i >= char_num) //Character not found? + return 1; + for(j=0,p=0;j= char_num){ //Character not found? Sent empty packet. + WFIFOW(fd,2)=13; + }else{ + for (p=13,j = 0; j < char_dat[i].global_num; j++) { + if (char_dat[i].global[j].str[0]) { + p+= sprintf(WFIFOP(fd,p), "%s", char_dat[i].global[j].str)+1; //We add 1 to consider the '\0' in place. + p+= sprintf(WFIFOP(fd,p), "%s", char_dat[i].global[j].value)+1; + } + } + WFIFOW(fd,2)=p; + } + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +int search_mapserver(unsigned short map, long ip, short port); + +int parse_frommap(int fd) { + int i, j; + int id; + RFIFOHEAD(fd); + + for(id = 0; id < MAX_MAP_SERVERS; id++) + if (server_fd[id] == fd) + break; + if(id==MAX_MAP_SERVERS) + session[fd]->eof=1; + if(session[fd]->eof){ + if (id < MAX_MAP_SERVERS) { + unsigned char buf[16384]; + ShowStatus("Map-server %d has disconnected.\n", id); + //Notify other map servers that this one is gone. [Skotlex] + WBUFW(buf,0) = 0x2b20; + WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) + if (server[id].map[i]) + WBUFW(buf,10+(j++)*4) = server[id].map[i]; + if (j > 0) { + WBUFW(buf,2) = j * 4 + 10; + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + server_fd[id] = -1; + online_char_db->foreach(online_char_db,char_db_setoffline,i); //Tag relevant chars as 'in disconnected' server. + } + do_close(fd); + create_online_files(); + return 0; + } + + while(RFIFOREST(fd) >= 2 && !session[fd]->eof) { +// printf("parse_frommap: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { + + // map-server alive packet + case 0x2718: + if (RFIFOREST(fd) < 2) + return 0; + RFIFOSKIP(fd,2); + break; + + // request from map-server to reload GM accounts. Transmission to login-server (by Yor) + case 0x2af7: + if (login_fd > 0) { // don't send request if no login-server + WFIFOHEAD(login_fd, 2); + WFIFOW(login_fd,0) = 0x2709; + WFIFOSET(login_fd, 2); +// printf("char : request from map-server to reload GM accounts -> login-server.\n"); + } + RFIFOSKIP(fd,2); + break; + + // Receiving map names list from the map-server + case 0x2afa: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + memset(server[id].map, 0, sizeof(server[id].map)); + j = 0; + for(i = 4; i < RFIFOW(fd,2); i += 4) { + server[id].map[j] = RFIFOW(fd,i); + j++; + } + { + unsigned char *p = (unsigned char *)&server[id].ip; + ShowStatus("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n", + id, j, p[0], p[1], p[2], p[3], server[id].port); + ShowStatus("Map-server %d loading complete.\n", id); + char_log("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d. Map-server %d loading complete." RETCODE, + id, j, p[0], p[1], p[2], p[3], server[id].port, id); + if (kick_on_disconnect) + set_all_offline(); + if (max_account_id != DEFAULT_MAX_ACCOUNT_ID || max_char_id != DEFAULT_MAX_CHAR_ID) + mapif_send_maxid(max_account_id, max_char_id); //Send the current max ids to the server to keep in sync [Skotlex] + } + WFIFOHEAD(fd, 3 + NAME_LENGTH); + WFIFOW(fd,0) = 0x2afb; + WFIFOB(fd,2) = 0; + memcpy(WFIFOP(fd,3), wisp_server_name, NAME_LENGTH); // name for wisp to player + WFIFOSET(fd,3+NAME_LENGTH); + //WFIFOSET(fd,27); + { + unsigned char buf[16384]; + int x; + if (j == 0) { + ShowWarning("Map-Server %d have NO map.\n", id); + char_log("WARNING: Map-Server %d have NO map." RETCODE, id); + // Transmitting maps information to the other map-servers + } else { + WBUFW(buf,0) = 0x2b04; + WBUFW(buf,2) = j * 4 + 10; + WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; + memcpy(WBUFP(buf,10), RFIFOP(fd,4), j * 4); + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + // Transmitting the maps of the other map-servers to the new map-server + for(x = 0; x < MAX_MAP_SERVERS; x++) { + if (server_fd[x] >= 0 && x != id) { + WFIFOW(fd,0) = 0x2b04; + WFIFOL(fd,4) = server[x].ip; + WFIFOW(fd,8) = server[x].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) + if (server[x].map[i]) + WFIFOW(fd,10+(j++)*4) = server[x].map[i]; + if (j > 0) { + WFIFOW(fd,2) = j * 4 + 10; + WFIFOSET(fd,WFIFOW(fd,2)); + } + } + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + //Packet command is now used for sc_data request. [Skotlex] + case 0x2afc: + if (RFIFOREST(fd) < 10) + return 0; + { +#ifdef ENABLE_SC_SAVING + int aid, cid; + struct scdata *data; + aid = RFIFOL(fd,2); + cid = RFIFOL(fd,6); +#endif + RFIFOSKIP(fd, 10); +#ifdef ENABLE_SC_SAVING + data = status_search_scdata(aid, cid); + if (data->count > 0) + { //Deliver status change data. + int i; + + WFIFOW(fd,0) = 0x2b1d; + WFIFOW(fd,2) = 14 + data->count*sizeof(struct status_change_data); + WFIFOL(fd,4) = aid; + WFIFOL(fd,8) = cid; + WFIFOW(fd,12) = data->count; + for (i = 0; i < data->count; i++) + memcpy(WFIFOP(fd,14+i*sizeof(struct status_change_data)), &data->data[i], sizeof(struct status_change_data)); + WFIFOSET(fd, WFIFOW(fd,2)); + status_delete_scdata(aid, cid); //Data sent, so it needs be discarded now. + } +#endif + break; + } + + //set MAP user count + case 0x2afe: + if (RFIFOREST(fd) < 4) + return 0; + if (RFIFOW(fd,2) != server[id].users) { + server[id].users = RFIFOW(fd,2); + ShowInfo("User Count: %d (Server: %d)\n", server[id].users, id); + } + RFIFOSKIP(fd, 4); + break; + //set MAP users + case 0x2aff: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + server[id].users = RFIFOW(fd,4); + // add online players in the list by [Yor], adapted to use dbs by [Skotlex] + j = 0; + online_char_db->foreach(online_char_db,char_db_setoffline,id); //Set all chars from this server as 'unknown' + for(i = 0; i < server[id].users; i++) { + int aid, cid; + struct online_char_data* character; + aid = RFIFOL(fd,6+i*8); + cid = RFIFOL(fd,6+i*8+4); + character = idb_ensure(online_char_db, aid, create_online_char_data); + if (online_check && character->server > -1 && character->server != id) + { + ShowNotice("Set map user: Character (%d:%d) marked on map server %d, but map server %d claims to have (%d:%d) online!\n", + character->account_id, character->char_id, character->server, id, aid, cid); + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + } + character->char_id = cid; + character->server = id; + } + if (update_online < time(NULL)) { // Time is done + update_online = time(NULL) + 8; + create_online_files(); // only every 8 sec. (normally, 1 server send users every 5 sec.) Don't update every time, because that takes time, but only every 2 connection. + // it set to 8 sec because is more than 5 (sec) and if we have more than 1 map-server, informations can be received in shifted. + } + //If any chars remain in -2, they will be cleaned in the cleanup timer. + RFIFOSKIP(fd,6+i*8); + break; + + // キャラデータ保存 + // Recieve character data from map-server + case 0x2b01: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.account_id == RFIFOL(fd,4) && + char_dat[i].status.char_id == RFIFOL(fd,8)) + break; + } + if (i != char_num) + memcpy(&char_dat[i].status, RFIFOP(fd,13), sizeof(struct mmo_charstatus)); + if (RFIFOB(fd,12)) { //Flag, set character offline. [Skotlex] + set_char_offline(RFIFOL(fd,8),RFIFOL(fd,4)); + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // キャラセレ要求 + case 0x2b02: + if (RFIFOREST(fd) < 18) + return 0; + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd,2); + auth_fifo[auth_fifo_pos].char_id = 0; + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd,6); + auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); + auth_fifo[auth_fifo_pos].delflag = 2; + auth_fifo[auth_fifo_pos].char_pos = 0; + auth_fifo[auth_fifo_pos].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,14); + auth_fifo_pos++; + WFIFOW(fd,0) = 0x2b03; + WFIFOL(fd,2) = RFIFOL(fd,2); + WFIFOB(fd,6) = 0; + WFIFOSET(fd,7); + RFIFOSKIP(fd,18); + break; + + // request "change map server" + case 0x2b05: + if (RFIFOREST(fd) < 35) + return 0; + { + unsigned short name; + int map_id, map_fd = -1, i; + struct online_char_data* data; + struct mmo_charstatus* char_data; + + name = RFIFOW(fd,18); + map_id = search_mapserver(name, RFIFOL(fd,24), RFIFOW(fd,28)); //Locate mapserver by ip and port. + if (map_id >= 0) + map_fd = server_fd[map_id]; + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.account_id == RFIFOL(fd,2) && + char_dat[i].status.char_id == RFIFOL(fd,14)) + break; + } + char_data = i< char_num? &char_dat[i].status:NULL; + //Tell the new map server about this player using Kevin's new auth packet. [Skotlex] + if (map_fd>=0 && session[map_fd] && char_data) + { //Send the map server the auth of this player. + //Update the "last map" as this is where the player must be spawned on the new map server. + char_data->last_point.map = RFIFOW(fd,18); + char_data->last_point.x = RFIFOW(fd,20); + char_data->last_point.y = RFIFOW(fd,22); + + WFIFOW(map_fd,0) = 0x2afd; + WFIFOW(map_fd,2) = 20 + sizeof(struct mmo_charstatus); + WFIFOL(map_fd,4) = RFIFOL(fd, 2); //Account ID + WFIFOL(map_fd,8) = RFIFOL(fd, 6); //Login1 + WFIFOL(map_fd,16) = RFIFOL(fd,10); //Login2 + WFIFOL(map_fd,12) = (unsigned long)0; //TODO: connect_until_time, how do I figure it out right now? + memcpy(WFIFOP(map_fd,20), char_data, sizeof(struct mmo_charstatus)); + WFIFOSET(map_fd, WFIFOW(map_fd,2)); + data = idb_ensure(online_char_db, RFIFOL(fd, 2), create_online_char_data); + data->char_id = char_data->char_id; + data->server = map_id; //Update server where char is. + + //Reply with an ack. + WFIFOW(fd, 0) = 0x2b06; + memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 28); + WFIFOSET(fd, 30); + } else { //Reply with nak + WFIFOW(fd, 0) = 0x2b06; + memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 28); + WFIFOL(fd, 6) = 0; //Set login1 to 0. + WFIFOSET(fd, 30); + } + RFIFOSKIP(fd, 35); + } + break; + + // キャラ名検索 + case 0x2b08: + if (RFIFOREST(fd) < 6) + return 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].status.char_id == RFIFOL(fd,2)) + break; + } + WFIFOW(fd,0) = 0x2b09; + WFIFOL(fd,2) = RFIFOL(fd,2); + if (i != char_num) + memcpy(WFIFOP(fd,6), char_dat[i].status.name, NAME_LENGTH); + else + memcpy(WFIFOP(fd,6), unknown_char_name, NAME_LENGTH); + WFIFOSET(fd,6+NAME_LENGTH); + //WFIFOSET(fd,30); + RFIFOSKIP(fd,6); + break; + + // it is a request to become GM + case 0x2b0a: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; +// printf("parse_frommap: change gm -> login, account: %d, pass: '%s'.\n", RFIFOL(fd,4), RFIFOP(fd,8)); + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2720; + memcpy(WFIFOP(login_fd,2), RFIFOP(fd,2), RFIFOW(fd,2)-2); + WFIFOSET(login_fd, RFIFOW(fd,2)); + } else { + WFIFOW(fd,0) = 0x2b0b; + WFIFOL(fd,2) = RFIFOL(fd,4); + WFIFOL(fd,6) = 0; + WFIFOSET(fd, 10); + } + RFIFOSKIP(fd, RFIFOW(fd,2)); + break; + + // Map server send information to change an email of an account -> login-server + case 0x2b0c: + if (RFIFOREST(fd) < 86) + return 0; + if (login_fd > 0) { // don't send request if no login-server + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), 86); // 0x2722 .L .40B .40B + WFIFOW(login_fd,0) = 0x2722; + WFIFOSET(login_fd, 86); + } + RFIFOSKIP(fd, 86); + break; + + // Map server ask char-server about a character name to do some operations (all operations are transmitted to login-server) + case 0x2b0e: + if (RFIFOREST(fd) < 44) + return 0; + { + char character_name[NAME_LENGTH]; + int acc = RFIFOL(fd,2); // account_id of who ask (-1 if nobody) + memcpy(character_name, RFIFOP(fd,6), NAME_LENGTH-1); + character_name[NAME_LENGTH -1] = '\0'; + // prepare answer + WFIFOW(fd,0) = 0x2b0f; // answer + WFIFOL(fd,2) = acc; // who want do operation + WFIFOW(fd,30) = RFIFOW(fd, 30); // type of operation: 1-block, 2-ban, 3-unblock, 4-unban, 5-changesex + // search character + i = search_character_index(character_name); + if (i >= 0) { + memcpy(WFIFOP(fd,6), search_character_name(i), NAME_LENGTH); // put correct name if found + WFIFOW(fd,6+NAME_LENGTH) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + //WFIFOW(fd,32) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + switch(RFIFOW(fd, 30)) { + case 1: // block + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = char_dat[i].status.account_id; // account value + WFIFOL(login_fd,6) = 5; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 5); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 2: // ban + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2725; + WFIFOL(login_fd, 2) = char_dat[i].status.account_id; // account value + WFIFOW(login_fd, 6) = RFIFOW(fd,32); // year + WFIFOW(login_fd, 8) = RFIFOW(fd,34); // month + WFIFOW(login_fd,10) = RFIFOW(fd,36); // day + WFIFOW(login_fd,12) = RFIFOW(fd,38); // hour + WFIFOW(login_fd,14) = RFIFOW(fd,40); // minute + WFIFOW(login_fd,16) = RFIFOW(fd,42); // second + WFIFOSET(login_fd,18); +// printf("char : status -> login: account %d, ban: %dy %dm %dd %dh %dmn %ds\n", +// char_dat[i].account_id, (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), (short)RFIFOW(fd,38), (short)RFIFOW(fd,40), (short)RFIFOW(fd,42)); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 3: // unblock + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = char_dat[i].status.account_id; // account value + WFIFOL(login_fd,6) = 0; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 0); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 4: // unban + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x272a; + WFIFOL(login_fd, 2) = char_dat[i].status.account_id; // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, unban request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 5: // changesex + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2727; + WFIFOL(login_fd, 2) = char_dat[i].status.account_id; // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, change sex request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + } + } else { + // character name not found + memcpy(WFIFOP(fd,6), character_name, NAME_LENGTH); + WFIFOW(fd,8+NAME_LENGTH) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + //WFIFOW(fd,32) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } + // send answer if a player ask, not if the server ask + if (acc != -1) { + //WFIFOSET(fd, 34); + WFIFOSET(fd, 10+NAME_LENGTH); + } + RFIFOSKIP(fd, 44); + break; + } + +// case 0x2b0f: not more used (available for futur usage) + + //Packet 0x2b10 deprecated in favor of packet 0x3004 for registry saving. [Skotlex] + //case 0x2b10: + + // Recieve rates [Wizputer] + case 0x2b16: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,8)) + return 0; + // Txt doesn't need this packet, so just skip it + RFIFOSKIP(fd,RFIFOW(fd,8)); + break; + + // Character disconnected set online 0 [Wizputer] + case 0x2b17: + if (RFIFOREST(fd) < 6) + return 0; + //printf("Setting %d char offline\n",RFIFOL(fd,2)); + set_char_offline(RFIFOL(fd,2),RFIFOL(fd,6)); + RFIFOSKIP(fd,10); + break; + + // Reset all chars to offline [Wizputer] + case 0x2b18: + ShowNotice("Map server [%d] requested to set all characters offline.\n", id); + set_all_offline(); + RFIFOSKIP(fd,2); + break; + + // Character set online [Wizputer] + case 0x2b19: + if (RFIFOREST(fd) < 6) + return 0; + //printf("Setting %d char online\n",RFIFOL(fd,2)); + set_char_online(id, RFIFOL(fd,2),RFIFOL(fd,6)); + RFIFOSKIP(fd,10); + break; + + // Request sending of fame list + case 0x2b1a: + if (RFIFOREST(fd) < 2) + return 0; + { + int i, j, k, len = 8; + unsigned char buf[32000]; + struct fame_list fame_item; + //struct mmo_charstatus *dat; + //dat = (struct mmo_charstatus *)aCalloc(char_num, sizeof(struct mmo_charstatus *)); + CREATE_BUFFER(id, int, char_num); + + // copy character list into buffer + //for (i = 0; i < char_num; i++) + // dat[i] = char_dat[i]; + // sort according to fame + // qsort(dat, char_num, sizeof(struct mmo_charstatus *), sort_fame); + + for(i = 0; i < char_num; i++) { + id[i] = i; + for(j = 0; j < i; j++) { + if (char_dat[i].status.fame > char_dat[id[j]].status.fame) { + for(k = i; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[i] + break; + } + } + } + + // starting to send to map + WBUFW(buf,0) = 0x2b1b; + // add list for blacksmiths + for (i = 0, j = 0; i < char_num && j < 10; i++) { + if (char_dat[id[i]].status.fame && (char_dat[id[i]].status.class_ == 10 || + char_dat[id[i]].status.class_ == 4011 || + char_dat[id[i]].status.class_ == 4033)) + { + fame_item.id = char_dat[id[i]].status.char_id; + fame_item.fame = char_dat[id[i]].status.fame; + strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH); + + memcpy(WBUFP(buf, len), &fame_item, sizeof(struct fame_list)); + len += sizeof(struct fame_list); + j++; + } + } + // add blacksmith's block length + WBUFW(buf, 6) = len; + + // add list for alchemists + for (i = 0, j = 0; i < char_num && j < 10; i++) { + if (char_dat[id[i]].status.fame && (char_dat[id[i]].status.class_ == 18 || + char_dat[id[i]].status.class_ == 4019 || + char_dat[id[i]].status.class_ == 4041)) + { + fame_item.id = char_dat[id[i]].status.char_id; + fame_item.fame = char_dat[id[i]].status.fame; + strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH); + + memcpy(WBUFP(buf, len), &fame_item, sizeof(struct fame_list)); + len += sizeof(struct fame_list); + j++; + } + } + // add alchemist's block length + WBUFW(buf, 4) = len; + + // adding list for taekwons + for (i = 0, j = 0; i < char_num && j < 10; i++) { + if (char_dat[id[i]].status.fame && char_dat[id[i]].status.class_ == 4046) + { + fame_item.id = char_dat[id[i]].status.char_id; + fame_item.fame = char_dat[id[i]].status.fame; + strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH); + + memcpy(WBUFP(buf, len), &fame_item, sizeof(struct fame_list)); + len += sizeof(struct fame_list); + j++; + } + } + // add total packet length + WBUFW(buf, 2) = len; + + // sending to all maps + mapif_sendall(buf, len); + // done! + //aFree(dat); + DELETE_BUFFER(id); + RFIFOSKIP(fd,2); + break; + } + //Request to save status change data. [Skotlex] + case 0x2b1c: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { +#ifdef ENABLE_SC_SAVING + int count, aid, cid, i; + struct scdata *data; + aid = RFIFOL(fd, 4); + cid = RFIFOL(fd, 8); + count = RFIFOW(fd, 12); + data = status_search_scdata(aid, cid); + if (data->count != count) + { + data->count = count; + data->data = aRealloc(data->data, count*sizeof(struct status_change_data)); + } + for (i = 0; i < count; i++) + memcpy (&data->data[i], RFIFOP(fd, 14+i*sizeof(struct status_change_data)), sizeof(struct status_change_data)); +#endif + RFIFOSKIP(fd, RFIFOW(fd, 2)); + break; + } + default: + // inter server処理に渡す + { + int r = inter_parse_frommap(fd); + if (r == 1) // 処理できた + break; + if (r == 2) // パケット長が足りない + return 0; + } + // inter server処理でもない場合は切断 + ShowError("Unknown packet 0x%04x from map server, disconnecting.\n", RFIFOW(fd,0)); + session[fd]->eof = 1; + return 0; + } + } + return 0; +} + +int search_mapserver(unsigned short map, long ip, short port) { + int i, j; + + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) + for (j = 0; server[i].map[j]; j++) + if (server[i].map[j] == map) { + if (ip > 0 && server[i].ip != ip) + continue; + if (port > 0 && server[i].port != port) + continue; + return i; + } + + return -1; +} + +// char_mapifの初期化処理(現在はinter_mapif初期化のみ) +static int char_mapif_init(int fd) { + return inter_mapif_init(fd); +} + +//----------------------------------------------------- +// Test to know if an IP come from LAN or WAN. by [Yor] +//----------------------------------------------------- +int lan_ip_check(unsigned char *p){ + int i; + int lancheck = 1; + +// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +// p[0], p[1], p[2], p[3], +// subneti[0], subneti[1], subneti[2], subneti[3], +// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + for(i = 0; i < 4; i++) { + if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { + lancheck = 0; + break; + } + } + ShowInfo("LAN test (result): %s source"CL_RESET".\n", (lancheck) ? CL_CYAN"LAN" : CL_GREEN"WAN"); + return lancheck; +} + +int parse_char(int fd) { + int i, ch; + unsigned short cmd; + char email[40]; + int map_fd; + struct char_session_data *sd; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + RFIFOHEAD(fd); + + sd = (struct char_session_data*)session[fd]->session_data; + + if (login_fd < 0) + session[fd]->eof = 1; + if(session[fd]->eof) { // disconnect any player (already connected to char-server or coming back from map-server) if login-server is diconnected. + if (fd == login_fd) + login_fd = -1; + if (sd != NULL) + { + struct online_char_data* data = idb_get(online_char_db, sd->account_id); + if (!data || data->server== -1) //If it is not in any server, send it offline. [Skotlex] + set_char_offline(99,sd->account_id); + } + do_close(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2 && !session[fd]->eof) { + cmd = RFIFOW(fd,0); + // crc32のスキップ用 + if( sd==NULL && // 未ログインor管理パケット + RFIFOREST(fd)>=4 && // 最低バイト数制限 & 0x7530,0x7532管理パケ除去 + RFIFOREST(fd)<=21 && // 最大バイト数制限 & サーバーログイン除去 + cmd!=0x20b && // md5通知パケット除去 + (RFIFOREST(fd)<6 || RFIFOW(fd,4)==0x65) ){ // 次に何かパケットが来てるなら、接続でないとだめ + RFIFOSKIP(fd,4); + cmd = RFIFOW(fd,0); + ShowDebug("parse_char : %d crc32 skipped\n",fd); + if(RFIFOREST(fd)==0) + return 0; + } + +//For use in packets that depend on an sd being present [Skotlex] +#define FIFOSD_CHECK(rest) { if(RFIFOREST(fd) < rest) return 0; if (sd==NULL) { RFIFOSKIP(fd,rest); return 0; } } + + switch(cmd){ + case 0x20b: //20040622暗号化ragexe対応 + if (RFIFOREST(fd) < 19) + return 0; + RFIFOSKIP(fd,19); + break; + + case 0x65: // 接続要求 + if (RFIFOREST(fd) < 17) + return 0; + { + int GM_value; + if ((GM_value = isGM(RFIFOL(fd,2)))) + ShowInfo("Account Logged On; Account ID: %d (GM level %d).\n", RFIFOL(fd,2), GM_value); + else + ShowInfo("Account Logged On; Account ID: %d.\n", RFIFOL(fd,2)); + if (sd == NULL) { + sd = (struct char_session_data*)aCalloc(sizeof(struct char_session_data), 1); + session[fd]->session_data = sd; + +// memset(sd, 0, sizeof(struct char_session_data)); aCalloc does this [Skotlex] + strncpy(sd->email, "no mail", 40); // put here a mail without '@' to refuse deletion if we don't receive the e-mail + sd->connect_until_time = 0; // unknow or illimited (not displaying on map-server) + } + sd->account_id = RFIFOL(fd,2); + sd->login_id1 = RFIFOL(fd,6); + sd->login_id2 = RFIFOL(fd,10); + sd->sex = RFIFOB(fd,16); + // send back account_id + WFIFOHEAD(fd, 4); + WFIFOL(fd,0) = RFIFOL(fd,2); + WFIFOSET(fd,4); + // search authentification + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == sd->account_id && + auth_fifo[i].login_id1 == sd->login_id1 && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == sd->login_id2 && // relate to the versions higher than 18 +#endif + (!check_ip_flag || auth_fifo[i].ip == session[fd]->client_addr.sin_addr.s_addr) && + auth_fifo[i].delflag == 2) { + auth_fifo[i].delflag = 1; + + if (online_check) + { // check if character is not online already. [Skotlex] + struct online_char_data* character; + character = idb_get(online_char_db, sd->account_id); + + if (character) + { + if(character->server > -1) + { //Kick it from the map server it is on. + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + if (!character->waiting_disconnect) + add_timer(gettick()+20000, chardb_waiting_disconnect, character->account_id, 0); + character->waiting_disconnect = 1; + /* Not a good idea because this would trigger when you do a char-change from the map server! [Skotlex] + } else { //Manual kick from char server. + struct char_session_data *tsd; + int i; + for(i = 0; i < fd_max; i++) { + if (session[i] && i!=fd && (tsd = (struct char_session_data*)session[i]->session_data) && tsd->account_id == sd->account_id) + { + WFIFOW(i,0) = 0x81; + WFIFOB(i,2) = 2; + WFIFOSET(i,3); + break; + } + } + if (i == fd_max) //Shouldn't happen, but just in case. + set_char_offline(99, sd->account_id); + */ + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 8; + WFIFOSET(fd,3); + break; + } + } + } + + if (max_connect_user == 0 || count_users() < max_connect_user) { + if (login_fd > 0) { // don't send request if no login-server + // request to login-server to obtain e-mail/time limit + WFIFOW(login_fd,0) = 0x2716; + WFIFOL(login_fd,2) = sd->account_id; + WFIFOSET(login_fd,6); + } + // send characters to player + mmo_char_send006b(fd, sd); + } else { + // refuse connection (over populated) + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } + break; + } + } + // authentification not found + if (i == AUTH_FIFO_SIZE) { + if (login_fd > 0) { // don't send request if no login-server + 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; // relate to the versions higher than 18 + WFIFOB(login_fd,14) = sd->sex; + WFIFOL(login_fd,15) = session[fd]->client_addr.sin_addr.s_addr; + WFIFOSET(login_fd,19); + } else { // if no login-server, we must refuse connection + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } + } + } + RFIFOSKIP(fd,17); + break; + + case 0x66: // キャラ選択 + FIFOSD_CHECK(3); + { + int char_num = RFIFOB(fd,2); + struct mmo_charstatus *cd; + RFIFOSKIP(fd,3); + + // if we activated email creation and email is default email + if (email_creation != 0 && strcmp(sd->email, "a@a.com") == 0 && login_fd > 0) { // to modify an e-mail, login-server must be online + WFIFOHEAD(fd, 3); + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + break; + } + // otherwise, load the character + for (ch = 0; ch < 9; ch++) + if (sd->found_char[ch] >= 0 && char_dat[sd->found_char[ch]].status.char_num == char_num) + break; + if (ch == 9) + { //Not found?? May be forged packet. + break; + } + cd = &char_dat[sd->found_char[ch]].status; + char_log("Character Selected, Account ID: %d, Character Slot: %d, Character Name: %s." RETCODE, + sd->account_id, char_num, cd->name); + + cd->sex = sd->sex; + + // searching map server + i = search_mapserver(cd->last_point.map,-1,-1); + // if map is not found, we check major cities + if (i < 0) { + unsigned short j; + ShowWarning("Unable to find map-server for '%s', resorting to sending to a major city.\n", mapindex_id2name(cd->last_point.map)); + if ((i = search_mapserver((j=mapindex_name2id(MAP_PRONTERA)),-1,-1)) >= 0) { + cd->last_point.map = j; + cd->last_point.x = 273; // savepoint coordinates + cd->last_point.y = 354; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_GEFFEN)),-1,-1)) >= 0) { + cd->last_point.map = j; + cd->last_point.x = 120; // savepoint coordinates + cd->last_point.y = 100; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_MORROC)),-1,-1)) >= 0) { + cd->last_point.map = j; + cd->last_point.x = 160; // savepoint coordinates + cd->last_point.y = 94; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_ALBERTA)),-1,-1)) >= 0) { + cd->last_point.map = j; + cd->last_point.x = 116; // savepoint coordinates + cd->last_point.y = 57; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_PAYON)),-1,-1)) >= 0) { + cd->last_point.map = j; + cd->last_point.x = 87; // savepoint coordinates + cd->last_point.y = 117; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_IZLUDE)),-1,-1)) >= 0) { + cd->last_point.map = j; + cd->last_point.x = 94; // savepoint coordinates + cd->last_point.y = 103; + } else { + // get first online server (with a map) + i = 0; + for(j = 0; j < MAX_MAP_SERVERS; j++) + if (server_fd[j] >= 0 && server[j].map[0]) { // change save point to one of map found on the server (the first) + i = j; + cd->last_point.map = server[j].map[0]; + ShowInfo("Map-server #%d found with a map: '%s'.\n", j, mapindex_id2name(server[j].map[0])); + // coordinates are unknown + break; + } + // if no map-server is connected, we send: server closed + if (j == MAX_MAP_SERVERS) { + WFIFOHEAD(fd, 3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + break; + } + } + } + WFIFOHEAD(fd, 28); + WFIFOW(fd,0) = 0x71; + WFIFOL(fd,2) = cd->char_id; + memcpy(WFIFOP(fd,6), mapindex_id2name(cd->last_point.map), MAP_NAME_LENGTH); + ShowInfo("Character selection '%s' (account: %d, slot: %d).\n", cd->name, sd->account_id, ch); + if (lan_ip_check(p)) + WFIFOL(fd, 22) = inet_addr(lan_map_ip); + else + WFIFOL(fd, 22) = server[i].ip; + WFIFOW(fd,26) = server[i].port; + WFIFOSET(fd,28); + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + auth_fifo[auth_fifo_pos].account_id = sd->account_id; + auth_fifo[auth_fifo_pos].char_id = cd->char_id; + auth_fifo[auth_fifo_pos].login_id1 = sd->login_id1; + auth_fifo[auth_fifo_pos].login_id2 = sd->login_id2; + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].char_pos = sd->found_char[ch]; + auth_fifo[auth_fifo_pos].sex = sd->sex; + auth_fifo[auth_fifo_pos].connect_until_time = sd->connect_until_time; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + + //Send NEW auth packet [Kevin] + if ((map_fd = server_fd[i]) < 1 || session[map_fd] == NULL) + { //0 Should not be a valid server_fd [Skotlex] + ShowError("parse_char: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", map_fd, i); + server_fd[i] = -1; + memset(&server[i], 0, sizeof(struct mmo_map_server)); + //Send server closed. + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + break; + } + WFIFOW(map_fd,0) = 0x2afd; + WFIFOW(map_fd,2) = 20 + sizeof(struct mmo_charstatus); + WFIFOL(map_fd,4) = auth_fifo[auth_fifo_pos].account_id; + WFIFOL(map_fd,8) = auth_fifo[auth_fifo_pos].login_id1; + WFIFOL(map_fd,16) = auth_fifo[auth_fifo_pos].login_id2; + WFIFOL(map_fd,12) = (unsigned long)auth_fifo[auth_fifo_pos].connect_until_time; + memcpy(WFIFOP(map_fd,20), cd, sizeof(struct mmo_charstatus)); + WFIFOSET(map_fd, WFIFOW(map_fd,2)); + + set_char_online(i, cd->char_id, cd->account_id); + //Sets char online in the party and breaks even share if needed. + inter_party_logged(cd->party_id, cd->account_id, cd->char_id); + + auth_fifo_pos++; + } + break; + + case 0x67: // 作成 + FIFOSD_CHECK(37); + + if(char_new == 0) //turn character creation on/off [Kevin] + i = -2; + else + i = make_new_char(fd, RFIFOP(fd,2)); + + if(i == -1){ //added some better faile reporting to client on the txt version [Kevin] + //already exists + WFIFOHEAD(fd, 3); + WFIFOW(fd, 0) = 0x6e; + WFIFOB(fd, 2) = 0x00; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 37); + break; + }else if(i == -2){ + //denied + WFIFOHEAD(fd, 3); + WFIFOW(fd, 0) = 0x6e; + WFIFOB(fd, 2) = 0x02; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 37); + break; + }else if(i == -3){ + //underaged XD + WFIFOHEAD(fd, 3); + WFIFOW(fd, 0) = 0x6e; + WFIFOB(fd, 2) = 0x01; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 37); + break; + } + + WFIFOHEAD(fd, 108); + WFIFOW(fd,0) = 0x6d; + memset(WFIFOP(fd,2), 0, 106); + + WFIFOL(fd,2) = char_dat[i].status.char_id; + WFIFOL(fd,2+4) = char_dat[i].status.base_exp; + WFIFOL(fd,2+8) = char_dat[i].status.zeny; + WFIFOL(fd,2+12) = char_dat[i].status.job_exp; + WFIFOL(fd,2+16) = char_dat[i].status.job_level; + + WFIFOL(fd,2+28) = char_dat[i].status.karma; + WFIFOL(fd,2+32) = char_dat[i].status.manner; + + WFIFOW(fd,2+40) = 0x30; + WFIFOW(fd,2+42) = (char_dat[i].status.hp > 0x7fff) ? 0x7fff : char_dat[i].status.hp; + WFIFOW(fd,2+44) = (char_dat[i].status.max_hp > 0x7fff) ? 0x7fff : char_dat[i].status.max_hp; + WFIFOW(fd,2+46) = (char_dat[i].status.sp > 0x7fff) ? 0x7fff : char_dat[i].status.sp; + WFIFOW(fd,2+48) = (char_dat[i].status.max_sp > 0x7fff) ? 0x7fff : char_dat[i].status.max_sp; + WFIFOW(fd,2+50) = DEFAULT_WALK_SPEED; // char_dat[i].status.speed; + WFIFOW(fd,2+52) = char_dat[i].status.class_; + WFIFOW(fd,2+54) = char_dat[i].status.hair; + + WFIFOW(fd,2+58) = char_dat[i].status.base_level; + WFIFOW(fd,2+60) = char_dat[i].status.skill_point; + + WFIFOW(fd,2+64) = char_dat[i].status.shield; + WFIFOW(fd,2+66) = char_dat[i].status.head_top; + WFIFOW(fd,2+68) = char_dat[i].status.head_mid; + WFIFOW(fd,2+70) = char_dat[i].status.hair_color; + + memcpy(WFIFOP(fd,2+74), char_dat[i].status.name, NAME_LENGTH); + + WFIFOB(fd,2+98) = (char_dat[i].status.str > 255) ? 255 : char_dat[i].status.str; + WFIFOB(fd,2+99) = (char_dat[i].status.agi > 255) ? 255 : char_dat[i].status.agi; + WFIFOB(fd,2+100) = (char_dat[i].status.vit > 255) ? 255 : char_dat[i].status.vit; + WFIFOB(fd,2+101) = (char_dat[i].status.int_ > 255) ? 255 : char_dat[i].status.int_; + WFIFOB(fd,2+102) = (char_dat[i].status.dex > 255) ? 255 : char_dat[i].status.dex; + WFIFOB(fd,2+103) = (char_dat[i].status.luk > 255) ? 255 : char_dat[i].status.luk; + WFIFOB(fd,2+104) = char_dat[i].status.char_num; + + WFIFOSET(fd,108); + RFIFOSKIP(fd,37); + for(ch = 0; ch < 9; ch++) { + if (sd->found_char[ch] == -1) { + sd->found_char[ch] = i; + break; + } + } + + case 0x68: // delete char //Yor's Fix + FIFOSD_CHECK(46); + + memcpy(email, RFIFOP(fd,6), 40); + if (e_mail_check(email) == 0) + strncpy(email, "a@a.com", 40); // default e-mail + + // if we activated email creation and email is default email + if (email_creation != 0 && strcmp(sd->email, "a@a.com") == 0 && login_fd > 0) { // to modify an e-mail, login-server must be online + // if sended email is incorrect e-mail + if (strcmp(email, "a@a.com") == 0) { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + RFIFOSKIP(fd,46); + // we act like we have selected a character + } else { + // we change the packet to set it like selection. + for (i = 0; i < 9; i++) + if (char_dat[sd->found_char[i]].status.char_id == RFIFOL(fd,2)) { + // we save new e-mail + memcpy(sd->email, email, 40); + // we send new e-mail to login-server ('online' login-server is checked before) + WFIFOW(login_fd,0) = 0x2715; + WFIFOL(login_fd,2) = sd->account_id; + memcpy(WFIFOP(login_fd, 6), email, 40); + WFIFOSET(login_fd,46); + // skip part of the packet! (46, but leave the size of select packet: 3) + RFIFOSKIP(fd,43); + // change value to put new packet (char selection) + RFIFOW(fd, 0) = 0x66; + RFIFOB(fd, 2) = char_dat[sd->found_char[i]].status.char_num; + // not send packet, it's modify of actual packet + break; + } + if (i == 9) { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + RFIFOSKIP(fd,46); + } + } + + // otherwise, we delete the character + } else { + if (strcmpi(email, sd->email) != 0) { // if it's an invalid email + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + // if mail is correct + } else { + for (i = 0; i < 9; i++) { + struct mmo_charstatus *cs = NULL; + if ((cs = &char_dat[sd->found_char[i]].status)->char_id == RFIFOL(fd,2)) { + char_delete(cs); // deletion process + + if (sd->found_char[i] != char_num - 1) { + memcpy(&char_dat[sd->found_char[i]], &char_dat[char_num-1], sizeof(struct mmo_charstatus)); + // Correct moved character reference in the character's owner + { + int j, k; + struct char_session_data *sd2; + for (j = 0; j < fd_max; j++) { + if (session[j] && (sd2 = (struct char_session_data*)session[j]->session_data) && + sd2->account_id == char_dat[char_num-1].status.account_id) { + for (k = 0; k < 9; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = sd->found_char[i]; + break; + } + } + break; + } + } + } + } + + char_num--; + for(ch = i; ch < 9-1; ch++) + sd->found_char[ch] = sd->found_char[ch+1]; + sd->found_char[8] = -1; + WFIFOW(fd,0) = 0x6f; + WFIFOSET(fd,2); + break; + } + } + + if (i == 9) { + WFIFOW(fd,0) = 0x70; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + } + } + RFIFOSKIP(fd,46); + } + break; + + case 0x2af8: // マップサーバーログイン + if (RFIFOREST(fd) < 60) + return 0; + WFIFOW(fd,0) = 0x2af9; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] < 0) + break; + } + if (i == MAX_MAP_SERVERS || strcmp((char*)RFIFOP(fd,2), userid) || strcmp((char*)RFIFOP(fd,26), passwd)){ + WFIFOB(fd,2) = 3; + WFIFOSET(fd,3); + RFIFOSKIP(fd,60); + } else { + int len; + WFIFOB(fd,2) = 0; + session[fd]->func_parse = parse_frommap; + server_fd[i] = fd; + server[i].ip = RFIFOL(fd,54); + server[i].port = RFIFOW(fd,58); + server[i].users = 0; + memset(server[i].map, 0, sizeof(server[i].map)); + WFIFOSET(fd,3); + RFIFOSKIP(fd,60); + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + char_mapif_init(fd); + // send gm acccounts level to map-servers + len = 4; + WFIFOW(fd,0) = 0x2b15; + for(i = 0; i < GM_num; i++) { + WFIFOL(fd,len) = gm_account[i].account_id; + WFIFOB(fd,len+4) = (unsigned char)gm_account[i].level; + len += 5; + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + return 0; + } + break; + + case 0x187: // Alive信号? + if (RFIFOREST(fd) < 6) + return 0; + RFIFOSKIP(fd, 6); + break; + + case 0x7530: // Athena情報所得 + WFIFOW(fd,0) = 0x7531; + WFIFOB(fd,2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd,3) = ATHENA_MINOR_VERSION; + WFIFOB(fd,4) = ATHENA_REVISION; + WFIFOB(fd,5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd,6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7) = ATHENA_SERVER_INTER | ATHENA_SERVER_CHAR; + WFIFOW(fd,8) = ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + return 0; + + case 0x7532: // 接続の切断(defaultと処理は一緒だが明示的にするため) + default: + session[fd]->eof = 1; + return 0; + } + } + RFIFOFLUSH(fd); + return 0; +} + +// Console Command Parser [Wizputer] +int parse_console(char *buf) { + char *type,*command; + + type = (char *)aCalloc(64,1); + command = (char *)aCalloc(64,1); + +// memset(type,0,64); +// memset(command,0,64); + + ShowStatus("Console: %s\n",buf); + + if ( sscanf(buf, "%[^:]:%[^\n]", type , command ) < 2 ) + sscanf(buf,"%[^\n]",type); + + ShowDebug("Type of command: %s || Command: %s \n",type,command); + + if(buf) aFree(buf); + if(type) aFree(type); + if(command) aFree(command); + + return 0; +} + +// 全てのMAPサーバーにデータ送信(送信したmap鯖の数を返す) +int mapif_sendall(unsigned char *buf, unsigned int len) { + int i, c; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0) { + if (session[fd] == NULL) + { //Could this be the crash's source? [Skotlex] + ShowError("mapif_sendall: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", fd, i); + server_fd[i] = -1; + memset(&server[i], 0, sizeof(struct mmo_map_server)); + continue; + } + WFIFOHEAD(fd, len); + if (WFIFOSPACE(fd) < len) //Increase buffer size. + realloc_writefifo(fd, len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + return c; +} + +// 自分以外の全てのMAPサーバーにデータ送信(送信したmap鯖の数を返す) +int mapif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0 && fd != sfd) { + WFIFOHEAD(fd, len); + if (WFIFOSPACE(fd) < len) //Increase buffer size. + realloc_writefifo(fd, len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; + } + } + return c; +} +// MAPサーバーにデータ送信(map鯖生存確認有り) +int mapif_send(int fd, unsigned char *buf, unsigned int len) { + int i; + + if (fd >= 0) { + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (fd == server_fd[i]) { + WFIFOHEAD(fd, len); + if (WFIFOSPACE(fd) < len) //Increase buffer size. + realloc_writefifo(fd, len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + return 1; + } + } + } + return 0; +} + +int send_users_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(); + unsigned char buf[16]; + + if (login_fd > 0 && session[login_fd]) { + // send number of user to login server + WFIFOHEAD(login_fd, 6); + WFIFOW(login_fd,0) = 0x2714; + WFIFOL(login_fd,2) = users; + WFIFOSET(login_fd,6); + } + // send number of players to all map-servers + WBUFW(buf,0) = 0x2b00; + WBUFL(buf,2) = users; + mapif_sendall(buf, 6); + + return 0; +} + +static int send_accounts_tologin_sub(DBKey key, void* data, va_list ap) { + struct online_char_data* character = (struct online_char_data*)data; + int *i = va_arg(ap, int*); + int count = va_arg(ap, int); + if ((*i) >= count) + return 0; //This is an error that shouldn't happen.... + if(character->server > -1) { + WFIFOHEAD(login_fd, 8+count*4); + WFIFOL(login_fd, 8+(*i)*4) =character->account_id; + (*i)++; + return 1; + } + return 0; +} + +int send_accounts_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(), i=0; + + if (login_fd > 0 && session[login_fd]) { + // send account list to login server + WFIFOHEAD(login_fd, 8+users*4); + WFIFOW(login_fd,0) = 0x272d; + WFIFOL(login_fd,4) = users; + online_char_db->foreach(online_char_db, send_accounts_tologin_sub, &i); + WFIFOW(login_fd,2) = 8+ i*4; + if (i > 0) + WFIFOSET(login_fd,WFIFOW(login_fd,2)); + } + return 0; +} + +int check_connect_login_server(int tid, unsigned int tick, int id, int data) { + if (login_fd <= 0 || session[login_fd] == NULL) { + ShowInfo("Attempt to connect to login-server...\n"); + login_fd = make_connection(login_ip, login_port); + if (login_fd == -1) + { //Try again later... [Skotlex] + login_fd = 0; + return 0; + } + session[login_fd]->func_parse = parse_tologin; + realloc_fifo(login_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + WFIFOHEAD(login_fd, 86); + WFIFOW(login_fd,0) = 0x2710; + memset(WFIFOP(login_fd,2), 0, 24); + memcpy(WFIFOP(login_fd,2), userid, strlen(userid) < 24 ? strlen(userid) : 24); + memset(WFIFOP(login_fd,26), 0, 24); + memcpy(WFIFOP(login_fd,26), passwd, strlen(passwd) < 24 ? strlen(passwd) : 24); + WFIFOL(login_fd,50) = 0; + WFIFOL(login_fd,54) = char_ip; + WFIFOL(login_fd,58) = char_port; + memset(WFIFOP(login_fd,60), 0, 20); + memcpy(WFIFOP(login_fd,60), server_name, strlen(server_name) < 20 ? strlen(server_name) : 20); + WFIFOW(login_fd,80) = 0; + WFIFOW(login_fd,82) = char_maintenance; + + WFIFOW(login_fd,84) = char_new_display; //only display (New) if they want to [Kevin] + + WFIFOSET(login_fd,86); + } + return 0; +} + +//------------------------------------------------ +//Invoked 15 seconds after mapif_disconnectplayer in case the map server doesn't +//replies/disconnect the player we tried to kick. [Skotlex] +//------------------------------------------------ +static int chardb_waiting_disconnect(int tid, unsigned int tick, int id, int data) +{ + struct online_char_data* character; + if ((character = idb_get(online_char_db, id)) != NULL && character->waiting_disconnect) + { //Mark it offline due to timeout. + set_char_offline(character->char_id, character->account_id); + } + return 0; +} + +//---------------------------------------------------------- +// Return numerical value of a switch configuration by [Yor] +// on/off, english, fran軋is, deutsch, espaol +//---------------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +//------------------------------------------- +// Reading Lan Support configuration by [Yor] +//------------------------------------------- +int lan_config_read(const char *lancfgName) { + int j; + struct hostent * h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + // set default configuration + strncpy(lan_map_ip, "127.0.0.1", sizeof(lan_map_ip)); + subneti[0] = 127; + subneti[1] = 0; + subneti[2] = 0; + subneti[3] = 1; + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + + fp = fopen(lancfgName, "r"); + + if (fp == NULL) { + ShowError("LAN support configuration file not found: %s\n", lancfgName); + return 1; + } + + ShowInfo("reading configuration file %s...\n", lancfgName); + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars((unsigned char *)w1); + remove_control_chars((unsigned char *)w2); + if (strcmpi(w1, "lan_map_ip") == 0) { // Read map-server Lan IP Address + h = gethostbyname(w2); + if (h != NULL) { + sprintf(lan_map_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + strncpy(lan_map_ip, w2, sizeof(lan_map_ip)); + lan_map_ip[sizeof(lan_map_ip)-1] = 0; + } + ShowStatus("LAN IP of map-server: %s.\n", lan_map_ip); + } else if (strcmpi(w1, "subnet") == 0) { // Read Subnetwork + for(j = 0; j < 4; j++) + subneti[j] = 0; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subneti[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subneti[0], &subneti[1], &subneti[2], &subneti[3]); + } + ShowStatus("Sub-network of the map-server: %d.%d.%d.%d.\n", subneti[0], subneti[1], subneti[2], subneti[3]); + } else if (strcmpi(w1, "subnetmask") == 0){ // Read Subnetwork Mask + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subnetmaski[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subnetmaski[0], &subnetmaski[1], &subnetmaski[2], &subnetmaski[3]); + } + ShowStatus("Sub-network mask of the map-server: %d.%d.%d.%d.\n", subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + } + } + fclose(fp); + + // sub-network check of the map-server + { + unsigned int a0, a1, a2, a3; + unsigned char p[4]; + sscanf(lan_map_ip, "%d.%d.%d.%d", &a0, &a1, &a2, &a3); + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + ShowInfo("LAN test of LAN IP of the map-server...\n"); + if (lan_ip_check(p) == 0) { + ShowError(CL_RED" LAN IP of the map-server doesn't belong to the specified Sub-network."CL_RESET"\n"); + } + } + + ShowInfo("done reading %s.\n", lancfgName); + + return 0; +} + +int char_config_read(const char *cfgName) { + struct hostent *h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp = fopen(cfgName, "r"); + + if (fp == NULL) { + ShowFatalError("Configuration file not found: %s.\n", cfgName); + exit(1); + } + + ShowInfo("Reading configuration file %s...\n", cfgName); + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars((unsigned char *)w1); + remove_control_chars((unsigned char *)w2); + if(strcmpi(w1,"timestamp_format") == 0) { + strncpy(timestamp_format, w2, 20); + } else if(strcmpi(w1,"console_silent")==0){ + msg_silent = 0; //To always allow the next line to show up. + ShowInfo("Console Silent Setting: %d\n", atoi(w2)); + msg_silent = atoi(w2); + } else if (strcmpi(w1, "userid") == 0) { + memcpy(userid, w2, 24); + } else if (strcmpi(w1, "passwd") == 0) { + memcpy(passwd, w2, 24); + } else if (strcmpi(w1, "server_name") == 0) { + memcpy(server_name, w2, sizeof(server_name)); + server_name[sizeof(server_name) - 1] = '\0'; + ShowStatus("%s server has been initialized\n", w2); + } else if (strcmpi(w1, "wisp_server_name") == 0) { + if (strlen(w2) >= 4) { + memcpy(wisp_server_name, w2, sizeof(wisp_server_name)); + wisp_server_name[sizeof(wisp_server_name) - 1] = '\0'; + } + } else if (strcmpi(w1, "login_ip") == 0) { + login_ip_set_ = 1; + h = gethostbyname(w2); + if (h != NULL) { + ShowStatus("Login server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(login_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(login_ip_str, w2, 16); + } else if (strcmpi(w1, "login_port") == 0) { + login_port = atoi(w2); + } else if (strcmpi(w1, "char_ip") == 0) { + char_ip_set_ = 1; + h = gethostbyname(w2); + if (h != NULL) { + ShowStatus("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(char_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(char_ip_str, w2, 16); + } else if (strcmpi(w1, "bind_ip") == 0) { + bind_ip_set_ = 1; + h = gethostbyname(w2); + if (h != NULL) { + ShowStatus("Character server binding IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(bind_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(bind_ip_str, w2, 16); + } else if (strcmpi(w1, "char_port") == 0) { + char_port = atoi(w2); + } else if (strcmpi(w1, "char_maintenance") == 0) { + char_maintenance = atoi(w2); + } else if (strcmpi(w1, "char_new") == 0) { + char_new = atoi(w2); + } else if (strcmpi(w1, "char_new_display") == 0) { + char_new_display = atoi(w2); + } else if (strcmpi(w1, "email_creation") == 0) { + email_creation = config_switch(w2); + } else if (strcmpi(w1, "char_txt") == 0) { + strcpy(char_txt, w2); + } else if (strcmpi(w1, "scdata_txt") == 0) { //By Skotlex + strcpy(scdata_txt, w2); + } else if (strcmpi(w1, "backup_txt") == 0) { //By zanetheinsane + strcpy(backup_txt, w2); + } else if (strcmpi(w1, "friends_txt") == 0) { //By davidsiaw + strcpy(friends_txt, w2); + } else if (strcmpi(w1, "backup_txt_flag") == 0) { // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. By [Yor] + backup_txt_flag = config_switch(w2); + } else if (strcmpi(w1, "max_connect_user") == 0) { + max_connect_user = atoi(w2); + if (max_connect_user < 0) + max_connect_user = 0; // unlimited online players + } else if(strcmpi(w1, "gm_allow_level") == 0) { + gm_allow_level = atoi(w2); + if(gm_allow_level < 0) + gm_allow_level = 99; + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "online_check") == 0) { + online_check = config_switch(w2); + } else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2)*1000; + if (autosave_interval <= 0) + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; + } else if (strcmpi(w1, "save_log") == 0) { + save_log = config_switch(w2); + } else if (strcmpi(w1, "start_point") == 0) { + char map[32]; + int x, y; + if (sscanf(w2, "%[^,],%d,%d", map, &x, &y) < 3) + continue; + if (strstr(map, ".gat") != NULL) { // Verify at least if '.gat' is in the map name + start_point.map = mapindex_name2id(map); + if (!start_point.map) { + ShowError("Specified start_point %s not found in map-index cache.\n", map); + start_point.map = 0; + } + start_point.x = x; + start_point.y = y; + } + } else if(strcmpi(w1,"log_char")==0) { //log char or not [devil] + log_char = atoi(w2); + } else if (strcmpi(w1, "start_zeny") == 0) { + start_zeny = atoi(w2); + if (start_zeny < 0) + start_zeny = 0; + } else if (strcmpi(w1, "start_weapon") == 0) { + start_weapon = atoi(w2); + if (start_weapon < 0) + start_weapon = 0; + } else if (strcmpi(w1, "start_armor") == 0) { + start_armor = atoi(w2); + if (start_armor < 0) + start_armor = 0; + } else if (strcmpi(w1, "unknown_char_name") == 0) { + strcpy(unknown_char_name, w2); + unknown_char_name[NAME_LENGTH-1] = '\0'; + } else if (strcmpi(w1, "char_log_filename") == 0) { + strcpy(char_log_filename, w2); + } else if (strcmpi(w1, "name_ignoring_case") == 0) { + name_ignoring_case = config_switch(w2); + } else if (strcmpi(w1, "char_name_option") == 0) { + char_name_option = atoi(w2); + } else if (strcmpi(w1, "char_name_letters") == 0) { + strcpy(char_name_letters, w2); +// online files options + } else if (strcmpi(w1, "online_txt_filename") == 0) { + strcpy(online_txt_filename, w2); + } else if (strcmpi(w1, "online_html_filename") == 0) { + strcpy(online_html_filename, w2); + } else if (strcmpi(w1, "online_sorting_option") == 0) { + online_sorting_option = atoi(w2); + } else if (strcmpi(w1, "online_display_option") == 0) { + online_display_option = atoi(w2); + } else if (strcmpi(w1, "online_gm_display_min_level") == 0) { // minimum GM level to display 'GM' when we want to display it + online_gm_display_min_level = atoi(w2); + if (online_gm_display_min_level < 5) // send online file every 5 seconds to player is enough + online_gm_display_min_level = 5; + } else if (strcmpi(w1, "online_refresh_html") == 0) { + online_refresh_html = atoi(w2); + if (online_refresh_html < 1) + online_refresh_html = 1; + } else if(strcmpi(w1,"db_path")==0) { + strcpy(db_path,w2); + } else if (strcmpi(w1, "console") == 0) { + if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 ) + console = 1; + } else if (strcmpi(w1, "import") == 0) { + char_config_read(w2); + } + } + fclose(fp); + + ShowInfo("done reading %s.\n", cfgName); + return 0; +} + +int chardb_final(int key, void* data, va_list va) +{ + aFree(data); + return 0; +} +void do_final(void) { + ShowStatus("Terminating server.\n"); + // write online players files with no player + online_char_db->clear(online_char_db, NULL); //clean the db... + create_online_files(); + online_char_db->destroy(online_char_db, NULL); //dispose the db... + + mmo_char_sync(); + inter_save(); + set_all_offline(); + + if(gm_account) aFree(gm_account); + if(char_dat) aFree(char_dat); + + delete_session(login_fd); + delete_session(char_fd); + +#ifdef ENABLE_SC_SAVING + status_final(); +#endif + inter_final(); + mapindex_final(); + + char_log("----End of char-server (normal end with closing of all files)." RETCODE); +} + +void set_server_type(void) +{ + SERVER_TYPE = ATHENA_SERVER_CHAR; +} + +static int online_data_cleanup_sub(DBKey key, void *data, va_list ap) +{ + struct online_char_data *character= (struct online_char_data*)data; + if (character->server == -2) //Unknown server.. set them offline + set_char_offline(character->char_id, character->account_id); + if (character->server < 0) + //Free data from players that have not been online for a while. + db_remove(online_char_db, key); + return 0; +} + +static int online_data_cleanup(int tid, unsigned int tick, int id, int data) +{ + online_char_db->foreach(online_char_db, online_data_cleanup_sub); + return 0; +} + +int do_init(int argc, char **argv) { + int i; + + mapindex_init(); //Needed here for the start-point reading. + start_point.map = mapindex_name2id("new_1-1.gat"); + char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]); + lan_config_read((argc > 1) ? argv[1] : LOGIN_LAN_CONF_NAME); + + // a newline in the log... + char_log(""); + // moved behind char_config_read in case we changed the filename [celest] + char_log("The char-server starting..." RETCODE); + + if ((naddr_ != 0) && (login_ip_set_ == 0 || char_ip_set_ == 0)) { + // The char server should know what IP address it is running on + // - MouseJstr + int localaddr = ntohl(addr_[0]); + unsigned char *ptr = (unsigned char *) &localaddr; + char buf[16]; + sprintf(buf, "%d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]);; + if (naddr_ != 1) + ShowStatus("Multiple interfaces detected.. using %s as our IP address\n", buf); + else + ShowStatus("Defaulting to %s as our IP address\n", buf); + if (login_ip_set_ == 0) + strcpy(login_ip_str, buf); + if (char_ip_set_ == 0) + strcpy(char_ip_str, buf); + + if (ptr[0] == 192 && ptr[1] == 168) + ShowWarning("Firewall detected.. edit lan_support.conf and char_athena.conf\n"); + } + + login_ip = inet_addr(login_ip_str); + char_ip = inet_addr(char_ip_str); + + for(i = 0; i < MAX_MAP_SERVERS; i++) { + memset(&server[i], 0, sizeof(struct mmo_map_server)); + server_fd[i] = -1; + } + + online_char_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + mmo_char_init(); +#ifdef ENABLE_SC_SAVING + status_init(); +#endif + update_online = time(NULL); + create_online_files(); // update online players files at start of the server + + inter_init((argc > 2) ? argv[2] : inter_cfgName); // inter server 初期化 + + set_defaultparse(parse_char); + + if (bind_ip_set_) + char_fd = make_listen_bind(inet_addr(bind_ip_str),char_port); + else + char_fd = make_listen_bind(INADDR_ANY,char_port); + + add_timer_func_list(check_connect_login_server, "check_connect_login_server"); + add_timer_func_list(send_users_tologin, "send_users_tologin"); + add_timer_func_list(send_accounts_tologin, "send_accounts_tologin"); + add_timer_func_list(mmo_char_sync_timer, "mmo_char_sync_timer"); + add_timer_func_list(chardb_waiting_disconnect, "chardb_waiting_disconnect"); + add_timer_func_list(online_data_cleanup, "online_data_cleanup"); + + add_timer_interval(gettick() + 600*1000, online_data_cleanup, 0, 0, 600 * 1000); + add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000); + add_timer_interval(gettick() + 1000, send_users_tologin, 0, 0, 5 * 1000); + add_timer_interval(gettick() + 3600*1000, send_accounts_tologin, 0, 0, 3600*1000); //Sync online accounts every hour + add_timer_interval(gettick() + autosave_interval, mmo_char_sync_timer, 0, 0, autosave_interval); + + if(console) { + set_defaultconsoleparse(parse_console); + start_console(); + } + + char_log("The char-server is ready (Server is listening on the port %d)." RETCODE, char_port); + + ShowStatus("The char-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %d).\n\n", char_port); + + return 0; +} diff --git a/src/char/char.h b/src/char/char.h new file mode 100644 index 000000000..743890c68 --- /dev/null +++ b/src/char/char.h @@ -0,0 +1,45 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CHAR_H_ +#define _CHAR_H_ + +#include "../common/mmo.h" +#include "../common/mapindex.h" + +#define START_CHAR_NUM 150000 +#define MAX_MAP_SERVERS 30 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +#define LOGIN_LAN_CONF_NAME "conf/lan_support.conf" + +#define DEFAULT_AUTOSAVE_INTERVAL 300*1000 + +struct mmo_map_server{ + long ip; + short port; + int users; + unsigned short map[MAX_MAP_PER_SERVER]; +}; + +int search_character_index(char* character_name); +char * search_character_name(int index); + +int mapif_sendall(unsigned char *buf, unsigned int len); +int mapif_sendallwos(int fd,unsigned char *buf, unsigned int len); +int mapif_send(int fd,unsigned char *buf, unsigned int len); + +int char_married(int pl1,int pl2); +int char_child(int parent_id, int child_id); + +int char_log(char *fmt, ...); + +int request_accreg2(int account_id, int char_id); +int char_parse_Registry(int account_id, int char_id, unsigned char *buf, int len); +int save_accreg2(unsigned char *buf, int len); +int char_account_reg_reply(int fd,int account_id,int char_id); +extern int autosave_interval; +extern char db_path[]; + +#endif diff --git a/src/char/int_guild.c b/src/char/int_guild.c new file mode 100644 index 000000000..a1f4d418e --- /dev/null +++ b/src/char/int_guild.c @@ -0,0 +1,1530 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include + +#include "../common/mmo.h" +#include "../common/socket.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/showmsg.h" +#include "char.h" +#include "inter.h" +#include "int_storage.h" +#include "int_guild.h" + +char guild_txt[1024] = "save/guild.txt"; +char castle_txt[1024] = "save/castle.txt"; + +static struct dbt *guild_db; +static struct dbt *castle_db; + +static int guild_newid = 10000; + +static int guild_exp[100]; + +int mapif_parse_GuildLeave(int fd, int guild_id, int account_id, int char_id, int flag, const char *mes); +int mapif_guild_broken(int guild_id, int flag); +int guild_check_empty(struct guild *g); +int guild_calcinfo(struct guild *g); +int mapif_guild_basicinfochanged(int guild_id, int type, const void *data, int len); +int mapif_guild_info(int fd, struct guild *g); +int guild_break_sub(DBKey key, void *data, va_list ap); + +// ギルドデータの文字列への変換 +int inter_guild_tostr(char *str, struct guild *g) { + int i, c, len; + + // 基本データ + len = sprintf(str, "%d\t%s\t%s\t%d,%d,%d,%d,%d\t%s#\t%s#\t", + g->guild_id, g->name, g->master, + g->guild_lv, g->max_member, g->exp, g->skill_point, g->castle_id, + g->mes1, g->mes2); + // メンバー + for(i = 0; i < g->max_member; i++) { + struct guild_member *m = &g->member[i]; + len += sprintf(str + len, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\t%s\t", + m->account_id, m->char_id, + m->hair, m->hair_color, m->gender, + m->class_, m->lv, m->exp, m->exp_payper, m->position, + ((m->account_id > 0) ? m->name : "-")); + } + // 役職 + for(i = 0; i < MAX_GUILDPOSITION; i++) { + struct guild_position *p = &g->position[i]; + len += sprintf(str + len, "%d,%d\t%s#\t", p->mode, p->exp_mode, p->name); + } + // エンブレム + len += sprintf(str + len, "%d,%d,", g->emblem_len, g->emblem_id); + for(i = 0; i < g->emblem_len; i++) { + len += sprintf(str + len, "%02x", (unsigned char)(g->emblem_data[i])); + } + len += sprintf(str + len, "$\t"); + // 同盟リスト + c = 0; + for(i = 0; i < MAX_GUILDALLIANCE; i++) + if (g->alliance[i].guild_id > 0) + c++; + len += sprintf(str + len, "%d\t", c); + for(i = 0; i < MAX_GUILDALLIANCE; i++) { + struct guild_alliance *a = &g->alliance[i]; + if (a->guild_id > 0) + len += sprintf(str + len, "%d,%d\t%s\t", a->guild_id, a->opposition, a->name); + } + // 追放リスト + c = 0; + for(i = 0; i < MAX_GUILDEXPLUSION; i++) + if (g->explusion[i].account_id > 0) + c++; + len += sprintf(str + len, "%d\t", c); + for(i = 0; i < MAX_GUILDEXPLUSION; i++) { + struct guild_explusion *e = &g->explusion[i]; + if (e->account_id > 0) + len += sprintf(str + len, "%d,%d,%d,%d\t%s\t%s\t%s#\t", + e->account_id, e->rsv1, e->rsv2, e->rsv3, + e->name, e->acc, e->mes ); + } + // ギルドスキル + for(i = 0; i < MAX_GUILDSKILL; i++) { + len += sprintf(str + len, "%d,%d ", g->skill[i].id, g->skill[i].lv); + } + len += sprintf(str + len, "\t"); + + return 0; +} + +// ギルドデータの文字列からの変換 +int inter_guild_fromstr(char *str, struct guild *g) { + int i, j, c; + int tmp_int[16]; + char tmp_str[4][256]; + char tmp_str2[4096]; + char *pstr; + + // 基本データ + memset(g, 0, sizeof(struct guild)); + if (sscanf(str, "%d\t%[^\t]\t%[^\t]\t%d,%d,%d,%d,%d\t%[^\t]\t%[^\t]\t", &tmp_int[0], + tmp_str[0], tmp_str[1], + &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], &tmp_int[5], + tmp_str[2], tmp_str[3]) < 8) + return 1; + + g->guild_id = tmp_int[0]; + g->guild_lv = tmp_int[1]; + g->max_member = tmp_int[2]; + g->exp = tmp_int[3]; + g->skill_point = tmp_int[4]; + g->castle_id = tmp_int[5]; + memcpy(g->name, tmp_str[0], NAME_LENGTH-1); + memcpy(g->master, tmp_str[1], NAME_LENGTH-1); + memcpy(g->mes1, tmp_str[2], 60); + memcpy(g->mes2, tmp_str[3], 120); + g->mes1[strlen(g->mes1)-1] = 0; + g->mes2[strlen(g->mes2)-1] = 0; + + for(j = 0; j < 6 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); +// printf("GuildBaseInfo OK\n"); + + // メンバー + for(i = 0; i < g->max_member; i++) { + struct guild_member *m = &g->member[i]; + if (sscanf(str + 1, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\t%[^\t]\t", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], + &tmp_int[5], &tmp_int[6], &tmp_int[7], &tmp_int[8], &tmp_int[9], + tmp_str[0]) < 11) + return 1; + m->account_id = tmp_int[0]; + m->char_id = tmp_int[1]; + m->hair = tmp_int[2]; + m->hair_color = tmp_int[3]; + m->gender = tmp_int[4]; + m->class_ = tmp_int[5]; + m->lv = tmp_int[6]; + m->exp = tmp_int[7]; + m->exp_payper = tmp_int[8]; + m->position = tmp_int[9]; + memcpy(m->name, tmp_str[0], NAME_LENGTH-1); + + for(j = 0; j < 2 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); + } +// printf("GuildMemberInfo OK\n"); + // 役職 + i = 0; + while (sscanf(str+1, "%d,%d%n", &tmp_int[0], &tmp_int[1], &j) == 2 && str[1+j] == '\t') { + struct guild_position *p = &g->position[i]; + if (sscanf(str+1, "%d,%d\t%[^\t]\t", &tmp_int[0], &tmp_int[1], tmp_str[0]) < 3) + return 1; + p->mode = tmp_int[0]; + p->exp_mode = tmp_int[1]; + tmp_str[0][strlen(tmp_str[0])-1] = 0; + memcpy(p->name, tmp_str[0], NAME_LENGTH-1); + + for(j = 0; j < 2 && str != NULL; j++) // 位置スキップ + str = strchr(str+1, '\t'); + i++; + } +// printf("GuildPositionInfo OK\n"); + // エンブレム + tmp_int[1] = 0; + if (sscanf(str + 1, "%d,%d,%[^\t]\t", &tmp_int[0], &tmp_int[1], tmp_str2)< 3 && + sscanf(str + 1, "%d,%[^\t]\t", &tmp_int[0], tmp_str2) < 2) + return 1; + g->emblem_len = tmp_int[0]; + g->emblem_id = tmp_int[1]; + for(i = 0, pstr = tmp_str2; i < g->emblem_len; i++, pstr += 2) { + int c1 = pstr[0], c2 = pstr[1], x1 = 0, x2 = 0; + if (c1 >= '0' && c1 <= '9') x1 = c1 - '0'; + if (c1 >= 'a' && c1 <= 'f') x1 = c1 - 'a' + 10; + if (c1 >= 'A' && c1 <= 'F') x1 = c1 - 'A' + 10; + if (c2 >= '0' && c2 <= '9') x2 = c2 - '0'; + if (c2 >= 'a' && c2 <= 'f') x2 = c2 - 'a' + 10; + if (c2 >= 'A' && c2 <= 'F') x2 = c2 - 'A' + 10; + g->emblem_data[i] = (x1<<4) | x2; + } +// printf("GuildEmblemInfo OK\n"); + str=strchr(str + 1, '\t'); // 位置スキップ + + // 同盟リスト + if (sscanf(str + 1, "%d\t", &c) < 1) + return 1; + str = strchr(str + 1, '\t'); // 位置スキップ + for(i = 0; i < c; i++) { + struct guild_alliance *a = &g->alliance[i]; + if (sscanf(str + 1, "%d,%d\t%[^\t]\t", &tmp_int[0], &tmp_int[1], tmp_str[0]) < 3) + return 1; + a->guild_id = tmp_int[0]; + a->opposition = tmp_int[1]; + memcpy(a->name, tmp_str[0], NAME_LENGTH-1); + + for(j = 0; j < 2 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); + } +// printf("GuildAllianceInfo OK\n"); + // 追放リスト + if (sscanf(str+1, "%d\t", &c) < 1) + return 1; + str = strchr(str + 1, '\t'); // 位置スキップ + for(i = 0; i < c; i++) { + struct guild_explusion *e = &g->explusion[i]; + if (sscanf(str + 1, "%d,%d,%d,%d\t%[^\t]\t%[^\t]\t%[^\t]\t", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + tmp_str[0], tmp_str[1], tmp_str[2]) < 6) + return 1; + e->account_id = tmp_int[0]; + e->rsv1 = tmp_int[1]; + e->rsv2 = tmp_int[2]; + e->rsv3 = tmp_int[3]; + memcpy(e->name, tmp_str[0], NAME_LENGTH-1); + memcpy(e->acc, tmp_str[1], 24); + tmp_str[2][strlen(tmp_str[2])-1] = 0; + memcpy(e->mes, tmp_str[2], 40); + + for(j = 0; j < 4 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); + } +// printf("GuildExplusionInfo OK\n"); + // ギルドスキル + for(i = 0; i < MAX_GUILDSKILL; i++) { + if (sscanf(str+1,"%d,%d ", &tmp_int[0], &tmp_int[1]) < 2) + break; + g->skill[i].id = tmp_int[0]; + g->skill[i].lv = tmp_int[1]; + str = strchr(str + 1, ' '); + } + str = strchr(str + 1, '\t'); +// printf("GuildSkillInfo OK\n"); + + return 0; +} + +// ギルド城データの文字列への変換 +int inter_guildcastle_tostr(char *str, struct guild_castle *gc) { + int len; + + len = sprintf(str, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", // added Guardian HP [Valaris] + gc->castle_id, gc->guild_id, gc->economy, gc->defense, gc->triggerE, + gc->triggerD, gc->nextTime, gc->payTime, gc->createTime, gc->visibleC, + gc->guardian[0].visible, gc->guardian[1].visible, gc->guardian[2].visible, gc->guardian[3].visible, + gc->guardian[4].visible, gc->guardian[5].visible, gc->guardian[6].visible, gc->guardian[7].visible, + gc->guardian[0].hp, gc->guardian[1].hp, gc->guardian[2].hp, gc->guardian[3].hp, + gc->guardian[4].hp, gc->guardian[5].hp, gc->guardian[6].hp, gc->guardian[7].hp); + + return 0; +} + +// ギルド城データの文字列からの変換 +int inter_guildcastle_fromstr(char *str, struct guild_castle *gc) { + int tmp_int[26]; + + memset(gc, 0, sizeof(struct guild_castle)); + // new structure of guild castle + if (sscanf(str, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], &tmp_int[13], + &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], &tmp_int[24], &tmp_int[25]) == 26) { + gc->castle_id = tmp_int[0]; + gc->guild_id = tmp_int[1]; + gc->economy = tmp_int[2]; + gc->defense = tmp_int[3]; + gc->triggerE = tmp_int[4]; + gc->triggerD = tmp_int[5]; + gc->nextTime = tmp_int[6]; + gc->payTime = tmp_int[7]; + gc->createTime = tmp_int[8]; + gc->visibleC = tmp_int[9]; + gc->guardian[0].visible = tmp_int[10]; + gc->guardian[1].visible = tmp_int[11]; + gc->guardian[2].visible = tmp_int[12]; + gc->guardian[3].visible = tmp_int[13]; + gc->guardian[4].visible = tmp_int[14]; + gc->guardian[5].visible = tmp_int[15]; + gc->guardian[6].visible = tmp_int[16]; + gc->guardian[7].visible = tmp_int[17]; + gc->guardian[0].hp = tmp_int[18]; + gc->guardian[1].hp = tmp_int[19]; + gc->guardian[2].hp = tmp_int[20]; + gc->guardian[3].hp = tmp_int[21]; + gc->guardian[4].hp = tmp_int[22]; + gc->guardian[5].hp = tmp_int[23]; + gc->guardian[6].hp = tmp_int[24]; + gc->guardian[7].hp = tmp_int[25]; // end additions [Valaris] + // old structure of guild castle + } else if (sscanf(str, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], &tmp_int[13], + &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17]) == 18) { + int i; + + gc->castle_id = tmp_int[0]; + gc->guild_id = tmp_int[1]; + gc->economy = tmp_int[2]; + gc->defense = tmp_int[3]; + gc->triggerE = tmp_int[4]; + gc->triggerD = tmp_int[5]; + gc->nextTime = tmp_int[6]; + gc->payTime = tmp_int[7]; + gc->createTime = tmp_int[8]; + gc->visibleC = tmp_int[9]; + gc->guardian[0].visible = tmp_int[10]; + gc->guardian[1].visible = tmp_int[11]; + gc->guardian[2].visible = tmp_int[12]; + gc->guardian[3].visible = tmp_int[13]; + gc->guardian[4].visible = tmp_int[14]; + gc->guardian[5].visible = tmp_int[15]; + gc->guardian[6].visible = tmp_int[16]; + gc->guardian[7].visible = tmp_int[17]; + + for (i = 0; i < MAX_GUARDIANS; i++) + { + if (gc->guardian[i].visible) + gc->guardian[i].hp = 15000 + 2000 * gc->defense; + else + gc->guardian[i].hp = 0; + } + } else { + return 1; + } + + return 0; +} + +// ギルド関連データベース読み込み +int inter_guild_readdb(void) { + int i; + FILE *fp; + char line[1024]; + char path[1024]; + + sprintf(path, "%s%s", db_path, "/exp_guild.txt"); + fp = fopen(path, "r"); + if (fp == NULL) { + ShowError("can't read db/exp_guild.txt\n"); + return 1; + } + i = 0; + while(fgets(line, sizeof(line)-1, fp) && i < 100){ + if (line[0] == '/' && line[1] == '/') + continue; + guild_exp[i] = atoi(line); + i++; + } + fclose(fp); + + return 0; +} + +// ギルドデータの読み込み +int inter_guild_init() { + char line[16384]; + struct guild *g; + struct guild_castle *gc; + FILE *fp; + int i, j, c = 0; + + inter_guild_readdb(); + + guild_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + castle_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + if ((fp = fopen(guild_txt,"r")) == NULL) + return 1; + while(fgets(line, sizeof(line)-1, fp)) { + j = 0; + if (sscanf(line, "%d\t%%newid%%\n%n", &i, &j) == 1 && j > 0 && guild_newid <= i) { + guild_newid = i; + continue; + } + + g = (struct guild *) aCalloc(sizeof(struct guild), 1); + if(g == NULL){ + ShowFatalError("int_guild: out of memory!\n"); + exit(0); + } +// memset(g, 0, sizeof(struct guild)); not needed... + if (inter_guild_fromstr(line, g) == 0 && g->guild_id > 0) { + if (g->guild_id >= guild_newid) + guild_newid = g->guild_id + 1; + idb_put(guild_db, g->guild_id, g); + guild_check_empty(g); + guild_calcinfo(g); + } else { + ShowError("int_guild: broken data [%s] line %d\n", guild_txt, c); + aFree(g); + } + c++; + } + fclose(fp); +// printf("int_guild: %s read done (%d guilds)\n", guild_txt, c); + + c = 0;//カウンタ初期化 + + if ((fp = fopen(castle_txt, "r")) == NULL) { + return 1; + } + + while(fgets(line, sizeof(line)-1, fp)) { + gc = (struct guild_castle *) aCalloc(sizeof(struct guild_castle), 1); + if(gc == NULL){ + ShowFatalError("int_guild: out of memory!\n"); + exit(0); + } +// memset(gc, 0, sizeof(struct guild_castle)); No need... + if (inter_guildcastle_fromstr(line, gc) == 0) { + idb_put(castle_db, gc->castle_id, gc); + } else { + ShowError("int_guild: broken data [%s] line %d\n", castle_txt, c); + aFree(gc); + } + c++; + } + + if (!c) { + ShowStatus(" %s - making Default Data...\n", castle_txt); + //デフォルトデータを作成 + for(i = 0; i < MAX_GUILDCASTLE; i++) { + gc = (struct guild_castle *) aCalloc(sizeof(struct guild_castle), 1); + if (gc == NULL) { + ShowFatalError("int_guild: out of memory!\n"); + exit(0); + } + gc->castle_id = i; + idb_put(castle_db, gc->castle_id, gc); + } + ShowStatus(" %s - making done\n",castle_txt); + return 0; + } + + fclose(fp); + + return 0; +} + +void inter_guild_final() { + castle_db->destroy(castle_db, NULL); + guild_db->destroy(guild_db, NULL); + return; +} + +struct guild *inter_guild_search(int guild_id) { + return idb_get(guild_db, guild_id); +} + +// ギルドデータのセーブ用 +int inter_guild_save_sub(DBKey key,void *data,va_list ap) { + char line[16384]; + FILE *fp; + + inter_guild_tostr(line,(struct guild *)data); + fp=va_arg(ap,FILE *); + fprintf(fp,"%s" RETCODE,line); + + return 0; +} + +// ギルド城データのセーブ用 +int inter_castle_save_sub(DBKey key, void *data, va_list ap) { + char line[16384]; + FILE *fp; + + inter_guildcastle_tostr(line, (struct guild_castle *)data); + fp = va_arg(ap, FILE *); + fprintf(fp, "%s" RETCODE, line); + + return 0; +} + +// ギルドデータのセーブ +int inter_guild_save() { + FILE *fp; + int lock; + + if ((fp = lock_fopen(guild_txt, &lock)) == NULL) { + ShowError("int_guild: cant write [%s] !!! data is lost !!!\n", guild_txt); + return 1; + } + guild_db->foreach(guild_db, inter_guild_save_sub, fp); +// fprintf(fp, "%d\t%%newid%%\n", guild_newid); + lock_fclose(fp, guild_txt, &lock); +// printf("int_guild: %s saved.\n", guild_txt); + + if ((fp = lock_fopen(castle_txt,&lock)) == NULL) { + ShowError("int_guild: cant write [%s] !!! data is lost !!!\n", castle_txt); + return 1; + } + castle_db->foreach(castle_db, inter_castle_save_sub, fp); + lock_fclose(fp, castle_txt, &lock); + + return 0; +} + +// ギルド名検索用 +int search_guildname_sub(DBKey key, void *data, va_list ap) { + struct guild *g = (struct guild *)data, **dst; + char *str; + + str = va_arg(ap, char *); + dst = va_arg(ap, struct guild **); + if (strcmpi(g->name, str) == 0) + *dst = g; + return 0; +} + +// ギルド名検索 +struct guild* search_guildname(char *str) { + struct guild *g = NULL; + guild_db->foreach(guild_db, search_guildname_sub, str, &g); + return g; +} + +// ギルドが空かどうかチェック +int guild_check_empty(struct guild *g) { + int i; + + for(i = 0; i < g->max_member; i++) { + if (g->member[i].account_id > 0) { + return 0; + } + } + // 誰もいないので解散 + guild_db->foreach(guild_db, guild_break_sub, g->guild_id); + inter_guild_storage_delete(g->guild_id); + mapif_guild_broken(g->guild_id, 0); + idb_remove(guild_db, g->guild_id); + return 1; +} + +// キャラの競合がないかチェック用 +int guild_check_conflict_sub(DBKey key, void *data, va_list ap) { + struct guild *g = (struct guild *)data; + int guild_id, account_id, char_id, i; + + guild_id = va_arg(ap, int); + account_id = va_arg(ap, int); + char_id = va_arg(ap, int); + + if (g->guild_id == guild_id) // 本来の所属なので問題なし + return 0; + + for(i = 0; i < MAX_GUILD; i++) { + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) { + // 別のギルドに偽の所属データがあるので脱退 + ShowWarning("int_guild: guild conflict! %d,%d %d!=%d\n", account_id, char_id, guild_id, g->guild_id); + mapif_parse_GuildLeave(-1, g->guild_id, account_id, char_id, 0, "**データ競合**"); + } + } + + return 0; +} +// キャラの競合がないかチェック +int guild_check_conflict(int guild_id, int account_id, int char_id) { + guild_db->foreach(guild_db, guild_check_conflict_sub, guild_id, account_id, char_id); + + return 0; +} + +int guild_nextexp (int level) +{ + if (level == 0) + return 1; + if (level > 0 && level < 100) + return guild_exp[level-1]; + + return 0; +} + +// ギルドスキルがあるか確認 +int guild_checkskill(struct guild *g, int id) { + int idx = id - GD_SKILLBASE; + + + if(idx < 0 || idx >= MAX_GUILDSKILL) + + return 0; + + return g->skill[idx].lv; +} + +// ギルドの情報の再計算 +int guild_calcinfo(struct guild *g) { + int i, c, nextexp; + struct guild before = *g; + + // スキルIDの設定 + for(i = 0; i < MAX_GUILDSKILL; i++) + g->skill[i].id=i+GD_SKILLBASE; + + // ギルドレベル + if (g->guild_lv <= 0) + g->guild_lv = 1; + nextexp = guild_nextexp(g->guild_lv); + if (nextexp > 0) { + while(g->exp >= nextexp && nextexp > 0) { //fixed guild exp overflow [Kevin] + g->exp -= nextexp; + g->guild_lv++; + g->skill_point++; + nextexp = guild_nextexp(g->guild_lv); + } + } + + // ギルドの次の経験値 + g->next_exp = guild_nextexp(g->guild_lv); + + // メンバ上限(ギルド拡張適用) + g->max_member = 16 + guild_checkskill(g, GD_EXTENSION) * 6; //Guild Extention skill - currently adds 6 to max per skill lv. + if(g->max_member > MAX_GUILD) + { + ShowError("Guild %d:%s has capacity for too many guild members (%d), max supported is %d\n", g->guild_id, g->name, g->max_member, MAX_GUILD); + g->max_member = MAX_GUILD; + } + + // 平均レベルとオンライン人数 + g->average_lv = 0; + g->connect_member = 0; + c = 0; + for(i = 0; i < g->max_member; i++) { + if (g->member[i].account_id > 0) { + g->average_lv += g->member[i].lv; + c++; + if (g->member[i].online > 0) + g->connect_member++; + } + } + if(c) g->average_lv /= c; + + // 全データを送る必要がありそう + if (g->max_member != before.max_member || + g->guild_lv != before.guild_lv || + g->skill_point != before.skill_point) { + mapif_guild_info(-1, g); + return 1; + } + + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// ギルド作成可否 +int mapif_guild_created(int fd, int account_id, struct guild *g) { + WFIFOHEAD(fd, 10); + WFIFOW(fd,0) = 0x3830; + WFIFOL(fd,2) = account_id; + if (g != NULL) { + WFIFOL(fd,6) = g->guild_id; + ShowInfo("Created Guild (%d %s)\n", g->guild_id, g->name); + }else{ + WFIFOL(fd,6) = 0; + } + WFIFOSET(fd,10); + return 0; +} + +// ギルド情報見つからず +int mapif_guild_noinfo(int fd, int guild_id) { + WFIFOHEAD(fd, 8); + WFIFOW(fd,0) = 0x3831; + WFIFOW(fd,2) = 8; + WFIFOL(fd,4) = guild_id; + WFIFOSET(fd,8); + ShowNotice("int_guild: info not found %d\n", guild_id); + + return 0; +} + +// ギルド情報まとめ送り +int mapif_guild_info(int fd, struct guild *g) { + unsigned char buf[8+sizeof(struct guild)]; + + WBUFW(buf,0) = 0x3831; + memcpy(buf + 4, g, sizeof(struct guild)); + WBUFW(buf,2) = 4 + sizeof(struct guild); +// printf("int_guild: sizeof(guild)=%d\n", sizeof(struct guild)); + if (fd < 0) + mapif_sendall(buf, WBUFW(buf,2)); + else + mapif_send(fd, buf, WBUFW(buf,2)); +// printf("int_guild: info %d %s\n", p->guild_id, p->name); + + return 0; +} + +// メンバ追加可否 +int mapif_guild_memberadded(int fd, int guild_id, int account_id, int char_id, int flag) { + WFIFOHEAD(fd, 15); + WFIFOW(fd,0) = 0x3832; + WFIFOL(fd,2) = guild_id; + WFIFOL(fd,6) = account_id; + WFIFOL(fd,10) = char_id; + WFIFOB(fd,14) = flag; + WFIFOSET(fd, 15); + + return 0; +} + +// 脱退/追放通知 +int mapif_guild_leaved(int guild_id, int account_id, int char_id, int flag, const char *name, const char *mes) { + unsigned char buf[79]; + + WBUFW(buf, 0) = 0x3834; + WBUFL(buf, 2) = guild_id; + WBUFL(buf, 6) = account_id; + WBUFL(buf,10) = char_id; + WBUFB(buf,14) = flag; + memcpy(WBUFP(buf,15), mes, 40); + memcpy(WBUFP(buf,55), name, NAME_LENGTH); + mapif_sendall(buf, 55+NAME_LENGTH); +// mapif_sendall(buf, 79); + ShowInfo("Character left guild (Guild %d, %d - %s: %s)\n", guild_id, account_id, name, mes); + + return 0; +} + +// オンライン状態とLv更新通知 +int mapif_guild_memberinfoshort(struct guild *g, int idx) { + unsigned char buf[19]; + + WBUFW(buf, 0) = 0x3835; + WBUFL(buf, 2) = g->guild_id; + WBUFL(buf, 6) = g->member[idx].account_id; + WBUFL(buf,10) = g->member[idx].char_id; + WBUFB(buf,14) = (unsigned char)g->member[idx].online; + WBUFW(buf,15) = g->member[idx].lv; + WBUFW(buf,17) = g->member[idx].class_; + mapif_sendall(buf, 19); + return 0; +} + +// 解散通知 +int mapif_guild_broken(int guild_id, int flag) { + unsigned char buf[7]; + + WBUFW(buf,0) = 0x3836; + WBUFL(buf,2) = guild_id; + WBUFB(buf,6) = flag; + mapif_sendall(buf, 7); + ShowInfo("Guild Break (%d)\n", guild_id); + + return 0; +} + +// ギルド内発言 +int mapif_guild_message(int guild_id, int account_id, char *mes, int len, int sfd) { + unsigned char buf[2048]; + + WBUFW(buf,0) = 0x3837; + WBUFW(buf,2) = len + 12; + WBUFL(buf,4) = guild_id; + WBUFL(buf,8) = account_id; + memcpy(WBUFP(buf,12), mes, len); + mapif_sendallwos(sfd, buf, len + 12); + + return 0; +} + +// ギルド基本情報変更通知 +int mapif_guild_basicinfochanged(int guild_id, int type, const void *data, int len) { + unsigned char buf[2048]; + + WBUFW(buf,0) = 0x3839; + WBUFW(buf,2) = len+10; + WBUFL(buf,4) = guild_id; + WBUFW(buf,8) = type; + memcpy(WBUFP(buf,10),data,len); + mapif_sendall(buf,len+10); + return 0; +} + +// ギルドメンバ情報変更通知 +int mapif_guild_memberinfochanged(int guild_id, int account_id, int char_id, int type, const void *data, int len) { + unsigned char buf[4096]; + + WBUFW(buf, 0) = 0x383a; + WBUFW(buf, 2) = len + 18; + WBUFL(buf, 4) = guild_id; + WBUFL(buf, 8) = account_id; + WBUFL(buf,12) = char_id; + WBUFW(buf,16) = type; + memcpy(WBUFP(buf,18), data, len); + mapif_sendall(buf,len+18); + + return 0; +} + +// ギルドスキルアップ通知 +int mapif_guild_skillupack(int guild_id, int skill_num, int account_id) { + unsigned char buf[14]; + + WBUFW(buf, 0) = 0x383c; + WBUFL(buf, 2) = guild_id; + WBUFL(buf, 6) = skill_num; + WBUFL(buf,10) = account_id; + mapif_sendall(buf, 14); + + return 0; +} + +// ギルド同盟/敵対通知 +int mapif_guild_alliance(int guild_id1, int guild_id2, int account_id1, int account_id2, int flag, const char *name1, const char *name2) { + unsigned char buf[67]; + + WBUFW(buf, 0) = 0x383d; + WBUFL(buf, 2) = guild_id1; + WBUFL(buf, 6) = guild_id2; + WBUFL(buf,10) = account_id1; + WBUFL(buf,14) = account_id2; + WBUFB(buf,18) = flag; + memcpy(WBUFP(buf,19), name1, NAME_LENGTH); + memcpy(WBUFP(buf,19+NAME_LENGTH), name2, NAME_LENGTH); + mapif_sendall(buf,19+2*NAME_LENGTH); +/* + memcpy(WBUFP(buf,43), name2, NAME_LENGTH); + mapif_sendall(buf, 67); +*/ + return 0; +} + +// ギルド役職変更通知 +int mapif_guild_position(struct guild *g, int idx) { + unsigned char buf[2048]; + + WBUFW(buf,0) = 0x383b; + WBUFW(buf,2) = sizeof(struct guild_position) + 12; + WBUFL(buf,4) = g->guild_id; + WBUFL(buf,8) = idx; + memcpy(WBUFP(buf,12), &g->position[idx], sizeof(struct guild_position)); + mapif_sendall(buf, WBUFW(buf,2)); + + return 0; +} + +// ギルド告知変更通知 +int mapif_guild_notice(struct guild *g) { + unsigned char buf[186]; + + WBUFW(buf,0) = 0x383e; + WBUFL(buf,2) = g->guild_id; + memcpy(WBUFP(buf,6), g->mes1, 60); + memcpy(WBUFP(buf,66), g->mes2, 120); + mapif_sendall(buf, 186); + + return 0; +} + +// ギルドエンブレム変更通知 +int mapif_guild_emblem(struct guild *g) { + unsigned char buf[2048]; + + WBUFW(buf,0) = 0x383f; + WBUFW(buf,2) = g->emblem_len + 12; + WBUFL(buf,4) = g->guild_id; + WBUFL(buf,8) = g->emblem_id; + memcpy(WBUFP(buf,12), g->emblem_data, g->emblem_len); + mapif_sendall(buf, WBUFW(buf,2)); + + return 0; +} + +int mapif_guild_master_changed(struct guild *g, int position) +{ + unsigned char buf[12]; + WBUFW(buf,0)=0x3843; + WBUFL(buf,2)=g->guild_id; + WBUFL(buf,6)=position; + mapif_sendall(buf,10); + return 0; +} + +int mapif_guild_castle_dataload(int castle_id, int index, int value) { + unsigned char buf[9]; + + WBUFW(buf,0) = 0x3840; + WBUFW(buf,2) = castle_id; + WBUFB(buf,4) = index; + WBUFL(buf,5) = value; + mapif_sendall(buf,9); + + return 0; +} + +int mapif_guild_castle_datasave(int castle_id, int index, int value) { + unsigned char buf[9]; + + WBUFW(buf,0) = 0x3841; + WBUFW(buf,2) = castle_id; + WBUFB(buf,4) = index; + WBUFL(buf,5) = value; + mapif_sendall(buf,9); + + return 0; +} + +int mapif_guild_castle_alldataload_sub(DBKey key, void *data, va_list ap) { + int fd = va_arg(ap, int); + int *p = va_arg(ap, int*); + + WFIFOHEAD(fd, sizeof(struct guild_castle)); + memcpy(WFIFOP(fd,*p), (struct guild_castle*)data, sizeof(struct guild_castle)); + (*p) += sizeof(struct guild_castle); + + return 0; +} + +int mapif_guild_castle_alldataload(int fd) { + int len = 4; + + WFIFOHEAD(fd, 0); + WFIFOW(fd,0) = 0x3842; + castle_db->foreach(castle_db, mapif_guild_castle_alldataload_sub, fd, &len); + WFIFOW(fd,2) = len; + WFIFOSET(fd, len); + + return 0; +} + +//------------------------------------------------------------------- +// map serverからの通信 + +// ギルド作成要求 +int mapif_parse_CreateGuild(int fd, int account_id, char *name, struct guild_member *master) { + struct guild *g; + int i; + + for(i = 0; i < NAME_LENGTH && name[i]; i++) { + if (!(name[i] & 0xe0) || name[i] == 0x7f) { + ShowInfo("Create Guild: illegal guild name [%s]\n", name); + mapif_guild_created(fd, account_id, NULL); + return 0; + } + } + + if ((g = search_guildname(name)) != NULL) { + ShowInfo("Create Guild: same name guild exists [%s]\n", name); + mapif_guild_created(fd, account_id, NULL); + return 0; + } + g = (struct guild *) aCalloc(sizeof(struct guild), 1); + if (g == NULL) { + ShowFatalError("int_guild: CreateGuild: out of memory !\n"); + mapif_guild_created(fd, account_id, NULL); + exit(0); + } +// memset(g, 0, sizeof(struct guild)); Meh... + g->guild_id = guild_newid++; + memcpy(g->name, name, NAME_LENGTH-1); + memcpy(g->master, master->name, NAME_LENGTH-1); + memcpy(&g->member[0], master, sizeof(struct guild_member)); + + g->position[0].mode = 0x11; + strcpy(g->position[ 0].name, "GuildMaster"); + strcpy(g->position[MAX_GUILDPOSITION-1].name, "Newbie"); + for(i = 1; i < MAX_GUILDPOSITION-1; i++) + sprintf(g->position[i].name, "Position %d", i + 1); + + // ここでギルド情報計算が必要と思われる + g->max_member = 16; + g->average_lv = master->lv; + for(i = 0; i < MAX_GUILDSKILL; i++) + g->skill[i].id=i + GD_SKILLBASE; + + idb_put(guild_db, g->guild_id, g); + + mapif_guild_created(fd, account_id, g); + mapif_guild_info(fd, g); + + if(log_inter) + inter_log("guild %s (id=%d) created by master %s (id=%d)" RETCODE, + name, g->guild_id, master->name, master->account_id); + + return 0; +} + +// ギルド情報要求 +int mapif_parse_GuildInfo(int fd, int guild_id) { + struct guild *g; + + g = idb_get(guild_db, guild_id); + if (g != NULL){ + guild_calcinfo(g); + mapif_guild_info(fd, g); + } else + mapif_guild_noinfo(fd, guild_id); + + return 0; +} + +// ギルドメンバ追加要求 +int mapif_parse_GuildAddMember(int fd, int guild_id, struct guild_member *m) { + struct guild *g; + int i; + + g = idb_get(guild_db, guild_id); + if (g == NULL) { + mapif_guild_memberadded(fd, guild_id, m->account_id, m->char_id, 1); + return 0; + } + + for(i = 0; i < g->max_member; i++) { + if (g->member[i].account_id == 0) { + memcpy(&g->member[i], m, sizeof(struct guild_member)); + mapif_guild_memberadded(fd, guild_id, m->account_id, m->char_id, 0); + guild_calcinfo(g); + mapif_guild_info(-1, g); + + return 0; + } + } + mapif_guild_memberadded(fd, guild_id, m->account_id, m->char_id, 1); + + return 0; +} + +// ギルド脱退/追放要求 +int mapif_parse_GuildLeave(int fd, int guild_id, int account_id, int char_id, int flag, const char *mes) { + struct guild *g = NULL; + int i, j; + + g = idb_get(guild_db, guild_id); + if (g != NULL) { + for(i = 0; i < MAX_GUILD; i++) { + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) { +// printf("%d %d\n", i, (int)(&g->member[i])); +// printf("%d %s\n", i, g->member[i].name); + + if (flag) { // 追放の場合追放リストに入れる + for(j = 0; j < MAX_GUILDEXPLUSION; j++) { + if (g->explusion[j].account_id == 0) + break; + } + if (j == MAX_GUILDEXPLUSION) { // 一杯なので古いのを消す + for(j = 0; j < MAX_GUILDEXPLUSION - 1; j++) + g->explusion[j] = g->explusion[j+1]; + j = MAX_GUILDEXPLUSION - 1; + } + g->explusion[j].account_id = account_id; + memcpy(g->explusion[j].acc, "dummy", NAME_LENGTH-1); + memcpy(g->explusion[j].name, g->member[i].name, NAME_LENGTH-1); + memcpy(g->explusion[j].mes, mes, 40); + } + + mapif_guild_leaved(guild_id, account_id, char_id, flag, g->member[i].name, mes); +// printf("%d %d\n", i, (int)(&g->member[i])); +// printf("%d %s\n", i, (&g->member[i])->name); + memset(&g->member[i], 0, sizeof(struct guild_member)); + + if (guild_check_empty(g) == 0) + mapif_guild_info(-1,g);// まだ人がいるのでデータ送信 + + return 0; + } + } + } + return 0; +} + +// オンライン/Lv更新 +int mapif_parse_GuildChangeMemberInfoShort(int fd, int guild_id, int account_id, int char_id, int online, int lv, int class_) { + struct guild *g; + int i, alv, c; + + g = idb_get(guild_db, guild_id); + if (g == NULL) + return 0; + + g->connect_member = 0; + + alv = 0; + c = 0; + for(i = 0; i < MAX_GUILD; i++) { + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) { + g->member[i].online = online; + g->member[i].lv = lv; + g->member[i].class_ = class_; + mapif_guild_memberinfoshort(g, i); + } + if (g->member[i].account_id > 0) { + alv += g->member[i].lv; + c++; + } + if (g->member[i].online) + g->connect_member++; + } + + if (c) + // 平均レベル + g->average_lv = alv / c; + + return 0; +} + +// ギルド解散処理用(同盟/敵対を解除) +int guild_break_sub(DBKey key, void *data, va_list ap) { + struct guild *g = (struct guild *)data; + int guild_id = va_arg(ap, int); + int i; + + for(i = 0; i < MAX_GUILDALLIANCE; i++) { + if (g->alliance[i].guild_id == guild_id) + g->alliance[i].guild_id = 0; + } + return 0; +} + +// ギルド解散要求 +int mapif_parse_BreakGuild(int fd, int guild_id) { + struct guild *g; + + g = idb_get(guild_db, guild_id); + if(g == NULL) + return 0; + + guild_db->foreach(guild_db, guild_break_sub, guild_id); + inter_guild_storage_delete(guild_id); + mapif_guild_broken(guild_id, 0); + + if(log_inter) + inter_log("guild %s (id=%d) broken" RETCODE, g->name, guild_id); + + idb_remove(guild_db, guild_id); + return 0; +} + +// ギルドメッセージ送信 +int mapif_parse_GuildMessage(int fd, int guild_id, int account_id, char *mes, int len) { + return mapif_guild_message(guild_id, account_id, mes, len, fd); +} + +// ギルド基本データ変更要求 +int mapif_parse_GuildBasicInfoChange(int fd, int guild_id, int type, const char *data, int len) { + struct guild *g; + short dw = *((short *)data); + + g = idb_get(guild_db, guild_id); + if (g == NULL) + return 0; + + switch(type) { + case GBI_GUILDLV: + if (dw > 0 && g->guild_lv + dw <= 50) { + g->guild_lv+=dw; + g->skill_point+=dw; + } else if (dw < 0 && g->guild_lv + dw >= 1) + g->guild_lv += dw; + mapif_guild_info(-1, g); + return 0; + default: + ShowError("int_guild: GuildBasicInfoChange: Unknown type %d\n", type); + break; + } + mapif_guild_basicinfochanged(guild_id, type, data, len); + + return 0; +} + +// ギルドメンバデータ変更要求 +int mapif_parse_GuildMemberInfoChange(int fd, int guild_id, int account_id, int char_id, int type, const char *data, int len) { + int i; + struct guild *g; + + g = idb_get(guild_db, guild_id); + if(g == NULL) + return 0; + + for(i = 0; i < g->max_member; i++) + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) + break; + if (i == g->max_member) { + ShowWarning("int_guild: GuildMemberChange: Not found %d,%d in %d[%s]\n", account_id, char_id, guild_id, g->name); + return 0; + } + switch(type) { + case GMI_POSITION: // 役職 + g->member[i].position = *((int *)data); + break; + case GMI_EXP: // EXP + { + int exp, oldexp = g->member[i].exp; + exp = g->member[i].exp = *((unsigned int *)data); + g->exp += (exp - oldexp); + guild_calcinfo(g); // Lvアップ判断 + mapif_guild_basicinfochanged(guild_id, GBI_EXP, &g->exp, 4); + break; + } + case GMI_HAIR: + { + g->member[i].hair=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + break; + } + case GMI_HAIR_COLOR: + { + g->member[i].hair_color=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + break; + } + case GMI_GENDER: + { + g->member[i].gender=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + break; + } + case GMI_CLASS: + { + g->member[i].class_=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + break; + } + case GMI_LEVEL: + { + g->member[i].lv=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + break; + } + + default: + ShowError("int_guild: GuildMemberInfoChange: Unknown type %d\n", type); + break; + } + mapif_guild_memberinfochanged(guild_id, account_id, char_id, type, data, len); + + return 0; +} + +int inter_guild_sex_changed(int guild_id,int account_id,int char_id, int gender) +{ + return mapif_parse_GuildMemberInfoChange(0, guild_id, account_id, char_id, GMI_GENDER, (const char*)&gender, sizeof(gender)); +} + +// ギルド役職名変更要求 +int mapif_parse_GuildPosition(int fd, int guild_id, int idx, struct guild_position *p) { + struct guild *g = idb_get(guild_db, guild_id); + + if (g == NULL || idx < 0 || idx >= MAX_GUILDPOSITION) { + return 0; + } + memcpy(&g->position[idx], p, sizeof(struct guild_position)); + mapif_guild_position(g, idx); + ShowInfo("int_guild: position [%d] changed\n", idx); + + return 0; +} + +// ギルドスキルアップ要求 +int mapif_parse_GuildSkillUp(int fd, int guild_id, int skill_num, int account_id) { + struct guild *g = idb_get(guild_db, guild_id); + int idx = skill_num - GD_SKILLBASE; + + if (g == NULL || idx < 0 || idx >= MAX_GUILDSKILL) + return 0; + + if (g->skill_point > 0 && g->skill[idx].id > 0 && g->skill[idx].lv < 10) { + g->skill[idx].lv++; + g->skill_point--; + if (guild_calcinfo(g) == 0) + mapif_guild_info(-1, g); + mapif_guild_skillupack(guild_id, skill_num, account_id); + ShowInfo("int_guild: skill %d up\n", skill_num); + } + + return 0; +} + +//Manual deletion of an alliance when partnering guild does not exists. [Skotlex] +static int mapif_parse_GuildDeleteAlliance(struct guild *g, int guild_id, int account_id1, int account_id2, int flag) +{ + int i; + char name[NAME_LENGTH]; + for(i=0;ialliance[i].guild_id == guild_id) + { + strcpy(name, g->alliance[i].name); + g->alliance[i].guild_id=0; + break; + } + if (i == MAX_GUILDALLIANCE) + return -1; + + mapif_guild_alliance(g->guild_id,guild_id,account_id1,account_id2,flag,g->name,name); + return 0; +} +// ギルド同盟要求 +int mapif_parse_GuildAlliance(int fd, int guild_id1, int guild_id2, int account_id1, int account_id2, int flag) { + struct guild *g[2]; + int j, i; + + g[0] = idb_get(guild_db, guild_id1); + g[1] = idb_get(guild_db, guild_id2); + + if(g[0] && g[1]==NULL && (flag&0x8)) //Requested to remove an alliance with a not found guild. + return mapif_parse_GuildDeleteAlliance(g[0], guild_id2, + account_id1, account_id2, flag); //Try to do a manual removal of said guild. + + if (g[0] == NULL || g[1] == NULL) + return 0; + + if (!(flag & 0x8)) { + for(i = 0; i < 2 - (flag & 1); i++) { + for(j = 0; j < MAX_GUILDALLIANCE; j++) + if (g[i]->alliance[j].guild_id == 0) { + g[i]->alliance[j].guild_id = g[1-i]->guild_id; + memcpy(g[i]->alliance[j].name, g[1-i]->name, NAME_LENGTH-1); + g[i]->alliance[j].opposition = flag & 1; + break; + } + } + } else { // 関係解消 + for(i = 0; i < 2 - (flag & 1); i++) { + for(j = 0; j < MAX_GUILDALLIANCE; j++) + if (g[i]->alliance[j].guild_id == g[1-i]->guild_id && g[i]->alliance[j].opposition == (flag & 1)) { + g[i]->alliance[j].guild_id = 0; + break; + } + } + } + mapif_guild_alliance(guild_id1, guild_id2, account_id1, account_id2, flag, g[0]->name, g[1]->name); + + return 0; +} + +// ギルド告知変更要求 +int mapif_parse_GuildNotice(int fd, int guild_id, const char *mes1, const char *mes2) { + struct guild *g; + + g = idb_get(guild_db, guild_id); + if (g == NULL) + return 0; + memcpy(g->mes1, mes1, 60); + memcpy(g->mes2, mes2, 120); + + return mapif_guild_notice(g); +} + +// ギルドエンブレム変更要求 +int mapif_parse_GuildEmblem(int fd, int len, int guild_id, int dummy, const char *data) { + struct guild *g; + + g = idb_get(guild_db, guild_id); + if (g == NULL) + return 0; + memcpy(g->emblem_data, data, len); + g->emblem_len = len; + g->emblem_id++; + + return mapif_guild_emblem(g); +} + +int mapif_parse_GuildCastleDataLoad(int fd, int castle_id, int index) { + struct guild_castle *gc = idb_get(castle_db, castle_id); + + if (gc == NULL) { + return mapif_guild_castle_dataload(castle_id, 0, 0); + } + switch(index) { + case 1: return mapif_guild_castle_dataload(gc->castle_id, index, gc->guild_id); + case 2: return mapif_guild_castle_dataload(gc->castle_id, index, gc->economy); + case 3: return mapif_guild_castle_dataload(gc->castle_id, index, gc->defense); + case 4: return mapif_guild_castle_dataload(gc->castle_id, index, gc->triggerE); + case 5: return mapif_guild_castle_dataload(gc->castle_id, index, gc->triggerD); + case 6: return mapif_guild_castle_dataload(gc->castle_id, index, gc->nextTime); + case 7: return mapif_guild_castle_dataload(gc->castle_id, index, gc->payTime); + case 8: return mapif_guild_castle_dataload(gc->castle_id, index, gc->createTime); + case 9: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleC); + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + return mapif_guild_castle_dataload(gc->castle_id, index, gc->guardian[index-10].visible); + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + return mapif_guild_castle_dataload(gc->castle_id, index, gc->guardian[index-18].hp); // end additions [Valaris] + + default: + ShowError("mapif_parse_GuildCastleDataLoad ERROR!! (Not found index=%d)\n", index); + return 0; + } + + return 0; +} + +int mapif_parse_GuildCastleDataSave(int fd, int castle_id, int index, int value) { + struct guild_castle *gc= idb_get(castle_db, castle_id); + + if (gc == NULL) { + return mapif_guild_castle_datasave(castle_id, index, value); + } + switch(index) { + case 1: + if (gc->guild_id != value) { + int gid = (value) ? value : gc->guild_id; + struct guild *g = idb_get(guild_db, gid); + if(log_inter) + inter_log("guild %s (id=%d) %s castle id=%d" RETCODE, + (g) ? g->name : "??", gid, (value) ? "occupy" : "abandon", castle_id); + } + gc->guild_id = value; + break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + gc->guardian[index-10].visible = value; break; + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + gc->guardian[index-18].hp = value; break; // end additions [Valaris] + default: + ShowError("mapif_parse_GuildCastleDataSave ERROR!! (Not found index=%d)\n", index); + return 0; + } + + return mapif_guild_castle_datasave(gc->castle_id, index, value); +} + +// ギルドチェック要求 +int mapif_parse_GuildCheck(int fd, int guild_id, int account_id, int char_id) { + return guild_check_conflict(guild_id, account_id, char_id); +} + +int mapif_parse_GuildMasterChange(int fd, int guild_id, const char* name, int len) +{ + struct guild *g = idb_get(guild_db, guild_id); + struct guild_member gm; + int pos; + + if(g==NULL || g->guild_id<=0 || len > NAME_LENGTH) + return 0; + + for (pos = 0; pos < g->max_member && strncmp(g->member[pos].name, name, len); pos++); + + if (pos == g->max_member) + return 0; //Character not found?? + + memcpy(&gm, &g->member[pos], sizeof (struct guild_member)); + memcpy(&g->member[pos], &g->member[0], sizeof(struct guild_member)); + memcpy(&g->member[0], &gm, sizeof(struct guild_member)); + + g->member[pos].position = g->member[0].position; + g->member[0].position = 0; //Position 0: guild Master. + strncpy(g->master, name, len); + if (len < NAME_LENGTH) + g->master[len] = '\0'; + + ShowInfo("int_guild: Guildmaster Changed to %s (Guild %d - %s)\n",name, guild_id, g->name); + return mapif_guild_master_changed(g, pos); +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_guild_parse_frommap(int fd) { + RFIFOHEAD(fd); + switch(RFIFOW(fd,0)) { + case 0x3030: mapif_parse_CreateGuild(fd, RFIFOL(fd,4), (char*)RFIFOP(fd,8), (struct guild_member *)RFIFOP(fd,32)); break; + case 0x3031: mapif_parse_GuildInfo(fd, RFIFOL(fd,2)); break; + case 0x3032: mapif_parse_GuildAddMember(fd, RFIFOL(fd,4), (struct guild_member *)RFIFOP(fd,8)); break; + case 0x3033: mapif_parse_GuildMasterChange(fd,RFIFOL(fd,4),(const char*)RFIFOP(fd,8),RFIFOW(fd,2)-8); break; + case 0x3034: mapif_parse_GuildLeave(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOB(fd,14), (const char*)RFIFOP(fd,15)); break; + case 0x3035: mapif_parse_GuildChangeMemberInfoShort(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOB(fd,14), RFIFOW(fd,15), RFIFOW(fd,17)); break; + case 0x3036: mapif_parse_BreakGuild(fd, RFIFOL(fd,2)); break; + case 0x3037: mapif_parse_GuildMessage(fd, RFIFOL(fd,4), RFIFOL(fd,8), (char*)RFIFOP(fd,12), RFIFOW(fd,2)-12); break; + case 0x3038: mapif_parse_GuildMasterChange(fd,RFIFOL(fd,4),(const char*)RFIFOP(fd,8),RFIFOW(fd,2)-8); break; + case 0x3039: mapif_parse_GuildBasicInfoChange(fd, RFIFOL(fd,4), RFIFOW(fd,8), (const char*)RFIFOP(fd,10), RFIFOW(fd,2)-10); break; + case 0x303A: mapif_parse_GuildMemberInfoChange(fd, RFIFOL(fd,4), RFIFOL(fd,8), RFIFOL(fd,12), RFIFOW(fd,16), (const char*)RFIFOP(fd,18), RFIFOW(fd,2)-18); break; + case 0x303B: mapif_parse_GuildPosition(fd, RFIFOL(fd,4), RFIFOL(fd,8), (struct guild_position *)RFIFOP(fd,12)); break; + case 0x303C: mapif_parse_GuildSkillUp(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x303D: mapif_parse_GuildAlliance(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOL(fd,14), RFIFOB(fd,18)); break; + case 0x303E: mapif_parse_GuildNotice(fd, RFIFOL(fd,2), (const char*)RFIFOP(fd,6), (const char*)RFIFOP(fd,66)); break; + case 0x303F: mapif_parse_GuildEmblem(fd, RFIFOW(fd,2)-12, RFIFOL(fd,4), RFIFOL(fd,8), (const char*)RFIFOP(fd,12)); break; + case 0x3040: mapif_parse_GuildCastleDataLoad(fd, RFIFOW(fd,2), RFIFOB(fd,4)); break; + case 0x3041: mapif_parse_GuildCastleDataSave(fd, RFIFOW(fd,2), RFIFOB(fd,4), RFIFOL(fd,5)); break; + + default: + return 0; + } + + return 1; +} + +// マップサーバーの接続時処理 +int inter_guild_mapif_init(int fd) { + return mapif_guild_castle_alldataload(fd); +} + +// サーバーから脱退要求(キャラ削除用) +int inter_guild_leave(int guild_id, int account_id, int char_id) { + return mapif_parse_GuildLeave(-1, guild_id, account_id, char_id, 0, "** Character Deleted **"); +} diff --git a/src/char/int_guild.h b/src/char/int_guild.h new file mode 100644 index 000000000..0b0105af3 --- /dev/null +++ b/src/char/int_guild.h @@ -0,0 +1,19 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_GUILD_H_ +#define _INT_GUILD_H_ + +int inter_guild_init(void); +void inter_guild_final(void); +int inter_guild_save(void); +int inter_guild_parse_frommap(int fd); +struct guild *inter_guild_search(int guild_id); +int inter_guild_mapif_init(int fd); +int inter_guild_leave(int guild_id,int account_id,int char_id); +int inter_guild_sex_changed(int guild_id,int account_id,int char_id, int gender); + +extern char guild_txt[1024]; +extern char castle_txt[1024]; + +#endif diff --git a/src/char/int_party.c b/src/char/int_party.c new file mode 100644 index 000000000..1226da650 --- /dev/null +++ b/src/char/int_party.c @@ -0,0 +1,654 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include + +#include "../common/mmo.h" +#include "../common/socket.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/showmsg.h" +#include "char.h" +#include "inter.h" +#include "int_party.h" + +char party_txt[1024] = "save/party.txt"; + +static struct dbt *party_db; +static int party_newid = 100; + +int mapif_party_broken(int party_id, int flag); +int party_check_empty(struct party *p); +int mapif_parse_PartyLeave(int fd, int party_id, int account_id, int char_id); + +// パ?ティデ?タの文字列への?換 +int inter_party_tostr(char *str, struct party *p) { + int i, len; + + len = sprintf(str, "%d\t%s\t%d,%d\t", p->party_id, p->name, p->exp, p->item); + for(i = 0; i < MAX_PARTY; i++) { + struct party_member *m = &p->member[i]; + len += sprintf(str + len, "%d,%d,%d\t%s\t", m->account_id, m->char_id, m->leader, ((m->account_id > 0) ? m->name : "NoMember")); + } + + return 0; +} + +// パ?ティデ?タの文字列からの?換 +int inter_party_fromstr(char *str, struct party *p) { + int i, j; + int tmp_int[16]; + char tmp_str[256]; + + memset(p, 0, sizeof(struct party)); + +// printf("sscanf party main info\n"); + if (sscanf(str, "%d\t%255[^\t]\t%d,%d\t", &tmp_int[0], tmp_str, &tmp_int[1], &tmp_int[2]) != 4) + return 1; + + p->party_id = tmp_int[0]; + memcpy(p->name, tmp_str, NAME_LENGTH-1); + p->exp = tmp_int[1]?1:0; + p->item = tmp_int[2]; +// printf("%d [%s] %d %d\n", tmp_int[0], tmp_str[0], tmp_int[1], tmp_int[2]); + + for(j = 0; j < 3 && str != NULL; j++) + str = strchr(str + 1, '\t'); + + for(i = 0; i < MAX_PARTY; i++) { + struct party_member *m = &p->member[i]; + if (str == NULL) + return 1; +// printf("sscanf party member info %d\n", i); + + if (sscanf(str + 1, "%d,%d,%d\t%255[^\t]\t", &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str) != 4) + return 1; + + m->account_id = tmp_int[0]; + m->char_id = tmp_int[1]; + m->leader = tmp_int[2]?1:0; + memcpy(m->name, tmp_str, NAME_LENGTH-1); +// printf(" %d %d [%s]\n", tmp_int[0], tmp_int[1], tmp_str); + + for(j = 0; j < 2 && str != NULL; j++) + str = strchr(str + 1, '\t'); + } + + return 0; +} + +// パ?ティデ?タのロ?ド +int inter_party_init() { + char line[8192]; + struct party *p; + FILE *fp; + int c = 0; + int i, j; + + party_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + if ((fp = fopen(party_txt, "r")) == NULL) + return 1; + + while(fgets(line, sizeof(line) - 1, fp)) { + j = 0; + if (sscanf(line, "%d\t%%newid%%\n%n", &i, &j) == 1 && j > 0 && party_newid <= i) { + party_newid = i; + continue; + } + + p = (struct party*)aCalloc(sizeof(struct party), 1); + if (p == NULL){ + ShowFatalError("int_party: out of memory!\n"); + exit(0); + } + memset(p, 0, sizeof(struct party)); + if (inter_party_fromstr(line, p) == 0 && p->party_id > 0) { + if (p->party_id >= party_newid) + party_newid = p->party_id + 1; + idb_put(party_db, p->party_id, p); + party_check_empty(p); + } else { + ShowError("int_party: broken data [%s] line %d\n", party_txt, c + 1); + aFree(p); + } + c++; + } + fclose(fp); + + return 0; +} + +void inter_party_final() +{ + party_db->destroy(party_db, NULL); + return; +} + +// パ?ティ?デ?タのセ?ブ用 +int inter_party_save_sub(DBKey key, void *data, va_list ap) { + char line[8192]; + FILE *fp; + + inter_party_tostr(line, (struct party *)data); + fp = va_arg(ap, FILE *); + fprintf(fp, "%s" RETCODE, line); + + return 0; +} + +// パ?ティ?デ?タのセ?ブ +int inter_party_save() { + FILE *fp; + int lock; + + if ((fp = lock_fopen(party_txt, &lock)) == NULL) { + ShowError("int_party: cant write [%s] !!! data is lost !!!\n", party_txt); + return 1; + } + party_db->foreach(party_db, inter_party_save_sub, fp); + lock_fclose(fp,party_txt, &lock); + return 0; +} + +// パ?ティ名?索用 +int search_partyname_sub(DBKey key,void *data,va_list ap) { + struct party *p = (struct party *)data,**dst; + char *str; + + str = va_arg(ap, char *); + dst = va_arg(ap, struct party **); + if (strncmpi(p->name, str, NAME_LENGTH) == 0) + *dst = p; + + return 0; +} + +// パ?ティ名?索 +struct party* search_partyname(char *str) { + struct party *p = NULL; + party_db->foreach(party_db, search_partyname_sub, str, &p); + return p; +} + +// EXP公平分配できるかチェック +int party_check_exp_share(struct party *p) { + int i, oi[MAX_PARTY], dudes=0; + int maxlv = 0, minlv = 0x7fffffff; + + for(i = 0; i < MAX_PARTY; i++) { + int lv = p->member[i].lv; + if (p->member[i].online) { + if (lv < minlv) + minlv = lv; + if (maxlv < lv) + maxlv = lv; + if( lv >= 70 ) + dudes+=1000; + oi[dudes%1000] = i; + dudes++; + } + } + if((dudes/1000 >= 2) && (dudes%1000 == 3) && maxlv-minlv>party_share_level) { + int pl1=0,pl2=0,pl3=0; + pl1=search_character_index(p->member[oi[0]].name); + pl2=search_character_index(p->member[oi[1]].name); + pl3=search_character_index(p->member[oi[2]].name); + ShowDebug("PARTY: group of 3 Id1 %d lv %d name %s Id2 %d lv %d name %s Id3 %d lv %d name %s\n",pl1,p->member[oi[0]].lv,p->member[oi[0]].name,pl2,p->member[oi[1]].lv,p->member[oi[1]].name,pl3,p->member[oi[2]].lv,p->member[oi[2]].name); + if (char_married(pl1,pl2) && char_child(pl1,pl3)) + return 1; + if (char_married(pl1,pl3) && char_child(pl1,pl2)) + return 1; + if (char_married(pl2,pl3) && char_child(pl2,pl1)) + return 1; + } + return (maxlv==0 || maxlv-minlv<=party_share_level); +} + +// パ?ティが空かどうかチェック +int party_check_empty(struct party *p) { + int i; + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id > 0) { + return 0; + } + } + mapif_party_broken(p->party_id, 0); + idb_remove(party_db, p->party_id); + + return 1; +} + +// キャラの競合がないかチェック用 +int party_check_conflict_sub(DBKey key, void *data, va_list ap) { + struct party *p = (struct party *)data; + int party_id, account_id, char_id, i; + + party_id=va_arg(ap, int); + account_id=va_arg(ap, int); + char_id=va_arg(ap, int); + + if (p->party_id == party_id) //No conflict to check + return 0; + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id && p->member[i].char_id == char_id) { + ShowWarning("int_party: party conflict! %d %d %d\n", account_id, party_id, p->party_id); + mapif_parse_PartyLeave(-1, p->party_id, account_id, char_id); + } + } + + return 0; +} + +// キャラの競合がないかチェック +int party_check_conflict(int party_id, int account_id, int char_id) { + party_db->foreach(party_db, party_check_conflict_sub, party_id, account_id, char_id); + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// パ?ティ作成可否 +int mapif_party_created(int fd,int account_id, int char_id, struct party *p) { + WFIFOHEAD(fd, 39); + WFIFOW(fd,0) = 0x3820; + WFIFOL(fd,2) = account_id; + WFIFOL(fd,6) = char_id; + if (p != NULL) { + WFIFOB(fd,10) = 0; + WFIFOL(fd,11) = p->party_id; + memcpy(WFIFOP(fd,15), p->name, NAME_LENGTH); + ShowInfo("Created party (%d - %s)\n", p->party_id, p->name); + } else { + WFIFOB(fd,10) = 1; + WFIFOL(fd,11) = 0; + memset(WFIFOP(fd,15), 0, NAME_LENGTH); + } + WFIFOSET(fd,39); + return 0; +} + +// パ?ティ情報見つからず +int mapif_party_noinfo(int fd, int party_id) { + WFIFOHEAD(fd, 8); + WFIFOW(fd,0) = 0x3821; + WFIFOW(fd,2) = 8; + WFIFOL(fd,4) = party_id; + WFIFOSET(fd,8); + ShowWarning("int_party: info not found %d\n", party_id); + + return 0; +} + +// パ?ティ情報まとめ送り +int mapif_party_info(int fd, struct party *p) { + unsigned char buf[2048]; + + WBUFW(buf,0) = 0x3821; + memcpy(buf + 4, p, sizeof(struct party)); + WBUFW(buf,2) = 4 + sizeof(struct party); + if (fd < 0) + mapif_sendall(buf, WBUFW(buf,2)); + else + mapif_send(fd, buf, WBUFW(buf,2)); + return 0; +} + +// パ?ティメンバ追加可否 +int mapif_party_memberadded(int fd, int party_id, int account_id, int char_id, int flag) { + WFIFOHEAD(fd, 15); + WFIFOW(fd,0) = 0x3822; + WFIFOL(fd,2) = party_id; + WFIFOL(fd,6) = account_id; + WFIFOL(fd,10) = char_id; + WFIFOB(fd,14) = flag; + WFIFOSET(fd,15); + + return 0; +} + +// パ?ティ設定?更通知 +int mapif_party_optionchanged(int fd,struct party *p, int account_id, int flag) { + unsigned char buf[15]; + + WBUFW(buf,0) = 0x3823; + WBUFL(buf,2) = p->party_id; + WBUFL(buf,6) = account_id; + WBUFW(buf,10) = p->exp; + WBUFW(buf,12) = p->item; + WBUFB(buf,14) = flag; + if (flag == 0) + mapif_sendall(buf, 15); + else + mapif_send(fd, buf, 15); + return 0; +} + +//Checks whether the even-share setting of a party is broken when a character logs in. [Skotlex] +int inter_party_logged(int party_id, int account_id, int char_id) +{ + struct party *p; + int i; + if (!party_id) + return 0; + + p = idb_get(party_db, party_id); + if(p==NULL) + return 0; + for (i = 0; i < MAX_PARTY; i++) + if (p->member[i].account_id == account_id && p->member[i].char_id == char_id) + { + p->member[i].online = 1; + break; + } + if(p->exp && !party_check_exp_share(p)) + { + p->exp=0; + mapif_party_optionchanged(0,p,0,0); + return 1; + } + return 0; +} + +// パ?ティ?退通知 +int mapif_party_leaved(int party_id,int account_id, int char_id) { + unsigned char buf[16]; + + WBUFW(buf,0) = 0x3824; + WBUFL(buf,2) = party_id; + WBUFL(buf,6) = account_id; + WBUFL(buf,10) = char_id; + mapif_sendall(buf, 14); + return 0; +} + +// パ?ティマップ更新通知 +int mapif_party_membermoved(struct party *p, int idx) { + unsigned char buf[20]; + + WBUFW(buf,0) = 0x3825; + WBUFL(buf,2) = p->party_id; + WBUFL(buf,6) = p->member[idx].account_id; + WBUFL(buf,10) = p->member[idx].char_id; + WBUFW(buf,14) = p->member[idx].map; + WBUFB(buf,16) = p->member[idx].online; + WBUFW(buf,17) = p->member[idx].lv; + mapif_sendall(buf, 19); + return 0; +} + +// パ?ティ解散通知 +int mapif_party_broken(int party_id, int flag) { + unsigned char buf[7]; + WBUFW(buf,0) = 0x3826; + WBUFL(buf,2) = party_id; + WBUFB(buf,6) = flag; + mapif_sendall(buf, 7); + ShowInfo("Party broken (%d)\n", party_id); + + return 0; +} + +// パ?ティ??言 +int mapif_party_message(int party_id, int account_id, char *mes, int len, int sfd) { + unsigned char buf[2048]; + + WBUFW(buf,0) = 0x3827; + WBUFW(buf,2) = len + 12; + WBUFL(buf,4) = party_id; + WBUFL(buf,8) = account_id; + memcpy(WBUFP(buf,12), mes, len); + mapif_sendallwos(sfd, buf,len + 12); + + return 0; +} + +//------------------------------------------------------------------- +// map serverからの通信 + + +// パ?ティ +int mapif_parse_CreateParty(int fd, int account_id, int char_id, char *name, char *nick, unsigned short map, int lv, int item, int item2) { + struct party *p; + int i; + + for(i = 0; i < NAME_LENGTH && name[i]; i++) { + if (!(name[i] & 0xe0) || name[i] == 0x7f) { + ShowInfo("int_party: illegal party name [%s]\n", name); + mapif_party_created(fd, account_id, char_id, NULL); + return 0; + } + } + + if ((p = search_partyname(name)) != NULL) { + ShowInfo("int_party: same name party exists [%s]\n", name); + mapif_party_created(fd, account_id, char_id, NULL); + return 0; + } + p = (struct party *) aCalloc(sizeof(struct party), 1); + if (p == NULL) { + ShowFatalError("int_party: out of memory !\n"); + mapif_party_created(fd,account_id,char_id,NULL); + return 0; + } + p->party_id = party_newid++; + memcpy(p->name, name, NAME_LENGTH); + p->exp = 0; + p->item=(item?1:0)|(item2?2:0); + + p->member[0].account_id = account_id; + p->member[0].char_id = char_id; + memcpy(p->member[0].name, nick, NAME_LENGTH); + p->member[0].map = map; + p->member[0].leader = 1; + p->member[0].online = 1; + p->member[0].lv = lv; + + idb_put(party_db, p->party_id, p); + + mapif_party_created(fd, account_id, char_id, p); + mapif_party_info(fd, p); + + return 0; +} + +// パ?ティ情報要求 +int mapif_parse_PartyInfo(int fd, int party_id) { + struct party *p; + + p = idb_get(party_db, party_id); + if (p != NULL) + mapif_party_info(fd, p); + else + mapif_party_noinfo(fd, party_id); + + return 0; +} + +// パ?ティ追加要求 +int mapif_parse_PartyAddMember(int fd, int party_id, int account_id, int char_id, char *nick, unsigned short map, int lv) { + struct party *p; + int i; + + p = idb_get(party_db, party_id); + if (p == NULL) { + mapif_party_memberadded(fd, party_id, account_id, char_id, 1); + return 0; + } + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == 0) { + int flag = 0; + + p->member[i].account_id = account_id; + p->member[i].char_id = char_id; + memcpy(p->member[i].name, nick, NAME_LENGTH); + p->member[i].map = map; + p->member[i].leader = 0; + p->member[i].online = 1; + p->member[i].lv = lv; + mapif_party_memberadded(fd, party_id, account_id, char_id, 0); + mapif_party_info(-1, p); + + if (p->exp && !party_check_exp_share(p)) { + p->exp = 0; + flag = 0x01; + } + if (flag) + mapif_party_optionchanged(fd, p, 0, 0); + return 0; + } + } + mapif_party_memberadded(fd, party_id, account_id, char_id, 1); + + return 0; +} + +// パ?ティ?設定?更要求 +int mapif_parse_PartyChangeOption(int fd, int party_id, int account_id, int exp, int flag) { + struct party *p; + //NOTE: No clue what that flag is about, in all observations so far it always comes as 0. [Skotlex] + flag = 0; + + p = idb_get(party_db, party_id); + if (p == NULL) + return 0; + + p->exp = exp; + if (exp>0 && !party_check_exp_share(p)) { + flag |= 0x01; + p->exp = 0; + } + + mapif_party_optionchanged(fd, p, account_id, flag); + return 0; +} + +// パ?ティ?退要求 +int mapif_parse_PartyLeave(int fd, int party_id, int account_id, int char_id) { + struct party *p; + int i; + + p = idb_get(party_db, party_id); + if (p != NULL) { + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id && p->member[i].char_id == char_id) + { + mapif_party_leaved(party_id, account_id, char_id); + memset(&p->member[i], 0, sizeof(struct party_member)); + if (party_check_empty(p) == 0) + mapif_party_info(-1, p);// まだ人がいるのでデ?タ送信 + return 0; + } + } + } + + return 0; +} + +// パ?ティマップ更新要求 +int mapif_parse_PartyChangeMap(int fd, int party_id, int account_id, int char_id, unsigned short map, int online, int lv) { + struct party *p; + int i; + + p = idb_get(party_db, party_id); + if (p == NULL) + return 0; + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id && p->member[i].char_id == char_id) + { + p->member[i].map = map; + p->member[i].online = online; + if (p->member[i].lv != lv) { + p->member[i].lv = lv; + if (p->exp && !party_check_exp_share(p)) { + p->exp = 0; + mapif_party_optionchanged(fd, p, 0, 0); + } + } + mapif_party_membermoved(p, i); + break; + } + } + + return 0; +} + +// パ?ティ解散要求 +int mapif_parse_BreakParty(int fd, int party_id) { + struct party *p; + + p = idb_get(party_db, party_id); + if (p == NULL) + return 0; + + idb_remove(party_db, party_id); + mapif_party_broken(fd, party_id); + + return 0; +} + +// パ?ティメッセ?ジ送信 +int mapif_parse_PartyMessage(int fd, int party_id, int account_id, char *mes, int len) { + return mapif_party_message(party_id, account_id, mes, len, fd); +} +// パ?ティチェック要求 +int mapif_parse_PartyCheck(int fd, int party_id, int account_id, int char_id) { + return party_check_conflict(party_id, account_id, char_id); +} + +int mapif_parse_PartyLeaderChange(int fd,int party_id,int account_id,int char_id) +{ + struct party *p; + int i; + + p = idb_get(party_db, party_id); + if (p == NULL) + return 0; + + for (i = 0; i < MAX_PARTY; i++) + { + if(p->member[i].leader) + p->member[i].leader = 0; + if(p->member[i].account_id == account_id && p->member[i].char_id == char_id) + p->member[i].leader = 1; + } + return 1; +} + +// map server からの通信 +// ?1パケットのみ解析すること +// ?パケット長デ?タはinter.cにセットしておくこと +// ?パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ?エラ?なら0(false)、そうでないなら1(true)をかえさなければならない +int inter_party_parse_frommap(int fd) { + RFIFOHEAD(fd); + switch(RFIFOW(fd,0)) { + case 0x3020: mapif_parse_CreateParty(fd, RFIFOL(fd,2), RFIFOL(fd,6),(char*)RFIFOP(fd,10), (char*)RFIFOP(fd,34), RFIFOW(fd,58), RFIFOW(fd,60), RFIFOB(fd,62), RFIFOB(fd,63)); break; + case 0x3021: mapif_parse_PartyInfo(fd, RFIFOL(fd,2)); break; + case 0x3022: mapif_parse_PartyAddMember(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), (char*)RFIFOP(fd,14), RFIFOW(fd,38), RFIFOW(fd,40)); break; + case 0x3023: mapif_parse_PartyChangeOption(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOW(fd,10), RFIFOW(fd,12)); break; + case 0x3024: mapif_parse_PartyLeave(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x3025: mapif_parse_PartyChangeMap(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOW(fd,14), RFIFOB(fd,16), RFIFOW(fd,17)); break; + case 0x3026: mapif_parse_BreakParty(fd, RFIFOL(fd,2)); break; + case 0x3027: mapif_parse_PartyMessage(fd, RFIFOL(fd,4), RFIFOL(fd,8), (char*)RFIFOP(fd,12), RFIFOW(fd,2)-12); break; + case 0x3028: mapif_parse_PartyCheck(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x3029: mapif_parse_PartyLeaderChange(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + default: + return 0; + } + + return 1; +} + +// サ?バ?から?退要求(キャラ削除用) +int inter_party_leave(int party_id, int account_id, int char_id) { + return mapif_parse_PartyLeave(-1, party_id, account_id, char_id); +} + diff --git a/src/char/int_party.h b/src/char/int_party.h new file mode 100644 index 000000000..8b54948b0 --- /dev/null +++ b/src/char/int_party.h @@ -0,0 +1,18 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_PARTY_H_ +#define _INT_PARTY_H_ + +int inter_party_init(void); +void inter_party_final(void); +int inter_party_save(void); + +int inter_party_parse_frommap(int fd); + +int inter_party_leave(int party_id,int account_id, int char_id); +int inter_party_logged(int party_id, int account_id, int char_id); + +extern char party_txt[1024]; + +#endif diff --git a/src/char/int_pet.c b/src/char/int_pet.c new file mode 100644 index 000000000..a81acf3ed --- /dev/null +++ b/src/char/int_pet.c @@ -0,0 +1,380 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include + +#include "../common/mmo.h" +#include "../common/socket.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/showmsg.h" +#include "char.h" +#include "inter.h" +#include "int_pet.h" + +char pet_txt[1024]="save/pet.txt"; + +static struct dbt *pet_db; +static int pet_newid = 100; + +int inter_pet_tostr(char *str,struct s_pet *p) +{ + int len; + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + len=sprintf(str,"%d,%d,%s\t%d,%d,%d,%d,%d,%d,%d,%d,%d", + p->pet_id,p->class_,p->name,p->account_id,p->char_id,p->level,p->egg_id, + p->equip,p->intimate,p->hungry,p->rename_flag,p->incuvate); + + return 0; +} + +int inter_pet_fromstr(char *str,struct s_pet *p) +{ + int s; + int tmp_int[16]; + char tmp_str[256]; + + memset(p,0,sizeof(struct s_pet)); + +// printf("sscanf pet main info\n"); + s=sscanf(str,"%d,%d,%[^\t]\t%d,%d,%d,%d,%d,%d,%d,%d,%d",&tmp_int[0],&tmp_int[1],tmp_str,&tmp_int[2], + &tmp_int[3],&tmp_int[4],&tmp_int[5],&tmp_int[6],&tmp_int[7],&tmp_int[8],&tmp_int[9],&tmp_int[10]); + + if(s!=12) + return 1; + + p->pet_id = tmp_int[0]; + p->class_ = tmp_int[1]; + memcpy(p->name,tmp_str,NAME_LENGTH-1); + p->account_id = tmp_int[2]; + p->char_id = tmp_int[3]; + p->level = tmp_int[4]; + p->egg_id = tmp_int[5]; + p->equip = tmp_int[6]; + p->intimate = tmp_int[7]; + p->hungry = tmp_int[8]; + p->rename_flag = tmp_int[9]; + p->incuvate = tmp_int[10]; + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + return 0; +} + +int inter_pet_init() +{ + char line[8192]; + struct s_pet *p; + FILE *fp; + int c=0; + + pet_db= db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + if( (fp=fopen(pet_txt,"r"))==NULL ) + return 1; + while(fgets(line,sizeof(line),fp)){ + p = (struct s_pet*)aCalloc(sizeof(struct s_pet), 1); + if(p==NULL){ + ShowFatalError("int_pet: out of memory!\n"); + exit(0); + } + memset(p,0,sizeof(struct s_pet)); + if(inter_pet_fromstr(line,p)==0 && p->pet_id>0){ + if( p->pet_id >= pet_newid) + pet_newid=p->pet_id+1; + idb_put(pet_db,p->pet_id,p); + }else{ + ShowError("int_pet: broken data [%s] line %d\n",pet_txt,c); + aFree(p); + } + c++; + } + fclose(fp); +// printf("int_pet: %s read done (%d pets)\n",pet_txt,c); + return 0; +} + +void inter_pet_final() +{ + pet_db->destroy(pet_db, NULL); + return; +} + +int inter_pet_save_sub(DBKey key,void *data,va_list ap) +{ + char line[8192]; + FILE *fp; + inter_pet_tostr(line,(struct s_pet *)data); + fp=va_arg(ap,FILE *); + fprintf(fp,"%s" RETCODE,line); + return 0; +} + +int inter_pet_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(pet_txt,&lock))==NULL ){ + ShowError("int_pet: cant write [%s] !!! data is lost !!!\n",pet_txt); + return 1; + } + pet_db->foreach(pet_db,inter_pet_save_sub,fp); + lock_fclose(fp,pet_txt,&lock); +// printf("int_pet: %s saved.\n",pet_txt); + return 0; +} + +int inter_pet_delete(int pet_id) +{ + struct s_pet *p; + p = idb_get(pet_db,pet_id); + if( p == NULL) + return 1; + else { + idb_remove(pet_db,pet_id); + ShowInfo("Deleted pet (pet_id: %d)\n",pet_id); + } + return 0; +} + +int mapif_pet_created(int fd,int account_id,struct s_pet *p) +{ + WFIFOHEAD(fd, 11); + WFIFOW(fd,0)=0x3880; + WFIFOL(fd,2)=account_id; + if(p!=NULL){ + WFIFOB(fd,6)=0; + WFIFOL(fd,7)=p->pet_id; + ShowInfo("Created pet (%d - %s)\n",p->pet_id,p->name); + }else{ + WFIFOB(fd,6)=1; + WFIFOL(fd,7)=0; + } + WFIFOSET(fd,11); + + return 0; +} + +int mapif_pet_info(int fd,int account_id,struct s_pet *p) +{ + WFIFOHEAD(fd, sizeof(struct s_pet) + 9); + WFIFOW(fd,0)=0x3881; + WFIFOW(fd,2)=sizeof(struct s_pet) + 9; + WFIFOL(fd,4)=account_id; + WFIFOB(fd,8)=0; + memcpy(WFIFOP(fd,9),p,sizeof(struct s_pet)); + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +int mapif_pet_noinfo(int fd,int account_id) +{ + WFIFOHEAD(fd, sizeof(struct s_pet) + 9); + WFIFOW(fd,0)=0x3881; + WFIFOW(fd,2)=sizeof(struct s_pet) + 9; + WFIFOL(fd,4)=account_id; + WFIFOB(fd,8)=1; + memset(WFIFOP(fd,9),0,sizeof(struct s_pet)); + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +int mapif_save_pet_ack(int fd,int account_id,int flag) +{ + WFIFOHEAD(fd, 7); + WFIFOW(fd,0)=0x3882; + WFIFOL(fd,2)=account_id; + WFIFOB(fd,6)=flag; + WFIFOSET(fd,7); + + return 0; +} + +int mapif_delete_pet_ack(int fd,int flag) +{ + WFIFOHEAD(fd, 3); + WFIFOW(fd,0)=0x3883; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,3); + + return 0; +} + +int mapif_create_pet(int fd,int account_id,int char_id,short pet_class,short pet_lv,short pet_egg_id, + short pet_equip,short intimate,short hungry,char rename_flag,char incuvate,char *pet_name) +{ + struct s_pet *p; + p= (struct s_pet *) aCalloc(sizeof(struct s_pet), 1); + if(p==NULL){ + ShowFatalError("int_pet: out of memory !\n"); + mapif_pet_created(fd,account_id,NULL); + return 0; + } +// memset(p,0,sizeof(struct s_pet)); unnecessary after aCalloc [Skotlex] + p->pet_id = pet_newid++; + memcpy(p->name,pet_name,NAME_LENGTH-1); + if(incuvate == 1) + p->account_id = p->char_id = 0; + else { + p->account_id = account_id; + p->char_id = char_id; + } + p->class_ = pet_class; + p->level = pet_lv; + p->egg_id = pet_egg_id; + p->equip = pet_equip; + p->intimate = intimate; + p->hungry = hungry; + p->rename_flag = rename_flag; + p->incuvate = incuvate; + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + idb_put(pet_db,p->pet_id,p); + + mapif_pet_created(fd,account_id,p); + + return 0; +} + +int mapif_load_pet(int fd,int account_id,int char_id,int pet_id) +{ + struct s_pet *p; + p= idb_get(pet_db,pet_id); + if(p!=NULL) { + if(p->incuvate == 1) { + p->account_id = p->char_id = 0; + mapif_pet_info(fd,account_id,p); + } + else if(account_id == p->account_id && char_id == p->char_id) + mapif_pet_info(fd,account_id,p); + else + mapif_pet_noinfo(fd,account_id); + } + else + mapif_pet_noinfo(fd,account_id); + + return 0; +} + +static void* create_pet(DBKey key, va_list args) { + struct s_pet *p; + p=(struct s_pet *)aCalloc(sizeof(struct s_pet),1); + p->pet_id = key.i; + return p; +} +int mapif_save_pet(int fd,int account_id,struct s_pet *data) +{ + struct s_pet *p; + int pet_id, len; + RFIFOHEAD(fd); + len=RFIFOW(fd,2); + + if(sizeof(struct s_pet)!=len-8) { + ShowError("inter pet: data size error %d %d\n",sizeof(struct s_pet),len-8); + } + else{ + pet_id = data->pet_id; + if (pet_id == 0) + pet_id = data->pet_id = pet_newid++; + p= idb_ensure(pet_db,pet_id,create_pet); + if(data->hungry < 0) + data->hungry = 0; + else if(data->hungry > 100) + data->hungry = 100; + if(data->intimate < 0) + data->intimate = 0; + else if(data->intimate > 1000) + data->intimate = 1000; + memcpy(p,data,sizeof(struct s_pet)); + if(p->incuvate == 1) + p->account_id = p->char_id = 0; + + mapif_save_pet_ack(fd,account_id,0); + } + + return 0; +} + +int mapif_delete_pet(int fd,int pet_id) +{ + mapif_delete_pet_ack(fd,inter_pet_delete(pet_id)); + + return 0; +} + +int mapif_parse_CreatePet(int fd) +{ + RFIFOHEAD(fd); + mapif_create_pet(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOW(fd,10),RFIFOW(fd,12),RFIFOW(fd,14),RFIFOW(fd,16),RFIFOL(fd,18), + RFIFOL(fd,20),RFIFOB(fd,22),RFIFOB(fd,23),(char*)RFIFOP(fd,24)); + return 0; +} + +int mapif_parse_LoadPet(int fd) +{ + RFIFOHEAD(fd); + mapif_load_pet(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); + return 0; +} + +int mapif_parse_SavePet(int fd) +{ + RFIFOHEAD(fd); + mapif_save_pet(fd,RFIFOL(fd,4),(struct s_pet *)RFIFOP(fd,8)); + return 0; +} + +int mapif_parse_DeletePet(int fd) +{ + RFIFOHEAD(fd); + mapif_delete_pet(fd,RFIFOL(fd,2)); + return 0; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_pet_parse_frommap(int fd) +{ + RFIFOHEAD(fd); + switch(RFIFOW(fd,0)){ + case 0x3080: mapif_parse_CreatePet(fd); break; + case 0x3081: mapif_parse_LoadPet(fd); break; + case 0x3082: mapif_parse_SavePet(fd); break; + case 0x3083: mapif_parse_DeletePet(fd); break; + default: + return 0; + } + return 1; +} + diff --git a/src/char/int_pet.h b/src/char/int_pet.h new file mode 100644 index 000000000..0495994fe --- /dev/null +++ b/src/char/int_pet.h @@ -0,0 +1,16 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_PET_H_ +#define _INT_PET_H_ + +int inter_pet_init(void); +void inter_pet_final(void); +int inter_pet_save(void); +int inter_pet_delete(int pet_id); + +int inter_pet_parse_frommap(int fd); + +extern char pet_txt[1024]; + +#endif diff --git a/src/char/int_status.c b/src/char/int_status.c new file mode 100644 index 000000000..3c3b65dc4 --- /dev/null +++ b/src/char/int_status.c @@ -0,0 +1,187 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include + +#include "int_status.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" + +static struct dbt * scdata_db = NULL; //Contains all the status change data in-memory. [Skotlex] +char scdata_txt[1024]="save/scdata.txt"; //By [Skotlex] + +#ifdef ENABLE_SC_SAVING +static void* create_scdata(DBKey key, va_list args) { + struct scdata *data; + data = aCalloc(1, sizeof(struct scdata)); + data->account_id = va_arg(args, int); + data->char_id = key.i; + return data; +} + +/*========================================== + * Loads status change data of the player given. [Skotlex] + *------------------------------------------ + */ +struct scdata* status_search_scdata(int aid, int cid) +{ + struct scdata *data; + data = scdata_db->ensure(scdata_db, i2key(cid), create_scdata, aid); + return data; +} + +/*========================================== + * Deletes status change data of the player given. [Skotlex] + * Should be invoked after the data of said player was successfully loaded. + *------------------------------------------ + */ +void status_delete_scdata(int aid, int cid) +{ + struct scdata *scdata = idb_remove(scdata_db, cid); + if (scdata) + { + if (scdata->data) + aFree(scdata->data); + aFree(scdata); + } +} + + +static void inter_status_tostr(char* line, struct scdata *sc_data) +{ + int i, len; + + len = sprintf(line, "%d,%d,%d\t", sc_data->account_id, sc_data->char_id, sc_data->count); + for(i = 0; i < sc_data->count; i++) { + len += sprintf(line + len, "%d,%d,%d,%d,%d,%d\t", sc_data->data[i].type, sc_data->data[i].tick, + sc_data->data[i].val1, sc_data->data[i].val2, sc_data->data[i].val3, sc_data->data[i].val4); + } + return; +} + +static int inter_scdata_fromstr(char *line, struct scdata *sc_data) +{ + int i, len, next; + + if (sscanf(line,"%d,%d,%d\t%n",&sc_data->account_id, &sc_data->char_id, &sc_data->count, &next) < 3) + return 0; + + if (sc_data->count < 1) + return 0; + + sc_data->data = aCalloc(sc_data->count, sizeof (struct status_change_data)); + + for (i = 0; i < sc_data->count; i++) + { + if (sscanf(line + next, "%hu,%d,%d,%d,%d,%d\t%n", &sc_data->data[i].type, &sc_data->data[i].tick, + &sc_data->data[i].val1, &sc_data->data[i].val2, &sc_data->data[i].val3, &sc_data->data[i].val4, &len) < 6) + { + aFree(sc_data->data); + return 0; + } + next+=len; + } + return 1; +} +/*========================================== + * Loads all scdata from the given filename. + *------------------------------------------ + */ +void status_load_scdata(const char* filename) +{ + FILE *fp; + int sd_count=0, sc_count=0; + char line[8192]; + struct scdata *sc; + + if ((fp = fopen(filename, "r")) == NULL) { + ShowError("status_load_scdata: Cannot open file %s!\n", filename); + return; + } + + while(fgets(line, sizeof(line) - 1, fp)) { + sc = (struct scdata*)aCalloc(1, sizeof(struct scdata)); + if (inter_scdata_fromstr(line, sc)) { + sd_count++; + sc_count+= sc->count; + sc = idb_put(scdata_db, sc->char_id, sc); + if (sc) { + ShowError("Duplicate entry in %s for character %d\n", filename, sc->char_id); + if (sc->data) aFree(sc->data); + aFree(sc); + } + } else { + ShowError("status_load_scdata: Broken line data: %s\n", line); + aFree(sc); + } + } + fclose(fp); + ShowStatus("Loaded %d saved status changes for %d characters.\n", sc_count, sd_count); +} + +static int inter_status_save_sub(DBKey key, void *data, va_list ap) { + char line[8192]; + struct scdata * sc_data; + FILE *fp; + + sc_data = (struct scdata *)data; + if (sc_data->count < 1) + return 0; + + fp = va_arg(ap, FILE *); + inter_status_tostr(line, sc_data); + fprintf(fp, "%s" RETCODE, line); + return 0; +} + +/*========================================== + * Saves all scdata to the given filename. + *------------------------------------------ + */ +void inter_status_save() +{ + FILE *fp; + int lock; + + if ((fp = lock_fopen(scdata_txt, &lock)) == NULL) { + ShowError("int_status: cant write [%s] !!! data is lost !!!\n", scdata_txt); + return; + } + scdata_db->foreach(scdata_db, inter_status_save_sub, fp); + lock_fclose(fp,scdata_txt, &lock); +} + +/*========================================== + * Initializes db. + *------------------------------------------ + */ +void status_init() +{ + scdata_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_BASE,sizeof(int)); + status_load_scdata(scdata_txt); +} + +/*========================================== + * Frees up memory. + *------------------------------------------ + */ +static int scdata_db_final(DBKey k,void *d,va_list ap) +{ + struct scdata *data = (struct scdata*)d; + if (data->data) + aFree(data->data); + aFree(data); + return 0; +} + +/*========================================== + * Final cleanup. + *------------------------------------------ + */ +void status_final(void) +{ + scdata_db->destroy(scdata_db, scdata_db_final); +} +#endif diff --git a/src/char/int_status.h b/src/char/int_status.h new file mode 100644 index 000000000..0e22fb201 --- /dev/null +++ b/src/char/int_status.h @@ -0,0 +1,24 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef __INT_STATUS__ +#define __INT_STATUS__ + +#include "char.h" + +struct scdata { + int account_id, char_id; + int count; + struct status_change_data* data; +}; + +extern char scdata_txt[1024]; + +#ifdef ENABLE_SC_SAVING +struct scdata *status_search_scdata(int aid, int cid); +void status_delete_scdata(int aid, int cid); +void inter_status_save(void); +void status_init(void); +void status_final(void); +#endif +#endif diff --git a/src/char/int_storage.c b/src/char/int_storage.c new file mode 100644 index 000000000..f8ae42c0f --- /dev/null +++ b/src/char/int_storage.c @@ -0,0 +1,476 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include + +#include "../common/mmo.h" +#include "../common/socket.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/showmsg.h" +#include "char.h" +#include "inter.h" +#include "int_storage.h" +#include "int_pet.h" +#include "int_guild.h" + +// ファイル名のデフォルト +// inter_config_read()で再設定される +char storage_txt[1024]="save/storage.txt"; +char guild_storage_txt[1024]="save/g_storage.txt"; + +static struct dbt *storage_db; +static struct dbt *guild_storage_db; + +// 倉庫データを文字列に変換 +int storage_tostr(char *str,struct storage *p) +{ + int i,j,f=0; + char *str_p = str; + str_p += sprintf(str_p,"%d,%d\t",p->account_id,p->storage_amount); + + for(i=0;istorage_[i].nameid) && (p->storage_[i].amount) ){ + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d", + p->storage_[i].id,p->storage_[i].nameid,p->storage_[i].amount,p->storage_[i].equip, + p->storage_[i].identify,p->storage_[i].refine,p->storage_[i].attribute); + for(j=0; jstorage_[i].card[j]); + str_p += sprintf(str_p," "); + f++; + } + + *(str_p++)='\t'; + + *str_p='\0'; + if(!f) + str[0]=0; + return 0; +} + +// 文字列を倉庫データに変換 +int storage_fromstr(char *str,struct storage *p) +{ + int tmp_int[256]; + char tmp_str[256]; + int set,next,len,i,j; + + set=sscanf(str,"%d,%d%n",&tmp_int[0],&tmp_int[1],&next); + p->storage_amount=tmp_int[1]; + + if(set!=2) + return 1; + if(str[next]=='\n' || str[next]=='\r') + return 0; + next++; + for(i=0;str[next] && str[next]!='\t' && i < MAX_STORAGE;i++){ + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str, &len) == 8) { + p->storage_[i].id = tmp_int[0]; + p->storage_[i].nameid = tmp_int[1]; + p->storage_[i].amount = tmp_int[2]; + p->storage_[i].equip = tmp_int[3]; + p->storage_[i].identify = tmp_int[4]; + p->storage_[i].refine = tmp_int[5]; + p->storage_[i].attribute = tmp_int[6]; + + for(j = 0; j < MAX_SLOTS && tmp_str && sscanf(tmp_str, ",%d%[0-9,-]",&tmp_int[0], tmp_str) > 0; j++) + p->storage_[i].card[j] = tmp_int[0]; + + next += len; + if (str[next] == ' ') + next++; + } + else return 1; + } + if (i >= MAX_STORAGE && str[next] && str[next]!='\t') + ShowWarning("storage_fromstr: Found a storage line with more items than MAX_STORAGE (%d), remaining items have been discarded!\n", MAX_STORAGE); + return 0; +} + +int guild_storage_tostr(char *str,struct guild_storage *p) +{ + int i,j,f=0; + char *str_p = str; + str_p+=sprintf(str,"%d,%d\t",p->guild_id,p->storage_amount); + + for(i=0;istorage_[i].nameid) && (p->storage_[i].amount) ){ + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d", + p->storage_[i].id,p->storage_[i].nameid,p->storage_[i].amount,p->storage_[i].equip, + p->storage_[i].identify,p->storage_[i].refine,p->storage_[i].attribute); + for(j=0; jstorage_[i].card[j]); + str_p += sprintf(str_p," "); + f++; + } + + *(str_p++)='\t'; + + *str_p='\0'; + if(!f) + str[0]=0; + return 0; +} + +int guild_storage_fromstr(char *str,struct guild_storage *p) +{ + int tmp_int[256]; + char tmp_str[256]; + int set,next,len,i,j; + + set=sscanf(str,"%d,%d%n",&tmp_int[0],&tmp_int[1],&next); + p->storage_amount=tmp_int[1]; + + if(set!=2) + return 1; + if(str[next]=='\n' || str[next]=='\r') + return 0; + next++; + for(i=0;str[next] && str[next]!='\t' && i < MAX_GUILD_STORAGE;i++){ + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str, &len) == 8) + { + p->storage_[i].id = tmp_int[0]; + p->storage_[i].nameid = tmp_int[1]; + p->storage_[i].amount = tmp_int[2]; + p->storage_[i].equip = tmp_int[3]; + p->storage_[i].identify = tmp_int[4]; + p->storage_[i].refine = tmp_int[5]; + p->storage_[i].attribute = tmp_int[6]; + for(j = 0; j < MAX_SLOTS && tmp_str && sscanf(tmp_str, ",%d%[0-9,-]",&tmp_int[0], tmp_str) > 0; j++) + p->storage_[i].card[j] = tmp_int[0]; + next += len; + if (str[next] == ' ') + next++; + } + else return 1; + } + if (i >= MAX_GUILD_STORAGE && str[next] && str[next]!='\t') + ShowWarning("guild_storage_fromstr: Found a storage line with more items than MAX_GUILD_STORAGE (%d), remaining items have been discarded!\n", MAX_GUILD_STORAGE); + return 0; +} + +static void* create_storage(DBKey key, va_list args) { + struct storage *s; + s = (struct storage *) aCalloc(sizeof(struct storage), 1); + s->account_id=key.i; + return s; +} + +// アカウントから倉庫データインデックスを得る(新規倉庫追加可能) +struct storage *account2storage(int account_id) +{ + struct storage *s; + s= idb_ensure(storage_db, account_id, create_storage); + return s; +} + +static void* create_guildstorage(DBKey key, va_list args) { + struct guild_storage *gs = NULL; + gs = (struct guild_storage *) aCalloc(sizeof(struct guild_storage), 1); + gs->guild_id=key.i; + return gs; +} + +struct guild_storage *guild2storage(int guild_id) +{ + struct guild_storage *gs = NULL; + if(inter_guild_search(guild_id) != NULL) + gs= idb_ensure(guild_storage_db, guild_id, create_guildstorage); + return gs; +} + +//--------------------------------------------------------- +// 倉庫データを読み込む +int inter_storage_init() +{ + char line[65536]; + int c=0,tmp_int; + struct storage *s; + struct guild_storage *gs; + FILE *fp; + + storage_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + fp=fopen(storage_txt,"r"); + if(fp==NULL){ + ShowError("cant't read : %s\n",storage_txt); + return 1; + } + while(fgets(line,65535,fp)){ + sscanf(line,"%d",&tmp_int); + s = (struct storage*)aCalloc(sizeof(struct storage), 1); + if(s==NULL){ + ShowFatalError("int_storage: out of memory!\n"); + exit(0); + } +// memset(s,0,sizeof(struct storage)); aCalloc does this... + s->account_id=tmp_int; + if(s->account_id > 0 && storage_fromstr(line,s) == 0) { + idb_put(storage_db,s->account_id,s); + } + else{ + ShowError("int_storage: broken data [%s] line %d\n",storage_txt,c); + aFree(s); + } + c++; + } + fclose(fp); + + c = 0; + guild_storage_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + fp=fopen(guild_storage_txt,"r"); + if(fp==NULL){ + ShowError("cant't read : %s\n",guild_storage_txt); + return 1; + } + while(fgets(line,65535,fp)){ + sscanf(line,"%d",&tmp_int); + gs = (struct guild_storage*)aCalloc(sizeof(struct guild_storage), 1); + if(gs==NULL){ + ShowFatalError("int_storage: out of memory!\n"); + exit(0); + } +// memset(gs,0,sizeof(struct guild_storage)); aCalloc... + gs->guild_id=tmp_int; + if(gs->guild_id > 0 && guild_storage_fromstr(line,gs) == 0) { + idb_put(guild_storage_db,gs->guild_id,gs); + } + else{ + ShowError("int_storage: broken data [%s] line %d\n",guild_storage_txt,c); + aFree(gs); + } + c++; + } + fclose(fp); + + return 0; +} + +void inter_storage_final() { + storage_db->destroy(storage_db, NULL); + guild_storage_db->destroy(guild_storage_db, NULL); + return; +} + +int inter_storage_save_sub(DBKey key,void *data,va_list ap) +{ + char line[65536]; + FILE *fp; + storage_tostr(line,(struct storage *)data); + fp=va_arg(ap,FILE *); + if(*line) + fprintf(fp,"%s" RETCODE,line); + return 0; +} +//--------------------------------------------------------- +// 倉庫データを書き込む +int inter_storage_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(storage_txt,&lock))==NULL ){ + ShowError("int_storage: cant write [%s] !!! data is lost !!!\n",storage_txt); + return 1; + } + storage_db->foreach(storage_db,inter_storage_save_sub,fp); + lock_fclose(fp,storage_txt,&lock); +// printf("int_storage: %s saved.\n",storage_txt); + return 0; +} + +int inter_guild_storage_save_sub(DBKey key,void *data,va_list ap) +{ + char line[65536]; + FILE *fp; + if(inter_guild_search(((struct guild_storage *)data)->guild_id) != NULL) { + guild_storage_tostr(line,(struct guild_storage *)data); + fp=va_arg(ap,FILE *); + if(*line) + fprintf(fp,"%s" RETCODE,line); + } + return 0; +} +//--------------------------------------------------------- +// 倉庫データを書き込む +int inter_guild_storage_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(guild_storage_txt,&lock))==NULL ){ + ShowError("int_storage: cant write [%s] !!! data is lost !!!\n",guild_storage_txt); + return 1; + } + guild_storage_db->foreach(guild_storage_db,inter_guild_storage_save_sub,fp); + lock_fclose(fp,guild_storage_txt,&lock); +// printf("int_storage: %s saved.\n",guild_storage_txt); + return 0; +} + +// 倉庫データ削除 +int inter_storage_delete(int account_id) +{ + struct storage *s = idb_get(storage_db,account_id); + if(s) { + int i; + for(i=0;istorage_amount;i++){ + if(s->storage_[i].card[0] == (short)0xff00) + inter_pet_delete( MakeDWord(s->storage_[i].card[1],s->storage_[i].card[2]) ); + } + idb_remove(storage_db,account_id); + } + return 0; +} + +// ギルド倉庫データ削除 +int inter_guild_storage_delete(int guild_id) +{ + struct guild_storage *gs = idb_get(guild_storage_db,guild_id); + if(gs) { + int i; + for(i=0;istorage_amount;i++){ + if(gs->storage_[i].card[0] == (short)0xff00) + inter_pet_delete( MakeDWord(gs->storage_[i].card[1],gs->storage_[i].card[2]) ); + } + idb_remove(guild_storage_db,guild_id); + } + return 0; +} + +//--------------------------------------------------------- +// map serverへの通信 + +// 倉庫データの送信 +int mapif_load_storage(int fd,int account_id) +{ + struct storage *s=account2storage(account_id); + WFIFOHEAD(fd, sizeof(struct storage)+8); + WFIFOW(fd,0)=0x3810; + WFIFOW(fd,2)=sizeof(struct storage)+8; + WFIFOL(fd,4)=account_id; + memcpy(WFIFOP(fd,8),s,sizeof(struct storage)); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +// 倉庫データ保存完了送信 +int mapif_save_storage_ack(int fd,int account_id) +{ + WFIFOHEAD(fd, 7); + WFIFOW(fd,0)=0x3811; + WFIFOL(fd,2)=account_id; + WFIFOB(fd,6)=0; + WFIFOSET(fd,7); + return 0; +} + +int mapif_load_guild_storage(int fd,int account_id,int guild_id) +{ + struct guild_storage *gs=guild2storage(guild_id); + WFIFOHEAD(fd, sizeof(struct guild_storage)+12); + WFIFOW(fd,0)=0x3818; + if(gs) { + WFIFOW(fd,2)=sizeof(struct guild_storage)+12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=guild_id; + memcpy(WFIFOP(fd,12),gs,sizeof(struct guild_storage)); + } + else { + WFIFOW(fd,2)=12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=0; + } + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} +int mapif_save_guild_storage_ack(int fd,int account_id,int guild_id,int fail) +{ + WFIFOHEAD(fd, 11); + WFIFOW(fd,0)=0x3819; + WFIFOL(fd,2)=account_id; + WFIFOL(fd,6)=guild_id; + WFIFOB(fd,10)=fail; + WFIFOSET(fd,11); + return 0; +} + +//--------------------------------------------------------- +// map serverからの通信 + +// 倉庫データ要求受信 +int mapif_parse_LoadStorage(int fd) +{ + RFIFOHEAD(fd); + mapif_load_storage(fd,RFIFOL(fd,2)); + return 0; +} +// 倉庫データ受信&保存 +int mapif_parse_SaveStorage(int fd) +{ + struct storage *s; + int account_id, len; + RFIFOHEAD(fd); + account_id=RFIFOL(fd,4); + len=RFIFOW(fd,2); + if(sizeof(struct storage)!=len-8){ + ShowError("inter storage: data size error %d %d\n",sizeof(struct storage),len-8); + } + else { + s=account2storage(account_id); + memcpy(s,RFIFOP(fd,8),sizeof(struct storage)); + mapif_save_storage_ack(fd,account_id); + } + return 0; +} + +int mapif_parse_LoadGuildStorage(int fd) +{ + RFIFOHEAD(fd); + mapif_load_guild_storage(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + return 0; +} +int mapif_parse_SaveGuildStorage(int fd) +{ + struct guild_storage *gs; + int guild_id, len; + RFIFOHEAD(fd); + guild_id=RFIFOL(fd,8); + len=RFIFOW(fd,2); + if(sizeof(struct guild_storage)!=len-12){ + ShowError("inter storage: data size error %d %d\n",sizeof(struct guild_storage),len-12); + } + else { + gs=guild2storage(guild_id); + if(gs) { + memcpy(gs,RFIFOP(fd,12),sizeof(struct guild_storage)); + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,0); + } + else + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,1); + } + return 0; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_storage_parse_frommap(int fd) +{ + RFIFOHEAD(fd); + switch(RFIFOW(fd,0)){ + case 0x3010: mapif_parse_LoadStorage(fd); break; + case 0x3011: mapif_parse_SaveStorage(fd); break; + case 0x3018: mapif_parse_LoadGuildStorage(fd); break; + case 0x3019: mapif_parse_SaveGuildStorage(fd); break; + default: + return 0; + } + return 1; +} diff --git a/src/char/int_storage.h b/src/char/int_storage.h new file mode 100644 index 000000000..678312b1d --- /dev/null +++ b/src/char/int_storage.h @@ -0,0 +1,19 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_STORAGE_H_ +#define _INT_STORAGE_H_ + +int inter_storage_init(void); +void inter_storage_final(void); +int inter_storage_save(void); +int inter_guild_storage_save(void); +int inter_storage_delete(int account_id); +int inter_guild_storage_delete(int guild_id); + +int inter_storage_parse_frommap(int fd); + +extern char storage_txt[1024]; +extern char guild_storage_txt[1024]; + +#endif diff --git a/src/char/inter.c b/src/char/inter.c new file mode 100644 index 000000000..adedc6a7b --- /dev/null +++ b/src/char/inter.c @@ -0,0 +1,656 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include + +#include "../common/db.h" +#include "../common/mmo.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/malloc.h" +#include "../common/lock.h" +#include "../common/showmsg.h" + +#include "char.h" +#include "inter.h" +#include "int_party.h" +#include "int_guild.h" +#include "int_status.h" +#include "int_storage.h" +#include "int_pet.h" + +#define WISDATA_TTL (60*1000) // Existence time of Wisp/page data (60 seconds) + // that is the waiting time of answers of all map-servers +#define WISDELLIST_MAX 256 // Number of elements of Wisp/page data deletion list + +char inter_log_filename[1024] = "log/inter.log"; +char main_chat_nick[16] = "Main"; + +char accreg_txt[1024] = "save/accreg.txt"; +static struct dbt *accreg_db = NULL; + +struct accreg { + int account_id, char_id; + int reg_num; + struct global_reg reg[ACCOUNT_REG_NUM]; +}; + +int party_share_level = 10; +int kick_on_disconnect = 1; + +// 送信パケット長リスト +int inter_send_packet_length[] = { + -1,-1,27,-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3000-0x300f + -1, 7, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, + 35,-1,11,15, 34,29, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, + 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, + 9, 9,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; +// recv. packet list +int inter_recv_packet_length[] = { + -1,-1, 7,-1, -1,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3000-0x300f + 6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, //0x3010-0x301f + 64, 6,42,14, 14,19, 6,-1, 14,14, 0, 0, 0, 0, 0, 0, //0x3020-0x302f + -1, 6,-1,-1, 55,19, 6,-1, 14,-1,-1,-1, 14,19,186,-1, //0x3030-0x303f + 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3080-0x308f +}; + +struct WisData { + int id, fd, count, len; + unsigned long tick; + unsigned char src[24], dst[24], msg[1024]; +}; +static struct dbt * wis_db = NULL; +static int wis_dellist[WISDELLIST_MAX], wis_delnum; + + +//-------------------------------------------------------- + +// アカウント変数を文字列へ変換 +int inter_accreg_tostr(char *str, struct accreg *reg) { + int j; + char *p = str; + + p += sprintf(p, "%d\t", reg->account_id); + for(j = 0; j < reg->reg_num; j++) { + p += sprintf(p,"%s,%s ", reg->reg[j].str, reg->reg[j].value); + } + + return 0; +} + +// アカウント変数を文字列から変換 +int inter_accreg_fromstr(const char *str, struct accreg *reg) { + int j, n; + const char *p = str; + + if (sscanf(p, "%d\t%n", ®->account_id, &n ) != 1 || reg->account_id <= 0) + return 1; + + for(j = 0, p += n; j < ACCOUNT_REG_NUM; j++, p += n) { + if (sscanf(p, "%[^,],%[^ ] %n", reg->reg[j].str, reg->reg[j].value, &n) != 2) + break; + } + reg->reg_num = j; + + return 0; +} + +// アカウント変数の読み込み +int inter_accreg_init(void) { + char line[8192]; + FILE *fp; + int c = 0; + struct accreg *reg; + + accreg_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + if( (fp = fopen(accreg_txt, "r")) == NULL) + return 1; + while(fgets(line, sizeof(line)-1, fp)){ + line[sizeof(line)-1] = '\0'; + + reg = (struct accreg*)aCalloc(sizeof(struct accreg), 1); + if (reg == NULL) { + ShowFatalError("inter: accreg: out of memory!\n"); + exit(0); + } + if (inter_accreg_fromstr(line, reg) == 0 && reg->account_id > 0) { + idb_put(accreg_db, reg->account_id, reg); + } else { + ShowError("inter: accreg: broken data [%s] line %d\n", accreg_txt, c); + aFree(reg); + } + c++; + } + fclose(fp); +// printf("inter: %s read done (%d)\n", accreg_txt, c); + + return 0; +} + +// アカウント変数のセーブ用 +int inter_accreg_save_sub(DBKey key, void *data, va_list ap) { + char line[8192]; + FILE *fp; + struct accreg *reg = (struct accreg *)data; + + if (reg->reg_num > 0) { + inter_accreg_tostr(line,reg); + fp = va_arg(ap, FILE *); + fprintf(fp, "%s" RETCODE, line); + } + + return 0; +} + +// アカウント変数のセーブ +int inter_accreg_save(void) { + FILE *fp; + int lock; + + if ((fp = lock_fopen(accreg_txt,&lock)) == NULL) { + ShowError("int_accreg: cant write [%s] !!! data is lost !!!\n", accreg_txt); + return 1; + } + accreg_db->foreach(accreg_db, inter_accreg_save_sub,fp); + lock_fclose(fp, accreg_txt, &lock); +// printf("inter: %s saved.\n", accreg_txt); + + return 0; +} + +//-------------------------------------------------------- + +/*========================================== + * 設定ファイルを読み込む + *------------------------------------------ + */ +int inter_config_read(const char *cfgName) { + 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) - 1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + line[sizeof(line)-1] = '\0'; + + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if (strcmpi(w1, "storage_txt") == 0) { + strncpy(storage_txt, w2, sizeof(storage_txt)); + } else if (strcmpi(w1, "party_txt") == 0) { + strncpy(party_txt, w2, sizeof(party_txt)); + } else if (strcmpi(w1, "guild_txt") == 0) { + strncpy(guild_txt, w2, sizeof(guild_txt)); + } else if (strcmpi(w1, "pet_txt") == 0) { + strncpy(pet_txt, w2, sizeof(pet_txt)); + } else if (strcmpi(w1, "castle_txt") == 0) { + strncpy(castle_txt, w2, sizeof(castle_txt)); + } else if (strcmpi(w1, "accreg_txt") == 0) { + strncpy(accreg_txt, w2, sizeof(accreg_txt)); + } else if (strcmpi(w1, "guild_storage_txt") == 0) { + strncpy(guild_storage_txt, w2, sizeof(guild_storage_txt)); + } else if (strcmpi(w1, "kick_on_disconnect") == 0) { + kick_on_disconnect = atoi(w2); + } else if (strcmpi(w1, "party_share_level") == 0) { + party_share_level = atoi(w2); + if (party_share_level < 0) + party_share_level = 0; + } else if (strcmpi(w1, "inter_log_filename") == 0) { + strncpy(inter_log_filename, w2, sizeof(inter_log_filename)); + } else if(strcmpi(w1,"log_inter")==0) { + log_inter = atoi(w2); + } else if(strcmpi(w1, "main_chat_nick")==0){ // Main chat nick [LuzZza] + strcpy(main_chat_nick, w2); // + } else if (strcmpi(w1, "import") == 0) { + inter_config_read(w2); + } + } + fclose(fp); + + return 0; +} + +// ログ書き出し +int inter_log(char *fmt,...) { + FILE *logfp; + va_list ap; + + va_start(ap,fmt); + logfp = fopen(inter_log_filename, "a"); + if (logfp) { + vfprintf(logfp, fmt, ap); + fclose(logfp); + } + va_end(ap); + + return 0; +} + +// セーブ +int inter_save(void) { +#ifdef ENABLE_SC_SAVING + inter_status_save(); +#endif + inter_party_save(); + inter_guild_save(); + inter_storage_save(); + inter_guild_storage_save(); + inter_pet_save(); + inter_accreg_save(); + + return 0; +} + +// 初期化 +int inter_init(const char *file) { + inter_config_read(file); + + wis_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + inter_party_init(); + inter_guild_init(); + inter_storage_init(); + inter_pet_init(); + inter_accreg_init(); + + return 0; +} + +// finalize +void inter_final(void) { + accreg_db->destroy(accreg_db, NULL); + wis_db->destroy(wis_db, NULL); + + inter_party_final(); + inter_guild_final(); + inter_storage_final(); + inter_pet_final(); + + return; +} + +// マップサーバー接続 +int inter_mapif_init(int fd) { + inter_guild_mapif_init(fd); + + return 0; +} + +//-------------------------------------------------------- +// sended packets to map-server + +//Sends to map server the current max Account/Char id [Skotlex] +void mapif_send_maxid(int account_id, int char_id) +{ + unsigned char buf[12]; + + WBUFW(buf,0) = 0x2b07; + WBUFL(buf,2) = account_id; + WBUFL(buf,6) = char_id; + mapif_sendall(buf, 10); +} + +// GMメッセージ送信 +int mapif_GMmessage(unsigned char *mes, int len, unsigned long color, int sfd) { + unsigned char buf[2048]; + + if (len > 2048) len = 2047; //Make it fit to avoid crashes. [Skotlex] + WBUFW(buf,0) = 0x3800; + WBUFW(buf,2) = len; + WBUFL(buf,4) = color; + memcpy(WBUFP(buf,8), mes, len - 8); + mapif_sendallwos(sfd, buf, len); +// printf("inter server: GM:%d %s\n", len, mes); + + return 0; +} + +// Wisp/page transmission to all map-server +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; +} + +// Wisp/page transmission result to map-server +int mapif_wis_end(struct WisData *wd, int flag) { + unsigned char buf[27]; + + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), wd->src, 24); + WBUFB(buf,26) = flag; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(wd->fd, buf, 27); +// printf("inter server wis_end: flag: %d\n", flag); + + return 0; +} + +// アカウント変数送信 +int mapif_account_reg(int fd, unsigned char *src) { + unsigned char *buf = aCalloc(1,WBUFW(src,2)); + + memcpy(WBUFP(buf,0),src,WBUFW(src,2)); + WBUFW(buf, 0) = 0x3804; + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + + aFree(buf); + + return 0; +} + +// アカウント変数要求返信 +int mapif_account_reg_reply(int fd,int account_id, int char_id) { + struct accreg *reg = idb_get(accreg_db,account_id); + + WFIFOHEAD(fd, ACCOUNT_REG_NUM * 288+ 13); + WFIFOW(fd,0) = 0x3804; + WFIFOL(fd,4) = account_id; + WFIFOL(fd,8) = char_id; + WFIFOB(fd,12) = 2; //Acc Reg + if (reg == NULL) { + WFIFOW(fd,2) = 13; + } else { + int i, p; + for (p=13,i = 0; i < reg->reg_num; i++) { + p+= sprintf(WFIFOP(fd,p), "%s", reg->reg[i].str)+1; //We add 1 to consider the '\0' in place. + p+= sprintf(WFIFOP(fd,p), "%s", reg->reg[i].value)+1; + } + WFIFOW(fd,2)=p; + } + WFIFOSET(fd,WFIFOW(fd,2)); + 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) + return -1; + + WFIFOHEAD(fd, 7); + WFIFOW(fd,0) = 0x2b1f; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = reason; + WFIFOSET(fd,7); + + return 0; +} + +//-------------------------------------------------------- + +// Existence check of WISP data +int check_ttl_wisdata_sub(DBKey key, void *data, va_list ap) { + unsigned long tick; + struct WisData *wd = (struct WisData *)data; + tick = va_arg(ap, unsigned long); + + if (DIFF_TICK(tick, wd->tick) > WISDATA_TTL && wis_delnum < WISDELLIST_MAX) + wis_dellist[wis_delnum++] = wd->id; + + return 0; +} + +int check_ttl_wisdata(void) { + unsigned long tick = gettick(); + int i; + + do { + wis_delnum = 0; + wis_db->foreach(wis_db, check_ttl_wisdata_sub, tick); + for(i = 0; i < wis_delnum; i++) { + struct WisData *wd = 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 wisper, 1: target character is not loged in?, 2: ignored by target + idb_remove(wis_db, wd->id); + } + } while(wis_delnum >= WISDELLIST_MAX); + + return 0; +} + +//-------------------------------------------------------- +// received packets from map-server + +// GMメッセージ送信 +int mapif_parse_GMmessage(int fd) { + RFIFOHEAD(fd); + mapif_GMmessage(RFIFOP(fd,8), RFIFOW(fd,2), RFIFOL(fd,4), fd); + + return 0; +} + +// Wisp/page request to send +int mapif_parse_WisRequest(int fd) { + struct WisData* wd; + char name[NAME_LENGTH]; + static int wisid = 0; + int index; + RFIFOHEAD(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) { // normaly, impossible, but who knows... + ShowError("inter: Wis message doesn't exist.\n"); + return 0; + } + + memcpy(name, RFIFOP(fd,28), NAME_LENGTH); //Received name may be too large and not contain \0! [Skotlex] + name[NAME_LENGTH-1]= '\0'; + // search if character exists before to ask all map-servers + if ((index = search_character_index(name)) == -1) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), NAME_LENGTH); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + // Character exists. So, ask all map-servers + } else { + // to be sure of the correct name, rewrite it + memset(name, 0, NAME_LENGTH); + strncpy(name, search_character_name(index), NAME_LENGTH); + // if source is destination, don't ask other servers. + if (strcmp((char*)RFIFOP(fd,4),name) == 0) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), NAME_LENGTH); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + } else { + + wd = (struct WisData *)aCalloc(sizeof(struct WisData), 1); + if (wd == NULL){ + ShowFatalError("inter: WisRequest: out of memory !\n"); + return 0; + } + + // Whether the failure of previous wisp/page transmission (timeout) + 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 = gettick(); + idb_put(wis_db, wd->id, wd); + mapif_wis_message(wd); + } + } + + return 0; +} + +// Wisp/page transmission result +int mapif_parse_WisReply(int fd) { + int id, flag; + struct WisData *wd; + RFIFOHEAD(fd); + id = RFIFOL(fd,2); + flag = RFIFOB(fd,6); + wd = 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 wisper, 1: target character is not loged 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 .w .24B .w .?B + RFIFOHEAD(fd); + memcpy(WBUFP(buf,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf, 0) = 0x3803; + mapif_sendall(buf, RFIFOW(fd,2)); + + return 0; +} + +static void* create_accreg(DBKey key, va_list args) { + struct accreg *reg; + reg = (struct accreg*)aCalloc(sizeof(struct accreg), 1); + reg->account_id = key.i; + return reg; +} + +// アカウント変数保存要求 +int mapif_parse_Registry(int fd) { + int j, p, len; + struct accreg *reg; + RFIFOHEAD(fd); + + switch (RFIFOB(fd,12)) { + case 3: //Character registry + return char_parse_Registry(RFIFOL(fd,4), RFIFOL(fd,8), RFIFOP(fd,13), RFIFOW(fd,2)-13); + case 2: //Acc Reg + break; + case 1: //Acc Reg2, forward to login + return save_accreg2(RFIFOP(fd,4), RFIFOW(fd,2)-4); + default: //Error? + return 1; + } + reg = idb_ensure(accreg_db, RFIFOL(fd,4), create_accreg); + + for(j=0,p=13;jreg[j].str,&len); + reg->reg[j].str[len]='\0'; + p +=len+1; //+1 to skip the '\0' between strings. + sscanf(RFIFOP(fd,p), "%255c%n",reg->reg[j].value,&len); + reg->reg[j].value[len]='\0'; + p +=len+1; + } + reg->reg_num=j; + mapif_account_reg(fd, RFIFOP(fd,0)); // 他のMAPサーバーに送信 + + return 0; +} + +// Request the value of all registries. +int mapif_parse_RegistryRequest(int fd) +{ + //Load Char Registry + if (RFIFOB(fd,12)) + char_account_reg_reply(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + //Load Account Registry + if (RFIFOB(fd,11)) + mapif_account_reg_reply(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + //Ask Login Server for Account2 values. + if (RFIFOB(fd,10)) + request_accreg2(RFIFOL(fd,2),RFIFOL(fd,6)-2); + return 1; +} + +//-------------------------------------------------------- + +// map server からの通信(1パケットのみ解析すること) +// エラーなら0(false)、処理できたなら1、 +// パケット長が足りなければ2をかえさなければならない +int inter_parse_frommap(int fd) { + int cmd, len; + RFIFOHEAD(fd); + cmd = RFIFOW(fd,0); + len = 0; + + // inter鯖管轄かを調べる + if (cmd < 0x3000 || cmd >= 0x3000 + (sizeof(inter_recv_packet_length) / sizeof(inter_recv_packet_length[0]))) + return 0; + + if (inter_recv_packet_length[cmd-0x3000] == 0) //This is necessary, because otherwise we return 2 and the char server will just hang waiting for packets! [Skotlex] + return 0; + + // パケット長を調べる + if ((len = inter_check_length(fd, inter_recv_packet_length[cmd - 0x3000])) == 0) + return 2; + + switch(cmd) { + case 0x3000: mapif_parse_GMmessage(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; + default: + if (inter_party_parse_frommap(fd)) + break; + if (inter_guild_parse_frommap(fd)) + break; + if (inter_storage_parse_frommap(fd)) + break; + if (inter_pet_parse_frommap(fd)) + break; + return 0; + } + RFIFOSKIP(fd, len); + return 1; +} + +// RFIFOのパケット長確認 +// 必要パケット長があればパケット長、まだ足りなければ0 +int inter_check_length(int fd, int length) { + if (length == -1) { // 可変パケット長 + RFIFOHEAD(fd); + if (RFIFOREST(fd) < 4) // パケット長が未着 + return 0; + length = RFIFOW(fd,2); + } + + if ((int)RFIFOREST(fd) < length) // パケットが未着 + return 0; + + return length; +} + diff --git a/src/char/inter.h b/src/char/inter.h new file mode 100644 index 000000000..ac8709cdc --- /dev/null +++ b/src/char/inter.h @@ -0,0 +1,28 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INTER_H_ +#define _INTER_H_ + +int inter_init(const char *file); +void inter_final(void); +int inter_save(void); +int inter_parse_frommap(int fd); +int inter_mapif_init(int fd); +void mapif_send_maxid(int, int); +int mapif_disconnectplayer(int fd, int account_id, int char_id, int reason); + +int inter_check_length(int fd,int length); + +int inter_log(char *fmt,...); + +#define inter_cfgName "conf/inter_athena.conf" + +extern int party_share_level; +extern int kick_on_disconnect; +extern char inter_log_filename[1024]; +extern int log_inter; + +extern char main_chat_nick[16]; + +#endif diff --git a/src/char_sql/Makefile b/src/char_sql/Makefile new file mode 100644 index 000000000..f67a95315 --- /dev/null +++ b/src/char_sql/Makefile @@ -0,0 +1,27 @@ +all sql: char-server_sql + +COMMON_OBJ = ../common/obj/core.o ../common/obj/socket.o ../common/obj/timer.o \ + ../common/obj/db.o ../common/obj/plugins.o ../common/obj/lock.o \ + ../common/obj/malloc.o ../common/obj/showmsg.o ../common/obj/utils.o \ + ../common/obj/strlib.o ../common/obj/graph.o ../common/obj/grfio.o \ + ../common/obj/mapindex.o ../common/obj/ers.o ../zlib/unz.o +COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h \ + ../common/version.h ../common/db.h ../common/plugins.h ../common/lock.h \ + ../common/malloc.h ../common/showmsg.h ../common/utils.h ../common/strlib.h \ + ../common/graph.h ../common/grfio.h ../common/mapindex.h + +char-server_sql: char.o inter.o int_party.o int_guild.o int_storage.o int_pet.o itemdb.o $(COMMON_OBJ) + $(CC) -o ../../$@ $^ $(LIB_S) + +clean: + rm -f *.o ../../char-server_sql + +# DO NOT DELETE + +char.o: char.c char.h ../common/strlib.h itemdb.h ../common/showmsg.h +inter.o: inter.c inter.h int_party.h int_guild.h int_storage.h int_pet.h ../common/mmo.h char.h ../common/socket.h ../common/showmsg.h +int_party.o: int_party.c int_party.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/timer.h ../common/db.h ../common/showmsg.h +int_guild.o: int_guild.c int_guild.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/db.h ../common/showmsg.h +int_storage.o: int_storage.c int_storage.h char.h itemdb.h ../common/showmsg.h +int_pet.o: int_pet.c int_pet.h inter.h char.h ../common/mmo.h ../common/socket.h ../common/db.h ../common/showmsg.h +itemdb.o: itemdb.c itemdb.h ../common/db.h ../common/mmo.h ../common/showmsg.h diff --git a/src/char_sql/char.c b/src/char_sql/char.c new file mode 100644 index 000000000..7faed1e87 --- /dev/null +++ b/src/char_sql/char.c @@ -0,0 +1,4307 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by Jioh L. Jung +// TXT 1.105 +#include + +#ifdef _WIN32 +#include +typedef long in_addr_t; +//#pragma lib // Not required [Lance] +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "char.h" +#include "../common/utils.h" +#include "../common/strlib.h" +#include "../common/showmsg.h" +#include "itemdb.h" +#include "inter.h" +#include "db.h" +#include "malloc.h" +#include "int_guild.h" + +static struct dbt *char_db_; + +char char_db[256] = "char"; +char scdata_db[256] = "sc_data"; +char cart_db[256] = "cart_inventory"; +char inventory_db[256] = "inventory"; +char charlog_db[256] = "charlog"; +char storage_db[256] = "storage"; +char interlog_db[256] = "interlog"; +char reg_db[256] = "global_reg_value"; +char skill_db[256] = "skill"; +char memo_db[256] = "memo"; +char guild_db[256] = "guild"; +char guild_alliance_db[256] = "guild_alliance"; +char guild_castle_db[256] = "guild_castle"; +char guild_expulsion_db[256] = "guild_expulsion"; +char guild_member_db[256] = "guild_member"; +char guild_position_db[256] = "guild_position"; +char guild_skill_db[256] = "guild_skill"; +char guild_storage_db[256] = "guild_storage"; +char party_db[256] = "party"; +char pet_db[256] = "pet"; +char friend_db[256] = "friends"; +int db_use_sqldbs; + +char login_db_account_id[32] = "account_id"; +char login_db_level[32] = "level"; + +int lowest_gm_level = 1; + +char *SQL_CONF_NAME = "conf/inter_athena.conf"; + +struct mmo_map_server server[MAX_MAP_SERVERS]; +int server_fd[MAX_MAP_SERVERS]; + +int login_fd, char_fd; +char userid[24]; +char passwd[24]; +char server_name[20]; +char wisp_server_name[NAME_LENGTH] = "Server"; +int login_ip_set_ = 0; +char login_ip_str[128]; +in_addr_t login_ip; +int login_port = 6900; +int char_ip_set_ = 0; +char char_ip_str[128]; +int bind_ip_set_ = 0; +char bind_ip_str[128]; +in_addr_t char_ip; +int char_port = 6121; +int char_maintenance; +int char_new; +int char_new_display; +int name_ignoring_case = 0; // Allow or not identical name for characters but with a different case by [Yor] +int char_name_option = 0; // Option to know which letters/symbols are authorised in the name of a character (0: all, 1: only those in char_name_letters, 2: all EXCEPT those in char_name_letters) by [Yor] +char char_name_letters[1024] = ""; // list of letters/symbols used to authorise or not a name of a character. by [Yor] +//The following are characters that are trimmed regardless because they cause confusion and problems on the servers. [Skotlex] +#define TRIM_CHARS "\032\t\n " +int char_per_account = 0; //Maximum charas per account (default unlimited) [Sirius] + +int log_char = 1; // loggin char or not [devil] +int log_inter = 1; // loggin inter or not [devil] + +char lan_map_ip[128]; // Lan map ip added by kashy +int subnetmaski[4]; // Subnetmask added by kashy +char unknown_char_name[NAME_LENGTH] = "Unknown"; +char db_path[1024]="db"; + +//These are used to aid the map server in identifying valid clients. [Skotlex] +static int max_account_id = DEFAULT_MAX_ACCOUNT_ID, max_char_id = DEFAULT_MAX_CHAR_ID; +static int online_check = 1; //If one, it won't let players connect when their account is already registered online and will send the relevant map server a kick user request. [Skotlex] + +struct char_session_data{ + int account_id, login_id1, login_id2,sex; + int found_char[9]; + char email[40]; // e-mail (default: a@a.com) by [Yor] + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id, char_id, login_id1, login_id2, ip, char_pos, delflag,sex; + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos = 0; + +int check_ip_flag = 1; // It's to check IP of a player between char-server and other servers (part of anti-hacking system) + +int char_id_count = START_CHAR_NUM; +struct mmo_charstatus *char_dat; +int char_num,char_max; +int max_connect_user = 0; +int gm_allow_level = 99; +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int save_log = 1; +int start_zeny = 500; +int start_weapon = 1201; +int start_armor = 2301; + +// check for exit signal +// 0 is saving complete +// other is char_id +unsigned int save_flag = 0; + +// start point (you can reset point on conf file) +struct point start_point = { 0, 53, 111}; + +struct gm_account *gm_account = NULL; +int GM_num = 0; + +int console = 0; + +//Structure for holding in memory which characters are online on the map servers connected. +struct online_char_data { + int account_id; + int char_id; + short server; + unsigned waiting_disconnect :1; +}; + +struct dbt *online_char_db; //Holds all online characters. + +#ifndef SQL_DEBUG + +#define mysql_query(_x, _y) mysql_real_query(_x, _y, strlen(_y)) //supports ' in names and runs faster [Kevin] + +#else + +#define mysql_query(_x, _y) debug_mysql_query(__FILE__, __LINE__, _x, _y) + +#endif + +static int chardb_waiting_disconnect(int tid, unsigned int tick, int id, int data); + +static void * create_online_char_data(DBKey key, va_list args) { + struct online_char_data* character; + character = aCalloc(1, sizeof(struct online_char_data)); + character->account_id = key.i; + character->char_id = -1; + character->server = -1; + return character; +} + +//------------------------------------------------- +// Set Character online/offline [Wizputer] +//------------------------------------------------- + +void set_char_online(int map_id, int char_id, int account_id) { + struct online_char_data* character; + if ( char_id != 99 ) { + sprintf(tmp_sql, "UPDATE `%s` SET `online`='1' WHERE `char_id`='%d'",char_db,char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + if (max_account_id < account_id || max_char_id < char_id) + { //Notify map-server of the new max IDs [Skotlex] + if (account_id > max_account_id) + max_account_id = account_id; + if (char_id > max_char_id) + max_char_id = char_id; + mapif_send_maxid(max_account_id, max_char_id); + } + } + + character = idb_ensure(online_char_db, account_id, create_online_char_data); + if (online_check && character->char_id != -1 && character->server > -1 && character->server != map_id) + { + ShowNotice("set_char_online: Character %d:%d marked in map server %d, but map server %d claims to have (%d:%d) online!\n", + character->account_id, character->char_id, character->server, map_id, account_id, char_id); + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + } + character->char_id = (char_id==99)?-1:char_id; + character->server = (char_id==99)?-1:map_id; + character->waiting_disconnect = 0; + if (char_id != 99) + { //Set char online in guild cache. If char is in memory, use the guild id on it, otherwise seek it. + struct mmo_charstatus *cp; + cp = idb_get(char_db_,char_id); + inter_guild_CharOnline(char_id, cp?cp->guild_id:-1); + } + if (login_fd <= 0 || session[login_fd]->eof) + return; + + WFIFOW(login_fd,0) = 0x272b; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); +} + +void set_char_offline(int char_id, int account_id) { + struct mmo_charstatus *cp; + struct online_char_data* character; + + if ( char_id == 99 ) + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `account_id`='%d'", char_db, account_id); + else { + cp = idb_get(char_db_,char_id); + inter_guild_CharOffline(char_id, cp?cp->guild_id:-1); + if (cp) + idb_remove(char_db_,char_id); + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `char_id`='%d'", char_db, char_id); + + if (mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if ((character = idb_get(online_char_db, account_id)) != NULL) + { //We don't free yet to avoid aCalloc/aFree spamming during char change. [Skotlex] + character->char_id = -1; + character->server = -1; + character->waiting_disconnect = 0; + } + + if (login_fd <= 0 || session[login_fd]->eof) + return; + + WFIFOW(login_fd,0) = 0x272c; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); +} + +static int char_db_setoffline(DBKey key, void* data, va_list ap) { + struct online_char_data* character = (struct online_char_data*)data; + int server = va_arg(ap, int); + if (server == -1) { + character->char_id = -1; + character->server = -1; + character->waiting_disconnect = 0; + } else if (character->server == server) + character->server = -2; //In some map server that we aren't connected to. + return 0; +} + +void set_all_offline(void) { + MYSQL_RES* sql_res2; //Needed because it is used inside inter_guild_CharOffline; [Skotlex] + int char_id; + sprintf(tmp_sql, "SELECT `account_id`, `char_id`, `guild_id` FROM `%s` WHERE `online`='1'",char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + ShowNotice("Sending all users offline.\n"); + sql_res2 = mysql_store_result(&mysql_handle); + if (sql_res2) { + while((sql_row = mysql_fetch_row(sql_res2))) { + char_id = atoi(sql_row[1]); + inter_guild_CharOffline(char_id, atoi(sql_row[2])); + + if ( login_fd > 0 ) { + ShowInfo("send user offline: %d\n",char_id); + WFIFOW(login_fd,0) = 0x272c; + WFIFOL(login_fd,2) = char_id; + WFIFOSET(login_fd,6); + } + } + mysql_free_result(sql_res2); + } + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `online`='1'", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + online_char_db->foreach(online_char_db,char_db_setoffline,-1); +} +//---------------------------------------------------------------------- +// Determine if an account (id) is a GM account +// and returns its level (or 0 if it isn't a GM account or if not found) +//---------------------------------------------------------------------- +// Removed since nothing GM related goes on in the char server [CLOWNISIUS] +int isGM(int account_id) { + int i; + + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == account_id) + return gm_account[i].level; + return 0; +} + + +int compare_item(struct item *a, struct item *b) { + + if(a->id == b->id && + a->nameid == b->nameid && + a->amount == b->amount && + a->equip == b->equip && + a->identify == b->identify && + a->refine == b->refine && + a->attribute == b->attribute) + { + int i; + for (i=0; icard[i]==b->card[i]; i++); + return (i == MAX_SLOTS); + } + return 0; +} + +static void* create_charstatus(DBKey key, va_list args) { + struct mmo_charstatus *cp; + cp = (struct mmo_charstatus *) aCalloc(1,sizeof(struct mmo_charstatus)); + cp->char_id = key.i; + return cp; +} + +int mmo_char_tosql(int char_id, struct mmo_charstatus *p){ + int i=0,j,party_exist,guild_exist; + int count = 0; + int diff = 0; + char *tmp_ptr; //Building a single query should be more efficient than running + //multiple queries for each thing about to be saved, right? [Skotlex] + char save_status[128]; //For displaying save information. [Skotlex] + struct mmo_charstatus *cp; + struct itemtmp mapitem[MAX_GUILD_STORAGE]; + + if (char_id!=p->char_id) return 0; + + cp = idb_ensure(char_db_, char_id, create_charstatus); + +// ShowInfo("Saving char "CL_WHITE"%d"CL_RESET" (%s)...\n",char_id,char_dat[0].name); + memset(save_status, 0, sizeof(save_status)); + diff = 0; + //map inventory data + for(i=0;iinventory[i], &cp->inventory[i])) + diff = 1; + if(p->inventory[i].nameid>0){ + mapitem[count].flag=0; + mapitem[count].id = p->inventory[i].id; + mapitem[count].nameid=p->inventory[i].nameid; + mapitem[count].amount = p->inventory[i].amount; + mapitem[count].equip = p->inventory[i].equip; + mapitem[count].identify = p->inventory[i].identify; + mapitem[count].refine = p->inventory[i].refine; + mapitem[count].attribute = p->inventory[i].attribute; + for (j=0; jinventory[i].card[j]; + count++; + } + } + //printf("- Save item data to MySQL!\n"); + if (diff) + if (!memitemdata_to_sql(mapitem, count, p->char_id,TABLE_INVENTORY)) + strcat(save_status, " inventory"); + + count = 0; + diff = 0; + + //map cart data + for(i=0;icart[i], &cp->cart[i])) + diff = 1; + if(p->cart[i].nameid>0){ + mapitem[count].flag=0; + mapitem[count].id = p->cart[i].id; + mapitem[count].nameid=p->cart[i].nameid; + mapitem[count].amount = p->cart[i].amount; + mapitem[count].equip = p->cart[i].equip; + mapitem[count].identify = p->cart[i].identify; + mapitem[count].refine = p->cart[i].refine; + mapitem[count].attribute = p->cart[i].attribute; + for (j=0; jcart[i].card[j]; + count++; + } + } + + if (diff) + if (!memitemdata_to_sql(mapitem, count, p->char_id,TABLE_CART)) + strcat(save_status, " cart"); + + if ( + (p->base_exp != cp->base_exp) || (p->base_level != cp->base_level) || + (p->job_level != cp->job_level) || (p->job_exp != cp->job_exp) || + (p->zeny != cp->zeny) || + (p->last_point.x != cp->last_point.x) || (p->last_point.y != cp->last_point.y) || + (p->max_hp != cp->max_hp) || (p->hp != cp->hp) || + (p->max_sp != cp->max_sp) || (p->sp != cp->sp) || + (p->status_point != cp->status_point) || (p->skill_point != cp->skill_point) || + (p->str != cp->str) || (p->agi != cp->agi) || (p->vit != cp->vit) || + (p->int_ != cp->int_) || (p->dex != cp->dex) || (p->luk != cp->luk) || + (p->option != cp->option) || + (p->party_id != cp->party_id) || (p->guild_id != cp->guild_id) || + (p->pet_id != cp->pet_id) || (p->weapon != cp->weapon) || + (p->shield != cp->shield) || (p->head_top != cp->head_top) || + (p->head_mid != cp->head_mid) || (p->head_bottom != cp->head_bottom) + ) + { //Save status + //Check for party + party_exist=1; + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `party_id` = '%d'",party_db, p->party_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else { //In case of failure, don't touch the data. [Skotlex + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) + party_exist = atoi(sql_row[0]); + mysql_free_result(sql_res); + } + + //check guild_exist + guild_exist=1; + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id` = '%d'",guild_db, p->guild_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else { //If we fail to confirm, don't touch the data. + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) + guild_exist = atoi(sql_row[0]); + mysql_free_result(sql_res); + } + + if (guild_exist==0) p->guild_id=0; + if (party_exist==0) p->party_id=0; + + //query + sprintf(tmp_sql ,"UPDATE `%s` SET `base_level`='%d', `job_level`='%d'," + "`base_exp`='%d', `job_exp`='%d', `zeny`='%d'," + "`max_hp`='%d',`hp`='%d',`max_sp`='%d',`sp`='%d',`status_point`='%d',`skill_point`='%d'," + "`str`='%d',`agi`='%d',`vit`='%d',`int`='%d',`dex`='%d',`luk`='%d'," + "`option`='%d',`party_id`='%d',`guild_id`='%d',`pet_id`='%d'," + "`weapon`='%d',`shield`='%d',`head_top`='%d',`head_mid`='%d',`head_bottom`='%d'," + "`last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d'" + " WHERE `account_id`='%d' AND `char_id` = '%d'", + char_db, p->base_level, p->job_level, + p->base_exp, p->job_exp, p->zeny, + p->max_hp, p->hp, p->max_sp, p->sp, p->status_point, p->skill_point, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, + p->option, p->party_id, p->guild_id, p->pet_id, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, + mapindex_id2name(p->last_point.map), p->last_point.x, p->last_point.y, + mapindex_id2name(p->save_point.map), p->save_point.x, p->save_point.y, + p->account_id, p->char_id + ); + + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " status"); + } + + //Values that will seldom change (to speed up saving) + if ( + (p->hair != cp->hair) || (p->hair_color != cp->hair_color) || (p->clothes_color != cp->clothes_color) || + (p->class_ != cp->class_) || + (p->partner_id != cp->partner_id) || (p->father != cp->father) || + (p->mother != cp->mother) || (p->child != cp->child) || + (p->karma != cp->karma) || (p->manner != cp->manner) || + (p->fame != cp->fame) + ) + { + sprintf(tmp_sql ,"UPDATE `%s` SET `class`='%d'," + "`hair`='%d',`hair_color`='%d',`clothes_color`='%d'," + "`partner_id`='%d', `father`='%d', `mother`='%d', `child`='%d'," + "`karma`='%d',`manner`='%d', `fame`='%d'" + " WHERE `account_id`='%d' AND `char_id` = '%d'", + char_db, p->class_, + p->hair, p->hair_color, p->clothes_color, + p->partner_id, p->father, p->mother, p->child, + p->karma, p->manner, p->fame, + p->account_id, p->char_id + ); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " status2"); + } + + + diff = 0; + + for(i=0;imemo_point[i].map == cp->memo_point[i].map && p->memo_point[i].x == cp->memo_point[i].x && p->memo_point[i].y == cp->memo_point[i].y) + continue; + diff = 1; + break; + } + + if (diff) + { //Save memo + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",memo_db, p->char_id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //insert here. + tmp_ptr = tmp_sql; + tmp_ptr += sprintf(tmp_ptr, "INSERT INTO `%s`(`char_id`,`map`,`x`,`y`) VALUES ", memo_db); + count = 0; + for(i=0;imemo_point[i].map){ + tmp_ptr += sprintf(tmp_ptr,"('%d', '%s', '%d', '%d'),", + char_id, mapindex_id2name(p->memo_point[i].map), p->memo_point[i].x, p->memo_point[i].y); + count++; + } + } + if (count) + { //Dangerous? Only if none of the above sprintf worked. [Skotlex] + tmp_ptr[-1] = '\0'; //Remove the trailing comma. + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " memo"); + } else //Memo Points cleared (how is this possible?). + strcat(save_status, " memo"); + } + + diff = 0; + for(i=0;iskill[i].lv != 0) && (p->skill[i].id == 0)) + p->skill[i].id = i; // Fix skill tree + + if((p->skill[i].id != cp->skill[i].id) || (p->skill[i].lv != cp->skill[i].lv) || + (p->skill[i].flag != cp->skill[i].flag)) + { + diff = 1; + break; + } + } + + if (diff) + { //Save skills + + //`skill` (`char_id`, `id`, `lv`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",skill_db, p->char_id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + tmp_ptr = tmp_sql; + tmp_ptr += sprintf(tmp_ptr,"INSERT INTO `%s`(`char_id`,`id`,`lv`) VALUES ", skill_db); + count = 0; + //insert here. + for(i=0;iskill[i].id && p->skill[i].flag!=1) + { + tmp_ptr += sprintf(tmp_ptr,"('%d','%d','%d'),", + char_id, p->skill[i].id, (p->skill[i].flag==0)?p->skill[i].lv:p->skill[i].flag-2); + count++; + } + } + + if (count) + { + tmp_ptr[-1] = '\0'; //Remove trailing comma. + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " skills"); + } else //Skills removed (reset?) + strcat(save_status, " skills"); + } +/* Saving of global registry values is now handled by the inter-server. [Skotlex] + diff = 0; + for(i=0;iglobal_reg_num;i++) { + if ((p->global_reg[i].str == NULL) && (cp->global_reg[i].str == NULL)) + continue; + if (((p->global_reg[i].str == NULL) != (cp->global_reg[i].str == NULL)) || + strcmp(p->global_reg[i].value, cp->global_reg[i].value) != 0 || + strcmp(p->global_reg[i].str, cp->global_reg[i].str) != 0 + ) { + diff = 1; + break; + } + } + + if (diff) + { //Save global registry. + //`global_reg_value` (`char_id`, `str`, `value`) + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, p->char_id); + if (mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //insert here. + tmp_ptr = tmp_sql; + tmp_ptr += sprintf(tmp_ptr,"INSERT INTO `%s` (`type`, `char_id`, `str`, `value`) VALUES", reg_db); + count = 0; + for(i=0;iglobal_reg_num;i++) + { + if (p->global_reg[i].str && p->global_reg[i].value) + { + tmp_ptr += sprintf(tmp_ptr,"('3','%d','%s','%s'),", + char_id, jstrescapecpy(temp_str,p->global_reg[i].str), jstrescapecpy(temp_str2,p->global_reg[i].value)); + if (++count%100 == 0) + { //Save every X registers to avoid overflowing tmp_sql [Skotlex] + tmp_ptr[-1] = '\0'; + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " global_reg"); + + tmp_ptr = tmp_sql; + tmp_ptr += sprintf(tmp_ptr,"INSERT INTO `%s` (`type`, `char_id`, `str`, `value`) VALUES", reg_db); + count = 0; + } + } + } + + if (count) + { + tmp_ptr[-1] = '\0'; + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " global_reg"); + } else //Values cleared. + strcat(save_status, " global_reg"); + } +*/ + diff = 0; + for(i = 0; i < MAX_FRIENDS; i++){ + if(p->friends[i].char_id != cp->friends[i].char_id || + p->friends[i].account_id != cp->friends[i].account_id){ + diff = 1; + break; + } + } + + if(diff == 1) + { //Save friends + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `char_id`='%d'", friend_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + tmp_ptr = tmp_sql; + tmp_ptr += sprintf(tmp_ptr, "INSERT INTO `%s` (`char_id`, `friend_account`, `friend_id`) VALUES ", friend_db); + count = 0; + for(i = 0; i < MAX_FRIENDS; i++){ + if(p->friends[i].char_id > 0) + { + tmp_ptr += sprintf(tmp_ptr, "('%d','%d','%d'),", char_id, p->friends[i].account_id, p->friends[i].char_id); + count++; + } + } + if (count) + { + tmp_ptr[-1] = '\0'; //Remove the last comma. [Skotlex] + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else + strcat(save_status, " friends"); + } else //Friend list cleared. + strcat(save_status, " friends"); + + } + + if (save_status[0]!='\0' && save_log) + ShowInfo("Saved char %d - %s:%s.\n", char_id, char_dat[0].name, save_status); + memcpy(cp, p, sizeof(struct mmo_charstatus)); + + return 0; +} + +// [Ilpalazzo-sama] +int memitemdata_to_sql(struct itemtmp mapitem[], int count, int char_id, int tableswitch) +{ + int i,j, flag, id; + char *tablename; + char selectoption[16]; + char * str_p = tmp_sql; + + switch (tableswitch) { + case TABLE_INVENTORY: + tablename = inventory_db; // no need for sprintf here as *_db are char*. + sprintf(selectoption,"char_id"); + break; + case TABLE_CART: + tablename = cart_db; + sprintf(selectoption,"char_id"); + break; + case TABLE_STORAGE: + tablename = storage_db; + sprintf(selectoption,"account_id"); + break; + case TABLE_GUILD_STORAGE: + tablename = guild_storage_db; + sprintf(selectoption,"guild_id"); + break; + default: + ShowError("Invalid table name!\n"); + return 1; + } + + //=======================================mysql database data > memory=============================================== + + str_p += sprintf(str_p, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`"); + + for (j=0; j SQL =============================== + if(!itemdb_isequip(mapitem[i].nameid)) + { //Quick update of stackable items. Update Qty and Equip should be enough, but in case we are also updating identify + sprintf(tmp_sql,"UPDATE `%s` SET `equip`='%d', `identify`='%d', `amount`='%d' WHERE `id`='%d' LIMIT 1", + tablename, mapitem[i].equip, mapitem[i].identify,mapitem[i].amount, id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } else + { //Equipment or Misc item, just update all fields. + str_p = tmp_sql; + str_p += sprintf(str_p,"UPDATE `%s` SET `equip`='%d', `identify`='%d', `refine`='%d',`attribute`='%d'", + tablename, mapitem[i].equip, mapitem[i].identify, mapitem[i].refine, mapitem[i].attribute); + + for(j=0; jchar_id = char_id; + if (save_log) + ShowInfo("Char load request (%d)\n", char_id); + //`char`( `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`, //9 + //`str`,`agi`,`vit`,`int`,`dex`,`luk`, //15 + //`max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point`, //21 + //`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`, //27 + //`hair`,`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`, //35 + //`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`) + //splite 2 parts. cause veeeery long SQL syntax + + sprintf(tmp_sql, "SELECT `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`," + "`str`,`agi`,`vit`,`int`,`dex`,`luk`, `max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point` FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); // TBR + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + if (!sql_row) + { //Just how does this happens? [Skotlex] + ShowError("Requested non-existant character id: %d!\n", char_id); + return 0; + } + + p->char_id = char_id; + p->account_id = atoi(sql_row[1]); + p->char_num = atoi(sql_row[2]); + strcpy(p->name, sql_row[3]); + p->class_ = atoi(sql_row[4]); + p->base_level = atoi(sql_row[5]); + p->job_level = atoi(sql_row[6]); + p->base_exp = atoi(sql_row[7]); + p->job_exp = atoi(sql_row[8]); + p->zeny = atoi(sql_row[9]); + p->str = atoi(sql_row[10]); + p->agi = atoi(sql_row[11]); + p->vit = atoi(sql_row[12]); + p->int_ = atoi(sql_row[13]); + p->dex = atoi(sql_row[14]); + p->luk = atoi(sql_row[15]); + p->max_hp = atoi(sql_row[16]); + p->hp = atoi(sql_row[17]); + p->max_sp = atoi(sql_row[18]); + p->sp = atoi(sql_row[19]); + p->status_point = atoi(sql_row[20]); + p->skill_point = atoi(sql_row[21]); + //free mysql result. + mysql_free_result(sql_res); + strcat (t_msg, " status"); + } else + ShowError("Load char failed (%d - table %s).\n", char_id, char_db); //Error?! ERRRRRR WHAT THAT SAY!? + + sprintf(tmp_sql, "SELECT `option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`hair`,`hair_color`," + "`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`," + "`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`, `partner_id`, `father`, `mother`, `child`, `fame`" + "FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) { + + p->option = atoi(sql_row[0]); p->karma = atoi(sql_row[1]); p->manner = atoi(sql_row[2]); + p->party_id = atoi(sql_row[3]); p->guild_id = atoi(sql_row[4]); p->pet_id = atoi(sql_row[5]); + + p->hair = atoi(sql_row[6]); p->hair_color = atoi(sql_row[7]); p->clothes_color = atoi(sql_row[8]); + p->weapon = atoi(sql_row[9]); p->shield = atoi(sql_row[10]); + p->head_top = atoi(sql_row[11]); p->head_mid = atoi(sql_row[12]); p->head_bottom = atoi(sql_row[13]); + p->last_point.map = mapindex_name2id(sql_row[14]); p->last_point.x = atoi(sql_row[15]); p->last_point.y = atoi(sql_row[16]); + p->save_point.map = mapindex_name2id(sql_row[17]); p->save_point.x = atoi(sql_row[18]); p->save_point.y = atoi(sql_row[19]); + p->partner_id = atoi(sql_row[20]); p->father = atoi(sql_row[21]); p->mother = atoi(sql_row[22]); p->child = atoi(sql_row[23]); + p->fame = atoi(sql_row[24]); + + //free mysql result. + mysql_free_result(sql_res); + strcat (t_msg, " status2"); + } else + ShowError("Char load failed (%d - table %s)\n", char_id, char_db); //Error?! ERRRRRR WHAT THAT SAY!? + + if (p->last_point.x == 0 || p->last_point.y == 0 || p->last_point.map == 0) + memcpy(&p->last_point, &start_point, sizeof(start_point)); + + if (p->save_point.x == 0 || p->save_point.y == 0 || p->save_point.map == 0) + memcpy(&p->save_point, &start_point, sizeof(start_point)); + + //read memo data + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + sprintf(tmp_sql, "SELECT `map`,`x`,`y` FROM `%s` WHERE `char_id`='%d' ORDER by `memo_id`",memo_db, char_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + p->memo_point[i].map = mapindex_name2id(sql_row[0]); + p->memo_point[i].x=atoi(sql_row[1]); + p->memo_point[i].y=atoi(sql_row[2]); + //i ++; + } + mysql_free_result(sql_res); + strcat (t_msg, " memo"); + } + + //read inventory + //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + str_p += sprintf(str_p, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`"); + + for (j=0; jinventory[i].id = atoi(sql_row[0]); + p->inventory[i].nameid = atoi(sql_row[1]); + p->inventory[i].amount = atoi(sql_row[2]); + p->inventory[i].equip = atoi(sql_row[3]); + p->inventory[i].identify = atoi(sql_row[4]); + p->inventory[i].refine = atoi(sql_row[5]); + p->inventory[i].attribute = atoi(sql_row[6]); + for (j=0; jinventory[i].card[j] = atoi(sql_row[7+j]); + } + mysql_free_result(sql_res); + strcat (t_msg, " inventory"); + } + + //read cart. + //`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + str_p = tmp_sql; + str_p += sprintf(str_p, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`"); + + for (j=0; jcart[i].id = atoi(sql_row[0]); + p->cart[i].nameid = atoi(sql_row[1]); + p->cart[i].amount = atoi(sql_row[2]); + p->cart[i].equip = atoi(sql_row[3]); + p->cart[i].identify = atoi(sql_row[4]); + p->cart[i].refine = atoi(sql_row[5]); + p->cart[i].attribute = atoi(sql_row[6]); + for(j=0; jcart[i].card[j] = atoi(sql_row[7+j]); + } + mysql_free_result(sql_res); + strcat (t_msg, " cart"); + } + + //read skill + //`skill` (`char_id`, `id`, `lv`) + sprintf(tmp_sql, "SELECT `id`, `lv` FROM `%s` WHERE `char_id`='%d'",skill_db, char_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + n = atoi(sql_row[0]); + p->skill[n].id = n; //memory!? shit!. + p->skill[n].lv = atoi(sql_row[1]); + } + mysql_free_result(sql_res); + strcat (t_msg, " skills"); + } +/* Global-reg loading is now handled by the inter-server. + //global_reg + //`global_reg_value` (`char_id`, `str`, `value`) + sprintf(tmp_sql, "SELECT `str`, `value` FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, char_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + i = 0; + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + strcpy (p->global_reg[i].str, sql_row[0]); + strcpy (p->global_reg[i].value, sql_row[1]); + } + mysql_free_result(sql_res); + strcat (t_msg, " reg_values"); + } + p->global_reg_num=i; +*/ + //Shamelessly stolen from its_sparky (ie: thanks) and then assimilated by [Skotlex] + //Friend list + sprintf(tmp_sql, "SELECT f.friend_account, f.friend_id, c.name FROM `%s` f LEFT JOIN `%s` c ON f.friend_account=c.account_id AND f.friend_id=c.char_id WHERE f.char_id='%d'", friend_db, char_db, char_id); + + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + if(sql_res) + { + for(i = 0; (sql_row = mysql_fetch_row(sql_res)) && ifriends[i].account_id = atoi(sql_row[0]); + p->friends[i].char_id = atoi(sql_row[1]); + strncpy(p->friends[i].name, sql_row[2], NAME_LENGTH-1); //The -1 is to avoid losing the ending \0 [Skotlex] + } + } + } + mysql_free_result(sql_res); + strcat (t_msg, " friends"); + } + + if (save_log) + ShowInfo("Loaded char (%d - %s): %s\n", char_id, p->name, t_msg); //ok. all data load successfuly! + + cp = idb_ensure(char_db_, char_id, create_charstatus); + memcpy(cp, p, sizeof(struct mmo_charstatus)); + return 1; +} + +// For quick selection of data when displaying the char menu. [Skotlex] +// +int mmo_char_fromsql_short(int char_id, struct mmo_charstatus *p){ + char t_msg[128]; + + memset(p, 0, sizeof(struct mmo_charstatus)); + t_msg[0]= '\0'; + + p->char_id = char_id; +// ShowInfo("Quick Char load request (%d)\n", char_id); + //`char`( `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`, //9 + //`str`,`agi`,`vit`,`int`,`dex`,`luk`, //15 + //`max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point`, //21 + //`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`, //27 + //`hair`,`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`, //35 + //`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`) + //splite 2 parts. cause veeeery long SQL syntax + + sprintf(tmp_sql, "SELECT `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`," + "`str`,`agi`,`vit`,`int`,`dex`,`luk`, `max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point` FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); // TBR + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + if (!sql_row) + { //Just how does this happens? [Skotlex] + ShowError("Requested non-existant character id: %d!\n", char_id); + return 0; + } + p->char_id = char_id; + p->account_id = atoi(sql_row[1]); + p->char_num = atoi(sql_row[2]); + strcpy(p->name, sql_row[3]); + p->class_ = atoi(sql_row[4]); + p->base_level = atoi(sql_row[5]); + p->job_level = atoi(sql_row[6]); + p->base_exp = atoi(sql_row[7]); + p->job_exp = atoi(sql_row[8]); + p->zeny = atoi(sql_row[9]); + p->str = atoi(sql_row[10]); + p->agi = atoi(sql_row[11]); + p->vit = atoi(sql_row[12]); + p->int_ = atoi(sql_row[13]); + p->dex = atoi(sql_row[14]); + p->luk = atoi(sql_row[15]); + p->max_hp = atoi(sql_row[16]); + p->hp = atoi(sql_row[17]); + p->max_sp = atoi(sql_row[18]); + p->sp = atoi(sql_row[19]); + p->status_point = atoi(sql_row[20]); + p->skill_point = atoi(sql_row[21]); + //free mysql result. + mysql_free_result(sql_res); + strcat (t_msg, " status"); + } else + ShowError("Load char failed (%d - table %s).\n", char_id, char_db); //Error?! ERRRRRR WHAT THAT SAY!? + + sprintf(tmp_sql, "SELECT `option`,`karma`,`manner`,`hair`,`hair_color`," + "`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`" + "FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) { + + p->option = atoi(sql_row[0]); p->karma = atoi(sql_row[1]); p->manner = atoi(sql_row[2]); + p->hair = atoi(sql_row[3]); p->hair_color = atoi(sql_row[4]); p->clothes_color = atoi(sql_row[5]); + p->weapon = atoi(sql_row[6]); p->shield = atoi(sql_row[7]); + p->head_top = atoi(sql_row[8]); p->head_mid = atoi(sql_row[9]); p->head_bottom = atoi(sql_row[10]); + + //free mysql result. + mysql_free_result(sql_res); + strcat (t_msg, " status2"); + } else + ShowError("Char load failed (%d - table %s)\n", char_id, char_db); //Error?! ERRRRRR WHAT THAT SAY!? + + if (save_log) + ShowInfo("Quick Loaded char (%d - %s): %s\n", char_id, p->name, t_msg); //ok. all data load successfuly! + + return 1; +} +//========================================================================================================== +int mmo_char_sql_init(void) { + int charcount; + + + ShowInfo("Begin Initializing.......\n"); + char_db_= db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA, sizeof(int)); + // memory initialize + // no need to set twice size in this routine. but some cause segmentation error. :P + // The hell? Why segmentation faults? Sounds like a bug that needs addressing... [Skotlex] + ShowDebug("initializing char memory...(%d byte)\n",sizeof(struct mmo_charstatus)*2); + CREATE(char_dat, struct mmo_charstatus, 2); + + memset(char_dat, 0, sizeof(struct mmo_charstatus)*2); + + //Check for max id (in case new chars would get their IDs set below 150K) [Skotlex] + sprintf(tmp_sql , "SELECT max(`char_id`) FROM `%s`", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else { + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) + { + if (mysql_num_rows(sql_res) > 0 && + (sql_row = mysql_fetch_row(sql_res)) != NULL && + sql_row[0] != NULL && atoi(sql_row[0]) >= char_id_count) + char_id_count = 0; //No need for setting the char id. + mysql_free_result(sql_res); + } + } + + sprintf(tmp_sql, "SELECT `char_id` FROM `%s`", char_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + }else{ + sql_res = mysql_store_result(&mysql_handle); + if(sql_res){ + charcount = (int)mysql_num_rows(sql_res); + ShowStatus("total char data -> '%d'.......\n", charcount); + mysql_free_result(sql_res); + }else{ + ShowStatus("total char data -> '0'.......\n"); + } + } + + if(char_per_account == 0){ + ShowStatus("Chars per Account: 'Unlimited'.......\n"); + }else{ + ShowStatus("Chars per Account: '%d'.......\n", char_per_account); + } + + //the 'set offline' part is now in check_login_conn ... + //if the server connects to loginserver + //it will dc all off players + //and send the loginserver the new state.... + + ShowInfo("Finished initilizing.......\n"); + + return 0; +} + +//========================================================================================================== + +int make_new_char_sql(int fd, unsigned char *dat) { + struct char_session_data *sd; + char name[NAME_LENGTH]; + char t_name[NAME_LENGTH*2]; + unsigned int i; // Used in for loop and comparing with strlen, safe to be unsigned. [Lance] + int char_id, temp; + + strncpy(name, dat, NAME_LENGTH); + name[NAME_LENGTH-1] = '\0'; //Always terminate string. + trim(name,TRIM_CHARS); //Trim character name. [Skotlex] + jstrescapecpy(t_name, name); + + // disabled until fixed >.> + // Note: escape characters should be added to jstrescape()! + //mysql_real_escape_string(&mysql_handle, t_name, t_name_temp, sizeof(t_name_temp)); + + if (!session_isValid(fd) || !(sd = (struct char_session_data*)session[fd]->session_data)) + return -2; + + ShowInfo("New character request (%d)\n", sd->account_id); + + //check name != main chat nick [LuzZza] + if(strcmpi(name, main_chat_nick) == 0) { + ShowInfo("Create char failed (%d): this nick (%s) reserved for mainchat messages.\n", sd->account_id, name); + return -2; + } + + //check for charcount (maxchars) :) + if(char_per_account != 0){ + sprintf(tmp_sql, "SELECT `account_id` FROM `%s` WHERE `account_id` = '%d'", char_db, sd->account_id); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if(sql_res){ + //ok + temp = (int)mysql_num_rows(sql_res); + if(temp >= char_per_account){ + //hehe .. limit exceeded :P + ShowInfo("Create char failed (%d): charlimit exceeded.\n", sd->account_id); + mysql_free_result(sql_res); + return -2; + } + mysql_free_result(sql_res); + } + } + + // Check Authorised letters/symbols in the name of the character + if (char_name_option == 1) { // only letters/symbols in char_name_letters are authorised + for (i = 0; i < NAME_LENGTH && name[i]; i++) + if (strchr(char_name_letters, name[i]) == NULL) + return -2; + } 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) + return -2; + } // else, all letters/symbols are authorised (except control char removed before) + + //check stat error + if ((dat[24]+dat[25]+dat[26]+dat[27]+dat[28]+dat[29]!=6*5 ) || // stats + (dat[30] >= 9) || // slots (dat[30] can not be negativ) + (dat[33] <= 0) || (dat[33] >= 24) || // hair style + (dat[31] >= 9)) { // hair color (dat[31] can not be negativ) + if (log_char) { + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char error", sd->account_id, dat[30], t_name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + ShowWarning("Create char failed (%d): stats error (bot cheat?!)\n", sd->account_id); + return -2; + } // for now we have checked: stat points used <31, char slot is less then 9, hair style/color values are acceptable + + // check individual stat value + for(i = 24; i <= 29; i++) { + if (dat[i] < 1 || dat[i] > 9) { + if (log_char) { + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char error", sd->account_id, dat[30], t_name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + ShowWarning("Create char failed (%d): stats error (bot cheat?!)\n", sd->account_id); + return -2; + } + } // now we know that every stat has proper value but we have to check if str/int agi/luk vit/dex pairs are correct + + if( ((dat[24]+dat[27]) > 10) || ((dat[25]+dat[29]) > 10) || ((dat[26]+dat[28]) > 10) ) { + if (log_char) { + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char error", sd->account_id, dat[30], t_name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + ShowWarning("Create char failed (%d): stats error (bot cheat?!)\n", sd->account_id); + return -2; + } // now when we have passed all stat checks + + if (log_char) { + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s`(`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char", sd->account_id, dat[30], t_name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + //printf("make new char %d-%d %s %d, %d, %d, %d, %d, %d - %d, %d" RETCODE, + // fd, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + + //Check Name (already in use?) + sprintf(tmp_sql, "SELECT `name` FROM `%s` WHERE `name` = '%s'",char_db, t_name); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return -2; + } + sql_res = mysql_store_result(&mysql_handle); + if(sql_res){ + temp = (int)mysql_num_rows(sql_res); + mysql_free_result(sql_res); + if (temp > 0) { + ShowInfo("Create char failed: charname already in use\n"); + return -1; + } + } + + // check char slot. + sprintf(tmp_sql, "SELECT `account_id`, `char_num` FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d'",char_db, sd->account_id, dat[30]); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + + if(sql_res){ + temp = (int)mysql_num_rows(sql_res); + mysql_free_result(sql_res); + if (temp > 0) { + ShowWarning("Create char failed (%d, slot: %d), slot already in use\n", sd->account_id, dat[30]); + return -2; + } + } + + //New Querys [Sirius] + //Insert the char to the 'chardb' ^^ + if (char_id_count) //Force initial char id. [Skotlex] + sprintf(tmp_sql, "INSERT INTO `%s` (`char_id`,`account_id`, `char_num`, `name`, `zeny`, `str`, `agi`, `vit`, `int`, `dex`, `luk`, `max_hp`, `hp`," + "`max_sp`, `sp`, `hair`, `hair_color`, `last_map`, `last_x`, `last_y`, `save_map`, `save_x`, `save_y`) VALUES (" + "'%d', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d','%d', '%d','%d', '%d', '%s', '%d', '%d', '%s', '%d', '%d')", + char_db, char_id_count, sd->account_id , dat[30] , t_name, start_zeny, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], + (40 * (100 + dat[26])/100) , (40 * (100 + dat[26])/100 ), (11 * (100 + dat[27])/100), (11 * (100 + dat[27])/100), dat[33], dat[31], + mapindex_id2name(start_point.map), start_point.x, start_point.y, mapindex_id2name(start_point.map), start_point.x, start_point.y); + else + sprintf(tmp_sql, "INSERT INTO `%s` (`account_id`, `char_num`, `name`, `zeny`, `str`, `agi`, `vit`, `int`, `dex`, `luk`, `max_hp`, `hp`," + "`max_sp`, `sp`, `hair`, `hair_color`, `last_map`, `last_x`, `last_y`, `save_map`, `save_x`, `save_y`) VALUES (" + "'%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d','%d', '%d','%d', '%d', '%s', '%d', '%d', '%s', '%d', '%d')", + char_db, sd->account_id , dat[30] , t_name, start_zeny, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], + (40 * (100 + dat[26])/100) , (40 * (100 + dat[26])/100 ), (11 * (100 + dat[27])/100), (11 * (100 + dat[27])/100), dat[33], dat[31], + mapindex_id2name(start_point.map), start_point.x, start_point.y, mapindex_id2name(start_point.map), start_point.x, start_point.y); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return -2; //No, stop the procedure! + } + + if (char_id_count) //Clear this out for future inserts. + char_id_count = 0; + + //Now we need the charid from sql! + sprintf(tmp_sql, "SELECT `char_id` FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id , dat[30] , t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + //delete the char ..(no trash in DB!) + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id, dat[30], t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -2; //XD end of the (World? :P) .. charcreate (denied) + } else { + //query ok -> get the data! + sql_res = mysql_store_result(&mysql_handle); + if(sql_res){ + sql_row = mysql_fetch_row(sql_res); + char_id = sql_row?atoi(sql_row[0]):0; //char id :) + mysql_free_result(sql_res); + if(char_id <= 0){ + ShowError("failed (get char id..) CHARID (%d) wrong!\n", char_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id, dat[30], t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -2; //charcreate denied .. + } + }else{ + //prevent to crash (if its false, and we want to free -> segfault :) + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id, dat[30], t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -2; //end ...... -> charcreate failed :) + } + } + + //Give the char the default items + //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + if (start_weapon > 0) { //add Start Weapon (Knife?) + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`,`nameid`, `amount`, `equip`, `identify`) VALUES ('%d', '%d', '%d', '%d', '%d')", inventory_db, char_id, start_weapon,1,0x02,1); + if (mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id, dat[30], t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -2;//end XD + } + } + if (start_armor > 0) { //Add default armor (cotton shirt?) + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`,`nameid`, `amount`, `equip`, `identify`) VALUES ('%d', '%d', '%d', '%d', '%d')", inventory_db, char_id, start_armor,1,0x10,1); + if (mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d' AND `name` = '%s'", char_db, sd->account_id, dat[30], t_name); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `char_id` = '%d'", inventory_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -2; //end.... + } + } + + ShowInfo("Created char: account: %d, char: %d, slot: %d, name: %s\n", sd->account_id, char_id, dat[30], name); + return char_id; +} + +/*----------------------------------------------------------------------------------------------------------*/ +/* Delete char - davidsiaw */ +/*----------------------------------------------------------------------------------------------------------*/ +/* Returns 0 if successful + * Returns < 0 for error + */ +int delete_char_sql(int char_id, int partner_id) +{ + char char_name[NAME_LENGTH], t_name[NAME_LENGTH*2]; //Name needs be escaped. + int account_id=0, party_id=0, guild_id=0; + + sprintf(tmp_sql, "SELECT `name`,`account_id`,`party_id`,`guild_id` FROM `%s` WHERE `char_id`='%d'",char_db, char_id); + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + + if(sql_res) + sql_row = mysql_fetch_row(sql_res); + + if (sql_res == NULL || sql_row == NULL) + { + ShowError("delete_char_sql: Unable to fetch character data, deletion aborted.\n"); + return -1; + } + strncpy(char_name, sql_row[0], NAME_LENGTH); + char_name[NAME_LENGTH-1] = '\0'; + jstrescapecpy(t_name, char_name); //Escape string for sql use... [Skotlex] + account_id = atoi(sql_row[1]); + party_id = atoi(sql_row[2]); + guild_id = atoi(sql_row[3]); + mysql_free_result(sql_res); //Let's free this as soon as possible to avoid problems later on. + + /* Divorce [Wizputer] */ + if (partner_id) { + sprintf(tmp_sql,"UPDATE `%s` SET `partner_id`='0' WHERE `char_id`='%d'",char_db,partner_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql,"DELETE FROM `%s` WHERE (`nameid`='%d' OR `nameid`='%d') AND `char_id`='%d'",inventory_db,WEDDING_RING_M,WEDDING_RING_F,partner_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + //Make the character leave the party [Skotlex] + if (party_id) + inter_party_leave(party_id, account_id, char_id); + + /* delete char's pet */ + //Delete the hatched pet if you have one... + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d' AND `incuvate` = '0'",pet_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + // Komurka's suggested way to clear pets, modified by [Skotlex] (because I always personalize what I do :X) + //Removing pets that are in the char's inventory.... + { //NOTE: The syntax for multi-table deletes is a bit changed between 4.0 and 4.1 regarding aliases, so we have to consider the version... [Skotlex] + //Since we only care about the major and minor version, a double conversion is good enough. (4.1.20 -> 4.10000) + double mysql_version = atof(mysql_get_server_info(&mysql_handle)); + + sprintf(tmp_sql, + "delete FROM `%s` USING `%s` as c LEFT JOIN `%s` as i ON c.char_id = i.char_id, `%s` as p WHERE c.char_id = '%d' AND i.card0 = -256 AND p.pet_id = (i.card1|(i.card2<<2))", + (mysql_version<4.1?pet_db:"p"), char_db, inventory_db, pet_db, char_id); + + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //Removing pets that are in the char's cart.... + sprintf(tmp_sql, + "delete FROM `%s` USING `%s` as c LEFT JOIN `%s` as i ON c.char_id = i.char_id, `%s` as p WHERE c.char_id = '%d' AND i.card0 = -256 AND p.pet_id = (i.card1|(i.card2<<2))", + (mysql_version<4.1?pet_db:"p"), char_db, cart_db, pet_db, char_id); + + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + /* delete char's friends list */ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `char_id` = '%d'",friend_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete char from other's friend list */ + //NOTE: Won't this cause problems for people who are already online? [Skotlex] + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `friend_id` = '%d'",friend_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete inventory */ + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",inventory_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete cart inventory */ + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",cart_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete memo areas */ + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",memo_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete skills */ + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",skill_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* delete character */ + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",char_db, char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + /* No need as we used inter_guild_leave [Skotlex] + // Also delete info from guildtables. + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",guild_member_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + */ + + sprintf(tmp_sql, "SELECT `guild_id` FROM `%s` WHERE `master` = '%s'", guild_db, t_name); + + if (mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else { + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res == NULL) { + if (mysql_errno(&mysql_handle) != 0) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return -1; + } else { + int rows = (int)mysql_num_rows(sql_res); + mysql_free_result(sql_res); + if (rows > 0) { + mapif_parse_BreakGuild(0,guild_id); + } + else if (guild_id) //Leave your guild. + inter_guild_leave(guild_id, account_id, char_id); + } + } + return 0; +} + +//========================================================================================================== + +void mmo_char_sync(void) +{ + ShowWarning("mmo_char_sync() - nothing to do\n"); +} + +// to do +/////////////////////////// + +int mmo_char_sync_timer(int tid, unsigned int tick, int id, int data) { + ShowWarning("mmo_char_sync_timer() tic - no works to do\n"); + return 0; +} + +int count_users(void) { + int i, users; + + if (login_fd > 0 && session[login_fd]){ + users = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] > 0) { + users += server[i].users; + } + } + return users; + } + return 0; +} + +int mmo_char_send006b(int fd, struct char_session_data *sd) { + int i, j, found_num = 0; + struct mmo_charstatus *p = NULL; +// hehe. commented other. anyway there's no need to use older version. +// if use older packet version just uncomment that! +//#ifdef NEW_006b + const int offset = 24; +//#else +// int offset = 4; +//#endif + +// ShowDebug("mmo_char_send006b start.. (account:%d)\n",sd->account_id); +// printf("offset -> %d...\n",offset); + + set_char_online(-1, 99,sd->account_id); + + //search char. + sprintf(tmp_sql, "SELECT `char_id` FROM `%s` WHERE `account_id` = '%d'",char_db, sd->account_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + found_num = (int)mysql_num_rows(sql_res); +// ShowInfo("number of chars: %d\n", found_num); + i = 0; + while((sql_row = mysql_fetch_row(sql_res))) { + sd->found_char[i] = atoi(sql_row[0]); + i++; + } + mysql_free_result(sql_res); + } + +// printf("char fetching end (total: %d)....\n", found_num); + + for(i = found_num; i < 9; i++) + sd->found_char[i] = -1; + + memset(WFIFOP(fd, 0), 0, offset + found_num * 106); + WFIFOW(fd, 0) = 0x6b; + WFIFOW(fd, 2) = offset + found_num * 106; + + if (save_log) + ShowInfo("Request Char Data ("CL_BOLD"%d"CL_RESET"):\n",sd->account_id); + + for(i = 0; i < found_num; i++) { + mmo_char_fromsql_short(sd->found_char[i], char_dat); + + p = &char_dat[0]; + + j = offset + (i * 106); // increase speed of code + + WFIFOL(fd,j) = p->char_id; + WFIFOL(fd,j+4) = p->base_exp; + WFIFOL(fd,j+8) = p->zeny; + WFIFOL(fd,j+12) = p->job_exp; + WFIFOL(fd,j+16) = p->job_level; + + WFIFOL(fd,j+20) = 0; + WFIFOL(fd,j+24) = 0; + WFIFOL(fd,j+28) = p->option; + + WFIFOL(fd,j+32) = p->karma; + WFIFOL(fd,j+36) = p->manner; + + WFIFOW(fd,j+40) = p->status_point; + WFIFOW(fd,j+42) = (p->hp > 0x7fff) ? 0x7fff : p->hp; + WFIFOW(fd,j+44) = (p->max_hp > 0x7fff) ? 0x7fff : p->max_hp; + WFIFOW(fd,j+46) = (p->sp > 0x7fff) ? 0x7fff : p->sp; + WFIFOW(fd,j+48) = (p->max_sp > 0x7fff) ? 0x7fff : p->max_sp; + WFIFOW(fd,j+50) = DEFAULT_WALK_SPEED; // p->speed; + WFIFOW(fd,j+52) = p->class_; + WFIFOW(fd,j+54) = p->hair; + + // pecopeco knights/crusaders crash fix + if (p->class_ == 13 || p->class_ == 21 || + p->class_ == 4014 || p->class_ == 4022 || + p->class_ == 4036 || p->class_ == 4044) + WFIFOW(fd,j+56) = 0; + else WFIFOW(fd,j+56) = p->weapon; + + WFIFOW(fd,j+58) = p->base_level; + WFIFOW(fd,j+60) = p->skill_point; + WFIFOW(fd,j+62) = p->head_bottom; + WFIFOW(fd,j+64) = p->shield; + WFIFOW(fd,j+66) = p->head_top; + WFIFOW(fd,j+68) = p->head_mid; + WFIFOW(fd,j+70) = p->hair_color; + WFIFOW(fd,j+72) = p->clothes_color; + + memcpy(WFIFOP(fd,j+74), p->name, NAME_LENGTH); + + WFIFOB(fd,j+98) = (p->str > 255) ? 255 : p->str; + WFIFOB(fd,j+99) = (p->agi > 255) ? 255 : p->agi; + WFIFOB(fd,j+100) = (p->vit > 255) ? 255 : p->vit; + WFIFOB(fd,j+101) = (p->int_ > 255) ? 255 : p->int_; + WFIFOB(fd,j+102) = (p->dex > 255) ? 255 : p->dex; + WFIFOB(fd,j+103) = (p->luk > 255) ? 255 : p->luk; + WFIFOB(fd,j+104) = p->char_num; + } + + WFIFOSET(fd,WFIFOW(fd,2)); +// printf("mmo_char_send006b end..\n"); + return 0; +} + +int parse_tologin(int fd) { + int i; + struct char_session_data *sd; + + // only login-server can have an access to here. + // so, if it isn't the login-server, we disconnect the session. + //session eof check! + if(fd != login_fd) + session[fd]->eof = 1; + if(session[fd]->eof) { + if (fd == login_fd) { + ShowWarning("Connection to login-server lost (connection #%d).\n", fd); + login_fd = -1; + } + do_close(fd); + return 0; + } + + sd = (struct char_session_data*)session[fd]->session_data; + + // hehe. no need to set user limit on SQL version. :P + // but char limitation is good way to maintain server. :D + while(RFIFOREST(fd) >= 2 && !session[fd]->eof) { +// printf("parse_tologin : %d %d %x\n", fd, RFIFOREST(fd), RFIFOW(fd, 0)); + + switch(RFIFOW(fd, 0)){ + case 0x2711: + if (RFIFOREST(fd) < 3) + return 0; + if (RFIFOB(fd, 2)) { + //printf("connect login server error : %d\n", RFIFOB(fd, 2)); + ShowError("Can not connect to login-server.\n"); + ShowError("The server communication passwords (default s1/p1) are probably invalid.\n"); + ShowError("Also, please make sure your login db has the correct coounication username/passwords and the gender of the account is S.\n"); + ShowError("The communication passwords are set in map_athena.conf and char_athena.conf\n"); + return 0; + //exit(1); //fixed for server shutdown. + }else { + ShowStatus("Connected to login-server (connection #%d).\n", fd); + if (kick_on_disconnect) + set_all_offline(); + // if no map-server already connected, display a message... + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] > 0 && server[i].map[0]) // if map-server online and at least 1 map + break; + if (i == MAX_MAP_SERVERS) + ShowStatus("Awaiting maps from map-server.\n"); + } + RFIFOSKIP(fd, 3); + break; + + case 0x2713: + if(RFIFOREST(fd)<51) + return 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = (struct char_session_data*)session[i]->session_data) && sd->account_id == RFIFOL(fd,2)) { + if (RFIFOB(fd,6) != 0) { + WFIFOW(i,0) = 0x6c; + WFIFOB(i,2) = 0x42; + WFIFOSET(i,3); + } else if (max_connect_user == 0 || count_users() < max_connect_user) { +// if (max_connect_user == 0) +// printf("max_connect_user (unlimited) -> accepted.\n"); +// else +// printf("count_users(): %d < max_connect_user (%d) -> accepted.\n", count_users(), max_connect_user); + sd->connect_until_time = (time_t)RFIFOL(fd,47); + memcpy(sd->email, RFIFOP(fd, 7), 40); + // send characters to player + mmo_char_send006b(i, sd); + } else if(isGM(sd->account_id) >= gm_allow_level) { + sd->connect_until_time = (time_t)RFIFOL(fd,47); + memcpy(sd->email, RFIFOP(fd, 7), 40); + // send characters to player + mmo_char_send006b(i, sd); + } else { + // refuse connection: too much online players +// printf("count_users(): %d < max_connect_use (%d) -> fail...\n", count_users(), max_connect_user); + WFIFOW(i,0) = 0x6c; + WFIFOW(i,2) = 0; + WFIFOSET(i,3); + } + } + } + RFIFOSKIP(fd,51); + break; + + case 0x2717: + if (RFIFOREST(fd) < 50) + return 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) { + if (sd->account_id == RFIFOL(fd,2)) { + sd->connect_until_time = (time_t)RFIFOL(fd,46); + break; + } + } + } + RFIFOSKIP(fd,50); + break; + + // login-server alive packet + case 0x2718: + if (RFIFOREST(fd) < 2) + return 0; + RFIFOSKIP(fd,2); + break; + + // Receiving authentification from Freya-type login server (to avoid char->login->char) + case 0x2719: + if (RFIFOREST(fd) < 18) + return 0; + // to conserv a maximum of authentification, search if account is already authentified and replace it + // that will reduce multiple connection too + for(i = 0; i < AUTH_FIFO_SIZE; i++) + if (auth_fifo[i].account_id == RFIFOL(fd,2)) + break; + // if not found, use next value + if (i == AUTH_FIFO_SIZE) { + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + i = auth_fifo_pos; + auth_fifo_pos++; + } + //printf("auth_fifo set (auth #%d) - account: %d, secure: %08x-%08x\n", i, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); + auth_fifo[i].account_id = RFIFOL(fd,2); + auth_fifo[i].char_id = 0; + auth_fifo[i].login_id1 = RFIFOL(fd,6); + auth_fifo[i].login_id2 = RFIFOL(fd,10); + auth_fifo[i].delflag = 2; // 0: auth_fifo canceled/void, 2: auth_fifo received from login/map server in memory, 1: connection authentified + auth_fifo[i].char_pos = 0; + auth_fifo[i].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[i].ip = RFIFOL(fd,14); + //auth_fifo[i].map_auth = 0; + RFIFOSKIP(fd,18); + break; + +/* case 0x2721: // gm reply. I don't want to support this function. + printf("0x2721:GM reply\n"); + { + int oldacc, newacc; + unsigned char buf[64]; + if (RFIFOREST(fd) < 10) + return 0; + oldacc = RFIFOL(fd, 2); + newacc = RFIFOL(fd, 6); + RFIFOSKIP(fd, 10); + if (newacc > 0) { + for(i=0;i map\n"); + } + break; +*/ + case 0x2723: // changesex reply (modified by [Yor]) + if (RFIFOREST(fd) < 7) + return 0; + { + int acc, sex; + unsigned char buf[16]; + + acc = RFIFOL(fd,2); + sex = RFIFOB(fd,6); + RFIFOSKIP(fd, 7); + if (acc > 0) { + sprintf(tmp_sql, "SELECT `char_id`,`class`,`skill_point`,`guild_id` FROM `%s` WHERE `account_id` = '%d'",char_db, acc); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + + while(sql_res && (sql_row = mysql_fetch_row(sql_res))) { + int char_id, guild_id, jobclass, skill_point, class_; + char_id = atoi(sql_row[0]); + jobclass = atoi(sql_row[1]); + skill_point = atoi(sql_row[2]); + guild_id = atoi(sql_row[3]); + class_ = jobclass; + if (jobclass == 19 || jobclass == 20 || + jobclass == 4020 || jobclass == 4021 || + jobclass == 4042 || jobclass == 4043) { + // job modification + if (jobclass == 19 || jobclass == 20) { + class_ = (sex) ? 19 : 20; + } else if (jobclass == 4020 || jobclass == 4021) { + class_ = (sex) ? 4020 : 4021; + } else if (jobclass == 4042 || jobclass == 4043) { + class_ = (sex) ? 4042 : 4043; + } + // remove specifical skills of classes 19,20 4020,4021 and 4042,4043 + sprintf(tmp_sql, "SELECT `lv` FROM `%s` WHERE `char_id` = '%d' AND `id` >= '315' AND `id` <= '330'",skill_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + while(( sql_row = mysql_fetch_row(sql_res))) { + skill_point += atoi(sql_row[0]); + } + } + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `char_id` = '%d' AND `id` >= '315' AND `id` <= '330'",skill_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + sprintf(tmp_sql, "UPDATE `%s` SET `equip` = '0' WHERE `char_id` = '%d'",inventory_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql, "UPDATE `%s` SET `class`='%d' , `skill_point`='%d' , `weapon`='0' , `shield='0' , `head_top`='0' , `head_mid`='0' , `head_bottom`='0' WHERE `char_id` = '%d'",char_db, class_, skill_point, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + if (guild_id) //If there is a guild, update the guild_member data [Skotlex] + inter_guild_sex_changed(guild_id, acc, char_id, sex); + } + } + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) { + if (sd->account_id == acc) { + session[i]->eof = 1; + break; + } + } + } + + WBUFW(buf,0) = 0x2b0d; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + + mapif_sendall(buf, 7); + } + break; + + // account_reg2変更通知 + case 0x2729: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { //Receive account_reg2 registry, forward to map servers. + unsigned char buf[ACCOUNT_REG2_NUM*(256+32+2)+16]; + memcpy(buf,RFIFOP(fd,0), RFIFOW(fd,2)); +// WBUFW(buf,0) = 0x2b11; + WBUFW(buf,0) = 0x3804; //Map server can now receive all kinds of reg values with the same packet. [Skotlex] + mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd, RFIFOW(fd,2)); + } + break; + + // State change of account/ban notification (from login-server) by [Yor] + case 0x2731: + if (RFIFOREST(fd) < 11) + return 0; + // send to all map-servers to disconnect the player + { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2b14; + WBUFL(buf,2) = RFIFOL(fd,2); + WBUFB(buf,6) = RFIFOB(fd,6); // 0: change of statut, 1: ban + WBUFL(buf,7) = RFIFOL(fd,7); // status or final date of a banishment + mapif_sendall(buf, 11); + } + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) { + if (sd->account_id == RFIFOL(fd,2)) { + session[i]->eof = 1; + break; + } + } + } + RFIFOSKIP(fd,11); + break; + + case 0x2732: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + unsigned char buf[32000]; + if (gm_account != NULL) + aFree(gm_account); + gm_account = (struct gm_account*)aCalloc(sizeof(struct gm_account) * ((RFIFOW(fd,2) - 4) / 5), 1); + GM_num = 0; + for (i = 4; i < RFIFOW(fd,2); i = i + 5) { + gm_account[GM_num].account_id = RFIFOL(fd,i); + gm_account[GM_num].level = (int)RFIFOB(fd,i+4); + //printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } + ShowStatus("From login-server: receiving information of %d GM accounts.\n", GM_num); + // send new gm acccounts level to map-servers + memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x2b15; + mapif_sendall(buf, RFIFOW(fd,2)); + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // Receive GM accounts [Freya login server packet by Yor] + case 0x2733: + // add test here to remember that the login-server is Freya-type + // sprintf (login_server_type, "Freya"); + if (RFIFOREST(fd) < 7) + return 0; + { + int new_level = 0; + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == RFIFOL(fd,2)) { + if (gm_account[i].level != (int)RFIFOB(fd,6)) { + gm_account[i].level = (int)RFIFOB(fd,6); + new_level = 1; + } + break; + } + // if not found, add it + if (i == GM_num) { + // limited to 4000, because we send information to char-servers (more than 4000 GM accounts???) + // int (id) + int (level) = 8 bytes * 4000 = 32k (limit of packets in windows) + if (((int)RFIFOB(fd,6)) > 0 && GM_num < 4000) { + if (GM_num == 0) { + gm_account = (struct gm_account*)aMalloc(sizeof(struct gm_account)); + } else { + gm_account = (struct gm_account*)aRealloc(gm_account, sizeof(struct gm_account) * (GM_num + 1)); + } + gm_account[GM_num].account_id = RFIFOL(fd,2); + gm_account[GM_num].level = (int)RFIFOB(fd,6); + new_level = 1; + GM_num++; + if (GM_num >= 4000) + ShowWarning("4000 GM accounts found. Next GM accounts are not readed.\n"); + } + } + if (new_level == 1) { + ShowStatus("From login-server: receiving GM account information (%d: level %d).\n", RFIFOL(fd,2), (int)RFIFOB(fd,6)); + mapif_send_gmaccounts(); + + //create_online_files(); // not change online file for only 1 player (in next timer, that will be done + // send gm acccounts level to map-servers + } + } + RFIFOSKIP(fd,7); + break; + + //Login server request to kick a character out. [Skotlex] + case 0x2734: + if (RFIFOREST(fd) < 6) + return 0; + { + struct online_char_data* character; + int aid = RFIFOL(fd,2); + if ((character = idb_get(online_char_db, aid)) != NULL) + { //Kick out this player. + if (character->server > -1) + { //Kick it from the map server it is on. + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + if (!character->waiting_disconnect) + add_timer(gettick()+15000, chardb_waiting_disconnect, character->account_id, 0); + character->waiting_disconnect = 1; + } else { //Manual kick from char server. + struct char_session_data *tsd; + int i; + for(i = 0; i < fd_max; i++) { + if (session[i] && (tsd = (struct char_session_data*)session[i]->session_data) && tsd->account_id == aid) + { + WFIFOW(i,0) = 0x81; + WFIFOB(i,2) = 2; + WFIFOSET(i,3); + break; + } + } + if (i == fd_max) //Shouldn't happen, but just in case. + set_char_offline(99, aid); + } + } + RFIFOSKIP(fd,6); + } + break; + + default: + ShowError("Unknown packet 0x%04x from login server, disconnecting.\n", RFIFOW(fd, 0)); + session[fd]->eof = 1; + return 0; + } + } + + RFIFOFLUSH(fd); + + return 0; +} + +int request_accreg2(int account_id, int char_id) { + if (login_fd > 0) { + WFIFOW(login_fd, 0) = 0x272e; + WFIFOL(login_fd, 2) = account_id; + WFIFOL(login_fd, 6) = char_id; + WFIFOSET(login_fd, 10); + return 1; + } + return 0; +} +//Send packet forward to login-server for account saving +int save_accreg2(unsigned char* buf, int len) { + if (login_fd > 0) { + WFIFOHEAD(login_fd, len+4); + memcpy(WFIFOP(login_fd,4), buf, len); + WFIFOW(login_fd,0) = 0x2728; + WFIFOW(login_fd,2) = len+4; + WFIFOSET(login_fd,len+4); + return 1; + } + return 0; +} +int search_mapserver(unsigned short map, long ip, short port); + +int parse_frommap(int fd) { + int i = 0, j = 0; + int id; +// int auth_fifo_flag=0; + + // Sometimes fd=0, and it will cause server crash. Don't know why. :( + if (fd <= 0) { + ShowError("parse_frommap error fd=%d\n", fd); + return 0; + } + + for(id = 0; id < MAX_MAP_SERVERS; id++) + if (server_fd[id] == fd) + break; + if(id == MAX_MAP_SERVERS) + session[fd]->eof = 1; + if(session[fd]->eof) { + if (id < MAX_MAP_SERVERS) { + unsigned char buf[16384]; + ShowStatus("Map-server %d (session #%d) has disconnected.\n", id, fd); + //Notify other map servers that this one is gone. [Skotlex] + WBUFW(buf,0) = 0x2b20; + WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) + if (server[id].map[i]) + WBUFW(buf,10+(j++)*4) = server[id].map[i]; + if (j > 0) { + WBUFW(buf,2) = j * 4 + 10; + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + memset(&server[id], 0, sizeof(struct mmo_map_server)); + sprintf(tmp_sql, "DELETE FROM `ragsrvinfo` WHERE `index`='%d'", server_fd[id]); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + server_fd[id] = -1; + online_char_db->foreach(online_char_db,char_db_setoffline,id); //Tag relevant chars as 'in disconnected' server. + } + do_close(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2 && !session[fd]->eof) { +// printf("parse_frommap : %d %d %x\n", fd, RFIFOREST(fd), RFIFOW(fd,0)); + + switch(RFIFOW(fd, 0)) { + + // map-server alive packet + case 0x2718: + if (RFIFOREST(fd) < 2) + return 0; + RFIFOSKIP(fd,2); + break; + + case 0x2af7: + RFIFOSKIP(fd,2); + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2709; + WFIFOSET(login_fd, 2); + } + break; + + // mapserver -> map names recv. + case 0x2afa: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + memset(server[id].map, 0, sizeof(server[id].map)); + j = 0; + for(i = 4; i < RFIFOW(fd,2); i += 4) { + server[id].map[j] = RFIFOW(fd,i); +// printf("set map %d.%d : %s\n", id, j, server[id].map[j]); + j++; + } + i = server[id].ip; + { + unsigned char *p = (unsigned char *)&server[id].ip; + ShowStatus("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n", + id, j, p[0], p[1], p[2], p[3], server[id].port); + ShowStatus("Map-server %d loading complete.\n", id); + if (kick_on_disconnect) + set_all_offline(); + if (max_account_id != DEFAULT_MAX_ACCOUNT_ID || max_char_id != DEFAULT_MAX_CHAR_ID) + mapif_send_maxid(max_account_id, max_char_id); //Send the current max ids to the server to keep in sync [Skotlex] + } + WFIFOW(fd,0) = 0x2afb; + WFIFOB(fd,2) = 0; + memcpy(WFIFOP(fd,3), wisp_server_name, NAME_LENGTH); // name for wisp to player + WFIFOSET(fd,3+NAME_LENGTH); + //WFIFOSET(fd,27); + { + unsigned char buf[16384]; + int x; + if (j == 0) { + ShowWarning("Map-Server %d have NO maps.\n", id); + // Transmitting maps information to the other map-servers + } else { + WBUFW(buf,0) = 0x2b04; + WBUFW(buf,2) = j * 4 + 10; + WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; + memcpy(WBUFP(buf,10), RFIFOP(fd,4), j * 4); + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + // Transmitting the maps of the other map-servers to the new map-server + for(x = 0; x < MAX_MAP_SERVERS; x++) { + if (server_fd[x] > 0 && x != id) { + WFIFOW(fd,0) = 0x2b04; + WFIFOL(fd,4) = server[x].ip; + WFIFOW(fd,8) = server[x].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) + if (server[x].map[i]) + WFIFOW(fd,10+(j++)*4) = server[x].map[i]; + if (j > 0) { + WFIFOW(fd,2) = j * 4 + 10; + WFIFOSET(fd,WFIFOW(fd,2)); + } + } + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + //Packet command is now used for sc_data request. [Skotlex] + case 0x2afc: + if (RFIFOREST(fd) < 10) + return 0; + { + int aid, cid; + aid = RFIFOL(fd,2); + cid = RFIFOL(fd,6); + RFIFOSKIP(fd, 10); +#ifdef ENABLE_SC_SAVING + sprintf(tmp_sql, "SELECT type, tick, val1, val2, val3, val4 from `%s` WHERE `account_id` = '%d' AND `char_id`='%d'", + scdata_db, aid, cid); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + break; + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + struct status_change_data data; + int count = 0; + WFIFOW(fd, 0) = 0x2b1d; + WFIFOL(fd, 4) = aid; + WFIFOL(fd, 8) = cid; + while((sql_row = mysql_fetch_row(sql_res))) + { + data.type = atoi(sql_row[0]); + data.tick = atoi(sql_row[1]); + data.val1 = atoi(sql_row[2]); + data.val2 = atoi(sql_row[3]); + data.val3 = atoi(sql_row[4]); + data.val4 = atoi(sql_row[5]); + memcpy(WFIFOP(fd, 14+count*sizeof(struct status_change_data)), &data, sizeof(struct status_change_data)); + count++; + } + if (count > 0) + { + WFIFOW(fd, 2) = 14 + count*sizeof(struct status_change_data); + WFIFOW(fd, 12) = count; + WFIFOSET(fd, WFIFOW(fd,2)); + + //Clear the data once loaded. + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'", scdata_db, aid, cid); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } +#endif + break; + } + //set MAP user count + case 0x2afe: + if (RFIFOREST(fd) < 4) + return 0; + if (RFIFOW(fd,2) != server[id].users) { + server[id].users = RFIFOW(fd,2); + ShowInfo("User Count: %d (Server: %d)\n", server[id].users, id); + } + RFIFOSKIP(fd, 4); + break; + // set MAP user + case 0x2aff: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int i, aid, cid; + struct online_char_data* character; + + online_char_db->foreach(online_char_db,char_db_setoffline,id); //Set all chars from this server as 'unknown' + server[id].users = RFIFOW(fd,4); + for(i = 0; i < server[id].users; i++) { + aid = RFIFOL(fd,6+i*8); + cid = RFIFOL(fd,6+i*8+4); + character = idb_ensure(online_char_db, aid, create_online_char_data); + if (character->server > -1 && character->server != id) + { + ShowNotice("Set map user: Character (%d:%d) marked on map server %d, but map server %d claims to have (%d:%d) online!\n", + character->account_id, character->char_id, character->server, id, aid, cid); + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + } + character->server = id; + character->char_id = cid; + } + //If any chars remain in -2, they will be cleaned in the cleanup timer. + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + } + // char saving + case 0x2b01: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int aid = RFIFOL(fd,4), cid = RFIFOL(fd,8); + i = 0; + //check account + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'",char_db, aid, cid); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) i = atoi(sql_row[0]); + if (sql_res) mysql_free_result(sql_res); + + if (i == 1) { + memcpy(&char_dat[0], RFIFOP(fd,13), sizeof(struct mmo_charstatus)); + mmo_char_tosql(cid, char_dat); + //save to DB + } else + ShowError("parse_from_map (save-char): Received data for non-existant character (%d:%d)!\n", aid, cid); + if (RFIFOB(fd,12)) { //Flag? Set character offline after saving [Skotlex] + set_char_offline(cid, aid); + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + } + // req char selection + case 0x2b02: + if (RFIFOREST(fd) < 18) + return 0; + + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd, 2); + auth_fifo[auth_fifo_pos].char_id = 0; + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd, 6); + auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); + auth_fifo[auth_fifo_pos].delflag = 2; + auth_fifo[auth_fifo_pos].char_pos = 0; + auth_fifo[auth_fifo_pos].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,14); + auth_fifo_pos++; + + WFIFOW(fd, 0) = 0x2b03; + WFIFOL(fd, 2) = RFIFOL(fd, 2); + WFIFOB(fd, 6) = 0; + WFIFOSET(fd, 7); + + RFIFOSKIP(fd, 18); + break; + + // request "change map server" + case 0x2b05: + if (RFIFOREST(fd) < 35) + return 0; + { + unsigned short name; + int map_id, map_fd = -1; + struct online_char_data* data; + struct mmo_charstatus* char_data; + + + name = RFIFOW(fd,18); + map_id = search_mapserver(name, RFIFOL(fd,24), RFIFOW(fd,28)); //Locate mapserver by ip and port. + if (map_id >= 0) + map_fd = server_fd[map_id]; + //Char should just had been saved before this packet, so this should be safe. [Skotlex] + char_data = uidb_get(char_db_,RFIFOL(fd,14)); + if (char_data == NULL) + { //Really shouldn't happen. + mmo_char_fromsql(RFIFOL(fd,14), char_dat); + char_data = char_dat; + } + //Tell the new map server about this player using Kevin's new auth packet. [Skotlex] + if (map_fd>=0 && session[map_fd] && char_data) + { //Send the map server the auth of this player. + //Update the "last map" as this is where the player must be spawned on the new map server. + char_data->last_point.map = RFIFOW(fd,18); + char_data->last_point.x = RFIFOW(fd,20); + char_data->last_point.y = RFIFOW(fd,22); + + WFIFOW(map_fd,0) = 0x2afd; + WFIFOW(map_fd,2) = 20 + sizeof(struct mmo_charstatus); + WFIFOL(map_fd,4) = RFIFOL(fd, 2); //Account ID + WFIFOL(map_fd,8) = RFIFOL(fd, 6); //Login1 + WFIFOL(map_fd,16) = RFIFOL(fd,10); //Login2 + WFIFOL(map_fd,12) = (unsigned long)0; //TODO: connect_until_time, how do I figure it out right now? + memcpy(WFIFOP(map_fd,20), char_data, sizeof(struct mmo_charstatus)); + WFIFOSET(map_fd, WFIFOW(map_fd,2)); + data = idb_ensure(online_char_db, RFIFOL(fd, 2), create_online_char_data); + data->char_id = char_data->char_id; + data->server = map_id; //Update server where char is. + + //Reply with an ack. + WFIFOW(fd, 0) = 0x2b06; + memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 28); + WFIFOSET(fd, 30); + } else { //Reply with nak + WFIFOW(fd, 0) = 0x2b06; + memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 28); + WFIFOL(fd, 6) = 0; //Set login1 to 0. + WFIFOSET(fd, 30); + } + RFIFOSKIP(fd, 35); + } + break; + + // char name check + case 0x2b08: + if (RFIFOREST(fd) < 6) + return 0; + + sprintf(tmp_sql, "SELECT `name` FROM `%s` WHERE `char_id`='%d'", char_db, (int)RFIFOL(fd,2)); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + + WFIFOW(fd,0) = 0x2b09; + WFIFOL(fd,2) = RFIFOL(fd,2); + + if (sql_row) + memcpy(WFIFOP(fd,6), sql_row[0], NAME_LENGTH); + else + memcpy(WFIFOP(fd,6), unknown_char_name, NAME_LENGTH); + if (sql_res) mysql_free_result(sql_res); + + WFIFOSET(fd,30); + + RFIFOSKIP(fd,6); + break; + + // I want become GM - fuck! + case 0x2b0a: + if(RFIFOREST(fd)<4) + return 0; + if(RFIFOREST(fd) login %d %s %d\n", RFIFOL(fd, 4), RFIFOP(fd, 8), RFIFOW(fd, 2)); + */ + ShowWarning("packet 0x2ba (become GM) is not supported by the Char-Server.\n"); + RFIFOSKIP(fd, RFIFOW(fd, 2)); + break; + + //Packet 0x2b10 deprecated in favor of packet 0x3004 for registry saving. [Skotlex] + //case 0x2b10: + + // Map server send information to change an email of an account -> login-server + case 0x2b0c: + if (RFIFOREST(fd) < 86) + return 0; + if (login_fd > 0) { // don't send request if no login-server + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), 86); // 0x2722 .L .40B .40B + WFIFOW(login_fd,0) = 0x2722; + WFIFOSET(login_fd, 86); + } + RFIFOSKIP(fd, 86); + break; + + // Receiving from map-server a status change resquest. Transmission to login-server (by Yor) + case 0x2b0e: + if (RFIFOREST(fd) < 44) + return 0; + { + char character_name[NAME_LENGTH], t_name[NAME_LENGTH*2]; + int acc = RFIFOL(fd,2); // account_id of who ask (-1 if nobody) + memcpy(character_name, RFIFOP(fd,6), NAME_LENGTH); + character_name[NAME_LENGTH-1] = '\0'; + jstrescapecpy(t_name, character_name); //Escape string for sql use... [Skotlex] + // prepare answer + WFIFOW(fd,0) = 0x2b0f; // answer + WFIFOL(fd,2) = acc; // who want do operation + WFIFOW(fd,30) = RFIFOW(fd, 30); // type of operation: 1-block, 2-ban, 3-unblock, 4-unban + sprintf(tmp_sql, "SELECT `account_id`,`name` FROM `%s` WHERE `name` = '%s'",char_db, t_name); + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + if (mysql_num_rows(sql_res)) { + sql_row = mysql_fetch_row(sql_res); + memcpy(WFIFOP(fd,6), sql_row[1], NAME_LENGTH); // put correct name if found + WFIFOW(fd,32) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + switch(RFIFOW(fd, 30)) { + case 1: // block + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = atoi(sql_row[0]); // account value + WFIFOL(login_fd,6) = 5; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 5); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 2: // ban + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2725; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOW(login_fd, 6) = RFIFOW(fd,32); // year + WFIFOW(login_fd, 8) = RFIFOW(fd,34); // month + WFIFOW(login_fd,10) = RFIFOW(fd,36); // day + WFIFOW(login_fd,12) = RFIFOW(fd,38); // hour + WFIFOW(login_fd,14) = RFIFOW(fd,40); // minute + WFIFOW(login_fd,16) = RFIFOW(fd,42); // second + WFIFOSET(login_fd,18); +// printf("char : status -> login: account %d, ban: %dy %dm %dd %dh %dmn %ds\n", +// char_dat[i].account_id, (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), (short)RFIFOW(fd,38), (short)RFIFOW(fd,40), (short)RFIFOW(fd,42)); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 3: // unblock + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = atoi(sql_row[0]); // account value + WFIFOL(login_fd,6) = 0; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 0); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 4: // unban + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x272a; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, unban request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 5: // changesex + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2727; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, change sex request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + } + } else { + // character name not found + memcpy(WFIFOP(fd,6), character_name, NAME_LENGTH); + WFIFOW(fd,32) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } + // send answer if a player ask, not if the server ask + if (acc != -1) { + WFIFOSET(fd, 34); + } + } + } + RFIFOSKIP(fd, 44); + break; + + // Recieve rates [Wizputer] + case 0x2b16: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,8)) + return 0; + { + char motd[256], t_name[512]; //Required for jstrescapecpy [Skotlex] + strncpy(motd, RFIFOP(fd,10), 255); //First copy it to make sure the motd fits. + motd[255]='\0'; + jstrescapecpy(t_name,motd); + + sprintf(tmp_sql, "INSERT INTO `ragsrvinfo` SET `index`='%d',`name`='%s',`exp`='%d',`jexp`='%d',`drop`='%d',`motd`='%s'", + fd, server_name, RFIFOW(fd,2), RFIFOW(fd,4), RFIFOW(fd,6), t_name); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + RFIFOSKIP(fd,RFIFOW(fd,8)); + break; + } + + // Character disconnected set online 0 [Wizputer] + case 0x2b17: + if (RFIFOREST(fd) < 6 ) + return 0; + //printf("Setting %d char offline\n",RFIFOL(fd,2)); + set_char_offline(RFIFOL(fd,2),RFIFOL(fd,6)); + RFIFOSKIP(fd,10); + break; + // Reset all chars to offline [Wizputer] + case 0x2b18: + ShowNotice("Map server [%d] requested to set all characters offline.\n", id); + set_all_offline(); + RFIFOSKIP(fd,2); + break; + // Character set online [Wizputer] + case 0x2b19: + if (RFIFOREST(fd) < 6 ) + return 0; + set_char_online(id, RFIFOL(fd,2),RFIFOL(fd,6)); + RFIFOSKIP(fd,10); + break; + + // Request sending of fame list + case 0x2b1a: + if (RFIFOREST(fd) < 2) + return 0; + { + int len = 8, num = 0; + unsigned char buf[32000]; + struct fame_list fame_item; + + WBUFW(buf,0) = 0x2b1b; + sprintf(tmp_sql, "SELECT `char_id`,`fame`, `name` FROM `%s` WHERE `fame`>0 AND (`class`='10' OR `class`='4011'OR `class`='4033') ORDER BY `fame` DESC LIMIT 0,10", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { + fame_item.id = atoi(sql_row[0]); + fame_item.fame = atoi(sql_row[1]); + strncpy(fame_item.name, sql_row[2], NAME_LENGTH); + + memcpy(WBUFP(buf,len), &fame_item, sizeof(struct fame_list)); + len += sizeof(struct fame_list); + if (++num == 10) + break; + } + } + mysql_free_result(sql_res); + WBUFW(buf, 6) = len; //Blacksmith block size + + num = 0; + sprintf(tmp_sql, "SELECT `char_id`,`fame`,`name` FROM `%s` WHERE `fame`>0 AND (`class`='18' OR `class`='4019' OR `class`='4041') ORDER BY `fame` DESC LIMIT 0,10", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { + fame_item.id = atoi(sql_row[0]); + fame_item.fame = atoi(sql_row[1]); + strncpy(fame_item.name, sql_row[2], NAME_LENGTH); + memcpy(WBUFP(buf,len), &fame_item, sizeof(struct fame_list)); + len += sizeof(struct fame_list); + if (++num == 10) + break; + } + } + mysql_free_result(sql_res); + WBUFW(buf, 4) = len; //Alchemist block size + + num = 0; + sprintf(tmp_sql, "SELECT `char_id`,`fame`,`name` FROM `%s` WHERE `fame`>0 AND `class`='4046' ORDER BY `fame` DESC LIMIT 0,10", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { + fame_item.id = atoi(sql_row[0]); + fame_item.fame = atoi(sql_row[1]); + strncpy(fame_item.name, sql_row[2], NAME_LENGTH); + memcpy(WBUFP(buf,len), &fame_item, sizeof(struct fame_list)); + len += sizeof(struct fame_list); + if (++num == 10) + break; + } + } + mysql_free_result(sql_res); + WBUFW(buf, 2) = len; //Total packet length + + mapif_sendall(buf, len); + RFIFOSKIP(fd,2); + break; + } + //Request saving sc_data of a player. [Skotlex] + case 0x2b1c: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { +#ifdef ENABLE_SC_SAVING + int count, aid, cid, i; + struct status_change_data data; + + aid = RFIFOL(fd, 4); + cid = RFIFOL(fd, 8); + count = RFIFOW(fd, 12); + + sprintf(tmp_sql, "INSERT INTO `%s` (`account_id`, `char_id`, `type`, `tick`, `val1`, `val2`, `val3`, `val4`) VALUES ", scdata_db); + + for (i = 0; i < count; i++) + { + memcpy (&data, RFIFOP(fd, 14+i*sizeof(struct status_change_data)), sizeof(struct status_change_data)); + sprintf (tmp_sql, "%s ('%d','%d','%hu','%d','%d','%d','%d','%d'),", tmp_sql, aid, cid, + data.type, data.tick, data.val1, data.val2, data.val3, data.val4); + } + if (count > 0) + { + tmp_sql[strlen(tmp_sql)-1] = '\0'; //Remove final comma. + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } +#endif + RFIFOSKIP(fd, RFIFOW(fd, 2)); + break; + } + + default: + // inter server - packet + { + int r = inter_parse_frommap(fd); + if (r == 1) break; // processed + if (r == 2) return 0; // need more packet + } + + // no inter server packet. no char server packet -> disconnect + ShowError("Unknown packet 0x%04x from map server, disconnecting.\n", RFIFOW(fd,0)); + session[fd]->eof = 1; + return 0; + } + } + return 0; +} + +int search_mapserver(unsigned short map, long ip, short port) { + int i, j; + + if (!map) + return -1; + + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] > 0) + for (j = 0; server[i].map[j]; j++) + if (server[i].map[j] == map) { + if (ip > 0 && server[i].ip != ip) + continue; + if (port > 0 && server[i].port != port) + continue; + return i; + } + + return -1; +} + +int char_mapif_init(int fd) { + return inter_mapif_init(fd); +} + +//----------------------------------------------------- +// Test to know if an IP come from LAN or WAN. by [Yor] +//----------------------------------------------------- +int lan_ip_check(unsigned char *p){ + int i; + int lancheck = 1; + int subneti[4]; + unsigned int k0, k1, k2, k3; + + sscanf(lan_map_ip, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + subneti[0] = k0; subneti[1] = k1; subneti[2] = k2; subneti[3] = k3; + +// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +// p[0], p[1], p[2], p[3], +// subneti[0], subneti[1], subneti[2], subneti[3], +// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + for(i = 0; i < 4; i++) { + if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { + lancheck = 0; + break; + } + } + return lancheck; +} + +int parse_char(int fd) { + int i, ch = 0; + char email[40]; + unsigned char buf[64]; + unsigned short cmd; + int map_fd; + struct char_session_data *sd; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + + sd = (struct char_session_data*)session[fd]->session_data; + + if(login_fd < 0) + session[fd]->eof = 1; + if(session[fd]->eof) { + if (fd == login_fd) + login_fd = -1; + if (sd != NULL) + { + struct online_char_data* data = idb_get(online_char_db, sd->account_id); + if (!data || data->server== -1) //If it is not in any server, send it offline. [Skotlex] + set_char_offline(99,sd->account_id); + } + do_close(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2 && !session[fd]->eof) { + cmd = RFIFOW(fd,0); + // crc32のスキップ用 + if( sd==NULL && // 未ログインor管理パケット + RFIFOREST(fd)>=4 && // 最低バイト数制限 & 0x7530,0x7532管理パケ除去 + RFIFOREST(fd)<=21 && // 最大バイト数制限 & サーバーログイン除去 + cmd!=0x20b && // md5通知パケット除去 + (RFIFOREST(fd)<6 || RFIFOW(fd,4)==0x65) ){ // 次に何かパケットが来てるなら、接続でないとだめ + RFIFOSKIP(fd,4); + cmd = RFIFOW(fd,0); + ShowDebug("parse_char : %d crc32 skipped\n",fd); + if(RFIFOREST(fd)==0) + return 0; + } + +//For use in packets that depend on an sd being present [Skotlex] +#define FIFOSD_CHECK(rest) { if(RFIFOREST(fd) < rest) return 0; if (sd==NULL) { RFIFOSKIP(fd,rest); return 0; } } + + switch(cmd){ + case 0x20b: //20040622 encryption ragexe correspondence + if (RFIFOREST(fd) < 19) + return 0; + RFIFOSKIP(fd,19); + break; + + case 0x65: // request to connect + ShowInfo("request connect - account_id:%d/login_id1:%d/login_id2:%d\n", RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOL(fd, 10)); + if (RFIFOREST(fd) < 17) + return 0; + { + if (sd == NULL) { + CREATE(session[fd]->session_data, struct char_session_data, 1); + sd = (struct char_session_data*)session[fd]->session_data; + sd->connect_until_time = 0; // unknow or illimited (not displaying on map-server) + } + sd->account_id = RFIFOL(fd, 2); + sd->login_id1 = RFIFOL(fd, 6); + sd->login_id2 = RFIFOL(fd, 10); + sd->sex = RFIFOB(fd, 16); + + WFIFOL(fd, 0) = RFIFOL(fd, 2); + WFIFOSET(fd, 4); + + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == sd->account_id && + auth_fifo[i].login_id1 == sd->login_id1 && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == sd->login_id2 && // relate to the versions higher than 18 +#endif + (!check_ip_flag || auth_fifo[i].ip == session[fd]->client_addr.sin_addr.s_addr) && + auth_fifo[i].delflag == 2) { + auth_fifo[i].delflag = 1; + + if (online_check) + { // check if character is not online already. [Skotlex] + struct online_char_data* character; + character = idb_get(online_char_db, sd->account_id); + + if (character) + { + if (character->server > -1) + { //Character already online. KICK KICK KICK + mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2); + if (!character->waiting_disconnect) + add_timer(gettick()+20000, chardb_waiting_disconnect, character->account_id, 0); + character->waiting_disconnect = 1; + /* Not a good idea because this would trigger when you do a char-change from the map server! [Skotlex] + } else { //Kick from char server. + struct char_session_data *tsd; + int i; + for(i = 0; i < fd_max; i++) { + if (session[i] && i != fd && (tsd = (struct char_session_data*)session[i]->session_data) && tsd->account_id == sd->account_id) + { + WFIFOW(i,0) = 0x81; + WFIFOB(i,2) = 2; + WFIFOSET(i,3); + break; + } + if (i == fd_max) //Shouldn't happen, but just in case. + set_char_offline(99, sd->account_id); + } + */ + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 8; + WFIFOSET(fd,3); + break; + } + } + } + + if (max_connect_user == 0 || count_users() < max_connect_user) { + if (login_fd > 0) { // don't send request if no login-server + // request to login-server to obtain e-mail/time limit + WFIFOW(login_fd,0) = 0x2716; + WFIFOL(login_fd,2) = sd->account_id; + WFIFOSET(login_fd,6); + } + // send characters to player + mmo_char_send006b(fd, sd); + } else { + // refuse connection (over populated) + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } +// printf("connection request> set delflag 1(o:2)- account_id:%d/login_id1:%d(fifo_id:%d)\n", sd->account_id, sd->login_id1, i); + break; + } + } + if (i == AUTH_FIFO_SIZE) { + if (login_fd > 0) { // don't send request if no login-server + 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) = session[fd]->client_addr.sin_addr.s_addr; + WFIFOSET(login_fd,19); + } else { // if no login-server, we must refuse connection + WFIFOW(fd,0) = 0x6c; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + } + } + } + RFIFOSKIP(fd, 17); + break; + + case 0x66: // char select + FIFOSD_CHECK(3); + + sprintf(tmp_sql, "SELECT `char_id` FROM `%s` WHERE `account_id`='%d' AND `char_num`='%d'",char_db, sd->account_id, RFIFOB(fd, 2)); + RFIFOSKIP(fd, 3); + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + + if (sql_row) + { + mmo_char_fromsql(atoi(sql_row[0]), char_dat); + char_dat[0].sex = sd->sex; + } else { + mysql_free_result(sql_res); + break; + } + + if (log_char) { + char escaped_name[NAME_LENGTH*2]; + sprintf(tmp_sql,"INSERT INTO `%s`(`time`, `account_id`,`char_num`,`name`) VALUES (NOW(), '%d', '%d', '%s')", + charlog_db, sd->account_id, RFIFOB(fd, 2), jstrescapecpy(escaped_name, char_dat[0].name)); + //query + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + ShowInfo("Selected char: (Account %d: %d - %s)" RETCODE, sd->account_id, RFIFOB(fd, 2), char_dat[0].name); + + i = search_mapserver(char_dat[0].last_point.map, -1, -1); + + // if map is not found, we check major cities + if (i < 0) { + unsigned short j; + ShowWarning("Unable to find map-server for '%s', resorting to sending to a major city.\n", mapindex_id2name(char_dat[0].last_point.map)); + if ((i = search_mapserver((j=mapindex_name2id(MAP_PRONTERA)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 273; // savepoint coordinates + char_dat[0].last_point.y = 354; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_GEFFEN)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 120; // savepoint coordinates + char_dat[0].last_point.y = 100; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_MORROC)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 160; // savepoint coordinates + char_dat[0].last_point.y = 94; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_ALBERTA)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 116; // savepoint coordinates + char_dat[0].last_point.y = 57; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_PAYON)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 87; // savepoint coordinates + char_dat[0].last_point.y = 117; + } else if ((i = search_mapserver((j=mapindex_name2id(MAP_IZLUDE)),-1,-1)) >= 0) { + char_dat[0].last_point.map = j; + char_dat[0].last_point.x = 94; // savepoint coordinates + char_dat[0].last_point.y = 103; + } else { + // get first online server + i = 0; + for(j = 0; j < MAX_MAP_SERVERS; j++) + if (server_fd[j] > 0 && server[j].map[0]) { + i = j; + ShowDebug("Map-server #%d found with a map: '%s'.\n", j, server[j].map[0]); + break; + } + // if no map-servers are connected, we send: server closed + if (j == MAX_MAP_SERVERS) { + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + break; + } + } + } + WFIFOW(fd, 0) =0x71; + WFIFOL(fd, 2) =char_dat[0].char_id; + memcpy(WFIFOP(fd,6), mapindex_id2name(char_dat[0].last_point.map), MAP_NAME_LENGTH); + //Lan check added by Kashy + if (lan_ip_check(p)) + WFIFOL(fd, 22) = inet_addr(lan_map_ip); + else + WFIFOL(fd, 22) = server[i].ip; + WFIFOW(fd,26) = server[i].port; + WFIFOSET(fd,28); + if (auth_fifo_pos >= AUTH_FIFO_SIZE) { + auth_fifo_pos = 0; + } + auth_fifo[auth_fifo_pos].account_id = sd->account_id; + auth_fifo[auth_fifo_pos].char_id = char_dat[0].char_id; + auth_fifo[auth_fifo_pos].login_id1 = sd->login_id1; + auth_fifo[auth_fifo_pos].login_id2 = sd->login_id2; + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].char_pos = 0; + auth_fifo[auth_fifo_pos].sex = sd->sex; + auth_fifo[auth_fifo_pos].connect_until_time = sd->connect_until_time; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + + //Send NEW auth packet [Kevin] + if ((map_fd = server_fd[i]) < 1 || session[map_fd] == NULL) + { + ShowError("parse_char: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", map_fd, i); + server_fd[i] = -1; + memset(&server[i], 0, sizeof(struct mmo_map_server)); + break; + } + + WFIFOW(map_fd,0) = 0x2afd; + WFIFOW(map_fd,2) = 20 + sizeof(struct mmo_charstatus); + WFIFOL(map_fd,4) = auth_fifo[auth_fifo_pos].account_id; + WFIFOL(map_fd,8) = auth_fifo[auth_fifo_pos].login_id1; + WFIFOL(map_fd,16) = auth_fifo[auth_fifo_pos].login_id2; + WFIFOL(map_fd,12) = (unsigned long)auth_fifo[auth_fifo_pos].connect_until_time; + memcpy(WFIFOP(map_fd,20), &char_dat[0], sizeof(struct mmo_charstatus)); + WFIFOSET(map_fd, WFIFOW(map_fd,2)); + + set_char_online(i, auth_fifo[auth_fifo_pos].char_id, auth_fifo[auth_fifo_pos].account_id); + //Checks to see if the even share setting of the party must be broken. + inter_party_logged(char_dat[0].party_id, char_dat[0].account_id, char_dat[0].char_id); + auth_fifo_pos++; + break; + + case 0x67: // make new + FIFOSD_CHECK(37); + + if(char_new == 0) //turn character creation on/off [Kevin] + i = -2; + else + i = make_new_char_sql(fd, RFIFOP(fd, 2)); + + //if (i < 0) { + // WFIFOW(fd, 0) = 0x6e; + // WFIFOB(fd, 2) = 0x00; + // WFIFOSET(fd, 3); + // RFIFOSKIP(fd, 37); + // break; + //} + //Changed that we can support 'Charname already exists' (-1) amd 'Char creation denied' (-2) + //And 'You are underaged' (-3) (XD) [Sirius] + if(i == -1){ + //already exists + WFIFOW(fd, 0) = 0x6e; + WFIFOB(fd, 2) = 0x00; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 37); + break; + }else if(i == -2){ + //denied + WFIFOW(fd, 0) = 0x6e; + WFIFOB(fd, 2) = 0x02; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 37); + break; + }else if(i == -3){ + //underaged XD + WFIFOW(fd, 0) = 0x6e; + WFIFOB(fd, 2) = 0x01; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 37); + break; + } + + WFIFOW(fd, 0) = 0x6d; + memset(WFIFOP(fd, 2), 0x00, 106); + + mmo_char_fromsql_short(i, char_dat); //Only the short data is needed. + //mmo_char_fromsql(i, char_dat); + i = 0; + WFIFOL(fd, 2) = char_dat[i].char_id; + WFIFOL(fd,2+4) = char_dat[i].base_exp; + WFIFOL(fd,2+8) = char_dat[i].zeny; + WFIFOL(fd,2+12) = char_dat[i].job_exp; + WFIFOL(fd,2+16) = char_dat[i].job_level; + + WFIFOL(fd,2+28) = char_dat[i].karma; + WFIFOL(fd,2+32) = char_dat[i].manner; + + WFIFOW(fd,2+40) = 0x30; + WFIFOW(fd,2+42) = (char_dat[i].hp > 0x7fff) ? 0x7fff : char_dat[i].hp; + WFIFOW(fd,2+44) = (char_dat[i].max_hp > 0x7fff) ? 0x7fff : char_dat[i].max_hp; + WFIFOW(fd,2+46) = (char_dat[i].sp > 0x7fff) ? 0x7fff : char_dat[i].sp; + WFIFOW(fd,2+48) = (char_dat[i].max_sp > 0x7fff) ? 0x7fff : char_dat[i].max_sp; + WFIFOW(fd,2+50) = DEFAULT_WALK_SPEED; // char_dat[i].speed; + WFIFOW(fd,2+52) = char_dat[i].class_; + WFIFOW(fd,2+54) = char_dat[i].hair; + + WFIFOW(fd,2+58) = char_dat[i].base_level; + WFIFOW(fd,2+60) = char_dat[i].skill_point; + + WFIFOW(fd,2+64) = char_dat[i].shield; + WFIFOW(fd,2+66) = char_dat[i].head_top; + WFIFOW(fd,2+68) = char_dat[i].head_mid; + WFIFOW(fd,2+70) = char_dat[i].hair_color; + + memcpy(WFIFOP(fd,2+74), char_dat[i].name, NAME_LENGTH); + + WFIFOB(fd,2+98) = char_dat[i].str>255?255:char_dat[i].str; + WFIFOB(fd,2+99) = char_dat[i].agi>255?255:char_dat[i].agi; + WFIFOB(fd,2+100) = char_dat[i].vit>255?255:char_dat[i].vit; + WFIFOB(fd,2+101) = char_dat[i].int_>255?255:char_dat[i].int_; + WFIFOB(fd,2+102) = char_dat[i].dex>255?255:char_dat[i].dex; + WFIFOB(fd,2+103) = char_dat[i].luk>255?255:char_dat[i].luk; + WFIFOB(fd,2+104) = char_dat[i].char_num; + + WFIFOSET(fd, 108); + RFIFOSKIP(fd, 37); + + //to do + for(ch = 0; ch < 9; ch++) { + if (sd->found_char[ch] == -1) { + sd->found_char[ch] = char_dat[i].char_id; + break; + } + } + break; + case 0x68: /* delete char */ + FIFOSD_CHECK(46); + { + int cid = RFIFOL(fd,2); + ShowInfo(CL_RED" Request Char Deletion:"CL_GREEN"%d (%d)"CL_RESET"\n", sd->account_id, cid); + memcpy(email, RFIFOP(fd,6), 40); + + /* Check if e-mail is correct */ + if(strcmpi(email, sd->email)){ + if(strcmp("a@a.com", sd->email) == 0){ + if(strcmp("a@a.com", email) == 0 || strcmp("", email) == 0){ + //ignore + }else{ + //del fail + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 46); + break; + } + }else{ + //del fail + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 46); + break; + } + } + + for(i = 0; i < 9; i++) { + /* Debug: + printf("Checking if char to be deleted: %d - %d (%d)\n", sd->found_char[i], RFIFOL(fd, 2), sd->account_id); + */ + if (sd->found_char[i] == cid) { + for(ch = i; ch < 9-1; ch++) + sd->found_char[ch] = sd->found_char[ch+1]; + sd->found_char[8] = -1; + break; + } + } + /* Such a character does not exist in the account */ + /* If so, you are so screwed. */ + if (i == 9) { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + break; + } + + /* Grab the partner id */ + sprintf(tmp_sql, "SELECT `partner_id` FROM `%s` WHERE `char_id`='%d'",char_db, cid); + + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle); + + if(sql_res) + { + int char_pid=0; + sql_row = mysql_fetch_row(sql_res); + if (sql_row) + char_pid = atoi(sql_row[0]); + mysql_free_result(sql_res); + + /* Delete character and partner (if any) */ + delete_char_sql(cid, char_pid); + if (char_pid != 0) + { /* If there is partner, tell map server to do divorce */ + WBUFW(buf,0) = 0x2b12; + WBUFL(buf,2) = RFIFOL(fd,2); + WBUFL(buf,6) = char_pid; + mapif_sendall(buf,10); + } + } + /* Char successfully deleted. <- For sure? There could had been an sql db error, what is done then?. [Skotlex] */ + WFIFOW(fd, 0) = 0x6f; + WFIFOSET(fd, 2); + + RFIFOSKIP(fd, 46); + break; + } + case 0x2af8: // login as map-server + if (RFIFOREST(fd) < 60) + return 0; + WFIFOW(fd, 0) = 0x2af9; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] <= 0) + break; + } + if (i == MAX_MAP_SERVERS || strcmp((const char*)RFIFOP(fd,2), userid) || strcmp((const char*)RFIFOP(fd,26), passwd)) { + WFIFOB(fd,2) = 3; + WFIFOSET(fd, 3); + } else { +// int len; + WFIFOB(fd,2) = 0; + WFIFOSET(fd, 3); + session[fd]->func_parse = parse_frommap; + server_fd[i] = fd; + server[i].ip = RFIFOL(fd, 54); + server[i].port = RFIFOW(fd, 58); + server[i].users = 0; + memset(server[i].map, 0, sizeof(server[i].map)); + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + char_mapif_init(fd); + // send gm acccounts level to map-servers +/* removed by CLOWNISIUS due to isGM + len = 4; + WFIFOW(fd,0) = 0x2b15; + for(i = 0; i < GM_num; i++) { + WFIFOL(fd,len) = gm_account[i].account_id; + WFIFOB(fd,len+4) = (unsigned char)gm_account[i].level; + len += 5; + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len);*/ + } + RFIFOSKIP(fd,60); + break; + + case 0x187: // Alive? + if (RFIFOREST(fd) < 6) { + return 0; + } + RFIFOSKIP(fd, 6); + break; + + case 0x7530: // Athena info get + WFIFOW(fd, 0) = 0x7531; + WFIFOB(fd, 2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd, 3) = ATHENA_MINOR_VERSION; + WFIFOB(fd, 4) = ATHENA_REVISION; + WFIFOB(fd, 5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd, 6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd, 7) = ATHENA_SERVER_INTER | ATHENA_SERVER_CHAR; + WFIFOW(fd, 8) = ATHENA_MOD_VERSION; + WFIFOSET(fd, 10); + RFIFOSKIP(fd, 2); + return 0; + + case 0x7532: // disconnect(default also disconnect) + default: + session[fd]->eof = 1; + return 0; + } + } + RFIFOFLUSH(fd); + + return 0; +} + +// Console Command Parser [Wizputer] +int parse_console(char *buf) { + char *type,*command; + + type = (char *)aMalloc(64); + command = (char *)aMalloc(64); + + memset(type,0,64); + memset(command,0,64); + + ShowNotice("Console: %s\n",buf); + + if ( sscanf(buf, "%[^:]:%[^\n]", type , command ) < 2 ) + sscanf(buf,"%[^\n]",type); + + ShowNotice("Type of command: %s || Command: %s \n",type,command); + + if(buf) aFree(buf); + if(type) aFree(type); + if(command) aFree(command); + + return 0; +} + +// MAP send all +int mapif_sendall(unsigned char *buf, unsigned int len) { + int i, c; + int fd; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if ((fd = server_fd[i]) > 0) { //0 Should not be a valid server_fd [Skotlex] + if (session[fd] == NULL) + { //Could this be the crash's source? [Skotlex] + ShowError("mapif_sendall: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", fd, i); + server_fd[i] = -1; + memset(&server[i], 0, sizeof(struct mmo_map_server)); + continue; + } + if (WFIFOSPACE(fd) < len) //Increase buffer size. + realloc_writefifo(fd, len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + + return c; +} + +int mapif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + int fd; + + c = 0; + for(i=0, c=0;i 0 && fd != sfd) { + if (WFIFOSPACE(fd) < len) //Increase buffer size. + realloc_writefifo(fd, len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; + } + } + + return c; +} + +int mapif_send(int fd, unsigned char *buf, unsigned int len) { + int i; + + if (fd >= 0) { + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (fd == server_fd[i]) { + if (WFIFOSPACE(fd) < len) //Increase buffer size. + realloc_writefifo(fd, len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + return 1; + } + } + } + return 0; +} + +int send_users_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(); + unsigned char buf[16]; + + if (login_fd > 0 && session[login_fd]) { + // send number of user to login server + WFIFOW(login_fd,0) = 0x2714; + WFIFOL(login_fd,2) = users; + WFIFOSET(login_fd,6); + } + // send number of players to all map-servers + WBUFW(buf,0) = 0x2b00; + WBUFL(buf,2) = users; + mapif_sendall(buf, 6); + + return 0; +} + +static int send_accounts_tologin_sub(DBKey key, void* data, va_list ap) { + struct online_char_data* character = (struct online_char_data*)data; + int *i = va_arg(ap, int*); + int count = va_arg(ap, int); + if ((*i) >= count) + return 0; //This is an error that shouldn't happen.... + if(character->server > -1) { + WFIFOHEAD(login_fd, 8+count*4); + WFIFOL(login_fd, 8+(*i)*4) =character->account_id; + (*i)++; + return 1; + } + return 0; +} + +int send_accounts_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(), i=0; + + if (login_fd > 0 && session[login_fd]) { + // send account list to login server + WFIFOHEAD(login_fd, 8+users*4); + WFIFOW(login_fd,0) = 0x272d; + WFIFOL(login_fd,4) = users; + online_char_db->foreach(online_char_db, send_accounts_tologin_sub, &i, users); + WFIFOW(login_fd,2) = 8+ i*4; + if (i > 0) + WFIFOSET(login_fd,WFIFOW(login_fd,2)); + } + return 0; +} + +int check_connect_login_server(int tid, unsigned int tick, int id, int data) { + if (login_fd <= 0 || session[login_fd] == NULL) { + struct char_session_data *sd; + int i, cc; + unsigned char buf[16]; + + ShowInfo("Attempt to connect to login-server...\n"); + login_fd = make_connection(login_ip, login_port); + if (login_fd == -1) + { //Try again later. [Skotlex] + login_fd = 0; + return 0; + } + session[login_fd]->func_parse = parse_tologin; + realloc_fifo(login_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + WFIFOW(login_fd,0) = 0x2710; + memset(WFIFOP(login_fd,2), 0, 24); + memcpy(WFIFOP(login_fd,2), userid, strlen(userid) < 24 ? strlen(userid) : 24); + memset(WFIFOP(login_fd,26), 0, 24); + memcpy(WFIFOP(login_fd,26), passwd, strlen(passwd) < 24 ? strlen(passwd) : 24); + WFIFOL(login_fd,50) = 0; + WFIFOL(login_fd,54) = char_ip; + WFIFOL(login_fd,58) = char_port; + memset(WFIFOP(login_fd,60), 0, 20); + memcpy(WFIFOP(login_fd,60), server_name, strlen(server_name) < 20 ? strlen(server_name) : 20); + WFIFOW(login_fd,80) = 0; + WFIFOW(login_fd,82) = char_maintenance; + + WFIFOW(login_fd,84) = char_new_display; //only display (New) if they want to [Kevin] + + WFIFOSET(login_fd,86); + + //(re)connected to login-server, + //now wi'll look in sql which player's are ON and set them OFF + //AND send to all mapservers (if we have one / ..) to kick the players + //so the bug is fixed, if'ure using more than one charservers (worlds) + //that the player'S got reejected from server after a 'world' crash^^ + //2b1f AID.L B1 + + sprintf(tmp_sql, "SELECT `account_id`, `online` FROM `%s` WHERE `online` = '1'", char_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return -1; + } + + sql_res = mysql_store_result(&mysql_handle); + if(sql_res){ + cc = (int)mysql_num_rows(sql_res); + ShowStatus("Setting %d Players offline\n", cc); + while((sql_row = mysql_fetch_row(sql_res))){ + //sql_row[0] == AID + //tell the loginserver + WFIFOW(login_fd, 0) = 0x272c; //set off + WFIFOL(login_fd, 2) = atoi(sql_row[0]); //AID + WFIFOSET(login_fd, 6); + + //tell map to 'kick' the player (incase of 'on' ..) + WBUFW(buf, 0) = 0x2b1f; + WBUFL(buf, 2) = atoi(sql_row[0]); + WBUFB(buf, 6) = 1; + mapif_sendall(buf, 7); + + //kick the player if he's on charselect + for(i = 0; i < fd_max; i++){ + if(session[i] && (sd = (struct char_session_data*)session[i]->session_data)){ + if(sd->account_id == atoi(sql_row[0])){ + session[i]->eof = 1; + break; + } + } + } + + } + mysql_free_result(sql_res); + }else{ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return -1; + } + + //Now Update all players to 'OFFLINE' + sprintf(tmp_sql, "UPDATE `%s` SET `online` = '0'", char_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql, "UPDATE `%s` SET `online` = '0'", guild_member_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql, "UPDATE `%s` SET `connect_member` = '0'", guild_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + } + return 0; +} + +//------------------------------------------------ +//Invoked 15 seconds after mapif_disconnectplayer in case the map server doesn't +//replies/disconnect the player we tried to kick. [Skotlex] +//------------------------------------------------ +static int chardb_waiting_disconnect(int tid, unsigned int tick, int id, int data) +{ + struct online_char_data* character; + if ((character = idb_get(online_char_db, id)) != NULL && character->waiting_disconnect) + { //Mark it offline due to timeout. + set_char_offline(character->char_id, character->account_id); + } + return 0; +} + +//---------------------------------------------------------- +// Return numerical value of a switch configuration by [Yor] +// on/off, english, fran軋is, deutsch, espaol +//---------------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +// Lan Support conf reading added by Kashy +int char_lan_config_read(const char *lancfgName){ + char subnetmask[128]; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + struct hostent * h = NULL; + + if ((fp = fopen(lancfgName, "r")) == NULL) { + ShowError("file not found: %s\n", lancfgName); + return 1; + } + + ShowInfo("Reading file %s...\n", lancfgName); + + while(fgets(line, sizeof(line)-1, fp)){ + if (line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + else if (strcmpi(w1, "lan_map_ip") == 0) { + h = gethostbyname(w2); + if (h != NULL) { + sprintf(lan_map_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + strncpy(lan_map_ip, w2, sizeof(lan_map_ip)); + lan_map_ip[sizeof(lan_map_ip)-1] = 0; + } + ShowStatus("set Lan_map_IP : %s\n", lan_map_ip); + } + + else if (strcmpi(w1, "subnetmask") == 0) { + unsigned int k0, k1, k2, k3; + strcpy(subnetmask, w2); + sscanf(subnetmask, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + subnetmaski[0] = k0; subnetmaski[1] = k1; subnetmaski[2] = k2; subnetmaski[3] = k3; + ShowStatus("set subnetmask : %s\n", w2); + } + } + fclose(fp); + + ShowInfo("Done reading %s.\n", lancfgName); + return 0; +} + +void do_final(void) { + ShowInfo("Doing final stage...\n"); + //mmo_char_sync(); + //inter_save(); + do_final_itemdb(); + //check SQL save progress. + //wait until save char complete + + set_all_offline(); + + inter_final(); + + flush_fifos(); + + mapindex_final(); + + sprintf(tmp_sql,"DELETE FROM `ragsrvinfo"); + if (mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + if(gm_account) { + aFree(gm_account); + gm_account = 0; + } + + if(char_dat) { + aFree(char_dat); + char_dat = 0; + } + + delete_session(login_fd); + delete_session(char_fd); + char_db_->destroy(char_db_, NULL); + online_char_db->destroy(online_char_db, NULL); + + mysql_close(&mysql_handle); + mysql_close(&lmysql_handle); + + ShowInfo("ok! all done...\n"); +} + +void sql_config_read(const char *cfgName){ /* Kalaspuff, to get login_db */ + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + ShowInfo("Reading file %s...\n", cfgName); + + if ((fp = fopen(cfgName, "r")) == NULL) { + ShowFatalError("file not found: %s\n", cfgName); + exit(1); + } + + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if(strcmpi(w1,"char_db")==0){ + strcpy(char_db,w2); + }else if(strcmpi(w1,"scdata_db")==0){ + strcpy(scdata_db,w2); + }else if(strcmpi(w1,"cart_db")==0){ + strcpy(cart_db,w2); + }else if(strcmpi(w1,"inventory_db")==0){ + strcpy(inventory_db,w2); + }else if(strcmpi(w1,"charlog_db")==0){ + strcpy(charlog_db,w2); + }else if(strcmpi(w1,"storage_db")==0){ + strcpy(storage_db,w2); + }else if(strcmpi(w1,"reg_db")==0){ + strcpy(reg_db,w2); + }else if(strcmpi(w1,"skill_db")==0){ + strcpy(skill_db,w2); + }else if(strcmpi(w1,"interlog_db")==0){ + strcpy(interlog_db,w2); + }else if(strcmpi(w1,"memo_db")==0){ + strcpy(memo_db,w2); + }else if(strcmpi(w1,"guild_db")==0){ + strcpy(guild_db,w2); + }else if(strcmpi(w1,"guild_alliance_db")==0){ + strcpy(guild_alliance_db,w2); + }else if(strcmpi(w1,"guild_castle_db")==0){ + strcpy(guild_castle_db,w2); + }else if(strcmpi(w1,"guild_expulsion_db")==0){ + strcpy(guild_expulsion_db,w2); + }else if(strcmpi(w1,"guild_member_db")==0){ + strcpy(guild_member_db,w2); + }else if(strcmpi(w1,"guild_skill_db")==0){ + strcpy(guild_skill_db,w2); + }else if(strcmpi(w1,"guild_position_db")==0){ + strcpy(guild_position_db,w2); + }else if(strcmpi(w1,"guild_storage_db")==0){ + strcpy(guild_storage_db,w2); + }else if(strcmpi(w1,"party_db")==0){ + strcpy(party_db,w2); + }else if(strcmpi(w1,"pet_db")==0){ + strcpy(pet_db,w2); + }else if(strcmpi(w1,"friend_db")==0){ + strcpy(friend_db,w2); + }else if(strcmpi(w1,"db_path")==0){ + strcpy(db_path,w2); + //Map server option to use SQL db or not + }else if(strcmpi(w1,"use_sql_db")==0){ // added for sql item_db read for char server [Valaris] + db_use_sqldbs = config_switch(w2); + ShowStatus("Using SQL dbs: %s\n",w2); + //custom columns for login database + }else if(strcmpi(w1,"login_db_level")==0){ + strcpy(login_db_level,w2); + }else if(strcmpi(w1,"login_db_account_id")==0){ + strcpy(login_db_account_id,w2); + }else if(strcmpi(w1,"lowest_gm_level")==0){ + lowest_gm_level = atoi(w2); + ShowStatus("set lowest_gm_level : %s\n",w2); + //support the import command, just like any other config + }else if(strcmpi(w1,"import")==0){ + sql_config_read(w2); + } + + } + fclose(fp); + ShowInfo("done reading %s.\n", cfgName); +} + +int char_config_read(const char *cfgName) { + struct hostent *h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + if ((fp = fopen(cfgName, "r")) == NULL) { + ShowFatalError("Configuration file not found: %s.\n", cfgName); + exit(1); + } + + ShowInfo("Reading file %s...\n", cfgName); + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars((unsigned char *) w1); + remove_control_chars((unsigned char *) w2); + if(strcmpi(w1,"timestamp_format")==0) { + strncpy(timestamp_format, w2, 20); + } else if(strcmpi(w1,"console_silent")==0){ + msg_silent = 0; //To always allow the next line to show up. + ShowInfo("Console Silent Setting: %d\n", atoi(w2)); + msg_silent = atoi(w2); + } else if (strcmpi(w1, "userid") == 0) { + memcpy(userid, w2, 24); + } else if (strcmpi(w1, "passwd") == 0) { + memcpy(passwd, w2, 24); + } else if (strcmpi(w1, "server_name") == 0) { + memcpy(server_name, w2, sizeof(server_name)); + server_name[sizeof(server_name) - 1] = '\0'; + ShowStatus("%s server has been initialized\n", w2); + } else if (strcmpi(w1, "wisp_server_name") == 0) { + if (strlen(w2) >= 4) { + memcpy(wisp_server_name, w2, sizeof(wisp_server_name)); + wisp_server_name[sizeof(wisp_server_name) - 1] = '\0'; + } + } else if (strcmpi(w1, "login_ip") == 0) { + login_ip_set_ = 1; + h = gethostbyname (w2); + if (h != NULL) { + ShowStatus("Login server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(login_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(login_ip_str,w2,16); + } else if (strcmpi(w1, "login_port") == 0) { + login_port=atoi(w2); + } else if (strcmpi(w1, "char_ip") == 0) { + char_ip_set_ = 1; + h = gethostbyname (w2); + if(h != NULL) { + ShowStatus("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(char_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(char_ip_str, w2, 16); + } else if (strcmpi(w1, "bind_ip") == 0) { + bind_ip_set_ = 1; + h = gethostbyname (w2); + if(h != NULL) { + ShowStatus("Character server binding IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(bind_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(bind_ip_str, w2, 16); + } else if (strcmpi(w1, "char_port") == 0) { + char_port = atoi(w2); + } else if (strcmpi(w1, "char_maintenance") == 0) { + char_maintenance = atoi(w2); + } else if (strcmpi(w1, "char_new")==0){ + char_new = atoi(w2); + } else if (strcmpi(w1, "char_new_display")==0){ + char_new_display = atoi(w2); + } else if (strcmpi(w1, "max_connect_user") == 0) { + max_connect_user = atoi(w2); + if (max_connect_user < 0) + max_connect_user = 0; // unlimited online players + } else if(strcmpi(w1, "gm_allow_level") == 0) { + gm_allow_level = atoi(w2); + if(gm_allow_level < 0) + gm_allow_level = 99; + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "online_check") == 0) { + online_check = config_switch(w2); + } else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2)*1000; + if (autosave_interval <= 0) + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; + } else if (strcmpi(w1, "save_log") == 0) { + save_log = config_switch(w2); + } else if (strcmpi(w1, "start_point") == 0) { + char map[MAP_NAME_LENGTH]; + int x, y; + if (sscanf(w2,"%16[^,],%d,%d", map, &x, &y) < 3) + continue; + if (strstr(map, ".gat") != NULL) { // Verify at least if '.gat' is in the map name + start_point.map = mapindex_name2id(map); + if (!start_point.map) + ShowError("Specified start_point %s not found in map-index cache.\n", map); + start_point.x = x; + start_point.y = y; + } + } else if (strcmpi(w1, "start_zeny") == 0) { + start_zeny = atoi(w2); + if (start_zeny < 0) + start_zeny = 0; + } else if (strcmpi(w1, "start_weapon") == 0) { + start_weapon = atoi(w2); + if (start_weapon < 0) + start_weapon = 0; + } else if (strcmpi(w1, "start_armor") == 0) { + start_armor = atoi(w2); + if (start_armor < 0) + start_armor = 0; + } else if(strcmpi(w1,"log_char")==0){ //log char or not [devil] + log_char = atoi(w2); + } else if (strcmpi(w1, "unknown_char_name") == 0) { + strcpy(unknown_char_name, w2); + unknown_char_name[NAME_LENGTH-1] = 0; + } else if (strcmpi(w1, "name_ignoring_case") == 0) { + name_ignoring_case = config_switch(w2); + } else if (strcmpi(w1, "char_name_option") == 0) { + char_name_option = atoi(w2); + } else if (strcmpi(w1, "char_name_letters") == 0) { + strcpy(char_name_letters, w2); + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "chars_per_account") == 0) { //maxchars per account [Sirius] + char_per_account = atoi(w2); + } else if (strcmpi(w1, "console") == 0) { + if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 ) + console = 1; + } else if (strcmpi(w1, "import") == 0) { + char_config_read(w2); + } + } + fclose(fp); + + ShowInfo("Done reading %s.\n", cfgName); + + return 0; +} + +void set_server_type(void) +{ + SERVER_TYPE = ATHENA_SERVER_CHAR; +} + +static int online_data_cleanup_sub(DBKey key, void *data, va_list ap) +{ + struct online_char_data *character= (struct online_char_data*)data; + if (character->server == -2) //Unknown server.. set them offline + set_char_offline(character->char_id, character->account_id); + if (character->server < 0) + //Free data from players that have not been online for a while. + db_remove(online_char_db, key); + return 0; +} + +static int online_data_cleanup(int tid, unsigned int tick, int id, int data) +{ + online_char_db->foreach(online_char_db, online_data_cleanup_sub); + return 0; +} + +int do_init(int argc, char **argv){ + int i; + + for(i = 0; i < MAX_MAP_SERVERS; i++) { + memset(&server[i], 0, sizeof(struct mmo_map_server)); + server_fd[i] = -1; + } + + //Read map indexes + mapindex_init(); + start_point.map = mapindex_name2id("new_1-1.gat"); + + char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]); + char_lan_config_read((argc > 1) ? argv[1] : LAN_CONF_NAME); + sql_config_read(SQL_CONF_NAME); + + ShowInfo("Finished reading the char-server configuration.\n"); + + inter_init((argc > 2) ? argv[2] : inter_cfgName); // inter server テハア篳ュ + ShowInfo("Finished reading the inter-server configuration.\n"); + + //Read ItemDB + do_init_itemdb(); + + ShowInfo("Initializing char server.\n"); + online_char_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + mmo_char_sql_init(); + ShowInfo("char server initialized.\n"); + +// ShowDebug("set parser -> parse_char()...\n"); + set_defaultparse(parse_char); + +// ShowDebug("set terminate function -> do_final().....\n"); + + if ((naddr_ != 0) && (login_ip_set_ == 0 || char_ip_set_ == 0)) { + // The char server should know what IP address it is running on + // - MouseJstr + int localaddr = ntohl(addr_[0]); + unsigned char *ptr = (unsigned char *) &localaddr; + char buf[16]; + sprintf(buf, "%d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]); + if (naddr_ != 1) + ShowStatus("Multiple interfaces detected.. using %s as our IP address\n", buf); + else + ShowStatus("Defaulting to %s as our IP address\n", buf); + if (login_ip_set_ == 0) + strcpy(login_ip_str, buf); + if (char_ip_set_ == 0) + strcpy(char_ip_str, buf); + + if (ptr[0] == 192 && ptr[1] == 168) + ShowWarning("Firewall detected.. edit lan_support.conf and char_athena.conf\n"); + } + + login_ip = inet_addr(login_ip_str); + char_ip = inet_addr(char_ip_str); + + ShowInfo("open port %d.....\n",char_port); + //char_fd = make_listen_port(char_port); + if (bind_ip_set_) + char_fd = make_listen_bind(inet_addr(bind_ip_str),char_port); + else + char_fd = make_listen_bind(INADDR_ANY,char_port); + + add_timer_func_list(check_connect_login_server, "check_connect_login_server"); + add_timer_func_list(send_users_tologin, "send_users_tologin"); + add_timer_func_list(send_accounts_tologin, "send_accounts_tologin"); + add_timer_func_list(chardb_waiting_disconnect, "chardb_waiting_disconnect"); + + add_timer_func_list(online_data_cleanup, "online_data_cleanup"); + add_timer_interval(gettick() + 600*1000, online_data_cleanup, 0, 0, 600 * 1000); + + // send ALIVE PING to login server. + add_timer_interval(gettick() + 10, check_connect_login_server, 0, 0, 10 * 1000); + // send USER COUNT PING to login server. + add_timer_interval(gettick() + 10, send_users_tologin, 0, 0, 5 * 1000); + add_timer_interval(gettick() + 3600*1000, send_accounts_tologin, 0, 0, 3600 * 1000); //Sync online accounts every hour. + + if ( console ) { + set_defaultconsoleparse(parse_console); + start_console(); + } + + //Cleaning the tables for NULL entrys @ startup [Sirius] + //Chardb clean + ShowInfo("Cleaning the '%s' table...\n", char_db); + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `account_id` = '0'", char_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //guilddb clean + ShowInfo("Cleaning the '%s' table...\n", guild_db); + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_lv` = '0' AND `max_member` = '0' AND `exp` = '0' AND `next_exp` = '0' AND `average_lv` = '0'", guild_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //guildmemberdb clean + ShowInfo("Cleaning the '%s' table...\n", guild_member_db); + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '0' AND `account_id` = '0' AND `char_id` = '0'", guild_member_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + ShowInfo("End of char server initilization function.\n"); + ShowStatus("The char-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %d).\n\n", char_port); + return 0; +} + +#undef mysql_query + +int debug_mysql_query(char *file, int line, void *mysql, const char *q) { +#ifdef TWILIGHT + ShowDebug("sql: %s:%d# %s\n", file, line, q); +#endif + return mysql_query((MYSQL *) mysql, q); +} + +int char_child(int parent_id, int child_id) { + int tmp_id = 0; + sprintf (tmp_sql, "SELECT `child` FROM `%s` WHERE `char_id` = '%d'", char_db, parent_id); + if (mysql_query (&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result (&mysql_handle); + sql_row = sql_res?mysql_fetch_row (sql_res):NULL; + if (sql_row) + tmp_id = atoi (sql_row[0]); + else + ShowError("CHAR: child Failed!\n"); + if (sql_res) mysql_free_result (sql_res); + if ( tmp_id == child_id ) + return 1; + else + return 0; +} + +int char_married(int pl1,int pl2) { + int tmp_id = 0; + sprintf (tmp_sql, "SELECT `partner_id` FROM `%s` WHERE `char_id` = '%d'", char_db, pl1); + if (mysql_query (&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result (&mysql_handle); + sql_row = sql_res?mysql_fetch_row (sql_res):NULL; + if (sql_row) + tmp_id = atoi (sql_row[0]); + else + ShowError("CHAR: married Failed!\n"); + if (sql_res) mysql_free_result (sql_res); + if ( tmp_id == pl2 ) + return 1; + else + return 0; +} + +int char_nick2id (char *name) { + int char_id = 0; + sprintf (tmp_sql, "SELECT `char_id` FROM `%s` WHERE `name` = '%s'", char_db, name); + if (mysql_query (&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result (&mysql_handle); + sql_row = sql_res?mysql_fetch_row(sql_res):NULL; + if (sql_row) + char_id = atoi (sql_row[0]); + else + ShowError ("CHAR: nick2id Failed!\n"); + if (sql_res) mysql_free_result (sql_res); + return char_id; +} diff --git a/src/char_sql/char.h b/src/char_sql/char.h new file mode 100644 index 000000000..db1a0ca95 --- /dev/null +++ b/src/char_sql/char.h @@ -0,0 +1,103 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/core.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/mmo.h" +#include "../common/version.h" +#include "../common/db.h" +#include "../common/mapindex.h" + +#ifndef _CHAR_H_ +#define _CHAR_H_ + +#define START_CHAR_NUM 150000 +#define MAX_MAP_SERVERS 30 + +#define LAN_CONF_NAME "conf/lan_support.conf" + +#define DEFAULT_AUTOSAVE_INTERVAL 300*1000 + +struct mmo_map_server{ + long ip; + short port; + int users; + unsigned short map[MAX_MAP_PER_SERVER]; +}; +struct itemtmp { + int flag;//checked = 1 else 0 + int id; + short nameid; + short amount; + unsigned short equip; + char identify; + char refine; + char attribute; + short card[4]; +}; +enum { + TABLE_INVENTORY, + TABLE_CART, + TABLE_STORAGE, + TABLE_GUILD_STORAGE, +}; +struct itemtemp{ + struct itemtmp equip[MAX_GUILD_STORAGE],notequip[MAX_GUILD_STORAGE]; +}; +int memitemdata_to_sql(struct itemtmp mapitem[], int count, int char_id,int tableswitch); + +//int memitemdataNEW_to_sql(struct itemtmp mapitem[], int count, int char_id,int tableswitch); +int mapif_sendall(unsigned char *buf,unsigned int len); +int mapif_sendallwos(int fd,unsigned char *buf,unsigned int len); +int mapif_send(int fd,unsigned char *buf,unsigned int len); + +int char_nick2id (char *name); +int char_married(int pl1,int pl2); +int char_child(int parent_id, int child_id); + +int request_accreg2(int account_id, int char_id); +int save_accreg2(unsigned char* buf, int len); + +extern int autosave_interval; +extern int save_log; +extern int charsave_method; +extern char db_path[]; +extern char char_db[256]; +extern char scdata_db[256]; +extern char cart_db[256]; +extern char inventory_db[256]; +extern char charlog_db[256]; +extern char storage_db[256]; +extern char interlog_db[256]; +extern char reg_db[256]; +extern char skill_db[256]; +extern char memo_db[256]; +extern char guild_db[256]; +extern char guild_alliance_db[256]; +extern char guild_castle_db[256]; +extern char guild_expulsion_db[256]; +extern char guild_member_db[256]; +extern char guild_position_db[256]; +extern char guild_skill_db[256]; +extern char guild_storage_db[256]; +extern char party_db[256]; +extern char pet_db[256]; + +extern int db_use_sqldbs; // added for sql item_db read for char server [Valaris] +extern char login_db_level[32]; +extern char login_db_account_id[32]; + +extern int lowest_gm_level; +extern int GM_num; +extern struct gm_account *gm_account; + +extern int debug_mysql_query(char *file, int line, void *mysql, const char *q); + +#endif + +#include "inter.h" +#include "int_pet.h" +#include "int_guild.h" +#include "int_party.h" +#include "int_storage.h" diff --git a/src/char_sql/int_guild.c b/src/char_sql/int_guild.c new file mode 100644 index 000000000..6e1f24ed6 --- /dev/null +++ b/src/char_sql/int_guild.c @@ -0,0 +1,1914 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by hack + +#include +#include +#include + +#include "char.h" +#include "../common/strlib.h" +#include "../common/showmsg.h" +// #include "int_storage.h" +#include "inter.h" +#include "int_guild.h" +#include "mmo.h" +#include "socket.h" +#include "db.h" +#include "malloc.h" + +//Guild cache +static struct dbt *guild_db_; + +struct guild_castle castles[MAX_GUILDCASTLE]; + +static int guild_newid=30000; + +static int guild_exp[100]; + +#define GS_BASIC 0x01 +#define GS_MEMBER 0x02 +#define GS_POSITION 0x04 +#define GS_ALLIANCE 0x08 +#define GS_EXPULSION 0x10 +#define GS_SKILL 0x20 +#define GS_MASK 0x7F +#define GS_REMOVE 0x80 + +int mapif_parse_GuildLeave(int fd,int guild_id,int account_id,int char_id,int flag,const char *mes); +int mapif_guild_broken(int guild_id,int flag); +int guild_check_empty(struct guild *g); +int guild_calcinfo(struct guild *g); +int mapif_guild_basicinfochanged(int guild_id,int type,const void *data,int len); +int mapif_guild_info(int fd,struct guild *g); +int guild_break_sub(int key,void *data,va_list ap); +int inter_guild_tosql(struct guild *g,int flag); + + +#define mysql_query(_x, _y) debug_mysql_query(__FILE__, __LINE__, _x, _y) + +static int guild_save(DBKey key, void *data, va_list ap) { + struct guild *g = (struct guild*) data; + int *last_id = va_arg(ap, int *); + int *state = va_arg(ap, int *); + + if ((*state) == 0 && g->guild_id == (*last_id)) + (*state)++; //Save next guild in the list. + else if (g->save_flag&GS_MASK && (*state) == 1) { + inter_guild_tosql(g, g->save_flag&GS_MASK); + g->save_flag &= ~GS_MASK; + + //Some guild saved. + (*last_id) = g->guild_id; + (*state)++; + } + + if(g->save_flag == GS_REMOVE) { //Nothing to save, guild is ready for removal. + if (save_log) + ShowInfo("Guild Unloaded (%d - %s)\n", g->guild_id, g->name); + db_remove(guild_db_, key); + } + return 0; +} + +static int guild_save_timer(int tid, unsigned int tick, int id, int data) { + static int last_id = 0; //To know in which guild we were. + int state = 0; //0: Have not reached last guild. 1: Reached last guild, ready for save. 2: Some guild saved, don't do further saving. + if (!last_id) //Save the first guild in the list. + state = 1; + guild_db_->foreach(guild_db_, guild_save, &last_id, &state); + if (state != 2) //Reached the end of the guild db without saving. + last_id = 0; //Reset guild saved, return to beginning. + + state = guild_db_->size(guild_db_); + if (state < 1) state = 1; //Calculate the time slot for the next save. + add_timer(tick + autosave_interval/state, guild_save_timer, 0, 0); + return 0; +} + +// Save guild into sql +int inter_guild_tosql(struct guild *g,int flag) +{ + // GS_BASIC `guild` (`guild_id`, `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data`) + // GS_MEMBER `guild_member` (`guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`rsv1`,`rsv2`,`name`) + // GS_POSITION `guild_position` (`guild_id`,`position`,`name`,`mode`,`exp_mode`) + // GS_ALLIANCE `guild_alliance` (`guild_id`,`opposition`,`alliance_id`,`name`) + // GS_EXPULSION `guild_expulsion` (`guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3`) + // GS_SKILL `guild_skill` (`guild_id`,`id`,`lv`) + + // temporary storage for str convertion. They must be twice the size of the + // original string to ensure no overflows will occur. [Skotlex] + char t_name[NAME_LENGTH*2], + t_master[NAME_LENGTH*2], + t_mes1[120], + t_mes2[240], + t_member[NAME_LENGTH*2], + t_position[NAME_LENGTH*2], + t_alliance[NAME_LENGTH*2], + t_ename[NAME_LENGTH*2], + t_emes[80], + t_info[240]; + char emblem_data[4096]; + int i=0; + + if (g->guild_id<=0) return -1; + +#ifdef NOISY + ShowInfo("Save guild request ("CL_BOLD"%d"CL_RESET" - flag 0x%x).",g->guild_id, flag); +#endif + + jstrescapecpy(t_name, g->name); + + t_info[0]='\0'; + // Insert new guild to sqlserver + if (flag&GS_BASIC){ + int len=0; + char updateflag=1; + strcat(t_info, " guild"); + + // Check if the guild exists. + sprintf(tmp_sql,"SELECT guild_id FROM `%s` WHERE guild_id='%d'",guild_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + updateflag = 0; //Assume insert? + } else { + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res==NULL || mysql_num_rows(sql_res)<=0) { //Guild does not exists + updateflag = 0; + } + mysql_free_result(sql_res); //Don't need it anymore... + } + + //printf("- Insert guild %d to guild\n",g->guild_id); + for(i=0;iemblem_len;i++){ + len+=sprintf(emblem_data+len,"%02x",(unsigned char)(g->emblem_data[i])); + //printf("%02x",(unsigned char)(g->emblem_data[i])); + } + emblem_data[len] = '\0'; + //printf("- emblem_len = %d \n",g->emblem_len); + if (updateflag) { + sprintf(tmp_sql,"UPDATE `%s` SET" + " `guild_id`=%d, `name`='%s', `master`='%s',`guild_lv`=%d, `connect_member`=%d,`max_member`=%d, " + "`average_lv`=%d,`exp`=%d,`next_exp`=%d,`skill_point`=%d,`mes1`='%s',`mes2`='%s'," + "`emblem_len`=%d,`emblem_id`=%d,`emblem_data`='%s',`char_id`=%d WHERE `guild_id`=%d", + guild_db, g->guild_id,t_name,jstrescapecpy(t_master,g->master), + g->guild_lv,g->connect_member,g->max_member,g->average_lv,g->exp,g->next_exp,g->skill_point, + jstrescapecpy(t_mes1,g->mes1),jstrescapecpy(t_mes2,g->mes2),g->emblem_len,g->emblem_id,emblem_data, + g->member[0].char_id, g->guild_id); + //printf(" %s\n",tmp_sql); + + } else { + sprintf(tmp_sql,"INSERT INTO `%s` " + "(`guild_id`, `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data`,`char_id`) " + "VALUES ('%d', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%s', '%s', '%d', '%d', '%s', '%d')", + guild_db, g->guild_id,t_name,jstrescapecpy(t_master,g->master), + g->guild_lv,g->connect_member,g->max_member,g->average_lv,g->exp,g->next_exp,g->skill_point, + jstrescapecpy(t_mes1,g->mes1),jstrescapecpy(t_mes2,g->mes2),g->emblem_len,g->emblem_id,emblem_data, + g->member[0].char_id); + //printf(" %s\n",tmp_sql); + + } + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if (flag&GS_MEMBER){ + struct guild_member *m; + strcat(t_info, " members"); + // Re-writing from scratch (Aru) + sprintf(tmp_sql,"DELETE from `%s` where `guild_id` = '%d'", + guild_member_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql,"UPDATE `%s` SET `guild_id` = '0' WHERE `guild_id` = '%d'", + char_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + for(i=0;imax_member;i++){ + m = &g->member[i]; + if(m->account_id) { + //Since nothing references guild member table as foreign keys, it's safe to use REPLACE INTO + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`name`) " + "VALUES ('%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%s')", + guild_member_db, g->guild_id, m->account_id,m->char_id, + m->hair,m->hair_color,m->gender, + m->class_,m->lv,m->exp,m->exp_payper,m->online,m->position, + jstrescapecpy(t_member,m->name)); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql,"UPDATE `%s` SET `guild_id` = '%d' WHERE `char_id` = '%d'", + char_db, g->guild_id, m->char_id); + if(mysql_query(&mysql_handle, tmp_sql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + } + + if (flag&GS_POSITION){ + strcat(t_info, " positions"); + //printf("- Insert guild %d to guild_position\n",g->guild_id); + for(i=0;iposition[i]; + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`position`,`name`,`mode`,`exp_mode`) VALUES ('%d','%d', '%s','%d','%d')", + guild_position_db, g->guild_id, i, jstrescapecpy(t_position,p->name),p->mode,p->exp_mode); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + + if (flag&GS_ALLIANCE){ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d' OR `alliance_id`='%d'",guild_alliance_db, g->guild_id,g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + else + { + //printf("- Insert guild %d to guild_alliance\n",g->guild_id); + for(i=0;ialliance[i]; + if(a->guild_id>0){ + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`opposition`,`alliance_id`,`name`) " + "VALUES ('%d','%d','%d','%s')", + guild_alliance_db, g->guild_id,a->opposition,a->guild_id,jstrescapecpy(t_alliance,a->name)); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`opposition`,`alliance_id`,`name`) " + "VALUES ('%d','%d','%d','%s')", + guild_alliance_db, a->guild_id,a->opposition,g->guild_id,t_name); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + } + } + + if (flag&GS_EXPULSION){ + strcat(t_info, " expulsions"); + //printf("- Insert guild %d to guild_expulsion\n",g->guild_id); + for(i=0;iexplusion[i]; + if(e->account_id>0){ + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3`) " + "VALUES ('%d','%s','%s','%s','%d','%d','%d','%d')", + guild_expulsion_db, g->guild_id, + jstrescapecpy(t_ename,e->name),jstrescapecpy(t_emes,e->mes),e->acc,e->account_id,e->rsv1,e->rsv2,e->rsv3 ); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + } + + if (flag&GS_SKILL){ + strcat(t_info, " skills"); + //printf("- Insert guild %d to guild_skill\n",g->guild_id); + for(i=0;iskill[i].id>0 && g->skill[i].lv>0){ + sprintf(tmp_sql,"REPLACE INTO `%s` (`guild_id`,`id`,`lv`) VALUES ('%d','%d','%d')", + guild_skill_db, g->guild_id,g->skill[i].id,g->skill[i].lv); + //printf("%s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + } + + if (save_log) + ShowInfo("Saved guild (%d - %s):%s\n",g->guild_id,g->name,t_info); + return 0; +} + +// Read guild from sql +struct guild * inter_guild_fromsql(int guild_id) +{ + int i; + char emblem_data[4096]; + char *pstr; + struct guild *g; + + if (guild_id<=0) return NULL; + + g = idb_get(guild_db_,guild_id); + if (g) return g; + + g = (struct guild*)aCalloc(sizeof(struct guild), 1); + +#ifdef NOISY + ShowInfo("Guild load request (%d)...\n", guild_id); +#endif + + sprintf(tmp_sql,"SELECT `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data` " + "FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res==NULL || mysql_num_rows(sql_res)<1) { + //Guild does not exists. + if (sql_res) mysql_free_result(sql_res); + aFree(g); + return NULL; + } + + sql_row = mysql_fetch_row(sql_res); + if (sql_row==NULL) { + mysql_free_result(sql_res); + aFree(g); + return NULL; + } + + g->guild_id=guild_id; + strncpy(g->name,sql_row[0],NAME_LENGTH-1); + strncpy(g->master,sql_row[1],NAME_LENGTH-1); + g->guild_lv=atoi(sql_row[2]); + g->connect_member=atoi(sql_row[3]); + if (atoi(sql_row[4]) > MAX_GUILD) // Fix reduction of MAX_GUILD [PoW] + g->max_member = MAX_GUILD; + else + g->max_member = atoi(sql_row[4]); + g->average_lv=atoi(sql_row[5]); + g->exp=atoi(sql_row[6]); + g->next_exp=atoi(sql_row[7]); + g->skill_point=atoi(sql_row[8]); + //There shouldn't be a need to copy the very last char, as it's the \0 [Skotlex] + strncpy(g->mes1,sql_row[9],59); + strncpy(g->mes2,sql_row[10],119); + g->emblem_len=atoi(sql_row[11]); + g->emblem_id=atoi(sql_row[12]); + strncpy(emblem_data,sql_row[13],4096); + for(i=0,pstr=emblem_data;iemblem_len;i++,pstr+=2){ + int c1=pstr[0],c2=pstr[1],x1=0,x2=0; + if(c1>='0' && c1<='9')x1=c1-'0'; + if(c1>='a' && c1<='f')x1=c1-'a'+10; + if(c1>='A' && c1<='F')x1=c1-'A'+10; + if(c2>='0' && c2<='9')x2=c2-'0'; + if(c2>='a' && c2<='f')x2=c2-'a'+10; + if(c2>='A' && c2<='F')x2=c2-'A'+10; + g->emblem_data[i]=(x1<<4)|x2; + } + mysql_free_result(sql_res); + + //printf("- Read guild_member %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`rsv1`,`rsv2`,`name` " + "FROM `%s` WHERE `guild_id`='%d' ORDER BY `position`", guild_member_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&imax_member);i++){ + struct guild_member *m = &g->member[i]; + m->account_id=atoi(sql_row[1]); + m->char_id=atoi(sql_row[2]); + m->hair=atoi(sql_row[3]); + m->hair_color=atoi(sql_row[4]); + m->gender=atoi(sql_row[5]); + m->class_=atoi(sql_row[6]); + m->lv=atoi(sql_row[7]); + m->exp=atoi(sql_row[8]); + m->exp_payper=atoi(sql_row[9]); + m->online=atoi(sql_row[10]); + if (atoi(sql_row[11]) >= MAX_GUILDPOSITION) // Fix reduction of MAX_GUILDPOSITION [PoW] + m->position = MAX_GUILDPOSITION - 1; + else + m->position = atoi(sql_row[11]); + + strncpy(m->name,sql_row[14],NAME_LENGTH-1); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_position %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`position`,`name`,`mode`,`exp_mode` FROM `%s` WHERE `guild_id`='%d'",guild_position_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&iposition[position]; + strncpy(p->name,sql_row[2],NAME_LENGTH-1); + p->mode=atoi(sql_row[3]); + p->exp_mode=atoi(sql_row[4]); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_alliance %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`opposition`,`alliance_id`,`name` FROM `%s` WHERE `guild_id`='%d'",guild_alliance_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&ialliance[i]; + a->opposition=atoi(sql_row[1]); + a->guild_id=atoi(sql_row[2]); + strncpy(a->name,sql_row[3],NAME_LENGTH-1); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_expulsion %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3` FROM `%s` WHERE `guild_id`='%d'",guild_expulsion_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&iexplusion[i]; + + strncpy(e->name,sql_row[1],NAME_LENGTH-1); + //No need to copy char 40, the null terminator. [Skotlex] + strncpy(e->mes,sql_row[2],39); + strncpy(e->acc,sql_row[3],39); + e->account_id=atoi(sql_row[4]); + e->rsv1=atoi(sql_row[5]); + e->rsv2=atoi(sql_row[6]); + e->rsv3=atoi(sql_row[7]); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_skill %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`id`,`lv` FROM `%s` WHERE `guild_id`='%d' ORDER BY `id`",guild_skill_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(g); + return NULL; + } + + for(i = 0; i < MAX_GUILDSKILL; i++) + { //Skill IDs must always be initialized. [Skotlex] + g->skill[i].id = i + GD_SKILLBASE; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + while ((sql_row = mysql_fetch_row(sql_res))){ + int id = atoi(sql_row[1])-GD_SKILLBASE; + if (id >= 0 && id < MAX_GUILDSKILL) + //I know this seems ridiculous, but the skills HAVE to be placed on their 'correct' array slot or things break x.x [Skotlex] + g->skill[id].lv=atoi(sql_row[2]); + } + } + mysql_free_result(sql_res); + + idb_put(guild_db_, guild_id, g); //Add to cache + g->save_flag |= GS_REMOVE; //But set it to be removed, in case it is not needed for long. + + if (save_log) + ShowInfo("Guild loaded (%d - %s)\n", guild_id, g->name); + + return g; +} + +int inter_guildcastle_tosql(struct guild_castle *gc){ + // `guild_castle` (`castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`, `visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`) + + if (gc==NULL) return 0; + #ifdef GUILD_DEBUG +ShowDebug("Save guild_castle (%d)\n", gc->castle_id); + #endif + +// sql_query("DELETE FROM `%s` WHERE `castle_id`='%d'",guild_castle_db, gc->castle_id); + + sprintf(tmp_sql,"REPLACE INTO `%s` " + "(`castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`," + "`visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`," + "`Ghp0`, `Ghp1`, `Ghp2`, `Ghp3`, `Ghp4`, `Ghp5`, `Ghp6`, `Ghp7`)" + "VALUES ('%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d')", + guild_castle_db, gc->castle_id, gc->guild_id, gc->economy, gc->defense, gc->triggerE, gc->triggerD, gc->nextTime, gc->payTime, + gc->createTime, gc->visibleC, + gc->guardian[0].visible, gc->guardian[1].visible, gc->guardian[2].visible, gc->guardian[3].visible, gc->guardian[4].visible, gc->guardian[5].visible, gc->guardian[6].visible, gc->guardian[7].visible, + gc->guardian[0].hp, gc->guardian[1].hp, gc->guardian[2].hp, gc->guardian[3].hp, gc->guardian[4].hp, gc->guardian[5].hp, gc->guardian[6].hp, gc->guardian[7].hp); + + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + + memcpy(&castles[gc->castle_id],gc,sizeof(struct guild_castle)); + + return 0; +} + + +// Read guild_castle from sql +int inter_guildcastle_fromsql(int castle_id,struct guild_castle *gc) +{ + static int castles_init=0; + if (gc==NULL) return 0; + if (castle_id==-1) return 0; + + if(!castles_init) + { + int i; + for(i=0;icastle_id=castle_id; + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + if (sql_row==NULL){ + mysql_free_result(sql_res); + return 1; //Assume empty castle. + } + gc->guild_id = atoi (sql_row[1]); + gc->economy = atoi (sql_row[2]); + gc->defense = atoi (sql_row[3]); + gc->triggerE = atoi (sql_row[4]); + gc->triggerD = atoi (sql_row[5]); + gc->nextTime = atoi (sql_row[6]); + gc->payTime = atoi (sql_row[7]); + gc->createTime = atoi (sql_row[8]); + gc->visibleC = atoi (sql_row[9]); + gc->guardian[0].visible = atoi (sql_row[10]); + gc->guardian[1].visible = atoi (sql_row[11]); + gc->guardian[2].visible = atoi (sql_row[12]); + gc->guardian[3].visible = atoi (sql_row[13]); + gc->guardian[4].visible = atoi (sql_row[14]); + gc->guardian[5].visible = atoi (sql_row[15]); + gc->guardian[6].visible = atoi (sql_row[16]); + gc->guardian[7].visible = atoi (sql_row[17]); + gc->guardian[0].hp = atoi (sql_row[18]); + gc->guardian[1].hp = atoi (sql_row[19]); + gc->guardian[2].hp = atoi (sql_row[20]); + gc->guardian[3].hp = atoi (sql_row[21]); + gc->guardian[4].hp = atoi (sql_row[22]); + gc->guardian[5].hp = atoi (sql_row[23]); + gc->guardian[6].hp = atoi (sql_row[24]); + gc->guardian[7].hp = atoi (sql_row[25]); + + if (save_log) + ShowInfo("Loaded Castle %d (guild %d)\n",castle_id,gc->guild_id); + + } + mysql_free_result(sql_res) ; //resource free + + memcpy(&castles[castle_id],gc,sizeof(struct guild_castle)); + + return 1; +} + + +// Read exp_guild.txt +int inter_guild_ReadEXP() +{ + int i; + FILE *fp; + char line[1024]; + for (i=0;i<100;i++) guild_exp[i]=0; + + sprintf(line, "%s/exp_guild.txt", db_path); + fp=fopen(line,"r"); + if(fp==NULL){ + ShowError("can't read %s\n", line); + return 1; + } + i=0; + while(fgets(line,256,fp) && i<100){ + if(line[0]=='/' && line[1]=='/') + continue; + guild_exp[i]=atoi(line); + i++; + } + fclose(fp); + + return 0; +} + + +int inter_guild_CharOnline(int char_id, int guild_id) { + + struct guild *g; + int i; + + if (guild_id == -1) { + //Get guild_id from the database + sprintf (tmp_sql , "SELECT guild_id FROM `%s` WHERE char_id='%d'",char_db,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res == NULL) + return 0; //Eh? No guild? + + sql_row = mysql_fetch_row(sql_res); + guild_id = sql_row?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + } + if (guild_id == 0) + return 0; //No guild... + + g = inter_guild_fromsql(guild_id); + if(!g) { + ShowError("Character %d's guild %d not found!\n", char_id, guild_id); + return 0; + } + + //Member has logged in before saving, tell saver not to delete + if(g->save_flag & GS_REMOVE) + g->save_flag &= ~GS_REMOVE; + + //Set member online + for(i=0; imax_member; i++) { + if (g->member[i].char_id == char_id) { + g->member[i].online = 1; + break; + } + } + return 1; +} + +int inter_guild_CharOffline(int char_id, int guild_id) { + struct guild *g=NULL; + int online_count=0, i; + + if (guild_id == -1) { + //Get guild_id from the database + sprintf (tmp_sql , "SELECT guild_id FROM `%s` WHERE char_id='%d'",char_db,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res == NULL) + return 0; //Eh? No guild? + + sql_row = mysql_fetch_row(sql_res); + guild_id = sql_row?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + } + if (guild_id == 0) + return 0; //No guild... + + //Character has a guild, set character offline and check if they were the only member online + g = inter_guild_fromsql(guild_id); + if (g == NULL) //Guild not found? + return 0; + + //Set member offline + for(i=0; imax_member; i++) { + if(g->member[i].char_id == char_id) + g->member[i].online = 0; + if(g->member[i].online) + online_count++; + } + + if(online_count == 0) + g->save_flag |= GS_REMOVE; + + return 1; +} + +// Initialize guild sql +int inter_guild_sql_init() +{ + int i; + + //Initialize the guild cache + guild_db_= db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + //Read exp file + inter_guild_ReadEXP(); + + //Set the new guild ID + sprintf (tmp_sql , "SELECT max(`guild_id`) FROM `%s`",guild_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + exit(0); + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res) { + sql_row = mysql_fetch_row(sql_res); + if(sql_row[0]) { + if((i = atoi(sql_row[0])) != 0) { + guild_newid = i+1; + } + } + mysql_free_result(sql_res); + } + + add_timer_func_list(guild_save_timer, "guild_save_timer"); + add_timer(gettick() + 10000, guild_save_timer, 0, 0); + return 0; +} + +static int guild_db_final(DBKey key, void *data, va_list ap) +{ + struct guild *g = (struct guild*)data; + if (g->save_flag&GS_MASK) { + inter_guild_tosql(g, g->save_flag&GS_MASK); + return 1; + } + return 0; +} + +void inter_guild_sql_final() +{ + guild_db_->destroy(guild_db_, guild_db_final); + return; +} + +// Get guild_id by its name. Returns 0 if not found, -1 on error. +int search_guildname(char *str) +{ + int guild_id; + + //Lookup guilds with the same name + sprintf (tmp_sql , "SELECT guild_id FROM `%s` WHERE name='%s'",guild_db,jstrescape(str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return -1; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res) sql_row = mysql_fetch_row(sql_res); + + guild_id = (sql_row&&sql_res&&sql_row[0])?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + return guild_id; +} + +// Check if guild is empty +int guild_check_empty(struct guild *g) +{ + int i; + for(i=0;imax_member;i++){ + if(g->member[i].account_id>0){ + return 0; + } + } + //Let the calling function handle the guild removal in case they need + //to do something else with it before freeing the data. [Skotlex] + return 1; +} + +int guild_nextexp(int level) +{ + if (level == 0) + return 1; + if (level < 100 && level > 0) // Change by hack + return guild_exp[level-1]; + + return 0; +} + +// ギルドスキルが・るか確認 +int guild_checkskill(struct guild *g,int id) { + + int idx = id - GD_SKILLBASE; + + + if(idx < 0 || idx >= MAX_GUILDSKILL) + + return 0; + + return g->skill[idx].lv; + +} + +// ギルドの情報の再計算 +int guild_calcinfo(struct guild *g) +{ + int i,c,nextexp; + struct guild before=*g; + + // スキルIDの設定 + for(i=0;iskill[i].id=i+GD_SKILLBASE; + + // ギルドレベル + if(g->guild_lv<=0) g->guild_lv=1; + nextexp = guild_nextexp(g->guild_lv); + if(nextexp > 0) { + while(g->exp >= nextexp && nextexp > 0){ //fixed guild exp overflow [Kevin] + g->exp-=nextexp; + g->guild_lv++; + g->skill_point++; + nextexp = guild_nextexp(g->guild_lv); + } + } + + // ギルドの次の経験値 + g->next_exp = guild_nextexp(g->guild_lv); + + // メンバ上限(ギルド拡張適用) + g->max_member = 16 + guild_checkskill(g, GD_EXTENSION) * 6; //Guild Extention skill - currently adds 6 to max per skill lv. + if(g->max_member > MAX_GUILD) + { + ShowError("Guild %d:%s has capacity for too many guild members (%d), max supported is %d\n", g->guild_id, g->name, g->max_member, MAX_GUILD); + g->max_member = MAX_GUILD; + } + + // 平均レベルとオンライン人数 + g->average_lv=0; + g->connect_member=0; + for(i=c=0;imax_member;i++){ + if(g->member[i].account_id>0){ + g->average_lv+=g->member[i].lv; + c++; + + if(g->member[i].online>0) + g->connect_member++; + } + } + if(c) g->average_lv/=c; + + // 全データを送る必要が・りそう + if(g->max_member!=before.max_member || + g->guild_lv!=before.guild_lv || + g->skill_point!=before.skill_point ){ + mapif_guild_info(-1,g); + return 1; + } + + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// ギルド作成可否 +int mapif_guild_created(int fd,int account_id,struct guild *g) +{ + WFIFOW(fd,0)=0x3830; + WFIFOL(fd,2)=account_id; + if(g!=NULL){ + WFIFOL(fd,6)=g->guild_id; + ShowInfo("int_guild: Guild created (%d - %s)\n",g->guild_id,g->name); + }else{ + WFIFOL(fd,6)=0; + } + WFIFOSET(fd,10); + return 0; +} +// ギルド情報見つからず +int mapif_guild_noinfo(int fd,int guild_id) +{ + unsigned char buf[12]; + WBUFW(buf,0)=0x3831; + WBUFW(buf,2)=8; + WBUFL(buf,4)=guild_id; + ShowWarning("int_guild: info not found %d\n",guild_id); + if(fd<0) + mapif_sendall(buf,8); + else + mapif_send(fd,buf,8); + return 0; +} +// ギルド情報まとめ送り +int mapif_guild_info(int fd,struct guild *g) +{ + unsigned char buf[8+sizeof(struct guild)]; + WBUFW(buf,0)=0x3831; + memcpy(buf+4,g,sizeof(struct guild)); + WBUFW(buf,2)=4+sizeof(struct guild); + if(fd<0) + mapif_sendall(buf,WBUFW(buf,2)); + else + mapif_send(fd,buf,WBUFW(buf,2)); + return 0; +} + +// メンバ追加可否 +int mapif_guild_memberadded(int fd,int guild_id,int account_id,int char_id,int flag) +{ + WFIFOW(fd,0)=0x3832; + WFIFOL(fd,2)=guild_id; + WFIFOL(fd,6)=account_id; + WFIFOL(fd,10)=char_id; + WFIFOB(fd,14)=flag; + WFIFOSET(fd,15); + return 0; +} +// 脱退/追放通知 +int mapif_guild_leaved(int guild_id,int account_id,int char_id,int flag, + const char *name,const char *mes) +{ + unsigned char buf[128]; + WBUFW(buf, 0)=0x3834; + WBUFL(buf, 2)=guild_id; + WBUFL(buf, 6)=account_id; + WBUFL(buf,10)=char_id; + WBUFB(buf,14)=flag; + memcpy(WBUFP(buf,15),mes,40); + memcpy(WBUFP(buf,55),name,NAME_LENGTH); + mapif_sendall(buf,55+NAME_LENGTH); +// mapif_sendall(buf,79); + ShowInfo("int_guild: guild leaved (%d - %d: %s - %s)\n",guild_id,account_id,name,mes); + return 0; +} + +// オンライン状態とLv更新通知 +int mapif_guild_memberinfoshort(struct guild *g,int idx) +{ + unsigned char buf[32]; + WBUFW(buf, 0)=0x3835; + WBUFL(buf, 2)=g->guild_id; + WBUFL(buf, 6)=g->member[idx].account_id; + WBUFL(buf,10)=g->member[idx].char_id; + WBUFB(buf,14)=(unsigned char)g->member[idx].online; + WBUFW(buf,15)=g->member[idx].lv; + WBUFW(buf,17)=g->member[idx].class_; + mapif_sendall(buf,19); + return 0; +} + +// 解散通知 +int mapif_guild_broken(int guild_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3836; + WBUFL(buf,2)=guild_id; + WBUFB(buf,6)=flag; + mapif_sendall(buf,7); + ShowInfo("int_guild: Guild broken (%d)\n",guild_id); + return 0; +} + +// ギルド内発言 +int mapif_guild_message(int guild_id,int account_id,char *mes,int len, int sfd) +{ + unsigned char buf[512]; + WBUFW(buf,0)=0x3837; + WBUFW(buf,2)=len+12; + WBUFL(buf,4)=guild_id; + WBUFL(buf,8)=account_id; + memcpy(WBUFP(buf,12),mes,len); + mapif_sendallwos(sfd, buf,len+12); + return 0; +} + +// ギルド基本情報変更通知 +int mapif_guild_basicinfochanged(int guild_id,int type,const void *data,int len) +{ + unsigned char buf[2048]; + WBUFW(buf, 0)=0x3839; + WBUFW(buf, 2)=len+10; + WBUFL(buf, 4)=guild_id; + WBUFW(buf, 8)=type; + memcpy(WBUFP(buf,10),data,len); + mapif_sendall(buf,len+10); + return 0; +} +// ギルドメンバ情報変更通知 +int mapif_guild_memberinfochanged(int guild_id,int account_id,int char_id, + int type,const void *data,int len) +{ + unsigned char buf[2048]; + WBUFW(buf, 0)=0x383a; + WBUFW(buf, 2)=len+18; + WBUFL(buf, 4)=guild_id; + WBUFL(buf, 8)=account_id; + WBUFL(buf,12)=char_id; + WBUFW(buf,16)=type; + memcpy(WBUFP(buf,18),data,len); + mapif_sendall(buf,len+18); + return 0; +} +// ギルドスキルアップ通知 +int mapif_guild_skillupack(int guild_id,int skill_num,int account_id) +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x383c; + WBUFL(buf, 2)=guild_id; + WBUFL(buf, 6)=skill_num; + WBUFL(buf,10)=account_id; + mapif_sendall(buf,14); + return 0; +} +// ギルド同盟/敵対通知 +int mapif_guild_alliance(int guild_id1,int guild_id2,int account_id1,int account_id2, + int flag,const char *name1,const char *name2) +{ + unsigned char buf[128]; + WBUFW(buf, 0)=0x383d; + WBUFL(buf, 2)=guild_id1; + WBUFL(buf, 6)=guild_id2; + WBUFL(buf,10)=account_id1; + WBUFL(buf,14)=account_id2; + WBUFB(buf,18)=flag; + memcpy(WBUFP(buf,19),name1,NAME_LENGTH); + memcpy(WBUFP(buf,19+NAME_LENGTH),name2,NAME_LENGTH); + mapif_sendall(buf,19+2*NAME_LENGTH); +// memcpy(WBUFP(buf,43),name2,NAME_LENGTH); +// mapif_sendall(buf,67); + return 0; +} + +// ギルド役職変更通知 +int mapif_guild_position(struct guild *g,int idx) +{ + unsigned char buf[128]; + WBUFW(buf,0)=0x383b; + WBUFW(buf,2)=sizeof(struct guild_position)+12; + WBUFL(buf,4)=g->guild_id; + WBUFL(buf,8)=idx; + memcpy(WBUFP(buf,12),&g->position[idx],sizeof(struct guild_position)); + mapif_sendall(buf,WBUFW(buf,2)); + return 0; +} + +// ギルド告知変更通知 +int mapif_guild_notice(struct guild *g) +{ + unsigned char buf[256]; + WBUFW(buf,0)=0x383e; + WBUFL(buf,2)=g->guild_id; + memcpy(WBUFP(buf,6),g->mes1,60); + memcpy(WBUFP(buf,66),g->mes2,120); + mapif_sendall(buf,186); + return 0; +} +// ギルドエンブレム変更通知 +int mapif_guild_emblem(struct guild *g) +{ + unsigned char buf[2048]; + WBUFW(buf,0)=0x383f; + WBUFW(buf,2)=g->emblem_len+12; + WBUFL(buf,4)=g->guild_id; + WBUFL(buf,8)=g->emblem_id; + memcpy(WBUFP(buf,12),g->emblem_data,g->emblem_len); + mapif_sendall(buf,WBUFW(buf,2)); + return 0; +} +int mapif_guild_master_changed(struct guild *g, int position) +{ + unsigned char buf[12]; + WBUFW(buf,0)=0x3843; + WBUFL(buf,2)=g->guild_id; + WBUFL(buf,6)=position; + mapif_sendall(buf,10); + return 0; +} + +int mapif_guild_castle_dataload(int castle_id,int index,int value) // +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x3840; + WBUFW(buf, 2)=castle_id; + WBUFB(buf, 4)=index; + WBUFL(buf, 5)=value; + mapif_sendall(buf,9); + return 0; +} + +int mapif_guild_castle_datasave(int castle_id,int index,int value) // +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x3841; + WBUFW(buf, 2)=castle_id; + WBUFB(buf, 4)=index; + WBUFL(buf, 5)=value; + mapif_sendall(buf,9); + return 0; +} + +int mapif_guild_castle_alldataload(int fd) { + struct guild_castle* gc = (struct guild_castle *)aMalloc(sizeof(struct guild_castle)); + int i, len = 4; + + WFIFOW(fd,0) = 0x3842; + sprintf(tmp_sql,"SELECT * FROM `%s` ORDER BY `castle_id`", guild_castle_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res != NULL && mysql_num_rows(sql_res) > 0) { + for(i = 0; ((sql_row = mysql_fetch_row(sql_res)) && i < MAX_GUILDCASTLE); i++) { + memset(gc, 0, sizeof(struct guild_castle)); + gc->castle_id = atoi(sql_row[0]); + gc->guild_id = atoi(sql_row[1]); + gc->economy = atoi(sql_row[2]); + gc->defense = atoi(sql_row[3]); + gc->triggerE = atoi(sql_row[4]); + gc->triggerD = atoi(sql_row[5]); + gc->nextTime = atoi(sql_row[6]); + gc->payTime = atoi(sql_row[7]); + gc->createTime = atoi(sql_row[8]); + gc->visibleC = atoi(sql_row[9]); + gc->guardian[0].visible = atoi(sql_row[10]); + gc->guardian[1].visible = atoi(sql_row[11]); + gc->guardian[2].visible = atoi(sql_row[12]); + gc->guardian[3].visible = atoi(sql_row[13]); + gc->guardian[4].visible = atoi(sql_row[14]); + gc->guardian[5].visible = atoi(sql_row[15]); + gc->guardian[6].visible = atoi(sql_row[16]); + gc->guardian[7].visible = atoi(sql_row[17]); + gc->guardian[0].visible = atoi(sql_row[18]); + gc->guardian[1].visible = atoi(sql_row[19]); + gc->guardian[2].visible = atoi(sql_row[20]); + gc->guardian[3].visible = atoi(sql_row[21]); + gc->guardian[4].visible = atoi(sql_row[22]); + gc->guardian[5].visible = atoi(sql_row[23]); + gc->guardian[6].visible = atoi(sql_row[24]); + gc->guardian[7].visible = atoi(sql_row[25]); + memcpy(WFIFOP(fd,len), gc, sizeof(struct guild_castle)); + len += sizeof(struct guild_castle); + } + } + mysql_free_result(sql_res); + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + + aFree(gc); + + return 0; +} + + +//------------------------------------------------------------------- +// map serverからの通信 + + +// ギルド作成要求 +int mapif_parse_CreateGuild(int fd,int account_id,char *name,struct guild_member *master) +{ + struct guild *g; + int i=0; +#ifdef NOISY + ShowInfo("Creating Guild (%s)\n", name); +#endif + if(search_guildname(name) != 0){ + ShowInfo("int_guild: guild with same name exists [%s]\n",name); + mapif_guild_created(fd,account_id,NULL); + return 0; + } + g = (struct guild *)aMalloc(sizeof(struct guild)); + memset(g,0,sizeof(struct guild)); + g->guild_id=guild_newid++; + if (inter_guild_fromsql(g->guild_id) != NULL) { + ShowWarning("mapif_parse_CreateGuild: New Guild ID [%d] already exists!\n", g->guild_id); + mapif_guild_created(fd,account_id,NULL); + aFree(g); + return 0; + } + memcpy(g->name,name,NAME_LENGTH); + memcpy(g->master,master->name,NAME_LENGTH); + memcpy(&g->member[0],master,sizeof(struct guild_member)); + + g->position[0].mode=0x11; + strcpy(g->position[0].name,"GuildMaster"); + strcpy(g->position[MAX_GUILDPOSITION-1].name,"Newbie"); + for(i=1;iposition[i].name,"Position %d",i+1); + + // Initialize guild property + g->max_member=16; + g->average_lv=master->lv; + for(i=0;iskill[i].id=i + GD_SKILLBASE; + //Add to cache + ShowInfo("Created Guild %d - %s (Guild Master: %s)\n", g->guild_id, g->name, g->master); + idb_put(guild_db_, g->guild_id, g); + inter_guild_tosql(g,GS_BASIC|GS_POSITION|GS_SKILL); //Better save the whole guild right now. + + // Report to client + mapif_guild_created(fd,account_id,g); + mapif_guild_info(fd,g); + + if(log_inter) + inter_log("guild %s (id=%d) created by master %s (id=%d)" RETCODE, + name, g->guild_id, master->name, master->account_id ); + + return 0; +} +// Return guild info to client +int mapif_parse_GuildInfo(int fd,int guild_id) +{ + struct guild * g = inter_guild_fromsql(guild_id); //We use this because on start-up the info of castle-owned guilds is requied. [Skotlex] + if(g){ + if (!guild_calcinfo(g)) + mapif_guild_info(fd,g); + //inter_guild_tosql(g,GS_BASIC); // Change guild + }else + mapif_guild_noinfo(fd,guild_id); + return 0; +} +// Add member to guild +int mapif_parse_GuildAddMember(int fd,int guild_id,struct guild_member *m) +{ + struct guild * g; + int i; + + g = inter_guild_fromsql(guild_id); + if(g==NULL){ + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,1); + return 0; + } + + for(i=0;imax_member;i++){ + if(g->member[i].account_id==0){ + + memcpy(&g->member[i],m,sizeof(struct guild_member)); + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,0); + if (!guild_calcinfo(g)) //Send members if it was not invoked. + mapif_guild_info(fd,g); + g->save_flag |= (GS_BASIC|GS_MEMBER); + if (g->save_flag&GS_REMOVE) + g->save_flag&=~GS_REMOVE; + return 0; + } + } + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,1); + return 0; +} +// Delete member from guild +int mapif_parse_GuildLeave(int fd,int guild_id,int account_id,int char_id,int flag,const char *mes) +{ + struct guild * g = inter_guild_fromsql(guild_id); + + if(g){ + int i; + for(i=0;imax_member;i++){ + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id){ + if(flag){ // 追放の場合追放リストに入れる + int j; + for(j=0;jexplusion[j].account_id==0) + break; + } + if(j==MAX_GUILDEXPLUSION){ // 一杯なので古いのを消す + for(j=0;jexplusion[j]=g->explusion[j+1]; + j=MAX_GUILDEXPLUSION-1; + } + g->explusion[j].account_id=account_id; + memcpy(g->explusion[j].acc,"dummy",NAME_LENGTH-1); + memcpy(g->explusion[j].name,g->member[i].name,NAME_LENGTH-1); + memcpy(g->explusion[j].mes,mes,40); + } + + mapif_guild_leaved(guild_id,account_id,char_id,flag,g->member[i].name,mes); + memset(&g->member[i],0,sizeof(struct guild_member)); + + if(!guild_check_empty(g)) { + break; + } + //Guild empty? break it. + mapif_parse_BreakGuild(-1,guild_id); //Break the guild. + return 0; + } + } + //Update member info. + if (!guild_calcinfo(g)) + mapif_guild_info(fd,g); + g->save_flag |= (GS_BASIC|GS_MEMBER|GS_EXPULSION); + }else{ + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `account_id`='%d' AND `char_id`='%d'",char_db, account_id,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + /* mapif_guild_leaved(guild_id,account_id,char_id,flag,g->member[i].name,mes); */ + } + + return 0; +} +// Change member info +int mapif_parse_GuildChangeMemberInfoShort(int fd,int guild_id, + int account_id,int char_id,int online,int lv,int class_) +{ + // Could speed up by manipulating only guild_member + struct guild * g; + int i,alv,c; + int prev_count; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + prev_count = g->connect_member; + g->connect_member=0; + + for(i=0,alv=0,c=0;imax_member;i++){ + if(g->member[i].account_id==account_id && + g->member[i].char_id==char_id){ + + g->member[i].online=online; + g->member[i].lv=lv; + g->member[i].class_=class_; + mapif_guild_memberinfoshort(g,i); + } + if( g->member[i].account_id>0 ){ + alv+=g->member[i].lv; + c++; + } + if( g->member[i].online ) + g->connect_member++; + } + + if (c) + { + alv= alv/c; + if (g->connect_member != prev_count || g->average_lv != alv) + { + g->average_lv=alv; + g->save_flag |= GS_BASIC; //FIXME: Save the base guild just because the avl/connect count changed? + } + if (g->save_flag & GS_REMOVE) + g->save_flag &= ~GS_REMOVE; + } + g->save_flag |= GS_MEMBER; //Update guild member data + return 0; +} + +// BreakGuild +int mapif_parse_BreakGuild(int fd,int guild_id) +{ + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + // Delete guild from sql + //printf("- Delete guild %d from guild\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_member_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_castle_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_storage_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d' OR `alliance_id` = '%d'", guild_alliance_db, guild_id, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_position_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_skill_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_expulsion_db, guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + //printf("- Update guild %d of char\n",guild_id); + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `guild_id`='%d'",char_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + mapif_guild_broken(guild_id,0); + + if(log_inter) + inter_log("guild %s (id=%d) broken" RETCODE,g->name,guild_id); + + //Remove the guild from memory. [Skotlex] + g = idb_remove(guild_db_, guild_id); + return 0; +} + +// ギルドメッセージ送信 +int mapif_parse_GuildMessage(int fd,int guild_id,int account_id,char *mes,int len) +{ + return mapif_guild_message(guild_id,account_id,mes,len, fd); +} +// ギルド基本データ変更要求 +int mapif_parse_GuildBasicInfoChange(int fd,int guild_id, + int type,const char *data,int len) +{ + struct guild * g; +// int dd=*((int *)data); + short dw=*((short *)data); + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + switch(type){ + case GBI_GUILDLV: { + ShowDebug("GBI_GUILDLV\n"); + if(dw>0 && g->guild_lv+dw<=50){ + g->guild_lv+=dw; + g->skill_point+=dw; + }else if(dw<0 && g->guild_lv+dw>=1) + g->guild_lv+=dw; + mapif_guild_info(-1,g); + g->save_flag |= GS_BASIC; + } return 0; + default: + ShowError("int_guild: GuildBasicInfoChange: Unknown type %d\n",type); + break; + } + mapif_guild_basicinfochanged(guild_id,type,data,len); + return 0; +} + +// ギルドメンバデータ変更要求 +int mapif_parse_GuildMemberInfoChange(int fd,int guild_id,int account_id,int char_id, + int type,const char *data,int len) +{ + // Could make some improvement in speed, because only change guild_member + int i; + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + for(i=0;imax_member;i++) + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id ) + break; + if(i==g->max_member){ + ShowWarning("int_guild: GuildMemberChange: Not found %d,%d in guild (%d - %s)\n", + account_id,char_id,guild_id,g->name); + return 0; + } + switch(type){ + case GMI_POSITION: // 役職 + { + g->member[i].position=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= (GS_BASIC|GS_MEMBER); + break; + } + case GMI_EXP: + { // EXP + int exp,oldexp=g->member[i].exp; + exp=g->member[i].exp=*((unsigned int *)data); + g->exp+=(exp-oldexp); + guild_calcinfo(g); // Lvアップ判断 + mapif_guild_basicinfochanged(guild_id,GBI_EXP,&g->exp,4); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= (GS_BASIC|GS_MEMBER); + break; + } + case GMI_HAIR: + { + g->member[i].hair=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_HAIR_COLOR: + { + g->member[i].hair_color=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_GENDER: + { + g->member[i].gender=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_CLASS: + { + g->member[i].class_=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + case GMI_LEVEL: + { + g->member[i].lv=*((int *)data); + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + g->save_flag |= GS_MEMBER; //Save new data. + break; + } + default: + ShowError("int_guild: GuildMemberInfoChange: Unknown type %d\n",type); + break; + } + return 0; +} + +int inter_guild_sex_changed(int guild_id,int account_id,int char_id, int gender) +{ + return mapif_parse_GuildMemberInfoChange(0, guild_id, account_id, char_id, GMI_GENDER, (const char*)&gender, sizeof(gender)); +} + +// ギルド役職名変更要求 +int mapif_parse_GuildPosition(int fd,int guild_id,int idx,struct guild_position *p) +{ + // Could make some improvement in speed, because only change guild_position + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL || idx<0 || idx>=MAX_GUILDPOSITION) + return 0; + + memcpy(&g->position[idx],p,sizeof(struct guild_position)); + mapif_guild_position(g,idx); + ShowInfo("int_guild: position data changed (Guild %d, position %d)\n",guild_id, idx); + g->save_flag |= GS_POSITION; // Change guild_position + return 0; +} +// ギルドスキルアップ要求 +int mapif_parse_GuildSkillUp(int fd,int guild_id,int skill_num,int account_id) +{ + // Could make some improvement in speed, because only change guild_position + struct guild * g; + int idx = skill_num - GD_SKILLBASE; + + g = inter_guild_fromsql(guild_id); + if(g == NULL || idx < 0 || idx >= MAX_GUILDSKILL) + return 0; + + if(g->skill_point>0 && g->skill[idx].id>0 && + g->skill[idx].lv<10 ){ + g->skill[idx].lv++; + g->skill_point--; + if (!guild_calcinfo(g)) + mapif_guild_info(-1,g); + mapif_guild_skillupack(guild_id,skill_num,account_id); + ShowDebug("int_guild: skill %d up\n",skill_num); + g->save_flag |= (GS_BASIC|GS_SKILL); // Change guild & guild_skill + } + + return 0; +} + +//Manual deletion of an alliance when partnering guild does not exists. [Skotlex] +static int mapif_parse_GuildDeleteAlliance(struct guild *g, int guild_id, int account_id1, int account_id2, int flag) +{ + int i; + char name[NAME_LENGTH]; + for(i=0;ialliance[i].guild_id == guild_id) + { + strcpy(name, g->alliance[i].name); + g->alliance[i].guild_id=0; + break; + } + if (i == MAX_GUILDALLIANCE) + return -1; + + mapif_guild_alliance(g->guild_id,guild_id,account_id1,account_id2,flag,g->name,name); + g->save_flag |= GS_ALLIANCE; + return 0; +} +// ギルド同盟要求 +int mapif_parse_GuildAlliance(int fd,int guild_id1,int guild_id2, + int account_id1,int account_id2,int flag) +{ + // Could speed up + struct guild *g[2]; + int j,i; + g[0] = inter_guild_fromsql(guild_id1); + g[1] = inter_guild_fromsql(guild_id2); + + if(g[0] && g[1]==NULL && (flag&0x8)) //Requested to remove an alliance with a not found guild. + return mapif_parse_GuildDeleteAlliance(g[0], guild_id2, + account_id1, account_id2, flag); //Try to do a manual removal of said guild. + + if(g[0]==NULL || g[1]==NULL) + return 0; + + if(!(flag&0x8)){ + for(i=0;i<2-(flag&1);i++){ + for(j=0;jalliance[j].guild_id==0){ + g[i]->alliance[j].guild_id=g[1-i]->guild_id; + memcpy(g[i]->alliance[j].name,g[1-i]->name,NAME_LENGTH-1); + g[i]->alliance[j].opposition=flag&1; + break; + } + } + }else{ // 関係解消 + for(i=0;i<2-(flag&1);i++){ + for(j=0;jalliance[j].guild_id==g[1-i]->guild_id && + g[i]->alliance[j].opposition==(flag&1)){ + g[i]->alliance[j].guild_id=0; + break; + } + } + } + mapif_guild_alliance(guild_id1,guild_id2,account_id1,account_id2,flag, + g[0]->name,g[1]->name); + g[0]->save_flag |= GS_ALLIANCE; + g[1]->save_flag |= GS_ALLIANCE; + return 0; +} +// ギルド告知変更要求 +int mapif_parse_GuildNotice(int fd,int guild_id,const char *mes1,const char *mes2) +{ + struct guild *g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + + memcpy(g->mes1,mes1,60); + memcpy(g->mes2,mes2,120); + g->save_flag |= GS_BASIC; //Change mes of guild + return mapif_guild_notice(g); +} +// ギルドエンブレム変更要求 +int mapif_parse_GuildEmblem(int fd,int len,int guild_id,int dummy,const char *data) +{ + struct guild * g; + + g = inter_guild_fromsql(guild_id); + if(g==NULL) + return 0; + memcpy(g->emblem_data,data,len); + g->emblem_len=len; + g->emblem_id++; + g->save_flag |= GS_BASIC; //Change guild + return mapif_guild_emblem(g); +} + +int mapif_parse_GuildCastleDataLoad(int fd,int castle_id,int index) // +{ + struct guild_castle gc; + if (!inter_guildcastle_fromsql(castle_id, &gc)) { + return mapif_guild_castle_dataload(castle_id,0,0); + } + switch(index){ + case 1: return mapif_guild_castle_dataload(gc.castle_id,index,gc.guild_id); break; + case 2: return mapif_guild_castle_dataload(gc.castle_id,index,gc.economy); break; + case 3: return mapif_guild_castle_dataload(gc.castle_id,index,gc.defense); break; + case 4: return mapif_guild_castle_dataload(gc.castle_id,index,gc.triggerE); break; + case 5: return mapif_guild_castle_dataload(gc.castle_id,index,gc.triggerD); break; + case 6: return mapif_guild_castle_dataload(gc.castle_id,index,gc.nextTime); break; + case 7: return mapif_guild_castle_dataload(gc.castle_id,index,gc.payTime); break; + case 8: return mapif_guild_castle_dataload(gc.castle_id,index,gc.createTime); break; + case 9: return mapif_guild_castle_dataload(gc.castle_id,index,gc.visibleC); break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + return mapif_guild_castle_dataload(gc.castle_id,index,gc.guardian[index-10].visible); break; + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + return mapif_guild_castle_dataload(gc.castle_id,index,gc.guardian[index-18].hp); break; + default: + ShowError("mapif_parse_GuildCastleDataLoad ERROR!! (Not found index=%d)\n", index); + return 0; + } +} + +int mapif_parse_GuildCastleDataSave(int fd,int castle_id,int index,int value) // +{ + struct guild_castle gc; + if(!inter_guildcastle_fromsql(castle_id, &gc)) + return mapif_guild_castle_datasave(castle_id,index,value); + + switch(index){ + case 1: + if( gc.guild_id!=value ){ + int gid=(value)?value:gc.guild_id; + struct guild *g=idb_get(guild_db_, gid); + if(log_inter) + inter_log("guild %s (id=%d) %s castle id=%d" RETCODE, + (g)?g->name:"??" ,gid, (value)?"occupy":"abandon", castle_id); + } + gc.guild_id = value; + break; + case 2: gc.economy = value; break; + case 3: gc.defense = value; break; + case 4: gc.triggerE = value; break; + case 5: gc.triggerD = value; break; + case 6: gc.nextTime = value; break; + case 7: gc.payTime = value; break; + case 8: gc.createTime = value; break; + case 9: gc.visibleC = value; break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + gc.guardian[index-10].visible = value; break; + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + gc.guardian[index-18].hp = value; break; // end additions [Valaris] + default: + ShowError("mapif_parse_GuildCastleDataSave ERROR!! (Not found index=%d)\n", index); + return 0; + } + inter_guildcastle_tosql(&gc); + mapif_guild_castle_datasave(gc.castle_id,index,value); + return 0; +} + +int mapif_parse_GuildMasterChange(int fd, int guild_id, const char* name, int len) +{ + struct guild * g; + struct guild_member gm; + int pos; + + g = inter_guild_fromsql(guild_id); + + if(g==NULL || len > NAME_LENGTH) + return 0; + + for (pos = 0; pos < g->max_member && strncmp(g->member[pos].name, name, len); pos++); + + if (pos == g->max_member) + return 0; //Character not found?? + + memcpy(&gm, &g->member[pos], sizeof (struct guild_member)); + memcpy(&g->member[pos], &g->member[0], sizeof(struct guild_member)); + memcpy(&g->member[0], &gm, sizeof(struct guild_member)); + + g->member[pos].position = g->member[0].position; + g->member[0].position = 0; //Position 0: guild Master. + strncpy(g->master, name, len); + if (len < NAME_LENGTH) + g->master[len] = '\0'; + + ShowInfo("int_guild: Guildmaster Changed to %s (Guild %d - %s)\n",g->master, guild_id, g->name); + g->save_flag |= (GS_BASIC|GS_POSITION); //Save main data and member data. + return mapif_guild_master_changed(g, pos); +} + +// ギルドチェック要求 +int mapif_parse_GuildCheck(int fd,int guild_id,int account_id,int char_id) +{ + // What does this mean? Check if belong to another guild? + return 0; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_guild_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)){ + case 0x3030: mapif_parse_CreateGuild(fd,RFIFOL(fd,4),(char*)RFIFOP(fd,8),(struct guild_member *)RFIFOP(fd,32)); break; + case 0x3031: mapif_parse_GuildInfo(fd,RFIFOL(fd,2)); break; + case 0x3032: mapif_parse_GuildAddMember(fd,RFIFOL(fd,4),(struct guild_member *)RFIFOP(fd,8)); break; + case 0x3033: mapif_parse_GuildMasterChange(fd,RFIFOL(fd,4),(const char*)RFIFOP(fd,8),RFIFOW(fd,2)-8); break; + case 0x3034: mapif_parse_GuildLeave(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),(const char*)RFIFOP(fd,15)); break; + case 0x3035: mapif_parse_GuildChangeMemberInfoShort(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),RFIFOW(fd,15),RFIFOW(fd,17)); break; + case 0x3036: mapif_parse_BreakGuild(fd,RFIFOL(fd,2)); break; + case 0x3037: mapif_parse_GuildMessage(fd,RFIFOL(fd,4),RFIFOL(fd,8),(char*)RFIFOP(fd,12),RFIFOW(fd,2)-12); break; + case 0x3038: mapif_parse_GuildCheck(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x3039: mapif_parse_GuildBasicInfoChange(fd,RFIFOL(fd,4),RFIFOW(fd,8),(const char*)RFIFOP(fd,10),RFIFOW(fd,2)-10); break; + case 0x303A: mapif_parse_GuildMemberInfoChange(fd,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOL(fd,12),RFIFOW(fd,16),(const char*)RFIFOP(fd,18),RFIFOW(fd,2)-18); break; + case 0x303B: mapif_parse_GuildPosition(fd,RFIFOL(fd,4),RFIFOL(fd,8),(struct guild_position *)RFIFOP(fd,12)); break; + case 0x303C: mapif_parse_GuildSkillUp(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); break; + case 0x303D: mapif_parse_GuildAlliance(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOL(fd,14),RFIFOB(fd,18)); break; + case 0x303E: mapif_parse_GuildNotice(fd,RFIFOL(fd,2),(const char*)RFIFOP(fd,6),(const char*)RFIFOP(fd,66)); break; + case 0x303F: mapif_parse_GuildEmblem(fd,RFIFOW(fd,2)-12,RFIFOL(fd,4),RFIFOL(fd,8),(const char*)RFIFOP(fd,12)); break; + case 0x3040: mapif_parse_GuildCastleDataLoad(fd,RFIFOW(fd,2),RFIFOB(fd,4)); break; + case 0x3041: mapif_parse_GuildCastleDataSave(fd,RFIFOW(fd,2),RFIFOB(fd,4),RFIFOL(fd,5)); break; + + default: + return 0; + } + return 1; +} + +int inter_guild_mapif_init(int fd) +{ + return mapif_guild_castle_alldataload(fd); +} + +// サーバーから脱退要求(キャラ削除用) +int inter_guild_leave(int guild_id,int account_id,int char_id) +{ + return mapif_parse_GuildLeave(-1,guild_id,account_id,char_id,0,"** Character Deleted **"); +} + +int inter_guild_broken(int guild_id) +{ + return mapif_guild_broken(guild_id, 0); +} diff --git a/src/char_sql/int_guild.h b/src/char_sql/int_guild.h new file mode 100644 index 000000000..452d55612 --- /dev/null +++ b/src/char_sql/int_guild.h @@ -0,0 +1,18 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_GUILD_H_ +#define _INT_GUILD_H_ + +int inter_guild_parse_frommap(int fd); +int inter_guild_sql_init(); +void inter_guild_sql_final(); +int inter_guild_mapif_init(int fd); +int inter_guild_leave(int guild_id,int account_id,int char_id); +int mapif_parse_BreakGuild(int fd,int guild_id); +int inter_guild_broken(int guild_id); +int inter_guild_sex_changed(int guild_id,int account_id,int char_id, int gender); +int inter_guild_CharOnline(int char_id, int guild_id); +int inter_guild_CharOffline(int char_id, int guild_id); + +#endif diff --git a/src/char_sql/int_party.c b/src/char_sql/int_party.c new file mode 100644 index 000000000..0479df848 --- /dev/null +++ b/src/char_sql/int_party.c @@ -0,0 +1,865 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by hack + +#include +#include +#include +#include "char.h" +#include "../common/db.h" +#include "../common/strlib.h" +#include "../common/socket.h" +#include "../common/showmsg.h" + +static struct party *party_pt; +static int party_newid = 100; +static struct dbt *party_db_; + +int mapif_party_broken(int party_id,int flag); +int party_check_empty(struct party *p); +int mapif_parse_PartyLeave(int fd, int party_id, int account_id, int char_id); + +#ifndef SQL_DEBUG + +#define mysql_query(_x, _y) mysql_real_query(_x, _y, strlen(_y)) //supports ' in names and runs faster [Kevin] + +#else + +#define mysql_query(_x, _y) debug_mysql_query(__FILE__, __LINE__, _x, _y) + +#endif + +//Party Flags on what to save/delete. +//Create a new party entry (index holds leader's info) +#define PS_CREATE 0x01 +//Update basic party info. +#define PS_BASIC 0x02 +//Update party's leader +#define PS_LEADER 0x04 +//Specify new party member (index specifies which party member) +#define PS_ADDMEMBER 0x08 +//Specify member that left (index specifies which party member) +#define PS_DELMEMBER 0x10 +//Specify that this party must be deleted. +#define PS_BREAK 0x20 + +// Save party to mysql +int inter_party_tosql(int party_id,struct party *p, int flag, int index) +{ + // 'party' ('party_id','name','exp','item','leader_id','leader_char') + char t_name[NAME_LENGTH*2]; //Required for jstrescapecpy [Skotlex] + int party_exist = 0; + if (p == NULL || party_id == 0 || p->party_id == 0 || party_id != p->party_id) { + ShowError("Party pointer or party_id error (id: %d)\n", party_id); + return 0; + } +#ifdef NOISY + ShowInfo("Save party request ("CL_BOLD"%d"CL_RESET" - %s).\n", party_id, p->name); +#endif + jstrescapecpy(t_name, p->name); + + if (flag&PS_BREAK) { //Break the party + // we'll skip name-checking and just reset everyone with the same party id [celest] + sprintf (tmp_sql, "UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d'", char_db, party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `party_id`='%d'", party_db, party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + //Remove from memory + idb_remove(party_db_, party_id); + return 1; + } + + if(flag&PS_CREATE) { + //Create party, first check if ID exists. + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `party_id`='%d'", party_db, party_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res != NULL && mysql_num_rows(sql_res) > 0) { + sql_row = mysql_fetch_row(sql_res); + party_exist = atoi (sql_row[0]); + } + mysql_free_result(sql_res); + if (party_exist) { //TODO: Can't we just use an index, and then retrieve the new party's index from SQL? [Skotlex] + ShowError("inter_party_tosql: Creating party with already existing ID %d!\n", party_id); + aFree(p); //Free party, couldn't create it. + return 0; + } + sprintf(tmp_sql, "INSERT INTO `%s` (`party_id`, `name`, `exp`, `item`, `leader_id`, `leader_char`) VALUES ('%d', '%s', '%d', '%d', '%d', '%d')", + party_db, party_id, t_name, p->exp, p->item, p->member[index].account_id, p->member[index].char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + aFree(p); //Free party, couldn't create it. + return 0; + } + //Add party to db + idb_put(party_db_, party_id, p); + } + + if (flag&PS_BASIC) { + //Update party info. + sprintf(tmp_sql, "UPDATE `%s` SET `name`='%s', `exp`='%d', `item`='%d' WHERE `party_id`='%d'", + party_db, t_name, p->exp, p->item, party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if (flag&PS_LEADER) { + //Update leader + sprintf(tmp_sql, "UPDATE `%s` SET `leader_id`='%d', `leader_char`='%d' WHERE `party_id`='%d'", + party_db, p->member[index].account_id, p->member[index].char_id, party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if (flag&PS_ADDMEMBER) { + //Add one party member. + sprintf (tmp_sql, "UPDATE `%s` SET `party_id`='%d' WHERE `account_id`='%d' AND `char_id`='%d'", + char_db, party_id, p->member[index].account_id, p->member[index].char_id); + if (mysql_query (&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if (flag&PS_DELMEMBER) { + //Remove one party member. + sprintf (tmp_sql, "UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d' AND `account_id`='%d' AND `char_id`='%d'", + char_db, party_id, p->member[index].account_id, p->member[index].char_id); + if (mysql_query (&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + if (save_log) + ShowInfo("Party Saved (%d - %s)\n", party_id, p->name); + return 1; +} + +// Read party from mysql +struct party *inter_party_fromsql(int party_id) +{ + int leader_id = 0, leader_char = 0; + struct party *p; +#ifdef NOISY + ShowInfo("Load party request ("CL_BOLD"%d"CL_RESET")\n", party_id); +#endif + if (party_id <=0) + return NULL; + + //Load from memory + if ((p = idb_get(party_db_, party_id)) != NULL) + return p; + + p = party_pt; + memset(p, 0, sizeof(struct party)); + + sprintf(tmp_sql, "SELECT `party_id`, `name`,`exp`,`item`, `leader_id`, `leader_char` FROM `%s` WHERE `party_id`='%d'", + party_db, party_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return NULL; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res != NULL && mysql_num_rows(sql_res) > 0) { + sql_row = mysql_fetch_row(sql_res); + p->party_id = party_id; + memcpy(p->name, sql_row[1], NAME_LENGTH-1); + p->exp = atoi(sql_row[2])?1:0; + p->item = atoi(sql_row[3]); + leader_id = atoi(sql_row[4]); + leader_char = atoi(sql_row[5]); + } else { + mysql_free_result(sql_res); + return NULL; + } + mysql_free_result(sql_res); + + // Load members + sprintf(tmp_sql,"SELECT `account_id`,`char_id`,`name`,`base_level`,`last_map`,`online` FROM `%s` WHERE `party_id`='%d'", + char_db, party_id); // TBR + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return NULL; + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res != NULL && mysql_num_rows(sql_res) > 0) { + int i; + for (i = 0; (sql_row = mysql_fetch_row(sql_res)); i++) { + struct party_member *m = &p->member[i]; + m->account_id = atoi(sql_row[0]); + m->char_id = atoi(sql_row[1]); + m->leader = (m->account_id == leader_id && m->char_id == leader_char)?1:0; + memcpy(m->name, sql_row[2], NAME_LENGTH); + m->lv = atoi(sql_row[3]); + m->map = mapindex_name2id(sql_row[4]); + m->online = atoi(sql_row[5])?1:0; + } + } + mysql_free_result(sql_res); + + if (save_log) + ShowInfo("Party loaded (%d - %s).\n",party_id, p->name); + //Add party to memory. + p = aCalloc(1, sizeof(struct party)); + memcpy(p, party_pt, sizeof(struct party)); + idb_put(party_db_, party_id, p); + return p; +} + +int inter_party_sql_init(){ + int i; + + //memory alloc + party_db_ = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + party_pt = (struct party*)aCalloc(sizeof(struct party), 1); + if (!party_pt) { + ShowFatalError("inter_party_sql_init: Out of Memory!\n"); + exit(1); + } + sprintf (tmp_sql , "SELECT count(*) FROM `%s`", party_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + ShowStatus("total party data -> '%s'.......\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i > 0) { + //set party_newid + sprintf (tmp_sql , "SELECT max(`party_id`) FROM `%s`", party_db); + if(mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle) ; + + sql_row = mysql_fetch_row(sql_res); + party_newid = atoi (sql_row[0])+1; + mysql_free_result(sql_res); + } + + ShowDebug("set party_newid: %d.......\n", party_newid); + + /* Uncomment the following if you want to do a party_db cleanup (remove parties with no members) on startup.[Skotlex] + ShowStatus("cleaning party table...\n"); + sprintf (tmp_sql, + "DELETE FROM `%s` USING `%s` LEFT JOIN `%s` ON `%s`.leader_id =`%s`.account_id AND `%s`.leader_char = `%s`.char_id WHERE `%s`.account_id IS NULL", + party_db, party_db, char_db, party_db, char_db, party_db, char_db, char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + */ + return 0; +} + +void inter_party_sql_final() +{ + party_db_->destroy(party_db_, NULL); + aFree(party_pt); + return; +} + +// Search for the party according to its name +struct party* search_partyname(char *str) +{ + char t_name[NAME_LENGTH*2]; + int party_id; + + sprintf(tmp_sql,"SELECT `party_id` FROM `%s` WHERE `name`='%s'",party_db, jstrescapecpy(t_name,str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res==NULL || mysql_num_rows(sql_res)<=0) + { + if (sql_res) mysql_free_result(sql_res); + return NULL; + } + sql_row = mysql_fetch_row(sql_res); + party_id = sql_row?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + + return inter_party_fromsql(party_id); +} + +// EXP公平分配できるかチェック +int party_check_exp_share(struct party *p) +{ + int i, oi[MAX_PARTY], dudes=0; + int maxlv=0,minlv=0x7fffffff; + + for(i=0;imember[i].lv; + if (!lv) continue; + if( p->member[i].online ){ + if( lv < minlv ) minlv=lv; + if( maxlv < lv ) maxlv=lv; + if( lv >= 70 ) dudes+=1000; + oi[dudes%1000] = i; + dudes++; + } + } + if((dudes/1000 >= 2) && (dudes%1000 == 3) && maxlv-minlv>party_share_level) + { + int pl1=0,pl2=0,pl3=0; + pl1=char_nick2id(p->member[oi[0]].name); + pl2=char_nick2id(p->member[oi[1]].name); + pl3=char_nick2id(p->member[oi[2]].name); + ShowDebug("PARTY: group of 3 Id1 %d lv %d name %s Id2 %d lv %d name %s Id3 %d lv %d name %s\n",pl1,p->member[oi[0]].lv,p->member[oi[0]].name,pl2,p->member[oi[1]].lv,p->member[oi[1]].name,pl3,p->member[oi[2]].lv,p->member[oi[2]].name); + if (char_married(pl1,pl2) && char_child(pl1,pl3)) + return 1; + if (char_married(pl1,pl3) && char_child(pl1,pl2)) + return 1; + if (char_married(pl2,pl3) && char_child(pl2,pl1)) + return 1; + } + return (maxlv==0 || maxlv-minlv<=party_share_level); +} + +// Is there any member in the party? +int party_check_empty(struct party *p) +{ + int i; + if (p==NULL||p->party_id==0) return 1; + for(i=0;imember[i].account_id>0){ + return 0; + } + } + // If there is no member, then break the party + mapif_party_broken(p->party_id,0); + inter_party_tosql(p->party_id,p, PS_BREAK, 0); + return 1; +} + + +// Check if a member is in two party, not necessary :) +int party_check_conflict(int party_id,int account_id,int char_id) +{ + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// パーティ作成可否 +int mapif_party_created(int fd,int account_id,int char_id,struct party *p) +{ + WFIFOW(fd,0)=0x3820; + WFIFOL(fd,2)=account_id; + WFIFOL(fd,6)=char_id; + if(p!=NULL){ + WFIFOB(fd,10)=0; + WFIFOL(fd,11)=p->party_id; + memcpy(WFIFOP(fd,15),p->name,NAME_LENGTH); + ShowInfo("int_party: Party created (%d - %s)\n",p->party_id,p->name); + }else{ + WFIFOB(fd,10)=1; + WFIFOL(fd,11)=0; + memset(WFIFOP(fd,15),0,NAME_LENGTH); + } + WFIFOSET(fd,39); + + return 0; +} + +// パーティ情報見つからず +int mapif_party_noinfo(int fd,int party_id) +{ + WFIFOW(fd,0)=0x3821; + WFIFOW(fd,2)=8; + WFIFOL(fd,4)=party_id; + WFIFOSET(fd,8); + ShowWarning("int_party: info not found %d\n",party_id); + return 0; +} +// パーティ情報まとめ送り +int mapif_party_info(int fd,struct party *p) +{ + unsigned char buf[10+sizeof(struct party)]; + WBUFW(buf,0)=0x3821; + memcpy(buf+4,p,sizeof(struct party)); + WBUFW(buf,2)=4+sizeof(struct party); + if(fd<0) + mapif_sendall(buf,WBUFW(buf,2)); + else + mapif_send(fd,buf,WBUFW(buf,2)); + return 0; +} +// パーティメンバ追加可否 +int mapif_party_memberadded(int fd, int party_id, int account_id, int char_id, int flag) { + WFIFOHEAD(fd, 15); + WFIFOW(fd,0) = 0x3822; + WFIFOL(fd,2) = party_id; + WFIFOL(fd,6) = account_id; + WFIFOL(fd,10) = char_id; + WFIFOB(fd,14) = flag; + WFIFOSET(fd,15); + + return 0; +} + +// パーティ設定変更通知 +int mapif_party_optionchanged(int fd,struct party *p,int account_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3823; + WBUFL(buf,2)=p->party_id; + WBUFL(buf,6)=account_id; + WBUFW(buf,10)=p->exp; + WBUFW(buf,12)=p->item; + WBUFB(buf,14)=flag; + if(flag==0) + mapif_sendall(buf,15); + else + mapif_send(fd,buf,15); + return 0; +} + +//Checks whether the even-share setting of a party is broken when a character logs in. [Skotlex] +int inter_party_logged(int party_id, int account_id, int char_id) +{ + struct party *p; + int i; + + if (party_id <= 0) + return 0; + + if (!party_id) + return 0; + p = inter_party_fromsql(party_id); + if(!p) //Non existant party? + return 0; + + for(i = 0; i < MAX_PARTY; i++) + if (p->member[i].account_id==account_id && p->member[i].char_id==char_id) { + p->member[i].online = 1; + break; + } + + if(p->exp && !party_check_exp_share(p)) + { + p->exp=0; + mapif_party_optionchanged(0,p,0,0); + return 1; + } + return 0; +} + +// パーティ脱退通知 +int mapif_party_leaved(int party_id,int account_id, int char_id) { + unsigned char buf[16]; + + WBUFW(buf,0) = 0x3824; + WBUFL(buf,2) = party_id; + WBUFL(buf,6) = account_id; + WBUFL(buf,10) = char_id; + mapif_sendall(buf, 14); + return 0; +} + +// パーティマップ更新通知 +int mapif_party_membermoved(struct party *p,int idx) +{ + unsigned char buf[20]; + + WBUFW(buf,0) = 0x3825; + WBUFL(buf,2) = p->party_id; + WBUFL(buf,6) = p->member[idx].account_id; + WBUFL(buf,10) = p->member[idx].char_id; + WBUFW(buf,14) = p->member[idx].map; + WBUFB(buf,16) = p->member[idx].online; + WBUFW(buf,17) = p->member[idx].lv; + mapif_sendall(buf, 19); + return 0; +} + +// パーティ解散通知 +int mapif_party_broken(int party_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3826; + WBUFL(buf,2)=party_id; + WBUFB(buf,6)=flag; + mapif_sendall(buf,7); + //printf("int_party: broken %d\n",party_id); + return 0; +} +// パーティ内発言 +int mapif_party_message(int party_id,int account_id,char *mes,int len, int sfd) +{ + unsigned char buf[512]; + WBUFW(buf,0)=0x3827; + WBUFW(buf,2)=len+12; + WBUFL(buf,4)=party_id; + WBUFL(buf,8)=account_id; + memcpy(WBUFP(buf,12),mes,len); + mapif_sendallwos(sfd, buf,len+12); + return 0; +} + +//------------------------------------------------------------------- +// map serverからの通信 + + +// Create Party +int mapif_parse_CreateParty(int fd, int account_id, int char_id, char *name, char *nick, unsigned short map, int lv, int item, int item2) +{ + struct party *p; + if( (p=search_partyname(name))!=NULL){ + mapif_party_created(fd,account_id,char_id,NULL); + return 0; + } + p= aCalloc(1, sizeof(struct party)); + + p->party_id=party_newid++; + memcpy(p->name,name,NAME_LENGTH); + p->exp=0; + p->item=(item?1:0)|(item2?2:0); + p->itemc = 0; + + p->member[0].account_id=account_id; + p->member[0].char_id =char_id; + memcpy(p->member[0].name,nick,NAME_LENGTH-1); + p->member[0].map = map; + p->member[0].leader=1; + p->member[0].online=1; + p->member[0].lv=lv; + + if (inter_party_tosql(p->party_id,p,PS_CREATE|PS_ADDMEMBER,0)) { + mapif_party_created(fd,account_id,char_id,p); + mapif_party_info(fd,p); + } else //Failed to create party. + mapif_party_created(fd,account_id,char_id,NULL); + + return 0; +} +// パーティ情報要求 +int mapif_parse_PartyInfo(int fd,int party_id) +{ + struct party *p; + p = inter_party_fromsql(party_id); + + if (p) + mapif_party_info(fd,p); + else + mapif_party_noinfo(fd,party_id); + return 0; +} +// パーティ追加要求 +int mapif_parse_PartyAddMember(int fd, int party_id, int account_id, int char_id, char *nick, unsigned short map, int lv) { + struct party *p; + int i; + + p = inter_party_fromsql(party_id); + + if(!p){ + mapif_party_memberadded(fd,party_id,account_id,char_id,1); + return 0; + } + + for(i=0;imember[i].account_id==0){ + int flag=0; + + p->member[i].account_id=account_id; + p->member[i].char_id=char_id; + memcpy(p->member[i].name,nick,NAME_LENGTH); + p->member[i].map = map; + p->member[i].leader=0; + p->member[i].online=1; + p->member[i].lv=lv; + mapif_party_memberadded(fd,party_id,account_id,char_id,0); + mapif_party_info(-1,p); + + if( p->exp && !party_check_exp_share(p) ){ + p->exp=0; + flag=0x01; + } + if(flag) + mapif_party_optionchanged(fd,p,0,0); + + inter_party_tosql(party_id, p, PS_ADDMEMBER, i); + return 0; + } + } + mapif_party_memberadded(fd,party_id,account_id,char_id,1); + return 0; +} +// パーティー設定変更要求 +int mapif_parse_PartyChangeOption(int fd,int party_id,int account_id,int exp,int flag) +{ + struct party *p; + //NOTE: No clue what that flag is about, in all observations so far it always comes as 0. [Skotlex] + flag = 0; + p = inter_party_fromsql(party_id); + + if(!p) + return 0; + + p->exp=exp; + if( exp && !party_check_exp_share(p) ){ + flag|=0x01; + p->exp=0; + } + mapif_party_optionchanged(fd,p,account_id,flag); + inter_party_tosql(party_id, p, PS_BASIC, 0); + return 0; +} +// パーティ脱退要求 +int mapif_parse_PartyLeave(int fd, int party_id, int account_id, int char_id) +{ + struct party *p; + int i; + + p = inter_party_fromsql(party_id); + if (!p) { //Party does not exists? + sprintf(tmp_sql, "UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d'", char_db, party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return 0; + } + + for (i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id && p->member[i].char_id == char_id) + break; + } + if (i>= MAX_PARTY) + return 0; //Member not found? + + mapif_party_leaved(party_id, account_id, char_id); + + if (p->member[i].leader){ + int j; + for (j = 0; j < MAX_PARTY; j++) { + if (p->member[j].account_id > 0 && j != i) { + mapif_party_leaved(party_id, p->member[j].account_id, p->member[j].char_id); + p->member[j].account_id = 0; + } + p->member[i].account_id = 0; + } + //Party gets deleted on the check_empty call below. + } else { + inter_party_tosql(party_id,p,PS_DELMEMBER,i); + memset(&p->member[i], 0, sizeof(struct party_member)); + } + + if (party_check_empty(p) == 0) + mapif_party_info(-1,p); + return 0; +} +// When member goes to other map +int mapif_parse_PartyChangeMap(int fd, int party_id, int account_id, int char_id, unsigned short map, int online, int lv) +{ + struct party *p; + int i; + + p = inter_party_fromsql(party_id); + if (p == NULL) + return 0; + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id && p->member[i].char_id == char_id) + { + p->member[i].map = map; + p->member[i].online = online; + if (p->member[i].lv != lv) { + p->member[i].lv = lv; + if (p->exp && !party_check_exp_share(p)) { + p->exp = 0; + mapif_party_optionchanged(fd, p, 0, 0); + } + } + mapif_party_membermoved(p, i); + break; + } + } + return 0; +} +// パーティ解散要求 +int mapif_parse_BreakParty(int fd,int party_id) +{ + struct party *p; + + p = inter_party_fromsql(party_id); + + if(!p) + return 0; + inter_party_tosql(party_id,p,PS_BREAK,0); + mapif_party_broken(fd,party_id); + return 0; +} +// パーティメッセージ送信 +int mapif_parse_PartyMessage(int fd,int party_id,int account_id,char *mes,int len) +{ + return mapif_party_message(party_id,account_id,mes,len, fd); +} +// パーティチェック要求 +int mapif_parse_PartyCheck(int fd,int party_id,int account_id,int char_id) +{ + return party_check_conflict(party_id,account_id,char_id); +} + +int mapif_parse_PartyLeaderChange(int fd,int party_id,int account_id,int char_id) +{ + struct party *p; + int i; + + p = inter_party_fromsql(party_id); + + if(!p) + return 0; + + for (i = 0; i < MAX_PARTY; i++) + { + if(p->member[i].leader) + p->member[i].leader = 0; + if(p->member[i].account_id == account_id && p->member[i].char_id == char_id) { + p->member[i].leader = 1; + inter_party_tosql(party_id,p,PS_LEADER, i); + } + } + return 1; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_party_parse_frommap(int fd) +{ + RFIFOHEAD(fd); + switch(RFIFOW(fd,0)) { + case 0x3020: mapif_parse_CreateParty(fd, RFIFOL(fd,2), RFIFOL(fd,6),(char*)RFIFOP(fd,10), (char*)RFIFOP(fd,34), RFIFOW(fd,58), RFIFOW(fd,60), RFIFOB(fd,62), RFIFOB(fd,63)); break; + case 0x3021: mapif_parse_PartyInfo(fd, RFIFOL(fd,2)); break; + case 0x3022: mapif_parse_PartyAddMember(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), (char*)RFIFOP(fd,14), RFIFOW(fd,38), RFIFOW(fd,40)); break; + case 0x3023: mapif_parse_PartyChangeOption(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOW(fd,10), RFIFOW(fd,12)); break; + case 0x3024: mapif_parse_PartyLeave(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x3025: mapif_parse_PartyChangeMap(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOW(fd,14), RFIFOB(fd,16), RFIFOW(fd,17)); break; + case 0x3026: mapif_parse_BreakParty(fd, RFIFOL(fd,2)); break; + case 0x3027: mapif_parse_PartyMessage(fd, RFIFOL(fd,4), RFIFOL(fd,8), (char*)RFIFOP(fd,12), RFIFOW(fd,2)-12); break; + case 0x3028: mapif_parse_PartyCheck(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x3029: mapif_parse_PartyLeaderChange(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + default: + return 0; + } + return 1; +} + +// サーバーから脱退要求(キャラ削除用) +int inter_party_leave(int party_id,int account_id, int char_id) +{ + return mapif_parse_PartyLeave(-1,party_id,account_id, char_id); +} + +int inter_party_CharOnline(int char_id, int party_id) { + struct party *p; + int i; + + if (party_id == -1) { + //Get guild_id from the database + sprintf (tmp_sql , "SELECT party_id FROM `%s` WHERE char_id='%d'",char_db,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res == NULL) + return 0; //Eh? No party? + + sql_row = mysql_fetch_row(sql_res); + party_id = sql_row?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + } + if (party_id == 0) + return 0; //No party... + + p = inter_party_fromsql(party_id); + if(!p) { + ShowError("Character %d's party %d not found!\n", char_id, party_id); + return 0; + } + + //Set member online + for(i=0; imember[i].char_id == char_id) { + p->member[i].online = 1; + break; + } + } + return 1; +} + +int inter_party_CharOffline(int char_id, int party_id) { + struct party *p=NULL; + int online_count=0, i; + + if (party_id == -1) { + //Get guild_id from the database + sprintf (tmp_sql , "SELECT party_id FROM `%s` WHERE char_id='%d'",char_db,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if(sql_res == NULL) + return 0; //Eh? No party? + + sql_row = mysql_fetch_row(sql_res); + party_id = sql_row?atoi(sql_row[0]):0; + mysql_free_result(sql_res); + } + if (party_id == 0) + return 0; //No party... + + //Character has a party, set character offline and check if they were the only member online + if ((p = inter_party_fromsql(party_id)) == NULL) + return 0; + + //Set member offline + for(i=0; i< MAX_PARTY; i++) { + if(p->member[i].char_id == char_id) + p->member[i].online = 0; + if(p->member[i].online) + online_count++; + } + + if(online_count == 0) + //Parties don't have any data that needs be saved at this point... so just remove it from memory. + idb_remove(party_db_, party_id); + return 1; +} diff --git a/src/char_sql/int_party.h b/src/char_sql/int_party.h new file mode 100644 index 000000000..353787a2d --- /dev/null +++ b/src/char_sql/int_party.h @@ -0,0 +1,14 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_PARTY_H_ +#define _INT_PARTY_H_ + +int inter_party_parse_frommap(int fd); +int inter_party_sql_init(); +void inter_party_sql_final(); +int inter_party_leave(int party_id,int account_id, int char_id); +int inter_party_logged(int party_id, int account_id, int char_id); +int inter_party_CharOnline(int char_id, int party_id); +int inter_party_CharOffline(int char_id, int party_id); +#endif diff --git a/src/char_sql/int_pet.c b/src/char_sql/int_pet.c new file mode 100644 index 000000000..cc0e9330e --- /dev/null +++ b/src/char_sql/int_pet.c @@ -0,0 +1,351 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by Jioh L. Jung + +#include +#include +#include + +#include "char.h" +#include "../common/strlib.h" +#include "../common/showmsg.h" + +struct s_pet *pet_pt; +static int pet_newid = 100; + + + +#ifndef SQL_DEBUG + +#define mysql_query(_x, _y) mysql_real_query(_x, _y, strlen(_y)) //supports ' in names and runs faster [Kevin] + +#else + +#define mysql_query(_x, _y) debug_mysql_query(__FILE__, __LINE__, _x, _y) + +#endif + +//--------------------------------------------------------- +int inter_pet_tosql(int pet_id, struct s_pet *p) { + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + char t_name[NAME_LENGTH*2]; + +// ShowInfo("Saving pet (%d)...\n",pet_id); + + jstrescapecpy(t_name, p->name); + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + sprintf(tmp_sql,"SELECT * FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) + //row reside -> updating + sprintf(tmp_sql, "UPDATE `%s` SET `class`='%d',`name`='%s',`account_id`='%d',`char_id`='%d',`level`='%d',`egg_id`='%d',`equip`='%d',`intimate`='%d',`hungry`='%d',`rename_flag`='%d',`incuvate`='%d' WHERE `pet_id`='%d'", + pet_db, p->class_, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate, p->pet_id); + else //no row -> insert + sprintf(tmp_sql,"INSERT INTO `%s` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) VALUES ('%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + pet_db, pet_id, p->class_, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate); + mysql_free_result(sql_res) ; //resource free + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + if (save_log) + ShowInfo("Pet saved %d - %s.\n", pet_id, p->name); + return 0; +} + +int inter_pet_fromsql(int pet_id, struct s_pet *p){ + +#ifdef NOISY + ShowInfo("Loading pet (%d)...\n",pet_id); +#endif + memset(p, 0, sizeof(struct s_pet)); + + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + + sprintf(tmp_sql,"SELECT `pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate` FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + + p->pet_id = pet_id; + p->class_ = atoi(sql_row[1]); + memcpy(p->name, sql_row[2],NAME_LENGTH-1); + p->account_id = atoi(sql_row[3]); + p->char_id = atoi(sql_row[4]); + p->level = atoi(sql_row[5]); + p->egg_id = atoi(sql_row[6]); + p->equip = atoi(sql_row[7]); + p->intimate = atoi(sql_row[8]); + p->hungry = atoi(sql_row[9]); + p->rename_flag = atoi(sql_row[10]); + p->incuvate = atoi(sql_row[11]); + } + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + mysql_free_result(sql_res); + + if (save_log) + ShowInfo("Pet loaded (%d - %s).\n", pet_id, p->name); + return 0; +} +//---------------------------------------------- + +int inter_pet_sql_init(){ + int i; + + //memory alloc + ShowDebug("interserver pet memory initialize.... (%d byte)\n",sizeof(struct s_pet)); + pet_pt = (struct s_pet*)aCalloc(sizeof(struct s_pet), 1); + + sprintf (tmp_sql , "SELECT count(*) FROM `%s`", pet_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + exit(0); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + ShowStatus("total pet data: '%s'\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i > 0) { + //set pet_newid + sprintf (tmp_sql , "SELECT max(`pet_id`) FROM `%s`",pet_db ); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + sql_res = mysql_store_result(&mysql_handle) ; + + sql_row = mysql_fetch_row(sql_res); + pet_newid = atoi (sql_row[0])+1; //should SET MAX existing PET ID + 1 [Lupus] + mysql_free_result(sql_res); + } + + ShowDebug("set pet_newid: %d.\n",pet_newid); + + return 0; +} +void inter_pet_sql_final(){ + if (pet_pt) aFree(pet_pt); + return; +} +//---------------------------------- +int inter_pet_delete(int pet_id){ + ShowInfo("delete pet request: %d...\n",pet_id); + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return 0; +} +//------------------------------------------------------ +int mapif_pet_created(int fd, int account_id, struct s_pet *p) +{ + WFIFOW(fd, 0) =0x3880; + WFIFOL(fd, 2) =account_id; + if(p!=NULL){ + WFIFOB(fd, 6)=0; + WFIFOL(fd, 7) =p->pet_id; + ShowInfo("int_pet: created pet %d - %s\n", p->pet_id, p->name); + }else{ + WFIFOB(fd, 6)=1; + WFIFOL(fd, 7)=0; + } + WFIFOSET(fd, 11); + + return 0; +} + +int mapif_pet_info(int fd, int account_id, struct s_pet *p){ + WFIFOW(fd, 0) =0x3881; + WFIFOW(fd, 2) =sizeof(struct s_pet) + 9; + WFIFOL(fd, 4) =account_id; + WFIFOB(fd, 8)=0; + memcpy(WFIFOP(fd, 9), p, sizeof(struct s_pet)); + WFIFOSET(fd, WFIFOW(fd, 2)); + + return 0; +} + +int mapif_pet_noinfo(int fd, int account_id){ + WFIFOW(fd, 0) =0x3881; + WFIFOW(fd, 2) =sizeof(struct s_pet) + 9; + WFIFOL(fd, 4) =account_id; + WFIFOB(fd, 8)=1; + memset(WFIFOP(fd, 9), 0, sizeof(struct s_pet)); + WFIFOSET(fd, WFIFOW(fd, 2)); + + return 0; +} + +int mapif_save_pet_ack(int fd, int account_id, int flag){ + WFIFOW(fd, 0) =0x3882; + WFIFOL(fd, 2) =account_id; + WFIFOB(fd, 6) =flag; + WFIFOSET(fd, 7); + + return 0; +} + +int mapif_delete_pet_ack(int fd, int flag){ + WFIFOW(fd, 0) =0x3883; + WFIFOB(fd, 2) =flag; + WFIFOSET(fd, 3); + + return 0; +} + +int mapif_create_pet(int fd, int account_id, int char_id, short pet_class, short pet_lv, short pet_egg_id, + short pet_equip, short intimate, short hungry, char rename_flag, char incuvate, char *pet_name){ + + memset(pet_pt, 0, sizeof(struct s_pet)); + pet_pt->pet_id = pet_newid++; + memcpy(pet_pt->name, pet_name, NAME_LENGTH-1); + if(incuvate == 1) + pet_pt->account_id = pet_pt->char_id = 0; + else { + pet_pt->account_id = account_id; + pet_pt->char_id = char_id; + } + pet_pt->class_ = pet_class; + pet_pt->level = pet_lv; + pet_pt->egg_id = pet_egg_id; + pet_pt->equip = pet_equip; + pet_pt->intimate = intimate; + pet_pt->hungry = hungry; + pet_pt->rename_flag = rename_flag; + pet_pt->incuvate = incuvate; + + if(pet_pt->hungry < 0) + pet_pt->hungry = 0; + else if(pet_pt->hungry > 100) + pet_pt->hungry = 100; + if(pet_pt->intimate < 0) + pet_pt->intimate = 0; + else if(pet_pt->intimate > 1000) + pet_pt->intimate = 1000; + + inter_pet_tosql(pet_pt->pet_id,pet_pt); + + mapif_pet_created(fd, account_id, pet_pt); + + return 0; +} + +int mapif_load_pet(int fd, int account_id, int char_id, int pet_id){ + memset(pet_pt, 0, sizeof(struct s_pet)); + + inter_pet_fromsql(pet_id, pet_pt); + + if(pet_pt!=NULL) { + if(pet_pt->incuvate == 1) { + pet_pt->account_id = pet_pt->char_id = 0; + mapif_pet_info(fd, account_id, pet_pt); + } + else if(account_id == pet_pt->account_id && char_id == pet_pt->char_id) + mapif_pet_info(fd, account_id, pet_pt); + else + mapif_pet_noinfo(fd, account_id); + } + else + mapif_pet_noinfo(fd, account_id); + + return 0; +} + +int mapif_save_pet(int fd, int account_id, struct s_pet *data) { + //here process pet save request. + int len=RFIFOW(fd, 2); + if(sizeof(struct s_pet)!=len-8) { + ShowError("inter pet: data size error %d %d\n", sizeof(struct s_pet), len-8); + } + + else{ + if(data->hungry < 0) + data->hungry = 0; + else if(data->hungry > 100) + data->hungry = 100; + if(data->intimate < 0) + data->intimate = 0; + else if(data->intimate > 1000) + data->intimate = 1000; + inter_pet_tosql(data->pet_id,data); + mapif_save_pet_ack(fd, account_id, 0); + } + + return 0; +} + +int mapif_delete_pet(int fd, int pet_id){ + mapif_delete_pet_ack(fd, inter_pet_delete(pet_id)); + + return 0; +} + +int mapif_parse_CreatePet(int fd){ + mapif_create_pet(fd, RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOW(fd, 10), RFIFOW(fd, 12), RFIFOW(fd, 14), RFIFOW(fd, 16), RFIFOL(fd, 18), + RFIFOL(fd, 20), RFIFOB(fd, 22), RFIFOB(fd, 23), (char*)RFIFOP(fd, 24)); + return 0; +} + +int mapif_parse_LoadPet(int fd){ + mapif_load_pet(fd, RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOL(fd, 10)); + return 0; +} + +int mapif_parse_SavePet(int fd){ + mapif_save_pet(fd, RFIFOL(fd, 4), (struct s_pet *) RFIFOP(fd, 8)); + return 0; +} + +int mapif_parse_DeletePet(int fd){ + mapif_delete_pet(fd, RFIFOL(fd, 2)); + return 0; +} + +int inter_pet_parse_frommap(int fd){ + switch(RFIFOW(fd, 0)){ + case 0x3080: mapif_parse_CreatePet(fd); break; + case 0x3081: mapif_parse_LoadPet(fd); break; + case 0x3082: mapif_parse_SavePet(fd); break; + case 0x3083: mapif_parse_DeletePet(fd); break; + default: + return 0; + } + return 1; +} + diff --git a/src/char_sql/int_pet.h b/src/char_sql/int_pet.h new file mode 100644 index 000000000..d39574344 --- /dev/null +++ b/src/char_sql/int_pet.h @@ -0,0 +1,16 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_PET_H_ +#define _INT_PET_H_ + +int inter_pet_init(); +void inter_pet_sql_final(); +int inter_pet_save(); +int inter_pet_delete(int pet_id); + +int inter_pet_parse_frommap(int fd); +int inter_pet_sql_init(); +//extern char pet_txt[256]; + +#endif diff --git a/src/char_sql/int_storage.c b/src/char_sql/int_storage.c new file mode 100644 index 000000000..1dac9dc51 --- /dev/null +++ b/src/char_sql/int_storage.c @@ -0,0 +1,364 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by Jioh L. Jung + +#include +#include + +#include "char.h" +#include "itemdb.h" +#include "../common/showmsg.h" + +#define STORAGE_MEMINC 16 + +// reset by inter_config_read() +struct storage *storage_pt=NULL; +struct guild_storage *guild_storage_pt=NULL; + +#ifndef SQL_DEBUG + +#define mysql_query(_x, _y) mysql_real_query(_x, _y, strlen(_y)) //supports ' in names and runs faster [Kevin] + +#else + +#define mysql_query(_x, _y) debug_mysql_query(__FILE__, __LINE__, _x, _y) + +#endif + +// storage data -> DB conversion +int storage_tosql(int account_id,struct storage *p){ + int i,j; +// int eqcount=1; +// int noteqcount=1; + int count=0; + struct itemtmp mapitem[MAX_GUILD_STORAGE]; + for(i=0;istorage_[i].nameid>0){ + mapitem[count].flag=0; + mapitem[count].id = p->storage_[i].id; + mapitem[count].nameid=p->storage_[i].nameid; + mapitem[count].amount = p->storage_[i].amount; + mapitem[count].equip = p->storage_[i].equip; + mapitem[count].identify = p->storage_[i].identify; + mapitem[count].refine = p->storage_[i].refine; + mapitem[count].attribute = p->storage_[i].attribute; + for(j=0; jstorage_[i].card[j]; + count++; + } + } + + memitemdata_to_sql(mapitem, count, account_id,TABLE_STORAGE); + + //printf ("storage dump to DB - id: %d (total: %d)\n", account_id, j); + return 0; +} + +// DB -> storage data conversion +int storage_fromsql(int account_id, struct storage *p){ + int i=0,j; + char * str_p = tmp_sql; + + memset(p,0,sizeof(struct storage)); //clean up memory + p->storage_amount = 0; + p->account_id = account_id; + + // storage {`account_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + str_p += sprintf(str_p,"SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`"); + + for (j=0; jstorage_[i].id= atoi(sql_row[0]); + p->storage_[i].nameid= atoi(sql_row[1]); + p->storage_[i].amount= atoi(sql_row[2]); + p->storage_[i].equip= atoi(sql_row[3]); + p->storage_[i].identify= atoi(sql_row[4]); + p->storage_[i].refine= atoi(sql_row[5]); + p->storage_[i].attribute= atoi(sql_row[6]); + for (j=0; jstorage_[i].card[j]= atoi(sql_row[7+j]); + p->storage_amount = ++i; + } + mysql_free_result(sql_res); + } + + ShowInfo ("storage load complete from DB - id: %d (total: %d)\n", account_id, p->storage_amount); + return 1; +} + +// Save guild_storage data to sql +int guild_storage_tosql(int guild_id, struct guild_storage *p){ + int i,j; +// int eqcount=1; +// int noteqcount=1; + int count=0; + struct itemtmp mapitem[MAX_GUILD_STORAGE]; + for(i=0;istorage_[i].nameid>0){ + mapitem[count].flag=0; + mapitem[count].id = p->storage_[i].id; + mapitem[count].nameid=p->storage_[i].nameid; + mapitem[count].amount = p->storage_[i].amount; + mapitem[count].equip = p->storage_[i].equip; + mapitem[count].identify = p->storage_[i].identify; + mapitem[count].refine = p->storage_[i].refine; + mapitem[count].attribute = p->storage_[i].attribute; + for (j=0; jstorage_[i].card[j]; + count++; + } + } + + memitemdata_to_sql(mapitem, count, guild_id,TABLE_GUILD_STORAGE); + + ShowInfo ("guild storage save to DB - id: %d (total: %d)\n", guild_id,i); + return 0; +} + +// Load guild_storage data to mem +int guild_storage_fromsql(int guild_id, struct guild_storage *p){ + int i=0,j; + struct guild_storage *gs=guild_storage_pt; + char * str_p = tmp_sql; + p=gs; + + memset(p,0,sizeof(struct guild_storage)); //clean up memory + p->storage_amount = 0; + p->guild_id = guild_id; + + // storage {`guild_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + str_p += sprintf(str_p,"SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`"); + + for (j=0; jstorage_[i].id= atoi(sql_row[0]); + p->storage_[i].nameid= atoi(sql_row[1]); + p->storage_[i].amount= atoi(sql_row[2]); + p->storage_[i].equip= atoi(sql_row[3]); + p->storage_[i].identify= atoi(sql_row[4]); + p->storage_[i].refine= atoi(sql_row[5]); + p->storage_[i].attribute= atoi(sql_row[6]); + for (j=0; jstorage_[i].card[j] = atoi(sql_row[7+j]); + p->storage_amount = ++i; + if (i >= MAX_GUILD_STORAGE) + break; + } + mysql_free_result(sql_res); + } + ShowInfo ("guild storage load complete from DB - id: %d (total: %d)\n", guild_id, p->storage_amount); + return 0; +} + +//--------------------------------------------------------- +// storage data initialize +int inter_storage_sql_init(){ + + //memory alloc + ShowDebug("interserver storage memory initialize....(%d byte)\n",sizeof(struct storage)); + storage_pt = (struct storage*)aCalloc(sizeof(struct storage), 1); + guild_storage_pt = (struct guild_storage*)aCalloc(sizeof(struct guild_storage), 1); +// memset(storage_pt,0,sizeof(struct storage)); //Calloc sets stuff to 0 already. [Skotlex] +// memset(guild_storage_pt,0,sizeof(struct guild_storage)); + + return 1; +} +// storage data finalize +void inter_storage_sql_final() +{ + if (storage_pt) aFree(storage_pt); + if (guild_storage_pt) aFree(guild_storage_pt); + return; +} +// q?f[^? +int inter_storage_delete(int account_id) +{ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id`='%d'",storage_db, account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return 0; +} +int inter_guild_storage_delete(int guild_id) +{ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_storage_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + return 0; +} + +//--------------------------------------------------------- +// packet from map server + +// recive packet about storage data +int mapif_load_storage(int fd,int account_id){ + //load from DB + storage_fromsql(account_id, storage_pt); + WFIFOW(fd,0)=0x3810; + WFIFOW(fd,2)=sizeof(struct storage)+8; + WFIFOL(fd,4)=account_id; + memcpy(WFIFOP(fd,8),storage_pt,sizeof(struct storage)); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +// send ack to map server which is "storage data save ok." +int mapif_save_storage_ack(int fd,int account_id){ + WFIFOW(fd,0)=0x3811; + WFIFOL(fd,2)=account_id; + WFIFOB(fd,6)=0; + WFIFOSET(fd,7); + return 0; +} + +int mapif_load_guild_storage(int fd,int account_id,int guild_id) +{ + int guild_exist=1; + WFIFOW(fd,0)=0x3818; + +#if 0 // innodb guilds should render this check unnecessary [Aru] + // Check if guild exists, I may write a function for this later, coz I use it several times. + //printf("- Check if guild %d exists\n",g->guild_id); + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_exist = atoi (sql_row[0]); + //printf("- Check if guild %d exists : %s\n",g->guild_id,((guild_exist==0)?"No":"Yes")); + } + mysql_free_result(sql_res) ; //resource free +#endif + if(guild_exist==1) { + guild_storage_fromsql(guild_id,guild_storage_pt); + WFIFOW(fd,2)=sizeof(struct guild_storage)+12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=guild_id; + memcpy(WFIFOP(fd,12),guild_storage_pt,sizeof(struct guild_storage)); + } + else { + WFIFOW(fd,2)=12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=0; + } + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} +int mapif_save_guild_storage_ack(int fd,int account_id,int guild_id,int fail) +{ + WFIFOW(fd,0)=0x3819; + WFIFOL(fd,2)=account_id; + WFIFOL(fd,6)=guild_id; + WFIFOB(fd,10)=fail; + WFIFOSET(fd,11); + return 0; +} + +//--------------------------------------------------------- +// packet from map server + +// recive request about storage data +int mapif_parse_LoadStorage(int fd){ + mapif_load_storage(fd,RFIFOL(fd,2)); + return 0; +} +// storage data recive and save +int mapif_parse_SaveStorage(int fd){ + int account_id=RFIFOL(fd,4); + int len=RFIFOW(fd,2); + + if(sizeof(struct storage)!=len-8){ + ShowError("inter storage: data size error %d %d\n",sizeof(struct storage),len-8); + }else{ + memcpy(&storage_pt[0],RFIFOP(fd,8),sizeof(struct storage)); + storage_tosql(account_id, storage_pt); + mapif_save_storage_ack(fd,account_id); + } + return 0; +} + +int mapif_parse_LoadGuildStorage(int fd) +{ + mapif_load_guild_storage(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + return 0; +} + +int mapif_parse_SaveGuildStorage(int fd) +{ + int guild_exist=1; + int guild_id=RFIFOL(fd,8); + int len=RFIFOW(fd,2); + if(sizeof(struct guild_storage)!=len-12){ + ShowError("inter storage: data size error %d %d\n",sizeof(struct guild_storage),len-12); + } + else { +#if 0 // Again, innodb key checks make the check pointless + // Check if guild exists, I may write a function for this later, coz I use it several times. + //printf("- Check if guild %d exists\n",g->guild_id); + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_exist = atoi (sql_row[0]); + //printf("- Check if guild %d exists : %s\n",g->guild_id,((guild_exist==0)?"No":"Yes")); + } + mysql_free_result(sql_res) ; //resource free +#endif + if(guild_exist==1) { + memcpy(guild_storage_pt,RFIFOP(fd,12),sizeof(struct guild_storage)); + guild_storage_tosql(guild_id,guild_storage_pt); + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,0); + } + else + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,1); + } + return 0; +} + + +int inter_storage_parse_frommap(int fd){ + switch(RFIFOW(fd,0)){ + case 0x3010: mapif_parse_LoadStorage(fd); break; + case 0x3011: mapif_parse_SaveStorage(fd); break; + case 0x3018: mapif_parse_LoadGuildStorage(fd); break; + case 0x3019: mapif_parse_SaveGuildStorage(fd); break; + default: + return 0; + } + return 1; +} + diff --git a/src/char_sql/int_storage.h b/src/char_sql/int_storage.h new file mode 100644 index 000000000..f3b56a6d1 --- /dev/null +++ b/src/char_sql/int_storage.h @@ -0,0 +1,17 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INT_STORAGE_H_ +#define _INT_STORAGE_H_ + +int inter_storage_sql_init(); +void inter_storage_sql_final(); +int inter_storage_delete(int account_id); +int inter_guild_storage_delete(int guild_id); + +int inter_storage_parse_frommap(int fd); + + +//extern char storage_txt[256]; + +#endif diff --git a/src/char_sql/inter.c b/src/char_sql/inter.c new file mode 100644 index 000000000..0c6145bf9 --- /dev/null +++ b/src/char_sql/inter.c @@ -0,0 +1,789 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// original code from athena +// SQL conversion by Jioh L. Jung + +#include +#include + +#include "char.h" +#include "../common/strlib.h" +#include "../common/showmsg.h" +#include "inter.h" +#include "int_party.h" +#include "int_guild.h" +#include "int_storage.h" +#include "int_pet.h" + +#define WISDATA_TTL (60*1000) // Wisデータの生存時間(60秒) +#define WISDELLIST_MAX 256 // Wisデータ削除リストの要素数 + + +struct accreg { + int account_id, char_id; + int reg_num; + struct global_reg reg[MAX_REG_NUM]; +}; + +static struct accreg *accreg_pt; + + +int party_share_level = 10; +int kick_on_disconnect = 1; +MYSQL mysql_handle; +MYSQL_RES* sql_res ; +MYSQL_ROW sql_row ; +int sql_fields, sql_cnt; +char tmp_sql[65535]; + +MYSQL lmysql_handle; +MYSQL_RES* lsql_res ; +MYSQL_ROW lsql_row ; + +int char_server_port = 3306; +char char_server_ip[32] = "127.0.0.1"; +char char_server_id[32] = "ragnarok"; +char char_server_pw[32] = "ragnarok"; +char char_server_db[32] = "ragnarok"; +char default_codepage[32] = ""; //Feature by irmin. + +int login_server_port = 3306; +char login_server_ip[32] = "127.0.0.1"; +char login_server_id[32] = "ragnarok"; +char login_server_pw[32] = "ragnarok"; +char login_server_db[32] = "ragnarok"; + +char main_chat_nick[16] = "Main"; + +// sending packet list +// NOTE: This variable ain't used at all! And it's confusing.. where do I add that the length of packet 0x2b07 is 10? x.x [Skotlex] +int inter_send_packet_length[]={ + -1,-1,27,-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -1, 7, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, + 35,-1,11,15, 34,29, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, + 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, + 9, 9,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; +// recv. packet list +int inter_recv_packet_length[]={ + -1,-1, 7,-1, -1,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3000-0x300f + 6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, //0x3010-0x301f + 64, 6,42,14, 14,19, 6,-1, 14,14, 0, 0, 0, 0, 0, 0, //0x3020-0x302f + -1, 6,-1,-1, 55,19, 6,-1, 14,-1,-1,-1, 14,19,186,-1, //0x3030-0x303f + 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3080-0x308f +}; + +struct WisData { + int id, fd, count,len; + unsigned long tick; + unsigned char src[24], dst[24], msg[512]; +}; +static struct dbt * wis_db = NULL; +static int wis_dellist[WISDELLIST_MAX], wis_delnum; + +int inter_sql_test (void); + +//-------------------------------------------------------- +// Save registry to sql +int inter_accreg_tosql(int account_id, int char_id, struct accreg *reg, int type){ + + int j; + char temp_str[64]; //Needs be twice the source to ensure it fits [Skotlex] + char temp_str2[512]; + if (account_id<=0) return 0; + reg->account_id=account_id; + reg->char_id = char_id; + + switch (type) { + case 3: //Char Reg + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, char_id); + break; + case 2: //Account Reg + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `type`=2 AND `account_id`='%d'",reg_db, account_id); + break; + case 1: //Account2 Reg + ShowError("inter_accreg_tosql: Char server shouldn't handle type 1 registry values (##). That is the login server's work!\n"); + return 0; + default: + ShowError("inter_accreg_tosql: Invalid type %d\n", type); + return 0; + + } + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + if (reg->reg_num<=0) return 0; + + for(j=0;jreg_num;j++){ + if(reg->reg[j].str != NULL){ + sprintf(tmp_sql,"INSERT INTO `%s` (`type`, `account_id`, `char_id`, `str`, `value`) VALUES ('%d','%d','%d','%s','%s')", + reg_db, type, type!=3?reg->account_id:0, type==3?reg->char_id:0, + jstrescapecpy(temp_str,reg->reg[j].str), jstrescapecpy(temp_str2,reg->reg[j].value)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + return 1; +} + +// Load account_reg from sql (type=2) +int inter_accreg_fromsql(int account_id,int char_id, struct accreg *reg, int type) +{ + int j=0; + if (reg==NULL) return 0; + memset(reg, 0, sizeof(struct accreg)); + reg->account_id=account_id; + reg->char_id=char_id; + + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + switch (type) { + case 3: //char reg + sprintf (tmp_sql, "SELECT `str`, `value` FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, reg->char_id); + break; + case 2: //account reg + sprintf (tmp_sql, "SELECT `str`, `value` FROM `%s` WHERE `type`=2 AND `account_id`='%d'",reg_db, reg->account_id); + 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; + } + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + for(j=0;(sql_row = mysql_fetch_row(sql_res));j++){ + strcpy(reg->reg[j].str, sql_row[0]); + strcpy(reg->reg[j].value, sql_row[1]); + } + mysql_free_result(sql_res); + } + reg->reg_num=j; + return 1; +} + +// Initialize +int inter_accreg_sql_init() +{ + CREATE(accreg_pt, struct accreg, 1); + return 0; + +} + +/*========================================== + * read config file + *------------------------------------------ + */ +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; + } + + ShowInfo("reading file %s...\n",cfgName); + + while(fgets(line, 1020, fp)){ + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + if(strcmpi(w1,"char_server_ip")==0){ + strcpy(char_server_ip, w2); + ShowStatus ("set char_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"char_server_port")==0){ + char_server_port=atoi(w2); + ShowStatus ("set char_server_port : %s\n",w2); + } + else if(strcmpi(w1,"char_server_id")==0){ + strcpy(char_server_id, w2); + ShowStatus ("set char_server_id : %s\n",w2); + } + else if(strcmpi(w1,"char_server_pw")==0){ + strcpy(char_server_pw, w2); + ShowStatus ("set char_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"char_server_db")==0){ + strcpy(char_server_db, w2); + ShowStatus ("set char_server_db : %s\n",w2); + } + else if(strcmpi(w1,"default_codepage")==0){ + strcpy(default_codepage, w2); + ShowStatus ("set default_codepage : %s\n",w2); + } + //Logins information to be read from the inter_athena.conf + //for character deletion (checks email in the loginDB) + + else if(strcmpi(w1,"login_server_ip")==0){ + strcpy(login_server_ip, w2); + ShowStatus ("set login_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"login_server_port")==0){ + login_server_port=atoi(w2); + ShowStatus ("set login_server_port : %s\n",w2); + } + else if(strcmpi(w1,"login_server_id")==0){ + strcpy(login_server_id, w2); + ShowStatus ("set login_server_id : %s\n",w2); + } + else if(strcmpi(w1,"login_server_pw")==0){ + strcpy(login_server_pw, w2); + ShowStatus ("set login_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + ShowStatus ("set login_server_db : %s\n",w2); + } + else if(strcmpi(w1,"kick_on_disconnect")==0){ + kick_on_disconnect=atoi(w2); + } + else if(strcmpi(w1,"party_share_level")==0){ + party_share_level=atoi(w2); + if(party_share_level < 0) party_share_level = 0; + } + else if(strcmpi(w1,"log_inter")==0){ + log_inter = atoi(w2); + } + else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + ShowStatus ("set login_server_db : %s\n",w2); + } + else if(strcmpi(w1, "main_chat_nick")==0){ // Main chat nick [LuzZza] + strcpy(main_chat_nick, w2); // + } + else if(strcmpi(w1,"import")==0){ + inter_config_read(w2); + } + } + fclose(fp); + + ShowInfo ("done reading %s.\n", cfgName); + + return 0; +} + +// Save interlog into sql +int inter_log(char *fmt,...) +{ + char str[255]; + char temp_str[510]; //Needs be twice as long as str[] //Skotlex + va_list ap; + va_start(ap,fmt); + + vsprintf(str,fmt,ap); + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `log`) VALUES (NOW(), '%s')",interlog_db, jstrescapecpy(temp_str,str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + va_end(ap); + return 0; +} + + +// initialize +int inter_init(const char *file) +{ + //int i; + + ShowInfo ("interserver initialize...\n"); + inter_config_read(file); + + //DB connection initialized + mysql_init(&mysql_handle); + ShowInfo("Connect Character DB server.... (Character Server)\n"); + if(!mysql_real_connect(&mysql_handle, char_server_ip, char_server_id, char_server_pw, + char_server_db ,char_server_port, (char *)NULL, 0)) { + //pointer check + ShowFatalError("%s\n",mysql_error(&mysql_handle)); + exit(1); + } + else if (inter_sql_test()) { + ShowStatus("Connect Success! (Character Server)\n"); + } + + mysql_init(&lmysql_handle); + ShowInfo("Connect Character DB server.... (login server)\n"); + if(!mysql_real_connect(&lmysql_handle, login_server_ip, login_server_id, login_server_pw, + login_server_db ,login_server_port, (char *)NULL, 0)) { + //pointer check + ShowFatalError("%s\n",mysql_error(&lmysql_handle)); + exit(1); + }else { + ShowStatus ("Connect Success! (Login Server)\n"); + } + if(strlen(default_codepage) > 0 ) { + sprintf( tmp_sql, "SET NAMES %s", default_codepage ); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + if (mysql_query(&lmysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&lmysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + wis_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + inter_guild_sql_init(); + inter_storage_sql_init(); + inter_party_sql_init(); + + inter_pet_sql_init(); + inter_accreg_sql_init(); + + //printf ("interserver timer initializing : %d sec...\n",autosave_interval); + //i=add_timer_interval(gettick()+autosave_interval,inter_save_timer,0,0,autosave_interval); + + return 0; +} + +int inter_sql_test (void) +{ + const char fields[][24] = { + "father", // version 1363 + "fame", // version 1491 + }; + char buf[1024] = ""; + int i; + + sprintf(tmp_sql, "EXPLAIN `%s`",char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + // store DB fields + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { + strcat (buf, sql_row[0]); + strcat (buf, " "); + } + } + + // check DB strings + for (i = 0; i < (int)(sizeof(fields) / sizeof(fields[0])); i++) { + if(!strstr(buf, fields[i])) { + ShowSQL ("Field `%s` not be found in `%s`. Consider updating your database!\n", fields[i], char_db); + exit(1); + } + } + + mysql_free_result(sql_res); + + return 1; +} + +// finalize +void inter_final() { + wis_db->destroy(wis_db, NULL); + + inter_guild_sql_final(); + inter_storage_sql_final(); + inter_party_sql_final(); + inter_pet_sql_final(); + + if (accreg_pt) aFree(accreg_pt); + return; +} + +int inter_mapif_init(int fd) { + inter_guild_mapif_init(fd); + + return 0; +} + + +//-------------------------------------------------------- + +// GM message sending +int mapif_GMmessage(unsigned char *mes, int len, unsigned long color, int sfd) { + unsigned char buf[2048]; + + if (len > 2048) len = 2047; //Make it fit to avoid crashes. [Skotlex] + WBUFW(buf, 0) = 0x3800; + WBUFW(buf, 2) = len; + WBUFL(buf, 4) = color; + memcpy(WBUFP(buf, 8), mes, len-8); + mapif_sendallwos(sfd, buf, len); + 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; +} +// Wis sending result +int mapif_wis_end(struct WisData *wd,int flag) +{ + unsigned char buf[27]; + + WBUFW(buf, 0)=0x3802; + memcpy(WBUFP(buf, 2),wd->src,24); + WBUFB(buf,26)=flag; + mapif_send(wd->fd,buf,27); +// printf("inter server wis_end %d\n",flag); + return 0; +} + +int mapif_account_reg(int fd,unsigned char *src) +{ +// unsigned char buf[WBUFW(src,2)]; <- Hey, can this really be done? [Skotlex] + unsigned char *buf = aCalloc(1,WBUFW(src,2)); // [Lance] - Skot... Dynamic allocation is better :D + memcpy(WBUFP(buf,0),src,WBUFW(src,2)); + WBUFW(buf, 0)=0x3804; + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + aFree(buf); + return 0; +} + +// Send the requested account_reg +int mapif_account_reg_reply(int fd,int account_id,int char_id, int type) +{ + struct accreg *reg=accreg_pt; + inter_accreg_fromsql(account_id,char_id,reg,type); + + WFIFOW(fd,0)=0x3804; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=char_id; + WFIFOB(fd,12)=type; + if(reg->reg_num==0){ + WFIFOW(fd,2)=13; + }else{ + int i,p; + for (p=13,i = 0; i < reg->reg_num; i++) { + p+= sprintf(WFIFOP(fd,p), "%s", reg->reg[i].str)+1; //We add 1 to consider the '\0' in place. + p+= sprintf(WFIFOP(fd,p), "%s", reg->reg[i].value)+1; + } + WFIFOW(fd,2)=p; + } + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +int mapif_send_gmaccounts() +{ + int i, len = 4; + unsigned char buf[32000]; + + // forward the gm accounts to the map server + len = 4; + WBUFW(buf,0) = 0x2b15; + + for(i = 0; i < GM_num; i++) { + WBUFL(buf, len) = gm_account[i].account_id; + WBUFB(buf, len+4) = (unsigned char)gm_account[i].level; + len += 5; + } + WBUFW(buf, 2) = len; + mapif_sendall(buf, len); + + return 0; +} + +//Sends to map server the current max Account/Char id [Skotlex] +void mapif_send_maxid(int account_id, int char_id) +{ + unsigned char buf[12]; + + WBUFW(buf,0) = 0x2b07; + WBUFL(buf,2) = account_id; + WBUFL(buf,6) = char_id; + mapif_sendall(buf, 10); +} + +//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) + return -1; + + WFIFOW(fd,0) = 0x2b1f; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = reason; + WFIFOSET(fd,7); + return 0; +} + +//-------------------------------------------------------- + +// Existence check of WISP data +int check_ttl_wisdata_sub(DBKey key, void *data, va_list ap) { + unsigned long tick; + struct WisData *wd = (struct WisData *)data; + tick = va_arg(ap, unsigned long); + + if (DIFF_TICK(tick, wd->tick) > WISDATA_TTL && wis_delnum < WISDELLIST_MAX) + wis_dellist[wis_delnum++] = wd->id; + + return 0; +} + +int check_ttl_wisdata() { + unsigned long tick = gettick(); + int i; + + do { + wis_delnum = 0; + wis_db->foreach(wis_db, check_ttl_wisdata_sub, tick); + for(i = 0; i < wis_delnum; i++) { + struct WisData *wd = 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 wisper, 1: target character is not loged in?, 2: ignored by target + idb_remove(wis_db, wd->id); + } + } while(wis_delnum >= WISDELLIST_MAX); + + return 0; +} + +//-------------------------------------------------------- + +// GM message sending +int mapif_parse_GMmessage(int fd) +{ + mapif_GMmessage(RFIFOP(fd, 8), RFIFOW(fd, 2), RFIFOL(fd, 4), 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], t_name[NAME_LENGTH*2]; //Needs space to allocate names with escaped chars [Skotlex] + + 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) { // normaly, impossible, but who knows... + ShowError("inter: Wis message doesn't exist.\n"); + return 0; + } + memcpy(name, RFIFOP(fd,28), NAME_LENGTH); //Received name may be too large and not contain \0! [Skotlex] + name[NAME_LENGTH-1]= '\0'; + + sprintf (tmp_sql, "SELECT `name` FROM `%s` WHERE `name`='%s'", + char_db, jstrescapecpy(t_name, name)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + + // search if character exists before to ask all map-servers + if (!(sql_row = mysql_fetch_row(sql_res))) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), NAME_LENGTH); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + // Character exists. So, ask all map-servers + } else { + // to be sure of the correct name, rewrite it + memset(name, 0, NAME_LENGTH); + strncpy(name, sql_row[0], NAME_LENGTH); + // if source is destination, don't ask other servers. + if (strcmp((char*)RFIFOP(fd,4),name) == 0) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), NAME_LENGTH); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + } else { + + CREATE(wd, struct WisData, 1); + + // Whether the failure of previous wisp/page transmission (timeout) + 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 = gettick(); + idb_put(wis_db, wd->id, wd); + mapif_wis_message(wd); + } + } + + //Freeing ... O.o + if(sql_res){ + mysql_free_result(sql_res); + } + + return 0; +} + + +// Wisp/page transmission result +int mapif_parse_WisReply(int fd) { + int id = RFIFOL(fd,2), flag = RFIFOB(fd,6); + struct WisData *wd = 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 wisper, 1: target character is not loged 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 .w .24B .w .?B + + ShowDebug("Sent packet back!\n"); + 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 j,p,len, max; + struct accreg *reg=accreg_pt; + + memset(accreg_pt,0,sizeof(struct accreg)); + switch (RFIFOB(fd, 12)) { + case 3: //Character registry + max = GLOBAL_REG_NUM; + break; + case 2: //Account Registry + max = ACCOUNT_REG_NUM; + break; + case 1: //Account2 registry, must be sent over to login server. + return save_accreg2(RFIFOP(fd,4), RFIFOW(fd,2)-4); + default: + return 1; + } + for(j=0,p=13;jreg[j].str,&len); + reg->reg[j].str[len]='\0'; + p +=len+1; //+1 to skip the '\0' between strings. + sscanf(RFIFOP(fd,p), "%255c%n",reg->reg[j].value,&len); + reg->reg[j].value[len]='\0'; + p +=len+1; + } + reg->reg_num=j; + + inter_accreg_tosql(RFIFOL(fd,4),RFIFOL(fd,8),reg, RFIFOB(fd,12)); + mapif_account_reg(fd,RFIFOP(fd,0)); // Send updated accounts to other map servers. + return 0; +} + +// Request the value of all registries. +int mapif_parse_RegistryRequest(int fd) +{ + RFIFOHEAD(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)) + request_accreg2(RFIFOL(fd,2),RFIFOL(fd,6)-2); + return 1; +} + +//-------------------------------------------------------- +int inter_parse_frommap(int fd) +{ + int cmd=RFIFOW(fd,0); + int len=0; + + // inter鯖管轄かを調べる + if(cmd < 0x3000 || cmd >= 0x3000 + (sizeof(inter_recv_packet_length)/ + sizeof(inter_recv_packet_length[0]) ) ) + return 0; + + if (inter_recv_packet_length[cmd-0x3000] == 0) //This is necessary, because otherwise we return 2 and the char server will just hang waiting for packets! [Skotlex] + return 0; + + // パケット長を調べる + if((len = inter_check_length(fd, inter_recv_packet_length[cmd - 0x3000])) == 0) + return 2; + + switch(cmd){ + case 0x3000: mapif_parse_GMmessage(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; + default: + if(inter_party_parse_frommap(fd)) + break; + if(inter_guild_parse_frommap(fd)) + break; + if(inter_storage_parse_frommap(fd)) + break; + if(inter_pet_parse_frommap(fd)) + break; + return 0; + } + + RFIFOSKIP(fd, len); + return 1; +} + +// RFIFO check +int inter_check_length(int fd, int length) +{ + if(length==-1){ // v-len packet + if(RFIFOREST(fd)<4) // packet not yet + return 0; + length = RFIFOW(fd, 2); + } + + if((int)RFIFOREST(fd) < length) // packet not yet + return 0; + + return length; +} diff --git a/src/char_sql/inter.h b/src/char_sql/inter.h new file mode 100644 index 000000000..c1b40db01 --- /dev/null +++ b/src/char_sql/inter.h @@ -0,0 +1,58 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _INTER_H_ +#define _INTER_H_ + +int inter_init(const char *file); +void inter_final(); +int inter_parse_frommap(int fd); +int inter_mapif_init(int fd); +int mapif_send_gmaccounts(); +void mapif_send_maxid(int, int); +int mapif_disconnectplayer(int fd, int account_id, int char_id, int reason); + +int inter_check_length(int fd,int length); + +int inter_log(char *fmt,...); + +#define inter_cfgName "conf/inter_athena.conf" + +extern int party_share_level; +extern int kick_on_disconnect; //For deciding whether characters are kicked or not on reconnections. [Skotlex] +extern char inter_log_filename[1024]; + +#ifdef __WIN32 +//Windows.h need to be included before mysql.h +#include +#endif +//add include for DBMS(mysql) +#include + +extern MYSQL mysql_handle; +extern char tmp_sql[65535]; +extern MYSQL_RES* sql_res ; +extern MYSQL_ROW sql_row ; +extern int sql_cnt; + +extern MYSQL lmysql_handle; +extern MYSQL_RES* lsql_res ; +extern MYSQL_ROW lsql_row ; + +extern int char_server_port; +extern char char_server_ip[32]; +extern char char_server_id[32]; +extern char char_server_pw[32]; +extern char char_server_db[32]; + +extern int login_db_server_port; +extern char login_db_server_ip[32]; +extern char login_db_server_id[32]; +extern char login_db_server_pw[32]; +extern char login_db_server_db[32]; + +extern int log_inter; + +extern char main_chat_nick[16]; + +#endif diff --git a/src/char_sql/itemdb.c b/src/char_sql/itemdb.c new file mode 100644 index 000000000..25c55e73d --- /dev/null +++ b/src/char_sql/itemdb.c @@ -0,0 +1,229 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include + +#include "itemdb.h" +#include "db.h" +#include "inter.h" +#include "char.h" +#include "utils.h" +#include "../common/showmsg.h" + +#define MAX_RANDITEM 2000 + +// ** ITEMDB_OVERRIDE_NAME_VERBOSE ** +// 定義すると、itemdb.txtとgrfで名前が異なる場合、表示します. +//#define ITEMDB_OVERRIDE_NAME_VERBOSE 1 + +char item_db_db[256]="item_db"; // added to specify item_db sql table [Valaris] + +static struct dbt* item_db; + +static void* create_item(DBKey key, va_list args) { + struct item_data *id; + int nameid = key.i; + + CREATE(id, struct item_data, 1); + id->nameid = nameid; + if(nameid>500 && nameid<600) + id->type=0; //heal item + else if(nameid>600 && nameid<700) + id->type=2; //use item + else if((nameid>700 && nameid<1100) || + (nameid>7000 && nameid<8000)) + id->type=3; //correction + else if(nameid>=1750 && nameid<1771) + id->type=10; //arrow + else if(nameid>1100 && nameid<2000) + id->type=4; //weapon + else if((nameid>2100 && nameid<3000) || + (nameid>5000 && nameid<6000)) + id->type=5; //armor + else if(nameid>4000 && nameid<5000) + id->type=6; //card + else if(nameid>9000 && nameid<10000) + id->type=7; //egg + else if(nameid>10000) + id->type=8; //petequip + return id; +} +/*========================================== + * DBの検索 + *------------------------------------------ + */ +struct item_data* itemdb_search(int nameid) +{ + return idb_ensure(item_db,nameid,create_item); +} + +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip(int nameid) +{ + int type=itemdb_type(nameid); + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + return 1; +} +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip2(struct item_data *data) +{ + if(data) { + int type=data->type; + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + else + return 1; + } + return 0; +} + + + +/*========================================== + * アイテムデータベースの読み込み + *------------------------------------------ + */ +static int itemdb_readdb(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int nameid,j; + char *str[128],*p,*np; + struct item_data *id; + + sprintf(line, "%s/item_db.txt", db_path); + fp=fopen(line,"r"); + if(fp==NULL){ + ShowError("can't read %s\n", str); + exit(1); + } + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,np=p=line;j<17 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p){ *p++=0; np=p; } + } + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<=0 || nameid>=20000) + continue; + ln++; + + //ID,Name,Jname,Type,Price,Sell,Weight,ATK,DEF,Range,Slot,Job,Gender,Loc,wLV,eLV,View + id=itemdb_search(nameid); + memcpy(id->name,str[1],ITEM_NAME_LENGTH-1); + memcpy(id->jname,str[2],ITEM_NAME_LENGTH-1); + id->type=atoi(str[3]); + + } + fclose(fp); + ShowStatus("done reading item_db.txt (count=%d)\n",ln); + return 0; +} + +static int itemdb_read_sqldb(void) // sql item_db read, shortened version of map-server item_db read [Valaris] +{ + unsigned int nameid; // Type should be "unsigned short int", but currently isn't for compatibility with numdb_insert() + struct item_data *id; + + // ---------- + + // Output query to retrieve all rows from the item database table + sprintf(tmp_sql, "SELECT * FROM `%s`", item_db_db); + + // Execute the query; if the query execution fails, output an error + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + // Store the query result + sql_res = mysql_store_result(&mysql_handle); + + // If the storage of the query result succeeded + if (sql_res) { + // Parse each row in the query result into sql_row + while ((sql_row = mysql_fetch_row(sql_res))) { + nameid = atoi(sql_row[0]); + + // If the identifier is not within the valid range, process the next row + if (nameid == 0 || nameid >= 20000) { // Should ">= 20000" be "> 20000"? + continue; + } + + // ---------- + + // Update/Insert row into the item database + id=itemdb_search(nameid); + + memcpy(id->name, sql_row[1], ITEM_NAME_LENGTH-1); + memcpy(id->jname, sql_row[2], ITEM_NAME_LENGTH-1); + + id->type = atoi(sql_row[3]); + } + + // If the retrieval failed, output an error + if (mysql_errno(&mysql_handle)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + ShowInfo("read %s done (count = %lu)\n", item_db_db, (unsigned long) mysql_num_rows(sql_res)); + + // Free the query result + mysql_free_result(sql_res); + } else { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + return 0; +} + +static int itemdb_final(DBKey key,void *data,va_list ap) +{ + struct item_data *id = (struct item_data*)data; + if(id->use_script) + aFree(id->use_script); + if(id->equip_script) + aFree(id->equip_script); + return 0; +} + + +/*========================================== + * + *------------------------------------------ + */ +void do_final_itemdb(void) +{ + if(item_db){ + item_db->destroy(item_db,itemdb_final); + item_db=NULL; + } +} +int do_init_itemdb(void) +{ + item_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); + + if (db_use_sqldbs) // it db_use_sqldbs in inter config are yes, will read from item_db for char server display [Valaris] + itemdb_read_sqldb(); + else + itemdb_readdb(); + return 0; +} diff --git a/src/char_sql/itemdb.h b/src/char_sql/itemdb.h new file mode 100644 index 000000000..350e42b2c --- /dev/null +++ b/src/char_sql/itemdb.h @@ -0,0 +1,38 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _ITEMDB_H_ +#define _ITEMDB_H_ +#include "mmo.h" + +struct item_data { + int nameid; + char name[ITEM_NAME_LENGTH],jname[ITEM_NAME_LENGTH]; + int value_buy,value_sell,value_notdc,value_notoc; + int type; + int class_; + int sex; + int equip; + int weight; + int atk; + int def; + int range; + int slot; + int look; + int elv; + int wlv; + char *use_script; // 回復とかも全部この中でやろうかなと + char *equip_script; // 攻撃,防御の属性設定もこの中で可能かな? + char available; +}; + +struct item_data* itemdb_search(int nameid); +#define itemdb_type(n) itemdb_search(n)->type + +int itemdb_isequip(int); +int itemdb_isequip2(struct item_data *); + +void do_final_itemdb(void); +int do_init_itemdb(void); + +#endif diff --git a/src/char_sql/make.sh b/src/char_sql/make.sh new file mode 100644 index 000000000..d5f109f3e --- /dev/null +++ b/src/char_sql/make.sh @@ -0,0 +1,10 @@ +#!/bin/sh + rsqlt=`rm -rf *.o` + gcc -c char.c -I/usr/local/include/mysql/ + gcc -c int_guild.c -I/usr/local/include/mysql/ + gcc -c int_party.c -I/usr/local/include/mysql/ + gcc -c int_pet.c -I/usr/local/include/mysql/ + gcc -c int_storage.c -I/usr/local/include/mysql/ + gcc -c inter.c -I/usr/local/include/mysql/ + gcc -c itemdb.c -I../common/ + gcc -o ../char-server inter.o char.o int_pet.o int_storage.o int_guild.o int_party.o ../common/strlib.o itemdb.o ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o -L/usr/local/lib/mysql -lmysqlclient -lz diff --git a/src/common/Makefile b/src/common/Makefile new file mode 100644 index 000000000..60b588b1e --- /dev/null +++ b/src/common/Makefile @@ -0,0 +1,56 @@ +txt sql all: obj common + +obj: + mkdir obj + +common: obj/core.o obj/socket.o obj/timer.o obj/db.o obj/plugins.o obj/lock.o \ + obj/nullpo.o obj/malloc.o obj/showmsg.o obj/strlib.o obj/utils.o \ + obj/graph.o obj/grfio.o obj/minicore.o obj/minisocket.o obj/minimalloc.o \ + obj/mapindex.o obj/unz.o obj/ers.o + + +obj/%.o: %.c + $(COMPILE.c) $(OUTPUT_OPTION) $< + +obj/mini%.o: %.c + $(COMPILE.c) -DMINICORE $(OUTPUT_OPTION) $< + +obj/unz.o: + $(MAKE) -C ../zlib + @touch $@ + + +clean: + rm -rf *.o obj + +HAVESVN = $(shell which svnversion) + +ifeq ($(findstring /,$(HAVESVN)), /) +svnversion.h: ../../Changelog-SVN.txt + @printf "#define SVNVERSION " > svnversion.h + @svnversion . >> svnversion.h +else +svnversion.h: + @printf "" > svnversion.h +endif + +obj/minicore.o: core.c core.h +obj/minisocket.o: socket.c socket.h +obj/minimalloc.o: malloc.c malloc.h + +# DO NOT DELETE + +obj/core.o: core.c core.h showmsg.h svnversion.h +obj/socket.o: socket.c socket.h mmo.h showmsg.h plugins.h +obj/timer.o: timer.c timer.h showmsg.h +obj/ers.o: ers.c ers.h cbasetypes.h +obj/db.o: db.c db.h showmsg.h ers.h +obj/lock.o: lock.c lock.h showmsg.h +obj/grfio.o: grfio.c grfio.h +obj/graph.o: graph.c graph.h +obj/nullpo.o: nullpo.c nullpo.h showmsg.h +obj/malloc.o: malloc.c malloc.h showmsg.h +obj/plugins.o: plugins.c plugins.h plugin.h +obj/showmsg.o: showmsg.c showmsg.h +obj/strlib.o: strlib.c strlib.h utils.h +obj/mapindex.o: mapindex.c mapindex.h diff --git a/src/common/cbasetypes.h b/src/common/cbasetypes.h new file mode 100644 index 000000000..ec539a3db --- /dev/null +++ b/src/common/cbasetypes.h @@ -0,0 +1,253 @@ +#ifndef _CBASETYPES_H_ +#define _CBASETYPES_H_ +/* +--------+-----------+--------+---------+ + * | ILP32 | LP64 | ILP64 | (LL)P64 | + * +------------+--------+-----------+--------+---------+ + * | ints | 32-bit | 32-bit | 64-bit | 32-bit | + * | longs | 32-bit | 64-bit | 64-bit | 32-bit | + * | long-longs | 64-bit | 64-bit | 64-bit | 64-bit | + * | pointers | 32-bit | 64-bit | 64-bit | 64-bit | + * +------------+--------+-----------+--------+---------+ + * | where | -- | Tiger | Alpha | Windows | + * | used | | Unix | Cray | | + * | | | Sun & SGI | | | + * +------------+--------+-----------+--------+---------+ + * Taken from http://developer.apple.com/macosx/64bit.html + */ + +////////////////////////////////////////////////////////////////////////// +// basic include for all basics +// introduces types and global functions +////////////////////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////////////////////// +// setting some defines on platforms +////////////////////////////////////////////////////////////////////////// +#if (defined(__WIN32__) || defined(__WIN32) || defined(_WIN32) || defined(_WIN64) || defined(_MSC_VER) || defined(__BORLANDC__)) && !defined(WIN32) +#define WIN32 +#endif + +// __APPLE__ is the only predefined macro on MacOS X +#if defined(__APPLE__) +#define __DARWIN__ +#endif + +// 64bit OS +#if defined(_M_IA64) || defined(_M_X64) || defined(_WIN64) || defined(_LP64) || defined(_ILP64) || defined(__LP64__) || defined(__ppc64__) +#define __64BIT__ +#endif + +#if defined(_ILP64) +#error "this specific 64bit architecture is not supported" +#endif + +// debug mode +#if defined(_DEBUG) && !defined(DEBUG) +#define DEBUG +#endif + +// disable attributed stuff on non-GNU +#ifndef __GNUC__ +# define __attribute__(x) +#endif + + +////////////////////////////////////////////////////////////////////////// +// useful typedefs +////////////////////////////////////////////////////////////////////////// +typedef unsigned char uchar; +typedef signed char schar; +typedef signed short sshort; +typedef unsigned short ushort; +typedef signed int sint; // don't use (only for ie. scanf) +typedef unsigned int uint; // don't use +typedef signed long slong; // don't use (only for ie. file-io) +typedef unsigned long ulong; // don't use + +typedef char* pchar; +typedef const char* cchar; +typedef unsigned char* puchar; +typedef void* ptr; +typedef int* pint; + + +////////////////////////////////////////////////////////////////////////// +// typedefs to compensate type size change from 32bit to 64bit +// MS implements LLP64 model, normal unix does LP64, +// only Silicon Graphics/Cray goes ILP64 so don't care (and don't support) +////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +// Integers with guaranteed _exact_ size. +////////////////////////////////////////////////////////////////////////// + +////////////////////////////// +#ifdef WIN32 +////////////////////////////// +typedef __int8 int8; +typedef __int16 int16; +typedef __int32 int32; + +typedef signed __int8 sint8; +typedef signed __int16 sint16; +typedef signed __int32 sint32; + +typedef unsigned __int8 uint8; +typedef unsigned __int16 uint16; +typedef unsigned __int32 uint32; +////////////////////////////// +#else // GNU +////////////////////////////// +typedef char int8; +typedef short int16; +typedef int int32; + +typedef signed char sint8; +typedef signed short sint16; +typedef signed int sint32; + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +////////////////////////////// +#endif +////////////////////////////// + +#undef UINT8_MIN +#undef UINT16_MIN +#undef UINT32_MIN +#define UINT8_MIN (uint8) 0 +#define UINT16_MIN (uint16)0 +#define UINT32_MIN (uint32)0 + +#undef UINT8_MAX +#undef UINT16_MAX +#undef UINT32_MAX +#define UINT8_MAX (uint8) 0xFF +#define UINT16_MAX (uint16)0xFFFF +#define UINT32_MAX (uint32)0xFFFFFFFF + +#undef SINT8_MIN +#undef SINT16_MIN +#undef SINT32_MIN +#define SINT8_MIN (sint8) 0x80 +#define SINT16_MIN (sint16)0x8000 +#define SINT32_MIN (sint32)0x80000000 + +#undef SINT8_MAX +#undef SINT16_MAX +#undef SINT32_MAX +#define SINT8_MAX (sint8) 0x7F +#define SINT16_MAX (sint16)0x7FFF +#define SINT32_MAX (sint32)0x7FFFFFFF + + +////////////////////////////////////////////////////////////////////////// +// Integers with guaranteed _minimum_ size. +// These could be larger than you expect, +// they are designed for speed. +////////////////////////////////////////////////////////////////////////// +typedef long int ppint; +typedef long int ppint8; +typedef long int ppint16; +typedef long int ppint32; + +typedef unsigned long int ppuint; +typedef unsigned long int ppuint8; +typedef unsigned long int ppuint16; +typedef unsigned long int ppuint32; + + +////////////////////////////////////////////////////////////////////////// +// integer with exact processor width (and best speed) +// size_t already defined in stdio.h +////////////////////////////// +#ifdef WIN32 // does not have a signed size_t +////////////////////////////// +#if defined(_WIN64) // naive 64bit windows platform +typedef __int64 ssize_t; +#else +typedef int ssize_t; +#endif +////////////////////////////// +#endif +////////////////////////////// + + +////////////////////////////////////////////////////////////////////////// +// portable 64-bit integers +////////////////////////////////////////////////////////////////////////// +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 int64; +typedef signed __int64 sint64; +typedef unsigned __int64 uint64; +#define LLCONST(a) (a##i64) +#else +typedef long long int64; +typedef signed long long sint64; +typedef unsigned long long uint64; +#define LLCONST(a) (a##ll) +#endif + +#ifndef INT64_MIN +#define INT64_MIN (LLCONST(-9223372036854775807)-1) +#endif +#ifndef INT64_MAX +#define INT64_MAX (LLCONST(9223372036854775807)) +#endif +#ifndef UINT64_MAX +#define UINT64_MAX (LLCONST(18446744073709551615u)) +#endif + + +////////////////////////////////////////////////////////////////////////// +// some redefine of function redefines for some Compilers +////////////////////////////////////////////////////////////////////////// +#if defined(_MSC_VER) || defined(__BORLANDC__) +#define strcasecmp stricmp +#define strncasecmp strnicmp +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#endif + +// keyword replacement in windows +#ifdef _WIN32 +#define inline __inline +#endif + +///////////////////////////// +// for those still not building c++ +#ifndef __cplusplus +////////////////////////////// + +// boolean types for C +typedef int bool; +#define false (1==0) +#define true (1==1) + +////////////////////////////// +#endif // not cplusplus +////////////////////////////// + +#ifdef swap // just to be sure +#undef swap +#endif +// hmm only ints? +//#define swap(a,b) { int temp=a; a=b; b=temp;} +// if using macros then something that is type independent +#define swap(a,b) ((a == b) || ((a ^= b), (b ^= a), (a ^= b))) + +////////////////////////////////////////////////////////////////////////// +// should not happen +#ifndef NULL +#define NULL (void *)0 +#endif + +////////////////////////////////////////////////////////////////////////// +// number of bits in a byte +#ifndef NBBY +#define NBBY 8 +#endif + +#endif /* _CBASETYPES_H_ */ diff --git a/src/common/core.c b/src/common/core.c new file mode 100644 index 000000000..b0b847ca2 --- /dev/null +++ b/src/common/core.c @@ -0,0 +1,269 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#ifndef _WIN32 +#include +#endif +#include +#include + +#include "core.h" +#include "../common/db.h" +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/graph.h" +#include "../common/grfio.h" +#include "../common/plugins.h" +#include "../common/version.h" +#include "../common/showmsg.h" + +#ifndef _WIN32 + #include "svnversion.h" +#endif + +int runflag = 1; +int arg_c = 0; +char **arg_v = NULL; + +char *SERVER_NAME = NULL; +char SERVER_TYPE = ATHENA_SERVER_NONE; +static void (*term_func)(void) = NULL; + +/*====================================== + * CORE : Set function + *-------------------------------------- + */ +void set_termfunc(void (*termfunc)(void)) +{ + term_func = termfunc; +} + +#ifndef MINICORE // minimalist Core +// Added by Gabuzomeu +// +// This is an implementation of signal() using sigaction() for portability. +// (sigaction() is POSIX; signal() is not.) Taken from Stevens' _Advanced +// Programming in the UNIX Environment_. +// +#ifdef WIN32 // windows don't have SIGPIPE +#define SIGPIPE SIGINT +#endif + +#ifndef POSIX +#define compat_signal(signo, func) signal(signo, func) +#else +sigfunc *compat_signal(int signo, sigfunc *func) +{ + struct sigaction sact, oact; + + sact.sa_handler = func; + sigemptyset(&sact.sa_mask); + sact.sa_flags = 0; +#ifdef SA_INTERRUPT + sact.sa_flags |= SA_INTERRUPT; /* SunOS */ +#endif + + if (sigaction(signo, &sact, &oact) < 0) + return (SIG_ERR); + + return (oact.sa_handler); +} +#endif + +/*====================================== + * CORE : Signal Sub Function + *-------------------------------------- + */ +static void sig_proc(int sn) +{ + static int is_called = 0; + + switch (sn) { + case SIGINT: + case SIGTERM: + if (++is_called > 3) + exit(0); + runflag = 0; + break; +#ifndef _WIN32 + case SIGXFSZ: + // ignore and allow it to set errno to EFBIG + ShowWarning ("Max file size reached!\n"); + //run_flag = 0; // should we quit? + break; + case SIGPIPE: + ShowMessage ("Broken pipe found... closing socket\n"); // set to eof in socket.c + break; // does nothing here +#endif + } +} + +void signals_init (void) +{ + compat_signal(SIGTERM, sig_proc); + compat_signal(SIGINT, sig_proc); + + // Signal to create coredumps by system when necessary (crash) + compat_signal(SIGSEGV, SIG_DFL); + compat_signal(SIGFPE, SIG_DFL); + compat_signal(SIGILL, SIG_DFL); + #ifndef _WIN32 + compat_signal(SIGXFSZ, sig_proc); + compat_signal(SIGPIPE, sig_proc); + compat_signal(SIGBUS, SIG_DFL); + compat_signal(SIGTRAP, SIG_DFL); + #endif +} +#endif + +#ifdef SVNVERSION + #define xstringify(x) stringify(x) + #define stringify(x) #x + const char *get_svn_revision(void) + { + return xstringify(SVNVERSION); + } +#else +const char* get_svn_revision(void) +{ + static char version[10]; + FILE *fp; + + if ((fp = fopen(".svn/entries", "r")) != NULL) { + char line[1024]; + int rev; + while (fgets(line,1023,fp)) + if (strstr(line,"revision=")) break; + fclose(fp); + if (sscanf(line," %*[^\"]\"%d%*[^\n]", &rev) == 1) { + sprintf(version, "%d", rev); + return version; + } + } + + // if getting revision has failed + return "Unknown"; +} +#endif + +/*====================================== + * CORE : Display title + *-------------------------------------- + */ + +static void display_title(void) +{ + ClearScreen(); // clear screen and go up/left (0, 0 position in text) + ShowMessage(""CL_WTBL" (=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=)"CL_CLL""CL_NORMAL"\n"); // white writing (37) on blue background (44), \033[K clean until end of file + ShowMessage(""CL_XXBL" ("CL_BT_YELLOW" (c)2005 eAthena Development Team presents "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // yellow writing (33) + ShowMessage(""CL_XXBL" ("CL_BOLD" ______ __ __ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" /\\ _ \\/\\ \\__/\\ \\ v%2d.%02d.%02d "CL_XXBL")"CL_CLL""CL_NORMAL"\n", ATHENA_MAJOR_VERSION, ATHENA_MINOR_VERSION, ATHENA_REVISION); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" __\\ \\ \\_\\ \\ \\ ,_\\ \\ \\___ __ ___ __ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" /'__`\\ \\ __ \\ \\ \\/\\ \\ _ `\\ /'__`\\/' _ `\\ /'__`\\ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" /\\ __/\\ \\ \\/\\ \\ \\ \\_\\ \\ \\ \\ \\/\\ __//\\ \\/\\ \\/\\ \\_\\.\\_ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" \\ \\____\\\\ \\_\\ \\_\\ \\__\\\\ \\_\\ \\_\\ \\____\\ \\_\\ \\_\\ \\__/.\\_\\ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" \\/____/ \\/_/\\/_/\\/__/ \\/_/\\/_/\\/____/\\/_/\\/_/\\/__/\\/_/ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" _ _ _ _ _ _ _ _ _ _ _ _ _ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" ( e | n | g | l | i | s | h ) ( A | t | h | e | n | a ) "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // 1: bold char, 0: normal char + ShowMessage(""CL_XXBL" ("CL_BOLD" "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // yellow writing (33) + ShowMessage(""CL_XXBL" ("CL_BT_YELLOW" Advanced Fusion Maps (c) 2003-2005 The Fusion Project "CL_XXBL")"CL_CLL""CL_NORMAL"\n"); // yellow writing (33) + ShowMessage(""CL_WTBL" (=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=)"CL_CLL""CL_NORMAL"\n\n"); // reset color + + ShowInfo("SVN Revision: '"CL_WHITE"%s"CL_RESET"'.\n", get_svn_revision()); +} + +// Warning if logged in as superuser (root) +void usercheck(void){ +#ifndef _WIN32 + if ((getuid() == 0) && (getgid() == 0)) { + ShowWarning ("You are running eAthena as the root superuser.\n"); + ShowWarning ("It is unnecessary and unsafe to run eAthena with root privileges.\n"); + sleep(3); + } +#endif +} + +/*====================================== + * CORE : MAINROUTINE + *-------------------------------------- + */ +#ifndef MINICORE // minimalist Core +int main (int argc, char **argv) +{ + int next; + + // initialise program arguments + { + char *p = SERVER_NAME = argv[0]; + while ((p = strchr(p, '/')) != NULL) + SERVER_NAME = ++p; + arg_c = argc; + arg_v = argv; + } + + set_server_type(); + display_title(); + usercheck(); + + malloc_init(); /* 一番最初に実行する必要がある */ + db_init(); + signals_init(); + + timer_init(); + socket_init(); + plugins_init(); + + do_init(argc,argv); + graph_init(); + plugin_event_trigger("Athena_Init"); + + while (runflag) { + next = do_timer(gettick_nocache()); + do_sendrecv(next); +#ifndef TURBO + do_parsepacket(); +#endif + } + + plugin_event_trigger("Athena_Final"); + graph_final(); + do_final(); + + timer_final(); + plugins_final(); + socket_final(); + db_final(); + malloc_final(); + + return 0; +} +#else +int main (int argc, char **argv) +{ + // initialise program arguments + { + char *p = SERVER_NAME = argv[0]; + while ((p = strchr(p, '/')) != NULL) + SERVER_NAME = ++p; + arg_c = argc; + arg_v = argv; + } + + display_title(); + usercheck(); + do_init(argc,argv); + do_final(); + + return 0; +} +#endif + +#ifdef BCHECK +unsigned int __invalid_size_argument_for_IOC; +#endif diff --git a/src/common/core.h b/src/common/core.h new file mode 100644 index 000000000..22f625a5d --- /dev/null +++ b/src/common/core.h @@ -0,0 +1,30 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _CORE_H_ +#define _CORE_H_ + +//#define SQL_DEBUG //uncomment for debug_mysql_query instead of mysql_real_query + +/* REMOVED because these type of function defines with va_args are a GCC feature and won't compile under Windows [Skotlex] +//Added here, so its avail in 'all' files .. +#define eprintf(mes, args...) \ + fprintf(stderr, "%s:%d: "mes"", __FILE__, __LINE__, args); +#define eprint(mes) \ + fprintf(stderr, "%s:%d: "mes"", __FILE__, __LINE__); +*/ + +extern int arg_c; +extern char **arg_v; + +extern int runflag; +extern char *SERVER_NAME; +extern char SERVER_TYPE; + +extern const char *get_svn_revision(void); +extern int do_init(int,char**); +extern void set_server_type(void); +extern void set_termfunc(void (*termfunc)(void)); +extern void do_final(void); + +#endif // _CORE_H_ diff --git a/src/common/db.c b/src/common/db.c new file mode 100644 index 000000000..65abecea7 --- /dev/null +++ b/src/common/db.c @@ -0,0 +1,2344 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + * This file is separated in five sections: * + * (1) Private typedefs, enums, structures, defines and gblobal variables * + * (2) Private functions * + * (3) Protected functions used internally * + * (4) Protected functions used in the interface of the database * + * (5) Public functions * + * * + * The databases are structured as a hashtable of RED-BLACK trees. * + * * + * Properties of the RED-BLACK trees being used: * + * 1. The value of any node is greater than the value of its left child and * + * less than the value of its right child. * + * 2. Every node is colored either RED or BLACK. * + * 3. Every red node that is not a leaf has only black children. * + * 4. Every path from the root to a leaf contains the same number of black * + * nodes. * + * 5. The root node is black. * + * An n node in a RED-BLACK tree has the property that its * + * height is O(lg(n)). * + * Another important property is that after adding a node to a RED-BLACK * + * tree, the tree can be readjusted in O(lg(n)) time. * + * Similarly, after deleting a node from a RED-BLACK tree, the tree can be * + * readjusted in O(lg(n)) time. * + * {@link http://www.cs.mcgill.ca/~cs251/OldCourses/1997/topic18/} * + * * + * How to add new database types: * + * 1. Add the identifier of the new database type to the enum DBType * + * 2. If not already there, add the data type of the key to the union DBKey * + * 3. If the key can be considered NULL, update the function db_is_key_null * + * 4. If the key can be duplicated, update the functions db_dup_key and * + * db_dup_key_free * + * 5. Create a comparator and update the function db_default_cmp * + * 6. Create a hasher and update the function db_default_hash * + * 7. If the new database type requires or does not support some options, * + * update the function db_fix_options * + * * + * TODO: * + * - create test cases to test the database system thoroughly * + * - make data an enumeration * + * - finish this header describing the database system * + * - create custom database allocator * + * - make the system thread friendly * + * - change the structure of the database to T-Trees * + * - create a db that organizes itself by splaying * + * * + * HISTORY: * + * 2.1 (Athena build #???#) - Portability fix * + * - Fixed the portability of casting to union and added the functions * + * {@link DBInterface#ensure(DBInterface,DBKey,DBCreateData,...)} and * + * {@link DBInterface#clear(DBInterface,DBApply,...)}. * + * 2.0 (Athena build 4859) - Transition version * + * - Almost everything recoded with a strategy similar to objects, * + * database structure is maintained. * + * 1.0 (up to Athena build 4706) * + * - Previous database system. * + * * + * @version 2.1 (Athena build #???#) - Portability fix * + * @author (Athena build 4859) Flavio @ Amazon Project * + * @author (up to Athena build 4706) Athena Dev Teams * + * @encoding US-ASCII * + * @see common#db.h * +\*****************************************************************************/ +#include +#include +#include + +#include "db.h" +#include "../common/mmo.h" +#include "../common/utils.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" +#include "../common/ers.h" + +/*****************************************************************************\ + * (1) Private typedefs, enums, structures, defines and global variables of * + * the database system. * + * DB_ENABLE_STATS - Define to enable database statistics. * + * HASH_SIZE - Define with the size of the hashtable. * + * DBNColor - Enumeration of colors of the nodes. * + * DBNode - Structure of a node in RED-BLACK trees. * + * struct db_free - Structure that holds a deleted node to be freed. * + * Database - Struture of the database. * + * stats - Statistics about the database system. * +\*****************************************************************************/ + +/** + * If defined statistics about database nodes, database creating/destruction + * and function usage are keept and displayed when finalizing the database + * system. + * WARNING: This adds overhead to every database operation (not shure how much). + * @private + * @see #DBStats + * @see #stats + * @see #db_final(void) + */ +//#define DB_ENABLE_STATS + +/** + * Size of the hashtable in the database. + * @private + * @see Database#ht + */ +#define HASH_SIZE (256+27) + +/** + * A node in a RED-BLACK tree of the database. + * @param parent Parent node + * @param left Left child node + * @param right Right child node + * @param key Key of this database entry + * @param data Data of this database entry + * @param deleted If the node is deleted + * @param color Color of the node + * @private + * @see Database#ht + */ +typedef struct dbn { + // Tree structure + struct dbn *parent; + struct dbn *left; + struct dbn *right; + // Node data + DBKey key; + void *data; + // Other + enum {RED, BLACK} color; + unsigned deleted : 1; +} *DBNode; + +/** + * Structure that holds a deleted node. + * @param node Deleted node + * @param root Address to the root of the tree + * @private + * @see Database#free_list + */ +struct db_free { + DBNode node; + DBNode *root; +}; + +/** + * Complete database structure. + * @param dbi Interface of the database + * @param alloc_file File where the database was allocated + * @param alloc_line Line in the file where the database was allocated + * @param free_list Array of deleted nodes to be freed + * @param free_count Number of deleted nodes in free_list + * @param free_max Current maximum capacity of free_list + * @param free_lock Lock for freeing the nodes + * @param nodes Manager of reusable tree nodes + * @param cmp Comparator of the database + * @param hash Hasher of the database + * @param release Releaser of the database + * @param ht Hashtable of RED-BLACK trees + * @param type Type of the database + * @param options Options of the database + * @param item_count Number of items in the database + * @param maxlen Maximum length of strings in DB_STRING and DB_ISTRING databases + * @param global_lock Global lock of the database + * @private + * @see common\db.h#DBInterface + * @see #HASH_SIZE + * @see #DBNode + * @see #struct db_free + * @see common\db.h#DBComparator(void *,void *) + * @see common\db.h#DBHasher(void *) + * @see common\db.h#DBReleaser(void *,void *,DBRelease) + * @see common\db.h#DBOptions + * @see common\db.h#DBType + * @see #db_alloc(const char *,int,DBOptions,DBType,...) + */ +typedef struct db { + // Database interface + struct dbt dbi; + // File and line of allocation + const char *alloc_file; + int alloc_line; + // Lock system + struct db_free *free_list; + unsigned int free_count; + unsigned int free_max; + unsigned int free_lock; + // Other + ERInterface nodes; + DBComparator cmp; + DBHasher hash; + DBReleaser release; + DBNode ht[HASH_SIZE]; + DBType type; + DBOptions options; + unsigned int item_count; + unsigned short maxlen; + unsigned global_lock : 1; +} *Database; + +#ifdef DB_ENABLE_STATS +/** + * Structure with what is counted when the database estatistics are enabled. + * @private + * @see #DB_ENABLE_STATS + * @see #stats + */ +static struct { + // Node alloc/free + unsigned int db_node_alloc; + unsigned int db_node_free; + // Database creating/destruction counters + unsigned int db_int_alloc; + unsigned int db_uint_alloc; + unsigned int db_string_alloc; + unsigned int db_istring_alloc; + unsigned int db_int_destroy; + unsigned int db_uint_destroy; + unsigned int db_string_destroy; + unsigned int db_istring_destroy; + // Function usage counters + unsigned int db_rotate_left; + unsigned int db_rotate_right; + unsigned int db_rebalance; + unsigned int db_rebalance_erase; + unsigned int db_is_key_null; + unsigned int db_dup_key; + unsigned int db_dup_key_free; + unsigned int db_free_add; + unsigned int db_free_remove; + unsigned int db_free_lock; + unsigned int db_free_unlock; + unsigned int db_int_cmp; + unsigned int db_uint_cmp; + unsigned int db_string_cmp; + unsigned int db_istring_cmp; + unsigned int db_int_hash; + unsigned int db_uint_hash; + unsigned int db_string_hash; + unsigned int db_istring_hash; + unsigned int db_release_nothing; + unsigned int db_release_key; + unsigned int db_release_data; + unsigned int db_release_both; + unsigned int db_get; + unsigned int db_getall; + unsigned int db_vgetall; + unsigned int db_ensure; + unsigned int db_vensure; + unsigned int db_put; + unsigned int db_remove; + unsigned int db_foreach; + unsigned int db_vforeach; + unsigned int db_clear; + unsigned int db_vclear; + unsigned int db_destroy; + unsigned int db_vdestroy; + unsigned int db_size; + unsigned int db_type; + unsigned int db_options; + unsigned int db_fix_options; + unsigned int db_default_cmp; + unsigned int db_default_hash; + unsigned int db_default_release; + unsigned int db_custom_release; + unsigned int db_alloc; + unsigned int db_i2key; + unsigned int db_ui2key; + unsigned int db_str2key; + unsigned int db_init; + unsigned int db_final; +} stats = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; +#endif /* DB_ENABLE_STATS */ + +/*****************************************************************************\ + * (2) Section of private functions used by the database system. * + * db_rotate_left - Rotate a tree node to the left. * + * db_rotate_right - Rotate a tree node to the right. * + * db_rebalance - Rebalance the tree. * + * db_rebalance_erase - Rebalance the tree after a BLACK node was erased. * + * db_is_key_null - Returns not 0 if the key is considered NULL. * + * db_dup_key - Duplicate a key for internal use. * + * db_dup_key_free - Free the duplicated key. * + * db_free_add - Add a node to the free_list of a database. * + * db_free_remove - Remove a node from the free_list of a database. * + * db_free_lock - Increment the free_lock of a database. * + * db_free_unlock - Decrement the free_lock of a database. * + * If it was the last lock, frees the nodes in free_list. * + * NOTE: Keeps the database trees balanced. * +\*****************************************************************************/ + +/** + * Rotate a node to the left. + * @param node Node to be rotated + * @param root Pointer to the root of the tree + * @private + * @see #db_rebalance(DBNode,DBNode *) + * @see #db_rebalance_erase(DBNode,DBNode *) + */ +static void db_rotate_left(DBNode node, DBNode *root) +{ + DBNode y = node->right; + +#ifdef DB_ENABLE_STATS + if (stats.db_rotate_left != (unsigned int)~0) stats.db_rotate_left++; +#endif /* DB_ENABLE_STATS */ + // put the left of y at the right of node + node->right = y->left; + if (y->left) + y->left->parent = node; + y->parent = node->parent; + // link y and node's parent + if (node == *root) { + *root = y; // node was root + } else if (node == node->parent->left) { + node->parent->left = y; // node was at the left + } else { + node->parent->right = y; // node was at the right + } + // put node at the left of y + y->left = node; + node->parent = y; +} + +/** + * Rotate a node to the right + * @param node Node to be rotated + * @param root Pointer to the root of the tree + * @private + * @see #db_rebalance(DBNode,DBNode *) + * @see #db_rebalance_erase(DBNode,DBNode *) + */ +static void db_rotate_right(DBNode node, DBNode *root) +{ + DBNode y = node->left; + +#ifdef DB_ENABLE_STATS + if (stats.db_rotate_right != (unsigned int)~0) stats.db_rotate_right++; +#endif /* DB_ENABLE_STATS */ + // put the right of y at the left of node + node->left = y->right; + if (y->right != 0) + y->right->parent = node; + y->parent = node->parent; + // link y and node's parent + if (node == *root) { + *root = y; // node was root + } else if (node == node->parent->right) { + node->parent->right = y; // node was at the right + } else { + node->parent->left = y; // node was at the left + } + // put node at the right of y + y->right = node; + node->parent = y; +} + +/** + * Rebalance the RED-BLACK tree. + * Called when the node and it's parent are both RED. + * @param node Node to be rebalanced + * @param root Pointer to the root of the tree + * @private + * @see #db_rotate_left(DBNode,DBNode *) + * @see #db_rotate_right(DBNode,DBNode *) + * @see #db_put(DBInterface,DBKey,void *) + */ +static void db_rebalance(DBNode node, DBNode *root) +{ + DBNode y; + +#ifdef DB_ENABLE_STATS + if (stats.db_rebalance != (unsigned int)~0) stats.db_rebalance++; +#endif /* DB_ENABLE_STATS */ + // Restore the RED-BLACK properties + node->color = RED; + while (node != *root && node->parent->color == RED) { + if (node->parent == node->parent->parent->left) { + // If node's parent is a left, y is node's right 'uncle' + y = node->parent->parent->right; + if (y && y->color == RED) { // case 1 + // change the colors and move up the tree + node->parent->color = BLACK; + y->color = BLACK; + node->parent->parent->color = RED; + node = node->parent->parent; + } else { + if (node == node->parent->right) { // case 2 + // move up and rotate + node = node->parent; + db_rotate_left(node, root); + } + // case 3 + node->parent->color = BLACK; + node->parent->parent->color = RED; + db_rotate_right(node->parent->parent, root); + } + } else { + // If node's parent is a right, y is node's left 'uncle' + y = node->parent->parent->left; + if (y && y->color == RED) { // case 1 + // change the colors and move up the tree + node->parent->color = BLACK; + y->color = BLACK; + node->parent->parent->color = RED; + node = node->parent->parent; + } else { + if (node == node->parent->left) { // case 2 + // move up and rotate + node = node->parent; + db_rotate_right(node, root); + } + // case 3 + node->parent->color = BLACK; + node->parent->parent->color = RED; + db_rotate_left(node->parent->parent, root); + } + } + } + (*root)->color = BLACK; // the root can and should always be black +} + +/** + * Erase a node from the RED-BLACK tree, keeping the tree balanced. + * @param node Node to be erased from the tree + * @param root Root of the tree + * @private + * @see #db_rotate_left(DBNode,DBNode *) + * @see #db_rotate_right(DBNode,DBNode *) + * @see #db_free_unlock(Database) + */ +static void db_rebalance_erase(DBNode node, DBNode *root) +{ + DBNode y = node; + DBNode x = NULL; + DBNode x_parent = NULL; + DBNode w; + +#ifdef DB_ENABLE_STATS + if (stats.db_rebalance_erase != (unsigned int)~0) stats.db_rebalance_erase++; +#endif /* DB_ENABLE_STATS */ + // Select where to change the tree + if (y->left == NULL) { // no left + x = y->right; + } else if (y->right == NULL) { // no right + x = y->left; + } else { // both exist, go to the leftmost node of the right sub-tree + y = y->right; + while (y->left != NULL) + y = y->left; + x = y->right; + } + + // Remove the node from the tree + if (y != node) { // both childs existed + // put the left of 'node' in the left of 'y' + node->left->parent = y; + y->left = node->left; + + // 'y' is not the direct child of 'node' + if (y != node->right) { + // put 'x' in the old position of 'y' + x_parent = y->parent; + if (x) x->parent = y->parent; + y->parent->left = x; + // put the right of 'node' in 'y' + y->right = node->right; + node->right->parent = y; + // 'y' is a direct child of 'node' + } else { + x_parent = y; + } + + // link 'y' and the parent of 'node' + if (*root == node) { + *root = y; // 'node' was the root + } else if (node->parent->left == node) { + node->parent->left = y; // 'node' was at the left + } else { + node->parent->right = y; // 'node' was at the right + } + y->parent = node->parent; + // switch colors + { + int tmp = y->color; + y->color = node->color; + node->color = tmp; + } + y = node; + } else { // one child did not exist + // put x in node's position + x_parent = y->parent; + if (x) x->parent = y->parent; + // link x and node's parent + if (*root == node) { + *root = x; // node was the root + } else if (node->parent->left == node) { + node->parent->left = x; // node was at the left + } else { + node->parent->right = x; // node was at the right + } + } + + // Restore the RED-BLACK properties + if (y->color != RED) { + while (x != *root && (x == NULL || x->color == BLACK)) { + if (x == x_parent->left) { + w = x_parent->right; + if (w->color == RED) { + w->color = BLACK; + x_parent->color = RED; + db_rotate_left(x_parent, root); + w = x_parent->right; + } + if ((w->left == NULL || w->left->color == BLACK) && + (w->right == NULL || w->right->color == BLACK)) { + w->color = RED; + x = x_parent; + x_parent = x_parent->parent; + } else { + if (w->right == NULL || w->right->color == BLACK) { + if (w->left) w->left->color = BLACK; + w->color = RED; + db_rotate_right(w, root); + w = x_parent->right; + } + w->color = x_parent->color; + x_parent->color = BLACK; + if (w->right) w->right->color = BLACK; + db_rotate_left(x_parent, root); + break; + } + } else { + w = x_parent->left; + if (w->color == RED) { + w->color = BLACK; + x_parent->color = RED; + db_rotate_right(x_parent, root); + w = x_parent->left; + } + if ((w->right == NULL || w->right->color == BLACK) && + (w->left == NULL || w->left->color == BLACK)) { + w->color = RED; + x = x_parent; + x_parent = x_parent->parent; + } else { + if (w->left == NULL || w->left->color == BLACK) { + if (w->right) w->right->color = BLACK; + w->color = RED; + db_rotate_left(w, root); + w = x_parent->left; + } + w->color = x_parent->color; + x_parent->color = BLACK; + if (w->left) w->left->color = BLACK; + db_rotate_right(x_parent, root); + break; + } + } + } + if (x) x->color = BLACK; + } +} + +/** + * Returns not 0 if the key is considerd to be NULL. + * @param type Type of database + * @param key Key being tested + * @return not 0 if considered NULL, 0 otherwise + * @private + * @see common\db.h#DBType + * @see common\db.h#DBKey + * @see #db_get(DBInterface,DBKey) + * @see #db_put(DBInterface,DBKey,void *) + * @see #db_remove(DBInterface,DBKey) + */ +static int db_is_key_null(DBType type, DBKey key) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_is_key_null != (unsigned int)~0) stats.db_is_key_null++; +#endif /* DB_ENABLE_STATS */ + switch (type) { + case DB_STRING: + case DB_ISTRING: + return (key.str == NULL); + + default: // Not a pointer + return 0; + } +} + +/** + * Duplicate the key used in the database. + * @param db Database the key is being used in + * @param key Key to be duplicated + * @param Duplicated key + * @private + * @see #db_free_add(Database,DBNode,DBNode *) + * @see #db_free_remove(Database,DBNode) + * @see #db_put(DBInterface,DBKey,void *) + * @see #db_dup_key_free(Database,DBKey) + */ +static DBKey db_dup_key(Database db, DBKey key) +{ + unsigned char *str; + +#ifdef DB_ENABLE_STATS + if (stats.db_dup_key != (unsigned int)~0) stats.db_dup_key++; +#endif /* DB_ENABLE_STATS */ + switch (db->type) { + case DB_STRING: + case DB_ISTRING: + if (db->maxlen) { + CREATE(str, unsigned char, db->maxlen +1); + memcpy(str, key.str, db->maxlen); + str[db->maxlen] = '\0'; + key.str = str; + } else { + key.str = (unsigned char *)aStrdup((const char *)key.str); + } + return key; + + default: + return key; + } +} + +/** + * Free a key duplicated by db_dup_key. + * @param db Database the key is being used in + * @param key Key to be freed + * @private + * @see #db_dup_key(Database,DBKey) + */ +static void db_dup_key_free(Database db, DBKey key) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_dup_key_free != (unsigned int)~0) stats.db_dup_key_free++; +#endif /* DB_ENABLE_STATS */ + switch (db->type) { + case DB_STRING: + case DB_ISTRING: + aFree(key.str); + return; + + default: + return; + } +} + +/** + * Add a node to the free_list of the database. + * Marks the node as deleted. + * If the key isn't duplicated, the key is duplicated and released. + * @param db Target database + * @param root Root of the tree from the node + * @param node Target node + * @private + * @see #struct db_free + * @see Database#free_list + * @see Database#free_count + * @see Database#free_max + * @see #db_remove(DBInterface,DBKey) + * @see #db_free_remove(Database,DBNode) + */ +static void db_free_add(Database db, DBNode node, DBNode *root) +{ + DBKey old_key; + +#ifdef DB_ENABLE_STATS + if (stats.db_free_add != (unsigned int)~0) stats.db_free_add++; +#endif /* DB_ENABLE_STATS */ + if (db->free_lock == (unsigned int)~0) { + ShowFatalError("db_free_add: free_lock overflow\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + exit(EXIT_FAILURE); + } + if (!(db->options&DB_OPT_DUP_KEY)) { // Make shure we have a key until the node is freed + old_key = node->key; + node->key = db_dup_key(db, node->key); + db->release(old_key, node->data, DB_RELEASE_KEY); + } + if (db->free_count == db->free_max) { // No more space, expand free_list + db->free_max = (db->free_max<<2) +3; // = db->free_max*4 +3 + if (db->free_max <= db->free_count) { + if (db->free_count == (unsigned int)~0) { + ShowFatalError("db_free_add: free_count overflow\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + exit(EXIT_FAILURE); + } + db->free_max = (unsigned int)~0; + } + RECREATE(db->free_list, struct db_free, db->free_max); + } + node->deleted = 1; + db->free_list[db->free_count].node = node; + db->free_list[db->free_count].root = root; + db->free_count++; + db->item_count--; +} + +/** + * Remove a node from the free_list of the database. + * Marks the node as not deleted. + * NOTE: Frees the duplicated key of the node. + * @param db Target database + * @param node Node being removed from free_list + * @private + * @see #struct db_free + * @see Database#free_list + * @see Database#free_count + * @see #db_put(DBInterface,DBKey,void *) + * @see #db_free_add(Database,DBNode *,DBNode) + */ +static void db_free_remove(Database db, DBNode node) +{ + unsigned int i; + +#ifdef DB_ENABLE_STATS + if (stats.db_free_remove != (unsigned int)~0) stats.db_free_remove++; +#endif /* DB_ENABLE_STATS */ + for (i = 0; i < db->free_count; i++) { + if (db->free_list[i].node == node) { + if (i < db->free_count -1) // copy the last item to where the removed one was + memcpy(&db->free_list[i], &db->free_list[db->free_count -1], sizeof(struct db_free)); + db_dup_key_free(db, node->key); + break; + } + } + node->deleted = 0; + if (i == db->free_count) { + ShowWarning("db_free_remove: node was not found - database allocated at %s:%d\n", db->alloc_file, db->alloc_line); + } else { + db->free_count--; + } + db->item_count++; +} + +/** + * Increment the free_lock of the database. + * @param db Target database + * @private + * @see Database#free_lock + * @see #db_unlock(Database) + */ +static void db_free_lock(Database db) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_free_lock != (unsigned int)~0) stats.db_free_lock++; +#endif /* DB_ENABLE_STATS */ + if (db->free_lock == (unsigned int)~0) { + ShowFatalError("db_free_lock: free_lock overflow\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + exit(EXIT_FAILURE); + } + db->free_lock++; +} + +/** + * Decrement the free_lock of the database. + * If it was the last lock, frees the nodes of the database. + * Keeps the tree balanced. + * NOTE: Frees the duplicated keys of the nodes + * @param db Target database + * @private + * @see Database#free_lock + * @see #db_free_dbn(DBNode) + * @see #db_lock(Database) + */ +static void db_free_unlock(Database db) +{ + unsigned int i; + +#ifdef DB_ENABLE_STATS + if (stats.db_free_unlock != (unsigned int)~0) stats.db_free_unlock++; +#endif /* DB_ENABLE_STATS */ + if (db->free_lock == 0) { + ShowWarning("db_free_unlock: free_lock was already 0\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + } else { + db->free_lock--; + } + if (db->free_lock) + return; // Not last lock + + for (i = 0; i < db->free_count ; i++) { + db_rebalance_erase(db->free_list[i].node, db->free_list[i].root); + db_dup_key_free(db, db->free_list[i].node->key); +#ifdef DB_ENABLE_STATS + if (stats.db_node_free != (unsigned int)~0) stats.db_node_free++; +#endif /* DB_ENABLE_STATS */ + ers_free(db->nodes, db->free_list[i].node); + } + db->free_count = 0; +} + +/*****************************************************************************\ + * (3) Section of protected functions used internally. * + * NOTE: the protected functions used in the database interface are in the * + * next section. * + * db_int_cmp - Default comparator for DB_INT databases. * + * db_uint_cmp - Default comparator for DB_UINT databases. * + * db_string_cmp - Default comparator for DB_STRING databases. * + * db_istring_cmp - Default comparator for DB_ISTRING databases. * + * db_int_hash - Default hasher for DB_INT databases. * + * db_uint_hash - Default hasher for DB_UINT databases. * + * db_string_hash - Default hasher for DB_STRING databases. * + * db_istring_hash - Default hasher for DB_ISTRING databases. * + * db_release_nothing - Releaser that releases nothing. * + * db_release_key - Releaser that only releases the key. * + * db_release_data - Releaser that only releases the data. * + * db_release_both - Releaser that releases key and data. * +\*****************************************************************************/ + +/** + * Default comparator for DB_INT databases. + * Compares key1 to key2. + * Return 0 if equal, negative if lower and positive if higher. + * maxlen is ignored. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_INT + * @see common\db.h#DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_int_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_int_cmp != (unsigned int)~0) stats.db_int_cmp++; +#endif /* DB_ENABLE_STATS */ + if (key1.i < key2.i) return -1; + if (key1.i > key2.i) return 1; + return 0; +} + +/** + * Default comparator for DB_UINT databases. + * Compares key1 to key2. + * Return 0 if equal, negative if lower and positive if higher. + * maxlen is ignored. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_UINT + * @see common\db.h#DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_uint_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_uint_cmp != (unsigned int)~0) stats.db_uint_cmp++; +#endif /* DB_ENABLE_STATS */ + if (key1.ui < key2.ui) return -1; + if (key1.ui > key2.ui) return 1; + return 0; +} + +/** + * Default comparator for DB_STRING databases. + * Compares key1 to key2. + * Return 0 if equal, negative if lower and positive if higher. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_STRING + * @see common\db.h#DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_string_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_string_cmp != (unsigned int)~0) stats.db_string_cmp++; +#endif /* DB_ENABLE_STATS */ + if (maxlen == 0) maxlen = (unsigned short)~0; + return strncmp((const char *)key1.str, (const char *)key2.str, maxlen); +} + +/** + * Default comparator for DB_ISTRING databases. + * Compares key1 to key2 case insensitively. + * Return 0 if equal, negative if lower and positive if higher. + * @param key1 Key to be compared + * @param key2 Key being compared to + * @param maxlen Maximum length of the key to hash + * @return 0 if equal, negative if lower and positive if higher + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_ISTRING + * @see common\db.h#DBComparator + * @see #db_default_cmp(DBType) + */ +static int db_istring_cmp(DBKey key1, DBKey key2, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_istring_cmp != (unsigned int)~0) stats.db_istring_cmp++; +#endif /* DB_ENABLE_STATS */ + if (maxlen == 0) maxlen = (unsigned short)~0; + return strncasecmp((const char *)key1.str, (const char *)key2.str, maxlen); +} + +/** + * Default hasher for DB_INT databases. + * Returns the value of the key as an unsigned int. + * maxlen is ignored. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_INT + * @see common\db.h#DBHasher + * @see #db_default_hash(DBType) + */ +static unsigned int db_int_hash(DBKey key, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_int_hash != (unsigned int)~0) stats.db_int_hash++; +#endif /* DB_ENABLE_STATS */ + return (unsigned int)key.i; +} + +/** + * Default hasher for DB_UINT databases. + * Just returns the value of the key. + * maxlen is ignored. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_UINT + * @see #db_default_hash(DBType) + */ +static unsigned int db_uint_hash(DBKey key, unsigned short maxlen) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_uint_hash != (unsigned int)~0) stats.db_uint_hash++; +#endif /* DB_ENABLE_STATS */ + return key.ui; +} + +/** + * Default hasher for DB_STRING databases. + * If maxlen if 0, the maximum number of maxlen is used instead. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_STRING + * @see #db_default_hash(DBType) + */ +static unsigned int db_string_hash(DBKey key, unsigned short maxlen) +{ + unsigned char *k = key.str; + unsigned int hash = 0; + unsigned short i; + +#ifdef DB_ENABLE_STATS + if (stats.db_string_hash != (unsigned int)~0) stats.db_string_hash++; +#endif /* DB_ENABLE_STATS */ + if (maxlen == 0) + maxlen = (unsigned short)~0; // Maximum + + for (i = 0; *k; i++) { + hash = (hash*33 + *k++)^(hash>>24); + if (i == maxlen) + break; + } + + return hash; +} + +/** + * Default hasher for DB_ISTRING databases. + * If maxlen if 0, the maximum number of maxlen is used instead. + * @param key Key to be hashed + * @param maxlen Maximum length of the key to hash + * @return hash of the key + * @see common\db.h#DBKey + * @see common\db.h\DBType#DB_ISTRING + * @see #db_default_hash(DBType) + */ +static unsigned int db_istring_hash(DBKey key, unsigned short maxlen) +{ + unsigned char *k = key.str; + unsigned int hash = 0; + unsigned short i; + +#ifdef DB_ENABLE_STATS + if (stats.db_istring_hash != (unsigned int)~0) stats.db_istring_hash++; +#endif /* DB_ENABLE_STATS */ + if (maxlen == 0) + maxlen = (unsigned short)~0; // Maximum + + for (i = 0; *k; i++) { + hash = (hash*33 + LOWER(*k))^(hash>>24); + k++; + if (i == maxlen) + break; + } + + return hash; +} + +/** + * Releaser that releases nothing. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see common\db.h#DBKey + * @see common\db.h#DBRelease + * @see common\db.h#DBReleaser + * @see #db_default_releaser(DBType,DBOptions) + */ +static void db_release_nothing(DBKey key, void *data, DBRelease which) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_release_nothing != (unsigned int)~0) stats.db_release_nothing++; +#endif /* DB_ENABLE_STATS */ +} + +/** + * Releaser that only releases the key. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see common\db.h#DBKey + * @see common\db.h#DBRelease + * @see common\db.h#DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_key(DBKey key, void *data, DBRelease which) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_release_key != (unsigned int)~0) stats.db_release_key++; +#endif /* DB_ENABLE_STATS */ + if (which&DB_RELEASE_KEY) aFree(key.str); // needs to be a pointer +} + +/** + * Releaser that only releases the data. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see common\db.h#DBKey + * @see common\db.h#DBRelease + * @see common\db.h#DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_data(DBKey key, void *data, DBRelease which) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_release_data != (unsigned int)~0) stats.db_release_data++; +#endif /* DB_ENABLE_STATS */ + if (which&DB_RELEASE_DATA) aFree(data); +} + +/** + * Releaser that releases both key and data. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @protected + * @see common\db.h#DBKey + * @see common\db.h#DBRelease + * @see common\db.h#DBReleaser + * @see #db_default_release(DBType,DBOptions) + */ +static void db_release_both(DBKey key, void *data, DBRelease which) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_release_both != (unsigned int)~0) stats.db_release_both++; +#endif /* DB_ENABLE_STATS */ + if (which&DB_RELEASE_KEY) aFree(key.str); // needs to be a pointer + if (which&DB_RELEASE_DATA) aFree(data); +} + +/*****************************************************************************\ + * (4) Section with protected functions used in the interface of the * + * database. * + * db_obj_get - Get the data identified by the key. * + * db_obj_vgetall - Get the data of the matched entries. * + * db_obj_getall - Get the data of the matched entries. * + * db_obj_vensure - Get the data identified by the key, creating if it * + * doesn't exist yet. * + * db_obj_ensure - Get the data identified by the key, creating if it * + * doesn't exist yet. * + * db_obj_put - Put data identified by the key in the database. * + * db_obj_remove - Remove an entry from the database. * + * db_obj_vforeach - Apply a function to every entry in the database. * + * db_obj_foreach - Apply a function to every entry in the database. * + * db_obj_vclear - Remove all entries from the database. * + * db_obj_clear - Remove all entries from the database. * + * db_obj_vdestroy - Destroy the database, freeing all the used memory. * + * db_obj_destroy - Destroy the database, freeing all the used memory. * + * db_obj_size - Return the size of the database. * + * db_obj_type - Return the type of the database. * + * db_obj_options - Return the options of the database. * +\*****************************************************************************/ + +/** + * Get the data of the entry identifid by the key. + * @param self Interface of the database + * @param key Key that identifies the entry + * @return Data of the entry or NULL if not found + * @protected + * @see common\db.h#DBKey + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#get(DBInterface,DBKey) + */ +static void *db_obj_get(DBInterface self, DBKey key) +{ + Database db = (Database)self; + DBNode node; + int c; + void *data = NULL; + +#ifdef DB_ENABLE_STATS + if (stats.db_get != (unsigned int)~0) stats.db_get++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return NULL; // nullpo candidate + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_get: Attempted to retrieve non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + + db_free_lock(db); + node = db->ht[db->hash(key, db->maxlen)%HASH_SIZE]; + while (node) { + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { + data = node->data; + break; + } + if (c < 0) + node = node->left; + else + node = node->right; + } + db_free_unlock(db); + return data; +} + +/** + * Get the data of the entries matched by match. + * It puts a maximum of max entries into buf. + * If buf is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than max, only the + * first max entries found are put into the buffer. + * @param self Interface of the database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see common\db.h#DBInterface + * @see common\db.h#DBMatcher(DBKey key, void *data, va_list args) + * @see common\db.h\DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list) + */ +static unsigned int db_obj_vgetall(DBInterface self, void **buf, unsigned int max, DBMatcher match, va_list args) +{ + Database db = (Database)self; + unsigned int i; + DBNode node; + DBNode parent; + unsigned int ret = 0; + +#ifdef DB_ENABLE_STATS + if (stats.db_vgetall != (unsigned int)~0) stats.db_vgetall++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return 0; // nullpo candidate + if (match == NULL) return 0; // nullpo candidate + + db_free_lock(db); + for (i = 0; i < HASH_SIZE; i++) { + // Match in the order: current node, left tree, right tree + node = db->ht[i]; + while (node) { + parent = node->parent; + if (!(node->deleted) && match(node->key, node->data, args) == 0) { + if (buf && ret < max) + buf[ret] = node->data; + ret++; + } + if (node->left) { + node = node->left; + continue; + } + if (node->right) { + node = node->right; + continue; + } + while (node) { + parent = node->parent; + if (parent && parent->right && parent->left == node) { + node = parent->right; + break; + } + node = parent; + } + } + } + db_free_unlock(db); + return ret; +} + +/** + * Just calls {@link common\db.h\DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list)}. + * Get the data of the entries matched by match. + * It puts a maximum of max entries into buf. + * If buf is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than max, only the + * first max entries found are put into the buffer. + * @param self Interface of the database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see common\db.h#DBMatcher(DBKey key, void *data, va_list args) + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list) + * @see common\db.h\DBInterface#getall(DBInterface,void **,unsigned int,DBMatch,...) + */ +static unsigned int db_obj_getall(DBInterface self, void **buf, unsigned int max, DBMatcher match, ...) +{ + va_list args; + unsigned int ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_getall != (unsigned int)~0) stats.db_getall++; +#endif /* DB_ENABLE_STATS */ + if (self == NULL) return 0; // nullpo candidate + + va_start(args, match); + ret = self->vgetall(self, buf, max, match, args); + va_end(args); + return ret; +} + +/** + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * create. + * @param self Interface of the database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param args Extra arguments for create + * @return Data of the entry + * @protected + * @see common\db.h#DBKey + * @see common\db.h#DBCreateData + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list) + */ +static void *db_obj_vensure(DBInterface self, DBKey key, DBCreateData create, va_list args) +{ + Database db = (Database)self; + DBNode node; + DBNode parent = NULL; + unsigned int hash; + int c = 0; + void *data = NULL; + +#ifdef DB_ENABLE_STATS + if (stats.db_vensure != (unsigned int)~0) stats.db_vensure++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return NULL; // nullpo candidate + if (create == NULL) { + ShowError("db_ensure: Create function is NULL for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_ensure: Attempted to use non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + + db_free_lock(db); + hash = db->hash(key, db->maxlen)%HASH_SIZE; + node = db->ht[hash]; + while (node) { + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { + break; + } + parent = node; + if (c < 0) + node = node->left; + else + node = node->right; + } + // Create node if necessary + if (node == NULL) { + if (db->item_count == (unsigned int)~0) { + ShowError("db_vensure: item_count overflow, aborting item insertion.\n" + "Database allocated at %s:%d", + db->alloc_file, db->alloc_line); + return NULL; + } +#ifdef DB_ENABLE_STATS + if (stats.db_node_alloc != (unsigned int)~0) stats.db_node_alloc++; +#endif /* DB_ENABLE_STATS */ + node = ers_alloc(db->nodes, struct dbn); + node->left = NULL; + node->right = NULL; + node->deleted = 0; + db->item_count++; + if (c == 0) { // hash entry is empty + node->color = BLACK; + node->parent = NULL; + db->ht[hash] = node; + } else { + node->color = RED; + if (c < 0) { // put at the left + parent->left = node; + node->parent = parent; + } else { // put at the right + parent->right = node; + node->parent = parent; + } + if (parent->color == RED) // two consecutive RED nodes, must rebalance + db_rebalance(node, &db->ht[hash]); + } + // put key and data in the node + if (db->options&DB_OPT_DUP_KEY) { + node->key = db_dup_key(db, key); + if (db->options&DB_OPT_RELEASE_KEY) + db->release(key, data, DB_RELEASE_KEY); + } else { + node->key = key; + } + node->data = create(key, args); + } + data = node->data; + db_free_unlock(db); + return data; +} + +/** + * Just calls {@link common\db.h\DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list)}. + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * create. + * @param self Interface of the database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param ... Extra arguments for create + * @return Data of the entry + * @protected + * @see common\db.h#DBKey + * @see common\db.h#DBCreateData + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list) + * @see common\db.h\DBInterface#ensure(DBInterface,DBKey,DBCreateData,...) + */ +static void *db_obj_ensure(DBInterface self, DBKey key, DBCreateData create, ...) +{ + va_list args; + void *ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_ensure != (unsigned int)~0) stats.db_ensure++; +#endif /* DB_ENABLE_STATS */ + if (self == NULL) return 0; // nullpo candidate + + va_start(args, create); + ret = self->vensure(self, key, create, args); + va_end(args); + return ret; +} + +/** + * Put the data identified by the key in the database. + * Returns the previous data if the entry exists or NULL. + * NOTE: Uses the new key, the old one is released. + * @param self Interface of the database + * @param key Key that identifies the data + * @param data Data to be put in the database + * @return The previous data if the entry exists or NULL + * @protected + * @see common\db.h#DBKey + * @see common\db.h#DBInterface + * @see #db_malloc_dbn(void) + * @see common\db.h\DBInterface#put(DBInterface,DBKey,void *) + */ +static void *db_obj_put(DBInterface self, DBKey key, void *data) +{ + Database db = (Database)self; + DBNode node; + DBNode parent = NULL; + int c = 0; + unsigned int hash; + void *old_data = NULL; + +#ifdef DB_ENABLE_STATS + if (stats.db_put != (unsigned int)~0) stats.db_put++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return NULL; // nullpo candidate + if (db->global_lock) { + ShowError("db_put: Database is being destroyed, aborting entry insertion.\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_put: Attempted to use non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + if (!(data || db->options&DB_OPT_ALLOW_NULL_DATA)) { + ShowError("db_put: Attempted to use non-allowed NULL data for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + + if (db->item_count == (unsigned int)~0) { + ShowError("db_put: item_count overflow, aborting item insertion.\n" + "Database allocated at %s:%d", + db->alloc_file, db->alloc_line); + return NULL; + } + // search for an equal node + db_free_lock(db); + hash = db->hash(key, db->maxlen)%HASH_SIZE; + for (node = db->ht[hash]; node; ) { + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { // equal entry, replace + if (node->deleted) { + db_free_remove(db, node); + } else { + db->release(node->key, node->data, DB_RELEASE_BOTH); + } + old_data = node->data; + break; + } + parent = node; + if (c < 0) { + node = node->left; + } else { + node = node->right; + } + } + // allocate a new node if necessary + if (node == NULL) { +#ifdef DB_ENABLE_STATS + if (stats.db_node_alloc != (unsigned int)~0) stats.db_node_alloc++; +#endif /* DB_ENABLE_STATS */ + node = ers_alloc(db->nodes, struct dbn); + node->left = NULL; + node->right = NULL; + node->deleted = 0; + db->item_count++; + if (c == 0) { // hash entry is empty + node->color = BLACK; + node->parent = NULL; + db->ht[hash] = node; + } else { + node->color = RED; + if (c < 0) { // put at the left + parent->left = node; + node->parent = parent; + } else { // put at the right + parent->right = node; + node->parent = parent; + } + if (parent->color == RED) // two consecutive RED nodes, must rebalance + db_rebalance(node, &db->ht[hash]); + } + } + // put key and data in the node + if (db->options&DB_OPT_DUP_KEY) { + node->key = db_dup_key(db, key); + if (db->options&DB_OPT_RELEASE_KEY) + db->release(key, data, DB_RELEASE_KEY); + } else { + node->key = key; + } + node->data = data; + db_free_unlock(db); + return old_data; +} + +/** + * Remove an entry from the database. + * Returns the data of the entry. + * NOTE: The key (of the database) is released in {@link #db_free_add(Database,DBNode,DBNode *)}. + * @param self Interface of the database + * @param key Key that identifies the entry + * @return The data of the entry or NULL if not found + * @protected + * @see common\db.h#DBKey + * @see common\db.h#DBInterface + * @see #db_free_add(Database,DBNode,DBNode *) + * @see common\db.h\DBInterface#remove(DBInterface,DBKey) + */ +static void *db_obj_remove(DBInterface self, DBKey key) +{ + Database db = (Database)self; + void *data = NULL; + DBNode node; + unsigned int hash; + int c = 0; + +#ifdef DB_ENABLE_STATS + if (stats.db_remove != (unsigned int)~0) stats.db_remove++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return NULL; // nullpo candidate + if (db->global_lock) { + ShowError("db_remove: Database is being destroyed. Aborting entry deletion.\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) { + ShowError("db_remove: Attempted to use non-allowed NULL key for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return NULL; // nullpo candidate + } + + db_free_lock(db); + hash = db->hash(key, db->maxlen)%HASH_SIZE; + for(node = db->ht[hash]; node; ){ + c = db->cmp(key, node->key, db->maxlen); + if (c == 0) { + if (!(node->deleted)) { + data = node->data; + db->release(node->key, node->data, DB_RELEASE_DATA); + db_free_add(db, node, &db->ht[hash]); + } + break; + } + if (c < 0) + node = node->left; + else + node = node->right; + } + db_free_unlock(db); + return data; +} + +/** + * Apply func to every entry in the database. + * Returns the sum of values returned by func. + * @param self Interface of the database + * @param func Function to be applyed + * @param args Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see common\db.h#DBInterface + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h\DBInterface#vforeach(DBInterface,DBApply,va_list) + */ +static int db_obj_vforeach(DBInterface self, DBApply func, va_list args) +{ + Database db = (Database)self; + unsigned int i; + int sum = 0; + DBNode node; + DBNode parent; + +#ifdef DB_ENABLE_STATS + if (stats.db_vforeach != (unsigned int)~0) stats.db_vforeach++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return 0; // nullpo candidate + if (func == NULL) { + ShowError("db_foreach: Passed function is NULL for db allocated at %s:%d\n",db->alloc_file, db->alloc_line); + return 0; // nullpo candidate + } + + db_free_lock(db); + for (i = 0; i < HASH_SIZE; i++) { + // Apply func in the order: current node, left node, right node + node = db->ht[i]; + while (node) { + parent = node->parent; + if (!(node->deleted)) + sum += func(node->key, node->data, args); + if (node->left) { + node = node->left; + continue; + } + if (node->right) { + node = node->right; + continue; + } + while (node) { + parent = node->parent; + if (parent && parent->right && parent->left == node) { + node = parent->right; + break; + } + node = parent; + } + } + } + db_free_unlock(db); + return sum; +} + +/** + * Just calls {@link common\db.h\DBInterface#vforeach(DBInterface,DBApply,va_list)}. + * Apply func to every entry in the database. + * Returns the sum of values returned by func. + * @param self Interface of the database + * @param func Function to be applyed + * @param ... Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see common\db.h#DBInterface + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h\DBInterface#vforeach(DBInterface,DBApply,va_list) + * @see common\db.h\DBInterface#foreach(DBInterface,DBApply,...) + */ +static int db_obj_foreach(DBInterface self, DBApply func, ...) +{ + va_list args; + int ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_foreach != (unsigned int)~0) stats.db_foreach++; +#endif /* DB_ENABLE_STATS */ + if (self == NULL) return 0; // nullpo candidate + + va_start(args, func); + ret = self->vforeach(self, func, args); + va_end(args); + return ret; +} + +/** + * Removes all entries from the database. + * Before deleting an entry, func is applyed to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * @param self Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vclear(DBInterface,DBApply,va_list) + */ +static int db_obj_vclear(DBInterface self, DBApply func, va_list args) +{ + Database db = (Database)self; + int sum = 0; + unsigned int i; + DBNode node; + DBNode parent; + +#ifdef DB_ENABLE_STATS + if (stats.db_vclear != (unsigned int)~0) stats.db_vclear++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return 0; // nullpo candidate + + db_free_lock(db); + for (i = 0; i < HASH_SIZE; i++) { + // Apply the func and delete in the order: left tree, right tree, current node + node = db->ht[i]; + db->ht[i] = NULL; + while (node) { + parent = node->parent; + if (node->left) { + node = node->left; + continue; + } + if (node->right) { + node = node->right; + continue; + } + if (node->deleted) { + db_dup_key_free(db, node->key); + } else { + if (func) + sum += func(node->key, node->data, args); + db->release(node->key, node->data, DB_RELEASE_BOTH); + node->deleted = 1; + } +#ifdef DB_ENABLE_STATS + if (stats.db_node_free != (unsigned int)~0) stats.db_node_free++; +#endif /* DB_ENABLE_STATS */ + ers_free(db->nodes, node); + if (parent) { + if (parent->left == node) + parent->left = NULL; + else + parent->right = NULL; + } + node = parent; + } + db->ht[i] = NULL; + } + db->free_count = 0; + db->item_count = 0; + db_free_unlock(db); + return sum; +} + +/** + * Just calls {@link common\db.h\DBInterface#vclear(DBInterface,DBApply,va_list)}. + * Removes all entries from the database. + * Before deleting an entry, func is applyed to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param self Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vclear(DBInterface,DBApply,va_list) + * @see common\db.h\DBInterface#clear(DBInterface,DBApply,...) + */ +static int db_obj_clear(DBInterface self, DBApply func, ...) +{ + va_list args; + int ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_clear != (unsigned int)~0) stats.db_clear++; +#endif /* DB_ENABLE_STATS */ + if (self == NULL) return 0; // nullpo candidate + + va_start(args, func); + ret = self->vclear(self, func, args); + va_end(args); + return ret; +} + +/** + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applyed to it. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param self Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vdestroy(DBInterface,DBApply,va_list) + */ +static int db_obj_vdestroy(DBInterface self, DBApply func, va_list args) +{ + Database db = (Database)self; + int sum; + +#ifdef DB_ENABLE_STATS + if (stats.db_vdestroy != (unsigned int)~0) stats.db_vdestroy++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return 0; // nullpo candidate + if (db->global_lock) { + ShowError("db_vdestroy: Database is already locked for destruction. Aborting second database destruction.\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line); + return 0; + } + if (db->free_lock) + ShowWarning("db_vdestroy: Database is still in use, %u lock(s) left. Continuing database destruction.\n" + "Database allocated at %s:%d\n", + db->alloc_file, db->alloc_line, db->free_lock); + +#ifdef DB_ENABLE_STATS + switch (db->type) { + case DB_INT: + stats.db_int_destroy++; + break; + case DB_UINT: + stats.db_uint_destroy++; + break; + case DB_STRING: + stats.db_string_destroy++; + break; + case DB_ISTRING: + stats.db_istring_destroy++; + break; + } +#endif /* DB_ENABLE_STATS */ + db_free_lock(db); + db->global_lock = 1; + sum = self->vclear(self, func, args); + aFree(db->free_list); + db->free_list = NULL; + db->free_max = 0; + ers_destroy(db->nodes); + db_free_unlock(db); + aFree(db); + return sum; +} + +/** + * Just calls {@link common\db.h\DBInterface#db_vdestroy(DBInterface,DBApply,va_list)}. + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applyed to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted. + * @param self Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see common\db.h#DBApply(DBKey,void *,va_list) + * @see common\db.h#DBInterface + * @see common\db.h\DBInterface#vdestroy(DBInterface,DBApply,va_list) + * @see common\db.h\DBInterface#destroy(DBInterface,DBApply,...) + */ +static int db_obj_destroy(DBInterface self, DBApply func, ...) +{ + va_list args; + int ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_destroy != (unsigned int)~0) stats.db_destroy++; +#endif /* DB_ENABLE_STATS */ + if (self == NULL) return 0; // nullpo candidate + + va_start(args, func); + ret = self->vdestroy(self, func, args); + va_end(args); + return ret; +} + +/** + * Return the size of the database (number of items in the database). + * @param self Interface of the database + * @return Size of the database + * @protected + * @see common\db.h#DBInterface + * @see Database#item_count + * @see common\db.h\DBInterface#size(DBInterface) + */ +static unsigned int db_obj_size(DBInterface self) +{ + Database db = (Database)self; + unsigned int item_count; + +#ifdef DB_ENABLE_STATS + if (stats.db_size != (unsigned int)~0) stats.db_size++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return 0; // nullpo candidate + + db_free_lock(db); + item_count = db->item_count; + db_free_unlock(db); + + return item_count; +} + +/** + * Return the type of database. + * @param self Interface of the database + * @return Type of the database + * @protected + * @see common\db.h#DBType + * @see common\db.h#DBInterface + * @see Database#type + * @see common\db.h\DBInterface#type(DBInterface) + */ +static DBType db_obj_type(DBInterface self) +{ + Database db = (Database)self; + DBType type; + +#ifdef DB_ENABLE_STATS + if (stats.db_type != (unsigned int)~0) stats.db_type++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return -1; // nullpo candidate - TODO what should this return? + + db_free_lock(db); + type = db->type; + db_free_unlock(db); + + return type; +} + +/** + * Return the options of the database. + * @param self Interface of the database + * @return Options of the database + * @protected + * @see common\db.h#DBOptions + * @see common\db.h#DBInterface + * @see Database#options + * @see common\db.h\DBInterface#options(DBInterface) + */ +static DBOptions db_obj_options(DBInterface self) +{ + Database db = (Database)self; + DBOptions options; + +#ifdef DB_ENABLE_STATS + if (stats.db_options != (unsigned int)~0) stats.db_options++; +#endif /* DB_ENABLE_STATS */ + if (db == NULL) return DB_OPT_BASE; // nullpo candidate - TODO what should this return? + + db_free_lock(db); + options = db->options; + db_free_unlock(db); + + return options; +} + +/*****************************************************************************\ + * (5) Section with public functions. * + * db_fix_options - Apply database type restrictions to the options. * + * db_default_cmp - Get the default comparator for a type of database. * + * db_default_hash - Get the default hasher for a type of database. * + * db_default_release - Get the default releaser for a type of database * + * with the specified options. * + * db_custom_release - Get a releaser that behaves a certains way. * + * db_alloc - Allocate a new database. * + * db_i2key - Manual cast from 'int' to 'DBKey'. * + * db_ui2key - Manual cast from 'unsigned int' to 'DBKey'. * + * db_str2key - Manual cast from 'unsigned char *' to 'DBKey'. * + * db_init - Initialize the database system. * + * db_final - Finalize the database system. * +\*****************************************************************************/ + +/** + * Returns the fixed options according to the database type. + * Sets required options and unsets unsupported options. + * For numeric databases DB_OPT_DUP_KEY and DB_OPT_RELEASE_KEY are unset. + * @param type Type of the database + * @param options Original options of the database + * @return Fixed options of the database + * @private + * @see common\db.h#DBType + * @see common\db.h#DBOptions + * @see #db_default_release(DBType,DBOptions) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + * @see common\db.h#db_fix_options(DBType,DBOptions) + */ +DBOptions db_fix_options(DBType type, DBOptions options) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_fix_options != (unsigned int)~0) stats.db_fix_options++; +#endif /* DB_ENABLE_STATS */ + switch (type) { + case DB_INT: + case DB_UINT: // Numeric database, do nothing with the keys + return options&~(DB_OPT_DUP_KEY|DB_OPT_RELEASE_KEY); + + default: + ShowError("db_fix_options: Unknown database type %u with options %x\n", type, options); + case DB_STRING: + case DB_ISTRING: // String databases, no fix required + return options; + } +} + +/** + * Returns the default comparator for the specified type of database. + * @param type Type of database + * @return Comparator for the type of database or NULL if unknown database + * @public + * @see common\db.h#DBType + * @see #db_int_cmp(DBKey,DBKey,unsigned short) + * @see #db_uint_cmp(DBKey,DBKey,unsigned short) + * @see #db_string_cmp(DBKey,DBKey,unsigned short) + * @see #db_istring_cmp(DBKey,DBKey,unsigned short) + * @see common\db.h#db_default_cmp(DBType) + */ +DBComparator db_default_cmp(DBType type) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_default_cmp != (unsigned int)~0) stats.db_default_cmp++; +#endif /* DB_ENABLE_STATS */ + switch (type) { + case DB_INT: return db_int_cmp; + case DB_UINT: return db_uint_cmp; + case DB_STRING: return db_string_cmp; + case DB_ISTRING: return db_istring_cmp; + default: + ShowError("db_default_cmp: Unknown database type %u\n", type); + return NULL; + } +} + +/** + * Returns the default hasher for the specified type of database. + * @param type Type of database + * @return Hasher of the type of database or NULL if unknown database + * @public + * @see common\db.h#DBType + * @see #db_int_hash(DBKey,unsigned short) + * @see #db_uint_hash(DBKey,unsigned short) + * @see #db_string_hash(DBKey,unsigned short) + * @see #db_istring_hash(DBKey,unsigned short) + * @see common\db.h#db_default_hash(DBType) + */ +DBHasher db_default_hash(DBType type) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_default_hash != (unsigned int)~0) stats.db_default_hash++; +#endif /* DB_ENABLE_STATS */ + switch (type) { + case DB_INT: return db_int_hash; + case DB_UINT: return db_uint_hash; + case DB_STRING: return db_string_hash; + case DB_ISTRING: return db_istring_hash; + default: + ShowError("db_default_hash: Unknown database type %u\n", type); + return NULL; + } +} + +/** + * Returns the default releaser for the specified type of database with the + * specified options. + * NOTE: the options are fixed with {@link #db_fix_options(DBType,DBOptions)} + * before choosing the releaser. + * @param type Type of database + * @param options Options of the database + * @return Default releaser for the type of database with the specified options + * @public + * @see common\db.h#DBType + * @see common\db.h#DBOptions + * @see common\db.h#DBReleaser + * @see #db_release_nothing(DBKey,void *,DBRelease) + * @see #db_release_key(DBKey,void *,DBRelease) + * @see #db_release_data(DBKey,void *,DBRelease) + * @see #db_release_both(DBKey,void *,DBRelease) + * @see #db_custom_release(DBRelease) + * @see common\db.h#db_default_release(DBType,DBOptions) + */ +DBReleaser db_default_release(DBType type, DBOptions options) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_default_release != (unsigned int)~0) stats.db_default_release++; +#endif /* DB_ENABLE_STATS */ + options = db_fix_options(type, options); + if (options&DB_OPT_RELEASE_DATA) { // Release data, what about the key? + if (options&(DB_OPT_DUP_KEY|DB_OPT_RELEASE_KEY)) + return db_release_both; // Release both key and data + return db_release_data; // Only release data + } + if (options&(DB_OPT_DUP_KEY|DB_OPT_RELEASE_KEY)) + return db_release_key; // Only release key + return db_release_nothing; // Release nothing +} + +/** + * Returns the releaser that releases the specified release options. + * @param which Options that specified what the releaser releases + * @return Releaser for the specified release options + * @public + * @see common\db.h#DBRelease + * @see common\db.h#DBReleaser + * @see #db_release_nothing(DBKey,void *,DBRelease) + * @see #db_release_key(DBKey,void *,DBRelease) + * @see #db_release_data(DBKey,void *,DBRelease) + * @see #db_release_both(DBKey,void *,DBRelease) + * @see #db_default_release(DBType,DBOptions) + * @see common\db.h#db_custom_release(DBRelease) + */ +DBReleaser db_custom_release(DBRelease which) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_custom_release != (unsigned int)~0) stats.db_custom_release++; +#endif /* DB_ENABLE_STATS */ + switch (which) { + case DB_RELEASE_NOTHING: return db_release_nothing; + case DB_RELEASE_KEY: return db_release_key; + case DB_RELEASE_DATA: return db_release_data; + case DB_RELEASE_BOTH: return db_release_both; + default: + ShowError("db_custom_release: Unknown release options %u\n", which); + return NULL; + } +} + +/** + * Allocate a new database of the specified type. + * NOTE: the options are fixed by {@link #db_fix_options(DBType,DBOptions)} + * before creating the database. + * @param file File where the database is being allocated + * @param line Line of the file where the database is being allocated + * @param type Type of database + * @param options Options of the database + * @param maxlen Maximum length of the string to be used as key in string + * databases + * @return The interface of the database + * @public + * @see common\db.h#DBType + * @see common\db.h#DBInterface + * @see #Database + * @see #db_fix_options(DBType,DBOptions) + * @see common\db.h#db_alloc(const char *,int,DBType,unsigned short) + */ +DBInterface db_alloc(const char *file, int line, DBType type, DBOptions options, unsigned short maxlen) +{ + Database db; + unsigned int i; + +#ifdef DB_ENABLE_STATS + if (stats.db_alloc != (unsigned int)~0) stats.db_alloc++; + switch (type) { + case DB_INT: + stats.db_int_alloc++; + break; + case DB_UINT: + stats.db_uint_alloc++; + break; + case DB_STRING: + stats.db_string_alloc++; + break; + case DB_ISTRING: + stats.db_istring_alloc++; + break; + } +#endif /* DB_ENABLE_STATS */ + CREATE(db, struct db, 1); + + options = db_fix_options(type, options); + /* Interface of the database */ + db->dbi.get = db_obj_get; + db->dbi.getall = db_obj_getall; + db->dbi.vgetall = db_obj_vgetall; + db->dbi.ensure = db_obj_ensure; + db->dbi.vensure = db_obj_vensure; + db->dbi.put = db_obj_put; + db->dbi.remove = db_obj_remove; + db->dbi.foreach = db_obj_foreach; + db->dbi.vforeach = db_obj_vforeach; + db->dbi.clear = db_obj_clear; + db->dbi.vclear = db_obj_vclear; + db->dbi.destroy = db_obj_destroy; + db->dbi.vdestroy = db_obj_vdestroy; + db->dbi.size = db_obj_size; + db->dbi.type = db_obj_type; + db->dbi.options = db_obj_options; + /* File and line of allocation */ + db->alloc_file = file; + db->alloc_line = line; + /* Lock system */ + db->free_list = NULL; + db->free_count = 0; + db->free_max = 0; + db->free_lock = 0; + /* Other */ + db->nodes = ers_new((uint32)sizeof(struct dbn)); + db->cmp = db_default_cmp(type); + db->hash = db_default_hash(type); + db->release = db_default_release(type, options); + for (i = 0; i < HASH_SIZE; i++) + db->ht[i] = NULL; + db->type = type; + db->options = options; + db->item_count = 0; + db->maxlen = maxlen; + db->global_lock = 0; + + return &db->dbi; +} + +#ifdef DB_MANUAL_CAST_TO_UNION +/** + * Manual cast from 'int' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see common\db.h#DB_MANUAL_CAST_TO_UNION + * @see #db_ui2key(unsigned int) + * @see #db_str2key(unsigned char *) + * @see common\db.h#db_i2key(int) + */ +DBKey db_i2key(int key) +{ + DBKey ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_i2key != (unsigned int)~0) stats.db_i2key++; +#endif /* DB_ENABLE_STATS */ + ret.i = key; + return ret; +} + +/** + * Manual cast from 'unsigned int' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see common\db.h#DB_MANUAL_CAST_TO_UNION + * @see #db_i2key(int) + * @see #db_str2key(unsigned char *) + * @see common\db.h#db_ui2key(unsigned int) + */ +DBKey db_ui2key(unsigned int key) +{ + DBKey ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_ui2key != (unsigned int)~0) stats.db_ui2key++; +#endif /* DB_ENABLE_STATS */ + ret.ui = key; + return ret; +} + +/** + * Manual cast from 'unsigned char *' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see common\db.h#DB_MANUAL_CAST_TO_UNION + * @see #db_i2key(int) + * @see #db_ui2key(unsigned int) + * @see common\db.h#db_str2key(unsigned char *) + */ +DBKey db_str2key(unsigned char *key) +{ + DBKey ret; + +#ifdef DB_ENABLE_STATS + if (stats.db_str2key != (unsigned int)~0) stats.db_str2key++; +#endif /* DB_ENABLE_STATS */ + ret.str = key; + return ret; +} +#endif /* DB_MANUAL_CAST_TO_UNION */ + +/** + * Initialize the database system. + * @public + * @see #db_final(void) + * @see common\db.h#db_init(void) + */ +void db_init(void) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_init != (unsigned int)~0) stats.db_init++; +#endif /* DB_ENABLE_STATS */ +} + +/** + * Finalize the database system. + * Frees the memory used by the block reusage system. + * @public + * @see common\db.h#DB_FINAL_NODE_CHECK + * @see #db_init(void) + * @see common\db.h#db_final(void) + */ +void db_final(void) +{ +#ifdef DB_ENABLE_STATS + if (stats.db_final != (unsigned int)~0) + stats.db_final++; + ShowInfo(CL_WHITE"Database nodes"CL_RESET":\n" + "allocated %u, freed %u\n", + stats.db_node_alloc, stats.db_node_free); + ShowInfo(CL_WHITE"Database types"CL_RESET":\n" + "DB_INT : allocated %10u, destroyed %10u\n" + "DB_UINT : allocated %10u, destroyed %10u\n" + "DB_STRING : allocated %10u, destroyed %10u\n" + "DB_ISTRING : allocated %10u, destroyed %10u\n", + stats.db_int_alloc, stats.db_int_destroy, + stats.db_uint_alloc, stats.db_uint_destroy, + stats.db_string_alloc, stats.db_string_destroy, + stats.db_istring_alloc, stats.db_istring_destroy); + ShowInfo(CL_WHITE"Database function counters"CL_RESET":\n" + "db_rotate_left %10u, db_rotate_right %10u,\n" + "db_rebalance %10u, db_rebalance_erase %10u,\n" + "db_is_key_null %10u,\n" + "db_dup_key %10u, db_dup_key_free %10u,\n" + "db_free_add %10u, db_free_remove %10u,\n" + "db_free_lock %10u, db_free_unlock %10u,\n" + "db_int_cmp %10u, db_uint_cmp %10u,\n" + "db_string_cmp %10u, db_istring_cmp %10u,\n" + "db_int_hash %10u, db_uint_hash %10u,\n" + "db_string_hash %10u, db_istring_hash %10u,\n" + "db_release_nothing %10u, db_release_key %10u,\n" + "db_release_data %10u, db_release_both %10u,\n" + "db_get %10u,\n" + "db_getall %10u, db_vgetall %10u,\n" + "db_ensure %10u, db_vensure %10u,\n" + "db_put %10u, db_remove %10u,\n" + "db_foreach %10u, db_vforeach %10u,\n" + "db_clear %10u, db_vclear %10u,\n" + "db_destroy %10u, db_vdestroy %10u,\n" + "db_size %10u, db_type %10u,\n" + "db_options %10u, db_fix_options %10u,\n" + "db_default_cmp %10u, db_default_hash %10u,\n" + "db_default_release %10u, db_custom_release %10u,\n" + "db_alloc %10u, db_i2key %10u,\n" + "db_ui2key %10u, db_str2key %10u,\n" + "db_init %10u, db_final %10u\n", + stats.db_rotate_left, stats.db_rotate_right, + stats.db_rebalance, stats.db_rebalance_erase, + stats.db_is_key_null, + stats.db_dup_key, stats.db_dup_key_free, + stats.db_free_add, stats.db_free_remove, + stats.db_free_lock, stats.db_free_unlock, + stats.db_int_cmp, stats.db_uint_cmp, + stats.db_string_cmp, stats.db_istring_cmp, + stats.db_int_hash, stats.db_uint_hash, + stats.db_string_hash, stats.db_istring_hash, + stats.db_release_nothing, stats.db_release_key, + stats.db_release_data, stats.db_release_both, + stats.db_get, + stats.db_getall, stats.db_vgetall, + stats.db_ensure, stats.db_vensure, + stats.db_put, stats.db_remove, + stats.db_foreach, stats.db_vforeach, + stats.db_clear, stats.db_vclear, + stats.db_destroy, stats.db_vdestroy, + stats.db_size, stats.db_type, + stats.db_options, stats.db_fix_options, + stats.db_default_cmp, stats.db_default_hash, + stats.db_default_release, stats.db_custom_release, + stats.db_alloc, stats.db_i2key, + stats.db_ui2key, stats.db_str2key, + stats.db_init, stats.db_final); +#endif /* DB_ENABLE_STATS */ +} + diff --git a/src/common/db.h b/src/common/db.h new file mode 100644 index 000000000..dcc583bfa --- /dev/null +++ b/src/common/db.h @@ -0,0 +1,734 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + * This file is separated in two sections: * + * (1) public typedefs, enums, unions, structures and defines * + * (2) public functions * + * * + * Notes on the release system: * + * Whenever an entry is removed from the database both the key and the * + * data are requested to be released. * + * At least one entry is removed when replacing an entry, removing an * + * entry, clearing the database or destroying the database. * + * What is actually released is defined by the release function, the * + * functions of the database only ask for the key and/or data to be * + * released. * + * * + * TODO: * + * - create an enum for the data (with int, unsigned int and void *) * + * - create a custom database allocator * + * - see what functions need or should be added to the database interface * + * * + * HISTORY: * + * 2.1 (Athena build #???#) - Portability fix * + * - Fixed the portability of casting to union and added the functions * + * {@link DBInterface#ensure(DBInterface,DBKey,DBCreateData,...)} and * + * {@link DBInterface#clear(DBInterface,DBApply,...)}. * + * 2.0 (Athena build 4859) - Transition version * + * - Almost everything recoded with a strategy similar to objects, * + * database structure is maintained. * + * 1.0 (up to Athena build 4706) * + * - Previous database system. * + * * + * @version 2.1 (Athena build #???#) - Portability fix * + * @author (Athena build 4859) Flavio @ Amazon Project * + * @author (up to Athena build 4706) Athena Dev Teams * + * @encoding US-ASCII * + * @see common#db.c * +\*****************************************************************************/ +#ifndef _DB_H_ +#define _DB_H_ + +#include + +/*****************************************************************************\ + * (1) Section with public typedefs, enums, unions, structures and defines. * + * DB_MANUAL_CAST_TO_UNION - Define when the compiler doesn't allow casting * + * to unions. * + * DBRelease - Enumeration of release options. * + * DBType - Enumeration of database types. * + * DBOptions - Bitfield enumeration of database options. * + * DBKey - Union of used key types. * + * DBApply - Format of functions applyed to the databases. * + * DBMatcher - Format of matchers used in DBInterface->getall. * + * DBComparator - Format of the comparators used by the databases. * + * DBHasher - Format of the hashers used by the databases. * + * DBReleaser - Format of the releasers used by the databases. * + * DBInterface - Structure of the interface of the database. * +\*****************************************************************************/ + +/** + * Define this to enable the functions that cast to unions. + * Required when the compiler doesn't support casting to unions. + * NOTE: It is recommened that the conditional tests to determine if this + * should be defined be located in a makefile or a header file specific for + * of compatibility and portability issues. + * @public + * @see #db_i2key(int) + * @see #db_ui2key(unsigned int) + * @see #db_str2key(unsigned char *) + */ +//#define DB_MANUAL_CAST_TO_UNION + +/** + * Bitfield with what should be released by the releaser function (if the + * function supports it). + * @public + * @see #DBReleaser + * @see #db_custom_release(DBRelease) + */ +typedef enum { + DB_RELEASE_NOTHING = 0, + DB_RELEASE_KEY = 1, + DB_RELEASE_DATA = 2, + DB_RELEASE_BOTH = 3 +} DBRelease; + +/** + * Supported types of database. + * See {@link #db_fix_options(DBType,DBOptions)} for restrictions of the + * types of databases. + * @param DB_INT Uses int's for keys + * @param DB_UINT Uses unsigned int's for keys + * @param DB_STRING Uses strings for keys. + * @param DB_ISTRING Uses case insensitive strings for keys. + * @public + * @see #DBOptions + * @see #DBKey + * @see #db_fix_options(DBType,DBOptions) + * @see #db_default_cmp(DBType) + * @see #db_default_hash(DBType) + * @see #db_default_release(DBType,DBOptions) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +typedef enum { + DB_INT, + DB_UINT, + DB_STRING, + DB_ISTRING +} DBType; + +/** + * Bitfield of options that define the behaviour of the database. + * See {@link #db_fix_options(DBType,DBOptions)} for restrictions of the + * types of databases. + * @param DB_OPT_BASE Base options: does not duplicate keys, releases nothing + * and does not allow NULL keys or NULL data. + * @param DB_OPT_DUP_KEY Duplicates the keys internally. If DB_OPT_RELEASE_KEY + * is defined, the real key is freed as soon as the entry is added. + * @param DB_OPT_RELEASE_KEY Releases the key. + * @param DB_OPT_RELEASE_DATA Releases the data whenever an entry is removed + * from the database. + * WARNING: for funtions that return the data (like DBInterface->remove), + * a dangling pointer will be returned. + * @param DB_OPT_RELEASE_BOTH Releases both key and data. + * @param DB_OPT_ALLOW_NULL_KEY Allow NULL keys in the database. + * @param DB_OPT_ALLOW_NULL_DATA Allow NULL data in the database. + * @public + * @see #db_fix_options(DBType,DBOptions) + * @see #db_default_release(DBType,DBOptions) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +typedef enum { + DB_OPT_BASE = 0, + DB_OPT_DUP_KEY = 1, + DB_OPT_RELEASE_KEY = 2, + DB_OPT_RELEASE_DATA = 4, + DB_OPT_RELEASE_BOTH = 6, + DB_OPT_ALLOW_NULL_KEY = 8, + DB_OPT_ALLOW_NULL_DATA = 16, +} DBOptions; + +/** + * Union of key types used by the database. + * @param i Type of key for DB_INT databases + * @param ui Type of key for DB_UINT databases + * @param str Type of key for DB_STRING and DB_ISTRING databases + * @public + * @see #DBType + * @see #DBApply(DBKey,void *,va_list) + * @see #DBMatcher(DBKey,void *,va_list) + * @see #DBComparator(DBKey,DBKey,unsigned short) + * @see #DBHasher(DBKey,unsigned short) + * @see #DBReleaser(DBKey,void *,DBRelease) + * @see DBInterface#get(DBInterface,DBKey) + * @see DBInterface#put(DBInterface,DBKey,void *) + * @see DBInterface#remove(DBInterface,DBKey) + */ +typedef union { + int i; + unsigned int ui; + unsigned char *str; +} DBKey; + +/** + * Format of funtions that create the data for the key when the entry doesn't + * exist in the database yet. + * @param key Key of the database entry + * @param args Extra arguments of the funtion + * @return Data identified by the key to be put in the database + * @public + * @see #DBKey + * @see DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list) + * @see DBInterface#ensure(DBInterface,DBKey,DBCreateData,...) + */ +typedef void *(*DBCreateData)(DBKey key, va_list args); + +/** + * Format of functions to be applyed to an unspecified quantity of entries of + * a database. + * Any function that applyes this function to the database will return the sum + * of values returned by this function. + * @param key Key of the database entry + * @param data Data of the database entry + * @param args Extra arguments of the funtion + * @return Value to be added up by the funtion that is applying this + * @public + * @see #DBKey + * @see DBInterface#vforeach(DBInterface,DBApply,va_list) + * @see DBInterface#foreach(DBInterface,DBApply,...) + * @see DBInterface#vdestroy(DBInterface,DBApply,va_list) + * @see DBInterface#destroy(DBInterface,DBApply,...) + */ +typedef int (*DBApply)(DBKey key, void *data, va_list args); + +/** + * Format of functions that match database entries. + * The purpose of the match depends on the function that is calling the matcher. + * Returns 0 if it is a match, another number otherwise. + * @param key Key of the database entry + * @param data Data of the database entry + * @param args Extra arguments of the function + * @return 0 if a match, another number otherwise + * @public + * @see #DBKey + * @see DBInterface#getall(DBInterface,void **,unsigned int,DBMatcher,...) + */ +typedef int (*DBMatcher)(DBKey key, void *data, va_list args); + +/** + * Format of the comparators used internally by the database system. + * Compares key1 to key2. + * maxlen is the maximum number of character used in DB_STRING and + * DB_ISTRING databases. If 0, the maximum number of maxlen is used (64K). + * Returns 0 is equal, negative if lower and positive is higher. + * @param key1 Key being compared + * @param key2 Key we are comparing to + * @param maxlen Maximum number of characters used in DB_STRING and DB_ISTRING + * databases. + * @return 0 if equal, negative if lower and positive if higher + * @public + * @see #DBKey + * @see #db_default_cmp(DBType) + */ +typedef int (*DBComparator)(DBKey key1, DBKey key2, unsigned short maxlen); + +/** + * Format of the hashers used internally by the database system. + * Creates the hash of the key. + * maxlen is the maximum number of character used in DB_STRING and + * DB_ISTRING databases. If 0, the maximum number of maxlen is used (64K). + * @param key Key being hashed + * @param maxlen Maximum number of characters used in DB_STRING and DB_ISTRING + * databases. + * @return Hash of the key + * @public + * @see #DBKey + * @see #db_default_hash(DBType) + */ +typedef unsigned int (*DBHasher)(DBKey key, unsigned short maxlen); + +/** + * Format of the releaser used by the database system. + * Releases nothing, the key, the data or both. + * All standard releasers use aFree to release. + * @param key Key of the database entry + * @param data Data of the database entry + * @param which What is being requested to be released + * @public + * @see #DBRelease + * @see #DBKey + * @see #db_default_releaser(DBType,DBOptions) + * @see #db_custom_release(DBRelease) + */ +typedef void (*DBReleaser)(DBKey key, void *data, DBRelease which); + +/** + * Public interface of a database. Only contains funtions. + * All the functions take the interface as the first argument. + * @public + * @see DBInterface#get(DBInterface,DBKey) + * @see DBInterface#getall(DBInterface,void **,unsigned int,DBMatch,...) + * @see DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list) + * @see DBInterface#put(DBInterface,DBKey,void *) + * @see DBInterface#remove(DBInterface,DBKey) + * @see DBInterface#foreach(DBInterface,DBApply,...) + * @see DBInterface#vforeach(DBInterface,DBApply,va_list) + * @see DBInterface#destroy(DBInterface,DBApply,...) + * @see DBInterface#destroy(DBInterface,DBApply,va_list) + * @see DBInterface#size(DBInterface) + * @see DBInterface#type(DBInterface) + * @see DBInterface#options(DBInterface) + * @see #db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +typedef struct dbt { + + /** + * Get the data of the entry identifid by the key. + * @param dbi Interface of the database + * @param key Key that identifies the entry + * @return Data of the entry or NULL if not found + * @protected + * @see #DBKey + * @see #DBInterface + * @see common\db.c#db_get(DBInterface,DBKey) + */ + void *(*get)(struct dbt *dbi, DBKey key); + + /** + * Just calls {@link DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list)}. + * Get the data of the entries matched by match. + * It puts a maximum of max entries into buf. + * If buf is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than max, only the + * first max entries found are put into the buffer. + * @param dbi Interface of the database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see #DBMatcher(DBKey key, void *data, va_list args) + * @see #DBInterface + * @see DBInterface#vgetall(DBInterface,void **,unsigned int,DBMatch,va_list) + * @see common\db.c#db_getall(DBInterface,void **,unsigned int,DBMatch,...) + */ + unsigned int (*getall)(struct dbt *dbi, void **buf, unsigned int max, DBMatcher match, ...); + + /** + * Get the data of the entries matched by match. + * It puts a maximum of max entries into buf. + * If buf is NULL, it only counts the matches. + * Returns the number of entries that matched. + * NOTE: if the value returned is greater than max, only the + * first max entries found are put into the buffer. + * @param dbi Interface of the database + * @param buf Buffer to put the data of the matched entries + * @param max Maximum number of data entries to be put into buf + * @param match Function that matches the database entries + * @param ... Extra arguments for match + * @return The number of entries that matched + * @protected + * @see #DBMatcher(DBKey key, void *data, va_list args) + * @see #DBInterface + * @see DBInterface#getall(DBInterface,void **,unsigned int,DBMatch,...) + * @see common\db.c#db_vgetall(DBInterface,void **,unsigned int,DBMatch,va_list) + */ + unsigned int (*vgetall)(struct dbt *dbi, void **buf, unsigned int max, DBMatcher match, va_list args); + + /** + * Just calls {@link common\db.h\DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list)}. + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * create. + * @param dbi Interface of the database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param ... Extra arguments for create + * @return Data of the entry + * @protected + * @see #DBKey + * @see #DBCreateData + * @see #DBInterface + * @see DBInterface#vensure(DBInterface,DBKey,DBCreateData,va_list) + * @see common\db.c#db_ensure(DBInterface,DBKey,DBCreateData,...) + */ + void *(*ensure)(struct dbt *dbi, DBKey key, DBCreateData create, ...); + + /** + * Get the data of the entry identified by the key. + * If the entry does not exist, an entry is added with the data returned by + * create. + * @param dbi Interface of the database + * @param key Key that identifies the entry + * @param create Function used to create the data if the entry doesn't exist + * @param args Extra arguments for create + * @return Data of the entry + * @protected + * @see #DBKey + * @see #DBCreateData + * @see #DBInterface + * @see DBInterface#ensure(DBInterface,DBKey,DBCreateData,...) + * @see common\db.c#db_vensure(DBInterface,DBKey,DBCreateData,va_list) + */ + void *(*vensure)(struct dbt *dbi, DBKey key, DBCreateData create, va_list args); + + /** + * Put the data identified by the key in the database. + * Returns the previous data if the entry exists or NULL. + * NOTE: Uses the new key, the old one is released. + * @param dbi Interface of the database + * @param key Key that identifies the data + * @param data Data to be put in the database + * @return The previous data if the entry exists or NULL + * @protected + * @see #DBKey + * @see #DBInterface + * @see common\db.c#db_put(DBInterface,DBKey,void *) + */ + void *(*put)(struct dbt *dbi, DBKey key, void *data); + + /** + * Remove an entry from the database. + * Returns the data of the entry. + * NOTE: The key (of the database) is released. + * @param dbi Interface of the database + * @param key Key that identifies the entry + * @return The data of the entry or NULL if not found + * @protected + * @see #DBKey + * @see #DBInterface + * @see common\db.c#db_remove(DBInterface,DBKey) + */ + void *(*remove)(struct dbt *dbi, DBKey key); + + /** + * Just calls {@link DBInterface#vforeach(DBInterface,DBApply,va_list)}. + * Apply func to every entry in the database. + * Returns the sum of values returned by func. + * @param dbi Interface of the database + * @param func Function to be applyed + * @param ... Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see #DBInterface + * @see #DBApply(DBKey,void *,va_list) + * @see DBInterface#vforeach(DBInterface,DBApply,va_list) + * @see common\db.c#db_foreach(DBInterface,DBApply,...) + */ + int (*foreach)(struct dbt *dbi, DBApply func, ...); + + /** + * Apply func to every entry in the database. + * Returns the sum of values returned by func. + * @param dbi Interface of the database + * @param func Function to be applyed + * @param args Extra arguments for func + * @return Sum of the values returned by func + * @protected + * @see #DBApply(DBKey,void *,va_list) + * @see #DBInterface + * @see DBInterface#foreach(DBInterface,DBApply,...) + * @see common\db.c#db_vforeach(DBInterface,DBApply,va_list) + */ + int (*vforeach)(struct dbt *dbi, DBApply func, va_list args); + + /** + * Just calls {@link DBInterface#vclear(DBInterface,DBApply,va_list)}. + * Removes all entries from the database. + * Before deleting an entry, func is applyed to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * @param dbi Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see #DBApply(DBKey,void *,va_list) + * @see #DBInterface + * @see DBInterface#vclear(DBInterface,DBApply,va_list) + * @see common\db.c#db_clear(DBInterface,DBApply,...) + */ + int (*clear)(struct dbt *dbi, DBApply func, ...); + + /** + * Removes all entries from the database. + * Before deleting an entry, func is applyed to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * @param dbi Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see #DBApply(DBKey,void *,va_list) + * @see #DBInterface + * @see DBInterface#clear(DBInterface,DBApply,...) + * @see common\db.c#vclear(DBInterface,DBApply,va_list) + */ + int (*vclear)(struct dbt *dbi, DBApply func, va_list args); + + /** + * Just calls {@link DBInterface#vdestroy(DBInterface,DBApply,va_list)}. + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applyed to it. + * Releases the key and the data. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param dbi Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param ... Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see #DBApply(DBKey,void *,va_list) + * @see #DBInterface + * @see DBInterface#vdestroy(DBInterface,DBApply,va_list) + * @see common\db.c#db_destroy(DBInterface,DBApply,...) + */ + int (*destroy)(struct dbt *dbi, DBApply func, ...); + + /** + * Finalize the database, feeing all the memory it uses. + * Before deleting an entry, func is applyed to it. + * Returns the sum of values returned by func, if it exists. + * NOTE: This locks the database globally. Any attempt to insert or remove + * a database entry will give an error and be aborted (except for clearing). + * @param dbi Interface of the database + * @param func Function to be applyed to every entry before deleting + * @param args Extra arguments for func + * @return Sum of values returned by func + * @protected + * @see #DBInterface + * @see #DBApply(DBKey,void *,va_list) + * @see DBInterface#destroy(DBInterface,DBApply,...) + * @see common\db.c#db_vdestroy(DBInterface,DBApply,va_list) + */ + int (*vdestroy)(struct dbt *dbi, DBApply func, va_list args); + + /** + * Return the size of the database (number of items in the database). + * @param dbi Interface of the database + * @return Size of the database + * @protected + * @see #DBInterface + * @see common\db.c#db_size(DBInterface) + */ + unsigned int (*size)(struct dbt *dbi); + + /** + * Return the type of the database. + * @param dbi Interface of the database + * @return Type of the database + * @protected + * @see #DBType + * @see #DBInterface + * @see common\db.c#db_type(DBInterface) + */ + DBType (*type)(struct dbt *dbi); + + /** + * Return the options of the database. + * @param dbi Interface of the database + * @return Options of the database + * @protected + * @see #DBOptions + * @see #DBInterface + * @see common\db.c#db_options(DBInterface) + */ + DBOptions (*options)(struct dbt *dbi); + +} *DBInterface; + +//For easy access to the common functions. +#ifdef DB_MANUAL_CAST_TO_UNION +# define i2key db_i2key +# define ui2key db_ui2key +# define str2key db_str2key +#else /* not DB_MANUAL_CAST_TO_UNION */ +# define i2key(k) ((DBKey)(int)(k)) +# define ui2key(k) ((DBKey)(unsigned int)(k)) +# define str2key(k) ((DBKey)(unsigned char *)(k)) +#endif /* DB_MANUAL_CAST_TO_UNION / not DB_MANUAL_CAST_TO_UNION */ + +#define db_get(db,k) (db)->get((db),(k)) +#define idb_get(db,k) (db)->get((db),i2key(k)) +#define uidb_get(db,k) (db)->get((db),ui2key(k)) +#define strdb_get(db,k) (db)->get((db),str2key(k)) + +#define db_put(db,k,d) (db)->put((db),(k),(d)) +#define idb_put(db,k,d) (db)->put((db),i2key(k),(d)) +#define uidb_put(db,k,d) (db)->put((db),ui2key(k),(d)) +#define strdb_put(db,k,d) (db)->put((db),str2key(k),(d)) + +#define db_remove(db,k) (db)->remove((db),(k)) +#define idb_remove(db,k) (db)->remove((db),i2key(k)) +#define uidb_remove(db,k) (db)->remove((db),ui2key(k)) +#define strdb_remove(db,k) (db)->remove((db),str2key(k)) + +//These are discarding the possible vargs you could send to the function, so those +//that require vargs must not use these defines. +#define db_ensure(db,k,f) (db)->ensure((db),(k),f) +#define idb_ensure(db,k,f) (db)->ensure((db),i2key(k),f) +#define uidb_ensure(db,k,f) (db)->ensure((db),ui2key(k),f) +#define strdb_ensure(db,k,f) (db)->ensure((db),str2key(k),f) + +/*****************************************************************************\ + * (2) Section with public functions. * + * db_fix_options - Fix the options for a type of database. * + * db_default_cmp - Get the default comparator for a type of database. * + * db_default_hash - Get the default hasher for a type of database. * + * db_default_release - Get the default releaser for a type of database * + * with the fixed options. * + * db_custom_release - Get the releaser that behaves as specified. * + * db_alloc - Allocate a new database. * + * db_i2key - Manual cast from 'int' to 'DBKey'. * + * db_ui2key - Manual cast from 'unsigned int' to 'DBKey'. * + * db_str2key - Manual cast from 'unsigned char *' to 'DBKey'. * + * db_init - Initialise the database system. * + * db_final - Finalise the database system. * +\*****************************************************************************/ + +/** + * Returns the fixed options according to the database type. + * Sets required options and unsets unsupported options. + * For numeric databases DB_OPT_DUP_KEY and DB_OPT_RELEASE_KEY are unset. + * @param type Type of the database + * @param options Original options of the database + * @return Fixed options of the database + * @private + * @see #DBType + * @see #DBOptions + * @see #db_default_release(DBType,DBOptions) + * @see common\db.c#db_fix_options(DBType,DBOptions) + */ +DBOptions db_fix_options(DBType type, DBOptions options); + +/** + * Returns the default comparator for the type of database. + * @param type Type of database + * @return Comparator for the type of database or NULL if unknown database + * @public + * @see #DBType + * @see #DBComparator + * @see common\db.c#db_default_cmp(DBType) + */ +DBComparator db_default_cmp(DBType type); + +/** + * Returns the default hasher for the specified type of database. + * @param type Type of database + * @return Hasher of the type of database or NULL if unknown database + * @public + * @see #DBType + * @see #DBHasher + * @see common\db.c#db_default_hash(DBType) + */ +DBHasher db_default_hash(DBType type); + +/** + * Returns the default releaser for the specified type of database with the + * specified options. + * NOTE: the options are fixed by {@link #db_fix_options(DBType,DBOptions)} + * before choosing the releaser + * @param type Type of database + * @param options Options of the database + * @return Default releaser for the type of database with the fixed options + * @public + * @see #DBType + * @see #DBOptions + * @see #DBReleaser + * @see #db_fix_options(DBType,DBOptions) + * @see #db_custom_release(DBRelease) + * @see common\db.c#db_default_release(DBType,DBOptions) + */ +DBReleaser db_default_release(DBType type, DBOptions options); + +/** + * Returns the releaser that behaves as which specifies. + * @param which Defines what the releaser releases + * @return Releaser for the specified release options + * @public + * @see #DBRelease + * @see #DBReleaser + * @see #db_default_release(DBType,DBOptions) + * @see common\db.c#db_custom_release(DBRelease) + */ +DBReleaser db_custom_release(DBRelease which); + +/** + * Allocate a new database of the specified type. + * It uses the default comparator, hasher and releaser of the specified + * database type and fixed options. + * NOTE: the options are fixed by {@link #db_fix_options(DBType,DBOptions)} + * before creating the database. + * @param file File where the database is being allocated + * @param line Line of the file where the database is being allocated + * @param type Type of database + * @param options Options of the database + * @param maxlen Maximum length of the string to be used as key in string + * databases + * @return The interface of the database + * @public + * @see #DBType + * @see #DBInterface + * @see #db_default_cmp(DBType) + * @see #db_default_hash(DBType) + * @see #db_default_release(DBType,DBOptions) + * @see #db_fix_options(DBType,DBOptions) + * @see common\db.c#db_alloc(const char *,int,DBType,DBOptions,unsigned short) + */ +DBInterface db_alloc(const char *file, int line, DBType type, DBOptions options, unsigned short maxlen); + +#ifdef DB_MANUAL_CAST_TO_UNION +/** + * Manual cast from 'int' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see #DB_MANUAL_CAST_TO_UNION + * @see #db_ui2key(unsigned int) + * @see #db_str2key(unsigned char *) + * @see common\db.c#db_i2key(int) + */ +DBKey db_i2key(int key); + +/** + * Manual cast from 'unsigned int' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see #DB_MANUAL_CAST_TO_UNION + * @see #db_i2key(int) + * @see #db_str2key(unsigned char *) + * @see common\db.c#db_ui2key(unsigned int) + */ +DBKey db_ui2key(unsigned int key); + +/** + * Manual cast from 'unsigned char *' to the union DBKey. + * Created for compilers that don't support casting to unions. + * @param key Key to be casted + * @return The key as a DBKey union + * @public + * @see #DB_MANUAL_CAST_TO_UNION + * @see #db_i2key(int) + * @see #db_ui2key(unsigned int) + * @see common\db.c#db_str2key(unsigned char *) + */ +DBKey db_str2key(unsigned char *key); +#endif /* DB_MANUAL_CAST_TO_UNION */ + +/** + * Initialize the database system. + * @public + * @see #db_final(void) + * @see common\db.c#db_init(void) + */ +void db_init(void); + +/** + * Finalize the database system. + * Frees the memory used by the block reusage system. + * @public + * @see #db_init(void) + * @see common\db.c#db_final(void) + */ +void db_final(void); + +#endif diff --git a/src/common/ers.c b/src/common/ers.c new file mode 100644 index 000000000..b54d22977 --- /dev/null +++ b/src/common/ers.c @@ -0,0 +1,532 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + *

Entry Reusage System

* + * * + * There are several root entry managers, each with a different entry size. * + * Each manager will keep track of how many instances have been 'created'. * + * They will only automatically destroy themselves after the last instance * + * is destroyed. * + * * + * Entries can be allocated from the managers. * + * If it has reusable entries (freed entry), it uses one. * + * So no assumption should be made about the data of the entry. * + * Entries should be freed in the manager they where allocated from. * + * Failure to do so can lead to unexpected behaviours. * + * * + *

Advantages:

* + * - The same manager is used for entries of the same size. * + * So entries freed in one instance of the manager can be used by other * + * instances of the manager. * + * - Much less memory allocation/deallocation - program will be faster. * + * - Avoids memory fragmentaion - program will run better for longer. * + * * + *

Disavantages:

* + * - Unused entries are almost inevitable - memory being wasted. * + * - A manager will only auto-destroy when all of its instances are * + * destroyed so memory will usually only be recovered near the end. * + * - Always wastes space for entries smaller than a pointer. * + * * + * WARNING: The system is not thread-safe at the moment. * + * * + * HISTORY: * + * 0.1 - Initial version * + * * + * @version 0.1 - Initial version * + * @author Flavio @ Amazon Project * + * @encoding US-ASCII * + * @see common#ers.h * +\*****************************************************************************/ +#include + +#include "ers.h" +#include "../common/malloc.h" // CREATE, RECREATE, aMalloc, aFree +#include "../common/showmsg.h" // ShowMessage, ShowError, ShowFatalError, CL_BOLD, CL_NORMAL + +#ifndef DISABLE_ERS +/*****************************************************************************\ + * (1) Private defines, structures and global variables. * + * ERS_BLOCK_ENTRIES - Number of entries in each block. * + * ERS_ROOT_SIZE - Maximum number of root entry managers. * + * ERLinkedList - Structure of a linked list of reusable entries. * + * ERSystem - Class of an entry manager. * + * ers_root - Array of root entry managers. * + * ers_num - Number of root entry managers in the array. * +\*****************************************************************************/ + +/** + * Number of entries in each block. + * @private + * @see #ers_obj_alloc_entry(ERInterface eri) + */ +#define ERS_BLOCK_ENTRIES 4096 + +/** + * Maximum number of root entry managers. + * @private + * @see #ers_root + * @see #ers_num + */ +#define ERS_ROOT_SIZE 256 + +/** + * Linked list of reusable entries. + * The minimum size of the entries is the size of this structure. + * @private + * @see ERSystem#reuse + */ +typedef struct ers_ll { + struct ers_ll *next; +} *ERLinkedList; + +/** + * Class of the object that manages entries of a certain size. + * @param eri Public interface of the object + * @param reuse Linked list of reusable data entries + * @param blocks Array with blocks of entries + * @param free Number of unused entries in the last block + * @param num Number of blocks in the array + * @param max Current maximum capacity of the array + * @param destroy Destroy lock + * @param size Size of the entries of the manager + * @private + */ +typedef struct ers { + + /** + * Public interface of the entry manager. + * @param alloc Allocate an entry from this manager + * @param free Free an entry allocated from this manager + * @param entry_size Return the size of the entries of this manager + * @param destroy Destroy this instance of the manager + * @public + * @see #ERSystem + * @see common\ers.h#ERInterface + */ + struct eri eri; + + /** + * Linked list of reusable entries. + * @private + * @see #ERSystem + */ + ERLinkedList reuse; + + /** + * Array with blocks of entries. + * @private + * @see #ERSystem + */ + uint8 **blocks; + + /** + * Number of unused entries in the last block. + * @private + * @see #ERSystem + */ + uint32 free; + + /** + * Number of blocks in the array. + * @private + * @see #ERSystem + */ + uint32 num; + + /** + * Current maximum capacity of the array. + * @private + * @see #ERSystem + */ + uint32 max; + + /** + * Destroy lock. + * @private + * @see #ERSystem + */ + uint32 destroy; + + /** + * Size of the entries of the manager. + * @private + * @see #ERSystem + */ + uint32 size; + +} *ERSystem; + +/** + * Root array with entry managers. + * @private + * @static + * @see #ERS_ROOT_SIZE + * @see #ers_num + */ +static ERSystem ers_root[ERS_ROOT_SIZE]; + +/** + * Number of entry managers in the root array. + * @private + * @static + * @see #ERS_ROOT_SIZE + * @see #ers_root + */ +static uint32 ers_num = 0; + +/*****************************************************************************\ + * (2) Protected functions. * + * ers_obj_alloc_entry - Allocate an entry from the manager. * + * ers_obj_free_entry - Free an entry allocated from the manager. * + * ers_obj_entry_size - Return the size of the entries of the manager. * + * ers_obj_destroy - Destroy the instance of the manager. * +\*****************************************************************************/ + +/** + * Allocate an entry from this entry manager. + * If there are reusable entries available, it reuses one instead. + * @param self Interface of the entry manager + * @return An entry + * @protected + * @see #ERS_BLOCK_ENTRIES + * @see #ERLinkedList + * @see #ERSystem + * @see common\ers.h\ERInterface#alloc(ERInterface) + */ +static void *ers_obj_alloc_entry(ERInterface self) +{ + ERSystem obj = (ERSystem)self; + void *ret; + + if (obj == NULL) { + ShowError("ers_obj_alloc_entry: NULL object, aborting entry allocation.\n"); + return NULL; + } + + if (obj->reuse) { // Reusable entry + ret = obj->reuse; + obj->reuse = obj->reuse->next; + } else if (obj->free) { // Unused entry + obj->free--; + ret = &obj->blocks[obj->num -1][obj->free*obj->size]; + } else { // allocate a new block + if (obj->num == obj->max) { // expand the block array + if (obj->max == UINT32_MAX) { // No more space for blocks + ShowFatalError("ers_obj_alloc_entry: maximum number of blocks reached, increase ERS_BLOCK_ENTRIES.\n" + "exiting the program...\n"); + exit(EXIT_FAILURE); + } + obj->max = (obj->max<<2) +3; // = obj->max*4 +3; - overflow won't happen + RECREATE(obj->blocks, uint8 *, obj->max); + } + CREATE(obj->blocks[obj->num], uint8, obj->size*ERS_BLOCK_ENTRIES); + obj->free = ERS_BLOCK_ENTRIES -1; + ret = &obj->blocks[obj->num][obj->free*obj->size]; + obj->num++; + } + return ret; +} + +/** + * Free an entry allocated from this manager. + * WARNING: Does not check if the entry was allocated by this manager. + * Freeing such an entry can lead to unexpected behaviour. + * @param self Interface of the entry manager + * @param entry Entry to be freed + * @protected + * @see #ERLinkedList + * @see #ERSystem + * @see ERSystem#reuse + * @see common\ers.h\ERInterface#free(ERInterface,void *) + */ +static void ers_obj_free_entry(ERInterface self, void *entry) +{ + ERSystem obj = (ERSystem)self; + ERLinkedList reuse; + + if (obj == NULL) { + ShowError("ers_obj_free_entry: NULL object, aborting entry freeing.\n"); + return; + } else if (entry == NULL) { + ShowError("ers_obj_free_entry: NULL entry, nothing to free.\n"); + return; + } + + reuse = (ERLinkedList)entry; + reuse->next = obj->reuse; + obj->reuse = reuse; +} + +/** + * Return the size of the entries allocated from this manager. + * @param self Interface of the entry manager + * @return Size of the entries of this manager in bytes + * @protected + * @see #ERSystem + * @see ERSystem#size + * @see common\ers.h\ERInterface#enty_size(ERInterface) + */ +static uint32 ers_obj_entry_size(ERInterface self) +{ + ERSystem obj = (ERSystem)self; + + if (obj == NULL) { + ShowError("ers_obj_entry_size: NULL object, returning 0.\n"); + return 0; + } + + return obj->size; +} + +/** + * Destroy this instance of the manager. + * The manager is actually only destroyed when all the instances are destroyed. + * When destroying the manager a warning is shown if the manager has + * missing/extra entries. + * @param self Interface of the entry manager + * @protected + * @see #ERLinkedList + * @see #ERSystem + * @see common\ers.h\ERInterface#destroy(ERInterface) + */ +static void ers_obj_destroy(ERInterface self) +{ + ERSystem obj = (ERSystem)self; + ERLinkedList reuse; + uint32 i, count; + + if (obj == NULL) { + ShowError("ers_obj_destroy: NULL object, aborting instance destruction.\n"); + return; + } + + obj->destroy--; + if (obj->destroy) + return; // Not last instance + + // Remove manager from root array + for (i = 0; i < ers_num; i++) { + if (ers_root[i] == obj) { + ers_num--; + if (i < ers_num) // put the last manager in the free slot + ers_root[i] = ers_root[ers_num]; + break; + } + } + reuse = obj->reuse; + count = 0; + // Check for missing/extra entries + for (i = 0; i < obj->num; i++) { + if (i == 0) { + count = ERS_BLOCK_ENTRIES -obj->free; + } else if (count > UINT32_MAX -ERS_BLOCK_ENTRIES) { + count = UINT32_MAX; + break; + } else { + count += ERS_BLOCK_ENTRIES; + } + while (reuse && count) { + count--; + reuse = reuse->next; + } + } + if (count) { // missing entries + ShowWarning("ers_obj_destroy: %u entries missing, continuing destruction.\n" + "Manager for entries of size %u.\n", + count, obj->size); + } else if (reuse) { // extra entries + while (reuse && count != UINT32_MAX) { + count++; + reuse = reuse->next; + } + ShowWarning("ers_obj_destroy: %u extra entries found, continuing destruction.\n" + "Manager for entries of size %u.\n", + count, obj->size); + } + // destroy the entry manager + if (obj->max) { + for (i = 0; i < obj->num; i++) + aFree(obj->blocks[i]); // release block of entries + aFree(obj->blocks); // release array of blocks + } + aFree(obj); // release manager +} + +/*****************************************************************************\ + * (3) Public functions. * + * ers_new - Get a new instance of an entry manager. * + * ers_report - Print a report about the current state. * + * ers_force_destroy_all - Force the destruction of all the managers. * +\*****************************************************************************/ + +/** + * Get a new instance of the manager that handles the specified entry size. + * Size has to greater than 0. + * If the specified size is smaller than a pointer, the size of a pointer is + * used instead. + * It's also aligned to ERS_ALIGNED bytes, so the smallest multiple of + * ERS_ALIGNED that is greater or equal to size is what's actually used. + * @param The requested size of the entry in bytes + * @return Interface of the object + * @public + * @see #ERSystem + * @see #ers_root + * @see #ers_num + * @see common\ers.h#ERInterface + * @see common\ers.h\ERInterface#destroy(ERInterface) + * @see common\ers.h#ers_new_(uint32) + */ +ERInterface ers_new(uint32 size) +{ + ERSystem obj; + uint32 i; + + if (size == 0) { + ShowError("ers_new: invalid size %u, aborting instance creation.\n", + size); + return NULL; + } + + if (size < sizeof(struct ers_ll)) // Minimum size + size = sizeof(struct ers_ll); + if (size%ERS_ALIGNED) // Align size + size += ERS_ALIGNED -size%ERS_ALIGNED; + + for (i = 0; i < ers_num; i++) { + obj = ers_root[i]; + if (obj->size == size) { + // found a manager that handles the entry size + obj->destroy++; + return &obj->eri; + } + } + // create a new manager to handle the entry size + if (ers_num == ERS_ROOT_SIZE) { + ShowFatalError("ers_alloc: too many root objects, increase ERS_ROOT_SIZE.\n" + "exiting the program...\n"); + exit(EXIT_FAILURE); + } + obj = (ERSystem)aMalloc(sizeof(struct ers)); + // Public interface + obj->eri.alloc = ers_obj_alloc_entry; + obj->eri.free = ers_obj_free_entry; + obj->eri.entry_size = ers_obj_entry_size; + obj->eri.destroy = ers_obj_destroy; + // Block reusage system + obj->reuse = NULL; + obj->blocks = NULL; + obj->free = 0; + obj->num = 0; + obj->max = 0; + obj->destroy = 1; + // Properties + obj->size = size; + ers_root[ers_num++] = obj; + return &obj->eri; +} + +/** + * Print a report about the current state of the Entry Reusage System. + * Shows information about the global system and each entry manager. + * The number of entries are checked and a warning is shown if extra reusable + * entries are found. + * The extra entries are included in the count of reusable entries. + * @public + * @see #ERLinkedList + * @see #ERSystem + * @see #ers_root + * @see #ers_num + * @see common\ers.h#ers_report(void) + */ +void ers_report(void) +{ + uint32 i, j, used, reusable, extra; + ERLinkedList reuse; + ERSystem obj; + + // Root system report + ShowMessage(CL_BOLD"Entry Reusage System report:\n"CL_NORMAL); + ShowMessage("root array size : %u\n", ERS_ROOT_SIZE); + ShowMessage("root entry managers : %u\n", ers_num); + ShowMessage("entries per block : %u\n", ERS_BLOCK_ENTRIES); + for (i = 0; i < ers_num; i++) { + obj = ers_root[i]; + reuse = obj->reuse; + used = 0; + reusable = 0; + // Count used and reusable entries + for (j = 0; j < obj->num; j++) { + if (j == 0) { // take into acount the free entries + used = ERS_BLOCK_ENTRIES -obj->free; + } else if (reuse) { // counting reusable entries + used = ERS_BLOCK_ENTRIES; + } else { // no more reusable entries, count remaining used entries + for (; j < obj->num; j++) { + if (used > UINT32_MAX -ERS_BLOCK_ENTRIES) { // overflow + used = UINT32_MAX; + break; + } + used += ERS_BLOCK_ENTRIES; + } + break; + } + while (used && reuse) { // count reusable entries + used--; + if (reusable != UINT32_MAX) + reusable++; + reuse = reuse->next; + } + } + // Count extra reusable entries + extra = 0; + while (reuse && extra != UINT32_MAX) { + extra++; + reuse = reuse->next; + } + // Entry manager report + ShowMessage(CL_BOLD"[Entry manager #%u report]\n"CL_NORMAL, i); + ShowMessage("\tinstances : %u\n", obj->destroy); + ShowMessage("\tentry size : %u\n", obj->size); + ShowMessage("\tblock array size : %u\n", obj->max); + ShowMessage("\tallocated blocks : %u\n", obj->num); + ShowMessage("\tentries being used : %u\n", used); + ShowMessage("\tunused entries : %u\n", obj->free); + ShowMessage("\treusable entries : %u\n", reusable); + if (extra) + ShowMessage("\tWARNING - %u extra reusable entries were found.\n", extra); + } + ShowMessage("End of report\n"); +} + +/** + * Forcibly destroy all the entry managers, checking for nothing. + * The system is left as if no instances or entries had ever been allocated. + * All previous entries and instances of the managers become invalid. + * The use of this is NOT recommended. + * It should only be used in extreme situations to make shure all the memory + * allocated by this system is released. + * @public + * @see #ERSystem + * @see #ers_root + * @see #ers_num + * @see common\ers.h#ers_force_destroy_all(void) + */ +void ers_force_destroy_all(void) +{ + uint32 i, j; + ERSystem obj; + + for (i = 0; i < ers_num; i++) { + obj = ers_root[i]; + if (obj->max) { + for (j = 0; j < obj->num; j++) + aFree(obj->blocks[j]); // block of entries + aFree(obj->blocks); // array of blocks + } + aFree(obj); // entry manager object + } + ers_num = 0; +} +#endif /* not DISABLE_ERS */ + diff --git a/src/common/ers.h b/src/common/ers.h new file mode 100644 index 000000000..a512f6365 --- /dev/null +++ b/src/common/ers.h @@ -0,0 +1,193 @@ +/*****************************************************************************\ + * Copyright (c) Athena Dev Teams - Licensed under GNU GPL * + * For more information, see LICENCE in the main folder * + * * + *

Entry Reusage System

* + * * + * There are several root entry managers, each with a different entry size. * + * Each manager will keep track of how many instances have been 'created'. * + * They will only automatically destroy themselves after the last instance * + * is destroyed. * + * * + * Entries can be allocated from the managers. * + * If it has reusable entries (freed entry), it uses one. * + * So no assumption should be made about the data of the entry. * + * Entries should be freed in the manager they where allocated from. * + * Failure to do so can lead to unexpected behaviours. * + * * + *

Advantages:

* + * - The same manager is used for entries of the same size. * + * So entries freed in one instance of the manager can be used by other * + * instances of the manager. * + * - Much less memory allocation/deallocation - program will be faster. * + * - Avoids memory fragmentaion - program will run better for longer. * + * * + *

Disavantages:

* + * - Unused entries are almost inevitable - memory being wasted. * + * - A manager will only auto-destroy when all of its instances are * + * destroyed so memory will usually only be recovered near the end. * + * - Always wastes space for entries smaller than a pointer. * + * * + * WARNING: The system is not thread-safe at the moment. * + * * + * HISTORY: * + * 0.1 - Initial version * + * * + * @version 0.1 - Initial version * + * @author Flavio @ Amazon Project * + * @encoding US-ASCII * + * @see common#ers.c * +\*****************************************************************************/ +#ifndef _ERS_H_ +#define _ERS_H_ + +#include "../common/cbasetypes.h" + +/*****************************************************************************\ + * (1) All public parts of the Entry Reusage System. * + * DISABLE_ERS - Define to disable this system. * + * ERS_ALIGNED - Alignment of the entries in the blocks. * + * ERInterface - Interface of the entry manager. * + * ers_new - Allocate an instance of an entry manager. * + * ers_report - Print a report about the current state. * + * ers_force_destroy_all - Force the destruction of all the managers. * +\*****************************************************************************/ + +/** + * Define this to disable the Entry Reusage System. + * All code except the typedef of ERInterface will be disabled. + * To allow a smooth transition, + * @public + */ +//#define DISABLE_ERS + +/** + * Entries are aligned to ERS_ALIGNED bytes in the blocks of entries. + * By default it aligns to one byte, using the "natural order" of the entries. + * This should NEVER be set to zero or less. + * If greater than one, some memory can be wasted. This should never be needed + * but is here just in case some aligment issues arise. + * @public + * @see #ers_new(uint32) + */ +#ifndef ERS_ALIGNED +# define ERS_ALIGNED 1 +#endif /* not ERS_ALIGN_ENTRY */ + +/** + * Public interface of the entry manager. + * @param alloc Allocate an entry from this manager + * @param free Free an entry allocated from this manager + * @param entry_size Return the size of the entries of this manager + * @param destroy Destroy this instance of the manager + * @public + * @see #ers_new(uint32) + */ +typedef struct eri { + + /** + * Allocate an entry from this entry manager. + * If there are reusable entries available, it reuses one instead. + * @param self Interface of the entry manager + * @return An entry + * @protected + * @see #ERInterface + * @see ERInterface#free(ERInterface,void *) + */ + void *(*alloc)(struct eri *self); + + /** + * Free an entry allocated from this manager. + * WARNING: Does not check if the entry was allocated by this manager. + * Freeing such an entry can lead to unexpected behaviour. + * @param self Interface of the entry manager + * @param entry Entry to be freed + * @protected + * @see #ERInterface + * @see ERInterface#alloc(ERInterface) + */ + void (*free)(struct eri *self, void *entry); + + /** + * Return the size of the entries allocated from this manager. + * @param self Interface of the entry manager + * @return Size of the entries of this manager in bytes + * @protected + * @see #ERInterface + */ + uint32 (*entry_size)(struct eri *self); + + /** + * Destroy this instance of the manager. + * The manager is actually only destroyed when all the instances are destroyed. + * When destroying the manager a warning is shown if the manager has + * missing/extra entries. + * @param self Interface of the entry manager + * @protected + * @see #ERInterface + * @see #ers_new(uint32) + */ + void (*destroy)(struct eri *self); + +} *ERInterface; + +#ifdef DISABLE_ERS +// Use memory manager to allocate/free and disable other interface functions +# define ers_alloc(obj,type) (type *)aMalloc(sizeof(type)) +# define ers_free(obj,entry) aFree(entry) +# define ers_entry_size(obj) (uint32)0 +# define ers_destroy(obj) +// Disable the public functions +# define ers_new(size) NULL +# define ers_report() +# define ers_force_destroy_all() +#else /* not DISABLE_ERS */ +// These defines should be used to allow the code to keep working whenever +// the system is disabled +# define ers_alloc(obj,type) (type *)(obj)->alloc(obj) +# define ers_free(obj,entry) (obj)->free((obj),(entry)) +# define ers_entry_size(obj) (obj)->entry_size(obj) +# define ers_destroy(obj) (obj)->destroy(obj) + +/** + * Get a new instance of the manager that handles the specified entry size. + * Size has to greater than 0. + * If the specified size is smaller than a pointer, the size of a pointer is + * used instead. + * It's also aligned to ERS_ALIGNED bytes, so the smallest multiple of + * ERS_ALIGNED that is greater or equal to size is what's actually used. + * @param The requested size of the entry in bytes + * @return Interface of the object + * @public + * @see #ERS_ALIGNED + * @see #ERInterface + * @see ERInterface#destroy(ERInterface) + * @see common\ers.c#ers_new(uint32) + */ +ERInterface ers_new(uint32 size); + +/** + * Print a report about the current state of the Entry Reusage System. + * Shows information about the global system and each entry manager. + * The number of entries are checked and a warning is shown if extra reusable + * entries are found. + * The extra entries are included in the count of reusable entries. + * @public + * @see common\ers.c#ers_report(void) + */ +void ers_report(void); + +/** + * Forcibly destroy all the entry managers, checking for nothing. + * The system is left as if no instances or entries had ever been allocated. + * All previous entries and instances of the managers become invalid. + * The use of this is NOT recommended. + * It should only be used in extreme situations to make shure all the memory + * allocated by this system is released. + * @public + * @see common\ers.c#ers_force_destroy_all(void) + */ +void ers_force_destroy_all(void); +#endif /* DISABLE_ERS / not DISABLE_ERS */ + +#endif /* _ERS_H_ */ diff --git a/src/common/graph.c b/src/common/graph.c new file mode 100644 index 000000000..a68d39ce0 --- /dev/null +++ b/src/common/graph.c @@ -0,0 +1,318 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// graph creation is enabled +#define ENABLE_GRAPH + +#ifdef ENABLE_GRAPH + +#include +#include +#include +#ifndef _WIN32 + #include +#endif +#ifdef MINGW + #include +#endif + +#include "../common/core.h" +#include "../common/timer.h" +#include "../common/grfio.h" +#include "../common/malloc.h" +#include "graph.h" + +struct graph { + int width; + int height; + int pallet_count; + int png_len; + int png_dirty; + unsigned char* raw_data; + unsigned char* png_data; + int * graph_value; + int graph_max; +}; + +void graph_write_dword(unsigned char* p,unsigned int v) { + p[0] = (unsigned char)((v >> 24) & 0xFF); + p[1] = (unsigned char)((v >> 16) & 0xFF); + p[2] = (unsigned char)((v >> 8) & 0xFF); + p[3] = (unsigned char)(v & 0xFF); +} + +struct graph* graph_create(unsigned int x,unsigned int y) { + struct graph *g = (struct graph*)aCalloc(sizeof(struct graph),1); + if(g == NULL) return NULL; + // 256 * 3 : パレットデータ + // x * y * 2 : イメージのバッファ + // 256 : チャンクデータなどの予備 + g->png_data = (unsigned char *) aMalloc(4 * 256 + (x + 1) * y * 2); + g->raw_data = (unsigned char *) aCalloc( (x + 1) * y , 1); + memcpy( + g->png_data, + "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x08\x03\x00\x00\x00\xFF\xFF\xFF" + "\xFF\x00\x00\x00\x03\x50\x4C\x54\x45\xFF\xFF\xFF\xA7\xC4\x1B\xC8",0x30 + ); + graph_write_dword(g->png_data + 0x10,x); + graph_write_dword(g->png_data + 0x14,y); + graph_write_dword(g->png_data + 0x1D,grfio_crc32(g->png_data+0x0C,0x11)); + g->pallet_count = 1; + g->width = x; + g->height = y; + g->png_dirty = 1; + g->graph_value = (int *) aCalloc(x,sizeof(int)); + g->graph_max = 1; + return g; +} + +void graph_pallet(struct graph* g, int index,unsigned long c) { + if(g == NULL || c >= 256) return; + + if(g->pallet_count <= index) { + memset(g->png_data + 0x29 + 3 * g->pallet_count,0,(index - g->pallet_count) * 3); + g->pallet_count = index + 1; + } + g->png_data[0x29 + index * 3 ] = (unsigned char)((c >> 16) & 0xFF); // R + g->png_data[0x29 + index * 3 + 1] = (unsigned char)((c >> 8) & 0xFF); // G + g->png_data[0x29 + index * 3 + 2] = (unsigned char)( c & 0xFF); // B + graph_write_dword(g->png_data + 0x21,g->pallet_count * 3); + graph_write_dword( + g->png_data + 0x29 + g->pallet_count * 3, + grfio_crc32(g->png_data + 0x25,g->pallet_count * 3 + 4) + ); + g->png_dirty = 1; +} + +void graph_setpixel(struct graph* g,int x,int y,int color) { + if(g == NULL || color >= 256) { return; } + if(x < 0) x = 0; + if(y < 0) y = 0; + if(x >= g->width) { x = g->width - 1; } + if(y >= g->height) { y = g->height - 1; } + if(color >= g->pallet_count) { graph_pallet(g,color,graph_rgb(0,0,0)); } + + g->raw_data[y * (g->width + 1) + x + 1] = (unsigned char)color; + g->png_dirty = 1; +} + +int graph_getpixel(struct graph* g,int x,int y) { + if(x < 0) x = 0; + if(y < 0) y = 0; + if(x >= g->width) { x = g->width - 1; } + if(y >= g->height) { y = g->height - 1; } + return g->raw_data[y * (g->width + 1) + x + 1]; +} + +const unsigned char* graph_output(struct graph* g,int *len) { + unsigned long inflate_len; + unsigned char *p; + + if(g == NULL) return NULL; + if(g->png_dirty == 0) { + *len = g->png_len; + return g->png_data; + } + + p = g->png_data + 0x2D + 3 * g->pallet_count; + inflate_len = 2 * (g->width + 1) * g->height; + memcpy(p + 4,"IDAT",4); + encode_zip(p + 8,&inflate_len,g->raw_data,(g->width + 1) * g->height); + graph_write_dword(p,inflate_len); + graph_write_dword(p + 8 + inflate_len,grfio_crc32(p + 4, inflate_len + 4)); + + p += 0x0C + inflate_len; + memcpy(p,"\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82",0x0C); + p += 0x0C; + g->png_len = p - g->png_data; + g->png_dirty = 0; + *len = g->png_len; + return g->png_data; +} + +void graph_free(struct graph* g) { + if(g != NULL) { + aFree(g->png_data); + aFree(g->raw_data); + aFree(g->graph_value); + aFree(g); + } +} + +// とりあえず不効率版。後ほど書き直し予定 +void graph_square(struct graph* g,int x,int y,int xe,int ye,int color) { + int i,j; + if(g == NULL) return; + if(x < 0) { x = 0; } + if(y < 0) { y = 0; } + if(xe > g->width) { xe = g->width; } + if(ye > g->height) { ye = g->height; } + for(i = y;i < ye ; i++) { + for(j = x; j < xe ; j++) { + graph_setpixel(g,j,i,color); + } + } +} + +// とりあえず不効率版。後ほど書き直し予定 +void graph_scroll(struct graph* g,int n,int color) { + int x,y; + if(g == NULL) return; + for(y = 0; y < g->height; y++) { + for(x = 0; x < g->width - n; x++) { + graph_setpixel(g,x,y,graph_getpixel(g,x + n,y)); + } + for( ; x < g->width; x++) { + graph_setpixel(g,x,y,color); + } + } +} + +void graph_data(struct graph* g,int value) { + int i, j, start; + if(g == NULL) return; + memmove(&g->graph_value[0],&g->graph_value[1],sizeof(int) * (g->width - 1)); + g->graph_value[g->width - 1] = value; + if(value > g->graph_max) { + // 最大値を超えたので再描画 + g->graph_max = value; + graph_square(g,0,0,g->width,g->height,0); + start = 0; + } else { + // スクロールしてポイント打つ + graph_scroll(g,1,0); + start = g->width - 1; + } + for(i = start; i < g->width; i++) { + int h0 = (i == 0 ? 0 : g->graph_value[i - 1]) * g->height / g->graph_max; + int h1 = (g->graph_value[i] ) * g->height / g->graph_max; + int h2 = (h0 < h1 ? 1 : -1); + for(j = h0; j != h1; j += h2) { + graph_setpixel(g,i,g->height - 1 - j,1); + } + graph_setpixel(g,i,g->height - 1 - h1,1); + } +} + +// 上の関数群を利用して、自動的にグラフを作成するタイマー群 + +#define GRP_WIDTH 300 // グラフの幅 +#define GRP_HEIGHT 200 // グラフの高さ +#define GRP_COLOR graph_rgb(0,0,255) // グラフの色 +#define GRP_INTERVEL 60*1000 // グラフの更新間隔 + +#define GRP_PATH "httpd/" + +struct graph_sensor { + struct graph* graph; + char* str; + char hash[32]; + int scanid; + int drawid; + int interval; + unsigned int (*func)(void); +}; + +static struct graph_sensor *sensor; +static int sensor_max; + +static int graph_scan_timer(int tid,unsigned int tick,int id,int data) +{ + if(id >= 0 && id < sensor_max) + graph_data(sensor[id].graph,sensor[id].func()); + return 0; +} + +// modified by Celest -- i'm trying to separate it from httpd if possible ^^; +static int graph_draw_timer(int tid,unsigned int tick,int id,int data) +{ + char png_file[24]; + FILE *fp; + + // create/update the png file + do { + const char *png_data; + int len; + sprintf (png_file, GRP_PATH"%s.png", sensor[id].hash); + fp = fopen(png_file, "w"); + // if another png of the same hash exists + // (i.e 2nd login server with the same sensors) + // this will fail = not good >.< + if (fp == NULL) + break; + png_data = graph_output(sensor[id].graph, &len); + fwrite(png_data,1,len,fp); + fclose(fp); + } while (0); + + // create/update text snippet + do { + char buf[8192], *p; + p = buf; + sprintf (png_file, GRP_PATH"%s.graph", sensor[id].hash); + fp = fopen(png_file, "w"); + if (fp == NULL) + break; + p += sprintf(p,"

%s

\n\n", + sensor[id].str); + p += sprintf(p,"

\n", + sensor[id].hash, GRP_WIDTH,GRP_HEIGHT); + p += sprintf(p,"

Max: %d, Interval: %d sec

\n\n", + sensor[id].graph->graph_max, sensor[id].interval / 1000); + fprintf(fp, buf); + fclose(fp); + } while (0); + + return 0; +} + +void graph_add_sensor(const char* string, int interval, unsigned int (*callback_func)(void)) +{ + int draw_interval = interval * 2; + struct graph *g = graph_create(GRP_WIDTH,GRP_HEIGHT); + graph_pallet(g,1,GRP_COLOR); + + sensor = (struct graph_sensor *) aRealloc(sensor, sizeof(struct graph_sensor) * (sensor_max + 1)); + sensor[sensor_max].graph = g; + sensor[sensor_max].str = aStrdup(string); + // create crc32 hash of the sensor's name + sprintf (sensor[sensor_max].hash, "%lu%c", grfio_crc32(string,strlen(string)), 'a' + SERVER_TYPE); + sensor[sensor_max].func = callback_func; + sensor[sensor_max].scanid = add_timer_interval(gettick() + 500, graph_scan_timer, sensor_max, 0, interval); + sensor[sensor_max].drawid = add_timer_interval(gettick() + 1000, graph_draw_timer, sensor_max, 0, draw_interval < 60000 ? 60000 : draw_interval); + sensor[sensor_max].interval = interval; + sensor_max++; + +} + +void graph_final (void) +{ + int i; + for(i = 0; i < sensor_max; i++) { + char png_file[24]; + // remove the png and snippet file + sprintf (png_file, GRP_PATH"%s.png", sensor[i].hash); + unlink (png_file); + sprintf (png_file, GRP_PATH"%s.graph", sensor[i].hash); + unlink (png_file); + graph_free(sensor[i].graph); + aFree(sensor[i].str); + //delete_timer(sensor[i].scanid,graph_scan_timer); + //delete_timer(sensor[i].drawid,graph_draw_timer); + } + aFree(sensor); + sensor_max = 0; +} + +void graph_init (void) +{ + graph_add_sensor ("Memory Usage", 1000, malloc_usage); + add_timer_func_list(graph_scan_timer, "graph_scan_timer"); + add_timer_func_list(graph_draw_timer, "graph_draw_timer"); +} + +#else +void graph_init (void) {} +void graph_final (void) {} +#endif diff --git a/src/common/graph.h b/src/common/graph.h new file mode 100644 index 000000000..6c80dd41c --- /dev/null +++ b/src/common/graph.h @@ -0,0 +1,27 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _GRAPH_H_ +#define _GRAPH_H_ + +void graph_init (void); +void graph_final (void); + +struct graph* graph_create(unsigned int x,unsigned int y); +void graph_pallet(struct graph* g, int index,unsigned long c); +const unsigned char* graph_output(struct graph* g,int *len); +void graph_setpixel(struct graph* g,int x,int y,int color); +void graph_scroll(struct graph* g,int n,int color); +void graph_square(struct graph* g,int x,int y,int xe,int ye,int color); + +// athenaの状態を調査するセンサーを追加する。 +// string : センサーの名称(Login Users など) +// inetrval : センサーの値を所得する間隔(msec) +// callback_func : センサーの値を返す関数( unsigned int login_users(void); など) + +void graph_add_sensor(const char* string, int interval, unsigned int (*callback_func)(void)); + +#define graph_rgb(r,g,b) (((r) << 16) | ((g) << 8) | (b)) + +#endif + diff --git a/src/common/grfio.c b/src/common/grfio.c new file mode 100644 index 000000000..a3907a7f2 --- /dev/null +++ b/src/common/grfio.c @@ -0,0 +1,1146 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +/********************************************************************* + * + * Ragnarok Online Emulator : grfio.c -- grf file I/O Module + *-------------------------------------------------------------------- + * special need library : zlib + ********************************************************************* + * $Id: grfio.c,v 1.2 2004/09/29 17:31:49 kalaspuff Exp $ + * + * 2002/12/18... the original edition + * 2003/01/23 ... Code correction + * 2003/02/01 ... An addition and decryption processing are improved for LocalFile and two or more GRF(s) check processing. + * 2003/02/02 ... Even if there is no grf it does not stop -- as -- correction + * 2003/02/02... grf reading specification can be added later -- as -- correction (grfio_add function addition) + * 2003/02 / 03... at the time of grfio_resourcecheck processing the entry addition processing method -- correction + * 2003/02/05... change of the processing in grfio_init + * 2003/02/23... a local file check -- GRFIO_LOCAL -- switch (Defoe -- Function Off) + * 2003/10/21 ... The data of alpha client was read. + * 2003/11/10 ... Ready new grf format. + * 2003/11/11 ... version check fix & bug fix + */ + +#include +#include +#include +#include +#include + +#include "grfio.h" +#include "../common/mmo.h" +#include "../common/showmsg.h" +#include "../common/malloc.h" +#include "../zlib/unzip.h" + +#define CHUNK 16384 + +#ifdef __WIN32 + #include "../zlib/zlib.h" + #include "../zlib/iowin32.h" +#else + #include +#endif + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; + +//static char data_file[1024] = ""; // "data.grf"; +//static char sdata_file[1024] = ""; // "sdata.grf"; +//static char adata_file[1024] = ""; // "adata.grf"; +static char data_dir[1024] = ""; // "../"; + +//---------------------------- +// file entry table struct +//---------------------------- +typedef struct { + int srclen; // compressed size + int srclen_aligned; // + int declen; // original size + int srcpos; + short next; + char cycle; + char type; + char fn[128-4*5]; // file name + char gentry; // read grf file select +} FILELIST; +//gentry ... 0 : It acquires from a local file. +// It acquires from the resource file of 1>=:gentry_table[gentry-1]. +// 1<=: Check a local file. +// If it is, after re-setting to 0, it acquires from a local file. +// If there is nothing, mark reversal will be carried out, and it will re-set, and will acquire from a resource file as well as 1>=. + +//Since char defines *FILELIST.gentry, the maximum which can be added by grfio_add becomes by 127 pieces. + +#define GENTRY_LIMIT 127 +#define FILELIST_LIMIT 65536 // temporary maximum, and a theory top maximum are 2G. + +static FILELIST *filelist = NULL; +static int filelist_entrys = 0; +static int filelist_maxentry = 0; + +static char **gentry_table = NULL; +static int gentry_entrys = 0; +static int gentry_maxentry = 0; + +#define RESNAME_LIMIT 1024 +#define RESNAME_ADDS 16 + +typedef struct resname_entry { + char src[64]; + char dst[64]; +} Resname; +static struct resname_entry *localresname = NULL; +static int resname_entrys = 0; +static int resname_maxentrys = 0; + +//---------------------------- +// file list hash table +//---------------------------- +static int filelist_hash[256]; + +//---------------------------- +// grf decode data table +//---------------------------- +static unsigned char BitMaskTable[8] = { + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +static char BitSwapTable1[64] = { + 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 +}; +static char BitSwapTable2[64] = { + 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 +}; +static char BitSwapTable3[32] = { + 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, + 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 +}; + +static unsigned char NibbleData[4][64]={ + { + 0xef, 0x03, 0x41, 0xfd, 0xd8, 0x74, 0x1e, 0x47, 0x26, 0xef, 0xfb, 0x22, 0xb3, 0xd8, 0x84, 0x1e, + 0x39, 0xac, 0xa7, 0x60, 0x62, 0xc1, 0xcd, 0xba, 0x5c, 0x96, 0x90, 0x59, 0x05, 0x3b, 0x7a, 0x85, + 0x40, 0xfd, 0x1e, 0xc8, 0xe7, 0x8a, 0x8b, 0x21, 0xda, 0x43, 0x64, 0x9f, 0x2d, 0x14, 0xb1, 0x72, + 0xf5, 0x5b, 0xc8, 0xb6, 0x9c, 0x37, 0x76, 0xec, 0x39, 0xa0, 0xa3, 0x05, 0x52, 0x6e, 0x0f, 0xd9, + }, { + 0xa7, 0xdd, 0x0d, 0x78, 0x9e, 0x0b, 0xe3, 0x95, 0x60, 0x36, 0x36, 0x4f, 0xf9, 0x60, 0x5a, 0xa3, + 0x11, 0x24, 0xd2, 0x87, 0xc8, 0x52, 0x75, 0xec, 0xbb, 0xc1, 0x4c, 0xba, 0x24, 0xfe, 0x8f, 0x19, + 0xda, 0x13, 0x66, 0xaf, 0x49, 0xd0, 0x90, 0x06, 0x8c, 0x6a, 0xfb, 0x91, 0x37, 0x8d, 0x0d, 0x78, + 0xbf, 0x49, 0x11, 0xf4, 0x23, 0xe5, 0xce, 0x3b, 0x55, 0xbc, 0xa2, 0x57, 0xe8, 0x22, 0x74, 0xce, + }, { + 0x2c, 0xea, 0xc1, 0xbf, 0x4a, 0x24, 0x1f, 0xc2, 0x79, 0x47, 0xa2, 0x7c, 0xb6, 0xd9, 0x68, 0x15, + 0x80, 0x56, 0x5d, 0x01, 0x33, 0xfd, 0xf4, 0xae, 0xde, 0x30, 0x07, 0x9b, 0xe5, 0x83, 0x9b, 0x68, + 0x49, 0xb4, 0x2e, 0x83, 0x1f, 0xc2, 0xb5, 0x7c, 0xa2, 0x19, 0xd8, 0xe5, 0x7c, 0x2f, 0x83, 0xda, + 0xf7, 0x6b, 0x90, 0xfe, 0xc4, 0x01, 0x5a, 0x97, 0x61, 0xa6, 0x3d, 0x40, 0x0b, 0x58, 0xe6, 0x3d, + }, { + 0x4d, 0xd1, 0xb2, 0x0f, 0x28, 0xbd, 0xe4, 0x78, 0xf6, 0x4a, 0x0f, 0x93, 0x8b, 0x17, 0xd1, 0xa4, + 0x3a, 0xec, 0xc9, 0x35, 0x93, 0x56, 0x7e, 0xcb, 0x55, 0x20, 0xa0, 0xfe, 0x6c, 0x89, 0x17, 0x62, + 0x17, 0x62, 0x4b, 0xb1, 0xb4, 0xde, 0xd1, 0x87, 0xc9, 0x14, 0x3c, 0x4a, 0x7e, 0xa8, 0xe2, 0x7d, + 0xa0, 0x9f, 0xf6, 0x5c, 0x6a, 0x09, 0x8d, 0xf0, 0x0f, 0xe3, 0x53, 0x25, 0x95, 0x36, 0x28, 0xcb, + } +}; +/*----------------- + * long data get + */ +static unsigned int getlong(unsigned char *p) +{ +// return *p+p[1]*256+(p[2]+p[3]*256)*65536; + return p[0] + | p[1] << 0x08 + | p[2] << 0x10 + | p[3] << 0x18; // Shinomori +} + +/*========================================== + * Grf data decode : Subs + *------------------------------------------ + */ +static void NibbleSwap(BYTE *Src, int len) +{ + for(;0>4) | (*Src<<4); + } +} + +static void BitConvert(BYTE *Src,char *BitSwapTable) +{ + int lop,prm; + BYTE tmp[8]; +// *(DWORD*)tmp=*(DWORD*)(tmp+4)=0; + memset(tmp,0,8); + for(lop=0;lop!=64;lop++) { + prm = BitSwapTable[lop]-1; + if (Src[(prm >> 3) & 7] & BitMaskTable[prm & 7]) { + tmp[(lop >> 3) & 7] |= BitMaskTable[lop & 7]; + } + } +// *(DWORD*)Src = *(DWORD*)tmp; +// *(DWORD*)(Src+4) = *(DWORD*)(tmp+4); + memcpy(Src,tmp,8); +} + +static void BitConvert4(BYTE *Src) +{ + int lop,prm; + BYTE tmp[8]; + tmp[0] = ((Src[7]<<5) | (Src[4]>>3)) & 0x3f; // ..0 vutsr + tmp[1] = ((Src[4]<<1) | (Src[5]>>7)) & 0x3f; // ..srqpo n + tmp[2] = ((Src[4]<<5) | (Src[5]>>3)) & 0x3f; // ..o nmlkj + tmp[3] = ((Src[5]<<1) | (Src[6]>>7)) & 0x3f; // ..kjihg f + tmp[4] = ((Src[5]<<5) | (Src[6]>>3)) & 0x3f; // ..g fedcb + tmp[5] = ((Src[6]<<1) | (Src[7]>>7)) & 0x3f; // ..cba98 7 + tmp[6] = ((Src[6]<<5) | (Src[7]>>3)) & 0x3f; // ..8 76543 + tmp[7] = ((Src[7]<<1) | (Src[4]>>7)) & 0x3f; // ..43210 v + + for(lop=0;lop!=4;lop++) { + tmp[lop] = (NibbleData[lop][tmp[lop*2]] & 0xf0) + | (NibbleData[lop][tmp[lop*2+1]] & 0x0f); + } + + *(DWORD*)(tmp+4)=0; + for(lop=0;lop!=32;lop++) { + prm = BitSwapTable3[lop]-1; + if (tmp[prm >> 3] & BitMaskTable[prm & 7]) { + tmp[(lop >> 3) + 4] |= BitMaskTable[lop & 7]; + } + } +// *(DWORD*)Src ^= *(DWORD*)(tmp+4); + Src[0] ^= tmp[4]; + Src[1] ^= tmp[5]; + Src[2] ^= tmp[6]; + Src[3] ^= tmp[7]; +} + +static void decode_des_etc(BYTE *buf,int len,int type,int cycle) +{ + int lop,cnt=0; + if(cycle<3) cycle=3; + else if(cycle<5) cycle++; + else if(cycle<7) cycle+=9; + else cycle+=15; + + for(lop=0;lop*8 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = (Bytef*) dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} + +int encode_zip(unsigned char *dest, unsigned long* destLen, const unsigned char* source, unsigned long sourceLen) { + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = (Bytef*) dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = deflateInit(&stream,Z_DEFAULT_COMPRESSION); + if (err != Z_OK) return err; + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = deflateEnd(&stream); + return err; +} + +/*========================================== +* Decompress from file source to file dest until stream ends or EOF. +* inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be +* allocated for processing, Z_DATA_ERROR if the deflate data is +* invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and +* the version of the library linked do not match, or Z_ERRNO if there +* is an error reading or writing the files. +* +* Version 1.2 9 November 2004 Mark Adler +*------------------------------------------ +*/ +int decode_file (FILE *source, FILE *dest) +{ + int err; + unsigned have; + z_stream strm; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + + err = inflateInit(&strm); + if (err != Z_OK) return 0; //return err; + + /* decompress until deflate stream ends or end of file */ + do { + strm.avail_in = fread(in, 1, CHUNK, source); + if (ferror(source)) { + inflateEnd(&strm); + return 0; + } + if (strm.avail_in == 0) + break; + strm.next_in = in; + + /* run inflate() on input until output buffer not full */ + do { + strm.avail_out = CHUNK; + strm.next_out = out; + err = inflate(&strm, Z_NO_FLUSH); + Assert(err != Z_STREAM_ERROR); /* state not clobbered */ + switch (err) { + case Z_NEED_DICT: + err = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm); + //return err; + return 0; + } + have = CHUNK - strm.avail_out; + if (fwrite(out, 1, have, dest) != have || ferror(dest)) { + inflateEnd(&strm); + //return Z_ERRNO; + return 0; + } + } while (strm.avail_out == 0); + Assert(strm.avail_in == 0); /* all input will be used */ + + /* done when inflate() says it's done */ + } while (err != Z_STREAM_END); + + /* clean up and return */ + inflateEnd(&strm); + return err == Z_STREAM_END ? 1 : 0; +} + +/* =================================== +* Unzips a file. 1: success, 0: error +* Adapted from miniunz.c [Celest] +* Version 1.01b, May 30th, 2004 +* Copyright (C) 1998-2004 Gilles Vollant +* ------------------------------------- +*/ +int deflate_file (const char *source, const char *filename) +{ +#ifdef _WIN32 + zlib_filefunc_def ffunc; +#endif + unzFile uf = NULL; + int err = UNZ_OK; + uInt size_buf = 8192; + FILE *fout = NULL; + void *buf; + +#ifdef _WIN32 + fill_win32_filefunc(&ffunc); + uf = unzOpen2(source, &ffunc); +#else + uf = unzOpen(source); +#endif + + if (uf == NULL) { + //printf("Cannot open %s\n", source); + return 0; + } + //printf("%s opened\n", source); + + if (unzLocateFile(uf, filename, 0) != UNZ_OK) { + //printf("file %s not found in the zipfile\n", filename); + return 0; + } + + err = unzOpenCurrentFilePassword(uf, NULL); + //if (err != UNZ_OK) + // printf("error %d with zipfile in unzOpenCurrentFilePassword\n", err); + + fout = fopen(filename,"wb"); + if (fout == NULL) { + //printf("error opening %s\n", filename); + return 0; + } + + buf = (void *)aMalloc(size_buf); + do { + err = unzReadCurrentFile(uf, buf, size_buf); + if (err < 0) { + //printf("error %d with zipfile in unzReadCurrentFile\n", err); + break; + } + if (err > 0 && + fwrite(buf, err, 1, fout)!=1) + { + //printf("error in writing extracted file\n"); + err = UNZ_ERRNO; + break; + } + } while (err > 0); + + if (fout) fclose(fout); + + if (err == UNZ_OK) { + err = unzCloseCurrentFile (uf); + //if (err != UNZ_OK) + // printf("error %d with zipfile in unzCloseCurrentFile\n", err); + aFree(buf); + return (err == UNZ_OK); + } + + unzCloseCurrentFile(uf); /* don't lose the error */ + + return 0; +} + +unsigned long grfio_crc32 (const char *buf, unsigned int len) +{ + return crc32(crc32(0L, Z_NULL, 0), buf, len); +} + +/*********************************************************** + *** File List Sobroutines *** + ***********************************************************/ + +/*========================================== + * File List : Hash make + *------------------------------------------ + */ +static int filehash(unsigned char *fname) +{ + unsigned int hash=0; + while(*fname) { + hash = ((hash<<1)+(hash>>7)*9+tolower(*fname)); + fname++; + } + return hash & 255; +} + +/*========================================== + * File List : Hash initalize + *------------------------------------------ + */ +static void hashinit(void) +{ + int lop; + for (lop = 0; lop < 256; lop++) + filelist_hash[lop] = -1; +} + +/*========================================== + * File List : File find + *------------------------------------------ + */ +FILELIST *filelist_find(char *fname) +{ + int hash; + + if (!filelist) + return NULL; + + for (hash = filelist_hash[filehash((unsigned char *) fname)]; hash >= 0; hash = filelist[hash].next) { + if(strcmpi(filelist[hash].fn, fname) == 0) + break; + } + + return (hash >= 0) ? &filelist[hash] : NULL; +} + +/*========================================== + * File List : Filelist add + *------------------------------------------ + */ +#define FILELIST_ADDS 1024 // number increment of file lists ` + +static FILELIST* filelist_add(FILELIST *entry) +{ + int hash; + + if (filelist_entrys >= FILELIST_LIMIT) { + ShowFatalError("filelist limit : filelist_add\n"); + exit(1); + } + + if (filelist_entrys >= filelist_maxentry) { + filelist = (FILELIST *)aRealloc(filelist, (filelist_maxentry + FILELIST_ADDS) * sizeof(FILELIST)); + memset(filelist + filelist_maxentry, '\0', FILELIST_ADDS * sizeof(FILELIST)); + filelist_maxentry += FILELIST_ADDS; + } + + memcpy (&filelist[filelist_entrys], entry, sizeof(FILELIST)); + + hash = filehash((unsigned char *) entry->fn); + filelist[filelist_entrys].next = filelist_hash[hash]; + filelist_hash[hash] = filelist_entrys; + + filelist_entrys++; + + return &filelist[filelist_entrys - 1]; +} + +static FILELIST* filelist_modify(FILELIST *entry) +{ + FILELIST *fentry; + if ((fentry = filelist_find(entry->fn)) != NULL) { + int tmp = fentry->next; + memcpy(fentry, entry, sizeof(FILELIST)); + fentry->next = tmp; + } else { + fentry = filelist_add(entry); + } + return fentry; +} + +/*========================================== + * File List : filelist size adjust + *------------------------------------------ + */ +static void filelist_adjust(void) +{ + if (filelist != NULL) { + if (filelist_maxentry > filelist_entrys) { + filelist = (FILELIST *)aRealloc( + filelist, filelist_entrys * sizeof(FILELIST)); + filelist_maxentry = filelist_entrys; + } + } +} + +/*********************************************************** + *** Grfio Sobroutines *** + ***********************************************************/ +/*========================================== + * Grfio : Local Resnametable replace + *------------------------------------------ + */ +static void grfio_resnametable(char *src, char *dest) +{ + int lop; + if (localresname == NULL || + sscanf(src, "%*5s%s", dest) < 1) + { + // if not found copy the unresolved name into buffer + strcpy(dest, src); + return; + } + + for (lop = 0; lop < resname_entrys; lop++) { + if (strcmpi(localresname[lop].src, dest) == 0) { + sprintf(dest, "data\\%s", localresname[lop].dst); + return; + } + } + + return; +} + +/*========================================== + * Grfio : Local Resnametable Initialize + *------------------------------------------ + */ +static void grfio_resnameinit (void) +{ + FILE *fp; + char *p; + // max length per entry is 34 in resnametable + char w1[64], w2[64], restable[256], line[256]; + + sprintf(restable, "%sdata\\resnametable.txt", data_dir); + for (p = &restable[0]; *p != 0; p++) + if (*p == '\\') *p = '/'; + + fp = fopen(restable,"rb"); + if (fp == NULL) { + //ShowError("%s not found (grfio_resnameinit)\n", restable); + return; + } + + while (fgets(line, sizeof(line) - 1, fp)){ + if (sscanf(line, "%[^#]#%[^#]#", w1, w2) != 2) + continue; + // only save up necessary resource files + if (strstr(w1, ".gat") == NULL && + strstr(w1, ".txt") == NULL) + continue; + if (resname_entrys >= RESNAME_LIMIT) + break; + if (resname_entrys >= resname_maxentrys) { + resname_maxentrys += RESNAME_ADDS; + localresname = (Resname*) aRealloc (localresname, resname_maxentrys * sizeof(Resname)); + memset(localresname + (resname_maxentrys - RESNAME_ADDS), '\0', sizeof(Resname) * RESNAME_ADDS); + } + strcpy(localresname[resname_entrys].src, w1); + strcpy(localresname[resname_entrys].dst, w2); + resname_entrys++; + } + fclose(fp); + + // free up unused sections + if (resname_maxentrys > resname_entrys) { + localresname = (Resname*) aRealloc (localresname, resname_entrys * sizeof(Resname)); + resname_maxentrys = resname_entrys; + } +} + +/*========================================== + * Grfio : Resource file size get + *------------------------------------------ + */ +int grfio_size(char *fname) +{ + FILELIST *entry; + + entry = filelist_find(fname); + + if (entry == NULL || entry->gentry < 0) { // LocalFileCheck + char lfname[256], rname[256], *p; + FILELIST lentry; + struct stat st; + + grfio_resnametable(fname, rname); + sprintf(lfname, "%s%s", data_dir, rname); + + for (p = &lfname[0]; *p != 0; p++) + if (*p=='\\') *p = '/'; // * At the time of Unix + + if (stat(lfname, &st) == 0) { + strncpy(lentry.fn, fname, sizeof(lentry.fn) - 1); + lentry.declen = st.st_size; + lentry.gentry = 0; // 0:LocalFile + entry = filelist_modify(&lentry); + } else if (entry == NULL) { + ShowError("%s not found (grfio_size)\n", fname); + //exit(1); + return -1; + } + } + return entry->declen; +} + +/*========================================== + * Grfio : Resource file read & size get + *------------------------------------------ + */ +void* grfio_reads(char *fname, int *size) +{ + FILE *in; + FILELIST *entry; + unsigned char *buf2 = NULL; + + entry = filelist_find(fname); + + if (entry == NULL || entry->gentry <= 0) { // LocalFileCheck + char lfname[256], rname[256], *p; + FILELIST lentry; + + // resolve filename into rname + grfio_resnametable(fname, rname); + sprintf(lfname, "%s%s", data_dir, rname); + + for (p = &lfname[0]; *p != 0; p++) + if (*p == '\\') *p = '/'; // * At the time of Unix + + in = fopen(lfname, "rb"); + if (in != NULL) { + if (entry != NULL && entry->gentry == 0) { + lentry.declen = entry->declen; + } else { + fseek(in,0,2); // SEEK_END + lentry.declen = ftell(in); + } + fseek(in,0,0); // SEEK_SET + buf2 = (unsigned char *)aCallocA(lentry.declen + 1024, 1); + fread(buf2, 1, lentry.declen, in); + fclose(in); + strncpy(lentry.fn, fname, sizeof(lentry.fn) - 1); + lentry.gentry = 0; // 0:LocalFile + entry = filelist_modify(&lentry); + } else { + if (entry != NULL && entry->gentry < 0) { + entry->gentry = -entry->gentry; // local file checked + } else { + ShowError("%s not found (grfio_reads - local file %s)\n", fname, lfname); + return NULL; + } + } + } + if (entry != NULL && entry->gentry > 0) { // Archive[GRF] File Read + char *gfname = gentry_table[entry->gentry - 1]; + in = fopen(gfname, "rb"); + if(in != NULL) { + unsigned char *buf = (unsigned char *)aCallocA(entry->srclen_aligned + 1024, 1); + fseek(in, entry->srcpos, 0); + fread(buf, 1, entry->srclen_aligned, in); + fclose(in); + buf2 = (unsigned char *)aCallocA(entry->declen + 1024, 1); + if (entry->type == 1 || entry->type == 3 || entry->type == 5) { + uLongf len; + if (entry->cycle >= 0) + decode_des_etc(buf, entry->srclen_aligned, entry->cycle == 0, entry->cycle); + len = entry->declen; + decode_zip(buf2, &len, buf, entry->srclen); + if (len != entry->declen) { + ShowError("decode_zip size miss match err: %d != %d\n", (int)len, entry->declen); + aFree(buf); + aFree(buf2); + return NULL; + } + } else { + memcpy(buf2, buf, entry->declen); + } + aFree(buf); + } else { + ShowError("%s not found (grfio_reads - grf file %s)\n", fname, gfname); + return NULL; + } + } + if (size != NULL && entry != NULL) + *size = entry->declen; + + return buf2; +} + +/*========================================== + * Grfio : Resource file read + *------------------------------------------ + */ +void* grfio_read(char *fname) +{ + return grfio_reads(fname, NULL); +} + +/*========================================== + * Resource filename decode + *------------------------------------------ + */ +static char * decode_filename(unsigned char *buf,int len) +{ + int lop; + for(lop=0;lop> 8; + + if (grf_version == 0x01) { //****** Grf version 01xx ****** + list_size = grf_size - ftell(fp); + grf_filelist = (unsigned char *) aCallocA(list_size, 1); + /*if (grf_filelist == NULL){ + fclose(fp); + ShowError("out of memory : grf_filelist\n"); + return 3; // 3:memory alloc error + }*/ + fread(grf_filelist,1,list_size,fp); + fclose(fp); + + entrys = getlong(grf_header+0x26) - getlong(grf_header+0x22) - 7; + + // Get an entry + for (entry = 0,ofs = 0; entry < entrys; entry++) { + int ofs2, srclen, srccount, type; + char *period_ptr; + FILELIST aentry; + + ofs2 = ofs+getlong(grf_filelist+ofs)+4; + type = grf_filelist[ofs2+12]; + if (type != 0) { // Directory Index ... skip + fname = decode_filename(grf_filelist+ofs+6, grf_filelist[ofs]-6); + if (strlen(fname) > sizeof(aentry.fn) - 1) { + ShowFatalError("file name too long : %s\n",fname); + aFree(grf_filelist); + exit(1); + } + srclen = 0; + if ((period_ptr = strrchr(fname, '.')) != NULL) { + for(lop = 0; lop < 4; lop++) { + if (strcmpi(period_ptr, ".gnd\0.gat\0.act\0.str"+lop*5) == 0) + break; + } + srclen = getlong(grf_filelist+ofs2) - getlong(grf_filelist+ofs2+8) - 715; + if(lop == 4) { + for(lop = 10, srccount = 1; srclen >= lop; lop = lop * 10, srccount++); + } else { + srccount = 0; + } + } else { + srccount = 0; + } + + aentry.srclen = srclen; + aentry.srclen_aligned = getlong(grf_filelist+ofs2+4)-37579; + aentry.declen = getlong(grf_filelist+ofs2+8); + aentry.srcpos = getlong(grf_filelist+ofs2+13)+0x2e; + aentry.cycle = srccount; + aentry.type = type; + strncpy(aentry.fn, fname,sizeof(aentry.fn)-1); +#ifdef GRFIO_LOCAL + aentry.gentry = -(gentry+1); // As Flag for making it a negative number carrying out the first time LocalFileCheck +#else + aentry.gentry = gentry+1; // With no first time LocalFileCheck +#endif + filelist_modify(&aentry); + } + ofs = ofs2 + 17; + } + aFree(grf_filelist); + + } else if (grf_version == 0x02) { //****** Grf version 02xx ****** + unsigned char eheader[8]; + unsigned char *rBuf; + uLongf rSize, eSize; + + fread(eheader,1,8,fp); + rSize = getlong(eheader); // Read Size + eSize = getlong(eheader+4); // Extend Size + + if ((long)rSize > grf_size-ftell(fp)) { // Warning fix [Lance] + fclose(fp); + ShowError("Illegal data format : grf compress entry size\n"); + return 4; + } + + rBuf = (unsigned char *)aCallocA(rSize , 1); // Get a Read Size + /*if (rBuf==NULL) { + fclose(fp); + ShowError("out of memory : grf compress entry table buffer\n"); + return 3; + }*/ + grf_filelist = (unsigned char *)aCallocA(eSize , 1); // Get a Extend Size + /*if (grf_filelist==NULL) { + aFree(rBuf); + fclose(fp); + ShowError("out of memory : grf extract entry table buffer\n"); + return 3; + }*/ + fread(rBuf,1,rSize,fp); + fclose(fp); + decode_zip(grf_filelist, &eSize, rBuf, rSize); // Decode function + list_size = eSize; + aFree(rBuf); + + entrys = getlong(grf_header+0x26) - 7; + + // Get an entry + for(entry = 0, ofs = 0; entry < entrys; entry++){ + int ofs2, srclen, srccount, type; + FILELIST aentry; + + fname = (char*)(grf_filelist+ofs); + if (strlen(fname) > sizeof(aentry.fn)-1) { + ShowFatalError("grf : file name too long : %s\n",fname); + aFree(grf_filelist); + exit(1); + } + //ofs2 = ofs+strlen((char*)(grf_filelist+ofs))+1; + ofs2 = ofs + strlen(fname)+1; + type = grf_filelist[ofs2+12]; + if (type == 1 || type == 3 || type == 5) { + srclen = getlong(grf_filelist+ofs2); + if (grf_filelist[ofs2+12] == 3) { + for (lop = 10, srccount = 1; srclen >= lop; lop = lop * 10, srccount++); + } else if (grf_filelist[ofs2+12] == 5) { + srccount = 0; + } else { // if (grf_filelist[ofs2+12]==1) { + srccount = -1; + } + + aentry.srclen = srclen; + aentry.srclen_aligned = getlong(grf_filelist+ofs2+4); + aentry.declen = getlong(grf_filelist+ofs2+8); + aentry.srcpos = getlong(grf_filelist+ofs2+13)+0x2e; + aentry.cycle = srccount; + aentry.type = type; + strncpy(aentry.fn,fname,sizeof(aentry.fn)-1); +#ifdef GRFIO_LOCAL + aentry.gentry = -(gentry+1); // As Flag for making it a negative number carrying out the first time LocalFileCheck +#else + aentry.gentry = gentry+1; // With no first time LocalFileCheck +#endif + filelist_modify(&aentry); + } + ofs = ofs2 + 17; + } + aFree(grf_filelist); + + } else { //****** Grf Other version ****** + fclose(fp); + ShowError("not support grf versions : %04x\n",getlong(grf_header+0x2a)); + return 4; + } + + filelist_adjust(); // Unnecessary area release of filelist + + return 0; // 0:no error +} + +/*========================================== + * Grfio : Resource file check + *------------------------------------------ + */ +static void grfio_resourcecheck(void) +{ + int size; + char *buf, *ptr; + char w1[256], w2[256], src[256], dst[256]; + FILELIST *entry; + + buf = (char *)grfio_reads("data\\resnametable.txt", &size); + if (buf == NULL) + return; + buf[size] = 0; + + for (ptr = buf; ptr - buf < size;) { + if (sscanf(ptr,"%[^#]#%[^#]#",w1,w2) == 2) { + if (strstr(w2, "bmp")) { + sprintf(src, "data\\texture\\%s", w1); + sprintf(dst, "data\\texture\\%s", w2); + } else { + sprintf(src, "data\\%s", w1); + sprintf(dst, "data\\%s", w2); + } + entry = filelist_find(dst); + if (entry != NULL) { + FILELIST fentry; + memcpy(&fentry, entry, sizeof(FILELIST)); + strncpy(fentry.fn, src, sizeof(fentry.fn) - 1); + filelist_modify(&fentry); + } else { + //ShowError("file not found in data.grf : %s < %s\n",dst,src); + } + } + ptr = strchr(ptr,'\n'); // Next line + if (!ptr) break; + ptr++; + } + aFree(buf); + filelist_adjust(); // Unnecessary area release of filelist +} + +/*========================================== + * Grfio : Resource add + *------------------------------------------ + */ +#define GENTRY_ADDS 16 // The number increment of gentry_table entries + +int grfio_add(char *fname) +{ + int len,result; + char *buf; + + if (gentry_entrys >= GENTRY_LIMIT) { + ShowFatalError("gentrys limit : grfio_add\n"); + exit(1); + } + + if (gentry_entrys >= gentry_maxentry) { + gentry_maxentry += GENTRY_ADDS; + gentry_table = (char**)aRealloc(gentry_table, gentry_maxentry * sizeof(char*)); + memset(gentry_table + (gentry_maxentry - GENTRY_ADDS), 0, sizeof(char*) * GENTRY_ADDS); + } + len = strlen( fname ); + buf = (char*)aCallocA(len + 1, 1); + strcpy(buf, fname); + gentry_table[gentry_entrys++] = buf; + + result = grfio_entryread(fname, gentry_entrys - 1); + if (result == 0) + // Resource check + grfio_resourcecheck(); + + return result; +} + +/*========================================== + * Grfio : Finalize + *------------------------------------------ + */ +void grfio_final(void) +{ + if (filelist != NULL) + aFree(filelist); + filelist_entrys = filelist_maxentry = 0; + + if (gentry_table != NULL) { + int lop; + for (lop = 0; lop < gentry_entrys; lop++) { + if (gentry_table[lop] != NULL) + aFree(gentry_table[lop]); + } + aFree(gentry_table); + } + gentry_table = NULL; + gentry_entrys = gentry_maxentry = 0; + + if (localresname) aFree(localresname); +} + +/*========================================== + * Grfio : Initialize + *------------------------------------------ + */ +void grfio_init(char *fname) +{ + FILE *data_conf; + char line[1024], w1[1024], w2[1024]; + int result = 0; + + hashinit(); // hash table initialization + + data_conf = fopen(fname, "r"); + // It will read, if there is grf-files.txt. + if (data_conf) { + while(fgets(line, sizeof(line) - 1, data_conf)) { + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + // Entry table reading + if(strcmp(w1, "grf") == 0 || + strcmp(w1, "data") == 0 || // Primary data file + strcmp(w1, "sdata") == 0 || // Sakray data file + strcmp(w1, "adata") == 0) // Alpha version data file + // increment if successfully loaded + result += (grfio_add(w2) == 0); + else if(strcmp(w1,"data_dir") == 0) // Data directory + strcpy(data_dir, w2); + } + + fclose(data_conf); + ShowStatus("Done reading '"CL_WHITE"%s"CL_RESET"'.\n", fname); + } // end of reading grf-files.txt + + if (result == 0) { + ShowInfo("No grf's loaded.. using default data directory\n"); + //exit(1); // It ends, if a resource cannot read one. + } + + // initialise Resnametable + grfio_resnameinit(); + + return; +} diff --git a/src/common/grfio.h b/src/common/grfio.h new file mode 100644 index 000000000..a7faafc1c --- /dev/null +++ b/src/common/grfio.h @@ -0,0 +1,21 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _GRFIO_H_ +#define _GRFIO_H_ + +void grfio_init(char*); // GRFIO Initialize +void grfio_final(void); // GRFIO Finalize +int grfio_add(char*); // GRFIO Resource file add +void* grfio_read(char*); // GRFIO data file read +void* grfio_reads(char*,int*); // GRFIO data file read & size get +int grfio_size(char*); // GRFIO data file size get +unsigned long grfio_crc32(const char *buf, unsigned int len); + +int decode_zip(unsigned char *dest, unsigned long* destLen, const unsigned char* source, unsigned long sourceLen); +int encode_zip(unsigned char *dest, unsigned long* destLen, const unsigned char* source, unsigned long sourceLen); +int decode_file (FILE *source, FILE *dest); + +int deflate_file (const char *source, const char *filename); + +#endif // _GRFIO_H_ diff --git a/src/common/lock.c b/src/common/lock.c new file mode 100644 index 000000000..c7bf623e5 --- /dev/null +++ b/src/common/lock.c @@ -0,0 +1,71 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#ifndef WIN32 +#include +#else +#include +#define F_OK 0x0 +#define R_OK 0x4 +#endif +#include "lock.h" +#include "showmsg.h" + +#ifndef _WIN32 + #define exists(filename) (!access(filename, F_OK)) +#else +// could be speed up maybe? +int exists(char *file) { + FILE *fp; + if ((fp = fopen(file,"r")) && fclose(fp) == 0) return 1; + return 0; +} +#endif + +// 書き込みファイルの保護処理 +// (書き込みが終わるまで、旧ファイルを保管しておく) + +// 新しいファイルの書き込み開始 +FILE* lock_fopen (const char* filename, int *info) { + char newfile[512]; + FILE *fp; + int no = 0; + + // 安全なファイル名を得る(手抜き) + do { + sprintf(newfile, "%s_%04d.tmp", filename, ++no); + } while((fp = fopen(newfile,"r")) && (fclose(fp), no < 9999)); + *info = no; + return fopen(newfile,"w"); +} + +// 旧ファイルを削除&新ファイルをリネーム +int lock_fclose (FILE *fp, const char* filename, int *info) { + int ret = 1; + char newfile[512]; + char oldfile[512]; + if (fp != NULL) { + ret = fclose(fp); + sprintf(newfile, "%s_%04d.tmp", filename, *info); + sprintf(oldfile, "%s.bak", filename); // old backup file + + if (exists(oldfile)) remove(oldfile); // remove backup file if it already exists + rename (filename, oldfile); // backup our older data instead of deleting it + + // このタイミングで落ちると最悪。 + if ((ret = rename(newfile,filename)) != 0) { // rename our temporary file to its correct name +#if defined(__NETBSD__) || defined(_WIN32) || defined(sun) || defined (_sun) || defined (__sun__) + ShowError("%s - '"CL_WHITE"%s"CL_RESET"'\n", strerror(errno), newfile); +#else + char ebuf[255]; + ShowError("%s - '"CL_WHITE"%s"CL_RESET"'\n", strerror_r(errno, ebuf, sizeof(ebuf)), newfile); +#endif + } + } + + return ret; +} + diff --git a/src/common/lock.h b/src/common/lock.h new file mode 100644 index 000000000..5c846eb73 --- /dev/null +++ b/src/common/lock.h @@ -0,0 +1,11 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _LOCK_H_ +#define _LOCK_H_ + +FILE* lock_fopen(const char* filename,int *info); +int lock_fclose(FILE *fp,const char* filename,int *info); + +#endif + diff --git a/src/common/malloc.c b/src/common/malloc.c new file mode 100644 index 000000000..1e3af2d40 --- /dev/null +++ b/src/common/malloc.c @@ -0,0 +1,715 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#include "malloc.h" +#include "../common/core.h" +#include "../common/showmsg.h" + +#ifdef MINICORE + #undef LOG_MEMMGR +#endif + +void* aMalloc_ (size_t size, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + void *ret = MALLOC(size); +#else + void *ret = mwMalloc(size, file, line); +#endif + // ShowMessage("%s:%d: in func %s: malloc %d\n",file,line,func,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: malloc error out of memory!\n",file,line,func); + exit(1); + } + + return ret; +} +void* aMallocA_ (size_t size, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + void *ret = MALLOCA(size); +#else + void *ret = mwMalloc(size, file, line); +#endif + // ShowMessage("%s:%d: in func %s: malloc %d\n",file,line,func,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: malloc error out of memory!\n",file,line,func); + exit(1); + } + + return ret; +} +void* aCalloc_ (size_t num, size_t size, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + void *ret = CALLOC(num, size); +#else + void *ret = mwCalloc(num, size, file, line); +#endif + // ShowMessage("%s:%d: in func %s: calloc %d %d\n",file,line,func,num,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: calloc error out of memory!\n", file, line, func); + exit(1); + } + return ret; +} +void* aCallocA_ (size_t num, size_t size, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + void *ret = CALLOCA(num, size); +#else + void *ret = mwCalloc(num, size, file, line); +#endif + // ShowMessage("%s:%d: in func %s: calloc %d %d\n",file,line,func,num,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: calloc error out of memory!\n",file,line,func); + exit(1); + } + return ret; +} +void* aRealloc_ (void *p, size_t size, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + void *ret = REALLOC(p, size); +#else + void *ret = mwRealloc(p, size, file, line); +#endif + // ShowMessage("%s:%d: in func %s: realloc %p %d\n",file,line,func,p,size); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: realloc error out of memory!\n",file,line,func); + exit(1); + } + return ret; +} +char* aStrdup_ (const char *p, const char *file, int line, const char *func) +{ +#ifndef MEMWATCH + char *ret = STRDUP(p); +#else + char *ret = mwStrdup(p, file, line); +#endif + // ShowMessage("%s:%d: in func %s: strdup %p\n",file,line,func,p); + if (ret == NULL){ + ShowFatalError("%s:%d: in func %s: strdup error out of memory!\n", file, line, func); + exit(1); + } + return ret; +} +void aFree_ (void *p, const char *file, int line, const char *func) +{ + // ShowMessage("%s:%d: in func %s: free %p\n",file,line,func,p); + if (p) + #ifndef MEMWATCH + FREE(p); + #else + mwFree(p, file, line); + #endif + + p = NULL; +} + +#ifdef GCOLLECT + +void* _bcallocA(size_t size, size_t cnt) +{ + void *ret = MALLOCA(size * cnt); + if (ret) memset(ret, 0, size * cnt); + return ret; +} +void* _bcalloc(size_t size, size_t cnt) +{ + void *ret = MALLOC(size * cnt); + if (ret) memset(ret, 0, size * cnt); + return ret; +} +char* _bstrdup(const char *chr) +{ + int len = strlen(chr); + char *ret = (char*)MALLOC(len + 1); + if (ret) memcpy(ret, chr, len + 1); + return ret; +} + +#endif + +#ifdef USE_MEMMGR + +/* USE_MEMMGR */ + +/* + * メモリマネージャ + * malloc , free の処理を効率的に出来るようにしたもの。 + * 複雑な処理を行っているので、若干重くなるかもしれません。 + * + * データ構造など(説明下手ですいません^^; ) + * ・メモリを複数の「ブロック」に分けて、さらにブロックを複数の「ユニット」 + * に分けています。ユニットのサイズは、1ブロックの容量を複数個に均等配分 + * したものです。たとえば、1ユニット32KBの場合、ブロック1つは32Byteのユ + * ニットが、1024個集まって出来ていたり、64Byteのユニットが 512個集まって + * 出来ていたりします。(padding,unit_head を除く) + * + * ・ブロック同士はリンクリスト(block_prev,block_next) でつながり、同じサイ + * ズを持つブロック同士もリンクリスト(samesize_prev,samesize_nect) でつな + * がっています。それにより、不要となったメモリの再利用が効率的に行えます。 + */ + +/* ブロックに入るデータ量 */ +#define BLOCK_DATA_SIZE 80*1024 + +/* 一度に確保するブロックの数。 */ +#define BLOCK_ALLOC 32 + +/* ブロックのアライメント */ +#define BLOCK_ALIGNMENT 64 + +/* ブロック */ +struct block { + int block_no; /* ブロック番号 */ + struct block* block_prev; /* 前に確保した領域 */ + struct block* block_next; /* 次に確保した領域 */ + int samesize_no; /* 同じサイズの番号 */ + struct block* samesize_prev; /* 同じサイズの前の領域 */ + struct block* samesize_next; /* 同じサイズの次の領域 */ + size_t unit_size; /* ユニットのバイト数 0=未使用 */ + size_t unit_hash; /* ユニットのハッシュ */ + int unit_count; /* ユニットの数 */ + int unit_used; /* 使用済みユニット */ + char data[BLOCK_DATA_SIZE]; +}; + +struct unit_head { + struct block* block; + size_t size; + const char* file; + int line; + unsigned int checksum; +}; + +struct chunk { + char *block; + struct chunk *next; +}; + +static struct block* block_first = NULL; +static struct block* block_last = NULL; +static struct block* block_unused = NULL; + +/* ユニットへのハッシュ。80KB/64Byte = 1280個 */ +static struct block* unit_first[BLOCK_DATA_SIZE/BLOCK_ALIGNMENT]; /* 最初 */ +static struct block* unit_unfill[BLOCK_DATA_SIZE/BLOCK_ALIGNMENT]; /* 埋まってない */ +static struct block* unit_last[BLOCK_DATA_SIZE/BLOCK_ALIGNMENT]; /* 最後 */ + +/* メモリを使い回せない領域用のデータ */ +struct unit_head_large { + struct unit_head_large* prev; + struct unit_head_large* next; + struct unit_head unit_head; +}; +static struct unit_head_large *unit_head_large_first = NULL; + +static struct chunk *chunk_first = NULL; + +static struct block* block_malloc(void); +static void block_free(struct block* p); +static void memmgr_info(void); +static unsigned int memmgr_usage_bytes = 0; + +void* _mmalloc(size_t size, const char *file, int line, const char *func ) { + int i; + struct block *block; + size_t size_hash; + + if (((long) size) < 0) { + printf("_mmalloc: %d\n", size); + return 0; + } + + size_hash = (size+BLOCK_ALIGNMENT-1) / BLOCK_ALIGNMENT; + if(size == 0) { + return NULL; + } + memmgr_usage_bytes += size; + + /* ブロック長を超える領域の確保には、malloc() を用いる */ + /* その際、unit_head.block に NULL を代入して区別する */ + if(size_hash * BLOCK_ALIGNMENT > BLOCK_DATA_SIZE - sizeof(struct unit_head)) { +#ifdef MEMWATCH + struct unit_head_large* p = (struct unit_head_large*)mwMalloc(sizeof(struct unit_head_large) + size,file,line); +#else + struct unit_head_large* p = (struct unit_head_large*) MALLOC (sizeof(struct unit_head_large) + size); +#endif + if(p != NULL) { + p->unit_head.block = NULL; + p->unit_head.size = size; + p->unit_head.file = file; + p->unit_head.line = line; + p->prev = NULL; + if (unit_head_large_first == NULL) + p->next = NULL; + else { + unit_head_large_first->prev = p; + p->next = unit_head_large_first; + } + unit_head_large_first = p; + *(int*)((char*)p + sizeof(struct unit_head_large) - sizeof(int) + size) = 0xdeadbeaf; + return (char *)p + sizeof(struct unit_head_large) - sizeof(int); + } else { + ShowFatalError("Memory manager::memmgr_alloc failed.\n"); + exit(1); + } + } + + /* 同一サイズのブロックが確保されていない時、新たに確保する */ + if(unit_unfill[size_hash] == NULL) { + block = block_malloc(); + if(unit_first[size_hash] == NULL) { + /* 初回確保 */ + unit_first[size_hash] = block; + unit_last[size_hash] = block; + block->samesize_no = 0; + block->samesize_prev = NULL; + block->samesize_next = NULL; + } else { + /* 連結作業 */ + unit_last[size_hash]->samesize_next = block; + block->samesize_no = unit_last[size_hash]->samesize_no + 1; + block->samesize_prev = unit_last[size_hash]; + block->samesize_next = NULL; + unit_last[size_hash] = block; + } + unit_unfill[size_hash] = block; + block->unit_size = size_hash * BLOCK_ALIGNMENT + sizeof(struct unit_head); + block->unit_count = (int)(BLOCK_DATA_SIZE / block->unit_size); + block->unit_used = 0; + block->unit_hash = size_hash; + /* 未使用Flagを立てる */ + for(i=0;iunit_count;i++) { + ((struct unit_head*)(&block->data[block->unit_size * i]))->block = NULL; + } + } + /* ユニット使用個数加算 */ + block = unit_unfill[size_hash]; + block->unit_used++; + + /* ユニット内を全て使い果たした */ + if(block->unit_count == block->unit_used) { + do { + unit_unfill[size_hash] = unit_unfill[size_hash]->samesize_next; + } while( + unit_unfill[size_hash] != NULL && + unit_unfill[size_hash]->unit_count == unit_unfill[size_hash]->unit_used + ); + } + + /* ブロックの中の空きユニット捜索 */ + for(i=0;iunit_count;i++) { + struct unit_head *head = (struct unit_head*)(&block->data[block->unit_size * i]); + if(head->block == NULL) { + head->block = block; + head->size = size; + head->line = line; + head->file = file; + *(int*)((char*)head + sizeof(struct unit_head) - sizeof(int) + size) = 0xdeadbeaf; + return (char *)head + sizeof(struct unit_head) - sizeof(int); + } + } + // ここに来てはいけない。 + ShowFatalError("Memory manager::memmgr_malloc() serious error.\n"); + memmgr_info(); + exit(1); + return NULL; +}; + +void* _mcalloc(size_t num, size_t size, const char *file, int line, const char *func ) { + void *p = _mmalloc(num * size,file,line,func); + memset(p,0,num * size); + return p; +} + +void* _mrealloc(void *memblock, size_t size, const char *file, int line, const char *func ) { + size_t old_size; + if(memblock == NULL) { + return _mmalloc(size,file,line,func); + } + + old_size = ((struct unit_head *)((char *)memblock - sizeof(struct unit_head) + sizeof(int)))->size; + if(old_size > size) { + // サイズ縮小 -> そのまま返す(手抜き) + return memblock; + } else { + // サイズ拡大 + void *p = _mmalloc(size,file,line,func); + if(p != NULL) { + memcpy(p,memblock,old_size); + } + _mfree(memblock,file,line,func); + return p; + } +} + +char* _mstrdup(const char *p, const char *file, int line, const char *func ) { + if(p == NULL) { + return NULL; + } else { + size_t len = strlen(p); + char *string = (char *)_mmalloc(len + 1,file,line,func); + memcpy(string,p,len+1); + return string; + } +} + +void _mfree(void *ptr, const char *file, int line, const char *func ) { + struct unit_head *head; + size_t size_hash; + + if (ptr == NULL) + return; + + head = (struct unit_head *)((char *)ptr - sizeof(struct unit_head) + sizeof(int)); + size_hash = (head->size+BLOCK_ALIGNMENT-1) / BLOCK_ALIGNMENT; + + if(head->block == NULL) { + if(size_hash * BLOCK_ALIGNMENT > BLOCK_DATA_SIZE - sizeof(struct unit_head)) { + /* malloc() で直に確保された領域 */ + struct unit_head_large *head_large = (struct unit_head_large *)((char *)ptr - sizeof(struct unit_head_large) + sizeof(int)); + if( + *(int*)((char*)head_large + sizeof(struct unit_head_large) - sizeof(int) + head->size) + != 0xdeadbeaf) + { + ShowError("Memory manager: args of aFree is overflowed pointer %s line %d\n", file, line); + } + if(head_large->prev) { + head_large->prev->next = head_large->next; + } else { + unit_head_large_first = head_large->next; + } + if(head_large->next) { + head_large->next->prev = head_large->prev; + } + head->block = NULL; + memmgr_usage_bytes -= head->size; + FREE (head_large); + } else { + ShowError("Memory manager: args of aFree is freed pointer %s line %d\n", file, line); + } + ptr = NULL; + return; + } else { + /* ユニット解放 */ + struct block *block = head->block; + if((unsigned long)block % sizeof(struct block) != 0) { + ShowError("Memory manager: args of aFree is not valid pointer %s line %d\n", file, line); + } else if(*(int*)((char*)head + sizeof(struct unit_head) - sizeof(int) + head->size) != 0xdeadbeaf) { + ShowError("Memory manager: args of aFree is overflowed pointer %s line %d\n", file, line); + } else { + head->block = NULL; + memmgr_usage_bytes -= head->size; + if(--block->unit_used == 0) { + /* ブロックの解放 */ + if(unit_unfill[block->unit_hash] == block) { + /* 空きユニットに指定されている */ + do { + unit_unfill[block->unit_hash] = unit_unfill[block->unit_hash]->samesize_next; + } while( + unit_unfill[block->unit_hash] != NULL && + unit_unfill[block->unit_hash]->unit_count == unit_unfill[block->unit_hash]->unit_used + ); + } + if(block->samesize_prev == NULL && block->samesize_next == NULL) { + /* 独立ブロックの解放 */ + unit_first[block->unit_hash] = NULL; + unit_last[block->unit_hash] = NULL; + unit_unfill[block->unit_hash] = NULL; + } else if(block->samesize_prev == NULL) { + /* 先頭ブロックの解放 */ + unit_first[block->unit_hash] = block->samesize_next; + (block->samesize_next)->samesize_prev = NULL; + } else if(block->samesize_next == NULL) { + /* 末端ブロックの解放 */ + unit_last[block->unit_hash] = block->samesize_prev; + (block->samesize_prev)->samesize_next = NULL; + } else { + /* 中間ブロックの解放 */ + (block->samesize_next)->samesize_prev = block->samesize_prev; + (block->samesize_prev)->samesize_next = block->samesize_next; + } + block_free(block); + } else { + /* 空きユニットの再設定 */ + if( + unit_unfill[block->unit_hash] == NULL || + unit_unfill[block->unit_hash]->samesize_no > block->samesize_no + ) { + unit_unfill[block->unit_hash] = block; + } + } + ptr = NULL; + } + } +} + +/* 現在の状況を表示する */ +static void memmgr_info(void) { + int i; + struct block *p; + ShowInfo("** Memory Manager Information **\n"); + if(block_first == NULL) { + ShowMessage("Uninitialized.\n"); + return; + } + ShowMessage( + "Blocks: %04u , BlockSize: %06u Byte , Used: %08uKB\n", + block_last->block_no+1,sizeof(struct block), + (block_last->block_no+1) * sizeof(struct block) / 1024 + ); + p = block_first; + for(i=0;i<=block_last->block_no;i++) { + ShowMessage(" Block #%04u : ",p->block_no); + if(p->unit_size == 0) { + ShowMessage("unused.\n"); + } else { + ShowMessage( + "size: %05u byte. used: %04u/%04u prev:", + p->unit_size - sizeof(struct unit_head),p->unit_used,p->unit_count + ); + if(p->samesize_prev == NULL) { + ShowMessage("NULL"); + } else { + ShowMessage("%04u",(p->samesize_prev)->block_no); + } + ShowMessage(" next:"); + if(p->samesize_next == NULL) { + ShowMessage("NULL"); + } else { + ShowMessage("%04u",(p->samesize_next)->block_no); + } + ShowMessage("\n"); + } + p = p->block_next; + } +} + +/* ブロックを確保する */ +static struct block* block_malloc(void) { + if(block_unused != NULL) { + /* ブロック用の領域は確保済み */ + struct block* ret = block_unused; + do { + block_unused = block_unused->block_next; + } while(block_unused != NULL && block_unused->unit_size != 0); + return ret; + } else { + /* ブロック用の領域を新たに確保する */ + int i; + int block_no; + struct block* p; + struct chunk* chunk; + char *pb = (char *) CALLOC (sizeof(struct block),BLOCK_ALLOC + 1); + if(pb == NULL) { + ShowFatalError("Memory manager::block_alloc failed.\n"); + exit(1); + } + + // store original block address in chunk + chunk = (struct chunk *) MALLOC (sizeof(struct chunk)); + if (chunk == NULL) { + ShowFatalError("Memory manager::block_alloc failed.\n"); + exit(1); + } + chunk->block = pb; + chunk->next = (chunk_first) ? chunk_first : NULL; + chunk_first = chunk; + + // ブロックのポインタの先頭をsizeof(block) アライメントに揃える + // このアドレスをfree() することはないので、直接ポインタを変更している。 + pb += sizeof(struct block) - ((unsigned long)pb % sizeof(struct block)); + p = (struct block*)pb; + if(block_first == NULL) { + /* 初回確保 */ + block_no = 0; + block_first = p; + } else { + block_no = block_last->block_no + 1; + block_last->block_next = p; + p->block_prev = block_last; + } + block_last = &p[BLOCK_ALLOC - 1]; + /* ブロックを連結させる */ + for(i=0;iunit_size = 1; + return p; + } +} + +static void block_free(struct block* p) { + /* free() せずに、未使用フラグを付けるだけ */ + p->unit_size = 0; + /* 未使用ポインターを更新する */ + if(block_unused == NULL) { + block_unused = p; + } else if(block_unused->block_no > p->block_no) { + block_unused = p; + } +} + +unsigned int memmgr_usage (void) +{ + return memmgr_usage_bytes / 1024; +} + +#ifdef LOG_MEMMGR +static char memmer_logfile[128]; +static FILE *log_fp; + +static void memmgr_log (char *buf) +{ + if (!log_fp) { + log_fp = fopen(memmer_logfile,"w"); + if (!log_fp) log_fp = stdout; + fprintf(log_fp, "Memory manager: Memory leaks found.\n"); + } + fprintf(log_fp, buf); + return; +} +#endif + +static void memmgr_final (void) +{ + struct block *block = block_first; + struct chunk *chunk = chunk_first, *chunk2; + struct unit_head_large *large = unit_head_large_first, *large2; + int i; + +#ifdef LOG_MEMMGR + int count = 0; + char buf[128]; +#endif + + while (block) { + if (block->unit_size) { + for (i = 0; i < block->unit_count; i++) { + struct unit_head *head = (struct unit_head*)(&block->data[block->unit_size * i]); + if(head->block != NULL) + { + #ifdef LOG_MEMMGR + sprintf (buf, + "%04d : %s line %d size %d\n", ++count, + head->file, head->line, head->size); + memmgr_log (buf); + #endif + // get block pointer and free it [celest] + _mfree ((char *)head + sizeof(struct unit_head) - sizeof(int), ALC_MARK); + } + } + } + //if (block->block_no >= block2->block_no + BLOCK_ALLOC - 1) { + // reached a new block array + //block = block->block_next; + + /* Okay wise guys... this is how block2 was allocated: [Skotlex] + struct block* p; + char *pb = (char *) CALLOC (sizeof(struct block),BLOCK_ALLOC + 1); + pb += sizeof(struct block) - ((unsigned long)pb % sizeof(struct block)); + p = (struct block*)pb; + + The reason we get an invalid pointer is that we allocated pb, not p. + So how do you get pb when you only have p? + The answer is, you can't, because the original pointer was lost when + memory-aligning the block. So we either forget this FREE or use a + self-reference... + Since we are already quitting, it might be ok to just not free the block + as it is. + */ + // didn't realise that before o.o -- block chunks are now freed below [celest] + // FREE(block2); + //block2 = block; + //continue; + //} + block = block->block_next; + } + + // free the allocated block chunks + chunk = chunk_first; + while (chunk) { + chunk2 = chunk->next; + FREE(chunk->block); + FREE(chunk); + chunk = chunk2; + } + + while(large) { + large2 = large->next; + #ifdef LOG_MEMMGR + sprintf (buf, + "%04d : %s line %d size %d\n", ++count, + large->unit_head.file, large->unit_head.line, large->unit_head.size); + memmgr_log (buf); + #endif + FREE (large); + large = large2; + } +#ifdef LOG_MEMMGR + if(count == 0) { + ShowInfo("Memory manager: No memory leaks found.\n"); + } else { + ShowWarning("Memory manager: Memory leaks found and fixed.\n"); + fclose(log_fp); + } +#endif + return; +} + +static void memmgr_init (void) +{ + #ifdef LOG_MEMMGR + sprintf(memmer_logfile, "log/%s.leaks", SERVER_NAME); + ShowStatus("Memory manager initialised: "CL_WHITE"%s"CL_RESET"\n", memmer_logfile); + #endif + return; +} +#endif + + +/*====================================== + * Initialise + *-------------------------------------- + */ + +unsigned int malloc_usage (void) +{ +#ifdef USE_MEMMGR + return memmgr_usage (); +#else + return 0; +#endif +} + +void malloc_final (void) +{ +#ifdef USE_MEMMGR + memmgr_final (); +#endif + return; +} + +void malloc_init (void) +{ +#ifdef USE_MEMMGR + memmgr_init (); +#endif + return; +} diff --git a/src/common/malloc.h b/src/common/malloc.h new file mode 100644 index 000000000..e9dbb9d44 --- /dev/null +++ b/src/common/malloc.h @@ -0,0 +1,153 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MALLOC_H_ +#define _MALLOC_H_ + +#ifndef __NETBSD__ +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#endif +#endif +#define ALC_MARK __FILE__, __LINE__, __func__ + +////////////////////////////////////////////////////////////////////// +// Whether to use Athena's built-in Memory Manager (enabled by default) +// To disable just comment the following line +#if !defined(DMALLOC) && !defined(BCHECK) + #define USE_MEMMGR +#endif +// Whether to enable Memory Manager's logging +#define LOG_MEMMGR + +#ifdef USE_MEMMGR + +# define aMalloc(n) _mmalloc(n,ALC_MARK) +# define aMallocA(n) _mmalloc(n,ALC_MARK) +# define aCalloc(m,n) _mcalloc(m,n,ALC_MARK) +# define aCallocA(m,n) _mcalloc(m,n,ALC_MARK) +# define aRealloc(p,n) _mrealloc(p,n,ALC_MARK) +# define aStrdup(p) _mstrdup(p,ALC_MARK) +# define aFree(p) _mfree(p,ALC_MARK) + + void* _mmalloc (size_t, const char *, int, const char *); + void* _mcalloc (size_t, size_t, const char *, int, const char *); + void* _mrealloc (void *, size_t, const char *, int, const char *); + char* _mstrdup (const char *, const char *, int, const char *); + void _mfree (void *, const char *, int, const char *); + +#else + +# define aMalloc(n) aMalloc_(n,ALC_MARK) +# define aMallocA(n) aMallocA_(n,ALC_MARK) +# define aCalloc(m,n) aCalloc_(m,n,ALC_MARK) +# define aCallocA(m,n) aCallocA_(m,n,ALC_MARK) +# define aRealloc(p,n) aRealloc_(p,n,ALC_MARK) +# define aStrdup(p) aStrdup_(p,ALC_MARK) +# define aFree(p) aFree_(p,ALC_MARK) + + void* aMalloc_ (size_t, const char *, int, const char *); + void* aMallocA_ (size_t, const char *, int, const char *); + void* aCalloc_ (size_t, size_t, const char *, int, const char *); + void* aCallocA_ (size_t, size_t, const char *, int, const char *); + void* aRealloc_ (void *, size_t, const char *, int, const char *); + char* aStrdup_ (const char *, const char *, int, const char *); + void aFree_ (void *, const char *, int, const char *); + +#endif + +////////////// Memory Managers ////////////////// + +#ifdef MEMWATCH + +# include "memwatch.h" +# define MALLOC(n) mwMalloc(n,__FILE__, __LINE__) +# define MALLOCA(n) mwMalloc(n,__FILE__, __LINE__) +# define CALLOC(m,n) mwCalloc(m,n,__FILE__, __LINE__) +# define CALLOCA(m,n) mwCalloc(m,n,__FILE__, __LINE__) +# define REALLOC(p,n) mwRealloc(p,n,__FILE__, __LINE__) +# define STRDUP(p) mwStrdup(p,__FILE__, __LINE__) +# define FREE(p) mwFree(p,__FILE__, __LINE__) + +#elif defined(DMALLOC) + +# include "dmalloc.h" +# define MALLOC(n) dmalloc_malloc(__FILE__, __LINE__, (n), DMALLOC_FUNC_MALLOC, 0, 0) +# define MALLOCA(n) dmalloc_malloc(__FILE__, __LINE__, (n), DMALLOC_FUNC_MALLOC, 0, 0) +# define CALLOC(m,n) dmalloc_malloc(__FILE__, __LINE__, (m)*(n), DMALLOC_FUNC_CALLOC, 0, 0) +# define CALLOCA(m,n) dmalloc_malloc(__FILE__, __LINE__, (m)*(n), DMALLOC_FUNC_CALLOC, 0, 0) +# define REALLOC(p,n) dmalloc_realloc(__FILE__, __LINE__, (p), (n), DMALLOC_FUNC_REALLOC, 0) +# define STRDUP(p) strdup(p) +# define FREE(p) free(p) + +#elif defined(GCOLLECT) + +# include "gc.h" +# define MALLOC(n) GC_MALLOC(n) +# define MALLOCA(n) GC_MALLOC_ATOMIC(n) +# define CALLOC(m,n) _bcalloc(m,n) +# define CALLOCA(m,n) _bcallocA(m,n) +# define REALLOC(p,n) GC_REALLOC(p,n) +# define STRDUP(p) _bstrdup(p) +# define FREE(p) GC_FREE(p) + + void * _bcalloc(size_t, size_t); + void * _bcallocA(size_t, size_t); + char * _bstrdup(const char *); + +#elif defined(BCHECK) + +# define MALLOC(n) malloc(n) +# define MALLOCA(n) malloc(n) +# define CALLOC(m,n) calloc(m,n) +# define CALLOCA(m,n) calloc(m,n) +# define REALLOC(p,n) realloc(p,n) +# define STRDUP(p) strdup(p) +# define FREE(p) free(p) + +#else + +# define MALLOC(n) malloc(n) +# define MALLOCA(n) malloc(n) +# define CALLOC(m,n) calloc(m,n) +# define CALLOCA(m,n) calloc(m,n) +# define REALLOC(p,n) realloc(p,n) +# define STRDUP(p) strdup(p) +# define FREE(p) free(p) + +#endif + +/////////////// Buffer Creation ///////////////// +// Full credit for this goes to Shinomori [Ajarn] + +#ifdef __GNUC__ // GCC has variable length arrays + + #define CREATE_BUFFER(name, type, size) type name[size] + #define DELETE_BUFFER(name) + +#else // others don't, so we emulate them + + #define CREATE_BUFFER(name, type, size) type *name = (type *) aCalloc (size, sizeof(type)) + #define DELETE_BUFFER(name) aFree(name) + +#endif + +////////////// Others ////////////////////////// +// should be merged with any of above later +#define CREATE(result, type, number) (result) = (type *) aCalloc ((number), sizeof(type)); + +#define CREATE_A(result, type, number) (result) = (type *) aCallocA ((number), sizeof(type)); + +#define RECREATE(result, type, number) (result) = (type *) aRealloc ((result), sizeof(type) * (number)); + +//////////////////////////////////////////////// + +unsigned int malloc_usage (void); +void malloc_init (void); +void malloc_final (void); + +#endif diff --git a/src/common/mapindex.c b/src/common/mapindex.c new file mode 100644 index 000000000..e600b5ad7 --- /dev/null +++ b/src/common/mapindex.c @@ -0,0 +1,130 @@ +#include "mmo.h" +#include +#include +#include +#include + +#define MAX_MAPINDEX 2000 + +//Leave an extra char of space to hold the terminator, in case for the strncpy(mapindex_id2name()) calls. +struct { + char name[MAP_NAME_LENGTH+1]; //Stores map name + int length; //Stores string length WITHOUT the extension for quick lookup. +} indexes[MAX_MAPINDEX]; + +static unsigned short max_index = 0; + +char mapindex_cfgfile[80] = "db/map_index.txt"; + +unsigned short mapindex_name2id(char* name) { + //TODO: Perhaps use a db to speed this up? [Skotlex] + int i; + int length = strlen(name); + char *ext = strstr(name, "."); + if (ext) + length = ext-name; //Base map-name length without the extension. + for (i = 1; i < max_index; i++) + { + if (indexes[i].length == length && strncmp(indexes[i].name,name,length)==0) + return i; + } +#ifdef MAPINDEX_AUTOADD + if (i < MAX_MAPINDEX) { + char map_name[MAP_NAME_LENGTH+5]; + length = strlen(name); + if (length > MAP_NAME_LENGTH) + return; + memcpy(map_name, name, length+1); + if ((ext = strstr(map_name, ".")) != NULL) { + length = ext-map_name; + sprintf(ext, ".gat"); + } else { //No extension? + length = strlen(map_name); + strcat(map_name, ".gat"); + } + if (length > MAP_NAME_LENGTH - 4) + return 0; //Can't be added. + strncpy(indexes[i].name, map_name, MAP_NAME_LENGTH); + indexes[i].length = strlen(map_name); + ShowDebug("mapindex_name2id: Added map \"%s\" to position %d\n", indexes[i], i); + return i; + } +#endif + ShowDebug("mapindex_name2id: Map \"%s\" not found in index list!\n", name); + return 0; +} + +char* mapindex_id2name(unsigned short id) { + if (id > MAX_MAPINDEX || !indexes[id].length) { + ShowDebug("mapindex_id2name: Requested name for non-existant map index [%d] in cache.\n", id); + return indexes[0].name; //Theorically this should never happen, hence we return this string to prevent null pointer crashes. + } + return indexes[id].name; +} + +void mapindex_init(void) { + FILE *fp; + char line[1024]; + char *ext; + int last_index = -1; + int index, length; + char map_name[1024]; + + memset (&indexes, 0, sizeof (indexes)); + fp=fopen(mapindex_cfgfile,"r"); + if(fp==NULL){ + ShowFatalError("Unable to read mapindex config file %s!\n", mapindex_cfgfile); + exit(1); //Server can't really run without this file. + } + while(fgets(line,1020,fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + switch (sscanf(line,"%1000s\t%d",map_name,&index)) { + case 1: //Map with no ID given, auto-assign + index = last_index+1; + case 2: //Map with ID given + if (index < 0 || index >= MAX_MAPINDEX) { + ShowError("(mapindex_init) Map index (%d) for \"%s\" out of range (max is %d)\n", index, map_name, MAX_MAPINDEX); + continue; + } + length = strlen(map_name); + if (length > MAP_NAME_LENGTH) { + ShowError("(mapindex_init) Map name %s is too long. Maps are limited to %d characters.\n", map_name, MAP_NAME_LENGTH); + continue; + } + if ((ext = strstr(map_name, ".gat")) != NULL) { //Gat map + length = ext-map_name; + } else if ((ext = strstr(map_name, ".afm")) != NULL || (ext = strstr(map_name, ".af2")) != NULL) { //afm map + length = ext-map_name; + sprintf(ext, ".gat"); //Change the extension to gat + } else if ((ext = strstr(map_name, ".")) != NULL) { //Generic extension? + length = ext-map_name; + sprintf(ext, ".gat"); + } else { //No extension? + length = strlen(map_name); + strcat(map_name, ".gat"); + } + if (length > MAP_NAME_LENGTH - 4) { + ShowError("(mapindex_init) Adjusted Map name %s is too long. Maps are limited to %d characters.\n", map_name, MAP_NAME_LENGTH); + continue; + } + + if (indexes[index].length) + ShowWarning("(mapindex_init) Overriding index %d: map \"%s\" -> \"%s\"\n", indexes[index].name, map_name); + + strncpy(indexes[index].name, map_name, MAP_NAME_LENGTH); + indexes[index].length = length; + if (max_index <= index) + max_index = index+1; + break; + default: + continue; + } + last_index = index; + } +} + +void mapindex_final(void) { +} + diff --git a/src/common/mapindex.h b/src/common/mapindex.h new file mode 100644 index 000000000..7e2bbe289 --- /dev/null +++ b/src/common/mapindex.h @@ -0,0 +1,37 @@ +#ifndef _MAX_INDEX_H +#define _MAX_INDEX_H +//File in charge of assigning a numberic ID to each map in existance for space saving when passing map info between servers. +extern char mapindex_cfgfile[80]; + +//whether to enable auto-adding of maps during run. Not so secure as the map indexes will vary! +#define MAPINDEX_AUTOADD + +//Some definitions for the mayor city maps. +#define MAP_PRONTERA "prontera.gat" +#define MAP_GEFFEN "geffen.gat" +#define MAP_MORROC "morocc.gat" +#define MAP_ALBERTA "alberta.gat" +#define MAP_PAYON "payon.gat" +#define MAP_IZLUDE "izlude.gat" +#define MAP_ALDEBARAN "aldebaran.gat" +#define MAP_LUTIE "xmas.gat" +#define MAP_COMODO "comodo.gat" +#define MAP_YUNO "yuno.gat" +#define MAP_AMATSU "amatsu.gat" +#define MAP_GONRYUN "gonryun.gat" +#define MAP_UMBALA "umbala.gat" +#define MAP_NIFLHEIM "niflheim.gat" +#define MAP_LOUYANG "louyang.gat" +#define MAP_JAWAII "jawaii.gat" +#define MAP_AYOTHAYA "ayothaya.gat" +#define MAP_EINBROCH "einbroch.gat" +#define MAP_LIGHTHALZEN "lighthalzen.gat" +#define MAP_EINBECH "einbech.gat" +#define MAP_HUGEL "hugel.gat" +#define MAP_JAIL "sec_pri.gat" +unsigned short mapindex_name2id(char*); +const char* mapindex_id2name(unsigned short); +void mapindex_init(void); +void mapindex_final(void); + +#endif diff --git a/src/common/mmo.h b/src/common/mmo.h new file mode 100644 index 000000000..d0d4685e2 --- /dev/null +++ b/src/common/mmo.h @@ -0,0 +1,403 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MMO_H_ +#define _MMO_H_ + +#include +#include "utils.h" // _WIN32 + +#if ! defined(Assert) +#if defined(RELEASE) +#define Assert(EX) +#else +// extern "C" { +#include +// } +#ifndef DEFCPP +#if defined(_WIN32) && !defined(MINGW) +#include +#endif +#endif +#define Assert(EX) assert(EX) +#endif +#endif /* ! defined(Assert) */ + +#ifdef CYGWIN +// txtやlogなどの書き出すファイルの改行コード +#define RETCODE "\r\n" // (CR/LF:Windows系) +#else +#define RETCODE "\n" // (LF:Unix系) +#endif + +#define RET RETCODE + +#define FIFOSIZE_SERVERLINK 256*1024 + +// set to 0 to not check IP of player between each server. +// set to another value if you want to check (1) +#define CMP_AUTHFIFO_IP 1 + +#define CMP_AUTHFIFO_LOGIN2 1 + +//Remove/Comment this line to disable sc_data saving. [Skotlex] +#define ENABLE_SC_SAVING + +#define MAX_MAP_PER_SERVER 1024 +#define MAX_INVENTORY 100 +//Number of slots carded equipment can have. Never set to less than 4 as they are also used to keep the data of forged items/equipment. [Skotlex] +//Note: The client seems unable to receive data for more than 4 slots due to all related packets having a fixed size. +#define MAX_SLOTS 4 +#define MAX_AMOUNT 30000 +#define MAX_ZENY 1000000000 +#define MAX_FAME 1000000000 +#define MAX_CART 100 +#define MAX_SKILL 1100 // Bumped to 1100 for new quest skills, will need to further increase one day... [DracoRPG] +#define GLOBAL_REG_NUM 96 +#define ACCOUNT_REG_NUM 32 +#define ACCOUNT_REG2_NUM 16 +//Should hold the max of GLOBAL/ACCOUNT/ACCOUNT2 (needed for some arrays that hold all three) +#define MAX_REG_NUM 96 +#define DEFAULT_WALK_SPEED 150 +#define MIN_WALK_SPEED 0 +#define MAX_WALK_SPEED 1000 +#define MAX_STORAGE 300 +#define MAX_GUILD_STORAGE 1000 +#define MAX_PARTY 12 +#define MAX_GUILD 16+10*6 // increased max guild members +6 per 1 extension levels [Lupus] +#define MAX_GUILDPOSITION 20 // increased max guild positions to accomodate for all members [Valaris] (removed) [PoW] +#define MAX_GUILDEXPLUSION 32 +#define MAX_GUILDALLIANCE 16 +#define MAX_GUILDSKILL 15 // increased max guild skills because of new skills [Sara-chan] +#define MAX_GUILDCASTLE 24 // increased to include novice castles [Valaris] +#define MAX_GUILDLEVEL 50 +#define MAX_GUARDIANS 8 //Local max per castle. [Skotlex] + +#define MIN_HAIR_STYLE battle_config.min_hair_style +#define MAX_HAIR_STYLE battle_config.max_hair_style +#define MIN_HAIR_COLOR battle_config.min_hair_color +#define MAX_HAIR_COLOR battle_config.max_hair_color +#define MIN_CLOTH_COLOR battle_config.min_cloth_color +#define MAX_CLOTH_COLOR battle_config.max_cloth_color + +// for produce +#define MIN_ATTRIBUTE 0 +#define MAX_ATTRIBUTE 4 +#define ATTRIBUTE_NORMAL 0 +#define MIN_STAR 0 +#define MAX_STAR 3 + +#define MIN_PORTAL_MEMO 0 +#define MAX_PORTAL_MEMO 2 + +#define MAX_STATUS_TYPE 5 + +#define WEDDING_RING_M 2634 +#define WEDDING_RING_F 2635 + +//For character names, title names, guilds, maps, etc. +//Includes null-terminator as it is the length of the array. +#define NAME_LENGTH 24 +//For item names, which tend to have much longer names. +#define ITEM_NAME_LENGTH 24 +//For Map Names, which the client considers to be 16 in length +#define MAP_NAME_LENGTH 16 + +#define MAX_FRIENDS 40 +#define MAX_MEMOPOINTS 10 + +//These max values can be exceeded and the char/map servers will update them with no problems +//These are just meant to minimize the updating needed between char/map servers as players login. +//Room for initial 10K accounts +#define DEFAULT_MAX_ACCOUNT_ID 2010000 +//Room for initial 100k characters +#define DEFAULT_MAX_CHAR_ID 250000 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +struct item { + int id; + short nameid; + short amount; + unsigned short equip; + char identify; + char refine; + char attribute; + short card[MAX_SLOTS]; +}; + +struct point{ + unsigned short map; + short x,y; +}; + +struct skill { + unsigned short id,lv,flag; +}; + +struct global_reg { + char str[32]; + char value[256]; // [zBuffer] +}; + +//For saving status changes across sessions. [Skotlex] +struct status_change_data { + unsigned short type; //SC_type + int val1, val2, val3, val4, tick; //Remaining duration. +}; + +struct s_pet { + int account_id; + int char_id; + int pet_id; + short class_; + short level; + short egg_id;//pet egg id + short equip;//pet equip name_id + short intimate;//pet friendly + short hungry;//pet hungry + char name[NAME_LENGTH]; + char rename_flag; + char incuvate; +}; + +struct friend { + int account_id; + int char_id; + char name[NAME_LENGTH]; +}; + +struct mmo_charstatus { + int char_id; + int account_id; + int partner_id; + int father; + int mother; + int child; + + int base_exp,job_exp,zeny; + + short class_; + short status_point,skill_point; + int hp,max_hp,sp,max_sp; + short option,manner; + unsigned char karma; + short hair,hair_color,clothes_color; + int party_id,guild_id,pet_id; + int fame; + + short weapon,shield; + short head_top,head_mid,head_bottom; + + char name[NAME_LENGTH]; + unsigned int base_level,job_level; + short str,agi,vit,int_,dex,luk; + unsigned char char_num,sex; + + unsigned long mapip; + unsigned int mapport; + + struct point last_point,save_point,memo_point[MAX_MEMOPOINTS]; + struct item inventory[MAX_INVENTORY],cart[MAX_CART]; + struct skill skill[MAX_SKILL]; + + struct friend friends[MAX_FRIENDS]; //New friend system [Skotlex] +}; + +struct registry { + int global_num; + struct global_reg global[GLOBAL_REG_NUM]; + int account_num; + struct global_reg account[ACCOUNT_REG_NUM]; + int account2_num; + struct global_reg account2[ACCOUNT_REG2_NUM]; +}; + +struct storage { + int dirty; + int account_id; + short storage_status; + short storage_amount; + struct item storage_[MAX_STORAGE]; +}; + +struct guild_storage { + int dirty; + int guild_id; + short storage_status; + short storage_amount; + struct item storage_[MAX_GUILD_STORAGE]; +}; + +struct map_session_data; + +struct gm_account { + int account_id; + int level; +}; + +struct party_member { + int account_id; + int char_id; + char name[NAME_LENGTH]; + struct map_session_data *sd; + unsigned short map; + unsigned short lv; + unsigned leader : 1, + online : 1; +}; + +struct party { + int party_id; + char name[NAME_LENGTH]; + unsigned exp : 1, + item : 2; //&1: Party-Share (round-robin), &2: pickup style: shared. + short itemc; //For item sharing through round-robin, holds last item receiver. + struct party_member member[MAX_PARTY]; +}; + +struct guild_member { + int account_id, char_id; + short hair,hair_color,gender,class_,lv; + int exp,exp_payper; + short online,position; + int rsv1,rsv2; + char name[NAME_LENGTH]; + struct map_session_data *sd; +}; + +struct guild_position { + char name[NAME_LENGTH]; + int mode; + int exp_mode; +}; + +struct guild_alliance { + int opposition; + int guild_id; + char name[NAME_LENGTH]; +}; + +struct guild_explusion { + char name[NAME_LENGTH]; + char mes[40]; + char acc[40]; + int account_id; + int rsv1,rsv2,rsv3; +}; + +struct guild_skill { + int id,lv; +}; + +struct guild { + int guild_id; + short guild_lv, connect_member, max_member, average_lv; + int exp,next_exp,skill_point; +#ifdef TXT_ONLY + //FIXME: Gotta remove this variable completely, but doing so screws up the format of the txt save file... + int castle_id; +#endif + char name[NAME_LENGTH],master[NAME_LENGTH]; + struct guild_member member[MAX_GUILD]; + struct guild_position position[MAX_GUILDPOSITION]; + char mes1[60],mes2[120]; + int emblem_len,emblem_id; + char emblem_data[2048]; + struct guild_alliance alliance[MAX_GUILDALLIANCE]; + struct guild_explusion explusion[MAX_GUILDEXPLUSION]; + struct guild_skill skill[MAX_GUILDSKILL]; +#ifndef TXT_ONLY + unsigned char save_flag; +#endif +}; + +struct guild_castle { + int castle_id; + char map_name[MAP_NAME_LENGTH]; + char castle_name[NAME_LENGTH]; + char castle_event[NAME_LENGTH]; + int guild_id; + int economy; + int defense; + int triggerE; + int triggerD; + int nextTime; + int payTime; + int createTime; + int visibleC; + struct { + unsigned visible : 1; + int hp; + int id; + } guardian[MAX_GUARDIANS]; //New simplified structure. [Skotlex] +}; +struct square { + int val1[5]; + int val2[5]; +}; + +struct fame_list { + int id; + int fame; + char name[NAME_LENGTH]; +}; + +enum { + GBI_EXP =1, // ギルドのEXP + GBI_GUILDLV, // ギルドのLv + GBI_SKILLPOINT, // ギルドのスキルポイント + GBI_SKILLLV, // ギルドスキルLv +}; + +enum { + GMI_POSITION =0, // メンバーの役職変更 + GMI_EXP, + GMI_HAIR, + GMI_HAIR_COLOR, + GMI_GENDER, + GMI_CLASS, + GMI_LEVEL, +}; + +enum { + GD_SKILLBASE=10000, + GD_APPROVAL=10000, + GD_KAFRACONTRACT=10001, + GD_GUARDIANRESEARCH=10002, + GD_GUARDUP=10003, + GD_EXTENSION=10004, + GD_GLORYGUILD=10005, + GD_LEADERSHIP=10006, + GD_GLORYWOUNDS=10007, + GD_SOULCOLD=10008, + GD_HAWKEYES=10009, + GD_BATTLEORDER=10010, + GD_REGENERATION=10011, + GD_RESTORE=10012, + GD_EMERGENCYCALL=10013, + GD_DEVELOPMENT=10014, +}; + +#ifndef __WIN32 + #ifndef strcmpi + #define strcmpi strcasecmp + #endif + #ifndef stricmp + #define stricmp strcasecmp + #endif + #ifndef strncmpi + #define strncmpi strncasecmp + #endif + #ifndef strnicmp + #define strnicmp strncasecmp + #endif +#else + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #ifndef strncmpi + #define strncmpi strnicmp + #endif +#endif + +#endif // _MMO_H_ diff --git a/src/common/nullpo.c b/src/common/nullpo.c new file mode 100644 index 000000000..8508f1333 --- /dev/null +++ b/src/common/nullpo.c @@ -0,0 +1,94 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#include "nullpo.h" +#include "../common/showmsg.h" +// #include "logs.h" // 布石してみる + +static void nullpo_info_core(const char *file, int line, const char *func, + const char *fmt, va_list ap); + +/*====================================== + * Nullチェック 及び 情報出力 + *-------------------------------------- + */ +int nullpo_chk_f(const char *file, int line, const char *func, const void *target, + const char *fmt, ...) +{ + va_list ap; + + if (target != NULL) + return 0; + + va_start(ap, fmt); + nullpo_info_core(file, line, func, fmt, ap); + va_end(ap); + return 1; +} + +int nullpo_chk(const char *file, int line, const char *func, const void *target) +{ + if (target != NULL) + return 0; + + nullpo_info_core(file, line, func, NULL, NULL); + return 1; +} + + +/*====================================== + * nullpo情報出力(外部呼出し向けラッパ) + *-------------------------------------- + */ +void nullpo_info_f(const char *file, int line, const char *func, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + nullpo_info_core(file, line, func, fmt, ap); + va_end(ap); +} + +void nullpo_info(const char *file, int line, const char *func) +{ + nullpo_info_core(file, line, func, NULL, NULL); +} + + +/*====================================== + * nullpo情報出力(Main) + *-------------------------------------- + */ +static void nullpo_info_core(const char *file, int line, const char *func, + const char *fmt, va_list ap) +{ + if (file == NULL) + file = "??"; + + func = + func == NULL ? "unknown": + func[0] == '\0' ? "unknown": + func; + + ShowMessage("--- nullpo info --------------------------------------------\n"); + ShowMessage("%s:%d: in func `%s'\n", file, line, func); + if (fmt != NULL) + { + if (fmt[0] != '\0') + { + vprintf(fmt, ap); + + // 最後に改行したか確認 + if (fmt[strlen(fmt)-1] != '\n') + ShowMessage("\n"); + } + } + ShowMessage("--- end nullpo info ----------------------------------------\n"); + + // ここらでnullpoログをファイルに書き出せたら + // まとめて提出できるなと思っていたり。 +} diff --git a/src/common/nullpo.h b/src/common/nullpo.h new file mode 100644 index 000000000..66d984224 --- /dev/null +++ b/src/common/nullpo.h @@ -0,0 +1,237 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _NULLPO_H_ +#define _NULLPO_H_ + + +#define NULLPO_CHECK 1 + // 全体のスイッチを宣言しているヘッダがあれば + // そこに移動していただけると + +#ifndef __NETBSD__ +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#endif +#endif + +#ifdef _WIN32 +#define __attribute__(x) /* nothing */ +#endif + + +#define NLP_MARK __FILE__, __LINE__, __func__ + +/*---------------------------------------------------------------------------- + * Macros + *---------------------------------------------------------------------------- + */ +/*====================================== + * Nullチェック 及び 情報出力後 return + *・展開するとifとかreturn等が出るので + * 一行単体で使ってください。 + *・nullpo_ret(x = func()); + * のような使用法も想定しています。 + *-------------------------------------- + * nullpo_ret(t) + * 戻り値 0固定 + * [引数] + * t チェック対象 + *-------------------------------------- + * nullpo_retv(t) + * 戻り値 なし + * [引数] + * t チェック対象 + *-------------------------------------- + * nullpo_retr(ret, t) + * 戻り値 指定 + * [引数] + * ret return(ret); + * t チェック対象 + *-------------------------------------- + * nullpo_ret_f(t, fmt, ...) + * 詳細情報出力用 + * 戻り値 0 + * [引数] + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + * nullpo_retv_f(t, fmt, ...) + * 詳細情報出力用 + * 戻り値 なし + * [引数] + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + * nullpo_retr_f(ret, t, fmt, ...) + * 詳細情報出力用 + * 戻り値 指定 + * [引数] + * ret return(ret); + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + */ + +#if NULLPO_CHECK + +#define nullpo_ret(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return(0);} + +#define nullpo_retv(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return;} + +#define nullpo_retr(ret, t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return(ret);} + +#define nullpo_retb(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {break;} + +// 可変引数マクロに関する条件コンパイル +#if __STDC_VERSION__ >= 199901L +/* C99に対応 */ +#define nullpo_ret_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return(0);} + +#define nullpo_retv_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return;} + +#define nullpo_retr_f(ret, t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return(ret);} + +#define nullpo_retb_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {break;} + +#elif __GNUC__ >= 2 +/* GCC用 */ +#define nullpo_ret_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return(0);} + +#define nullpo_retv_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return;} + +#define nullpo_retr_f(ret, t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return(ret);} + +#define nullpo_retb_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {break;} + +#else + +/* その他の場合・・・ orz */ + +#endif + +#else /* NULLPO_CHECK */ +/* No Nullpo check */ + +// if((t)){;} +// 良い方法が思いつかなかったので・・・苦肉の策です。 +// 一応ワーニングは出ないはず + +#define nullpo_ret(t) if((t)){;} +#define nullpo_retv(t) if((t)){;} +#define nullpo_retr(ret, t) if((t)){;} +#define nullpo_retb(t) if((t)){;} + +// 可変引数マクロに関する条件コンパイル +#if __STDC_VERSION__ >= 199901L +/* C99に対応 */ +#define nullpo_ret_f(t, fmt, ...) if((t)){;} +#define nullpo_retv_f(t, fmt, ...) if((t)){;} +#define nullpo_retr_f(ret, t, fmt, ...) if((t)){;} +#define nullpo_retb_f(t, fmt, ...) if((t)){;} + +#elif __GNUC__ >= 2 +/* GCC用 */ +#define nullpo_ret_f(t, fmt, args...) if((t)){;} +#define nullpo_retv_f(t, fmt, args...) if((t)){;} +#define nullpo_retr_f(ret, t, fmt, args...) if((t)){;} +#define nullpo_retb_f(t, fmt, args...) if((t)){;} + +#else +/* その他の場合・・・ orz */ +#endif + +#endif /* NULLPO_CHECK */ + +/*---------------------------------------------------------------------------- + * Functions + *---------------------------------------------------------------------------- + */ +/*====================================== + * nullpo_chk + * Nullチェック 及び 情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * target チェック対象 + * [返り値] + * 0 OK + * 1 NULL + *-------------------------------------- + */ +int nullpo_chk(const char *file, int line, const char *func, const void *target); + + +/*====================================== + * nullpo_chk_f + * Nullチェック 及び 詳細な情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * target チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + * [返り値] + * 0 OK + * 1 NULL + *-------------------------------------- + */ +int nullpo_chk_f(const char *file, int line, const char *func, const void *target, + const char *fmt, ...) + __attribute__((format(printf,5,6))); + + +/*====================================== + * nullpo_info + * nullpo情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + *-------------------------------------- + */ +void nullpo_info(const char *file, int line, const char *func); + + +/*====================================== + * nullpo_info_f + * nullpo詳細情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + */ +void nullpo_info_f(const char *file, int line, const char *func, + const char *fmt, ...) + __attribute__((format(printf,4,5))); + + +#endif diff --git a/src/common/plugin.h b/src/common/plugin.h new file mode 100644 index 000000000..2ccefb6bd --- /dev/null +++ b/src/common/plugin.h @@ -0,0 +1,40 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _PLUGIN_H_ +#define _PLUGIN_H_ + +////// Plugin functions /////////////// + +#define PLUGIN_VERSION "1.02" + +typedef struct _Plugin_Info { + char *name; + char type; + char *version; + char *req_version; + char *description; +} Plugin_Info; + +typedef struct _Plugin_Event_Table { + char *func_name; + char *event_name; +} Plugin_Event_Table; + +////// Plugin Export functions ///////////// + +#define PLUGIN_ALL 0 +#define PLUGIN_LOGIN 1 +#define PLUGIN_CHAR 2 +#define PLUGIN_MAP 8 +#define PLUGIN_CORE 16 + +#define IMPORT_SYMBOL(s,n) (s) = plugin_call_table[n] + +////// Global Plugin variables ///////////// + +#define PLUGIN_INFO struct _Plugin_Info plugin_info +#define PLUGIN_EVENTS_TABLE struct _Plugin_Event_Table plugin_event_table[] +void **plugin_call_table; + +#endif // _PLUGIN_H_ diff --git a/src/common/plugins.c b/src/common/plugins.c new file mode 100644 index 000000000..fbadca065 --- /dev/null +++ b/src/common/plugins.c @@ -0,0 +1,367 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "plugin.h" +#include "plugins.h" +#include "../common/mmo.h" +#include "../common/core.h" +#include "../common/timer.h" +#include "../common/utils.h" +#include "../common/socket.h" +#include "../common/malloc.h" +#include "../common/version.h" +#include "../common/showmsg.h" + +////////////////////////////////////////////// + +typedef struct _Plugin_Event { + void (*func)(void); + struct _Plugin_Event *next; +} Plugin_Event; + +typedef struct _Plugin_Event_List { + char *name; + struct _Plugin_Event_List *next; + struct _Plugin_Event *events; +} Plugin_Event_List; + +static int auto_search = 1; +static int load_priority = 0; +Plugin_Event_List *event_head = NULL; +Plugin *plugin_head = NULL; + +Plugin_Info default_info = { "Unknown", PLUGIN_ALL, "0", PLUGIN_VERSION, "Unknown" }; + +static size_t call_table_size = 0; +static size_t max_call_table = 0; + +////// Plugin Events Functions ////////////////// + +int register_plugin_func (char *name) +{ + Plugin_Event_List *evl; + if (name) { + evl = (Plugin_Event_List *) aMalloc(sizeof(Plugin_Event_List)); + evl->name = (char *) aMalloc (strlen(name) + 1); + + evl->next = event_head; + strcpy(evl->name, name); + evl->events = NULL; + event_head = evl; + } + return 0; +} + +Plugin_Event_List *search_plugin_func (char *name) +{ + Plugin_Event_List *evl = event_head; + while (evl) { + if (strcmpi(evl->name, name) == 0) + return evl; + evl = evl->next; + } + return NULL; +} + +int register_plugin_event (void (*func)(void), char* name) +{ + Plugin_Event_List *evl = search_plugin_func(name); + if (!evl) { + // register event if it doesn't exist already + register_plugin_func(name); + // relocate the new event list + evl = search_plugin_func(name); + } + if (evl) { + Plugin_Event *ev; + + ev = (Plugin_Event *) aMalloc(sizeof(Plugin_Event)); + ev->func = func; + ev->next = NULL; + + if (evl->events == NULL) + evl->events = ev; + else { + Plugin_Event *ev2 = evl->events; + while (ev2) { + if (ev2->next == NULL) { + ev2->next = ev; + break; + } + ev2 = ev2->next; + } + } + } + return 0; +} + +int plugin_event_trigger (char *name) +{ + int c = 0; + Plugin_Event_List *evl = search_plugin_func(name); + if (evl) { + Plugin_Event *ev = evl->events; + while (ev) { + ev->func(); + ev = ev->next; + c++; + } + } + return c; +} + +////// Plugins Call Table Functions ///////// + +int export_symbol (void *var, int offset) +{ + //printf ("0x%x\n", var); + + // add to the end of the list + if (offset < 0) + offset = call_table_size; + + // realloc if not large enough + if ((size_t)offset >= max_call_table) { + max_call_table = 1 + offset; + plugin_call_table = (void**)aRealloc(plugin_call_table, max_call_table*sizeof(void*)); + + // clear the new alloced block + memset(plugin_call_table + call_table_size, 0, (max_call_table-call_table_size)*sizeof(void*)); + } + + // the new table size is delimited by the new element at the end + if ((size_t)offset >= call_table_size) + call_table_size = offset+1; + + // put the pointer at the selected place + plugin_call_table[offset] = var; + return 0; +} + +////// Plugins Core ///////////////////////// + +Plugin *plugin_open (const char *filename) +{ + Plugin *plugin; + Plugin_Info *info; + Plugin_Event_Table *events; + void **procs; + int init_flag = 1; + + //printf ("loading %s\n", filename); + + // Check if the plugin has been loaded before + plugin = plugin_head; + while (plugin) { + // returns handle to the already loaded plugin + if (plugin->state && strcmpi(plugin->filename, filename) == 0) { + //printf ("not loaded (duplicate) : %s\n", filename); + return plugin; + } + plugin = plugin->next; + } + + plugin = (Plugin *)aCalloc(1, sizeof(Plugin)); + plugin->state = -1; // not loaded + + plugin->dll = DLL_OPEN(filename); + if (!plugin->dll) { + //printf ("not loaded (invalid file) : %s\n", filename); + plugin_unload(plugin); + return NULL; + } + + // Retrieve plugin information + plugin->state = 0; // initialising + DLL_SYM (info, plugin->dll, "plugin_info"); + // For high priority plugins (those that are explicitly loaded from the conf file) + // we'll ignore them even (could be a 3rd party dll file) + if ((!info && load_priority == 0) || + (info && ((atof(info->req_version) < atof(PLUGIN_VERSION)) || // plugin is based on older code + (info->type != PLUGIN_ALL && info->type != PLUGIN_CORE && info->type != SERVER_TYPE) || // plugin is not for this server + (info->type == PLUGIN_CORE && SERVER_TYPE != PLUGIN_LOGIN && SERVER_TYPE != PLUGIN_CHAR && SERVER_TYPE != PLUGIN_MAP)))) + { + //printf ("not loaded (incompatible) : %s\n", filename); + plugin_unload(plugin); + return NULL; + } + plugin->info = (info) ? info : &default_info; + + plugin->filename = (char *) aMalloc (strlen(filename) + 1); + strcpy(plugin->filename, filename); + + // Initialise plugin call table (For exporting procedures) + DLL_SYM (procs, plugin->dll, "plugin_call_table"); + if (procs) *procs = plugin_call_table; + + // Register plugin events + DLL_SYM (events, plugin->dll, "plugin_event_table"); + if (events) { + int i = 0; + while (events[i].func_name) { + if (strcmpi(events[i].event_name, "Plugin_Test") == 0) { + int (*test_func)(void); + DLL_SYM (test_func, plugin->dll, events[i].func_name); + if (test_func && test_func() == 0) { + // plugin has failed test, disabling + //printf ("disabled (failed test) : %s\n", filename); + init_flag = 0; + } + } else { + void (*func)(void); + DLL_SYM (func, plugin->dll, events[i].func_name); + if (func) register_plugin_event (func, events[i].event_name); + } + i++; + } + } + + plugin->next = plugin_head; + plugin_head = plugin; + + plugin->state = init_flag; // fully loaded + ShowStatus ("Done loading plugin '"CL_WHITE"%s"CL_RESET"'\n", (info) ? plugin->info->name : filename); + + return plugin; +} + +void plugin_load (const char *filename) +{ + plugin_open(filename); +} + +void plugin_unload (Plugin *plugin) +{ + if (plugin == NULL) + return; + if (plugin->filename) aFree(plugin->filename); + if (plugin->dll) DLL_CLOSE(plugin->dll); + aFree(plugin); +} + +#ifdef _WIN32 +char *DLL_ERROR(void) +{ + static char dllbuf[80]; + DWORD dw = GetLastError(); + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dw, 0, dllbuf, 80, NULL); + return dllbuf; +} +#endif + +////// Initialize/Finalize //////////////////// + +int plugins_config_read(const char *cfgName) +{ + 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, 1020, fp)) { + if(line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if (strcmpi(w1, "auto_search") == 0) { + if(strcmpi(w2, "yes")==0) + auto_search = 1; + else if(strcmpi(w2, "no")==0) + auto_search = 0; + else auto_search = atoi(w2); + } else if (strcmpi(w1, "plugin") == 0) { + char filename[128]; + sprintf (filename, "plugins/%s%s", w2, DLL_EXT); + plugin_load(filename); + } else if (strcmpi(w1, "import") == 0) + plugins_config_read(w2); + } + fclose(fp); + return 0; +} + +void plugins_init (void) +{ + char *PLUGIN_CONF_FILENAME = "conf/plugin_athena.conf"; + register_plugin_func("Plugin_Init"); + register_plugin_func("Plugin_Final"); + register_plugin_func("Athena_Init"); + register_plugin_func("Athena_Final"); + + // networking + export_symbol (func_parse_table, 18); + export_symbol (RFIFOSKIP, 17); + export_symbol (WFIFOSET, 16); + export_symbol (delete_session, 15); + export_symbol (session, 14); + export_symbol (&fd_max, 13); + export_symbol (addr_, 12); + // timers + export_symbol (get_uptime, 11); + export_symbol (delete_timer, 10); + export_symbol (add_timer_func_list, 9); + export_symbol (add_timer_interval, 8); + export_symbol (add_timer, 7); + export_symbol ((void *)get_svn_revision, 6); + export_symbol (gettick, 5); + // core + export_symbol (&runflag, 4); + export_symbol (arg_v, 3); + export_symbol (&arg_c, 2); + export_symbol (SERVER_NAME, 1); + export_symbol (&SERVER_TYPE, 0); + + load_priority = 1; + plugins_config_read (PLUGIN_CONF_FILENAME); + load_priority = 0; + + if (auto_search) + findfile("plugins", DLL_EXT, plugin_load); + + plugin_event_trigger("Plugin_Init"); + + return; +} + +void plugins_final (void) +{ + Plugin *plugin = plugin_head, *plugin2; + Plugin_Event_List *evl = event_head, *evl2; + Plugin_Event *ev, *ev2; + + plugin_event_trigger("Plugin_Final"); + + while (plugin) { + plugin2 = plugin->next; + plugin_unload(plugin); + plugin = plugin2; + } + + while (evl) { + ev = evl->events; + while (ev) { + ev2 = ev->next; + aFree(ev); + ev = ev2; + } + evl2 = evl->next; + aFree(evl->name); + aFree(evl); + evl = evl2; + } + + aFree(plugin_call_table); + + return; +} diff --git a/src/common/plugins.h b/src/common/plugins.h new file mode 100644 index 000000000..d642b5965 --- /dev/null +++ b/src/common/plugins.h @@ -0,0 +1,61 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _PLUGINS_H_ +#define _PLUGINS_H_ + +////// Dynamic Link Library functions /////////////// + +#ifdef _WIN32 + + #include + #define DLL_OPEN(x) LoadLibrary(x) + #define DLL_SYM(x,y,z) (FARPROC)(x) = GetProcAddress(y,z) + #define DLL_CLOSE(x) FreeLibrary(x) + #define DLL_EXT ".dll" + #define DLL HINSTANCE + char *DLL_ERROR(void); + +#else + + #include + #define DLL_OPEN(x) dlopen(x,RTLD_NOW) + #define DLL_SYM(x,y,z) (x) = (void *)dlsym(y,z) + #define DLL_CLOSE(x) dlclose(x) + #define DLL_ERROR dlerror + + #ifdef CYGWIN + #define DLL_EXT ".dll" + #else + #define DLL_EXT ".so" + #endif + #define DLL void * + +#endif + +////// Plugin Definitions /////////////////// + +typedef struct _Plugin { + DLL dll; + char state; + char *filename; + struct _Plugin_Info *info; + struct _Plugin *next; +} Plugin; + +///////////////////////////////////////////// + +int register_plugin_func (char *); +int register_plugin_event (void (*)(void), char *); +int plugin_event_trigger (char *); + +int export_symbol (void *, int); +#define EXPORT_SYMBOL(s) export_symbol((s), -1); + +Plugin *plugin_open (const char *); +void plugin_load (const char *); +void plugin_unload (Plugin *); +void plugins_init (void); +void plugins_final (void); + +#endif // _PLUGINS_H_ diff --git a/src/common/showmsg.c b/src/common/showmsg.c new file mode 100644 index 000000000..7d940b189 --- /dev/null +++ b/src/common/showmsg.c @@ -0,0 +1,219 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#include +#include "showmsg.h" + +#ifdef _WIN32 + #ifdef DEBUGLOGMAP + #define DEBUGLOGPATH "log\\map-server.log" + #else + #ifdef DEBUGLOGCHAR + #define DEBUGLOGPATH "log\\char-server.log" + #else + #ifdef DEBUGLOGLOGIN + #define DEBUGLOGPATH "log\\login-server.log" + #endif + #endif + #endif +#else + #ifdef DEBUGLOGMAP + #define DEBUGLOGPATH "log/map-server.log" + #else + #ifdef DEBUGLOGCHAR + #define DEBUGLOGPATH "log/char-server.log" + #else + #ifdef DEBUGLOGLOGIN + #define DEBUGLOGPATH "log/login-server.log" + #endif + #endif + #endif +#endif + +int msg_silent; //Specifies how silent the console is. +char tmp_output[1024] = {"\0"}; +char timestamp_format[20] = ""; //For displaying Timestamps +// by MC Cameri +int _vShowMessage(enum msg_type flag, const char *string, va_list ap) +{ + // _ShowMessage MUST be used instead of printf as of 10/24/2004. + // Return: 0 = Successful, 1 = Failed. +// int ret = 0; + char prefix[100]; +#if defined(DEBUGLOGMAP) || defined(DEBUGLOGCHAR) || defined(DEBUGLOGLOGIN) + FILE *fp; +#endif + + if (!string || strlen(string) <= 0) { + ShowError("Empty string passed to _vShowMessage().\n"); + return 1; + } + + if (timestamp_format) + { //Display time format. [Skotlex] + time_t t = time(NULL); + strftime(prefix, 80, timestamp_format, localtime(&t)); + } else prefix[0]='\0'; + + + switch (flag) { + case MSG_NONE: // direct printf replacement + break; + case MSG_STATUS: //Bright Green (To inform about good things) + strcat(prefix,CL_GREEN"[Status]"CL_RESET":"); + break; + case MSG_SQL: //Bright Violet (For dumping out anything related with SQL) <- Actually, this is mostly used for SQL errors with the database, as successes can as well just be anything else... [Skotlex] + strcat(prefix,CL_MAGENTA"[SQL]"CL_RESET":"); + break; + case MSG_INFORMATION: //Bright White (Variable information) + strcat(prefix,CL_WHITE"[Info]"CL_RESET":"); + break; + case MSG_NOTICE: //Bright White (Less than a warning) + strcat(prefix,CL_WHITE"[Notice]"CL_RESET":"); + break; + case MSG_WARNING: //Bright Yellow + strcat(prefix,CL_YELLOW"[Warning]"CL_RESET":"); + break; + case MSG_DEBUG: //Bright Cyan, important stuff! + strcat(prefix,CL_CYAN"[Debug]"CL_RESET":"); + break; + case MSG_ERROR: //Bright Red (Regular errors) + strcat(prefix,CL_RED"[Error]"CL_RESET":"); + break; + case MSG_FATALERROR: //Bright Red (Fatal errors, abort(); if possible) + strcat(prefix,CL_RED"[Fatal Error]"CL_RESET":"); + break; + default: + ShowError("In function _vShowMessage() -> Invalid flag passed.\n"); + return 1; + } + + if ((flag == MSG_DEBUG && !SHOW_DEBUG_MSG) || + (flag == MSG_INFORMATION && msg_silent&1) || + (flag == MSG_STATUS && msg_silent&2) || + (flag == MSG_NOTICE && msg_silent&4) || + (flag == MSG_WARNING && msg_silent&8) || + (flag == MSG_ERROR && msg_silent&16) || + (flag == MSG_SQL && msg_silent&16) + ) ; //Do not print it. + else { + if (flag == MSG_ERROR || flag == MSG_FATALERROR || flag == MSG_SQL) + { //Send Errors to StdErr [Skotlex] + fprintf (stderr, "%s ", prefix); + vfprintf (stderr, string, ap); + fflush (stderr); + } else { + if (flag != MSG_NONE) + printf ("%s ", prefix); + vprintf (string, ap); + fflush (stdout); + } + } + +#if defined(DEBUGLOGMAP) || defined(DEBUGLOGCHAR) || defined(DEBUGLOGLOGIN) + if(strlen(DEBUGLOGPATH) > 0) { + fp=fopen(DEBUGLOGPATH,"a"); + if (fp == NULL) { + printf(CL_RED"[ERROR]"CL_RESET": Could not open '"CL_WHITE"%s"CL_RESET"', access denied.\n",DEBUGLOGPATH); + fflush(stdout); + return 0; + } + fprintf(fp,"%s ", prefix); + vfprintf(fp,string,ap); + fclose(fp); + } else { + printf(CL_RED"[ERROR]"CL_RESET": DEBUGLOGPATH not defined!\n"); + } +#endif + + va_end(ap); +/* + if ((core_config.debug_output_level > -1) && (flag >= core_config.debug_output_level)) { + FILE *fp; + fp=fopen(OUTPUT_MESSAGES_LOG,"a"); + if (fp == NULL) { + ShowError("Could not open '"CL_WHITE"%s"CL_RESET"', file not found.\n",OUTPUT_MESSAGES_LOG); + fflush(stdout); + return; + } + StripColor(output); + strcpy(output,"\r"); + fwrite(output,strlen(output),1,fp); + fclose(fp); + } +*/ + return 0; +} + +void ClearScreen(void) +{ +#ifndef _WIN32 + ShowMessage(CL_CLS); // to prevent empty string passed messages +#endif +} +int _ShowMessage(enum msg_type flag, const char *string, ...) +{ + va_list ap; + + va_start(ap, string); + return _vShowMessage(flag, string, ap); +} + +// direct printf replacement +int ShowMessage(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_NONE, string, ap); +} +int ShowStatus(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_STATUS, string, ap); +} +int ShowSQL(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_SQL, string, ap); +} +int ShowInfo(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_INFORMATION, string, ap); +} +int ShowNotice(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_NOTICE, string, ap); +} +int ShowWarning(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_WARNING, string, ap); +} +int ShowDebug(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_DEBUG, string, ap); +} +int ShowError(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_ERROR, string, ap); +} +int ShowFatalError(const char *string, ...) { + va_list ap; + + va_start(ap, string); + return _vShowMessage(MSG_FATALERROR, string, ap); +} diff --git a/src/common/showmsg.h b/src/common/showmsg.h new file mode 100644 index 000000000..af851de40 --- /dev/null +++ b/src/common/showmsg.h @@ -0,0 +1,88 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _SHOWMSG_H_ +#define _SHOWMSG_H_ + +#define SHOW_DEBUG_MSG 1 + +// for help with the console colors look here: +// http://www.edoceo.com/liberum/?doc=printf-with-color +// some code explanation (used here): +// \033[2J : clear screen and go up/left (0, 0 position) +// \033[K : clear line from actual position to end of the line +// \033[0m : reset color parameter +// \033[1m : use bold for font + +#ifdef _WIN32 + #define CL_RESET "" + #define CL_CLS "" + #define CL_CLL "" + #define CL_BOLD "" + #define CL_NORMAL CL_RESET + #define CL_NONE CL_RESET + #define CL_WHITE "" + #define CL_GRAY "" + #define CL_RED "" + #define CL_GREEN "" + #define CL_YELLOW "" + #define CL_BLUE "" + #define CL_MAGENTA "" + #define CL_CYAN "" + #define CL_BT_YELLOW "" + #define CL_WTBL "" + #define CL_XXBL "" + #define CL_PASS "" +#else + #define CL_RESET "\033[0;0m" + #define CL_CLS "\033[2J" + #define CL_CLL "\033[K" + + // font settings + #define CL_BOLD "\033[1m" + #define CL_NORMAL CL_RESET + #define CL_NONE CL_RESET + + #define CL_WHITE "\033[1;29m" + #define CL_GRAY "\033[1;30m" + #define CL_RED "\033[1;31m" + #define CL_GREEN "\033[1;32m" + #define CL_YELLOW "\033[1;33m" + #define CL_BLUE "\033[1;34m" + #define CL_MAGENTA "\033[1;35m" + #define CL_CYAN "\033[1;36m" + + #define CL_BT_YELLOW "\033[1;33m" + #define CL_WTBL "\033[37;44m" // white on blue + #define CL_XXBL "\033[0;44m" // default on blue + #define CL_PASS "\033[0;32;42m" // green on green +#endif + +extern int msg_silent; //Specifies how silent the console is. [Skotlex] +extern char timestamp_format[20]; //For displaying Timestamps [Skotlex] +extern char tmp_output[1024]; + +enum msg_type { + MSG_NONE, + MSG_STATUS, + MSG_SQL, + MSG_INFORMATION, + MSG_NOTICE, + MSG_WARNING, + MSG_DEBUG, + MSG_ERROR, + MSG_FATALERROR +}; + +extern void ClearScreen(void); +extern int ShowMessage(const char *, ...); +extern int ShowStatus(const char *, ...); +extern int ShowSQL(const char *, ...); +extern int ShowInfo(const char *, ...); +extern int ShowNotice(const char *, ...); +extern int ShowWarning(const char *, ...); +extern int ShowDebug(const char *, ...); +extern int ShowError(const char *, ...); +extern int ShowFatalError(const char *, ...); + +#endif diff --git a/src/common/socket.c b/src/common/socket.c new file mode 100644 index 000000000..2558e83ac --- /dev/null +++ b/src/common/socket.c @@ -0,0 +1,1390 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#include + +#ifdef __WIN32 +#define __USE_W32_SOCKETS +#include +#include +typedef int socklen_t; +#else +#include +#include +#include +#include +#include +#include +#include + +#ifndef SIOCGIFCONF +#include // SIOCGIFCONF on Solaris, maybe others? [Shinomori] +#endif + +#endif + +#include +#include + +#include "../common/socket.h" +#include "../common/mmo.h" // [Valaris] thanks to fov +#include "../common/timer.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" + +fd_set readfds; +int fd_max; +time_t last_tick; +time_t stall_time = 60; +int ip_rules = 1; + +// reuse port +#ifndef SO_REUSEPORT + #define SO_REUSEPORT 15 +#endif + +// values derived from freya +// a player that send more than 2k is probably a hacker without be parsed +// biggest known packet: S 0153 .w .?B -> 24x24 256 color .bmp (0153 + len.w + 1618/1654/1756 bytes) +size_t rfifo_size = (16*1024); +size_t wfifo_size = (16*1024); + +#ifndef TCP_FRAME_LEN +#define TCP_FRAME_LEN 1053 +#endif + +#define CONVIP(ip) ip&0xFF,(ip>>8)&0xFF,(ip>>16)&0xFF,ip>>24 + +struct socket_data *session[FD_SETSIZE]; + +static int null_parse(int fd); +static int (*default_func_parse)(int) = null_parse; + +static int null_console_parse(char *buf); +static int (*default_console_parse)(char*) = null_console_parse; +#ifndef MINICORE +static int connect_check(unsigned int ip); +#else + #define connect_check(n) 1 +#endif + +/*====================================== + * CORE : Set function + *-------------------------------------- + */ +void set_defaultparse(int (*defaultparse)(int)) +{ + default_func_parse = defaultparse; +} + +void set_nonblocking(int fd, int yes) { + setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); +} + +static void setsocketopts(int fd) +{ + int yes = 1; // reuse fix + size_t buff; + size_t buff_size = sizeof (buff); + + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof yes); +#ifdef SO_REUSEPORT + setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&yes,sizeof yes); +#endif + setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); + +#ifdef __WIN32 +{ //set SO_LINGER option (from Freya) + //(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winsock/winsock/closesocket_2.asp) + struct linger opt; + opt.l_onoff = 1; + opt.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char*)&opt, sizeof(opt))) + ShowWarning("setsocketopts: Unable to set SO_LINGER mode for connection %d!\n",fd); +} +#endif + + setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&wfifo_size , sizeof(wfifo_size )); + if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&buff, &buff_size) == 0) + { + if (buff < wfifo_size) //We are not going to complain if we get more, aight? [Skotlex] + ShowError("setsocketopts: Requested send buffer size failed (requested %d bytes buffer, received a buffer of size %d)\n", wfifo_size, buff); + } + else + perror("setsocketopts: getsockopt wfifo"); + setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *) &rfifo_size , sizeof(rfifo_size )); + if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *) &buff, &buff_size) == 0) + { + if (buff < rfifo_size) + ShowError("setsocketopts: Requested receive buffer size failed (requested %d bytes buffer, received a buffer of size %d)\n", rfifo_size, buff); + } + else + perror("setsocketopts: getsockopt rfifo"); +} + +/*====================================== + * CORE : Socket Sub Function + *-------------------------------------- + */ +static void set_eof(int fd) +{ //Marks a connection eof and invokes the parse_function to disconnect it right away. [Skotlex] + if (session_isActive(fd)) + session[fd]->eof=1; +} + +static int recv_to_fifo(int fd) +{ + int len; + + if( (fd<0) || (fd>=FD_SETSIZE) || (NULL==session[fd]) ) + return -1; + + if(session[fd]->eof) + return -1; + +#ifdef __WIN32 + len=recv(fd,(char *)session[fd]->rdata+session[fd]->rdata_size, RFIFOSPACE(fd), 0); + if (len == SOCKET_ERROR) { + if (WSAGetLastError() == WSAECONNABORTED) { + ShowWarning("recv_to_fifo: Software caused connection abort on session #%d\n", fd); + FD_CLR(fd, &readfds); //Remove the socket so the select() won't hang on it. +// exit(1); //Windows can't really recover from this one. [Skotlex] + } + if (WSAGetLastError() != WSAEWOULDBLOCK) { +// ShowDebug("recv_to_fifo: error %d, ending connection #%d\n", WSAGetLastError(), fd); + set_eof(fd); + } + return 0; + } +#else + len=read(fd,session[fd]->rdata+session[fd]->rdata_size, RFIFOSPACE(fd)); + if (len == -1) + { + if (errno == ECONNABORTED) + { + ShowFatalError("recv_to_fifo: Network broken (Software caused connection abort on session #%d)\n", fd); +// exit(1); //Temporal debug, maybe this can be fixed. + } + if (errno != EAGAIN) { //Connection error. +// perror("closing session: recv_to_fifo"); + set_eof(fd); + } + return 0; + } +#endif + if (len <= 0) { //Normal connection end. + set_eof(fd); + return 0; + } + + session[fd]->rdata_size+=len; + session[fd]->rdata_tick = last_tick; + return 0; +} + +static int send_from_fifo(int fd) +{ + int len; + if( !session_isValid(fd) ) + return -1; + +// if (s->eof) // if we close connection, we can not send last information (you're been disconnected, etc...) [Yor] +// return -1; +/* + // clear write buffer if not connected <- I really like more the idea of sending the last information. [Skotlex] + if( session[fd]->eof ) + { + session[fd]->wdata_size = 0; + return -1; + } +*/ + + if (session[fd]->wdata_size == 0) + return 0; + +#ifdef __WIN32 + len=send(fd, (const char *)session[fd]->wdata,session[fd]->wdata_size, 0); + if (len == SOCKET_ERROR) { + if (WSAGetLastError() == WSAECONNABORTED) { + ShowWarning("send_from_fifo: Software caused connection abort on session #%d\n", fd); + session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex] + set_eof(fd); + FD_CLR(fd, &readfds); //Remove the socket so the select() won't hang on it. + } + if (WSAGetLastError() != WSAEWOULDBLOCK) { +// ShowDebug("send_from_fifo: error %d, ending connection #%d\n", WSAGetLastError(), fd); + session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex] + set_eof(fd); + } + return 0; + } +#else + len=write(fd,session[fd]->wdata,session[fd]->wdata_size); + if (len == -1) + { + if (errno == ECONNABORTED) + { + ShowWarning("send_from_fifo: Network broken (Software caused connection abort on session #%d)\n", fd); + session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex] + set_eof(fd); + } + if (errno != EAGAIN) { +// perror("closing session: send_from_fifo"); + session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex] + set_eof(fd); + } + return 0; + } +#endif + + //{ int i; ShowMessage("send %d : ",fd); for(i=0;iwdata[i]); } ShowMessage("\n");} + if(len>0){ + if((unsigned int)lenwdata_size){ + memmove(session[fd]->wdata,session[fd]->wdata+len,session[fd]->wdata_size-len); + session[fd]->wdata_size-=len; + } else { + session[fd]->wdata_size=0; + } + } + return 0; +} + +void flush_fifo(int fd) +{ + if(session[fd] != NULL && session[fd]->func_send == send_from_fifo) + { + set_nonblocking(fd, 1); + send_from_fifo(fd); + set_nonblocking(fd, 0); + } +} + +void flush_fifos(void) +{ + int i; + for(i=1;ifunc_send == send_from_fifo) + send_from_fifo(i); +} + +static int null_parse(int fd) +{ + ShowMessage("null_parse : %d\n",fd); + session[fd]->rdata_pos = session[fd]->rdata_size; //RFIFOSKIP(fd, RFIFOREST(fd)); simplify calculation + return 0; +} + +/*====================================== + * CORE : Socket Function + *-------------------------------------- + */ + +static int connect_client(int listen_fd) +{ + int fd; + struct sockaddr_in client_address; +#ifdef __WIN32 + int len; +#else + socklen_t len; +#endif + //ShowMessage("connect_client : %d\n",listen_fd); + + len=sizeof(client_address); + + fd = accept(listen_fd,(struct sockaddr*)&client_address,&len); +#ifdef __WIN32 + if (fd == SOCKET_ERROR || fd == INVALID_SOCKET || fd < 0) { + ShowError("accept failed (code %d)!\n", fd, WSAGetLastError()); + return -1; + } +#else + if(fd==-1) { + perror("accept"); + return -1; + } +#endif + + if(fd_max<=fd) fd_max=fd+1; + + setsocketopts(fd); + +#ifdef __WIN32 + { + unsigned long val = 1; + if (ioctlsocket(fd, FIONBIO, &val) != 0) + ShowError("Couldn't set the socket to non-blocking mode (code %d)!\n", WSAGetLastError()); + } +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) + perror("connect_client (set nonblock)"); +#endif + + if (ip_rules && !connect_check(*(unsigned int*)(&client_address.sin_addr))) { + do_close(fd); + return -1; + } else + FD_SET(fd,&readfds); + + CREATE(session[fd], struct socket_data, 1); + CREATE_A(session[fd]->rdata, unsigned char, rfifo_size); + CREATE_A(session[fd]->wdata, unsigned char, wfifo_size); + + session[fd]->max_rdata = (int)rfifo_size; + session[fd]->max_wdata = (int)wfifo_size; + session[fd]->func_recv = recv_to_fifo; + session[fd]->func_send = send_from_fifo; + if(!session[listen_fd]->func_parse) + session[fd]->func_parse = default_func_parse; + else + session[fd]->func_parse = session[listen_fd]->func_parse; + session[fd]->client_addr = client_address; + session[fd]->rdata_tick = last_tick; + session[fd]->type = SESSION_UNKNOWN; // undefined type + + //ShowMessage("new_session : %d %d\n",fd,session[fd]->eof); + return fd; +} + +int make_listen_port(int port) +{ + struct sockaddr_in server_address; + int fd; + int result; + + fd = socket( AF_INET, SOCK_STREAM, 0 ); +#ifdef __WIN32 + if (fd == INVALID_SOCKET) { + ShowError("socket() creation failed (code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if (fd == -1) { + perror("make_listen_port:socket()"); + exit(1); + } +#endif + +#ifdef __WIN32 + { + unsigned long val = 1; + if (ioctlsocket(fd, FIONBIO, &val) != 0) + ShowError("Couldn't set the socket to non-blocking mode (code %d)!\n", WSAGetLastError()); + } +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) + perror("make_listen_port (set nonblock)"); +#endif + + setsocketopts(fd); + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = htonl( INADDR_ANY ); + server_address.sin_port = htons((unsigned short)port); + + result = bind(fd, (struct sockaddr*)&server_address, sizeof(server_address)); +#ifdef __WIN32 + if( result == SOCKET_ERROR ) { + ShowError("bind failed (socket %d, code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if( result == -1 ) { + perror("bind"); + exit(1); + } +#endif + result = listen( fd, 5 ); +#ifdef __WIN32 + if( result == SOCKET_ERROR ) { + ShowError("listen failed (socket %d, code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if( result != 0 ) { /* error */ + perror("listen"); + exit(1); + } +#endif + if ( fd < 0 || fd > FD_SETSIZE ) + { //Crazy error that can happen in Windows? (info from Freya) + ShowFatalError("listen() returned invalid fd %d!\n",fd); + exit(1); + } + + if(fd_max<=fd) fd_max=fd+1; + FD_SET(fd, &readfds ); + + CREATE(session[fd], struct socket_data, 1); + + memset(session[fd],0,sizeof(*session[fd])); + session[fd]->func_recv = connect_client; + + return fd; +} + +int make_listen_bind(long ip,int port) +{ + struct sockaddr_in server_address; + int fd; + int result; + + fd = (int)socket( AF_INET, SOCK_STREAM, 0 ); + +#ifdef __WIN32 + if (fd == INVALID_SOCKET) { + ShowError("socket() creation failed (code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if (fd == -1) { + perror("make_listen_port:socket()"); + exit(1); + } +#endif + +#ifdef __WIN32 + { + unsigned long val = 1; + if (ioctlsocket(fd, FIONBIO, &val) != 0) + ShowError("Couldn't set the socket to non-blocking mode (code %d)!\n", WSAGetLastError()); + } +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) + perror("make_listen_bind (set nonblock)"); +#endif + + setsocketopts(fd); + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = ip; + server_address.sin_port = htons((unsigned short)port); + + result = bind(fd, (struct sockaddr*)&server_address, sizeof(server_address)); +#ifdef __WIN32 + if( result == SOCKET_ERROR ) { + ShowError("bind failed (socket %d, code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if( result == -1 ) { + perror("bind"); + exit(1); + } +#endif + result = listen( fd, 5 ); +#ifdef __WIN32 + if( result == SOCKET_ERROR ) { + ShowError("listen failed (socket %d, code %d)!\n", fd, WSAGetLastError()); + exit(1); + } +#else + if( result != 0) { /* error */ + perror("listen"); + exit(1); + } +#endif + if ( fd < 0 || fd > FD_SETSIZE ) + { //Crazy error that can happen in Windows? (info from Freya) + ShowFatalError("listen() returned invalid fd %d!\n",fd); + exit(1); + } + + if(fd_max<=fd) fd_max=fd+1; + FD_SET(fd, &readfds ); + + CREATE(session[fd], struct socket_data, 1); + + memset(session[fd],0,sizeof(*session[fd])); + session[fd]->func_recv = connect_client; + + ShowStatus("Open listen port on %d.%d.%d.%d:%i\n", + (ip)&0xFF,(ip>>8)&0xFF,(ip>>16)&0xFF,(ip>>24)&0xFF,port); + + return fd; +} + +// Console Reciever [Wizputer] +int console_recieve(int i) { + int n; + char *buf; + + CREATE_A(buf, char, 64); + memset(buf,0,sizeof(64)); + + n = read(0, buf , 64); + if ( n < 0 ) + ShowError("Console input read error\n"); + else + { + ShowNotice ("Sorry, the console is currently non-functional.\n"); +// session[0]->func_console(buf); + } + + aFree(buf); + return 0; +} + +void set_defaultconsoleparse(int (*defaultparse)(char*)) +{ + default_console_parse = defaultparse; +} + +static int null_console_parse(char *buf) +{ + ShowMessage("null_console_parse : %s\n",buf); + return 0; +} + +// function parse table +// To-do: -- use dynamic arrays +// -- add a register_parse_func(); +struct func_parse_table func_parse_table[SESSION_MAX]; + +int default_func_check (struct socket_data *sd) { return 1; } + +void func_parse_check (struct socket_data *sd) +{ + int i; + for (i = SESSION_HTTP; i < SESSION_MAX; i++) { + if (func_parse_table[i].func && + func_parse_table[i].check && + func_parse_table[i].check(sd) != 0) + { + sd->type = i; + sd->func_parse = func_parse_table[i].func; + return; + } + } + + // undefined -- treat as raw socket (using default parse) + sd->type = SESSION_RAW; +} + +// Console Input [Wizputer] +int start_console(void) { + + //Until a better plan is came up with... can't be using session[0] anymore! [Skotlex] + ShowNotice("The console is currently nonfunctional.\n"); + return 0; + + FD_SET(0,&readfds); + + if (!session[0]) { // dummy socket already uses fd 0 + CREATE(session[0], struct socket_data, 1); + } + memset(session[0],0,sizeof(*session[0])); + + session[0]->func_recv = console_recieve; + session[0]->func_console = default_console_parse; + + return 0; +} + +int make_connection(long ip,int port) +{ + struct sockaddr_in server_address; + int fd; + int result; + + fd = (int)socket( AF_INET, SOCK_STREAM, 0 ); + +#ifdef __WIN32 + if (fd == INVALID_SOCKET) { + ShowError("socket() creation failed (code %d)!\n", fd, WSAGetLastError()); + return -1; + } +#else + if (fd == -1) { + perror("make_connection:socket()"); + return -1; + } +#endif + + setsocketopts(fd); + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = ip; + server_address.sin_port = htons((unsigned short)port); + + ShowStatus("Connecting to %d.%d.%d.%d:%i\n", + (ip)&0xFF,(ip>>8)&0xFF,(ip>>16)&0xFF,(ip>>24)&0xFF,port); + + result = connect(fd, (struct sockaddr *)(&server_address), sizeof(struct sockaddr_in)); +#ifdef __WIN32 + if( result == SOCKET_ERROR ) { + ShowError("connect failed (socket %d, code %d)!\n", fd, WSAGetLastError()); + do_close(fd); + return -1; + } +#else + if (result < 0) { //This is only used when the map/char server try to connect to each other, so it can be handled. [Skotlex] + perror("make_connection"); + do_close(fd); + return -1; + } +#endif +//Now the socket can be made non-blocking. [Skotlex] +#ifdef __WIN32 + { + unsigned long val = 1; + if (ioctlsocket(fd, FIONBIO, &val) != 0) + ShowError("Couldn't set the socket to non-blocking mode (code %d)!\n", WSAGetLastError()); + } +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) + perror("make_connection (set nonblock)"); +#endif + + if (fd_max <= fd) + fd_max = fd + 1; + FD_SET(fd,&readfds); + + CREATE(session[fd], struct socket_data, 1); + CREATE_A(session[fd]->rdata, unsigned char, rfifo_size); + CREATE_A(session[fd]->wdata, unsigned char, wfifo_size); + + session[fd]->max_rdata = (int)rfifo_size; + session[fd]->max_wdata = (int)wfifo_size; + session[fd]->func_recv = recv_to_fifo; + session[fd]->func_send = send_from_fifo; + session[fd]->func_parse = default_func_parse; + session[fd]->rdata_tick = last_tick; + + return fd; +} + +int delete_session(int fd) +{ + if (fd <= 0 || fd >= FD_SETSIZE) + return -1; + FD_CLR(fd, &readfds); + if (session[fd]){ + if (session[fd]->rdata) + aFree(session[fd]->rdata); + if (session[fd]->wdata) + aFree(session[fd]->wdata); + if (session[fd]->session_data) + aFree(session[fd]->session_data); + aFree(session[fd]); + session[fd] = NULL; + } + //ShowMessage("delete_session:%d\n",fd); + return 0; +} + +int realloc_fifo(int fd,unsigned int rfifo_size,unsigned int wfifo_size) +{ + if( !session_isValid(fd) ) + return 0; + + if( session[fd]->max_rdata != rfifo_size && session[fd]->rdata_size < rfifo_size){ + RECREATE(session[fd]->rdata, unsigned char, rfifo_size); + session[fd]->max_rdata = rfifo_size; + } + + if( session[fd]->max_wdata != wfifo_size && session[fd]->wdata_size < wfifo_size){ + RECREATE(session[fd]->wdata, unsigned char, wfifo_size); + session[fd]->max_wdata = wfifo_size; + } + return 0; +} + +int realloc_writefifo(int fd, size_t addition) +{ + size_t newsize; + + if( !session_isValid(fd) ) // might not happen + return 0; + + if( session[fd]->wdata_size + (int)addition > session[fd]->max_wdata ) + { // grow rule; grow in multiples of wfifo_size + newsize = wfifo_size; + while( session[fd]->wdata_size + addition > newsize ) newsize += newsize; + } + else if( session[fd]->max_wdata>=FIFOSIZE_SERVERLINK) { + //Inter-server adjust. [Skotlex] + if ((session[fd]->wdata_size+(int)addition)*4 < session[fd]->max_wdata) + newsize = session[fd]->max_wdata/2; + else + return 0; //No change + } else if( session[fd]->max_wdata>(int)wfifo_size && + (session[fd]->wdata_size+(int)addition)*4 < session[fd]->max_wdata ) + { // shrink rule, shrink by 2 when only a quater of the fifo is used, don't shrink below 4*addition + newsize = session[fd]->max_wdata/2; + } + else // no change + return 0; + + RECREATE(session[fd]->wdata, unsigned char, newsize); + session[fd]->max_wdata = (int)newsize; + + return 0; +} + +int WFIFOSET(int fd,int len) +{ + size_t newreserve; + struct socket_data *s = session[fd]; + + if( !session_isValid(fd) || s->wdata == NULL ) + return 0; + + // we have written len bytes to the buffer already before calling WFIFOSET + if(s->wdata_size+len > s->max_wdata) + { // actually there was a buffer overflow already + unsigned char *sin_addr = (unsigned char *)&s->client_addr.sin_addr; + ShowFatalError("socket: Buffer Overflow. Connection %d (%d.%d.%d.%d) has written %d byteson a %d/%d bytes buffer.\n", fd, + sin_addr[0], sin_addr[1], sin_addr[2], sin_addr[3], len, s->wdata_size, s->max_wdata); + ShowDebug("Likely command that caused it: 0x%x\n", WFIFOW(fd,0)); + // no other chance, make a better fifo model + exit(1); + } + + s->wdata_size += len; + // always keep a wfifo_size reserve in the buffer + // For inter-server connections, let the reserve be 1/8th of the link size. + newreserve = s->wdata_size + (s->max_wdata>=FIFOSIZE_SERVERLINK?FIFOSIZE_SERVERLINK<<3:wfifo_size); + + if (s->wdata_size > (TCP_FRAME_LEN)) + send_from_fifo(fd); + + // realloc after sending + // readfifo does not need to be realloced at all + // Even the inter-server buffer may need reallocating! [Skotlex] + realloc_writefifo(fd, newreserve); + + return 0; +} + +int do_sendrecv(int next) +{ + fd_set rfd,wfd,efd; //Added the Error Set so that such sockets can be made eof. They are the same as the rfd for now. [Skotlex] + struct timeval timeout; + int ret,i; + + last_tick = time(0); + + //memcpy(&rfd, &readfds, sizeof(rfd)); + //memcpy(&efd, &readfds, sizeof(efd)); + FD_ZERO(&wfd); + + for (i = 1; i < fd_max; i++){ //Session 0 is never a valid session, so it's best to skip it. [Skotlex] + if(!session[i]) { + if (FD_ISSET(i, &readfds)){ + ShowDebug("force clear fds %d\n", i); + FD_CLR(i, &readfds); + //FD_CLR(i, &rfd); + //FD_CLR(i, &efd); + } + continue; + } + if(session[i]->wdata_size) + FD_SET(i, &wfd); + } + + timeout.tv_sec = next/1000; + timeout.tv_usec = next%1000*1000; + memcpy(&rfd, &readfds, sizeof(rfd)); + memcpy(&efd, &readfds, sizeof(efd)); + ret = select(fd_max, &rfd, &wfd, &efd, &timeout); + +#ifdef __WIN32 + if (ret == SOCKET_ERROR) { + if (WSAGetLastError() == WSAEWOULDBLOCK) + return 0; //Eh... try again later? + ShowError("do_sendrecv: select error (code %d)\n", WSAGetLastError()); +#else + if (ret < 0) { + perror("do_sendrecv"); + if (errno == 11) //Isn't there a constantI can use instead of this hardcoded value? This should be "resource temporarily unavailable": ie: try again. + return 0; +#endif + + //if error, remove invalid connections + //Individual socket handling code shamelessly assimilated from Freya :3 + // an error give invalid values in fd_set structures -> init them again + FD_ZERO(&rfd); + FD_ZERO(&wfd); + FD_ZERO(&efd); + for(i = 1; i < fd_max; i++) { //Session 0 is not parsed, it's a 'vacuum' for disconnected sessions. [Skotlex] + if (!session[i]) { +#ifdef __WIN32 + //Debug to locate runaway sockets in Windows [Skotlex] + if (FD_ISSET(i, &readfds)) { + FD_CLR(i, &readfds); + ShowDebug("Socket %d was set (read fifos) without a session, removed.\n", i); + } +#endif + continue; + } + if (FD_ISSET(i, &readfds)){ + FD_SET(i, &rfd); + FD_SET(i, &efd); + } + if (session[i]->wdata_size) + FD_SET(i, &wfd); + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select(i + 1, &rfd, &wfd, &efd, &timeout) >= 0 && !FD_ISSET(i, &efd)) { + if (FD_ISSET(i, &wfd)) { + if (session[i]->func_send) + session[i]->func_send(i); + FD_CLR(i, &wfd); + } + if (FD_ISSET(i, &rfd)) { + if (session[i]->func_recv) + session[i]->func_recv(i); + FD_CLR(i, &rfd); + } + FD_CLR(i, &efd); + } else { + ShowDebug("do_sendrecv: Session #%d caused error in select(), disconnecting.\n", i); + set_eof(i); // set eof + // an error gives invalid values in fd_set structures -> init them again + FD_ZERO(&rfd); + FD_ZERO(&wfd); + FD_ZERO(&efd); + } + } + return 0; + }else if(ret > 0) { + for (i = 1; i < fd_max; i++){ + if(!session[i]) + continue; + + if(FD_ISSET(i,&efd)){ + //ShowMessage("error:%d\n",i); + ShowDebug("do_sendrecv: Connection error on Session %d.\n", i); + set_eof(i); + continue; + } + + if (FD_ISSET(i, &wfd)) { + //ShowMessage("write:%d\n",i); + if(session[i]->func_send) + session[i]->func_send(i); + } + + if(FD_ISSET(i,&rfd)){ + //ShowMessage("read:%d\n",i); + if(session[i]->func_recv) + session[i]->func_recv(i); + } + + + if(session[i] && session[i]->eof) //The session check is for when the connection ended in func_parse + { //Finally, even if there is no data to parse, connections signalled eof should be closed, so we call parse_func [Skotlex] + if (session[i]->func_parse) + session[i]->func_parse(i); //This should close the session inmediately. + } + } // for (i = 0 + } + return 0; +} + +int do_parsepacket(void) +{ + int i; + struct socket_data *sd; + for(i = 1; i < fd_max; i++){ + sd = session[i]; + if(!sd) + continue; + if ((sd->rdata_tick != 0) && DIFF_TICK(last_tick,sd->rdata_tick) > stall_time) { + ShowInfo ("Session #%d timed out\n", i); + sd->eof = 1; + } + if(sd->rdata_size == 0 && sd->eof == 0) + continue; + if(sd->func_parse){ + if(sd->type == SESSION_UNKNOWN) + func_parse_check(sd); + if(sd->type != SESSION_UNKNOWN) + sd->func_parse(i); + if(!session[i]) + continue; + /* after parse, check client's RFIFO size to know if there is an invalid packet (too big and not parsed) */ + if (session[i]->rdata_size == rfifo_size && session[i]->max_rdata == rfifo_size) { + session[i]->eof = 1; + continue; + } + } + RFIFOHEAD(i); + RFIFOFLUSH(i); + } + return 0; +} + +/* DDoS 攻撃対策 */ +#ifndef MINICORE +enum { + ACO_DENY_ALLOW=0, + ACO_ALLOW_DENY, + ACO_MUTUAL_FAILTURE, +}; + +struct _access_control { + unsigned int ip; + unsigned int mask; +}; + +static struct _access_control *access_allow; +static struct _access_control *access_deny; +static int access_order=ACO_DENY_ALLOW; +static int access_allownum=0; +static int access_denynum=0; +static int access_debug=0; +static int ddos_count = 10; +static int ddos_interval = 3000; +static int ddos_autoreset = 600*1000; + +struct _connect_history { + struct _connect_history *next; + struct _connect_history *prev; + int status; + int count; + unsigned int ip; + unsigned int tick; +}; +static struct _connect_history *connect_history[0x10000]; +static int connect_check_(unsigned int ip); + +// 接続できるかどうかの確認 +// false : 接続OK +// true : 接続NG +static int connect_check(unsigned int ip) { + int result = connect_check_(ip); + if(access_debug) { + ShowMessage("connect_check: Connection from %d.%d.%d.%d %s\n", + CONVIP(ip),result ? "allowed." : "denied!"); + } + return result; +} + +static int connect_check_(unsigned int ip) { + struct _connect_history *hist = connect_history[ip & 0xFFFF]; + struct _connect_history *hist_new; + int i,is_allowip = 0,is_denyip = 0,connect_ok = 0; + + // allow , deny リストに入っているか確認 + for(i = 0;i < access_allownum; i++) { + if((ip & access_allow[i].mask) == (access_allow[i].ip & access_allow[i].mask)) { + if(access_debug) { + ShowMessage("connect_check: Found match from allow list:%d.%d.%d.%d IP:%d.%d.%d.%d Mask:%d.%d.%d.%d\n", + CONVIP(ip), + CONVIP(access_allow[i].ip), + CONVIP(access_allow[i].mask)); + } + is_allowip = 1; + break; + } + } + for(i = 0;i < access_denynum; i++) { + if((ip & access_deny[i].mask) == (access_deny[i].ip & access_deny[i].mask)) { + if(access_debug) { + ShowMessage("connect_check: Found match from deny list:%d.%d.%d.%d IP:%d.%d.%d.%d Mask:%d.%d.%d.%d\n", + CONVIP(ip), + CONVIP(access_deny[i].ip), + CONVIP(access_deny[i].mask)); + } + is_denyip = 1; + break; + } + } + // コネクト出来るかどうか確認 + // connect_ok + // 0 : 無条件に拒否 + // 1 : 田代砲チェックの結果次第 + // 2 : 無条件に許可 + switch(access_order) { + case ACO_DENY_ALLOW: + default: + if(is_allowip) { + connect_ok = 2; + } else if(is_denyip) { + connect_ok = 0; + } else { + connect_ok = 1; + } + break; + case ACO_ALLOW_DENY: + if(is_denyip) { + connect_ok = 0; + } else if(is_allowip) { + connect_ok = 2; + } else { + connect_ok = 1; + } + break; + case ACO_MUTUAL_FAILTURE: + if(is_allowip) { + connect_ok = 2; + } else { + connect_ok = 0; + } + break; + } + + // 接続履歴を調べる + while(hist) { + if(ip == hist->ip) { + // 同じIP発見 + if(hist->status) { + // ban フラグが立ってる + return (connect_ok == 2 ? 1 : 0); + } else if(DIFF_TICK(gettick(),hist->tick) < ddos_interval) { + // ddos_interval秒以内にリクエスト有り + hist->tick = gettick(); + if(hist->count++ >= ddos_count) { + // ddos 攻撃を検出 + hist->status = 1; + ShowWarning("connect_check: DDOS Attack detected from %d.%d.%d.%d!\n", + CONVIP(ip)); + return (connect_ok == 2 ? 1 : 0); + } else { + return connect_ok; + } + } else { + // ddos_interval秒以内にリクエスト無いのでタイマークリア + hist->tick = gettick(); + hist->count = 0; + return connect_ok; + } + } + hist = hist->next; + } + // IPリストに無いので新規作成 + hist_new = (struct _connect_history *) aCalloc(1,sizeof(struct _connect_history)); + hist_new->ip = ip; + hist_new->tick = gettick(); + if(connect_history[ip & 0xFFFF] != NULL) { + hist = connect_history[ip & 0xFFFF]; + hist->prev = hist_new; + hist_new->next = hist; + } + connect_history[ip & 0xFFFF] = hist_new; + return connect_ok; +} + +static int connect_check_clear(int tid,unsigned int tick,int id,int data) { + int i; + int clear = 0; + int list = 0; + struct _connect_history *hist , *hist2; + for(i = 0;i < 0x10000 ; i++) { + hist = connect_history[i]; + while(hist) { + if ((DIFF_TICK(tick,hist->tick) > ddos_interval * 3 && !hist->status) || + (DIFF_TICK(tick,hist->tick) > ddos_autoreset && hist->status)) { + // clear data + hist2 = hist->next; + if(hist->prev) { + hist->prev->next = hist->next; + } else { + connect_history[i] = hist->next; + } + if(hist->next) { + hist->next->prev = hist->prev; + } + aFree(hist); + hist = hist2; + clear++; + } else { + hist = hist->next; + } + list++; + } + } + if(access_debug) { + ShowMessage("connect_check_clear: Cleared %d of %d from IP list.\n", clear, list); + } + return list; +} + +// IPマスクチェック +int access_ipmask(const char *str,struct _access_control* acc) +{ + unsigned int mask=0,i=0,m,ip, a0,a1,a2,a3; + if( !strcmp(str,"all") ) { + ip = 0; + mask = 0; + } else { + if( sscanf(str,"%d.%d.%d.%d%n",&a0,&a1,&a2,&a3,&i)!=4 || i==0) { + ShowError("access_ipmask: Unknown format %s!\n",str); + return 0; + } + ip = (a3 << 24) | (a2 << 16) | (a1 << 8) | a0; + + if(sscanf(str+i,"/%d.%d.%d.%d",&a0,&a1,&a2,&a3)==4 ){ + mask = (a3 << 24) | (a2 << 16) | (a1 << 8) | a0; + } else if(sscanf(str+i,"/%d",&m) == 1) { + for(i=0;i> 1) | 0x80000000; + } + mask = ntohl(mask); + } else { + mask = 0xFFFFFFFF; + } + } + if(access_debug) { + ShowMessage("access_ipmask: Loaded IP:%d.%d.%d.%d mask:%d.%d.%d.%d\n", + CONVIP(ip), CONVIP(mask)); + } + acc->ip = ip; + acc->mask = mask; + return 1; +} +#endif + +int socket_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,1020,fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + if(strcmpi(w1,"stall_time")==0){ + stall_time = atoi(w2); + #ifndef MINICORE + } else if(strcmpi(w1,"enable_ip_rules")==0){ + if(strcmpi(w2,"yes")==0) + ip_rules = 1; + else if(strcmpi(w2,"no")==0) + ip_rules = 0; + else ip_rules = atoi(w2); + } else if(strcmpi(w1,"order")==0){ + access_order=atoi(w2); + if(strcmpi(w2,"deny,allow")==0) access_order=ACO_DENY_ALLOW; + if(strcmpi(w2,"allow,deny")==0) access_order=ACO_ALLOW_DENY; + if(strcmpi(w2,"mutual-failure")==0) access_order=ACO_MUTUAL_FAILTURE; + } else if(strcmpi(w1,"allow")==0){ + access_allow = (struct _access_control *) aRealloc(access_allow,(access_allownum+1)*sizeof(struct _access_control)); + if(access_ipmask(w2,&access_allow[access_allownum])) { + access_allownum++; + } + } else if(strcmpi(w1,"deny")==0){ + access_deny = (struct _access_control *) aRealloc(access_deny,(access_denynum+1)*sizeof(struct _access_control)); + if(access_ipmask(w2,&access_deny[access_denynum])) { + access_denynum++; + } + } else if(!strcmpi(w1,"ddos_interval")){ + ddos_interval = atoi(w2); + } else if(!strcmpi(w1,"ddos_count")){ + ddos_count = atoi(w2); + } else if(!strcmpi(w1,"ddos_autoreset")){ + ddos_autoreset = atoi(w2); + } else if(!strcmpi(w1,"debug")){ + if(strcmpi(w2,"yes")==0) + access_debug = 1; + else if(strcmpi(w2,"no")==0) + access_debug = 0; + else access_debug = atoi(w2); + #endif + } else if (strcmpi(w1, "import") == 0) + socket_config_read(w2); + } + fclose(fp); + return 0; +} + +int RFIFOSKIP(int fd,int len) +{ + struct socket_data *s; + + if ( !session_isActive(fd) ) //Nullpo error here[Kevin] + return 0; + + s = session[fd]; + + if (s->rdata_size-s->rdata_pos-len<0) { + //fprintf(stderr,"too many skip\n"); + //exit(1); + //better than a COMPLETE program abort // TEST! :) + ShowError("too many skip (%d) now skipped: %d (FD: %d)\n", len, RFIFOREST(fd), fd); + len = RFIFOREST(fd); + } + s->rdata_pos = s->rdata_pos+len; + return 0; +} + + +unsigned int addr_[16]; // ip addresses of local host (host byte order) +unsigned int naddr_ = 0; // # of ip addresses + +void socket_final (void) +{ + int i; +#ifndef MINICORE + struct _connect_history *hist , *hist2; + for(i = 0; i < 0x10000; i++) { + hist = connect_history[i]; + while(hist) { + hist2 = hist->next; + aFree(hist); + hist = hist2; + } + } + if (access_allow) + aFree(access_allow); + if (access_deny) + aFree(access_deny); +#endif + + for (i = 1; i < fd_max; i++) { + if(session[i]) + delete_session(i); + } + + // session[0] のダミーデータを削除 + aFree(session[0]->rdata); + aFree(session[0]->wdata); + aFree(session[0]); +} + +//Closes a socket. +//Needed to simplify shutdown code as well as manage the subtle differences in socket management from Windows and *nix. +void do_close(int fd) +{ +//We don't really care if these closing functions return an error, we are just shutting down and not reusing this socket. +#ifdef __WIN32 +// shutdown(fd, SD_BOTH); //FIXME: Shutdown requires winsock2.h! What would be the proper shutting down method for winsock1? + if (session[fd] && session[fd]->func_send == send_from_fifo) + session[fd]->func_send(fd); //Flush the data as it is gonna be closed down, but it may not succeed as it is a nonblocking socket! [Skotlex] + closesocket(fd); +#else + if (close(fd)) + perror("do_close: close"); +#endif + if (session[fd]) + delete_session(fd); +} + +void socket_init (void) +{ + char *SOCKET_CONF_FILENAME = "conf/packet_athena.conf"; +#ifdef __WIN32 + char** a; + unsigned int i; + char fullhost[255]; + struct hostent* hent; + + /* Start up the windows networking */ + WORD version_wanted = MAKEWORD(1, 1); //Demand at least WinSocket version 1.1 (from Freya) + WSADATA wsaData; + + if ( WSAStartup(version_wanted, &wsaData) != 0 ) { + ShowFatalError("SYSERR: WinSock not available!\n"); + exit(1); + } + + if(gethostname(fullhost, sizeof(fullhost)) == SOCKET_ERROR) { + ShowError("Ugg.. no hostname defined!\n"); + return; + } + + // XXX This should look up the local IP addresses in the registry + // instead of calling gethostbyname. However, the way IP addresses + // are stored in the registry is annoyingly complex, so I'll leave + // this as T.B.D. + hent = gethostbyname(fullhost); + if (hent == NULL) { + ShowError("Cannot resolve our own hostname to a IP address"); + return; + } + + a = hent->h_addr_list; + for(i = 0; a[i] != 0 && i < 16; ++i) { + unsigned long addr1 = ntohl(*(unsigned long*) a[i]); + addr_[i] = addr1; + } + naddr_ = i; +#else + int pos; + int fdes = socket(AF_INET, SOCK_STREAM, 0); + char buf[16 * sizeof(struct ifreq)]; + struct ifconf ic; + + // The ioctl call will fail with Invalid Argument if there are more + // interfaces than will fit in the buffer + ic.ifc_len = sizeof(buf); + ic.ifc_buf = buf; + if(ioctl(fdes, SIOCGIFCONF, &ic) == -1) { + ShowError("SIOCGIFCONF failed!\n"); + return; + } + + for(pos = 0; pos < ic.ifc_len;) + { + struct ifreq * ir = (struct ifreq *) (ic.ifc_buf + pos); + + struct sockaddr_in * a = (struct sockaddr_in *) &(ir->ifr_addr); + + if(a->sin_family == AF_INET) { + u_long ad = ntohl(a->sin_addr.s_addr); + if(ad != INADDR_LOOPBACK) { + addr_[naddr_ ++] = ad; + if(naddr_ == 16) + break; + } + } + + #if defined(_AIX) || defined(__APPLE__) + pos += ir->ifr_addr.sa_len; // For when we port athena to run on Mac's :) + pos += sizeof(ir->ifr_name); + #else + pos += sizeof(struct ifreq); + #endif + } +#endif + + FD_ZERO(&readfds); + + socket_config_read(SOCKET_CONF_FILENAME); + + // initialise last send-receive tick + last_tick = time(0); + + // session[0] Was for the console (whatever that was?), but is now currently used for disconnected sessions of the map + // server, and as such, should hold enough buffer (it is a vacuum so to speak) as it is never flushed. [Skotlex] + CREATE(session[0], struct socket_data, 1); + CREATE_A(session[0]->rdata, unsigned char, 2*rfifo_size); + CREATE_A(session[0]->wdata, unsigned char, 2*wfifo_size); + session[0]->max_rdata = (int)2*rfifo_size; + session[0]->max_wdata = (int)2*wfifo_size; + + memset (func_parse_table, 0, sizeof(func_parse_table)); + func_parse_table[SESSION_RAW].check = default_func_check; + func_parse_table[SESSION_RAW].func = default_func_parse; + +#ifndef MINICORE + // とりあえず5分ごとに不要なデータを削除する + add_timer_func_list(connect_check_clear, "connect_check_clear"); + add_timer_interval(gettick()+1000,connect_check_clear,0,0,300*1000); +#endif +} + + +bool session_isValid(int fd) +{ //End of Exam has pointed out that fd==0 is actually an unconnected session! [Skotlex] + //But this is not so true, it is used... for... something. The console uses it, would this not cause problems? [Skotlex] + return ( (fd>0) && (fdeof ); +} diff --git a/src/common/socket.h b/src/common/socket.h new file mode 100644 index 000000000..ba27e34a8 --- /dev/null +++ b/src/common/socket.h @@ -0,0 +1,189 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _SOCKET_H_ +#define _SOCKET_H_ + +#include + +#ifdef __WIN32 +#define __USE_W32_SOCKETS +#include +#else +#include +#include +#include +#endif +#include +#include "malloc.h" + +extern time_t last_tick; +extern time_t stall_time; + +// define declaration + +#define RFIFOSPACE(fd) (session[fd]->max_rdata-session[fd]->rdata_size) +#ifdef TURBO +#define RFIFOHEAD(fd) char *rbPtr = session[fd]->rdata+session[fd]->rdata_pos +#define RFIFOP(fd,pos) (&rbPtr[pos]) +#else +//Make it a comment so it does not disrupts the rest of code. +#define RFIFOHEAD(fd) // +#define RFIFOP(fd,pos) (session[fd]->rdata+session[fd]->rdata_pos+(pos)) +#endif +// use function instead of macro. +#define RFIFOB(fd,pos) (*(unsigned char*)RFIFOP(fd,pos)) +#define RFIFOW(fd,pos) (*(unsigned short*)RFIFOP(fd,pos)) +#define RFIFOL(fd,pos) (*(unsigned long*)RFIFOP(fd,pos)) +#define RFIFOREST(fd) (session[fd]->rdata_size-session[fd]->rdata_pos) +#define RFIFOFLUSH(fd) (memmove(session[fd]->rdata,RFIFOP(fd,0),RFIFOREST(fd)),session[fd]->rdata_size=RFIFOREST(fd),session[fd]->rdata_pos=0) +//#define RFIFOSKIP(fd,len) ((session[fd]->rdata_size-session[fd]->rdata_pos-(len)<0) ? (fprintf(stderr,"too many skip\n"),exit(1)) : (session[fd]->rdata_pos+=(len))) + +#define RBUFP(p,pos) (((unsigned char*)(p))+(pos)) +#define RBUFB(p,pos) (*(unsigned char*)RBUFP((p),(pos))) +#define RBUFW(p,pos) (*(unsigned short*)RBUFP((p),(pos))) +#define RBUFL(p,pos) (*(unsigned long*)RBUFP((p),(pos))) + +#define WFIFOSPACE(fd) (session[fd]->max_wdata-session[fd]->wdata_size) +#ifdef TURBO +#define WFIFOHEAD(fd, x) char *wbPtr = session[fd]->wdata+session[fd]->wdata_size; +#define WFIFOP(fd,pos) (&wbPtr[pos]) +#else +#define WFIFOHEAD(fd, x) ; +#define WFIFOP(fd,pos) (session[fd]->wdata+session[fd]->wdata_size+(pos)) +#endif +#define WFIFOB(fd,pos) (*(unsigned char*)WFIFOP(fd,pos)) +#define WFIFOW(fd,pos) (*(unsigned short*)WFIFOP(fd,pos)) +#define WFIFOL(fd,pos) (*(unsigned long*)WFIFOP(fd,pos)) +// use function instead of macro. +//#define WFIFOSET(fd,len) (session[fd]->wdata_size = (session[fd]->wdata_size + (len) + 2048 < session[fd]->max_wdata) ? session[fd]->wdata_size + len : session[fd]->wdata_size) +#define WBUFP(p,pos) (((unsigned char*)(p)) + (pos)) +#define WBUFB(p,pos) (*(unsigned char*)((p) + (pos))) +#define WBUFW(p,pos) (*(unsigned short*)((p) + (pos))) +#define WBUFL(p,pos) (*(unsigned long*)((p) + (pos))) + +//FD_SETSIZE must be modified on the project files/Makefile, since a change here won't affect +// dependant windows libraries. +/* +#ifdef __WIN32 +//The default FD_SETSIZE is kinda small for windows systems. + #ifdef FD_SETSIZE + #undef FD_SETSIZE + #endif +#define FD_SETSIZE 4096 +#endif +*/ +#ifdef __INTERIX +#define FD_SETSIZE 4096 +#endif // __INTERIX + +/* Removed Cygwin FD_SETSIZE declarations, now are directly passed on to the compiler through Makefile [Valaris] */ + +// Session type +enum SessionType { + SESSION_UNKNOWN = -1, + SESSION_RAW = 0, + SESSION_HTTP = 1, +//----------------- + SESSION_MAX = 2 +}; + +// Struct declaration + +struct socket_data{ + unsigned char eof; + unsigned char *rdata, *wdata; + unsigned int max_rdata, max_wdata; + unsigned int rdata_size, wdata_size; + int rdata_pos; + time_t rdata_tick; + struct sockaddr_in client_addr; + int (*func_recv)(int); + int (*func_send)(int); + int (*func_parse)(int); + int (*func_console)(char*); + void* session_data; + void* session_data2; + enum SessionType type; +}; + +// Parse functions table +struct func_parse_table { + int (*func)(int); + int (*check)(struct socket_data *); +}; +extern struct func_parse_table func_parse_table[SESSION_MAX]; + + +// Data prototype declaration + +extern struct socket_data *session[FD_SETSIZE]; + +extern int fd_max; + + + + + +///////////////////////////// +// for those still not building c++ +#ifndef __cplusplus +////////////////////////////// + +// boolean types for C +typedef int bool; +#define false (1==0) +#define true (1==1) + +////////////////////////////// +#endif // not cplusplus +////////////////////////////// + + + +////////////////////////////////// +// some checking on sockets +extern bool session_isValid(int fd); +extern bool session_isActive(int fd); +////////////////////////////////// + + + + + + + + + + +// Function prototype declaration + +int make_listen_port(int); +int make_listen_bind(long,int); +int make_connection(long,int); +int delete_session(int); +int realloc_fifo(int fd,unsigned int rfifo_size,unsigned int wfifo_size); +int realloc_writefifo(int fd, size_t addition); +int WFIFOSET(int fd,int len); +int RFIFOSKIP(int fd,int len); + +int do_sendrecv(int next); +int do_parsepacket(void); +void do_close(int fd); +void socket_init(void); +void socket_final(void); + +extern void flush_fifo(int fd); +extern void flush_fifos(void); +extern void set_nonblocking(int fd, int yes); + +int start_console(void); + +void set_defaultparse(int (*defaultparse)(int)); +void set_defaultconsoleparse(int (*defaultparse)(char*)); + +extern unsigned int addr_[16]; // ip addresses of local host (host byte order) +extern unsigned int naddr_; // # of ip addresses + + +#endif // _SOCKET_H_ diff --git a/src/common/strlib.c b/src/common/strlib.c new file mode 100644 index 000000000..12c34556f --- /dev/null +++ b/src/common/strlib.c @@ -0,0 +1,133 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include + +#include "strlib.h" +#include "utils.h" +#include "malloc.h" + +//----------------------------------------------- +// string lib. +char* jstrescape (char* pt) { + //copy from here + char *ptr; + int i =0, j=0; + + //copy string to temporary + CREATE_A(ptr, char, J_MAX_MALLOC_SIZE); + strcpy(ptr,pt); + + while (ptr[i] != '\0') { + switch (ptr[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + case '%': + pt[j++] = '_'; i++; + break; + default: + pt[j++] = ptr[i++]; + } + } + pt[j++] = '\0'; + aFree (ptr); + return &pt[0]; +} + +char* jstrescapecpy (char* pt,char* spt) { + //copy from here + //WARNING: Target string pt should be able to hold strlen(spt)*2, as each time + //a escape character is found, the target's final length increases! [Skotlex] + int i =0, j=0; + + while (spt[i] != '\0') { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '%': + pt[j++] = '_'; i++; + break; + default: + pt[j++] = spt[i++]; + } + } + pt[j++] = '\0'; + return &pt[0]; +} +int jmemescapecpy (char* pt,char* spt, int size) { + //copy from here + int i =0, j=0; + + while (i < size) { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '%': + pt[j++] = '_'; i++; + break; + default: + pt[j++] = spt[i++]; + } + } + // copy size is 0 ~ (j-1) + return j; +} + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +//int remove_control_chars(char *str) { +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//Trims a string, also removes illegal characters such as \t and reduces continous spaces to a single one. by [Foruken] +char *trim(char *str, const char *delim) +{ + char *strp = strtok(str,delim); + char buf[1024]; + char *bufp = buf; + memset(buf,0,sizeof buf); + + while(strp) { + strcpy(bufp, strp); + bufp = bufp + strlen(strp); + strp = strtok(NULL, delim); + if (strp) { + strcpy(bufp," "); + bufp++; + } + } + strcpy(str,buf); + return str; +} diff --git a/src/common/strlib.h b/src/common/strlib.h new file mode 100644 index 000000000..f4ee7074b --- /dev/null +++ b/src/common/strlib.h @@ -0,0 +1,17 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _J_STR_LIB_H_ +#define _J_STR_LIB_H_ +#define J_MAX_MALLOC_SIZE 65535 +// String function library. +// code by Jioh L. Jung (ziozzang@4wish.net) +// This code is under license "BSD" +char* jstrescape (char* pt); +char* jstrescapecpy (char* pt,char* spt); +int jmemescapecpy (char* pt,char* spt, int size); + +// custom functions +int remove_control_chars(unsigned char *); +char *trim(char *str, const char *delim); +#endif diff --git a/src/common/timer.c b/src/common/timer.c new file mode 100644 index 000000000..7b7ac5e2c --- /dev/null +++ b/src/common/timer.c @@ -0,0 +1,429 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include + +#ifdef __WIN32 +#define __USE_W32_SOCKETS +// Well, this won't last another 30++ years (where conversion will truncate). +//#define _USE_32BIT_TIME_T // use 32 bit time variables on 64bit windows +#include +#else +#include +#include +#endif + + +#include +#include +#include +#include + +#include "timer.h" +#include "malloc.h" +#include "showmsg.h" + +// タイマー間隔の最小値。モンスターの大量召還時、多数のクライアント接続時に +// サーバーが反応しなくなる場合は、TIMER_MIN_INTERVAL を増やしてください。 + +// If the server shows no reaction when processing thousands of monsters +// or connected by many clients, please increase TIMER_MIN_INTERVAL. + +#define TIMER_MIN_INTERVAL 50 + +static struct TimerData* timer_data = NULL; +static int timer_data_max = 0; +static int timer_data_num = 0; + +static int* free_timer_list = NULL; +static int free_timer_list_max = 0; +static int free_timer_list_pos = 0; + +static int timer_heap_num = 0; +static int timer_heap_max = 0; +static int* timer_heap = NULL; + +static int fix_heap_flag =0; //Flag for fixing the stack only once per tick loop. May not be the best way, but it's all I can think of currently :X [Skotlex] + +// for debug +struct timer_func_list { + int (*func)(int,unsigned int,int,int); + struct timer_func_list* next; + char* name; +}; +static struct timer_func_list* tfl_root; + +time_t start_time; + +#ifdef __WIN32 +/* Modified struct timezone to void - we pass NULL anyway */ +void gettimeofday (struct timeval *t, void *dummy) +{ + DWORD millisec = GetTickCount(); + + t->tv_sec = (int) (millisec / 1000); + t->tv_usec = (millisec % 1000) * 1000; +} +#endif + +// +int add_timer_func_list(int (*func)(int,unsigned int,int,int), char* name) +{ + struct timer_func_list* tfl; + + if (name) { + tfl = (struct timer_func_list*) aCalloc (sizeof(struct timer_func_list), 1); + tfl->name = (char *) aMalloc (strlen(name) + 1); + + tfl->next = tfl_root; + tfl->func = func; + strcpy(tfl->name, name); + tfl_root = tfl; + } + return 0; +} + +char* search_timer_func_list(int (*func)(int,unsigned int,int,int)) +{ + struct timer_func_list* tfl = tfl_root; + while (tfl) { + if (func == tfl->func) + return tfl->name; + tfl = tfl->next; + } + + return "unknown timer function"; +} + +/*---------------------------- + * Get tick time + *----------------------------*/ +static unsigned int gettick_cache; +static int gettick_count; + +unsigned int gettick_nocache(void) +{ + struct timeval tval; + + gettimeofday(&tval, NULL); + gettick_count = 256; + + return gettick_cache = tval.tv_sec * 1000 + tval.tv_usec / 1000; +} + +unsigned int gettick(void) +{ + gettick_count--; + if (gettick_count < 0) + return gettick_nocache(); + + return gettick_cache; +} + +/*====================================== + * CORE : Timer Heap + *-------------------------------------- + */ +static void push_timer_heap(int index) +{ + int i, j; + int min, max, pivot; // for sorting + + // check number of element + if (timer_heap_num >= timer_heap_max) { + if (timer_heap_max == 0) { + timer_heap_max = 256; + timer_heap = (int *) aCalloc( sizeof(int) , 256); + } else { + timer_heap_max += 256; + timer_heap = (int *) aRealloc( timer_heap, sizeof(int) * timer_heap_max); + memset(timer_heap + (timer_heap_max - 256), 0, sizeof(int) * 256); + } + } + + // do a sorting from higher to lower + j = timer_data[index].tick; // speed up + // with less than 4 values, it's speeder to use simple loop + if (timer_heap_num < 4) { + for(i = timer_heap_num; i > 0; i--) +// if (j < timer_data[timer_heap[i - 1]].tick) //Plain comparisons break on bound looping timers. [Skotlex] + if (DIFF_TICK(j, timer_data[timer_heap[i - 1]].tick) < 0) + break; + else + timer_heap[i] = timer_heap[i - 1]; + timer_heap[i] = index; + // searching by dichotomie + } else { + // if lower actual item is higher than new +// if (j < timer_data[timer_heap[timer_heap_num - 1]].tick) //Plain comparisons break on bound looping timers. [Skotlex] + if (DIFF_TICK(j, timer_data[timer_heap[timer_heap_num - 1]].tick) < 0) + timer_heap[timer_heap_num] = index; + else { + // searching position + min = 0; + max = timer_heap_num - 1; + while (min < max) { + pivot = (min + max) / 2; +// if (j < timer_data[timer_heap[pivot]].tick) //Plain comparisons break on bound looping timers. [Skotlex] + if (DIFF_TICK(j, timer_data[timer_heap[pivot]].tick) < 0) + min = pivot + 1; + else + max = pivot; + } + // move elements - do loop if there are a little number of elements to move + if (timer_heap_num - min < 5) { + for(i = timer_heap_num; i > min; i--) + timer_heap[i] = timer_heap[i - 1]; + // move elements - else use memmove (speeder for a lot of elements) + } else + memmove(&timer_heap[min + 1], &timer_heap[min], sizeof(int) * (timer_heap_num - min)); + // save new element + timer_heap[min] = index; + } + } + + timer_heap_num++; +} + +/*========================== + * Timer Management + *-------------------------- + */ + +int acquire_timer (void) +{ + int i; + + if (free_timer_list_pos) { + do { + i = free_timer_list[--free_timer_list_pos]; + } while(i >= timer_data_num && free_timer_list_pos > 0); + } else + i = timer_data_num; + + if (i >= timer_data_num) + for (i = timer_data_num; i < timer_data_max && timer_data[i].type; i++); + if (i >= timer_data_num && i >= timer_data_max) { + if (timer_data_max == 0) { + timer_data_max = 256; + timer_data = (struct TimerData*) aCalloc( sizeof(struct TimerData) , timer_data_max); + } else { + timer_data_max += 256; + timer_data = (struct TimerData *) aRealloc( timer_data, sizeof(struct TimerData) * timer_data_max); + memset(timer_data + (timer_data_max - 256), 0, sizeof(struct TimerData) * 256); + } + } + + return i; +} + +int add_timer(unsigned int tick,int (*func)(int,unsigned int,int,int), int id, int data) +{ + int tid = acquire_timer(); + + timer_data[tid].tick = tick; + timer_data[tid].func = func; + timer_data[tid].id = id; + timer_data[tid].data = data; + timer_data[tid].type = TIMER_ONCE_AUTODEL; + timer_data[tid].interval = 1000; + push_timer_heap(tid); + + if (tid >= timer_data_num) + timer_data_num = tid + 1; + + return tid; +} + +int add_timer_interval(unsigned int tick, int (*func)(int,unsigned int,int,int), int id, int data, int interval) +{ + int tid = acquire_timer(); + + timer_data[tid].tick = tick; + timer_data[tid].func = func; + timer_data[tid].id = id; + timer_data[tid].data = data; + timer_data[tid].type = TIMER_INTERVAL; + timer_data[tid].interval = interval; + push_timer_heap(tid); + + if (tid >= timer_data_num) + timer_data_num = tid + 1; + + return tid; +} + +int delete_timer(int id, int (*func)(int,unsigned int,int,int)) +{ + if (id <= 0 || id >= timer_data_num) { + ShowError("delete_timer error : no such timer %d\n", id); + return -1; + } + if (timer_data[id].func != func) { + ShowError("delete_timer error : function mismatch %08x(%s) != %08x(%s)\n", + (int)timer_data[id].func, search_timer_func_list(timer_data[id].func), + (int)func, search_timer_func_list(func)); + return -2; + } + // そのうち消えるにまかせる + timer_data[id].func = NULL; + timer_data[id].type = TIMER_ONCE_AUTODEL; + + return 0; +} + +int addtick_timer(int tid, unsigned int tick) +{ + return timer_data[tid].tick += tick; +} + +//Sets the tick at which the timer triggers directly (meant as a replacement of delete_timer + add_timer) [Skotlex] +//FIXME: DON'T use this function yet, it is not correctly reorganizing the timer stack causing unexpected problems later on! +int settick_timer(int tid, unsigned int tick) +{ + int i,j; + if (timer_data[tid].tick == tick) + return tick; + + //FIXME: This search is not all that effective... there doesn't seems to be a better way to locate an element in the heap. + for(i = timer_heap_num-1; i >= 0 && timer_heap[i] != tid; i--); + + if (i < 0) + return -1; //Sort of impossible, isn't it? + if (DIFF_TICK(timer_data[tid].tick, tick) > 0) + { //Timer is accelerated, shift timer near the end of the heap. + if (i == timer_heap_num-1) //Nothing to shift. + j = timer_heap_num-1; + else { + for (j = i+1; j < timer_heap_num && DIFF_TICK(timer_data[j].tick, tick) > 0; j++); + j--; + memmove(&timer_heap[i], &timer_heap[i+1], (j-i)*sizeof(int)); + } + } else { //Timer is delayed, shift timer near the beginning of the heap. + if (i == 0) //Nothing to shift. + j = 0; + else { + for (j = i-1; j >= 0 && DIFF_TICK(timer_data[j].tick, tick) < 0; j--); + j++; + memmove(&timer_heap[j+1], &timer_heap[j], (i-j)*sizeof(int)); + } + } + timer_heap[j] = tid; + timer_data[tid].tick = tick; + return tick; +} + +struct TimerData* get_timer(int tid) +{ + return &timer_data[tid]; +} + +//Correcting the heap when the tick overflows is an idea taken from jA to +//prevent timer problems. Thanks to [End of Exam] for providing the required data. [Skotlex] +//This funtion will rearrange the heap and assign new tick values. +static void fix_timer_heap(unsigned int tick) +{ + if (timer_heap_num >= 0 && tick < 0x00010000 && timer_data[timer_heap[0]].tick > 0xf0000000) + { //The last timer is way too far into the future, and the current tick is too close to 0, overflow was very likely + //(not perfect, but will work as long as the timer is not expected to happen 50 or so days into the future) + int i; + int *tmp_heap; + for (i=0; i < timer_heap_num && timer_data[timer_heap[i]].tick > 0xf0000000; i++) + { //All functions with high tick value should had been executed already... + timer_data[timer_heap[i]].tick = 0; + } + //Move elements to readjust the heap. + tmp_heap = aCalloc(sizeof(int), i); + memmove(&tmp_heap[0], &timer_heap[0], i*sizeof(int)); + memmove(&timer_heap[0], &timer_heap[i], (timer_heap_num-i)*sizeof(int)); + memmove(&timer_heap[timer_heap_num-i], &tmp_heap[0], i*sizeof(int)); + aFree(tmp_heap); + } +} + +int do_timer(unsigned int tick) +{ + int i, nextmin = 1000; + + if (tick < 0x010000 && fix_heap_flag) + { + fix_timer_heap(tick); + fix_heap_flag = 0; + } + + while(timer_heap_num) { + i = timer_heap[timer_heap_num - 1]; // next shorter element + if ((nextmin = DIFF_TICK(timer_data[i].tick, tick)) > 0) + break; + if (timer_heap_num > 0) // suppress the actual element from the table + timer_heap_num--; + timer_data[i].type |= TIMER_REMOVE_HEAP; + if (timer_data[i].func) { + if (nextmin < -1000) { + // 1秒以上の大幅な遅延が発生しているので、 + // timer処理タイミングを現在値とする事で + // 呼び出し時タイミング(引数のtick)相対で処理してる + // timer関数の次回処理タイミングを遅らせる + timer_data[i].func(i, tick, timer_data[i].id, timer_data[i].data); + } else { + timer_data[i].func(i, timer_data[i].tick, timer_data[i].id, timer_data[i].data); + } + } + if (timer_data[i].type & TIMER_REMOVE_HEAP) { + switch(timer_data[i].type & ~TIMER_REMOVE_HEAP) { + case TIMER_ONCE_AUTODEL: + timer_data[i].type = 0; + if (free_timer_list_pos >= free_timer_list_max) { + free_timer_list_max += 256; + free_timer_list = (int *) aRealloc(free_timer_list, sizeof(int) * free_timer_list_max); + memset(free_timer_list + (free_timer_list_max - 256), 0, 256 * sizeof(int)); + } + free_timer_list[free_timer_list_pos++] = i; + break; + case TIMER_INTERVAL: + if (DIFF_TICK(timer_data[i].tick , tick) < -1000) { + timer_data[i].tick = tick + timer_data[i].interval; + } else { + timer_data[i].tick += timer_data[i].interval; + } + timer_data[i].type &= ~TIMER_REMOVE_HEAP; + push_timer_heap(i); + break; + } + } + } + + if (nextmin < TIMER_MIN_INTERVAL) + nextmin = TIMER_MIN_INTERVAL; + + if ((unsigned int)(tick + nextmin) < tick) //Tick will loop, rearrange the heap on the next iteration. + fix_heap_flag = 1; + return nextmin; +} + +unsigned long get_uptime (void) +{ + return (unsigned long) difftime (time(NULL), start_time); +} + +void timer_init(void) +{ + time(&start_time); +} + +void timer_final(void) +{ + struct timer_func_list* tfl = tfl_root, *tfl2; + + while (tfl) { + tfl2 = tfl->next; // copy next pointer + aFree(tfl->name); // free structures + aFree(tfl); + tfl = tfl2; // use copied pointer for next cycle + } + + if (timer_data) aFree(timer_data); + if (timer_heap) aFree(timer_heap); + if (free_timer_list) aFree(free_timer_list); +} + diff --git a/src/common/timer.h b/src/common/timer.h new file mode 100644 index 000000000..aafefd1e2 --- /dev/null +++ b/src/common/timer.h @@ -0,0 +1,60 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _TIMER_H_ +#define _TIMER_H_ + +#ifdef __WIN32 +/* We need winsock lib to have timeval struct - windows is weirdo */ +#define __USE_W32_SOCKETS +#include +#endif + +#define BASE_TICK 5 + +#define TIMER_ONCE_AUTODEL 1 +#define TIMER_INTERVAL 2 +#define TIMER_REMOVE_HEAP 16 + +#define DIFF_TICK(a,b) ((int)((a)-(b))) + +// Struct declaration + +struct TimerData { + unsigned int tick; + int (*func)(int,unsigned int,int,int); + int id; + int data; + int type; + int interval; + int heap_pos; +}; + +// Function prototype declaration + +#ifdef __WIN32 +void gettimeofday(struct timeval *t, void *dummy); +#endif + +unsigned int gettick_nocache(void); +unsigned int gettick(void); + +int add_timer(unsigned int,int (*)(int,unsigned int,int,int),int,int); +int add_timer_interval(unsigned int,int (*)(int,unsigned int,int,int),int,int,int); +int delete_timer(int,int (*)(int,unsigned int,int,int)); + +int addtick_timer(int tid,unsigned int tick); +int settick_timer(int tid,unsigned int tick); +struct TimerData *get_timer(int tid); + +int do_timer(unsigned int tick); + +int add_timer_func_list(int (*)(int,unsigned int,int,int),char*); +char* search_timer_func_list(int (*)(int,unsigned int,int,int)); + +unsigned long get_uptime(void); + +void timer_init(void); +void timer_final(void); + +#endif // _TIMER_H_ diff --git a/src/common/utils.c b/src/common/utils.c new file mode 100644 index 000000000..57dc1f480 --- /dev/null +++ b/src/common/utils.c @@ -0,0 +1,384 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#include + +#ifdef WIN32 + #include + #define PATHSEP '\\' +#else + #include + #include + #include + #define PATHSEP '/' +#endif + +#include "utils.h" +#include "../common/mmo.h" +#include "../common/malloc.h" +#include "../common/showmsg.h" + +void dump(unsigned char *buffer, int num) +{ + int icnt,jcnt; + + printf(" Hex ASCII\n"); + printf(" ----------------------------------------------- ----------------"); + + for (icnt=0;icnt 31 && buffer[jcnt] < 127) + printf("%c",buffer[jcnt]); + else + printf("."); + } else + printf(" "); + } + } + printf("\n"); +} + +//NOTE: There is no need to use this function as the standard sqrt is plenty fast as it is. [Skotlex] +int newt_sqrt(int input) +{ + int new_value, value = input/2, count = 0; + if (!value) //Division by zero fix, pointed out by Shinomori. [Skotlex] + return input; + do + { + new_value = (value + input/value)>>1; + if (abs(value - new_value) <= 1) + return new_value; + value = new_value; + } + while (count++ < 25); + return new_value; +} + +#if defined(_WIN32) && !defined(MINGW) +char *rindex(char *str, char c) +{ + char *sptr; + + sptr = str; + while(*sptr) + ++sptr; + if (c == '\0') + return(sptr); + while(str != sptr) + if (*sptr-- == c) + return(++sptr); + return(NULL); +} + +int strcasecmp(const char *arg1, const char *arg2) +{ + int chk, i; + + if (arg1 == NULL || arg2 == NULL) { + ShowError("strcasecmp: received a NULL pointer, %p or %p.\n", arg1, arg2); + return (0); + } + + for (i = 0; arg1[i] || arg2[i]; i++) + if ((chk = LOWER(arg1[i]) - LOWER(arg2[i])) != 0) + return (chk); /* not equal */ + + return (0); +} + +int strncasecmp(const char *arg1, const char *arg2, int n) +{ + int chk, i; + + if (arg1 == NULL || arg2 == NULL) { + ShowError("strncasecmp(): received a NULL pointer, %p or %p.\n", arg1, arg2); + return (0); + } + + for (i = 0; (arg1[i] || arg2[i]) && (n > 0); i++, n--) + if ((chk = LOWER(arg1[i]) - LOWER(arg2[i])) != 0) + return (chk); /* not equal */ + + return (0); +} + +void str_upper(char *name) +{ + + int len = (int)strlen(name); + while (len--) { + if (*name >= 'a' && *name <= 'z') + *name -= ('a' - 'A'); + name++; + } +} + +void str_lower(char *name) +{ + int len = (int)strlen(name); + + while (len--) { + if (*name >= 'A' && *name <= 'Z') + *name += ('a' - 'A'); + name++; + } +} + +#endif + +// Allocate a StringBuf [MouseJstr] +struct StringBuf * StringBuf_Malloc() +{ + struct StringBuf * ret = (struct StringBuf *) aMallocA(sizeof(struct StringBuf)); + StringBuf_Init(ret); + return ret; +} + +// Initialize a previously allocated StringBuf [MouseJstr] +void StringBuf_Init(struct StringBuf * sbuf) { + sbuf->max_ = 1024; + sbuf->ptr_ = sbuf->buf_ = (char *) aMallocA(sbuf->max_ + 1); +} + +// printf into a StringBuf, moving the pointer [MouseJstr] +int StringBuf_Printf(struct StringBuf *sbuf,const char *fmt,...) +{ + va_list ap; + int n, size, off; + + while (1) { + /* Try to print in the allocated space. */ + va_start(ap, fmt); + size = sbuf->max_ - (sbuf->ptr_ - sbuf->buf_); + n = vsnprintf (sbuf->ptr_, size, fmt, ap); + va_end(ap); + /* If that worked, return the length. */ + if (n > -1 && n < size) { + sbuf->ptr_ += n; + return (int)(sbuf->ptr_ - sbuf->buf_); + } + /* Else try again with more space. */ + sbuf->max_ *= 2; // twice the old size + off = (int)(sbuf->ptr_ - sbuf->buf_); + sbuf->buf_ = (char *) aRealloc(sbuf->buf_, sbuf->max_ + 1); + sbuf->ptr_ = sbuf->buf_ + off; + } +} + +// Append buf2 onto the end of buf1 [MouseJstr] +int StringBuf_Append(struct StringBuf *buf1,const struct StringBuf *buf2) +{ + int buf1_avail = buf1->max_ - (buf1->ptr_ - buf1->buf_); + int size2 = (int)(buf2->ptr_ - buf2->buf_); + + if (size2 >= buf1_avail) { + int off = (int)(buf1->ptr_ - buf1->buf_); + buf1->max_ += size2; + buf1->buf_ = (char *) aRealloc(buf1->buf_, buf1->max_ + 1); + buf1->ptr_ = buf1->buf_ + off; + } + + memcpy(buf1->ptr_, buf2->buf_, size2); + buf1->ptr_ += size2; + return (int)(buf1->ptr_ - buf1->buf_); +} + +// Destroy a StringBuf [MouseJstr] +void StringBuf_Destroy(struct StringBuf *sbuf) +{ + aFree(sbuf->buf_); + sbuf->ptr_ = sbuf->buf_ = 0; +} + +// Free a StringBuf returned by StringBuf_Malloc [MouseJstr] +void StringBuf_Free(struct StringBuf *sbuf) +{ + StringBuf_Destroy(sbuf); + aFree(sbuf); +} + +// Return the built string from the StringBuf [MouseJstr] +char * StringBuf_Value(struct StringBuf *sbuf) +{ + *sbuf->ptr_ = '\0'; + return sbuf->buf_; +} + +#ifdef WIN32 + +char* checkpath(char *path, const char *srcpath) +{ // just make sure the char*path is not const + char *p=path; + if(NULL!=path && NULL!=srcpath) + while(*srcpath) { + if (*srcpath=='/') { + *p++ = '\\'; + srcpath++; + } + else + *p++ = *srcpath++; + } + *p = *srcpath; //EOS + return path; +} + +void findfile(const char *p, const char *pat, void (func)(const char*)) +{ + WIN32_FIND_DATA FindFileData; + HANDLE hFind; + char tmppath[MAX_PATH+1]; + + const char *path = (p ==NULL)? "." : p; + const char *pattern = (pat==NULL)? "" : pat; + + checkpath(tmppath,path); + if( PATHSEP != tmppath[strlen(tmppath)-1]) + strcat(tmppath, "\\*"); + else + strcat(tmppath, "*"); + + hFind = FindFirstFile(tmppath, &FindFileData); + if (hFind != INVALID_HANDLE_VALUE) + { + do + { + if (strcmp(FindFileData.cFileName, ".") == 0) + continue; + if (strcmp(FindFileData.cFileName, "..") == 0) + continue; + + sprintf(tmppath,"%s%c%s",path,PATHSEP,FindFileData.cFileName); + + if (FindFileData.cFileName && strstr(FindFileData.cFileName, pattern)) { + func( tmppath ); + } + + + if( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) + { + findfile(tmppath, pat, func); + } + }while (FindNextFile(hFind, &FindFileData) != 0); + FindClose(hFind); + } + return; +} +#else + +#define MAX_DIR_PATH 2048 + +char* checkpath(char *path, const char*srcpath) +{ // just make sure the char*path is not const + char *p=path; + if(NULL!=path && NULL!=srcpath) + while(*srcpath) { + if (*srcpath=='\\') { + *p++ = '/'; + srcpath++; + } + else + *p++ = *srcpath++; + } + *p = *srcpath; //EOS + return path; +} + +void findfile(const char *p, const char *pat, void (func)(const char*)) +{ + DIR* dir; // pointer to the scanned directory. + struct dirent* entry; // pointer to one directory entry. + struct stat dir_stat; // used by stat(). + char tmppath[MAX_DIR_PATH+1]; + char path[MAX_DIR_PATH+1]= "."; + const char *pattern = (pat==NULL)? "" : pat; + if(p!=NULL) strcpy(path,p); + + // open the directory for reading + dir = opendir( checkpath(path, path) ); + if (!dir) { + ShowError("Cannot read directory '%s'\n", path); + return; + } + + // scan the directory, traversing each sub-directory + // matching the pattern for each file name. + while ((entry = readdir(dir))) { + // skip the "." and ".." entries. + if (strcmp(entry->d_name, ".") == 0) + continue; + if (strcmp(entry->d_name, "..") == 0) + continue; + + sprintf(tmppath,"%s%c%s",path, PATHSEP, entry->d_name); + + // check if the pattern matchs. + if (entry->d_name && strstr(entry->d_name, pattern)) { + func( tmppath ); + } + // check if it is a directory. + if (stat(tmppath, &dir_stat) == -1) { + ShowError("stat error %s\n': ", tmppath); + continue; + } + // is this a directory? + if (S_ISDIR(dir_stat.st_mode)) { + // decent recursivly + findfile(tmppath, pat, func); + } + }//end while +} +#endif + +unsigned char GetByte(unsigned long val, size_t num) +{ + switch(num) + { + case 0: + return (unsigned char)((val & 0x000000FF) ); + case 1: + return (unsigned char)((val & 0x0000FF00)>>0x08); + case 2: + return (unsigned char)((val & 0x00FF0000)>>0x10); + case 3: + return (unsigned char)((val & 0xFF000000)>>0x18); + default: + return 0; //better throw something here + } +} +unsigned short GetWord(unsigned long val, size_t num) +{ + switch(num) + { + case 0: + return (unsigned short)((val & 0x0000FFFF) ); + case 1: + return (unsigned short)((val & 0xFFFF0000)>>0x10); + default: + return 0; //better throw something here + } +} +unsigned short MakeWord(unsigned char byte0, unsigned char byte1) +{ + return byte0 | (byte1<<0x08); +} +unsigned long MakeDWord(unsigned short word0, unsigned short word1) +{ + return ((unsigned long)word0) + | ((unsigned long)word1<<0x10); +} + diff --git a/src/common/utils.h b/src/common/utils.h new file mode 100644 index 000000000..9d2febe1b --- /dev/null +++ b/src/common/utils.h @@ -0,0 +1,52 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef COMMON_UTILS_H +#define COMMON_UTILS_H + + +#ifndef NULL +#define NULL (void *)0 +#endif + +#define LOWER(c) (((c)>='A' && (c) <= 'Z') ? ((c)+('a'-'A')) : (c)) +#define UPPER(c) (((c)>='a' && (c) <= 'z') ? ((c)+('A'-'a')) : (c) ) + +/* strcasecmp -> stricmp -> str_cmp */ +#if defined(_WIN32) && !defined(MINGW) + int strcasecmp(const char *arg1, const char *arg2); + int strncasecmp(const char *arg1, const char *arg2, int n); + void str_upper(char *name); + void str_lower(char *name); + char *rindex(char *str, char c); +#endif + +void dump(unsigned char *buffer, int num); +int newt_sqrt(int value); //Newton aproximation for getting a fast sqrt. + +struct StringBuf { + char *buf_; + char *ptr_; + unsigned int max_; +}; + +struct StringBuf * StringBuf_Malloc(void); +void StringBuf_Init(struct StringBuf *); +int StringBuf_Printf(struct StringBuf *,const char *,...); +int StringBuf_Append(struct StringBuf *,const struct StringBuf *); +char * StringBuf_Value(struct StringBuf *); +void StringBuf_Destroy(struct StringBuf *); +void StringBuf_Free(struct StringBuf *); + +void findfile(const char *p, const char *pat, void (func)(const char*)); + +////////////////////////////////////////////////////////////////////////// +// byte word dword access [Shinomori] +////////////////////////////////////////////////////////////////////////// + +extern unsigned char GetByte(unsigned long val, size_t num); +extern unsigned short GetWord(unsigned long val, size_t num); +extern unsigned short MakeWord(unsigned char byte0, unsigned char byte1); +extern unsigned long MakeDWord(unsigned short word0, unsigned short word1); + +#endif diff --git a/src/common/version.h b/src/common/version.h new file mode 100644 index 000000000..1c7961ee1 --- /dev/null +++ b/src/common/version.h @@ -0,0 +1,30 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _VERSION_H_ +#define _VERSION_H_ + +#define ATHENA_MAJOR_VERSION 1 // Major Version +#define ATHENA_MINOR_VERSION 0 // Minor Version +#define ATHENA_REVISION 0 // Revision + +#define ATHENA_RELEASE_FLAG 1 // 1=Develop,0=Stable +#define ATHENA_OFFICIAL_FLAG 1 // 1=Mod,0=Official + +#define ATHENA_SERVER_NONE 0 // not defined +#define ATHENA_SERVER_LOGIN 1 // login server +#define ATHENA_SERVER_CHAR 2 // char server +#define ATHENA_SERVER_INTER 4 // inter server +#define ATHENA_SERVER_MAP 8 // map server + +// ATHENA_MOD_VERSIONはパッチ番号です。 +// これは無理に変えなくても気が向いたら変える程度の扱いで。 +// (毎回アップロードの度に変更するのも面倒と思われるし、そもそも +//  この項目を参照する人がいるかどうかで疑問だから。) +// その程度の扱いなので、サーバーに問い合わせる側も、あくまで目安程度の扱いで +// あんまり信用しないこと。 +// 鯖snapshotの時や、大きな変更があった場合は設定してほしいです。 +// C言語の仕様上、最初に0を付けると8進数になるので間違えないで下さい。 +#define ATHENA_MOD_VERSION 1249 // mod version (patch No.) + +#endif diff --git a/src/ladmin/Makefile b/src/ladmin/Makefile new file mode 100644 index 000000000..f669a6ef1 --- /dev/null +++ b/src/ladmin/Makefile @@ -0,0 +1,17 @@ +all txt sql: ladmin + +COMMON_OBJ = ../common/obj/minicore.o ../common/obj/minisocket.o ../common/obj/timer.o \ + ../common/obj/malloc.o ../common/obj/showmsg.o ../common/obj/strlib.o +COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h \ + ../common/version.h ../common/malloc.h ../common/showmsg.h ../common/strlib.h + +ladmin: ladmin.o md5calc.o $(COMMON_OBJ) + $(CC) -o ../../$@ ladmin.o md5calc.o $(COMMON_OBJ) $(LIB_S) + +clean: + rm -f *.o ../../ladmin + +# DO NOT DELETE + +ladmin.o: ladmin.c ladmin.h md5calc.h $(COMMON_H) +md5calc.o: md5calc.c md5calc.h \ No newline at end of file diff --git a/src/ladmin/ladmin.c b/src/ladmin/ladmin.c new file mode 100644 index 000000000..8a3877dc4 --- /dev/null +++ b/src/ladmin/ladmin.c @@ -0,0 +1,4410 @@ +// (c) eAthena Dev Team - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +/////////////////////////////////////////////////////////////////////////// +// EAthena login-server remote administration tool +// Ladamin in C by [Yor] +// if you modify this software, modify ladmin in tool too. +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +void Gettimeofday(struct timeval *timenow) +{ + time_t t; + t = clock(); + timenow->tv_usec = t; + timenow->tv_sec = t / CLK_TCK; + return; +} +#define gettimeofday(timenow, dummy) Gettimeofday(timenow) +#else +#include +#include +#include // gettimeofday +#include +#include // close +#include // inet_addr +#include // gethostbyname +#endif +#include +#include +#include +#include +#include // str* +#include // valist +#include // tolower + +#include "../common/core.h" +#include "../common/strlib.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "ladmin.h" +#include "../common/version.h" +#include "../common/mmo.h" + +#ifdef PASSWORDENC +#include "md5calc.h" +#endif + +//-------------------------------INSTRUCTIONS------------------------------ +// Set the variables below: +// IP of the login server. +// Port where the login-server listens incoming packets. +// Password of administration (same of config_athena.conf). +// Displayed language of the sofware (if not correct, english is used). +// IMPORTANT: +// Be sure that you authorize remote administration in login-server +// (see login_athena.conf, 'admin_state' parameter) +//------------------------------------------------------------------------- +char loginserverip[16] = "127.0.0.1"; // IP of login-server +int loginserverport = 6900; // Port of login-server +char loginserveradminpassword[24] = "admin"; // Administration password +#ifdef PASSWORDENC +int passenc = 2; // Encoding type of the password +#else +int passenc = 0; // Encoding type of the password +#endif +char defaultlanguage = 'E'; // Default language (F: Fran軋is/E: English) + // (if it's not 'F', default is English) +char ladmin_log_filename[1024] = "log/ladmin.log"; +char date_format[32] = "%Y-%m-%d %H:%M:%S"; +//------------------------------------------------------------------------- +// LIST of COMMANDs that you can type at the prompt: +// To use these commands you can only type only the first letters. +// You must type a minimum of letters (you can not type 'a', +// because ladmin doesn't know if it's for 'aide' or for 'add') +// q <= quit, li <= list, pass <= passwd, etc. +// +// Note: every time you must give a account_name, you can use "" or '' (spaces can be included) +// +// aide/help/? +// Display the description of the commands +// aide/help/? [command] +// Display the description of the specified command +// +// add +// Create an account with the default email (a@a.com). +// Concerning the sex, only the first letter is used (F or M). +// The e-mail is set to a@a.com (default e-mail). It's like to have no e-mail. +// When the password is omitted, the input is done without displaying of the pressed keys. +// add testname Male testpass +// +// ban/banish yyyy/mm/dd hh:mm:ss +// Changes the final date of a banishment of an account. +// Like banset, but is at end. +// +// banadd +// Adds or substracts time from the final date of a banishment of an account. +// Modifier is done as follows: +// Adjustment value (-1, 1, +1, etc...) +// Modified element: +// a or y: year +// m: month +// j or d: day +// h: hour +// mn: minute +// s: second +// banadd testname +1m-2mn1s-6y +// this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time. +// NOTE: If you modify the final date of a non-banished account, +// you fix the final date to (actual time +- adjustments) +// +// banset yyyy/mm/dd [hh:mm:ss] +// Changes the final date of a banishment of an account. +// Default time [hh:mm:ss]: 23:59:59. +// banset 0 +// Set a non-banished account (0 = unbanished). +// +// block +// Set state 5 (You have been blocked by the GM Team) to an account. +// Like state 5. +// +// check +// Check the validity of a password for an account +// NOTE: Server will never sends back a password. +// It's the only method you have to know if a password is correct. +// The other method is to have a ('physical') access to the accounts file. +// +// create +// Like the 'add' command, but with e-mail moreover. +// create testname Male my@mail.com testpass +// +// del +// Remove an account. +// This order requires confirmation. After confirmation, the account is deleted. +// +// email +// Modify the e-mail of an account. +// +// getcount +// Give the number of players online on all char-servers. +// +// gm [GM_level] +// Modify the GM level of an account. +// Default value remove GM level (GM level = 0). +// gm testname 80 +// +// id +// Give the id of an account. +// +// info +// Display complete information of an account. +// +// kami +// Sends a broadcast message on all map-server (in yellow). +// kamib +// Sends a broadcast message on all map-server (in blue). +// +// language +// Change the language of displaying. +// +// list/ls [start_id [end_id]] +// Display a list of accounts. +// 'start_id', 'end_id': indicate end and start identifiers. +// Research by name is not possible with this command. +// list 10 9999999 +// +// listBan/lsBan [start_id [end_id]] +// Like list/ls, but only for accounts with state or banished +// +// listGM/lsGM [start_id [end_id]] +// Like list/ls, but only for GM accounts +// +// listOK/lsOK [start_id [end_id]] +// Like list/ls, but only for accounts without state and not banished +// +// memo +// Modify the memo of an account. +// 'memo': it can have until 253 characters (with spaces or not). +// +// name +// Give the name of an account. +// +// passwd +// Change the password of an account. +// When new password is omitted, the input is done without displaying of the pressed keys. +// +// quit/end/exit +// End of the program of administration +// +// reloadGM +// Reload GM configuration file +// +// search +// Seek accounts. +// Displays the accounts whose names correspond. +// search -r/-e/--expr/--regex +// Seek accounts by regular expression. +// Displays the accounts whose names correspond. +// +// sex +// Modify the sex of an account. +// sex testname Male +// +// state +// Change the state of an account. +// 'new_state': state is the state of the packet 0x006a + 1. The possibilities are: +// 0 = Account ok 6 = Your Game's EXE file is not the latest version +// 1 = Unregistered ID 7 = You are Prohibited to log in until %s +// 2 = Incorrect Password 8 = Server is jammed due to over populated +// 3 = This ID is expired 9 = No MSG +// 4 = Rejected from Server 100 = This ID has been totally erased +// 5 = You have been blocked by the GM Team +// all other values are 'No MSG', then use state 9 please. +// 'error_message_#7': message of the code error 6 = Your are Prohibited to log in until %s (packet 0x006a) +// +// timeadd +// Adds or substracts time from the validity limit of an account. +// Modifier is done as follows: +// Adjustment value (-1, 1, +1, etc...) +// Modified element: +// a or y: year +// m: month +// j or d: day +// h: hour +// mn: minute +// s: second +// timeadd testname +1m-2mn1s-6y +// this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time. +// NOTE: You can not modify a unlimited validity limit. +// If you want modify it, you want probably create a limited validity limit. +// So, at first, you must set the validity limit to a date/time. +// +// timeset yyyy/mm/dd [hh:mm:ss] +// Changes the validity limit of an account. +// Default time [hh:mm:ss]: 23:59:59. +// timeset 0 +// Gives an unlimited validity limit (0 = unlimited). +// +// unban/unbanish +// Unban an account. +// Like banset 0. +// +// unblock +// Set state 0 (Account ok) to an account. +// Like state 0. +// +// version +// Display the version of the login-server. +// +// who +// Displays complete information of an account. +// +//------------------------------------------------------------------------- +int login_fd; +int login_ip; +int bytes_to_read = 0; // flag to know if we waiting bytes from login-server +char command[1024]; +char parameters[1024]; +int list_first, list_last, list_type, list_count; // parameter to display a list of accounts +int already_exit_function = 0; // sometimes, the exit function is called twice... so, don't log twice the message + +//------------------------------ +// Writing function of logs file +//------------------------------ +int ladmin_log(char *fmt, ...) { + FILE *logfp; + va_list ap; + struct timeval tv; + char tmpstr[2048]; + + va_start(ap, fmt); + + logfp = fopen(ladmin_log_filename, "a"); + if (logfp) { + if (fmt[0] == '\0') // jump a line if no message + fprintf(logfp, RETCODE); + else { + gettimeofday(&tv, NULL); + strftime(tmpstr, 24, date_format, localtime((const time_t*)&(tv.tv_sec))); + sprintf(tmpstr + strlen(tmpstr), ".%03d: %s", (int)tv.tv_usec / 1000, fmt); + vfprintf(logfp, tmpstr, ap); + } + fclose(logfp); + } + + va_end(ap); + return 0; +} + +//--------------------------------------------- +// Function to return ordonal text of a number. +//--------------------------------------------- +char* makeordinal(int number) { + if (defaultlanguage == 'F') { + if (number == 0) + return ""; + else if (number == 1) + return "er"; + else + return "鑪e"; + } else { + if ((number % 10) < 4 && (number % 10) != 0 && (number < 10 || number > 20)) { + if ((number % 10) == 1) + return "st"; + else if ((number % 10) == 2) + return "nd"; + else + return "rd"; + } else { + return "th"; + } + } + return ""; +} + +//----------------------------------------------------------------------------------------- +// Function to test of the validity of an account name (return 0 if incorrect, and 1 if ok) +//----------------------------------------------------------------------------------------- +int verify_accountname(char* account_name) { + int i; + + for(i = 0; account_name[i]; i++) { + if (account_name[i] < 32) { + if (defaultlanguage == 'F') { + printf("Caract鑽e interdit trouv dans le nom du compte (%d%s caract鑽e).\n", i+1, makeordinal(i+1)); + ladmin_log("Caract鑽e interdit trouv dans le nom du compte (%d%s caract鑽e)." RETCODE, i+1, makeordinal(i+1)); + } else { + printf("Illegal character found in the account name (%d%s character).\n", i+1, makeordinal(i+1)); + ladmin_log("Illegal character found in the account name (%d%s character)." RETCODE, i+1, makeordinal(i+1)); + } + return 0; + } + } + + if (strlen(account_name) < 4) { + if (defaultlanguage == 'F') { + printf("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es.\n"); + ladmin_log("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es." RETCODE); + } else { + printf("Account name is too short. Please input an account name of 4-23 bytes.\n"); + ladmin_log("Account name is too short. Please input an account name of 4-23 bytes." RETCODE); + } + return 0; + } + + if (strlen(account_name) > 23) { + if (defaultlanguage == 'F') { + printf("Nom du compte trop long. Entrez un nom de compte de 4-23 caract鑽es.\n"); + ladmin_log("Nom du compte trop long. Entrez un nom de compte de 4-23 caract鑽es." RETCODE); + } else { + printf("Account name is too long. Please input an account name of 4-23 bytes.\n"); + ladmin_log("Account name is too long. Please input an account name of 4-23 bytes." RETCODE); + } + return 0; + } + + return 1; +} + +//--------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//--------------------------------------------------- +int e_mail_check(char *email) { + char ch; + char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//---------------------------------- +// Sub-function: Input of a password +//---------------------------------- +int typepasswd(char * password) { + char password1[1023], password2[1023]; + int letter; + int i; + + if (defaultlanguage == 'F') { + ladmin_log("Aucun mot de passe n'a 騁 donn. Demande d'un mot de passe." RETCODE); + } else { + ladmin_log("No password was given. Request to obtain a password." RETCODE); + } + + memset(password1, '\0', sizeof(password1)); + memset(password2, '\0', sizeof(password2)); + if (defaultlanguage == 'F') + printf("\033[1;36m Entrez le mot de passe > \033[0;32;42m"); + else + printf("\033[1;36m Type the password > \033[0;32;42m"); + i = 0; + while ((letter = getchar()) != '\n') + password1[i++] = letter; + if (defaultlanguage == 'F') + printf("\033[0m\033[1;36m R-entrez le mot de passe > \033[0;32;42m"); + else + printf("\033[0m\033[1;36m Verify the password > \033[0;32;42m"); + i = 0; + while ((letter = getchar()) != '\n') + password2[i++] = letter; + + printf("\033[0m"); + fflush(stdout); + fflush(stdin); + + if (strcmp(password1, password2) != 0) { + if (defaultlanguage == 'F') { + printf("Erreur de v駻ification du mot de passe: Saisissez le m麥e mot de passe svp.\n"); + ladmin_log("Erreur de v駻ification du mot de passe: Saisissez le m麥e mot de passe svp." RETCODE); + ladmin_log(" Premier mot de passe: %s, second mot de passe: %s." RETCODE, password1, password2); + } else { + printf("Password verification failed. Please input same password.\n"); + ladmin_log("Password verification failed. Please input same password." RETCODE); + ladmin_log(" First password: %s, second password: %s." RETCODE, password1, password2); + } + return 0; + } + if (defaultlanguage == 'F') { + ladmin_log("Mot de passe saisi: %s." RETCODE, password1); + } else { + ladmin_log("Typed password: %s." RETCODE, password1); + } + strcpy(password, password1); + return 1; +} + +//------------------------------------------------------------------------------------ +// Sub-function: Test of the validity of password (return 0 if incorrect, and 1 if ok) +//------------------------------------------------------------------------------------ +int verify_password(char * password) { + int i; + + for(i = 0; password[i]; i++) { + if (password[i] < 32) { + if (defaultlanguage == 'F') { + printf("Caract鑽e interdit trouv dans le mot de passe (%d%s caract鑽e).\n", i+1, makeordinal(i+1)); + ladmin_log("Caract鑽e interdit trouv dans le nom du compte (%d%s caract鑽e)." RETCODE, i+1, makeordinal(i+1)); + } else { + printf("Illegal character found in the password (%d%s character).\n", i+1, makeordinal(i+1)); + ladmin_log("Illegal character found in the password (%d%s character)." RETCODE, i+1, makeordinal(i+1)); + } + return 0; + } + } + + if (strlen(password) < 4) { + if (defaultlanguage == 'F') { + printf("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es.\n"); + ladmin_log("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es." RETCODE); + } else { + printf("Account name is too short. Please input an account name of 4-23 bytes.\n"); + ladmin_log("Account name is too short. Please input an account name of 4-23 bytes." RETCODE); + } + return 0; + } + + if (strlen(password) > 23) { + if (defaultlanguage == 'F') { + printf("Mot de passe trop long. Entrez un mot de passe de 4-23 caract鑽es.\n"); + ladmin_log("Mot de passe trop long. Entrez un mot de passe de 4-23 caract鑽es." RETCODE); + } else { + printf("Password is too long. Please input a password of 4-23 bytes.\n"); + ladmin_log("Password is too long. Please input a password of 4-23 bytes." RETCODE); + } + return 0; + } + + return 1; +} + +//------------------------------------------------------------------ +// Sub-function: Check the name of a command (return complete name) +//----------------------------------------------------------------- +int check_command(char * command) { +// help + if (strncmp(command, "aide", 2) == 0 && strncmp(command, "aide", strlen(command)) == 0) // not 1 letter command: 'aide' or 'add'? + strcpy(command, "aide"); + else if (strncmp(command, "help", 1) == 0 && strncmp(command, "help", strlen(command)) == 0) + strcpy(command, "help"); +// general commands + else if (strncmp(command, "add", 2) == 0 && strncmp(command, "add", strlen(command)) == 0) // not 1 letter command: 'aide' or 'add'? + strcpy(command, "add"); + else if ((strncmp(command, "ban", 3) == 0 && strncmp(command, "ban", strlen(command)) == 0) || + (strncmp(command, "banish", 4) == 0 && strncmp(command, "banish", strlen(command)) == 0)) + strcpy(command, "ban"); + else if ((strncmp(command, "banadd", 4) == 0 && strncmp(command, "banadd", strlen(command)) == 0) || // not 1 letter command: 'ba' or 'bs'? 'banadd' or 'banset' ? + strcmp(command, "ba") == 0) + strcpy(command, "banadd"); + else if ((strncmp(command, "banset", 4) == 0 && strncmp(command, "banset", strlen(command)) == 0) || // not 1 letter command: 'ba' or 'bs'? 'banadd' or 'banset' ? + strcmp(command, "bs") == 0) + strcpy(command, "banset"); + else if (strncmp(command, "block", 2) == 0 && strncmp(command, "block", strlen(command)) == 0) + strcpy(command, "block"); + else if (strncmp(command, "check", 2) == 0 && strncmp(command, "check", strlen(command)) == 0) // not 1 letter command: 'check' or 'create'? + strcpy(command, "check"); + else if (strncmp(command, "create", 2) == 0 && strncmp(command, "create", strlen(command)) == 0) // not 1 letter command: 'check' or 'create'? + strcpy(command, "create"); + else if (strncmp(command, "delete", 1) == 0 && strncmp(command, "delete", strlen(command)) == 0) + strcpy(command, "delete"); + else if ((strncmp(command, "email", 2) == 0 && strncmp(command, "email", strlen(command)) == 0) || // not 1 letter command: 'email', 'end' or 'exit'? + (strncmp(command, "e-mail", 2) == 0 && strncmp(command, "e-mail", strlen(command)) == 0)) + strcpy(command, "email"); + else if (strncmp(command, "getcount", 2) == 0 && strncmp(command, "getcount", strlen(command)) == 0) // not 1 letter command: 'getcount' or 'gm'? + strcpy(command, "getcount"); +// else if (strncmp(command, "gm", 2) == 0 && strncmp(command, "gm", strlen(command)) == 0) // not 1 letter command: 'getcount' or 'gm'? +// strcpy(command, "gm"); +// else if (strncmp(command, "id", 2) == 0 && strncmp(command, "id", strlen(command)) == 0) // not 1 letter command: 'id' or 'info'? +// strcpy(command, "id"); + else if (strncmp(command, "info", 2) == 0 && strncmp(command, "info", strlen(command)) == 0) // not 1 letter command: 'id' or 'info'? + strcpy(command, "info"); +// else if (strncmp(command, "kami", 4) == 0 && strncmp(command, "kami", strlen(command)) == 0) // only all letters command: 'kami' or 'kamib'? +// strcpy(command, "kami"); +// else if (strncmp(command, "kamib", 5) == 0 && strncmp(command, "kamib", strlen(command)) == 0) // only all letters command: 'kami' or 'kamib'? +// strcpy(command, "kamib"); + else if ((strncmp(command, "language", 2) == 0 && strncmp(command, "language", strlen(command)) == 0)) // not 1 letter command: 'language' or 'list'? + strcpy(command, "language"); + else if ((strncmp(command, "list", 2) == 0 && strncmp(command, "list", strlen(command)) == 0) || // 'list' is default list command // not 1 letter command: 'language' or 'list'? + strcmp(command, "ls") == 0) + strcpy(command, "list"); + else if ((strncmp(command, "listban", 5) == 0 && strncmp(command, "listban", strlen(command)) == 0) || + (strncmp(command, "lsban", 3) == 0 && strncmp(command, "lsban", strlen(command)) == 0) || + strcmp(command, "lb") == 0) + strcpy(command, "listban"); + else if ((strncmp(command, "listgm", 5) == 0 && strncmp(command, "listgm", strlen(command)) == 0) || + (strncmp(command, "lsgm", 3) == 0 && strncmp(command, "lsgm", strlen(command)) == 0) || + strcmp(command, "lg") == 0) + strcpy(command, "listgm"); + else if ((strncmp(command, "listok", 5) == 0 && strncmp(command, "listok", strlen(command)) == 0) || + (strncmp(command, "lsok", 3) == 0 && strncmp(command, "lsok", strlen(command)) == 0) || + strcmp(command, "lo") == 0) + strcpy(command, "listok"); + else if (strncmp(command, "memo", 1) == 0 && strncmp(command, "memo", strlen(command)) == 0) + strcpy(command, "memo"); + else if (strncmp(command, "name", 1) == 0 && strncmp(command, "name", strlen(command)) == 0) + strcpy(command, "name"); + else if ((strncmp(command, "password", 1) == 0 && strncmp(command, "password", strlen(command)) == 0) || + strcmp(command, "passwd") == 0) + strcpy(command, "password"); + else if (strncmp(command, "reloadgm", 1) == 0 && strncmp(command, "reloadgm", strlen(command)) == 0) + strcpy(command, "reloadgm"); + else if (strncmp(command, "search", 3) == 0 && strncmp(command, "search", strlen(command)) == 0) // not 1 letter command: 'search', 'state' or 'sex'? + strcpy(command, "search"); // not 2 letters command: 'search' or 'sex'? +// else if (strncmp(command, "sex", 3) == 0 && strncmp(command, "sex", strlen(command)) == 0) // not 1 letter command: 'search', 'state' or 'sex'? +// strcpy(command, "sex"); // not 2 letters command: 'search' or 'sex'? + else if (strncmp(command, "state", 2) == 0 && strncmp(command, "state", strlen(command)) == 0) // not 1 letter command: 'search', 'state' or 'sex'? + strcpy(command, "state"); + else if ((strncmp(command, "timeadd", 5) == 0 && strncmp(command, "timeadd", strlen(command)) == 0) || // not 1 letter command: 'ta' or 'ts'? 'timeadd' or 'timeset'? + strcmp(command, "ta") == 0) + strcpy(command, "timeadd"); + else if ((strncmp(command, "timeset", 5) == 0 && strncmp(command, "timeset", strlen(command)) == 0) || // not 1 letter command: 'ta' or 'ts'? 'timeadd' or 'timeset'? + strcmp(command, "ts") == 0) + strcpy(command, "timeset"); + else if ((strncmp(command, "unban", 5) == 0 && strncmp(command, "unban", strlen(command)) == 0) || + (strncmp(command, "unbanish", 4) == 0 && strncmp(command, "unbanish", strlen(command)) == 0)) + strcpy(command, "unban"); + else if (strncmp(command, "unblock", 4) == 0 && strncmp(command, "unblock", strlen(command)) == 0) + strcpy(command, "unblock"); + else if (strncmp(command, "version", 1) == 0 && strncmp(command, "version", strlen(command)) == 0) + strcpy(command, "version"); + else if (strncmp(command, "who", 1) == 0 && strncmp(command, "who", strlen(command)) == 0) + strcpy(command, "who"); +// quit + else if (strncmp(command, "quit", 1) == 0 && strncmp(command, "quit", strlen(command)) == 0) + strcpy(command, "quit"); + else if (strncmp(command, "exit", 2) == 0 && strncmp(command, "exit", strlen(command)) == 0) // not 1 letter command: 'email', 'end' or 'exit'? + strcpy(command, "exit"); + else if (strncmp(command, "end", 2) == 0 && strncmp(command, "end", strlen(command)) == 0) // not 1 letter command: 'email', 'end' or 'exit'? + strcpy(command, "end"); + + return 0; +} + +//----------------------------------------- +// Sub-function: Display commands of ladmin +//----------------------------------------- +void display_help(char* param, int language) { + char command[1023]; + int i; + + memset(command, '\0', sizeof(command)); + + if (sscanf(param, "%s ", command) < 1 || strlen(command) == 0) + strcpy(command, ""); // any value that is not a command + + if (command[0] == '?') { + if (defaultlanguage == 'F') + strcpy(command, "aide"); + else + strcpy(command, "help"); + } + + // lowercase for command + for (i = 0; command[i]; i++) + command[i] = tolower(command[i]); + + // Analyse of the command + check_command(command); // give complete name to the command + + if (defaultlanguage == 'F') { + ladmin_log("Affichage des commandes ou d'une commande." RETCODE); + } else { + ladmin_log("Displaying of the commands or a command." RETCODE); + } + + if (language == 1) { + if (strcmp(command, "aide") == 0) { + printf("aide/help/?\n"); + printf(" Affiche la description des commandes\n"); + printf("aide/help/? [commande]\n"); + printf(" Affiche la description de la commande specifi馥\n"); + } else if (strcmp(command, "help") == 0 ) { + printf("aide/help/?\n"); + printf(" Display the description of the commands\n"); + printf("aide/help/? [command]\n"); + printf(" Display the description of the specified command\n"); +// general commands + } else if (strcmp(command, "add") == 0) { + printf("add \n"); + printf(" Cr馥 un compte avec l'email par d馭aut (a@a.com).\n"); + printf(" Concernant le sexe, seule la premi鑽e lettre compte (F ou M).\n"); + printf(" L'e-mail est a@a.com (e-mail par d馭aut). C'est comme n'avoir aucun e-mail.\n"); + printf(" Lorsque motdepasse est omis, la saisie se fait sans que la frappe se voit.\n"); + printf(" add testname Male testpass\n"); + } else if (strcmp(command, "ban") == 0) { + printf("ban/banish aaaa/mm/jj hh:mm:ss \n"); + printf(" Change la date de fin de bannissement d'un compte.\n"); + printf(" Comme banset, mais est la fin.\n"); + } else if (strcmp(command, "banadd") == 0) { + printf("banadd \n"); + printf(" Ajoute ou soustrait du temps la date de banissement d'un compte.\n"); + printf(" Les modificateurs sont construits comme suit:\n"); + printf(" Valeur d'ajustement (-1, 1, +1, etc...)\n"); + printf(" El駑ent modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" banadd testname +1m-2mn1s-6a\n"); + printf(" Cette exemple ajoute 1 mois et une seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + printf("NOTE: Si vous modifez la date de banissement d'un compte non bani,\n"); + printf(" vous indiquez comme date (le moment actuel +- les ajustements)\n"); + } else if (strcmp(command, "banset") == 0) { + printf("banset aaaa/mm/jj [hh:mm:ss]\n"); + printf(" Change la date de fin de bannissement d'un compte.\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + printf("banset 0\n"); + printf(" D饕anni un compte (0 = de-banni).\n"); + } else if (strcmp(command, "block") == 0) { + printf("block \n"); + printf(" Place le status d'un compte 5 (You have been blocked by the GM Team).\n"); + printf(" La commande est l'駲uivalent de state 5.\n"); + } else if (strcmp(command, "check") == 0) { + printf("check \n"); + printf(" V駻ifie la validit d'un mot de passe pour un compte\n"); + printf(" NOTE: Le serveur n'enverra jamais un mot de passe.\n"); + printf(" C'est la seule m騁hode que vous poss馘ez pour savoir\n"); + printf(" si un mot de passe est le bon. L'autre m騁hode est\n"); + printf(" d'avoir un acc鑚 ('physique') au fichier des comptes.\n"); + } else if (strcmp(command, "create") == 0) { + printf("create \n"); + printf(" Comme la commande add, mais avec l'e-mail en plus.\n"); + printf(" create testname Male mon@mail.com testpass\n"); + } else if (strcmp(command, "delete") == 0) { + printf("del \n"); + printf(" Supprime un compte.\n"); + printf(" La commande demande confirmation. Apr鑚 confirmation, le compte est d騁ruit.\n"); + } else if (strcmp(command, "email") == 0) { + printf("email \n"); + printf(" Modifie l'e-mail d'un compte.\n"); + } else if (strcmp(command, "getcount") == 0) { + printf("getcount\n"); + printf(" Donne le nombre de joueurs en ligne par serveur de char.\n"); + } else if (strcmp(command, "gm") == 0) { + printf("gm [Niveau_GM]\n"); + printf(" Modifie le niveau de GM d'un compte.\n"); + printf(" Valeur par d馭aut: 0 (suppression du niveau de GM).\n"); + printf(" gm nomtest 80\n"); + } else if (strcmp(command, "id") == 0) { + printf("id \n"); + printf(" Donne l'id d'un compte.\n"); + } else if (strcmp(command, "info") == 0) { + printf("info \n"); + printf(" Affiche les informations sur un compte.\n"); + } else if (strcmp(command, "kami") == 0) { + printf("kami \n"); + printf(" Envoi un message g駭駻al sur tous les serveurs de map (en jaune).\n"); + } else if (strcmp(command, "kamib") == 0) { + printf("kamib \n"); + printf(" Envoi un message g駭駻al sur tous les serveurs de map (en bleu).\n"); + } else if (strcmp(command, "language") == 0) { + printf("language \n"); + printf(" Change la langue d'affichage.\n"); + printf(" Langues possibles: 'Fran軋is' ou 'English'.\n"); + } else if (strcmp(command, "list") == 0) { + printf("list/ls [Premier_id [Dernier_id]]\n"); + printf(" Affiche une liste de comptes.\n"); + printf(" 'Premier_id', 'Dernier_id': indique les identifiants de d駱art et de fin.\n"); + printf(" La recherche par nom n'est pas possible avec cette commande.\n"); + printf(" list 10 9999999\n"); + } else if (strcmp(command, "listban") == 0) { + printf("listBan/lsBan [Premier_id [Dernier_id]]\n"); + printf(" Comme list/ls, mais seulement pour les comptes avec statut ou bannis.\n"); + } else if (strcmp(command, "listgm") == 0) { + printf("listGM/lsGM [Premier_id [Dernier_id]]\n"); + printf(" Comme list/ls, mais seulement pour les comptes GM.\n"); + } else if (strcmp(command, "listok") == 0) { + printf("listOK/lsOK [Premier_id [Dernier_id]]\n"); + printf(" Comme list/ls, mais seulement pour les comptes sans statut et non bannis.\n"); + } else if (strcmp(command, "memo") == 0) { + printf("memo \n"); + printf(" Modifie le m駑o d'un compte.\n"); + printf(" 'memo': Il peut avoir jusqu' 253 caract鑽es (avec des espaces ou non).\n"); + } else if (strcmp(command, "name") == 0) { + printf("name \n"); + printf(" Donne le nom d'un compte.\n"); + } else if (strcmp(command, "password") == 0) { + printf("passwd \n"); + printf(" Change le mot de passe d'un compte.\n"); + printf(" Lorsque nouveaumotdepasse est omis,\n"); + printf(" la saisie se fait sans que la frappe ne se voit.\n"); + } else if (strcmp(command, "reloadgm") == 0) { + printf("reloadGM\n"); + printf(" Reload GM configuration file\n"); + } else if (strcmp(command, "search") == 0) { + printf("search \n"); + printf(" Cherche des comptes.\n"); + printf(" Affiche les comptes dont les noms correspondent.\n"); +// printf("search -r/-e/--expr/--regex \n"); +// printf(" Cherche des comptes par expression reguli鑽e.\n"); +// printf(" Affiche les comptes dont les noms correspondent.\n"); + } else if (strcmp(command, "sex") == 0) { + printf("sex \n"); + printf(" Modifie le sexe d'un compte.\n"); + printf(" sex testname Male\n"); + } else if (strcmp(command, "state") == 0) { + printf("state \n"); + printf(" Change le statut d'un compte.\n"); + printf(" 'nouveaustatut': Le statut est le m麥e que celui du packet 0x006a + 1.\n"); + printf(" les possibilit駸 sont:\n"); + printf(" 0 = Compte ok\n"); + printf(" 1 = Unregistered ID\n"); + printf(" 2 = Incorrect Password\n"); + printf(" 3 = This ID is expired\n"); + printf(" 4 = Rejected from Server\n"); + printf(" 5 = You have been blocked by the GM Team\n"); + printf(" 6 = Your Game's EXE file is not the latest version\n"); + printf(" 7 = You are Prohibited to log in until...\n"); + printf(" 8 = Server is jammed due to over populated\n"); + printf(" 9 = No MSG\n"); + printf(" 100 = This ID has been totally erased\n"); + printf(" all other values are 'No MSG', then use state 9 please.\n"); + printf(" 'message_erreur_7': message du code erreur 6 =\n"); + printf(" = Your are Prohibited to log in until... (packet 0x006a)\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeadd \n"); + printf(" Ajoute/soustrait du temps la limite de validit d'un compte.\n"); + printf(" Le modificateur est compos comme suit:\n"); + printf(" Valeur modificatrice (-1, 1, +1, etc...)\n"); + printf(" El駑ent modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" timeadd testname +1m-2mn1s-6a\n"); + printf(" Cette exemple ajoute 1 mois et une seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + printf("NOTE: Vous ne pouvez pas modifier une limite de validit illimit馥. Si vous\n"); + printf(" d駸irez le faire, c'est que vous voulez probablement cr馥r un limite de\n"); + printf(" validit limit馥. Donc, en premier, fix une limite de valitid.\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeset aaaa/mm/jj [hh:mm:ss]\n"); + printf(" Change la limite de validit d'un compte.\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + printf("timeset 0\n"); + printf(" Donne une limite de validit illimit馥 (0 = illimit馥).\n"); + } else if (strcmp(command, "unban") == 0) { + printf("unban/unbanish \n"); + printf(" Ote le banissement d'un compte.\n"); + printf(" La commande est l'駲uivalent de banset 0.\n"); + } else if (strcmp(command, "unblock") == 0) { + printf("unblock \n"); + printf(" Place le status d'un compte 0 (Compte ok).\n"); + printf(" La commande est l'駲uivalent de state 0.\n"); + } else if (strcmp(command, "version") == 0) { + printf("version\n"); + printf(" Affiche la version du login-serveur.\n"); + } else if (strcmp(command, "who") == 0) { + printf("who \n"); + printf(" Affiche les informations sur un compte.\n"); +// quit + } else if (strcmp(command, "quit") == 0 || + strcmp(command, "exit") == 0 || + strcmp(command, "end") == 0) { + printf("quit/end/exit\n"); + printf(" Fin du programme d'administration.\n"); +// unknown command + } else { + if (strlen(command) > 0) + printf("Commande inconnue [%s] pour l'aide. Affichage de toutes les commandes.\n", command); + printf(" aide/help/? -- Affiche cet aide\n"); + printf(" aide/help/? [commande] -- Affiche l'aide de la commande\n"); + printf(" add -- Cr馥 un compte (sans email)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss -- Fixe la date finale de banismnt\n"); + printf(" banadd/ba -- Ajout/soustrait du temps la\n"); + printf(" exemple: ba moncompte +1m-2mn1s-2y date finale de banissement\n"); + printf(" banset/bs aaaa/mm/jj [hh:mm:ss] -- Change la date fin de banisemnt\n"); + printf(" banset/bs 0 -- D-banis un compte.\n"); + printf(" block -- Mets le status d'un compte 5 (blocked by the GM Team)\n"); + printf(" check -- V駻ifie un mot de passe d'un compte\n"); + printf(" create -- Cr馥 un compte (avec email)\n"); + printf(" del -- Supprime un compte\n"); + printf(" email -- Modifie l'e-mail d'un compte\n"); + printf(" getcount -- Donne le nb de joueurs en ligne\n"); + printf(" gm [Niveau_GM] -- Modifie le niveau de GM d'un compte\n"); + printf(" id -- Donne l'id d'un compte\n"); + printf(" info -- Affiche les infos sur un compte\n"); + printf(" kami -- Envoi un message g駭駻al (en jaune)\n"); + printf(" kamib -- Envoi un message g駭駻al (en bleu)\n"); + printf(" language -- Change la langue d'affichage.\n"); + printf(" list/ls [Premier_id [Dernier_id] ] -- Affiche une liste de comptes\n"); + printf(" listBan/lsBan [Premier_id [Dernier_id] ] -- Affiche une liste de comptes\n"); + printf(" avec un statut ou bannis\n"); + printf(" listGM/lsGM [Premier_id [Dernier_id] ] -- Affiche une liste de comptes GM\n"); + printf(" listOK/lsOK [Premier_id [Dernier_id] ] -- Affiche une liste de comptes\n"); + printf(" sans status et non bannis\n"); + printf(" memo -- Modifie le memo d'un compte\n"); + printf(" name -- Donne le nom d'un compte\n"); + printf(" passwd -- Change le mot de passe d'un compte\n"); + printf(" quit/end/exit -- Fin du programme d'administation\n"); + printf(" reloadGM -- Recharger le fichier de config des GM\n"); + printf(" search -- Cherche des comptes\n"); +// printf(" search -e/-r/--expr/--regex -- Cherche des comptes par REGEX\n"); + printf(" sex -- Modifie le sexe d'un compte\n"); + printf(" state -- Change le statut d'1 compte\n"); + printf(" timeadd/ta -- Ajout/soustrait du temps la\n"); + printf(" exemple: ta moncompte +1m-2mn1s-2y limite de validit饅n"); + printf(" timeset/ts aaaa/mm/jj [hh:mm:ss] -- Change la limite de validit饅n"); + printf(" timeset/ts 0 -- limite de validit = illimit馥\n"); + printf(" unban/unbanish -- Ote le banissement d'un compte\n"); + printf(" unblock -- Mets le status d'un compte 0 (Compte ok)\n"); + printf(" version -- Donne la version du login-serveur\n"); + printf(" who -- Affiche les infos sur un compte\n"); + printf(" Note: Pour les noms de compte avec des espaces, tapez \"\" (ou ').\n"); + } + } else { + if (strcmp(command, "aide") == 0) { + printf("aide/help/?\n"); + printf(" Display the description of the commands\n"); + printf("aide/help/? [command]\n"); + printf(" Display the description of the specified command\n"); + } else if (strcmp(command, "help") == 0 ) { + printf("aide/help/?\n"); + printf(" Display the description of the commands\n"); + printf("aide/help/? [command]\n"); + printf(" Display the description of the specified command\n"); +// general commands + } else if (strcmp(command, "add") == 0) { + printf("add \n"); + printf(" Create an account with the default email (a@a.com).\n"); + printf(" Concerning the sex, only the first letter is used (F or M).\n"); + printf(" The e-mail is set to a@a.com (default e-mail). It's like to have no e-mail.\n"); + printf(" When the password is omitted,\n"); + printf(" the input is done without displaying of the pressed keys.\n"); + printf(" add testname Male testpass\n"); + } else if (strcmp(command, "ban") == 0) { + printf("ban/banish yyyy/mm/dd hh:mm:ss \n"); + printf(" Changes the final date of a banishment of an account.\n"); + printf(" Like banset, but is at end.\n"); + } else if (strcmp(command, "banadd") == 0) { + printf("banadd \n"); + printf(" Adds or substracts time from the final date of a banishment of an account.\n"); + printf(" Modifier is done as follows:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" banadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + printf("NOTE: If you modify the final date of a non-banished account,\n"); + printf(" you fix the final date to (actual time +- adjustments)\n"); + } else if (strcmp(command, "banset") == 0) { + printf("banset yyyy/mm/dd [hh:mm:ss]\n"); + printf(" Changes the final date of a banishment of an account.\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + printf("banset 0\n"); + printf(" Set a non-banished account (0 = unbanished).\n"); + } else if (strcmp(command, "block") == 0) { + printf("block \n"); + printf(" Set state 5 (You have been blocked by the GM Team) to an account.\n"); + printf(" This command works like state 5.\n"); + } else if (strcmp(command, "check") == 0) { + printf("check \n"); + printf(" Check the validity of a password for an account.\n"); + printf(" NOTE: Server will never sends back a password.\n"); + printf(" It's the only method you have to know if a password is correct.\n"); + printf(" The other method is to have a ('physical') access to the accounts file.\n"); + } else if (strcmp(command, "create") == 0) { + printf("create \n"); + printf(" Like the 'add' command, but with e-mail moreover.\n"); + printf(" create testname Male my@mail.com testpass\n"); + } else if (strcmp(command, "delete") == 0) { + printf("del \n"); + printf(" Remove an account.\n"); + printf(" This order requires confirmation. After confirmation, the account is deleted.\n"); + } else if (strcmp(command, "email") == 0) { + printf("email \n"); + printf(" Modify the e-mail of an account.\n"); + } else if (strcmp(command, "getcount") == 0) { + printf("getcount\n"); + printf(" Give the number of players online on all char-servers.\n"); + } else if (strcmp(command, "gm") == 0) { + printf("gm [GM_level]\n"); + printf(" Modify the GM level of an account.\n"); + printf(" Default value remove GM level (GM level = 0).\n"); + printf(" gm testname 80\n"); + } else if (strcmp(command, "id") == 0) { + printf("id \n"); + printf(" Give the id of an account.\n"); + } else if (strcmp(command, "info") == 0) { + printf("info \n"); + printf(" Display complete information of an account.\n"); + } else if (strcmp(command, "kami") == 0) { + printf("kami \n"); + printf(" Sends a broadcast message on all map-server (in yellow).\n"); + } else if (strcmp(command, "kamib") == 0) { + printf("kamib \n"); + printf(" Sends a broadcast message on all map-server (in blue).\n"); + } else if (strcmp(command, "language") == 0) { + printf("language \n"); + printf(" Change the language of displaying.\n"); + printf(" Possible languages: Fran軋is or English.\n"); + } else if (strcmp(command, "list") == 0) { + printf("list/ls [start_id [end_id]]\n"); + printf(" Display a list of accounts.\n"); + printf(" 'start_id', 'end_id': indicate end and start identifiers.\n"); + printf(" Research by name is not possible with this command.\n"); + printf(" list 10 9999999\n"); + } else if (strcmp(command, "listban") == 0) { + printf("listBan/lsBan [start_id [end_id]]\n"); + printf(" Like list/ls, but only for accounts with state or banished.\n"); + } else if (strcmp(command, "listgm") == 0) { + printf("listGM/lsGM [start_id [end_id]]\n"); + printf(" Like list/ls, but only for GM accounts.\n"); + } else if (strcmp(command, "listok") == 0) { + printf("listOK/lsOK [start_id [end_id]]\n"); + printf(" Like list/ls, but only for accounts without state and not banished.\n"); + } else if (strcmp(command, "memo") == 0) { + printf("memo \n"); + printf(" Modify the memo of an account.\n"); + printf(" 'memo': it can have until 253 characters (with spaces or not).\n"); + } else if (strcmp(command, "name") == 0) { + printf("name \n"); + printf(" Give the name of an account.\n"); + } else if (strcmp(command, "password") == 0) { + printf("passwd \n"); + printf(" Change the password of an account.\n"); + printf(" When new password is omitted,\n"); + printf(" the input is done without displaying of the pressed keys.\n"); + } else if (strcmp(command, "reloadgm") == 0) { + printf("reloadGM\n"); + printf(" Reload GM configuration file\n"); + } else if (strcmp(command, "search") == 0) { + printf("search \n"); + printf(" Seek accounts.\n"); + printf(" Displays the accounts whose names correspond.\n"); +// printf("search -r/-e/--expr/--regex \n"); +// printf(" Seek accounts by regular expression.\n"); +// printf(" Displays the accounts whose names correspond.\n"); + } else if (strcmp(command, "sex") == 0) { + printf("sex \n"); + printf(" Modify the sex of an account.\n"); + printf(" sex testname Male\n"); + } else if (strcmp(command, "state") == 0) { + printf("state \n"); + printf(" Change the state of an account.\n"); + printf(" 'new_state': state is the state of the packet 0x006a + 1.\n"); + printf(" The possibilities are:\n"); + printf(" 0 = Account ok\n"); + printf(" 1 = Unregistered ID\n"); + printf(" 2 = Incorrect Password\n"); + printf(" 3 = This ID is expired\n"); + printf(" 4 = Rejected from Server\n"); + printf(" 5 = You have been blocked by the GM Team\n"); + printf(" 6 = Your Game's EXE file is not the latest version\n"); + printf(" 7 = You are Prohibited to log in until...\n"); + printf(" 8 = Server is jammed due to over populated\n"); + printf(" 9 = No MSG\n"); + printf(" 100 = This ID has been totally erased\n"); + printf(" all other values are 'No MSG', then use state 9 please.\n"); + printf(" 'error_message_#7': message of the code error 6\n"); + printf(" = Your are Prohibited to log in until... (packet 0x006a)\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeadd \n"); + printf(" Adds or substracts time from the validity limit of an account.\n"); + printf(" Modifier is done as follows:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" timeadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + printf("NOTE: You can not modify a unlimited validity limit.\n"); + printf(" If you want modify it, you want probably create a limited validity limit.\n"); + printf(" So, at first, you must set the validity limit to a date/time.\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeset yyyy/mm/dd [hh:mm:ss]\n"); + printf(" Changes the validity limit of an account.\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + printf("timeset 0\n"); + printf(" Gives an unlimited validity limit (0 = unlimited).\n"); + } else if (strcmp(command, "unban") == 0) { + printf("unban/unbanish \n"); + printf(" Remove the banishment of an account.\n"); + printf(" This command works like banset 0.\n"); + } else if (strcmp(command, "unblock") == 0) { + printf("unblock \n"); + printf(" Set state 0 (Account ok) to an account.\n"); + printf(" This command works like state 0.\n"); + } else if (strcmp(command, "version") == 0) { + printf("version\n"); + printf(" Display the version of the login-server.\n"); + } else if (strcmp(command, "who") == 0) { + printf("who \n"); + printf(" Displays complete information of an account.\n"); +// quit + } else if (strcmp(command, "quit") == 0 || + strcmp(command, "exit") == 0 || + strcmp(command, "end") == 0) { + printf("quit/end/exit\n"); + printf(" End of the program of administration.\n"); +// unknown command + } else { + if (strlen(command) > 0) + printf("Unknown command [%s] for help. Displaying of all commands.\n", command); + printf(" aide/help/? -- Display this help\n"); + printf(" aide/help/? [command] -- Display the help of the command\n"); + printf(" add -- Create an account with default email\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss -- Change final date of a ban\n"); + printf(" banadd/ba -- Add or substract time from the final\n"); + printf(" example: ba apple +1m-2mn1s-2y date of a banishment of an account\n"); + printf(" banset/bs yyyy/mm/dd [hh:mm:ss] -- Change final date of a ban\n"); + printf(" banset/bs 0 -- Un-banish an account\n"); + printf(" block -- Set state 5 (blocked by the GM Team) to an account\n"); + printf(" check -- Check the validity of a password\n"); + printf(" create -- Create an account with email\n"); + printf(" del -- Remove an account\n"); + printf(" email -- Modify an email of an account\n"); + printf(" getcount -- Give the number of players online\n"); + printf(" gm [GM_level] -- Modify the GM level of an account\n"); + printf(" id -- Give the id of an account\n"); + printf(" info -- Display all information of an account\n"); + printf(" kami -- Sends a broadcast message (in yellow)\n"); + printf(" kamib -- Sends a broadcast message (in blue)\n"); + printf(" language -- Change the language of displaying.\n"); + printf(" list/ls [First_id [Last_id]] -- Display a list of accounts\n"); + printf(" listBan/lsBan [First_id [Last_id] ] -- Display a list of accounts\n"); + printf(" with state or banished\n"); + printf(" listGM/lsGM [First_id [Last_id]] -- Display a list of GM accounts\n"); + printf(" listOK/lsOK [First_id [Last_id] ] -- Display a list of accounts\n"); + printf(" without state and not banished\n"); + printf(" memo -- Modify the memo of an account\n"); + printf(" name -- Give the name of an account\n"); + printf(" passwd -- Change the password of an account\n"); + printf(" quit/end/exit -- End of the program of administation\n"); + printf(" reloadGM -- Reload GM configuration file\n"); + printf(" search -- Seek accounts\n"); +// printf(" search -e/-r/--expr/--regex -- Seek accounts by regular-expression\n"); + printf(" sex -- Modify the sex of an account\n"); + printf(" state -- Change the state\n"); + printf(" timeadd/ta -- Add or substract time from the\n"); + printf(" example: ta apple +1m-2mn1s-2y validity limit of an account\n"); + printf(" timeset/ts yyyy/mm/dd [hh:mm:ss] -- Change the validify limit\n"); + printf(" timeset/ts 0 -- Give a unlimited validity limit\n"); + printf(" unban/unbanish -- Remove the banishment of an account\n"); + printf(" unblock -- Set state 0 (Account ok) to an account\n"); + printf(" version -- Gives the version of the login-server\n"); + printf(" who -- Display all information of an account\n"); + printf(" who -- Display all information of an account\n"); + printf(" Note: To use spaces in an account name, type \"\" (or ').\n"); + } + } +} + +//----------------------------- +// Sub-function: add an account +//----------------------------- +int addaccount(char* param, int emailflag) { + char name[1023], sex[1023], email[1023], password[1023]; +// int i; + + memset(name, '\0', sizeof(name)); + memset(sex, '\0', sizeof(sex)); + memset(email, '\0', sizeof(email)); + memset(password, '\0', sizeof(password)); + + if (emailflag == 0) { // add command + if (sscanf(param, "\"%[^\"]\" %s %[^\r\n]", name, sex, password) < 2 && // password can be void + sscanf(param, "'%[^']' %s %[^\r\n]", name, sex, password) < 2 && // password can be void + sscanf(param, "%s %s %[^\r\n]", name, sex, password) < 2) { // password can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, un sexe et un mot de passe svp.\n"); + printf(" add nomtest Male motdepassetest\n"); + ladmin_log("Nombre incorrect de param鑼res pour cr馥r un compte (commande 'add')." RETCODE); + } else { + printf("Please input an account name, a sex and a password.\n"); + printf(" add testname Male testpass\n"); + ladmin_log("Incomplete parameters to create an account ('add' command)." RETCODE); + } + return 136; + } + strcpy(email, "a@a.com"); // default email + } else { // 1: create command + if (sscanf(param, "\"%[^\"]\" %s %s %[^\r\n]", name, sex, email, password) < 3 && // password can be void + sscanf(param, "'%[^']' %s %s %[^\r\n]", name, sex, email, password) < 3 && // password can be void + sscanf(param, "%s %s %s %[^\r\n]", name, sex, email, password) < 3) { // password can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, un sexe et un mot de passe svp.\n"); + printf(" create nomtest Male mo@mail.com motdepassetest\n"); + ladmin_log("Nombre incorrect de param鑼res pour cr馥r un compte (commande 'create')." RETCODE); + } else { + printf("Please input an account name, a sex and a password.\n"); + printf(" create testname Male my@mail.com testpass\n"); + ladmin_log("Incomplete parameters to create an account ('create' command)." RETCODE); + } + return 136; + } + } + if (verify_accountname(name) == 0) { + return 102; + } + +/* for(i = 0; name[i]; i++) { + if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_", name[i]) == NULL) { + if (defaultlanguage == 'F') { + printf("Caract鑽e interdit (%c) trouv dans le nom du compte (%d%s caract鑽e).\n", name[i], i+1, makeordinal(i+1)); + ladmin_log("Caract鑽e interdit (%c) trouv dans le nom du compte (%d%s caract鑽e)." RETCODE, name[i], i+1, makeordinal(i+1)); + } else { + printf("Illegal character (%c) found in the account name (%d%s character).\n", name[i], i+1, makeordinal(i+1)); + ladmin_log("Illegal character (%c) found in the account name (%d%s character)." RETCODE, name[i], i+1, makeordinal(i+1)); + } + return 101; + } + }*/ + + sex[0] = toupper(sex[0]); + if (strchr("MF", sex[0]) == NULL) { + if (defaultlanguage == 'F') { + printf("Sexe incorrect [%s]. Entrez M ou F svp.\n", sex); + ladmin_log("Sexe incorrect [%s]. Entrez M ou F svp." RETCODE, sex); + } else { + printf("Illegal gender [%s]. Please input M or F.\n", sex); + ladmin_log("Illegal gender [%s]. Please input M or F." RETCODE, sex); + } + return 103; + } + + if (strlen(email) < 3) { + if (defaultlanguage == 'F') { + printf("Email trop courte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email trop courte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Email is too short [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Email is too short [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + if (strlen(email) > 39) { + if (defaultlanguage == 'F') { + printf("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp.\n", email); + ladmin_log("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp." RETCODE, email); + } else { + printf("Email is too long [%s]. Please input an e-mail with 39 bytes at the most.\n", email); + ladmin_log("Email is too long [%s]. Please input an e-mail with 39 bytes at the most." RETCODE, email); + } + return 109; + } + if (e_mail_check(email) == 0) { + if (defaultlanguage == 'F') { + printf("Email incorrecte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email incorrecte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Invalid email [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Invalid email [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + + if (strlen(password) == 0) { + if (typepasswd(password) == 0) + return 108; + } + if (verify_password(password) == 0) + return 104; + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour cr馥r un compte." RETCODE); + } else { + ladmin_log("Request to login-server to create an account." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7930; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), password, 24); + WFIFOB(login_fd,50) = sex[0]; + memcpy(WFIFOP(login_fd,51), email, 40); + WFIFOSET(login_fd,91); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------------------------------------------- +// Sub-function: Add/substract time to the final date of a banishment of an account +//--------------------------------------------------------------------------------- +int banaddaccount(char* param) { + char name[1023], modif[1023]; + int year, month, day, hour, minute, second; + char * p_modif; + int value, i; + + memset(name, '\0', sizeof(name)); + memset(modif, '\0', sizeof(modif)); + year = month = day = hour = minute = second = 0; + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, modif) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, modif) < 2 && + sscanf(param, "%s %[^\r\n]", name, modif) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un modificateur svp.\n"); + printf(" banadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Nombre incorrect de param鑼res pour modifier la fin de ban d'un compte (commande 'banadd')." RETCODE); + } else { + printf("Please input an account name and a modifier.\n"); + printf(" : banadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("Incomplete parameters to modify the ban date/time of an account ('banadd' command)." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + // lowercase for modif + for (i = 0; modif[i]; i++) + modif[i] = tolower(modif[i]); + p_modif = modif; + while (strlen(p_modif) > 0) { + value = atoi(p_modif); + if (value == 0) { + p_modif++; + } else { + if (p_modif[0] == '-' || p_modif[0] == '+') + p_modif++; + while (strlen(p_modif) > 0 && p_modif[0] >= '0' && p_modif[0] <= '9') { + p_modif++; + } + if (p_modif[0] == 's') { + second = value; + p_modif++; + } else if (p_modif[0] == 'm' && p_modif[1] == 'n') { + minute = value; + p_modif += 2; + } else if (p_modif[0] == 'h') { + hour = value; + p_modif++; + } else if (p_modif[0] == 'd' || p_modif[0] == 'j') { + day = value; + p_modif += 2; + } else if (p_modif[0] == 'm') { + month = value; + p_modif++; + } else if (p_modif[0] == 'y' || p_modif[0] == 'a') { + year = value; + p_modif++; + } else { + p_modif++; + } + } + } + + if (defaultlanguage == 'F') { + printf(" ann馥: %d\n", year); + printf(" mois: %d\n", month); + printf(" jour: %d\n", day); + printf(" heure: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" seconde: %d\n", second); + } else { + printf(" year: %d\n", year); + printf(" month: %d\n", month); + printf(" day: %d\n", day); + printf(" hour: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" second: %d\n", second); + } + + if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0) { + if (defaultlanguage == 'F') { + printf("Vous devez entrer un ajustement avec cette commande, svp:\n"); + printf(" Valeur d'ajustement (-1, 1, +1, etc...)\n"); + printf(" Element modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" banadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Aucun ajustement n'est pas un ajustement (commande 'banadd')." RETCODE); + } else { + printf("Please give an adjustment with this command:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" banadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("No adjustment isn't an adjustment ('banadd' command)." RETCODE); + } + return 137; + } + if (year > 127 || year < -127) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'ann馥s correct (de -127 127), svp.\n"); + ladmin_log("Ajustement de l'ann馥 hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the years (from -127 to 127).\n"); + ladmin_log("Abnormal adjustement for the year ('banadd' command)." RETCODE); + } + return 137; + } + if (month > 255 || month < -255) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de mois correct (de -255 255), svp.\n"); + ladmin_log("Ajustement du mois hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the months (from -255 to 255).\n"); + ladmin_log("Abnormal adjustement for the month ('banadd' command)." RETCODE); + } + return 137; + } + if (day > 32767 || day < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de jours correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des jours hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the days (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the days ('banadd' command)." RETCODE); + } + return 137; + } + if (hour > 32767 || hour < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'heures correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des heures hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the hours (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the hours ('banadd' command)." RETCODE); + } + return 137; + } + if (minute > 32767 || minute < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de minutes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des minutes hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the minutes (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the minutes ('banadd' command)." RETCODE); + } + return 137; + } + if (second > 32767 || second < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de secondes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des secondes hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the seconds (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the seconds ('banadd' command)." RETCODE); + } + return 137; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour modifier la date d'un bannissement." RETCODE); + } else { + ladmin_log("Request to login-server to modify a ban date/time." RETCODE); + } + + WFIFOW(login_fd,0) = 0x794c; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOW(login_fd,26) = (short)year; + WFIFOW(login_fd,28) = (short)month; + WFIFOW(login_fd,30) = (short)day; + WFIFOW(login_fd,32) = (short)hour; + WFIFOW(login_fd,34) = (short)minute; + WFIFOW(login_fd,36) = (short)second; + WFIFOSET(login_fd,38); + bytes_to_read = 1; + + return 0; +} + +//----------------------------------------------------------------------- +// Sub-function of sub-function banaccount, unbanaccount or bansetaccount +// Set the final date of a banishment of an account +//----------------------------------------------------------------------- +int bansetaccountsub(char* name, char* date, char* time) { + int year, month, day, hour, minute, second; + time_t ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + struct tm *tmtime; + + year = month = day = hour = minute = second = 0; + ban_until_time = 0; + tmtime = localtime(&ban_until_time); // initialize + + if (verify_accountname(name) == 0) { + return 102; + } + + if (atoi(date) != 0 && + ((sscanf(date, "%d/%d/%d", &year, &month, &day) < 3 && + sscanf(date, "%d-%d-%d", &year, &month, &day) < 3 && + sscanf(date, "%d.%d.%d", &year, &month, &day) < 3) || + sscanf(time, "%d:%d:%d", &hour, &minute, &second) < 3)) { + if (defaultlanguage == 'F') { + printf("Entrez une date et une heure svp (format: aaaa/mm/jj hh:mm:ss).\n"); + printf("Vous pouvez aussi mettre 0 la place si vous utilisez la commande 'banset'.\n"); + ladmin_log("Format incorrect pour la date/heure (commande'banset' ou 'ban')." RETCODE); + } else { + printf("Please input a date and a time (format: yyyy/mm/dd hh:mm:ss).\n"); + printf("You can imput 0 instead of if you use 'banset' command.\n"); + ladmin_log("Invalid format for the date/time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + + if (atoi(date) == 0) { + ban_until_time = 0; + } else { + if (year < 70) { + year = year + 100; + } + if (year >= 1900) { + year = year - 1900; + } + if (month < 1 || month > 12) { + if (defaultlanguage == 'F') { + printf("Entrez un mois correct svp (entre 1 et 12).\n"); + ladmin_log("Mois incorrect pour la date (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the month (from 1 to 12).\n"); + ladmin_log("Invalid month for the date ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + month = month - 1; + if (day < 1 || day > 31) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct svp (entre 1 et 31).\n"); + ladmin_log("Jour incorrect pour la date (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the day (from 1 to 31).\n"); + ladmin_log("Invalid day for the date ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (((month == 3 || month == 5 || month == 8 || month == 10) && day > 30) || + (month == 1 && day > 29)) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct en fonction du mois (%d) svp.\n", month); + ladmin_log("Jour incorrect pour ce mois correspondant (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for a day of this month (%d).\n", month); + ladmin_log("Invalid day for this month ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (hour < 0 || hour > 23) { + if (defaultlanguage == 'F') { + printf("Entrez une heure correcte svp (entre 0 et 23).\n"); + ladmin_log("Heure incorrecte pour l'heure (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the hour (from 0 to 23).\n"); + ladmin_log("Invalid hour for the time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (minute < 0 || minute > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des minutes correctes svp (entre 0 et 59).\n"); + ladmin_log("Minute incorrecte pour l'heure (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the minutes (from 0 to 59).\n"); + ladmin_log("Invalid minute for the time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (second < 0 || second > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des secondes correctes svp (entre 0 et 59).\n"); + ladmin_log("Seconde incorrecte pour l'heure (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the seconds (from 0 to 59).\n"); + ladmin_log("Invalid second for the time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + tmtime->tm_year = year; + tmtime->tm_mon = month; + tmtime->tm_mday = day; + tmtime->tm_hour = hour; + tmtime->tm_min = minute; + tmtime->tm_sec = second; + tmtime->tm_isdst = -1; // -1: no winter/summer time modification + ban_until_time = mktime(tmtime); + if (ban_until_time == -1) { + if (defaultlanguage == 'F') { + printf("Date incorrecte.\n"); + printf("Entrez une date et une heure svp (format: aaaa/mm/jj hh:mm:ss).\n"); + printf("Vous pouvez aussi mettre 0 la place si vous utilisez la commande 'banset'.\n"); + ladmin_log("Date incorrecte. (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Invalid date.\n"); + printf("Please input a date and a time (format: yyyy/mm/dd hh:mm:ss).\n"); + printf("You can imput 0 instead of if you use 'banset' command.\n"); + ladmin_log("Invalid date. ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour fixer un ban." RETCODE); + } else { + ladmin_log("Request to login-server to set a ban." RETCODE); + } + + WFIFOW(login_fd,0) = 0x794a; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOL(login_fd,26) = (int)ban_until_time; + WFIFOSET(login_fd,30); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------------------------------- +// Sub-function: Set the final date of a banishment of an account (ban) +//--------------------------------------------------------------------- +int banaccount(char* param) { + char name[1023], date[1023], time[1023]; + + memset(name, '\0', sizeof(name)); + memset(date, '\0', sizeof(date)); + memset(time, '\0', sizeof(time)); + + if (sscanf(param, "%s %s \"%[^\"]\"", date, time, name) < 3 && + sscanf(param, "%s %s '%[^']'", date, time, name) < 3 && + sscanf(param, "%s %s %[^\r\n]", date, time, name) < 3) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, une date et une heure svp.\n"); + printf(": banset aaaa/mm/jj [hh:mm:ss]\n"); + printf(" banset 0 (0 = d-bani)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss \n"); + printf(" unban/unbanish \n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer un ban (commande 'banset' ou 'ban')." RETCODE); + } else { + printf("Please input an account name, a date and a hour.\n"); + printf(": banset yyyy/mm/dd [hh:mm:ss]\n"); + printf(" banset 0 (0 = un-banished)\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss \n"); + printf(" unban/unbanish \n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a ban ('banset' or 'ban' command)." RETCODE); + } + return 136; + } + + return bansetaccountsub(name, date, time); +} + +//------------------------------------------------------------------------ +// Sub-function: Set the final date of a banishment of an account (banset) +//------------------------------------------------------------------------ +int bansetaccount(char* param) { + char name[1023], date[1023], time[1023]; + + memset(name, '\0', sizeof(name)); + memset(date, '\0', sizeof(date)); + memset(time, '\0', sizeof(time)); + + if (sscanf(param, "\"%[^\"]\" %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "'%[^']' %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "%s %s %[^\r\n]", name, date, time) < 2) { // if date = 0, time can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, une date et une heure svp.\n"); + printf(": banset aaaa/mm/jj [hh:mm:ss]\n"); + printf(" banset 0 (0 = d-bani)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss \n"); + printf(" unban/unbanish \n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer un ban (commande 'banset' ou 'ban')." RETCODE); + } else { + printf("Please input an account name, a date and a hour.\n"); + printf(": banset yyyy/mm/dd [hh:mm:ss]\n"); + printf(" banset 0 (0 = un-banished)\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss \n"); + printf(" unban/unbanish \n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a ban ('banset' or 'ban' command)." RETCODE); + } + return 136; + } + + if (time[0] == '\0') + strcpy(time, "23:59:59"); + + return bansetaccountsub(name, date, time); +} + +//------------------------------------------------- +// Sub-function: unbanishment of an account (unban) +//------------------------------------------------- +int unbanaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf(": banset aaaa/mm/jj [hh:mm:ss]\n"); + printf(" banset 0 (0 = d-bani)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss \n"); + printf(" unban/unbanish \n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer un ban (commande 'unban')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf(": banset yyyy/mm/dd [hh:mm:ss]\n"); + printf(" banset 0 (0 = un-banished)\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss \n"); + printf(" unban/unbanish \n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a ban ('unban' command)." RETCODE); + } + return 136; + } + + return bansetaccountsub(name, "0", ""); +} + +//--------------------------------------------------------- +// Sub-function: Asking to check the validity of a password +// (Note: never send back a password with login-server!! security of passwords) +//--------------------------------------------------------- +int checkaccount(char* param) { + char name[1023], password[1023]; + + memset(name, '\0', sizeof(name)); + memset(password, '\0', sizeof(password)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, password) < 1 && // password can be void + sscanf(param, "'%[^']' %[^\r\n]", name, password) < 1 && // password can be void + sscanf(param, "%s %[^\r\n]", name, password) < 1) { // password can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf(" check testname motdepasse\n"); + ladmin_log("Nombre incorrect de param鑼res pour tester le mot d'un passe d'un compte (commande 'check')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf(" check testname password\n"); + ladmin_log("Incomplete parameters to check the password of an account ('check' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(password) == 0) { + if (typepasswd(password) == 0) + return 134; + } + if (verify_password(password) == 0) + return 131; + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour test un mot de passe." RETCODE); + } else { + ladmin_log("Request to login-server to check a password." RETCODE); + } + + WFIFOW(login_fd,0) = 0x793a; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), password, 24); + WFIFOSET(login_fd,50); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------ +// Sub-function: Asking for deletion of an account +//------------------------------------------------ +int delaccount(char* param) { + char name[1023]; + char letter; + char confirm[1023]; + int i; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf(" del nomtestasupprimer\n"); + ladmin_log("Aucun nom donn pour supprimer un compte (commande 'delete')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf(" del testnametodelete\n"); + ladmin_log("No name given to delete an account ('delete' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + memset(confirm, '\0', sizeof(confirm)); + while ((confirm[0] != 'o' || defaultlanguage != 'F') && confirm[0] != 'n' && (confirm[0] != 'y' || defaultlanguage == 'F')) { + if (defaultlanguage == 'F') + printf("\033[1;36m ** Etes-vous vraiment sr de vouloir SUPPRIMER le compte [$userid]? (o/n) > \033[0m"); + else + printf("\033[1;36m ** Are you really sure to DELETE account [$userid]? (y/n) > \033[0m"); + fflush(stdout); + memset(confirm, '\0', sizeof(confirm)); + i = 0; + while ((letter = getchar()) != '\n') + confirm[i++] = letter; + } + + if (confirm[0] == 'n') { + if (defaultlanguage == 'F') { + printf("Suppression annul馥.\n"); + ladmin_log("Suppression annul馥 par l'utilisateur (commande 'delete')." RETCODE); + } else { + printf("Deletion canceled.\n"); + ladmin_log("Deletion canceled by user ('delete' command)." RETCODE); + } + return 121; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour d騁ruire un compte." RETCODE); + } else { + ladmin_log("Request to login-server to delete an acount." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7932; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOSET(login_fd,26); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------- +// Sub-function: Asking to modification of an account e-mail +//---------------------------------------------------------- +int changeemail(char* param) { + char name[1023], email[1023]; + + memset(name, '\0', sizeof(name)); + memset(email, '\0', sizeof(email)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, email) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, email) < 2 && + sscanf(param, "%s %[^\r\n]", name, email) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et une email svp.\n"); + printf(" email testname nouveauemail\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer l'email d'un compte (commande 'email')." RETCODE); + } else { + printf("Please input an account name and an email.\n"); + printf(" email testname newemail\n"); + ladmin_log("Incomplete parameters to change the email of an account ('email' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(email) < 3) { + if (defaultlanguage == 'F') { + printf("Email trop courte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email trop courte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Email is too short [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Email is too short [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + if (strlen(email) > 39) { + if (defaultlanguage == 'F') { + printf("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp.\n", email); + ladmin_log("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp." RETCODE, email); + } else { + printf("Email is too long [%s]. Please input an e-mail with 39 bytes at the most.\n", email); + ladmin_log("Email is too long [%s]. Please input an e-mail with 39 bytes at the most." RETCODE, email); + } + return 109; + } + if (e_mail_check(email) == 0) { + if (defaultlanguage == 'F') { + printf("Email incorrecte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email incorrecte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Invalid email [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Invalid email [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer une email." RETCODE); + } else { + ladmin_log("Request to login-server to change an email." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7940; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), email, 40); + WFIFOSET(login_fd,66); + bytes_to_read = 1; + + return 0; +} + +//----------------------------------------------------- +// Sub-function: Asking of the number of online players +//----------------------------------------------------- +int getlogincount(void) { + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir le nombre de joueurs en jeu." RETCODE); + } else { + ladmin_log("Request to login-server to obtain the # of online players." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7938; + WFIFOSET(login_fd,2); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------- +// Sub-function: Asking to modify the GM level of an account +//---------------------------------------------------------- +int changegmlevel(char* param) { + char name[1023]; + int GM_level; + + memset(name, '\0', sizeof(name)); + GM_level = 0; + + if (sscanf(param, "\"%[^\"]\" %d", name, &GM_level) < 1 && + sscanf(param, "'%[^']' %d", name, &GM_level) < 1 && + sscanf(param, "%s %d", name, &GM_level) < 1) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un niveau de GM svp.\n"); + printf(" gm nomtest 80\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le Niveau de GM d'un compte (commande 'gm')." RETCODE); + } else { + printf("Please input an account name and a GM level.\n"); + printf(" gm testname 80\n"); + ladmin_log("Incomplete parameters to change the GM level of an account ('gm' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (GM_level < 0 || GM_level > 99) { + if (defaultlanguage == 'F') { + printf("Niveau de GM incorrect [%d]. Entrez une valeur de 0 99 svp.\n", GM_level); + ladmin_log("Niveau de GM incorrect [%d]. La valeur peut 黎re de 0 99." RETCODE, GM_level); + } else { + printf("Illegal GM level [%d]. Please input a value from 0 to 99.\n", GM_level); + ladmin_log("Illegal GM level [%d]. The value can be from 0 to 99." RETCODE, GM_level); + } + return 103; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un niveau de GM." RETCODE); + } else { + ladmin_log("Request to login-server to change a GM level." RETCODE); + } + + WFIFOW(login_fd,0) = 0x793e; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOB(login_fd,26) = GM_level; + WFIFOSET(login_fd,27); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------- +// Sub-function: Asking to obtain an account id +//--------------------------------------------- +int idaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf(" id nomtest\n"); + ladmin_log("Aucun nom donn pour rechecher l'id d'un compte (commande 'id')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf(" id testname\n"); + ladmin_log("No name given to search an account id ('id' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour connatre l'id d'un compte." RETCODE); + } else { + ladmin_log("Request to login-server to know an account id." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7944; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOSET(login_fd,26); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------------------------- +// Sub-function: Asking to displaying information about an account (by its id) +//---------------------------------------------------------------------------- +int infoaccount(int account_id) { + if (account_id < 0) { + if (defaultlanguage == 'F') { + printf("Entrez un id ayant une valeur positive svp.\n"); + ladmin_log("Une valeur n馮ative a 騁 donn pour trouver le compte." RETCODE); + } else { + printf("Please input a positive value for the id.\n"); + ladmin_log("Negative value was given to found the account." RETCODE); + } + return 136; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir le information d'un compte (par l'id)." RETCODE); + } else { + ladmin_log("Request to login-server to obtain information about an account (by its id)." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7954; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------- +// Sub-function: Send a broadcast message +//--------------------------------------- +int sendbroadcast(short type, char* message) { + if (strlen(message) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un message svp.\n"); + if (type == 0) { + printf(" kami un message\n"); + } else { + printf(" kamib un message\n"); + } + ladmin_log("Le message est vide (commande 'kami(b)')." RETCODE); + } else { + printf("Please input a message.\n"); + if (type == 0) { + printf(" kami a message\n"); + } else { + printf(" kamib a message\n"); + } + ladmin_log("The message is void ('kami(b)' command)." RETCODE); + } + return 136; + } + + WFIFOW(login_fd,0) = 0x794e; + WFIFOW(login_fd,2) = type; + WFIFOL(login_fd,4) = strlen(message)+1; + memcpy(WFIFOP(login_fd,8), message, strlen(message)+1); + WFIFOSET(login_fd,8+strlen(message)+1); + bytes_to_read = 1; + + return 0; +} + +//-------------------------------------------- +// Sub-function: Change language of displaying +//-------------------------------------------- +int changelanguage(char* language) { + if (strlen(language) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez une langue svp.\n"); + printf(" language english\n"); + printf(" language fran軋is\n"); + ladmin_log("La langue est vide (commande 'language')." RETCODE); + } else { + printf("Please input a language.\n"); + printf(" language english\n"); + printf(" language fran軋is\n"); + ladmin_log("The language is void ('language' command)." RETCODE); + } + return 136; + } + + language[0] = toupper(language[0]); + if (language[0] == 'F' || language[0] == 'E') { + defaultlanguage = language[0]; + if (defaultlanguage == 'F') { + printf("Changement de la langue d'affichage en Fran軋is.\n"); + ladmin_log("Changement de la langue d'affichage en Fran軋is." RETCODE); + } else { + printf("Displaying language changed to English.\n"); + ladmin_log("Displaying language changed to English." RETCODE); + } + } else { + if (defaultlanguage == 'F') { + printf("Langue non param騁r馥 (langues possibles: 'Fran軋is' ou 'English').\n"); + ladmin_log("Langue non param騁r馥 (Fran軋is ou English n馗essaire)." RETCODE); + } else { + printf("Undefined language (possible languages: Fran軋is or English).\n"); + ladmin_log("Undefined language (must be Fran軋is or English)." RETCODE); + } + } + + return 0; +} + +//-------------------------------------------------------- +// Sub-function: Asking to Displaying of the accounts list +//-------------------------------------------------------- +int listaccount(char* param, int type) { +//int list_first, list_last, list_type; // parameter to display a list of accounts + int i; + + list_type = type; + + // set default values + list_first = 0; + list_last = 0; + + if (list_type == 1) { // if listgm + // get all accounts = use default + } else if (list_type == 2) { // if search + for (i = 0; param[i]; i++) + param[i] = tolower(param[i]); + // get all accounts = use default + } else if (list_type == 3) { // if listban + // get all accounts = use default + } else if (list_type == 4) { // if listok + // get all accounts = use default + } else { // if list (list_type == 0) + switch(sscanf(param, "%d %d", &list_first, &list_last)) { + case 0: + // get all accounts = use default + break; + case 1: + list_last = 0; + // use tests of the following value + default: + if (list_first < 0) + list_first = 0; + if (list_last < list_first || list_last < 0) + list_last = 0; + break; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir la liste des comptes de %d %d." RETCODE, list_first, list_last); + } else { + ladmin_log("Request to login-server to obtain the list of accounts from %d to %d." RETCODE, list_first, list_last); + } + + WFIFOW(login_fd,0) = 0x7920; + WFIFOL(login_fd,2) = list_first; + WFIFOL(login_fd,6) = list_last; + WFIFOSET(login_fd,10); + bytes_to_read = 1; + + // 0123456789 01 01234567890123456789012301234 012345 0123456789012345678901234567 + if (defaultlanguage == 'F') { + printf(" id_compte GM nom_utilisateur sexe count statut\n"); + } else { + printf("account_id GM user_name sex count state\n"); + } + printf("-------------------------------------------------------------------------------\n"); + list_count = 0; + + return 0; +} + +//-------------------------------------------- +// Sub-function: Asking to modify a memo field +//-------------------------------------------- +int changememo(char* param) { + char name[1023], memo[1023]; + + memset(name, '\0', sizeof(name)); + memset(memo, '\0', sizeof(memo)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, memo) < 1 && // memo can be void + sscanf(param, "'%[^']' %[^\r\n]", name, memo) < 1 && // memo can be void + sscanf(param, "%s %[^\r\n]", name, memo) < 1) { // memo can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un m駑o svp.\n"); + printf(" memo nomtest nouveau memo\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le m駑o d'un compte (commande 'email')." RETCODE); + } else { + printf("Please input an account name and a memo.\n"); + printf(" memo testname new memo\n"); + ladmin_log("Incomplete parameters to change the memo of an account ('email' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(memo) > 254) { + if (defaultlanguage == 'F') { + printf("M駑o trop long (%d caract鑽es).\n", strlen(memo)); + printf("Entrez un m駑o de 254 caract鑽es maximum svp.\n"); + ladmin_log("M駑o trop long (%d caract鑽es). Entrez un m駑o de 254 caract鑽es maximum svp." RETCODE, strlen(memo)); + } else { + printf("Memo is too long (%d characters).\n", strlen(memo)); + printf("Please input a memo of 254 bytes at the maximum.\n"); + ladmin_log("Email is too long (%d characters). Please input a memo of 254 bytes at the maximum." RETCODE, strlen(memo)); + } + return 102; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un m駑o." RETCODE); + } else { + ladmin_log("Request to login-server to change a memo." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7942; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOW(login_fd,26) = strlen(memo); + if (strlen(memo) > 0) + memcpy(WFIFOP(login_fd,28), memo, strlen(memo)); + WFIFOSET(login_fd,28+strlen(memo)); + bytes_to_read = 1; + + return 0; +} + +//----------------------------------------------- +// Sub-function: Asking to obtain an account name +//----------------------------------------------- +int nameaccount(int id) { + if (id < 0) { + if (defaultlanguage == 'F') { + printf("Entrez un id ayant une valeur positive svp.\n"); + ladmin_log("Id n馮atif donn pour rechecher le nom d'un compte (commande 'name')." RETCODE); + } else { + printf("Please input a positive value for the id.\n"); + ladmin_log("Negativ id given to search an account name ('name' command)." RETCODE); + } + return 136; + } + + if (defaultlanguage == 'F') + ladmin_log("Envoi d'un requ黎e au serveur de logins pour connatre le nom d'un compte." RETCODE); + else + ladmin_log("Request to login-server to know an account name." RETCODE); + + WFIFOW(login_fd,0) = 0x7946; + WFIFOL(login_fd,2) = id; + WFIFOSET(login_fd,6); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------ +// Sub-function: Asking to modify a password +// (Note: never send back a password with login-server!! security of passwords) +//------------------------------------------ +int changepasswd(char* param) { + char name[1023], password[1023]; + + memset(name, '\0', sizeof(name)); + memset(password, '\0', sizeof(password)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, password) < 1 && + sscanf(param, "'%[^']' %[^\r\n]", name, password) < 1 && + sscanf(param, "%s %[^\r\n]", name, password) < 1) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf(" passwd nomtest nouveaumotdepasse\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le mot d'un passe d'un compte (commande 'password')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf(" passwd testname newpassword\n"); + ladmin_log("Incomplete parameters to change the password of an account ('password' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(password) == 0) { + if (typepasswd(password) == 0) + return 134; + } + if (verify_password(password) == 0) + return 131; + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un mot de passe." RETCODE); + } else { + ladmin_log("Request to login-server to change a password." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7934; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), password, 24); + WFIFOSET(login_fd,50); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------------------- +// Sub-function: Request to login-server to reload GM configuration file +// this function have no answer +//---------------------------------------------------------------------- +int reloadGM(void) { + WFIFOW(login_fd,0) = 0x7955; + WFIFOSET(login_fd,2); + bytes_to_read = 0; + + if (defaultlanguage == 'F') { + ladmin_log("Demande de recharger le fichier de configuration des GM envoy馥." RETCODE); + printf("Demande de recharger le fichier de configuration des GM envoy馥.\n"); + printf("V駻ifiez les comptes GM actuels (apr鑚 rechargement):\n"); + } else { + ladmin_log("Request to reload the GM configuration file sended." RETCODE); + printf("Request to reload the GM configuration file sended.\n"); + printf("Check the actual GM accounts (after reloading):\n"); + } + listaccount(parameters, 1); // 1: to list only GM + + return 180; +} + +//----------------------------------------------------- +// Sub-function: Asking to modify the sex of an account +//----------------------------------------------------- +int changesex(char* param) { + char name[1023], sex[1023]; + + memset(name, '\0', sizeof(name)); + memset(sex, '\0', sizeof(sex)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, sex) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, sex) < 2 && + sscanf(param, "%s %[^\r\n]", name, sex) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un sexe svp.\n"); + printf(" sex nomtest Male\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le sexe d'un compte (commande 'sex')." RETCODE); + } else { + printf("Please input an account name and a sex.\n"); + printf(" sex testname Male\n"); + ladmin_log("Incomplete parameters to change the sex of an account ('sex' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + sex[0] = toupper(sex[0]); + if (strchr("MF", sex[0]) == NULL) { + if (defaultlanguage == 'F') { + printf("Sexe incorrect [%s]. Entrez M ou F svp.\n", sex); + ladmin_log("Sexe incorrect [%s]. Entrez M ou F svp." RETCODE, sex); + } else { + printf("Illegal gender [%s]. Please input M or F.\n", sex); + ladmin_log("Illegal gender [%s]. Please input M or F." RETCODE, sex); + } + return 103; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un sexe." RETCODE); + } else { + ladmin_log("Request to login-server to change a sex." RETCODE); + } + + WFIFOW(login_fd,0) = 0x793c; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOB(login_fd,26) = sex[0]; + WFIFOSET(login_fd,27); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------------------------------- +// Sub-function of sub-function changestate, blockaccount or unblockaccount +// Asking to modify the state of an account +//------------------------------------------------------------------------- +int changestatesub(char* name, int state, char* error_message7) { + char error_message[1023]; // need to use, because we can modify error_message7 + + memset(error_message, '\0', sizeof(error_message)); + strncpy(error_message, error_message7, sizeof(error_message)-1); + + if ((state < 0 || state > 9) && state != 100) { // Valid values: 0: ok, or value of the 0x006a packet + 1 + if (defaultlanguage == 'F') { + printf("Entrez une des statuts suivantes svp:\n"); + printf(" 0 = Compte ok 6 = Your Game's EXE file is not the latest version\n"); + } else { + printf("Please input one of these states:\n"); + printf(" 0 = Account ok 6 = Your Game's EXE file is not the latest version\n"); + } + printf(" 1 = Unregistered ID 7 = You are Prohibited to log in until + message\n"); + printf(" 2 = Incorrect Password 8 = Server is jammed due to over populated\n"); + printf(" 3 = This ID is expired 9 = No MSG\n"); + printf(" 4 = Rejected from Server 100 = This ID has been totally erased\n"); + printf(" 5 = You have been blocked by the GM Team\n"); + if (defaultlanguage == 'F') { + printf(" state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block \n"); + printf(" unblock \n"); + ladmin_log("Valeur incorrecte pour le statut d'un compte (commande 'state', 'block' ou 'unblock')." RETCODE); + } else { + printf(" state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block \n"); + printf(" unblock \n"); + ladmin_log("Invalid value for the state of an account ('state', 'block' or 'unblock' command)." RETCODE); + } + return 151; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (state != 7) { + strcpy(error_message, "-"); + } else { + if (strlen(error_message) < 1) { + if (defaultlanguage == 'F') { + printf("Message d'erreur trop court. Entrez un message de 1-19 caract鑽es.\n"); + ladmin_log("Message d'erreur trop court. Entrez un message de 1-19 caract鑽es." RETCODE); + } else { + printf("Error message is too short. Please input a message of 1-19 bytes.\n"); + ladmin_log("Error message is too short. Please input a message of 1-19 bytes." RETCODE); + } + return 102; + } + if (strlen(error_message) > 19) { + if (defaultlanguage == 'F') { + printf("Message d'erreur trop long. Entrez un message de 1-19 caract鑽es.\n"); + ladmin_log("Message d'erreur trop long. Entrez un message de 1-19 caract鑽es." RETCODE); + } else { + printf("Error message is too long. Please input a message of 1-19 bytes.\n"); + ladmin_log("Error message is too long. Please input a message of 1-19 bytes." RETCODE); + } + return 102; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un statut." RETCODE); + } else { + ladmin_log("Request to login-server to change a state." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7936; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOL(login_fd,26) = state; + memcpy(WFIFOP(login_fd,30), error_message, 20); + WFIFOSET(login_fd,50); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------------- +// Sub-function: Asking to modify the state of an account +//------------------------------------------------------- +int changestate(char* param) { + char name[1023], error_message[1023]; + int state; + + memset(name, '\0', sizeof(name)); + memset(error_message, '\0', sizeof(error_message)); + + if (sscanf(param, "\"%[^\"]\" %d %[^\r\n]", name, &state, error_message) < 2 && + sscanf(param, "'%[^']' %d %[^\r\n]", name, &state, error_message) < 2 && + sscanf(param, "%s %d %[^\r\n]", name, &state, error_message) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un statut svp.\n"); + printf(" state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block \n"); + printf(" unblock \n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le statut d'un compte (commande 'state')." RETCODE); + } else { + printf("Please input an account name and a state.\n"); + printf(" state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block \n"); + printf(" unblock \n"); + ladmin_log("Incomplete parameters to change the state of an account ('state' command)." RETCODE); + } + return 136; + } + + return changestatesub(name, state, error_message); +} + +//------------------------------------------- +// Sub-function: Asking to unblock an account +//------------------------------------------- +int unblockaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf(" state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block \n"); + printf(" unblock \n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le statut d'un compte (commande 'unblock')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf(" state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block \n"); + printf(" unblock \n"); + ladmin_log("Incomplete parameters to change the state of an account ('unblock' command)." RETCODE); + } + return 136; + } + + return changestatesub(name, 0, "-"); // state 0, no error message +} + +//------------------------------------------- +// Sub-function: Asking to unblock an account +//------------------------------------------- +int blockaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf(" state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block \n"); + printf(" unblock \n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le statut d'un compte (commande 'block')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf(" state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block \n"); + printf(" unblock \n"); + ladmin_log("Incomplete parameters to change the state of an account ('block' command)." RETCODE); + } + return 136; + } + + return changestatesub(name, 5, "-"); // state 5, no error message +} + +//--------------------------------------------------------------------- +// Sub-function: Add/substract time to the validity limit of an account +//--------------------------------------------------------------------- +int timeaddaccount(char* param) { + char name[1023], modif[1023]; + int year, month, day, hour, minute, second; + char * p_modif; + int value, i; + + memset(name, '\0', sizeof(name)); + memset(modif, '\0', sizeof(modif)); + year = month = day = hour = minute = second = 0; + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, modif) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, modif) < 2 && + sscanf(param, "%s %[^\r\n]", name, modif) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un modificateur svp.\n"); + printf(" timeadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Nombre incorrect de param鑼res pour modifier une date limite d'utilisation (commande 'timeadd')." RETCODE); + } else { + printf("Please input an account name and a modifier.\n"); + printf(" : timeadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("Incomplete parameters to modify a limit time ('timeadd' command)." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + // lowercase for modif + for (i = 0; modif[i]; i++) + modif[i] = tolower(modif[i]); + p_modif = modif; + while (strlen(p_modif) > 0) { + value = atoi(p_modif); + if (value == 0) { + p_modif++; + } else { + if (p_modif[0] == '-' || p_modif[0] == '+') + p_modif++; + while (strlen(p_modif) > 0 && p_modif[0] >= '0' && p_modif[0] <= '9') { + p_modif++; + } + if (p_modif[0] == 's') { + second = value; + p_modif++; + } else if (p_modif[0] == 'm' && p_modif[1] == 'n') { + minute = value; + p_modif += 2; + } else if (p_modif[0] == 'h') { + hour = value; + p_modif++; + } else if (p_modif[0] == 'd' || p_modif[0] == 'j') { + day = value; + p_modif += 2; + } else if (p_modif[0] == 'm') { + month = value; + p_modif++; + } else if (p_modif[0] == 'y' || p_modif[0] == 'a') { + year = value; + p_modif++; + } else { + p_modif++; + } + } + } + + if (defaultlanguage == 'F') { + printf(" ann馥: %d\n", year); + printf(" mois: %d\n", month); + printf(" jour: %d\n", day); + printf(" heure: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" seconde: %d\n", second); + } else { + printf(" year: %d\n", year); + printf(" month: %d\n", month); + printf(" day: %d\n", day); + printf(" hour: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" second: %d\n", second); + } + + if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0) { + if (defaultlanguage == 'F') { + printf("Vous devez entrer un ajustement avec cette commande, svp:\n"); + printf(" Valeur d'ajustement (-1, 1, +1, etc...)\n"); + printf(" El駑ent modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" timeadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Aucun ajustement n'est pas un ajustement (commande 'timeadd')." RETCODE); + } else { + printf("Please give an adjustment with this command:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" timeadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("No adjustment isn't an adjustment ('timeadd' command)." RETCODE); + } + return 137; + } + if (year > 127 || year < -127) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'ann馥s correct (de -127 127), svp.\n"); + ladmin_log("Ajustement de l'ann馥 hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the years (from -127 to 127).\n"); + ladmin_log("Abnormal adjustement for the year ('timeadd' command)." RETCODE); + } + return 137; + } + if (month > 255 || month < -255) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de mois correct (de -255 255), svp.\n"); + ladmin_log("Ajustement du mois hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the months (from -255 to 255).\n"); + ladmin_log("Abnormal adjustement for the month ('timeadd' command)." RETCODE); + } + return 137; + } + if (day > 32767 || day < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de jours correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des jours hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the days (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the days ('timeadd' command)." RETCODE); + } + return 137; + } + if (hour > 32767 || hour < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'heures correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des heures hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the hours (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the hours ('timeadd' command)." RETCODE); + } + return 137; + } + if (minute > 32767 || minute < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de minutes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des minutes hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the minutes (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the minutes ('timeadd' command)." RETCODE); + } + return 137; + } + if (second > 32767 || second < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de secondes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des secondes hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the seconds (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the seconds ('timeadd' command)." RETCODE); + } + return 137; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour modifier une date limite d'utilisation." RETCODE); + } else { + ladmin_log("Request to login-server to modify a time limit." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7950; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOW(login_fd,26) = (short)year; + WFIFOW(login_fd,28) = (short)month; + WFIFOW(login_fd,30) = (short)day; + WFIFOW(login_fd,32) = (short)hour; + WFIFOW(login_fd,34) = (short)minute; + WFIFOW(login_fd,36) = (short)second; + WFIFOSET(login_fd,38); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------- +// Sub-function: Set a validity limit of an account +//------------------------------------------------- +int timesetaccount(char* param) { + char name[1023], date[1023], time[1023]; + int year, month, day, hour, minute, second; + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + struct tm *tmtime; + + memset(name, '\0', sizeof(name)); + memset(date, '\0', sizeof(date)); + memset(time, '\0', sizeof(time)); + year = month = day = hour = minute = second = 0; + connect_until_time = 0; + tmtime = localtime(&connect_until_time); // initialize + + if (sscanf(param, "\"%[^\"]\" %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "'%[^']' %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "%s %s %[^\r\n]", name, date, time) < 2) { // if date = 0, time can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, une date et une heure svp.\n"); + printf(": timeset aaaa/mm/jj [hh:mm:ss]\n"); + printf(" timeset 0 (0 = illimit)\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer une date limite d'utilisation (commande 'timeset')." RETCODE); + } else { + printf("Please input an account name, a date and a hour.\n"); + printf(": timeset yyyy/mm/dd [hh:mm:ss]\n"); + printf(" timeset 0 (0 = unlimited)\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a limit time ('timeset' command)." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + if (time[0] == '\0') + strcpy(time, "23:59:59"); + + if (atoi(date) != 0 && + ((sscanf(date, "%d/%d/%d", &year, &month, &day) < 3 && + sscanf(date, "%d-%d-%d", &year, &month, &day) < 3 && + sscanf(date, "%d.%d.%d", &year, &month, &day) < 3 && + sscanf(date, "%d'%d'%d", &year, &month, &day) < 3) || + sscanf(time, "%d:%d:%d", &hour, &minute, &second) < 3)) { + if (defaultlanguage == 'F') { + printf("Entrez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n"); + ladmin_log("Format incorrect pour la date/heure ('timeset' command)." RETCODE); + } else { + printf("Please input 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n"); + ladmin_log("Invalid format for the date/time ('timeset' command)." RETCODE); + } + return 102; + } + + if (atoi(date) == 0) { + connect_until_time = 0; + } else { + if (year < 70) { + year = year + 100; + } + if (year >= 1900) { + year = year - 1900; + } + if (month < 1 || month > 12) { + if (defaultlanguage == 'F') { + printf("Entrez un mois correct svp (entre 1 et 12).\n"); + ladmin_log("Mois incorrect pour la date ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the month (from 1 to 12).\n"); + ladmin_log("Invalid month for the date ('timeset' command)." RETCODE); + } + return 102; + } + month = month - 1; + if (day < 1 || day > 31) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct svp (entre 1 et 31).\n"); + ladmin_log("Jour incorrect pour la date ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the day (from 1 to 31).\n"); + ladmin_log("Invalid day for the date ('timeset' command)." RETCODE); + } + return 102; + } + if (((month == 3 || month == 5 || month == 8 || month == 10) && day > 30) || + (month == 1 && day > 29)) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct en fonction du mois (%d) svp.\n", month); + ladmin_log("Jour incorrect pour ce mois correspondant ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for a day of this month (%d).\n", month); + ladmin_log("Invalid day for this month ('timeset' command)." RETCODE); + } + return 102; + } + if (hour < 0 || hour > 23) { + if (defaultlanguage == 'F') { + printf("Entrez une heure correcte svp (entre 0 et 23).\n"); + ladmin_log("Heure incorrecte pour l'heure ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the hour (from 0 to 23).\n"); + ladmin_log("Invalid hour for the time ('timeset' command)." RETCODE); + } + return 102; + } + if (minute < 0 || minute > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des minutes correctes svp (entre 0 et 59).\n"); + ladmin_log("Minute incorrecte pour l'heure ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the minutes (from 0 to 59).\n"); + ladmin_log("Invalid minute for the time ('timeset' command)." RETCODE); + } + return 102; + } + if (second < 0 || second > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des secondes correctes svp (entre 0 et 59).\n"); + ladmin_log("Seconde incorrecte pour l'heure ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the seconds (from 0 to 59).\n"); + ladmin_log("Invalid second for the time ('timeset' command)." RETCODE); + } + return 102; + } + tmtime->tm_year = year; + tmtime->tm_mon = month; + tmtime->tm_mday = day; + tmtime->tm_hour = hour; + tmtime->tm_min = minute; + tmtime->tm_sec = second; + tmtime->tm_isdst = -1; // -1: no winter/summer time modification + connect_until_time = mktime(tmtime); + if (connect_until_time == -1) { + if (defaultlanguage == 'F') { + printf("Date incorrecte.\n"); + printf("Ajoutez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n"); + ladmin_log("Date incorrecte. ('timeset' command)." RETCODE); + } else { + printf("Invalid date.\n"); + printf("Please add 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n"); + ladmin_log("Invalid date. ('timeset' command)." RETCODE); + } + return 102; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour fixer une date limite d'utilisation." RETCODE); + } else { + ladmin_log("Request to login-server to set a time limit." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7948; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOL(login_fd,26) = (int)connect_until_time; + WFIFOSET(login_fd,30); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------------------------------------ +// Sub-function: Asking to displaying information about an account (by its name) +//------------------------------------------------------------------------------ +int whoaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf(" who nomtest\n"); + ladmin_log("Aucun nom n'a 騁 donn pour trouver le compte." RETCODE); + } else { + printf("Please input an account name.\n"); + printf(" who testname\n"); + ladmin_log("No name was given to found the account." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir le information d'un compte (par le nom)." RETCODE); + } else { + ladmin_log("Request to login-server to obtain information about an account (by its name)." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7952; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOSET(login_fd,26); + bytes_to_read = 1; + + return 0; +} + +//-------------------------------------------------------- +// Sub-function: Asking of the version of the login-server +//-------------------------------------------------------- +int checkloginversion(void) { + if (defaultlanguage == 'F') + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir sa version." RETCODE); + else + ladmin_log("Request to login-server to obtain its version." RETCODE); + + WFIFOW(login_fd,0) = 0x7530; + WFIFOSET(login_fd,2); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------- +// Prompt function +// this function wait until user type a command +// and analyse the command. +//--------------------------------------------- +int prompt(void) { + int i, j; + char buf[1024]; + char *p; + + // while we don't wait new packets + while (bytes_to_read == 0) { + // for help with the console colors look here: + // http://www.edoceo.com/liberum/?doc=printf-with-color + // some code explanation (used here): + // \033[2J : clear screen and go up/left (0, 0 position) + // \033[K : clear line from actual position to end of the line + // \033[0m : reset color parameter + // \033[1m : use bold for font + printf("\n"); + if (defaultlanguage == 'F') + printf("\033[32mPour afficher les commandes, tapez 'Entr馥'.\033[0m\n"); + else + printf("\033[32mTo list the commands, type 'enter'.\033[0m\n"); + printf("\033[0;36mLadmin-> \033[0m"); + printf("\033[1m"); + fflush(stdout); + + // get command and parameter + memset(buf, '\0', sizeof(buf)); + fflush(stdin); + fgets(buf, 1023, stdin); + buf[1023] = '\0'; + + printf("\033[0m"); + fflush(stdout); + + // remove final \n + if((p = strrchr(buf, '\n')) != NULL) + p[0] = '\0'; + // remove all control char + for (i = 0; buf[i]; i++) + if (buf[i] < 32) { + // remove cursor control. + if (buf[i] == 27 && buf[i+1] == '[' && + (buf[i+2] == 'H' || // home position (cursor) + buf[i+2] == 'J' || // clear screen + buf[i+2] == 'A' || // up 1 line + buf[i+2] == 'B' || // down 1 line + buf[i+2] == 'C' || // right 1 position + buf[i+2] == 'D' || // left 1 position + buf[i+2] == 'G')) { // center cursor (windows) + for (j = i; buf[j]; j++) + buf[j] = buf[j+3]; + } else if (buf[i] == 27 && buf[i+1] == '[' && buf[i+2] == '2' && buf[i+3] == 'J') { // clear screen + for (j = i; buf[j]; j++) + buf[j] = buf[j+4]; + } else if (buf[i] == 27 && buf[i+1] == '[' && buf[i+3] == '~' && + (buf[i+2] == '1' || // home (windows) + buf[i+2] == '2' || // insert (windows) + buf[i+2] == '3' || // del (windows) + buf[i+2] == '4' || // end (windows) + buf[i+2] == '5' || // pgup (windows) + buf[i+2] == '6')) { // pgdown (windows) + for (j = i; buf[j]; j++) + buf[j] = buf[j+4]; + } else { + // remove other control char. + for (j = i; buf[j]; j++) + buf[j] = buf[j+1]; + } + i--; + } + + // extract command name and parameters + memset(command, '\0', sizeof(command)); + memset(parameters, '\0', sizeof(parameters)); + sscanf(buf, "%1023s %[^\n]", command, parameters); + command[1023] = '\0'; + parameters[1023] = '\0'; + + // lowercase for command line + for (i = 0; command[i]; i++) + command[i] = tolower(command[i]); + + if (command[0] == '?' || strlen(command) == 0) { + if (defaultlanguage == 'F') { + strcpy(buf, "aide"); + strcpy(command, "aide"); + } else { + strcpy(buf, "help"); + strcpy(command, "help"); + } + } + + // Analyse of the command + check_command(command); // give complete name to the command + + if (strlen(parameters) == 0) { + if (defaultlanguage == 'F') { + ladmin_log("Commande: '%s' (sans param鑼re)" RETCODE, command, parameters); + } else { + ladmin_log("Command: '%s' (without parameters)" RETCODE, command, parameters); + } + } else { + if (defaultlanguage == 'F') { + ladmin_log("Commande: '%s', param鑼res: '%s'" RETCODE, command, parameters); + } else { + ladmin_log("Command: '%s', parameters: '%s'" RETCODE, command, parameters); + } + } + + // Analyse of the command +// help + if (strcmp(command, "aide") == 0) { + display_help(parameters, 1); // 1: french + } else if (strcmp(command, "help") == 0 ) { + display_help(parameters, 0); // 0: english +// general commands + } else if (strcmp(command, "add") == 0) { + addaccount(parameters, 0); // 0: no email + } else if (strcmp(command, "ban") == 0) { + banaccount(parameters); + } else if (strcmp(command, "banadd") == 0) { + banaddaccount(parameters); + } else if (strcmp(command, "banset") == 0) { + bansetaccount(parameters); + } else if (strcmp(command, "block") == 0) { + blockaccount(parameters); + } else if (strcmp(command, "check") == 0) { + checkaccount(parameters); + } else if (strcmp(command, "create") == 0) { + addaccount(parameters, 1); // 1: with email + } else if (strcmp(command, "delete") == 0) { + delaccount(parameters); + } else if (strcmp(command, "email") == 0) { + changeemail(parameters); + } else if (strcmp(command, "getcount") == 0) { + getlogincount(); + } else if (strcmp(command, "gm") == 0) { + changegmlevel(parameters); + } else if (strcmp(command, "id") == 0) { + idaccount(parameters); + } else if (strcmp(command, "info") == 0) { + infoaccount(atoi(parameters)); + } else if (strcmp(command, "kami") == 0) { + sendbroadcast(0, parameters); // flag for normal + } else if (strcmp(command, "kamib") == 0) { + sendbroadcast(0x10, parameters); // flag for blue + } else if (strcmp(command, "language") == 0) { + changelanguage(parameters); + } else if (strcmp(command, "list") == 0) { + listaccount(parameters, 0); // 0: to list all + } else if (strcmp(command, "listban") == 0) { + listaccount(parameters, 3); // 3: to list only accounts with state or bannished + } else if (strcmp(command, "listgm") == 0) { + listaccount(parameters, 1); // 1: to list only GM + } else if (strcmp(command, "listok") == 0) { + listaccount(parameters, 4); // 4: to list only accounts without state and not bannished + } else if (strcmp(command, "memo") == 0) { + changememo(parameters); + } else if (strcmp(command, "name") == 0) { + nameaccount(atoi(parameters)); + } else if (strcmp(command, "password") == 0) { + changepasswd(parameters); + } else if (strcmp(command, "reloadgm") == 0) { + reloadGM(); + } else if (strcmp(command, "search") == 0) { // no regex in C version + listaccount(parameters, 2); // 2: to list with pattern + } else if (strcmp(command, "sex") == 0) { + changesex(parameters); + } else if (strcmp(command, "state") == 0) { + changestate(parameters); + } else if (strcmp(command, "timeadd") == 0) { + timeaddaccount(parameters); + } else if (strcmp(command, "timeset") == 0) { + timesetaccount(parameters); + } else if (strcmp(command, "unban") == 0) { + unbanaccount(parameters); + } else if (strcmp(command, "unblock") == 0) { + unblockaccount(parameters); + } else if (strcmp(command, "version") == 0) { + checkloginversion(); + } else if (strcmp(command, "who") == 0) { + whoaccount(parameters); +// quit + } else if (strcmp(command, "quit") == 0 || + strcmp(command, "exit") == 0 || + strcmp(command, "end") == 0) { + if (defaultlanguage == 'F') { + printf("Au revoir.\n"); + } else { + printf("Bye.\n"); + } + exit(0); +// unknown command + } else { + if (defaultlanguage == 'F') { + printf("Commande inconnue [%s].\n", buf); + ladmin_log("Commande inconnue [%s]." RETCODE, buf); + } else { + printf("Unknown command [%s].\n", buf); + ladmin_log("Unknown command [%s]." RETCODE, buf); + } + } + } + + return 0; +} + +//------------------------------------------------------------- +// Function: Parse receiving informations from the login-server +//------------------------------------------------------------- +int parse_fromlogin(int fd) { + struct char_session_data *sd; + int id; + if (session[fd]->eof) { + if (defaultlanguage == 'F') { + printf("Impossible de se connecter au serveur de login [%s:%d] !\n", loginserverip, loginserverport); + ladmin_log("Impossible de se connecter au serveur de login [%s:%d] !" RETCODE, loginserverip, loginserverport); + } else { + printf("Impossible to have a connection with the login-server [%s:%d] !\n", loginserverip, loginserverport); + ladmin_log("Impossible to have a connection with the login-server [%s:%d] !" RETCODE, loginserverip, loginserverport); + } + close(fd); + delete_session(fd); + exit (0); + } + +// printf("parse_fromlogin : %d %d %d\n", fd, RFIFOREST(fd), RFIFOW(fd,0)); + sd = (struct char_session_data*)session[fd]->session_data; + + while(RFIFOREST(fd) >= 2) { + switch(RFIFOW(fd,0)) { + case 0x7919: // answer of a connection request + if (RFIFOREST(fd) < 3) + return 0; + if (RFIFOB(fd,2) != 0) { + if (defaultlanguage == 'F') { + printf("Erreur de login:\n"); + printf(" - mot de passe incorrect,\n"); + printf(" - syst鑪e d'administration non activ, ou\n"); + printf(" - IP non autoris馥.\n"); + ladmin_log("Erreur de login: mot de passe incorrect, syst鑪e d'administration non activ, ou IP non autoris馥." RETCODE); + } else { + printf("Error at login:\n"); + printf(" - incorrect password,\n"); + printf(" - administration system not activated, or\n"); + printf(" - unauthorised IP.\n"); + ladmin_log("Error at login: incorrect password, administration system not activated, or unauthorised IP." RETCODE); + } + session[fd]->eof = 1; + //bytes_to_read = 1; // not stop at prompt + } else { + if (defaultlanguage == 'F') { + printf("Connexion 騁ablie.\n"); + ladmin_log("Connexion 騁ablie." RETCODE); + printf("Lecture de la version du serveur de login...\n"); + ladmin_log("Lecture de la version du serveur de login..." RETCODE); + } else { + printf("Established connection.\n"); + ladmin_log("Established connection." RETCODE); + printf("Reading of the version of the login-server...\n"); + ladmin_log("Reading of the version of the login-server..." RETCODE); + } + //bytes_to_read = 1; // unchanged + checkloginversion(); + } + RFIFOSKIP(fd,3); + break; + +#ifdef PASSWORDENC + case 0x01dc: // answer of a coding key request + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + char md5str[64] = "", md5bin[32], md5key[RFIFOW(fd,2) - 4 + 1]; + memcpy(md5key, RFIFOP(fd,4), RFIFOW(fd,2) - 4); + md5key[sizeof(md5key)-1] = '0'; + if (passenc == 1) { + strncpy(md5str, (const char*)RFIFOP(fd,4), RFIFOW(fd,2) - 4); + strcat(md5str, loginserveradminpassword); + } else if (passenc == 2) { + strncpy(md5str, loginserveradminpassword, sizeof(loginserveradminpassword)); + strcat(md5str, (const char*)RFIFOP(fd,4)); + } + MD5_String2binary(md5str, md5bin); + WFIFOW(login_fd,0) = 0x7918; // Request for administation login (encrypted password) + WFIFOW(login_fd,2) = passenc; // Encrypted type + memcpy(WFIFOP(login_fd,4), md5bin, 16); + WFIFOSET(login_fd,20); + if (defaultlanguage == 'F') { + printf("R馗eption de la clef MD5.\n"); + ladmin_log("R馗eption de la clef MD5." RETCODE); + printf("Envoi du mot de passe crypt...\n"); + ladmin_log("Envoi du mot de passe crypt..." RETCODE); + } else { + printf("Receiving of the MD5 key.\n"); + ladmin_log("Receiving of the MD5 key." RETCODE); + printf("Sending of the encrypted password...\n"); + ladmin_log("Sending of the encrypted password..." RETCODE); + } + } + bytes_to_read = 1; + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; +#endif + + case 0x7531: // Displaying of the version of the login-server + if (RFIFOREST(fd) < 10) + return 0; + printf(" Login-Server [%s:%d]\n", loginserverip, loginserverport); + if (((int)RFIFOB(login_fd,5)) == 0) { + printf(" eAthena version stable-%d.%d", (int)RFIFOB(login_fd,2), (int)RFIFOB(login_fd,3)); + } else { + printf(" eAthena version dev-%d.%d", (int)RFIFOB(login_fd,2), (int)RFIFOB(login_fd,3)); + } + if (((int)RFIFOB(login_fd,4)) == 0) + printf(" revision %d", (int)RFIFOB(login_fd,4)); + if (((int)RFIFOB(login_fd,6)) == 0) + printf("%d.\n", RFIFOW(login_fd,8)); + else + printf("-mod%d.\n", RFIFOW(login_fd,8)); + bytes_to_read = 0; + RFIFOSKIP(fd,10); + break; + + case 0x7921: // Displaying of the list of accounts + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + if (RFIFOW(fd,2) < 5) { + if (defaultlanguage == 'F') { + ladmin_log(" R馗eption d'une liste des comptes vide." RETCODE); + if (list_count == 0) + printf("Aucun compte trouv.\n"); + else if (list_count == 1) + printf("1 compte trouv.\n"); + else + printf("%d comptes trouv駸.\n", list_count); + } else { + ladmin_log(" Receiving of a void accounts list." RETCODE); + if (list_count == 0) + printf("No account found.\n"); + else if (list_count == 1) + printf("1 account found.\n"); + else + printf("%d accounts found.\n", list_count); + } + bytes_to_read = 0; + } else { + int i; + if (defaultlanguage == 'F') + ladmin_log(" R馗eption d'une liste des comptes." RETCODE); + else + ladmin_log(" Receiving of a accounts list." RETCODE); + for(i = 4; i < RFIFOW(fd,2); i += 38) { + int j; + char userid[24]; + char lower_userid[24]; + memcpy(userid, RFIFOP(fd,i + 5), sizeof(userid)); + userid[sizeof(userid)-1] = '\0'; + memset(lower_userid, '\0', sizeof(lower_userid)); + for (j = 0; userid[j]; j++) + lower_userid[j] = tolower(userid[j]); + list_first = RFIFOL(fd,i) + 1; + // here are checks... + if (list_type == 0 || + (list_type == 1 && RFIFOB(fd,i+4) > 0) || + (list_type == 2 && strstr(lower_userid, parameters) != NULL) || + (list_type == 3 && RFIFOL(fd,i+34) != 0) || + (list_type == 4 && RFIFOL(fd,i+34) == 0)) { + printf("%10d ", (int)RFIFOL(fd,i)); + if (RFIFOB(fd,i+4) == 0) + printf(" "); + else + printf("%2d ", (int)RFIFOB(fd,i+4)); + printf("%-24s", userid); + if (defaultlanguage == 'F') { + if (RFIFOB(fd,i+29) == 0) + printf("%-5s ", "Femme"); + else if (RFIFOB(fd,i+29) == 1) + printf("%-5s ", "Male"); + else + printf("%-5s ", "Servr"); + } else { + if (RFIFOB(fd,i+29) == 0) + printf("%-5s ", "Femal"); + else if (RFIFOB(fd,i+29) == 1) + printf("%-5s ", "Male"); + else + printf("%-5s ", "Servr"); + } + printf("%6d ", (int)RFIFOL(fd,i+30)); + switch(RFIFOL(fd,i+34)) { + case 0: + if (defaultlanguage == 'F') + printf("%-27s\n", "Compte Ok"); + else + printf("%-27s\n", "Account OK"); + break; + case 1: + printf("%-27s\n", "Unregistered ID"); + break; + case 2: + printf("%-27s\n", "Incorrect Password"); + break; + case 3: + printf("%-27s\n", "This ID is expired"); + break; + case 4: + printf("%-27s\n", "Rejected from Server"); + break; + case 5: + printf("%-27s\n", "Blocked by the GM Team"); // You have been blocked by the GM Team + break; + case 6: + printf("%-27s\n", "Your EXE file is too old"); // Your Game's EXE file is not the latest version + break; + case 7: + printf("%-27s\n", "Banishement or"); + printf(" Prohibited to login until...\n"); // You are Prohibited to log in until %s + break; + case 8: + printf("%-27s\n", "Server is over populated"); + break; + case 9: + printf("%-27s\n", "No MSG"); + break; + default: // 100 + printf("%-27s\n", "This ID is totally erased"); // This ID has been totally erased + break; + } + list_count++; + } + } + // asking of the following acounts + if (defaultlanguage == 'F') + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir la liste des comptes de %d %d (compl駑ent)." RETCODE, list_first, list_last); + else + ladmin_log("Request to login-server to obtain the list of accounts from %d to %d (complement)." RETCODE, list_first, list_last); + WFIFOW(login_fd,0) = 0x7920; + WFIFOL(login_fd,2) = list_first; + WFIFOL(login_fd,6) = list_last; + WFIFOSET(login_fd,10); + bytes_to_read = 1; + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + case 0x7931: // Answer of login-server about an account creation + if (RFIFOREST(fd) < 30) + return 0; + id=RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Echec la cr饌tion du compte [%s]. Un compte identique existe d駛.\n", RFIFOP(fd,6)); + ladmin_log("Echec la cr饌tion du compte [%s]. Un compte identique existe d駛." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] creation failed. Same account already exists.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] creation failed. Same account already exists." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Compte [%s] cr鳬 avec succ鑚 [id: %d].\n", RFIFOP(fd,6), id); + ladmin_log("Compte [%s] cr鳬 avec succ鑚 [id: %d]." RETCODE, RFIFOP(fd,6), id); + } else { + printf("Account [%s] is successfully created [id: %d].\n", RFIFOP(fd,6), id); + ladmin_log("Account [%s] is successfully created [id: %d]." RETCODE, RFIFOP(fd,6), id); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7933: // Answer of login-server about an account deletion + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la suppression du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la suppression du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] deletion failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] deletion failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Compte [%s][id: %d] SUPPRIME avec succ鑚.\n", RFIFOP(fd,6), (int)RFIFOL(fd,2)); + ladmin_log("Compte [%s][id: %d] SUPPRIME avec succ鑚." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] is successfully DELETED.\n", RFIFOP(fd,6), (int)RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] is successfully DELETED." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7935: // answer of the change of an account password + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification du mot de passe du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la modification du mot de passe du compte. Le compte [%s] n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] password changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account password changing failed. The compte [%s] doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Modification du mot de passe du compte [%s][id: %d] r騏ssie.\n", RFIFOP(fd,6), (int)RFIFOL(fd,2)); + ladmin_log("Modification du mot de passe du compte [%s][id: %d] r騏ssie." RETCODE, RFIFOP(fd,6), (int)RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] password successfully changed.\n", RFIFOP(fd,6), (int)RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] password successfully changed." RETCODE, RFIFOP(fd,6), (int)RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7937: // answer of the change of an account state + if (RFIFOREST(fd) < 34) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement du statut du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement du statut du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] state changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] state changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + char tmpstr[256]; + if (defaultlanguage == 'F') { + sprintf(tmpstr, "Statut du compte [%s] chang avec succ鑚 en [", RFIFOP(fd,6)); + } else { + sprintf(tmpstr, "Account [%s] state successfully changed in [", RFIFOP(fd,6)); + } + switch(RFIFOL(fd,30)) { + case 0: + if (defaultlanguage == 'F') + strcat(tmpstr, "0: Compte Ok"); + else + strcat(tmpstr, "0: Account OK"); + break; + case 1: + strcat(tmpstr, "1: Unregistered ID"); + break; + case 2: + strcat(tmpstr, "2: Incorrect Password"); + break; + case 3: + strcat(tmpstr, "3: This ID is expired"); + break; + case 4: + strcat(tmpstr, "4: Rejected from Server"); + break; + case 5: + strcat(tmpstr, "5: You have been blocked by the GM Team"); + break; + case 6: + strcat(tmpstr, "6: [Your Game's EXE file is not the latest version"); + break; + case 7: + strcat(tmpstr, "7: You are Prohibited to log in until..."); + break; + case 8: + strcat(tmpstr, "8: Server is jammed due to over populated"); + break; + case 9: + strcat(tmpstr, "9: No MSG"); + break; + default: // 100 + strcat(tmpstr, "100: This ID is totally erased"); + break; + } + strcat(tmpstr, "]"); + printf("%s\n", tmpstr); + ladmin_log("%s%s", tmpstr, RETCODE); + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x7939: // answer of the number of online players + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + // Get length of the received packet + int i; + char name[20]; + if (defaultlanguage == 'F') { + ladmin_log(" R馗eption du nombre de joueurs en ligne." RETCODE); + } else { + ladmin_log(" Receiving of the number of online players." RETCODE); + } + // Read information of the servers + if (RFIFOW(fd,2) < 5) { + if (defaultlanguage == 'F') { + printf(" Aucun serveur n'est connect au login serveur.\n"); + } else { + printf(" No server is connected to the login-server.\n"); + } + } else { + if (defaultlanguage == 'F') { + printf(" Nombre de joueurs en ligne (serveur: nb):\n"); + } else { + printf(" Number of online players (server: number).\n"); + } + // Displaying of result + for(i = 4; i < RFIFOW(fd,2); i += 32) { + memcpy(name, RFIFOP(fd,i+6), sizeof(name)); + name[sizeof(name) - 1] = '\0'; + printf(" %-20s : %5d\n", name, RFIFOW(fd,i+26)); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + case 0x793b: // answer of the check of a password + if (RFIFOREST(fd) < 30) + return 0; + id = RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Le compte [%s] n'existe pas ou le mot de passe est incorrect.\n", RFIFOP(fd,6)); + ladmin_log("Le compte [%s] n'existe pas ou le mot de passe est incorrect." RETCODE, RFIFOP(fd,6)); + } else { + printf("The account [%s] doesn't exist or the password is incorrect.\n", RFIFOP(fd,6)); + ladmin_log("The account [%s] doesn't exist or the password is incorrect." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Le mot de passe donn correspond bien au compte [%s][id: %d].\n", RFIFOP(fd,6), id); + ladmin_log("Le mot de passe donn correspond bien au compte [%s][id: %d]." RETCODE, RFIFOP(fd,6), id); + } else { + printf("The proposed password is correct for the account [%s][id: %d].\n", RFIFOP(fd,6), id); + ladmin_log("The proposed password is correct for the account [%s][id: %d]." RETCODE, RFIFOP(fd,6), id); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x793d: // answer of the change of an account sex + if (RFIFOREST(fd) < 30) + return 0; + id = RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification du sexe du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas ou le sexe est d駛 celui demand.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la modification du sexe du compte. Le compte [%s] n'existe pas ou le sexe est d駛 celui demand." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] sex changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist or the sex is already the good sex.\n", RFIFOP(fd,6)); + ladmin_log("Account sex changing failed. The compte [%s] doesn't exist or the sex is already the good sex." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Sexe du compte [%s][id: %d] chang avec succ鑚.\n", RFIFOP(fd,6), id); + ladmin_log("Sexe du compte [%s][id: %d] chang avec succ鑚." RETCODE, RFIFOP(fd,6), id); + } else { + printf("Account [%s][id: %d] sex successfully changed.\n", RFIFOP(fd,6), id); + ladmin_log("Account [%s][id: %d] sex successfully changed." RETCODE, RFIFOP(fd,6), id); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x793f: // answer of the change of an account GM level + if (RFIFOREST(fd) < 30) + return 0; + id = RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification du niveau de GM du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas, le niveau de GM est d駛 celui demand饅n", RFIFOP(fd,6)); + printf("ou il est impossible de modifier le fichier des comptes GM.\n"); + ladmin_log("Echec de la modification du niveau de GM du compte. Le compte [%s] n'existe pas, le niveau de GM est d駛 celui demand ou il est impossible de modifier le fichier des comptes GM." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] GM level changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist, the GM level is already the good GM level\n", RFIFOP(fd,6)); + printf("or it's impossible to modify the GM accounts file.\n"); + ladmin_log("Account GM level changing failed. The compte [%s] doesn't exist, the GM level is already the good sex or it's impossible to modify the GM accounts file." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Niveau de GM du compte [%s][id: %d] chang avec succ鑚.\n", RFIFOP(fd,6), id); + ladmin_log("Niveau de GM du compte [%s][id: %d] chang avec succ鑚." RETCODE, RFIFOP(fd,6), id); + } else { + printf("Account [%s][id: %d] GM level successfully changed.\n", RFIFOP(fd,6), id); + ladmin_log("Account [%s][id: %d] GM level successfully changed." RETCODE, RFIFOP(fd,6), id); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7941: // answer of the change of an account email + if (RFIFOREST(fd) < 30) + return 0; + id = RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification de l'e-mail du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la modification de l'e-mail du compte. Le compte [%s] n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] e-mail changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account e-mail changing failed. The compte [%s] doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Modification de l'e-mail du compte [%s][id: %d] r騏ssie.\n", RFIFOP(fd,6), id); + ladmin_log("Modification de l'e-mail du compte [%s][id: %d] r騏ssie." RETCODE, RFIFOP(fd,6), id); + } else { + printf("Account [%s][id: %d] e-mail successfully changed.\n", RFIFOP(fd,6), id); + ladmin_log("Account [%s][id: %d] e-mail successfully changed." RETCODE, RFIFOP(fd,6), id); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7943: // answer of the change of an account memo + if (RFIFOREST(fd) < 30) + return 0; + id = RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement du m駑o du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement du m駑o du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] memo changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] memo changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("M駑o du compte [%s][id: %d] chang avec succ鑚.\n", RFIFOP(fd,6), id); + ladmin_log("M駑o du compte [%s][id: %d] chang avec succ鑚." RETCODE, RFIFOP(fd,6), id); + } else { + printf("Account [%s][id: %d] memo successfully changed.\n", RFIFOP(fd,6), id); + ladmin_log("Account [%s][id: %d] memo successfully changed." RETCODE, RFIFOP(fd,6), id); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7945: // answer of an account id search + if (RFIFOREST(fd) < 30) + return 0; + id = RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver l'id du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Impossible de trouver l'id du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Unable to find the account [%s] id. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Unable to find the account [%s] id. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Le compte [%s] a pour id: %d.\n", RFIFOP(fd,6), id); + ladmin_log("Le compte [%s] a pour id: %d." RETCODE, RFIFOP(fd,6), id); + } else { + printf("The account [%s] have the id: %d.\n", RFIFOP(fd,6), id); + ladmin_log("The account [%s] have the id: %d." RETCODE, RFIFOP(fd,6), id); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7947: // answer of an account name search + if (RFIFOREST(fd) < 30) + return 0; + id = RFIFOL(fd,2); + if (strcmp((const char*)RFIFOP(fd,6), "") == 0) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver le nom du compte [%d]. Le compte n'existe pas.\n", id); + ladmin_log("Impossible de trouver le nom du compte [%d]. Le compte n'existe pas." RETCODE, id); + } else { + printf("Unable to find the account [%d] name. Account doesn't exist.\n", id); + ladmin_log("Unable to find the account [%d] name. Account doesn't exist." RETCODE, id); + } + } else { + if (defaultlanguage == 'F') { + printf("Le compte [id: %d] a pour nom: %s.\n", id, RFIFOP(fd,6)); + ladmin_log("Le compte [id: %d] a pour nom: %s." RETCODE, id, RFIFOP(fd,6)); + } else { + printf("The account [id: %d] have the name: %s.\n", id, RFIFOP(fd,6)); + ladmin_log("The account [id: %d] have the name: %s." RETCODE, id, RFIFOP(fd,6)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7949: // answer of an account validity limit set + if (RFIFOREST(fd) < 34) + return 0; + id = RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la validit du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la validit du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] validity limit changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] validity limit changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 en [illimit饐.\n", RFIFOP(fd,6), id); + ladmin_log("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 en [illimit饐." RETCODE, RFIFOP(fd,6), id); + } else { + printf("Validity Limit of the account [%s][id: %d] successfully changed to [unlimited].\n", RFIFOP(fd,6), id); + ladmin_log("Validity Limit of the account [%s][id: %d] successfully changed to [unlimited]." RETCODE, RFIFOP(fd,6), id); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), id, tmpstr); + ladmin_log("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), id, tmpstr); + } else { + printf("Validity Limit of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), id, tmpstr); + ladmin_log("Validity Limit of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), id, tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x794b: // answer of an account ban set + if (RFIFOREST(fd) < 34) + return 0; + id = RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] final date of banishment changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] final date of banishment changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie].\n", RFIFOP(fd,6), id); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie]." RETCODE, RFIFOP(fd,6), id); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished].\n", RFIFOP(fd,6), id); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished]." RETCODE, RFIFOP(fd,6), id); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), id, tmpstr); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), id, tmpstr); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), id, tmpstr); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), id, tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x794d: // answer of an account ban date/time changing + if (RFIFOREST(fd) < 34) + return 0; + id = RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] final date of banishment changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] final date of banishment changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie].\n", RFIFOP(fd,6), id); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie]." RETCODE, RFIFOP(fd,6), id); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished].\n", RFIFOP(fd,6), id); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished]." RETCODE, RFIFOP(fd,6), id); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), id, tmpstr); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), id, tmpstr); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), id, tmpstr); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), id, tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x794f: // answer of a broadcast + if (RFIFOREST(fd) < 4) + return 0; + if (RFIFOW(fd,2) == (unsigned short)-1) { + if (defaultlanguage == 'F') { + printf("Echec de l'envoi du message. Aucun server de char en ligne.\n"); + ladmin_log("Echec de l'envoi du message. Aucun server de char en ligne." RETCODE); + } else { + printf("Message sending failed. No online char-server.\n"); + ladmin_log("Message sending failed. No online char-server." RETCODE); + } + } else { + if (defaultlanguage == 'F') { + printf("Message transmis au server de logins avec succ鑚.\n"); + ladmin_log("Message transmis au server de logins avec succ鑚." RETCODE); + } else { + printf("Message successfully sended to login-server.\n"); + ladmin_log("Message successfully sended to login-server." RETCODE); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,4); + break; + + case 0x7951: // answer of an account validity limit changing + if (RFIFOREST(fd) < 34) + return 0; + id = RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la validit du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la validit du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] validity limit changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] validity limit changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] inchang馥.\n", RFIFOP(fd,6), id); + printf("Le compte a une validit illimit馥 ou\n"); + printf("la modification est impossible avec les ajustements demand駸.\n"); + ladmin_log("Limite de validit du compte [%s][id: %d] inchang馥. Le compte a une validit illimit馥 ou la modification est impossible avec les ajustements demand駸." RETCODE, RFIFOP(fd,6), id); + } else { + printf("Validity limit of the account [%s][id: %d] unchanged.\n", RFIFOP(fd,6), id); + printf("The account have an unlimited validity limit or\n"); + printf("the changing is impossible with the proposed adjustments.\n"); + ladmin_log("Validity limit of the account [%s][id: %d] unchanged. The account have an unlimited validity limit or the changing is impossible with the proposed adjustments." RETCODE, RFIFOP(fd,6), id); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), id, tmpstr); + ladmin_log("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), id, tmpstr); + } else { + printf("Validity limit of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), id, tmpstr); + ladmin_log("Validity limit of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), id, tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x7953: // answer of a request about informations of an account (by account name/id) + if (RFIFOREST(fd) < 150 || RFIFOREST(fd) < (150 + RFIFOW(fd,148))) + return 0; + { + char userid[24], error_message[20], lastlogin[24], last_ip[16], email[40], memo[255]; + time_t ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + memcpy(userid, RFIFOP(fd,7), sizeof(userid)); + userid[sizeof(userid)-1] = '\0'; + memcpy(error_message, RFIFOP(fd,40), sizeof(error_message)); + error_message[sizeof(error_message)-1] = '\0'; + memcpy(lastlogin, RFIFOP(fd,60), sizeof(lastlogin)); + lastlogin[sizeof(lastlogin)-1] = '\0'; + memcpy(last_ip, RFIFOP(fd,84), sizeof(last_ip)); + last_ip[sizeof(last_ip)-1] = '\0'; + memcpy(email, RFIFOP(fd,100), sizeof(email)); + email[sizeof(email)-1] = '\0'; + connect_until_time = (time_t)RFIFOL(fd,140); + ban_until_time = (time_t)RFIFOL(fd,144); + memset(memo, '\0', sizeof(memo)); + strncpy(memo, (const char*)RFIFOP(fd,150), RFIFOW(fd,148)); + id = RFIFOL(fd,2); + if (id == -1) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver le compte [%s]. Le compte n'existe pas.\n", parameters); + ladmin_log("Impossible de trouver le compte [%s]. Le compte n'existe pas." RETCODE, parameters); + } else { + printf("Unabled to find the account [%s]. Account doesn't exist.\n", parameters); + ladmin_log("Unabled to find the account [%s]. Account doesn't exist." RETCODE, parameters); + } + } else if (strlen(userid) == 0) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver le compte [id: %s]. Le compte n'existe pas.\n", parameters); + ladmin_log("Impossible de trouver le compte [id: %s]. Le compte n'existe pas." RETCODE, parameters); + } else { + printf("Unabled to find the account [id: %s]. Account doesn't exist.\n", parameters); + ladmin_log("Unabled to find the account [id: %s]. Account doesn't exist." RETCODE, parameters); + } + } else { + if (defaultlanguage == 'F') { + ladmin_log("R馗eption d'information concernant un compte." RETCODE); + printf("Le compte a les caract駻istiques suivantes:\n"); + } else { + ladmin_log("Receiving information about an account." RETCODE); + printf("The account is set with:\n"); + } + if (RFIFOB(fd,6) == 0) { + printf(" Id: %d (non-GM)\n", id); + } else { + if (defaultlanguage == 'F') { + printf(" Id: %d (GM niveau %d)\n", id, (int)RFIFOB(fd,6)); + } else { + printf(" Id: %d (GM level %d)\n", id, (int)RFIFOB(fd,6)); + } + } + if (defaultlanguage == 'F') { + printf(" Nom: '%s'\n", userid); + if (RFIFOB(fd,31) == 0) + printf(" Sexe: Femme\n"); + else if (RFIFOB(fd,31) == 1) + printf(" Sexe: Male\n"); + else + printf(" Sexe: Serveur\n"); + } else { + printf(" Name: '%s'\n", userid); + if (RFIFOB(fd,31) == 0) + printf(" Sex: Female\n"); + else if (RFIFOB(fd,31) == 1) + printf(" Sex: Male\n"); + else + printf(" Sex: Server\n"); + } + printf(" E-mail: %s\n", email); + switch(RFIFOL(fd,36)) { + case 0: + if (defaultlanguage == 'F') + printf(" Statut: 0 [Compte Ok]\n"); + else + printf(" Statut: 0 [Account OK]\n"); + break; + case 1: + printf(" Statut: 1 [Unregistered ID]\n"); + break; + case 2: + printf(" Statut: 2 [Incorrect Password]\n"); + break; + case 3: + printf(" Statut: 3 [This ID is expired]\n"); + break; + case 4: + printf(" Statut: 4 [Rejected from Server]\n"); + break; + case 5: + printf(" Statut: 5 [You have been blocked by the GM Team]\n"); + break; + case 6: + printf(" Statut: 6 [Your Game's EXE file is not the latest version]\n"); + break; + case 7: + printf(" Statut: 7 [You are Prohibited to log in until %s]\n", error_message); + break; + case 8: + printf(" Statut: 8 [Server is jammed due to over populated]\n"); + break; + case 9: + printf(" Statut: 9 [No MSG]\n"); + break; + default: // 100 + printf(" Statut: %d [This ID is totally erased]\n", (int)RFIFOL(fd,36)); + break; + } + if (defaultlanguage == 'F') { + if (ban_until_time == 0) { + printf(" Banissement: non banni.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&ban_until_time)); + printf(" Banissement: jusqu'au %s.\n", tmpstr); + } + if (RFIFOL(fd,32) > 1) + printf(" Compteur: %d connexions.\n", (int)RFIFOL(fd,32)); + else + printf(" Compteur: %d connexion.\n", (int)RFIFOL(fd,32)); + printf(" Derni鑽e connexion le: %s (ip: %s)\n", lastlogin, last_ip); + if (connect_until_time == 0) { + printf(" Limite de validit: illimit.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&connect_until_time)); + printf(" Limite de validit: jusqu'au %s.\n", tmpstr); + } + } else { + if (ban_until_time == 0) { + printf(" Banishment: not banished.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&ban_until_time)); + printf(" Banishment: until %s.\n", tmpstr); + } + if (RFIFOL(fd,32) > 1) + printf(" Count: %d connections.\n", (int)RFIFOL(fd,32)); + else + printf(" Count: %d connection.\n", (int)RFIFOL(fd,32)); + printf(" Last connection at: %s (ip: %s)\n", lastlogin, last_ip); + if (connect_until_time == 0) { + printf(" Validity limit: unlimited.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&connect_until_time)); + printf(" Validity limit: until %s.\n", tmpstr); + } + } + printf(" Memo: '%s'\n", memo); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,150 + RFIFOW(fd,148)); + break; + + default: + printf("Remote administration has been disconnected (unknown packet).\n"); + ladmin_log("'End of connection, unknown packet." RETCODE); + session[fd]->eof = 1; + return 0; + } + } + + // if we don't wait new packets, do the prompt + prompt(); + + return 0; +} + +//------------------------------------ +// Function to connect to login-server +//------------------------------------ +int Connect_login_server(void) { + if (defaultlanguage == 'F') { + printf("Essai de connection au server de logins...\n"); + ladmin_log("Essai de connection au server de logins..." RETCODE); + } else { + printf("Attempt to connect to login-server...\n"); + ladmin_log("Attempt to connect to login-server..." RETCODE); + } + + login_fd = make_connection(login_ip, loginserverport); + if (login_fd == -1) + { //Might not be the most elegant way to handle this, but I've never used ladmin so I dunno what else you could do. [Skotlex] + printf("Error: Failed to connect to Login Server\n"); + exit(1); + } +#ifdef PASSWORDENC + if (passenc == 0) { +#endif + WFIFOW(login_fd,0) = 0x7918; // Request for administation login + WFIFOW(login_fd,2) = 0; // no encrypted + memcpy(WFIFOP(login_fd,4), loginserveradminpassword, 24); + WFIFOSET(login_fd,28); + bytes_to_read = 1; + if (defaultlanguage == 'F') { + printf("Envoi du mot de passe...\n"); + ladmin_log("Envoi du mot de passe..." RETCODE); + } else { + printf("Sending of the password...\n"); + ladmin_log("Sending of the password..." RETCODE); + } +#ifdef PASSWORDENC + } else { + WFIFOW(login_fd,0) = 0x791a; // Sending request about the coding key + WFIFOSET(login_fd,2); + bytes_to_read = 1; + if (defaultlanguage == 'F') { + printf("Demande de la clef MD5...\n"); + ladmin_log("Demande de la clef MD5..." RETCODE); + } else { + printf("Request about the MD5 key...\n"); + ladmin_log("Request about the MD5 key..." RETCODE); + } + } +#endif + + return 0; +} + +//------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, fran軋is, deutsch, espaol +//------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +//----------------------------------- +// Reading general configuration file +//----------------------------------- +int ladmin_config_read(const char *cfgName) { + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp = fopen(cfgName, "r"); + if (fp == NULL) { + if (defaultlanguage == 'F') { + printf("\033[0mFichier de configuration (%s) non trouv.\n", cfgName); + } else { + printf("\033[0mConfiguration file (%s) not found.\n", cfgName); + } + return 1; + } + + if (defaultlanguage == 'F') { + printf("\033[0m---D饕ut de lecture du fichier de configuration Ladmin (%s)\n", cfgName); + } else { + printf("\033[0m---Start reading of Ladmin configuration file (%s)\n", cfgName); + } + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + remove_control_chars((unsigned char *) w1); + remove_control_chars((unsigned char *) w2); + + if(strcmpi(w1,"login_ip")==0){ + struct hostent *h = gethostbyname (w2); + if (h != NULL) { + if (defaultlanguage == 'F') { + printf("Adresse du serveur de logins: %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + printf("Login server IP address: %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } + sprintf(loginserverip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(loginserverip, w2, 16); + } else if (strcmpi(w1, "login_port") == 0) { + loginserverport = atoi(w2); + } else if (strcmpi(w1, "admin_pass") == 0) { + strncpy(loginserveradminpassword, w2, sizeof(loginserveradminpassword)); + loginserveradminpassword[sizeof(loginserveradminpassword)-1] = '\0'; +#ifdef PASSWORDENC + } else if (strcmpi(w1, "passenc") == 0) { + passenc = atoi(w2); + if (passenc < 0 || passenc > 2) + passenc = 0; +#endif + } else if (strcmpi(w1, "defaultlanguage") == 0) { + if (w2[0] == 'F' || w2[0] == 'E') + defaultlanguage = w2[0]; + } else if (strcmpi(w1, "ladmin_log_filename") == 0) { + strncpy(ladmin_log_filename, w2, sizeof(ladmin_log_filename)); + ladmin_log_filename[sizeof(ladmin_log_filename)-1] = '\0'; + } else if (strcmpi(w1, "date_format") == 0) { // note: never have more than 19 char for the date! + switch (atoi(w2)) { + case 0: + strcpy(date_format, "%d-%m-%Y %H:%M:%S"); // 31-12-2004 23:59:59 + break; + case 1: + strcpy(date_format, "%m-%d-%Y %H:%M:%S"); // 12-31-2004 23:59:59 + break; + case 2: + strcpy(date_format, "%Y-%d-%m %H:%M:%S"); // 2004-31-12 23:59:59 + break; + case 3: + strcpy(date_format, "%Y-%m-%d %H:%M:%S"); // 2004-12-31 23:59:59 + break; + } + } else if (strcmpi(w1, "import") == 0) { + ladmin_config_read(w2); + } + } + } + fclose(fp); + + login_ip = inet_addr(loginserverip); + + if (defaultlanguage == 'F') { + printf("---Lecture du fichier de configuration Ladmin termin馥.\n"); + } else { + printf("---End reading of Ladmin configuration file.\n"); + } + + return 0; +} + +//-------------------------------------- +// Function called at exit of the server +//-------------------------------------- +void do_final(void) { + + if (already_exit_function == 0) { + delete_session(login_fd); + + if (defaultlanguage == 'F') { + printf("\033[0m----Fin de Ladmin (fin normale avec fermeture de tous les fichiers).\n"); + ladmin_log("----Fin de Ladmin (fin normale avec fermeture de tous les fichiers)." RETCODE); + } else { + printf("\033[0m----End of Ladmin (normal end with closing of all files).\n"); + ladmin_log("----End of Ladmin (normal end with closing of all files)." RETCODE); + } + + already_exit_function = 1; + } +} + +//------------------------ +// Main function of ladmin +//------------------------ +int do_init(int argc, char **argv) +{ + int next; + socket_init(); + + // read ladmin configuration + ladmin_config_read((argc > 1) ? argv[1] : LADMIN_CONF_NAME); + + ladmin_log(""); + if (defaultlanguage == 'F') { + ladmin_log("Fichier de configuration lu." RETCODE); + } else { + ladmin_log("Configuration file readed." RETCODE); + } + + srand(time(NULL)); + + set_defaultparse(parse_fromlogin); + + if (defaultlanguage == 'F') { + printf("Outil d'administration distance de eAthena.\n"); + printf("(pour eAthena version %d.%d.%d.)\n", ATHENA_MAJOR_VERSION, ATHENA_MINOR_VERSION, ATHENA_REVISION); + } else { + printf("EAthena login-server administration tool.\n"); + printf("(for eAthena version %d.%d.%d.)\n", ATHENA_MAJOR_VERSION, ATHENA_MINOR_VERSION, ATHENA_REVISION); + } + + if (defaultlanguage == 'F') { + ladmin_log("Ladmin est pr黎." RETCODE); + printf("Ladmin est \033[1;32mpr黎\033[0m.\n\n"); + } else { + ladmin_log("Ladmin is ready." RETCODE); + printf("Ladmin is \033[1;32mready\033[0m.\n\n"); + } + + Connect_login_server(); + + // minimalist core doesn't have sockets parsing, + // so we have to do this ourselves + while (runflag) { + next = do_timer(gettick_nocache()); + do_sendrecv(next); +#ifndef TURBO + do_parsepacket(); +#endif + } + + return 0; +} diff --git a/src/ladmin/ladmin.h b/src/ladmin/ladmin.h new file mode 100644 index 000000000..5a1e8311a --- /dev/null +++ b/src/ladmin/ladmin.h @@ -0,0 +1,13 @@ +// (c) eAthena Dev Team - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _LADMIN_H_ +#define _LADMIN_H_ + +#define LADMIN_CONF_NAME "conf/ladmin_athena.conf" +#define PASSWORDENC 3 // A definition is given when making an encryption password correspond. + // It is 1 at the time of passwordencrypt. + // It is made into 2 at the time of passwordencrypt2. + // When it is made 3, it corresponds to both. + +#endif diff --git a/src/ladmin/md5calc.c b/src/ladmin/md5calc.c new file mode 100644 index 000000000..f0acb4679 --- /dev/null +++ b/src/ladmin/md5calc.c @@ -0,0 +1,239 @@ +// (c) eAthena Dev Team - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +/*********************************************************** + * md5 calculation algorithm + * + * The source code referred to the following URL. + * http://www.geocities.co.jp/SiliconValley-Oakland/8878/lab17/lab17.html + * + ***********************************************************/ + +#include "md5calc.h" +#include +#include + +#ifndef UINT_MAX +#define UINT_MAX 4294967295U +#endif + +// Global variable +static unsigned int *pX; + +// Stirng Table +static const unsigned int T[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8 + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16 + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, //20 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24 + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32 + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, //40 + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48 + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56 + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 //60 +}; + +// ROTATE_LEFT The left is made to rotate x [ n-bit ]. This is diverted as it is from RFC. +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +// The function used for other calculation +static unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Y) | (~X & Z); +} +static unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Z) | (Y & ~Z); +} +static unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) +{ + return X ^ Y ^ Z; +} +static unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) +{ + return Y ^ (X | ~Z); +} + +static unsigned int Round(unsigned int a, unsigned int b, unsigned int FGHI, + unsigned int k, unsigned int s, unsigned int i) +{ + return b + ROTATE_LEFT(a + FGHI + pX[k] + T[i], s); +} + +static void Round1(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, F(b,c,d), k, s, i); +} +static void Round2(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, G(b,c,d), k, s, i); +} +static void Round3(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, H(b,c,d), k, s, i); +} +static void Round4(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, I(b,c,d), k, s, i); +} + +static void MD5_Round_Calculate(const unsigned char *block, + unsigned int *A2, unsigned int *B2, unsigned int *C2, unsigned int *D2) +{ + //create X It is since it is required. + unsigned int X[16]; //512bit 64byte + int j,k; + + //Save A as AA, B as BB, C as CC, and and D as DD (saving of A, B, C, and D) + unsigned int A=*A2, B=*B2, C=*C2, D=*D2; + unsigned int AA = A,BB = B,CC = C,DD = D; + + //It is a large region variable reluctantly because of calculation of a round. . . for Round1...4 + pX = X; + + //Copy block(padding_message) i into X + for (j=0,k=0; j<64; j+=4,k++) + X[k] = ( (unsigned int )block[j] ) // 8byte*4 -> 32byte conversion + | ( ((unsigned int )block[j+1]) << 8 ) // A function called Decode as used in the field of RFC + | ( ((unsigned int )block[j+2]) << 16 ) + | ( ((unsigned int )block[j+3]) << 24 ); + + + //Round 1 + Round1(&A,B,C,D, 0, 7, 0); Round1(&D,A,B,C, 1, 12, 1); Round1(&C,D,A,B, 2, 17, 2); Round1(&B,C,D,A, 3, 22, 3); + Round1(&A,B,C,D, 4, 7, 4); Round1(&D,A,B,C, 5, 12, 5); Round1(&C,D,A,B, 6, 17, 6); Round1(&B,C,D,A, 7, 22, 7); + Round1(&A,B,C,D, 8, 7, 8); Round1(&D,A,B,C, 9, 12, 9); Round1(&C,D,A,B, 10, 17, 10); Round1(&B,C,D,A, 11, 22, 11); + Round1(&A,B,C,D, 12, 7, 12); Round1(&D,A,B,C, 13, 12, 13); Round1(&C,D,A,B, 14, 17, 14); Round1(&B,C,D,A, 15, 22, 15); + + //Round 2 + Round2(&A,B,C,D, 1, 5, 16); Round2(&D,A,B,C, 6, 9, 17); Round2(&C,D,A,B, 11, 14, 18); Round2(&B,C,D,A, 0, 20, 19); + Round2(&A,B,C,D, 5, 5, 20); Round2(&D,A,B,C, 10, 9, 21); Round2(&C,D,A,B, 15, 14, 22); Round2(&B,C,D,A, 4, 20, 23); + Round2(&A,B,C,D, 9, 5, 24); Round2(&D,A,B,C, 14, 9, 25); Round2(&C,D,A,B, 3, 14, 26); Round2(&B,C,D,A, 8, 20, 27); + Round2(&A,B,C,D, 13, 5, 28); Round2(&D,A,B,C, 2, 9, 29); Round2(&C,D,A,B, 7, 14, 30); Round2(&B,C,D,A, 12, 20, 31); + + //Round 3 + Round3(&A,B,C,D, 5, 4, 32); Round3(&D,A,B,C, 8, 11, 33); Round3(&C,D,A,B, 11, 16, 34); Round3(&B,C,D,A, 14, 23, 35); + Round3(&A,B,C,D, 1, 4, 36); Round3(&D,A,B,C, 4, 11, 37); Round3(&C,D,A,B, 7, 16, 38); Round3(&B,C,D,A, 10, 23, 39); + Round3(&A,B,C,D, 13, 4, 40); Round3(&D,A,B,C, 0, 11, 41); Round3(&C,D,A,B, 3, 16, 42); Round3(&B,C,D,A, 6, 23, 43); + Round3(&A,B,C,D, 9, 4, 44); Round3(&D,A,B,C, 12, 11, 45); Round3(&C,D,A,B, 15, 16, 46); Round3(&B,C,D,A, 2, 23, 47); + + //Round 4 + Round4(&A,B,C,D, 0, 6, 48); Round4(&D,A,B,C, 7, 10, 49); Round4(&C,D,A,B, 14, 15, 50); Round4(&B,C,D,A, 5, 21, 51); + Round4(&A,B,C,D, 12, 6, 52); Round4(&D,A,B,C, 3, 10, 53); Round4(&C,D,A,B, 10, 15, 54); Round4(&B,C,D,A, 1, 21, 55); + Round4(&A,B,C,D, 8, 6, 56); Round4(&D,A,B,C, 15, 10, 57); Round4(&C,D,A,B, 6, 15, 58); Round4(&B,C,D,A, 13, 21, 59); + Round4(&A,B,C,D, 4, 6, 60); Round4(&D,A,B,C, 11, 10, 61); Round4(&C,D,A,B, 2, 15, 62); Round4(&B,C,D,A, 9, 21, 63); + + // Then perform the following additions. (let's add) + *A2 = A + AA; + *B2 = B + BB; + *C2 = C + CC; + *D2 = D + DD; + + //The clearance of confidential information + memset(pX, 0, sizeof(X)); +} + +//------------------------------------------------------------------- +// The function for the exteriors + +/** output is the coded binary in the character sequence which wants to code string. */ +void MD5_String2binary(const char * string, char * output) +{ +//var + /*8bit*/ + unsigned char padding_message[64]; //Extended message 512bit 64byte + unsigned char *pstring; //The position of string in the present scanning notes is held. + +// unsigned char digest[16]; + /*32bit*/ + unsigned int string_byte_len, //The byte chief of string is held. + string_bit_len, //The bit length of string is held. + copy_len, //The number of bytes which is used by 1-3 and which remained + msg_digest[4]; //Message digest 128bit 4byte + unsigned int *A = &msg_digest[0], //The message digest in accordance with RFC (reference) + *B = &msg_digest[1], + *C = &msg_digest[2], + *D = &msg_digest[3]; + int i; + +//prog + //Step 3.Initialize MD Buffer (although it is the initialization; step 3 of A, B, C, and D -- unavoidable -- a head) + *A = 0x67452301; + *B = 0xefcdab89; + *C = 0x98badcfe; + *D = 0x10325476; + + //Step 1.Append Padding Bits (extension of a mark bit) + //1-1 + string_byte_len = strlen(string); //The byte chief of a character sequence is acquired. + pstring = (unsigned char *)string; //The position of the present character sequence is set. + + //1-2 Repeat calculation until length becomes less than 64 bytes. + for (i=string_byte_len; 64<=i; i-=64,pstring+=64) + MD5_Round_Calculate(pstring, A,B,C,D); + + //1-3 + copy_len = string_byte_len % 64; //The number of bytes which remained is computed. + strncpy((char *)padding_message, (char *)pstring, copy_len); //A message is copied to an extended bit sequence. + memset(padding_message+copy_len, 0, 64 - copy_len); //It buries by 0 until it becomes extended bit length. + padding_message[copy_len] |= 0x80; //The next of a message is 1. + + //1-4 + //If 56 bytes or more (less than 64 bytes) of remainder becomes, it will calculate by extending to 64 bytes. + if (56 <= copy_len) { + MD5_Round_Calculate(padding_message, A,B,C,D); + memset(padding_message, 0, 56); //56 bytes is newly fill uped with 0. + } + + + //Step 2.Append Length (the information on length is added) + string_bit_len = string_byte_len * 8; //From the byte chief to bit length (32 bytes of low rank) + memcpy(&padding_message[56], &string_bit_len, 4); //32 bytes of low rank is set. + + //When bit length cannot be expressed in 32 bytes of low rank, it is a beam raising to a higher rank. + if (UINT_MAX / 8 < string_byte_len) { + unsigned int high = (string_byte_len - UINT_MAX / 8) * 8; + memcpy(&padding_message[60], &high, 4); + } else + memset(&padding_message[60], 0, 4); //In this case, it is good for a higher rank at 0. + + //Step 4.Process Message in 16-Word Blocks (calculation of MD5) + MD5_Round_Calculate(padding_message, A,B,C,D); + + + //Step 5.Output (output) + memcpy(output,msg_digest,16); +// memcpy (digest, msg_digest, and 16); //8 byte*4 < - 32byte conversion A function called Encode as used in the field of RFC +/* sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]);*/ +} + +/** output is the coded character sequence in the character sequence which wants to code string. */ +void MD5_String(const char * string, char * output) +{ + unsigned char digest[16]; + + MD5_String2binary(string,(char*)digest); + sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]); +} + diff --git a/src/ladmin/md5calc.h b/src/ladmin/md5calc.h new file mode 100644 index 000000000..1c42b16d9 --- /dev/null +++ b/src/ladmin/md5calc.h @@ -0,0 +1,10 @@ +// (c) eAthena Dev Team - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MD5CALC_H_ +#define _MD5CALC_H_ + +void MD5_String(const char * string, char * output); +void MD5_String2binary(const char * string, char * output); + +#endif diff --git a/src/login/Makefile b/src/login/Makefile new file mode 100644 index 000000000..549d1f170 --- /dev/null +++ b/src/login/Makefile @@ -0,0 +1,25 @@ +all txt: login-server + +COMMON_OBJ = ../common/obj/core.o ../common/obj/socket.o ../common/obj/timer.o \ + ../common/obj/db.o ../common/obj/plugins.o ../common/obj/lock.o \ + ../common/obj/malloc.o ../common/obj/showmsg.o ../common/obj/utils.o \ + ../common/obj/strlib.o ../common/obj/graph.o ../common/obj/grfio.o \ + ../common/obj/mapindex.o ../common/obj/ers.o ../zlib/unz.o +COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h \ + ../common/version.h ../common/db.h ../common/plugins.h ../common/lock.h \ + ../common/malloc.h ../common/showmsg.h ../common/utils.h ../common/strlib.h \ + ../common/graph.h ../common/grfio.h ../common/mapindex.h + +%.o: %.c + $(COMPILE.c) -DTXT_ONLY $(OUTPUT_OPTION) $< + +login-server: login.o md5calc.o $(COMMON_OBJ) + $(CC) -o ../../$@ login.o md5calc.o $(COMMON_OBJ) $(LIB_S) + +clean: + rm -f *.o ../../login-server + +# DO NOT DELETE + +login.o: login.c login.h md5calc.h $(COMMON_H) +md5calc.o: md5calc.c md5calc.h diff --git a/src/login/login.c b/src/login/login.c new file mode 100644 index 000000000..f24e16499 --- /dev/null +++ b/src/login/login.c @@ -0,0 +1,4153 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +// new version of the login-server by [Yor] + +#include +#ifdef __WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +void Gettimeofday(struct timeval *timenow) +{ + time_t t; + t = clock(); + timenow->tv_usec = (long)t; + timenow->tv_sec = (long)(t / CLK_TCK); + return; +} +#define gettimeofday(timenow, dummy) Gettimeofday(timenow) +#define in_addr_t unsigned long +#else +#include +#include +#include +#include +#include +#include +#include +#endif +#include +#include +#include // for stat/lstat/fstat +#include +#include +#include +#include + +#include "login.h" +#include "../common/core.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/mmo.h" +#include "../common/showmsg.h" +#include "../common/version.h" +#include "../common/db.h" +#include "../common/lock.h" +#include "../common/malloc.h" +#include "../common/strlib.h" +#include "../common/showmsg.h" + +#ifdef PASSWORDENC +#include "md5calc.h" +#endif + +int account_id_count = START_ACCOUNT_NUM; +int server_num; +int new_account_flag = 0; +int bind_ip_set_ = 0; +char bind_ip_str[128]; +int login_port = 6900; +char lan_char_ip[16]; +int subneti[4]; +int subnetmaski[4]; + +char account_filename[1024] = "save/account.txt"; +char GM_account_filename[1024] = "conf/GM_account.txt"; +char login_log_filename[1024] = "log/login.log"; +FILE *log_fp = NULL; +char login_log_unknown_packets_filename[1024] = "log/login_unknown_packets.log"; +char date_format[32] = "%Y-%m-%d %H:%M:%S"; +int save_unknown_packets = 0; +long creation_time_GM_account_file; +int gm_account_filename_check_timer = 15; // Timer to check if GM_account file has been changed and reload GM account automaticaly (in seconds; default: 15) + +int log_login = 1; + +int display_parse_login = 0; // 0: no, 1: yes +int display_parse_admin = 0; // 0: no, 1: yes +int display_parse_fromchar = 0; // 0: no, 1: yes (without packet 0x2714), 2: all packets + +struct mmo_char_server server[MAX_SERVERS]; +int server_fd[MAX_SERVERS]; + +int login_fd; + +static int online_check=1; //When set to 1, login server rejects incoming players that are already registered as online. [Skotlex] +//Account flood protection [Kevin] +unsigned int new_reg_tick=0; +int allowed_regs=1; +int num_regs=0; +int time_allowed=10; //Init this to 10 seconds. [Skotlex] + +enum { + ACO_DENY_ALLOW = 0, + ACO_ALLOW_DENY, + ACO_MUTUAL_FAILTURE, + ACO_STRSIZE = 128, +}; + +int access_order = ACO_DENY_ALLOW; +int access_allownum = 0; +int access_denynum = 0; +char *access_allow = NULL; +char *access_deny = NULL; + +int access_ladmin_allownum = 0; +char *access_ladmin_allow = NULL; + +int min_level_to_connect = 0; // minimum level of player/GM (0: player, 1-99: gm) to connect on the server +int add_to_unlimited_account = 0; // Give possibility or not to adjust (ladmin command: timeadd) the time of an unlimited account. +int start_limited_time = -1; // Starting additional sec from now for the limited time at creation of accounts (-1: unlimited time, 0 or more: additional sec from now) +int check_ip_flag = 1; // It's to check IP of a player between login-server and char-server (part of anti-hacking system) + +int check_client_version = 0; //Client version check ON/OFF .. (sirius) +int client_version_to_connect = 20; //Client version needed to connect ..(sirius) + + + +struct login_session_data { + unsigned int md5keylen; + char md5key[20]; +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id, login_id1, login_id2; + int ip, sex, delflag; +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos = 0; + +struct online_login_data { + int account_id; + short char_server; + short waiting_disconnect; +}; + +struct auth_dat { + int account_id, sex; + char userid[24], pass[33], lastlogin[24]; // 33 for 32 + NULL terminated + int logincount; + int state; // packet 0x006a value + 1 (0: compte OK) + char email[40]; // e-mail (by default: a@a.com) + char error_message[20]; // Message of error code #6 = Your are Prohibited to log in until %s (packet 0x006a) + time_t ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + char last_ip[16]; // save of last IP of connection + char memo[255]; // a memo field + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; +} *auth_dat = NULL; + +unsigned int auth_num = 0, auth_max = 0; + +// define the number of times that some players must authentify them before to save account file. +// it's just about normal authentification. If an account is created or modified, save is immediatly done. +// An authentification just change last connected IP and date. It already save in log file. +// set minimum auth change before save: +#define AUTH_BEFORE_SAVE_FILE 10 +// set divider of auth_num to found number of change before save +#define AUTH_SAVE_FILE_DIVIDER 50 +int auth_before_save_file = 0; // Counter. First save when 1st char-server do connection. + +int admin_state = 0; +char admin_pass[24] = ""; +unsigned int GM_num; +unsigned int GM_max=256; +char gm_pass[64] = ""; +int level_new_gm = 60; + +struct gm_account *gm_account_db; + +static struct dbt *online_db; + +int dynamic_pass_failure_ban = 1; +int dynamic_pass_failure_ban_time = 5; +int dynamic_pass_failure_ban_how_many = 3; +int dynamic_pass_failure_ban_how_long = 1; + +int use_md5_passwds = 0; + +int console = 0; + +//------------------------------ +// Writing function of logs file +//------------------------------ +int login_log(char *fmt, ...) { + if (log_login) { + va_list ap; + time_t raw_time; + char tmpstr[2048]; + + if(!log_fp) + log_fp = fopen(login_log_filename, "a"); + + if (log_fp) { + if (fmt[0] == '\0') // jump a line if no message + fprintf(log_fp, RETCODE); + else { + va_start(ap, fmt); + // Platform/Compiler dependant clock() for time check is removed. [Lance] + // clock() is originally used to track processing ticks on program execution. + time(&raw_time); + strftime(tmpstr, 24, date_format, localtime(&raw_time)); + sprintf(tmpstr + strlen(tmpstr), ": %s", fmt); + vfprintf(log_fp, tmpstr, ap); + va_end(ap); + } + fflush(log_fp); // under cygwin or windows, if software is stopped, data are not written in the file -> fflush at every line + } + } + + return 0; +} + +static void* create_online_user(DBKey key, va_list args) { + struct online_login_data *p; + p = aCalloc(1, sizeof(struct online_login_data)); + p->account_id = key.i; + p->char_server = -1; + return p; +} +//----------------------------------------------------- +// Online User Database [Wizputer] +//----------------------------------------------------- + +void add_online_user (int char_server, int account_id) { + struct online_login_data *p; + if (!online_check) + return; + p = idb_ensure(online_db, account_id, create_online_user); + p->char_server = char_server; + p->waiting_disconnect = 0; +} +int is_user_online (int account_id) { + return (idb_get(online_db, account_id) != NULL); +} +void remove_online_user (int account_id) { + if(!online_check) + return; + if (account_id == 99) { // reset all to offline + online_db->clear(online_db, NULL); // purge db + return; + } + idb_remove(online_db,account_id); +} + +int waiting_disconnect_timer(int tid, unsigned int tick, int id, int data) +{ + struct online_login_data *p; + if ((p= idb_get(online_db, id)) != NULL && p->waiting_disconnect) + remove_online_user(p->account_id); + return 0; +} + +//---------------------------------------------------------------------- +// Determine if an account (id) is a GM account +// and returns its level (or 0 if it isn't a GM account or if not found) +//---------------------------------------------------------------------- +int isGM(int account_id) { + unsigned int i; + for(i=0; i < GM_num; i++) + if(gm_account_db[i].account_id == account_id) + return gm_account_db[i].level; + return 0; +} + +//---------------------------------------------------------------------- +// Adds a new GM using acc id and level +//---------------------------------------------------------------------- +void addGM(int account_id, int level) { + unsigned int i; + int do_add = 0; + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id==account_id) { + do_add = 1; + break; + } + } + for(i = 0; i < GM_num; i++) + if (gm_account_db[i].account_id == account_id) { + if (gm_account_db[i].level == level) + ShowWarning("addGM: GM account %d defined twice (same level: %d).\n", account_id, level); + else { + ShowWarning("addGM: GM account %d defined twice (levels: %d and %d).\n", account_id, gm_account_db[i].level, level); + gm_account_db[i].level = level; + } + return; + } + + // if new account + if (i == GM_num && do_add) { + if (GM_num >= GM_max) { + GM_max += 256; + gm_account_db = (struct gm_account*)aRealloc(gm_account_db, sizeof(struct gm_account) * GM_max); + memset(gm_account_db + (GM_max - 256), 0, sizeof(struct gm_account) * 256); + } + gm_account_db[GM_num].account_id = account_id; + gm_account_db[GM_num].level = level; + GM_num++; + if (GM_num >= 4000) { + ShowWarning("4000 GM accounts found. Next GM accounts are not read.\n"); + login_log("***WARNING: 4000 GM accounts found. Next GM accounts are not read." RETCODE); + } + } +} + +//------------------------------------------------------- +// Reading function of GM accounts file (and their level) +//------------------------------------------------------- +int read_gm_account(void) { + char line[512]; + FILE *fp; + int account_id, level; + int line_counter; + struct stat file_stat; + int start_range = 0, end_range = 0, is_range = 0, current_id = 0; + + if(gm_account_db) aFree(gm_account_db); + GM_num = 0; + if(GM_max < 0) GM_max = 256; + gm_account_db = (struct gm_account*)aCalloc(GM_max, sizeof(struct gm_account)); + + // get last modify time/date + if (stat(GM_account_filename, &file_stat)) + creation_time_GM_account_file = 0; // error + else + creation_time_GM_account_file = (long)file_stat.st_mtime; + + if ((fp = fopen(GM_account_filename, "r")) == NULL) { + ShowError("read_gm_account: GM accounts file [%s] not found.\n", GM_account_filename); + ShowError(" Actually, there is no GM accounts on the server.\n"); + login_log("read_gm_account: GM accounts file [%s] not found." RETCODE, GM_account_filename); + login_log(" Actually, there is no GM accounts on the server." RETCODE); + return 1; + } + + line_counter = 0; + // limited to 4000, because we send information to char-servers (more than 4000 GM accounts???) + // int (id) + int (level) = 8 bytes * 4000 = 32k (limit of packets in windows) + while(fgets(line, sizeof(line)-1, fp) && GM_num < 4000) { + line_counter++; + if ((line[0] == '/' && line[1] == '/') || line[0] == '\0' || line[0] == '\n' || line[0] == '\r') + continue; + is_range = (sscanf(line, "%d%*[-~]%d %d",&start_range,&end_range,&level)==3); // ID Range [MC Cameri] + if (!is_range && sscanf(line, "%d %d", &account_id, &level) != 2 && sscanf(line, "%d: %d", &account_id, &level) != 2) + ShowError("read_gm_account: file [%s], invalid 'acount_id|range level' format (line #%d).\n", GM_account_filename, line_counter); + else if (level <= 0) + ShowError("read_gm_account: file [%s] %dth account (line #%d) (invalid level [0 or negative]: %d).\n", GM_account_filename, GM_num+1, line_counter, level); + else { + if (level > 99) { + ShowNotice("read_gm_account: file [%s] %dth account (invalid level, but corrected: %d->99).\n", GM_account_filename, GM_num+1, level); + level = 99; + } + if (is_range) { + if (start_range==end_range) + ShowError("read_gm_account: file [%s] invalid range, beginning of range is equal to end of range (line #%d).\n", GM_account_filename, line_counter); + else if (start_range>end_range) + ShowError("read_gm_account: file [%s] invalid range, beginning of range must be lower than end of range (line #%d).\n", GM_account_filename, line_counter); + else + for (current_id = start_range;current_id<=end_range;current_id++) + addGM(current_id,level); + } else { + addGM(account_id,level); + } + } + } + fclose(fp); + + ShowStatus("read_gm_account: file '%s' read (%d GM accounts found).\n", GM_account_filename, GM_num); + login_log("read_gm_account: file '%s' read (%d GM accounts found)." RETCODE, GM_account_filename, GM_num); + + return 0; +} + +//-------------------------------------------------------------- +// Test of the IP mask +// (ip: IP to be tested, str: mask x.x.x.x/# or x.x.x.x/y.y.y.y) +//-------------------------------------------------------------- +int check_ipmask(unsigned int ip, const unsigned char *str) { + unsigned int mask = 0, i = 0, m, ip2, a0, a1, a2, a3; + unsigned char *p = (unsigned char *)&ip2, *p2 = (unsigned char *)&mask; + + if (sscanf((const char*)str, "%d.%d.%d.%d/%n", &a0, &a1, &a2, &a3, &i) != 4 || i == 0) + return 0; + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + + if (sscanf((const char*)str+i, "%d.%d.%d.%d", &a0, &a1, &a2, &a3) == 4) { + p2[0] = a0; p2[1] = a1; p2[2] = a2; p2[3] = a3; + mask = ntohl(mask); + } else if (sscanf((const char*)(str+i), "%d", &m) == 1 && m >= 0 && m <= 32) { + for(i = 0; i < m && i < 32; i++) + mask = (mask >> 1) | 0x80000000; + } else { + ShowError("check_ipmask: invalid mask [%s].\n", str); + return 0; + } + +// printf("Tested IP: %08x, network: %08x, network mask: %08x\n", +// (unsigned int)ntohl(ip), (unsigned int)ntohl(ip2), (unsigned int)mask); + return ((ntohl(ip) & mask) == (ntohl(ip2) & mask)); +} + +//--------------------- +// Access control by IP +//--------------------- +int check_ip(unsigned int ip) { + int i; + unsigned char *p = (unsigned char *)&ip; + char buf[16]; + char * access_ip; + enum { ACF_DEF, ACF_ALLOW, ACF_DENY } flag = ACF_DEF; + + if (access_allownum == 0 && access_denynum == 0) + return 1; // When there is no restriction, all IP are authorised. + +// + 012.345.: front match form, or +// all: all IP are matched, or +// 012.345.678.901/24: network form (mask with # of bits), or +// 012.345.678.901/255.255.255.0: network form (mask with ip mask) +// + Note about the DNS resolution (like www.ne.jp, etc.): +// There is no guarantee to have an answer. +// If we have an answer, there is no guarantee to have a 100% correct value. +// And, the waiting time (to check) can be long (over 1 minute to a timeout). That can block the software. +// So, DNS notation isn't authorised for ip checking. + sprintf(buf, "%d.%d.%d.%d.", p[0], p[1], p[2], p[3]); + + for(i = 0; i < access_allownum; i++) { + access_ip = access_allow + i * ACO_STRSIZE; + if (memcmp(access_ip, buf, strlen(access_ip)) == 0 || check_ipmask(ip, (unsigned char*)access_ip)) { + if(access_order == ACO_ALLOW_DENY) + return 1; // With 'allow, deny' (deny if not allow), allow has priority + flag = ACF_ALLOW; + break; + } + } + + for(i = 0; i < access_denynum; i++) { + access_ip = access_deny + i * ACO_STRSIZE; + if (memcmp(access_ip, buf, strlen(access_ip)) == 0 || check_ipmask(ip, (unsigned char*)access_ip)) { + //flag = ACF_DENY; // not necessary to define flag + return 0; // At this point, if it's 'deny', we refuse connection. + } + } + + return (flag == ACF_ALLOW || access_order == ACO_DENY_ALLOW) ? 1:0; + // With 'mutual-failture', only 'allow' and non 'deny' IP are authorised. + // A non 'allow' (even non 'deny') IP is not authorised. It's like: if allowed and not denied, it's authorised. + // So, it's disapproval if you have no description at the time of 'mutual-failture'. + // With 'deny,allow' (allow if not deny), because here it's not deny, we authorise. +} + +//-------------------------------- +// Access control by IP for ladmin +//-------------------------------- +int check_ladminip(unsigned int ip) { + int i; + unsigned char *p = (unsigned char *)&ip; + char buf[16]; + char * access_ip; + + if (access_ladmin_allownum == 0) + return 1; // When there is no restriction, all IP are authorised. + +// + 012.345.: front match form, or +// all: all IP are matched, or +// 012.345.678.901/24: network form (mask with # of bits), or +// 012.345.678.901/255.255.255.0: network form (mask with ip mask) +// + Note about the DNS resolution (like www.ne.jp, etc.): +// There is no guarantee to have an answer. +// If we have an answer, there is no guarantee to have a 100% correct value. +// And, the waiting time (to check) can be long (over 1 minute to a timeout). That can block the software. +// So, DNS notation isn't authorised for ip checking. + sprintf(buf, "%d.%d.%d.%d.", p[0], p[1], p[2], p[3]); + + for(i = 0; i < access_ladmin_allownum; i++) { + access_ip = access_ladmin_allow + i * ACO_STRSIZE; + if (memcmp(access_ip, buf, strlen(access_ip)) == 0 || check_ipmask(ip, (unsigned char*)access_ip)) { + return 1; + } + } + + return 0; +} + +//--------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//--------------------------------------------------- +int e_mail_check(char *email) { + char ch; + char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) + if (strchr(last_arobas, ch) != NULL) + return 0; + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//----------------------------------------------- +// Search an account id +// (return account index or -1 (if not found)) +// If exact account name is not found, +// the function checks without case sensitive +// and returns index if only 1 account is found +// and similar to the searched name. +//----------------------------------------------- +int search_account_index(char* account_name) { + unsigned int i, quantity; + int index; + + quantity = 0; + index = -1; + + for(i = 0; i < auth_num; i++) { + // Without case sensitive check (increase the number of similar account names found) + if (stricmp(auth_dat[i].userid, account_name) == 0) { + // Strict comparison (if found, we finish the function immediatly with correct value) + if (strcmp(auth_dat[i].userid, account_name) == 0) + return i; + quantity++; + index = i; + } + } + // Here, the exact account name is not found + // We return the found index of a similar account ONLY if there is 1 similar account + if (quantity == 1) + return index; + + // Exact account name is not found and 0 or more than 1 similar accounts have been found ==> we say not found + return -1; +} + +//-------------------------------------------------------- +// Create a string to save the account in the account file +//-------------------------------------------------------- +int mmo_auth_tostr(char *str, struct auth_dat *p) { + int i; + char *str_p = str; + + str_p += sprintf(str_p, "%d\t%s\t%s\t%s\t%c\t%d\t%d\t" + "%s\t%s\t%ld\t%s\t%s\t%ld\t", + p->account_id, p->userid, p->pass, p->lastlogin, + (p->sex == 2) ? 'S' : (p->sex ? 'M' : 'F'), + p->logincount, p->state, + p->email, p->error_message, + (long)p->connect_until_time, p->last_ip, p->memo, (long)p->ban_until_time); + + for(i = 0; i < p->account_reg2_num; i++) + if (p->account_reg2[i].str[0]) + str_p += sprintf(str_p, "%s,%s ", p->account_reg2[i].str, p->account_reg2[i].value); + + return 0; +} + +//--------------------------------- +// Reading of the accounts database +//--------------------------------- +int mmo_auth_init(void) { + FILE *fp; + int account_id, logincount, state, n, i; + unsigned int j; + char line[2048], *p, userid[2048], pass[2048], lastlogin[2048], sex, email[2048], error_message[2048], last_ip[2048], memo[2048]; + long ban_until_time; + long connect_until_time; + char str[2048]; + char v[2048]; + int GM_count = 0; + int server_count = 0; + + auth_max = 256; + auth_dat = (struct auth_dat*)aCalloc(auth_max, sizeof(struct auth_dat)); + + if ((fp = fopen(account_filename, "r")) == NULL) { + // no account file -> no account -> no login, including char-server (ERROR) + ShowError(CL_RED"mmmo_auth_init: Accounts file [%s] not found."CL_RESET"\n", account_filename); + return 0; + } + + while(fgets(line, sizeof(line)-1, fp) != NULL) { + if (line[0] == '/' && line[1] == '/') + continue; + line[sizeof(line)-1] = '\0'; + // remove carriage return if exist + while(line[0] != '\0' && (line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r')) + line[strlen(line)-1] = '\0'; + p = line; + + memset(userid, 0, sizeof(userid)); + memset(pass, 0, sizeof(pass)); + memset(lastlogin, 0, sizeof(lastlogin)); + memset(email, 0, sizeof(email)); + memset(error_message, 0, sizeof(error_message)); + memset(last_ip, 0, sizeof(last_ip)); + memset(memo, 0, sizeof(memo)); + + // database version reading (v2) + if (((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%d\t" + "%[^\t]\t%[^\t]\t%ld\t%[^\t]\t%[^\t]\t%ld%n", + &account_id, userid, pass, lastlogin, &sex, &logincount, &state, + email, error_message, &connect_until_time, last_ip, memo, &ban_until_time, &n)) == 13 && line[n] == '\t') || + ((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%d\t" + "%[^\t]\t%[^\t]\t%ld\t%[^\t]\t%[^\t]%n", + &account_id, userid, pass, lastlogin, &sex, &logincount, &state, + email, error_message, &connect_until_time, last_ip, memo, &n)) == 12 && line[n] == '\t')) { + n = n + 1; + + // Some checks + if (account_id > END_ACCOUNT_NUM) { + ShowError(CL_RED"mmmo_auth_init: an account has an id higher than %d\n", END_ACCOUNT_NUM); + ShowError(" account id #%d -> account not read (saved in log file)."CL_RESET"\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an id higher than %d." RETCODE, END_ACCOUNT_NUM); + login_log(" account id #%d -> account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + continue; + } + userid[23] = '\0'; + remove_control_chars((unsigned char *)userid); + for(j = 0; j < auth_num; j++) { + if (auth_dat[j].account_id == account_id) { + ShowError(CL_RED"mmmo_auth_init: an account has an identical id to another.\n"); + ShowError(" account id #%d -> new account not read (saved in log file)."CL_RED"\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an identical id to another." RETCODE); + login_log(" account id #%d -> new account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + break; + } else if (strcmp(auth_dat[j].userid, userid) == 0) { + ShowError(CL_RED"mmmo_auth_init: account name already exists.\n"); + ShowError(" account name '%s' -> new account not read (saved in log file)."CL_RESET"\n", userid); // 2 lines, account name can be long. + login_log("mmmo_auth_init: ******Error: an account has an identical name to another." RETCODE); + login_log(" account name '%s' -> new account not read (saved in next line):" RETCODE, userid); + login_log("%s", line); + break; + } + } + if (j != auth_num) + continue; + + if (auth_num >= auth_max) { + auth_max += 256; + auth_dat = (struct auth_dat*)aRealloc(auth_dat, sizeof(struct auth_dat) * auth_max); + } + + memset(&auth_dat[auth_num], '\0', sizeof(struct auth_dat)); + + auth_dat[auth_num].account_id = account_id; + + strncpy(auth_dat[auth_num].userid, userid, 24); + + pass[23] = '\0'; + remove_control_chars((unsigned char *)pass); + strncpy(auth_dat[auth_num].pass, pass, 24); + + lastlogin[23] = '\0'; + remove_control_chars((unsigned char *)lastlogin); + strncpy(auth_dat[auth_num].lastlogin, lastlogin, 24); + + auth_dat[auth_num].sex = (sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'); + + if (logincount >= 0) + auth_dat[auth_num].logincount = logincount; + else + auth_dat[auth_num].logincount = 0; + + if (state > 255) + auth_dat[auth_num].state = 100; + else if (state < 0) + auth_dat[auth_num].state = 0; + else + auth_dat[auth_num].state = state; + + if (e_mail_check(email) == 0) { + ShowNotice("Account %s (%d): invalid e-mail (replaced par a@a.com).\n", auth_dat[auth_num].userid, auth_dat[auth_num].account_id); + strncpy(auth_dat[auth_num].email, "a@a.com", 40); + } else { + remove_control_chars((unsigned char *)email); + strncpy(auth_dat[auth_num].email, email, 40); + } + + error_message[19] = '\0'; + remove_control_chars((unsigned char *)error_message); + if (error_message[0] == '\0' || state != 7) { // 7, because state is packet 0x006a value + 1 + strncpy(auth_dat[auth_num].error_message, "-", 20); + } else { + strncpy(auth_dat[auth_num].error_message, error_message, 20); + } + + if (i == 13) + auth_dat[auth_num].ban_until_time = (time_t)ban_until_time; + else + auth_dat[auth_num].ban_until_time = 0; + + auth_dat[auth_num].connect_until_time = (time_t)connect_until_time; + + last_ip[15] = '\0'; + remove_control_chars((unsigned char *)last_ip); + strncpy(auth_dat[auth_num].last_ip, last_ip, 16); + + memo[254] = '\0'; + remove_control_chars((unsigned char *)memo); + strncpy(auth_dat[auth_num].memo, memo, 255); + + for(j = 0; j < ACCOUNT_REG2_NUM; j++) { + p += n; + if (sscanf(p, "%[^\t,],%[^\t ] %n", str, v, &n) != 2) { + // We must check if a str is void. If it's, we can continue to read other REG2. + // Account line will have something like: str2,9 ,9 str3,1 (here, ,9 is not good) + if (p[0] == ',' && sscanf(p, ",%[^\t ] %n", v, &n) == 1) { + j--; + continue; + } else + break; + } + str[31] = '\0'; + remove_control_chars((unsigned char *)str); + strncpy(auth_dat[auth_num].account_reg2[j].str, str, 32); + strncpy(auth_dat[auth_num].account_reg2[j].value,v,256); + } + auth_dat[auth_num].account_reg2_num = j; + + if (isGM(account_id) > 0) + GM_count++; + if (auth_dat[auth_num].sex == 2) + server_count++; + + auth_num++; + if (account_id >= account_id_count) + account_id_count = account_id + 1; + + // Old athena database version reading (v1) + } else if ((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%d\t%n", + &account_id, userid, pass, lastlogin, &sex, &logincount, &state, &n)) >= 5) { + if (account_id > END_ACCOUNT_NUM) { + ShowError(CL_RED"mmmo_auth_init: an account has an id higher than %d\n", END_ACCOUNT_NUM); + ShowError(" account id #%d -> account not read (saved in log file)."CL_RESET"\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an id higher than %d." RETCODE, END_ACCOUNT_NUM); + login_log(" account id #%d -> account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + continue; + } + userid[23] = '\0'; + remove_control_chars((unsigned char *)userid); + for(j = 0; j < auth_num; j++) { + if (auth_dat[j].account_id == account_id) { + ShowError(CL_RED"mmo_auth_init: an account has an identical id to another.\n"); + ShowError(" account id #%d -> new account not read (saved in log file)."CL_RESET"\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an identical id to another." RETCODE); + login_log(" account id #%d -> new account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + break; + } else if (strcmp(auth_dat[j].userid, userid) == 0) { + ShowError(CL_RED"mmo_auth_init: account name already exists.\n"); + ShowError(" account name '%s' -> new account not read (saved in log file)."CL_RESET"\n", userid); + login_log("mmmo_auth_init: ******Error: an account has an identical id to another." RETCODE); + login_log(" account id #%d -> new account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + break; + } + } + if (j != auth_num) + continue; + + if (auth_num >= auth_max) { + auth_max += 256; + auth_dat = (struct auth_dat*)aRealloc(auth_dat, sizeof(struct auth_dat) * auth_max); + } + + memset(&auth_dat[auth_num], '\0', sizeof(struct auth_dat)); + + auth_dat[auth_num].account_id = account_id; + + strncpy(auth_dat[auth_num].userid, userid, 24); + + pass[23] = '\0'; + remove_control_chars((unsigned char *)pass); + strncpy(auth_dat[auth_num].pass, pass, 24); + + lastlogin[23] = '\0'; + remove_control_chars((unsigned char *)lastlogin); + strncpy(auth_dat[auth_num].lastlogin, lastlogin, 24); + + auth_dat[auth_num].sex = (sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'); + + if (i >= 6) { + if (logincount >= 0) + auth_dat[auth_num].logincount = logincount; + else + auth_dat[auth_num].logincount = 0; + } else + auth_dat[auth_num].logincount = 0; + + if (i >= 7) { + if (state > 255) + auth_dat[auth_num].state = 100; + else if (state < 0) + auth_dat[auth_num].state = 0; + else + auth_dat[auth_num].state = state; + } else + auth_dat[auth_num].state = 0; + + // Initialization of new data + strncpy(auth_dat[auth_num].email, "a@a.com", 40); + strncpy(auth_dat[auth_num].error_message, "-", 20); + auth_dat[auth_num].ban_until_time = 0; + auth_dat[auth_num].connect_until_time = 0; + strncpy(auth_dat[auth_num].last_ip, "-", 16); + strncpy(auth_dat[auth_num].memo, "-", 255); + + for(j = 0; j < ACCOUNT_REG2_NUM; j++) { + p += n; + if (sscanf(p, "%[^\t,],%[^\t ] %n", str, v, &n) != 2) { + // We must check if a str is void. If it's, we can continue to read other REG2. + // Account line will have something like: str2,9 ,9 str3,1 (here, ,9 is not good) + if (p[0] == ',' && sscanf(p, ",%[^\t ] %n", v, &n) == 1) { + j--; + continue; + } else + break; + } + str[31] = '\0'; + remove_control_chars((unsigned char *)str); + strncpy(auth_dat[auth_num].account_reg2[j].str, str, 32); + strncpy(auth_dat[auth_num].account_reg2[j].value,v,256); + } + auth_dat[auth_num].account_reg2_num = j; + + if (isGM(account_id) > 0) + GM_count++; + if (auth_dat[auth_num].sex == 2) + server_count++; + + auth_num++; + if (account_id >= account_id_count) + account_id_count = account_id + 1; + + } else { + i = 0; + if (sscanf(line, "%d\t%%newid%%\n%n", &account_id, &i) == 1 && + i > 0 && account_id > account_id_count) + account_id_count = account_id; + } + } + fclose(fp); + + if (auth_num == 0) { + ShowNotice("mmo_auth_init: No account found in %s.\n", account_filename); + sprintf(line, "No account found in %s.", account_filename); + } else { + if (auth_num == 1) { + ShowStatus("mmo_auth_init: 1 account read in %s,\n", account_filename); + sprintf(line, "1 account read in %s,", account_filename); + } else { + ShowStatus("mmo_auth_init: %d accounts read in %s,\n", auth_num, account_filename); + sprintf(line, "%d accounts read in %s,", auth_num, account_filename); + } + if (GM_count == 0) { + ShowStatus(" of which is no GM account, and "); + sprintf(str, "%s of which is no GM account and", line); + } else if (GM_count == 1) { + ShowStatus(" of which is 1 GM account, and "); + sprintf(str, "%s of which is 1 GM account and", line); + } else { + ShowStatus(" of which is %d GM accounts, and ", GM_count); + sprintf(str, "%s of which is %d GM accounts and", line, GM_count); + } + if (server_count == 0) { + printf("no server account ('S').\n"); + sprintf(line, "%s no server account ('S').", str); + } else if (server_count == 1) { + printf("1 server account ('S').\n"); + sprintf(line, "%s 1 server account ('S').", str); + } else { + printf("%d server accounts ('S').\n", server_count); + sprintf(line, "%s %d server accounts ('S').", str, server_count); + } + } + login_log("%s" RETCODE, line); + + return 0; +} + +//------------------------------------------ +// Writing of the accounts database file +// (accounts are sorted by id before save) +//------------------------------------------ +void mmo_auth_sync(void) { + FILE *fp; + unsigned int i, j, k; + int lock; + int account_id; + //int id[auth_num]; + //int *id = (int *)aCalloc(auth_num, sizeof(int)); + CREATE_BUFFER(id, int, auth_num); + char line[65536]; + + // Sorting before save + for(i = 0; i < auth_num; i++) { + id[i] = i; + account_id = auth_dat[i].account_id; + for(j = 0; j < i; j++) { + if (account_id < auth_dat[id[j]].account_id) { + for(k = i; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[i] + break; + } + } + } + + // Data save + if ((fp = lock_fopen(account_filename, &lock)) == NULL) { + //if (id) aFree(id); // aFree, right? + DELETE_BUFFER(id); + return; + } + + fprintf(fp, "// Accounts file: here are saved all information about the accounts.\n"); + fprintf(fp, "// Structure: ID, account name, password, last login time, sex, # of logins, state, email, error message for state 7, validity time, last (accepted) login ip, memo field, ban timestamp, repeated(register text, register value)\n"); + fprintf(fp, "// Some explanations:\n"); + fprintf(fp, "// account name : between 4 to 23 char for a normal account (standard client can't send less than 4 char).\n"); + fprintf(fp, "// account password: between 4 to 23 char\n"); + fprintf(fp, "// sex : M or F for normal accounts, S for server accounts\n"); + fprintf(fp, "// state : 0: account is ok, 1 to 256: error code of packet 0x006a + 1\n"); + fprintf(fp, "// email : between 3 to 39 char (a@a.com is like no email)\n"); + fprintf(fp, "// error message : text for the state 7: 'Your are Prohibited to login until '. Max 19 char\n"); + fprintf(fp, "// valitidy time : 0: unlimited account, : date calculated by addition of 1/1/1970 + value (number of seconds since the 1/1/1970)\n"); + fprintf(fp, "// memo field : max 254 char\n"); + fprintf(fp, "// ban time : 0: no ban, : banned until the date: date calculated by addition of 1/1/1970 + value (number of seconds since the 1/1/1970)\n"); + for(i = 0; i < auth_num; i++) { + k = id[i]; // use of sorted index + if (auth_dat[k].account_id < 0) + continue; + + mmo_auth_tostr(line, &auth_dat[k]); + fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%\n", account_id_count); + + lock_fclose(fp, account_filename, &lock); + + // set new counter to minimum number of auth before save + auth_before_save_file = auth_num / AUTH_SAVE_FILE_DIVIDER; // Re-initialise counter. We have save. + if (auth_before_save_file < AUTH_BEFORE_SAVE_FILE) + auth_before_save_file = AUTH_BEFORE_SAVE_FILE; + + //if (id) aFree(id); + DELETE_BUFFER(id); + + return; +} + +//----------------------------------------------------- +// Check if we must save accounts file or not +// every minute, we check if we must save because we +// have do some authentifications without arrive to +// the minimum of authentifications for the save. +// Note: all other modification of accounts (deletion, +// change of some informations excepted lastip/ +// lastlogintime, creation) are always save +// immediatly and set the minimum of +// authentifications to its initialization value. +//----------------------------------------------------- +int check_auth_sync(int tid, unsigned int tick, int id, int data) { + // we only save if necessary: + // we have do some authentifications without do saving + if (auth_before_save_file < AUTH_BEFORE_SAVE_FILE || + auth_before_save_file < (int)(auth_num / AUTH_SAVE_FILE_DIVIDER)) + mmo_auth_sync(); + + return 0; +} + +//-------------------------------------------------------------------- +// Packet send to all char-servers, except one (wos: without our self) +//-------------------------------------------------------------------- +int charif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c, fd; + + for(i = 0, c = 0; i < MAX_SERVERS; i++) { + if ((fd = server_fd[i]) >= 0 && fd != sfd) { + WFIFOHEAD(fd, len); + if (WFIFOSPACE(fd) < len) //Increase buffer size. + realloc_writefifo(fd, len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; + } + } + return c; +} + +//----------------------------------------------------- +// Send GM accounts to all char-server +//----------------------------------------------------- +void send_GM_accounts(void) { + unsigned int i; + unsigned char buf[32767]; + int len; + + len = 4; + WBUFW(buf,0) = 0x2732; + for(i = 0; i < GM_num; i++) + // send only existing accounts. We can not create a GM account when server is online. + if (gm_account_db[i].level > 0) { + WBUFL(buf,len) = gm_account_db[i].account_id; + WBUFB(buf,len+4) = (unsigned char)gm_account_db[i].level; + len += 5; + } + WBUFW(buf,2) = len; + charif_sendallwos(-1, buf, len); + + return; +} + +//----------------------------------------------------- +// Check if GM file account have been changed +//----------------------------------------------------- +int check_GM_file(int tid, unsigned int tick, int id, int data) { + struct stat file_stat; + long new_time; + + // if we would not check + if (gm_account_filename_check_timer < 1) + return 0; + + // get last modify time/date + if (stat(GM_account_filename, &file_stat)) + new_time = 0; // error + else + new_time = (long)file_stat.st_mtime; + + if (new_time != creation_time_GM_account_file) { + read_gm_account(); + send_GM_accounts(); + } + + return 0; +} + +//------------------------------------- +// Account creation (with e-mail check) +//------------------------------------- +int mmo_auth_new(struct mmo_account* account, char sex, char* email) { + time_t timestamp, timestamp_temp; + struct tm *tmtime; + int i = auth_num; + + if (auth_num >= auth_max) { + auth_max += 256; + auth_dat = (struct auth_dat*)aRealloc(auth_dat, sizeof(struct auth_dat) * auth_max); + } + + memset(&auth_dat[i], '\0', sizeof(struct auth_dat)); + + while (isGM(account_id_count) > 0) + account_id_count++; + + auth_dat[i].account_id = account_id_count++; + + strncpy(auth_dat[i].userid, account->userid, 24); + auth_dat[i].userid[23] = '\0'; + + strncpy(auth_dat[i].pass, account->passwd, 32); + auth_dat[i].pass[23] = '\0'; + + memcpy(auth_dat[i].lastlogin, "-", 2); + + auth_dat[i].sex = (sex == 'M' || sex == 'm'); + + auth_dat[i].logincount = 0; + + auth_dat[i].state = 0; + + if (e_mail_check(email) == 0) + strncpy(auth_dat[i].email, "a@a.com", 40); + else + strncpy(auth_dat[i].email, email, 40); + + strncpy(auth_dat[i].error_message, "-", 20); + + auth_dat[i].ban_until_time = 0; + + if (start_limited_time < 0) + auth_dat[i].connect_until_time = 0; // unlimited + else { // limited time + timestamp = time(NULL) + start_limited_time; + // double conversion to be sure that it is possible + tmtime = localtime(×tamp); + timestamp_temp = mktime(tmtime); + if (timestamp_temp != -1 && (timestamp_temp + 3600) >= timestamp) // check possible value and overflow (and avoid summer/winter hour) + auth_dat[i].connect_until_time = timestamp_temp; + else + auth_dat[i].connect_until_time = 0; // unlimited + } + + strncpy(auth_dat[i].last_ip, "-", 16); + + strncpy(auth_dat[i].memo, "-", 255); + + auth_dat[i].account_reg2_num = 0; + + auth_num++; + + return (account_id_count - 1); +} + +//--------------------------------------- +// Check/authentification of a connection +//--------------------------------------- +int mmo_auth(struct mmo_account* account, int fd) { + unsigned int i; + time_t raw_time; + char tmpstr[256]; + int len, newaccount = 0; +#ifdef PASSWORDENC + struct login_session_data *ld; +#endif + int encpasswdok; + char md5str[64], md5bin[32]; + char ip[16]; + unsigned char *sin_addr = (unsigned char *)&session[fd]->client_addr.sin_addr; + char user_password[256]; + + sprintf(ip, "%d.%d.%d.%d", sin_addr[0], sin_addr[1], sin_addr[2], sin_addr[3]); + + len = strlen(account->userid) - 2; + // Account creation with _M/_F + if (account->passwdenc == 0 && account->userid[len] == '_' && + (account->userid[len+1] == 'F' || account->userid[len+1] == 'M' || + account->userid[len+1] == 'f' || account->userid[len+1] == 'm') + && new_account_flag && account_id_count <= END_ACCOUNT_NUM && len >= 4 && strlen(account->passwd) >= 4) { + + //only continue if amount in this time limit is allowed (account registration flood protection)[Kevin] + if(gettick() <= new_reg_tick && num_regs >= allowed_regs) { + ShowNotice("Account registration denied (registration limit exceeded) to %s!\n", ip); + login_log("Notice: Account registration denied (registration limit exceeded) to %s!", ip); + return 3; + } else { + num_regs=0; + } + + newaccount = 1; + account->userid[len] = '\0'; + } + + //EXE Version check [Sirius] + if (check_client_version == 1 && account->version != 0 && + account->version != client_version_to_connect) + return 5; + + // Strict account search + for(i = 0; i < auth_num; i++) { + if (strcmp(account->userid, auth_dat[i].userid) == 0) + break; + } + // if there is no creation request and strict account search fails, we do a no sensitive case research for index + if (!newaccount && i == auth_num) { + i = search_account_index(account->userid); + if (i == -1) + i = auth_num; + else + memcpy(account->userid, auth_dat[i].userid, NAME_LENGTH); // for the possible tests/checks afterwards (copy correcte sensitive case). + } + + if (i != auth_num) { + if (newaccount) { + login_log("Attempt of creation of an already existant account (account: %s_%c, pass: %s, received pass: %s, ip: %s)" RETCODE, + account->userid, account->userid[len+1], auth_dat[i].pass, account->passwd, ip); + return 1; // 1 = Incorrect Password + } + if(use_md5_passwds) + MD5_String(account->passwd, user_password); + else + memcpy(user_password, account->passwd, 25); + encpasswdok = 0; +#ifdef PASSWORDENC + ld = (struct login_session_data*)session[fd]->session_data; + if (account->passwdenc > 0) { + int j = account->passwdenc; + if (!ld) { + login_log("Md5 key not created (account: %s, ip: %s)" RETCODE, account->userid, ip); + return 1; // 1 = Incorrect Password + } + if (j > 2) + j = 1; + do { + if (j == 1) { + sprintf(md5str, "%s%s", ld->md5key, auth_dat[i].pass); // 20 + 24 + } else if (j == 2) { + sprintf(md5str, "%s%s", auth_dat[i].pass, ld->md5key); // 24 + 20 + } else + md5str[0] = '\0'; + md5str[sizeof(md5str)-1] = '\0'; // 64 + MD5_String2binary(md5str, md5bin); + encpasswdok = (memcmp(account->passwd, md5bin, 16) == 0); + } while (j < 2 && !encpasswdok && (j++) != account->passwdenc); +// printf("key[%s] md5 [%s] ", md5key, md5); +// printf("client [%s] accountpass [%s]\n", account->passwd, auth_dat[i].pass); + } +#endif + if ((strcmp(account->passwd, auth_dat[i].pass) && !encpasswdok)) { + if (account->passwdenc == 0) + login_log("Invalid password (account: %s, pass: %s, received pass: %s, ip: %s)" RETCODE, account->userid, auth_dat[i].pass, account->passwd, ip); +#ifdef PASSWORDENC + else { + char logbuf[512], *p = logbuf; + unsigned int j; + p += sprintf(p, "Invalid password (account: %s, received md5[", account->userid); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)account->passwd)[j]); + p += sprintf(p,"] calculated md5["); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)md5bin)[j]); + p += sprintf(p, "] md5 key["); + for(j = 0; j < ld->md5keylen; j++) + p += sprintf(p, "%02x", ((unsigned char *)ld->md5key)[j]); + p += sprintf(p, "], ip: %s)" RETCODE, ip); + login_log(logbuf); + } +#endif + return 1; // 1 = Incorrect Password + } + + if (auth_dat[i].state) { + login_log("Connection refused (account: %s, pass: %s, state: %d, ip: %s)" RETCODE, + account->userid, account->passwd, auth_dat[i].state, ip); + switch(auth_dat[i].state) { // packet 0x006a value + 1 + case 1: // 0 = Unregistered ID + case 2: // 1 = Incorrect Password + case 3: // 2 = This ID is expired + case 4: // 3 = Rejected from Server + case 5: // 4 = You have been blocked by the GM Team + case 6: // 5 = Your Game's EXE file is not the latest version + case 7: // 6 = Your are Prohibited to log in until %s + case 8: // 7 = Server is jammed due to over populated + case 9: // 8 = No more accounts may be connected from this company + case 10: // 9 = MSI_REFUSE_BAN_BY_DBA + case 11: // 10 = MSI_REFUSE_EMAIL_NOT_CONFIRMED + case 12: // 11 = MSI_REFUSE_BAN_BY_GM + case 13: // 12 = MSI_REFUSE_TEMP_BAN_FOR_DBWORK + case 14: // 13 = MSI_REFUSE_SELF_LOCK + case 15: // 14 = MSI_REFUSE_NOT_PERMITTED_GROUP + case 16: // 15 = MSI_REFUSE_NOT_PERMITTED_GROUP + case 100: // 99 = This ID has been totally erased + case 101: // 100 = Login information remains at %s. + case 102: // 101 = Account has been locked for a hacking investigation. Please contact the GM Team for more information + case 103: // 102 = This account has been temporarily prohibited from login due to a bug-related investigation + case 104: // 103 = This character is being deleted. Login is temporarily unavailable for the time being + case 105: // 104 = Your spouse character is being deleted. Login is temporarily unavailable for the time being + return auth_dat[i].state - 1; + default: + return 99; // 99 = ID has been totally erased + } + } + + if (online_check) { + unsigned char buf[8]; + struct online_login_data* data = idb_get(online_db,auth_dat[i].account_id); + if (data && data->char_server > -1) { + //Request char servers to kick this account out. [Skotlex] + ShowWarning("User [%d] is already online - Rejected.\n",auth_dat[i].account_id); + WBUFW(buf,0) = 0x2734; + WBUFL(buf,2) = auth_dat[i].account_id; + charif_sendallwos(-1, buf, 6); + if (!data->waiting_disconnect) + add_timer(gettick()+30000, waiting_disconnect_timer,auth_dat[i].account_id, 0); + data->waiting_disconnect = 1; + return 3; // Rejected + } + } + + if (auth_dat[i].ban_until_time != 0) { // if account is banned + strftime(tmpstr, 20, date_format, localtime(&auth_dat[i].ban_until_time)); + tmpstr[19] = '\0'; + if (auth_dat[i].ban_until_time > time(NULL)) { // always banned + login_log("Connection refused (account: %s, pass: %s, banned until %s, ip: %s)" RETCODE, + account->userid, account->passwd, tmpstr, ip); + return 6; // 6 = Your are Prohibited to log in until %s + } else { // ban is finished + login_log("End of ban (account: %s, pass: %s, previously banned until %s -> not more banned, ip: %s)" RETCODE, + account->userid, account->passwd, tmpstr, ip); + auth_dat[i].ban_until_time = 0; // reset the ban time + } + } + + if (auth_dat[i].connect_until_time != 0 && auth_dat[i].connect_until_time < time(NULL)) { + login_log("Connection refused (account: %s, pass: %s, expired ID, ip: %s)" RETCODE, + account->userid, account->passwd, ip); + return 2; // 2 = This ID is expired + } + + login_log("Authentification accepted (account: %s (id: %d), ip: %s)" RETCODE, account->userid, auth_dat[i].account_id, ip); + } else { + if (!newaccount) { + login_log("Unknown account (account: %s, received pass: %s, ip: %s)" RETCODE, + account->userid, account->passwd, ip); + return 0; // 0 = Unregistered ID + } else { + int new_id = mmo_auth_new(account, account->userid[len+1], "a@a.com"); + login_log("Account creation and authentification accepted (account %s (id: %d), pass: %s, sex: %c, connection with _F/_M, ip: %s)" RETCODE, + account->userid, new_id, account->passwd, account->userid[len+1], ip); + auth_before_save_file = 0; // Creation of an account -> save accounts file immediatly + + //restart ticker (account registration flood protection)[Kevin] + if(num_regs==0) { + new_reg_tick=gettick()+time_allowed*1000; + } + num_regs++; + } + } + + // auth start : time seed + // Platform/Compiler dependant clock() for time check is removed. [Lance] + // clock() is originally used to track processing ticks on program execution. + time(&raw_time); + strftime(tmpstr, 24, "%Y-%m-%d %H:%M:%S",localtime(&raw_time)); + + account->account_id = auth_dat[i].account_id; + account->login_id1 = rand(); + account->login_id2 = rand(); + memcpy(account->lastlogin, auth_dat[i].lastlogin, 24); + memcpy(auth_dat[i].lastlogin, tmpstr, 24); + account->sex = auth_dat[i].sex; + if (account->sex != 2 && account->account_id < 700000) + ShowWarning("Account %s has account id %d! Account IDs must be over 700000 to work properly!\n", account->userid, account->account_id); + + strncpy(auth_dat[i].last_ip, ip, 16); + auth_dat[i].logincount++; + + // Save until for change ip/time of auth is not very useful => limited save for that + // Save there informations isnot necessary, because they are saved in log file. + if (--auth_before_save_file <= 0) // Reduce counter. 0 or less, we save + mmo_auth_sync(); + + return -1; // account OK +} + +static int online_db_setoffline(DBKey key, void* data, va_list ap) { + struct online_login_data *p = (struct online_login_data *)data; + int server = va_arg(ap, int); + if (server == -1) { + p->char_server = -1; + p->waiting_disconnect = 0; + } else if (p->char_server == server) + p->char_server = -2; //Char server disconnected. + return 0; +} + +//-------------------------------- +// Packet parsing for char-servers +//-------------------------------- +int parse_fromchar(int fd) { + unsigned int i; + int j, id; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + int acc; + RFIFOHEAD(fd); + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + for(id = 0; id < MAX_SERVERS; id++) + if (server_fd[id] == fd) + break; + if (id == MAX_SERVERS) + session[fd]->eof = 1; + if(session[fd]->eof) { + if (id < MAX_SERVERS) { + ShowWarning("Char-server '%s' has disconnected.\n", server[id].name); + login_log("Char-server '%s' has disconnected (ip: %s)." RETCODE, + server[id].name, ip); + server_fd[id] = -1; + memset(&server[id], 0, sizeof(struct mmo_char_server)); + online_db->foreach(online_db,online_db_setoffline,id); //Set all chars from this char server to offline. + } + do_close(fd); + return 0; + } + + while (RFIFOREST(fd) >= 2) { + + if (display_parse_fromchar == 2 || (display_parse_fromchar == 1 && RFIFOW(fd,0) != 0x2714)) // 0x2714 is done very often (number of players) + ShowDebug("parse_fromchar: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch (RFIFOW(fd,0)) { + // request from map-server via char-server to reload GM accounts (by Yor). + case 0x2709: + login_log("Char-server '%s': Request to re-load GM configuration file (ip: %s)." RETCODE, server[id].name, ip); + read_gm_account(); + // send GM accounts to all char-servers + send_GM_accounts(); + RFIFOSKIP(fd,2); + break; + + case 0x2712: // request from char-server to authentify an account + if (RFIFOREST(fd) < 19) + return 0; + { + int acc; + acc = RFIFOL(fd,2); // speed up + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == acc && + auth_fifo[i].login_id1 == RFIFOL(fd,6) && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == RFIFOL(fd,10) && // relate to the versions higher than 18 +#endif + auth_fifo[i].sex == RFIFOB(fd,14) && + (!check_ip_flag || auth_fifo[i].ip == RFIFOL(fd,15)) && + !auth_fifo[i].delflag) { + unsigned int k; + time_t connect_until_time = 0; + char email[40] = ""; + auth_fifo[i].delflag = 1; + login_log("Char-server '%s': authentification of the account %d accepted (ip: %s)." RETCODE, + server[id].name, acc, ip); +// printf("%d\n", i); + for(k = 0; k < auth_num; k++) { + if (auth_dat[k].account_id == acc) { + strcpy(email, auth_dat[k].email); + connect_until_time = auth_dat[k].connect_until_time; + break; + } + } + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = acc; + WFIFOB(fd,6) = 0; + memcpy(WFIFOP(fd, 7), email, 40); + WFIFOL(fd,47) = (unsigned long)connect_until_time; + WFIFOSET(fd,51); + break; + } + } + // authentification not found + if (i == AUTH_FIFO_SIZE) { + login_log("Char-server '%s': authentification of the account %d REFUSED (ip: %s)." RETCODE, + server[id].name, acc, ip); + WFIFOHEAD(fd, 51); + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = acc; + WFIFOB(fd,6) = 1; + // It is unnecessary to send email + // It is unnecessary to send validity date of the account + WFIFOSET(fd,51); + } + } + RFIFOSKIP(fd,19); + break; + + case 0x2714: + if (RFIFOREST(fd) < 6) + return 0; + //printf("parse_fromchar: Receiving of the users number of the server '%s': %d\n", server[id].name, RFIFOL(fd,2)); + server[id].users = RFIFOL(fd,2); + // send some answer + WFIFOHEAD(fd, 2); + WFIFOW(fd,0) = 0x2718; + WFIFOSET(fd,2); + + RFIFOSKIP(fd,6); + break; + + // we receive a e-mail creation of an account with a default e-mail (no answer) + case 0x2715: + if (RFIFOREST(fd) < 46) + return 0; + { + char email[40]; + acc = RFIFOL(fd,2); // speed up + memcpy(email, RFIFOP(fd,6), 40); + email[39] = '\0'; + remove_control_chars((unsigned char *)email); + //printf("parse_fromchar: an e-mail creation of an account with a default e-mail: server '%s', account: %d, e-mail: '%s'.\n", server[id].name, acc, RFIFOP(fd,6)); + if (e_mail_check(email) == 0) + login_log("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - e-mail is invalid (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else { + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc && (strcmp(auth_dat[i].email, "a@a.com") == 0 || auth_dat[i].email[0] == '\0')) { + memcpy(auth_dat[i].email, email, 40); + login_log("Char-server '%s': Create an e-mail on an account with a default e-mail (account: %d, new e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, email, ip); + // Save + mmo_auth_sync(); + break; + } + } + if (i == auth_num) + login_log("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - account doesn't exist or e-mail of account isn't default e-mail (account: %d, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } + RFIFOSKIP(fd,46); + break; + + // We receive an e-mail/limited time request, because a player comes back from a map-server to the char-server + case 0x2716: + if (RFIFOREST(fd) < 6) + return 0; + //printf("parse_fromchar: E-mail/limited time request from '%s' server (concerned account: %d)\n", server[id].name, RFIFOL(fd,2)); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == RFIFOL(fd,2)) { + login_log("Char-server '%s': e-mail of the account %d found (ip: %s)." RETCODE, + server[id].name, RFIFOL(fd,2), ip); + WFIFOW(fd,0) = 0x2717; + WFIFOL(fd,2) = RFIFOL(fd,2); + memcpy(WFIFOP(fd, 6), auth_dat[i].email, 40); + WFIFOL(fd,46) = (unsigned long)auth_dat[i].connect_until_time; + WFIFOSET(fd,50); + break; + } + } + if (i == auth_num) + login_log("Char-server '%s': e-mail of the account %d NOT found (ip: %s)." RETCODE, + server[id].name, RFIFOL(fd,2), ip); + RFIFOSKIP(fd,6); + break; + + case 0x2720: // To become GM request + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + unsigned char buf[10]; + FILE *fp; + acc = RFIFOL(fd,4); + //printf("parse_fromchar: Request to become a GM acount from %d account.\n", acc); + WBUFW(buf,0) = 0x2721; + WBUFL(buf,2) = acc; + WBUFL(buf,6) = 0; + if (strcmp((char*)RFIFOP(fd,8), gm_pass) == 0) { + // only non-GM can become GM + if (isGM(acc) == 0) { + // if we autorise creation + if (level_new_gm > 0) { + // if we can open the file to add the new GM + if ((fp = fopen(GM_account_filename, "a")) != NULL) { + char tmpstr[24]; + time_t raw_time; + time(&raw_time); + strftime(tmpstr, 23, date_format, localtime(&raw_time)); + fprintf(fp, RETCODE "// %s: @GM command on account %d" RETCODE "%d %d" RETCODE, tmpstr, acc, acc, level_new_gm); + fclose(fp); + WBUFL(buf,6) = level_new_gm; + read_gm_account(); + send_GM_accounts(); + ShowNotice("GM Change of the account %d: level 0 -> %d.\n", acc, level_new_gm); + login_log("Char-server '%s': GM Change of the account %d: level 0 -> %d (ip: %s)." RETCODE, + server[id].name, acc, level_new_gm, ip); + } else { + ShowError("Error of GM change (suggested account: %d, correct password, unable to add a GM account in GM accounts file)\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d, correct password, unable to add a GM account in GM accounts file, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + ShowError("Error of GM change (suggested account: %d, correct password, but GM creation is disable (level_new_gm = 0))\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d, correct password, but GM creation is disable (level_new_gm = 0), ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + ShowError("Error of GM change (suggested account: %d (already GM), correct password).\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d (already GM), correct password, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + ShowError("Error of GM change (suggested account: %d, invalid password).\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d, invalid password, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + charif_sendallwos(-1, buf, 10); + } + RFIFOSKIP(fd, RFIFOW(fd,2)); + return 0; + + // Map server send information to change an email of an account via char-server + case 0x2722: // 0x2722 .L .40B .40B + if (RFIFOREST(fd) < 86) + return 0; + { + char actual_email[40], new_email[40]; + acc = RFIFOL(fd,2); + memcpy(actual_email, RFIFOP(fd,6), 40); + actual_email[39] = '\0'; + remove_control_chars((unsigned char *)actual_email); + memcpy(new_email, RFIFOP(fd,46), 40); + new_email[39] = '\0'; + remove_control_chars((unsigned char *)new_email); + if (e_mail_check(actual_email) == 0) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual email is invalid (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (e_mail_check(new_email) == 0) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a invalid new e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (strcmpi(new_email, "a@a.com") == 0) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a default e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else { + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + if (strcmpi(auth_dat[i].email, actual_email) == 0) { + memcpy(auth_dat[i].email, new_email, 40); + login_log("Char-server '%s': Modify an e-mail on an account (@email GM command) (account: %d (%s), new e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, auth_dat[i].userid, new_email, ip); + // Save + mmo_auth_sync(); + } else + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual e-mail is incorrect (account: %d (%s), actual e-mail: %s, proposed e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, auth_dat[i].userid, auth_dat[i].email, actual_email, ip); + break; + } + } + if (i == auth_num) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but account doesn't exist (account: %d, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } + RFIFOSKIP(fd, 86); + break; + + // Receiving of map-server via char-server a status change resquest (by Yor) + case 0x2724: + if (RFIFOREST(fd) < 10) + return 0; + { + int acc, statut; + acc = RFIFOL(fd,2); + statut = RFIFOL(fd,6); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + if (auth_dat[i].state != statut) { + login_log("Char-server '%s': Status change (account: %d, new status %d, ip: %s)." RETCODE, + server[id].name, acc, statut, ip); + if (statut != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = 0; // 0: change of statut, 1: ban + WBUFL(buf,7) = statut; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == acc) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].state = statut; + // Save + mmo_auth_sync(); + } else + login_log("Char-server '%s': Error of Status change - actual status is already the good status (account: %d, status %d, ip: %s)." RETCODE, + server[id].name, acc, statut, ip); + break; + } + } + if (i == auth_num) { + login_log("Char-server '%s': Error of Status change (account: %d not found, suggested status %d, ip: %s)." RETCODE, + server[id].name, acc, statut, ip); + } + RFIFOSKIP(fd,10); + } + return 0; + + case 0x2725: // Receiving of map-server via char-server a ban resquest (by Yor) + if (RFIFOREST(fd) < 18) + return 0; + { + acc = RFIFOL(fd,2); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + time_t timestamp; + struct tm *tmtime; + if (auth_dat[i].ban_until_time == 0 || auth_dat[i].ban_until_time < time(NULL)) + timestamp = time(NULL); + else + timestamp = auth_dat[i].ban_until_time; + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,6); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,8); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,10); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,12); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,14); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,16); + timestamp = mktime(tmtime); + if (timestamp != -1) { + if (timestamp <= time(NULL)) + timestamp = 0; + if (auth_dat[i].ban_until_time != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + char tmpstr[2048]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + login_log("Char-server '%s': Ban request (account: %d, new final date of banishment: %d (%s), ip: %s)." RETCODE, + server[id].name, acc, timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = (unsigned int)timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == acc) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } else { + login_log("Char-server '%s': Error of ban request (account: %d, new date unbans the account, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + auth_dat[i].ban_until_time = timestamp; + // Save + mmo_auth_sync(); + } else { + login_log("Char-server '%s': Error of ban request (account: %d, no change for ban date, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + login_log("Char-server '%s': Error of ban request (account: %d, invalid date, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + break; + } + } + if (i == auth_num) + login_log("Char-server '%s': Error of ban request (account: %d not found, ip: %s)." RETCODE, + server[id].name, acc, ip); + RFIFOSKIP(fd,18); + } + return 0; + + case 0x2727: // Change of sex (sex is reversed) + if (RFIFOREST(fd) < 6) + return 0; + { + int sex; + acc = RFIFOL(fd,2); + for(i = 0; i < auth_num; i++) { +// printf("%d,", auth_dat[i].account_id); + if (auth_dat[i].account_id == acc) { + if (auth_dat[i].sex == 2) + login_log("Char-server '%s': Error of sex change - Server account (suggested account: %d, actual sex %d (Server), ip: %s)." RETCODE, + server[id].name, acc, auth_dat[i].sex, ip); + else { + unsigned char buf[16]; + if (auth_dat[i].sex == 0) + sex = 1; + else + sex = 0; + login_log("Char-server '%s': Sex change (account: %d, new sex %c, ip: %s)." RETCODE, + server[id].name, acc, (sex == 2) ? 'S' : (sex ? 'M' : 'F'), ip); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == acc) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + auth_dat[i].sex = sex; + WBUFW(buf,0) = 0x2723; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + charif_sendallwos(-1, buf, 7); + // Save + mmo_auth_sync(); + } + break; + } + } + if (i == auth_num) + login_log("Char-server '%s': Error of sex change (account: %d not found, sex would be reversed, ip: %s)." RETCODE, + server[id].name, acc, ip); + RFIFOSKIP(fd,6); + } + return 0; + + case 0x2728: // We receive account_reg2 from a char-server, and we send them to other map-servers. + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int p; + acc = RFIFOL(fd,4); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + //unsigned char buf[rfifow(fd,2)+1]; + unsigned char *buf; + int len; + buf = (unsigned char*)aCalloc(RFIFOW(fd,2)+1, sizeof(unsigned char)); + login_log("char-server '%s': receiving (from the char-server) of account_reg2 (account: %d, ip: %s)." RETCODE, + server[id].name, acc, ip); + for(j=0,p=13;jforeach(online_db,online_db_setoffline,id); //Set all chars from this char-server offline first + users = RFIFOW(fd,4); + for (i = 0; i < users; i++) { + aid = RFIFOL(fd,6+i*4); + p = idb_ensure(online_db, aid, create_online_user); + p->char_server = id; + p->waiting_disconnect = 0; + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + } + case 0x272e: //Request account_reg2 for a character. + if (RFIFOREST(fd) < 10) + return 0; + { + int account_id = RFIFOL(fd, 2); + int char_id = RFIFOL(fd, 6); + int p; + RFIFOSKIP(fd,10); + WFIFOW(fd,0) = 0x2729; + WFIFOL(fd,4) = account_id; + WFIFOL(fd,8) = char_id; + WFIFOB(fd,12) = 1; //Type 1 for Account2 registry + for(i = 0; i < auth_num && auth_dat[i].account_id != account_id; i++); + if (i == auth_num) { + //Account not found? Send at least empty data, map servers need a reply! + WFIFOW(fd,2) = 13; + WFIFOSET(fd,WFIFOW(fd,2)); + break; + } + for(p = 13,j=0;j disconnection" RETCODE, tmpstr); + fprintf(logfp, "parse_fromchar: connection #%d (ip: %s), packet: 0x%x (with being read: %d)." RETCODE, fd, ip, RFIFOW(fd,0), RFIFOREST(fd)); + fprintf(logfp, "Detail (in hex):" RETCODE); + fprintf(logfp, "---- 00-01-02-03-04-05-06-07 08-09-0A-0B-0C-0D-0E-0F" RETCODE); + memset(tmpstr, '\0', sizeof(tmpstr)); + for(i = 0; i < RFIFOREST(fd); i++) { + if ((i & 15) == 0) + fprintf(logfp, "%04X ",i); + fprintf(logfp, "%02x ", RFIFOB(fd,i)); + if (RFIFOB(fd,i) > 0x1f) + tmpstr[i % 16] = RFIFOB(fd,i); + else + tmpstr[i % 16] = '.'; + if ((i - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + else if ((i + 1) % 16 == 0) { + fprintf(logfp, " %s" RETCODE, tmpstr); + memset(tmpstr, '\0', sizeof(tmpstr)); + } + } + if (i % 16 != 0) { + for(j = i; j % 16 != 0; j++) { + fprintf(logfp, " "); + if ((j - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + } + fprintf(logfp, " %s" RETCODE, tmpstr); + } + fprintf(logfp, RETCODE); + fclose(logfp); + } + } + ShowWarning("parse_fromchar: Unknown packet 0x%x (from a char-server)! -> disconnection.\n", RFIFOW(fd,0)); + session[fd]->eof = 1; + ShowStatus("Char-server has been disconnected (unknown packet).\n"); + return 0; + } + } + RFIFOSKIP(fd,RFIFOREST(fd)); + return 0; +} + +//--------------------------------------- +// Packet parsing for administation login +//--------------------------------------- +int parse_admin(int fd) { + unsigned int i, j; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char* account_name; + char ip[16]; + RFIFOHEAD(fd); + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + if (session[fd]->eof) { + do_close(fd); + ShowInfo("Remote administration has disconnected (session #%d).\n", fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { + if (display_parse_admin == 1) { + + ShowDebug("parse_admin: connection #%d, packet: 0x%x (with being read: %d).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + } + + switch(RFIFOW(fd,0)) { + case 0x7530: // Request of the server version + login_log("'ladmin': Sending of the server version (ip: %s)" RETCODE, ip); + WFIFOHEAD(fd, 10); + WFIFOW(fd,0) = 0x7531; + WFIFOB(fd,2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd,3) = ATHENA_MINOR_VERSION; + WFIFOB(fd,4) = ATHENA_REVISION; + WFIFOB(fd,5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd,6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7) = ATHENA_SERVER_LOGIN; + WFIFOW(fd,8) = ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + break; + + case 0x7532: // Request of end of connection + login_log("'ladmin': End of connection (ip: %s)" RETCODE, ip); + RFIFOSKIP(fd,2); + session[fd]->eof = 1; + break; + + case 0x7920: // Request of an accounts list + if (RFIFOREST(fd) < 10) + return 0; + { + int st, ed, len; + //int id[auth_num]; + //int *id=(int *)aCalloc(auth_num, sizeof(int)); + CREATE_BUFFER(id, int, auth_num); + st = RFIFOL(fd,2); + ed = RFIFOL(fd,6); + RFIFOSKIP(fd,10); + WFIFOW(fd,0) = 0x7921; + if (st < 0) + st = 0; + if (ed > END_ACCOUNT_NUM || ed < st || ed <= 0) + ed = END_ACCOUNT_NUM; + login_log("'ladmin': Sending an accounts list (ask: from %d to %d, ip: %s)" RETCODE, st, ed, ip); + // Sort before send + for(i = 0; i < auth_num; i++) { + unsigned int k; + id[i] = i; + for(j = 0; j < i; j++) { + if (auth_dat[id[i]].account_id < auth_dat[id[j]].account_id) { + for(k = i; k > j; k--) { + id[k] = id[k-1]; + } + id[j] = i; // id[i] + break; + } + } + } + // Sending accounts information + len = 4; + for(i = 0; i < auth_num && len < 30000; i++) { + int account_id = auth_dat[id[i]].account_id; // use sorted index + if (account_id >= st && account_id <= ed) { + j = id[i]; + WFIFOL(fd,len) = account_id; + WFIFOB(fd,len+4) = (unsigned char)isGM(account_id); + memcpy(WFIFOP(fd,len+5), auth_dat[j].userid, 24); + WFIFOB(fd,len+29) = auth_dat[j].sex; + WFIFOL(fd,len+30) = auth_dat[j].logincount; + if (auth_dat[j].state == 0 && auth_dat[j].ban_until_time != 0) // if no state and banished + WFIFOL(fd,len+34) = 7; // 6 = Your are Prohibited to log in until %s + else + WFIFOL(fd,len+34) = auth_dat[j].state; + len += 38; + } + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + //if (id) free(id); + DELETE_BUFFER(id); + } + break; + + case 0x7930: // Request for an account creation + if (RFIFOREST(fd) < 91) + return 0; + { + struct mmo_account ma; + ma.userid = (char*)RFIFOP(fd, 2); + ma.userid[23] = '\0'; + memcpy(ma.passwd, RFIFOP(fd, 26), 24); + ma.passwd[23] = '\0'; + memcpy(ma.lastlogin, "-", 2); + ma.sex = RFIFOB(fd,50); + WFIFOW(fd,0) = 0x7931; + WFIFOL(fd,2) = 0xffffffff; + memcpy(WFIFOP(fd,6), RFIFOP(fd,2), 24); + if (strlen(ma.userid) > 23 || strlen(ma.passwd) > 23) { + login_log("'ladmin': Attempt to create an invalid account (account or pass is too long, ip: %s)" RETCODE, + ip); + } else if (strlen(ma.userid) < 4 || strlen(ma.passwd) < 4) { + login_log("'ladmin': Attempt to create an invalid account (account or pass is too short, ip: %s)" RETCODE, + ip); + } else if (ma.sex != 'F' && ma.sex != 'M') { + login_log("'ladmin': Attempt to create an invalid account (account: %s, received pass: %s, invalid sex, ip: %s)" RETCODE, + ma.userid, ma.passwd, ip); + } else if (account_id_count > END_ACCOUNT_NUM) { + login_log("'ladmin': Attempt to create an account, but there is no more available id number (account: %s, pass: %s, sex: %c, ip: %s)" RETCODE, + ma.userid, ma.passwd, ma.sex, ip); + } else { + remove_control_chars((unsigned char *)ma.userid); + remove_control_chars((unsigned char *)ma.passwd); + for(i = 0; i < auth_num; i++) { + if (strncmp(auth_dat[i].userid, ma.userid, 24) == 0) { + login_log("'ladmin': Attempt to create an already existing account (account: %s, pass: %s, received pass: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].pass, ma.passwd, ip); + break; + } + } + if (i == auth_num) { + int new_id; + char email[40]; + memcpy(email, RFIFOP(fd,51), 40); + email[39] = '\0'; + remove_control_chars((unsigned char *)email); + new_id = mmo_auth_new(&ma, ma.sex, email); + login_log("'ladmin': Account creation (account: %s (id: %d), pass: %s, sex: %c, email: %s, ip: %s)" RETCODE, + ma.userid, new_id, ma.passwd, ma.sex, auth_dat[i].email, ip); + WFIFOL(fd,2) = new_id; + mmo_auth_sync(); + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,91); + } + break; + + case 0x7932: // Request for an account deletion + if (RFIFOREST(fd) < 26) + return 0; + WFIFOW(fd,0) = 0x7933; + WFIFOL(fd,2) = 0xFFFFFFFF; + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + i = search_account_index(account_name); + if (i != -1) { + // Char-server is notified of deletion (for characters deletion). + unsigned char buf[65535]; + WBUFW(buf,0) = 0x2730; + WBUFL(buf,2) = auth_dat[i].account_id; + charif_sendallwos(-1, buf, 6); + // send answer + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + // save deleted account in log file + login_log("'ladmin': Account deletion (account: %s, id: %d, ip: %s) - saved in next line:" RETCODE, + auth_dat[i].userid, auth_dat[i].account_id, ip); + mmo_auth_tostr((char*)buf, &auth_dat[i]); + login_log("%s" RETCODE, buf); + // delete account + memset(auth_dat[i].userid, '\0', sizeof(auth_dat[i].userid)); + auth_dat[i].account_id = -1; + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to delete an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,26); + break; + + case 0x7934: // Request to change a password + if (RFIFOREST(fd) < 50) + return 0; + WFIFOW(fd,0) = 0x7935; + WFIFOL(fd,2) = 0xFFFFFFFF; /// WTF??? an unsigned being set to a -1 + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + memcpy(auth_dat[i].pass, RFIFOP(fd,26), 24); + auth_dat[i].pass[23] = '\0'; + remove_control_chars((unsigned char *)auth_dat[i].pass); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Modification of a password (account: %s, new password: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].pass, ip); + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to modify the password of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,50); + break; + + case 0x7936: // Request to modify a state + if (RFIFOREST(fd) < 50) + return 0; + { + char error_message[20]; + int statut; + WFIFOW(fd,0) = 0x7937; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + statut = RFIFOL(fd,26); + memcpy(error_message, RFIFOP(fd,30), 20); + error_message[19] = '\0'; + remove_control_chars((unsigned char *)error_message); + if (statut != 7 || error_message[0] == '\0') { // 7: // 6 = Your are Prohibited to log in until %s + strcpy(error_message, "-"); + } + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + if (auth_dat[i].state == statut && strcmp(auth_dat[i].error_message, error_message) == 0) + login_log("'ladmin': Modification of a state, but the state of the account is already the good state (account: %s, received state: %d, ip: %s)" RETCODE, + account_name, statut, ip); + else { + if (statut == 7) + login_log("'ladmin': Modification of a state (account: %s, new state: %d - prohibited to login until '%s', ip: %s)" RETCODE, + auth_dat[i].userid, statut, error_message, ip); + else + login_log("'ladmin': Modification of a state (account: %s, new state: %d, ip: %s)" RETCODE, + auth_dat[i].userid, statut, ip); + if (auth_dat[i].state == 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 0; // 0: change of statut, 1: ban + WBUFL(buf,7) = statut; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].state = statut; + memcpy(auth_dat[i].error_message, error_message, 20); + mmo_auth_sync(); + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to modify the state of an unknown account (account: %s, received state: %d, ip: %s)" RETCODE, + account_name, statut, ip); + } + WFIFOL(fd,30) = statut; + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,50); + break; + + case 0x7938: // Request for servers list and # of online players + login_log("'ladmin': Sending of servers list (ip: %s)" RETCODE, ip); + server_num = 0; + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) { + WFIFOL(fd,4+server_num*32) = server[i].ip; + WFIFOW(fd,4+server_num*32+4) = server[i].port; + memcpy(WFIFOP(fd,4+server_num*32+6), server[i].name, 20); + WFIFOW(fd,4+server_num*32+26) = server[i].users; + WFIFOW(fd,4+server_num*32+28) = server[i].maintenance; + WFIFOW(fd,4+server_num*32+30) = server[i].new_; + server_num++; + } + } + WFIFOW(fd,0) = 0x7939; + WFIFOW(fd,2) = 4 + 32 * server_num; + WFIFOSET(fd,4+32*server_num); + RFIFOSKIP(fd,2); + break; + + case 0x793a: // Request to password check + if (RFIFOREST(fd) < 50) + return 0; + WFIFOW(fd,0) = 0x793b; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + i = search_account_index(account_name); + if (i != -1) { + char pass[25]; + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + memcpy(pass, RFIFOP(fd,26), 24); + pass[24] = '\0'; + remove_control_chars((unsigned char *)pass); + if (strcmp(auth_dat[i].pass, pass) == 0) { + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Check of password OK (account: %s, password: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].pass, ip); + } else { + login_log("'ladmin': Failure of password check (account: %s, proposed pass: %s, ip: %s)" RETCODE, + auth_dat[i].userid, pass, ip); + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to check the password of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,50); + break; + + case 0x793c: // Request to modify sex + if (RFIFOREST(fd) < 27) + return 0; + WFIFOW(fd,0) = 0x793d; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + memcpy(WFIFOP(fd,6), account_name, 24); + { + char sex; + sex = RFIFOB(fd,26); + if (sex != 'F' && sex != 'M') { + if (sex > 31) + login_log("'ladmin': Attempt to give an invalid sex (account: %s, received sex: %c, ip: %s)" RETCODE, + account_name, sex, ip); + else + login_log("'ladmin': Attempt to give an invalid sex (account: %s, received sex: 'control char', ip: %s)" RETCODE, + account_name, ip); + } else { + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + if (auth_dat[i].sex != ((sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'))) { + unsigned char buf[16]; + WFIFOL(fd,2) = auth_dat[i].account_id; + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + auth_dat[i].sex = (sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'); + login_log("'ladmin': Modification of a sex (account: %s, new sex: %c, ip: %s)" RETCODE, + auth_dat[i].userid, sex, ip); + mmo_auth_sync(); + // send to all char-server the change + WBUFW(buf,0) = 0x2723; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = auth_dat[i].sex; + charif_sendallwos(-1, buf, 7); + } else { + login_log("'ladmin': Modification of a sex, but the sex is already the good sex (account: %s, sex: %c, ip: %s)" RETCODE, + auth_dat[i].userid, sex, ip); + } + } else { + login_log("'ladmin': Attempt to modify the sex of an unknown account (account: %s, received sex: %c, ip: %s)" RETCODE, + account_name, sex, ip); + } + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,27); + break; + + case 0x793e: // Request to modify GM level + if (RFIFOREST(fd) < 27) + return 0; + WFIFOW(fd,0) = 0x793f; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + memcpy(WFIFOP(fd,6), account_name, 24); + { + char new_gm_level; + new_gm_level = RFIFOB(fd,26); + if (new_gm_level < 0 || new_gm_level > 99) { + login_log("'ladmin': Attempt to give an invalid GM level (account: %s, received GM level: %d, ip: %s)" RETCODE, + account_name, (int)new_gm_level, ip); + } else { + i = search_account_index(account_name); + if (i != -1) { + int acc = auth_dat[i].account_id; + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + if (isGM(acc) != new_gm_level) { + // modification of the file + FILE *fp, *fp2; + int lock; + char line[512]; + int GM_account, GM_level; + int modify_flag; + char tmpstr[24]; + time_t raw_time; + if ((fp2 = lock_fopen(GM_account_filename, &lock)) != NULL) { + if ((fp = fopen(GM_account_filename, "r")) != NULL) { + time(&raw_time); + strftime(tmpstr, 23, date_format, localtime(&raw_time)); + modify_flag = 0; + // read/write GM file + while(fgets(line, sizeof(line)-1, fp)) { + while(line[0] != '\0' && (line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r')) + line[strlen(line)-1] = '\0'; + if ((line[0] == '/' && line[1] == '/') || line[0] == '\0') + fprintf(fp2, "%s" RETCODE, line); + else { + if (sscanf(line, "%d %d", &GM_account, &GM_level) != 2 && sscanf(line, "%d: %d", &GM_account, &GM_level) != 2) + fprintf(fp2, "%s" RETCODE, line); + else if (GM_account != acc) + fprintf(fp2, "%s" RETCODE, line); + else if (new_gm_level < 1) { + fprintf(fp2, "// %s: 'ladmin' GM level removed on account %d '%s' (previous level: %d)" RETCODE "//%d %d" RETCODE, tmpstr, acc, auth_dat[i].userid, GM_level, acc, new_gm_level); + modify_flag = 1; + } else { + fprintf(fp2, "// %s: 'ladmin' GM level on account %d '%s' (previous level: %d)" RETCODE "%d %d" RETCODE, tmpstr, acc, auth_dat[i].userid, GM_level, acc, new_gm_level); + modify_flag = 1; + } + } + } + if (modify_flag == 0) + fprintf(fp2, "// %s: 'ladmin' GM level on account %d '%s' (previous level: 0)" RETCODE "%d %d" RETCODE, tmpstr, acc, auth_dat[i].userid, acc, new_gm_level); + fclose(fp); + } else { + login_log("'ladmin': Attempt to modify of a GM level - impossible to read GM accounts file (account: %s (%d), received GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + if (lock_fclose(fp2, GM_account_filename, &lock) == 0) { + WFIFOL(fd,2) = acc; + login_log("'ladmin': Modification of a GM level (account: %s (%d), new GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + // read and send new GM informations + read_gm_account(); + send_GM_accounts(); + } else { + login_log("'ladmin': Attempt to modify of a GM level - impossible to write GM accounts file (account: %s (%d), received GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + } else { + login_log("'ladmin': Attempt to modify of a GM level - impossible to write GM accounts file (account: %s (%d), received GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + } else { + login_log("'ladmin': Attempt to modify of a GM level, but the GM level is already the good GM level (account: %s (%d), GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + } else { + login_log("'ladmin': Attempt to modify the GM level of an unknown account (account: %s, received GM level: %d, ip: %s)" RETCODE, + account_name, (int)new_gm_level, ip); + } + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,27); + break; + + case 0x7940: // Request to modify e-mail + if (RFIFOREST(fd) < 66) + return 0; + WFIFOW(fd,0) = 0x7941; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + memcpy(WFIFOP(fd,6), account_name, 24); + { + char email[40]; + memcpy(email, RFIFOP(fd,26), 40); + if (e_mail_check(email) == 0) { + login_log("'ladmin': Attempt to give an invalid e-mail (account: %s, ip: %s)" RETCODE, + account_name, ip); + } else { + remove_control_chars((unsigned char *)email); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + memcpy(auth_dat[i].email, email, 40); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Modification of an email (account: %s, new e-mail: %s, ip: %s)" RETCODE, + auth_dat[i].userid, email, ip); + mmo_auth_sync(); + } else { + login_log("'ladmin': Attempt to modify the e-mail of an unknown account (account: %s, received e-mail: %s, ip: %s)" RETCODE, + account_name, email, ip); + } + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,66); + break; + + case 0x7942: // Request to modify memo field + if ((int)RFIFOREST(fd) < 28 || (int)RFIFOREST(fd) < (28 + RFIFOW(fd,26))) + return 0; + WFIFOW(fd,0) = 0x7943; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + i = search_account_index(account_name); + if (i != -1) { + int size_of_memo = sizeof(auth_dat[i].memo); + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + memset(auth_dat[i].memo, '\0', size_of_memo); + if (RFIFOW(fd,26) == 0) { + strncpy(auth_dat[i].memo, "-", size_of_memo); + } else if (RFIFOW(fd,26) > size_of_memo - 1) { + memcpy(auth_dat[i].memo, RFIFOP(fd,28), size_of_memo - 1); + } else { + memcpy(auth_dat[i].memo, RFIFOP(fd,28), RFIFOW(fd,26)); + } + auth_dat[i].memo[size_of_memo - 1] = '\0'; + remove_control_chars((unsigned char *)auth_dat[i].memo); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Modification of a memo field (account: %s, new memo: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].memo, ip); + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to modify the memo field of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,28 + RFIFOW(fd,26)); + break; + + case 0x7944: // Request to found an account id + if (RFIFOREST(fd) < 26) + return 0; + WFIFOW(fd,0) = 0x7945; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Request (by the name) of an account id (account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].account_id, ip); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': ID request (by the name) of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,26); + break; + + case 0x7946: // Request to found an account name + if (RFIFOREST(fd) < 6) + return 0; + WFIFOW(fd,0) = 0x7947; + WFIFOL(fd,2) = RFIFOL(fd,2); + memset(WFIFOP(fd,6), '\0', 24); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == RFIFOL(fd,2)) { + strncpy((char*)WFIFOP(fd,6), auth_dat[i].userid, 24); + login_log("'ladmin': Request (by id) of an account name (account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, RFIFOL(fd,2), ip); + break; + } + } + if (i == auth_num) { + login_log("'ladmin': Name request (by id) of an unknown account (id: %d, ip: %s)" RETCODE, + RFIFOL(fd,2), ip); + strncpy((char*)WFIFOP(fd,6), "", 24); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,6); + break; + + case 0x7948: // Request to change the validity limit (timestamp) (absolute value) + if (RFIFOREST(fd) < 30) + return 0; + { + time_t timestamp; + char tmpstr[2048]; + WFIFOW(fd,0) = 0x7949; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + timestamp = (time_t)RFIFOL(fd,26); + strftime(tmpstr, 24, date_format, localtime(×tamp)); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + login_log("'ladmin': Change of a validity limit (account: %s, new validity: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, timestamp, (timestamp == 0 ? "unlimited" : tmpstr), ip); + auth_dat[i].connect_until_time = timestamp; + WFIFOL(fd,2) = auth_dat[i].account_id; + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to change the validity limit of an unknown account (account: %s, received validity: %d (%s), ip: %s)" RETCODE, + account_name, timestamp, (timestamp == 0 ? "unlimited" : tmpstr), ip); + } + WFIFOL(fd,30) = (unsigned int)timestamp; + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,30); + break; + + case 0x794a: // Request to change the final date of a banishment (timestamp) (absolute value) + if (RFIFOREST(fd) < 30) + return 0; + { + time_t timestamp; + char tmpstr[2048]; + WFIFOW(fd,0) = 0x794b; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + timestamp = (time_t)RFIFOL(fd,26); + if (timestamp <= time(NULL)) + timestamp = 0; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Change of the final date of a banishment (account: %s, new final date of banishment: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + if (auth_dat[i].ban_until_time != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = (unsigned int)timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].ban_until_time = timestamp; + mmo_auth_sync(); + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to change the final date of a banishment of an unknown account (account: %s, received final date of banishment: %d (%s), ip: %s)" RETCODE, + account_name, timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + } + WFIFOL(fd,30) = (unsigned int)timestamp; + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,30); + break; + + case 0x794c: // Request to change the final date of a banishment (timestamp) (relative change) + if (RFIFOREST(fd) < 38) + return 0; + { + time_t timestamp; + struct tm *tmtime; + char tmpstr[2048]; + WFIFOW(fd,0) = 0x794d; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + i = search_account_index(account_name); + if (i != -1) { + WFIFOL(fd,2) = auth_dat[i].account_id; + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + if (auth_dat[i].ban_until_time == 0 || auth_dat[i].ban_until_time < time(NULL)) + timestamp = time(NULL); + else + timestamp = auth_dat[i].ban_until_time; + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,26); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,28); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,30); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,32); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,34); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,36); + timestamp = mktime(tmtime); + if (timestamp != -1) { + if (timestamp <= time(NULL)) + timestamp = 0; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + login_log("'ladmin': Adjustment of a final date of a banishment (account: %s, (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + if (auth_dat[i].ban_until_time != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = (unsigned int)timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].ban_until_time = timestamp; + mmo_auth_sync(); + } + } else { + strftime(tmpstr, 24, date_format, localtime(&auth_dat[i].ban_until_time)); + login_log("'ladmin': Impossible to adjust the final date of a banishment (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> ???, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].ban_until_time, (auth_dat[i].ban_until_time == 0 ? "no banishment" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), ip); + } + WFIFOL(fd,30) = (unsigned long)auth_dat[i].ban_until_time; + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to adjust the final date of a banishment of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + WFIFOL(fd,30) = 0; + } + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,38); + break; + + case 0x794e: // Request to send a broadcast message + if (RFIFOREST(fd) < 8 || RFIFOREST(fd) < (8 + RFIFOL(fd,4))) + return 0; + WFIFOW(fd,0) = 0x794f; + WFIFOW(fd,2) = 0xFFFF; // WTF??? + if (RFIFOL(fd,4) < 1) { + login_log("'ladmin': Receiving a message for broadcast, but message is void (ip: %s)" RETCODE, + ip); + } else { + // at least 1 char-server + for(i = 0; i < MAX_SERVERS; i++) + if (server_fd[i] >= 0) + break; + if (i == MAX_SERVERS) { + login_log("'ladmin': Receiving a message for broadcast, but no char-server is online (ip: %s)" RETCODE, + ip); + } else { + unsigned char buf[32000]; + char message[32000]; + WFIFOW(fd,2) = 0; + memset(message, '\0', sizeof(message)); + memcpy(message, RFIFOP(fd,8), RFIFOL(fd,4)); + message[sizeof(message)-1] = '\0'; + remove_control_chars((unsigned char *)message); + if (RFIFOW(fd,2) == 0) + login_log("'ladmin': Receiving a message for broadcast (message (in yellow): %s, ip: %s)" RETCODE, + message, ip); + else + login_log("'ladmin': Receiving a message for broadcast (message (in blue): %s, ip: %s)" RETCODE, + message, ip); + // send same message to all char-servers (no answer) + memcpy(WBUFP(buf,0), RFIFOP(fd,0), 8 + RFIFOL(fd,4)); + WBUFW(buf,0) = 0x2726; + charif_sendallwos(-1, buf, 8 + RFIFOL(fd,4)); + } + } + WFIFOSET(fd,4); + RFIFOSKIP(fd,8 + RFIFOL(fd,4)); + break; + + case 0x7950: // Request to change the validity limite (timestamp) (relative change) + if (RFIFOREST(fd) < 38) + return 0; + { + time_t timestamp; + struct tm *tmtime; + char tmpstr[2048]; + char tmpstr2[2048]; + WFIFOW(fd,0) = 0x7951; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + i = search_account_index(account_name); + if (i != -1) { + WFIFOL(fd,2) = auth_dat[i].account_id; + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + timestamp = auth_dat[i].connect_until_time; + if (add_to_unlimited_account == 0 && timestamp == 0) { + login_log("'ladmin': Attempt to adjust the validity limit of an unlimited account (account: %s, ip: %s)" RETCODE, + auth_dat[i].userid, ip); + WFIFOL(fd,30) = 0; + } else { + if (timestamp == 0 || timestamp < time(NULL)) + timestamp = time(NULL); + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,26); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,28); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,30); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,32); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,34); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,36); + timestamp = mktime(tmtime); + if (timestamp != -1) { + strftime(tmpstr, 24, date_format, localtime(&auth_dat[i].connect_until_time)); + strftime(tmpstr2, 24, date_format, localtime(×tamp)); + login_log("'ladmin': Adjustment of a validity limit (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].connect_until_time, (auth_dat[i].connect_until_time == 0 ? "unlimited" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), timestamp, (timestamp == 0 ? "unlimited" : tmpstr2), ip); + auth_dat[i].connect_until_time = timestamp; + mmo_auth_sync(); + WFIFOL(fd,30) = (unsigned long)auth_dat[i].connect_until_time; + } else { + strftime(tmpstr, 24, date_format, localtime(&auth_dat[i].connect_until_time)); + login_log("'ladmin': Impossible to adjust a validity limit (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> ???, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].connect_until_time, (auth_dat[i].connect_until_time == 0 ? "unlimited" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), ip); + WFIFOL(fd,30) = 0; + } + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to adjust the validity limit of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + WFIFOL(fd,30) = 0; + } + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,38); + break; + + case 0x7952: // Request about informations of an account (by account name) + if (RFIFOREST(fd) < 26) + return 0; + WFIFOW(fd,0) = 0x7953; + WFIFOL(fd,2) = 0xFFFFFFFF; // WTF??? + account_name = (char*)RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars((unsigned char *)account_name); + i = search_account_index(account_name); + if (i != -1) { + WFIFOL(fd,2) = auth_dat[i].account_id; + WFIFOB(fd,6) = (unsigned char)isGM(auth_dat[i].account_id); + memcpy(WFIFOP(fd,7), auth_dat[i].userid, 24); + WFIFOB(fd,31) = auth_dat[i].sex; + WFIFOL(fd,32) = auth_dat[i].logincount; + WFIFOL(fd,36) = auth_dat[i].state; + memcpy(WFIFOP(fd,40), auth_dat[i].error_message, 20); + memcpy(WFIFOP(fd,60), auth_dat[i].lastlogin, 24); + memcpy(WFIFOP(fd,84), auth_dat[i].last_ip, 16); + memcpy(WFIFOP(fd,100), auth_dat[i].email, 40); + WFIFOL(fd,140) = (unsigned long)auth_dat[i].connect_until_time; + WFIFOL(fd,144) = (unsigned long)auth_dat[i].ban_until_time; + WFIFOW(fd,148) = strlen(auth_dat[i].memo); + if (auth_dat[i].memo[0]) { + memcpy(WFIFOP(fd,150), auth_dat[i].memo, strlen(auth_dat[i].memo)); + } + login_log("'ladmin': Sending information of an account (request by the name; account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].account_id, ip); + WFIFOSET(fd,150+strlen(auth_dat[i].memo)); + } else { + memcpy(WFIFOP(fd,7), account_name, 24); + WFIFOW(fd,148) = 0; + login_log("'ladmin': Attempt to obtain information (by the name) of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + WFIFOSET(fd,150); + } + RFIFOSKIP(fd,26); + break; + + case 0x7954: // Request about information of an account (by account id) + if (RFIFOREST(fd) < 6) + return 0; + WFIFOW(fd,0) = 0x7953; + WFIFOL(fd,2) = RFIFOL(fd,2); + memset(WFIFOP(fd,7), '\0', 24); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == RFIFOL(fd,2)) { + login_log("'ladmin': Sending information of an account (request by the id; account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, RFIFOL(fd,2), ip); + WFIFOB(fd,6) = (unsigned char)isGM(auth_dat[i].account_id); + memcpy(WFIFOP(fd,7), auth_dat[i].userid, 24); + WFIFOB(fd,31) = auth_dat[i].sex; + WFIFOL(fd,32) = auth_dat[i].logincount; + WFIFOL(fd,36) = auth_dat[i].state; + memcpy(WFIFOP(fd,40), auth_dat[i].error_message, 20); + memcpy(WFIFOP(fd,60), auth_dat[i].lastlogin, 24); + memcpy(WFIFOP(fd,84), auth_dat[i].last_ip, 16); + memcpy(WFIFOP(fd,100), auth_dat[i].email, 40); + WFIFOL(fd,140) = (unsigned long)auth_dat[i].connect_until_time; + WFIFOL(fd,144) = (unsigned long)auth_dat[i].ban_until_time; + WFIFOW(fd,148) = strlen(auth_dat[i].memo); + if (auth_dat[i].memo[0]) { + memcpy(WFIFOP(fd,150), auth_dat[i].memo, strlen(auth_dat[i].memo)); + } + WFIFOSET(fd,150+strlen(auth_dat[i].memo)); + break; + } + } + if (i == auth_num) { + login_log("'ladmin': Attempt to obtain information (by the id) of an unknown account (id: %d, ip: %s)" RETCODE, + RFIFOL(fd,2), ip); + strncpy((char*)WFIFOP(fd,7), "", 24); + WFIFOW(fd,148) = 0; + WFIFOSET(fd,150); + } + RFIFOSKIP(fd,6); + break; + + case 0x7955: // Request to reload GM file (no answer) + login_log("'ladmin': Request to re-load GM configuration file (ip: %s)." RETCODE, ip); + read_gm_account(); + // send GM accounts to all char-servers + send_GM_accounts(); + RFIFOSKIP(fd,2); + break; + + default: + { + FILE *logfp; + char tmpstr[24]; + time_t raw_time; + logfp = fopen(login_log_unknown_packets_filename, "a"); + if (logfp) { + time(&raw_time); + strftime(tmpstr, 23, date_format, localtime(&raw_time)); + fprintf(logfp, "%s: receiving of an unknown packet -> disconnection" RETCODE, tmpstr); + fprintf(logfp, "parse_admin: connection #%d (ip: %s), packet: 0x%x (with being read: %d)." RETCODE, fd, ip, RFIFOW(fd,0), RFIFOREST(fd)); + fprintf(logfp, "Detail (in hex):" RETCODE); + fprintf(logfp, "---- 00-01-02-03-04-05-06-07 08-09-0A-0B-0C-0D-0E-0F" RETCODE); + memset(tmpstr, '\0', sizeof(tmpstr)); + for(i = 0; i < RFIFOREST(fd); i++) { + if ((i & 15) == 0) + fprintf(logfp, "%04X ",i); + fprintf(logfp, "%02x ", RFIFOB(fd,i)); + if (RFIFOB(fd,i) > 0x1f) + tmpstr[i % 16] = RFIFOB(fd,i); + else + tmpstr[i % 16] = '.'; + if ((i - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + else if ((i + 1) % 16 == 0) { + fprintf(logfp, " %s" RETCODE, tmpstr); + memset(tmpstr, '\0', sizeof(tmpstr)); + } + } + if (i % 16 != 0) { + for(j = i; j % 16 != 0; j++) { + fprintf(logfp, " "); + if ((j - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + } + fprintf(logfp, " %s" RETCODE, tmpstr); + } + fprintf(logfp, RETCODE); + fclose(logfp); + } + } + login_log("'ladmin': End of connection, unknown packet (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + ShowWarning("Remote administration has been disconnected (unknown packet).\n"); + return 0; + } + //WFIFOW(fd,0) = 0x791f; + //WFIFOSET(fd,2); + } + RFIFOSKIP(fd,RFIFOREST(fd)); + return 0; +} + +//-------------------------------------------- +// Test to know if an IP come from LAN or WAN. +//-------------------------------------------- +int lan_ip_check(unsigned char *p) { + int i; + int lancheck = 1; + +// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +// p[0], p[1], p[2], p[3], +// subneti[0], subneti[1], subneti[2], subneti[3], +// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + for(i = 0; i < 4; i++) { + if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { + lancheck = 0; + break; + } + } + ShowMessage("LAN test (result): "CL_CYAN"%s source"CL_RESET".\n", (lancheck) ? "LAN" : "WAN"); + return lancheck; +} + +//---------------------------------------------------------------------------------------- +// Default packet parsing (normal players or administation/char-server connexion requests) +//---------------------------------------------------------------------------------------- +int parse_login(int fd) { + struct mmo_account account; + int result, j; + unsigned int i; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + RFIFOHEAD(fd); + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + memset(&account, 0, sizeof(account)); + + if (session[fd]->eof) { + do_close(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { + if (display_parse_login == 1) { + if (RFIFOW(fd,0) == 0x64 || RFIFOW(fd,0) == 0x01dd) { + if ((int)RFIFOREST(fd) >= ((RFIFOW(fd,0) == 0x64) ? 55 : 47)) + ShowDebug("parse_login: connection #%d, packet: 0x%x (with being read: %d), account: %s.\n", fd, RFIFOW(fd,0), RFIFOREST(fd), RFIFOP(fd,6)); + } else if (RFIFOW(fd,0) == 0x2710) { + if (RFIFOREST(fd) >= 86) + ShowDebug("parse_login: connection #%d, packet: 0x%x (with being read: %d), server: %s.\n", fd, RFIFOW(fd,0), RFIFOREST(fd), RFIFOP(fd,60)); + } else + ShowDebug("parse_login: connection #%d, packet: 0x%x (with being read: %d).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + } + + switch(RFIFOW(fd,0)) { + case 0x200: // New alive packet: structure: 0x200 .24B. used to verify if client is always alive. + if (RFIFOREST(fd) < 26) + return 0; + RFIFOSKIP(fd,26); + break; + + case 0x204: // New alive packet: structure: 0x204 .16B. (new ragexe from 22 june 2004) + if (RFIFOREST(fd) < 18) + return 0; + RFIFOSKIP(fd,18); + break; + + case 0x64: // Ask connection of a client + case 0x01dd: // Ask connection of a client (encryption mode) + if ((int)RFIFOREST(fd) < ((RFIFOW(fd,0) == 0x64) ? 55 : 47)) + return 0; + + account.version = RFIFOL(fd, 2); //for exe version check [Sirius] + account.userid = (char*)RFIFOP(fd,6); + account.userid[23] = '\0'; + remove_control_chars((unsigned char *)account.userid); + if (RFIFOW(fd,0) == 0x64) { + login_log("Request for connection (non encryption mode) of %s (ip: %s)." RETCODE, account.userid, ip); + memcpy(account.passwd, RFIFOP(fd,30), NAME_LENGTH); + account.passwd[23] = '\0'; + remove_control_chars((unsigned char *)account.passwd); + } else { + login_log("Request for connection (encryption mode) of %s (ip: %s)." RETCODE, account.userid, ip); + // If remove control characters from received password encrypted by md5, + // there would be a wrong result and failed to authentication. [End_of_exam] + memcpy(account.passwd, RFIFOP(fd,30), 16); + account.passwd[16] = '\0'; + } +#ifdef PASSWORDENC + account.passwdenc = (RFIFOW(fd,0) == 0x64) ? 0 : PASSWORDENC; +#else + account.passwdenc = 0; +#endif + + if (!check_ip(session[fd]->client_addr.sin_addr.s_addr)) { + login_log("Connection refused: IP isn't authorised (deny/allow, ip: %s)." RETCODE, ip); + WFIFOHEAD(fd, 23); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 3; // 3 = Rejected from Server + WFIFOSET(fd,23); + RFIFOSKIP(fd,(RFIFOW(fd,0) == 0x64) ? 55 : 47); + break; + } + + result = mmo_auth(&account, fd); + if (result == -1) { + int gm_level = isGM(account.account_id); + if (min_level_to_connect > gm_level) { + login_log("Connection refused: the minimum GM level for connection is %d (account: %s, GM level: %d, ip: %s)." RETCODE, + min_level_to_connect, account.userid, gm_level, ip); + WFIFOHEAD(fd, 3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } else { + if (gm_level) + ShowInfo("Connection of the GM (level:%d) account '%s' accepted.\n", gm_level, account.userid); + else + ShowInfo("Connection of the account '%s' accepted.\n", account.userid); + server_num = 0; + WFIFOHEAD(fd, 47+32*MAX_SERVERS); + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) { + if (lan_ip_check(p)) + WFIFOL(fd,47+server_num*32) = inet_addr(lan_char_ip); + else + WFIFOL(fd,47+server_num*32) = server[i].ip; + WFIFOW(fd,47+server_num*32+4) = server[i].port; + memcpy(WFIFOP(fd,47+server_num*32+6), server[i].name, 20); + WFIFOW(fd,47+server_num*32+26) = server[i].users; + WFIFOW(fd,47+server_num*32+28) = server[i].maintenance; + WFIFOW(fd,47+server_num*32+30) = server[i].new_; + server_num++; + } + } + // if at least 1 char-server + if (server_num > 0) { + WFIFOW(fd,0) = 0x69; + WFIFOW(fd,2) = 47+32*server_num; + WFIFOL(fd,4) = account.login_id1; + WFIFOL(fd,8) = account.account_id; + WFIFOL(fd,12) = account.login_id2; + WFIFOL(fd,16) = 0; // in old version, that was for ip (not more used) + memcpy(WFIFOP(fd,20), account.lastlogin, 24); // in old version, that was for name (not more used) + WFIFOB(fd,46) = account.sex; + WFIFOSET(fd,47+32*server_num); + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + auth_fifo[auth_fifo_pos].account_id = account.account_id; + auth_fifo[auth_fifo_pos].login_id1 = account.login_id1; + auth_fifo[auth_fifo_pos].login_id2 = account.login_id2; + auth_fifo[auth_fifo_pos].sex = account.sex; + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + auth_fifo_pos++; + // if no char-server, don't send void list of servers, just disconnect the player with proper message + } else { + login_log("Connection refused: there is no char-server online (account: %s, ip: %s)." RETCODE, + account.userid, ip); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } + } + } else { + WFIFOHEAD(fd, 23); + memset(WFIFOP(fd,0), '\0', 23); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = result; + if (result == 6) { // 6 = Your are Prohibited to log in until %s + i = search_account_index(account.userid); + if (i != -1) { + if (auth_dat[i].ban_until_time != 0) { // if account is banned, we send ban timestamp + char tmpstr[256]; + strftime(tmpstr, 20, date_format, localtime(&auth_dat[i].ban_until_time)); + tmpstr[19] = '\0'; + memcpy(WFIFOP(fd,3), tmpstr, 20); + } else { // we send error message + memcpy(WFIFOP(fd,3), auth_dat[i].error_message, 20); + } + } + } + WFIFOSET(fd,23); + } + RFIFOSKIP(fd,(RFIFOW(fd,0) == 0x64) ? 55 : 47); + break; + + case 0x01db: // Sending request of the coding key + case 0x791a: // Sending request of the coding key (administration packet) + { + struct login_session_data *ld; + if (session[fd]->session_data) { + ShowWarning("login: abnormal request of MD5 key (already opened session).\n"); + session[fd]->eof = 1; + return 0; + } + ld = (struct login_session_data*)aCalloc(1, sizeof(struct login_session_data)); + session[fd]->session_data = ld; + if (!ld) { + ShowFatalError("login: Request for md5 key: memory allocation failure (malloc)!\n"); + session[fd]->eof = 1; + return 0; + } + if (RFIFOW(fd,0) == 0x01db) + login_log("Sending request of the coding key (ip: %s)" RETCODE, ip); + else + login_log("'ladmin': Sending request of the coding key (ip: %s)" RETCODE, ip); + // Creation of the coding key + memset(ld->md5key, '\0', sizeof(ld->md5key)); + ld->md5keylen = rand() % 4 + 12; + for(i = 0; i < ld->md5keylen; i++) + ld->md5key[i] = rand() % 255 + 1; + RFIFOSKIP(fd,2); + WFIFOHEAD(fd, 4 + ld->md5keylen); + WFIFOW(fd,0) = 0x01dc; + WFIFOW(fd,2) = 4 + ld->md5keylen; + memcpy(WFIFOP(fd,4), ld->md5key, ld->md5keylen); + WFIFOSET(fd,WFIFOW(fd,2)); + } + break; + + case 0x2710: // Connection request of a char-server + if (RFIFOREST(fd) < 86) + return 0; + { + int GM_value, len; + char* server_name; + account.userid = (char*)RFIFOP(fd,2); + account.userid[23] = '\0'; + remove_control_chars((unsigned char *)account.userid); + memcpy(account.passwd, RFIFOP(fd,26), 24); + account.passwd[23] = '\0'; + remove_control_chars((unsigned char *)account.passwd); + account.passwdenc = 0; + server_name = (char*)RFIFOP(fd,60); + server_name[19] = '\0'; + remove_control_chars((unsigned char *)server_name); + login_log("Connection request of the char-server '%s' @ %d.%d.%d.%d:%d (ip: %s)" RETCODE, + server_name, RFIFOB(fd,54), RFIFOB(fd,55), RFIFOB(fd,56), RFIFOB(fd,57), RFIFOW(fd,58), ip); + result = mmo_auth(&account, fd); + if (result == -1 && account.sex == 2 && account.account_id < MAX_SERVERS && server_fd[account.account_id] == -1) { + login_log("Connection of the char-server '%s' accepted (account: %s, pass: %s, ip: %s)" RETCODE, + server_name, account.userid, account.passwd, ip); + ShowStatus("Connection of the char-server '%s' accepted.\n", server_name); + memset(&server[account.account_id], 0, sizeof(struct mmo_char_server)); + server[account.account_id].ip = RFIFOL(fd,54); + server[account.account_id].port = RFIFOW(fd,58); + memcpy(server[account.account_id].name, server_name, 20); + server[account.account_id].users = 0; + server[account.account_id].maintenance = RFIFOW(fd,82); + server[account.account_id].new_ = RFIFOW(fd,84); + server_fd[account.account_id] = fd; + WFIFOHEAD(fd, 3); + WFIFOW(fd,0) = 0x2711; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + session[fd]->func_parse = parse_fromchar; + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + // send GM account to char-server + len = 4; + WFIFOW(fd,0) = 0x2732; + for(i = 0; i < auth_num; i++) + // send only existing accounts. We can not create a GM account when server is online. + if ((GM_value = isGM(auth_dat[i].account_id)) > 0) { + WFIFOL(fd,len) = auth_dat[i].account_id; + WFIFOB(fd,len+4) = (unsigned char)GM_value; + len += 5; + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + } else { + if (server_fd[account.account_id] != -1) { + ShowNotice("Connection of the char-server '%s' REFUSED - already connected (account: %ld-%s, pass: %s, ip: %s)\n", + server_name, account.account_id, account.userid, account.passwd, ip); + login_log("Connexion of the char-server '%s' REFUSED - already connected (account: %ld-%s, pass: %s, ip: %s)" RETCODE, + server_name, account.account_id, account.userid, account.passwd, ip); + } else { + ShowNotice("Connection of the char-server '%s' REFUSED (account: %s, pass: %s, ip: %s).\n", server_name, account.userid, account.passwd, ip); + login_log("Connexion of the char-server '%s' REFUSED (account: %s, pass: %s, ip: %s)" RETCODE, + server_name, account.userid, account.passwd, ip); + } + WFIFOHEAD(fd, 3); + WFIFOW(fd,0) = 0x2711; + WFIFOB(fd,2) = 3; + WFIFOSET(fd,3); + } + } + RFIFOSKIP(fd,86); + return 0; + + case 0x7530: // Request of the server version + login_log("Sending of the server version (ip: %s)" RETCODE, ip); + WFIFOHEAD(fd, 10); + WFIFOW(fd,0) = 0x7531; + WFIFOB(fd,2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd,3) = ATHENA_MINOR_VERSION; + WFIFOB(fd,4) = ATHENA_REVISION; + WFIFOB(fd,5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd,6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7) = ATHENA_SERVER_LOGIN; + WFIFOW(fd,8) = ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + break; + + case 0x7532: // Request to end connection + login_log("End of connection (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + return 0; + + case 0x7918: // Request for administation login + if ((int)RFIFOREST(fd) < 4 || (int)RFIFOREST(fd) < ((RFIFOW(fd,2) == 0) ? 28 : 20)) + return 0; + WFIFOW(fd,0) = 0x7919; + WFIFOB(fd,2) = 1; + if (!check_ladminip(session[fd]->client_addr.sin_addr.s_addr)) { + login_log("'ladmin'-login: Connection in administration mode refused: IP isn't authorised (ladmin_allow, ip: %s)." RETCODE, ip); + } else { + struct login_session_data *ld = (struct login_session_data*)session[fd]->session_data; + if (RFIFOW(fd,2) == 0) { // non encrypted password + char* password=""; + memcpy(password, RFIFOP(fd,4), 24); + password[24] = '\0'; + remove_control_chars((unsigned char *)password); + // If remote administration is enabled and password sent by client matches password read from login server configuration file + if ((admin_state == 1) && (strcmp(password, admin_pass) == 0)) { + login_log("'ladmin'-login: Connection in administration mode accepted (non encrypted password: %s, ip: %s)" RETCODE, password, ip); + ShowNotice("Connection of a remote administration accepted (non encrypted password).\n"); + WFIFOB(fd,2) = 0; + session[fd]->func_parse = parse_admin; + } else if (admin_state != 1) + login_log("'ladmin'-login: Connection in administration mode REFUSED - remote administration is disabled (non encrypted password: %s, ip: %s)" RETCODE, password, ip); + else + login_log("'ladmin'-login: Connection in administration mode REFUSED - invalid password (non encrypted password: %s, ip: %s)" RETCODE, password, ip); + } else { // encrypted password + if (!ld) + ShowError("'ladmin'-login: error! MD5 key not created/requested for an administration login.\n"); + else { + char md5str[64] = "", md5bin[32]; + if (RFIFOW(fd,2) == 1) { + sprintf(md5str, "%s%s", ld->md5key, admin_pass); // 20 24 + } else if (RFIFOW(fd,2) == 2) { + sprintf(md5str, "%s%s", admin_pass, ld->md5key); // 24 20 + } + MD5_String2binary(md5str, md5bin); + // If remote administration is enabled and password hash sent by client matches hash of password read from login server configuration file + if ((admin_state == 1) && (memcmp(md5bin, RFIFOP(fd,4), 16) == 0)) { + login_log("'ladmin'-login: Connection in administration mode accepted (encrypted password, ip: %s)" RETCODE, ip); + ShowNotice("Connection of a remote administration accepted (encrypted password).\n"); + WFIFOB(fd,2) = 0; + session[fd]->func_parse = parse_admin; + } else if (admin_state != 1) + login_log("'ladmin'-login: Connection in administration mode REFUSED - remote administration is disabled (encrypted password, ip: %s)" RETCODE, ip); + else + login_log("'ladmin'-login: Connection in administration mode REFUSED - invalid password (encrypted password, ip: %s)" RETCODE, ip); + } + } + } + WFIFOSET(fd,3); + RFIFOSKIP(fd, (RFIFOW(fd,2) == 0) ? 28 : 20); + break; + + default: + if (save_unknown_packets) { + FILE *logfp; + char tmpstr[24]; + time_t raw_time; + logfp = fopen(login_log_unknown_packets_filename, "a"); + if (logfp) { + time(&raw_time); + strftime(tmpstr, 23, date_format, localtime(&raw_time)); + fprintf(logfp, "%s: receiving of an unknown packet -> disconnection" RETCODE, tmpstr); + fprintf(logfp, "parse_login: connection #%d (ip: %s), packet: 0x%x (with being read: %d)." RETCODE, fd, ip, RFIFOW(fd,0), RFIFOREST(fd)); + fprintf(logfp, "Detail (in hex):" RETCODE); + fprintf(logfp, "---- 00-01-02-03-04-05-06-07 08-09-0A-0B-0C-0D-0E-0F" RETCODE); + memset(tmpstr, '\0', sizeof(tmpstr)); + for(i = 0; i < RFIFOREST(fd); i++) { + if ((i & 15) == 0) + fprintf(logfp, "%04X ",i); + fprintf(logfp, "%02x ", RFIFOB(fd,i)); + if (RFIFOB(fd,i) > 0x1f) + tmpstr[i % 16] = RFIFOB(fd,i); + else + tmpstr[i % 16] = '.'; + if ((i - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + else if ((i + 1) % 16 == 0) { + fprintf(logfp, " %s" RETCODE, tmpstr); + memset(tmpstr, '\0', sizeof(tmpstr)); + } + } + if (i % 16 != 0) { + for(j = i; j % 16 != 0; j++) { + fprintf(logfp, " "); + if ((j - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + } + fprintf(logfp, " %s" RETCODE, tmpstr); + } + fprintf(logfp, RETCODE); + fclose(logfp); + } + } + login_log("End of connection, unknown packet (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + return 0; + } + } + RFIFOSKIP(fd,RFIFOREST(fd)); + return 0; +} + +//----------------------- +// Console Command Parser [Wizputer] +//----------------------- +int parse_console(char *buf) { + char command[256]; + + memset(command,0,sizeof(command)); + + sscanf(buf, "%[^\n]", command); + + login_log("Console command :%s" RETCODE, command); + + if(strcmpi("shutdown", command) == 0 || + strcmpi("exit", command) == 0 || + strcmpi("quit", command) == 0 || + strcmpi("end", command) == 0) + runflag = 0; + else if(strcmpi("alive", command) == 0 || + strcmpi("status", command) == 0) + ShowInfo(CL_CYAN"Console: "CL_BOLD"I'm Alive."CL_RESET"\n"); + else if(strcmpi("help", command) == 0) { + printf(CL_BOLD"Help of commands:"CL_RESET"\n"); + printf(" To shutdown the server:\n"); + printf(" 'shutdown|exit|qui|end'\n"); + printf(" To know if server is alive:\n"); + printf(" 'alive|status'\n"); + } + + return 0; +} + +static int online_data_cleanup_sub(DBKey key, void *data, va_list ap) +{ + struct online_login_data *character= (struct online_login_data*)data; + if (character->char_server == -2) //Unknown server.. set them offline + remove_online_user(character->account_id); + else if (character->char_server < 0) + //Free data from players that have not been online for a while. + db_remove(online_db, key); + return 0; +} + +static int online_data_cleanup(int tid, unsigned int tick, int id, int data) +{ + online_db->foreach(online_db, online_data_cleanup_sub); + return 0; +} +//------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, fran軋is, deutsch, espaol +//------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +//---------------------------------- +// Reading Lan Support configuration +//---------------------------------- +int login_lan_config_read(const char *lancfgName) { + int j; + struct hostent * h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + // set default configuration + strncpy(lan_char_ip, "127.0.0.1", sizeof(lan_char_ip)); + subneti[0] = 127; + subneti[1] = 0; + subneti[2] = 0; + subneti[3] = 1; + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + + fp = fopen(lancfgName, "r"); + + if (fp == NULL) { + ShowWarning("LAN Support configuration file is not found: %s\n", lancfgName); + return 1; + } + + ShowInfo("Reading the configuration file %s...\n", lancfgName); + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + memset(w2, 0, sizeof(w2)); + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars((unsigned char *)w1); + remove_control_chars((unsigned char *)w2); + if (strcmpi(w1, "lan_char_ip") == 0) { // Read Char-Server Lan IP Address + memset(lan_char_ip, 0, sizeof(lan_char_ip)); + h = gethostbyname(w2); + if (h != NULL) { + sprintf(lan_char_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + strncpy(lan_char_ip, w2, sizeof(lan_char_ip)); + lan_char_ip[sizeof(lan_char_ip)-1] = '\0'; + } + ShowStatus("LAN IP of char-server: %s.\n", lan_char_ip); + } else if (strcmpi(w1, "subnet") == 0) { // Read Subnetwork + for(j = 0; j < 4; j++) + subneti[j] = 0; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subneti[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subneti[0], &subneti[1], &subneti[2], &subneti[3]); + } + ShowStatus("Sub-network of the char-server: %d.%d.%d.%d.\n", subneti[0], subneti[1], subneti[2], subneti[3]); + } else if (strcmpi(w1, "subnetmask") == 0) { // Read Subnetwork Mask + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subnetmaski[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subnetmaski[0], &subnetmaski[1], &subnetmaski[2], &subnetmaski[3]); + } + ShowStatus("Sub-network mask of the char-server: %d.%d.%d.%d.\n", subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + } + } + fclose(fp); + + // log the LAN configuration + login_log("The LAN configuration of the server is set:" RETCODE); + login_log("- with LAN IP of char-server: %s." RETCODE, lan_char_ip); + login_log("- with the sub-network of the char-server: %d.%d.%d.%d/%d.%d.%d.%d." RETCODE, + subneti[0], subneti[1], subneti[2], subneti[3], subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + + // sub-network check of the char-server + { + unsigned int a0, a1, a2, a3; + unsigned char p[4]; + sscanf(lan_char_ip, "%d.%d.%d.%d", &a0, &a1, &a2, &a3); + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + ShowInfo("LAN test of LAN IP of the char-server: "); + if (lan_ip_check(p) == 0) { + ShowError(CL_RED" LAN IP of the char-server doesn't belong to the specified Sub-network"CL_RESET"\n"); + login_log("***ERROR: LAN IP of the char-server doesn't belong to the specified Sub-network." RETCODE); + } + } + + ShowInfo("Finished reading %s.\n", lancfgName); + + return 0; +} + +//----------------------------------- +// Reading general configuration file +//----------------------------------- +int login_config_read(const char *cfgName) { + struct hostent *h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + if ((fp = fopen(cfgName, "r")) == NULL) { + ShowError("Configuration file (%s) not found.\n", cfgName); + return 1; + } + + ShowInfo("Reading configuration file %s...\n", cfgName); + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + memset(w2, 0, sizeof(w2)); + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + remove_control_chars((unsigned char *)w1); + remove_control_chars((unsigned char *)w2); + + if(strcmpi(w1,"timestamp_format") == 0) { + strncpy(timestamp_format, w2, 20); + } else if(strcmpi(w1,"console_silent")==0){ + msg_silent = 0; //To always allow the next line to show up. + ShowInfo("Console Silent Setting: %d\n", atoi(w2)); + msg_silent = atoi(w2); + } else if (strcmpi(w1, "admin_state") == 0) { + admin_state = config_switch(w2); + } else if (strcmpi(w1, "admin_pass") == 0) { + memset(admin_pass, 0, sizeof(admin_pass)); + strncpy(admin_pass, w2, sizeof(admin_pass)); + admin_pass[sizeof(admin_pass)-1] = '\0'; + } else if (strcmpi(w1, "ladminallowip") == 0) { + if (strcmpi(w2, "clear") == 0) { + if (access_ladmin_allow) + aFree(access_ladmin_allow); + access_ladmin_allow = NULL; + access_ladmin_allownum = 0; + } else { + if (strcmpi(w2, "all") == 0) { + // reset all previous values + if (access_ladmin_allow) + aFree(access_ladmin_allow); + // set to all + access_ladmin_allow = (char*)aCalloc(ACO_STRSIZE, sizeof(char)); + access_ladmin_allownum = 1; + access_ladmin_allow[0] = '\0'; + } else if (w2[0] && !(access_ladmin_allownum == 1 && access_ladmin_allow[0] == '\0')) { // don't add IP if already 'all' + if (access_ladmin_allow) + access_ladmin_allow = (char*)aRealloc(access_ladmin_allow, (access_ladmin_allownum+1) * ACO_STRSIZE); + else + access_ladmin_allow = (char*)aCalloc(ACO_STRSIZE, sizeof(char)); + strncpy(access_ladmin_allow + (access_ladmin_allownum++) * ACO_STRSIZE, w2, ACO_STRSIZE); + access_ladmin_allow[access_ladmin_allownum * ACO_STRSIZE - 1] = '\0'; + } + } + } else if (strcmpi(w1, "gm_pass") == 0) { + memset(gm_pass, 0, sizeof(gm_pass)); + strncpy(gm_pass, w2, sizeof(gm_pass)); + gm_pass[sizeof(gm_pass)-1] = '\0'; + } else if (strcmpi(w1, "level_new_gm") == 0) { + level_new_gm = atoi(w2); + } else if (strcmpi(w1, "new_account") == 0) { + new_account_flag = config_switch(w2); + } else if (strcmpi(w1, "bind_ip") == 0) { + bind_ip_set_ = 1; + h = gethostbyname (w2); + if (h != NULL) { + ShowStatus("Login server binding IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(bind_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(bind_ip_str,w2,16); + } else if (strcmpi(w1, "login_port") == 0) { + login_port = atoi(w2); + } else if (strcmpi(w1, "account_filename") == 0) { + memset(account_filename, 0, sizeof(account_filename)); + strncpy(account_filename, w2, sizeof(account_filename)); + account_filename[sizeof(account_filename)-1] = '\0'; + } else if (strcmpi(w1, "gm_account_filename") == 0) { + memset(GM_account_filename, 0, sizeof(GM_account_filename)); + strncpy(GM_account_filename, w2, sizeof(GM_account_filename)); + GM_account_filename[sizeof(GM_account_filename)-1] = '\0'; + } else if (strcmpi(w1, "gm_account_filename_check_timer") == 0) { + gm_account_filename_check_timer = atoi(w2); + } else if (strcmpi(w1, "use_MD5_passwords") == 0) { + use_md5_passwds = config_switch(w2); + } else if (strcmpi(w1, "login_log_filename") == 0) { + memset(login_log_filename, 0, sizeof(login_log_filename)); + strncpy(login_log_filename, w2, sizeof(login_log_filename)); + login_log_filename[sizeof(login_log_filename)-1] = '\0'; + } else if (strcmpi(w1, "log_login") == 0) { + log_login = atoi(w2); + } else if (strcmpi(w1, "login_log_unknown_packets_filename") == 0) { + memset(login_log_unknown_packets_filename, 0, sizeof(login_log_unknown_packets_filename)); + strncpy(login_log_unknown_packets_filename, w2, sizeof(login_log_unknown_packets_filename)); + login_log_unknown_packets_filename[sizeof(login_log_unknown_packets_filename)-1] = '\0'; + } else if (strcmpi(w1, "save_unknown_packets") == 0) { + save_unknown_packets = config_switch(w2); + } else if (strcmpi(w1, "display_parse_login") == 0) { + display_parse_login = config_switch(w2); // 0: no, 1: yes + } else if (strcmpi(w1, "display_parse_admin") == 0) { + display_parse_admin = config_switch(w2); // 0: no, 1: yes + } else if (strcmpi(w1, "display_parse_fromchar") == 0) { + display_parse_fromchar = config_switch(w2); // 0: no, 1: yes (without packet 0x2714), 2: all packets + } else if (strcmpi(w1, "date_format") == 0) { // note: never have more than 19 char for the date! + memset(date_format, 0, sizeof(date_format)); + switch (atoi(w2)) { + case 0: + strcpy(date_format, "%d-%m-%Y %H:%M:%S"); // 31-12-2004 23:59:59 + break; + case 1: + strcpy(date_format, "%m-%d-%Y %H:%M:%S"); // 12-31-2004 23:59:59 + break; + case 2: + strcpy(date_format, "%Y-%d-%m %H:%M:%S"); // 2004-31-12 23:59:59 + break; + case 3: + strcpy(date_format, "%Y-%m-%d %H:%M:%S"); // 2004-12-31 23:59:59 + break; + } + } else if (strcmpi(w1, "min_level_to_connect") == 0) { + min_level_to_connect = atoi(w2); + } else if (strcmpi(w1, "add_to_unlimited_account") == 0) { + add_to_unlimited_account = config_switch(w2); + } else if (strcmpi(w1, "start_limited_time") == 0) { + start_limited_time = atoi(w2); + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "order") == 0) { + access_order = atoi(w2); + if (strcmpi(w2, "deny,allow") == 0 || + strcmpi(w2, "deny, allow") == 0) access_order = ACO_DENY_ALLOW; + if (strcmpi(w2, "allow,deny") == 0 || + strcmpi(w2, "allow, deny") == 0) access_order = ACO_ALLOW_DENY; + if (strcmpi(w2, "mutual-failture") == 0 || + strcmpi(w2, "mutual-failure") == 0) access_order = ACO_MUTUAL_FAILTURE; + } else if (strcmpi(w1, "allow") == 0) { + if (strcmpi(w2, "clear") == 0) { + if (access_allow) + aFree(access_allow); + access_allow = NULL; + access_allownum = 0; + } else { + if (strcmpi(w2, "all") == 0) { + // reset all previous values + if (access_allow) + aFree(access_allow); + // set to all + access_allow = (char*)aCalloc(ACO_STRSIZE, sizeof(char)); + access_allownum = 1; + access_allow[0] = '\0'; + } else if (w2[0] && !(access_allownum == 1 && access_allow[0] == '\0')) { // don't add IP if already 'all' + if (access_allow) + access_allow = (char*)aRealloc(access_allow, (access_allownum+1) * ACO_STRSIZE); + else + access_allow = (char*)aCalloc(ACO_STRSIZE, sizeof(char)); + strncpy(access_allow + (access_allownum++) * ACO_STRSIZE, w2, ACO_STRSIZE); + access_allow[access_allownum * ACO_STRSIZE - 1] = '\0'; + } + } + } else if (strcmpi(w1, "deny") == 0) { + if (strcmpi(w2, "clear") == 0) { + if (access_deny) + aFree(access_deny); + access_deny = NULL; + access_denynum = 0; + } else { + if (strcmpi(w2, "all") == 0) { + // reset all previous values + if (access_deny) + aFree(access_deny); + // set to all + access_deny = (char*)aCalloc(ACO_STRSIZE, sizeof(char)); + access_denynum = 1; + access_deny[0] = '\0'; + } else if (w2[0] && !(access_denynum == 1 && access_deny[0] == '\0')) { // don't add IP if already 'all' + if (access_deny) + access_deny = (char*)aRealloc(access_deny, (access_denynum+1) * ACO_STRSIZE); + else + access_deny = (char*)aCalloc(ACO_STRSIZE, sizeof(char)); + strncpy(access_deny + (access_denynum++) * ACO_STRSIZE, w2, ACO_STRSIZE); + access_deny[access_denynum * ACO_STRSIZE - 1] = '\0'; + } + } + // dynamic password error ban + } else if (strcmpi(w1, "dynamic_pass_failure_ban") == 0) { + dynamic_pass_failure_ban = config_switch(w2); + } else if (strcmpi(w1, "dynamic_pass_failure_ban_time") == 0) { + dynamic_pass_failure_ban_time = atoi(w2); + } else if (strcmpi(w1, "dynamic_pass_failure_ban_how_many") == 0) { + dynamic_pass_failure_ban_how_many = atoi(w2); + } else if (strcmpi(w1, "dynamic_pass_failure_ban_how_long") == 0) { + dynamic_pass_failure_ban_how_long = atoi(w2); + } else if(strcmpi(w1, "check_client_version") == 0){ //Added by Sirius for client version check + if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 ){ + check_client_version = 1; + } + if(strcmpi(w2,"off") == 0 || strcmpi(w2,"no") == 0 ){ + check_client_version = 0; + } + }else if(strcmpi(w1, "client_version_to_connect") == 0){ //Added by Sirius for client version check + client_version_to_connect = atoi(w2); //Added by Sirius for client version check + } else if (strcmpi(w1, "console") == 0) { + if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 ) + console = 1; + } else if (strcmpi(w1, "allowed_regs") == 0) { //account flood protection system [Kevin] + allowed_regs = atoi(w2); + } else if (strcmpi(w1, "time_allowed") == 0) { + time_allowed = atoi(w2); + } else if (strcmpi(w1, "online_check") == 0) { + if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 ) + online_check = 1; + else if(strcmpi(w2,"off") == 0 || strcmpi(w2,"no") == 0 ) + online_check = 0; + else + online_check = atoi(w2); + } else if (strcmpi(w1, "import") == 0) { + login_config_read(w2); + } + } + } + fclose(fp); + + ShowInfo("Finished reading %s.\n", cfgName); + + return 0; +} + +//------------------------------------- +// Displaying of configuration warnings +//------------------------------------- +void display_conf_warnings(void) { + if (admin_state != 0 && admin_state != 1) { + ShowWarning("Invalid value for admin_state parameter -> setting to 0 (no remote admin).\n"); + admin_state = 0; + } + + if (admin_state == 1) { + if (admin_pass[0] == '\0') { + ShowWarning("Administrator password is void (admin_pass).\n"); + } else if (strcmp(admin_pass, "admin") == 0) { + ShowWarning("You are using the default administrator password (admin_pass).\n"); + ShowWarning(" We highly recommend that you change it.\n"); + } + } + + if (gm_pass[0] == '\0') { + ShowWarning("'To GM become' password is void (gm_pass).\n"); + ShowWarning(" We highly recommend that you set one password.\n"); + } else if (strcmp(gm_pass, "gm") == 0) { + ShowWarning("You are using the default GM password (gm_pass).\n"); + ShowWarning(" We highly recommend that you change it.\n"); + } + + if (level_new_gm < 0 || level_new_gm > 99) { + ShowWarning("Invalid value for level_new_gm parameter -> setting to 60 (default).\n"); + level_new_gm = 60; + } + + if (new_account_flag != 0 && new_account_flag != 1) { + ShowWarning("Invalid value for new_account parameter -> setting to 0 (no new account).\n"); + new_account_flag = 0; + } + + if (login_port < 1024 || login_port > 65535) { + ShowWarning("Invalid value for login_port parameter -> setting to 6900 (default).\n"); + login_port = 6900; + } + + if (gm_account_filename_check_timer < 0) { + ShowWarning("Invalid value for gm_account_filename_check_timer parameter. Setting to 15 sec (default).\n"); + gm_account_filename_check_timer = 15; + } else if (gm_account_filename_check_timer == 1) { + ShowWarning("Invalid value for gm_account_filename_check_timer parameter. Setting to 2 sec (minimum value).\n"); + gm_account_filename_check_timer = 2; + } + + if (save_unknown_packets != 0 && save_unknown_packets != 1) { + ShowWarning("Invalid value for save_unknown_packets parameter -> setting to 0-no save.\n"); + save_unknown_packets = 0; + } + + if (display_parse_login != 0 && display_parse_login != 1) { // 0: no, 1: yes + ShowWarning("Invalid value for display_parse_login parameter -> setting to 0 (no display).\n"); + display_parse_login = 0; + } + + if (display_parse_admin != 0 && display_parse_admin != 1) { // 0: no, 1: yes + ShowWarning("Invalid value for display_parse_admin parameter -> setting to 0 (no display).\n"); + display_parse_admin = 0; + } + + if (display_parse_fromchar < 0 || display_parse_fromchar > 2) { // 0: no, 1: yes (without packet 0x2714), 2: all packets + ShowWarning("Invalid value for display_parse_fromchar parameter -> setting to 0 (no display).\n"); + display_parse_fromchar = 0; + } + + if (min_level_to_connect < 0) { // 0: all players, 1-99 at least gm level x + ShowWarning("Invalid value for min_level_to_connect (%d) parameter -> setting 0 (any player).\n", min_level_to_connect); + min_level_to_connect = 0; + } else if (min_level_to_connect > 99) { // 0: all players, 1-99 at least gm level x + ShowWarning("Invalid value for min_level_to_connect (%d) parameter -> setting to 99 (only GM level 99)\n", min_level_to_connect); + min_level_to_connect = 99; + } + + if (add_to_unlimited_account != 0 && add_to_unlimited_account != 1) { // 0: no, 1: yes + ShowWarning("Invalid value for add_to_unlimited_account parameter\n"); + ShowWarning(" -> setting to 0 (impossible to add a time to an unlimited account).\n"); + add_to_unlimited_account = 0; + } + + if (start_limited_time < -1) { // -1: create unlimited account, 0 or more: additionnal sec from now to create limited time + ShowWarning("Invalid value for start_limited_time parameter\n"); + ShowWarning(" -> setting to -1 (new accounts are created with unlimited time).\n"); + start_limited_time = -1; + } + + if (check_ip_flag != 0 && check_ip_flag != 1) { // 0: no, 1: yes + ShowWarning("Invalid value for check_ip_flag parameter\n"); + ShowWarning(" -> setting to 1 (check players ip between login-server & char-server).\n"); + check_ip_flag = 1; + } + + if (access_order == ACO_DENY_ALLOW) { + if (access_denynum == 1 && access_deny[0] == '\0') { + ShowWarning("The IP security order is 'deny,allow' (allow if not deny) and you refuse ALL IP.\n"); + } + } else if (access_order == ACO_ALLOW_DENY) { + if (access_allownum == 0) { + ShowWarning("The IP security order is 'allow,deny' (deny if not allow) but, NO IP IS AUTHORISED!\n"); + } + } else { // ACO_MUTUAL_FAILTURE + if (access_allownum == 0) { + ShowWarning("The IP security order is 'mutual-failture'\n"); + ShowWarning(" (allow if in the allow list and not in the deny list).\n"); + ShowWarning(" But, NO IP IS AUTHORISED!\n"); + } else if (access_denynum == 1 && access_deny[0] == '\0') { + ShowWarning("The IP security order is mutual-failture\n"); + ShowWarning(" (allow if in the allow list and not in the deny list).\n"); + ShowWarning(" But, you refuse ALL IP!\n"); + } + } + + if (dynamic_pass_failure_ban != 0) { + if (dynamic_pass_failure_ban_time < 1) { + ShowWarning("Invalid value for dynamic_pass_failure_ban_time (%d) parameter\n", dynamic_pass_failure_ban_time); + ShowWarning(" -> setting to 5 (5 minutes to look number of invalid passwords.\n"); + dynamic_pass_failure_ban_time = 5; + } + if (dynamic_pass_failure_ban_how_many < 1) { + ShowWarning("Invalid value for dynamic_pass_failure_ban_how_many (%d) parameter\n", dynamic_pass_failure_ban_how_many); + ShowWarning(" -> setting to 3 (3 invalid passwords before to temporarily ban.\n"); + dynamic_pass_failure_ban_how_many = 3; + } + if (dynamic_pass_failure_ban_how_long < 1) { + ShowWarning("Invalid value for dynamic_pass_failure_ban_how_long (%d) parameter\n", dynamic_pass_failure_ban_how_long); + ShowWarning(" -> setting to 1 (1 minute of temporarily ban.\n"); + dynamic_pass_failure_ban_how_long = 1; + } + } + + return; +} + +//------------------------------- +// Save configuration in log file +//------------------------------- +void save_config_in_log(void) { + int i; + + // a newline in the log... + login_log(""); + login_log("The login-server starting..." RETCODE); + + // save configuration in log file + login_log("The configuration of the server is set:" RETCODE); + + if (admin_state != 1) + login_log("- with no remote administration." RETCODE); + else if (admin_pass[0] == '\0') + login_log("- with a remote administration with a VOID password." RETCODE); + else if (strcmp(admin_pass, "admin") == 0) + login_log("- with a remote administration with the DEFAULT password." RETCODE); + else + login_log("- with a remote administration with the password of %d character(s)." RETCODE, strlen(admin_pass)); + if (access_ladmin_allownum == 0 || (access_ladmin_allownum == 1 && access_ladmin_allow[0] == '\0')) { + login_log("- to accept any IP for remote administration" RETCODE); + } else { + login_log("- to accept following IP for remote administration:" RETCODE); + for(i = 0; i < access_ladmin_allownum; i++) + login_log(" %s" RETCODE, (char *)(access_ladmin_allow + i * ACO_STRSIZE)); + } + + if (gm_pass[0] == '\0') + login_log("- with a VOID 'To GM become' password (gm_pass)." RETCODE); + else if (strcmp(gm_pass, "gm") == 0) + login_log("- with the DEFAULT 'To GM become' password (gm_pass)." RETCODE); + else + login_log("- with a 'To GM become' password (gm_pass) of %d character(s)." RETCODE, strlen(gm_pass)); + if (level_new_gm == 0) + login_log("- to refuse any creation of GM with @gm." RETCODE); + else + login_log("- to create GM with level '%d' when @gm is used." RETCODE, level_new_gm); + + if (new_account_flag == 1) + login_log("- to ALLOW new users (with _F/_M)." RETCODE); + else + login_log("- to NOT ALLOW new users (with _F/_M)." RETCODE); + login_log("- with port: %d." RETCODE, login_port); + login_log("- with the accounts file name: '%s'." RETCODE, account_filename); + login_log("- with the GM accounts file name: '%s'." RETCODE, GM_account_filename); + if (gm_account_filename_check_timer == 0) + login_log("- to NOT check GM accounts file modifications." RETCODE); + else + login_log("- to check GM accounts file modifications every %d seconds." RETCODE, gm_account_filename_check_timer); + + if (use_md5_passwds == 0) + login_log("- to save password in plain text." RETCODE); + else + login_log("- to save password with MD5 encrypting." RETCODE); + + // not necessary to log the 'login_log_filename', we are inside :) + + login_log("- with the unknown packets file name: '%s'." RETCODE, login_log_unknown_packets_filename); + if (save_unknown_packets) + login_log("- to SAVE all unkown packets." RETCODE); + else + login_log("- to SAVE only unkown packets sending by a char-server or a remote administration." RETCODE); + if (display_parse_login) + login_log("- to display normal parse packets on console." RETCODE); + else + login_log("- to NOT display normal parse packets on console." RETCODE); + if (display_parse_admin) + login_log("- to display administration parse packets on console." RETCODE); + else + login_log("- to NOT display administration parse packets on console." RETCODE); + if (display_parse_fromchar) + login_log("- to display char-server parse packets on console." RETCODE); + else + login_log("- to NOT display char-server parse packets on console." RETCODE); + + if (min_level_to_connect == 0) // 0: all players, 1-99 at least gm level x + login_log("- with no minimum level for connection." RETCODE); + else if (min_level_to_connect == 99) + login_log("- to accept only GM with level 99." RETCODE); + else + login_log("- to accept only GM with level %d or more." RETCODE, min_level_to_connect); + + if (add_to_unlimited_account) + login_log("- to authorize adjustment (with timeadd ladmin) on an unlimited account." RETCODE); + else + login_log("- to refuse adjustment (with timeadd ladmin) on an unlimited account. You must use timeset (ladmin command) before." RETCODE); + + if (start_limited_time < 0) + login_log("- to create new accounts with an unlimited time." RETCODE); + else if (start_limited_time == 0) + login_log("- to create new accounts with a limited time: time of creation." RETCODE); + else + login_log("- to create new accounts with a limited time: time of creation + %d second(s)." RETCODE, start_limited_time); + + if (check_ip_flag) + login_log("- with control of players IP between login-server and char-server." RETCODE); + else + login_log("- to not check players IP between login-server and char-server." RETCODE); + + if (access_order == ACO_DENY_ALLOW) { + if (access_denynum == 0) { + login_log("- with the IP security order: 'deny,allow' (allow if not deny). You refuse no IP." RETCODE); + } else if (access_denynum == 1 && access_deny[0] == '\0') { + login_log("- with the IP security order: 'deny,allow' (allow if not deny). You refuse ALL IP." RETCODE); + } else { + login_log("- with the IP security order: 'deny,allow' (allow if not deny). Refused IP are:" RETCODE); + for(i = 0; i < access_denynum; i++) + login_log(" %s" RETCODE, (char *)(access_deny + i * ACO_STRSIZE)); + } + } else if (access_order == ACO_ALLOW_DENY) { + if (access_allownum == 0) { + login_log("- with the IP security order: 'allow,deny' (deny if not allow). But, NO IP IS AUTHORISED!" RETCODE); + } else if (access_allownum == 1 && access_allow[0] == '\0') { + login_log("- with the IP security order: 'allow,deny' (deny if not allow). You authorise ALL IP." RETCODE); + } else { + login_log("- with the IP security order: 'allow,deny' (deny if not allow). Authorised IP are:" RETCODE); + for(i = 0; i < access_allownum; i++) + login_log(" %s" RETCODE, (char *)(access_allow + i * ACO_STRSIZE)); + } + } else { // ACO_MUTUAL_FAILTURE + login_log("- with the IP security order: 'mutual-failture' (allow if in the allow list and not in the deny list)." RETCODE); + if (access_allownum == 0) { + login_log(" But, NO IP IS AUTHORISED!" RETCODE); + } else if (access_denynum == 1 && access_deny[0] == '\0') { + login_log(" But, you refuse ALL IP!" RETCODE); + } else { + if (access_allownum == 1 && access_allow[0] == '\0') { + login_log(" You authorise ALL IP." RETCODE); + } else { + login_log(" Authorised IP are:" RETCODE); + for(i = 0; i < access_allownum; i++) + login_log(" %s" RETCODE, (char *)(access_allow + i * ACO_STRSIZE)); + } + login_log(" Refused IP are:" RETCODE); + for(i = 0; i < access_denynum; i++) + login_log(" %s" RETCODE, (char *)(access_deny + i * ACO_STRSIZE)); + } + + // dynamic password error ban + if (dynamic_pass_failure_ban == 0) + login_log("- with NO dynamic password error ban." RETCODE); + else { + login_log("- with a dynamic password error ban:" RETCODE); + login_log(" After %d invalid password in %d minutes" RETCODE, dynamic_pass_failure_ban_how_many, dynamic_pass_failure_ban_time); + login_log(" IP is banned for %d minutes" RETCODE, dynamic_pass_failure_ban_how_long); + } + } +} + +//-------------------------------------- +// Function called at exit of the server +//-------------------------------------- +void do_final(void) { + int i, fd; + ShowInfo("Terminating...\n"); + fflush(stdout); + mmo_auth_sync(); + online_db->destroy(online_db, NULL); + + if(auth_dat) aFree(auth_dat); + if(gm_account_db) aFree(gm_account_db); + if(access_ladmin_allow) aFree(access_ladmin_allow); + if(access_allow) aFree(access_allow); + if(access_deny) aFree(access_deny); + for (i = 0; i < MAX_SERVERS; i++) { + if ((fd = server_fd[i]) >= 0) { + server_fd[i] = -1; + memset(&server[i], 0, sizeof(struct mmo_char_server)); + do_close(fd); + } + } + do_close(login_fd); + + login_log("----End of login-server (normal end with closing of all files)." RETCODE); + + if(log_fp) + fclose(log_fp); + ShowStatus("Finished.\n"); +} + +//------------------------------ +// Main function of login-server +//------------------------------ +void set_server_type(void) +{ + SERVER_TYPE = ATHENA_SERVER_LOGIN; +} +int do_init(int argc, char **argv) { + int i, j; + + // read login-server configuration + login_config_read((argc > 1) ? argv[1] : LOGIN_CONF_NAME); + display_conf_warnings(); // not in login_config_read, because we can use 'import' option, and display same message twice or more + save_config_in_log(); // not before, because log file name can be changed + login_lan_config_read((argc > 1) ? argv[1] : LAN_CONF_NAME); + + srand((unsigned int)time(NULL)); + + for(i = 0; i< AUTH_FIFO_SIZE; i++) + auth_fifo[i].delflag = 1; + for(i = 0; i < MAX_SERVERS; i++) + server_fd[i] = -1; + + gm_account_db = NULL; + GM_num = 0; + GM_max = 0; + mmo_auth_init(); + read_gm_account(); + set_defaultparse(parse_login); + // Online user database init + online_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int)); // reinitialise + add_timer_func_list(waiting_disconnect_timer, "waiting_disconnect_timer"); + + if (bind_ip_set_) + login_fd = make_listen_bind(inet_addr(bind_ip_str),login_port); + else + login_fd = make_listen_bind(INADDR_ANY,login_port); + + add_timer_func_list(check_auth_sync, "check_auth_sync"); + add_timer_interval(gettick() + 60000, check_auth_sync, 0, 0, 60000); // every 60 sec we check if we must save accounts file (only if necessary to save) + + // add timer to check GM accounts file modification + j = gm_account_filename_check_timer; + if (j == 0) // if we would not to check, we check every 60 sec, just to have timer (if we change timer, is was not necessary to check if timer already exists) + j = 60; + + add_timer_func_list(check_GM_file, "check_GM_file"); + add_timer_interval(gettick() + j * 1000, check_GM_file, 0, 0, j * 1000); // every x sec we check if gm file has been changed + + + add_timer_func_list(online_data_cleanup, "online_data_cleanup"); + add_timer_interval(gettick() + 600*1000, online_data_cleanup, 0, 0, 600*1000); // every 10 minutes cleanup online account db. + if(console) { + set_defaultconsoleparse(parse_console); + start_console(); + } + + login_log("The login-server is ready (Server is listening on the port %d)." RETCODE, login_port); + ShowStatus("The login-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %d).\n\n", login_port); + + return 0; +} diff --git a/src/login/login.h b/src/login/login.h new file mode 100644 index 000000000..119a91595 --- /dev/null +++ b/src/login/login.h @@ -0,0 +1,44 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _LOGIN_H_ +#define _LOGIN_H_ + +#define MAX_SERVERS 30 + +#define LOGIN_CONF_NAME "conf/login_athena.conf" +#define LAN_CONF_NAME "conf/lan_support.conf" +#define PASSWORDENC 3 // A definition is given when making an encryption password correspond. + // It is 1 at the time of passwordencrypt. + // It is made into 2 at the time of passwordencrypt2. + // When it is made 3, it corresponds to both. +#define START_ACCOUNT_NUM 2000000 +#define END_ACCOUNT_NUM 100000000 + +extern int login_port; +struct mmo_account { + int version; //Added for version check [Sirius] + char* userid; + char passwd[33]; + int passwdenc; + + long account_id; + long login_id1; + long login_id2; + long char_id; + char lastlogin[24]; + int sex; +}; + +struct mmo_char_server { + char name[21]; + long ip; + short port; + int users; + int maintenance; + int new_; +}; + +extern struct mmo_char_server server[MAX_SERVERS]; +extern int server_fd[MAX_SERVERS]; +#endif diff --git a/src/login/md5calc.c b/src/login/md5calc.c new file mode 100644 index 000000000..5c52670c7 --- /dev/null +++ b/src/login/md5calc.c @@ -0,0 +1,236 @@ +/*********************************************************** + * md5 calculation algorithm + * + * The source code referred to the following URL. + * http://www.geocities.co.jp/SiliconValley-Oakland/8878/lab17/lab17.html + * + ***********************************************************/ + +#include "md5calc.h" +#include +#include + +#ifndef UINT_MAX +#define UINT_MAX 4294967295U +#endif + +// Global variable +static unsigned int *pX; + +// String Table +static const unsigned int T[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8 + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16 + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, //20 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24 + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32 + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, //40 + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48 + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56 + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 //60 +}; + +// ROTATE_LEFT The left is made to rotate x [ n-bit ]. This is diverted as it is from RFC. +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +// The function used for other calculation +static unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Y) | (~X & Z); +} +static unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Z) | (Y & ~Z); +} +static unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) +{ + return X ^ Y ^ Z; +} +static unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) +{ + return Y ^ (X | ~Z); +} + +static unsigned int Round(unsigned int a, unsigned int b, unsigned int FGHI, + unsigned int k, unsigned int s, unsigned int i) +{ + return b + ROTATE_LEFT(a + FGHI + pX[k] + T[i], s); +} + +static void Round1(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, F(b,c,d), k, s, i); +} +static void Round2(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, G(b,c,d), k, s, i); +} +static void Round3(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, H(b,c,d), k, s, i); +} +static void Round4(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, I(b,c,d), k, s, i); +} + +static void MD5_Round_Calculate(const unsigned char *block, + unsigned int *A2, unsigned int *B2, unsigned int *C2, unsigned int *D2) +{ + //create X It is since it is required. + unsigned int X[16]; //512bit 64byte + int j,k; + + //Save A as AA, B as BB, C as CC, and and D as DD (saving of A, B, C, and D) + unsigned int A=*A2, B=*B2, C=*C2, D=*D2; + unsigned int AA = A,BB = B,CC = C,DD = D; + + //It is a large region variable reluctantly because of calculation of a round. . . for Round1...4 + pX = X; + + //Copy block(padding_message) i into X + for (j=0,k=0; j<64; j+=4,k++) + X[k] = ( (unsigned int )block[j] ) // 8byte*4 -> 32byte conversion + | ( ((unsigned int )block[j+1]) << 8 ) // A function called Decode as used in the field of RFC + | ( ((unsigned int )block[j+2]) << 16 ) + | ( ((unsigned int )block[j+3]) << 24 ); + + + //Round 1 + Round1(&A,B,C,D, 0, 7, 0); Round1(&D,A,B,C, 1, 12, 1); Round1(&C,D,A,B, 2, 17, 2); Round1(&B,C,D,A, 3, 22, 3); + Round1(&A,B,C,D, 4, 7, 4); Round1(&D,A,B,C, 5, 12, 5); Round1(&C,D,A,B, 6, 17, 6); Round1(&B,C,D,A, 7, 22, 7); + Round1(&A,B,C,D, 8, 7, 8); Round1(&D,A,B,C, 9, 12, 9); Round1(&C,D,A,B, 10, 17, 10); Round1(&B,C,D,A, 11, 22, 11); + Round1(&A,B,C,D, 12, 7, 12); Round1(&D,A,B,C, 13, 12, 13); Round1(&C,D,A,B, 14, 17, 14); Round1(&B,C,D,A, 15, 22, 15); + + //Round 2 + Round2(&A,B,C,D, 1, 5, 16); Round2(&D,A,B,C, 6, 9, 17); Round2(&C,D,A,B, 11, 14, 18); Round2(&B,C,D,A, 0, 20, 19); + Round2(&A,B,C,D, 5, 5, 20); Round2(&D,A,B,C, 10, 9, 21); Round2(&C,D,A,B, 15, 14, 22); Round2(&B,C,D,A, 4, 20, 23); + Round2(&A,B,C,D, 9, 5, 24); Round2(&D,A,B,C, 14, 9, 25); Round2(&C,D,A,B, 3, 14, 26); Round2(&B,C,D,A, 8, 20, 27); + Round2(&A,B,C,D, 13, 5, 28); Round2(&D,A,B,C, 2, 9, 29); Round2(&C,D,A,B, 7, 14, 30); Round2(&B,C,D,A, 12, 20, 31); + + //Round 3 + Round3(&A,B,C,D, 5, 4, 32); Round3(&D,A,B,C, 8, 11, 33); Round3(&C,D,A,B, 11, 16, 34); Round3(&B,C,D,A, 14, 23, 35); + Round3(&A,B,C,D, 1, 4, 36); Round3(&D,A,B,C, 4, 11, 37); Round3(&C,D,A,B, 7, 16, 38); Round3(&B,C,D,A, 10, 23, 39); + Round3(&A,B,C,D, 13, 4, 40); Round3(&D,A,B,C, 0, 11, 41); Round3(&C,D,A,B, 3, 16, 42); Round3(&B,C,D,A, 6, 23, 43); + Round3(&A,B,C,D, 9, 4, 44); Round3(&D,A,B,C, 12, 11, 45); Round3(&C,D,A,B, 15, 16, 46); Round3(&B,C,D,A, 2, 23, 47); + + //Round 4 + Round4(&A,B,C,D, 0, 6, 48); Round4(&D,A,B,C, 7, 10, 49); Round4(&C,D,A,B, 14, 15, 50); Round4(&B,C,D,A, 5, 21, 51); + Round4(&A,B,C,D, 12, 6, 52); Round4(&D,A,B,C, 3, 10, 53); Round4(&C,D,A,B, 10, 15, 54); Round4(&B,C,D,A, 1, 21, 55); + Round4(&A,B,C,D, 8, 6, 56); Round4(&D,A,B,C, 15, 10, 57); Round4(&C,D,A,B, 6, 15, 58); Round4(&B,C,D,A, 13, 21, 59); + Round4(&A,B,C,D, 4, 6, 60); Round4(&D,A,B,C, 11, 10, 61); Round4(&C,D,A,B, 2, 15, 62); Round4(&B,C,D,A, 9, 21, 63); + + // Then perform the following additions. (let's add) + *A2 = A + AA; + *B2 = B + BB; + *C2 = C + CC; + *D2 = D + DD; + + //The clearance of confidential information + memset(pX, 0, sizeof(X)); +} + +//------------------------------------------------------------------- +// The function for the exteriors + +/** output is the coded binary in the character sequence which wants to code string. */ +void MD5_String2binary(const char * string, char * output) +{ +//var + /*8bit*/ + unsigned char padding_message[64]; //Extended message 512bit 64byte + unsigned char *pstring; //The position of string in the present scanning notes is held. + +// unsigned char digest[16]; + /*32bit*/ + unsigned int string_byte_len, //The byte chief of string is held. + string_bit_len, //The bit length of string is held. + copy_len, //The number of bytes which is used by 1-3 and which remained + msg_digest[4]; //Message digest 128bit 4byte + unsigned int *A = &msg_digest[0], //The message digest in accordance with RFC (reference) + *B = &msg_digest[1], + *C = &msg_digest[2], + *D = &msg_digest[3]; + int i; + +//prog + //Step 3.Initialize MD Buffer (although it is the initialization; step 3 of A, B, C, and D -- unavoidable -- a head) + *A = 0x67452301; + *B = 0xefcdab89; + *C = 0x98badcfe; + *D = 0x10325476; + + //Step 1.Append Padding Bits (extension of a mark bit) + //1-1 + string_byte_len = strlen(string); //The byte chief of a character sequence is acquired. + pstring = (unsigned char *)string; //The position of the present character sequence is set. + + //1-2 Repeat calculation until length becomes less than 64 bytes. + for (i=string_byte_len; 64<=i; i-=64,pstring+=64) + MD5_Round_Calculate(pstring, A,B,C,D); + + //1-3 + copy_len = string_byte_len % 64; //The number of bytes which remained is computed. + strncpy((char *)padding_message, (char *)pstring, copy_len); //A message is copied to an extended bit sequence. + memset(padding_message+copy_len, 0, 64 - copy_len); //It buries by 0 until it becomes extended bit length. + padding_message[copy_len] |= 0x80; //The next of a message is 1. + + //1-4 + //If 56 bytes or more (less than 64 bytes) of remainder becomes, it will calculate by extending to 64 bytes. + if (56 <= copy_len) { + MD5_Round_Calculate(padding_message, A,B,C,D); + memset(padding_message, 0, 56); //56 bytes is newly fill uped with 0. + } + + + //Step 2.Append Length (the information on length is added) + string_bit_len = string_byte_len * 8; //From the byte chief to bit length (32 bytes of low rank) + memcpy(&padding_message[56], &string_bit_len, 4); //32 bytes of low rank is set. + + //When bit length cannot be expressed in 32 bytes of low rank, it is a beam raising to a higher rank. + if (UINT_MAX / 8 < string_byte_len) { + unsigned int high = (string_byte_len - UINT_MAX / 8) * 8; + memcpy(&padding_message[60], &high, 4); + } else + memset(&padding_message[60], 0, 4); //In this case, it is good for a higher rank at 0. + + //Step 4.Process Message in 16-Word Blocks (calculation of MD5) + MD5_Round_Calculate(padding_message, A,B,C,D); + + + //Step 5.Output (output) + memcpy(output,msg_digest,16); +// memcpy (digest, msg_digest, and 16); //8 byte*4 < - 32byte conversion A function called Encode as used in the field of RFC +/* sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]);*/ +} + +/** output is the coded character sequence in the character sequence which wants to code string. */ +void MD5_String(const char * string, char * output) +{ + unsigned char digest[16]; + + MD5_String2binary(string,(char*)digest); + sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]); +} + diff --git a/src/login/md5calc.h b/src/login/md5calc.h new file mode 100644 index 000000000..04fb2d8c5 --- /dev/null +++ b/src/login/md5calc.h @@ -0,0 +1,7 @@ +#ifndef _MD5CALC_H_ +#define _MD5CALC_H_ + +void MD5_String(const char * string, char * output); +void MD5_String2binary(const char * string, char * output); + +#endif diff --git a/src/login_sql/Makefile b/src/login_sql/Makefile new file mode 100644 index 000000000..2a54f680b --- /dev/null +++ b/src/login_sql/Makefile @@ -0,0 +1,22 @@ +all sql: login-server_sql + +COMMON_OBJ = ../common/obj/core.o ../common/obj/socket.o ../common/obj/timer.o \ + ../common/obj/db.o ../common/obj/plugins.o ../common/obj/lock.o \ + ../common/obj/malloc.o ../common/obj/showmsg.o ../common/obj/utils.o \ + ../common/obj/strlib.o ../common/obj/graph.o ../common/obj/grfio.o \ + ../common/obj/mapindex.o ../common/obj/ers.o ../zlib/unz.o +COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h \ + ../common/version.h ../common/db.h ../common/plugins.h ../common/lock.h \ + ../common/malloc.h ../common/showmsg.h ../common/utils.h ../common/strlib.h \ + ../common/graph.h ../common/grfio.h ../common/mapindex.h + +login-server_sql: login.o md5calc.o $(COMMON_OBJ) + $(CC) -o ../../$@ $^ $(LIB_S) + +clean: + rm -f *.o ../../login-server_sql + +# DO NOT DELETE + +login.o: login.c login.h md5calc.h $(COMMON_H) +md5calc.o: md5calc.c md5calc.h diff --git a/src/login_sql/login.c b/src/login_sql/login.c new file mode 100644 index 000000000..fe4248183 --- /dev/null +++ b/src/login_sql/login.c @@ -0,0 +1,2256 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include + +#ifdef LCCWIN32 +#include +#pragma lib +#else +#ifdef __WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +void Gettimeofday(struct timeval *timenow) +{ + time_t t; + t = clock(); + timenow->tv_usec = (long)t; + timenow->tv_sec = (long)(t / CLK_TCK); + return; +} +#define gettimeofday(timenow, dummy) Gettimeofday(timenow) +#define in_addr_t unsigned long +#pragma comment(lib,"libmysql.lib") +#else +#include +#include +#include +#include +#include +#include +#include +#endif +#endif + +#include +#include +#include // for stat/lstat/fstat +#include +#include +#include + +//add include for DBMS(mysql) +#include + +#include "../common/core.h" +#include "../common/socket.h" +#include "../common/malloc.h" +#include "../common/db.h" +#include "../common/timer.h" +#include "../common/strlib.h" +#include "../common/mmo.h" +#include "../common/showmsg.h" +#include "../common/version.h" +#include "login.h" + +#ifdef PASSWORDENC +#include "md5calc.h" +#endif + +#define J_MAX_MALLOC_SIZE 65535 + +//----------------------------------------------------- +// global variable +//----------------------------------------------------- +int account_id_count = START_ACCOUNT_NUM; +int server_num; +int new_account_flag = 0; //Set from config too XD [Sirius] +int bind_ip_set_ = 0; +char bind_ip_str[128]; +int login_port = 6900; +char lan_char_ip[128]; // Lan char ip added by kashy +int subnetmaski[4]; // Subnetmask added by kashy + +struct mmo_char_server server[MAX_SERVERS]; +int server_fd[MAX_SERVERS]; + +int login_fd; + +//Account flood protection [Kevin] +unsigned int new_reg_tick=0; +int allowed_regs=1; +int num_regs=0; +int time_allowed=10; //Init this to 10 secs, not 10K secs [Skotlex] + +char date_format[32] = "%Y-%m-%d %H:%M:%S"; +unsigned int auth_num = 0, auth_max = 0; + +int min_level_to_connect = 0; // minimum level of player/GM (0: player, 1-99: gm) to connect on the server +int check_ip_flag = 1; // It's to check IP of a player between login-server and char-server (part of anti-hacking system) +int check_client_version = 0; //Client version check ON/OFF .. (sirius) +int client_version_to_connect = 20; //Client version needed to connect ..(sirius) +static int online_check=1; //When set to 1, login server rejects incoming players that are already registered as online. [Skotlex] + +MYSQL mysql_handle; + +int ipban = 1; +int dynamic_account_ban = 1; +int dynamic_account_ban_class = 0; +int dynamic_pass_failure_ban = 1; +int dynamic_pass_failure_ban_time = 5; +int dynamic_pass_failure_ban_how_many = 3; +int dynamic_pass_failure_ban_how_long = 60; + +int login_server_port = 3306; +char login_server_ip[32] = "127.0.0.1"; +char login_server_id[32] = "ragnarok"; +char login_server_pw[32] = "ragnarok"; +char login_server_db[32] = "ragnarok"; +char default_codepage[32] = ""; //Feature by irmin. +int use_md5_passwds = 0; +char login_db[256] = "login"; +int log_login=1; //Whether to log the logins or not. [Skotlex] +char loginlog_db[256] = "loginlog"; + +// added to help out custom login tables, without having to recompile +// source so options are kept in the login_athena.conf or the inter_athena.conf +char login_db_account_id[256] = "account_id"; +char login_db_userid[256] = "userid"; +char login_db_user_pass[256] = "user_pass"; +char login_db_level[256] = "level"; + +char reg_db[256] = "global_reg_value"; + +int lowest_gm_level; +struct gm_account *gm_account_db; +int GM_num; +char tmpsql[65535], tmp_sql[65535]; + +int console = 0; + +int case_sensitive = 1; + +//----------------------------------------------------- + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id,login_id1,login_id2; + int ip,sex,delflag; +} auth_fifo[AUTH_FIFO_SIZE]; + +int auth_fifo_pos = 0; + +struct online_login_data { + int account_id; + short char_server; + short waiting_disconnect; +}; + +//----------------------------------------------------- + +static char md5key[20], md5keylen = 16; + +struct dbt *online_db; + +static void* create_online_user(DBKey key, va_list args) { + struct online_login_data *p; + p = aCalloc(1, sizeof(struct online_login_data)); + p->account_id = key.i; + p->char_server = -1; + return p; +} + +//----------------------------------------------------- +// Online User Database [Wizputer] +//----------------------------------------------------- + +void add_online_user(int char_server, int account_id) { + struct online_login_data *p; + if (!online_check) + return; + p = idb_ensure(online_db, account_id, create_online_user); + p->char_server = char_server; + p->waiting_disconnect = 0; +} + +int is_user_online(int account_id) { + return (idb_get(online_db, account_id) != NULL); +} + +void remove_online_user(int account_id) { + if(!online_check) + return; + if (account_id == 99) { // reset all to offline + online_db->clear(online_db, NULL); + return; + } + idb_remove(online_db,account_id); +} + +int waiting_disconnect_timer(int tid, unsigned int tick, int id, int data) +{ + struct online_login_data *p; + if ((p= idb_get(online_db, id)) != NULL && p->waiting_disconnect) + remove_online_user(id); + return 0; +} + +//----------------------------------------------------- +// Read GM accounts +//----------------------------------------------------- +void read_gm_account(void) { + MYSQL_RES* sql_res ; + MYSQL_ROW sql_row; + + if (gm_account_db != NULL) + aFree(gm_account_db); + GM_num = 0; + + sprintf(tmp_sql, "SELECT `%s`,`%s` FROM `%s` WHERE `%s`>='%d'",login_db_account_id,login_db_level,login_db,login_db_level,lowest_gm_level); + if (mysql_query(&mysql_handle, tmp_sql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + gm_account_db = (struct gm_account*)aCalloc((size_t)mysql_num_rows(sql_res), sizeof(struct gm_account)); + while ((sql_row = mysql_fetch_row(sql_res))) { + gm_account_db[GM_num].account_id = atoi(sql_row[0]); + gm_account_db[GM_num].level = atoi(sql_row[1]); + GM_num++; + } + } + + mysql_free_result(sql_res); +} + +int charif_sendallwos(int sfd, unsigned char *buf, unsigned int len); + +//----------------------------------------------------- +// Send GM accounts to all char-server +//----------------------------------------------------- +void send_GM_accounts(int fd) { + int i; + unsigned char buf[32767]; + int len; + + len = 4; + WBUFW(buf,0) = 0x2732; + for(i = 0; i < GM_num; i++) + // send only existing accounts. We can not create a GM account when server is online. + if (gm_account_db[i].level > 0) { + WBUFL(buf,len) = gm_account_db[i].account_id; + WBUFB(buf,len+4) = (unsigned char)gm_account_db[i].level; + len += 5; + } + WBUFW(buf,2) = len; + if (fd == -1) + charif_sendallwos(-1, buf, len); + else + { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + } + return; +} + +//----------------------------------------------------- +// check user level +//----------------------------------------------------- +/* +int isGM(int account_id) { + int level; + + MYSQL_RES* sql_res; + MYSQL_ROW sql_row; + level = 0; + sprintf(tmpsql,"SELECT `%s` FROM `%s` WHERE `%s`='%d'", login_db_level, login_db, login_db_account_id, account_id); + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + level = atoi(sql_row[0]); + if (level > 99) + level = 99; + } + + if (level == 0) { + return 0; + //not GM + } + + mysql_free_result(sql_res); + + return level; +} +*/ + +//--------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//--------------------------------------------------- +int e_mail_check(char *email) { + char ch; + char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//----------------------------------------------------- +// Read Account database - mysql db +//----------------------------------------------------- +int mmo_auth_sqldb_init(void) { + + ShowStatus("Login server init....\n"); + + // memory initialize + ShowStatus("memory initialize....\n"); + + mysql_init(&mysql_handle); + + // DB connection start + ShowStatus("Connect Login Database Server....\n"); + if (!mysql_real_connect(&mysql_handle, login_server_ip, login_server_id, login_server_pw, + login_server_db, login_server_port, (char *)NULL, 0)) { + // pointer check + ShowFatalError("%s\n", mysql_error(&mysql_handle)); + exit(1); + } else { + ShowStatus("Connect success!\n"); + } + if( strlen(default_codepage) > 0 ) { + sprintf( tmpsql, "SET NAMES %s", default_codepage ); + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmpsql); + } + } + + if (log_login) + { + sprintf(tmpsql, "INSERT DELAYED INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '', 'lserver', '100','login server started')", loginlog_db); + + //query + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + if (new_account_flag) + { //Check if the next new account will need to have it's ID set (to avoid bad DBs which would otherwise insert + //new accounts with account_ids of less than 2M [Skotlex] + sprintf(tmp_sql, "SELECT max(`%s`) from `%s`", login_db_account_id, login_db); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } else { + MYSQL_RES* sql_res; + MYSQL_ROW sql_row; + + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) + { + if (mysql_num_rows(sql_res) > 0 && + (sql_row = mysql_fetch_row(sql_res)) != NULL && + sql_row[0] != NULL && atoi(sql_row[0]) >= account_id_count) + //Ok, chars already exist, no need to use this. + account_id_count = 0; + mysql_free_result(sql_res); + } + } + } + return 0; +} + +//----------------------------------------------------- +// DB server connect check +//----------------------------------------------------- +void mmo_auth_sqldb_sync(void) { + // db connect check? or close? + // ping pong DB server -if losted? then connect try. else crash. +} + +//----------------------------------------------------- +// close DB +//----------------------------------------------------- +void mmo_db_close(void) { + int i, fd; + + //set log. + if (log_login) + { + sprintf(tmpsql,"INSERT DELAYED INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '', 'lserver','100', 'login server shutdown')", loginlog_db); + + //query + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } +/* + //delete all server status + sprintf(tmpsql,"DELETE FROM `sstatus`"); + //query + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + mysql_close(&mysql_handle); + ShowStatus("close DB connect....\n"); +*/ + + for (i = 0; i < MAX_SERVERS; i++) { + if ((fd = server_fd[i]) >= 0) + { //Clean only data related to servers we are connected to. [Skotlex] + sprintf(tmpsql,"DELETE FROM `sstatus` WHERE `index` = '%d'", i); + if (mysql_query(&mysql_handle, tmpsql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + delete_session(fd); + } + } + mysql_close(&mysql_handle); + ShowStatus("close DB connect....\n"); + delete_session(login_fd); +} + +//----------------------------------------------------- +// Make new account +//----------------------------------------------------- +int mmo_auth_new(struct mmo_account* account, char sex) +{ + MYSQL_RES* sql_res; + unsigned int tick = gettick(); + char user_password[256]; + //Account Registration Flood Protection by [Kevin] + if(tick <= new_reg_tick && num_regs >= allowed_regs) { + ShowNotice("Account registration denied (registration limit exceeded)\n"); + return 3; + } + + //Check for preexisting account + sprintf(tmp_sql, "SELECT `%s` FROM `%s` WHERE `userid` = '%s'", login_db_userid, login_db, account->userid); + if(mysql_query(&mysql_handle, tmp_sql)){ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 1; //Return Incorrect user/pass? + } + + sql_res = mysql_store_result(&mysql_handle); + if(mysql_num_rows(sql_res) > 0){ + mysql_free_result(sql_res); + return 1; //Already exists, return incorrect user/pass. + } + mysql_free_result(sql_res); //Only needed for the already-exists check... + + mysql_real_escape_string(&mysql_handle, account->userid, account->userid, strlen(account->userid)); + mysql_real_escape_string(&mysql_handle, account->passwd, account->passwd, strlen(account->passwd)); + + if (sex == 'f') sex = 'F'; + else if (sex == 'm') sex = 'M'; + if (use_md5_passwds) + MD5_String(account->passwd,user_password); + else + jstrescapecpy(user_password, account->passwd); + + ShowInfo("New account: user: %s with passwd: %s sex: %c\n", account->userid, user_password, sex); + + if (account_id_count) //Force new Account ID + sprintf(tmp_sql, "INSERT INTO `%s` (`%s`, `%s`, `%s`, `sex`, `email`) VALUES ('%d', '%s', '%s', '%c', '%s')", login_db, login_db_account_id, login_db_userid, login_db_user_pass, account_id_count, account->userid, user_password, sex, "a@a.com"); + else + sprintf(tmp_sql, "INSERT INTO `%s` (`%s`, `%s`, `sex`, `email`) VALUES ('%s', '%s', '%c', '%s')", login_db, login_db_userid, login_db_user_pass, account->userid, user_password, sex, "a@a.com"); + + if(mysql_query(&mysql_handle, tmp_sql)){ + //Failed to insert new acc :/ + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 1; + } + + if (account_id_count) //Clear it or all new accounts will try to use the same id :P + account_id_count = 0; + + if(tick > new_reg_tick) + { //Update the registration check. + num_regs=0; + new_reg_tick=gettick()+time_allowed*1000; + } + num_regs++; + + return 0; +} + +#ifdef LCCWIN32 +extern void gettimeofday(struct timeval *t, struct timezone *dummy); +#endif + +// Send to char +int charif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + int fd; + + c = 0; + for(i = 0; i < MAX_SERVERS; i++) { + if ((fd = server_fd[i]) > 0 && fd != sfd) { + if (WFIFOSPACE(fd) < len) //Increase buffer size. + realloc_writefifo(fd, len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + + return c; +} + +//----------------------------------------------------- +// Auth +//----------------------------------------------------- +int mmo_auth( struct mmo_account* account , int fd){ + time_t ban_until_time, raw_time; + char tmpstr[256]; + char t_uid[256], t_pass[256]; + char user_password[256]; + + //added for account creation _M _F + int len; + + MYSQL_RES* sql_res; + MYSQL_ROW sql_row; + //int sql_fields, sql_cnt; + char md5str[64], md5bin[32]; + + char ip[16]; + + unsigned char *sin_addr = (unsigned char *)&session[fd]->client_addr.sin_addr; + + + sprintf(ip, "%d.%d.%d.%d", sin_addr[0], sin_addr[1], sin_addr[2], sin_addr[3]); + ShowInfo("auth start for %s...\n", ip); + + //accountreg with _M/_F .. [Sirius] + len = strlen(account->userid) -2; + + if (account->passwdenc == 0 && account->userid[len] == '_' && + (account->userid[len+1] == 'F' || account->userid[len+1] == 'M' || + account->userid[len+1] == 'f' || account->userid[len+1] == 'm') && + new_account_flag == 1 && + len >= 4 && strlen(account->passwd) >= 4) + { + int result; + account->userid[len] = '\0'; //Terminating the name. + if ((result = mmo_auth_new(account, account->userid[len+1]))) + return result; //Failed to make account. [Skotlex]. + } + + // auth start : time seed + // Platform/Compiler dependant clock() for time check is removed. [Lance] + // clock() is originally used to track processing ticks on program execution. + time(&raw_time); + strftime(tmpstr, 24, "%Y-%m-%d %H:%M:%S",localtime(&raw_time)); + + jstrescapecpy(t_uid,account->userid); + jstrescapecpy(t_pass, account->passwd); + + + // make query + sprintf(tmpsql, "SELECT `%s`,`%s`,`%s`,`lastlogin`,`logincount`,`sex`,`connect_until`,`last_ip`,`ban_until`,`state`,`%s`" + " FROM `%s` WHERE %s `%s`='%s'", login_db_account_id, login_db_userid, login_db_user_pass, login_db_level, login_db, case_sensitive ? "BINARY" : "", login_db_userid, t_uid); + //login {0-account_id/1-userid/2-user_pass/3-lastlogin/4-logincount/5-sex/6-connect_untl/7-last_ip/8-ban_until/9-state/10-level} + + // query + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); //row fetching + if (!sql_row) { + //there's no id. + ShowNotice("auth failed: no such account %s %s %s\n", tmpstr, account->userid, account->passwd); + mysql_free_result(sql_res); + return 0; + } + } else { + ShowError("mmo_auth DB result error ! \n"); + return 0; + } + + //Client Version check[Sirius] + if(check_client_version == 1 && account->version != 0){ + if(account->version != client_version_to_connect){ + mysql_free_result(sql_res); + return 5; + } + } + + // Documented by CLOWNISIUS || LLRO || Gunstar lead this one with me + // IF changed to diferent returns~ you get diferent responses from your msgstringtable.txt + //Ireturn 2 == line 9 + //Ireturn 5 == line 311 + //Ireturn 6 == line 450 + //Ireturn 7 == line 440 + //Ireturn 8 == line 682 + //Ireturn 9 == line 704 + //Ireturn 10 == line 705 + //Ireturn 11 == line 706 + //Ireturn 12 == line 707 + //Ireturn 13 == line 708 + //Ireturn 14 == line 709 + //Ireturn 15 == line 710 + //Ireturn -1 == line 010 + // Check status + { + int encpasswdok = 0; + + if (atoi(sql_row[9]) == -3) { + //id is banned + mysql_free_result(sql_res); + return -3; + } else if (atoi(sql_row[9]) == -2) { //dynamic ban + //id is banned + mysql_free_result(sql_res); + //add IP list. + return -2; + } + + if (use_md5_passwds) { + MD5_String(account->passwd,user_password); + } else { + jstrescapecpy(user_password, account->passwd); + } + ShowInfo("account id ok encval:%d\n",account->passwdenc); +#ifdef PASSWORDENC + if (account->passwdenc > 0) { + int j = account->passwdenc; + ShowInfo("start md5calc..\n"); + if (j > 2) + j = 1; + do { + if (j == 1) { + sprintf(md5str, "%s%s", md5key,sql_row[2]); + } else if (j == 2) { + sprintf(md5str, "%s%s", sql_row[2], md5key); + } else + md5str[0] = 0; + ShowDebug("j:%d mdstr:%s\n", j, md5str); + MD5_String2binary(md5str, md5bin); + encpasswdok = (memcmp(user_password, md5bin, 16) == 0); + } while (j < 2 && !encpasswdok && (j++) != account->passwdenc); + //printf("key[%s] md5 [%s] ", md5key, md5); + ShowInfo("client [%s] accountpass [%s]\n", user_password, sql_row[2]); + ShowInfo("end md5calc..\n"); + } +#endif + if ((strcmp(user_password, sql_row[2]) && !encpasswdok)) { + if (account->passwdenc == 0) { + ShowNotice("auth failed pass error %s %s %s" RETCODE, tmpstr, account->userid, user_password); +#ifdef PASSWORDENC + } else { + char logbuf[1024], *p = logbuf; + int j; + p += sprintf(p, "auth failed pass error %s %s recv-md5[", tmpstr, account->userid); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)user_password)[j]); + p += sprintf(p, "] calc-md5["); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)md5bin)[j]); + p += sprintf(p, "] md5key["); + for(j = 0; j < md5keylen; j++) + p += sprintf(p, "%02x", ((unsigned char *)md5key)[j]); + p += sprintf(p, "]" RETCODE); + ShowNotice("%s\n", p); +#endif + } + return 1; + } + ShowInfo("auth ok %s %s" RETCODE, tmpstr, account->userid); + } + +/* +// do not remove this section. this is meant for future, and current forums usage +// as a login manager and CP for login server. [CLOWNISIUS] + if (atoi(sql_row[10]) == 1) { + return 4; + } + + if (atoi(sql_row[10]) >= 5) { + switch(atoi(sql_row[10])) { + case 5: + return 5; + break; + case 6: + return 7; + break; + case 7: + return 9; + break; + case 8: + return 10; + break; + case 9: + return 11; + break; + default: + return 10; + break; + } + } +*/ + ban_until_time = atol(sql_row[8]); + + //login {0-account_id/1-userid/2-user_pass/3-lastlogin/4-logincount/5-sex/6-connect_untl/7-last_ip/8-ban_until/9-state} + if (ban_until_time != 0) { // if account is banned + strftime(tmpstr, 20, date_format, localtime(&ban_until_time)); + tmpstr[19] = '\0'; + if (ban_until_time > time(NULL)) { // always banned + return 6; // 6 = Your are Prohibited to log in until %s + } else { // ban is finished + // reset the ban time + if (atoi(sql_row[9])==7) {//it was a temp ban - so we set STATE to 0 + sprintf(tmpsql, "UPDATE `%s` SET `ban_until`='0', `state`='0' WHERE %s `%s`='%s'", login_db, case_sensitive ? "BINARY" : "", login_db_userid, t_uid); + strcpy(sql_row[9],"0"); //we clear STATE + } else //it was a permanent ban + temp ban. So we leave STATE = 5, but clear the temp ban + sprintf(tmpsql, "UPDATE `%s` SET `ban_until`='0' WHERE %s `%s`='%s'", login_db, case_sensitive ? "BINARY" : "", login_db_userid, t_uid); + + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + + if (atoi(sql_row[9])) { + switch(atoi(sql_row[9])) { // packet 0x006a value + 1 + case 1: // 0 = Unregistered ID + case 2: // 1 = Incorrect Password + case 3: // 2 = This ID is expired + case 4: // 3 = Rejected from Server + case 5: // 4 = You have been blocked by the GM Team + case 6: // 5 = Your Game's EXE file is not the latest version + case 7: // 6 = Your are Prohibited to log in until %s + case 8: // 7 = Server is jammed due to over populated + case 9: // 8 = No more accounts may be connected from this company + case 10: // 9 = MSI_REFUSE_BAN_BY_DBA + case 11: // 10 = MSI_REFUSE_EMAIL_NOT_CONFIRMED + case 12: // 11 = MSI_REFUSE_BAN_BY_GM + case 13: // 12 = MSI_REFUSE_TEMP_BAN_FOR_DBWORK + case 14: // 13 = MSI_REFUSE_SELF_LOCK + case 15: // 14 = MSI_REFUSE_NOT_PERMITTED_GROUP + case 16: // 15 = MSI_REFUSE_NOT_PERMITTED_GROUP + case 100: // 99 = This ID has been totally erased + case 101: // 100 = Login information remains at %s. + case 102: // 101 = Account has been locked for a hacking investigation. Please contact the GM Team for more information + case 103: // 102 = This account has been temporarily prohibited from login due to a bug-related investigation + case 104: // 103 = This character is being deleted. Login is temporarily unavailable for the time being + case 105: // 104 = Your spouse character is being deleted. Login is temporarily unavailable for the time being + ShowNotice("Auth Error #%d\n", atoi(sql_row[9])); + return atoi(sql_row[9]) - 1; + break; + default: + return 99; // 99 = ID has been totally erased + break; + } + } + + if (atol(sql_row[6]) != 0 && atol(sql_row[6]) < time(NULL)) { + return 2; // 2 = This ID is expired + } + + if (online_check) { + struct online_login_data* data = idb_get(online_db,atoi(sql_row[0])); + unsigned char buf[8]; + if (data && data->char_server > -1) { + //Request char servers to kick this account out. [Skotlex] + ShowWarning("User [%s] is already online - Rejected.\n",sql_row[1]); + WBUFW(buf,0) = 0x2734; + WBUFL(buf,2) = atol(sql_row[0]); + charif_sendallwos(-1, buf, 6); + if (!data->waiting_disconnect) + add_timer(gettick()+30000, waiting_disconnect_timer, atol(sql_row[0]), 0); + data->waiting_disconnect = 1; + return 3; // Rejected + } + } + + account->account_id = atoi(sql_row[0]); + account->login_id1 = rand(); + account->login_id2 = rand(); + memcpy(tmpstr, sql_row[3], 19); + memcpy(account->lastlogin, tmpstr, 24); + account->sex = sql_row[5][0] == 'S' ? 2 : sql_row[5][0]=='M'; + account->level = atoi(sql_row[10]) > 99 ? 99 : atoi(sql_row[10]); // as was in isGM() [zzo] + + if (account->sex != 2 && account->account_id < 700000) + ShowWarning("Account %s has account id %d! Account IDs must be over 700000 to work properly!\n", account->userid, account->account_id); + sprintf(tmpsql, "UPDATE `%s` SET `lastlogin` = NOW(), `logincount`=`logincount` +1, `last_ip`='%s' WHERE %s `%s` = '%s'", + login_db, ip, case_sensitive ? "BINARY" : "", login_db_userid, sql_row[1]); + mysql_free_result(sql_res) ; //resource free + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + return -1; +} + +static int online_db_setoffline(DBKey key, void* data, va_list ap) { + struct online_login_data *p = (struct online_login_data *)data; + int server = va_arg(ap, int); + if (server == -1) { + p->char_server = -1; + p->waiting_disconnect = 0; + } else if (p->char_server == server) + p->char_server = -2; //Char server disconnected. + return 0; +} + +//----------------------------------------------------- +// char-server packet parse +//----------------------------------------------------- +int parse_fromchar(int fd){ + int i, id; + MYSQL_RES* sql_res; + MYSQL_ROW sql_row = NULL; + + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + for(id = 0; id < MAX_SERVERS; id++) + if (server_fd[id] == fd) + break; + + if (id == MAX_SERVERS) + session[fd]->eof = 1; + if(session[fd]->eof) { + if (id < MAX_SERVERS) { + ShowStatus("Char-server '%s' has disconnected.\n", server[id].name); + server_fd[id] = -1; + memset(&server[id], 0, sizeof(struct mmo_char_server)); + online_db->foreach(online_db,online_db_setoffline,id); //Set all chars from this char server to offline. + // server delete + sprintf(tmpsql, "DELETE FROM `sstatus` WHERE `index`='%d'", id); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + do_close(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { +// printf("char_parse: %d %d packet case=%x\n", fd, RFIFOREST(fd), RFIFOW(fd, 0)); + + switch (RFIFOW(fd,0)) { + case 0x2709: + if (log_login) + { + sprintf(tmpsql,"INSERT DELAYED INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', '%s','%s', 'GM reload request')", loginlog_db, p[0], p[1], p[2], p[3], server[id].name, RETCODE); + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + read_gm_account(); + // send GM accounts to all char-servers + send_GM_accounts(-1); + RFIFOSKIP(fd,2); + break; + + case 0x2712: + if (RFIFOREST(fd) < 19) + return 0; + { + int account_id; + account_id = RFIFOL(fd,2); // speed up + for(i=0;i %d\n", i); + break; + } + } + + if (i != AUTH_FIFO_SIZE && account_id > 0) { // send ack + time_t connect_until_time = 0; + char email[40] = ""; + account_id=RFIFOL(fd,2); + sprintf(tmpsql, "SELECT `email`,`connect_until` FROM `%s` WHERE `%s`='%d'", login_db, login_db_account_id, account_id); + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + connect_until_time = atol(sql_row[1]); + strcpy(email, sql_row[0]); + mysql_free_result(sql_res); + } + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = 0; + memcpy(WFIFOP(fd, 7), email, 40); + WFIFOL(fd,47) = (unsigned long) connect_until_time; + WFIFOSET(fd,51); + } else { + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = 1; + WFIFOSET(fd,51); + } + } + RFIFOSKIP(fd,19); + break; + + case 0x2714: + if (RFIFOREST(fd) < 6) + return 0; + // how many users on world? (update) + if (server[id].users != RFIFOL(fd,2)) + { + ShowStatus("set users %s : %d\n", server[id].name, RFIFOL(fd,2)); + + server[id].users = RFIFOL(fd,2); + sprintf(tmpsql,"UPDATE `sstatus` SET `user` = '%d' WHERE `index` = '%d'", server[id].users, id); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + + // send some answer + WFIFOW(fd,0) = 0x2718; + WFIFOSET(fd,2); + + RFIFOSKIP(fd,6); + break; + + // We receive an e-mail/limited time request, because a player comes back from a map-server to the char-server + case 0x2716: + if (RFIFOREST(fd) < 6) + return 0; + { + int account_id; + time_t connect_until_time = 0; + char email[40] = ""; + account_id=RFIFOL(fd,2); + sprintf(tmpsql,"SELECT `email`,`connect_until` FROM `%s` WHERE `%s`='%d'",login_db, login_db_account_id, account_id); + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + connect_until_time = atol(sql_row[1]); + strcpy(email, sql_row[0]); + } + mysql_free_result(sql_res); + //printf("parse_fromchar: E-mail/limited time request from '%s' server (concerned account: %d)\n", server[id].name, RFIFOL(fd,2)); + WFIFOW(fd,0) = 0x2717; + WFIFOL(fd,2) = RFIFOL(fd,2); + memcpy(WFIFOP(fd, 6), email, 40); + WFIFOL(fd,46) = (unsigned long) connect_until_time; + WFIFOSET(fd,50); + } + RFIFOSKIP(fd,6); + break; + + case 0x2720: // GM + if (RFIFOREST(fd) < 4) + return 0; + if (RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + //oldacc = RFIFOL(fd,4); + ShowWarning("change GM isn't supported in this login server version.\n"); + ShowError("change GM error 0 %s\n", RFIFOP(fd, 8)); + + RFIFOSKIP(fd, RFIFOW(fd, 2)); + WFIFOW(fd, 0) = 0x2721; + WFIFOL(fd, 2) = RFIFOL(fd,4); // oldacc; + WFIFOL(fd, 6) = 0; // newacc; + WFIFOSET(fd, 10); + return 0; + + // Map server send information to change an email of an account via char-server + case 0x2722: // 0x2722 .L .40B .40B + if (RFIFOREST(fd) < 86) + return 0; + { + int acc; + char actual_email[40], new_email[40]; + acc = RFIFOL(fd,2); + memcpy(actual_email, RFIFOP(fd,6), 40); + memcpy(new_email, RFIFOP(fd,46), 40); + if (e_mail_check(actual_email) == 0) + ShowWarning("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual email is invalid (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (e_mail_check(new_email) == 0) + ShowWarning("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a invalid new e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (strcmpi(new_email, "a@a.com") == 0) + ShowWarning("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a default e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else { + sprintf(tmpsql, "SELECT `%s`,`email` FROM `%s` WHERE `%s` = '%d'", login_db_userid, login_db, login_db_account_id, acc); + if (mysql_query(&mysql_handle, tmpsql)) + { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); //row fetching + + if (strcmpi(sql_row[1], actual_email) == 0) { + sprintf(tmpsql, "UPDATE `%s` SET `email` = '%s' WHERE `%s` = '%d'", login_db, new_email, login_db_account_id, acc); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + ShowInfo("Char-server '%s': Modify an e-mail on an account (@email GM command) (account: %d (%s), new e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, sql_row[0], actual_email, ip); + } + } + + } + } + RFIFOSKIP(fd, 86); + break; + + case 0x2724: // Receiving of map-server via char-server a status change resquest (by Yor) + if (RFIFOREST(fd) < 10) + return 0; + { + int acc, statut; + acc = RFIFOL(fd,2); + statut = RFIFOL(fd,6); + sprintf(tmpsql, "SELECT `state` FROM `%s` WHERE `%s` = '%d'", login_db, login_db_account_id, acc); + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); // row fetching + } + if (atoi(sql_row[0]) != statut && statut != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = 0; // 0: change of statut, 1: ban + WBUFL(buf,7) = statut; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + } + sprintf(tmpsql,"UPDATE `%s` SET `state` = '%d' WHERE `%s` = '%d'", login_db, statut,login_db_account_id,acc); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + RFIFOSKIP(fd,10); + } + return 0; + + case 0x2725: // Receiving of map-server via char-server a ban resquest (by Yor) + if (RFIFOREST(fd) < 18) + return 0; + { + int acc; + struct tm *tmtime; + time_t timestamp, tmptime; + acc = RFIFOL(fd,2); + sprintf(tmpsql, "SELECT `ban_until` FROM `%s` WHERE `%s` = '%d'",login_db,login_db_account_id,acc); + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); // row fetching + } + tmptime = atol(sql_row[0]); + if (tmptime == 0 || tmptime < time(NULL)) + timestamp = time(NULL); + else + timestamp = tmptime; + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,6); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,8); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,10); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,12); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,14); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,16); + timestamp = mktime(tmtime); + if (timestamp != -1) { + if (timestamp <= time(NULL)) + timestamp = 0; + if (tmptime != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = (unsigned int)timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + } + ShowNotice("Account: %d Banned until: %ld\n", acc, timestamp); + sprintf(tmpsql, "UPDATE `%s` SET `ban_until` = '%ld', `state`='7' WHERE `%s` = '%d'", login_db, (unsigned long)timestamp, login_db_account_id, acc); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + } + RFIFOSKIP(fd,18); + break; + } + return 0; + + case 0x2727: + if (RFIFOREST(fd) < 6) + return 0; + { + int acc,sex; + unsigned char buf[16]; + acc=RFIFOL(fd,2); + sprintf(tmpsql,"SELECT `sex` FROM `%s` WHERE `%s` = '%d'",login_db,login_db_account_id,acc); + + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + + if (sql_res) { + if (mysql_num_rows(sql_res) == 0) { + mysql_free_result(sql_res); + return 0; + } + sql_row = mysql_fetch_row(sql_res); //row fetching + } + + if (strcmpi(sql_row[0], "M") == 0) + sex = 1; + else + sex = 0; + sprintf(tmpsql,"UPDATE `%s` SET `sex` = '%c' WHERE `%s` = '%d'", login_db, (sex==0?'M':'F'), login_db_account_id, acc); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + WBUFW(buf,0) = 0x2723; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + charif_sendallwos(-1, buf, 7); + RFIFOSKIP(fd,6); + } + return 0; + + case 0x2728: // save account_reg2 + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + if (RFIFOL(fd,4) > 0) { + int acc,p,j,len; + char str[32]; + char temp_str[64]; //Needs twice as much space as the original string. + char temp_str2[512]; + char value[256]; + unsigned char *buf; + acc=RFIFOL(fd,4); + buf = (unsigned char*)aCalloc(RFIFOW(fd,2)+1, sizeof(unsigned char)); + //Delete all global account variables.... + sprintf(tmpsql,"DELETE FROM `%s` WHERE `type`='1' AND `account_id`='%d';",reg_db,acc); + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + //Proceed to insert them.... + for(j=0,p=13;jforeach(online_db,online_db_setoffline,id); //Set all chars from this char-server offline first + users = RFIFOW(fd,4); + for (i = 0; i < users; i++) { + aid = RFIFOL(fd,6+i*4); + p = idb_ensure(online_db, aid, create_online_user); + p->char_server = id; + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + } + case 0x272e: //Request account_reg2 for a character. + if (RFIFOREST(fd) < 10) + return 0; + { + int account_id = RFIFOL(fd, 2); + int char_id = RFIFOL(fd, 6); + int p; + RFIFOSKIP(fd,10); + sprintf(tmpsql, "SELECT `str`,`value` FROM `%s` WHERE `type`='1' AND `account_id`='%d'",reg_db, account_id); + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + break; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (!sql_res) { + break; + } + WFIFOW(fd,0) = 0x2729; + WFIFOL(fd,4) = account_id; + WFIFOL(fd,8) = char_id; + WFIFOB(fd,12) = 1; //Type 1 for Account2 registry + for(p = 13; (sql_row = mysql_fetch_row(sql_res));){ + if (sql_row[0][0]) { + p+= sprintf(WFIFOP(fd,p), "%s", sql_row[0])+1; //We add 1 to consider the '\0' in place. + p+= sprintf(WFIFOP(fd,p), "%s", sql_row[1])+1; + } + } + WFIFOW(fd,2) = p; + WFIFOSET(fd,WFIFOW(fd,2)); + mysql_free_result(sql_res); + } + break; + default: + ShowError("login: unknown packet %x! (from char).\n", RFIFOW(fd,0)); + session[fd]->eof = 1; + return 0; + } + } + + RFIFOSKIP(fd,RFIFOREST(fd)); + return 0; +} + +//Lan ip check added by Kashy +int lan_ip_check(unsigned char *p) { + int y; + int lancheck = 1; + int lancharip[4]; + + unsigned int k0, k1, k2, k3; + sscanf(lan_char_ip, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + lancharip[0] = k0; lancharip[1] = k1; lancharip[2] = k2; lancharip[3] = k3; + + for(y = 0; y < 4; y++) { + if ((lancharip[y] & subnetmaski[y])!= (p[y])) + lancheck = 0; + break; } + + ShowInfo("LAN check: "CL_CYAN"%s"CL_RESET".\n", (lancheck) ? "LAN" : "WAN"); + return lancheck; +} + +//---------------------------------------------------------------------------------------- +// Default packet parsing (normal players or administation/char-server connection requests) +//---------------------------------------------------------------------------------------- +int parse_login(int fd) { + //int len; + + MYSQL_RES* sql_res ; + MYSQL_ROW sql_row = NULL; + + char t_uid[100]; + //int sql_fields, sql_cnt; + struct mmo_account account; + + int result, i; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + memset(&account, 0, sizeof(account)); + + if (ipban > 0) { + //ip ban + //p[0], p[1], p[2], p[3] + //request DB connection + //check + sprintf(tmpsql, "SELECT count(*) FROM `ipbanlist` WHERE `list` = '%d.*.*.*' OR `list` = '%d.%d.*.*' OR `list` = '%d.%d.%d.*' OR `list` = '%d.%d.%d.%d'", + p[0], p[0], p[1], p[0], p[1], p[2], p[0], p[1], p[2], p[3]); + if (mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + // close connection because we can't verify their connectivity. + session[fd]->eof = 1; + } else { //Avoid entering as it causes a crash. + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); //row fetching + + if (atoi(sql_row[0]) >0) { + // ip ban ok. + ShowWarning("packet from banned ip : %d.%d.%d.%d\n" RETCODE, p[0], p[1], p[2], p[3]); + if (log_login) + { + sprintf(tmpsql,"INSERT DELAYED INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', 'unknown','-3', 'ip banned')", loginlog_db, p[0], p[1], p[2], p[3]); + + // query + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + ShowInfo ("close session connection...\n"); + + // close connection + session[fd]->eof = 1; + + } else { + ShowInfo ("packet from ip (ban check ok) : %d.%d.%d.%d" RETCODE, p[0], p[1], p[2], p[3]); + } + mysql_free_result(sql_res); + } + } + if (session[fd]->eof) { + for(i = 0; i < MAX_SERVERS; i++) + if (server_fd[i] == fd) + server_fd[i] = -1; + do_close(fd); + return 0; + } + + while(RFIFOREST(fd)>=2){ + ShowDebug("parse_login : %d %d packet case=%x\n", fd, RFIFOREST(fd), RFIFOW(fd,0)); + + switch(RFIFOW(fd,0)){ + case 0x200: // New alive packet: structure: 0x200 .24B. used to verify if client is always alive. + if (RFIFOREST(fd) < 26) + return 0; + RFIFOSKIP(fd,26); + break; + + case 0x204: // New alive packet: structure: 0x204 .16B. (new ragexe from 22 june 2004) + if (RFIFOREST(fd) < 18) + return 0; + RFIFOSKIP(fd,18); + break; + + case 0x64: // request client login + case 0x01dd: // request client login with encrypt + if((int)RFIFOREST(fd)< ((RFIFOW(fd, 0) ==0x64)?55:47)) + return 0; + + ShowInfo("client connection request %s from %d.%d.%d.%d\n", RFIFOP(fd, 6), p[0], p[1], p[2], p[3]); + account.version = RFIFOL(fd, 2); + account.userid = (char*)RFIFOP(fd, 6); + account.userid[23] = '\0'; + account.passwd = (char*)RFIFOP(fd, 30); + account.passwd[23] = '\0'; +#ifdef PASSWORDENC + account.passwdenc= (RFIFOW(fd,0)==0x64)?0:PASSWORDENC; +#else + account.passwdenc=0; +#endif + result=mmo_auth(&account, fd); + + + jstrescapecpy(t_uid,(char*)RFIFOP(fd, 6)); + if(result==-1){ + // as we have queried account level earlier in mmo_auth anyway, no need to do this again [zzo] +// int gm_level = isGM(account.account_id); // removed by [zzo] + + if (min_level_to_connect > account.level) { + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } else { + + if (p[0] != 127 && log_login) { + sprintf(tmpsql,"INSERT DELAYED INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', '%s','100', 'login ok')", loginlog_db, p[0], p[1], p[2], p[3], t_uid); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + if (account.level) + ShowStatus("Connection of the GM (level:%d) account '%s' accepted.\n", account.level, account.userid); + else + ShowStatus("Connection of the account '%s' accepted.\n", account.userid); + server_num=0; + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) { + //Lan check added by Kashy + if (lan_ip_check(p)) + WFIFOL(fd,47+server_num*32) = inet_addr(lan_char_ip); + else + WFIFOL(fd,47+server_num*32) = server[i].ip; + WFIFOW(fd,47+server_num*32+4) = server[i].port; + memcpy(WFIFOP(fd,47+server_num*32+6), server[i].name, 20); + WFIFOW(fd,47+server_num*32+26) = server[i].users; + WFIFOW(fd,47+server_num*32+28) = server[i].maintenance; + WFIFOW(fd,47+server_num*32+30) = server[i].new_; + server_num++; + } + } + // if at least 1 char-server + if (server_num > 0) { + WFIFOW(fd,0)=0x69; + WFIFOW(fd,2)=47+32*server_num; + WFIFOL(fd,4)=account.login_id1; + WFIFOL(fd,8)=account.account_id; + WFIFOL(fd,12)=account.login_id2; + WFIFOL(fd,16)=0; + memcpy(WFIFOP(fd,20),account.lastlogin,24); + WFIFOB(fd,46)=account.sex; + WFIFOSET(fd,47+32*server_num); + if(auth_fifo_pos>=AUTH_FIFO_SIZE) + auth_fifo_pos=0; + auth_fifo[auth_fifo_pos].account_id=account.account_id; + auth_fifo[auth_fifo_pos].login_id1=account.login_id1; + auth_fifo[auth_fifo_pos].login_id2=account.login_id2; + auth_fifo[auth_fifo_pos].sex=account.sex; + auth_fifo[auth_fifo_pos].delflag=0; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + auth_fifo_pos++; + } else { + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } + } + } else { + char tmp_sql[512]; + char error[64]; + if (log_login) + { + sprintf(tmp_sql,"INSERT DELAYED INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', '%s', '%d','login failed : %%s')", loginlog_db, p[0], p[1], p[2], p[3], t_uid, result); + switch((result + 1)) { + case -2: //-3 = Account Banned + sprintf(tmpsql,tmp_sql,"Account banned."); + sprintf(error,"Account banned."); + break; + case -1: //-2 = Dynamic Ban + sprintf(tmpsql,tmp_sql,"dynamic ban (ip and account)."); + sprintf(error,"dynamic ban (ip and account)."); + break; + case 1: // 0 = Unregistered ID + sprintf(tmpsql,tmp_sql,"Unregisterd ID."); + sprintf(error,"Unregisterd ID."); + break; + case 2: // 1 = Incorrect Password + sprintf(tmpsql,tmp_sql,"Incorrect Password."); + sprintf(error,"Incorrect Password."); + break; + case 3: // 2 = This ID is expired + sprintf(tmpsql,tmp_sql,"Account Expired."); + sprintf(error,"Account Expired."); + break; + case 4: // 3 = Rejected from Server + sprintf(tmpsql,tmp_sql,"Rejected from server."); + sprintf(error,"Rejected from server."); + break; + case 5: // 4 = You have been blocked by the GM Team + sprintf(tmpsql,tmp_sql,"Blocked by GM."); + sprintf(error,"Blocked by GM."); + break; + case 6: // 5 = Your Game's EXE file is not the latest version + sprintf(tmpsql,tmp_sql,"Not latest game EXE."); + sprintf(error,"Not latest game EXE."); + break; + case 7: // 6 = Your are Prohibited to log in until %s + sprintf(tmpsql,tmp_sql,"Banned."); + sprintf(error,"Banned."); + break; + case 8: // 7 = Server is jammed due to over populated + sprintf(tmpsql,tmp_sql,"Server Over-population."); + sprintf(error,"Server Over-population."); + break; + case 9: // 8 = No more accounts may be connected from this company + sprintf(tmpsql,tmp_sql,"Account limit from company"); + sprintf(error,"Account limit from company"); + break; + case 10: // 9 = MSI_REFUSE_BAN_BY_DBA + sprintf(tmpsql,tmp_sql,"Ban by DBA"); + sprintf(error,"Ban by DBA"); + break; + case 11: // 10 = MSI_REFUSE_EMAIL_NOT_CONFIRMED + sprintf(tmpsql,tmp_sql,"Email not confirmed"); + sprintf(error,"Email not confirmed"); + break; + case 12: // 11 = MSI_REFUSE_BAN_BY_GM + sprintf(tmpsql,tmp_sql,"Ban by GM"); + sprintf(error,"Ban by GM"); + break; + case 13: // 12 = MSI_REFUSE_TEMP_BAN_FOR_DBWORK + sprintf(tmpsql,tmp_sql,"Working in DB"); + sprintf(error,"Working in DB"); + break; + case 14: // 13 = MSI_REFUSE_SELF_LOCK + sprintf(tmpsql,tmp_sql,"Self Lock"); + sprintf(error,"Self Lock"); + break; + case 15: // 14 = MSI_REFUSE_NOT_PERMITTED_GROUP + sprintf(tmpsql,tmp_sql,"Not Permitted Group"); + sprintf(error,"Not Permitted Group"); + break; + case 16: // 15 = MSI_REFUSE_NOT_PERMITTED_GROUP + sprintf(tmpsql,tmp_sql,"Not Permitted Group"); + sprintf(error,"Not Permitted Group"); + break; + case 100: // 99 = This ID has been totally erased + sprintf(tmpsql,tmp_sql,"Account gone."); + sprintf(error,"Account gone."); + break; + case 101: // 100 = Login information remains at %s + sprintf(tmpsql,tmp_sql,"Login info remains."); + sprintf(error,"Login info remains."); + break; + case 102: // 101 = Account has been locked for a hacking investigation. Please contact the GM Team for more information + sprintf(tmpsql,tmp_sql,"Hacking investigation."); + sprintf(error,"Hacking investigation."); + break; + case 103: // 102 = This account has been temporarily prohibited from login due to a bug-related investigation + sprintf(tmpsql,tmp_sql,"Bug investigation."); + sprintf(error,"Bug investigation."); + break; + case 104: // 103 = This character is being deleted. Login is temporarily unavailable for the time being + sprintf(tmpsql,tmp_sql,"Deleting char."); + sprintf(error,"Deleting char."); + break; + case 105: // 104 = This character is being deleted. Login is temporarily unavailable for the time being + sprintf(tmpsql,tmp_sql,"Deleting spouse char."); + sprintf(error,"Deleting spouse char."); + break; + default: + sprintf(tmpsql,tmp_sql,"Uknown Error."); + sprintf(error,"Uknown Error."); + break; + } + //query + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } //End login log of error. + if ((result == 1) && (dynamic_pass_failure_ban != 0) && log_login){ // failed password + sprintf(tmpsql,"SELECT count(*) FROM `%s` WHERE `ip` = '%d.%d.%d.%d' AND `rcode` = '1' AND `time` > NOW() - INTERVAL %d MINUTE", + loginlog_db, p[0], p[1], p[2], p[3], dynamic_pass_failure_ban_time); //how many times filed account? in one ip. + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + //check query result + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); //row fetching + + if (atoi(sql_row[0]) >= dynamic_pass_failure_ban_how_many ) { + sprintf(tmpsql,"INSERT INTO `ipbanlist`(`list`,`btime`,`rtime`,`reason`) VALUES ('%d.%d.%d.*', NOW() , NOW() + INTERVAL %d MINUTE ,'Password error ban: %s')", p[0], p[1], p[2], dynamic_pass_failure_ban_how_long, t_uid); + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + mysql_free_result(sql_res); + } + else if (result == -2){ //dynamic banned - add ip to ban list. + sprintf(tmpsql,"INSERT INTO `ipbanlist`(`list`,`btime`,`rtime`,`reason`) VALUES ('%d.%d.%d.*', NOW() , NOW() + INTERVAL 1 MONTH ,'Dynamic banned user id : %s')", p[0], p[1], p[2], t_uid); + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + result = -3; + }else if(result == 6){ //not lastet version .. + //result = 5; + } + + sprintf(tmpsql,"SELECT `ban_until` FROM `%s` WHERE %s `%s` = '%s'",login_db, case_sensitive ? "BINARY" : "",login_db_userid, t_uid); + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); //row fetching + } + //cannot connect login failed + memset(WFIFOP(fd,0),'\0',23); + WFIFOW(fd,0)=0x6a; + WFIFOB(fd,2)=result; + if (result == 6) { // 6 = Your are Prohibited to log in until %s + if (atol(sql_row[0]) != 0) { // if account is banned, we send ban timestamp + char tmpstr[256]; + time_t ban_until_time; + ban_until_time = atol(sql_row[0]); + strftime(tmpstr, 20, date_format, localtime(&ban_until_time)); + tmpstr[19] = '\0'; + memcpy(WFIFOP(fd,3), tmpstr, 20); + } else { // we send error message + memcpy(WFIFOP(fd,3), error, 20); + } + } + WFIFOSET(fd,23); + } + RFIFOSKIP(fd,(RFIFOW(fd,0)==0x64)?55:47); + break; + + case 0x01db: // request password key + if (session[fd]->session_data) { + ShowWarning("login: abnormal request of MD5 key (already opened session).\n"); + session[fd]->eof = 1; + return 0; + } + ShowDebug("Request Password key -%s\n",md5key); + RFIFOSKIP(fd,2); + WFIFOW(fd,0)=0x01dc; + WFIFOW(fd,2)=4+md5keylen; + memcpy(WFIFOP(fd,4),md5key,md5keylen); + WFIFOSET(fd,WFIFOW(fd,2)); + break; + + case 0x2710: // request Char-server connection + if(RFIFOREST(fd)<86) + return 0; + { + unsigned char* server_name; + if (log_login) + { + sprintf(tmpsql,"INSERT DELAYED INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', '%s@%s','100', 'charserver - %s@%d.%d.%d.%d:%d')", loginlog_db, p[0], p[1], p[2], p[3], RFIFOP(fd, 2),RFIFOP(fd, 60),RFIFOP(fd, 60), RFIFOB(fd, 54), RFIFOB(fd, 55), RFIFOB(fd, 56), RFIFOB(fd, 57), RFIFOW(fd, 58)); + + //query + if(mysql_query(&mysql_handle, tmpsql)) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + } + ShowInfo("server connection request %s @ %d.%d.%d.%d:%d (%d.%d.%d.%d)\n", + RFIFOP(fd, 60), RFIFOB(fd, 54), RFIFOB(fd, 55), RFIFOB(fd, 56), RFIFOB(fd, 57), RFIFOW(fd, 58), + p[0], p[1], p[2], p[3]); + account.userid = (char*)RFIFOP(fd, 2); + account.userid[23] = '\0'; + account.passwd = (char*)RFIFOP(fd, 26); + account.passwd[23] = '\0'; + account.passwdenc = 0; + server_name = RFIFOP(fd,60); + result = mmo_auth(&account, fd); + //printf("Result: %d - Sex: %d - Account ID: %d\n",result,account.sex,(int) account.account_id); + + if(result == -1 && account.sex==2 && account.account_idfunc_parse=parse_fromchar; + realloc_fifo(fd,FIFOSIZE_SERVERLINK,FIFOSIZE_SERVERLINK); + // send GM account to char-server + send_GM_accounts(fd); + } else { + WFIFOW(fd, 0) =0x2711; + WFIFOB(fd, 2)=3; + WFIFOSET(fd, 3); + } + } + RFIFOSKIP(fd, 86); + return 0; + + case 0x7530: // request Athena information + WFIFOW(fd,0)=0x7531; + WFIFOB(fd,2)=ATHENA_MAJOR_VERSION; + WFIFOB(fd,3)=ATHENA_MINOR_VERSION; + WFIFOB(fd,4)=ATHENA_REVISION; + WFIFOB(fd,5)=ATHENA_RELEASE_FLAG; + WFIFOB(fd,6)=ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7)=ATHENA_SERVER_LOGIN; + WFIFOW(fd,8)=ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + ShowInfo ("Athena version check...\n"); + break; + + case 0x7532: + default: + ShowStatus ("End of connection (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + return 0; + } + } + + RFIFOSKIP(fd,RFIFOREST(fd)); + return 0; +} + +// Console Command Parser [Wizputer] +int parse_console(char *buf) { + char *type,*command; + + type = (char *)aMalloc(64); + command = (char *)aMalloc(64); + + memset(type,0,64); + memset(command,0,64); + + ShowInfo("Console: %s\n",buf); + + if ( sscanf(buf, "%[^:]:%[^\n]", type , command ) < 2 ) + sscanf(buf,"%[^\n]",type); + + ShowInfo("Type of command: %s || Command: %s \n",type,command); + + if(buf) aFree(buf); + if(type) aFree(type); + if(command) aFree(command); + + return 0; +} + +static int online_data_cleanup_sub(DBKey key, void *data, va_list ap) +{ + struct online_login_data *character= (struct online_login_data*)data; + if (character->char_server == -2) //Unknown server.. set them offline + remove_online_user(character->account_id); + else if (character->char_server < 0) + //Free data from players that have not been online for a while. + db_remove(online_db, key); + return 0; +} + +static int online_data_cleanup(int tid, unsigned int tick, int id, int data) +{ + online_db->foreach(online_db, online_data_cleanup_sub); + return 0; +} + +//------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, fran軋is, deutsch, espaol +//------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + + +//Lan Support conf reading added by Kashy +int login_lan_config_read(const char *lancfgName){ + int i; + char subnetmask[128]; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(lancfgName, "r"); + + if (fp == NULL) { + ShowError("file not found: %s\n", lancfgName); + return 1; + } + ShowInfo("reading configuration file %s...\n", lancfgName); + while(fgets(line, sizeof(line)-1, fp)){ + if (line[0] == '/' && line[1] == '/') + continue; + + i = sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + else if(strcmpi(w1,"lan_char_ip")==0){ + strcpy(lan_char_ip, w2); + ShowStatus("set Lan_Char_IP : %s\n",w2); + } + + else if(strcmpi(w1,"subnetmask")==0){ + unsigned int k0, k1, k2, k3; + + strcpy(subnetmask, w2); + sscanf(subnetmask, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + subnetmaski[0] = k0; subnetmaski[1] = k1; subnetmaski[2] = k2; subnetmaski[3] = k3; + ShowStatus("set subnetmask : %s\n",w2); + } + } + fclose(fp); + + { + unsigned int a0, a1, a2, a3; + unsigned char p[4]; + sscanf(lan_char_ip, "%d.%d.%d.%d", &a0, &a1, &a2, &a3); + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + ShowInfo("LAN test of LAN IP of the char-server:\n"); + if (lan_ip_check(p) == 0) { + ShowError(CL_RED" LAN IP of the char-server doesn't belong to the specified Sub-network"CL_RESET"\n"); + } + } + + ShowInfo("Finished reading %s.\n",lancfgName); + + return 0; +} + +//----------------------------------------------------- +//BANNED IP CHECK. +//----------------------------------------------------- +int ip_ban_check(int tid, unsigned int tick, int id, int data){ + + //query + if(mysql_query(&mysql_handle, "DELETE FROM `ipbanlist` WHERE `rtime` <= NOW()")) { + ShowSQL("DB error - %s\n",mysql_error(&mysql_handle)); + ShowDebug("at %s:%d - %s\n", __FILE__,__LINE__,tmp_sql); + } + + return 0; +} + +//----------------------------------------------------- +// reading configuration +//----------------------------------------------------- +int login_config_read(const char *cfgName){ + int i; + struct hostent *h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + + if(fp==NULL){ + ShowError("Configuration file (%s) not found.\n", cfgName); + return 1; + } + ShowInfo("reading configuration file %s...\n", cfgName); + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + remove_control_chars((unsigned char *) w1); + remove_control_chars((unsigned char *) w2); + if(strcmpi(w1,"timestamp_format") == 0) { + strncpy(timestamp_format, w2, 20); + } else if(strcmpi(w1,"console_silent")==0){ + msg_silent = 0; //To always allow the next line to show up. + ShowInfo("Console Silent Setting: %d\n", atoi(w2)); + msg_silent = atoi(w2); + } else if (strcmpi(w1, "bind_ip") == 0) { + bind_ip_set_ = 1; + h = gethostbyname (w2); + if (h != NULL) { + ShowStatus("Login server binding IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(bind_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(bind_ip_str,w2,16); + } else if(strcmpi(w1,"login_port")==0){ + login_port=atoi(w2); + ShowStatus("set login_port : %s\n",w2); + } + else if(strcmpi(w1,"ipban")==0){ + ipban=atoi(w2); + ShowStatus("set ipban : %d\n",ipban); + } + //account ban -> ip ban + else if(strcmpi(w1,"dynamic_account_ban")==0){ + dynamic_account_ban=atoi(w2); + ShowStatus("set dynamic_account_ban : %d\n",dynamic_account_ban); + } + else if(strcmpi(w1,"dynamic_account_ban_class")==0){ + dynamic_account_ban_class=atoi(w2); + ShowStatus("set dynamic_account_ban_class : %d\n",dynamic_account_ban_class); + } + //dynamic password error ban + else if(strcmpi(w1,"dynamic_pass_failure_ban")==0){ + dynamic_pass_failure_ban=atoi(w2); + ShowStatus("set dynamic_pass_failure_ban : %d\n",dynamic_pass_failure_ban); + } + else if(strcmpi(w1,"dynamic_pass_failure_ban_time")==0){ + dynamic_pass_failure_ban_time=atoi(w2); + ShowStatus("set dynamic_pass_failure_ban_time : %d\n",dynamic_pass_failure_ban_time); + } + else if(strcmpi(w1,"dynamic_pass_failure_ban_how_many")==0){ + dynamic_pass_failure_ban_how_many=atoi(w2); + ShowStatus("set dynamic_pass_failure_ban_how_many : %d\n",dynamic_pass_failure_ban_how_many); + } + else if(strcmpi(w1,"dynamic_pass_failure_ban_how_long")==0){ + dynamic_pass_failure_ban_how_long=atoi(w2); + ShowStatus("set dynamic_pass_failure_ban_how_long : %d\n",dynamic_pass_failure_ban_how_long); + } else if(strcmpi(w1, "new_account") == 0){ //Added by Sirius for new account _M/_F + new_account_flag = atoi(w2); //Added by Sirius for new account _M/_F + } else if(strcmpi(w1, "check_client_version") == 0){ //Added by Sirius for client version check + //check_client_version = config_switch(w2); //Added by Sirius for client version check + if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 ){ + check_client_version = 1; + } else if(strcmpi(w2,"off") == 0 || strcmpi(w2,"no") == 0 ){ + check_client_version = 0; + } + } else if(strcmpi(w1, "client_version_to_connect") == 0){ //Added by Sirius for client version check + client_version_to_connect = atoi(w2); //Added by SIrius for client version check + } else if(strcmpi(w1,"use_MD5_passwords")==0){ + if (!strcmpi(w2,"yes")) { + use_md5_passwds=1; + } else if (!strcmpi(w2,"no")){ + use_md5_passwds=0; + } + ShowStatus("Using MD5 Passwords: %s \n",w2); + } + else if (strcmpi(w1, "date_format") == 0) { // note: never have more than 19 char for the date! + switch (atoi(w2)) { + case 0: + strcpy(date_format, "%d-%m-%Y %H:%M:%S"); // 31-12-2004 23:59:59 + break; + case 1: + strcpy(date_format, "%m-%d-%Y %H:%M:%S"); // 12-31-2004 23:59:59 + break; + case 2: + strcpy(date_format, "%Y-%d-%m %H:%M:%S"); // 2004-31-12 23:59:59 + break; + case 3: + strcpy(date_format, "%Y-%m-%d %H:%M:%S"); // 2004-12-31 23:59:59 + break; + } + } + else if (strcmpi(w1, "min_level_to_connect") == 0) { + min_level_to_connect = atoi(w2); + } + else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } + else if (strcmpi(w1, "console") == 0) { + if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 ) + console = 1; + } + else if (strcmpi(w1, "case_sensitive") == 0) { + if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 ) + case_sensitive = 1; + if(strcmpi(w2,"off") == 0 || strcmpi(w2,"no") == 0 ) + case_sensitive = 0; + else + case_sensitive = atoi(w2); + } else if (strcmpi(w1, "allowed_regs") == 0) { //account flood protection system [Kevin] + allowed_regs = atoi(w2); + } else if (strcmpi(w1, "time_allowed") == 0) { + time_allowed = atoi(w2); + } else if (strcmpi(w1, "online_check") == 0) { + if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 ) + online_check = 1; + else if(strcmpi(w2,"off") == 0 || strcmpi(w2,"no") == 0 ) + online_check = 0; + else + online_check = atoi(w2); + } else if (strcmpi(w1, "log_login") == 0) { + if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 ) + log_login = 1; + else if(strcmpi(w2,"off") == 0 || strcmpi(w2,"no") == 0 ) + log_login = 0; + else + log_login = atoi(w2); + } else if (strcmpi(w1, "import") == 0) { + login_config_read(w2); + } + } + fclose(fp); + ShowInfo("done reading %s.\n", cfgName); + return 0; +} + +void sql_config_read(const char *cfgName){ /* Kalaspuff, to get login_db */ + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp=fopen(cfgName,"r"); + if(fp==NULL){ + ShowFatalError("file not found: %s\n",cfgName); + exit(1); + } + ShowInfo("reading configuration file %s...\n", cfgName); + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + if (strcmpi(w1, "login_db") == 0) { + strcpy(login_db, w2); + } + //add for DB connection + else if(strcmpi(w1,"login_server_ip")==0){ + strcpy(login_server_ip, w2); + ShowStatus ("set login_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"login_server_port")==0){ + login_server_port=atoi(w2); + ShowStatus ("set login_server_port : %s\n",w2); + } + else if(strcmpi(w1,"login_server_id")==0){ + strcpy(login_server_id, w2); + ShowStatus ("set login_server_id : %s\n",w2); + } + else if(strcmpi(w1,"login_server_pw")==0){ + strcpy(login_server_pw, w2); + ShowStatus ("set login_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + ShowStatus ("set login_server_db : %s\n",w2); + } + else if(strcmpi(w1,"default_codepage")==0){ + strcpy(default_codepage, w2); + ShowStatus ("set default_codepage : %s\n",w2); + } + //added for custom column names for custom login table + else if(strcmpi(w1,"login_db_account_id")==0){ + strcpy(login_db_account_id, w2); + } + else if(strcmpi(w1,"login_db_userid")==0){ + strcpy(login_db_userid, w2); + } + else if(strcmpi(w1,"login_db_user_pass")==0){ + strcpy(login_db_user_pass, w2); + } + else if(strcmpi(w1,"login_db_level")==0){ + strcpy(login_db_level, w2); + } + else if (strcmpi(w1, "loginlog_db") == 0) { + strcpy(loginlog_db, w2); + } + else if (strcmpi(w1, "lowest_gm_level") == 0) { + lowest_gm_level = atoi(w2); + } + else if (strcmpi(w1, "reg_db") == 0) { + strcpy(reg_db, w2); + } + //support the import command, just like any other config + else if(strcmpi(w1,"import")==0){ + sql_config_read(w2); + } + } + fclose(fp); + ShowInfo("done reading %s.\n", cfgName); +} + +//-------------------------------------- +// Function called at exit of the server +//-------------------------------------- +void do_final(void) { + //sync account when terminating. + //but no need when you using DBMS (mysql) + mmo_db_close(); + online_db->destroy(online_db, NULL); + if (gm_account_db) + aFree(gm_account_db); +} + +void set_server_type(void) +{ + SERVER_TYPE = ATHENA_SERVER_LOGIN; +} +int do_init(int argc,char **argv){ + //initialize login server + int i; + + //read login configue + login_config_read( (argc>1)?argv[1]:LOGIN_CONF_NAME ); + sql_config_read(SQL_CONF_NAME); + login_lan_config_read((argc > 1) ? argv[1] : LAN_CONF_NAME); + //Generate Passworded Key. + ShowInfo("Initializing md5key...\n"); + memset(md5key, 0, sizeof(md5key)); + md5keylen=rand()%4+12; + for(i=0;i +#include + +#ifndef UINT_MAX +#define UINT_MAX 4294967295U +#endif + +// Global variable +static unsigned int *pX; + +// Stirng Table +static const unsigned int T[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8 + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16 + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, //20 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24 + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32 + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, //40 + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48 + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56 + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 //60 +}; + +// ROTATE_LEFT The left is made to rotate x [ n-bit ]. This is diverted as it is from RFC. +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +// The function used for other calculation +static unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Y) | (~X & Z); +} +static unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Z) | (Y & ~Z); +} +static unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) +{ + return X ^ Y ^ Z; +} +static unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) +{ + return Y ^ (X | ~Z); +} + +static unsigned int Round(unsigned int a, unsigned int b, unsigned int FGHI, + unsigned int k, unsigned int s, unsigned int i) +{ + return b + ROTATE_LEFT(a + FGHI + pX[k] + T[i], s); +} + +static void Round1(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, F(b,c,d), k, s, i); +} +static void Round2(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, G(b,c,d), k, s, i); +} +static void Round3(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, H(b,c,d), k, s, i); +} +static void Round4(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, I(b,c,d), k, s, i); +} + +static void MD5_Round_Calculate(const unsigned char *block, + unsigned int *A2, unsigned int *B2, unsigned int *C2, unsigned int *D2) +{ + //create X It is since it is required. + unsigned int X[16]; //512bit 64byte + int j,k; + + //Save A as AA, B as BB, C as CC, and and D as DD (saving of A, B, C, and D) + unsigned int A=*A2, B=*B2, C=*C2, D=*D2; + unsigned int AA = A,BB = B,CC = C,DD = D; + + //It is a large region variable reluctantly because of calculation of a round. . . for Round1...4 + pX = X; + + //Copy block(padding_message) i into X + for (j=0,k=0; j<64; j+=4,k++) + X[k] = ( (unsigned int )block[j] ) // 8byte*4 -> 32byte conversion + | ( ((unsigned int )block[j+1]) << 8 ) // A function called Decode as used in the field of RFC + | ( ((unsigned int )block[j+2]) << 16 ) + | ( ((unsigned int )block[j+3]) << 24 ); + + + //Round 1 + Round1(&A,B,C,D, 0, 7, 0); Round1(&D,A,B,C, 1, 12, 1); Round1(&C,D,A,B, 2, 17, 2); Round1(&B,C,D,A, 3, 22, 3); + Round1(&A,B,C,D, 4, 7, 4); Round1(&D,A,B,C, 5, 12, 5); Round1(&C,D,A,B, 6, 17, 6); Round1(&B,C,D,A, 7, 22, 7); + Round1(&A,B,C,D, 8, 7, 8); Round1(&D,A,B,C, 9, 12, 9); Round1(&C,D,A,B, 10, 17, 10); Round1(&B,C,D,A, 11, 22, 11); + Round1(&A,B,C,D, 12, 7, 12); Round1(&D,A,B,C, 13, 12, 13); Round1(&C,D,A,B, 14, 17, 14); Round1(&B,C,D,A, 15, 22, 15); + + //Round 2 + Round2(&A,B,C,D, 1, 5, 16); Round2(&D,A,B,C, 6, 9, 17); Round2(&C,D,A,B, 11, 14, 18); Round2(&B,C,D,A, 0, 20, 19); + Round2(&A,B,C,D, 5, 5, 20); Round2(&D,A,B,C, 10, 9, 21); Round2(&C,D,A,B, 15, 14, 22); Round2(&B,C,D,A, 4, 20, 23); + Round2(&A,B,C,D, 9, 5, 24); Round2(&D,A,B,C, 14, 9, 25); Round2(&C,D,A,B, 3, 14, 26); Round2(&B,C,D,A, 8, 20, 27); + Round2(&A,B,C,D, 13, 5, 28); Round2(&D,A,B,C, 2, 9, 29); Round2(&C,D,A,B, 7, 14, 30); Round2(&B,C,D,A, 12, 20, 31); + + //Round 3 + Round3(&A,B,C,D, 5, 4, 32); Round3(&D,A,B,C, 8, 11, 33); Round3(&C,D,A,B, 11, 16, 34); Round3(&B,C,D,A, 14, 23, 35); + Round3(&A,B,C,D, 1, 4, 36); Round3(&D,A,B,C, 4, 11, 37); Round3(&C,D,A,B, 7, 16, 38); Round3(&B,C,D,A, 10, 23, 39); + Round3(&A,B,C,D, 13, 4, 40); Round3(&D,A,B,C, 0, 11, 41); Round3(&C,D,A,B, 3, 16, 42); Round3(&B,C,D,A, 6, 23, 43); + Round3(&A,B,C,D, 9, 4, 44); Round3(&D,A,B,C, 12, 11, 45); Round3(&C,D,A,B, 15, 16, 46); Round3(&B,C,D,A, 2, 23, 47); + + //Round 4 + Round4(&A,B,C,D, 0, 6, 48); Round4(&D,A,B,C, 7, 10, 49); Round4(&C,D,A,B, 14, 15, 50); Round4(&B,C,D,A, 5, 21, 51); + Round4(&A,B,C,D, 12, 6, 52); Round4(&D,A,B,C, 3, 10, 53); Round4(&C,D,A,B, 10, 15, 54); Round4(&B,C,D,A, 1, 21, 55); + Round4(&A,B,C,D, 8, 6, 56); Round4(&D,A,B,C, 15, 10, 57); Round4(&C,D,A,B, 6, 15, 58); Round4(&B,C,D,A, 13, 21, 59); + Round4(&A,B,C,D, 4, 6, 60); Round4(&D,A,B,C, 11, 10, 61); Round4(&C,D,A,B, 2, 15, 62); Round4(&B,C,D,A, 9, 21, 63); + + // Then perform the following additions. (let's add) + *A2 = A + AA; + *B2 = B + BB; + *C2 = C + CC; + *D2 = D + DD; + + //The clearance of confidential information + memset(pX, 0, sizeof(X)); +} + +//------------------------------------------------------------------- +// The function for the exteriors + +/** output is the coded binary in the character sequence which wants to code string. */ +void MD5_String2binary(const char * string, char * output) +{ +//var + /*8bit*/ + unsigned char padding_message[64]; //Extended message 512bit 64byte + unsigned char *pstring; //The position of string in the present scanning notes is held. + +// unsigned char digest[16]; + /*32bit*/ + unsigned int string_byte_len, //The byte chief of string is held. + string_bit_len, //The bit length of string is held. + copy_len, //The number of bytes which is used by 1-3 and which remained + msg_digest[4]; //Message digest 128bit 4byte + unsigned int *A = &msg_digest[0], //The message digest in accordance with RFC (reference) + *B = &msg_digest[1], + *C = &msg_digest[2], + *D = &msg_digest[3]; + int i; + +//prog + //Step 3.Initialize MD Buffer (although it is the initialization; step 3 of A, B, C, and D -- unavoidable -- a head) + *A = 0x67452301; + *B = 0xefcdab89; + *C = 0x98badcfe; + *D = 0x10325476; + + //Step 1.Append Padding Bits (extension of a mark bit) + //1-1 + string_byte_len = strlen(string); //The byte chief of a character sequence is acquired. + pstring = (unsigned char *)string; //The position of the present character sequence is set. + + //1-2 Repeat calculation until length becomes less than 64 bytes. + for (i=string_byte_len; 64<=i; i-=64,pstring+=64) + MD5_Round_Calculate(pstring, A,B,C,D); + + //1-3 + copy_len = string_byte_len % 64; //The number of bytes which remained is computed. + strncpy((char *)padding_message, (char *)pstring, copy_len); //A message is copied to an extended bit sequence. + memset(padding_message+copy_len, 0, 64 - copy_len); //It buries by 0 until it becomes extended bit length. + padding_message[copy_len] |= 0x80; //The next of a message is 1. + + //1-4 + //If 56 bytes or more (less than 64 bytes) of remainder becomes, it will calculate by extending to 64 bytes. + if (56 <= copy_len) { + MD5_Round_Calculate(padding_message, A,B,C,D); + memset(padding_message, 0, 56); //56 bytes is newly fill uped with 0. + } + + + //Step 2.Append Length (the information on length is added) + string_bit_len = string_byte_len * 8; //From the byte chief to bit length (32 bytes of low rank) + memcpy(&padding_message[56], &string_bit_len, 4); //32 bytes of low rank is set. + + //When bit length cannot be expressed in 32 bytes of low rank, it is a beam raising to a higher rank. + if (UINT_MAX / 8 < string_byte_len) { + unsigned int high = (string_byte_len - UINT_MAX / 8) * 8; + memcpy(&padding_message[60], &high, 4); + } else + memset(&padding_message[60], 0, 4); //In this case, it is good for a higher rank at 0. + + //Step 4.Process Message in 16-Word Blocks (calculation of MD5) + MD5_Round_Calculate(padding_message, A,B,C,D); + + + //Step 5.Output (output) + memcpy(output,msg_digest,16); +// memcpy (digest, msg_digest, and 16); //8 byte*4 < - 32byte conversion A function called Encode as used in the field of RFC +/* sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]);*/ +} + +/** output is the coded character sequence in the character sequence which wants to code string. */ +void MD5_String(const char * string, char * output) +{ + unsigned char digest[16]; + + MD5_String2binary(string,(char*)digest); + sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]); +} + diff --git a/src/login_sql/md5calc.h b/src/login_sql/md5calc.h new file mode 100644 index 000000000..b3735788c --- /dev/null +++ b/src/login_sql/md5calc.h @@ -0,0 +1,10 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _MD5CALC_H_ +#define _MD5CALC_H_ + +void MD5_String(const char * string, char * output); +void MD5_String2binary(const char * string, char * output); + +#endif diff --git a/src/map/Makefile b/src/map/Makefile new file mode 100644 index 000000000..f3073d476 --- /dev/null +++ b/src/map/Makefile @@ -0,0 +1,99 @@ +all txt: txtobj map-server + +sql: sqlobj map-server_sql + +txtobj: + mkdir txtobj + +sqlobj: + mkdir sqlobj + +COMMON_OBJ = ../common/obj/core.o ../common/obj/socket.o ../common/obj/timer.o \ + ../common/obj/db.o ../common/obj/plugins.o ../common/obj/lock.o \ + ../common/obj/nullpo.o ../common/obj/malloc.o ../common/obj/showmsg.o \ + ../common/obj/utils.o ../common/obj/strlib.o ../common/obj/grfio.o \ + ../common/obj/graph.o ../common/obj/mapindex.o ../common/obj/ers.o \ + ../zlib/unz.o + +COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/db.h \ + ../common/plugins.h ../common/lock.h ../common/nullpo.h ../common/malloc.h \ + ../common/showmsg.h ../common/utils.h ../common/strlib.h ../common/grfio.h \ + ../common/graph.h ../common/mapindex.h + +OBJECTS = obj/map.o obj/chrif.o obj/clif.o obj/pc.o obj/status.o obj/npc.o \ + obj/npc_chat.o obj/chat.o obj/path.o obj/itemdb.o obj/mob.o obj/script.o \ + obj/storage.o obj/skill.o obj/atcommand.o obj/charcommand.o obj/battle.o \ + obj/intif.o obj/trade.o obj/party.o obj/vending.o obj/guild.o obj/pet.o \ + obj/log.o obj/mail.o obj/charsave.o obj/date.o $(COMMON_OBJ) + +map-server: $(OBJECTS:obj/%=txtobj/%) + $(CC) -o ../../$@ $> $(LIBS) $(LIB_S) + +map-server_sql: $(OBJECTS:obj/%=sqlobj/%) + $(CC) -o ../../$@ $> $(LIB_S) + +txtobj/%.o: %.c + $(COMPILE.c) -DTXT_ONLY $(OUTPUT_OPTION) $< + +sqlobj/%.o: %.c + $(COMPILE.c) $(OUTPUT_OPTION) $< + +clean: + rm -rf *.o ../../map-server ../../map-server_sql sqlobj txtobj + +# DO NOT DELETE + +txtobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h $(COMMON_H) +txtobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h $(COMMON_H) +txtobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h charcommand.h $(COMMON_H) +txtobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h $(COMMON_H) +txtobj/status.o: status.c pc.h map.h clif.h status.h mob.h itemdb.h battle.h skill.h script.h pet.h guild.h $(COMMON_H) +txtobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h $(COMMON_H) +txtobj/npc_chat.o: npc_chat.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h $(COMMON_H) +txtobj/chat.o: chat.c map.h clif.h pc.h chat.h $(COMMON_H) +txtobj/path.o: path.c map.h battle.h $(COMMON_H) +txtobj/itemdb.o: itemdb.c map.h battle.h itemdb.h $(COMMON_H) +txtobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h date.h $(COMMON_H) +txtobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h log.h $(COMMON_H) +txtobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h $(COMMON_H) +txtobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h date.h $(COMMON_H) +txtobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h log.h $(COMMON_H) +txtobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h $(COMMON_H) +txtobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h $(COMMON_H) +txtobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h $(COMMON_H) +txtobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h $(COMMON_H) +txtobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h $(COMMON_H) +txtobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h $(COMMON_H) +txtobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h $(COMMON_H) +txtobj/log.o: log.c log.h map.h $(COMMON_H) +txtobj/charcommand.o: charcommand.c charcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h log.h $(COMMON_H) +txtobj/date.o: date.c date.h $(COMMON_H) + + +sqlobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h log.h $(COMMON_H) +sqlobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h $(COMMON_H) +sqlobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h charcommand.h $(COMMON_H) +sqlobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h log.h $(COMMON_H) +sqlobj/status.o: status.c pc.h map.h clif.h status.h mob.h itemdb.h battle.h skill.h script.h pet.h guild.h $(COMMON_H) +sqlobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h $(COMMON_H) +sqlobj/npc_chat.o: npc_chat.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h $(COMMON_H) +sqlobj/chat.o: chat.c map.h clif.h pc.h chat.h $(COMMON_H) +sqlobj/path.o: path.c map.h battle.h $(COMMON_H) +sqlobj/itemdb.o: itemdb.c map.h battle.h itemdb.h $(COMMON_H) +sqlobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h log.h date.h $(COMMON_H) +sqlobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h log.h $(COMMON_H) +sqlobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h $(COMMON_H) +sqlobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h log.h date.h $(COMMON_H) +sqlobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h log.h $(COMMON_H) +sqlobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h $(COMMON_H) +sqlobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h $(COMMON_H) +sqlobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h log.h $(COMMON_H) +sqlobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h $(COMMON_H) +sqlobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h log.h $(COMMON_H) +sqlobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h $(COMMON_H) +sqlobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h $(COMMON_H) +sqlobj/mail.o: mail.c mail.h $(COMMON_H) +sqlobj/log.o: log.c log.h map.h $(COMMON_H) +sqlobj/charcommand.o: charcommand.c charcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h log.h $(COMMON_H) +sqlobj/charsave.o: charsave.c charsave.h $(COMMON_H) +sqlobj/date.o: date.c date.h $(COMMON_H) diff --git a/src/map/Makefile.win32 b/src/map/Makefile.win32 new file mode 100644 index 000000000..4e1be4cc5 --- /dev/null +++ b/src/map/Makefile.win32 @@ -0,0 +1,99 @@ +# grab a copy of http://www.winimage.com/zLibDll/zlib122.zip +# and put safely into a subdirectory someplace +# then point ZLIBDIR at whereever you put it +# + +all: txt sql + +txt: txtobj map-server + +sql: sqlobj map-server_sql + +txtobj: + mkdir txtobj + +sqlobj: + mkdir sqlobj + +ZLIBDIR = ../zlib +PACKETDEF = -DPACKETVER=6 -DNEW_006b -D__WIN32 -DLOCALZLIB +# OPT = /MDd /D_DEBUG +OPT = +LINKOPT = /debug /SUBSYSTEM:CONSOLE +# OPT = /O2 +CFLAGS = $(OPT) /nologo /I../common /I$(ZLIBDIR) $(PACKETDEF) /D_WIN32 + +COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/grfio.o ../common/db.o ../common/lock.o ../common/nullpo.o ../common/malloc.o ../common/showmsg.o ../common/strlib.o ../common/utils.o + +LIBS = "WSOCK32.LIB" + +# "WSOCK32.LIB" "USER32.LIB" "ADVAPI32.LIB" "MSVCRT.LIB" "OLDNAMES.LIB" "KERNEL32.LIB" + +TXTOBJS = txtobj/map.o txtobj/chrif.o txtobj/clif.o txtobj/pc.o txtobj/status.o txtobj/npc.o txtobj/npc_chat.o txtobj/chat.o txtobj/path.o txtobj/itemdb.o txtobj/mob.o txtobj/script.o txtobj/storage.o txtobj/skill.o txtobj/atcommand.o txtobj/charcommand.o txtobj/battle.o txtobj/intif.o txtobj/trade.o txtobj/party.o txtobj/vending.o txtobj/guild.o txtobj/pet.o txtobj/log.o txtobj/date.o $(COMMON_OBJ) $(ZLIBDIR)/inflate.o $(ZLIBDIR)/deflate.o $(ZLIBDIR)/trees.o $(ZLIBDIR)/adler32.o $(ZLIBDIR)/compress.o $(ZLIBDIR)/crc32.o $(ZLIBDIR)/inftrees.o $(ZLIBDIR)/zutil.o $(ZLIBDIR)/inffast.o + +SQLOBJS = sqlobj/map.o sqlobj/chrif.o sqlobj/clif.o sqlobj/pc.o sqlobj/status.o sqlobj/npc.o sqlobj/npc_chat.o sqlobj/chat.o sqlobj/path.o sqlobj/itemdb.o sqlobj/mob.o sqlobj/script.o sqlobj/storage.o sqlobj/skill.o sqlobj/atcommand.o sqlobj/charcommand.o sqlobj/battle.o sqlobj/intif.o sqlobj/trade.o sqlobj/party.o sqlobj/vending.o sqlobj/guild.o sqlobj/pet.o sqlobj/log.o sqlobj/date.o sqlobj/charsave.o $(COMMON_OBJ) $(ZLIBDIR)/inflate.o $(ZLIBDIR)/adler32.o $(ZLIBDIR)/crc32.o $(ZLIBDIR)/inftrees.o $(ZLIBDIR)/zutil.o $(ZLIBDIR)/inffast.o + +map-server: $(TXTOBJS) + link $(LINKOPT) /out:../../$@.exe $(TXTOBJS) $(LIBS) + +map-server_sql: $(SQLOBJS) + link $(LINKOPT) /out:../../$@.exe $> $(LIBS) + +txtobj/%.o: %.c + Cl /c $(CFLAGS) -DTXT_ONLY /Fo$@ $< + +sqlobj/%.o: %.c + Cl /c $(CFLAGS) /Fo$@ $< + +%.o: %.c + Cl /c $(CFLAGS) /Fo$@ $< + +txtobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h ../common/core.h ../common/timer.h ../common/db.h ../common/grfio.h ../common/mmo.h ../common/showmsg.h +txtobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +txtobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h atcommand.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/showmsg.h +txtobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h ../common/timer.h ../common/mmo.h ../common/db.h ../common/showmsg.h +txtobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h ../common/db.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +txtobj/chat.o: chat.c map.h clif.h pc.h chat.h ../common/db.h ../common/mmo.h ../common/showmsg.h +txtobj/path.o: path.c map.h battle.h ../common/mmo.h ../common/showmsg.h +txtobj/itemdb.o: itemdb.c map.h battle.h itemdb.h ../common/db.h ../common/grfio.h ../common/mmo.h ../common/showmsg.h +txtobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h date.h ../common/timer.h ../common/socket.h ../common/mmo.h ../common/showmsg.h +txtobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h ../common/timer.h ../common/socket.h ../common/db.h ../common/mmo.h ../common/lock.h ../common/showmsg.h +txtobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h ../common/mmo.h ../common/db.h ../common/showmsg.h +txtobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h date.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +txtobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h log.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +txtobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +txtobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h ../common/socket.h ../common/mmo.h ../common/showmsg.h +txtobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h ../common/mmo.h ../common/showmsg.h +txtobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +txtobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h ../common/mmo.h ../common/showmsg.h +txtobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +txtobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +txtobj/date.o: date.c date.h ../common/timer.h + +sqlobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h log.h ../common/core.h ../common/timer.h ../common/db.h ../common/grfio.h ../common/mmo.h ../common/showmsg.h +sqlobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +sqlobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h atcommand.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/showmsg.h +sqlobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h log.h ../common/timer.h ../common/mmo.h ../common/db.h ../common/showmsg.h +sqlobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h ../common/db.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +sqlobj/chat.o: chat.c map.h clif.h pc.h chat.h ../common/db.h ../common/mmo.h ../common/showmsg.h +sqlobj/path.o: path.c map.h battle.h ../common/mmo.h ../common/showmsg.h +sqlobj/itemdb.o: itemdb.c map.h battle.h itemdb.h ../common/db.h ../common/grfio.h ../common/mmo.h ../common/showmsg.h +sqlobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h log.h date.h ../common/timer.h ../common/socket.h ../common/mmo.h ../common/showmsg.h +sqlobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h log.h ../common/timer.h ../common/socket.h ../common/db.h ../common/mmo.h ../common/lock.h ../common/showmsg.h +sqlobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h ../common/mmo.h ../common/db.h ../common/showmsg.h +sqlobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h log.h date.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +sqlobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h log.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +sqlobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +sqlobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h ../common/socket.h ../common/mmo.h ../common/showmsg.h +sqlobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h log.h ../common/mmo.h ../common/showmsg.h +sqlobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +sqlobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h log.h ../common/mmo.h ../common/showmsg.h +sqlobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +sqlobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/showmsg.h +sqlobj/date.o: date.c date.h ../common/timer.h +sqlobj/mail.o: mail.c mail.h ../common/showmsg.h ../common/strlib.h ../common/utils.h +sqlobj/log.o: log.c log.h map.h ../common/nullpo.h +sqlobj/charsave.o: charsave.c charsave.h $(COMMON_H) + +clean: + rm -rf *.o ../../map-server ../../map-server_sql sqlobj txtobj diff --git a/src/map/atcommand.c b/src/map/atcommand.c new file mode 100644 index 000000000..6d2d3cc2b --- /dev/null +++ b/src/map/atcommand.c @@ -0,0 +1,10039 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include +#include +#include +#include +#include + +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/nullpo.h" +#include "../common/mmo.h" +#include "../common/core.h" +#include "../common/showmsg.h" + +#include "log.h" +#include "clif.h" +#include "chrif.h" +#include "intif.h" +#include "itemdb.h" +#include "map.h" +#include "pc.h" +#include "status.h" +#include "skill.h" +#include "mob.h" +#include "pet.h" +#include "battle.h" +#include "party.h" +#include "guild.h" +#include "atcommand.h" +#include "script.h" +#include "npc.h" +#include "trade.h" + +#ifndef TXT_ONLY +#include "mail.h" +#endif + +static char command_symbol = '@'; // first char of the commands (by [Yor]) + +char *msg_table[MAX_MSG]; // Server messages (0-499 reserved for GM commands, 500-999 reserved for others) + +#define ACMD_FUNC(x) int atcommand_ ## x (const int fd, struct map_session_data* sd, const char* command, const char* message) +ACMD_FUNC(broadcast); +ACMD_FUNC(localbroadcast); +ACMD_FUNC(rura); +ACMD_FUNC(where); +ACMD_FUNC(jumpto); +ACMD_FUNC(jump); +ACMD_FUNC(who); +ACMD_FUNC(who2); +ACMD_FUNC(who3); +ACMD_FUNC(whomap); +ACMD_FUNC(whomap2); +ACMD_FUNC(whomap3); +ACMD_FUNC(whogm); // by Yor +ACMD_FUNC(whozeny); // [Valaris] +ACMD_FUNC(happyhappyjoyjoy); // [Valaris] +ACMD_FUNC(save); +ACMD_FUNC(load); +ACMD_FUNC(speed); +ACMD_FUNC(storage); +ACMD_FUNC(guildstorage); +ACMD_FUNC(option); +ACMD_FUNC(hide); +ACMD_FUNC(jobchange); +ACMD_FUNC(die); +ACMD_FUNC(kill); +ACMD_FUNC(alive); +ACMD_FUNC(kami); +ACMD_FUNC(heal); +ACMD_FUNC(item); +ACMD_FUNC(item2); +ACMD_FUNC(itemreset); +ACMD_FUNC(itemcheck); +ACMD_FUNC(baselevelup); +ACMD_FUNC(joblevelup); +ACMD_FUNC(help); +ACMD_FUNC(help2); +ACMD_FUNC(gm); +ACMD_FUNC(pvpoff); +ACMD_FUNC(pvpon); +ACMD_FUNC(gvgoff); +ACMD_FUNC(gvgon); +ACMD_FUNC(model); +ACMD_FUNC(go); +ACMD_FUNC(monster); +ACMD_FUNC(monstersmall); +ACMD_FUNC(monsterbig); +ACMD_FUNC(spawn); +ACMD_FUNC(killmonster); +ACMD_FUNC(killmonster2); +ACMD_FUNC(refine); +ACMD_FUNC(produce); +ACMD_FUNC(memo); +ACMD_FUNC(gat); +ACMD_FUNC(packet); +ACMD_FUNC(waterlevel); +ACMD_FUNC(statuspoint); +ACMD_FUNC(skillpoint); +ACMD_FUNC(zeny); +ACMD_FUNC(param); +ACMD_FUNC(guildlevelup); +ACMD_FUNC(makeegg); +ACMD_FUNC(hatch); +ACMD_FUNC(petfriendly); +ACMD_FUNC(pethungry); +ACMD_FUNC(petrename); +ACMD_FUNC(recall); +ACMD_FUNC(recallall); +ACMD_FUNC(revive); +ACMD_FUNC(night); +ACMD_FUNC(day); +ACMD_FUNC(doom); +ACMD_FUNC(doommap); +ACMD_FUNC(raise); +ACMD_FUNC(raisemap); +ACMD_FUNC(kick); +ACMD_FUNC(kickall); +ACMD_FUNC(allskill); +ACMD_FUNC(questskill); +ACMD_FUNC(lostskill); +ACMD_FUNC(spiritball); +ACMD_FUNC(party); +ACMD_FUNC(guild); +ACMD_FUNC(agitstart); +ACMD_FUNC(agitend); +ACMD_FUNC(reloaditemdb); +ACMD_FUNC(reloadmobdb); +ACMD_FUNC(reloadskilldb); +ACMD_FUNC(reloadscript); +ACMD_FUNC(reloadgmdb); // by Yor +ACMD_FUNC(reloadatcommand); +ACMD_FUNC(reloadbattleconf); +ACMD_FUNC(reloadstatusdb); +ACMD_FUNC(reloadpcdb); +ACMD_FUNC(reloadmotd); // [Valaris] +ACMD_FUNC(mapexit); +ACMD_FUNC(idsearch); +ACMD_FUNC(mapinfo); +ACMD_FUNC(dye); //** by fritz +ACMD_FUNC(hair_style); //** by fritz +ACMD_FUNC(hair_color); //** by fritz +ACMD_FUNC(stat_all); //** by fritz +ACMD_FUNC(char_block); // by Yor +ACMD_FUNC(char_ban); // by Yor +ACMD_FUNC(char_unblock); // by Yor +ACMD_FUNC(char_unban); // by Yor +ACMD_FUNC(mount_peco); // by Valaris +ACMD_FUNC(char_mount_peco); // by Yor +ACMD_FUNC(guildspy); // [Syrus22] +ACMD_FUNC(partyspy); // [Syrus22] +ACMD_FUNC(repairall); // [Valaris] +ACMD_FUNC(guildrecall); // by Yor +ACMD_FUNC(partyrecall); // by Yor +ACMD_FUNC(nuke); // [Valaris] +ACMD_FUNC(shownpc); +ACMD_FUNC(hidenpc); +ACMD_FUNC(loadnpc); +ACMD_FUNC(unloadnpc); +ACMD_FUNC(servertime); // by Yor +ACMD_FUNC(chardelitem); // by Yor +ACMD_FUNC(jail); // by Yor +ACMD_FUNC(unjail); // by Yor +ACMD_FUNC(disguise); // [Valaris] +ACMD_FUNC(undisguise); // by Yor +ACMD_FUNC(chardisguise); // Kalaspuff +ACMD_FUNC(charundisguise); // Kalaspuff +ACMD_FUNC(email); // by Yor +ACMD_FUNC(effect);//by Apple +ACMD_FUNC(character_cart_list); // by Yor +ACMD_FUNC(addwarp); // by MouseJstr +ACMD_FUNC(follow); // by MouseJstr +ACMD_FUNC(skillon); // by MouseJstr +ACMD_FUNC(skilloff); // by MouseJstr +ACMD_FUNC(killer); // by MouseJstr +ACMD_FUNC(npcmove); // by MouseJstr +ACMD_FUNC(killable); // by MouseJstr +ACMD_FUNC(charkillable); // by MouseJstr +ACMD_FUNC(dropall); // by MouseJstr +ACMD_FUNC(chardropall); // by MouseJstr +ACMD_FUNC(storeall); // by MouseJstr +ACMD_FUNC(charstoreall); // by MouseJstr +ACMD_FUNC(skillid); // by MouseJstr +ACMD_FUNC(useskill); // by MouseJstr +ACMD_FUNC(summon); +ACMD_FUNC(rain); +ACMD_FUNC(snow); +ACMD_FUNC(sakura); +ACMD_FUNC(clouds); +ACMD_FUNC(clouds2); // [Valaris] +ACMD_FUNC(fog); +ACMD_FUNC(fireworks); +ACMD_FUNC(leaves); +ACMD_FUNC(adjgmlvl); // by MouseJstr +ACMD_FUNC(adjcmdlvl); // by MouseJstr +ACMD_FUNC(trade); // by MouseJstr +ACMD_FUNC(send); // by davidsiaw +ACMD_FUNC(setbattleflag); // by MouseJstr +ACMD_FUNC(unmute); // [Valaris] +ACMD_FUNC(clearweather); // Dexity +ACMD_FUNC(uptime); // by MC Cameri +ACMD_FUNC(changesex); // by MC Cameri +ACMD_FUNC(mute); // celest +ACMD_FUNC(refresh); // by MC Cameri +ACMD_FUNC(petid); // by MC Cameri +ACMD_FUNC(identify); // by MC Cameri +ACMD_FUNC(gmotd); // Added by MC Cameri, created by davidsiaw +ACMD_FUNC(misceffect); // by MC Cameri +ACMD_FUNC(mobsearch); +ACMD_FUNC(cleanmap); +ACMD_FUNC(npctalk); +ACMD_FUNC(pettalk); +ACMD_FUNC(users); +ACMD_FUNC(autoloot); // Improved version imported from Freya. + +#ifndef TXT_ONLY +ACMD_FUNC(checkmail); // [Valaris] +ACMD_FUNC(listmail); // [Valaris] +ACMD_FUNC(listnewmail); // [Valaris] +ACMD_FUNC(readmail); // [Valaris] +ACMD_FUNC(sendmail); // [Valaris] +ACMD_FUNC(sendprioritymail); // [Valaris] +ACMD_FUNC(deletemail); // [Valaris] +ACMD_FUNC(refreshonline); // [Valaris] +#endif /* TXT_ONLY */ + +ACMD_FUNC(skilltree); // by MouseJstr + +ACMD_FUNC(marry); // by MouseJstr +ACMD_FUNC(divorce); // by MouseJstr + +ACMD_FUNC(grind); // by MouseJstr +ACMD_FUNC(grind2); // by MouseJstr + +#ifdef DMALLOC +ACMD_FUNC(dmstart); // by MouseJstr +ACMD_FUNC(dmtick); // by MouseJstr +#endif + +ACMD_FUNC(jumptoid); // by Dino9021 +ACMD_FUNC(jumptoid2); // by Dino9021 +ACMD_FUNC(recallid); // by Dino9021 +ACMD_FUNC(recallid2); // by Dino9021 +ACMD_FUNC(kickid); // by Dino9021 +ACMD_FUNC(kickid2); // by Dino9021 +ACMD_FUNC(reviveid); // by Dino9021 +ACMD_FUNC(reviveid2); // by Dino9021 +ACMD_FUNC(killid); // by Dino9021 +ACMD_FUNC(killid2); // by Dino9021 +ACMD_FUNC(charkillableid); // by Dino9021 +ACMD_FUNC(charkillableid2); // by Dino9021 +ACMD_FUNC(sound); +ACMD_FUNC(undisguiseall); +ACMD_FUNC(disguiseall); +ACMD_FUNC(changelook); +ACMD_FUNC(mobinfo); //by Lupus +ACMD_FUNC(adopt); // by Veider + +ACMD_FUNC(version); // by Ancyker + +ACMD_FUNC(mutearea); // by MouseJstr +ACMD_FUNC(shuffle); // by MouseJstr +ACMD_FUNC(rates); // by MouseJstr + +ACMD_FUNC(iteminfo); // Lupus +ACMD_FUNC(mapflag); // Lupus +ACMD_FUNC(me); //added by massdriller, code by lordalfa +ACMD_FUNC(monsterignore); // [Valaris] +ACMD_FUNC(fakename); //[Valaris] +ACMD_FUNC(size); //[Valaris] +ACMD_FUNC(showexp); //moved from charcommand [Kevin] +ACMD_FUNC(showzeny); +ACMD_FUNC(showdelay); //moved from charcommand [Kevin] +ACMD_FUNC(autotrade);// durf +ACMD_FUNC(changeleader);// [Skotlex] +ACMD_FUNC(changegm);// durf + +// Duel [LuzZza] +ACMD_FUNC(invite); +ACMD_FUNC(duel); +ACMD_FUNC(leave); +ACMD_FUNC(accept); +ACMD_FUNC(reject); + +ACMD_FUNC(away); // LuzZza +ACMD_FUNC(main); // LuzZza + +ACMD_FUNC(clone); // [Valaris] + +/*========================================== + *AtCommandInfo atcommand_info[]構造体の定義 + *------------------------------------------ + */ + +// First char of commands is configured in atcommand_athena.conf. Leave @ in this list for default value. +// to set default level, read atcommand_athena.conf first please. +static AtCommandInfo atcommand_info[] = { + { AtCommand_Rura, "@rura", 40, atcommand_rura }, + { AtCommand_Warp, "@warp", 40, atcommand_rura }, + { AtCommand_Where, "@where", 1, atcommand_where }, + { AtCommand_JumpTo, "@jumpto", 20, atcommand_jumpto }, // + /shift + { AtCommand_JumpTo, "@warpto", 20, atcommand_jumpto }, + { AtCommand_JumpTo, "@goto", 20, atcommand_jumpto }, + { AtCommand_Jump, "@jump", 40, atcommand_jump }, + { AtCommand_Who, "@who", 20, atcommand_who }, + { AtCommand_Who, "@whois", 20, atcommand_who }, + { AtCommand_Who, "@w", 20, atcommand_who }, + { AtCommand_Who2, "@who2", 20, atcommand_who2 }, + { AtCommand_Who3, "@who3", 20, atcommand_who3 }, + { AtCommand_WhoMap, "@whomap", 20, atcommand_whomap }, + { AtCommand_WhoMap2, "@whomap2", 20, atcommand_whomap2 }, + { AtCommand_WhoMap3, "@whomap3", 20, atcommand_whomap3 }, + { AtCommand_WhoGM, "@whogm", 20, atcommand_whogm }, // by Yor + { AtCommand_Save, "@save", 40, atcommand_save }, + { AtCommand_Load, "@return", 40, atcommand_load }, + { AtCommand_Load, "@load", 40, atcommand_load }, + { AtCommand_Speed, "@speed", 40, atcommand_speed }, + { AtCommand_Storage, "@storage", 1, atcommand_storage }, + { AtCommand_GuildStorage, "@gstorage", 50, atcommand_guildstorage }, + { AtCommand_Option, "@option", 40, atcommand_option }, + { AtCommand_Hide, "@hide", 40, atcommand_hide }, // + /hide + { AtCommand_JobChange, "@jobchange", 40, atcommand_jobchange }, + { AtCommand_JobChange, "@job", 40, atcommand_jobchange }, + { AtCommand_Die, "@die", 1, atcommand_die }, + { AtCommand_Kill, "@kill", 60, atcommand_kill }, + { AtCommand_Alive, "@alive", 60, atcommand_alive }, + { AtCommand_Kami, "@kami", 40, atcommand_kami }, + { AtCommand_KamiB, "@kamib", 40, atcommand_kami }, + { AtCommand_KamiC, "@kamic", 40, atcommand_kami }, //[LuzZza] + { AtCommand_Heal, "@heal", 40, atcommand_heal }, + { AtCommand_Item, "@item", 60, atcommand_item }, + { AtCommand_Item2, "@item2", 60, atcommand_item2 }, + { AtCommand_ItemReset, "@itemreset", 40, atcommand_itemreset }, + { AtCommand_ItemCheck, "@itemcheck", 60, atcommand_itemcheck }, + { AtCommand_BaseLevelUp, "@lvup", 60, atcommand_baselevelup }, + { AtCommand_BaseLevelUp, "@blevel", 60, atcommand_baselevelup }, + { AtCommand_BaseLevelUp, "@baselvlup", 60, atcommand_baselevelup }, + { AtCommand_JobLevelUp, "@jlevel", 60, atcommand_joblevelup }, + { AtCommand_JobLevelUp, "@joblvup", 60, atcommand_joblevelup }, + { AtCommand_JobLevelUp, "@joblvlup", 60, atcommand_joblevelup }, + { AtCommand_H, "@h", 20, atcommand_help }, + { AtCommand_Help, "@help", 20, atcommand_help }, + { AtCommand_H2, "@h2", 20, atcommand_help2 }, + { AtCommand_Help2, "@help2", 20, atcommand_help2 }, + { AtCommand_GM, "@gm", 100,atcommand_gm }, + { AtCommand_PvPOff, "@pvpoff", 40, atcommand_pvpoff }, + { AtCommand_PvPOn, "@pvpon", 40, atcommand_pvpon }, + { AtCommand_GvGOff, "@gvgoff", 40, atcommand_gvgoff }, + { AtCommand_GvGOff, "@gpvpoff", 40, atcommand_gvgoff }, + { AtCommand_GvGOn, "@gvgon", 40, atcommand_gvgon }, + { AtCommand_GvGOn, "@gpvpon", 40, atcommand_gvgon }, + { AtCommand_Model, "@model", 20, atcommand_model }, + { AtCommand_Go, "@go", 10, atcommand_go }, + { AtCommand_Spawn, "@monster", 50, atcommand_monster }, + { AtCommand_Spawn, "@spawn", 50, atcommand_monster }, + { AtCommand_MonsterSmall, "@monstersmall", 50, atcommand_monstersmall }, + { AtCommand_MonsterBig, "@monsterbig", 50, atcommand_monsterbig }, + { AtCommand_KillMonster, "@killmonster", 60, atcommand_killmonster }, + { AtCommand_KillMonster2, "@killmonster2", 40, atcommand_killmonster2 }, + { AtCommand_Refine, "@refine", 60, atcommand_refine }, + { AtCommand_Produce, "@produce", 60, atcommand_produce }, + { AtCommand_Memo, "@memo", 40, atcommand_memo }, + { AtCommand_GAT, "@gat", 99, atcommand_gat }, // debug function + { AtCommand_Packet, "@packet", 99, atcommand_packet }, // debug function + { AtCommand_Packet, "@packetmode", 99, atcommand_packet }, // debug function + { AtCommand_WaterLevel, "@waterlevel", 99, atcommand_waterlevel }, // debug function + { AtCommand_StatusPoint, "@stpoint", 60, atcommand_statuspoint }, + { AtCommand_SkillPoint, "@skpoint", 60, atcommand_skillpoint }, + { AtCommand_Zeny, "@zeny", 60, atcommand_zeny }, + { AtCommand_Strength, "@str", 60, atcommand_param }, + { AtCommand_Agility, "@agi", 60, atcommand_param }, + { AtCommand_Vitality, "@vit", 60, atcommand_param }, + { AtCommand_Intelligence, "@int", 60, atcommand_param }, + { AtCommand_Dexterity, "@dex", 60, atcommand_param }, + { AtCommand_Luck, "@luk", 60, atcommand_param }, + { AtCommand_GuildLevelUp, "@guildlvup", 60, atcommand_guildlevelup }, + { AtCommand_GuildLevelUp, "@guildlvlup", 60, atcommand_guildlevelup }, + { AtCommand_MakeEgg, "@makeegg", 60, atcommand_makeegg }, + { AtCommand_Hatch, "@hatch", 60, atcommand_hatch }, + { AtCommand_PetFriendly, "@petfriendly", 40, atcommand_petfriendly }, + { AtCommand_PetHungry, "@pethungry", 40, atcommand_pethungry }, + { AtCommand_PetRename, "@petrename", 1, atcommand_petrename }, + { AtCommand_Recall, "@recall", 60, atcommand_recall }, // + /recall + { AtCommand_Revive, "@revive", 60, atcommand_revive }, + { AtCommand_Night, "@night", 80, atcommand_night }, + { AtCommand_Day, "@day", 80, atcommand_day }, + { AtCommand_Doom, "@doom", 80, atcommand_doom }, + { AtCommand_DoomMap, "@doommap", 80, atcommand_doommap }, + { AtCommand_Raise, "@raise", 80, atcommand_raise }, + { AtCommand_RaiseMap, "@raisemap", 80, atcommand_raisemap }, + { AtCommand_Kick, "@kick", 20, atcommand_kick }, // + right click menu for GM "(name) force to quit" + { AtCommand_KickAll, "@kickall", 99, atcommand_kickall }, + { AtCommand_AllSkill, "@allskill", 60, atcommand_allskill }, + { AtCommand_AllSkill, "@allskills", 60, atcommand_allskill }, + { AtCommand_AllSkill, "@skillall", 60, atcommand_allskill }, + { AtCommand_AllSkill, "@skillsall", 60, atcommand_allskill }, + { AtCommand_QuestSkill, "@questskill", 40, atcommand_questskill }, + { AtCommand_LostSkill, "@lostskill", 40, atcommand_lostskill }, + { AtCommand_SpiritBall, "@spiritball", 40, atcommand_spiritball }, + { AtCommand_Party, "@party", 1, atcommand_party }, + { AtCommand_Guild, "@guild", 50, atcommand_guild }, + { AtCommand_AgitStart, "@agitstart", 60, atcommand_agitstart }, + { AtCommand_AgitEnd, "@agitend", 60, atcommand_agitend }, + { AtCommand_MapExit, "@mapexit", 99, atcommand_mapexit }, + { AtCommand_IDSearch, "@idsearch", 60, atcommand_idsearch }, + { AtCommand_MapMove, "@mapmove", 40, atcommand_rura }, // /mm command + { AtCommand_Broadcast, "@broadcast", 40, atcommand_broadcast }, // /b and /nb command + { AtCommand_LocalBroadcast, "@localbroadcast", 40, atcommand_localbroadcast }, // /lb and /nlb command + { AtCommand_RecallAll, "@recallall", 80, atcommand_recallall }, + { AtCommand_ReloadItemDB, "@reloaditemdb", 99, atcommand_reloaditemdb }, // admin command + { AtCommand_ReloadMobDB, "@reloadmobdb", 99, atcommand_reloadmobdb }, // admin command + { AtCommand_ReloadSkillDB, "@reloadskilldb", 99, atcommand_reloadskilldb }, // admin command + { AtCommand_ReloadScript, "@reloadscript", 99, atcommand_reloadscript }, // admin command + { AtCommand_ReloadGMDB, "@reloadgmdb", 99, atcommand_reloadgmdb }, // admin command + { AtCommand_ReloadAtcommand, "@reloadatcommand", 99, atcommand_reloadatcommand }, + { AtCommand_ReloadBattleConf, "@reloadbattleconf", 99, atcommand_reloadbattleconf }, + { AtCommand_ReloadStatusDB, "@reloadstatusdb", 99, atcommand_reloadstatusdb }, + { AtCommand_ReloadPcDB, "@reloadpcdb", 99, atcommand_reloadpcdb }, + { AtCommand_ReloadMOTD, "@reloadmotd", 99, atcommand_reloadmotd }, // [Valaris] + { AtCommand_MapInfo, "@mapinfo", 99, atcommand_mapinfo }, + { AtCommand_Dye, "@dye", 40, atcommand_dye }, // by fritz + { AtCommand_Dye, "@ccolor", 40, atcommand_dye }, // by fritz + { AtCommand_Hstyle, "@hairstyle", 40, atcommand_hair_style }, // by fritz + { AtCommand_Hstyle, "@hstyle", 40, atcommand_hair_style }, // by fritz + { AtCommand_Hcolor, "@haircolor", 40, atcommand_hair_color }, // by fritz + { AtCommand_Hcolor, "@hcolor", 40, atcommand_hair_color }, // by fritz + { AtCommand_StatAll, "@statall", 60, atcommand_stat_all }, // by fritz + { AtCommand_StatAll, "@statsall", 60, atcommand_stat_all }, + { AtCommand_StatAll, "@allstats", 60, atcommand_stat_all }, // by fritz + { AtCommand_StatAll, "@allstat", 60, atcommand_stat_all }, // by fritz + { AtCommand_CharBlock, "@block", 60, atcommand_char_block }, // by Yor + { AtCommand_CharBlock, "@charblock", 60, atcommand_char_block }, // by Yor + { AtCommand_CharBan, "@ban", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharBan, "@banish", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharBan, "@charban", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharBan, "@charbanish", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharUnBlock, "@unblock", 60, atcommand_char_unblock }, // by Yor + { AtCommand_CharUnBlock, "@charunblock", 60, atcommand_char_unblock }, // by Yor + { AtCommand_CharUnBan, "@unban", 60, atcommand_char_unban }, // by Yor + { AtCommand_CharUnBan, "@unbanish", 60, atcommand_char_unban }, // by Yor + { AtCommand_CharUnBan, "@charunban", 60, atcommand_char_unban }, // by Yor + { AtCommand_CharUnBan, "@charunbanish", 60, atcommand_char_unban }, // by Yor + { AtCommand_MountPeco, "@mountpeco", 20, atcommand_mount_peco }, // by Valaris + { AtCommand_CharMountPeco, "@charmountpeco", 50, atcommand_char_mount_peco }, // by Yor + { AtCommand_GuildSpy, "@guildspy", 60, atcommand_guildspy }, // [Syrus22] + { AtCommand_PartySpy, "@partyspy", 60, atcommand_partyspy }, // [Syrus22] + { AtCommand_RepairAll, "@repairall", 60, atcommand_repairall }, // [Valaris] + { AtCommand_GuildRecall, "@guildrecall", 60, atcommand_guildrecall }, // by Yor + { AtCommand_PartyRecall, "@partyrecall", 60, atcommand_partyrecall }, // by Yor + { AtCommand_Nuke, "@nuke", 60, atcommand_nuke }, // [Valaris] + { AtCommand_Shownpc, "@shownpc", 80, atcommand_shownpc }, // [] + { AtCommand_Hidenpc, "@hidenpc", 80, atcommand_hidenpc }, // [] + { AtCommand_Loadnpc, "@loadnpc", 80, atcommand_loadnpc }, // [] + { AtCommand_Unloadnpc, "@unloadnpc", 80, atcommand_unloadnpc }, // [] + { AtCommand_ServerTime, "@time", 1, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@date", 1, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@server_date", 1, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@serverdate", 1, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@server_time", 1, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@servertime", 1, atcommand_servertime }, // by Yor + { AtCommand_CharDelItem, "@chardelitem", 60, atcommand_chardelitem }, // by Yor + { AtCommand_Jail, "@jail", 60, atcommand_jail }, // by Yor + { AtCommand_UnJail, "@unjail", 60, atcommand_unjail }, // by Yor + { AtCommand_UnJail, "@discharge", 60, atcommand_unjail }, // by Yor + { AtCommand_Disguise, "@disguise", 20, atcommand_disguise }, // [Valaris] + { AtCommand_UnDisguise, "@undisguise", 20, atcommand_undisguise }, // by Yor + { AtCommand_CharDisguise, "@chardisguise", 60, atcommand_chardisguise }, // Kalaspuff + { AtCommand_CharUnDisguise, "@charundisguise", 60, atcommand_charundisguise }, // Kalaspuff + { AtCommand_EMail, "@email", 1, atcommand_email }, // by Yor + { AtCommand_Effect, "@effect", 40, atcommand_effect }, // by Apple + { AtCommand_Char_Cart_List, "@charcartlist", 40, atcommand_character_cart_list }, // by Yor + { AtCommand_Follow, "@follow", 20, atcommand_follow }, // by MouseJstr + { AtCommand_AddWarp, "@addwarp", 60, atcommand_addwarp }, // by MouseJstr + { AtCommand_SkillOn, "@skillon", 80, atcommand_skillon }, // by MouseJstr + { AtCommand_SkillOff, "@skilloff", 80, atcommand_skilloff }, // by MouseJstr + { AtCommand_Killer, "@killer", 60, atcommand_killer }, // by MouseJstr + { AtCommand_NpcMove, "@npcmove", 20, atcommand_npcmove }, // by MouseJstr + { AtCommand_Killable, "@killable", 40, atcommand_killable }, // by MouseJstr + { AtCommand_CharKillable, "@charkillable", 40, atcommand_charkillable }, // by MouseJstr + { AtCommand_Dropall, "@dropall", 40, atcommand_dropall }, // MouseJstr + { AtCommand_Chardropall, "@chardropall", 40, atcommand_chardropall }, // MouseJstr + { AtCommand_Storeall, "@storeall", 40, atcommand_storeall }, // MouseJstr + { AtCommand_Charstoreall, "@charstoreall", 40, atcommand_charstoreall }, // MouseJstr + { AtCommand_Skillid, "@skillid", 40, atcommand_skillid }, // MouseJstr + { AtCommand_Useskill, "@useskill", 40, atcommand_useskill }, // MouseJstr + { AtCommand_Rain, "@rain", 99, atcommand_rain }, + { AtCommand_Snow, "@snow", 99, atcommand_snow }, + { AtCommand_Sakura, "@sakura", 99, atcommand_sakura }, + { AtCommand_Clouds, "@clouds", 99, atcommand_clouds }, + { AtCommand_Clouds2, "@clouds2", 99, atcommand_clouds2 }, + { AtCommand_Fog, "@fog", 99, atcommand_fog }, + { AtCommand_Fireworks, "@fireworks", 99, atcommand_fireworks }, + { AtCommand_Leaves, "@leaves", 99, atcommand_leaves }, + { AtCommand_Summon, "@summon", 60, atcommand_summon }, + { AtCommand_AdjGmLvl, "@adjgmlvl", 99, atcommand_adjgmlvl }, + { AtCommand_AdjCmdLvl, "@adjcmdlvl", 99, atcommand_adjcmdlvl }, + { AtCommand_Trade, "@trade", 60, atcommand_trade }, + { AtCommand_Send, "@send", 60, atcommand_send }, + { AtCommand_SetBattleFlag, "@setbattleflag", 99, atcommand_setbattleflag }, + { AtCommand_UnMute, "@unmute", 60, atcommand_unmute }, // [Valaris] + { AtCommand_Clearweather, "@clearweather", 99, atcommand_clearweather }, // Dexity + { AtCommand_UpTime, "@uptime", 1, atcommand_uptime }, // by MC Cameri +// { AtCommand_ChangeSex, "@changesex", 1, atcommand_changesex }, // by MC Cameri <- do we still need this? [Foruken] + { AtCommand_Mute, "@mute", 99, atcommand_mute }, // [celest] + { AtCommand_Mute, "@red", 99, atcommand_mute }, // [celest] + { AtCommand_WhoZeny, "@whozeny", 20, atcommand_whozeny }, // [Valaris] + { AtCommand_HappyHappyJoyJoy, "@happyhappyjoyjoy", 40, atcommand_happyhappyjoyjoy }, // [Valaris] + { AtCommand_Refresh, "@refresh", 1, atcommand_refresh }, // by MC Cameri + { AtCommand_PetId, "@petid", 40, atcommand_petid }, // by MC Cameri + { AtCommand_Identify, "@identify", 40, atcommand_identify }, // by MC Cameri + { AtCommand_Gmotd, "@gmotd", 20, atcommand_gmotd }, // Added by MC Cameri, created by davidsiaw + { AtCommand_MiscEffect, "@misceffect", 50, atcommand_misceffect }, // by MC Cameri + { AtCommand_MobSearch, "@mobsearch", 10, atcommand_mobsearch }, + { AtCommand_CleanMap, "@cleanmap", 40, atcommand_cleanmap }, + { AtCommand_NpcTalk, "@npctalk", 20, atcommand_npctalk }, + { AtCommand_PetTalk, "@pettalk", 10, atcommand_pettalk }, + { AtCommand_Users, "@users", 40, atcommand_users }, + { AtCommand_ResetState, "/reset", 40, NULL }, + +#ifndef TXT_ONLY // sql-only commands + { AtCommand_CheckMail, "@checkmail", 1, atcommand_listmail }, // [Valaris] + { AtCommand_ListMail, "@listmail", 1, atcommand_listmail }, // [Valaris] + { AtCommand_ListNewMail, "@listnewmail", 1, atcommand_listmail }, // [Valaris] + { AtCommand_ReadMail, "@readmail", 1, atcommand_readmail }, // [Valaris] + { AtCommand_DeleteMail, "@deletemail", 1, atcommand_readmail }, // [Valaris] + { AtCommand_SendMail, "@sendmail", 1, atcommand_sendmail }, // [Valaris] + { AtCommand_SendPriorityMail, "@sendprioritymail", 80, atcommand_sendmail }, // [Valaris] + { AtCommand_RefreshOnline, "@refreshonline", 99, atcommand_refreshonline }, // [Valaris] + +#endif /* TXT_ONLY */ + { AtCommand_SkillTree, "@skilltree", 40, atcommand_skilltree }, // [MouseJstr] + { AtCommand_Marry, "@marry", 40, atcommand_marry }, // [MouseJstr] + { AtCommand_Divorce, "@divorce", 40, atcommand_divorce }, // [MouseJstr] + { AtCommand_Grind, "@grind", 99, atcommand_grind }, // [MouseJstr] + { AtCommand_Grind2, "@grind2", 99, atcommand_grind2 }, // [MouseJstr] + +#ifdef DMALLOC + { AtCommand_DMStart, "@dmstart", 99, atcommand_dmstart }, // [MouseJstr] + { AtCommand_DMTick, "@dmtick", 99, atcommand_dmtick }, // [MouseJstr] +#endif + + { AtCommand_JumpToId, "@jumptoid", 20, atcommand_jumptoid }, // [Dino9021] + { AtCommand_JumpToId, "@warptoid", 20, atcommand_jumptoid }, // [Dino9021] + { AtCommand_JumpToId, "@gotoid", 20, atcommand_jumptoid }, // [Dino9021] + { AtCommand_JumpToId2, "@jumptoid2", 20, atcommand_jumptoid2 }, // [Dino9021] + { AtCommand_JumpToId2, "@warptoid2", 20, atcommand_jumptoid2 }, // [Dino9021] + { AtCommand_JumpToId2, "@gotoid2", 20, atcommand_jumptoid2 }, // [Dino9021] + { AtCommand_RecallId, "@recallid", 60, atcommand_recallid }, // [Dino9021] + { AtCommand_RecallId2, "@recallid2", 60, atcommand_recallid2 }, // [Dino9021] + { AtCommand_KickId, "@kickid", 99, atcommand_kickid }, // [Dino9021] + { AtCommand_KickId2, "@kickid2", 99, atcommand_kickid2 }, // [Dino9021] + { AtCommand_ReviveId, "@reviveid", 60, atcommand_reviveid }, // [Dino9021] + { AtCommand_ReviveId2, "@reviveid2", 60, atcommand_reviveid2 }, // [Dino9021] + { AtCommand_KillId, "@killid", 60, atcommand_killid }, // [Dino9021] + { AtCommand_KillId2, "@killid2", 60, atcommand_killid2 }, // [Dino9021] + { AtCommand_CharKillableId, "@charkillableid", 40, atcommand_charkillableid }, // [Dino9021] + { AtCommand_CharKillableId2, "@charkillableid2", 40, atcommand_charkillableid2 }, // [Dino9021] + { AtCommand_Sound, "@sound", 40, atcommand_sound }, + { AtCommand_UndisguiseAll, "@undisguiseall", 99, atcommand_undisguiseall }, + { AtCommand_DisguiseAll, "@disguiseall", 99, atcommand_disguiseall }, + { AtCommand_ChangeLook, "@changelook", 99, atcommand_changelook }, + { AtCommand_AutoLoot, "@autoloot", 10, atcommand_autoloot }, // Upa-Kun + { AtCommand_MobInfo, "@mobinfo", 1, atcommand_mobinfo }, // [Lupus] + { AtCommand_MobInfo, "@monsterinfo", 1, atcommand_mobinfo }, // [Lupus] + { AtCommand_MobInfo, "@mi", 1, atcommand_mobinfo }, // [Lupus] + { AtCommand_Adopt, "@adopt", 40, atcommand_adopt }, // [Veider] + { AtCommand_Version, "@version", 1, atcommand_version }, + + { AtCommand_MuteArea, "@mutearea", 99, atcommand_mutearea }, // MouseJstr + { AtCommand_MuteArea, "@stfu", 99, atcommand_mutearea }, // MouseJstr + { AtCommand_Shuffle, "@shuffle", 40, atcommand_shuffle }, // MouseJstr + { AtCommand_Rates, "@rates", 1, atcommand_rates }, // MouseJstr + + { AtCommand_ItemInfo, "@iteminfo", 1, atcommand_iteminfo }, // [Lupus] + { AtCommand_ItemInfo, "@ii", 1, atcommand_iteminfo }, // [Lupus] + { AtCommand_MapFlag, "@mapflag", 99, atcommand_mapflag }, // [Lupus] + + { AtCommand_Me, "@me", 20, atcommand_me }, //added by massdriller, code by lordalfa + { AtCommand_MonsterIgnore, "@monsterignore", 99, atcommand_monsterignore }, // [Valaris] + { AtCommand_FakeName, "@fakename", 20, atcommand_fakename }, // [Valaris] + { AtCommand_Size, "@size", 20, atcommand_size }, + { AtCommand_ShowExp, "@showexp", 10, atcommand_showexp}, + { AtCommand_ShowZeny, "@showzeny", 10, atcommand_showzeny}, + { AtCommand_ShowDelay, "@showdelay", 1, atcommand_showdelay}, + { AtCommand_AutoTrade, "@autotrade", 10, atcommand_autotrade }, // durf + { AtCommand_AutoTrade, "@at", 10, atcommand_autotrade }, + { AtCommand_ChangeGM, "@changegm", 10, atcommand_changegm }, // durf + { AtCommand_ChangeLeader, "@changeleader", 10, atcommand_changeleader }, // durf + { AtCommand_Invite, "@invite", 1, atcommand_invite }, // By LuzZza + { AtCommand_Duel, "@duel", 1, atcommand_duel }, // By LuzZza + { AtCommand_Leave, "@leave", 1, atcommand_leave }, // By LuzZza + { AtCommand_Accept, "@accept", 1, atcommand_accept }, // By LuzZza + { AtCommand_Reject, "@reject", 1, atcommand_reject }, // By LuzZza + { AtCommand_Away, "@away", 1, atcommand_away }, // [LuzZza] + { AtCommand_Away, "@aw", 1, atcommand_away }, // [LuzZza] + { AtCommand_Main, "@main", 1, atcommand_main }, // [LuzZza] + { AtCommand_Clone, "@clone", 50, atcommand_clone }, + { AtCommand_Clone, "@slaveclone", 50, atcommand_clone }, + { AtCommand_Clone, "@evilclone", 50, atcommand_clone }, // [Valaris] + +// add new commands before this line + { AtCommand_Unknown, NULL, 1, NULL } +}; + +/*========================================= + * Generic variables + *----------------------------------------- + */ +char atcmd_output[200]; +char atcmd_player_name[100]; +char atcmd_temp[100]; + +/*========================================== + * estr_lower (replace strlwr, non ANSI function that doesn't exist in all C compilator) + *------------------------------------------ + */ +char *estr_lower(char *str) +{ + int i; + + for (i=0; str[i]; i++) + if ((str[i] >= 65) && (str[i] <= 90)) + str[i] += 32; + return str; +} + +// compare function for sorting high to lowest +int hightolow_compare (const void * a, const void * b) +{ + return ( *(int*)b - *(int*)a ); +} + +// compare function for sorting lowest to highest +int lowtohigh_compare (const void * a, const void * b) +{ + return ( *(int*)a - *(int*)b ); +} + +//----------------------------------------------------------- +// Return the message string of the specified number by [Yor] +//----------------------------------------------------------- +char * msg_txt(int msg_number) { + if (msg_number >= 0 && msg_number < MAX_MSG && + msg_table[msg_number] != NULL && msg_table[msg_number][0] != '\0') + return msg_table[msg_number]; + + return "??"; +} + +//----------------------------------------------------------- +// Returns Players title (from msg_athena.conf) [Lupus] +//----------------------------------------------------------- +char * player_title_txt(int level) { + if (level < battle_config.title_lvl1) + return ""; //w/o any titles + + if (level >= battle_config.title_lvl8) + sprintf(atcmd_temp, msg_table[332], level); + else + if (level >= battle_config.title_lvl7) + sprintf(atcmd_temp, msg_table[331], level); + else + if (level >= battle_config.title_lvl6) + sprintf(atcmd_temp, msg_table[330], level); + else + if (level >= battle_config.title_lvl5) + sprintf(atcmd_temp, msg_table[329], level); + else + if (level >= battle_config.title_lvl4) + sprintf(atcmd_temp, msg_table[328], level); + else + if (level >= battle_config.title_lvl3) + sprintf(atcmd_temp, msg_table[327], level); + else + if (level >= battle_config.title_lvl2) + sprintf(atcmd_temp, msg_table[326], level); + else + sprintf(atcmd_temp, msg_table[325], level); //lvl1 + return atcmd_temp; +} + +//------------------------------------------------------------ +// E-mail check: return 0 (not correct) or 1 (valid). by [Yor] +//------------------------------------------------------------ +int e_mail_check(char *email) { + char ch; + char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +/*========================================== + * get_atcommand_level @コマンドの必要レベルを取得 + *------------------------------------------ + */ +int get_atcommand_level(const AtCommandType type) { + int i; + + for (i = 0; atcommand_info[i].type != AtCommand_None; i++) + if (atcommand_info[i].type == type) + return atcommand_info[i].level; + + return 100; // 100: command can not be used +} + +/*========================================== + *is_atcommand @コマンドに存在するかどうか確認する + *------------------------------------------ + */ +AtCommandType +is_atcommand(const int fd, struct map_session_data* sd, const char* message, int gmlvl) { + const char* str = message; + int s_flag = 0; + AtCommandInfo info; + AtCommandType type; + + nullpo_retr(AtCommand_None, sd); + + if (!battle_config.allow_atcommand_when_mute && + sd->sc_count && sd->sc_data[SC_NOCHAT].timer != -1) { + return AtCommand_Unknown; + } + + if (!message || !*message) + return AtCommand_None; + + memset(&info, 0, sizeof(info)); + str += strlen(sd->status.name); + while (*str && (isspace(*str) || (s_flag == 0 && *str == ':'))) { + if (*str == ':') + s_flag = 1; + str++; + } + if (!*str) + return AtCommand_None; + + type = atcommand(sd, gmlvl > 0 ? gmlvl : pc_isGM(sd), str, &info); + if (type != AtCommand_None) { + char command[100]; + const char* p = str; + memset(command, '\0', sizeof(command)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + while (*p && !isspace(*p)) + p++; + if (p - str >= sizeof(command)) // too long + return AtCommand_Unknown; + strncpy(command, str, p - str); + while (isspace(*p)) + p++; + + if (type == AtCommand_Unknown || info.proc == NULL) { + sprintf(atcmd_output, msg_table[153], command); // %s is Unknown Command. + clif_displaymessage(fd, atcmd_output); + } else { + if (info.proc(fd, sd, command, p) != 0) { + // Command can not be executed + sprintf(atcmd_output, msg_table[154], command); // %s failed. + clif_displaymessage(fd, atcmd_output); + } + } + + return info.type; + } + + return AtCommand_None; +} + +/*========================================== + * + *------------------------------------------ + */ +AtCommandType atcommand(struct map_session_data* sd, const int level, const char* message, struct AtCommandInfo* info) { + char* p = (char *)message; // it's 'char' and not 'const char' to have possibility to modify the first character if necessary + + if (!info) + return AtCommand_None; + if (battle_config.atc_gmonly != 0 && !level) // level = pc_isGM(sd) + return AtCommand_None; + if (!p || !*p) { + ShowError("at command message is empty\n"); + return AtCommand_None; + } + + if (*p == command_symbol) { // check first char. + char command[101]; + int i = 0; + memset(info, 0, sizeof(AtCommandInfo)); + sscanf(p, "%100s", command); + command[sizeof(command)-1] = '\0'; + + while (atcommand_info[i].type != AtCommand_Unknown) { + if (strcmpi(command+1, atcommand_info[i].command+1) == 0 && level >= atcommand_info[i].level) { + p[0] = atcommand_info[i].command[0]; // set correct first symbol for after. + break; + } + i++; + } + + if (atcommand_info[i].type == AtCommand_Unknown) { + // doesn't return Unknown if player is normal player (display the text, not display: unknown command) + if (level == 0) + return AtCommand_None; + else + return AtCommand_Unknown; + } else if((log_config.gm) && (atcommand_info[i].level >= log_config.gm)) { + log_atcommand(sd, message); + } + memcpy(info, &atcommand_info[i], sizeof atcommand_info[i]); + } else { + return AtCommand_None; + } + + return info->type; +} + +/*========================================== + * Read Message Data + *------------------------------------------ + */ +int msg_config_read(const char *cfgName) { + int msg_number; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + static int called = 1; + + if ((fp = fopen(cfgName, "r")) == NULL) { + ShowError("Messages file not found: %s\n", cfgName); + return 1; + } + + if ((--called) == 0) + memset(&msg_table[0], 0, sizeof(msg_table[0]) * MAX_MSG); + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + if (strcmpi(w1, "import") == 0) { + msg_config_read(w2); + } else { + msg_number = atoi(w1); + if (msg_number >= 0 && msg_number < MAX_MSG) { + if (msg_table[msg_number] != NULL) + aFree(msg_table[msg_number]); + msg_table[msg_number] = (char *)aCalloc(strlen(w2) + 1, sizeof (char)); + strcpy(msg_table[msg_number],w2); + // printf("message #%d: '%s'.\n", msg_number, msg_table[msg_number]); + } + } + } + } + fclose(fp); + + return 0; +} + +/*========================================== + * Cleanup Message Data + *------------------------------------------ + */ +void do_final_msg (void) { + int i; + for (i = 0; i < MAX_MSG; i++) + aFree(msg_table[i]); + return; +} + +/*========================================== + * + *------------------------------------------ + */ +static AtCommandInfo* get_atcommandinfo_byname(const char* name) { + int i; + + for (i = 0; atcommand_info[i].type != AtCommand_Unknown; i++) + if (strcmpi(atcommand_info[i].command + 1, name) == 0) + return &atcommand_info[i]; + + return NULL; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_config_read(const char *cfgName) { + char line[1024], w1[1024], w2[1024]; + AtCommandInfo* p; + FILE* fp; + + if ((fp = fopen(cfgName, "r")) == NULL) { + ShowError("At commands configuration file not found: %s\n", cfgName); + return 1; + } + + while (fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%1023[^:]:%1023s", w1, w2) != 2) + continue; + p = get_atcommandinfo_byname(w1); + if (p != NULL) { + p->level = atoi(w2); + if (p->level > 100) + p->level = 100; + else if (p->level < 0) + p->level = 0; + } + + if (strcmpi(w1, "import") == 0) + atcommand_config_read(w2); + else if (strcmpi(w1, "command_symbol") == 0 && w2[0] > 31 && + w2[0] != '/' && // symbol of standard ragnarok GM commands + w2[0] != '%' && // symbol of party chat speaking + w2[0] != '$' && // symbol of guild chat + w2[0] != '#') // symbol of charcommand + command_symbol = w2[0]; + } + fclose(fp); + + return 0; +} + +/*========================================== + * Duel organizing functions [LuzZza] + *------------------------------------------ + */ +void duel_msg_foreach_sameduel_wos( + const unsigned int did, struct map_session_data* sd, char *output) +{ + int i; + struct map_session_data* msg_sd; + + for(i=0; isession_data) + && msg_sd->state.auth && msg_sd->duel_group == did && msg_sd != sd) + + clif_disp_onlyself(msg_sd, output, strlen(output)); + + return; +} + +void duel_savetime(struct map_session_data* sd) { + + time_t timer; + struct tm *t; + + time(&timer); + t = localtime(&timer); + + pc_setglobalreg(sd, "PC_LAST_DUEL_TIME", + t->tm_mday*24*60 + t->tm_hour*60 + t->tm_min); + return; +} + +int duel_checktime(struct map_session_data* sd) { + + int diff; + time_t timer; + struct tm *t; + + time(&timer); + t = localtime(&timer); + + diff = t->tm_mday*24*60 + t->tm_hour*60 + t->tm_min - + pc_readglobalreg(sd, "PC_LAST_DUEL_TIME"); + + return !(diff >= 0 && diff < battle_config.duel_time_interval); +} + +int duel_showinfo( + const unsigned int did, struct map_session_data* sd) +{ + int i, p=0; + char output[256]; + struct map_session_data* msg_sd; + + if(duel_list[did].max_players_limit > 0) + sprintf(output, msg_txt(370), //" -- Duels: %d/%d, Members: %d/%d, Max players: %d --" + did, duel_count, + duel_list[did].members_count, + duel_list[did].members_count + duel_list[did].invites_count, + duel_list[did].max_players_limit); + else + sprintf(output, msg_txt(371), //" -- Duels: %d/%d, Members: %d/%d --" + did, duel_count, + duel_list[did].members_count, + duel_list[did].members_count + duel_list[did].invites_count); + + clif_disp_onlyself(sd, output, strlen(output)); + + for(i=0; isession_data) + && msg_sd->state.auth && msg_sd->duel_group == did) { + + sprintf(output, " %d. %s", ++p, (unsigned char *) msg_sd->status.name); + clif_disp_onlyself(sd, output, strlen(output)); + } + + return 0; +} + +int duel_create( + struct map_session_data* sd, const unsigned int maxpl) +{ + int i=1; + char output[256]; + + while(duel_list[i].members_count > 0 && i < MAX_DUEL) i++; + if(i == MAX_DUEL) return 0; + + duel_count++; + sd->duel_group = i; + duel_list[i].members_count++; + duel_list[i].invites_count = 0; + duel_list[i].max_players_limit = maxpl; + + strcpy(output, msg_txt(372)); // " -- Duel has been created (@invite/@leave) --" + clif_disp_onlyself(sd, output, strlen(output)); + + clif_set0199(sd->fd, 1); + //clif_misceffect2(&sd->bl, 159); + return i; +} + +int duel_invite( + const unsigned int did, struct map_session_data* sd, + struct map_session_data* target_sd) +{ + char output[256]; + + sprintf(output, msg_txt(373), // " -- Player %s invites %s to duel --" + (unsigned char *)sd->status.name, (unsigned char *)target_sd->status.name); + + duel_msg_foreach_sameduel_wos(did, sd, output); + + target_sd->duel_invite = did; + duel_list[did].invites_count++; + + // "Blue -- Player %s invites you to PVP duel (@accept/@reject) --" + sprintf(output, msg_txt(374), (unsigned char *)sd->status.name); + clif_GMmessage((struct block_list *)target_sd, output, strlen(output)+1, 3); + return 0; +} + +int duel_leave( + const unsigned int did, struct map_session_data* sd) +{ + int i; + char output[256]; + struct map_session_data* msg_sd; + + // " <- Player %s has left duel --" + sprintf(output, msg_txt(375), (unsigned char *)sd->status.name); + duel_msg_foreach_sameduel_wos(did, sd, output); + + duel_list[did].members_count--; + + if(duel_list[did].members_count == 0) { + for (i=0; isession_data) + && msg_sd->state.auth && msg_sd->duel_invite == did && msg_sd != sd) { + + msg_sd->duel_invite = 0; + } + + duel_count--; + } + + sd->duel_group = 0; + duel_savetime(sd); + clif_set0199(sd->fd, 0); + return 0; +} + +int duel_accept( + const unsigned int did, struct map_session_data* sd) +{ + char output[256]; + + // " -> Player %s has accepted duel --" + sprintf(output, msg_txt(376), (unsigned char *)sd->status.name); + duel_msg_foreach_sameduel_wos(did, sd, output); + + duel_list[did].members_count++; + sd->duel_group = sd->duel_invite; + duel_list[did].invites_count--; + sd->duel_invite = 0; + + clif_set0199(sd->fd, 1); + //clif_misceffect2(&sd->bl, 159); + return 0; +} + +int duel_reject( + const unsigned int did, struct map_session_data* sd) +{ + char output[256]; + + // " -- Player %s has rejected duel --" + sprintf(output, msg_txt(377), (unsigned char *)sd->status.name); + duel_msg_foreach_sameduel_wos(did, sd, output); + + duel_list[did].invites_count--; + sd->duel_invite = 0; + return 0; +} + +/*========================================== +// @ command processing functions + *------------------------------------------ + */ + +/*========================================== + * @send (used for testing packet sends from the client) + *------------------------------------------ + */ +int atcommand_send( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i,type=0; + int info[20]; + + if (!message || !*message || sscanf(message, "%x %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", &type, &info[1], &info[2], &info[3], &info[4], &info[5], &info[6], &info[7], &info[8], &info[9], &info[10], &info[11], &info[12], &info[13], &info[14], &info[15], &info[16], &info[17], &info[18], &info[19], &info[20]) < 1) { + clif_displaymessage(fd, "Please enter a packet number, and - if required - up to 20 additional values."); + return -1; + } + + if (type > 0 && type < MAX_PACKET_DB) { + + switch (type) + { + case 0x209: { + WFIFOHEAD(fd, packet_db[sd->packet_ver][type].len); + WFIFOW(fd,0) = 0x209; + WFIFOW(fd,2) = 2; + memcpy(WFIFOP(fd, 12), sd->status.name, NAME_LENGTH); + WFIFOSET(fd, packet_db[sd->packet_ver][type].len); + break; + } + case 0x1b1: + case 0x1c2: + //case xxx: + // add others here + // break; + default: { + WFIFOHEAD(fd, packet_db[sd->packet_ver][type].len); + WFIFOW(fd,0) = type; + for(i=1;i<=sizeof(info);i++) + if(info[i]) + WFIFOW(fd,i) = info[i]; + WFIFOSET(fd, packet_db[sd->packet_ver][type].len); + break; + } + } + + sprintf (atcmd_output, msg_table[258], type, type); + clif_displaymessage(fd, atcmd_output); + } else { + clif_displaymessage(fd, msg_table[259]); + } + + return 0; +} + +// @rura +/*========================================== + * + *------------------------------------------ + */ +int atcommand_rura( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char map_name[MAP_NAME_LENGTH]; + unsigned short mapindex; + int x = 0, y = 0; + int m = -1; + + nullpo_retr(-1, sd); + + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message || sscanf(message, "%15s %d %d", map_name, &x, &y) < 1) { + clif_displaymessage(fd, "Please, enter a map (usage: @warp/@rura/@mapmove )."); + return -1; + } + + if (x <= 0) + x = rand() % 399 + 1; + if (y <= 0) + y = rand() % 399 + 1; + + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < MAP_NAME_LENGTH-4) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + + mapindex = mapindex_name2id(map_name); + if (mapindex) + m = map_mapindex2mapid(mapindex); + + if (!mapindex || m < 0) { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + + if (x > 0 && x < 400 && y > 0 && y < 400) { + if (map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[247]); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[248]); + return -1; + } + if (pc_setpos(sd, mapindex, x, y, 3) == 0) + clif_displaymessage(fd, msg_table[0]); // Warped. + else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[2]); // Coordinates out of range. + return -1; + } + + return 0; +} + +/*========================================== + * Displays where a character is. Corrected version by Silent. [Skotlex] + *------------------------------------------ + */ +int atcommand_where( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + + int GM_level, pl_GM_level; + memset(atcmd_player_name, '\0', sizeof atcmd_player_name); + + if (!message || !*message || sscanf(message, "%23[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @where )."); + return -1; + } + pl_sd = map_nick2sd(atcmd_player_name); + nullpo_retr(-1, sd); + + if (pl_sd == NULL) + return -1; + + if(strncmp(sd->status.name,atcmd_player_name,NAME_LENGTH)==0) + return -1; + + GM_level = pc_isGM(sd);//also hide gms depending on settings in battle_athena.conf, show if they are aid [Kevin] + pl_GM_level = pc_isGM(pl_sd); + + if (battle_config.hide_GM_session) { + if(!(GM_level >= pl_GM_level)) { + if (!(battle_config.who_display_aid > 0 && pc_isGM(sd) >= battle_config.who_display_aid)) { + return -1; + } + } + } + + snprintf(atcmd_output, sizeof atcmd_output, "%s %s %d %d", + atcmd_player_name, mapindex_id2name(pl_sd->mapindex), pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_jumpto( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%99[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @jumpto/@warpto/@goto )."); + return -1; + } + + memset(atcmd_player_name, '\0', sizeof atcmd_player_name); + if (sscanf(message, "%23[^\n]", atcmd_player_name) < 1) + return -1; + if(strncmp(sd->status.name,atcmd_player_name,NAME_LENGTH)==0) //Yourself mate? Tsk tsk tsk. + return -1; + + if ((pl_sd = map_nick2sd(atcmd_player_name)) != NULL) { + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[247]); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[248]); + return -1; + } + pc_setpos(sd, pl_sd->mapindex, pl_sd->bl.x, pl_sd->bl.y, 3); + sprintf(atcmd_output, msg_table[4], atcmd_player_name); // Jump to %s + clif_displaymessage(fd, atcmd_output); + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_jump( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int x = 0, y = 0; + + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + sscanf(message, "%d %d", &x, &y); + + if (x <= 0) //If coordinates are 'wrong', random jump. + x = -1; + if (y <= 0) + y = -1; + if (sd->bl.m >= 0 && (map[sd->bl.m].flag.nowarp || map[sd->bl.m].flag.nowarpto) && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[248]); + return -1; + } + pc_setpos(sd, sd->mapindex, x, y, 3); + sprintf(atcmd_output, msg_table[5], sd->bl.x, sd->bl.y); // Jump to %d %d + clif_displaymessage(fd, atcmd_output); + return 0; +} + +/*========================================== + * @who3 = Player name, his location + *------------------------------------------ + */ +int atcommand_who3( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char temp0[100]; + struct map_session_data *pl_sd, **pl_allsd; + int i, j, count, users; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[NAME_LENGTH]; + + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + pl_allsd = map_getallusers(&users); + for (i = 0; i < users; i++) { + if ((pl_sd = pl_allsd[i])) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_INVISIBLE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, NAME_LENGTH); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + + if (battle_config.who_display_aid > 0 && pc_isGM(sd) >= battle_config.who_display_aid) { + sprintf(atcmd_output, "(CID:%d/AID:%d) ", pl_sd->status.char_id, pl_sd->status.account_id); + } else { + atcmd_output[0]=0; + } + //Player name + sprintf(temp0, msg_txt(333), pl_sd->status.name); + strcat(atcmd_output,temp0); + //Player title, if exists + if (pl_GM_level > 0) { + //sprintf(temp0, "(%s) ", player_title_txt(pl_GM_level) ); + sprintf(temp0, msg_txt(334), player_title_txt(pl_GM_level) ); + strcat(atcmd_output,temp0); + } + //Players Location: map x y + sprintf(temp0, msg_txt(338), mapindex_id2name(pl_sd->mapindex), pl_sd->bl.x, pl_sd->bl.y); + strcat(atcmd_output,temp0); + + clif_displaymessage(fd, atcmd_output); + count++; + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[28]); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_table[29]); // 1 player found. + else { + sprintf(atcmd_output, msg_table[30], count); // %d players found. + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * Player name, BLevel, Job, + *------------------------------------------ + */ +int atcommand_who2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char temp0[100]; + struct map_session_data *pl_sd, **pl_allsd; + int i, j, count, users; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[NAME_LENGTH]; + + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + pl_allsd = map_getallusers(&users); + for (i = 0; i < users; i++) { + if ((pl_sd = pl_allsd[i])) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_INVISIBLE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, NAME_LENGTH); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + //Players Name + //sprintf(atcmd_output, "Name: %s ", pl_sd->status.name); + sprintf(atcmd_output, msg_txt(333), pl_sd->status.name); + //Player title, if exists + if (pl_GM_level > 0) { + //sprintf(temp0, "(%s) ", player_title_txt(pl_GM_level) ); + sprintf(temp0, msg_txt(334), player_title_txt(pl_GM_level) ); + strcat(atcmd_output,temp0); + } + //Players Base Level / Job name + //sprintf(temp0, "| L:%d/%d | Job: %s", pl_sd->status.base_level, pl_sd->status.job_level, job_name(pl_sd->status.class_) ); + sprintf(temp0, msg_txt(337), pl_sd->status.base_level, pl_sd->status.job_level, job_name(pl_sd->status.class_) ); + strcat(atcmd_output,temp0); + + clif_displaymessage(fd, atcmd_output); + count++; + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_txt(28)); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_txt(29)); // 1 player found. + else { + sprintf(atcmd_output, msg_txt(30), count); // %d players found. + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * Player name, Playrs Party / Guild name + *------------------------------------------ + */ +int atcommand_who( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char temp0[100]; + struct map_session_data *pl_sd, **pl_allsd; + int i, j, count, users; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[NAME_LENGTH]; + struct guild *g; + struct party *p; + + nullpo_retr(-1, sd); + + memset(temp0, '\0', sizeof(temp0)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + pl_allsd = map_getallusers(&users); + for (i = 0; i < users; i++) { + if ((pl_sd = pl_allsd[i])) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_INVISIBLE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, NAME_LENGTH); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + g = guild_search(pl_sd->status.guild_id); + p = party_search(pl_sd->status.party_id); + //Players Name + //sprintf(atcmd_output, "Name: %s ", pl_sd->status.name); + sprintf(atcmd_output, msg_txt(333), pl_sd->status.name); + //Player title, if exists + if (pl_GM_level > 0) { + //sprintf(temp0, "(%s) ", player_title_txt(pl_GM_level) ); + sprintf(temp0, msg_txt(334), player_title_txt(pl_GM_level) ); + strcat(atcmd_output,temp0); + } + //Players Party if exists + if (p != NULL) { + //sprintf(temp0," | Party: '%s'", p->name); + sprintf(temp0, msg_txt(335), p->name); + strcat(atcmd_output,temp0); + } + //Players Guild if exists + if (g != NULL) { + //sprintf(temp0," | Guild: '%s'", g->name); + sprintf(temp0, msg_txt(336), g->name); + strcat(atcmd_output,temp0); + } + clif_displaymessage(fd, atcmd_output); + count++; + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_txt(28)); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_txt(29)); // 1 player found. + else { + sprintf(atcmd_output, msg_txt(30), count); // %d players found. + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whomap3( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd, **pl_allsd; + int i, count, users; + int pl_GM_level, GM_level; + int map_id; + char map_name[MAP_NAME_LENGTH]; + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message) + map_id = sd->bl.m; + else { + sscanf(message, "%15s", map_name); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < MAP_NAME_LENGTH-4) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + count = 0; + GM_level = pc_isGM(sd); + pl_allsd = map_getallusers(&users); + for (i = 0; i < users; i++) { + if ((pl_sd = pl_allsd[i])) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_INVISIBLE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + if (pl_sd->bl.m == map_id) { + if (pl_GM_level > 0) + sprintf(atcmd_output, "Name: %s (GM:%d) | Location: %s %d %d", pl_sd->status.name, pl_GM_level, mapindex_id2name(pl_sd->mapindex), pl_sd->bl.x, pl_sd->bl.y); + else + sprintf(atcmd_output, "Name: %s | Location: %s %d %d", pl_sd->status.name, mapindex_id2name(pl_sd->mapindex), pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, atcmd_output); + count++; + } + } + } + } + + if (count == 0) + sprintf(atcmd_output, msg_txt(54), map[map_id].name); // No player found in map '%s'. + else if (count == 1) + sprintf(atcmd_output, msg_txt(55), map[map_id].name); // 1 player found in map '%s'. + else { + sprintf(atcmd_output, msg_txt(56), count, map[map_id].name); // %d players found in map '%s'. + } + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whomap2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd, **pl_allsd; + int i, count, users; + int pl_GM_level, GM_level; + int map_id = 0; + char map_name[MAP_NAME_LENGTH]; + + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message) + map_id = sd->bl.m; + else { + sscanf(message, "%15s", map_name); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < MAP_NAME_LENGTH-4) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + count = 0; + GM_level = pc_isGM(sd); + pl_allsd = map_getallusers(&users); + for (i = 0; i < users; i++) { + if ((pl_sd = pl_allsd[i])) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_INVISIBLE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + if (pl_sd->bl.m == map_id) { + if (pl_GM_level > 0) + sprintf(atcmd_output, "Name: %s (GM:%d) | BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.name, pl_GM_level, pl_sd->status.base_level, job_name(pl_sd->status.class_), pl_sd->status.job_level); + else + sprintf(atcmd_output, "Name: %s | BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.name, pl_sd->status.base_level, job_name(pl_sd->status.class_), pl_sd->status.job_level); + clif_displaymessage(fd, atcmd_output); + count++; + } + } + } + } + + if (count == 0) + sprintf(atcmd_output, msg_table[54], map[map_id].name); // No player found in map '%s'. + else if (count == 1) + sprintf(atcmd_output, msg_table[55], map[map_id].name); // 1 player found in map '%s'. + else { + sprintf(atcmd_output, msg_table[56], count, map[map_id].name); // %d players found in map '%s'. + } + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whomap( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char temp0[100]; + char temp1[100]; + struct map_session_data *pl_sd, **pl_allsd; + int i, count, users; + int pl_GM_level, GM_level; + int map_id = 0; + char map_name[MAP_NAME_LENGTH]; + struct guild *g; + struct party *p; + + nullpo_retr(-1, sd); + + memset(temp0, '\0', sizeof(temp0)); + memset(temp1, '\0', sizeof(temp1)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message) + map_id = sd->bl.m; + else { + sscanf(message, "%15s", map_name); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < MAP_NAME_LENGTH-4) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + count = 0; + GM_level = pc_isGM(sd); + + pl_allsd = map_getallusers(&users); + for (i = 0; i < users; i++) { + if ((pl_sd = pl_allsd[i])) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_INVISIBLE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + if (pl_sd->bl.m == map_id) { + g = guild_search(pl_sd->status.guild_id); + if (g == NULL) + sprintf(temp1, "None"); + else + sprintf(temp1, "%s", g->name); + p = party_search(pl_sd->status.party_id); + if (p == NULL) + sprintf(temp0, "None"); + else + sprintf(temp0, "%s", p->name); + if (pl_GM_level > 0) + sprintf(atcmd_output, "Name: %s (GM:%d) | Party: '%s' | Guild: '%s'", pl_sd->status.name, pl_GM_level, temp0, temp1); + else + sprintf(atcmd_output, "Name: %s | Party: '%s' | Guild: '%s'", pl_sd->status.name, temp0, temp1); + clif_displaymessage(fd, atcmd_output); + count++; + } + } + } + } + + if (count == 0) + sprintf(atcmd_output, msg_table[54], map[map_id].name); // No player found in map '%s'. + else if (count == 1) + sprintf(atcmd_output, msg_table[55], map[map_id].name); // 1 player found in map '%s'. + else { + sprintf(atcmd_output, msg_table[56], count, map[map_id].name); // %d players found in map '%s'. + } + clif_displaymessage(fd, atcmd_output); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whogm( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char temp0[100]; + char temp1[100]; + struct map_session_data *pl_sd, **pl_allsd; + int i, j, count, users; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[NAME_LENGTH]; + struct guild *g; + struct party *p; + + nullpo_retr(-1, sd); + + memset(temp0, '\0', sizeof(temp0)); + memset(temp1, '\0', sizeof(temp1)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + pl_allsd = map_getallusers(&users); + for (i = 0; i < users; i++) { + if ((pl_sd = pl_allsd[i])) { + pl_GM_level = pc_isGM(pl_sd); + if (pl_GM_level > 0) { + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_INVISIBLE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, NAME_LENGTH); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + sprintf(atcmd_output, "Name: %s (GM:%d) | Location: %s %d %d", pl_sd->status.name, pl_GM_level, mapindex_id2name(pl_sd->mapindex), pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, atcmd_output); + sprintf(atcmd_output, " BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.base_level, job_name(pl_sd->status.class_), pl_sd->status.job_level); + clif_displaymessage(fd, atcmd_output); + g = guild_search(pl_sd->status.guild_id); + if (g == NULL) + sprintf(temp1, "None"); + else + sprintf(temp1, "%s", g->name); + p = party_search(pl_sd->status.party_id); + if (p == NULL) + sprintf(temp0, "None"); + else + sprintf(temp0, "%s", p->name); + sprintf(atcmd_output, " Party: '%s' | Guild: '%s'", temp0, temp1); + clif_displaymessage(fd, atcmd_output); + count++; + } + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[150]); // No GM found. + else if (count == 1) + clif_displaymessage(fd, msg_table[151]); // 1 GM found. + else { + sprintf(atcmd_output, msg_table[152], count); // %d GMs found. + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +int atcommand_whozeny( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd, **pl_allsd; + int i, j, count,c, users; + char match_text[100]; + char player_name[NAME_LENGTH]; + int *zeny; + int *counted; + + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + pl_allsd = map_getallusers(&users); + if (users < 1) + { + clif_displaymessage(fd, msg_table[28]); // No player found. + return 0; + } + zeny = (int *)aCallocA(users, sizeof(int)); + counted = (int *)aCallocA(users, sizeof(int)); + for (i = 0; i < users; i++) { + if ((pl_sd = pl_allsd[i])) { + memcpy(player_name, pl_sd->status.name, NAME_LENGTH); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + zeny[count]=pl_sd->status.zeny; + counted[i]=0; + count++; + } + } + } + + qsort(zeny, count, sizeof(int), hightolow_compare); + for (c = 0; c < count && c < 50; c++) { + if(!zeny[c]) + continue; + for (i = 0; i < users; i++) { + if(!zeny[c]) + continue; + if ((pl_sd = pl_allsd[i]) && counted[i]==0) { + if(pl_sd->status.zeny==zeny[c]) { + sprintf(atcmd_output, "Name: %s | Zeny: %d", pl_sd->status.name, pl_sd->status.zeny); + clif_displaymessage(fd, atcmd_output); + zeny[c]=0; + counted[i]=1; + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[28]); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_table[29]); // 1 player found. + else { + sprintf(atcmd_output, msg_table[30], count); // %d players found. + clif_displaymessage(fd, atcmd_output); + } + + aFree(zeny); + aFree(counted); + + return 0; +} + + +// cause random emote on all online players [Valaris] +int atcommand_happyhappyjoyjoy( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd, **pl_allsd; + int i,e, users; + + nullpo_retr(-1, sd); + + pl_allsd = map_getallusers(&users); + + for (i = 0; i < users; i++) { + if ((pl_sd = pl_allsd[i])) { + e=rand()%40; + if(e==34) + e = 0; + clif_emotion(&pl_sd->bl,e); + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_save( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + + pc_setsavepoint(sd, sd->mapindex, sd->bl.x, sd->bl.y); + if (sd->status.pet_id > 0 && sd->pd) + intif_save_petdata(sd->status.account_id, &sd->pet); + + chrif_save(sd,0); + + clif_displaymessage(fd, msg_table[6]); // Character data respawn point saved. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_load( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int m; + + nullpo_retr(-1, sd); + + m = map_mapindex2mapid(sd->status.save_point.map); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[249]); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[248]); + return -1; + } + + pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, 0); + clif_displaymessage(fd, msg_table[7]); // Warping to respawn point. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_speed( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int speed; + + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message) { + sprintf(atcmd_output, "Please, enter a speed value (usage: @speed <%d-%d>).", MIN_WALK_SPEED, MAX_WALK_SPEED); + clif_displaymessage(fd, atcmd_output); + return -1; + } + + speed = atoi(message); + if (speed >= MIN_WALK_SPEED && speed <= MAX_WALK_SPEED) { + sd->speed = speed; + //sd->walktimer = x; + //この文を追加 by れ + clif_updatestatus(sd, SP_SPEED); + clif_displaymessage(fd, msg_table[8]); // Speed changed. + } else { + sprintf(atcmd_output, "Please, enter a valid speed value (usage: @speed <%d-%d>).", MIN_WALK_SPEED, MAX_WALK_SPEED); + clif_displaymessage(fd, atcmd_output); + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_storage( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct storage *stor; //changes from Freya/Yor + nullpo_retr(-1, sd); + + if (sd->state.storage_flag) { + clif_displaymessage(fd, msg_table[250]); + return -1; + } + + if ((stor = account2storage2(sd->status.account_id)) != NULL && stor->storage_status == 1) { + clif_displaymessage(fd, msg_table[250]); + return -1; + } + + storage_storageopen(sd); + + return 0; +} + + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_guildstorage( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct storage *stor; //changes from Freya/Yor + nullpo_retr(-1, sd); + + if (sd->status.guild_id > 0) { + if (sd->state.storage_flag) { + clif_displaymessage(fd, msg_table[251]); + return -1; + } + if ((stor = account2storage2(sd->status.account_id)) != NULL && stor->storage_status == 1) { + clif_displaymessage(fd, msg_table[251]); + return -1; + } + storage_guild_storageopen(sd); + } else { + clif_displaymessage(fd, msg_table[252]); + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_option( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int param1 = 0, param2 = 0, param3 = 0; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%d %d %d", ¶m1, ¶m2, ¶m3) < 1 || param1 < 0 || param2 < 0 || param3 < 0) { + clif_displaymessage(fd, "Please, enter at least a option (usage: @option )."); + return -1; + } + + sd->opt1 = param1; + sd->opt2 = param2; + if (!(sd->status.option & CART_MASK) && param3 & CART_MASK) { + if (sd->status.class_ == JOB_BABY_MERCHANT) + clif_cart_itemlist(sd); + clif_cart_equiplist(sd); + clif_updatestatus(sd, SP_CARTINFO); + } + pc_setoption(sd, param3); + + clif_displaymessage(fd, msg_table[9]); // Options changed. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_hide( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + if (sd->status.option & OPTION_INVISIBLE) { + sd->status.option &= ~OPTION_INVISIBLE; + clif_displaymessage(fd, msg_table[10]); // Invisible: Off + } else { + sd->status.option |= OPTION_INVISIBLE; + clif_displaymessage(fd, msg_table[11]); // Invisible: On + } + clif_changeoption(&sd->bl); + + return 0; +} + +/*========================================== + * 転職する upperを指定すると転生や養子にもなれる + *------------------------------------------ + */ +int atcommand_jobchange( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int job = 0, upper = 0; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%d %d", &job, &upper) < 1) { + + int i, found = 0; + const struct { char name[16]; int id; } jobs[] = { + { "novice", 0 }, + { "swordsman", 1 }, + { "mage", 2 }, + { "archer", 3 }, + { "acolyte", 4 }, + { "merchant", 5 }, + { "thief", 6 }, + { "knight", 7 }, + { "priest", 8 }, + { "priestess", 8 }, + { "wizard", 9 }, + { "blacksmith", 10 }, + { "hunter", 11 }, + { "assassin", 12 }, + { "crusader", 14 }, + { "monk", 15 }, + { "sage", 16 }, + { "rogue", 17 }, + { "alchemist", 18 }, + { "bard", 19 }, + { "dancer", 20 }, + { "super novice", 23 }, + { "supernovice", 23 }, + { "high novice", 4001 }, + { "swordsman high", 4002 }, + { "mage high", 4003 }, + { "archer high", 4004 }, + { "acolyte high", 4005 }, + { "merchant high", 4006 }, + { "thief high", 4007 }, + { "lord knight", 4008 }, + { "high priest", 4009 }, + { "high priestess", 4009 }, + { "high wizard", 4010 }, + { "whitesmith", 4011 }, + { "sniper", 4012 }, + { "assassin cross", 4013 }, + { "paladin", 4015 }, + { "champion", 4016 }, + { "professor", 4017 }, + { "stalker", 4018 }, + { "creator", 4019 }, + { "clown", 4020 }, + { "gypsy", 4021 }, + { "baby novice", 4023 }, + { "baby swordsman", 4024 }, + { "baby mage", 4025 }, + { "baby archer", 4026 }, + { "baby acolyte", 4027 }, + { "baby merchant", 4028 }, + { "baby thief", 4029 }, + { "baby knight", 4030 }, + { "baby priest", 4031 }, + { "baby priestess", 4031 }, + { "baby wizard", 4032 }, + { "baby blacksmith",4033 }, + { "baby hunter", 4034 }, + { "baby assassin", 4035 }, + { "baby crusader", 4037 }, + { "baby monk", 4038 }, + { "baby sage", 4039 }, + { "baby rogue", 4040 }, + { "baby alchemist", 4041 }, + { "baby bard", 4042 }, + { "baby dancer", 4043 }, + { "super baby", 4045 }, + { "taekwon", 4046 }, + { "taekwon boy", 4046 }, + { "taekwon girl", 4046 }, + { "star gladiator", 4047 }, + { "soul linker", 4049 }, + }; + + for (i=0; i < (int)(sizeof(jobs) / sizeof(jobs[0])); i++) { + if (strncmpi(message, jobs[i].name, 16) == 0) { + job = jobs[i].id; + upper = 0; + found = 1; + break; + } + } + + if (!found) { + clif_displaymessage(fd, "Please, enter job ID (usage: @job/@jobchange )."); + return -1; + } + } + + if (job == 37 ||job == 45) + return 0; + + if ((job >= 0 && job < MAX_PC_CLASS)) + { + int j; + + for (j=0; j < MAX_INVENTORY; j++) { + if(sd->status.inventory[j].nameid>0 && sd->status.inventory[j].equip!=0) + pc_unequipitem(sd, j, 3); + } + if (pc_jobchange(sd, job, upper) == 0) + clif_displaymessage(fd, msg_table[12]); // Your job has been changed. + else { + clif_displaymessage(fd, msg_table[155]); // Impossible to change your job. + return -1; + } + } else { + clif_displaymessage(fd, "Please, enter a valid job ID (usage: @job/@jobchange )."); + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_die( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + clif_specialeffect(&sd->bl,450,1); + pc_damage(NULL, sd, sd->status.hp); + clif_displaymessage(fd, msg_table[13]); // A pity! You've died. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_kill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + nullpo_retr(-1, sd); + + memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); + + if (!message || !*message || sscanf(message, "%23[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @kill )."); + return -1; + } + + if ((pl_sd = map_nick2sd(atcmd_player_name)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can kill only lower or same level + pc_damage(NULL, pl_sd, pl_sd->status.hp); + clif_displaymessage(fd, msg_table[14]); // Character killed. + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_alive( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + if (pc_isdead(sd)) { + sd->status.hp = sd->status.max_hp; + sd->status.sp = sd->status.max_sp; + clif_skill_nodamage(&sd->bl,&sd->bl,ALL_RESURRECTION,4,1); + pc_setstand(sd); + if (battle_config.pc_invincible_time > 0) + pc_setinvincibletimer(sd, battle_config.pc_invincible_time); + clif_updatestatus(sd, SP_HP); + clif_updatestatus(sd, SP_SP); + clif_resurrection(&sd->bl, 1); + clif_displaymessage(fd, msg_table[16]); // You've been revived! It's a miracle! + return 0; + } + return -1; +} + +/*========================================== + * +kamic [LuzZza] + *------------------------------------------ + */ +int atcommand_kami( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + + unsigned long color=0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if(*(command + 5) != 'c') { + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a message (usage: @kami )."); + return -1; + } + + sscanf(message, "%199[^\n]", atcmd_output); + intif_GMmessage(atcmd_output, strlen(atcmd_output) + 1, (*(command + 5) == 'b') ? 0x10 : 0); + + } else { + + if(!message || !*message || (sscanf(message, "%lx %199[^\n]", &color, atcmd_output) < 2)) { + clif_displaymessage(fd, "Please, enter color and message (usage: @kamic )."); + return -1; + } + + if(color < 0 || color > 0xFFFFFF) { + clif_displaymessage(fd, "Invalid color."); + return -1; + } + + intif_announce(atcmd_output, strlen(atcmd_output) + 1, color, 0); + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_heal( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int hp = 0, sp = 0; // [Valaris] thanks to fov + nullpo_retr(-1, sd); + + sscanf(message, "%d %d", &hp, &sp); + + if (hp == 0 && sp == 0) { + hp = sd->status.max_hp - sd->status.hp; + sp = sd->status.max_sp - sd->status.sp; + } else { + if (hp > 0 && (hp > sd->status.max_hp || hp > (sd->status.max_hp - sd->status.hp))) // fix positiv overflow + hp = sd->status.max_hp - sd->status.hp; + else if (hp < 0 && (hp < -sd->status.max_hp || hp < (1 - sd->status.hp))) // fix negativ overflow + hp = 1 - sd->status.hp; + if (sp > 0 && (sp > sd->status.max_sp || sp > (sd->status.max_sp - sd->status.sp))) // fix positiv overflow + sp = sd->status.max_sp - sd->status.sp; + else if (sp < 0 && (sp < -sd->status.max_sp || sp < (1 - sd->status.sp))) // fix negativ overflow + sp = 1 - sd->status.sp; + } + + if (hp > 0) // display like heal + clif_heal(fd, SP_HP, hp); + else if (hp < 0) // display like damage + clif_damage(&sd->bl,&sd->bl, gettick(), 0, 0, -hp, 0 , 4, 0); + if (sp > 0) // no display when we lost SP + clif_heal(fd, SP_SP, sp); + + if (hp != 0 || sp != 0) { + pc_heal(sd, hp, sp); + if (hp >= 0 && sp >= 0) + clif_displaymessage(fd, msg_table[17]); // HP, SP recovered. + else + clif_displaymessage(fd, msg_table[156]); // HP or/and SP modified. + } else { + clif_displaymessage(fd, msg_table[157]); // HP and SP are already with the good value. + return -1; + } + + return 0; +} + +/*========================================== + * @item command (usage: @item ) (modified by [Yor] for pet_egg) + *------------------------------------------ + */ +int atcommand_item( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char item_name[100]; + int number = 0, item_id, flag; + struct item item_tmp; + struct item_data *item_data; + int get_count, i, pet_id; + nullpo_retr(-1, sd); + + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || sscanf(message, "%99s %d", item_name, &number) < 1) { + clif_displaymessage(fd, "Please, enter an item name/id (usage: @item [quantity])."); + return -1; + } + + if (number <= 0) + number = 1; + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (item_id >= 500) { + get_count = number; + // check pet egg + pet_id = search_petDB_index(item_id, PET_EGG); + if (item_data->type == 4 || item_data->type == 5 || + item_data->type == 7 || item_data->type == 8) { + get_count = 1; + } + for (i = 0; i < number; i += get_count) { + // if pet egg + if (pet_id >= 0) { + sd->catch_target_class = pet_db[pet_id].class_; + intif_create_pet(sd->status.account_id, sd->status.char_id, + (short)pet_db[pet_id].class_, (short)mob_db(pet_db[pet_id].class_)->lv, + (short)pet_db[pet_id].EggID, 0, (short)pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + // if not pet egg + } else { + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = item_id; + item_tmp.identify = 1; + + if ((flag = pc_additem((struct map_session_data*)sd, &item_tmp, get_count))) + clif_additem((struct map_session_data*)sd, 0, 0, flag); + } + } + + //Logs (A)dmins items [Lupus] + if(log_config.pick > 0 ) { + log_pick(sd, "A", 0, item_id, number, NULL); + } + //Logs + + clif_displaymessage(fd, msg_table[18]); // Item created. + } else { + clif_displaymessage(fd, msg_table[19]); // Invalid item ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_item2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct item item_tmp; + struct item_data *item_data; + char item_name[100]; + int item_id, number = 0; + int identify = 0, refine = 0, attr = 0; + int c1 = 0, c2 = 0, c3 = 0, c4 = 0; + int flag; + int loop, get_count, i; + nullpo_retr(-1, sd); + + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || sscanf(message, "%99s %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4) < 9) { + clif_displaymessage(fd, "Please, enter all informations (usage: @item2 "); + clif_displaymessage(fd, " )."); + return -1; + } + + if (number <= 0) + number = 1; + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (item_id > 500) { + loop = 1; + get_count = number; + if (item_data->type == 4 || item_data->type == 5 || + item_data->type == 7 || item_data->type == 8) { + loop = number; + get_count = 1; + if (item_data->type == 7) { + identify = 1; + refine = 0; + } + if (item_data->type == 8) + refine = 0; + if (refine > 10) + refine = 10; + } else { + identify = 1; + refine = attr = 0; + } + for (i = 0; i < loop; i++) { + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = item_id; + item_tmp.identify = identify; + item_tmp.refine = refine; + item_tmp.attribute = attr; + item_tmp.card[0] = c1; + item_tmp.card[1] = c2; + item_tmp.card[2] = c3; + item_tmp.card[3] = c4; + if ((flag = pc_additem(sd, &item_tmp, get_count))) + clif_additem(sd, 0, 0, flag); + } + + //Logs (A)dmins items [Lupus] + if(log_config.pick > 0 ) { + log_pick(sd, "A", 0, item_tmp.nameid, number, &item_tmp); + } + //Logs + + clif_displaymessage(fd, msg_table[18]); // Item created. + } else { + clif_displaymessage(fd, msg_table[19]); // Invalid item ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_itemreset( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + nullpo_retr(-1, sd); + + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount && sd->status.inventory[i].equip == 0) { + + //Logs (A)dmins items [Lupus] + if(log_config.pick > 0 ) { + log_pick(sd, "A", 0, sd->status.inventory[i].nameid, -sd->status.inventory[i].amount, &sd->status.inventory[i]); + } + //Logs + + pc_delitem(sd, i, sd->status.inventory[i].amount, 0); + } + } + clif_displaymessage(fd, msg_table[20]); // All of your items have been removed. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_itemcheck( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + pc_checkitem(sd); + + return 0; +} + +/*========================================== + * Atcommand @lvlup + *------------------------------------------ + */ +int atcommand_baselevelup( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int level=0, i=0; + nullpo_retr(-1, sd); + level = atoi(message); + + if (!message || !*message || !level) { + clif_displaymessage(fd, "Please, enter a level adjustement (usage: @lvup/@blevel/@baselvlup )."); + return -1; + } + + if (level > 0) { + if (sd->status.base_level == battle_config.max_base_level) { /* check for max level by Valaris */ + clif_displaymessage(fd, msg_table[47]); /* Base level can't go any higher. */ + return -1; + } /* End Addition */ + if ((unsigned int)level > battle_config.max_base_level || (unsigned int)level > (battle_config.max_base_level - sd->status.base_level)) // fix positiv overflow + level = battle_config.max_base_level - sd->status.base_level; + for (i = 1; i <= level; i++) + sd->status.status_point += (sd->status.base_level + i + 14) / 5; + sd->status.base_level += level; + clif_updatestatus(sd, SP_BASELEVEL); + clif_updatestatus(sd, SP_NEXTBASEEXP); + clif_updatestatus(sd, SP_STATUSPOINT); + status_calc_pc(sd, 0); + pc_heal(sd, sd->status.max_hp, sd->status.max_sp); + clif_misceffect(&sd->bl, 0); + clif_displaymessage(fd, msg_table[21]); /* Base level raised. */ + } else { + if (sd->status.base_level == 1) { + clif_displaymessage(fd, msg_table[158]); /* Base level can't go any lower. */ + return -1; + } + if (level < -(int)battle_config.max_base_level || level < (1 - (int)sd->status.base_level)) /* fix negativ overflow */ + level = 1 - sd->status.base_level; + if (sd->status.status_point > 0) { + for (i = 0; i > level; i--) + sd->status.status_point -= (sd->status.base_level + i + 14) / 5; + if (sd->status.status_point < 0) + sd->status.status_point = 0; + clif_updatestatus(sd, SP_STATUSPOINT); + } /* to add: remove status points from stats */ + sd->status.base_level += level; + clif_updatestatus(sd, SP_BASELEVEL); + clif_updatestatus(sd, SP_NEXTBASEEXP); + pc_resetskill(sd); /* Skills are reset */ + status_calc_pc(sd, 0); + clif_displaymessage(fd, msg_table[22]); /* Base level lowered. */ + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_joblevelup( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + unsigned int up_level = battle_config.max_job_level; + int level=0; + nullpo_retr(-1, sd); + + level = atoi(message); + + if (!message || !*message || !level) { + clif_displaymessage(fd, "Please, enter a level adjustement (usage: @joblvup/@jlevel/@joblvlup )."); + return -1; + } + + if ((sd->class_&MAPID_UPPERMASK) == MAPID_NOVICE) //Novice + up_level = 10; + else if ((sd->class_&MAPID_BASEMASK) == MAPID_NOVICE) //S. Novice + up_level = battle_config.max_sn_level; + else if (sd->class_&JOBL_UPPER && sd->class_&JOBL_2) + up_level = battle_config.max_adv_level; //2nd Adv Class + + if (level > 0) { + if (sd->status.job_level == up_level) { + clif_displaymessage(fd, msg_table[23]); // Job level can't go any higher. + return -1; + } + if ((unsigned int)level > up_level || (unsigned int)level > (up_level - sd->status.job_level)) // fix positiv overflow + level = up_level - sd->status.job_level; + sd->status.job_level += level; + clif_updatestatus(sd, SP_JOBLEVEL); + clif_updatestatus(sd, SP_NEXTJOBEXP); + sd->status.skill_point += level; + clif_updatestatus(sd, SP_SKILLPOINT); + status_calc_pc(sd, 0); + clif_misceffect(&sd->bl, 1); + clif_displaymessage(fd, msg_table[24]); // Job level raised. + } else { + if (sd->status.job_level == 1) { + clif_displaymessage(fd, msg_table[159]); // Job level can't go any lower. + return -1; + } + if (level < -(int)up_level || level < (1 - (int)sd->status.job_level)) // fix negativ overflow + level = 1 - sd->status.job_level; + sd->status.job_level += level; + clif_updatestatus(sd, SP_JOBLEVEL); + clif_updatestatus(sd, SP_NEXTJOBEXP); + if (sd->status.skill_point > 0) { + sd->status.skill_point += level; + if (sd->status.skill_point < 0) + sd->status.skill_point = 0; + clif_updatestatus(sd, SP_SKILLPOINT); + } // to add: remove status points from skills + status_calc_pc(sd, 0); + clif_displaymessage(fd, msg_table[25]); // Job level lowered. + } + + return 0; +} + +/*========================================== + * @help + *------------------------------------------ + */ +int atcommand_help( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char buf[2048], w1[2048], w2[2048]; + int i, gm_level; + FILE* fp; + nullpo_retr(-1, sd); + + memset(buf, '\0', sizeof(buf)); + + if ((fp = fopen(help_txt, "r")) != NULL) { + clif_displaymessage(fd, msg_table[26]); /* Help commands: */ + gm_level = pc_isGM(sd); + while(fgets(buf, sizeof(buf) - 1, fp) != NULL) { + if (buf[0] == '/' && buf[1] == '/') + continue; + for (i = 0; buf[i] != '\0'; i++) { + if (buf[i] == '\r' || buf[i] == '\n') { + buf[i] = '\0'; + break; + } + } + if (sscanf(buf, "%2047[^:]:%2047[^\n]", w1, w2) < 2) + clif_displaymessage(fd, buf); + else if (gm_level >= atoi(w1)) + clif_displaymessage(fd, w2); + } + fclose(fp); + } else { + clif_displaymessage(fd, msg_table[27]); /* File help.txt not found. */ + return -1; + } + + return 0; +} + +/*========================================== + * @help2 - Char commands [Kayla] + *------------------------------------------ + */ +int atcommand_help2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char buf[2048], w1[2048], w2[2048]; + int i, gm_level; + FILE* fp; + nullpo_retr(-1, sd); + + memset(buf, '\0', sizeof(buf)); + + if ((fp = fopen(help2_txt, "r")) != NULL) { + clif_displaymessage(fd, msg_table[26]); /* Help commands: */ + gm_level = pc_isGM(sd); + while(fgets(buf, sizeof(buf) - 1, fp) != NULL) { + if (buf[0] == '/' && buf[1] == '/') + continue; + for (i = 0; buf[i] != '\0'; i++) { + if (buf[i] == '\r' || buf[i] == '\n') { + buf[i] = '\0'; + break; + } + } + if (sscanf(buf, "%2047[^:]:%2047[^\n]", w1, w2) < 2) + clif_displaymessage(fd, buf); + else if (gm_level >= atoi(w1)) + clif_displaymessage(fd, w2); + } + fclose(fp); + } else { + clif_displaymessage(fd, msg_table[27]); /* File help.txt not found. */ + return -1; + } + + return 0; +} + + +/*========================================== + * @gm + *------------------------------------------ + */ +int atcommand_gm( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char password[100]; + nullpo_retr(-1, sd); + + memset(password, '\0', sizeof(password)); + + if (!message || !*message || sscanf(message, "%99[^\n]", password) < 1) { + clif_displaymessage(fd, "Please, enter a password (usage: @gm )."); + return -1; + } + + if (pc_isGM(sd)) { /* a GM can not use this function. only a normal player (become gm is not for gm!) */ + clif_displaymessage(fd, msg_table[50]); /* You already have some GM powers. */ + return -1; + } else + chrif_changegm(sd->status.account_id, password, strlen(password) + 1); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_pvpoff( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd, **pl_allsd; + int i, users; + nullpo_retr(-1, sd); + + if (battle_config.pk_mode) { //disable command if server is in PK mode [Valaris] + clif_displaymessage(fd, msg_table[52]); // This option cannot be used in PK Mode. + return -1; + } + + if (map[sd->bl.m].flag.pvp) { + map[sd->bl.m].flag.pvp = 0; + clif_send0199(sd->bl.m, 0); + + pl_allsd = map_getallusers(&users); + for (i = 0; i < users; i++) { //人数分ループ + if ((pl_sd = pl_allsd[i]) && sd->bl.m == pl_sd->bl.m) { + clif_pvpset(pl_sd, 0, 0, 2); + if (pl_sd->pvp_timer != -1) { + delete_timer(pl_sd->pvp_timer, pc_calc_pvprank_timer); + pl_sd->pvp_timer = -1; + } + } + } + clif_displaymessage(fd, msg_table[31]); // PvP: Off. + } else { + clif_displaymessage(fd, msg_table[160]); // PvP is already Off. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_pvpon( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd, **pl_allsd; + int i, users; + nullpo_retr(-1, sd); + + if (battle_config.pk_mode) { //disable command if server is in PK mode [Valaris] + clif_displaymessage(fd, msg_table[52]); // This option cannot be used in PK Mode. + return -1; + } + + if (!map[sd->bl.m].flag.pvp && !map[sd->bl.m].flag.nopvp) { + map[sd->bl.m].flag.pvp = 1; + clif_send0199(sd->bl.m, 1); + pl_allsd = map_getallusers(&users); + for (i = 0; i < users; i++) { + if ((pl_sd = pl_allsd[i]) && sd->bl.m == pl_sd->bl.m && pl_sd->pvp_timer == -1) { + pl_sd->pvp_timer = add_timer(gettick() + 200, pc_calc_pvprank_timer, pl_sd->bl.id, 0); + pl_sd->pvp_rank = 0; + pl_sd->pvp_lastusers = 0; + pl_sd->pvp_point = 5; + pl_sd->pvp_won = 0; + pl_sd->pvp_lost = 0; + } + } + clif_displaymessage(fd, msg_table[32]); // PvP: On. + } else { + clif_displaymessage(fd, msg_table[161]); // PvP is already On. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_gvgoff( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + if (map[sd->bl.m].flag.gvg) { + map[sd->bl.m].flag.gvg = 0; + clif_send0199(sd->bl.m, 0); + clif_displaymessage(fd, msg_table[33]); // GvG: Off. + } else { + clif_displaymessage(fd, msg_table[162]); // GvG is already Off. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_gvgon( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + if (!map[sd->bl.m].flag.gvg) { + map[sd->bl.m].flag.gvg = 1; + clif_send0199(sd->bl.m, 3); + clif_displaymessage(fd, msg_table[34]); // GvG: On. + } else { + clif_displaymessage(fd, msg_table[163]); // GvG is already On. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_model( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int hair_style = 0, hair_color = 0, cloth_color = 0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d %d %d", &hair_style, &hair_color, &cloth_color) < 1) { + sprintf(atcmd_output, "Please, enter at least a value (usage: @model ).", + MIN_HAIR_STYLE, MAX_HAIR_STYLE, MIN_HAIR_COLOR, MAX_HAIR_COLOR, MIN_CLOTH_COLOR, MAX_CLOTH_COLOR); + clif_displaymessage(fd, atcmd_output); + return -1; + } + + if (hair_style >= MIN_HAIR_STYLE && hair_style <= MAX_HAIR_STYLE && + hair_color >= MIN_HAIR_COLOR && hair_color <= MAX_HAIR_COLOR && + cloth_color >= MIN_CLOTH_COLOR && cloth_color <= MAX_CLOTH_COLOR) { + /* Removed because this check is TOO strange. [Skotlex] + //秒フ色変更 + if (cloth_color != 0 && sd->status.sex == 1 && (sd->status.class_ == JOB_ASSASSIN || sd->status.class_ == JOB_ROGUE)) { + //The hell? Why Rogue/Assassins can't... change their option if they have clothes colors and are males? o.O [Skotlex] + //秒フ色未実装職の判定 + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + */ + pc_changelook(sd, LOOK_HAIR, hair_style); + pc_changelook(sd, LOOK_HAIR_COLOR, hair_color); + pc_changelook(sd, LOOK_CLOTHES_COLOR, cloth_color); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. +// } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @dye && @ccolor + *------------------------------------------ + */ +int atcommand_dye(const int fd, struct map_session_data* sd, const char* command, const char* message) +{ + int cloth_color = 0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d", &cloth_color) < 1) { + sprintf(atcmd_output, "Please, enter a clothes color (usage: @dye/@ccolor ).", MIN_CLOTH_COLOR, MAX_CLOTH_COLOR); + clif_displaymessage(fd, atcmd_output); + return -1; + } + + if (cloth_color >= MIN_CLOTH_COLOR && cloth_color <= MAX_CLOTH_COLOR) { + pc_changelook(sd, LOOK_CLOTHES_COLOR, cloth_color); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @hairstyle && @hstyle + *------------------------------------------ + */ +int atcommand_hair_style(const int fd, struct map_session_data* sd, const char* command, const char* message) +{ + int hair_style = 0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d", &hair_style) < 1) { + sprintf(atcmd_output, "Please, enter a hair style (usage: @hairstyle/@hstyle ).", MIN_HAIR_STYLE, MAX_HAIR_STYLE); + clif_displaymessage(fd, atcmd_output); + return -1; + } + + if (hair_style >= MIN_HAIR_STYLE && hair_style <= MAX_HAIR_STYLE) { + /* Removed because this check is TOO strange. [Skotlex] + if (hair_style != 0 && sd->status.sex == 1 && (sd->status.class_ == JOB_ASSASSIN || sd->status.class_ == JOB_ROGUE)) { //??? + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + */ + pc_changelook(sd, LOOK_HAIR, hair_style); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. +// } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @charhairstyle by [MouseJstr] + *------------------------------------------ + */ +int +atcommand_charhairstyle(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + return 0; +} + +/*========================================== + * @haircolor && @hcolor + *------------------------------------------ + */ +int atcommand_hair_color(const int fd, struct map_session_data* sd, const char* command, const char* message) +{ + int hair_color = 0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d", &hair_color) < 1) { + sprintf(atcmd_output, "Please, enter a hair color (usage: @haircolor/@hcolor ).", MIN_HAIR_COLOR, MAX_HAIR_COLOR); + clif_displaymessage(fd, atcmd_output); + return -1; + } + + if (hair_color >= MIN_HAIR_COLOR && hair_color <= MAX_HAIR_COLOR) { + /* Removed for being such a strange check. [Skotlex] + if (hair_color != 0 && sd->status.sex == 1 && (sd->status.class_ == JOB_ASSASSIN || sd->status.class_ == JOB_ROGUE)) { + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + */ + pc_changelook(sd, LOOK_HAIR_COLOR, hair_color); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. +// } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @go [city_number or city_name] - Updated by Harbin + *------------------------------------------ + */ +int atcommand_go( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + int town; + char map_name[MAP_NAME_LENGTH]; + int m; + + const struct { char map[MAP_NAME_LENGTH]; int x, y; } data[] = { + { MAP_PRONTERA, 156, 191 }, // 0=Prontera + { MAP_MORROC, 156, 93 }, // 1=Morroc + { MAP_GEFFEN, 119, 59 }, // 2=Geffen + { MAP_PAYON, 162, 233 }, // 3=Payon + { MAP_ALBERTA, 192, 147 }, // 4=Alberta + { MAP_IZLUDE, 128, 114 }, // 5=Izlude + { MAP_ALDEBARAN, 140, 131 }, // 6=Al de Baran + { MAP_LUTIE, 147, 134 }, // 7=Lutie + { MAP_COMODO, 209, 143 }, // 8=Comodo + { MAP_YUNO, 157, 51 }, // 9=Yuno + { MAP_AMATSU, 198, 84 }, // 10=Amatsu + { MAP_GONRYUN, 160, 120 }, // 11=Gon Ryun + { MAP_UMBALA, 89, 157 }, // 12=Umbala + { MAP_NIFLHEIM, 21, 153 }, // 13=Niflheim + { MAP_LOUYANG, 217, 40 }, // 14=Lou Yang + { "new_1-1.gat", 53, 111 }, // 15=Training Grounds + { MAP_JAIL, 23, 61 }, // 16=Prison + { MAP_JAWAII, 249, 127 }, // 17=Jawaii + { MAP_AYOTHAYA, 151, 117 }, // 18=Ayothaya + { MAP_EINBROCH, 64, 200 }, // 19=Einbroch + { MAP_LIGHTHALZEN, 158, 92 }, // 20=Lighthalzen + { MAP_EINBECH, 70, 95 }, // 21=Einbech + { MAP_HUGEL, 96, 145 }, // 22=Hugel + }; + + nullpo_retr(-1, sd); + + if(map[sd->bl.m].flag.nogo) { + clif_displaymessage(sd->fd,"You can not use @go on this map."); + return 0; + } + + memset(map_name, '\0', sizeof(map_name)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + // get the number + town = atoi(message); + + // if no value, display all value + if (!message || !*message || sscanf(message, "%15s", map_name) < 1 || town < -3 || town >= (int)(sizeof(data) / sizeof(data[0]))) { + clif_displaymessage(fd, msg_table[38]); // Invalid location number or name. + clif_displaymessage(fd, msg_table[82]); // Please, use one of this number/name: + clif_displaymessage(fd, " 0=Prontera 1=Morroc 2=Geffen"); + clif_displaymessage(fd, " 3=Payon 4=Alberta 5=Izlude"); + clif_displaymessage(fd, " 6=Al De Baran 7=Lutie 8=Comodo"); + clif_displaymessage(fd, " 9=Yuno 10=Amatsu 11=Gon Ryun"); + clif_displaymessage(fd, " 12=Umbala 13=Niflheim 14=Lou Yang"); + clif_displaymessage(fd, " 15=Novice Grounds 16=Prison 17=Jawaii"); + clif_displaymessage(fd, " 18=Ayothaya 19=Einbroch 20=Lighthalzen"); + clif_displaymessage(fd, " 21=Einbech 22=Hugel"); + return -1; + } else { + // get possible name of the city and add .gat if not in the name + map_name[MAP_NAME_LENGTH-1] = '\0'; + for (i = 0; map_name[i]; i++) + map_name[i] = tolower(map_name[i]); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < MAP_NAME_LENGTH-4) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + // try to see if it's a name, and not a number (try a lot of possibilities, write errors and abbreviations too) + if (strncmp(map_name, "prontera.gat", 3) == 0) { // 3 first characters + town = 0; + } else if (strncmp(map_name, "morocc.gat", 3) == 0) { // 3 first characters + town = 1; + } else if (strncmp(map_name, "geffen.gat", 3) == 0) { // 3 first characters + town = 2; + } else if (strncmp(map_name, "payon.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "paion.gat", 3) == 0) { // writing error (3 first characters) + town = 3; + } else if (strncmp(map_name, "alberta.gat", 3) == 0) { // 3 first characters + town = 4; + } else if (strncmp(map_name, "izlude.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "islude.gat", 3) == 0) { // writing error (3 first characters) + town = 5; + } else if (strncmp(map_name, "aldebaran.gat", 3) == 0 || // 3 first characters + strcmp(map_name, "al.gat") == 0) { // al (de baran) + town = 6; + } else if (strncmp(map_name, "lutie.gat", 3) == 0 || // name of the city, not name of the map (3 first characters) + strcmp(map_name, "christmas.gat") == 0 || // name of the symbol + strncmp(map_name, "xmas.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "x-mas.gat", 3) == 0) { // writing error (3 first characters) + town = 7; + } else if (strncmp(map_name, "comodo.gat", 3) == 0) { // 3 first characters + town = 8; + } else if (strncmp(map_name, "yuno.gat", 3) == 0) { // 3 first characters + town = 9; + } else if (strncmp(map_name, "amatsu.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "ammatsu.gat", 3) == 0) { // writing error (3 first characters) + town = 10; + } else if (strncmp(map_name, "gonryun.gat", 3) == 0) { // 3 first characters + town = 11; + } else if (strncmp(map_name, "umbala.gat", 3) == 0) { // 3 first characters + town = 12; + } else if (strncmp(map_name, "niflheim.gat", 3) == 0) { // 3 first characters + town = 13; + } else if (strncmp(map_name, "louyang.gat", 3) == 0) { // 3 first characters + town = 14; + } else if (strncmp(map_name, "new_1-1.gat", 3) == 0 || // 3 first characters (or "newbies") + strncmp(map_name, "startpoint.gat", 3) == 0 || // name of the position (3 first characters) + strncmp(map_name, "begining.gat", 3) == 0) { // name of the position (3 first characters) + town = 15; + } else if (strncmp(map_name, "sec_pri.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "prison.gat", 3) == 0 || // name of the position (3 first characters) + strncmp(map_name, "jails.gat", 3) == 0) { // name of the position + town = 16; + } else if (strncmp(map_name, "jawaii.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "jawai.gat", 3) == 0) { // writing error (3 first characters) + town = 17; + } else if (strncmp(map_name, "ayothaya.gat", 2) == 0 || // 2 first characters + strncmp(map_name, "ayotaya.gat", 2) == 0) { // writing error (2 first characters) + town = 18; + } else if (strncmp(map_name, "einbroch.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "ainbroch.gat", 3) == 0) { // writing error (3 first characters) + town = 19; + } else if (strncmp(map_name, "lighthalzen.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "reichthalzen.gat", 3) == 0) { // 'alternative' name (3 first characters) + town = 20; + } else if (strncmp(map_name, "einbech.gat", 5) == 0) { // 5 first characters + town = 21; + } else if (strncmp(map_name, "hugel.gat", 3) == 0) { // 3 first characters + town = 22; + } + + if (town >= -3 && town <= -1) { + if (sd->status.memo_point[-town-1].map) { + m = map_mapindex2mapid(sd->status.memo_point[-town-1].map); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[247]); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[248]); + return -1; + } + if (pc_setpos(sd, sd->status.memo_point[-town-1].map, sd->status.memo_point[-town-1].x, sd->status.memo_point[-town-1].y, 3) == 0) { + clif_displaymessage(fd, msg_table[0]); // Warped. + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + sprintf(atcmd_output, msg_table[164], -town-1); // Your memo point #%d doesn't exist. + clif_displaymessage(fd, atcmd_output); + return -1; + } + } else if (town >= 0 && town < (int)(sizeof(data) / sizeof(data[0]))) { + m = map_mapname2mapid((char *)data[town].map); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[247]); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[248]); + return -1; + } + if (pc_setpos(sd, mapindex_name2id((char *)data[town].map), data[town].x, data[town].y, 3) == 0) { + clif_displaymessage(fd, msg_table[0]); // Warped. + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { // if you arrive here, you have an error in town variable when reading of names + clif_displaymessage(fd, msg_table[38]); // Invalid location number or name. + return -1; + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_monster( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char name[NAME_LENGTH]; + char monster[NAME_LENGTH]; + int mob_id; + int number = 0; + int x = 0, y = 0; + int count; + int i, j, k; + int mx, my, range; + nullpo_retr(-1, sd); + + memset(name, '\0', sizeof(name)); + memset(monster, '\0', sizeof(monster)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message) { + clif_displaymessage(fd, msg_table[80]); // Give a display name and monster name/id please. + return -1; + } + if (sscanf(message, "\"%23[^\"]\" %23s %d %d %d", name, monster, &number, &x, &y) > 1 || + sscanf(message, "%23s \"%23[^\"]\" %d %d %d", monster, name, &number, &x, &y) > 1) { + //All data can be left as it is. + } else if ((count=sscanf(message, "%23s %d %23s %d %d", monster, &number, name, &x, &y)) > 1) { + //Here, it is possible name was not given and we are using monster for it. + if (count < 3) //Blank mob's name. + name[0] = '\0'; + } else if (sscanf(message, "%23s %23s %d %d %d", name, monster, &number, &x, &y) > 1) { + //All data can be left as it is. + } else if (sscanf(message, "%23s", monster) > 0) { + //As before, name may be already filled. + name[0] = '\0'; + } else { + clif_displaymessage(fd, msg_table[80]); // Give a display name and monster name/id please. + return -1; + } + + if ((mob_id = mobdb_searchname(monster)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = mobdb_checkid(atoi(monster)); + + if (mob_id == 0) { + clif_displaymessage(fd, msg_table[40]); // Invalid monster ID or name. + return -1; + } + + if (mob_id == MOBID_EMPERIUM) { + clif_displaymessage(fd, msg_table[83]); // Cannot spawn emperium. + return -1; + } + + if (number <= 0) + number = 1; + + if (strlen(name) < 1) + strcpy(name, "--ja--"); + + // If value of atcommand_spawn_quantity_limit directive is greater than or equal to 1 and quantity of monsters is greater than value of the directive + if (battle_config.atc_spawn_quantity_limit >= 1 && number > battle_config.atc_spawn_quantity_limit) + number = battle_config.atc_spawn_quantity_limit; + + if (battle_config.etc_log) + ShowInfo("%s monster='%s' name='%s' id=%d count=%d (%d,%d)\n", command, monster, name, mob_id, number, x, y); + + count = 0; + range = (int)sqrt(number) / 2; + range = range * 2 + 5; // calculation of an odd number (+ 4 area around) + for (i = 0; i < number; i++) { + j = 0; + k = 0; + while(j++ < 8 && k == 0) { // try 8 times to spawn the monster (needed for close area) + if (x <= 0) + mx = sd->bl.x + (rand() % range - (range / 2)); + else + mx = x; + if (y <= 0) + my = sd->bl.y + (rand() % range - (range / 2)); + else + my = y; + k = mob_once_spawn((struct map_session_data*)sd, "this", mx, my, name, mob_id, 1, ""); + } + count += (k != 0) ? 1 : 0; + } + + if (count != 0) + if (number == count) + clif_displaymessage(fd, msg_table[39]); // All monster summoned! + else { + sprintf(atcmd_output, msg_table[240], count); // %d monster(s) summoned! + clif_displaymessage(fd, atcmd_output); + } + else { + clif_displaymessage(fd, msg_table[40]); // Invalid monster ID or name. + return -1; + } + + return 0; +} + +// small monster spawning [Valaris] +int atcommand_monstersmall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) { + char name[NAME_LENGTH] = ""; + char monster[NAME_LENGTH] = ""; + int mob_id = 0; + int number = 0; + int x = 0; + int y = 0; + int count; + int i; + + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(fd, "Give a monster name/id please."); + return -1; + } + + if (sscanf(message, "\"%23[^\"]\" %23s %d %d %d", name, monster, &number, &x, &y) < 2 && + sscanf(message, "%23s \"%23[^\"]\" %d %d %d", monster, name, &number, &x, &y) < 2 && + sscanf(message, "%23s %d %23s %d %d", monster, &number, name, &x, &y) < 1) { + clif_displaymessage(fd, "Give a monster name/id please."); + return -1; + } + + // If monster identifier/name argument is a name + if ((mob_id = mobdb_searchname(monster)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = atoi(monster); + + if (mob_id == 0) { + clif_displaymessage(fd, msg_table[40]); + return -1; + } + + if (mob_id == MOBID_EMPERIUM) { + clif_displaymessage(fd, msg_table[83]); + return -1; + } + + if (mobdb_checkid(mob_id) == 0) { + clif_displaymessage(fd, "Invalid monster ID"); // Invalid Monster ID. + return -1; + } + + if (number <= 0) + number = 1; + + if (strlen(name) < 1) + strcpy(name, "--ja--"); + + // If value of atcommand_spawn_quantity_limit directive is greater than or equal to 1 and quantity of monsters is greater than value of the directive + if (battle_config.atc_spawn_quantity_limit >= 1 && number > battle_config.atc_spawn_quantity_limit) + number = battle_config.atc_spawn_quantity_limit; + + count = 0; + for (i = 0; i < number; i++) { + int mx, my; + if (x <= 0) + mx = sd->bl.x + (rand() % 11 - 5); + else + mx = x; + if (y <= 0) + my = sd->bl.y + (rand() % 11 - 5); + else + my = y; + count += (mob_once_spawn((struct map_session_data*)sd, "this", mx, my, name, mob_id+MAX_MOB_DB, 1, "") != 0) ? 1 : 0; + } + + if (count != 0) + clif_displaymessage(fd, msg_table[39]); // Monster Summoned!! + else + clif_displaymessage(fd, msg_table[40]); // Invalid Monster ID. + + return 0; +} +// big monster spawning [Valaris] +int atcommand_monsterbig( + const int fd, struct map_session_data* sd, + const char* command, const char* message) { + char name[NAME_LENGTH] = ""; + char monster[NAME_LENGTH] = ""; + int mob_id = 0; + int number = 0; + int x = 0; + int y = 0; + int count; + int i; + + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(fd, "Give a monster name/id please."); + return -1; + } + + if (sscanf(message, "\"%23[^\"]\" %23s %d %d %d", name, monster, &number, &x, &y) < 2 && + sscanf(message, "%23s \"%23[^\"]\" %d %d %d", monster, name, &number, &x, &y) < 2 && + sscanf(message, "%23s %d %23s %d %d", monster, &number, name, &x, &y) < 1) { + clif_displaymessage(fd, "Give a monster name/id please."); + return -1; + } + + // If monster identifier/name argument is a name + if ((mob_id = mobdb_searchname(monster)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = atoi(monster); + + if (mob_id == 0) { + clif_displaymessage(fd, msg_table[40]); + return -1; + } + + if (mob_id == MOBID_EMPERIUM) { + clif_displaymessage(fd, msg_table[83]); + return -1; + } + + if (mobdb_checkid(mob_id) == 0) { + clif_displaymessage(fd, "Invalid monster ID"); // Invalid Monster ID. + return -1; + } + + if (number <= 0) + number = 1; + + if (strlen(name) < 1) + strcpy(name, "--ja--"); + + // If value of atcommand_spawn_quantity_limit directive is greater than or equal to 1 and quantity of monsters is greater than value of the directive + if (battle_config.atc_spawn_quantity_limit >= 1 && number > battle_config.atc_spawn_quantity_limit) + number = battle_config.atc_spawn_quantity_limit; + + count = 0; + for (i = 0; i < number; i++) { + int mx, my; + if (x <= 0) + mx = sd->bl.x + (rand() % 11 - 5); + else + mx = x; + if (y <= 0) + my = sd->bl.y + (rand() % 11 - 5); + else + my = y; + count += (mob_once_spawn((struct map_session_data*)sd, "this", mx, my, name, mob_id+2*MAX_MOB_DB, 1, "") != 0) ? 1 : 0; + } + + if (count != 0) + clif_displaymessage(fd, msg_table[39]); // Monster Summoned!! + else + clif_displaymessage(fd, msg_table[40]); // Invalid Monster ID. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int atkillmonster_sub(struct block_list *bl, va_list ap) { + struct mob_data *md; + int flag; + + nullpo_retr(0, ap); + nullpo_retr(0, md=(struct mob_data *)bl); + flag = va_arg(ap, int); + + if (flag) + mob_damage(NULL, md, md->hp, 2); + else + mob_delete(md); + + return 0; +} +void atcommand_killmonster_sub( + const int fd, struct map_session_data* sd, const char* message, + const int drop) +{ + int map_id; + char map_name[MAP_NAME_LENGTH]; + + if (!sd) return; + + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message || sscanf(message, "%15s", map_name) < 1) + map_id = sd->bl.m; + else { + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < MAP_NAME_LENGTH-4) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + map_foreachinmap(atkillmonster_sub, map_id, BL_MOB, drop); + + clif_displaymessage(fd, msg_table[165]); // All monsters killed! + + return; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_killmonster( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (!sd) return 0; + atcommand_killmonster_sub(fd, sd, message, 1); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_killmonster2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (!sd) return 0; + atcommand_killmonster_sub(fd, sd, message, 0); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_refine( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i, position = 0, refine = 0, current_position, final_refine; + int count; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d %d", &position, &refine) < 2) { + clif_displaymessage(fd, "Please, enter a position and a amount (usage: @refine <+/- amount>)."); + return -1; + } + + if (refine < -10) + refine = -10; + else if (refine > 10) + refine = 10; + else if (refine == 0) + refine = 1; + + count = 0; + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].nameid && // 該当個所の装備を精錬する + (sd->status.inventory[i].equip & position || + (sd->status.inventory[i].equip && !position))) { + final_refine = sd->status.inventory[i].refine + refine; + if (final_refine > 10) + final_refine = 10; + else if (final_refine < 0) + final_refine = 0; + if (sd->status.inventory[i].refine != final_refine) { + sd->status.inventory[i].refine = final_refine; + current_position = sd->status.inventory[i].equip; + pc_unequipitem(sd, i, 3); + clif_refine(fd, sd, 0, i, sd->status.inventory[i].refine); + clif_delitem(sd, i, 1); + clif_additem(sd, i, 1, 0); + pc_equipitem(sd, i, current_position); + clif_misceffect((struct block_list*)&sd->bl, 3); + count++; + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[166]); // No item has been refined! + else if (count == 1) + clif_displaymessage(fd, msg_table[167]); // 1 item has been refined! + else { + sprintf(atcmd_output, msg_table[168], count); // %d items have been refined! + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_produce( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char item_name[100]; + int item_id, attribute = 0, star = 0; + int flag = 0; + struct item_data *item_data; + struct item tmp_item; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || sscanf(message, "%99s %d %d", item_name, &attribute, &star) < 1) { + clif_displaymessage(fd, "Please, enter at least an item name/id (usage: @produce <# of very's>)."); + return -1; + } + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (itemdb_exists(item_id) && + (item_id <= 500 || item_id > 1099) && + (item_id < 4001 || item_id > 4148) && + (item_id < 7001 || item_id > 10019) && + itemdb_isequip(item_id)) { + if (attribute < MIN_ATTRIBUTE || attribute > MAX_ATTRIBUTE) + attribute = ATTRIBUTE_NORMAL; + if (star < MIN_STAR || star > MAX_STAR) + star = 0; + memset(&tmp_item, 0, sizeof tmp_item); + tmp_item.nameid = item_id; + tmp_item.amount = 1; + tmp_item.identify = 1; + tmp_item.card[0] = 0x00ff; + tmp_item.card[1] = ((star * 5) << 8) + attribute; + tmp_item.card[2] = GetWord(sd->char_id, 0); + tmp_item.card[3] = GetWord(sd->char_id, 1); + clif_produceeffect(sd, 0, item_id); // 製造エフェクトパケット + clif_misceffect(&sd->bl, 3); // 他人にも成功を通知 + + //Logs (A)dmins items [Lupus] + if(log_config.pick > 0 ) { + log_pick(sd, "A", 0, tmp_item.nameid, 1, &tmp_item); + } + //Logs + + if ((flag = pc_additem(sd, &tmp_item, 1))) + clif_additem(sd, 0, 0, flag); + } else { + if (battle_config.error_log) + ShowError("@produce NOT WEAPON [%d]\n", item_id); + if (item_id != 0 && itemdb_exists(item_id)) + sprintf(atcmd_output, msg_table[169], item_id, item_data->name); // This item (%d: '%s') is not an equipment. + else + sprintf(atcmd_output, msg_table[170]); // This item is not an equipment. + clif_displaymessage(fd, atcmd_output); + return -1; + } + + return 0; +} + +/*========================================== + * Sub-function to display actual memo points + *------------------------------------------ + */ +void atcommand_memo_sub(struct map_session_data* sd) { + int i; + + if (!sd) return; + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + clif_displaymessage(sd->fd, "Your actual memo positions are (except respawn point):"); + for (i = MIN_PORTAL_MEMO; i <= MAX_PORTAL_MEMO; i++) { + if (sd->status.memo_point[i].map) + sprintf(atcmd_output, "%d - %s (%d,%d)", i, mapindex_id2name(sd->status.memo_point[i].map), sd->status.memo_point[i].x, sd->status.memo_point[i].y); + else + sprintf(atcmd_output, msg_table[171], i); // %d - void + clif_displaymessage(sd->fd, atcmd_output); + } + + return; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_memo( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int position = 0; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d", &position) < 1) + atcommand_memo_sub(sd); + else { + if (position >= MIN_PORTAL_MEMO && position <= MAX_PORTAL_MEMO) { + if (sd->bl.m >= 0 && (map[sd->bl.m].flag.nowarpto || map[sd->bl.m].flag.nomemo) && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, msg_table[253]); + return -1; + } + if (sd->status.memo_point[position].map) { + sprintf(atcmd_output, msg_table[172], position, mapindex_id2name(sd->status.memo_point[position].map), sd->status.memo_point[position].x, sd->status.memo_point[position].y); // You replace previous memo position %d - %s (%d,%d). + clif_displaymessage(fd, atcmd_output); + } + sd->status.memo_point[position].map = map[sd->bl.m].index; + sd->status.memo_point[position].x = sd->bl.x; + sd->status.memo_point[position].y = sd->bl.y; + clif_skill_memo(sd, 0); + if (pc_checkskill(sd, AL_WARP) <= (position + 1)) + clif_displaymessage(fd, msg_table[173]); // Note: you don't have the 'Warp' skill level to use it. + atcommand_memo_sub(sd); + } else { + sprintf(atcmd_output, "Please, enter a valid position (usage: @memo ).", MIN_PORTAL_MEMO, MAX_PORTAL_MEMO); + clif_displaymessage(fd, atcmd_output); + atcommand_memo_sub(sd); + return -1; + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_gat( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int y; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + for (y = 2; y >= -2; y--) { + sprintf(atcmd_output, "%s (x= %d, y= %d) %02X %02X %02X %02X %02X", + map[sd->bl.m].name, sd->bl.x - 2, sd->bl.y + y, + map_getcell(sd->bl.m, sd->bl.x - 2, sd->bl.y + y, CELL_GETTYPE), + map_getcell(sd->bl.m, sd->bl.x - 1, sd->bl.y + y, CELL_GETTYPE), + map_getcell(sd->bl.m, sd->bl.x, sd->bl.y + y, CELL_GETTYPE), + map_getcell(sd->bl.m, sd->bl.x + 1, sd->bl.y + y, CELL_GETTYPE), + map_getcell(sd->bl.m, sd->bl.x + 2, sd->bl.y + y, CELL_GETTYPE)); + + clif_displaymessage(fd, atcmd_output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_packet( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + static int packet_mode = 0; + int i, x = 0, y = 0; + nullpo_retr(-1, sd); + + if (strstr(command, "packetmode")) { + packet_mode = atoi(message); + clif_displaymessage(fd, "Packet mode changed."); + return 0; + } + + if (!message || !*message || (i = sscanf(message, "%d %d", &x, &y)) < 1) { + clif_displaymessage(fd, "Please, enter a status type/flag (usage: @packet )."); + return -1; + } + if (i == 1) y = 1; + + switch (packet_mode) + { + case 0: + clif_status_change(&sd->bl, x, y); + break; + case 1: + sd->status.skill[sd->cloneskill_id].id=0; + sd->status.skill[sd->cloneskill_id].lv=0; + sd->status.skill[sd->cloneskill_id].flag=0; + sd->cloneskill_id = x; + sd->status.skill[x].id = x; + sd->status.skill[x].lv = skill_get_max(x); + sd->status.skill[x].flag = 13;//cloneskill flag + clif_skillinfoblock(sd); + break; + case 2: + clif_skill_nodamage(&sd->bl,&sd->bl,x,y,1); + case 3: + clif_skill_poseffect(&sd->bl,x,y,sd->bl.x,sd->bl.y,gettick()); + default: + break; + //added later + } + + return 0; +} + +/*========================================== + * @waterlevel [Skotlex] + *------------------------------------------ + */ + +int atcommand_waterlevel( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int newlevel; + if (!message || !*message || sscanf(message, "%d", &newlevel) < 1) { + sprintf(atcmd_output, "%s's current water level: %d", map[sd->bl.m].name, map_waterheight(map[sd->bl.m].name)); + clif_displaymessage(fd, atcmd_output); + return 0; + } + + if (map_setwaterheight(sd->bl.m, map[sd->bl.m].name, newlevel)) { + if (newlevel > 0) + sprintf(atcmd_output, "%s's water level changed to: %d", map[sd->bl.m].name, newlevel); + else + sprintf(atcmd_output, "Removed %s's water level information.", map[sd->bl.m].name); + clif_displaymessage(fd, atcmd_output); + } else { + sprintf(atcmd_output, "Failed to change %s's water level.", map[sd->bl.m].name); + clif_displaymessage(fd, atcmd_output); + } + return 0; +} + +/*========================================== + * @stpoint (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_statuspoint( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int point, new_status_point; + + if (!message || !*message || (point = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter a number (usage: @stpoint )."); + return -1; + } + + new_status_point = (int)sd->status.status_point + point; + if (point > 0 && (point > 0x7FFF || new_status_point > 0x7FFF)) // fix positiv overflow + new_status_point = 0x7FFF; + else if (point < 0 && (point < -0x7FFF || new_status_point < 0)) // fix negativ overflow + new_status_point = 0; + + if (new_status_point != (int)sd->status.status_point) { + sd->status.status_point = (short)new_status_point; + clif_updatestatus(sd, SP_STATUSPOINT); + clif_displaymessage(fd, msg_table[174]); // Number of status points changed! + } else { + if (point < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * @skpoint (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_skillpoint( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int point, new_skill_point; + nullpo_retr(-1, sd); + + if (!message || !*message || (point = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter a number (usage: @skpoint )."); + return -1; + } + + new_skill_point = (int)sd->status.skill_point + point; + if (point > 0 && (point > 0x7FFF || new_skill_point > 0x7FFF)) // fix positiv overflow + new_skill_point = 0x7FFF; + else if (point < 0 && (point < -0x7FFF || new_skill_point < 0)) // fix negativ overflow + new_skill_point = 0; + + if (new_skill_point != (int)sd->status.skill_point) { + sd->status.skill_point = (short)new_skill_point; + clif_updatestatus(sd, SP_SKILLPOINT); + clif_displaymessage(fd, msg_table[175]); // Number of skill points changed! + } else { + if (point < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * @zeny (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_zeny( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int zeny, new_zeny; + nullpo_retr(-1, sd); + + if (!message || !*message || (zeny = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter an amount (usage: @zeny )."); + return -1; + } + + new_zeny = sd->status.zeny + zeny; + if (zeny > 0 && (zeny > MAX_ZENY || new_zeny > MAX_ZENY)) // fix positiv overflow + new_zeny = MAX_ZENY; + else if (zeny < 0 && (zeny < -MAX_ZENY || new_zeny < 0)) // fix negativ overflow + new_zeny = 0; + + if (new_zeny != sd->status.zeny) { + sd->status.zeny = new_zeny; + clif_updatestatus(sd, SP_ZENY); + clif_displaymessage(fd, msg_table[176]); // Number of zenys changed! + } else { + if (zeny < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_param( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i, index, value = 0, new_value; + const char* param[] = { "@str", "@agi", "@vit", "@int", "@dex", "@luk", NULL }; + short* status[] = { + &sd->status.str, &sd->status.agi, &sd->status.vit, + &sd->status.int_, &sd->status.dex, &sd->status.luk + }; + nullpo_retr(-1, sd); + + memset(atcmd_output, '\0', sizeof(atcmd_output)); + + if (!message || !*message || sscanf(message, "%d", &value) < 1 || value == 0) { + sprintf(atcmd_output, "Please, enter a valid value (usage: @str,@agi,@vit,@int,@dex,@luk <+/-adjustement>)."); + clif_displaymessage(fd, atcmd_output); + return -1; + } + + index = -1; + for (i = 0; param[i] != NULL; i++) { + if (strcmpi(command, param[i]) == 0) { + index = i; + break; + } + } + if (index < 0 || index > MAX_STATUS_TYPE) { // normaly impossible... + sprintf(atcmd_output, "Please, enter a valid value (usage: @str,@agi,@vit,@int,@dex,@luk <+/-adjustement>)."); + clif_displaymessage(fd, atcmd_output); + return -1; + } + + new_value = (int)*status[index] + value; + if (value > 0 && (value > pc_maxparameter(sd) || new_value > pc_maxparameter(sd))) // fix positiv overflow + new_value = pc_maxparameter(sd); + else if (value < 0 && (value < -(int)pc_maxparameter(sd) || new_value < 1)) // fix negativ overflow + new_value = 1; + + if (new_value != (int)*status[index]) { + *status[index] = new_value; + clif_updatestatus(sd, SP_STR + index); + clif_updatestatus(sd, SP_USTR + index); + status_calc_pc(sd, 0); + clif_displaymessage(fd, msg_table[42]); // Stat changed. + } else { + if (value < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +//** Stat all by fritz (rewritten by [Yor]) +int atcommand_stat_all( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int index, count, value = 0, new_value; + short* status[] = { + &sd->status.str, &sd->status.agi, &sd->status.vit, + &sd->status.int_, &sd->status.dex, &sd->status.luk + }; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%d", &value) < 1 || value == 0) + value = pc_maxparameter(sd); + + count = 0; + for (index = 0; index < (int)(sizeof(status) / sizeof(status[0])); index++) { + + new_value = (int)*status[index] + value; + if (value > 0 && (value > pc_maxparameter(sd) || new_value > pc_maxparameter(sd))) // fix positiv overflow + new_value = pc_maxparameter(sd); + else if (value < 0 && (value < -(int)pc_maxparameter(sd) || new_value < 1)) // fix negative overflow + new_value = 1; + + if (new_value != (int)*status[index]) { + *status[index] = new_value; + clif_updatestatus(sd, SP_STR + index); + clif_updatestatus(sd, SP_USTR + index); + status_calc_pc(sd, 0); + count++; + } + } + + if (count > 0) // if at least 1 stat modified + clif_displaymessage(fd, msg_table[84]); // All stats changed! + else { + if (value < 0) + clif_displaymessage(fd, msg_table[177]); // Impossible to decrease a stat. + else + clif_displaymessage(fd, msg_table[178]); // Impossible to increase a stat. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_guildlevelup( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int level = 0; + short added_level; + struct guild *guild_info; + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%d", &level) < 1 || level == 0) { + clif_displaymessage(fd, "Please, enter a valid level (usage: @guildlvup/@guildlvlup <# of levels>)."); + return -1; + } + + if (sd->status.guild_id <= 0 || (guild_info = guild_search(sd->status.guild_id)) == NULL) { + clif_displaymessage(fd, msg_table[43]); // You're not in a guild. + return -1; + } + if (strcmp(sd->status.name, guild_info->master) != 0) { + clif_displaymessage(fd, msg_table[44]); // You're not the master of your guild. + return -1; + } + + added_level = (short)level; + if (level > 0 && (level > MAX_GUILDLEVEL || added_level > ((short)MAX_GUILDLEVEL - guild_info->guild_lv))) // fix positiv overflow + added_level = (short)MAX_GUILDLEVEL - guild_info->guild_lv; + else if (level < 0 && (level < -MAX_GUILDLEVEL || added_level < (1 - guild_info->guild_lv))) // fix negativ overflow + added_level = 1 - guild_info->guild_lv; + + if (added_level != 0) { + intif_guild_change_basicinfo(guild_info->guild_id, GBI_GUILDLV, &added_level, 2); + clif_displaymessage(fd, msg_table[179]); // Guild level changed. + } else { + clif_displaymessage(fd, msg_table[45]); // Guild level change failed. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_makeegg( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct item_data *item_data; + int id, pet_id; + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a monter/egg name/id (usage: @makeegg )."); + return -1; + } + + if ((item_data = itemdb_searchname(message)) != NULL) // for egg name + id = item_data->nameid; + else if ((id = mobdb_searchname(message)) == 0) // for monster name + id = atoi(message); + + pet_id = search_petDB_index(id, PET_CLASS); + if (pet_id < 0) + pet_id = search_petDB_index(id, PET_EGG); + if (pet_id >= 0) { + sd->catch_target_class = pet_db[pet_id].class_; + intif_create_pet( + sd->status.account_id, sd->status.char_id, + (short)pet_db[pet_id].class_, (short)mob_db(pet_db[pet_id].class_)->lv, + (short)pet_db[pet_id].EggID, 0, (short)pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + } else { + clif_displaymessage(fd, msg_table[180]); // The monter/egg name/id doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_hatch( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + if (sd->status.pet_id <= 0) + clif_sendegg(sd); + else { + clif_displaymessage(fd, msg_table[181]); // You already have a pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_petfriendly( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int friendly; + int t; + nullpo_retr(-1, sd); + + if (!message || !*message || (friendly = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a valid value (usage: @petfriendly <0-1000>)."); + return -1; + } + + if (sd->status.pet_id > 0 && sd->pd) { + if (friendly >= 0 && friendly <= 1000) { + if (friendly != sd->pet.intimate) { + t = sd->pet.intimate; + sd->pet.intimate = friendly; + clif_send_petstatus(sd); + if (battle_config.pet_status_support) { + if ((sd->pet.intimate > 0 && t <= 0) || + (sd->pet.intimate <= 0 && t > 0)) { + if (sd->bl.prev != NULL) + status_calc_pc(sd, 0); + else + status_calc_pc(sd, 2); + } + } + clif_displaymessage(fd, msg_table[182]); // Pet friendly value changed! + } else { + clif_displaymessage(fd, msg_table[183]); // Pet friendly is already the good value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[184]); // Sorry, but you have no pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_pethungry( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int hungry; + nullpo_retr(-1, sd); + + if (!message || !*message || (hungry = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a valid number (usage: @pethungry <0-100>)."); + return -1; + } + + if (sd->status.pet_id > 0 && sd->pd) { + if (hungry >= 0 && hungry <= 100) { + if (hungry != sd->pet.hungry) { + sd->pet.hungry = hungry; + clif_send_petstatus(sd); + clif_displaymessage(fd, msg_table[185]); // Pet hungry value changed! + } else { + clif_displaymessage(fd, msg_table[186]); // Pet hungry is already the good value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[184]); // Sorry, but you have no pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_petrename( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + if (sd->status.pet_id > 0 && sd->pd) { + if (sd->pet.rename_flag != 0) { + sd->pet.rename_flag = 0; + intif_save_petdata(sd->status.account_id, &sd->pet); + clif_send_petstatus(sd); + clif_displaymessage(fd, msg_table[187]); // You can now rename your pet. + } else { + clif_displaymessage(fd, msg_table[188]); // You can already rename your pet. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[184]); // Sorry, but you have no pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int +atcommand_recall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + + nullpo_retr(-1, sd); + + if (!message || !*message || sscanf(message, "%23[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @recall )."); + return -1; + } + + memset(atcmd_player_name, '\0', sizeof atcmd_player_name); + if(sscanf(message, "%23[^\n]", atcmd_player_name) < 1) + return -1; + if(strncmp(sd->status.name,atcmd_player_name,NAME_LENGTH)==0) + return -1; + + if ((pl_sd = map_nick2sd(atcmd_player_name)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can recall only lower or same level + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp somenone to your actual map."); + return -1; + } + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorized to warp this player from its actual map."); + return -1; + } + pc_setpos(pl_sd, sd->mapindex, sd->bl.x, sd->bl.y, 2); + sprintf(atcmd_output, msg_table[46], atcmd_player_name); // %s recalled! + clif_displaymessage(fd, atcmd_output); + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_revive( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + nullpo_retr(-1, sd); + + memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); + + if (!message || !*message || sscanf(message, "%23[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @revive )."); + return -1; + } + + if ((pl_sd = map_nick2sd(atcmd_player_name)) != NULL) { + if (pc_isdead(pl_sd)) { + pl_sd->status.hp = pl_sd->status.max_hp; + clif_skill_nodamage(&sd->bl,&sd->bl,ALL_RESURRECTION,4,1); + pc_setstand(pl_sd); + if (battle_config.pc_invincible_time > 0) + pc_setinvincibletimer(pl_sd, battle_config.pc_invincible_time); + clif_updatestatus(pl_sd, SP_HP); + clif_updatestatus(pl_sd, SP_SP); + clif_resurrection(&pl_sd->bl, 1); + clif_displaymessage(fd, msg_table[51]); // Character revived. + return 0; + } + return -1; + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * charblock command (usage: charblock ) + * This command do a definitiv ban on a player + *------------------------------------------ + */ +int atcommand_char_block( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + + memset(atcmd_player_name, '\0', sizeof(atcmd_player_name)); + + if (!message || !*message || sscanf(message, "%99[^\n]", atcmd_player_name) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charblock/@block )."); + return -1; + } + + // check player name + if (strlen(atcmd_player_name) < 4) { + clif_displaymessage(fd, msg_table[86]); // Sorry, but a player name have at least 4 characters. + return -1; + } else if (strlen(atcmd_player_name) > 23) { + clif_displaymessage(fd, msg_table[87]); // Sorry, but a player name have 23 characters maximum. + return -1; + } else { + chrif_char_ask_name(sd->status.account_id, atcmd_player_name, 1, 0, 0, 0, 0, 0, 0); // type: 1 - block + clif_displaymessage(fd, msg_table[88]); // Character name sends to char-server to ask it. + } + + return 0; +} + +/*========================================== + * charban command (usage: charban