/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2010-2014 QuakeSpasm developers
Copyright (C) 2020-2021 Vittorio Romeo

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.

*/

// This is enables a simple IP banning mechanism
#define BAN_TEST

#include "q_stdinc.hpp"
#include "arch_def.hpp"
#include "net_sys.hpp"
#include "quakedef.hpp"
#include "net_defs.hpp"
#include "net_dgrm.hpp"
#include "progs.hpp"
#include "cmd.hpp"
#include "common.hpp"
#include "console.hpp"
#include "quakedef_macros.hpp"
#include "net.hpp"
#include "menu.hpp"
#include "keys.hpp"
#include "msg.hpp"
#include "screen.hpp"
#include "sys.hpp"
#include "server.hpp"
#include "vid.hpp"
#include "input.hpp"
#include "byteorder.hpp"
#include "server.hpp"
#include "zone.hpp"

// these two macros are to make the code more readable
#define sfunc net_landrivers[sock->landriver]
#define dfunc net_landrivers[net_landriverlevel]

static int net_landriverlevel;

/* statistic counters */
static int packetsSent = 0;
static int packetsReSent = 0;
static int packetsReceived = 0;
static int receivedDuplicateCount = 0;
static int shortPacketCount = 0;
static int droppedDatagrams;

// QSS
// cvars controlling dpmaster support:
// our servers might as well claim to be 'FTE-Quake' servers. this means FTE can
// see us, we can see FTE (when its pretending to be nq). we additionally look
// for 'DarkPlaces-Quake' servers too, because we can, but most of those servers
// will be using dpp7 and will (safely) not respond to our ccreq_server_info
// requests. we are not visible to DarkPlaces users - dp does not support
// fitz666 so that's not a viable option, at least by default, feel free to
// switch the order if you also change sv_protocol back to 15.
cvar_t sv_reportheartbeats = {"sv_reportheartbeats", "0"};
cvar_t sv_public = {"sv_public", "1"};
cvar_t com_protocolname = {"com_protocolname", "FTE-Quake DarkPlaces-Quake"};
cvar_t net_masters[] = {{"net_master1", ""}, {"net_master2", ""},
    {"net_master3", ""}, {"net_master4", ""},
    {"net_masterextra1", "master.frag-net.com:27950"},
    {"net_masterextra2", "dpmaster.deathmask.net:27950"},
    {"net_masterextra3", "dpmaster.tchr.no:27950"}, {nullptr}};
cvar_t rcon_password = {"rcon_password", ""};
extern cvar_t net_messagetimeout;
extern cvar_t net_connecttimeout;

static struct
{
    unsigned int length;
    unsigned int sequence;
    byte data[MAX_DATAGRAM];
} packetBuffer;

static int myDriverLevel;

extern bool m_return_onerror;
extern char m_return_reason[32];

// QSS
static double
    heartbeat_time; // when this is reached, send a heartbeat to all masters.


static char* StrAddr(struct qsockaddr* addr)
{
    static char buf[34];
    byte* p = (byte*)addr;
    int n;

    for(n = 0; n < 16; n++)
    {
        sprintf(buf + n * 2, "%02x", *p++);
    }
    return buf;
}


#ifdef BAN_TEST

static struct in_addr banAddr;
static struct in_addr banMask;

static void NET_Ban_f()
{
    char addrStr[32];
    char maskStr[32];
    void (*print_fn)(const char* fmt, ...) FUNCP_PRINTF(1, 2);

    if(cmd_source == src_command)
    {
        if(!sv.active)
        {
            Cmd_ForwardToServer();
            return;
        }
        print_fn = Con_Printf;
    }
    else
    {
        if(pr_global_struct->deathmatch)
        {
            return;
        }
        print_fn = SV_ClientPrintf;
    }

    switch(Cmd_Argc())
    {
        case 1:
            if(banAddr.s_addr != INADDR_ANY)
            {
                Q_strcpy(addrStr, inet_ntoa(banAddr));
                Q_strcpy(maskStr, inet_ntoa(banMask));
                print_fn("Banning %s [%s]\n", addrStr, maskStr);
            }
            else
            {
                print_fn("Banning not active\n");
            }
            break;

        case 2:
            if(q_strcasecmp(Cmd_Argv(1), "off") == 0)
            {
                banAddr.s_addr = INADDR_ANY;
            }
            else
            {
                banAddr.s_addr = inet_addr(Cmd_Argv(1));
            }
            banMask.s_addr = INADDR_NONE;
            break;

        case 3:
            banAddr.s_addr = inet_addr(Cmd_Argv(1));
            banMask.s_addr = inet_addr(Cmd_Argv(2));
            break;

        default: print_fn("BAN ip_address [mask]\n"); break;
    }
}
#endif // BAN_TEST


int Datagram_SendMessage(qsocket_t* sock, sizebuf_t* data)
{
    unsigned int packetLen;
    unsigned int dataLen;
    unsigned int eom;

#ifdef DEBUG
    if(data->cursize == 0)
        Sys_Error("Datagram_SendMessage: zero length message\n");

    if(data->cursize > NET_MAXMESSAGE)
        Sys_Error("Datagram_SendMessage: message too big %u\n", data->cursize);

    if(sock->canSend == false)
        Sys_Error("SendMessage: called with canSend == false\n");
#endif

    Q_memcpy(sock->sendMessage, data->data, data->cursize);
    sock->sendMessageLength = data->cursize;

    // QSS
    sock->max_datagram =
        sock->pending_max_datagram; // this can apply only at the start of a
                                    // reliable, to avoid issues with acks if
                                    // its resized later.

    // QSS
    if(data->cursize <= sock->max_datagram)
    {
        dataLen = data->cursize;
        eom = NETFLAG_EOM;
    }
    else
    {
        dataLen = sock->max_datagram; // QSS
        eom = 0;
    }
    packetLen = NET_HEADERSIZE + dataLen;

    packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
    packetBuffer.sequence = BigLong(sock->sendSequence++);
    Q_memcpy(packetBuffer.data, sock->sendMessage, dataLen);

    sock->canSend = false;

    if(sfunc.Write(
           sock->socket, (byte*)&packetBuffer, packetLen, &sock->addr) == -1)
    {
        return -1;
    }

    sock->lastSendTime = net_time;
    packetsSent++;
    return 1;
}


static int SendMessageNext(qsocket_t* sock)
{
    unsigned int packetLen;
    unsigned int dataLen;
    unsigned int eom;

    if(sock->sendMessageLength <= sock->max_datagram) // QSS
    {
        dataLen = sock->sendMessageLength;
        eom = NETFLAG_EOM;
    }
    else
    {
        dataLen = sock->max_datagram; // QSS
        eom = 0;
    }

    packetLen = NET_HEADERSIZE + dataLen;

    packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
    packetBuffer.sequence = BigLong(sock->sendSequence++);
    Q_memcpy(packetBuffer.data, sock->sendMessage, dataLen);

    sock->sendNext = false;

    if(sfunc.Write(
           sock->socket, (byte*)&packetBuffer, packetLen, &sock->addr) == -1)
    {
        return -1;
    }

    sock->lastSendTime = net_time;
    packetsSent++;
    return 1;
}


static int ReSendMessage(qsocket_t* sock)
{
    unsigned int packetLen;
    unsigned int dataLen;
    unsigned int eom;

    if(sock->sendMessageLength <= sock->max_datagram) // QSS
    {
        dataLen = sock->sendMessageLength;
        eom = NETFLAG_EOM;
    }
    else
    {
        dataLen = sock->max_datagram; // QSS
        eom = 0;
    }

    packetLen = NET_HEADERSIZE + dataLen;

    packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
    packetBuffer.sequence = BigLong(sock->sendSequence - 1);
    Q_memcpy(packetBuffer.data, sock->sendMessage, dataLen);

    sock->sendNext = false;

    if(sfunc.Write(
           sock->socket, (byte*)&packetBuffer, packetLen, &sock->addr) == -1)
    {
        return -1;
    }

    sock->lastSendTime = net_time;
    packetsReSent++;
    return 1;
}


bool Datagram_CanSendMessage(qsocket_t* sock)
{
    if(sock->sendNext)
    {
        SendMessageNext(sock);
    }

    return sock->canSend;
}


bool Datagram_CanSendUnreliableMessage(qsocket_t* sock)
{
    (void)sock;

    return true;
}


int Datagram_SendUnreliableMessage(qsocket_t* sock, sizebuf_t* data)
{
    int packetLen;

#ifdef DEBUG
    if(data->cursize == 0)
        Sys_Error("Datagram_SendUnreliableMessage: zero length message\n");

    if(data->cursize > MAX_DATAGRAM)
        Sys_Error("Datagram_SendUnreliableMessage: message too big %u\n",
            data->cursize);
#endif

    packetLen = NET_HEADERSIZE + data->cursize;

    packetBuffer.length = BigLong(packetLen | NETFLAG_UNRELIABLE);
    packetBuffer.sequence = BigLong(sock->unreliableSendSequence++);
    Q_memcpy(packetBuffer.data, data->data, data->cursize);

    if(sfunc.Write(
           sock->socket, (byte*)&packetBuffer, packetLen, &sock->addr) == -1)
    {
        return -1;
    }

    packetsSent++;
    return 1;
}

// QSS

static void _Datagram_ServerControlPacket(sys_socket_t acceptsock,
    struct qsockaddr* clientaddr, byte* data, unsigned int length);
bool Datagram_ProcessPacket(unsigned int length, qsocket_t* sock)
{
    unsigned int flags;
    unsigned int sequence;
    unsigned int count;

    if(length < NET_HEADERSIZE)
    {
        shortPacketCount++;
        return false;
    }

    length = BigLong(packetBuffer.length);
    flags = length & (~NETFLAG_LENGTH_MASK);
    length &= NETFLAG_LENGTH_MASK;

    if(flags & NETFLAG_CTL)
    {
        return false; // should only be for OOB packets.
    }

    sequence = BigLong(packetBuffer.sequence);
    packetsReceived++;

    if(flags & NETFLAG_UNRELIABLE)
    {
        if(sequence < sock->unreliableReceiveSequence)
        {
            Con_DPrintf("Got a stale datagram\n");
            return false;
        }
        if(sequence != sock->unreliableReceiveSequence)
        {
            count = sequence - sock->unreliableReceiveSequence;
            droppedDatagrams += count;
            Con_DPrintf("Dropped %u datagram(s)\n", count);
        }
        sock->unreliableReceiveSequence = sequence + 1;

        length -= NET_HEADERSIZE;

        if(length > (unsigned int)net_message.maxsize)
        { // is this even possible? maybe it will be in the future! either way,
          // no sys_errors please.
            Con_Printf("Over-sized unreliable\n");
            return -1;
        }
        SZ_Clear(&net_message);
        SZ_Write(&net_message, packetBuffer.data, length);

        unreliableMessagesReceived++;
        return true; // parse the unreliable
    }

    if(flags & NETFLAG_ACK)
    {
        if(sequence != (sock->sendSequence - 1))
        {
            Con_DPrintf("Stale ACK received\n");
            return false;
        }
        if(sequence == sock->ackSequence)
        {
            sock->ackSequence++;
            if(sock->ackSequence != sock->sendSequence)
            {
                Con_DPrintf("ack sequencing error\n");
            }
        }
        else
        {
            Con_DPrintf("Duplicate ACK received\n");
            return false;
        }
        sock->sendMessageLength -= sock->max_datagram;
        if(sock->sendMessageLength > 0)
        {
            memmove(sock->sendMessage, sock->sendMessage + sock->max_datagram,
                sock->sendMessageLength);
            sock->sendNext = true;
        }
        else
        {
            sock->sendMessageLength = 0;
            sock->canSend = true;
        }
        return false;
    }

    if(flags & NETFLAG_DATA)
    {
        packetBuffer.length = BigLong(NET_HEADERSIZE | NETFLAG_ACK);
        packetBuffer.sequence = BigLong(sequence);
        sfunc.Write(
            sock->socket, (byte*)&packetBuffer, NET_HEADERSIZE, &sock->addr);

        if(sequence != sock->receiveSequence)
        {
            receivedDuplicateCount++;
            return false;
        }
        sock->receiveSequence++;

        length -= NET_HEADERSIZE;

        if(flags & NETFLAG_EOM)
        {
            if(sock->receiveMessageLength + length >
                (unsigned int)net_message.maxsize)
            {
                Con_Printf("Over-sized reliable\n");
                return -1;
            }
            SZ_Clear(&net_message);
            SZ_Write(
                &net_message, sock->receiveMessage, sock->receiveMessageLength);
            SZ_Write(&net_message, packetBuffer.data, length);
            sock->receiveMessageLength = 0;

            messagesReceived++;
            return true; // parse this reliable!
        }

        if(sock->receiveMessageLength + length > sizeof(sock->receiveMessage))
        {
            Con_Printf("Over-sized reliable\n");
            return -1;
        }
        Q_memcpy(sock->receiveMessage + sock->receiveMessageLength,
            packetBuffer.data, length);
        sock->receiveMessageLength += length;
        return false; // still watiting for the eom
    }
    // unknown flags
    Con_DPrintf("Unknown packet flags\n");
    return false;
}

qsocket_t* Datagram_GetAnyMessage()
{
    qsocket_t* s;
    struct qsockaddr addr;
    int length;
    for(net_landriverlevel = 0; net_landriverlevel < net_numlandrivers;
        net_landriverlevel++)
    {
        sys_socket_t sock;
        if(!dfunc.initialized)
        {
            continue;
        }
        sock = dfunc.listeningSock;
        if(sock == INVALID_SOCKET)
        {
            continue;
        }

        while(1)
        {
            length =
                dfunc.Read(sock, (byte*)&packetBuffer, NET_DATAGRAMSIZE, &addr);
            if(length == -1 || !length)
            {
                // no more packets, move on to the next.
                break;
            }

            if(length < 4)
            {
                continue;
            }
            if(BigLong(packetBuffer.length) & NETFLAG_CTL)
            {
                _Datagram_ServerControlPacket(
                    sock, &addr, (byte*)&packetBuffer, length);
                continue;
            }

            // figure out which qsocket it was for
            for(s = net_activeSockets; s; s = s->next)
            {
                if(s->driver != net_driverlevel)
                {
                    continue;
                }
                if(s->disconnected)
                {
                    continue;
                }
                if(!s->isvirtual)
                {
                    continue;
                }
                if(dfunc.AddrCompare(&addr, &s->addr) == 0)
                {
                    // okay, looks like this is us. try to process it, and if
                    // there's new data
                    if(Datagram_ProcessPacket(length, s))
                    {
                        s->lastMessageTime = net_time;
                        return s; // the server needs to parse that packet.
                    }
                }
            }
            // stray packet... ignore it and just try the next
        }
    }
    for(s = net_activeSockets; s; s = s->next)
    {
        if(s->driver != net_driverlevel)
        {
            continue;
        }
        if(!s->isvirtual)
        {
            continue;
        }

        if(!s->canSend)
        {
            if((net_time - s->lastSendTime) > 1.0)
            {
                ReSendMessage(s);
            }
        }
        if(s->sendNext)
        {
            SendMessageNext(s);
        }

        if(net_time - s->lastMessageTime > ((!s->ackSequence)
                                                   ? net_connecttimeout.value
                                                   : net_messagetimeout.value))
        { // timed out, kick them
            // FIXME: add a proper challenge rather than assuming spoofers won't
            // fake acks
            int i;
            for(i = 0; i < svs.maxclients; i++)
            {
                if(svs.clients[i].netconnection == s)
                {
                    host_client = &svs.clients[i];
                    SV_DropClient(false);
                    break;
                }
            }
        }
    }

    return nullptr;
}

int Datagram_GetMessage(qsocket_t* sock)
{
    unsigned int length;
    unsigned int flags;
    int ret = 0;
    struct qsockaddr readaddr;
    unsigned int sequence;
    unsigned int count;

    if(!sock->canSend)
    {
        if((net_time - sock->lastSendTime) > 1.0)
        {
            ReSendMessage(sock);
        }
    }

    while(true)
    {
        length = (unsigned int)sfunc.Read(
            sock->socket, (byte*)&packetBuffer, NET_DATAGRAMSIZE, &readaddr);

        //	if ((rand() & 255) > 220)
        //		continue;

        if(length == 0)
        {
            break;
        }

        if(length == (unsigned int)-1)
        {
            Con_Printf("Read error\n");
            return -1;
        }

        // QSS
        if(sfunc.AddrCompare(&readaddr, &sock->addr) != 0)
        {
            Con_Printf("Stray/Forged packet received\n");
            Con_Printf(
                "Expected: %s\n", sfunc.AddrToString(&sock->addr, false));
            Con_Printf("Received: %s\n", sfunc.AddrToString(&readaddr, false));
            continue;
        }

        if(length < NET_HEADERSIZE)
        {
            shortPacketCount++;
            continue;
        }

        length = BigLong(packetBuffer.length);
        flags = length & (~NETFLAG_LENGTH_MASK);
        length &= NETFLAG_LENGTH_MASK;

        if(flags & NETFLAG_CTL)
        {
            continue;
        }

        sequence = BigLong(packetBuffer.sequence);
        packetsReceived++;

        if(flags & NETFLAG_UNRELIABLE)
        {
            if(sequence < sock->unreliableReceiveSequence)
            {
                Con_DPrintf("Got a stale datagram\n");
                ret = 0;
                break;
            }
            if(sequence != sock->unreliableReceiveSequence)
            {
                count = sequence - sock->unreliableReceiveSequence;
                droppedDatagrams += count;
                Con_DPrintf("Dropped %u datagram(s)\n", count);
            }
            sock->unreliableReceiveSequence = sequence + 1;

            length -= NET_HEADERSIZE;

            SZ_Clear(&net_message);
            SZ_Write(&net_message, packetBuffer.data, length);

            ret = 2;
            break;
        }

        if(flags & NETFLAG_ACK)
        {
            if(sequence != (sock->sendSequence - 1))
            {
                Con_DPrintf("Stale ACK received\n");
                continue;
            }
            if(sequence == sock->ackSequence)
            {
                sock->ackSequence++;
                if(sock->ackSequence != sock->sendSequence)
                {
                    Con_DPrintf("ack sequencing error\n");
                }
            }
            else
            {
                Con_DPrintf("Duplicate ACK received\n");
                continue;
            }

            sock->sendMessageLength -= sock->max_datagram; // QSS
            if(sock->sendMessageLength > 0)
            {
                memmove(sock->sendMessage,
                    sock->sendMessage + sock->max_datagram,
                    sock->sendMessageLength); // QSS
                sock->sendNext = true;
            }
            else
            {
                sock->sendMessageLength = 0;
                sock->canSend = true;
            }
            continue;
        }

        if(flags & NETFLAG_DATA)
        {
            packetBuffer.length = BigLong(NET_HEADERSIZE | NETFLAG_ACK);
            packetBuffer.sequence = BigLong(sequence);
            sfunc.Write(
                sock->socket, (byte*)&packetBuffer, NET_HEADERSIZE, &readaddr);

            if(sequence != sock->receiveSequence)
            {
                receivedDuplicateCount++;
                continue;
            }
            sock->receiveSequence++;

            length -= NET_HEADERSIZE;

            if(flags & NETFLAG_EOM)
            {
                // QSS
                if(sock->receiveMessageLength + length >
                    (unsigned int)net_message.maxsize)
                {
                    Con_Printf("Over-sized reliable\n");
                    return -1;
                }

                SZ_Clear(&net_message);
                SZ_Write(&net_message, sock->receiveMessage,
                    sock->receiveMessageLength);
                SZ_Write(&net_message, packetBuffer.data, length);
                sock->receiveMessageLength = 0;

                ret = 1;
                break;
            }

            // QSS
            if(sock->receiveMessageLength + length >
                sizeof(sock->receiveMessage))
            {
                Con_Printf("Over-sized reliable\n");
                return -1;
            }

            Q_memcpy(sock->receiveMessage + sock->receiveMessageLength,
                packetBuffer.data, length);
            sock->receiveMessageLength += length;
            continue;
        }
    }

    if(sock->sendNext)
    {
        SendMessageNext(sock);
    }

    return ret;
}


static void PrintStats(qsocket_t* s)
{
    Con_Printf("canSend = %4u   \n", s->canSend);
    Con_Printf("sendSeq = %4u   ", s->sendSequence);
    Con_Printf("recvSeq = %4u   \n", s->receiveSequence);
    Con_Printf("\n");
}

static void NET_Stats_f()
{
    qsocket_t* s;

    if(Cmd_Argc() == 1)
    {
        Con_Printf("unreliable messages sent   = %i\n", unreliableMessagesSent);
        Con_Printf(
            "unreliable messages recv   = %i\n", unreliableMessagesReceived);
        Con_Printf("reliable messages sent     = %i\n", messagesSent);
        Con_Printf("reliable messages received = %i\n", messagesReceived);
        Con_Printf("packetsSent                = %i\n", packetsSent);
        Con_Printf("packetsReSent              = %i\n", packetsReSent);
        Con_Printf("packetsReceived            = %i\n", packetsReceived);
        Con_Printf("receivedDuplicateCount     = %i\n", receivedDuplicateCount);
        Con_Printf("shortPacketCount           = %i\n", shortPacketCount);
        Con_Printf("droppedDatagrams           = %i\n", droppedDatagrams);
    }
    else if(Q_strcmp(Cmd_Argv(1), "*") == 0)
    {
        for(s = net_activeSockets; s; s = s->next)
        {
            PrintStats(s);
        }
        for(s = net_freeSockets; s; s = s->next)
        {
            PrintStats(s);
        }
    }
    else
    {
        // QSS
        for(s = net_activeSockets; s; s = s->next)
        {
            if(q_strcasecmp(Cmd_Argv(1), s->trueaddress) == 0 ||
                q_strcasecmp(Cmd_Argv(1), s->maskedaddress) == 0)
            {
                break;
            }
        }

        // QSS
        if(s == nullptr)
        {
            for(s = net_freeSockets; s; s = s->next)
            {
                if(q_strcasecmp(Cmd_Argv(1), s->trueaddress) == 0 ||
                    q_strcasecmp(Cmd_Argv(1), s->maskedaddress) == 0)
                {
                    break;
                }
            }
        }

        if(s == nullptr)
        {
            return;
        }

        PrintStats(s);
    }
}


// recognize ip:port (based on ProQuake)
static const char* Strip_Port(const char* host)
{
    static char noport[MAX_QPATH];
    /* array size as in Host_Connect_f() */
    char* p;
    int port;

    if(!host || !*host)
    {
        return host;
    }
    q_strlcpy(noport, host, sizeof(noport));
    if((p = Q_strrchr(noport, ':')) == nullptr)
    {
        return host;
    }

    // QSS
    if(strchr(p, ']'))
    {
        return host; //[::] should not be considered port 0
    }

    *p++ = '\0';
    port = Q_atoi(p);
    if(port > 0 && port < 65536 && port != net_hostport)
    {
        net_hostport = port;
        Con_Printf("Port set to %d\n", net_hostport);
    }
    return noport;
}


static bool testInProgress = false;
static int testPollCount;
static int testDriver;
static sys_socket_t testSocket;

static void Test_Poll(void*);
static PollProcedure testPollProcedure = {nullptr, 0.0, Test_Poll};

static void Test_Poll(void* unused)
{
    (void)unused;

    struct qsockaddr clientaddr;
    int control;
    int len;
    char name[32];
    char address[64];
    int colors;
    int frags;
    int connectTime;

    net_landriverlevel = testDriver;

    while(true)
    {
        len = dfunc.Read(
            testSocket, net_message.data, net_message.maxsize, &clientaddr);
        if(len < (int)sizeof(int))
        {
            break;
        }

        net_message.cursize = len;

        MSG_BeginReading();
        control = BigLong(*((int*)net_message.data));
        (void)MSG_ReadLong();
        if(control == -1)
        {
            break;
        }
        if((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL)
        {
            break;
        }
        if((control & NETFLAG_LENGTH_MASK) != len)
        {
            break;
        }

        if(MSG_ReadByte() != CCREP_PLAYER_INFO)
        {
            Sys_Error("Unexpected repsonse to Player Info request\n");
        }

        (void)MSG_ReadByte(); /* playerNumber */
        Q_strcpy(name, MSG_ReadString());
        colors = MSG_ReadLong();
        frags = MSG_ReadLong();
        connectTime = MSG_ReadLong();
        Q_strcpy(address, MSG_ReadString());

        Con_Printf("%s\n  frags:%3i  colors:%d %d  time:%d\n  %s\n", name,
            frags, colors >> 4, colors & 0x0f, connectTime / 60, address);
    }

    testPollCount--;
    if(testPollCount)
    {
        SchedulePollProcedure(&testPollProcedure, 0.1);
    }
    else
    {
        dfunc.Close_Socket(testSocket);
        testInProgress = false;
    }
}

static void Test_f()
{
    const char* host;
    size_t n;                         // QSS
    size_t maxusers = MAX_SCOREBOARD; // QSS
    struct qsockaddr sendaddr;

    if(testInProgress)
    {
        return;
    }

    host = Strip_Port(Cmd_Argv(1));

    if(host && hostCacheCount)
    {
        for(n = 0; n < hostCacheCount; n++)
        {
            if(q_strcasecmp(host, hostcache[n].name) == 0)
            {
                if(hostcache[n].driver != myDriverLevel)
                {
                    continue;
                }
                net_landriverlevel = hostcache[n].ldriver;
                maxusers = hostcache[n].maxusers;
                Q_memcpy(
                    &sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr));
                break;
            }
        }
        if(n < hostCacheCount)
        {
            goto JustDoIt;
        }
    }

    for(net_landriverlevel = 0; net_landriverlevel < net_numlandrivers;
        net_landriverlevel++)
    {
        if(!net_landrivers[net_landriverlevel].initialized)
        {
            continue;
        }

        // see if we can resolve the host name
        if(dfunc.GetAddrFromName(host, &sendaddr) != -1)
        {
            break;
        }
    }

    if(net_landriverlevel == net_numlandrivers)
    {
        Con_Printf("Could not resolve %s\n", host);
        return;
    }

JustDoIt:
    testSocket = dfunc.Open_Socket(0);
    if(testSocket == INVALID_SOCKET)
    {
        return;
    }

    testInProgress = true;
    testPollCount = 20;
    testDriver = net_landriverlevel;

    for(n = 0; n < maxusers; n++)
    {
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREQ_PLAYER_INFO);
        MSG_WriteByte(&net_message, n);
        *((int*)net_message.data) =
            BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(
            testSocket, net_message.data, net_message.cursize, &sendaddr);
    }
    SZ_Clear(&net_message);
    SchedulePollProcedure(&testPollProcedure, 0.1);
}


static bool test2InProgress = false;
static int test2Driver;
static sys_socket_t test2Socket;

static void Test2_Poll(void*);
static PollProcedure test2PollProcedure = {nullptr, 0.0, Test2_Poll};

static void Test2_Poll(void* unused)
{
    (void)unused;

    struct qsockaddr clientaddr;
    int control;
    int len;
    char name[256];
    char value[256];

    net_landriverlevel = test2Driver;
    name[0] = 0;

    len = dfunc.Read(
        test2Socket, net_message.data, net_message.maxsize, &clientaddr);
    if(len < (int)sizeof(int))
    {
        goto Reschedule;
    }

    net_message.cursize = len;

    MSG_BeginReading();
    control = BigLong(*((int*)net_message.data));
    (void)MSG_ReadLong();
    if(control == -1)
    {
        goto Error;
    }
    if((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL)
    {
        goto Error;
    }
    if((control & NETFLAG_LENGTH_MASK) != len)
    {
        goto Error;
    }

    if(MSG_ReadByte() != CCREP_RULE_INFO)
    {
        goto Error;
    }

    Q_strcpy(name, MSG_ReadString());
    if(name[0] == 0)
    {
        goto Done;
    }
    Q_strcpy(value, MSG_ReadString());

    Con_Printf("%-16.16s  %-16.16s\n", name, value);

    SZ_Clear(&net_message);
    // save space for the header, filled in later
    MSG_WriteLong(&net_message, 0);
    MSG_WriteByte(&net_message, CCREQ_RULE_INFO);
    MSG_WriteString(&net_message, name);
    *((int*)net_message.data) =
        BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
    dfunc.Write(
        test2Socket, net_message.data, net_message.cursize, &clientaddr);
    SZ_Clear(&net_message);

Reschedule:
    SchedulePollProcedure(&test2PollProcedure, 0.05);
    return;

Error:
    Con_Printf("Unexpected repsonse to Rule Info request\n");
Done:
    dfunc.Close_Socket(test2Socket);
    test2InProgress = false;
}

static void Test2_f()
{
    const char* host;
    size_t n; // QSS
    struct qsockaddr sendaddr;

    if(test2InProgress)
    {
        return;
    }

    host = Strip_Port(Cmd_Argv(1));

    if(host && hostCacheCount)
    {
        for(n = 0; n < hostCacheCount; n++)
        {
            if(q_strcasecmp(host, hostcache[n].name) == 0)
            {
                if(hostcache[n].driver != myDriverLevel)
                {
                    continue;
                }
                net_landriverlevel = hostcache[n].ldriver;
                Q_memcpy(
                    &sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr));
                break;
            }
        }

        if(n < hostCacheCount)
        {
            goto JustDoIt;
        }
    }

    for(net_landriverlevel = 0; net_landriverlevel < net_numlandrivers;
        net_landriverlevel++)
    {
        if(!net_landrivers[net_landriverlevel].initialized)
        {
            continue;
        }

        // see if we can resolve the host name
        if(dfunc.GetAddrFromName(host, &sendaddr) != -1)
        {
            break;
        }
    }

    if(net_landriverlevel == net_numlandrivers)
    {
        Con_Printf("Could not resolve %s\n", host);
        return;
    }

JustDoIt:
    test2Socket = dfunc.Open_Socket(0);
    if(test2Socket == INVALID_SOCKET)
    {
        return;
    }

    test2InProgress = true;
    test2Driver = net_landriverlevel;

    SZ_Clear(&net_message);
    // save space for the header, filled in later
    MSG_WriteLong(&net_message, 0);
    MSG_WriteByte(&net_message, CCREQ_RULE_INFO);
    MSG_WriteString(&net_message, "");
    *((int*)net_message.data) =
        BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
    dfunc.Write(test2Socket, net_message.data, net_message.cursize, &sendaddr);
    SZ_Clear(&net_message);
    SchedulePollProcedure(&test2PollProcedure, 0.05);
}


int Datagram_Init()
{
    int i;
    int num_inited;
    sys_socket_t csock;

#ifdef BAN_TEST
    banAddr.s_addr = INADDR_ANY;
    banMask.s_addr = INADDR_NONE;
#endif
    myDriverLevel = net_driverlevel;

    Cmd_AddCommand("net_stats", NET_Stats_f);

    if(safemode || COM_CheckParm("-nolan"))
    {
        return -1;
    }

    num_inited = 0;
    for(i = 0; i < net_numlandrivers; i++)
    {
        csock = net_landrivers[i].Init();
        if(csock == INVALID_SOCKET)
        {
            continue;
        }
        net_landrivers[i].initialized = true;
        net_landrivers[i].controlSock = csock;
        net_landrivers[i].listeningSock = INVALID_SOCKET; // QSS
        num_inited++;
    }

    if(num_inited == 0)
    {
        return -1;
    }

#ifdef BAN_TEST
    Cmd_AddCommand_ClientCommand("ban", NET_Ban_f); // QSS
#endif
    Cmd_AddCommand("test", Test_f);
    Cmd_AddCommand("test2", Test2_f);

    return 0;
}


void Datagram_Shutdown()
{
    int i;

    Datagram_Listen(false); // QSS

    //
    // shutdown the lan drivers
    //
    for(i = 0; i < net_numlandrivers; i++)
    {
        if(net_landrivers[i].initialized)
        {
            net_landrivers[i].Shutdown();
            net_landrivers[i].initialized = false;
        }
    }
}


void Datagram_Close(qsocket_t* sock)
{
    // QSS
    if(sock->isvirtual)
    {
        sock->isvirtual = false;
        sock->socket = INVALID_SOCKET;
    }
    else
    {
        sfunc.Close_Socket(sock->socket);
    }
}


void Datagram_Listen(bool state)
{
    int i;

    // QSS
    qsocket_t* s;
    bool islistening = false;
    heartbeat_time = 0; // reset it

    // QSS
    for(i = 0; i < net_numlandrivers; i++)
    {
        if(net_landrivers[i].initialized)
        {
            net_landrivers[i].listeningSock = net_landrivers[i].Listen(state);
            if(net_landrivers[i].listeningSock != INVALID_SOCKET)
            {
                islistening = true;
            }

            for(s = net_activeSockets; s; s = s->next)
            {
                if(s->isvirtual)
                {
                    s->isvirtual = false;
                    s->socket = INVALID_SOCKET;
                }
            }
        }
    }
    if(state && !islistening)
    {
        if(isDedicated)
        {
            Sys_Error("Unable to open any listening sockets\n");
        }
        Con_Warning("Unable to open any listening sockets\n");
    }
}

// QSS
static struct qsockaddr rcon_response_address;
static sys_socket_t rcon_response_socket;
static sys_socket_t rcon_response_landriver;
void Datagram_Rcon_Flush(const char* text)
{
    sizebuf_t msg;
    byte buffer[8192];
    msg.data = buffer;
    msg.maxsize = sizeof(buffer);
    msg.allowoverflow = true;
    SZ_Clear(&msg);
    // save space for the header, filled in later
    MSG_WriteLong(&msg, 0);
    MSG_WriteByte(&msg, CCREP_RCON);
    MSG_WriteString(&msg, text);
    if(msg.overflowed)
    {
        return;
    }
    *((int*)msg.data) =
        BigLong(NETFLAG_CTL | (msg.cursize & NETFLAG_LENGTH_MASK));
    net_landrivers[rcon_response_landriver].Write(
        rcon_response_socket, msg.data, msg.cursize, &rcon_response_address);
}
// ---

static void _Datagram_ServerControlPacket(sys_socket_t acceptsock,
    struct qsockaddr* clientaddr, byte* data, unsigned int length)
{
    struct qsockaddr newaddr;
    qsocket_t* sock;
    qsocket_t* s;
    int command;
    int control;
    int ret;
    int plnum;
    int mod; //, mod_ver, mod_flags, mod_passwd;	//proquake extensions

    control = BigLong(*((int*)data));
    if(control == -1)
    {
        if(!sv_public.value)
        {
            return;
        }
        data[length] = 0;
        Cmd_TokenizeString((char*)data + 4);
        if(!strcmp(Cmd_Argv(0), "getinfo") || !strcmp(Cmd_Argv(0), "getstatus"))
        { // master, as well as other clients, may send us one of these two
          // packets to get our serverinfo data
            // masters only really need gamename and player counts. actual
            // clients might want player names too.
            bool full = !strcmp(Cmd_Argv(0), "getstatus");
            char cookie[128];
            const char* s = Cmd_Args();
            const char* gamedir = COM_GetGameNames(false);
            unsigned int numclients = 0, numbots = 0;
            int i;
            size_t j;
            if(!s)
            {
                s = "";
            }
            q_strlcpy(cookie, s, sizeof(cookie));

            for(i = 0; i < svs.maxclients; i++)
            {
                if(svs.clients[i].active)
                {
                    numclients++;
                    if(!svs.clients[i].netconnection)
                    {
                        numbots++;
                    }
                }
            }

            SZ_Clear(&net_message);
            MSG_WriteLong(&net_message, -1);
            MSG_WriteString(
                &net_message, full ? "statusResponse" : "infoResponse\n");
            net_message.cursize--;
            COM_Parse(com_protocolname.string);
            if(*com_token) // the master server needs this. This tells the
                           // master which game we should be listed as.
            {
                MSG_WriteString(&net_message, va("\\gamename\\%s", com_token));
                net_message.cursize--;
            }
            MSG_WriteString(&net_message, "\\protocol\\3");
            net_message.cursize--; // this is stupid

            MSG_WriteString(&net_message, "\\ver\\" ENGINE_NAME_AND_VER);
            net_message.cursize--;
            MSG_WriteString(&net_message, va("\\nqprotocol\\%u", sv.protocol));
            net_message.cursize--;
            if(*gamedir)
            {
                MSG_WriteString(&net_message, va("\\modname\\%s", gamedir));
                net_message.cursize--;
            }
            if(*sv.name)
            {
                MSG_WriteString(&net_message, va("\\mapname\\%s", sv.name));
                net_message.cursize--;
            }
            if(*deathmatch.string)
            {
                MSG_WriteString(
                    &net_message, va("\\deathmatch\\%s", deathmatch.string));
                net_message.cursize--;
            }
            if(*teamplay.string)
            {
                MSG_WriteString(
                    &net_message, va("\\teamplay\\%s", teamplay.string));
                net_message.cursize--;
            }
            if(*hostname.string)
            {
                MSG_WriteString(
                    &net_message, va("\\hostname\\%s", hostname.string));
                net_message.cursize--;
            }
            MSG_WriteString(&net_message, va("\\clients\\%u", numclients));
            net_message.cursize--;
            if(numbots)
            {
                MSG_WriteString(&net_message, va("\\bots\\%u", numbots));
                net_message.cursize--;
            }
            MSG_WriteString(
                &net_message, va("\\sv_maxclients\\%i", svs.maxclients));
            net_message.cursize--;
            if(*cookie)
            {
                MSG_WriteString(&net_message, va("\\challenge\\%s", cookie));
                net_message.cursize--;
            }

            if(full)
            {
                for(i = 0; i < svs.maxclients; i++)
                {
                    if(svs.clients[i].active)
                    {
                        float total = 0;
                        for(j = 0; j < NUM_PING_TIMES; j++)
                        {
                            total += svs.clients[i].ping_times[j];
                        }
                        total /= NUM_PING_TIMES;
                        total *= 1000; // put it in ms

                        MSG_WriteString(&net_message,
                            va("\n%i %i %i_%i \"%s\"", svs.clients[i].old_frags,
                                (int)total, svs.clients[i].colors & 15,
                                svs.clients[i].colors >> 4,
                                svs.clients[i].name));
                        net_message.cursize--;
                    }
                }
            }

            dfunc.Write(
                acceptsock, net_message.data, net_message.cursize, clientaddr);
            SZ_Clear(&net_message);
        }
        return;
    }
    if((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL)
    {
        return;
    }
    if((control & NETFLAG_LENGTH_MASK) != length)
    {
        return;
    }

    // sigh... FIXME: potentially abusive memcpy
    SZ_Clear(&net_message);
    SZ_Write(&net_message, data, length);

    MSG_BeginReading();
    (void)MSG_ReadLong();

    command = MSG_ReadByte();
    if(command == CCREQ_SERVER_INFO)
    {
        if(Q_strcmp(MSG_ReadString(), "QUAKE") != 0)
        {
            return;
        }

        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_SERVER_INFO);
        dfunc.GetSocketAddr(acceptsock, &newaddr);
        MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr, false));
        MSG_WriteString(&net_message, hostname.string);
        MSG_WriteString(&net_message, sv.name);
        MSG_WriteByte(&net_message, net_activeconnections);
        MSG_WriteByte(&net_message, svs.maxclients);
        MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
        *((int*)net_message.data) =
            BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(
            acceptsock, net_message.data, net_message.cursize, clientaddr);
        SZ_Clear(&net_message);
        return;
    }

    if(command == CCREQ_PLAYER_INFO)
    {
        int playerNumber;
        int activeNumber;
        int clientNumber;
        client_t* client;

        playerNumber = MSG_ReadByte();
        activeNumber = -1;

        for(clientNumber = 0, client = svs.clients;
            clientNumber < svs.maxclients; clientNumber++, client++)
        {
            if(client->active)
            {
                activeNumber++;
                if(activeNumber == playerNumber)
                {
                    break;
                }
            }
        }

        if(clientNumber == svs.maxclients)
        {
            return;
        }

        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_PLAYER_INFO);
        MSG_WriteByte(&net_message, playerNumber);
        MSG_WriteString(&net_message, client->name);
        MSG_WriteLong(&net_message, client->colors);
        MSG_WriteLong(&net_message, (int)client->edict->v.frags);
        if(!client->netconnection)
        {
            MSG_WriteLong(&net_message, 0);
            MSG_WriteString(&net_message, "Bot");
        }
        else
        {
            MSG_WriteLong(&net_message,
                (int)(net_time - client->netconnection->connecttime));
            MSG_WriteString(&net_message,
                NET_QSocketGetMaskedAddressString(client->netconnection));
        }
        *((int*)net_message.data) =
            BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(
            acceptsock, net_message.data, net_message.cursize, clientaddr);
        SZ_Clear(&net_message);

        return;
    }

    if(command == CCREQ_RULE_INFO)
    {
        const char* prevCvarName;
        cvar_t* var;

        // find the search start location
        prevCvarName = MSG_ReadString();
        var = Cvar_FindVarAfter(prevCvarName, CVAR_SERVERINFO);

        // send the response
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_RULE_INFO);
        if(var)
        {
            MSG_WriteString(&net_message, var->name);
            MSG_WriteString(&net_message, var->string);
        }
        *((int*)net_message.data) =
            BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(
            acceptsock, net_message.data, net_message.cursize, clientaddr);
        SZ_Clear(&net_message);

        return;
    }

    if(command == CCREQ_RCON)
    {
        const char* password =
            MSG_ReadString(); // FIXME: this really needs crypto
        const char* response;

        rcon_response_address = *clientaddr;
        rcon_response_socket = acceptsock;
        rcon_response_landriver = net_landriverlevel;

        if(!*rcon_password.string)
        {
            response = "rcon is not enabled on this server";
        }
        else if(!strcmp(password, rcon_password.string))
        {
            Con_Redirect(Datagram_Rcon_Flush);
            Cmd_ExecuteString(MSG_ReadString(), src_command);
            Con_Redirect(nullptr);
            return;
        }
        else if(!strcmp(password, "password"))
        {
            response = "What, you really thought that would work? Seriously?";
        }
        else if(!strcmp(password, "thebackdoor"))
        {
            response =
                "Oh look! You found the backdoor. Don't let it slam you in the "
                "face on your way out.";
        }
        else
        {
            response = "Your password is just WRONG dude.";
        }

        Datagram_Rcon_Flush(response);
        return;
    }

    if(command != CCREQ_CONNECT)
    {
        return;
    }

    if(Q_strcmp(MSG_ReadString(), "QUAKE") != 0)
    {
        return;
    }

    if(MSG_ReadByte() != NET_PROTOCOL_VERSION)
    {
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_REJECT);
        MSG_WriteString(&net_message, "Incompatible version.\n");
        *((int*)net_message.data) =
            BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(
            acceptsock, net_message.data, net_message.cursize, clientaddr);
        SZ_Clear(&net_message);
        return;
    }

    // read proquake extensions
    mod = MSG_ReadByte();
    if(msg_badread)
    {
        mod = 0;
    }
#if 0
	mod_ver = MSG_ReadByte();
	if (msg_badread) mod_ver = 0;
	mod_flags = MSG_ReadByte();
	if (msg_badread) mod_flags = 0;
	mod_passwd = MSG_ReadLong();
	if (msg_badread) mod_passwd = 0;
	(void)mod_ver;
	(void)mod_flags;
	(void)mod_passwd;
#endif

#ifdef BAN_TEST
    // check for a ban
    // fixme: no ipv6
    // fixme: only a single address? someone seriously underestimates tor.
    if(clientaddr->qsa_family == AF_INET)
    {
        in_addr_t testAddr;
        testAddr = ((struct sockaddr_in*)clientaddr)->sin_addr.s_addr;
        if((testAddr & banMask.s_addr) == banAddr.s_addr)
        {
            SZ_Clear(&net_message);
            // save space for the header, filled in later
            MSG_WriteLong(&net_message, 0);
            MSG_WriteByte(&net_message, CCREP_REJECT);
            MSG_WriteString(&net_message, "You have been banned.\n");
            *((int*)net_message.data) = BigLong(
                NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
            dfunc.Write(
                acceptsock, net_message.data, net_message.cursize, clientaddr);
            SZ_Clear(&net_message);
            return;
        }
    }
#endif

    // see if this guy is already connected
    for(s = net_activeSockets; s; s = s->next)
    {
        if(s->driver != net_driverlevel)
        {
            continue;
        }
        if(s->disconnected)
        {
            continue;
        }
        ret = dfunc.AddrCompare(clientaddr, &s->addr);
        if(ret == 0)
        {
            int i;

            // is this a duplicate connection reqeust?
            if(ret == 0 && net_time - s->connecttime < 2.0)
            {
                // yes, so send a duplicate reply
                SZ_Clear(&net_message);
                // save space for the header, filled in later
                MSG_WriteLong(&net_message, 0);
                MSG_WriteByte(&net_message, CCREP_ACCEPT);
                dfunc.GetSocketAddr(s->socket, &newaddr);
                MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr));
                if(s->proquake_angle_hack)
                {
                    MSG_WriteByte(&net_message, 1); // proquake
                    MSG_WriteByte(&net_message,
                        30); // ver 30 should be safe. 34 screws with our
                             // single-server-socket stuff.
                    MSG_WriteByte(&net_message, 0); // no flags
                }
                *((int*)net_message.data) = BigLong(
                    NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
                dfunc.Write(acceptsock, net_message.data, net_message.cursize,
                    clientaddr);
                SZ_Clear(&net_message);
                return;
            }
            // it's somebody coming back in from a crash/disconnect
            // so close the old qsocket and let their retry get them back in
            //			NET_Close(s);
            //			return;

            // FIXME: ideally we would just switch the connection over and
            // restart it with a serverinfo packet. warning: there might be
            // packets in-flight which might mess up unreliable sequences. so we
            // attempt to ignore the request, and let the user restart.
            // FIXME: if this is an issue, it should be possible to reuse the
            // previous connection's outgoing unreliable sequence. reliables
            // should be less of an issue as stray ones will be ignored anyway.
            // FIXME: needs challenges, so that other clients can't determine
            // ip's and spoof a reconnect.
            for(i = 0; i < svs.maxclients; i++)
            {
                if(svs.clients[i].netconnection == s)
                {
                    NET_Close(s); // close early, to avoid svc_disconnects
                                  // confusing things.
                    host_client = &svs.clients[i];
                    SV_DropClient(false);
                    break;
                }
            }
            return;
        }
    }

    // find a free player slot
    for(plnum = 0; plnum < svs.maxclients; plnum++)
    {
        if(!svs.clients[plnum].active)
        {
            break;
        }
    }
    if(plnum < svs.maxclients)
    {
        sock = NET_NewQSocket();
    }
    else
    {
        sock = nullptr; // can happen due to botclients.
    }

    if(sock == nullptr)
    {
        // no room; try to let him know
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_REJECT);
        MSG_WriteString(&net_message, "Server is full.\n");
        *((int*)net_message.data) =
            BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(
            acceptsock, net_message.data, net_message.cursize, clientaddr);
        SZ_Clear(&net_message);
        return;
    }

    sock->proquake_angle_hack = (mod == 1);

    // everything is allocated, just fill in the details
    sock->isvirtual = true;
    sock->socket = acceptsock;
    sock->landriver = net_landriverlevel;
    sock->addr = *clientaddr;
    Q_strcpy(sock->trueaddress, dfunc.AddrToString(clientaddr, false));
    Q_strcpy(sock->maskedaddress, dfunc.AddrToString(clientaddr, true));

    // send him back the info about the server connection he has been allocated
    SZ_Clear(&net_message);
    // save space for the header, filled in later
    MSG_WriteLong(&net_message, 0);
    MSG_WriteByte(&net_message, CCREP_ACCEPT);
    dfunc.GetSocketAddr(sock->socket, &newaddr);
    MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr));
    //	MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr));
    if(sock->proquake_angle_hack)
    {
        MSG_WriteByte(&net_message, 1);  // proquake
        MSG_WriteByte(&net_message, 30); // ver 30 should be safe. 34 screws
                                         // with our single-server-socket stuff.
        MSG_WriteByte(&net_message, 0);
    }
    *((int*)net_message.data) =
        BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
    dfunc.Write(acceptsock, net_message.data, net_message.cursize, clientaddr);
    SZ_Clear(&net_message);

    // spawn the client.
    // FIXME: come up with some challenge mechanism so that we don't go to the
    // expense of spamming serverinfos+modellists+etc until we know that its an
    // actual connection attempt.
    svs.clients[plnum].netconnection = sock;
    SV_ConnectClient(plnum);
}

qsocket_t* Datagram_CheckNewConnections()
{
    // only needs to do master stuff now
    if(sv_public.value > 0)
    {
        if(Sys_DoubleTime() > heartbeat_time)
        {
            // darkplaces here refers to the master server protocol, rather than
            // the game protocol (specifies that the server responds to
            // infoRequest packets from the master)
            const char* str = "\377\377\377\377heartbeat DarkPlaces\n";
            size_t k;
            struct qsockaddr addr;
            heartbeat_time = Sys_DoubleTime() + 300;

            for(k = 0; net_masters[k].string; k++)
            {
                if(!*net_masters[k].string)
                {
                    continue;
                }
                for(net_landriverlevel = 0;
                    net_landriverlevel < net_numlandrivers;
                    net_landriverlevel++)
                {
                    if(net_landrivers[net_landriverlevel].initialized &&
                        dfunc.listeningSock != INVALID_SOCKET)
                    {
                        if(dfunc.GetAddrFromName(
                               net_masters[k].string, &addr) >= 0)
                        {
                            if(sv_reportheartbeats.value)
                            {
                                Con_Printf("Sending heartbeat to %s\n",
                                    net_masters[k].string);
                            }
                            dfunc.Write(dfunc.listeningSock, (byte*)str,
                                strlen(str), &addr);
                        }
                        else
                        {
                            if(sv_reportheartbeats.value)
                            {
                                Con_Printf("Unable to resolve %s\n",
                                    net_masters[k].string);
                            }
                        }
                    }
                }
            }
        }
    }

    return nullptr;
}

static void _Datagram_SendServerQuery(struct qsockaddr* addr, bool master)
{
    SZ_Clear(&net_message);
    if(master) // assume false if you want only the protocol 15 servers.
    {
        MSG_WriteLong(&net_message, ~0);
        MSG_WriteString(&net_message, "getinfo");
    }
    else
    {
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREQ_SERVER_INFO);
        MSG_WriteString(&net_message, "QUAKE");
        MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
        *((int*)net_message.data) =
            BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
    }
    dfunc.Write(dfunc.controlSock, net_message.data, net_message.cursize, addr);
    SZ_Clear(&net_message);
}
static struct HostList
{
    int driver;
    bool requery;
    bool master;
    struct qsockaddr addr;
} * hostlist;
size_t hostlist_count;
size_t hostlist_max;
static void _Datagram_AddPossibleHost(struct qsockaddr* addr, bool master)
{
    size_t u;
    for(u = 0; u < hostlist_count; u++)
    {
        if(!memcmp(&hostlist[u].addr, addr, sizeof(struct qsockaddr)) &&
            hostlist[u].driver == net_landriverlevel)
        { // we already know about it. it must have come from some other master.
          // don't respam.
            return;
        }
    }
    if(hostlist_count == hostlist_max)
    {
        hostlist_max = hostlist_count + 16;
        hostlist =
            (HostList*)Z_Realloc(hostlist, sizeof(*hostlist) * hostlist_max);
    }
    hostlist[hostlist_count].addr = *addr;
    hostlist[hostlist_count].requery = true;
    hostlist[hostlist_count].master = master;
    hostlist[hostlist_count].driver = net_landriverlevel;
    hostlist_count++;
}


static void Info_ReadKey(
    const char* info, const char* key, char* out, size_t outsize)
{
    size_t keylen = strlen(key);
    while(*info)
    {
        if(*info++ != '\\')
        {
            break; // error / end-of-string
        }

        if(!strncmp(info, key, keylen) && info[keylen] == '\\')
        {
            char *o = out, *e = out + outsize - 1;

            // skip the key name
            info += keylen + 1;
            // this is the old value for the key. copy it to the result
            while(*info && *info != '\\' && o < e)
            {
                *o++ = *info++;
            }
            *o++ = 0;

            // success!
            return;
        }
        else
        {
            // skip the key
            while(*info && *info != '\\')
            {
                info++;
            }

            // validate that its a value now
            if(*info++ != '\\')
            {
                break; // error
            }
            // skip the value
            while(*info && *info != '\\')
            {
                info++;
            }
        }
    }
    *out = 0;
}


static bool _Datagram_SearchForHosts(bool xmit)
{
    int ret;
    size_t n;
    size_t i;
    struct qsockaddr readaddr;
    struct qsockaddr myaddr;
    int control;
    bool sentsomething = false;

    dfunc.GetSocketAddr(dfunc.controlSock, &myaddr);
    if(xmit)
    {
        for(i = 0; i < hostlist_count; i++)
        {
            hostlist[i].requery = true;
        }

        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREQ_SERVER_INFO);
        MSG_WriteString(&net_message, "QUAKE");
        MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
        *((int*)net_message.data) =
            BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Broadcast(
            dfunc.controlSock, net_message.data, net_message.cursize);
        SZ_Clear(&net_message);

        if(slistScope == SLIST_INTERNET)
        {
            struct qsockaddr masteraddr;
            char* str;
            size_t m;
            for(m = 0; net_masters[m].string; m++)
            {
                if(!*net_masters[m].string)
                {
                    continue;
                }
                if(dfunc.GetAddrFromName(net_masters[m].string, &masteraddr) >=
                    0)
                {
                    const char* prot = com_protocolname.string;
                    while(*prot)
                    { // send a request for each protocol
                        prot = COM_Parse(prot);
                        if(!prot)
                        {
                            break;
                        }
                        if(*com_token)
                        {
                            if(masteraddr.qsa_family == AF_INET6)
                            {
                                str =
                                    va("%c%c%c%cgetserversExt %s %u empty full "
                                       "ipv6" /*\x0A\n"*/,
                                        255, 255, 255, 255, com_token,
                                        NET_PROTOCOL_VERSION);
                            }
                            else
                            {
                                str = va(
                                    "%c%c%c%cgetservers %s %u empty full" /*\x0A\n"*/
                                    ,
                                    255, 255, 255, 255, com_token,
                                    NET_PROTOCOL_VERSION);
                            }
                            dfunc.Write(dfunc.controlSock, (byte*)str,
                                strlen(str), &masteraddr);
                        }
                    }
                }
            }
        }
        sentsomething = true;
    }

    while((ret = dfunc.Read(dfunc.controlSock, net_message.data,
               net_message.maxsize, &readaddr)) > 0)
    {
        if(ret < (int)sizeof(int))
        {
            continue;
        }
        net_message.cursize = ret;

        // don't answer our own query
        // Note: this doesn't really work too well if we're multi-homed.
        // we should probably just refuse to respond to serverinfo requests
        // while we're scanning (chances are our server is going to die anyway).
        if(dfunc.AddrCompare(&readaddr, &myaddr) >= 0)
        {
            continue;
        }

        // is the cache full?
        if(hostCacheCount == HOSTCACHESIZE)
        {
            continue;
        }

        MSG_BeginReading();
        control = BigLong(*((int*)net_message.data));
        (void)MSG_ReadLong();
        if(control == -1)
        {
            if(msg_readcount + 19 <= net_message.cursize &&
                !strncmp((char*)net_message.data + msg_readcount,
                    "getserversResponse", 18))
            {
                struct qsockaddr addr;
                int i;
                msg_readcount += 18;
                for(;;)
                {
                    switch(MSG_ReadByte())
                    {
                        case '\\':
                            memset(&addr, 0, sizeof(addr));
                            addr.qsa_family = AF_INET;
                            for(i = 0; i < 4; i++)
                            {
                                ((byte*)&((struct sockaddr_in*)&addr)
                                        ->sin_addr)[i] = MSG_ReadByte();
                            }
                            ((byte*)&((struct sockaddr_in*)&addr)
                                    ->sin_port)[0] = MSG_ReadByte();
                            ((byte*)&((struct sockaddr_in*)&addr)
                                    ->sin_port)[1] = MSG_ReadByte();
                            if(!((struct sockaddr_in*)&addr)->sin_port)
                            {
                                msg_badread = true;
                            }
                            break;
                        case '/':
                            memset(&addr, 0, sizeof(addr));
                            addr.qsa_family = AF_INET6;
                            for(i = 0; i < 16; i++)
                            {
                                ((byte*)&((struct sockaddr_in6*)&addr)
                                        ->sin6_addr)[i] = MSG_ReadByte();
                            }
                            ((byte*)&((struct sockaddr_in6*)&addr)
                                    ->sin6_port)[0] = MSG_ReadByte();
                            ((byte*)&((struct sockaddr_in6*)&addr)
                                    ->sin6_port)[1] = MSG_ReadByte();
                            if(!((struct sockaddr_in6*)&addr)->sin6_port)
                            {
                                msg_badread = true;
                            }
                            break;
                        default:
                            memset(&addr, 0, sizeof(addr));
                            msg_badread = true;
                            break;
                    }
                    if(msg_badread)
                    {
                        break;
                    }
                    _Datagram_AddPossibleHost(&addr, true);
                    sentsomething = true;
                }
            }
            else if(msg_readcount + 13 <= net_message.cursize &&
                    !strncmp((char*)net_message.data + msg_readcount,
                        "infoResponse\n", 13))
            { // response from a dpp7 server (or possibly 15, no idea really)
                char tmp[1024];
                const char* info = MSG_ReadString() + 13;

                // search the cache for this server
                for(n = 0; n < hostCacheCount; n++)
                {
                    if(dfunc.AddrCompare(&readaddr, &hostcache[n].addr) == 0)
                    {
                        break;
                    }
                }

                // is it already there?
                if(n < hostCacheCount)
                {
                    if(*hostcache[n].cname)
                    {
                        continue;
                    }
                }
                else
                {
                    // add it
                    hostCacheCount++;
                }
                Info_ReadKey(info, "hostname", hostcache[n].name,
                    sizeof(hostcache[n].name));
                Info_ReadKey(info, "mapname", hostcache[n].map,
                    sizeof(hostcache[n].map));
                Info_ReadKey(info, "modname", tmp, sizeof(tmp));
                if(!COM_GameDirMatches(tmp))
                {
                    q_strlcpy(hostcache[n].name, va("{%s}", tmp),
                        sizeof(hostcache[n].name));
                    q_strlcpy(hostcache[n].map, "", sizeof(hostcache[n].map));
                }

                Info_ReadKey(info, "clients", tmp, sizeof(tmp));
                hostcache[n].users = atoi(tmp);
                Info_ReadKey(info, "sv_maxclients", tmp, sizeof(tmp));
                hostcache[n].maxusers = atoi(tmp);
                Info_ReadKey(info, "protocol", tmp, sizeof(tmp));
                if(atoi(tmp) != NET_PROTOCOL_VERSION)
                {
                    Q_strcpy(hostcache[n].cname, hostcache[n].name);
                    Q_strcpy(hostcache[n].name, "*");
                    Q_strcat(hostcache[n].name, hostcache[n].cname);
                }
                Q_memcpy(
                    &hostcache[n].addr, &readaddr, sizeof(struct qsockaddr));
                hostcache[n].driver = net_driverlevel;
                hostcache[n].ldriver = net_landriverlevel;
                q_strlcpy(hostcache[n].cname,
                    dfunc.AddrToString(&readaddr, false),
                    sizeof(hostcache[n].cname));

                // check for a name conflict
                for(i = 0; i < hostCacheCount; i++)
                {
                    if(i == n)
                    {
                        continue;
                    }
                    if(q_strcasecmp(hostcache[n].cname, hostcache[i].cname) ==
                        0)
                    { // this is a dupe.
                        hostCacheCount--;
                        break;
                    }
                    if(q_strcasecmp(hostcache[n].name, hostcache[i].name) == 0)
                    {
                        i = Q_strlen(hostcache[n].name);
                        if(i < 15 && hostcache[n].name[i - 1] > '8')
                        {
                            hostcache[n].name[i] = '0';
                            hostcache[n].name[i + 1] = 0;
                        }
                        else
                        {
                            hostcache[n].name[i - 1]++;
                        }

                        i = -1;
                    }
                }
            }
            continue;
        }
        if((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL)
        {
            continue;
        }
        if((control & NETFLAG_LENGTH_MASK) != ret)
        {
            continue;
        }

        if(MSG_ReadByte() != CCREP_SERVER_INFO)
        {
            continue;
        }

        (void)MSG_ReadString();
        // dfunc.GetAddrFromName(MSG_ReadString(), &peeraddr);
        /*if (dfunc.AddrCompare(&readaddr, &peeraddr) != 0)
        {
            char read[NET_NAMELEN];
            char peer[NET_NAMELEN];
            q_strlcpy(read, dfunc.AddrToString(&readaddr), sizeof(read));
            q_strlcpy(peer, dfunc.AddrToString(&peeraddr), sizeof(peer));
            Con_SafePrintf("Server at %s claimed to be at %s\n", read, peer);
        }*/

        // search the cache for this server
        for(n = 0; n < hostCacheCount; n++)
        {
            if(dfunc.AddrCompare(&readaddr, &hostcache[n].addr) == 0)
            {
                break;
            }
        }

        // is it already there?
        if(n < hostCacheCount)
        {
            if(*hostcache[n].cname)
            {
                continue;
            }
        }
        else
        {
            // add it
            hostCacheCount++;
        }
        q_strlcpy(
            hostcache[n].name, MSG_ReadString(), sizeof(hostcache[n].name));
        q_strlcpy(hostcache[n].map, MSG_ReadString(), sizeof(hostcache[n].map));
        hostcache[n].users = MSG_ReadByte();
        hostcache[n].maxusers = MSG_ReadByte();
        if(MSG_ReadByte() != NET_PROTOCOL_VERSION)
        {
            Q_strcpy(hostcache[n].cname, hostcache[n].name);
            hostcache[n].cname[14] = 0;
            Q_strcpy(hostcache[n].name, "*");
            Q_strcat(hostcache[n].name, hostcache[n].cname);
        }
        Q_memcpy(&hostcache[n].addr, &readaddr, sizeof(struct qsockaddr));
        hostcache[n].driver = net_driverlevel;
        hostcache[n].ldriver = net_landriverlevel;
        q_strlcpy(hostcache[n].cname, dfunc.AddrToString(&readaddr, false),
            sizeof(hostcache[n].cname));

        // check for a name conflict
        for(i = 0; i < hostCacheCount; i++)
        {
            if(i == n)
            {
                continue;
            }
            if(q_strcasecmp(hostcache[n].cname, hostcache[i].cname) == 0)
            { // this is a dupe.
                hostCacheCount--;
                break;
            }
            if(q_strcasecmp(hostcache[n].name, hostcache[i].name) == 0)
            {
                i = Q_strlen(hostcache[n].name);
                if(i < 15 && hostcache[n].name[i - 1] > '8')
                {
                    hostcache[n].name[i] = '0';
                    hostcache[n].name[i + 1] = 0;
                }
                else
                {
                    hostcache[n].name[i - 1]++;
                }

                i = -1;
            }
        }
    }

    if(!xmit)
    {
        n = 4; // should be time-based. meh.
        for(i = 0; i < hostlist_count; i++)
        {
            if(hostlist[i].requery && hostlist[i].driver == net_landriverlevel)
            {
                hostlist[i].requery = false;
                _Datagram_SendServerQuery(
                    &hostlist[i].addr, hostlist[i].master);
                sentsomething = true;
                n--;
                if(!n)
                {
                    break;
                }
            }
        }
    }
    return sentsomething;
}

bool Datagram_SearchForHosts(bool xmit)
{
    bool ret = false;
    for(net_landriverlevel = 0; net_landriverlevel < net_numlandrivers;
        net_landriverlevel++)
    {
        if(hostCacheCount == HOSTCACHESIZE)
        {
            break;
        }
        if(net_landrivers[net_landriverlevel].initialized)
        {
            ret |= _Datagram_SearchForHosts(xmit);
        }
    }
    return ret;
}


static qsocket_t* _Datagram_Connect(struct qsockaddr* serveraddr)
{
    struct qsockaddr readaddr;
    qsocket_t* sock;
    sys_socket_t newsock;
    int ret;
    int reps;
    double start_time;
    int control;
    const char* reason;

    newsock = dfunc.Open_Socket(0);
    if(newsock == INVALID_SOCKET)
    {
        return nullptr;
    }

    sock = NET_NewQSocket();
    if(sock == nullptr)
    {
        goto ErrorReturn2;
    }
    sock->socket = newsock;
    sock->landriver = net_landriverlevel;

    // connect to the host
    if(dfunc.Connect(newsock, serveraddr) == -1)
    {
        goto ErrorReturn;
    }

    sock->proquake_angle_hack = true;

    // send the connection request
    Con_SafePrintf("trying...\n");
    SCR_UpdateScreen();
    start_time = net_time;

    for(reps = 0; reps < 3; reps++)
    {
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREQ_CONNECT);
        MSG_WriteString(&net_message, "QUAKE");
        MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
        if(sock->proquake_angle_hack)
        { /*Spike -- proquake compat. if both engines claim to be using mod==1
             then 16bit client->server angles can be used. server->client angles
             remain 16bit*/
            Con_DWarning("Attempting to use ProQuake angle hack\n");
            MSG_WriteByte(&net_message, 1);  /*'mod', 1=proquake*/
            MSG_WriteByte(&net_message, 34); /*'mod' version*/
            MSG_WriteByte(&net_message, 0);  /*flags*/
            MSG_WriteLong(&net_message,
                0); // strtoul(password.string, NULL, 0)); /*password*/
        }
        *((int*)net_message.data) =
            BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(newsock, net_message.data, net_message.cursize, serveraddr);
        SZ_Clear(&net_message);

// for dp compat. DP sends these in addition to the above packet.
// if the (DP) server is running using vanilla protocols, it replies to the
// above, otherwise to the following, requiring both to be sent. (challenges
// hinder a DOS issue known as smurfing, in that the client must prove that it
// owns the IP that it might be spoofing before any serious resources are used)
#define DPGETCHALLENGE "\xff\xff\xff\xffgetchallenge\n"
        dfunc.Write(
            newsock, (byte*)DPGETCHALLENGE, strlen(DPGETCHALLENGE), serveraddr);

        do
        {
            ret = dfunc.Read(
                newsock, net_message.data, net_message.maxsize, &readaddr);
            // if we got something, validate it
            if(ret > 0)
            {
                // is it from the right place?
                if(dfunc.AddrCompare(&readaddr, serveraddr) != 0)
                {
                    Con_SafePrintf("wrong reply address\n");
                    Con_SafePrintf("Expected: %s | %s\n",
                        dfunc.AddrToString(serveraddr, false),
                        StrAddr(serveraddr));
                    Con_SafePrintf("Received: %s | %s\n",
                        dfunc.AddrToString(&readaddr, false),
                        StrAddr(&readaddr));
                    SCR_UpdateScreen();
                    ret = 0;
                    continue;
                }

                if(ret < (int)sizeof(int))
                {
                    ret = 0;
                    continue;
                }

                net_message.cursize = ret;
                MSG_BeginReading();

                control = BigLong(*((int*)net_message.data));
                (void)MSG_ReadLong();
                if(control == -1)
                {
                    const char* s = MSG_ReadString();
                    if(!strncmp(s, "challenge ", 10))
                    { // either a q2 or dp server...
                        char buf[1024];
                        q_snprintf(buf, sizeof(buf),
                            "%c%c%c%cconnect\\protocol\\darkplaces "
                            "3\\protocols\\RMQ FITZ DP7 NEHAHRABJP3 "
                            "QUAKE\\challenge\\%s",
                            255, 255, 255, 255, s + 10);
                        dfunc.Write(
                            newsock, (byte*)buf, strlen(buf), serveraddr);
                    }
                    else if(!strcmp(s, "accept"))
                    {
                        Q_memcpy(
                            &sock->addr, serveraddr, sizeof(struct qsockaddr));
                        sock->proquake_angle_hack = false;
                        goto dpserveraccepted;
                    }
                    /*else if (!strcmp(s, "reject"))
                    {
                        reason = MSG_ReadString();
                        Con_Printf("%s\n", reason);
                        q_strlcpy(m_return_reason, reason,
                    sizeof(m_return_reason)); goto ErrorReturn;
                    }*/

                    ret = 0;
                    continue;
                }
                if((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL)
                {
                    ret = 0;
                    continue;
                }
                if((control & NETFLAG_LENGTH_MASK) != ret)
                {
                    ret = 0;
                    continue;
                }
            }
        } while(ret == 0 && (SetNetTime() - start_time) < 2.5);

        if(ret)
        {
            break;
        }

        Con_SafePrintf("still trying...\n");
        SCR_UpdateScreen();
        start_time = SetNetTime();
    }

    if(ret == 0)
    {
        reason = "No Response";
        Con_Printf("%s\n", reason);
        Q_strcpy(m_return_reason, reason);
        goto ErrorReturn;
    }

    if(ret == -1)
    {
        reason = "Network Error";
        Con_Printf("%s\n", reason);
        Q_strcpy(m_return_reason, reason);
        goto ErrorReturn;
    }

    ret = MSG_ReadByte();
    if(ret == CCREP_REJECT)
    {
        reason = MSG_ReadString();
        Con_Printf("%s\n", reason);
        q_strlcpy(m_return_reason, reason, sizeof(m_return_reason));
        goto ErrorReturn;
    }

    if(ret == CCREP_ACCEPT)
    {
        int port;
        Q_memcpy(&sock->addr, serveraddr, sizeof(struct qsockaddr));
        port = MSG_ReadLong();
        if(port)
        { // spike --- don't change the remote port if the server doesn't
            // want us to. this allows servers to use port forwarding with
            // less issues, assuming the server uses the same port for all
            // clients.
            dfunc.SetSocketPort(&sock->addr, port);
        }
    }
    else
    {
        reason = "Bad Response";
        Con_Printf("%s\n", reason);
        Q_strcpy(m_return_reason, reason);
        goto ErrorReturn;
    }

    if(sock->proquake_angle_hack)
    {
        byte mod = (msg_readcount < net_message.cursize) ? MSG_ReadByte() : 0;
        byte ver = (msg_readcount < net_message.cursize) ? MSG_ReadByte() : 0;
        byte flags = (msg_readcount < net_message.cursize) ? MSG_ReadByte() : 0;
        (void)ver;

        if(mod == 1 /*MOD_PROQUAKE*/)
        {
            if(flags & 1 /*CHEATFREE*/)
            {
                reason = "Server is incompatible";
                Con_Printf("%s\n", reason);
                Q_strcpy(m_return_reason, reason);
                goto ErrorReturn;
            }
            sock->proquake_angle_hack = true;
        }
        else
        {
            sock->proquake_angle_hack = false;
        }
    }

dpserveraccepted:

    dfunc.GetNameFromAddr(serveraddr, sock->trueaddress);
    dfunc.GetNameFromAddr(serveraddr, sock->maskedaddress);

    Con_Printf("Connection accepted\n");
    sock->lastMessageTime = SetNetTime();

    // switch the connection to the specified address
    if(dfunc.Connect(newsock, &sock->addr) == -1)
    {
        reason = "Connect to Game failed";
        Con_Printf("%s\n", reason);
        Q_strcpy(m_return_reason, reason);
        goto ErrorReturn;
    }

    /*Spike's rant about NATs:
    We sent a packet to the server's control port.
    The server replied from that control port. all is well so far.
    The server is now about(or already did, yay resends) to send us a packet
    from its data port to our address. The nat will (correctly) see a packet
    from a different remote address:port. The local nat has two options here. 1)
    assume that the wrong port is fine. 2) drop it. Dropping it is far more
    likely. The NQ code will not send any unreliables until we have received the
    serverinfo. There are no reliables that need to be sent either. Normally we
    won't send ANYTHING until we get that packet. Which will never happen
    because the NAT will never let it through. So, if we want to get away
    without fixing everyone else's server (which is also quite messy), the easy
    way around this dilema is to just send some (small) useless packet to what
    we believe to be the server's data port. A single unreliable clc_nop should
    do it. There's unlikely to be much packetloss on our local lan (so long as
    our host buffers outgoing packets on a per-socket basis or something), so we
    don't normally need to resend. We don't really care if the server can even
    read it properly, but its best to avoid warning prints. With that small
    outgoing packet, our local nat will think we initiated the request.
    HOPEFULLY it'll reuse the same public port+address. Most home routers will,
    but not all, most hole-punching techniques depend upon such behaviour. Note
    that proquake 3.4+ will actually wait for a packet from the new client,
    which solves that (but makes the nop mandatory, so needs to be reliable).

    the nop is actually sent inside CL_EstablishConnection where it has cleaner
    access to the client's pending reliable message.

    Note that we do fix our own server. This means that we can easily run on a
    home nat. the heartbeats to the master will open up a public port with most
    routers. And if that doesn't work, then its easy enough to port-forward a
    single known port instead of having to DMZ the entire network. I don't
    really expect that many people will use this, but it'll be nice for the
    occasional coop game. (note that this makes the nop redundant, but that's a
    different can of worms)
    */

    m_return_onerror = false;
    return sock;

ErrorReturn:
    NET_FreeQSocket(sock);
ErrorReturn2:
    dfunc.Close_Socket(newsock);
    if(m_return_onerror)
    {
        key_dest = key_menu;
        m_state = m_return_state;
        m_return_onerror = false;

        IN_UpdateGrabs();
    }
    return nullptr;
}

qsocket_t* Datagram_Connect(const char* host)
{
    qsocket_t* ret = nullptr;
    bool resolved = false;
    struct qsockaddr addr;

    host = Strip_Port(host);
    for(net_landriverlevel = 0; net_landriverlevel < net_numlandrivers;
        net_landriverlevel++)
    {
        if(net_landrivers[net_landriverlevel].initialized)
        {
            // see if we can resolve the host name
            // Spike -- moved name resolution to here to avoid extraneous 'could
            // not resolves' when using other address families
            if(dfunc.GetAddrFromName(host, &addr) != -1)
            {
                resolved = true;
                if((ret = _Datagram_Connect(&addr)) != nullptr)
                {
                    break;
                }
            }
        }
    }
    if(!resolved)
    {
        Con_SafePrintf("Could not resolve %s\n", host);
    }
    return ret;
}

/*
Spike: added this to list more than one ipv4 address (many people are still
multi-homed)
*/
int Datagram_QueryAddresses(qhostaddr_t* addresses, int maxaddresses)
{
    int result = 0;
    for(net_landriverlevel = 0; net_landriverlevel < net_numlandrivers;
        net_landriverlevel++)
    {
        if(!net_landrivers[net_landriverlevel].initialized)
        {
            continue;
        }
        if(result == maxaddresses)
        {
            break;
        }
        if(net_landrivers[net_landriverlevel].QueryAddresses)
        {
            result += net_landrivers[net_landriverlevel].QueryAddresses(
                addresses + result, maxaddresses - result);
        }
    }
    return result;
}
