diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 38d2eab..15041ee 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -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 
diff --git a/src/common/d2dbs_d2gs_protocol.h b/src/common/d2dbs_d2gs_protocol.h
new file mode 100644
index 0000000..f803268
--- /dev/null
+++ b/src/common/d2dbs_d2gs_protocol.h
@@ -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
diff --git a/src/common/init_protocol.h b/src/common/init_protocol.h
index 58caaec..ecd1a4e 100644
--- a/src/common/init_protocol.h
+++ b/src/common/init_protocol.h
@@ -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
diff --git a/src/common/packet.cpp b/src/common/packet.cpp
index 584d4ac..6004721 100644
--- a/src/common/packet.cpp
+++ b/src/common/packet.cpp
@@ -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:
diff --git a/src/common/packet.h b/src/common/packet.h
index e88a20a..b58bf4d 100644
--- a/src/common/packet.h
+++ b/src/common/packet.h
@@ -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;
diff --git a/src/d2dbs/CMakeLists.txt b/src/d2dbs/CMakeLists.txt
index 2556180..d1230bc 100644
--- a/src/d2dbs/CMakeLists.txt
+++ b/src/d2dbs/CMakeLists.txt
@@ -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)
diff --git a/src/d2dbs/connection.cpp b/src/d2dbs/connection.cpp
new file mode 100644
index 0000000..3c359e4
--- /dev/null
+++ b/src/d2dbs/connection.cpp
@@ -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;
+				}
+			}
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/src/d2dbs/connection.h b/src/d2dbs/connection.h
new file mode 100644
index 0000000..9d98306
--- /dev/null
+++ b/src/d2dbs/connection.h
@@ -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
diff --git a/src/d2dbs/dbserver.cpp b/src/d2dbs/dbserver.cpp
index 0bc1c84..70bc8b2 100644
--- a/src/d2dbs/dbserver.cpp
+++ b/src/d2dbs/dbserver.cpp
@@ -16,6 +16,7 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
 #include "common/setup_before.h"
+#define SERVER_INTERNAL_ACCESS
 #include "setup.h"
 #include "dbserver.h"
 
@@ -28,15 +29,24 @@
 
 #include "compat/psock.h"
 #include "compat/strerror.h"
-#include "common/eventlog.h"
+
 #include "common/addr.h"
-#include "common/xalloc.h"
+#include "common/eventlog.h"
+#include "common/d2dbs_d2gs_protocol.h"
+#include "common/fdwatch.h"
 #include "common/network.h"
-#include "d2ladder.h"
-#include "prefs.h"
+#include "common/packet.h"
+#include "common/xalloc.h"
+
+#include "connection.h"
 #include "charlock.h"
-#include "dbspacket.h"
+#include "d2ladder.h"
+#include "handle_d2gs.h"
+#include "handle_init.h"
 #include "handle_signal.h"
+#include "pgsid.h"
+#include "prefs.h"
+#include "version.h"
 
 #ifdef HAVE_ARPA_INET_H
 # include <arpa/inet.h>
@@ -57,10 +67,7 @@ namespace pvpgn
 	namespace d2dbs
 	{
 
-		static int		dbs_packet_gs_id = 0;
-		static t_preset_d2gsid	*preset_d2gsid_head = NULL;
-		t_list * dbs_server_connection_list = NULL;
-		int dbs_server_listen_socket = -1;
+		static std::time_t curr_exittime;
 
 		/* dbs_server_main
 		 * The module's driver function -- we just call other functions and
@@ -68,210 +75,573 @@ namespace pvpgn
 		 */
 
 		static int dbs_handle_timed_events(void);
-		static void dbs_on_exit(void);
 
-		int dbs_server_init(void);
-		void dbs_server_loop(int ListeningSocket);
-		int dbs_server_setup_fdsets(fd_set * pReadFDs, fd_set * pWriteFDs,
-			fd_set * pExceptFDs, int ListeningSocket);
-		bool dbs_server_read_data(t_d2dbs_connection* conn);
-		bool dbs_server_write_data(t_d2dbs_connection* conn);
-		int dbs_server_list_add_socket(int sd, unsigned int ipaddr);
-		static int setsockopt_keepalive(int sock);
-		static unsigned int get_preset_d2gsid(unsigned int ipaddr);
+		static int handle_accept(void* data, t_fdwatch_type rw);
+		static int handle_tcp(void* data, t_fdwatch_type rw);
+
+		static void _server_mainloop(t_addrlist* laddrs);
+		static void dbs_check_timeout();
+		static void dbs_keepalive();
 
 
-		int dbs_server_main(void)
+		static char const* laddr_type_get_str(t_laddr_type laddr_type)
 		{
-			eventlog(eventlog_level_info, __FUNCTION__, "establishing the listener...");
-			dbs_server_listen_socket = dbs_server_init();
-			if (dbs_server_listen_socket < 0) {
-				eventlog(eventlog_level_error, __FUNCTION__, "dbs_server_init error ");
-				return 3;
+			switch (laddr_type)
+			{
+			case laddr_type_d2gs:
+				return "d2gs";
+			default:
+				return "UNKNOWN";
 			}
-			eventlog(eventlog_level_info, __FUNCTION__, "waiting for connections...");
-			dbs_server_loop(dbs_server_listen_socket);
-			dbs_on_exit();
+		}
+
+
+		static int sd_accept(t_addr const* curr_laddr, t_laddr_info const* laddr_info, int ssocket)
+		{
+			char               tempa[32];
+			int                csocket;
+			struct sockaddr_in caddr;
+			psock_t_socklen    caddr_len;
+			unsigned int       raddr;
+			unsigned short     rport;
+
+			if (!addr_get_addr_str(curr_laddr, tempa, sizeof(tempa)))
+				std::strcpy(tempa, "x.x.x.x:x");
+
+			/* accept the connection */
+			std::memset(&caddr, 0, sizeof(caddr)); /* not sure if this is needed... modern systems are ok anyway */
+			caddr_len = sizeof(caddr);
+			if ((csocket = psock_accept(ssocket, (struct sockaddr*)&caddr, &caddr_len)) < 0)
+			{
+				/* BSD, POSIX error for aborted connections, SYSV often uses EAGAIN or EPROTO */
+				if (
+#ifdef PSOCK_EWOULDBLOCK
+					psock_errno() == PSOCK_EWOULDBLOCK ||
+#endif
+#ifdef PSOCK_ECONNABORTED
+					psock_errno() == PSOCK_ECONNABORTED ||
+#endif
+#ifdef PSOCK_EPROTO
+					psock_errno() == PSOCK_EPROTO ||
+#endif
+					0)
+					eventlog(eventlog_level_error, __FUNCTION__, "client aborted connection on {} (psock_accept: {})", tempa, pstrerror(psock_errno()));
+				else /* EAGAIN can mean out of resources _or_ connection aborted :( */
+					if (
+#ifdef PSOCK_EINTR
+						psock_errno() != PSOCK_EINTR &&
+#endif
+						1)
+						eventlog(eventlog_level_error, __FUNCTION__, "could not accept new connection on {} (psock_accept: {})", tempa, pstrerror(psock_errno()));
+				return -1;
+			}
+
+			/* dont accept new connections while shutting down */
+			if (curr_exittime)
+			{
+				psock_shutdown(csocket, PSOCK_SHUT_RDWR);
+				psock_close(csocket);
+				return 0;
+			}
+
+			char addrstr[INET_ADDRSTRLEN] = {};
+			inet_ntop(AF_INET, &(caddr.sin_addr), addrstr, sizeof(addrstr));
+
+			eventlog(eventlog_level_info, __FUNCTION__, "[{}] accepted connection from {} on {}", csocket, addr_num_to_addr_str(ntohl(caddr.sin_addr.s_addr), ntohs(caddr.sin_port)), tempa);
+
+			{
+				int optval = 1;
+				psock_t_socklen	optlen = sizeof(optval);
+				if (psock_setsockopt(csocket, PSOCK_SOL_SOCKET, PSOCK_SO_KEEPALIVE, &optval, optlen))
+				{
+					eventlog(eventlog_level_info, __FUNCTION__, "[{}] could not set socket option SO_KEEPALIVE (psock_setsockopt: {})", csocket, pstrerror(psock_errno()));
+				}
+				else
+				{
+					eventlog(eventlog_level_info, __FUNCTION__, "[{}] set KEEPALIVE option", csocket);
+				}
+			}
+
+			{
+				struct sockaddr_in rsaddr;
+				psock_t_socklen    rlen;
+
+				std::memset(&rsaddr, 0, sizeof(rsaddr)); /* not sure if this is needed... modern systems are ok anyway */
+				rlen = sizeof(rsaddr);
+				if (psock_getsockname(csocket, (struct sockaddr*)&rsaddr, &rlen) < 0)
+				{
+					eventlog(eventlog_level_error, __FUNCTION__, "[{}] unable to determine real local port (psock_getsockname: {})", csocket, pstrerror(psock_errno()));
+					/* not a fatal error */
+					raddr = addr_get_ip(curr_laddr);
+					rport = addr_get_port(curr_laddr);
+				}
+				else
+				{
+					if (rsaddr.sin_family != PSOCK_AF_INET)
+					{
+						eventlog(eventlog_level_error, __FUNCTION__, "local address returned with bad address family {}", (int)rsaddr.sin_family);
+						/* not a fatal error */
+						raddr = addr_get_ip(curr_laddr);
+						rport = addr_get_port(curr_laddr);
+					}
+					else
+					{
+						raddr = ntohl(rsaddr.sin_addr.s_addr);
+						rport = ntohs(rsaddr.sin_port);
+					}
+				}
+			}
+
+			if (psock_ctl(csocket, PSOCK_NONBLOCK) < 0)
+			{
+				eventlog(eventlog_level_error, __FUNCTION__, "[{}] could not set TCP socket to non-blocking mode (closing connection) (psock_ctl: {})", csocket, pstrerror(psock_errno()));
+				psock_close(csocket);
+				return -1;
+			}
+
+			{
+				t_d2dbs_connection* c;
+				if (!(c = conn_create(csocket, raddr, rport, addr_get_ip(curr_laddr), addr_get_port(curr_laddr), ntohl(caddr.sin_addr.s_addr), ntohs(caddr.sin_port))))
+				{
+					eventlog(eventlog_level_error, __FUNCTION__, "[{}] unable to create new connection (closing connection)", csocket);
+					psock_close(csocket);
+					return -1;
+				}
+
+				if (conn_add_fdwatch(c, handle_tcp) < 0)
+				{
+					eventlog(eventlog_level_error, __FUNCTION__, "[{}] unable to add socket to fdwatch pool (max connections?)", csocket);
+					conn_set_state(c, conn_state_destroy);
+					return -1;
+				}
+
+				eventlog(eventlog_level_debug, __FUNCTION__, "[{}] client connected to a {} listening address", csocket, laddr_type_get_str(laddr_info->type));
+			}
+
 			return 0;
 		}
 
-		/* dbs_server_init
-		 * Sets up a listener on the given interface and port, returning the
-		 * listening socket if successful; if not, returns -1.
-		 */
-		/* FIXME: No it doesn't!  pcAddress is not ever referenced in this
-		 * function.
-		 * CreepLord: Fixed much better way (will accept dns hostnames)
-		 */
-		int dbs_server_init(void)
-		{
-			int sd;
-			struct sockaddr_in sinInterface;
-			int val;
-			t_addr	* servaddr;
 
-			dbs_server_connection_list = list_create();
+		static int sd_tcpinput(t_d2dbs_connection* c)
+		{
+			t_packet* packet = nullptr;
+			auto csocket = c->sd;
+			auto currsize = conn_get_in_size(c);
+
+			if (!conn_get_in_queue(c))
+			{
+				switch (conn_get_class(c))
+				{
+				case conn_class_init:
+					if (!(packet = packet_create(packet_class_init)))
+					{
+						eventlog(eventlog_level_error, __FUNCTION__, "could not allocate init packet for input");
+						return -1;
+					}
+					break;
+				case conn_class_d2gs:
+					if (!(packet = packet_create(packet_class_d2dbs_d2gs)))
+					{
+						eventlog(eventlog_level_error, __FUNCTION__, "could not allocate d2dbs_d2gs packet for input");
+						return -1;
+					}
+					break;
+				default:
+					eventlog(eventlog_level_error, __FUNCTION__, "[{}] connection has bad class (closing connection)", c->sd);
+					conn_close_read(c);
+					return -2;
+				}
+
+				conn_put_in_queue(c, packet);
+				currsize = 0;
+			}
+
+			packet = conn_get_in_queue(c);
+			switch (net_recv_packet(csocket, packet, &currsize))
+			{
+			case -1:
+				eventlog(eventlog_level_debug, __FUNCTION__, "[{}] read returned -1 (closing connection)", csocket);
+				conn_close_read(c);
+				return -2;
+
+			case 0: /* still working on it */
+				/* eventlog(eventlog_level_debug,__FUNCTION__,"[{}] still reading \"{}\" packet ({} of {} bytes so far)",conn_get_socket(c),packet_get_class_str(packet),conn_get_in_size(c),packet_get_size(packet)); */
+				conn_set_in_size(c, currsize);
+				break;
+
+			case 1: /* done reading */
+			{
+				conn_put_in_queue(c, nullptr);
+
+				int ret;
+				switch (conn_get_class(c))
+				{
+				case conn_class_init:
+					ret = handle_init_packet(c, packet);
+					break;
+				case conn_class_d2gs:
+					ret = handle_d2gs_packet(c, packet);
+					break;
+				default:
+					ret = -1;
+				}
+
+				packet_del_ref(packet);
+				if (ret < 0)
+				{
+					packet_del_ref(packet);
+					conn_close_read(c);
+					return -2;
+				}
+
+				conn_set_in_size(c, 0);
+			}
+			}
+
+			return 0;
+		}
+
+
+		static int sd_tcpoutput(t_d2dbs_connection* c)
+		{
+			unsigned int totsize = 0;
+			auto csocket = c->sd;
+
+			for (;;)
+			{
+				auto currsize = conn_get_out_size(c);
+
+				t_packet* packet = conn_peek_outqueue(c);
+				if (packet == nullptr)
+				{
+					return -2;
+				}
+
+				switch (net_send_packet(csocket, packet, &currsize)) /* avoid warning */
+				{
+				case -1:
+					/* marking connection as "destroyed", memory will be freed later */
+					conn_clear_outqueue(c);
+					conn_set_state(c, conn_state_destroy);
+					return -2;
+
+				case 0: /* still working on it */
+					conn_set_out_size(c, currsize);
+					return 0; /* bail out */
+
+				case 1: /* done sending */
+					packet = conn_pull_outqueue(c);
+					packet_del_ref(packet);
+					conn_set_out_size(c, 0);
+
+					/* stop if out of packets or EWOULDBLOCK) */
+					if (!conn_peek_outqueue(c))
+					{
+						return 0;
+					}
+
+					totsize += currsize;
+
+					break;
+				}
+			}
+
+			/* not reached */
+		}
+
+
+		static int handle_accept(void* data, t_fdwatch_type rw)
+		{
+			t_laddr_info* laddr_info = (t_laddr_info*)addr_get_data((t_addr*)data).p;
+
+			return sd_accept((t_addr*)data, laddr_info, laddr_info->ssocket);
+		}
+
+
+		static int handle_tcp(void* data, t_fdwatch_type rw)
+		{
+			switch (rw)
+			{
+			case fdwatch_type_read: return sd_tcpinput((t_d2dbs_connection*)data);
+			case fdwatch_type_write: return sd_tcpoutput((t_d2dbs_connection*)data);
+			default:
+				return -1;
+			}
+		}
+
+
+		static int _setup_add_addrs(t_addrlist** pladdrs, const char* str, unsigned int defaddr, unsigned short defport, t_laddr_type type)
+		{
+			t_addr* curr_laddr;
+			t_addr_data     laddr_data;
+			t_laddr_info* laddr_info;
+			t_elem const* acurr;
+
+			if (*pladdrs == NULL)
+			{
+				*pladdrs = addrlist_create(str, defaddr, defport);
+				if (*pladdrs == NULL) return -1;
+			}
+			else if (addrlist_append(*pladdrs, str, defaddr, defport)) return -1;
+
+			/* Mark all these laddrs for being classic Battle.net service */
+			LIST_TRAVERSE_CONST(*pladdrs, acurr)
+			{
+				curr_laddr = (t_addr*)elem_get_data(acurr);
+				if (addr_get_data(curr_laddr).p)
+					continue;
+				laddr_info = (t_laddr_info*)xmalloc(sizeof(t_laddr_info));
+				laddr_info->ssocket = -1;
+				laddr_info->type = type;
+				laddr_data.p = laddr_info;
+				if (addr_set_data(curr_laddr, laddr_data) < 0)
+				{
+					eventlog(eventlog_level_error, __FUNCTION__, "could not set address data");
+					if (laddr_info->ssocket != -1)
+					{
+						psock_close(laddr_info->ssocket);
+						laddr_info->ssocket = -1;
+					}
+					return -1;
+				}
+			}
+
+			return 0;
+		}
+
+
+		static int _set_reuseaddr(int sock)
+		{
+			int val = 1;
+
+			return psock_setsockopt(sock, PSOCK_SOL_SOCKET, PSOCK_SO_REUSEADDR, &val, (psock_t_socklen)sizeof(val));
+		}
+
+
+		static int _bind_socket(int sock, unsigned addr, short port)
+		{
+			struct sockaddr_in saddr;
+
+			std::memset(&saddr, 0, sizeof(saddr));
+			saddr.sin_family = PSOCK_AF_INET;
+			saddr.sin_port = htons(port);
+			saddr.sin_addr.s_addr = htonl(addr);
+			return psock_bind(sock, (struct sockaddr*)&saddr, (psock_t_socklen)sizeof(saddr));
+		}
+
+
+		static int _setup_listensock(t_addrlist* laddrs)
+		{
+			t_addr* curr_laddr;
+			t_laddr_info* laddr_info;
+			t_elem const* acurr;
+			char            tempa[32];
+			int		    fidx;
+
+			LIST_TRAVERSE_CONST(laddrs, acurr)
+			{
+				curr_laddr = (t_addr*)elem_get_data(acurr);
+				if (!(laddr_info = (t_laddr_info*)addr_get_data(curr_laddr).p))
+				{
+					eventlog(eventlog_level_error, __FUNCTION__, "NULL address info");
+					goto err;
+				}
+
+				if (!addr_get_addr_str(curr_laddr, tempa, sizeof(tempa)))
+					std::strcpy(tempa, "x.x.x.x:x");
+
+				laddr_info->ssocket = psock_socket(PSOCK_PF_INET, PSOCK_SOCK_STREAM, PSOCK_IPPROTO_TCP);
+				if (laddr_info->ssocket < 0)
+				{
+					eventlog(eventlog_level_error, __FUNCTION__, "could not create a {} listening socket (psock_socket: {})", laddr_type_get_str(laddr_info->type), pstrerror(psock_errno()));
+					goto err;
+				}
+
+				if (_set_reuseaddr(laddr_info->ssocket) < 0)
+					eventlog(eventlog_level_error, __FUNCTION__, "could not set option SO_REUSEADDR on {} socket {} (psock_setsockopt: {})", laddr_type_get_str(laddr_info->type), laddr_info->ssocket, pstrerror(psock_errno()));
+				/* not a fatal error... */
+
+				if (_bind_socket(laddr_info->ssocket, addr_get_ip(curr_laddr), addr_get_port(curr_laddr)) < 0)
+				{
+					eventlog(eventlog_level_error, __FUNCTION__, "could not bind {} socket to address {} TCP (psock_bind: {})", laddr_type_get_str(laddr_info->type), tempa, pstrerror(psock_errno()));
+					goto errsock;
+				}
+
+				/* tell socket to listen for connections */
+				if (psock_listen(laddr_info->ssocket, LISTEN_QUEUE) < 0)
+				{
+					eventlog(eventlog_level_error, __FUNCTION__, "could not set {} socket {} to listen (psock_listen: {})", laddr_type_get_str(laddr_info->type), laddr_info->ssocket, pstrerror(psock_errno()));
+					goto errsock;
+				}
+
+				if (psock_ctl(laddr_info->ssocket, PSOCK_NONBLOCK) < 0)
+					eventlog(eventlog_level_error, __FUNCTION__, "could not set {} TCP listen socket to non-blocking mode (psock_ctl: {})", laddr_type_get_str(laddr_info->type), pstrerror(psock_errno()));
+
+				/* index not stored persisently because we dont need to refer to it later */
+				fidx = fdwatch_add_fd(laddr_info->ssocket, fdwatch_type_read, handle_accept, curr_laddr);
+				if (fidx < 0)
+				{
+					eventlog(eventlog_level_error, __FUNCTION__, "could not add listening socket {} to fdwatch pool (max sockets?)", laddr_info->ssocket);
+					goto errsock;
+				}
+
+				eventlog(eventlog_level_info, __FUNCTION__, "listening for {} connections on {} TCP", laddr_type_get_str(laddr_info->type), tempa);
+			}
+
+			return 0;
+
+		errsock:
+			psock_close(laddr_info->ssocket);
+			laddr_info->ssocket = -1;
+
+		err:
+			return -1;
+		}
+
+
+		static void _server_mainloop(t_addrlist* laddrs)
+		{
+			while (1)
+			{
+
+#ifdef WIN32
+				if (g_ServiceStatus < 0 && kbhit() && getch() == 'q')
+					d2dbs_signal_quit_wrapper();
+				if (g_ServiceStatus == 0) d2dbs_signal_quit_wrapper();
+
+				while (g_ServiceStatus == 2) Sleep(1000);
+#endif
+
+				if (d2dbs_handle_signal() < 0)
+				{
+					eventlog(eventlog_level_info, __FUNCTION__, "the server is shutting down ({} connections left)", connlist().size());
+					break;
+				}
+
+				dbs_handle_timed_events();
+
+
+				/* no need to populate the fdwatch structures as they are populated on the fly
+				 * by sd_accept, conn_push_outqueue, conn_pull_outqueue, conn_destory */
+
+				 /* find which sockets need servicing */
+				switch (fdwatch(D2DBS_POLL_INTERVAL))
+				{
+				case -1: /* error */
+					if (
+#ifdef PSOCK_EINTR
+						psock_errno() != PSOCK_EINTR &&
+#endif
+						1)
+						eventlog(eventlog_level_error, __FUNCTION__, "fdwatch() failed (errno: {})", pstrerror(psock_errno()));
+				case 0: /* timeout... no sockets need checking */
+					continue;
+				}
+
+				/* cycle through the ready sockets and handle them */
+				fdwatch_handle();
+
+				/* reap dead connections */
+				connlist_reap();
+
+			}
+		}
+
+
+		static void _shutdown_addrs(t_addrlist* laddrs)
+		{
+			t_addr* curr_laddr;
+			t_laddr_info* laddr_info;
+			t_elem const* acurr;
+
+			LIST_TRAVERSE_CONST(laddrs, acurr)
+			{
+				curr_laddr = (t_addr*)elem_get_data(acurr);
+
+				if ((laddr_info = (t_laddr_info*)addr_get_data(curr_laddr).p))
+				{
+					if (laddr_info->ssocket != -1)
+					{
+						psock_close(laddr_info->ssocket);
+					}
+
+					xfree(laddr_info);
+				}
+			}
+
+			addrlist_destroy(laddrs);
+		}
+
+
+		int pre_server_startup()
+		{
+			eventlog(eventlog_level_info, __FUNCTION__, D2DBS_VERSION);
 
 			if (d2dbs_d2ladder_init() == -1)
 			{
-				eventlog(eventlog_level_error, __FUNCTION__, "d2ladder_init() failed");
-				return -1;
+				eventlog(eventlog_level_error, __FUNCTION__, "d2dbs_d2ladder_init() failed");
+				return STATUS_D2LADDER_FAILURE;
 			}
 
 			if (cl_init(DEFAULT_HASHTBL_LEN, DEFAULT_GS_MAX) == -1)
 			{
 				eventlog(eventlog_level_error, __FUNCTION__, "cl_init() failed");
-				return -1;
+				return STATUS_CHARLOCK_FAILURE;
 			}
 
-			if (psock_init() < 0)
+			if (fdwatch_init(D2DBS_FDWATCH_MAX_CONNECTIONS))
 			{
-				eventlog(eventlog_level_error, __FUNCTION__, "psock_init() failed");
-				return -1;
+				eventlog(eventlog_level_error, __FUNCTION__, "error initilizing fdwatch");
+				return STATUS_FDWATCH_FAILURE;
 			}
 
-			sd = psock_socket(PSOCK_PF_INET, PSOCK_SOCK_STREAM, PSOCK_IPPROTO_TCP);
-			if (sd == -1)
-			{
-				eventlog(eventlog_level_error, __FUNCTION__, "psock_socket() failed : {}", pstrerror(psock_errno()));
-				return -1;
-			}
-
-			val = 1;
-			if (psock_setsockopt(sd, PSOCK_SOL_SOCKET, PSOCK_SO_REUSEADDR, &val, sizeof(val)) < 0)
-			{
-				eventlog(eventlog_level_error, __FUNCTION__, "psock_setsockopt() failed : {}", pstrerror(psock_errno()));
-			}
-
-			if (!(servaddr = addr_create_str(d2dbs_prefs_get_servaddrs(), INADDR_ANY, DEFAULT_LISTEN_PORT)))
-			{
-				eventlog(eventlog_level_error, __FUNCTION__, "could not get servaddr");
-				return -1;
-			}
-
-			sinInterface.sin_family = PSOCK_AF_INET;
-			sinInterface.sin_addr.s_addr = htonl(addr_get_ip(servaddr));
-			sinInterface.sin_port = htons(addr_get_port(servaddr));
-			std::memset(sinInterface.sin_zero, 0, sizeof(sinInterface.sin_zero));
-			if (psock_bind(sd, (struct sockaddr*)&sinInterface, (psock_t_socklen)sizeof(struct sockaddr_in)) < 0)
-			{
-				eventlog(eventlog_level_error, __FUNCTION__, "psock_bind() failed : {}", pstrerror(psock_errno()));
-				addr_destroy(servaddr);
-				return -1;
-			}
-			if (psock_listen(sd, LISTEN_QUEUE) < 0)
-			{
-				eventlog(eventlog_level_error, __FUNCTION__, "psock_listen() failed : {}", pstrerror(psock_errno()));
-				addr_destroy(servaddr);
-				return -1;
-			}
-			addr_destroy(servaddr);
-			return sd;
+			return 0;
 		}
 
 
-		/* dbs_server_setup_fdsets
-		 * Set up the three FD sets used with select() with the sockets in the
-		 * connection list.  Also add one for the listener socket, if we have
-		 * one.
-		 */
-
-		int dbs_server_setup_fdsets(t_psock_fd_set * pReadFDs, t_psock_fd_set * pWriteFDs, t_psock_fd_set * pExceptFDs, int lsocket)
+		bool server_process()
 		{
-			t_elem const * elem;
-			t_d2dbs_connection* it;
-			int highest_fd;
-
-			PSOCK_FD_ZERO(pReadFDs);
-			PSOCK_FD_ZERO(pWriteFDs);
-			PSOCK_FD_ZERO(pExceptFDs); /* FIXME: don't check these... remove this code */
-			/* Add the listener socket to the read and except FD sets, if there is one. */
-			if (lsocket >= 0) {
-				PSOCK_FD_SET(lsocket, pReadFDs);
-				PSOCK_FD_SET(lsocket, pExceptFDs);
-			}
-			highest_fd = lsocket;
-
-			LIST_TRAVERSE_CONST(dbs_server_connection_list, elem)
+			t_addrlist* laddrs = nullptr;
+			if (_setup_add_addrs(&laddrs, d2dbs_prefs_get_servaddrs(), INADDR_ANY, DEFAULT_LISTEN_PORT, laddr_type_d2gs))
 			{
-				if (!(it = (t_d2dbs_connection*)elem_get_data(elem))) continue;
-				if (it->nCharsInReadBuffer < (kBufferSize - kMaxPacketLength)) {
-					/* There's space in the read buffer, so pay attention to incoming data. */
-					PSOCK_FD_SET(it->sd, pReadFDs);
-				}
-				if (it->nCharsInWriteBuffer > 0) {
-					PSOCK_FD_SET(it->sd, pWriteFDs);
-				}
-				PSOCK_FD_SET(it->sd, pExceptFDs);
-				if (highest_fd < it->sd) highest_fd = it->sd;
+				eventlog(eventlog_level_error, __FUNCTION__, "could not create {} server address list from \"{}\"", laddr_type_get_str(laddr_type_d2gs), d2dbs_prefs_get_servaddrs());
+				return false;
 			}
-			return highest_fd;
-		}
 
-		/* dbs_server_read_data
-		 * Data came in on a client socket, so read it into the buffer.  Returns
-		 * false on failure, or when the client closes its half of the
-		 * connection.  (EAGAIN doesn't count as a failure.)
-		 */
-		bool dbs_server_read_data(t_d2dbs_connection* conn)
-		{
-			int nBytes;
+			if (_setup_listensock(laddrs))
+			{
+				_shutdown_addrs(laddrs);
+				return false;
+			}
 
-			nBytes = net_recv(conn->sd, conn->ReadBuf + conn->nCharsInReadBuffer,
-				kBufferSize - conn->nCharsInReadBuffer);
+			_server_mainloop(laddrs);
+
+			// cleanup for server shutdown
+			connlist_destroy(); // equivalent to pvpgn::bnetd::_shutdown_conns()
+			_shutdown_addrs(laddrs);
 
-			if (nBytes < 0) return false;
-			conn->nCharsInReadBuffer += nBytes;
 			return true;
 		}
 
 
-		/* dbs_server_write_data
-		 * The connection is writable, so send any pending data.  Returns
-		 * false on failure.  (EAGAIN doesn't count as a failure.)
-		 */
-		bool dbs_server_write_data(t_d2dbs_connection* conn)
+		void post_server_shutdown(int status)
 		{
-			int nBytes;
-
-			nBytes = net_send(conn->sd, conn->WriteBuf,
-				conn->nCharsInWriteBuffer > kMaxPacketLength ? kMaxPacketLength : conn->nCharsInWriteBuffer);
-
-			if (nBytes < 0) return false;
-
-			conn->nCharsInWriteBuffer -= nBytes;
-			if (conn->nCharsInWriteBuffer)
-				std::memmove(conn->WriteBuf, conn->WriteBuf + nBytes, conn->nCharsInWriteBuffer);
-
-			return true;
+			switch (status)
+			{
+			case 0:
+				fdwatch_close();
+			case STATUS_FDWATCH_FAILURE:
+				cl_destroy();
+			case STATUS_CHARLOCK_FAILURE:
+				d2dbs_d2ladder_destroy();
+			case STATUS_D2LADDER_FAILURE:
+				pgsid_destroy();
+				break;
+			default:
+				eventlog(eventlog_level_error, __FUNCTION__, "got bad status \"{}\" during shutdown", status);
+			}
 		}
 
-		int dbs_server_list_add_socket(int sd, unsigned int ipaddr)
-		{
-			t_d2dbs_connection	*it;
-			struct in_addr		in;
-
-			it = (t_d2dbs_connection*)xmalloc(sizeof(t_d2dbs_connection));
-			std::memset(it, 0, sizeof(t_d2dbs_connection));
-			it->sd = sd;
-			it->ipaddr = ipaddr;
-			it->major = 0;
-			it->minor = 0;
-			it->type = 0;
-			it->stats = 0;
-			it->verified = 0;
-			it->serverid = get_preset_d2gsid(ipaddr);
-			it->last_active = std::time(NULL);
-			it->nCharsInReadBuffer = 0;
-			it->nCharsInWriteBuffer = 0;
-			list_append_data(dbs_server_connection_list, it);
-			in.s_addr = htonl(ipaddr);
-			char addrstr[INET_ADDRSTRLEN] = { 0 };
-			inet_ntop(AF_INET, &(in), addrstr, sizeof(addrstr));
-			std::strncpy((char*)it->serverip, addrstr, sizeof(it->serverip) - 1);
-
-			return 1;
-		}
 
 		static int dbs_handle_timed_events(void)
 		{
@@ -296,208 +666,52 @@ namespace pvpgn
 			return 0;
 		}
 
-		void dbs_server_loop(int lsocket)
+
+		static void dbs_check_timeout()
 		{
-			struct sockaddr_in sinRemote;
-			int sd;
-			fd_set ReadFDs, WriteFDs, ExceptFDs;
-			t_elem * elem;
-			t_d2dbs_connection* it;
-			bool bOK;
-			const char* pcErrorType;
-			struct timeval         tv;
-			int highest_fd;
-			psock_t_socklen nAddrSize = sizeof(sinRemote);
+			std::time_t now = std::time(nullptr);
+			unsigned int timeout = d2dbs_prefs_get_idletime();
 
-			while (1) {
-
-#ifdef WIN32
-				if (g_ServiceStatus < 0 && kbhit() && getch() == 'q')
-					d2dbs_signal_quit_wrapper();
-				if (g_ServiceStatus == 0) d2dbs_signal_quit_wrapper();
-
-				while (g_ServiceStatus == 2) Sleep(1000);
-#endif
-
-				if (d2dbs_handle_signal() < 0) break;
-
-				dbs_handle_timed_events();
-				highest_fd = dbs_server_setup_fdsets(&ReadFDs, &WriteFDs, &ExceptFDs, lsocket);
-
-				tv.tv_sec = 0;
-				tv.tv_usec = SELECT_TIME_OUT;
-				switch (psock_select(highest_fd + 1, &ReadFDs, &WriteFDs, &ExceptFDs, &tv)) {
-				case -1:
-					eventlog(eventlog_level_error, __FUNCTION__, "psock_select() failed : {}", pstrerror(psock_errno()));
-					continue;
-				case 0:
-					continue;
-				default:
-					break;
-				}
-
-				if (PSOCK_FD_ISSET(lsocket, &ReadFDs)) {
-					sd = psock_accept(lsocket, (struct sockaddr*)&sinRemote, &nAddrSize);
-					if (sd == -1) {
-						eventlog(eventlog_level_error, __FUNCTION__, "psock_accept() failed : {}", pstrerror(psock_errno()));
-						return;
-					}
-
-					char addrstr[INET_ADDRSTRLEN] = { 0 };
-					inet_ntop(AF_INET, &(sinRemote.sin_addr), addrstr, sizeof(addrstr));
-					eventlog(eventlog_level_info, __FUNCTION__, "accepted connection from {}:{} , socket {} .",
-						addrstr, ntohs(sinRemote.sin_port), sd);
-					eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "accepted connection from %s:%d , socket %d .",
-						addrstr, ntohs(sinRemote.sin_port), sd);
-					setsockopt_keepalive(sd);
-					dbs_server_list_add_socket(sd, ntohl(sinRemote.sin_addr.s_addr));
-					if (psock_ctl(sd, PSOCK_NONBLOCK) < 0) {
-						eventlog(eventlog_level_error, __FUNCTION__, "could not set TCP socket [{}] to non-blocking mode (closing connection) (psock_ctl: {})", sd, pstrerror(psock_errno()));
-						psock_close(sd);
-					}
-				}
-				else if (PSOCK_FD_ISSET(lsocket, &ExceptFDs)) {
-					eventlog(eventlog_level_error, __FUNCTION__, "exception on listening socket");
-					/* FIXME: exceptions are not errors with TCP, they are out-of-band data */
-					return;
-				}
-
-				LIST_TRAVERSE(dbs_server_connection_list, elem)
+			for (auto c : connlist())
+			{
+				if (!c)
 				{
-					bOK = true;
-					pcErrorType = 0;
-
-					if (!(it = (t_d2dbs_connection*)elem_get_data(elem))) continue;
-					if (PSOCK_FD_ISSET(it->sd, &ExceptFDs)) {
-						bOK = false;
-						pcErrorType = "General socket error"; /* FIXME: no no no no no */
-						PSOCK_FD_CLR(it->sd, &ExceptFDs);
-					}
-					else {
-
-						if (PSOCK_FD_ISSET(it->sd, &ReadFDs)) {
-							bOK = dbs_server_read_data(it);
-							pcErrorType = "Read error";
-							PSOCK_FD_CLR(it->sd, &ReadFDs);
-						}
-
-						if (PSOCK_FD_ISSET(it->sd, &WriteFDs)) {
-							bOK = dbs_server_write_data(it);
-							pcErrorType = "Write error";
-							PSOCK_FD_CLR(it->sd, &WriteFDs);
-						}
-					}
-
-					if (!bOK) {
-						int	err, errno2;
-						psock_t_socklen	errlen;
-
-						err = 0;
-						errlen = sizeof(err);
-						errno2 = psock_errno();
-
-						if (psock_getsockopt(it->sd, PSOCK_SOL_SOCKET, PSOCK_SO_ERROR, &err, &errlen) == 0) {
-							if (errlen && err != 0) {
-								err = err ? err : errno2;
-								eventlog(eventlog_level_error, __FUNCTION__, "data socket error : {}({})", pstrerror(err), err);
-							}
-						}
-						dbs_server_shutdown_connection(it);
-						list_remove_elem(dbs_server_connection_list, &elem);
-					}
-					else {
-						if (dbs_packet_handle(it) == -1) {
-							eventlog(eventlog_level_error, __FUNCTION__, "dbs_packet_handle() failed");
-							dbs_server_shutdown_connection(it);
-							list_remove_elem(dbs_server_connection_list, &elem);
-						}
-					}
+					continue;
 				}
-			}
-		}
 
-		static void dbs_on_exit(void)
-		{
-			t_elem * elem;
-			t_d2dbs_connection * it;
-
-			if (dbs_server_listen_socket >= 0)
-				psock_close(dbs_server_listen_socket);
-			dbs_server_listen_socket = -1;
-
-			LIST_TRAVERSE(dbs_server_connection_list, elem)
-			{
-				if (!(it = (t_d2dbs_connection*)elem_get_data(elem))) continue;
-				dbs_server_shutdown_connection(it);
-				list_remove_elem(dbs_server_connection_list, &elem);
-			}
-			cl_destroy();
-			d2dbs_d2ladder_destroy();
-			list_destroy(dbs_server_connection_list);
-			if (preset_d2gsid_head)
-			{
-				t_preset_d2gsid * curr;
-				t_preset_d2gsid * next;
-
-				for (curr = preset_d2gsid_head; curr; curr = next)
+				if (now - c->last_active > timeout)
 				{
-					next = curr->next;
-					xfree(curr);
+					eventlog(eventlog_level_debug, __FUNCTION__, "[{}] connection {} timed out", c->sd, c->serverid);
+
+					conn_set_state(c, conn_state_destroy);
 				}
 			}
-			eventlog(eventlog_level_info, __FUNCTION__, "dbserver stopped");
 		}
 
-		int dbs_server_shutdown_connection(t_d2dbs_connection* conn)
+		static void dbs_keepalive()
 		{
-			psock_shutdown(conn->sd, PSOCK_SHUT_RDWR);
-			psock_close(conn->sd);
-			if (conn->verified && conn->type == CONNECT_CLASS_D2GS_TO_D2DBS) {
-				eventlog(eventlog_level_info, __FUNCTION__, "unlock all characters on gs {}({})", conn->serverip, conn->serverid);
-				eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "unlock all characters on gs %s(%d)", conn->serverip, conn->serverid);
-				eventlog_step(prefs_get_logfile_gs(), eventlog_level_info, __FUNCTION__, "close connection to gs on socket %d", conn->sd);
-				cl_unlock_all_char_by_gsid(conn->serverid);
-			}
-			xfree(conn);
-			return 1;
-		}
-
-		static int setsockopt_keepalive(int sock)
-		{
-			int		optval;
-			psock_t_socklen	optlen;
-
-			optval = 1;
-			optlen = sizeof(optval);
-			if (psock_setsockopt(sock, PSOCK_SOL_SOCKET, PSOCK_SO_KEEPALIVE, &optval, optlen)) {
-				eventlog(eventlog_level_info, __FUNCTION__, "failed set KEEPALIVE for socket {}, errno={}", sock, psock_errno());
-				return -1;
-			}
-			else {
-				eventlog(eventlog_level_info, __FUNCTION__, "set KEEPALIVE option for socket {}", sock);
-				return 0;
-			}
-		}
-
-		static unsigned int get_preset_d2gsid(unsigned int ipaddr)
-		{
-			t_preset_d2gsid		*pgsid;
-
-			pgsid = preset_d2gsid_head;
-			while (pgsid)
+			for (auto c : connlist())
 			{
-				if (pgsid->ipaddr == ipaddr)
-					return pgsid->d2gsid;
-				pgsid = pgsid->next;
+				if (!c)
+				{
+					continue;
+				}
+
+				t_packet* rpacket = packet_create(packet_class_d2dbs_d2gs);
+				if (!rpacket)
+				{
+					continue;
+				}
+
+				packet_set_size(rpacket, sizeof(t_d2dbs_d2gs_echorequest));
+				packet_set_type(rpacket, D2DBS_D2GS_ECHOREQUEST);
+
+				bn_int_set(&rpacket->u.d2dbs_d2gs_echorequest.h.seqno, 0);
+
+				conn_push_outqueue(c, rpacket);
+
+				packet_del_ref(rpacket);
 			}
-			/* 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;
 		}
 
 	}
diff --git a/src/d2dbs/dbserver.h b/src/d2dbs/dbserver.h
index b05ca51..cefdbc0 100644
--- a/src/d2dbs/dbserver.h
+++ b/src/d2dbs/dbserver.h
@@ -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);
 
 	}
 
diff --git a/src/d2dbs/dbspacket.cpp b/src/d2dbs/dbspacket.cpp
deleted file mode 100644
index 0eb80a0..0000000
--- a/src/d2dbs/dbspacket.cpp
+++ /dev/null
@@ -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 */
-		}
-
-	}
-
-}
diff --git a/src/d2dbs/dbspacket.h b/src/d2dbs/dbspacket.h
deleted file mode 100644
index 8a2f8b7..0000000
--- a/src/d2dbs/dbspacket.h
+++ /dev/null
@@ -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
diff --git a/src/d2dbs/handle_d2gs.cpp b/src/d2dbs/handle_d2gs.cpp
new file mode 100644
index 0000000..da2b20c
--- /dev/null
+++ b/src/d2dbs/handle_d2gs.cpp
@@ -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 */
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/d2dbs/handle_d2gs.h b/src/d2dbs/handle_d2gs.h
new file mode 100644
index 0000000..b75768d
--- /dev/null
+++ b/src/d2dbs/handle_d2gs.h
@@ -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
diff --git a/src/d2dbs/handle_init.cpp b/src/d2dbs/handle_init.cpp
new file mode 100644
index 0000000..bbc85e8
--- /dev/null
+++ b/src/d2dbs/handle_init.cpp
@@ -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;
+		}
+
+	}
+
+}
diff --git a/src/d2dbs/handle_init.h b/src/d2dbs/handle_init.h
new file mode 100644
index 0000000..5cbe24b
--- /dev/null
+++ b/src/d2dbs/handle_init.h
@@ -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
diff --git a/src/d2dbs/handle_signal.cpp b/src/d2dbs/handle_signal.cpp
index f0ae4cc..eb72f98 100644
--- a/src/d2dbs/handle_signal.cpp
+++ b/src/d2dbs/handle_signal.cpp
@@ -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;
+		}
+
 	}
 
 }
diff --git a/src/d2dbs/handle_signal.h b/src/d2dbs/handle_signal.h
index 315efb1..237cba4 100644
--- a/src/d2dbs/handle_signal.h
+++ b/src/d2dbs/handle_signal.h
@@ -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();
+
 	}
 
 }
diff --git a/src/d2dbs/main.cpp b/src/d2dbs/main.cpp
index 7cea46c..5fea537 100644
--- a/src/d2dbs/main.cpp
+++ b/src/d2dbs/main.cpp
@@ -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;
-}
+}
\ No newline at end of file
diff --git a/src/d2dbs/pgsid.cpp b/src/d2dbs/pgsid.cpp
new file mode 100644
index 0000000..87dfbdd
--- /dev/null
+++ b/src/d2dbs/pgsid.cpp
@@ -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);
+				}
+			}
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/src/d2dbs/pgsid.h b/src/d2dbs/pgsid.h
new file mode 100644
index 0000000..25a074a
--- /dev/null
+++ b/src/d2dbs/pgsid.h
@@ -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
\ No newline at end of file
diff --git a/src/d2dbs/setup.h b/src/d2dbs/setup.h
index 52dc69b..29c3ecc 100644
--- a/src/d2dbs/setup.h
+++ b/src/d2dbs/setup.h
@@ -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"