Upgrade d2dbs network processing to use fdwatch system and utilize other common PvPGN code

This commit is contained in:
relesgoe 2021-02-25 22:00:31 -08:00
parent ac0cacfbd6
commit 16349d8163
22 changed files with 2670 additions and 1466 deletions

View file

@ -4,7 +4,7 @@ set(COMMON_SOURCES
bnettime.cpp bnettime.h bn_type.cpp bn_type.h bot_protocol.h conf.cpp
conf.h d2char_checksum.cpp d2char_checksum.h d2char_file.h
d2cs_bnetd_protocol.h d2cs_d2dbs_ladder.h d2cs_d2gs_character.h
d2cs_d2gs_protocol.h d2cs_protocol.h d2game_protocol.h elist.h
d2cs_d2gs_protocol.h d2cs_protocol.h d2dbs_d2gs_protocol.h d2game_protocol.h elist.h
eventlog.cpp eventlog.h fdwatch.cpp fdwatch_epoll.cpp fdwatch_epoll.h
fdwatch.h fdwatch_kqueue.cpp fdwatch_kqueue.h fdwatch_poll.cpp
fdwatch_poll.h fdwatch_select.cpp fdwatch_select.h fdwbackend.cpp

View file

@ -0,0 +1,158 @@
/*
* Copyright (C) 2001 faster (lqx@cic.tsinghua.edu.cn)
* Copyright (C) 2001 sousou (liupeng.cs@263.net)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef INCLUDED_D2DBS_D2GS_PROTOCOL_H
#define INCLUDED_D2DBS_D2GS_PROTOCOL_H
#include "common/bn_type.h"
namespace pvpgn
{
typedef struct
{
bn_short size;
bn_short type;
bn_int seqno;
} t_d2dbs_d2gs_header;
typedef struct
{
t_d2dbs_d2gs_header h;
} t_d2dbs_d2gs_generic;
typedef struct
{
bn_byte cclass;
} t_d2gs_d2dbs_connect;
/******************************************************/
#define D2GS_D2DBS_SAVE_DATA_REQUEST 0x30
typedef struct
{
t_d2dbs_d2gs_header h;
bn_short datatype;
bn_short datalen;
/* AccountName */
/* CharName */
/* RealmName */
/* data */
} t_d2gs_d2dbs_save_data_request;
#define D2GS_DATA_CHARSAVE 0x01
#define D2GS_DATA_PORTRAIT 0x02
/******************************************************/
/******************************************************/
#define D2DBS_D2GS_SAVE_DATA_REPLY 0x30
typedef struct
{
t_d2dbs_d2gs_header h;
bn_int result;
bn_short datatype;
/* CharName */
} t_d2dbs_d2gs_save_data_reply;
#define D2DBS_SAVE_DATA_SUCCESS 0
#define D2DBS_SAVE_DATA_FAILED 1
/******************************************************/
/******************************************************/
#define D2GS_D2DBS_GET_DATA_REQUEST 0x31
typedef struct
{
t_d2dbs_d2gs_header h;
bn_short datatype;
/* AccountName */
/* CharName */
/* RealmName */
} t_d2gs_d2dbs_get_data_request;
/******************************************************/
/******************************************************/
#define D2DBS_D2GS_GET_DATA_REPLY 0x31
typedef struct
{
t_d2dbs_d2gs_header h;
bn_int result;
bn_int charcreatetime;
bn_int allowladder;
bn_short datatype;
bn_short datalen;
/* CharName */
/* data */
} t_d2dbs_d2gs_get_data_reply;
#define D2DBS_GET_DATA_SUCCESS 0
#define D2DBS_GET_DATA_FAILED 1
#define D2DBS_GET_DATA_CHARLOCKED 2
/******************************************************/
/******************************************************/
#define D2GS_D2DBS_UPDATE_LADDER 0x32
typedef struct
{
t_d2dbs_d2gs_header h;
bn_int charlevel;
bn_int charexplow;
bn_int charexphigh;
bn_short charclass;
bn_short charstatus;
/* CharName */
/* RealmName */
} t_d2gs_d2dbs_update_ladder;
/******************************************************/
/******************************************************/
#define D2GS_D2DBS_CHAR_LOCK 0x33
typedef struct
{
t_d2dbs_d2gs_header h;
bn_int lockstatus;
/* AccountName */
/* CharName */
/* RealmName */
} t_d2gs_d2dbs_char_lock;
/******************************************************/
/******************************************************/
#define D2DBS_D2GS_ECHOREQUEST 0x34
typedef struct
{
t_d2dbs_d2gs_header h;
} t_d2dbs_d2gs_echorequest;
/******************************************************/
/******************************************************/
#define D2GS_D2DBS_ECHOREPLY 0x34
typedef struct
{
t_d2dbs_d2gs_header h;
} t_d2gs_d2dbs_echoreply;
/******************************************************/
}
#endif

View file

@ -64,6 +64,7 @@ namespace pvpgn
#define CLIENT_INITCONN_CLASS_D2CS 0x01
#define CLIENT_INITCONN_CLASS_D2GS 0x64
#define CLIENT_INITCONN_CLASS_D2CS_BNETD 0x65
#define CLIENT_INITCONN_CLASS_D2GS_D2DBS 0x65
#define CLIENT_INITCONN_CLASS_LOCALMACHINE 0x98 /* local computer connecting via 127.0.0.1 */
#endif

View file

@ -45,6 +45,7 @@ namespace pvpgn
pclass != packet_class_d2cs &&
pclass != packet_class_d2gs &&
pclass != packet_class_d2cs_bnetd &&
pclass != packet_class_d2dbs_d2gs &&
pclass != packet_class_w3route &&
pclass != packet_class_wolgameres)
{
@ -130,6 +131,8 @@ namespace pvpgn
return packet_class_d2gs;
case packet_class_d2cs_bnetd:
return packet_class_d2cs_bnetd;
case packet_class_d2dbs_d2gs:
return packet_class_d2dbs_d2gs;
case packet_class_w3route:
return packet_class_w3route;
case packet_class_wolgameres:
@ -171,6 +174,8 @@ namespace pvpgn
return "d2cs_bnetd";
case packet_class_d2cs:
return "d2cs";
case packet_class_d2dbs_d2gs:
return "d2dbs_d2gs";
case packet_class_w3route:
return "w3route";
case packet_class_wolgameres:
@ -205,6 +210,7 @@ namespace pvpgn
pclass != packet_class_d2cs &&
pclass != packet_class_d2gs &&
pclass != packet_class_d2cs_bnetd &&
pclass != packet_class_d2dbs_d2gs &&
pclass != packet_class_w3route &&
pclass != packet_class_wolgameres)
{
@ -287,6 +293,14 @@ namespace pvpgn
}
return bn_byte_get(packet->u.d2cs_client.h.type);
case packet_class_d2dbs_d2gs:
if (packet_get_size(packet) < sizeof(t_d2dbs_d2gs_header))
{
eventlog(eventlog_level_error, __FUNCTION__, "d2dbs_d2gs packet is shorter than header (len={})", packet_get_size(packet));
return 0;
}
return bn_byte_get(packet->u.d2dbs_d2gs.h.type);
case packet_class_w3route:
if (packet_get_size(packet) < sizeof(t_w3route_header))
{
@ -557,6 +571,9 @@ namespace pvpgn
case packet_class_d2cs_bnetd:
return "D2CS_BNETD";
case packet_class_d2dbs_d2gs:
return "D2DBS_D2GS";
case packet_class_w3route:
if (packet_get_size(packet) < sizeof(t_w3route_header))
{
@ -804,6 +821,9 @@ namespace pvpgn
case packet_class_d2cs_bnetd:
return "D2CS_BNETD";
case packet_class_d2dbs_d2gs:
return "D2DBS_D2GS";
case packet_class_w3route:
if (packet_get_size(packet) < sizeof(t_w3route_header))
{
@ -936,6 +956,15 @@ namespace pvpgn
bn_byte_set(&packet->u.d2cs_client.h.type, type);
return 0;
case packet_class_d2dbs_d2gs:
if (packet_get_size(packet) < sizeof(t_d2dbs_d2gs_header))
{
eventlog(eventlog_level_error, __FUNCTION__, "d2dbs_d2gs packet is shorter than header (len={})", packet_get_size(packet));
return -1;
}
bn_short_set(&packet->u.d2dbs_d2gs.h.type, type);
return 0;
case packet_class_w3route:
if (packet_get_size(packet) < sizeof(t_w3route_header))
{
@ -1000,6 +1029,9 @@ namespace pvpgn
case packet_class_d2cs:
size = (unsigned int)bn_short_get(packet->u.d2cs_client.h.size);
break;
case packet_class_d2dbs_d2gs:
size = (unsigned int)bn_short_get(packet->u.d2dbs_d2gs.h.size);
break;
case packet_class_w3route:
size = (unsigned int)bn_short_get(packet->u.w3route.h.size);
break;
@ -1093,6 +1125,9 @@ namespace pvpgn
case packet_class_d2cs_bnetd:
bn_short_set(&packet->u.d2cs_bnetd.h.size, size);
return 0;
case packet_class_d2dbs_d2gs:
bn_short_set(&packet->u.d2dbs_d2gs.h.size, size);
return 0;
case packet_class_w3route:
if (size != 0 && size < sizeof(t_w3route_header))
{
@ -1140,6 +1175,8 @@ namespace pvpgn
return sizeof(t_d2cs_d2gs_header);
case packet_class_d2cs_bnetd:
return sizeof(t_d2cs_bnetd_header);
case packet_class_d2dbs_d2gs:
return sizeof(t_d2dbs_d2gs_header);
case packet_class_w3route:
return sizeof(t_w3route_header);
case packet_class_wolgameres:

View file

@ -31,6 +31,7 @@
# include "d2cs_protocol.h"
# include "d2cs_d2gs_protocol.h"
# include "d2cs_bnetd_protocol.h"
# include "d2dbs_d2gs_protocol.h"
# include "wol_gameres_protocol.h"
#else
# define JUST_NEED_TYPES
@ -45,6 +46,7 @@
# include "d2cs_protocol.h"
# include "d2cs_d2gs_protocol.h"
# include "d2cs_bnetd_protocol.h"
# include "d2dbs_d2gs_protocol.h"
# include "wol_gameres_protocol.h"
# undef JUST_NEED_TYPES
#endif
@ -64,6 +66,7 @@ namespace pvpgn
packet_class_d2gs,
packet_class_d2cs,
packet_class_d2cs_bnetd,
packet_class_d2dbs_d2gs,
packet_class_w3route,
packet_class_wolgameres
} t_packet_class;
@ -286,6 +289,16 @@ namespace pvpgn
t_client_d2cs_convertcharreq client_d2cs_convertcharreq;
t_d2cs_client_convertcharreply d2cs_client_convertcharreply;
t_d2dbs_d2gs_generic d2dbs_d2gs;
t_d2gs_d2dbs_save_data_request d2gs_d2dbs_save_data_request;
t_d2dbs_d2gs_save_data_reply d2dbs_d2gs_save_data_reply;
t_d2gs_d2dbs_get_data_request d2gs_d2dbs_get_data_request;
t_d2dbs_d2gs_get_data_reply d2dbs_d2gs_get_data_reply;
t_d2gs_d2dbs_update_ladder d2gs_d2dbs_update_ladder;
t_d2gs_d2dbs_char_lock d2gs_d2dbs_char_lock;
t_d2dbs_d2gs_echorequest d2dbs_d2gs_echorequest;
t_d2gs_d2dbs_echoreply d2gs_d2dbs_echoreply;
t_client_friendslistreq client_friendslistreq;
t_server_friendslistreply server_friendslistreply;
t_client_friendinforeq client_friendinforeq;

View file

@ -1,7 +1,7 @@
set(D2DBS_SOURCES
charlock.cpp charlock.h cmdline.cpp cmdline.h d2ladder.cpp d2ladder.h
dbsdupecheck.cpp dbsdupecheck.h dbserver.cpp dbserver.h dbspacket.cpp
dbspacket.h handle_signal.cpp handle_signal.h main.cpp prefs.cpp
charlock.cpp charlock.h cmdline.cpp cmdline.h connection.cpp connection.h d2ladder.cpp d2ladder.h
dbsdupecheck.cpp dbsdupecheck.h dbserver.cpp dbserver.h handle_d2gs.cpp handle_d2gs.h handle_init.cpp
handle_init.h handle_signal.cpp handle_signal.h main.cpp pgsid.cpp pgsid.h prefs.cpp
prefs.h setup.h version.h ../win32/d2dbs_winmain.cpp ../win32/d2dbs_resource.h
../win32/d2dbs_resource.rc)
@ -11,6 +11,12 @@ else(WITH_WIN32_GUI)
add_executable(d2dbs ${D2DBS_SOURCES})
endif(WITH_WIN32_GUI)
target_include_directories(d2dbs
PRIVATE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include/>
$<INSTALL_INTERFACE:include/>
)
target_link_libraries(d2dbs PRIVATE common compat fmt::fmt win32 ${NETWORK_LIBRARIES})
install(TARGETS d2dbs DESTINATION ${SBINDIR})
if(WIN32 AND MSVC)

454
src/d2dbs/connection.cpp Normal file
View file

@ -0,0 +1,454 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "common/setup_before.h"
#include "setup.h"
#define CONNECTION_INTERNAL_ACCESS
#include "connection.h"
#include <algorithm>
#include <cstring>
#include <list>
#include "compat/psock.h"
#include "common/addr.h"
#include "common/eventlog.h"
#include "common/list.h"
#include "common/network.h"
#include "common/xalloc.h"
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#ifdef HAVE_WS2TCPIP_H
# include <Ws2tcpip.h>
#endif
#include "charlock.h"
#include "pgsid.h"
#include "prefs.h"
#include "common/setup_after.h"
namespace pvpgn
{
namespace d2dbs
{
static std::list<t_d2dbs_connection*> conn_head = {};
static std::list<t_d2dbs_connection*> conn_dead = {};
t_d2dbs_connection* conn_create(int sock, unsigned int real_local_addr, unsigned short real_local_port, unsigned int local_addr, unsigned short local_port, unsigned int addr, unsigned short port)
{
t_d2dbs_connection* conn = (t_d2dbs_connection*)xmalloc(sizeof(t_d2dbs_connection));
if (!conn)
{
return nullptr;
}
std::memset(conn, 0, sizeof(t_d2dbs_connection));
conn->sd = sock;
conn->ipaddr = addr;
conn->fdw_idx = -1;
conn->major = 0;
conn->minor = 0;
conn->type = 0;
conn->stats = 0;
conn->serverid = pgsid_get_id(addr);
conn->verified = 0;
{
std::memset(conn->serverip, 0, sizeof(conn->serverip));
struct in_addr in = {};
in.s_addr = htonl(addr);
char addrstr[INET_ADDRSTRLEN] = {};
inet_ntop(AF_INET, &(in), addrstr, sizeof(addrstr));
std::strncpy((char*)conn->serverip, addrstr, sizeof(conn->serverip) - 1);
}
conn->last_active = std::time(nullptr);
conn->cclass = conn_class_init;
conn->state = conn_state_initial;
conn->queues.outqueue = nullptr;
conn->queues.outsize = 0;
conn->queues.outsizep = 0;
conn->queues.inqueue = nullptr;
conn->queues.insize = 0;
conn_head.push_back(conn);
eventlog(eventlog_level_info, __FUNCTION__, "[{}] created connection serverip={} serverid={}", conn->sd, conn->serverip, conn->serverid);
return conn;
}
// Caller is responsible for removing connection from desired list
static void conn_destroy(t_d2dbs_connection* c)
{
if (c == nullptr)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return;
}
eventlog(eventlog_level_info, __FUNCTION__, "[{}] unlock all characters on gs {}({})", c->sd, c->serverip, c->serverid);
eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "unlock all characters on gs %s(%d)", c->serverip, c->serverid);
eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "close connection to gs on socket %d", c->sd);
cl_unlock_all_char_by_gsid(c->serverid);
// make sure the connection is closed
if (c->sd != -1)
{
// -1 means that the socket was already closed by conn_close()
fdwatch_del_fd(c->fdw_idx);
psock_shutdown(c->sd, PSOCK_SHUT_RDWR);
psock_close(c->sd);
}
// clear out the packet queues
if (c->queues.inqueue)
{
packet_del_ref(c->queues.inqueue);
}
queue_clear(&c->queues.outqueue);
eventlog(eventlog_level_info, __FUNCTION__, "[{}] closed {} connection", c->sd, conn_class_get_str(conn_get_class(c)));
xfree(c);
}
const char* conn_class_get_str(t_conn_class cclass)
{
switch (cclass)
{
case conn_class_init:
return "init";
case conn_class_d2gs:
return "d2gs";
default:
return "UNKNOWN";
}
}
t_conn_class conn_get_class(t_d2dbs_connection* c)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return conn_class_empty;
}
return c->cclass;
}
void conn_set_class(t_d2dbs_connection* c, t_conn_class cclass)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return;
}
c->cclass = cclass;
}
const char* conn_state_get_str(t_conn_state state)
{
switch (state)
{
case conn_state_empty:
return "empty";
case conn_state_initial:
return "initial";
case conn_state_loggedin:
return "loggedin";
case conn_state_destroy:
return "destroy";
default:
return "UNKNOWN";
}
}
t_conn_state conn_get_state(t_d2dbs_connection* c)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return conn_state_empty;
}
return c->state;
}
void conn_set_state(t_d2dbs_connection* c, t_conn_state state)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return;
}
// special case for destroying connections, add them to conn_dead list
if (state == conn_state_destroy && c->state != conn_state_destroy)
{
conn_dead.push_back(c);
}
else if (state != conn_state_destroy && c->state == conn_state_destroy)
{
auto it = std::find(conn_dead.begin(), conn_dead.end(), c);
if (it != conn_dead.end())
{
conn_dead.erase(it);
}
else
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] could not find connection in conn_dead", c->sd);
}
}
c->state = state;
}
void conn_clear_outqueue(t_d2dbs_connection* c)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return;
}
queue_clear(&c->queues.outqueue);
}
t_packet* conn_peek_outqueue(t_d2dbs_connection* c)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return nullptr;
}
if (c->queues.outqueue)
{
return queue_peek_packet((t_queue const* const*)&c->queues.outqueue);
}
else
{
return nullptr;
}
}
t_packet* conn_pull_outqueue(t_d2dbs_connection* c)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return nullptr;
}
if (c->queues.outsizep)
{
if (!(--c->queues.outsizep)) fdwatch_update_fd(c->fdw_idx, fdwatch_type_read);
return queue_pull_packet((t_queue**)&c->queues.outqueue);
}
return nullptr;
}
t_packet* conn_get_in_queue(t_d2dbs_connection* c)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return nullptr;
}
return c->queues.inqueue;
}
void conn_put_in_queue(t_d2dbs_connection* c, t_packet* packet)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return;
}
c->queues.inqueue = packet;
}
unsigned int conn_get_in_size(t_d2dbs_connection* c)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return 0;
}
return c->queues.insize;
}
void conn_set_in_size(t_d2dbs_connection* c, unsigned int size)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return;
}
c->queues.insize = size;
}
unsigned int conn_get_out_size(t_d2dbs_connection* c)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return 0;
}
return c->queues.outsize;
}
void conn_set_out_size(t_d2dbs_connection* c, unsigned int size)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return;
}
c->queues.outsize = size;
}
int conn_push_outqueue(t_d2dbs_connection* c, t_packet* packet)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return -1;
}
if (!packet)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL packet");
return -1;
}
queue_push_packet(&c->queues.outqueue, packet);
if (!c->queues.outsizep++)
{
fdwatch_update_fd(c->fdw_idx, fdwatch_type_read | fdwatch_type_write);
}
return 0;
}
int conn_add_fdwatch(t_d2dbs_connection* c, fdwatch_handler handle)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return -1;
}
c->fdw_idx = fdwatch_add_fd(c->sd, fdwatch_type_read, handle, c);
return c->fdw_idx;
}
void conn_close_read(t_d2dbs_connection* c)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return;
}
conn_set_state(c, conn_state_destroy);
/* only if we still got output packets remove the read availability
* from fdwatch, we are NOT allowed to remove all availability or
* remove it completely from fdwatch while handling read, also
* if the connection has no output packets is ok to leave it
* in read availability check cause it will be closed immediately
* in connlist_reap() anyway
*/
if (conn_peek_outqueue(c))
{
fdwatch_update_fd(c->fdw_idx, fdwatch_type_write);
}
}
const std::list<t_d2dbs_connection*>& connlist()
{
return conn_head;
}
void connlist_reap()
{
for (auto it = conn_dead.begin(); it != conn_dead.end(); )
{
t_d2dbs_connection* c = *it;
if (!conn_peek_outqueue(c))
{
conn_destroy(c); // also removes from fdwatch
conn_head.remove(c);
it = conn_dead.erase(it);
}
else
{
++it;
}
}
}
void connlist_destroy()
{
for (auto it = conn_head.begin(); it != conn_head.end(); )
{
t_d2dbs_connection* c = *it;
if (!conn_peek_outqueue(c))
{
conn_destroy(c); // also removes from fdwatch
conn_dead.remove(c);
it = conn_head.erase(it);
}
else
{
++it;
}
}
}
}
}

114
src/d2dbs/connection.h Normal file
View file

@ -0,0 +1,114 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef INCLUDED_CONNECTION_H
#define INCLUDED_CONNECTION_H
#include <ctime>
#include <list>
#include "common/queue.h"
#include "common/hashtable.h"
#include "common/packet.h"
#include "common/fdwatch.h"
namespace pvpgn
{
namespace d2dbs
{
enum t_conn_class
{
conn_class_empty,
conn_class_init,
conn_class_d2gs
};
enum t_conn_state
{
conn_state_empty,
conn_state_initial,
conn_state_loggedin,
conn_state_destroy
};
typedef struct
{
int sd; // tcp_sock
unsigned int ipaddr; // tcp_addr
int fdw_idx;
unsigned char major;
unsigned char minor;
unsigned char type;
unsigned char stats;
unsigned int serverid;
unsigned int verified;
unsigned char serverip[16];
std::time_t last_active;
#ifdef CONNECTION_INTERNAL_ACCESS
t_conn_class cclass;
t_conn_state state;
struct
{
t_queue* outqueue; /* packets waiting to be sent */
unsigned int outsize; /* amount sent from the current output packet */
unsigned int outsizep;
t_packet* inqueue; /* packet waiting to be processed */
unsigned int insize; /* amount received into the current input packet */
} queues; /* network queues and related data */
#endif
} t_d2dbs_connection;
t_d2dbs_connection* conn_create(int sock, unsigned int real_local_addr, unsigned short real_local_port, unsigned int local_addr, unsigned short local_port, unsigned int addr, unsigned short port);
const char* conn_class_get_str(t_conn_class cclass);
t_conn_class conn_get_class(t_d2dbs_connection* c);
void conn_set_class(t_d2dbs_connection* c, t_conn_class cclass);
const char* conn_state_get_str(t_conn_state state);
t_conn_state conn_get_state(t_d2dbs_connection* c);
void conn_set_state(t_d2dbs_connection* c, t_conn_state state);
void conn_clear_outqueue(t_d2dbs_connection* c);
t_packet* conn_peek_outqueue(t_d2dbs_connection* c);
t_packet* conn_pull_outqueue(t_d2dbs_connection* c);
t_packet* conn_get_in_queue(t_d2dbs_connection* c);
void conn_put_in_queue(t_d2dbs_connection* c, t_packet* packet);
unsigned int conn_get_in_size(t_d2dbs_connection* c);
void conn_set_in_size(t_d2dbs_connection* c, unsigned int size);
unsigned int conn_get_out_size(t_d2dbs_connection* c);
void conn_set_out_size(t_d2dbs_connection* c, unsigned int size);
int conn_push_outqueue(t_d2dbs_connection* c, t_packet* packet);
int conn_add_fdwatch(t_d2dbs_connection* c, fdwatch_handler handle);
void conn_close_read(t_d2dbs_connection* c);
const std::list<t_d2dbs_connection*>& connlist();
void connlist_reap();
void connlist_destroy();
}
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,10 @@
#ifndef INCLUDED_DBSERVER_H
#define INCLUDED_DBSERVER_H
#include "common/list.h"
#define STATUS_D2LADDER_FAILURE 20
#define STATUS_CHARLOCK_FAILURE 30
#define STATUS_FDWATCH_FAILURE 90
namespace pvpgn
{
@ -26,33 +29,25 @@ namespace pvpgn
namespace d2dbs
{
typedef struct {
int sd;
unsigned int ipaddr;
unsigned char major;
unsigned char minor;
unsigned char type;
unsigned char stats;
unsigned int serverid;
unsigned int verified;
unsigned char serverip[16];
int last_active;
int nCharsInReadBuffer;
int nCharsInWriteBuffer;
char ReadBuf[kBufferSize];
char WriteBuf[kBufferSize];
} t_d2dbs_connection;
#ifdef SERVER_INTERNAL_ACCESS
typedef struct raw_preset_d2gsid {
unsigned int ipaddr;
unsigned int d2gsid;
struct raw_preset_d2gsid *next;
} t_preset_d2gsid;
enum t_laddr_type
{
laddr_type_d2gs, // d2gs (port 4000)
};
int dbs_server_main(void);
int dbs_server_shutdown_connection(t_d2dbs_connection* conn);
// listen address structure
struct t_laddr_info
{
int ssocket; // TCP listen socket
t_laddr_type type;
};
extern t_list * dbs_server_connection_list;
#endif
int pre_server_startup();
bool server_process();
void post_server_shutdown(int status);
}

View file

@ -1,917 +0,0 @@
/*
* Copyright (C) 2001 sousou (liupeng.cs@263.net)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "common/setup_before.h"
#include "setup.h"
#include "dbspacket.h"
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <ctime>
#include <string>
#include <fmt/format.h>
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include "compat/strsep.h"
#include "compat/mkdir.h"
#include "compat/rename.h"
#include "compat/access.h"
#include "compat/statmacros.h"
#include "compat/psock.h"
#include "common/xstring.h"
#include "common/eventlog.h"
#include "common/d2cs_d2gs_character.h"
#include "common/d2char_checksum.h"
#include "common/xalloc.h"
#include "common/addr.h"
#include "prefs.h"
#include "charlock.h"
#include "d2ladder.h"
#include "common/setup_after.h"
namespace pvpgn
{
namespace d2dbs
{
static unsigned int dbs_packet_savedata_charsave(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int datalen);
static unsigned int dbs_packet_savedata_charinfo(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int datalen);
static unsigned int dbs_packet_getdata_charsave(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, long bufsize);
static unsigned int dbs_packet_getdata_charinfo(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int bufsize);
static unsigned int dbs_packet_echoreply(t_d2dbs_connection* conn);
static int dbs_packet_getdata(t_d2dbs_connection* conn);
static int dbs_packet_savedata(t_d2dbs_connection* conn);
static int dbs_packet_charlock(t_d2dbs_connection* conn);
static int dbs_packet_updateladder(t_d2dbs_connection* conn);
static int dbs_verify_ipaddr(char const * addrlist, t_d2dbs_connection * c);
static int dbs_packet_fix_charinfo(t_d2dbs_connection * conn, char * AccountName, char * CharName, char * charsave);
static void dbs_packet_set_charinfo_level(char * CharName, char * charinfo);
static unsigned int dbs_packet_savedata_charsave(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int datalen)
{
std::FILE * fd;
int checksum_header;
int checksum_calc;
strtolower(AccountName);
strtolower(CharName);
//check if checksum is ok
checksum_header = bn_int_get((bn_basic*)&data[D2CHARSAVE_CHECKSUM_OFFSET]);
checksum_calc = d2charsave_checksum((unsigned char *)data, datalen, D2CHARSAVE_CHECKSUM_OFFSET);
if (checksum_header != checksum_calc)
{
eventlog(eventlog_level_error, __FUNCTION__, "received ({}) and calculated({}) checksum do not match - discarding charsave", checksum_header, checksum_calc);
return 0;
}
std::string filename = fmt::format("{}/.{}.tmp", d2dbs_prefs_get_charsave_dir(), CharName);
fd = std::fopen(filename.c_str(), "wb");
if (!fd) {
eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename);
return 0;
}
std::size_t curlen = 0;
std::size_t leftlen = datalen;
while (curlen<datalen)
{
std::size_t writelen = leftlen > 2000 ? 2000 : leftlen;
std::size_t readlen = std::fwrite(data + curlen, 1, writelen, fd);
if (readlen <= 0) {
std::fclose(fd);
eventlog(eventlog_level_error, __FUNCTION__, "write() failed error : {}", std::strerror(errno));
return 0;
}
curlen += readlen;
leftlen -= readlen;
}
std::fclose(fd);
std::string bakfile = fmt::format("{}/{}", prefs_get_charsave_bak_dir(), CharName);
std::string savefile = fmt::format("{}/{}", d2dbs_prefs_get_charsave_dir(), CharName);
if (p_rename(savefile.c_str(), bakfile.c_str()) == -1) {
eventlog(eventlog_level_warn, __FUNCTION__, "error std::rename {} to {}", savefile, bakfile);
}
if (p_rename(filename.c_str(), savefile.c_str()) == -1) {
eventlog(eventlog_level_error, __FUNCTION__, "error std::rename {} to {}", filename, savefile);
return 0;
}
eventlog(eventlog_level_info, __FUNCTION__, "saved charsave {}(*{}) for gs {}({})", CharName, AccountName, conn->serverip, conn->serverid);
return datalen;
}
static unsigned int dbs_packet_savedata_charinfo(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int datalen)
{
std::FILE * fd;
struct stat statbuf;
strtolower(AccountName);
strtolower(CharName);
std::string filepath = fmt::format("{}/{}", prefs_get_charinfo_bak_dir(), AccountName);
if (stat(filepath.c_str(), &statbuf) == -1)
{
if (p_mkdir(filepath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0)
{
eventlog(eventlog_level_info, __FUNCTION__, "created charinfo directory: {}", filepath);
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "failed to create charinfo directory \"{}\" (errno: {})", filepath, errno);
return 0;
}
}
std::string filename = fmt::format("{}/{}/.{}.tmp", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName);
fd = std::fopen(filename.c_str(), "wb");
if (!fd) {
eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename);
return 0;
}
std::size_t curlen = 0;
std::size_t leftlen = datalen;
while (curlen < datalen)
{
std::size_t writelen = leftlen > 2000 ? 2000 : leftlen;
std::size_t readlen = std::fwrite(data + curlen, 1, writelen, fd);
if (readlen <= 0) {
std::fclose(fd);
eventlog(eventlog_level_error, __FUNCTION__, "write() failed error : {}", std::strerror(errno));
return 0;
}
curlen += readlen;
leftlen -= readlen;
}
std::fclose(fd);
std::string bakfile = fmt::format("{}/{}/{}", prefs_get_charinfo_bak_dir(), AccountName, CharName);
std::string savefile = fmt::format("{}/{}/{}", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName);
if (p_rename(savefile.c_str(), bakfile.c_str()) == -1) {
eventlog(eventlog_level_info, __FUNCTION__, "error std::rename {} to {}", savefile, bakfile);
}
if (p_rename(filename.c_str(), savefile.c_str()) == -1) {
eventlog(eventlog_level_error, __FUNCTION__, "error std::rename {} to {}", filename, savefile);
return 0;
}
eventlog(eventlog_level_info, __FUNCTION__, "saved charinfo {}(*{}) for gs {}({})", CharName, AccountName, conn->serverip, conn->serverid);
return datalen;
}
static unsigned int dbs_packet_getdata_charsave(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, long bufsize)
{
std::FILE * fd;
strtolower(AccountName);
strtolower(CharName);
std::string filename = fmt::format("{}/{}", d2dbs_prefs_get_charsave_dir(), CharName);
std::string filename_d2closed = fmt::format("{}/{}.d2s", d2dbs_prefs_get_charsave_dir(), CharName);
if ((access(filename.c_str(), F_OK) < 0) && (access(filename_d2closed.c_str(), F_OK) == 0))
{
if (std::rename(filename_d2closed.c_str(), filename.c_str()) != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "failed to rename file \"{}\" to \"{}\"", filename_d2closed, filename);
return 0;
}
}
fd = std::fopen(filename.c_str(), "rb");
if (!fd) {
eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename);
return 0;
}
std::fseek(fd, 0, SEEK_END);
long filesize = std::ftell(fd);
if (filesize == -1L)
{
std::fclose(fd);
eventlog(eventlog_level_error, __FUNCTION__, "ftell() failed");
return 0;
}
std::rewind(fd);
if (bufsize < filesize) {
std::fclose(fd);
eventlog(eventlog_level_error, __FUNCTION__, "not enough buffer");
return 0;
}
long curlen = 0;
std::size_t leftlen = filesize;
while (curlen < filesize)
{
std::size_t writelen = leftlen > 2000 ? 2000 : leftlen;
std::size_t readlen = std::fread(data + curlen, 1, writelen, fd);
if (readlen <= 0) {
std::fclose(fd);
eventlog(eventlog_level_error, __FUNCTION__, "read() failed error : {}", std::strerror(errno));
return 0;
}
leftlen -= readlen;
curlen += readlen;
}
std::fclose(fd);
eventlog(eventlog_level_info, __FUNCTION__, "loaded charsave {}(*{}) for gs {}({})", CharName, AccountName, conn->serverip, conn->serverid);
return filesize;
}
static unsigned int dbs_packet_getdata_charinfo(t_d2dbs_connection* conn, char * AccountName, char * CharName, char * data, unsigned int bufsize)
{
std::FILE * fd;
strtolower(AccountName);
strtolower(CharName);
std::string filename = fmt::format("{}/{}/{}", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName);
fd = std::fopen(filename.c_str(), "rb");
if (!fd) {
eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename);
return 0;
}
std::fseek(fd, 0, SEEK_END);
long filesize = std::ftell(fd);
std::rewind(fd);
if (filesize == -1) {
std::fclose(fd);
eventlog(eventlog_level_error, __FUNCTION__, "lseek() failed");
return 0;
}
if ((signed)bufsize < filesize) {
std::fclose(fd);
eventlog(eventlog_level_error, __FUNCTION__, "not enough buffer");
return 0;
}
std::size_t curlen = 0;
std::size_t leftlen = filesize;
while (curlen < filesize)
{
std::size_t writelen = leftlen > 2000 ? 2000 : leftlen;
std::size_t readlen = std::fread(data + curlen, 1, writelen, fd);
if (readlen <= 0)
{
std::fclose(fd);
eventlog(eventlog_level_error, __FUNCTION__, "read() failed error : {}", std::strerror(errno));
return 0;
}
leftlen -= readlen;
curlen += readlen;
}
std::fclose(fd);
eventlog(eventlog_level_info, __FUNCTION__, "loaded charinfo {}(*{}) for gs {}({})", CharName, AccountName, conn->serverip, conn->serverid);
return filesize;
}
static int dbs_packet_savedata(t_d2dbs_connection * conn)
{
unsigned short datatype;
unsigned short datalen;
unsigned int result;
char AccountName[MAX_USERNAME_LEN];
char CharName[MAX_CHARNAME_LEN];
char RealmName[MAX_REALMNAME_LEN];
t_d2gs_d2dbs_save_data_request * savecom;
char* readpos = conn->ReadBuf;
savecom = (t_d2gs_d2dbs_save_data_request *)readpos;
datatype = bn_short_get(savecom->datatype);
datalen = bn_short_get(savecom->datalen);
readpos += sizeof(*savecom);
std::strncpy(AccountName, readpos, MAX_USERNAME_LEN);
if (AccountName[MAX_USERNAME_LEN - 1] != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "max acccount name length exceeded");
return -1;
}
readpos += std::strlen(AccountName) + 1;
std::strncpy(CharName, readpos, MAX_CHARNAME_LEN);
if (CharName[MAX_CHARNAME_LEN - 1] != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "max char name length exceeded");
return -1;
}
readpos += std::strlen(CharName) + 1;
std::strncpy(RealmName, readpos, MAX_REALMNAME_LEN);
if (RealmName[MAX_REALMNAME_LEN - 1] != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "max realm name length exceeded");
return -1;
}
readpos += std::strlen(RealmName) + 1;
if (readpos + datalen != conn->ReadBuf + bn_short_get(savecom->h.size)) {
eventlog(eventlog_level_error, __FUNCTION__, "request packet size error");
return -1;
}
if (datatype == D2GS_DATA_CHARSAVE) {
if (dbs_packet_savedata_charsave(conn, AccountName, CharName, readpos, datalen) > 0 &&
dbs_packet_fix_charinfo(conn, AccountName, CharName, readpos)) {
result = D2DBS_SAVE_DATA_SUCCESS;
}
else {
datalen = 0;
result = D2DBS_SAVE_DATA_FAILED;
}
}
else if (datatype == D2GS_DATA_PORTRAIT) {
/* if level is > 255 , sets level to 255 */
dbs_packet_set_charinfo_level(CharName, readpos);
if (dbs_packet_savedata_charinfo(conn, AccountName, CharName, readpos, datalen) > 0) {
result = D2DBS_SAVE_DATA_SUCCESS;
}
else {
datalen = 0;
result = D2DBS_SAVE_DATA_FAILED;
}
}
else {
eventlog(eventlog_level_error, __FUNCTION__, "unknown data type {}", datatype);
return -1;
}
t_d2dbs_d2gs_save_data_reply* saveret;
std::size_t writelen = sizeof(*saveret) + std::strlen(CharName) + 1;
std::size_t buffer_available_len = sizeof(conn->WriteBuf) - conn->nCharsInWriteBuffer;
if (buffer_available_len < writelen)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] not enough space in write buffer (available space: {}, needed: {})", conn->sd, buffer_available_len, writelen);
return 0;
}
unsigned char* writepos = reinterpret_cast<unsigned char*>(conn->WriteBuf + conn->nCharsInWriteBuffer);
saveret = reinterpret_cast<t_d2dbs_d2gs_save_data_reply*>(writepos);
bn_short_set(&saveret->h.type, D2DBS_D2GS_SAVE_DATA_REPLY);
bn_short_set(&saveret->h.size, writelen);
bn_int_set(&saveret->h.seqno, bn_int_get(savecom->h.seqno));
bn_short_set(&saveret->datatype, bn_short_get(savecom->datatype));
bn_int_set(&saveret->result, result);
writepos += sizeof(*saveret);
std::memcpy(writepos, CharName, std::strlen(CharName) + 1);
conn->nCharsInWriteBuffer += writelen;
return 1;
}
static unsigned int dbs_packet_echoreply(t_d2dbs_connection * conn)
{
conn->last_active = std::time(NULL);
return 1;
}
static int dbs_packet_getdata(t_d2dbs_connection * conn)
{
unsigned short datatype;
unsigned short datalen;
unsigned int result;
char AccountName[MAX_USERNAME_LEN];
char CharName[MAX_CHARNAME_LEN];
char RealmName[MAX_REALMNAME_LEN];
t_d2gs_d2dbs_get_data_request * getcom;
t_d2dbs_d2gs_get_data_reply * getret;
char * readpos;
char * writepos;
char databuf[kBufferSize];
t_d2charinfo_file charinfo;
unsigned short charinfolen;
unsigned int gsid;
readpos = conn->ReadBuf;
getcom = (t_d2gs_d2dbs_get_data_request *)readpos;
datatype = bn_short_get(getcom->datatype);
readpos += sizeof(*getcom);
std::strncpy(AccountName, readpos, MAX_USERNAME_LEN);
if (AccountName[MAX_USERNAME_LEN - 1] != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "max account name length exceeded");
return -1;
}
readpos += std::strlen(AccountName) + 1;
std::strncpy(CharName, readpos, MAX_CHARNAME_LEN);
if (CharName[MAX_CHARNAME_LEN - 1] != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "max char name length exceeded");
return -1;
}
readpos += std::strlen(CharName) + 1;
std::strncpy(RealmName, readpos, MAX_REALMNAME_LEN);
if (RealmName[MAX_REALMNAME_LEN - 1] != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "max realm name length exceeded");
return -1;
}
readpos += std::strlen(RealmName) + 1;
if (readpos != conn->ReadBuf + bn_short_get(getcom->h.size)) {
eventlog(eventlog_level_error, __FUNCTION__, "request packet size error");
return -1;
}
writepos = conn->WriteBuf + conn->nCharsInWriteBuffer;
getret = (t_d2dbs_d2gs_get_data_reply *)writepos;
datalen = 0;
if (datatype == D2GS_DATA_CHARSAVE) {
if (cl_query_charlock_status((unsigned char*)CharName, (unsigned char*)RealmName, &gsid) != 0) {
eventlog(eventlog_level_warn, __FUNCTION__, "char {}(*{})@{} is already locked on gs {}", CharName, AccountName, RealmName, gsid);
result = D2DBS_GET_DATA_CHARLOCKED;
}
else if (cl_lock_char((unsigned char*)CharName, (unsigned char*)RealmName, conn->serverid) != 0) {
eventlog(eventlog_level_error, __FUNCTION__, "failed to lock char {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid);
result = D2DBS_GET_DATA_CHARLOCKED;
}
else {
eventlog(eventlog_level_info, __FUNCTION__, "lock char {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid);
datalen = dbs_packet_getdata_charsave(conn, AccountName, CharName, databuf, kBufferSize);
if (datalen > 0) {
result = D2DBS_GET_DATA_SUCCESS;
charinfolen = dbs_packet_getdata_charinfo(conn, AccountName, CharName, (char *)&charinfo, sizeof(charinfo));
if (charinfolen > 0) {
result = D2DBS_GET_DATA_SUCCESS;
}
else {
result = D2DBS_GET_DATA_FAILED;
if (cl_unlock_char((unsigned char*)CharName, (unsigned char*)RealmName, gsid) != 0) {
eventlog(eventlog_level_error, __FUNCTION__, "failed to unlock char {}(*{})@{} for gs {}({})", CharName, \
AccountName, RealmName, conn->serverip, conn->serverid);
}
else {
eventlog(eventlog_level_info, __FUNCTION__, "unlock char {}(*{})@{} for gs {}({})", CharName, \
AccountName, RealmName, conn->serverip, conn->serverid);
}
}
}
else {
datalen = 0;
result = D2DBS_GET_DATA_FAILED;
if (cl_unlock_char((unsigned char*)CharName, (unsigned char*)RealmName, gsid) != 0) {
eventlog(eventlog_level_error, __FUNCTION__, "faled to unlock char {}(*{})@{} for gs {}({})", CharName, \
AccountName, RealmName, conn->serverip, conn->serverid);
}
else {
eventlog(eventlog_level_info, __FUNCTION__, "unlock char {}(*{})@{} for gs {}({})", CharName, \
AccountName, RealmName, conn->serverip, conn->serverid);
}
}
}
if (result == D2DBS_GET_DATA_SUCCESS) {
bn_int_set(&getret->charcreatetime, bn_int_get(charinfo.header.create_time));
/* FIXME: this should be rewritten to support string formatted std::time */
if (bn_int_get(charinfo.header.create_time) >= prefs_get_ladderinit_time()) {
bn_int_set(&getret->allowladder, 1);
}
else {
bn_int_set(&getret->allowladder, 0);
}
}
else {
bn_int_set(&getret->charcreatetime, 0);
bn_int_set(&getret->allowladder, 0);
}
}
else if (datatype == D2GS_DATA_PORTRAIT) {
datalen = dbs_packet_getdata_charinfo(conn, AccountName, CharName, databuf, kBufferSize);
if (datalen > 0) result = D2DBS_GET_DATA_SUCCESS;
else {
datalen = 0;
result = D2DBS_GET_DATA_FAILED;
}
}
else {
eventlog(eventlog_level_error, __FUNCTION__, "unknown data type {}", datatype);
return -1;
}
std::size_t buffer_available_len = sizeof(conn->WriteBuf) - conn->nCharsInWriteBuffer;
std::size_t writelen = sizeof(*getret) + std::strlen(CharName) + 1 + datalen;
if (buffer_available_len < writelen)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] not enough space in write buffer (available space: {}, needed: {})", conn->sd, buffer_available_len, writelen);
return 0;
}
bn_short_set(&getret->h.type, D2DBS_D2GS_GET_DATA_REPLY);
bn_short_set(&getret->h.size, writelen);
bn_int_set(&getret->h.seqno, bn_int_get(getcom->h.seqno));
bn_short_set(&getret->datatype, bn_short_get(getcom->datatype));
bn_int_set(&getret->result, result);
bn_short_set(&getret->datalen, datalen);
writepos += sizeof(*getret);
std::memcpy(writepos, CharName, std::strlen(CharName) + 1);
writepos += std::strlen(CharName) + 1;
if (datalen)
{
std::memcpy(writepos, databuf, datalen);
}
conn->nCharsInWriteBuffer += writelen;
return 1;
}
static int dbs_packet_updateladder(t_d2dbs_connection * conn)
{
char CharName[MAX_CHARNAME_LEN];
char RealmName[MAX_REALMNAME_LEN];
t_d2gs_d2dbs_update_ladder * updateladder;
char * readpos;
t_d2ladder_info charladderinfo;
readpos = conn->ReadBuf;
updateladder = (t_d2gs_d2dbs_update_ladder *)readpos;
readpos += sizeof(*updateladder);
std::strncpy(CharName, readpos, MAX_CHARNAME_LEN);
if (CharName[MAX_CHARNAME_LEN - 1] != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "max char name length exceeded");
return -1;
}
readpos += std::strlen(CharName) + 1;
std::strncpy(RealmName, readpos, MAX_REALMNAME_LEN);
if (RealmName[MAX_REALMNAME_LEN - 1] != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "max realm name length exceeded");
return -1;
}
readpos += std::strlen(RealmName) + 1;
if (readpos != conn->ReadBuf + bn_short_get(updateladder->h.size)) {
eventlog(eventlog_level_error, __FUNCTION__, "request packet size error");
return -1;
}
std::strcpy(charladderinfo.charname, CharName);
charladderinfo.experience = bn_int_get(updateladder->charexplow);
charladderinfo.level = bn_int_get(updateladder->charlevel);
charladderinfo.status = bn_short_get(updateladder->charstatus);
charladderinfo.chclass = bn_short_get(updateladder->charclass);
eventlog(eventlog_level_info, __FUNCTION__, "update ladder for {}@{} for gs {}({})", CharName, RealmName, conn->serverip, conn->serverid);
d2ladder_update(&charladderinfo);
return 1;
}
static int dbs_packet_charlock(t_d2dbs_connection * conn)
{
char CharName[MAX_CHARNAME_LEN];
char AccountName[MAX_USERNAME_LEN];
char RealmName[MAX_REALMNAME_LEN];
t_d2gs_d2dbs_char_lock * charlock;
char * readpos;
readpos = conn->ReadBuf;
charlock = (t_d2gs_d2dbs_char_lock*)readpos;
readpos += sizeof(*charlock);
std::strncpy(AccountName, readpos, MAX_USERNAME_LEN);
if (AccountName[MAX_USERNAME_LEN - 1] != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "max account name length exceeded");
return -1;
}
readpos += std::strlen(AccountName) + 1;
std::strncpy(CharName, readpos, MAX_CHARNAME_LEN);
if (CharName[MAX_CHARNAME_LEN - 1] != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "max char name length exceeded");
return -1;
}
readpos += std::strlen(CharName) + 1;
std::strncpy(RealmName, readpos, MAX_REALMNAME_LEN);
if (RealmName[MAX_REALMNAME_LEN - 1] != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "max realm name length exceeded");
return -1;
}
readpos += std::strlen(RealmName) + 1;
if (readpos != conn->ReadBuf + bn_short_get(charlock->h.size)) {
eventlog(eventlog_level_error, __FUNCTION__, "request packet size error");
return -1;
}
if (bn_int_get(charlock->lockstatus)) {
if (cl_lock_char((unsigned char*)CharName, (unsigned char*)RealmName, conn->serverid) != 0) {
eventlog(eventlog_level_error, __FUNCTION__, "failed to lock character {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid);
}
else {
eventlog(eventlog_level_info, __FUNCTION__, "lock character {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid);
}
}
else {
if (cl_unlock_char((unsigned char*)CharName, (unsigned char*)RealmName, conn->serverid) != 0) {
eventlog(eventlog_level_error, __FUNCTION__, "failed to unlock character {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid);
}
else {
eventlog(eventlog_level_info, __FUNCTION__, "unlock character {}(*{})@{} for gs {}({})", CharName, AccountName, RealmName, conn->serverip, conn->serverid);
}
}
return 1;
}
/*
return value:
1 : process one or more packet
0 : not get a whole packet,do nothing
-1 : error
*/
extern int dbs_packet_handle(t_d2dbs_connection* conn)
{
unsigned short readlen, writelen;
t_d2dbs_d2gs_header * readhead;
int retval;
if (conn->stats == 0) {
if (conn->nCharsInReadBuffer < (signed)sizeof(t_d2gs_d2dbs_connect)) {
return 0;
}
conn->stats = 1;
conn->type = conn->ReadBuf[0];
if (conn->type == CONNECT_CLASS_D2GS_TO_D2DBS) {
if (dbs_verify_ipaddr(d2dbs_prefs_get_d2gs_list(), conn) < 0) {
eventlog(eventlog_level_error, __FUNCTION__, "d2gs connection from unknown ip address");
return -1;
}
readlen = 1;
writelen = 0;
eventlog(eventlog_level_info, __FUNCTION__, "set connection type for gs {}({}) on socket {}", conn->serverip, conn->serverid, conn->sd);
eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "set connection type for gs %s(%d) on socket %d", conn->serverip, conn->serverid, conn->sd);
}
else {
eventlog(eventlog_level_error, __FUNCTION__, "unknown connection type");
return -1;
}
conn->nCharsInReadBuffer -= readlen;
std::memmove(conn->ReadBuf, conn->ReadBuf + readlen, conn->nCharsInReadBuffer);
}
else if (conn->stats == 1) {
if (conn->type == CONNECT_CLASS_D2GS_TO_D2DBS) {
while (conn->nCharsInReadBuffer >= (signed)sizeof(*readhead)) {
readhead = (t_d2dbs_d2gs_header *)conn->ReadBuf;
readlen = bn_short_get(readhead->size);
if (conn->nCharsInReadBuffer < readlen) break;
switch (bn_short_get(readhead->type)) {
case D2GS_D2DBS_SAVE_DATA_REQUEST:
retval = dbs_packet_savedata(conn);
break;
case D2GS_D2DBS_GET_DATA_REQUEST:
retval = dbs_packet_getdata(conn);
break;
case D2GS_D2DBS_UPDATE_LADDER:
retval = dbs_packet_updateladder(conn);
break;
case D2GS_D2DBS_CHAR_LOCK:
retval = dbs_packet_charlock(conn);
break;
case D2GS_D2DBS_ECHOREPLY:
retval = dbs_packet_echoreply(conn);
break;
default:
eventlog(eventlog_level_error, __FUNCTION__, "unknown request type {}", \
bn_short_get(readhead->type));
retval = -1;
}
if (retval != 1) return retval;
conn->nCharsInReadBuffer -= readlen;
std::memmove(conn->ReadBuf, conn->ReadBuf + readlen, conn->nCharsInReadBuffer);
}
}
else {
eventlog(eventlog_level_error, __FUNCTION__, "unknown connection type {}", conn->type);
return -1;
}
}
else {
eventlog(eventlog_level_error, __FUNCTION__, "unknown connection stats");
return -1;
}
return 1;
}
/* FIXME: we should save client ipaddr into c->ipaddr after accept */
static int dbs_verify_ipaddr(char const * addrlist, t_d2dbs_connection * c)
{
char * adlist;
char * s, *temp;
t_elem * elem;
t_d2dbs_connection * tempc;
unsigned int valid;
unsigned int resolveipaddr;
adlist = xstrdup(addrlist);
temp = adlist;
valid = 0;
while ((s = strsep(&temp, ","))) {
host_lookup(s, &resolveipaddr);
if (resolveipaddr == 0) continue;
if (c->ipaddr == resolveipaddr) {
valid = 1;
break;
}
}
xfree(adlist);
if (valid) {
eventlog(eventlog_level_info, __FUNCTION__, "ip address {} is valid", addr_num_to_ip_str(c->ipaddr));
LIST_TRAVERSE(dbs_server_connection_list, elem)
{
if (!(tempc = (t_d2dbs_connection*)elem_get_data(elem))) continue;
if (tempc != c && tempc->ipaddr == c->ipaddr) {
eventlog(eventlog_level_info, __FUNCTION__, "destroying previous connection {}", tempc->serverid);
dbs_server_shutdown_connection(tempc);
list_remove_elem(dbs_server_connection_list, &elem);
}
}
c->verified = 1;
return 0;
}
else {
eventlog(eventlog_level_info, __FUNCTION__, "ip address {} is invalid", addr_num_to_ip_str(c->ipaddr));
}
return -1;
}
int dbs_check_timeout(void)
{
t_elem *elem;
t_d2dbs_connection *tempc;
std::time_t now;
int timeout;
now = std::time(NULL);
timeout = d2dbs_prefs_get_idletime();
LIST_TRAVERSE(dbs_server_connection_list, elem)
{
if (!(tempc = (t_d2dbs_connection*)elem_get_data(elem))) continue;
if (now - tempc->last_active > timeout) {
eventlog(eventlog_level_debug, __FUNCTION__, "connection {} timed out", tempc->serverid);
dbs_server_shutdown_connection(tempc);
list_remove_elem(dbs_server_connection_list, &elem);
continue;
}
}
return 0;
}
int dbs_keepalive(void)
{
t_elem *elem;
t_d2dbs_connection *tempc;
t_d2dbs_d2gs_echorequest *echoreq;
unsigned short writelen;
unsigned char *writepos;
std::time_t now;
writelen = sizeof(t_d2dbs_d2gs_echorequest);
now = std::time(NULL);
LIST_TRAVERSE(dbs_server_connection_list, elem)
{
if (!(tempc = (t_d2dbs_connection*)elem_get_data(elem))) continue;
if (writelen > kBufferSize - tempc->nCharsInWriteBuffer) continue;
writepos = (unsigned char*)(tempc->WriteBuf + tempc->nCharsInWriteBuffer);
echoreq = (t_d2dbs_d2gs_echorequest*)writepos;
bn_short_set(&echoreq->h.type, D2DBS_D2GS_ECHOREQUEST);
bn_short_set(&echoreq->h.size, writelen);
/* FIXME: sequence number not set */
bn_int_set(&echoreq->h.seqno, 0);
tempc->nCharsInWriteBuffer += writelen;
}
return 0;
}
/*************************************************************************************/
#define CHARINFO_SIZE 0xC0
#define CHARINFO_PORTRAIT_LEVEL_OFFSET 0x89
#define CHARINFO_PORTRAIT_STATUS_OFFSET 0x8A
#define CHARINFO_SUMMARY_LEVEL_OFFSET 0xB8
#define CHARINFO_SUMMARY_STATUS_OFFSET 0xB4
#define CHARINFO_PORTRAIT_GFX_OFFSET 0x72
#define CHARINFO_PORTRAIT_COLOR_OFFSET 0x7E
#define CHARSAVE_LEVEL_OFFSET 0x2B
#define CHARSAVE_STATUS_OFFSET 0x24
#define CHARSAVE_GFX_OFFSET 0x88
#define CHARSAVE_COLOR_OFFSET 0x98
#define charstatus_to_portstatus(status) ((((status & 0xFF00) << 1) | (status & 0x00FF)) | 0x8080)
#define portstatus_to_charstatus(status) (((status & 0x7F00) >> 1) | (status & 0x007F))
static void dbs_packet_set_charinfo_level(char * CharName, char * charinfo)
{
if (prefs_get_difficulty_hack()) { /* difficulty hack enabled */
unsigned int level = bn_int_get((bn_basic*)&charinfo[CHARINFO_SUMMARY_LEVEL_OFFSET]);
unsigned int plevel = bn_byte_get((bn_basic*)&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET]);
/* levels 257 thru 355 */
if (level != plevel) {
eventlog(eventlog_level_info, __FUNCTION__, "level mis-match for {} ( {} != {} ) setting to 255", CharName, level, plevel);
bn_byte_set((bn_byte *)&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET], 255);
bn_int_set((bn_int *)&charinfo[CHARINFO_SUMMARY_LEVEL_OFFSET], 255);
}
}
}
static int dbs_packet_fix_charinfo(t_d2dbs_connection * conn, char * AccountName, char * CharName, char * charsave)
{
if (prefs_get_difficulty_hack()) {
unsigned char charinfo[CHARINFO_SIZE];
unsigned int level = bn_byte_get((bn_basic*)&charsave[CHARSAVE_LEVEL_OFFSET]);
unsigned short status = bn_short_get((bn_basic*)&charsave[CHARSAVE_STATUS_OFFSET]);
unsigned short pstatus = charstatus_to_portstatus(status);
int i;
/*
* charinfo is only updated from level 1 to 99 (d2gs issue)
* from 100 to 256 d2gs does not send it
* when value rolls over (level 256 = 0)
* and charactar reaches level 257 (rolled over to level 1)
* d2gs starts sending it agian until level 356 (rolled over to 100)
* is reached agian. etc. etc. etc.
*/
if (level == 0) /* level 256, 512, 768, etc */
level = 255;
if (level < 100)
return 1; /* d2gs will send charinfo - level will be set to 255 at that std::time if needed */
eventlog(eventlog_level_info, __FUNCTION__, "level {} > 99 for {}", level, CharName);
if (!(dbs_packet_getdata_charinfo(conn, AccountName, CharName, (char*)charinfo, CHARINFO_SIZE))) {
eventlog(eventlog_level_error, __FUNCTION__, "unable to get charinfo for {}", CharName);
return 0;
}
/* if level in charinfo file is already set to 255,
* then is must have been set when d2gs sent the charinfo
* and got a level mis-match (levels 257 - 355)
* or level is actually 255. In eather case we set to 255
* this should work for any level mod
*/
if (bn_byte_get(&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET]) == 255)
level = 255;
eventlog(eventlog_level_info, __FUNCTION__, "updating charinfo for {} -> level = {} , status = 0x{:04X} , pstatus = 0x{:04X}", CharName, level, status, pstatus);
bn_byte_set((bn_byte *)&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET], level);
bn_int_set((bn_int *)&charinfo[CHARINFO_SUMMARY_LEVEL_OFFSET], level);
bn_short_set((bn_short *)&charinfo[CHARINFO_PORTRAIT_STATUS_OFFSET], pstatus);
bn_int_set((bn_int *)&charinfo[CHARINFO_SUMMARY_STATUS_OFFSET], status);
for (i = 0; i < 11; i++) {
bn_byte_set((bn_byte *)&charinfo[CHARINFO_PORTRAIT_GFX_OFFSET + i], bn_byte_get((bn_basic*)&charsave[CHARSAVE_GFX_OFFSET + i]));
bn_byte_set((bn_byte *)&charinfo[CHARINFO_PORTRAIT_COLOR_OFFSET + i], bn_byte_get((bn_basic*)&charsave[CHARSAVE_GFX_OFFSET + i]));
}
if (!(dbs_packet_savedata_charinfo(conn, AccountName, CharName, (char*)charinfo, CHARINFO_SIZE))) {
eventlog(eventlog_level_error, __FUNCTION__, "unable to save charinfo for {}", CharName);
return 0;
}
return 1; /* charinfo updated */
}
return 1; /* difficulty hack not enabled */
}
}
}

View file

@ -1,127 +0,0 @@
/*
* Copyright (C) 2001 faster (lqx@cic.tsinghua.edu.cn)
* Copyright (C) 2001 sousou (liupeng.cs@263.net)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef INCLUDED_DBSPACKET_H
#define INCLUDED_DBSPACKET_H
#include "common/bn_type.h"
#include "dbserver.h"
namespace pvpgn
{
namespace d2dbs
{
typedef struct {
bn_short size;
bn_short type;
bn_int seqno;
} t_d2dbs_d2gs_header;
typedef struct {
bn_byte cclass;
} t_d2gs_d2dbs_connect;
#define CONNECT_CLASS_D2GS_TO_D2DBS 0x65
#define D2GS_D2DBS_SAVE_DATA_REQUEST 0x30
typedef struct {
t_d2dbs_d2gs_header h;
bn_short datatype;
bn_short datalen;
/* AccountName */
/* CharName */
/* data */
} t_d2gs_d2dbs_save_data_request;
#define D2GS_DATA_CHARSAVE 0x01
#define D2GS_DATA_PORTRAIT 0x02
#define D2DBS_D2GS_SAVE_DATA_REPLY 0x30
typedef struct {
t_d2dbs_d2gs_header h;
bn_int result;
bn_short datatype;
/* CharName */
} t_d2dbs_d2gs_save_data_reply;
#define D2DBS_SAVE_DATA_SUCCESS 0
#define D2DBS_SAVE_DATA_FAILED 1
#define D2GS_D2DBS_GET_DATA_REQUEST 0x31
typedef struct {
t_d2dbs_d2gs_header h;
bn_short datatype;
/* AccountName */
/* CharName */
} t_d2gs_d2dbs_get_data_request;
#define D2DBS_D2GS_GET_DATA_REPLY 0x31
typedef struct {
t_d2dbs_d2gs_header h;
bn_int result;
bn_int charcreatetime;
bn_int allowladder;
bn_short datatype;
bn_short datalen;
/* CharName */
/* data */
} t_d2dbs_d2gs_get_data_reply;
#define D2DBS_GET_DATA_SUCCESS 0
#define D2DBS_GET_DATA_FAILED 1
#define D2DBS_GET_DATA_CHARLOCKED 2
#define D2GS_D2DBS_UPDATE_LADDER 0x32
typedef struct {
t_d2dbs_d2gs_header h;
bn_int charlevel;
bn_int charexplow;
bn_int charexphigh;
bn_short charclass;
bn_short charstatus;
/* CharName */
/* RealmName */
} t_d2gs_d2dbs_update_ladder;
#define D2GS_D2DBS_CHAR_LOCK 0x33
typedef struct {
t_d2dbs_d2gs_header h;
bn_int lockstatus;
/* CharName */
/* RealmName */
} t_d2gs_d2dbs_char_lock;
#define D2DBS_D2GS_ECHOREQUEST 0x34
typedef struct {
t_d2dbs_d2gs_header h;
} t_d2dbs_d2gs_echorequest;
#define D2GS_D2DBS_ECHOREPLY 0x34
typedef struct {
t_d2dbs_d2gs_header h;
} t_d2gs_d2dbs_echoreply;
extern int dbs_packet_handle(t_d2dbs_connection * conn);
extern int dbs_keepalive(void);
extern int dbs_check_timeout(void);
}
}
#endif

915
src/d2dbs/handle_d2gs.cpp Normal file
View file

@ -0,0 +1,915 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "common/setup_before.h"
#include "setup.h"
#include "handle_d2gs.h"
#include <cstdint>
#include <fstream>
#include <functional>
#include <type_traits>
#include <unordered_map>
#include <nonstd/optional.hpp>
#include "common/addr.h"
#include "common/bn_type.h"
#include "common/d2char_checksum.h"
#include "common/d2cs_d2gs_character.h"
#include "common/d2dbs_d2gs_protocol.h"
#include "common/eventlog.h"
#include "common/packet.h"
#include "common/xalloc.h"
#include "common/xstring.h"
#include "compat/mkdir.h"
#include "compat/rename.h"
#include "charlock.h"
#include "connection.h"
#include "d2ladder.h"
#include "prefs.h"
#include "common/setup_after.h"
namespace pvpgn
{
namespace d2dbs
{
static unsigned int dbs_packet_savedata_charsave(t_d2dbs_connection* c, const char* account_name, const char* char_name, const char* data, unsigned int datalen);
static unsigned int dbs_packet_savedata_charinfo(t_d2dbs_connection* c, const char* account_name, const char* char_name, const char* data, unsigned int datalen);
static nonstd::optional<std::vector<std::uint8_t>> dbs_packet_getdata_charsave(t_d2dbs_connection* c, const char* account_name, const char* char_name);
static nonstd::optional<std::vector<std::uint8_t>> dbs_packet_getdata_charinfo(t_d2dbs_connection* c, const char* account_name, const char* char_name);
static int dbs_packet_savedata(t_d2dbs_connection* c, const t_packet* const packet);
static int dbs_packet_getdata(t_d2dbs_connection* c, const t_packet* const packet);
static int dbs_packet_updateladder(t_d2dbs_connection* c, const t_packet* const packet);
static int dbs_packet_charlock(t_d2dbs_connection* c, const t_packet* const packet);
static int dbs_packet_echoreply(t_d2dbs_connection* c, const t_packet* const packet);
static int dbs_packet_fix_charinfo(t_d2dbs_connection* c, const char* AccountName, const char* CharName, const char* charsave);
static void dbs_packet_set_charinfo_level(const char* char_name, char* charinfo);
std::unordered_map<decltype(packet_get_type(std::declval<t_packet*>())), std::function<int(t_d2dbs_connection*, const t_packet* const packet)>> d2gs_packet_table = {
{ D2GS_D2DBS_SAVE_DATA_REQUEST, dbs_packet_savedata },
{ D2GS_D2DBS_GET_DATA_REQUEST, dbs_packet_getdata },
{ D2GS_D2DBS_UPDATE_LADDER, dbs_packet_updateladder },
{ D2GS_D2DBS_CHAR_LOCK, dbs_packet_charlock },
{ D2DBS_D2GS_ECHOREQUEST, dbs_packet_echoreply }
};
/*
* return value:
* 1 : process one or more packet
* 0 : not get a whole packet,do nothing
* -1 : error
*/
int handle_d2gs_packet(t_d2dbs_connection* c, const t_packet* const packet)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got NULL connection");
return -1;
}
if (!packet)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got NULL packet", c->sd);
return -1;
}
if (packet_get_class(packet) != packet_class_d2dbs_d2gs)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad packet (class {})", c->sd, static_cast<std::underlying_type<t_packet_class>::type>(packet_get_class(packet)));
return -1;
}
switch (conn_get_state(c))
{
case conn_state_loggedin:
switch (d2gs_packet_table.at(packet_get_type(packet))(c, packet))
{
case 1:
eventlog(eventlog_level_error, __FUNCTION__, "[{}] unknown (logged in) d2gs packet type 0x{:04x}, len {}", c->sd, packet_get_type(packet), packet_get_size(packet));
break;
case -1:
eventlog(eventlog_level_error, __FUNCTION__, "[{}] (logged in) got error handling packet type 0x{:04x}, len {}", c->sd, packet_get_type(packet), packet_get_size(packet));
break;
};
break;
default:
eventlog(eventlog_level_error, __FUNCTION__, "[{}] invalid login state {}", c->sd, static_cast<std::underlying_type<t_conn_state>::type>(conn_get_state(c)));
};
return 1;
}
static unsigned int dbs_packet_savedata_charsave(t_d2dbs_connection* c, const char* account_name, const char* char_name, const char* data, unsigned int datalen)
{
char* AccountName = xstrdup(account_name);
if (!AccountName)
{
return 0;
}
char* CharName = xstrdup(char_name);
if (!CharName)
{
xfree(AccountName);
return 0;
}
std::FILE* fd;
int checksum_header;
int checksum_calc;
strtolower(AccountName);
strtolower(CharName);
//check if checksum is ok
checksum_header = bn_int_get((bn_basic*)&data[D2CHARSAVE_CHECKSUM_OFFSET]);
checksum_calc = d2charsave_checksum((unsigned char*)data, datalen, D2CHARSAVE_CHECKSUM_OFFSET);
if (checksum_header != checksum_calc)
{
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "received ({}) and calculated({}) checksum do not match - discarding charsave", checksum_header, checksum_calc);
return 0;
}
std::string filename = fmt::format("{}/.{}.tmp", d2dbs_prefs_get_charsave_dir(), CharName);
fd = std::fopen(filename.c_str(), "wb");
if (!fd)
{
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename);
return 0;
}
std::size_t curlen = 0;
std::size_t leftlen = datalen;
while (curlen < datalen)
{
std::size_t writelen = leftlen > 2000 ? 2000 : leftlen;
std::size_t readlen = std::fwrite(data + curlen, 1, writelen, fd);
if (readlen <= 0)
{
std::fclose(fd);
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "write() failed error : {}", std::strerror(errno));
return 0;
}
curlen += readlen;
leftlen -= readlen;
}
std::fclose(fd);
std::string bakfile = fmt::format("{}/{}", prefs_get_charsave_bak_dir(), CharName);
std::string savefile = fmt::format("{}/{}", d2dbs_prefs_get_charsave_dir(), CharName);
if (p_rename(savefile.c_str(), bakfile.c_str()) == -1)
{
eventlog(eventlog_level_warn, __FUNCTION__, "error std::rename {} to {}", savefile, bakfile);
}
if (p_rename(filename.c_str(), savefile.c_str()) == -1)
{
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "error std::rename {} to {}", filename, savefile);
return 0;
}
eventlog(eventlog_level_info, __FUNCTION__, "saved charsave {}(*{}) for gs {}({})", CharName, AccountName, c->serverip, c->serverid);
xfree(AccountName);
xfree(CharName);
return datalen;
}
static unsigned int dbs_packet_savedata_charinfo(t_d2dbs_connection* c, const char* account_name, const char* char_name, const char* data, unsigned int datalen)
{
char* AccountName = xstrdup(account_name);
if (!AccountName)
{
return 0;
}
char* CharName = xstrdup(char_name);
if (!CharName)
{
xfree(AccountName);
return 0;
}
strtolower(AccountName);
strtolower(CharName);
std::string filepath = fmt::format("{}/{}", prefs_get_charinfo_bak_dir(), AccountName);
{
struct stat statbuf;
if (stat(filepath.c_str(), &statbuf) == -1)
{
if (p_mkdir(filepath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0)
{
eventlog(eventlog_level_info, __FUNCTION__, "created charinfo directory: {}", filepath);
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "failed to create charinfo directory \"{}\" (errno: {})", filepath, errno);
xfree(AccountName);
xfree(CharName);
return 0;
}
}
}
std::string filename = fmt::format("{}/{}/.{}.tmp", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName);
std::FILE* fd = std::fopen(filename.c_str(), "wb");
if (!fd)
{
eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename);
xfree(AccountName);
xfree(CharName);
return 0;
}
std::size_t curlen = 0;
std::size_t leftlen = datalen;
while (curlen < datalen)
{
std::size_t writelen = leftlen > 2000 ? 2000 : leftlen;
std::size_t readlen = std::fwrite(data + curlen, 1, writelen, fd);
if (readlen <= 0)
{
std::fclose(fd);
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "write() failed error : {}", std::strerror(errno));
return 0;
}
curlen += readlen;
leftlen -= readlen;
}
std::fclose(fd);
std::string bakfile = fmt::format("{}/{}/{}", prefs_get_charinfo_bak_dir(), AccountName, CharName);
std::string savefile = fmt::format("{}/{}/{}", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName);
if (p_rename(savefile.c_str(), bakfile.c_str()) == -1)
{
eventlog(eventlog_level_info, __FUNCTION__, "error std::rename {} to {}", savefile, bakfile);
}
if (p_rename(filename.c_str(), savefile.c_str()) == -1)
{
eventlog(eventlog_level_error, __FUNCTION__, "error std::rename {} to {}", filename, savefile);
xfree(AccountName);
xfree(CharName);
return 0;
}
eventlog(eventlog_level_info, __FUNCTION__, "saved charinfo {}(*{}) for gs {}({})", CharName, AccountName, c->serverip, c->serverid);
xfree(AccountName);
xfree(CharName);
return datalen;
}
static nonstd::optional<std::vector<std::uint8_t>> dbs_packet_getdata_charsave(t_d2dbs_connection* c, const char* account_name, const char* char_name)
{
char* AccountName = xstrdup(account_name);
if (!AccountName)
{
return nonstd::nullopt;
}
char* CharName = xstrdup(char_name);
if (!CharName)
{
xfree(AccountName);
return nonstd::nullopt;
}
strtolower(AccountName);
strtolower(CharName);
std::string filename = fmt::format("{}/{}", d2dbs_prefs_get_charsave_dir(), CharName);
std::string filename_d2closed = fmt::format("{}/{}.d2s", d2dbs_prefs_get_charsave_dir(), CharName);
if ((access(filename.c_str(), F_OK) < 0) && (access(filename_d2closed.c_str(), F_OK) == 0))
{
if (std::rename(filename_d2closed.c_str(), filename.c_str()) != 0)
{
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "failed to rename file \"{}\" to \"{}\"", filename_d2closed, filename);
return nonstd::nullopt;
}
}
std::FILE* fd = std::fopen(filename.c_str(), "rb");
if (!fd)
{
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename);
return nonstd::nullopt;
}
if (std::fseek(fd, 0, SEEK_END) != 0)
{
std::fclose(fd);
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "std::fseek() failed");
return nonstd::nullopt;
}
long filesize = std::ftell(fd);
if (filesize == -1L)
{
std::fclose(fd);
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "ftell() failed");
return nonstd::nullopt;
}
std::rewind(fd);
std::vector<std::uint8_t> charsave(filesize);
std::size_t readlen = std::fread(charsave.data(), sizeof(decltype(charsave)::value_type), charsave.size(), fd);
if (readlen < filesize)
{
std::fclose(fd);
eventlog(eventlog_level_error, __FUNCTION__, "failed to read charinfo {}(*{}): read {} bytes, expected {} bytes", CharName, AccountName, readlen, filesize);
xfree(AccountName);
xfree(CharName);
return nonstd::nullopt;
}
std::fclose(fd);
eventlog(eventlog_level_info, __FUNCTION__, "loaded charsave {}(*{}) for gs {}({})", CharName, AccountName, c->serverip, c->serverid);
xfree(AccountName);
xfree(CharName);
return charsave;
}
static nonstd::optional<std::vector<std::uint8_t>> dbs_packet_getdata_charinfo(t_d2dbs_connection* c, const char* account_name, const char* char_name)
{
char* AccountName = xstrdup(account_name);
if (!AccountName)
{
return nonstd::nullopt;
}
char* CharName = xstrdup(char_name);
if (!CharName)
{
xfree(AccountName);
return nonstd::nullopt;
}
strtolower(AccountName);
strtolower(CharName);
std::string filename = fmt::format("{}/{}/{}", d2dbs_prefs_get_charinfo_dir(), AccountName, CharName);
std::FILE* fd = std::fopen(filename.c_str(), "rb");
if (!fd)
{
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "open() failed : {}", filename);
return nonstd::nullopt;
}
if (std::fseek(fd, 0, SEEK_END) != 0)
{
std::fclose(fd);
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "std::fseek() failed");
return nonstd::nullopt;
}
long filesize = std::ftell(fd);
if (filesize == -1)
{
std::fclose(fd);
xfree(AccountName);
xfree(CharName);
eventlog(eventlog_level_error, __FUNCTION__, "std::ftell() failed");
return nonstd::nullopt;
}
std::rewind(fd);
std::vector<std::uint8_t> charinfo(filesize);
std::size_t readlen = std::fread(charinfo.data(), sizeof(decltype(charinfo)::value_type), charinfo.size(), fd);
if (readlen < filesize)
{
std::fclose(fd);
eventlog(eventlog_level_error, __FUNCTION__, "failed to read charinfo {}(*{}): read {} bytes, expected {} bytes", CharName, AccountName, readlen, filesize);
xfree(AccountName);
xfree(CharName);
return nonstd::nullopt;
}
std::fclose(fd);
eventlog(eventlog_level_info, __FUNCTION__, "loaded charinfo {}(*{}) for gs {}({})", CharName, AccountName, c->serverip, c->serverid);
xfree(AccountName);
xfree(CharName);
return charinfo;
}
static int dbs_packet_savedata(t_d2dbs_connection* c, const t_packet* const packet)
{
if (packet_get_size(packet) < sizeof(t_d2gs_d2dbs_save_data_request))
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad savedata packet (expected {} bytes, got {})", c->sd, sizeof(t_d2gs_d2dbs_save_data_request), packet_get_size(packet));
return -1;
}
const auto datatype = bn_short_get(packet->u.d2gs_d2dbs_save_data_request.datatype);
const auto datalen = bn_short_get(packet->u.d2gs_d2dbs_save_data_request.datalen);
const char* const account_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_save_data_request), MAX_USERNAME_LEN);
if (!account_name)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad SAVEDATAREQUEST (missing or too long account name)", c->sd);
return -1;
}
const char* const char_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_save_data_request) + std::strlen(account_name) + 1, MAX_CHARNAME_LEN);
if (!char_name)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad SAVEDATAREQUEST (missing or too long char name)", c->sd);
return -1;
}
const char* const realm_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_save_data_request) + std::strlen(account_name) + 1 + std::strlen(char_name) + 1, MAX_REALMNAME_LEN);
if (!realm_name)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad SAVEDATAREQUEST (missing or too long realm name)", c->sd);
return -1;
}
const void* const data = packet_get_data_const(packet, sizeof(t_d2gs_d2dbs_save_data_request) + std::strlen(account_name) + 1 + std::strlen(char_name) + 1 + std::strlen(realm_name) + 1, datalen);
if (!data)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad SAVEDATAREQUEST (missing or too long data)", c->sd);
return -1;
}
int result = D2DBS_SAVE_DATA_FAILED;
if (datatype == D2GS_DATA_CHARSAVE)
{
if (dbs_packet_savedata_charsave(c, account_name, char_name, static_cast<const char*>(data), datalen) > 0 &&
dbs_packet_fix_charinfo(c, account_name, char_name, static_cast<const char*>(data)))
{
result = D2DBS_SAVE_DATA_SUCCESS;
}
else
{
result = D2DBS_SAVE_DATA_FAILED;
}
}
else if (datatype == D2GS_DATA_PORTRAIT)
{
char* modified_data = static_cast<char*>(xmalloc(datalen));
if (!modified_data)
{
return -1;
}
std::memcpy(modified_data, data, datalen);
/* if level is > 255 , sets level to 255 */
dbs_packet_set_charinfo_level(char_name, modified_data);
if (dbs_packet_savedata_charinfo(c, account_name, char_name, modified_data, datalen) > 0)
{
result = D2DBS_SAVE_DATA_SUCCESS;
}
else
{
result = D2DBS_SAVE_DATA_FAILED;
}
xfree(modified_data);
}
else
{
eventlog(eventlog_level_error, __FUNCTION__, "unknown data type {}", datatype);
return -1;
}
{
t_packet* rpacket = packet_create(packet_class_d2dbs_d2gs);
if (!rpacket)
{
return -1;
}
packet_set_size(rpacket, sizeof(t_d2dbs_d2gs_save_data_reply));
packet_set_type(rpacket, D2DBS_D2GS_SAVE_DATA_REPLY);
bn_int_set(&rpacket->u.d2dbs_d2gs_save_data_reply.h.seqno, bn_int_get(packet->u.d2dbs_d2gs_save_data_reply.h.seqno));
bn_int_set(&rpacket->u.d2dbs_d2gs_save_data_reply.result, result);
bn_short_set(&rpacket->u.d2dbs_d2gs_save_data_reply.datatype, datatype);
packet_append_string(rpacket, char_name);
conn_push_outqueue(c, rpacket);
packet_del_ref(rpacket);
}
return 0;
}
static int dbs_packet_echoreply(t_d2dbs_connection* c, const t_packet* const packet)
{
c->last_active = std::time(nullptr);
return 0;
}
static int dbs_packet_getdata(t_d2dbs_connection* c, const t_packet* const packet)
{
if (packet_get_size(packet) < sizeof(t_d2gs_d2dbs_get_data_request))
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST packet (expected {} bytes, got {})", c->sd, sizeof(t_d2gs_d2dbs_get_data_request), packet_get_size(packet));
return -1;
}
const auto datatype = bn_short_get(packet->u.d2gs_d2dbs_save_data_request.datatype);
const char* const account_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_get_data_request), MAX_USERNAME_LEN);
if (!account_name)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long account name)", c->sd);
return -1;
}
const char* const char_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_get_data_request) + std::strlen(account_name) + 1, MAX_CHARNAME_LEN);
if (!char_name)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long char name)", c->sd);
return -1;
}
const char* const realm_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_get_data_request) + std::strlen(account_name) + 1 + std::strlen(char_name) + 1, MAX_REALMNAME_LEN);
if (!realm_name)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long realm name)", c->sd);
return -1;
}
nonstd::optional<std::vector<std::uint8_t>> charinfo = nonstd::nullopt;
nonstd::optional<std::vector<std::uint8_t>> charsave = nonstd::nullopt;
unsigned int result;
if (datatype == D2GS_DATA_CHARSAVE)
{
unsigned int gsid = 0;
if (cl_query_charlock_status((unsigned char*)char_name, (unsigned char*)realm_name, &gsid) != 0)
{
eventlog(eventlog_level_warn, __FUNCTION__, "char {}(*{})@{} is already locked on gs {}", char_name, account_name, realm_name, gsid);
result = D2DBS_GET_DATA_CHARLOCKED;
}
else if (cl_lock_char((unsigned char*)char_name, (unsigned char*)realm_name, c->serverid) != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "failed to lock char {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid);
result = D2DBS_GET_DATA_CHARLOCKED;
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "lock char {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid);
charsave = dbs_packet_getdata_charsave(c, account_name, char_name);
if (charsave.has_value())
{
charinfo = dbs_packet_getdata_charinfo(c, account_name, char_name);
if (charinfo.has_value())
{
result = D2DBS_GET_DATA_SUCCESS;
}
else
{
result = D2DBS_GET_DATA_FAILED;
if (cl_unlock_char((unsigned char*)char_name, (unsigned char*)realm_name, gsid) != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "failed to unlock char {}(*{})@{} for gs {}({})", char_name, \
account_name, realm_name, c->serverip, c->serverid);
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "unlock char {}(*{})@{} for gs {}({})", char_name, \
account_name, realm_name, c->serverip, c->serverid);
}
}
}
else
{
result = D2DBS_GET_DATA_FAILED;
if (cl_unlock_char((unsigned char*)char_name, (unsigned char*)realm_name, gsid) != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "faled to unlock char {}(*{})@{} for gs {}({})", char_name, \
account_name, realm_name, c->serverip, c->serverid);
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "unlock char {}(*{})@{} for gs {}({})", char_name, \
account_name, realm_name, c->serverip, c->serverid);
}
}
}
}
else if (datatype == D2GS_DATA_PORTRAIT)
{
charinfo = dbs_packet_getdata_charinfo(c, account_name, char_name);
result = charinfo.has_value() ? D2DBS_GET_DATA_SUCCESS : D2DBS_GET_DATA_FAILED;
}
else
{
eventlog(eventlog_level_error, __FUNCTION__, "unknown data type {}", datatype);
return -1;
}
{
t_packet* rpacket = packet_create(packet_class_d2dbs_d2gs);
if (!rpacket)
{
return -1;
}
packet_set_size(rpacket, sizeof(t_d2dbs_d2gs_get_data_reply));
packet_set_type(rpacket, D2DBS_D2GS_GET_DATA_REPLY);
bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.h.seqno, bn_int_get(packet->u.d2dbs_d2gs_get_data_reply.h.seqno));
bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.result, result);
if (datatype == D2GS_DATA_CHARSAVE && result == D2DBS_GET_DATA_SUCCESS && charinfo.has_value())
{
bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.charcreatetime, bn_int_get(reinterpret_cast<t_d2charinfo_file*>(charinfo.value().data())->header.create_time));
// FIXME: this should be rewritten to support string formatted std::time
if (bn_int_get(reinterpret_cast<t_d2charinfo_file*>(charinfo.value().data())->header.create_time) >= prefs_get_ladderinit_time())
{
bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.allowladder, 1);
}
else
{
bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.allowladder, 0);
}
}
else
{
bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.charcreatetime, 0);
bn_int_set(&rpacket->u.d2dbs_d2gs_get_data_reply.allowladder, 0);
}
bn_short_set(&rpacket->u.d2dbs_d2gs_get_data_reply.datatype, datatype);
packet_append_string(rpacket, char_name);
if (result == D2DBS_GET_DATA_SUCCESS)
{
if (datatype == D2GS_DATA_CHARSAVE)
{
bn_short_set(&rpacket->u.d2dbs_d2gs_get_data_reply.datalen, charsave.value().size());
packet_append_data(rpacket, charsave.value().data(), charsave.value().size());
}
else
{
bn_short_set(&rpacket->u.d2dbs_d2gs_get_data_reply.datalen, charinfo.value().size());
packet_append_data(rpacket, charinfo.value().data(), charinfo.value().size());
}
}
else
{
bn_short_set(&rpacket->u.d2dbs_d2gs_get_data_reply.datalen, 0);
}
conn_push_outqueue(c, rpacket);
packet_del_ref(rpacket);
}
return 0;
}
static int dbs_packet_updateladder(t_d2dbs_connection* c, const t_packet* const packet)
{
if (packet_get_size(packet) < sizeof(t_d2gs_d2dbs_update_ladder))
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad UPDATELADDER packet (expected {} bytes, got {})", c->sd, sizeof(t_d2gs_d2dbs_update_ladder), packet_get_size(packet));
return -1;
}
const char* const char_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_update_ladder), MAX_CHARNAME_LEN);
if (!char_name)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad UPDATELADDER (missing or too long char name)", c->sd);
return -1;
}
const char* const realm_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_update_ladder) + std::strlen(char_name) + 1, MAX_REALMNAME_LEN);
if (!realm_name)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad UPDATELADDER (missing or too long realm name)", c->sd);
return -1;
}
t_d2ladder_info charladderinfo = {};
std::strcpy(charladderinfo.charname, char_name);
charladderinfo.experience = bn_int_get(packet->u.d2gs_d2dbs_update_ladder.charexplow);
charladderinfo.level = bn_int_get(packet->u.d2gs_d2dbs_update_ladder.charlevel);
charladderinfo.status = bn_short_get(packet->u.d2gs_d2dbs_update_ladder.charstatus);
charladderinfo.chclass = bn_short_get(packet->u.d2gs_d2dbs_update_ladder.charclass);
eventlog(eventlog_level_info, __FUNCTION__, "update ladder for {}@{} for gs {}({})", char_name, realm_name, c->serverip, c->serverid);
d2ladder_update(&charladderinfo);
return 0;
}
static int dbs_packet_charlock(t_d2dbs_connection* c, const t_packet* const packet)
{
if (packet_get_size(packet) < sizeof(t_d2gs_d2dbs_char_lock))
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad CHARLOCK packet (expected {} bytes, got {})", c->sd, sizeof(t_d2gs_d2dbs_char_lock), packet_get_size(packet));
return -1;
}
const char* const account_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_char_lock), MAX_USERNAME_LEN);
if (!account_name)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long account name)", c->sd);
return -1;
}
const char* const char_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_char_lock) + std::strlen(account_name) + 1, MAX_CHARNAME_LEN);
if (!char_name)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long char name)", c->sd);
return -1;
}
const char* const realm_name = packet_get_str_const(packet, sizeof(t_d2gs_d2dbs_char_lock) + std::strlen(account_name) + 1 + std::strlen(char_name) + 1, MAX_REALMNAME_LEN);
if (!realm_name)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad GETDATAREQUEST (missing or too long realm name)", c->sd);
return -1;
}
if (bn_int_get(packet->u.d2gs_d2dbs_char_lock.lockstatus))
{
if (cl_lock_char((unsigned char*)char_name, (unsigned char*)realm_name, c->serverid) != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "failed to lock character {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid);
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "lock character {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid);
}
}
else
{
if (cl_unlock_char((unsigned char*)char_name, (unsigned char*)realm_name, c->serverid) != 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "failed to unlock character {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid);
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "unlock character {}(*{})@{} for gs {}({})", char_name, account_name, realm_name, c->serverip, c->serverid);
}
}
return 0;
}
/*************************************************************************************/
#define CHARINFO_SIZE 0xC0
#define CHARINFO_PORTRAIT_LEVEL_OFFSET 0x89
#define CHARINFO_PORTRAIT_STATUS_OFFSET 0x8A
#define CHARINFO_SUMMARY_LEVEL_OFFSET 0xB8
#define CHARINFO_SUMMARY_STATUS_OFFSET 0xB4
#define CHARINFO_PORTRAIT_GFX_OFFSET 0x72
#define CHARINFO_PORTRAIT_COLOR_OFFSET 0x7E
#define CHARSAVE_LEVEL_OFFSET 0x2B
#define CHARSAVE_STATUS_OFFSET 0x24
#define CHARSAVE_GFX_OFFSET 0x88
#define CHARSAVE_COLOR_OFFSET 0x98
#define charstatus_to_portstatus(status) ((((status & 0xFF00) << 1) | (status & 0x00FF)) | 0x8080)
#define portstatus_to_charstatus(status) (((status & 0x7F00) >> 1) | (status & 0x007F))
static void dbs_packet_set_charinfo_level(const char* char_name, char* charinfo)
{
if (prefs_get_difficulty_hack())
{ /* difficulty hack enabled */
unsigned int level = bn_int_get((bn_basic*)&charinfo[CHARINFO_SUMMARY_LEVEL_OFFSET]);
unsigned int plevel = bn_byte_get((bn_basic*)&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET]);
/* levels 257 thru 355 */
if (level != plevel)
{
eventlog(eventlog_level_info, __FUNCTION__, "level mis-match for {} ( {} != {} ) setting to 255", char_name, level, plevel);
bn_byte_set((bn_byte*)&charinfo[CHARINFO_PORTRAIT_LEVEL_OFFSET], 255);
bn_int_set((bn_int*)&charinfo[CHARINFO_SUMMARY_LEVEL_OFFSET], 255);
}
}
}
static int dbs_packet_fix_charinfo(t_d2dbs_connection* c, const char* AccountName, const char* CharName, const char* charsave)
{
if (prefs_get_difficulty_hack())
{
unsigned int level = bn_byte_get((bn_basic*)&charsave[CHARSAVE_LEVEL_OFFSET]);
unsigned short status = bn_short_get((bn_basic*)&charsave[CHARSAVE_STATUS_OFFSET]);
unsigned short pstatus = charstatus_to_portstatus(status);
int i;
/*
* charinfo is only updated from level 1 to 99 (d2gs issue)
* from 100 to 256 d2gs does not send it
* when value rolls over (level 256 = 0)
* and charactar reaches level 257 (rolled over to level 1)
* d2gs starts sending it agian until level 356 (rolled over to 100)
* is reached agian. etc. etc. etc.
*/
if (level == 0) /* level 256, 512, 768, etc */
level = 255;
if (level < 100)
return 1; /* d2gs will send charinfo - level will be set to 255 at that std::time if needed */
eventlog(eventlog_level_info, __FUNCTION__, "level {} > 99 for {}", level, CharName);
nonstd::optional<std::vector<std::uint8_t>> charinfo = dbs_packet_getdata_charinfo(c, AccountName, CharName);
if (!charinfo.has_value())
{
eventlog(eventlog_level_error, __FUNCTION__, "unable to get charinfo for {}", CharName);
return 0;
}
/* if level in charinfo file is already set to 255,
* then is must have been set when d2gs sent the charinfo
* and got a level mis-match (levels 257 - 355)
* or level is actually 255. In eather case we set to 255
* this should work for any level mod
*/
if (bn_byte_get(&charinfo.value().data()[CHARINFO_PORTRAIT_LEVEL_OFFSET]) == 255)
level = 255;
eventlog(eventlog_level_info, __FUNCTION__, "updating charinfo for {} -> level = {} , status = 0x{:04X} , pstatus = 0x{:04X}", CharName, level, status, pstatus);
bn_byte_set((bn_byte*)&charinfo.value().data()[CHARINFO_PORTRAIT_LEVEL_OFFSET], level);
bn_int_set((bn_int*)&charinfo.value().data()[CHARINFO_SUMMARY_LEVEL_OFFSET], level);
bn_short_set((bn_short*)&charinfo.value().data()[CHARINFO_PORTRAIT_STATUS_OFFSET], pstatus);
bn_int_set((bn_int*)&charinfo.value().data()[CHARINFO_SUMMARY_STATUS_OFFSET], status);
for (i = 0; i < 11; i++)
{
bn_byte_set((bn_byte*)&charinfo.value().data()[CHARINFO_PORTRAIT_GFX_OFFSET + i], bn_byte_get((bn_basic*)&charsave[CHARSAVE_GFX_OFFSET + i]));
bn_byte_set((bn_byte*)&charinfo.value().data()[CHARINFO_PORTRAIT_COLOR_OFFSET + i], bn_byte_get((bn_basic*)&charsave[CHARSAVE_GFX_OFFSET + i]));
}
if (!(dbs_packet_savedata_charinfo(c, AccountName, CharName, reinterpret_cast<const char*>(charinfo.value().data()), charinfo.value().size())))
{
eventlog(eventlog_level_error, __FUNCTION__, "unable to save charinfo for {}", CharName);
return 0;
}
return 1; /* charinfo updated */
}
return 1; /* difficulty hack not enabled */
}
}
}

35
src/d2dbs/handle_d2gs.h Normal file
View file

@ -0,0 +1,35 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef INCLUDED_HANDLE_D2GS_H
#define INCLUDED_HANDLE_D2GS_H
#include "connection.h"
#include "common/packet.h"
namespace pvpgn
{
namespace d2dbs
{
int handle_d2gs_packet(t_d2dbs_connection* conn, const t_packet* const packet);
}
}
#endif

150
src/d2dbs/handle_init.cpp Normal file
View file

@ -0,0 +1,150 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "common/setup_before.h"
#include "setup.h"
#include "handle_init.h"
#include "common/addr.h"
#include "common/bn_type.h"
#include "common/eventlog.h"
#include "common/init_protocol.h"
#include "common/packet.h"
#include "common/xalloc.h"
#include "compat/strsep.h"
#include "connection.h"
#include "prefs.h"
#include "common/setup_after.h"
namespace pvpgn
{
namespace d2dbs
{
static bool dbs_verify_ipaddr(t_d2dbs_connection* c);
int handle_init_packet(t_d2dbs_connection* c, const t_packet* const packet)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return -1;
}
if (!packet)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got NULL packet", c->sd);
return -1;
}
if (packet_get_class(packet) != packet_class_init)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad packet (class {})", c->sd, (int)packet_get_class(packet));
return -1;
}
switch (packet_get_type(packet))
{
case CLIENT_INITCONN:
switch (bn_byte_get(packet->u.client_initconn.cclass))
{
case CLIENT_INITCONN_CLASS_D2GS_D2DBS:
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client initiated d2gs connection", c->sd);
if (!dbs_verify_ipaddr(c))
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] d2gs connection from unknown ip address {}", c->sd, addr_num_to_ip_str(c->ipaddr));
return -1;
}
conn_set_class(c, conn_class_d2gs);
conn_set_state(c, conn_state_loggedin);
break;
default:
eventlog(eventlog_level_error, __FUNCTION__, "[{}] client requested unknown class 0x{:02x} (length {}) (closing connection)", c->sd, bn_byte_get(packet->u.client_initconn.cclass), packet_get_size(packet));
return -1;
}
break;
default:
eventlog(eventlog_level_error, __FUNCTION__, "[{}] unknown init packet type 0x{:04x}, len {}", c->sd, packet_get_type(packet), packet_get_size(packet));
return -1;
}
return 0;
}
static bool dbs_verify_ipaddr(t_d2dbs_connection* c)
{
bool c_ipaddr_is_valid = false;
{
char* adlist = xstrdup(d2dbs_prefs_get_d2gs_list());
char* temp = adlist;
char* s;
while ((s = strsep(&temp, ",")))
{
unsigned int resolveipaddr = 0;
host_lookup(s, &resolveipaddr);
if (resolveipaddr == 0) continue;
if (c->ipaddr == resolveipaddr)
{
c_ipaddr_is_valid = true;
break;
}
}
xfree(adlist);
}
if (c_ipaddr_is_valid)
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] ip address {} is valid", c->sd, addr_num_to_ip_str(c->ipaddr));
for (auto tempc : connlist())
{
if (!tempc)
{
continue;
}
if (tempc != c && tempc->ipaddr == c->ipaddr)
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] destroying previous connection {} from same ip address", c->sd, tempc->serverid);
conn_set_state(tempc, conn_state_destroy);
}
}
c->verified = 1;
return true;
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] ip address {} is invalid", c->sd, addr_num_to_ip_str(c->ipaddr));
}
return false;
}
}
}

34
src/d2dbs/handle_init.h Normal file
View file

@ -0,0 +1,34 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef INCLUDED_HANDLE_INIT_H
#define INCLUDED_HANDLE_INIT_H
#include "connection.h"
#include "common/packet.h"
namespace pvpgn
{
namespace d2dbs
{
int handle_init_packet(t_d2dbs_connection* c, const t_packet* const packet);
}
}
#endif

View file

@ -79,7 +79,7 @@ namespace pvpgn
}
if (signal_data.exit_time) {
now = std::time(NULL);
if (now >= (signed)signal_data.exit_time) {
if (now >= signal_data.exit_time) {
signal_data.exit_time = 0;
eventlog(eventlog_level_info, __FUNCTION__, "shutdown server due to std::signal");
return -1;
@ -182,6 +182,11 @@ namespace pvpgn
}
#endif
std::time_t d2dbs_get_exit_time()
{
return signal_data.exit_time;
}
}
}

View file

@ -18,6 +18,8 @@
#ifndef INCLUDED_HANDLE_SIGNAL_H
#define INCLUDED_HANDLE_SIGNAL_H
#include <ctime>
namespace pvpgn
{
@ -35,6 +37,8 @@ namespace pvpgn
extern int d2dbs_handle_signal(void);
extern std::time_t d2dbs_get_exit_time();
}
}

View file

@ -56,7 +56,6 @@
using namespace pvpgn::d2dbs;
using namespace pvpgn;
static std::FILE * eventlog_fp;
char serviceLongName[] = "d2dbs service";
char serviceName[] = "d2dbs";
@ -64,8 +63,6 @@ char serviceDescription[] = "Diablo 2 DataBase Server";
int g_ServiceStatus = -1;
static int init(void);
static int cleanup(void);
static int config_init(int argc, char * * argv);
static int config_cleanup(void);
static int setup_daemon(void);
@ -137,16 +134,6 @@ static char * write_to_pidfile(void)
return pidfile;
}
static int init(void)
{
return 0;
}
static int cleanup(void)
{
return 0;
}
static int config_init(int argc, char * * argv)
{
char const * levels;
@ -221,8 +208,9 @@ static int config_cleanup(void)
{
d2dbs_prefs_unload();
cmdline_unload();
#ifndef WIN32_GUI
eventlog_close();
if (eventlog_fp) std::fclose(eventlog_fp);
#endif
return 0;
}
@ -230,11 +218,11 @@ static int config_cleanup(void)
#ifdef WIN32_GUI
extern int app_main(int argc, char ** argv)
#else
extern int main(int argc, char ** argv)
extern int main(int argc, char** argv)
#endif
{
int pid;
char * pidfile;
char* pidfile;
#ifdef WIN32
// create a dump file whenever the gateway crashes
@ -243,29 +231,36 @@ extern int main(int argc, char ** argv)
eventlog_set(stderr);
pid = config_init(argc, argv);
if (!(pid == 0)) {
if (!(pid == 0))
{
// if (pid==1) pid=0;
return pid;
}
pidfile = write_to_pidfile();
eventlog(eventlog_level_info, __FUNCTION__, D2DBS_VERSION);
if (init() < 0) {
eventlog(eventlog_level_error, __FUNCTION__, "failed to init");
return -1;
}
else {
eventlog(eventlog_level_info, __FUNCTION__, "server initialized");
}
#ifndef WIN32
d2dbs_handle_signal_init();
#endif
dbs_server_main();
cleanup();
int startup_status = pre_server_startup();
if (startup_status == 0)
{
if (!server_process())
{
eventlog(eventlog_level_fatal, __FUNCTION__, "failed to initialize network (exiting)");
}
}
post_server_shutdown(startup_status);
if (pidfile) {
if (std::remove(pidfile) < 0)
eventlog(eventlog_level_error, __FUNCTION__, "could not remove pid file \"{}\" (std::remove: {})", pidfile, std::strerror(errno));
xfree((void *)pidfile); /* avoid warning */
}
eventlog(eventlog_level_info, __FUNCTION__, "server has shut down");
config_cleanup();
return 0;
}
}

75
src/d2dbs/pgsid.cpp Normal file
View file

@ -0,0 +1,75 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "common/setup_before.h"
#include "setup.h"
#include "pgsid.h"
#include "common/xalloc.h"
#include "common/setup_after.h"
namespace pvpgn
{
namespace d2dbs
{
static int dbs_packet_gs_id = 0;
static t_preset_d2gsid* preset_d2gsid_head = nullptr;
unsigned int pgsid_get_id(unsigned int ipaddr)
{
t_preset_d2gsid* pgsid;
pgsid = preset_d2gsid_head;
while (pgsid)
{
if (pgsid->ipaddr == ipaddr)
return pgsid->d2gsid;
pgsid = pgsid->next;
}
// not found, build a new item
pgsid = (t_preset_d2gsid*)xmalloc(sizeof(t_preset_d2gsid));
pgsid->ipaddr = ipaddr;
pgsid->d2gsid = ++dbs_packet_gs_id;
// add to list
pgsid->next = preset_d2gsid_head;
preset_d2gsid_head = pgsid;
return preset_d2gsid_head->d2gsid;
}
void pgsid_destroy()
{
if (preset_d2gsid_head)
{
t_preset_d2gsid* curr;
t_preset_d2gsid* next;
for (curr = preset_d2gsid_head; curr; curr = next)
{
next = curr->next;
xfree(curr);
}
}
}
}
}

41
src/d2dbs/pgsid.h Normal file
View file

@ -0,0 +1,41 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef INCLUDED_PGSID_H
#define INCLUDED_PGSID_H
namespace pvpgn
{
namespace d2dbs
{
typedef struct raw_preset_d2gsid
{
unsigned int ipaddr;
unsigned int d2gsid;
struct raw_preset_d2gsid* next;
} t_preset_d2gsid;
unsigned int pgsid_get_id(unsigned int ipaddr);
void pgsid_destroy();
}
}
#endif

View file

@ -38,6 +38,8 @@ constexpr long kBufferSize = 1024L * 20L;
#ifndef D2DBS_DEFAULT_CONF_FILE
# define D2DBS_DEFAULT_CONF_FILE "conf/d2dbs.conf"
#endif
#define D2DBS_POLL_INTERVAL 20 /* 20 ms */
#define D2DBS_FDWATCH_MAX_CONNECTIONS 512
#define DEFAULT_MEMLOG_FILE "/tmp/d2dbs-mem.std::log"
#define DEFAULT_LISTEN_PORT 6114
#define D2DBS_SERVER_ADDRS "0.0.0.0"