// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2018 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  mserv.c
/// \brief Commands used for communicate with the master server

#ifdef __GNUC__
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#endif

#if !defined (UNDER_CE)
#include <time.h>
#endif

#if (defined (NOMD5) || defined (NOMSERV)) && !defined (NONET)
#define NONET
#endif

#ifndef NONET

#ifndef NO_IPV6
#define HAVE_IPV6
#endif

#if (defined (_WIN32) || defined (_WIN32_WCE)) && !defined (_XBOX)
#define RPC_NO_WINDOWS_H
#ifdef HAVE_IPV6
#include <ws2tcpip.h>
#else
#include <winsock.h>     // socket(),...
#endif //!HAVE_IPV6
#else
#ifdef __OS2__
#include <sys/types.h>
#endif // __OS2__

#ifdef HAVE_LWIP
#include <lwip/inet.h>
#include <kos/net.h>
#include <lwip/lwip.h>
#define ioctl lwip_ioctl
#else
#include <arpa/inet.h>
#ifdef __APPLE_CC__
#ifndef _BSD_SOCKLEN_T_
#define _BSD_SOCKLEN_T_
#endif
#endif
#include <sys/socket.h> // socket(),...
#include <netinet/in.h> // sockaddr_in
#ifdef _PS3
#include <net/select.h>
#elif !defined(_arch_dreamcast)
#include <netdb.h> // getaddrinfo(),...
#include <sys/ioctl.h>
#endif
#endif

#ifdef _arch_dreamcast
#include "sdl12/SRB2DC/dchelp.h"
#endif

#include <sys/time.h> // timeval,... (TIMEOUT)
#include <errno.h>
#endif // _WIN32/_WIN32_WCE

#ifdef __OS2__
#include <errno.h>
#endif // __OS2__
#endif // !NONET

#include "doomstat.h"
#include "doomdef.h"
#include "command.h"
#include "i_net.h"
#include "console.h"
#include "mserv.h"
#include "d_net.h"
#include "i_tcp.h"
#include "i_system.h"
#include "byteptr.h"
#include "m_menu.h"
#include "m_argv.h" // Alam is going to kill me <3
#include "m_misc.h" //  GetRevisionString()

#ifdef _WIN32_WCE
#include "sdl12/SRB2CE/cehelp.h"
#endif

#include "i_addrinfo.h"

// ================================ DEFINITIONS ===============================

#define PACKET_SIZE 1024


#define  MS_NO_ERROR            	   0
#define  MS_SOCKET_ERROR        	-201
#define  MS_CONNECT_ERROR       	-203
#define  MS_WRITE_ERROR         	-210
#define  MS_READ_ERROR          	-211
#define  MS_CLOSE_ERROR         	-212
#define  MS_GETHOSTBYNAME_ERROR 	-220
#define  MS_GETHOSTNAME_ERROR   	-221
#define  MS_TIMEOUT_ERROR       	-231

// see master server code for the values
#define ADD_SERVER_MSG          	101
#define REMOVE_SERVER_MSG       	103
#define ADD_SERVERv2_MSG        	104
#define GET_SERVER_MSG          	200
#define GET_SHORT_SERVER_MSG    	205
#define ASK_SERVER_MSG          	206
#define ANSWER_ASK_SERVER_MSG   	207
#define ASK_SERVER_MSG          	206
#define ANSWER_ASK_SERVER_MSG   	207
#define GET_MOTD_MSG            	208
#define SEND_MOTD_MSG           	209
#define GET_ROOMS_MSG           	210
#define SEND_ROOMS_MSG          	211
#define GET_ROOMS_HOST_MSG      	212
#define GET_VERSION_MSG         	213
#define SEND_VERSION_MSG        	214
#define GET_BANNED_MSG          	215 // Someone's been baaaaaad!
#define PING_SERVER_MSG         	216

#define HEADER_SIZE (sizeof (INT32)*4)

#define HEADER_MSG_POS    0
#define IP_MSG_POS       16
#define PORT_MSG_POS     32
#define HOSTNAME_MSG_POS 40


#if defined(_MSC_VER)
#pragma pack(1)
#endif

/** A message to be exchanged with the master server.
  */
typedef struct
{
	INT32 id;                  ///< Unused?
	INT32 type;                ///< Type of message.
	INT32 room;                ///< Because everyone needs a roomie.
	UINT32 length;             ///< Length of the message.
	char buffer[PACKET_SIZE]; ///< Actual contents of the message.
} ATTRPACK msg_t;

#if defined(_MSC_VER)
#pragma pack()
#endif

typedef struct Copy_CVarMS_t
{
	char ip[64];
	char port[8];
	char name[64];
} Copy_CVarMS_s;
static Copy_CVarMS_s registered_server;
static time_t MSLastPing;

#if defined(_MSC_VER)
#pragma pack(1)
#endif
typedef struct
{
	char ip[16];         // Big enough to hold a full address.
	UINT16 port;
	UINT8 padding1[2];
	tic_t time;
} ATTRPACK ms_holepunch_packet_t;
#if defined(_MSC_VER)
#pragma pack()
#endif

// win32 or djgpp
#if defined (_WIN32) || defined (_WIN32_WCE) || defined (__DJGPP__)
#define ioctl ioctlsocket
#define close closesocket
#ifdef WATTCP
#define strerror strerror_s
#endif
#if defined (_WIN32) || defined (_WIN32_WCE)
#undef errno
#define errno h_errno // some very strange things happen when not using h_error
#endif
#ifndef AI_ADDRCONFIG
#define AI_ADDRCONFIG 0x00000400
#endif
#endif

#ifndef NONET
static void Command_Listserv_f(void);
#endif
static void MasterServer_OnChange(void);
static void ServerName_OnChange(void);

#define DEF_PORT "28900"
consvar_t cv_masterserver = {"masterserver", "ms.srb2.org:"DEF_PORT, CV_SAVE, NULL, MasterServer_OnChange, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_servername = {"servername", "SRB2 server", CV_SAVE|CV_CALL|CV_NOINIT, NULL, ServerName_OnChange, 0, NULL, NULL, 0, 0, NULL};

INT16 ms_RoomId = -1;

static enum { MSCS_NONE, MSCS_WAITING, MSCS_REGISTERED, MSCS_FAILED } con_state = MSCS_NONE;

static INT32 msnode = -1;
UINT16 current_port = 0;

#if (defined (_WIN32) || defined (_WIN32_WCE) || defined (_WIN32)) && !defined (NONET)
typedef SOCKET SOCKET_TYPE;
#define ERRSOCKET (SOCKET_ERROR)
#else
#if (defined (__unix__) && !defined (MSDOS)) || defined (__APPLE__) || defined (__HAIKU__) || defined (_PS3)
typedef int SOCKET_TYPE;
#else
typedef unsigned long SOCKET_TYPE;
#endif
#define ERRSOCKET (-1)
#endif

#if (defined (WATTCP) && !defined (__libsocket_socklen_t)) || defined (_WIN32)
typedef int socklen_t;
#endif

#ifndef NONET
static SOCKET_TYPE socket_fd = ERRSOCKET; // WINSOCK socket
static struct timeval select_timeout;
static fd_set wset;
static size_t recvfull(SOCKET_TYPE s, char *buf, size_t len, int flags);
#endif

// Room list is an external variable now.
// Avoiding having to get info ten thousand times...
msg_rooms_t room_list[NUM_LIST_ROOMS+1]; // +1 for easy test

/** Adds variables and commands relating to the master server.
  *
  * \sa cv_masterserver, cv_servername,
  *     Command_Listserv_f
  */
void AddMServCommands(void)
{
#ifndef NONET
	CV_RegisterVar(&cv_masterserver);
	CV_RegisterVar(&cv_servername);
	COM_AddCommand("listserv", Command_Listserv_f);
#endif
}

/** Closes the connection to the master server.
  *
  * \todo Fix for Windows?
  */
static void CloseConnection(void)
{
#ifndef NONET
	if (socket_fd != (SOCKET_TYPE)ERRSOCKET)
		close(socket_fd);
	socket_fd = ERRSOCKET;
#endif
}

//
// MS_Write():
//
static INT32 MS_Write(msg_t *msg)
{
#ifdef NONET
	(void)msg;
	return MS_WRITE_ERROR;
#else
	size_t len;

	if (msg->length == 0)
		msg->length = (INT32)strlen(msg->buffer);
	len = msg->length + HEADER_SIZE;

	msg->type = htonl(msg->type);
	msg->length = htonl(msg->length);
	msg->room = htonl(msg->room);

	if ((size_t)send(socket_fd, (char *)msg, (int)len, 0) != len)
		return MS_WRITE_ERROR;
	return 0;
#endif
}

//
// MS_Read():
//
static INT32 MS_Read(msg_t *msg)
{
#ifdef NONET
	(void)msg;
	return MS_READ_ERROR;
#else
	if (recvfull(socket_fd, (char *)msg, HEADER_SIZE, 0) != HEADER_SIZE)
		return MS_READ_ERROR;

	msg->type = ntohl(msg->type);
	msg->length = ntohl(msg->length);
	msg->room = ntohl(msg->room);

	if (!msg->length) // fix a bug in Windows 2000
		return 0;

	if (recvfull(socket_fd, (char *)msg->buffer, msg->length, 0) != msg->length)
		return MS_READ_ERROR;
	return 0;
#endif
}

#ifndef NONET
/** Gets a list of game servers from the master server.
  */
static INT32 GetServersList(void)
{
	msg_t msg;
	INT32 count = 0;

	msg.type = GET_SERVER_MSG;
	msg.length = 0;
	msg.room = 0;
	if (MS_Write(&msg) < 0)
		return MS_WRITE_ERROR;

	while (MS_Read(&msg) >= 0)
	{
		if (!msg.length)
		{
			if (!count)
				CONS_Alert(CONS_NOTICE, M_GetText("No servers currently running.\n"));
			return MS_NO_ERROR;
		}
		count++;
		CONS_Printf("%s",msg.buffer);
	}

	return MS_READ_ERROR;
}
#endif

//
// MS_Connect()
//
static INT32 MS_Connect(const char *ip_addr, const char *str_port, INT32 async)
{
#ifdef NONET
	(void)ip_addr;
	(void)str_port;
	(void)async;
#else
	struct my_addrinfo *ai, *runp, hints;
	int gaie;

	memset (&hints, 0x00, sizeof(hints));
#ifdef AI_ADDRCONFIG
	hints.ai_flags = AI_ADDRCONFIG;
#endif
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;

	//I_InitTcpNetwork(); this is already done on startup in D_SRB2Main()
	if (!I_InitTcpDriver()) // this is done only if not already done
		return MS_SOCKET_ERROR;

	gaie = I_getaddrinfo(ip_addr, str_port, &hints, &ai);
	if (gaie != 0)
		return MS_GETHOSTBYNAME_ERROR;
	else
		runp = ai;

	while (runp != NULL)
	{
		socket_fd = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol);
		if (socket_fd != (SOCKET_TYPE)ERRSOCKET)
		{
			if (async) // do asynchronous connection
			{
#ifdef FIONBIO
#ifdef WATTCP
				char res = 1;
#else
				unsigned long res = 1;
#endif

				ioctl(socket_fd, FIONBIO, &res);
#endif

				if (connect(socket_fd, runp->ai_addr, (socklen_t)runp->ai_addrlen) == ERRSOCKET)
				{
#ifdef _WIN32 // humm, on win32/win64 it doesn't work with EINPROGRESS (stupid windows)
					if (WSAGetLastError() != WSAEWOULDBLOCK)
#else
					if (errno != EINPROGRESS)
#endif
					{
						con_state = MSCS_FAILED;
						CloseConnection();
						I_freeaddrinfo(ai);
						return MS_CONNECT_ERROR;
					}
				}
				con_state = MSCS_WAITING;
				FD_ZERO(&wset);
				FD_SET(socket_fd, &wset);
				select_timeout.tv_sec = 0, select_timeout.tv_usec = 0;
				I_freeaddrinfo(ai);
				return 0;
			}
			else if (connect(socket_fd, runp->ai_addr, (socklen_t)runp->ai_addrlen) != ERRSOCKET)
			{
				I_freeaddrinfo(ai);
				return 0;
			}
		}
		runp = runp->ai_next;
	}
	I_freeaddrinfo(ai);
#endif
	return MS_CONNECT_ERROR;
}

#define NUM_LIST_SERVER MAXSERVERLIST
const msg_server_t *GetShortServersList(INT32 room)
{
	static msg_server_t server_list[NUM_LIST_SERVER+1]; // +1 for easy test
	msg_t msg;
	INT32 i;

	// we must be connected to the master server before writing to it
	if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
	{
		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
		M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING);
		return NULL;
	}

	msg.type = GET_SHORT_SERVER_MSG;
	msg.length = 0;
	msg.room = room;
	if (MS_Write(&msg) < 0)
		return NULL;

	for (i = 0; i < NUM_LIST_SERVER && MS_Read(&msg) >= 0; i++)
	{
		if (!msg.length)
		{
			server_list[i].header.buffer[0] = 0;
			CloseConnection();
			return server_list;
		}
		M_Memcpy(&server_list[i], msg.buffer, sizeof (msg_server_t));
		server_list[i].header.buffer[0] = 1;
	}
	CloseConnection();
	if (i == NUM_LIST_SERVER)
	{
		server_list[i].header.buffer[0] = 0;
		return server_list;
	}
	else
		return NULL;
}

INT32 GetRoomsList(boolean hosting)
{
	static msg_ban_t banned_info[1];
	msg_t msg;
	INT32 i;

	// we must be connected to the master server before writing to it
	if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
	{
		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
		M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING);
		return -1;
	}

	if (hosting)
		msg.type = GET_ROOMS_HOST_MSG;
	else
		msg.type = GET_ROOMS_MSG;
	msg.length = 0;
	msg.room = 0;
	if (MS_Write(&msg) < 0)
	{
		room_list[0].id = 1;
		strcpy(room_list[0].motd,"Master Server Offline.");
		strcpy(room_list[0].name,"Offline");
		return -1;
	}

	for (i = 0; i < NUM_LIST_ROOMS && MS_Read(&msg) >= 0; i++)
	{
		if(msg.type == GET_BANNED_MSG)
		{
			char banmsg[1000];
			M_Memcpy(&banned_info[0], msg.buffer, sizeof (msg_ban_t));
			if (hosting)
				sprintf(banmsg, M_GetText("You have been banned from\nhosting netgames.\n\nUnder the following IP Range:\n%s - %s\n\nFor the following reason:\n%s\n\nYour ban will expire on:\n%s"),banned_info[0].ipstart,banned_info[0].ipend,banned_info[0].reason,banned_info[0].endstamp);
			else
				sprintf(banmsg, M_GetText("You have been banned from\njoining netgames.\n\nUnder the following IP Range:\n%s - %s\n\nFor the following reason:\n%s\n\nYour ban will expire on:\n%s"),banned_info[0].ipstart,banned_info[0].ipend,banned_info[0].reason,banned_info[0].endstamp);
			M_StartMessage(banmsg, NULL, MM_NOTHING);
			ms_RoomId = -1;
			return -2;
		}
		if (!msg.length)
		{
			room_list[i].header.buffer[0] = 0;
			CloseConnection();
			return 1;
		}
		M_Memcpy(&room_list[i], msg.buffer, sizeof (msg_rooms_t));
		room_list[i].header.buffer[0] = 1;
	}
	CloseConnection();
	if (i == NUM_LIST_ROOMS)
	{
		room_list[i].header.buffer[0] = 0;
		return 1;
	}
	else
	{
		room_list[0].id = 1;
		strcpy(room_list[0].motd,M_GetText("Master Server Offline."));
		strcpy(room_list[0].name,M_GetText("Offline"));
		return -1;
	}
}

#ifdef UPDATE_ALERT
const char *GetMODVersion(void)
{
	static msg_t msg;

	// we must be connected to the master server before writing to it
	if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
	{
		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
		M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING);
		return NULL;
	}

	msg.type = GET_VERSION_MSG;
	msg.length = sizeof MODVERSION;
	msg.room = MODID; // Might as well use it for something.
	sprintf(msg.buffer,"%d",MODVERSION);
	if (MS_Write(&msg) < 0)
	{
		CONS_Alert(CONS_ERROR, M_GetText("Could not send to the Master Server\n"));
		M_StartMessage(M_GetText("Could not send to the Master Server\n"), NULL, MM_NOTHING);
		CloseConnection();
		return NULL;
	}

	if (MS_Read(&msg) < 0)
	{
		CONS_Alert(CONS_ERROR, M_GetText("No reply from the Master Server\n"));
		M_StartMessage(M_GetText("No reply from the Master Server\n"), NULL, MM_NOTHING);
		CloseConnection();
		return NULL;
	}

	CloseConnection();

	if(strcmp(msg.buffer,"NULL") != 0)
	{
		return msg.buffer;
	}
	else
		return NULL;
}

// Console only version of the above (used before game init)
void GetMODVersion_Console(void)
{
	static msg_t msg;

	// we must be connected to the master server before writing to it
	if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
	{
		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
		return;
	}

	msg.type = GET_VERSION_MSG;
	msg.length = sizeof MODVERSION;
	msg.room = MODID; // Might as well use it for something.
	sprintf(msg.buffer,"%d",MODVERSION);
	if (MS_Write(&msg) < 0)
	{
		CONS_Alert(CONS_ERROR, M_GetText("Could not send to the Master Server\n"));
		CloseConnection();
		return;
	}

	if (MS_Read(&msg) < 0)
	{
		CONS_Alert(CONS_ERROR, M_GetText("No reply from the Master Server\n"));
		CloseConnection();
		return;
	}

	CloseConnection();

	if(strcmp(msg.buffer,"NULL") != 0)
		I_Error(UPDATE_ALERT_STRING_CONSOLE, VERSIONSTRING, msg.buffer);
}
#endif

#ifndef NONET
/** Gets a list of game servers. Called from console.
  */
static void Command_Listserv_f(void)
{
	if (con_state == MSCS_WAITING)
	{
		CONS_Alert(CONS_NOTICE, M_GetText("Not yet connected to the Master Server.\n"));
		return;
	}

	CONS_Printf(M_GetText("Retrieving server list...\n"));

	if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
	{
		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
		return;
	}

	if (GetServersList())
		CONS_Alert(CONS_ERROR, M_GetText("Cannot get server list\n"));

	CloseConnection();
}
#endif

FUNCMATH static const char *int2str(INT32 n)
{
	INT32 i;
	static char res[16];

	res[15] = '\0';
	res[14] = (char)((char)(n%10)+'0');
	for (i = 13; (n /= 10); i--)
		res[i] = (char)((char)(n%10)+'0');

	return &res[i+1];
}

#ifndef NONET
static INT32 ConnectionFailed(void)
{
	con_state = MSCS_FAILED;
	CONS_Alert(CONS_ERROR, M_GetText("Connection to Master Server failed\n"));
	CloseConnection();
	return MS_CONNECT_ERROR;
}
#endif

/** Tries to register the local game server on the master server.
  */
static INT32 AddToMasterServer(boolean firstadd)
{
#ifdef NONET
	(void)firstadd;
#else
	static INT32 retry = 0;
	int i, res;
	socklen_t j;
	msg_t msg;
	msg_server_t *info = (msg_server_t *)msg.buffer;
	INT32 room = -1;
	fd_set tset;
	time_t timestamp = time(NULL);
	UINT32 signature, tmp;
	const char *insname;

	M_Memcpy(&tset, &wset, sizeof (tset));
	res = select(255, NULL, &tset, NULL, &select_timeout);
	if (res != ERRSOCKET && !res)
	{
		if (retry++ > 30) // an about 30 second timeout
		{
			retry = 0;
			CONS_Alert(CONS_ERROR, M_GetText("Master Server timed out\n"));
			MSLastPing = timestamp;
			return ConnectionFailed();
		}
		return MS_CONNECT_ERROR;
	}
	retry = 0;
	if (res == ERRSOCKET)
	{
		if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
		{
			CONS_Alert(CONS_ERROR, M_GetText("Master Server socket error #%u: %s\n"), errno, strerror(errno));
			MSLastPing = timestamp;
			return ConnectionFailed();
		}
	}

	// so, the socket is writable, but what does that mean, that the connection is
	// ok, or bad... let see that!
	j = (socklen_t)sizeof (i);
	getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&i, &j);
	if (i) // it was bad
	{
		CONS_Alert(CONS_ERROR, M_GetText("Master Server socket error #%u: %s\n"), errno, strerror(errno));
		MSLastPing = timestamp;
		return ConnectionFailed();
	}

#ifdef PARANOIA
	if (ms_RoomId <= 0)
		I_Error("Attmepted to host in room \"All\"!\n");
#endif
	room = ms_RoomId;

	for(signature = 0, insname = cv_servername.string; *insname; signature += *insname++);
	tmp = (UINT32)(signature * (size_t)&MSLastPing);
	signature *= tmp;
	signature &= 0xAAAAAAAA;
	M_Memcpy(&info->header.signature, &signature, sizeof (UINT32));

	strcpy(info->ip, "");
	strcpy(info->port, int2str(current_port));
	strcpy(info->name, cv_servername.string);
	M_Memcpy(&info->room, & room, sizeof (INT32));
#if VERSION > 0 || SUBVERSION > 0
	sprintf(info->version, "%d.%d.%d", VERSION/100, VERSION%100, SUBVERSION);
#else // Trunk build, send revision info
	strcpy(info->version, GetRevisionString());
#endif
	strcpy(registered_server.name, cv_servername.string);

	if(firstadd)
		msg.type = ADD_SERVER_MSG;
	else
		msg.type = PING_SERVER_MSG;

	msg.length = (UINT32)sizeof (msg_server_t);
	msg.room = 0;
	if (MS_Write(&msg) < 0)
	{
		MSLastPing = timestamp;
		return ConnectionFailed();
	}

	if(con_state != MSCS_REGISTERED)
		CONS_Printf(M_GetText("Master Server update successful.\n"));

	MSLastPing = timestamp;
	con_state = MSCS_REGISTERED;
	CloseConnection();
#endif
	return MS_NO_ERROR;
}

static INT32 RemoveFromMasterSever(void)
{
	msg_t msg;
	msg_server_t *info = (msg_server_t *)msg.buffer;

	strcpy(info->header.buffer, "");
	strcpy(info->ip, "");
	strcpy(info->port, int2str(current_port));
	strcpy(info->name, registered_server.name);
	sprintf(info->version, "%d.%d.%d", VERSION/100, VERSION%100, SUBVERSION);

	msg.type = REMOVE_SERVER_MSG;
	msg.length = (UINT32)sizeof (msg_server_t);
	msg.room = 0;
	if (MS_Write(&msg) < 0)
		return MS_WRITE_ERROR;

	return MS_NO_ERROR;
}

const char *GetMasterServerPort(void)
{
	const char *t = cv_masterserver.string;

	while ((*t != ':') && (*t != '\0'))
		t++;

	if (*t)
		return ++t;
	else
		return DEF_PORT;
}

/** Gets the IP address of the master server. Actually, it seems to just
  * return the hostname, instead; the lookup is done elsewhere.
  *
  * \return Hostname of the master server, without port number on the end.
  * \todo Rename function?
  */
const char *GetMasterServerIP(void)
{
	static char str_ip[64];
	char *t = str_ip;

	if (strstr(cv_masterserver.string, "srb2.ssntails.org:28910")
	 || strstr(cv_masterserver.string, "srb2.servegame.org:28910")
	 || strstr(cv_masterserver.string, "srb2.servegame.org:28900")
	   )
	{
		// replace it with the current default one
		CV_Set(&cv_masterserver, cv_masterserver.defaultvalue);
	}

	strcpy(t, cv_masterserver.string);

	while ((*t != ':') && (*t != '\0'))
		t++;
	*t = '\0';

	return str_ip;
}

void MSOpenUDPSocket(void)
{
#ifndef NONET
	if (I_NetMakeNodewPort)
	{
		// If it's already open, there's nothing to do.
		if (msnode < 0)
			msnode = I_NetMakeNodewPort(GetMasterServerIP(), GetMasterServerPort());
	}
	else
#endif
		msnode = -1;
}

void MSCloseUDPSocket(void)
{
	if (msnode != INT16_MAX) I_NetFreeNodenum(msnode);
	msnode = -1;
}

void RegisterServer(void)
{
	if (con_state == MSCS_REGISTERED || con_state == MSCS_WAITING)
			return;

	CONS_Printf(M_GetText("Registering this server on the Master Server...\n"));

	strcpy(registered_server.ip, GetMasterServerIP());
	strcpy(registered_server.port, GetMasterServerPort());

	if (MS_Connect(registered_server.ip, registered_server.port, 1))
	{
		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
		return;
	}
	MSOpenUDPSocket();

	// keep the TCP connection open until AddToMasterServer() is completed;
}

static inline void SendPingToMasterServer(void)
{
/*	static tic_t next_time = 0;
	tic_t cur_time;
	char *inbuffer = (char*)netbuffer;

	cur_time = I_GetTime();
	if (!netgame)
		UnregisterServer();
	else if (cur_time > next_time) // ping every 2 second if possible
	{
		next_time = cur_time+2*TICRATE;

		if (con_state == MSCS_WAITING)
			AddToMasterServer();

		if (con_state != MSCS_REGISTERED)
			return;

		// cur_time is just a dummy data to send
		WRITEUINT32(inbuffer, cur_time);
		doomcom->datalength = sizeof (cur_time);
		doomcom->remotenode = (INT16)msnode;
		I_NetSend();
	}
*/

// Here, have a simpler MS Ping... - Cue
	if(time(NULL) > (MSLastPing+(60*2)) && con_state != MSCS_NONE)
	{
		//CONS_Debug(DBG_NETPLAY, "%ld (current time) is greater than %d (Last Ping Time)\n", time(NULL), MSLastPing);
		if(MSLastPing < 1)
			AddToMasterServer(true);
		else
			AddToMasterServer(false);
	}
}

void SendAskInfoViaMS(INT32 node, tic_t asktime)
{
	const char *address;
	UINT16 port;
	char *inip;
	ms_holepunch_packet_t mshpp;

	MSOpenUDPSocket();

	// This must be called after calling MSOpenUDPSocket, due to the
	// static buffer.
	address = I_GetNodeAddress(node);

	// no address?
	if (!address)
		return;

	// Copy the IP address into the buffer.
	inip = mshpp.ip;
	while(*address && *address != ':') *inip++ = *address++;
	*inip = '\0';

	// Get the port.
	port = (UINT16)(*address++ ? atoi(address) : 0);
	mshpp.port = SHORT(port);

	// Set the time for ping calculation.
	mshpp.time = LONG(asktime);

	// Send to the MS.
	M_Memcpy(netbuffer, &mshpp, sizeof(mshpp));
	doomcom->datalength = sizeof(ms_holepunch_packet_t);
	doomcom->remotenode = (INT16)msnode;
	I_NetSend();
}

void UnregisterServer(void)
{
	if (con_state != MSCS_REGISTERED)
	{
		con_state = MSCS_NONE;
		CloseConnection();
		return;
	}

	con_state = MSCS_NONE;

	CONS_Printf(M_GetText("Removing this server from the Master Server...\n"));

	if (MS_Connect(registered_server.ip, registered_server.port, 0))
	{
		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
		return;
	}

	if (RemoveFromMasterSever() < 0)
		CONS_Alert(CONS_ERROR, M_GetText("Cannot remove this server from the Master Server\n"));

	CloseConnection();
	MSCloseUDPSocket();
	MSLastPing = 0;
}

void MasterClient_Ticker(void)
{
	if (server && ms_RoomId > 0)
		SendPingToMasterServer();
}

static void ServerName_OnChange(void)
{
	if (con_state == MSCS_REGISTERED)
		AddToMasterServer(false);
}

static void MasterServer_OnChange(void)
{
	UnregisterServer();
	RegisterServer();
}

#ifndef NONET
// Like recv, but waits until we've got enough data to fill the buffer.
static size_t recvfull(SOCKET_TYPE s, char *buf, size_t len, int flags)
{
	/* Total received. */
	size_t totallen = 0;

	while(totallen < len)
	{
		ssize_t ret = (ssize_t)recv(s, buf + totallen, (int)(len - totallen), flags);

		/* Error. */
		if(ret == -1)
			return (size_t)-1;

		totallen += ret;
	}

	return totallen;
}
#endif
