thx srb2! sadly we cannot really make use of ipv6 since vanilla clients cant really handle stuff well ontop of the server browser not supporting them anyways, sad! its probably better to just disable ipv6 compile completely however this also has minor cleanups and bugfixes for ipv4 too (server browser now should work proper if you rerfresh it often and then close and open it again etc)
1480 lines
36 KiB
C
1480 lines
36 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
|
// Copyright (C) 1999-2020 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 d_net.c
|
|
/// \brief SRB2 network game communication and protocol, all OS independent parts.
|
|
//
|
|
/// Implement a Sliding window protocol without receiver window
|
|
/// (out of order reception)
|
|
/// This protocol uses a mix of "goback n" and "selective repeat" implementation
|
|
/// The NOTHING packet is sent when connection is idle to acknowledge packets
|
|
|
|
#include "doomdef.h"
|
|
#include "g_game.h"
|
|
#include "i_time.h"
|
|
#include "i_net.h"
|
|
#include "i_system.h"
|
|
#include "m_argv.h"
|
|
#include "d_net.h"
|
|
#include "w_wad.h"
|
|
#include "d_netfil.h"
|
|
#include "d_clisrv.h"
|
|
#include "z_zone.h"
|
|
#include "i_tcp.h"
|
|
#include "d_main.h" // srb2home
|
|
#include "stun.h"
|
|
#include "byteptr.h"
|
|
|
|
#include "qs22j.h"
|
|
|
|
//
|
|
// NETWORKING
|
|
//
|
|
// gametic is the tic about to (or currently being) run
|
|
// Server:
|
|
// maketic is the tic that hasn't had control made for it yet
|
|
// nettics is the tic for each node
|
|
// firstticstosend is the lowest value of nettics
|
|
// Client:
|
|
// neededtic is the tic needed by the client to run the game
|
|
// firstticstosend is used to optimize a condition
|
|
// Normally maketic >= gametic > 0
|
|
|
|
#define FORCECLOSE 0x8000
|
|
tic_t connectiontimeout = (10*TICRATE);
|
|
|
|
/// \brief network packet
|
|
doomcom_t *doomcom = NULL;
|
|
/// \brief network packet data, points inside doomcom
|
|
doomdata_t *netbuffer = NULL;
|
|
/// \brief hole punching packet, also points inside doomcom
|
|
holepunch_t *holepunchpacket = NULL;
|
|
|
|
#ifdef DEBUGFILE
|
|
FILE *debugfile = NULL; // put some net info in a file during the game
|
|
#endif
|
|
|
|
#define MAXREBOUND 8
|
|
static doomdata_t reboundstore[MAXREBOUND];
|
|
static INT16 reboundsize[MAXREBOUND];
|
|
static INT32 rebound_head, rebound_tail;
|
|
|
|
/// \brief bandwith of netgame
|
|
INT32 net_bandwidth;
|
|
|
|
/// \brief max length per packet
|
|
INT16 hardware_MAXPACKETLENGTH;
|
|
|
|
boolean (*I_NetGet)(void) = NULL;
|
|
void (*I_NetSend)(void) = NULL;
|
|
boolean (*I_NetCanSend)(void) = NULL;
|
|
boolean (*I_NetCanGet)(void) = NULL;
|
|
void (*I_NetCloseSocket)(void) = NULL;
|
|
void (*I_NetFreeNodenum)(INT32 nodenum) = NULL;
|
|
SINT8 (*I_NetMakeNodewPort)(const char *address, const char* port) = NULL;
|
|
void (*I_NetRequestHolePunch)(INT32 node) = NULL;
|
|
void (*I_NetRegisterHolePunch)(void) = NULL;
|
|
boolean (*I_NetOpenSocket)(void) = NULL;
|
|
boolean (*I_Ban) (INT32 node) = NULL;
|
|
void (*I_ClearBans)(void) = NULL;
|
|
const char *(*I_GetNodeAddress) (INT32 node) = NULL;
|
|
const char *(*I_GetBanAddress) (size_t ban) = NULL;
|
|
const char *(*I_GetBanMask) (size_t ban) = NULL;
|
|
const char *(*I_GetBanUsername) (size_t ban) = NULL;
|
|
const char *(*I_GetBanReason) (size_t ban) = NULL;
|
|
time_t (*I_GetUnbanTime) (size_t ban) = NULL;
|
|
boolean (*I_SetBanAddress) (const char *address, const char *mask) = NULL;
|
|
boolean (*I_SetBanUsername) (const char *username) = NULL;
|
|
boolean (*I_SetBanReason) (const char *reason) = NULL;
|
|
boolean (*I_SetUnbanTime) (time_t timestamp) = NULL;
|
|
bannednode_t *bannednode = NULL;
|
|
|
|
|
|
// network stats
|
|
static tic_t statstarttic;
|
|
INT32 getbytes = 0;
|
|
INT64 sendbytes = 0;
|
|
static INT32 retransmit = 0, duppacket = 0;
|
|
static INT32 sendackpacket = 0, getackpacket = 0;
|
|
INT32 ticruned = 0, ticmiss = 0;
|
|
|
|
// globals
|
|
INT32 getbps, sendbps;
|
|
float lostpercent, duppercent, gamelostpercent;
|
|
INT32 packetheaderlength;
|
|
|
|
boolean Net_GetNetStat(void)
|
|
{
|
|
const tic_t t = I_GetTime();
|
|
static INT64 oldsendbyte = 0;
|
|
if (statstarttic+STATLENGTH <= t)
|
|
{
|
|
const tic_t df = t-statstarttic;
|
|
const INT64 newsendbyte = sendbytes - oldsendbyte;
|
|
sendbps = (INT32)(newsendbyte*TICRATE)/df;
|
|
getbps = (getbytes*TICRATE)/df;
|
|
if (sendackpacket)
|
|
lostpercent = 100.0f*(float)retransmit/(float)sendackpacket;
|
|
else
|
|
lostpercent = 0.0f;
|
|
if (getackpacket)
|
|
duppercent = 100.0f*(float)duppacket/(float)getackpacket;
|
|
else
|
|
duppercent = 0.0f;
|
|
if (ticruned)
|
|
gamelostpercent = 100.0f*(float)ticmiss/(float)ticruned;
|
|
else
|
|
gamelostpercent = 0.0f;
|
|
|
|
ticmiss = ticruned = 0;
|
|
oldsendbyte = sendbytes;
|
|
getbytes = 0;
|
|
sendackpacket = getackpacket = duppacket = retransmit = 0;
|
|
statstarttic = t;
|
|
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// Some structs and functions for acknowledgement of packets
|
|
// -----------------------------------------------------------------
|
|
#define MAXACKPACKETS 96 // Minimum number of nodes (wat)
|
|
#define MAXACKTOSEND 96
|
|
#define URGENTFREESLOTNUM 10
|
|
#define ACKTOSENDTIMEOUT (TICRATE/11)
|
|
|
|
typedef struct
|
|
{
|
|
UINT8 acknum;
|
|
UINT8 nextacknum;
|
|
UINT8 destinationnode; // The node to send the ack to
|
|
tic_t senttime; // The time when the ack was sent
|
|
UINT16 length; // The packet size
|
|
UINT16 resentnum; // The number of times the ack has been resent
|
|
union {
|
|
SINT8 raw[MAXPACKETLENGTH];
|
|
doomdata_t data;
|
|
} pak;
|
|
} ackpak_t;
|
|
|
|
typedef enum
|
|
{
|
|
NF_CLOSE = 1, // Flag is set when connection is closing
|
|
NF_TIMEOUT = 2, // Flag is set when the node got a timeout
|
|
} node_flags_t;
|
|
|
|
// Table of packets that were not acknowleged can be resent (the sender window)
|
|
static ackpak_t ackpak[MAXACKPACKETS];
|
|
|
|
typedef struct
|
|
{
|
|
// ack return to send (like sliding window protocol)
|
|
UINT8 firstacktosend;
|
|
|
|
// when no consecutive packets are received we keep in mind what packets
|
|
// we already received in a queue
|
|
UINT8 acktosend_head;
|
|
UINT8 acktosend_tail;
|
|
UINT8 acktosend[MAXACKTOSEND];
|
|
|
|
// automatically send keep alive packet when not enough trafic
|
|
tic_t lasttimeacktosend_sent;
|
|
// detect connection lost
|
|
tic_t lasttimepacketreceived;
|
|
|
|
// flow control: do not send too many packets with ack
|
|
UINT8 remotefirstack;
|
|
UINT8 nextacknum;
|
|
|
|
UINT8 flags;
|
|
} netnode_t;
|
|
|
|
static netnode_t nodes[MAXNETNODES];
|
|
#define NODETIMEOUT 14
|
|
|
|
// return <0 if a < b (mod 256)
|
|
// 0 if a = n (mod 256)
|
|
// >0 if a > b (mod 256)
|
|
// mnemonic: to use it compare to 0: cmpack(a,b)<0 is "a < b" ...
|
|
FUNCMATH static INT32 cmpack(UINT8 a, UINT8 b)
|
|
{
|
|
register INT32 d = a - b;
|
|
|
|
if (d >= 127 || d < -128)
|
|
return -d;
|
|
return d;
|
|
}
|
|
|
|
/** Sets freeack to a free acknum and copies the netbuffer in the ackpak table
|
|
*
|
|
* \param freeack The address to store the free acknum at
|
|
* \param lowtimer ???
|
|
* \return True if a free acknum was found
|
|
*/
|
|
static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
|
|
{
|
|
netnode_t *node = &nodes[doomcom->remotenode];
|
|
INT32 i, numfreeslot = 0;
|
|
|
|
if (cmpack((UINT8)((node->remotefirstack + MAXACKTOSEND) % 256), node->nextacknum) < 0)
|
|
{
|
|
DEBFILE(va("too fast %d %d\n",node->remotefirstack,node->nextacknum));
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < MAXACKPACKETS; i++)
|
|
if (!ackpak[i].acknum)
|
|
{
|
|
// For low priority packets, make sure to let freeslots so urgent packets can be sent
|
|
if (netbuffer->packettype >= PT_CANFAIL)
|
|
{
|
|
numfreeslot++;
|
|
if (numfreeslot <= URGENTFREESLOTNUM)
|
|
continue;
|
|
}
|
|
|
|
ackpak[i].acknum = node->nextacknum;
|
|
ackpak[i].nextacknum = node->nextacknum;
|
|
node->nextacknum++;
|
|
if (!node->nextacknum)
|
|
node->nextacknum++;
|
|
ackpak[i].destinationnode = (UINT8)(node - nodes);
|
|
ackpak[i].length = doomcom->datalength;
|
|
if (lowtimer)
|
|
{
|
|
// Lowtime means can't be sent now so try it as soon as possible
|
|
ackpak[i].senttime = 0;
|
|
ackpak[i].resentnum = 1;
|
|
}
|
|
else
|
|
{
|
|
ackpak[i].senttime = I_GetTime();
|
|
ackpak[i].resentnum = 0;
|
|
}
|
|
M_Memcpy(ackpak[i].pak.raw, netbuffer, ackpak[i].length);
|
|
|
|
*freeack = ackpak[i].acknum;
|
|
|
|
sendackpacket++; // For stat
|
|
|
|
return true;
|
|
}
|
|
#ifdef PARANOIA
|
|
CONS_Debug(DBG_NETPLAY, "No more free ackpacket\n");
|
|
#endif
|
|
if (netbuffer->packettype < PT_CANFAIL)
|
|
I_Error("Connection lost\n");
|
|
return false;
|
|
}
|
|
|
|
/** Counts how many acks are free
|
|
*
|
|
* \param urgent True if the type of the packet meant to
|
|
* use an ack is lower than PT_CANFAIL
|
|
* If for some reason you don't want use it
|
|
* for any packet type in particular,
|
|
* just set to false
|
|
* \return The number of free acks
|
|
*
|
|
*/
|
|
INT32 Net_GetFreeAcks(boolean urgent)
|
|
{
|
|
INT32 i, numfreeslot = 0;
|
|
INT32 n = 0; // Number of free acks found
|
|
|
|
for (i = 0; i < MAXACKPACKETS; i++)
|
|
if (!ackpak[i].acknum)
|
|
{
|
|
// For low priority packets, make sure to let freeslots so urgent packets can be sent
|
|
if (!urgent)
|
|
{
|
|
numfreeslot++;
|
|
if (numfreeslot <= URGENTFREESLOTNUM)
|
|
continue;
|
|
}
|
|
|
|
n++;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
// Get a ack to send in the queue of this node
|
|
static UINT8 GetAcktosend(INT32 node)
|
|
{
|
|
nodes[node].lasttimeacktosend_sent = I_GetTime();
|
|
return nodes[node].firstacktosend;
|
|
}
|
|
|
|
static void RemoveAck(INT32 i)
|
|
{
|
|
INT32 node = ackpak[i].destinationnode;
|
|
DEBFILE(va("Remove ack %d\n",ackpak[i].acknum));
|
|
ackpak[i].acknum = 0;
|
|
if (nodes[node].flags & NF_CLOSE)
|
|
Net_CloseConnection(node);
|
|
}
|
|
|
|
// We have got a packet, proceed the ack request and ack return
|
|
static boolean Processackpak(void)
|
|
{
|
|
INT32 i;
|
|
boolean goodpacket = true;
|
|
netnode_t *node = &nodes[doomcom->remotenode];
|
|
|
|
// Received an ack return, so remove the ack in the list
|
|
if (netbuffer->ackreturn && cmpack(node->remotefirstack, netbuffer->ackreturn) < 0)
|
|
{
|
|
node->remotefirstack = netbuffer->ackreturn;
|
|
// Search the ackbuffer and free it
|
|
for (i = 0; i < MAXACKPACKETS; i++)
|
|
if (ackpak[i].acknum && ackpak[i].destinationnode == doomcom->remotenode
|
|
&& cmpack(ackpak[i].acknum, netbuffer->ackreturn) <= 0)
|
|
{
|
|
RemoveAck(i);
|
|
}
|
|
}
|
|
|
|
// Received a packet with ack, queue it to send the ack back
|
|
if (netbuffer->ack)
|
|
{
|
|
UINT8 ack = netbuffer->ack;
|
|
getackpacket++;
|
|
if (cmpack(ack, node->firstacktosend) <= 0)
|
|
{
|
|
DEBFILE(va("Discard(1) ack %d (duplicated)\n", ack));
|
|
duppacket++;
|
|
goodpacket = false; // Discard packet (duplicate)
|
|
}
|
|
else
|
|
{
|
|
// Check if it is not already in the queue
|
|
for (i = node->acktosend_tail; i != node->acktosend_head; i = (i+1) % MAXACKTOSEND)
|
|
if (node->acktosend[i] == ack)
|
|
{
|
|
DEBFILE(va("Discard(2) ack %d (duplicated)\n", ack));
|
|
duppacket++;
|
|
goodpacket = false; // Discard packet (duplicate)
|
|
break;
|
|
}
|
|
if (goodpacket)
|
|
{
|
|
// Is a good packet so increment the acknowledge number,
|
|
// Then search for a "hole" in the queue
|
|
UINT8 nextfirstack = (UINT8)(node->firstacktosend + 1);
|
|
if (!nextfirstack)
|
|
nextfirstack = 1;
|
|
|
|
if (ack == nextfirstack)
|
|
{
|
|
UINT8 hm1; // head - 1
|
|
boolean change = true;
|
|
|
|
node->firstacktosend = nextfirstack++;
|
|
if (!nextfirstack)
|
|
nextfirstack = 1;
|
|
hm1 = (UINT8)((node->acktosend_head-1+MAXACKTOSEND) % MAXACKTOSEND);
|
|
while (change)
|
|
{
|
|
change = false;
|
|
for (i = node->acktosend_tail; i != node->acktosend_head;
|
|
i = (i+1) % MAXACKTOSEND)
|
|
{
|
|
if (cmpack(node->acktosend[i], nextfirstack) <= 0)
|
|
{
|
|
if (node->acktosend[i] == nextfirstack)
|
|
{
|
|
node->firstacktosend = nextfirstack++;
|
|
if (!nextfirstack)
|
|
nextfirstack = 1;
|
|
change = true;
|
|
}
|
|
if (i == node->acktosend_tail)
|
|
{
|
|
node->acktosend[node->acktosend_tail] = 0;
|
|
node->acktosend_tail = (UINT8)((i+1) % MAXACKTOSEND);
|
|
}
|
|
else if (i == hm1)
|
|
{
|
|
node->acktosend[hm1] = 0;
|
|
node->acktosend_head = hm1;
|
|
hm1 = (UINT8)((hm1-1+MAXACKTOSEND) % MAXACKTOSEND);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // Out of order packet
|
|
{
|
|
// Don't increment firsacktosend, put it in asktosend queue
|
|
// Will be incremented when the nextfirstack comes (code above)
|
|
UINT8 newhead = (UINT8)((node->acktosend_head+1) % MAXACKTOSEND);
|
|
DEBFILE(va("out of order packet (%d expected)\n", nextfirstack));
|
|
if (newhead != node->acktosend_tail)
|
|
{
|
|
node->acktosend[node->acktosend_head] = ack;
|
|
node->acktosend_head = newhead;
|
|
}
|
|
else // Buffer full discard packet, sender will resend it
|
|
{ // We can admit the packet but we will not detect the duplication after :(
|
|
DEBFILE("no more freeackret\n");
|
|
goodpacket = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return goodpacket;
|
|
}
|
|
|
|
// send special packet with only ack on it
|
|
void Net_SendAcks(INT32 node)
|
|
{
|
|
netbuffer->packettype = PT_NOTHING;
|
|
M_Memcpy(netbuffer->u.textcmd, nodes[node].acktosend, MAXACKTOSEND);
|
|
HSendPacket(node, false, 0, MAXACKTOSEND);
|
|
}
|
|
|
|
static void GotAcks(void)
|
|
{
|
|
INT32 i, j;
|
|
|
|
for (j = 0; j < MAXACKTOSEND; j++)
|
|
if (netbuffer->u.textcmd[j])
|
|
for (i = 0; i < MAXACKPACKETS; i++)
|
|
if (ackpak[i].acknum && ackpak[i].destinationnode == doomcom->remotenode)
|
|
{
|
|
if (ackpak[i].acknum == netbuffer->u.textcmd[j])
|
|
RemoveAck(i);
|
|
// nextacknum is first equal to acknum, then when receiving bigger ack
|
|
// there is big chance the packet is lost
|
|
// When resent, nextacknum = nodes[node].nextacknum
|
|
// will redo the same but with different value
|
|
else if (cmpack(ackpak[i].nextacknum, netbuffer->u.textcmd[j]) <= 0
|
|
&& ackpak[i].senttime > 0)
|
|
{
|
|
ackpak[i].senttime--; // hurry up
|
|
}
|
|
}
|
|
}
|
|
|
|
void Net_ConnectionTimeout(INT32 node)
|
|
{
|
|
// Don't timeout several times
|
|
if (nodes[node].flags & NF_TIMEOUT)
|
|
return;
|
|
nodes[node].flags |= NF_TIMEOUT;
|
|
|
|
// Send a very special packet to self (hack the reboundstore queue)
|
|
// Main code will handle it
|
|
reboundstore[rebound_head].packettype = PT_NODETIMEOUT;
|
|
reboundstore[rebound_head].ack = 0;
|
|
reboundstore[rebound_head].ackreturn = 0;
|
|
reboundstore[rebound_head].u.textcmd[0] = (UINT8)node;
|
|
reboundsize[rebound_head] = (INT16)(BASEPACKETSIZE + 1);
|
|
rebound_head = (rebound_head+1) % MAXREBOUND;
|
|
|
|
// Do not redo it quickly (if we do not close connection it is
|
|
// for a good reason!)
|
|
nodes[node].lasttimepacketreceived = I_GetTime();
|
|
}
|
|
|
|
// Resend the data if needed
|
|
void Net_AckTicker(void)
|
|
{
|
|
INT32 i;
|
|
|
|
for (i = 0; i < MAXACKPACKETS; i++)
|
|
{
|
|
const INT32 nodei = ackpak[i].destinationnode;
|
|
netnode_t *node = &nodes[nodei];
|
|
if (ackpak[i].acknum && ackpak[i].senttime + NODETIMEOUT < I_GetTime())
|
|
{
|
|
if (ackpak[i].resentnum > 20 && (node->flags & NF_CLOSE))
|
|
{
|
|
DEBFILE(va("ack %d sent 20 times so connection is supposed lost: node %d\n",
|
|
i, nodei));
|
|
Net_CloseConnection(nodei | FORCECLOSE);
|
|
|
|
ackpak[i].acknum = 0;
|
|
continue;
|
|
}
|
|
DEBFILE(va("Resend ack %d, %u<%d at %u\n", ackpak[i].acknum, ackpak[i].senttime,
|
|
NODETIMEOUT, I_GetTime()));
|
|
M_Memcpy(netbuffer, ackpak[i].pak.raw, ackpak[i].length);
|
|
ackpak[i].senttime = I_GetTime();
|
|
ackpak[i].resentnum++;
|
|
ackpak[i].nextacknum = node->nextacknum;
|
|
retransmit++; // For stat
|
|
HSendPacket((INT32)(node - nodes), false, ackpak[i].acknum,
|
|
(size_t)(ackpak[i].length - BASEPACKETSIZE));
|
|
}
|
|
}
|
|
|
|
for (i = 1; i < MAXNETNODES; i++)
|
|
{
|
|
// This is something like node open flag
|
|
if (nodes[i].firstacktosend)
|
|
{
|
|
// We haven't sent a packet for a long time
|
|
// Acknowledge packet if needed
|
|
if (nodes[i].lasttimeacktosend_sent + ACKTOSENDTIMEOUT < I_GetTime())
|
|
Net_SendAcks(i);
|
|
|
|
if (!(nodes[i].flags & NF_CLOSE)
|
|
&& nodes[i].lasttimepacketreceived + connectiontimeout < I_GetTime())
|
|
{
|
|
Net_ConnectionTimeout(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove last packet received ack before resending the ackreturn
|
|
// (the higher layer doesn't have room, or something else ....)
|
|
void Net_UnAcknowledgePacket(INT32 node)
|
|
{
|
|
INT32 hm1 = (nodes[node].acktosend_head-1+MAXACKTOSEND) % MAXACKTOSEND;
|
|
DEBFILE(va("UnAcknowledge node %d\n", node));
|
|
if (!node)
|
|
return;
|
|
if (nodes[node].acktosend[hm1] == netbuffer->ack)
|
|
{
|
|
nodes[node].acktosend[hm1] = 0;
|
|
nodes[node].acktosend_head = (UINT8)hm1;
|
|
}
|
|
else if (nodes[node].firstacktosend == netbuffer->ack)
|
|
{
|
|
nodes[node].firstacktosend--;
|
|
if (!nodes[node].firstacktosend)
|
|
nodes[node].firstacktosend = UINT8_MAX;
|
|
}
|
|
else
|
|
{
|
|
while (nodes[node].firstacktosend != netbuffer->ack)
|
|
{
|
|
nodes[node].acktosend_tail = (UINT8)
|
|
((nodes[node].acktosend_tail-1+MAXACKTOSEND) % MAXACKTOSEND);
|
|
nodes[node].acktosend[nodes[node].acktosend_tail] = nodes[node].firstacktosend;
|
|
|
|
nodes[node].firstacktosend--;
|
|
if (!nodes[node].firstacktosend)
|
|
nodes[node].firstacktosend = UINT8_MAX;
|
|
}
|
|
nodes[node].firstacktosend++;
|
|
if (!nodes[node].firstacktosend)
|
|
nodes[node].firstacktosend = 1;
|
|
}
|
|
}
|
|
|
|
/** Checks if all acks have been received
|
|
*
|
|
* \return True if all acks have been received
|
|
*
|
|
*/
|
|
static boolean Net_AllAcksReceived(void)
|
|
{
|
|
INT32 i;
|
|
|
|
for (i = 0; i < MAXACKPACKETS; i++)
|
|
if (ackpak[i].acknum)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Waits for all ackreturns
|
|
*
|
|
* \param timeout Timeout in seconds
|
|
*
|
|
*/
|
|
void Net_WaitAllAckReceived(UINT32 timeout)
|
|
{
|
|
tic_t tictac = I_GetTime();
|
|
timeout = tictac + timeout*NEWTICRATE;
|
|
|
|
HGetPacket();
|
|
while (timeout > I_GetTime() && !Net_AllAcksReceived())
|
|
{
|
|
while (tictac == I_GetTime())
|
|
{
|
|
I_Sleep(cv_sleep.value);
|
|
I_UpdateTime(cv_timescale.value);
|
|
}
|
|
tictac = I_GetTime();
|
|
HGetPacket();
|
|
Net_AckTicker();
|
|
}
|
|
}
|
|
|
|
static void InitNode(netnode_t *node)
|
|
{
|
|
node->acktosend_head = node->acktosend_tail = 0;
|
|
node->firstacktosend = 0;
|
|
node->nextacknum = 1;
|
|
node->remotefirstack = 0;
|
|
node->flags = 0;
|
|
}
|
|
|
|
static void InitAck(void)
|
|
{
|
|
INT32 i;
|
|
|
|
for (i = 0; i < MAXACKPACKETS; i++)
|
|
ackpak[i].acknum = 0;
|
|
|
|
for (i = 0; i < MAXNETNODES; i++)
|
|
InitNode(&nodes[i]);
|
|
}
|
|
|
|
/** Removes all acks of a given packet type
|
|
*
|
|
* \param packettype The packet type to forget
|
|
*
|
|
*/
|
|
void Net_AbortPacketType(UINT8 packettype)
|
|
{
|
|
INT32 i;
|
|
for (i = 0; i < MAXACKPACKETS; i++)
|
|
if (ackpak[i].acknum && (ackpak[i].pak.data.packettype == packettype
|
|
|| packettype == UINT8_MAX))
|
|
{
|
|
ackpak[i].acknum = 0;
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// end of acknowledge function
|
|
// -----------------------------------------------------------------
|
|
|
|
// remove a node, clear all ack from this node and reset askret
|
|
void Net_CloseConnection(INT32 node)
|
|
{
|
|
INT32 i;
|
|
boolean forceclose = (node & FORCECLOSE) != 0;
|
|
|
|
if (node == -1)
|
|
{
|
|
DEBFILE(M_GetText("Net_CloseConnection: node -1 detected!\n"));
|
|
return; // nope, just ignore it
|
|
}
|
|
|
|
node &= ~FORCECLOSE;
|
|
|
|
if (!node)
|
|
return;
|
|
|
|
if (node < 0 || node >= MAXNETNODES) // prevent invalid nodes from crashing the game
|
|
{
|
|
DEBFILE(va(M_GetText("Net_CloseConnection: invalid node %d detected!\n"), node));
|
|
return;
|
|
}
|
|
|
|
nodes[node].flags |= NF_CLOSE;
|
|
|
|
// try to Send ack back (two army problem)
|
|
if (GetAcktosend(node))
|
|
{
|
|
Net_SendAcks(node);
|
|
Net_SendAcks(node);
|
|
}
|
|
|
|
// check if we are waiting for an ack from this node
|
|
for (i = 0; i < MAXACKPACKETS; i++)
|
|
if (ackpak[i].acknum && ackpak[i].destinationnode == node)
|
|
{
|
|
if (!forceclose)
|
|
return; // connection will be closed when ack is returned
|
|
else
|
|
ackpak[i].acknum = 0;
|
|
}
|
|
|
|
InitNode(&nodes[node]);
|
|
SV_AbortSendFiles(node);
|
|
if (server)
|
|
SV_AbortLuaFileTransfer(node);
|
|
I_NetFreeNodenum(node);
|
|
}
|
|
|
|
//
|
|
// Checksum
|
|
//
|
|
static UINT32 NetbufferChecksum(void)
|
|
{
|
|
UINT32 c = 0x1234567;
|
|
const INT32 l = doomcom->datalength - 4;
|
|
const UINT8 *buf = (UINT8 *)netbuffer + 4;
|
|
INT32 i;
|
|
|
|
for (i = 0; i < l; i++, buf++)
|
|
c += (*buf) * (i+1);
|
|
|
|
return LONG(c);
|
|
}
|
|
|
|
#ifdef DEBUGFILE
|
|
|
|
static void fprintfstring(char *s, size_t len)
|
|
{
|
|
INT32 mode = 0;
|
|
size_t i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
if (s[i] < 32)
|
|
{
|
|
if (!mode)
|
|
{
|
|
fprintf(debugfile, "[%d", (UINT8)s[i]);
|
|
mode = 1;
|
|
}
|
|
else
|
|
fprintf(debugfile, ",%d", (UINT8)s[i]);
|
|
}
|
|
else
|
|
{
|
|
if (mode)
|
|
{
|
|
fprintf(debugfile, "]");
|
|
mode = 0;
|
|
}
|
|
fprintf(debugfile, "%c", s[i]);
|
|
}
|
|
if (mode)
|
|
fprintf(debugfile, "]");
|
|
}
|
|
|
|
static void fprintfstringnewline(char *s, size_t len)
|
|
{
|
|
fprintfstring(s, len);
|
|
fprintf(debugfile, "\n");
|
|
}
|
|
|
|
/// \warning Keep this up-to-date if you add/remove/rename packet types
|
|
static const char *packettypename[NUMPACKETTYPE] =
|
|
{
|
|
"NOTHING",
|
|
"SERVERCFG",
|
|
"CLIENTCMD",
|
|
"CLIENTMIS",
|
|
"CLIENT2CMD",
|
|
"CLIENT2MIS",
|
|
"NODEKEEPALIVE",
|
|
"NODEKEEPALIVEMIS",
|
|
"SERVERTICS",
|
|
"SERVERREFUSE",
|
|
"SERVERSHUTDOWN",
|
|
"CLIENTQUIT",
|
|
|
|
"ASKINFO",
|
|
"SERVERINFO",
|
|
"PLAYERINFO",
|
|
"REQUESTFILE",
|
|
"ASKINFOVIAMS",
|
|
|
|
"WILLRESENDGAMESTATE",
|
|
"CANRECEIVEGAMESTATE",
|
|
"RECEIVEDGAMESTATE",
|
|
|
|
"SENDINGLUAFILE",
|
|
"ASKLUAFILE",
|
|
"HASLUAFILE",
|
|
|
|
"CLIENT3CMD",
|
|
"CLIENT3MIS",
|
|
"CLIENT4CMD",
|
|
"CLIENT4MIS",
|
|
"BASICKEEPALIVE",
|
|
|
|
"FILEFRAGMENT",
|
|
"FILEACK",
|
|
"FILERECEIVED",
|
|
|
|
"TEXTCMD",
|
|
"TEXTCMD2",
|
|
"TEXTCMD3",
|
|
"TEXTCMD4",
|
|
"CLIENTJOIN",
|
|
"NODETIMEOUT",
|
|
|
|
"LOGIN",
|
|
|
|
"PING"
|
|
};
|
|
|
|
static void DebugPrintpacket(const char *header)
|
|
{
|
|
fprintf(debugfile, "%-12s (node %d,ack %d,ackret %d,size %d) type(%d) : %s\n",
|
|
header, doomcom->remotenode, netbuffer->ack, netbuffer->ackreturn, doomcom->datalength,
|
|
netbuffer->packettype, packettypename[netbuffer->packettype]);
|
|
|
|
switch (netbuffer->packettype)
|
|
{
|
|
case PT_ASKINFO:
|
|
case PT_ASKINFOVIAMS:
|
|
fprintf(debugfile, " time %u\n", (tic_t)LONG(netbuffer->u.askinfo.time));
|
|
break;
|
|
case PT_CLIENTJOIN:
|
|
fprintf(debugfile, " number %d mode %d\n", netbuffer->u.clientcfg.localplayers,
|
|
netbuffer->u.clientcfg.mode);
|
|
break;
|
|
case PT_SERVERTICS:
|
|
{
|
|
servertics_pak *serverpak = &netbuffer->u.serverpak;
|
|
UINT8 *cmd = (UINT8 *)(&serverpak->cmds[serverpak->numslots * serverpak->numtics]);
|
|
size_t ntxtcmd = &((UINT8 *)netbuffer)[doomcom->datalength] - cmd;
|
|
|
|
fprintf(debugfile, " firsttic %u ply %d tics %d ntxtcmd %s\n ",
|
|
(UINT32)serverpak->starttic, serverpak->numslots, serverpak->numtics, sizeu1(ntxtcmd));
|
|
/// \todo Display more readable information about net commands
|
|
fprintfstringnewline((char *)cmd, ntxtcmd);
|
|
/*fprintfstring((char *)cmd, 3);
|
|
if (ntxtcmd > 4)
|
|
{
|
|
fprintf(debugfile, "[%s]", netxcmdnames[*((cmd) + 3) - 1]);
|
|
fprintfstring(((char *)cmd) + 4, ntxtcmd - 4);
|
|
}
|
|
fprintf(debugfile, "\n");*/
|
|
break;
|
|
}
|
|
case PT_CLIENTCMD:
|
|
case PT_CLIENT2CMD:
|
|
case PT_CLIENT3CMD:
|
|
case PT_CLIENT4CMD:
|
|
case PT_CLIENTMIS:
|
|
case PT_CLIENT2MIS:
|
|
case PT_CLIENT3MIS:
|
|
case PT_CLIENT4MIS:
|
|
case PT_NODEKEEPALIVE:
|
|
case PT_NODEKEEPALIVEMIS:
|
|
fprintf(debugfile, " tic %4u resendfrom %u\n",
|
|
(UINT32)netbuffer->u.clientpak.client_tic,
|
|
(UINT32)netbuffer->u.clientpak.resendfrom);
|
|
break;
|
|
case PT_BASICKEEPALIVE:
|
|
fprintf(debugfile, " keep alive\n");
|
|
break;
|
|
case PT_TEXTCMD:
|
|
case PT_TEXTCMD2:
|
|
case PT_TEXTCMD3:
|
|
case PT_TEXTCMD4: {
|
|
UINT16 size;
|
|
|
|
{
|
|
UINT8 *p = netbuffer->u.textcmd;
|
|
|
|
size = READUINT16(p);
|
|
}
|
|
|
|
fprintf(debugfile, " length %d\n", size);
|
|
fprintf(debugfile, "[%s]", netxcmdnames[netbuffer->u.textcmd[2] - 1]);
|
|
fprintfstringnewline((char *)netbuffer->u.textcmd + 3, size - 2);
|
|
break;
|
|
}
|
|
case PT_SERVERCFG:
|
|
fprintf(debugfile, " playerslots %d clientnode %d serverplayer %d "
|
|
"gametic %u gamestate %d gametype %d modifiedgame %d\n",
|
|
netbuffer->u.servercfg.totalslotnum, netbuffer->u.servercfg.clientnode,
|
|
netbuffer->u.servercfg.serverplayer, (UINT32)LONG(netbuffer->u.servercfg.gametic),
|
|
netbuffer->u.servercfg.gamestate, netbuffer->u.servercfg.gametype,
|
|
netbuffer->u.servercfg.modifiedgame);
|
|
break;
|
|
case PT_SERVERINFO:
|
|
fprintf(debugfile, " '%s' player %d/%d, filenum %d, time %u \n",
|
|
netbuffer->u.serverinfo.servername, netbuffer->u.serverinfo.numberofplayer,
|
|
netbuffer->u.serverinfo.maxplayer,
|
|
netbuffer->u.serverinfo.fileneedednum,
|
|
(UINT32)LONG(netbuffer->u.serverinfo.time));
|
|
fprintfstringnewline((char *)netbuffer->u.serverinfo.fileneeded,
|
|
(UINT8)((UINT8 *)netbuffer + doomcom->datalength
|
|
- (UINT8 *)netbuffer->u.serverinfo.fileneeded));
|
|
break;
|
|
case PT_SERVERREFUSE:
|
|
fprintf(debugfile, " reason %s\n", netbuffer->u.serverrefuse.reason);
|
|
break;
|
|
case PT_FILEFRAGMENT: {
|
|
filetx_pak *pak = (void*)&netbuffer->u.filetxpak;
|
|
fprintf(debugfile, " fileid %d datasize %d position %u\n",
|
|
pak->fileid, (UINT16)SHORT(pak->size),
|
|
(UINT32)LONG(pak->position));
|
|
break;
|
|
}
|
|
case PT_REQUESTFILE:
|
|
default: // write as a raw packet
|
|
fprintfstringnewline((char *)netbuffer->u.textcmd,
|
|
(UINT8)((UINT8 *)netbuffer + doomcom->datalength - (UINT8 *)netbuffer->u.textcmd));
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef PACKETDROP
|
|
static INT32 packetdropquantity[NUMPACKETTYPE] = {0};
|
|
static INT32 packetdroprate = 0;
|
|
|
|
void Command_Drop(void)
|
|
{
|
|
INT32 packetquantity;
|
|
const char *packetname;
|
|
size_t i;
|
|
|
|
if (COM_Argc() < 2)
|
|
{
|
|
CONS_Printf("drop <packettype> [quantity]: drop packets\n"
|
|
"drop reset: cancel all packet drops\n");
|
|
return;
|
|
}
|
|
|
|
if (!(stricmp(COM_Argv(1), "reset") && stricmp(COM_Argv(1), "cancel") && stricmp(COM_Argv(1), "stop")))
|
|
{
|
|
memset(packetdropquantity, 0, sizeof(packetdropquantity));
|
|
return;
|
|
}
|
|
|
|
if (COM_Argc() >= 3)
|
|
{
|
|
packetquantity = atoi(COM_Argv(2));
|
|
if (packetquantity <= 0 && COM_Argv(2)[0] != '0')
|
|
{
|
|
CONS_Printf("Invalid quantity\n");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
packetquantity = -1;
|
|
|
|
packetname = COM_Argv(1);
|
|
|
|
if (!(stricmp(packetname, "all") && stricmp(packetname, "any")))
|
|
for (i = 0; i < NUMPACKETTYPE; i++)
|
|
packetdropquantity[i] = packetquantity;
|
|
else
|
|
{
|
|
for (i = 0; i < NUMPACKETTYPE; i++)
|
|
if (!stricmp(packetname, packettypename[i]))
|
|
{
|
|
packetdropquantity[i] = packetquantity;
|
|
return;
|
|
}
|
|
|
|
CONS_Printf("Unknown packet name\n");
|
|
}
|
|
}
|
|
|
|
void Command_Droprate(void)
|
|
{
|
|
INT32 droprate;
|
|
|
|
if (COM_Argc() < 2)
|
|
{
|
|
CONS_Printf("Packet drop rate: %d%%\n", packetdroprate);
|
|
return;
|
|
}
|
|
|
|
droprate = atoi(COM_Argv(1));
|
|
if ((droprate <= 0 && COM_Argv(1)[0] != '0') || droprate > 100)
|
|
{
|
|
CONS_Printf("Packet drop rate must be between 0 and 100!\n");
|
|
return;
|
|
}
|
|
|
|
packetdroprate = droprate;
|
|
}
|
|
|
|
static boolean ShouldDropPacket(void)
|
|
{
|
|
return (packetdropquantity[netbuffer->packettype])
|
|
|| (packetdroprate != 0 && rand() < (RAND_MAX * (packetdroprate / 100.f))) || packetdroprate == 100;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// HSendPacket
|
|
//
|
|
boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlength)
|
|
{
|
|
doomcom->datalength = (INT16)(packetlength + BASEPACKETSIZE);
|
|
if (node == 0) // Packet is to go back to us
|
|
{
|
|
if ((rebound_head+1) % MAXREBOUND == rebound_tail)
|
|
{
|
|
#ifdef PARANOIA
|
|
CONS_Debug(DBG_NETPLAY, "No more rebound buf\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
netbuffer->ack = netbuffer->ackreturn = 0; // don't hold over values from last packet sent/received
|
|
M_Memcpy(&reboundstore[rebound_head], netbuffer,
|
|
doomcom->datalength);
|
|
reboundsize[rebound_head] = doomcom->datalength;
|
|
rebound_head = (rebound_head+1) % MAXREBOUND;
|
|
#ifdef DEBUGFILE
|
|
if (debugfile)
|
|
{
|
|
doomcom->remotenode = (INT16)node;
|
|
DebugPrintpacket("SENDLOCAL");
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
if (!netgame)
|
|
I_Error("Tried to transmit to another node");
|
|
|
|
// do this before GetFreeAcknum because this function backups
|
|
// the current packet
|
|
doomcom->remotenode = (INT16)node;
|
|
if (doomcom->datalength <= 0)
|
|
{
|
|
DEBFILE("HSendPacket: nothing to send\n");
|
|
#ifdef DEBUGFILE
|
|
if (debugfile)
|
|
DebugPrintpacket("TRISEND");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
if (node < MAXNETNODES) // Can be a broadcast
|
|
netbuffer->ackreturn = GetAcktosend(node);
|
|
else
|
|
netbuffer->ackreturn = 0;
|
|
if (reliable)
|
|
{
|
|
if (I_NetCanSend && !I_NetCanSend())
|
|
{
|
|
if (netbuffer->packettype < PT_CANFAIL)
|
|
GetFreeAcknum(&netbuffer->ack, true);
|
|
|
|
DEBFILE("HSendPacket: Out of bandwidth\n");
|
|
return false;
|
|
}
|
|
else if (!GetFreeAcknum(&netbuffer->ack, false))
|
|
return false;
|
|
}
|
|
else
|
|
netbuffer->ack = acknum;
|
|
|
|
netbuffer->checksum = NetbufferChecksum();
|
|
sendbytes += packetheaderlength + doomcom->datalength; // For stat
|
|
|
|
#ifdef PACKETDROP
|
|
// Simulate internet :)
|
|
//if (rand() >= (INT32)(RAND_MAX * (PACKETLOSSRATE / 100.f)))
|
|
if (!ShouldDropPacket())
|
|
{
|
|
#endif
|
|
#ifdef DEBUGFILE
|
|
if (debugfile)
|
|
DebugPrintpacket("SENT");
|
|
#endif
|
|
I_NetSend();
|
|
#ifdef PACKETDROP
|
|
}
|
|
else
|
|
{
|
|
if (packetdropquantity[netbuffer->packettype] > 0)
|
|
packetdropquantity[netbuffer->packettype]--;
|
|
#ifdef DEBUGFILE
|
|
if (debugfile)
|
|
DebugPrintpacket("NOT SENT");
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// HGetPacket
|
|
// Returns false if no packet is waiting
|
|
// Check Datalength and checksum
|
|
//
|
|
boolean HGetPacket(void)
|
|
{
|
|
//boolean nodejustjoined;
|
|
|
|
// Get a packet from self
|
|
if (rebound_tail != rebound_head)
|
|
{
|
|
M_Memcpy(netbuffer, &reboundstore[rebound_tail], reboundsize[rebound_tail]);
|
|
doomcom->datalength = reboundsize[rebound_tail];
|
|
if (netbuffer->packettype == PT_NODETIMEOUT)
|
|
doomcom->remotenode = netbuffer->u.textcmd[0];
|
|
else
|
|
doomcom->remotenode = 0;
|
|
|
|
rebound_tail = (rebound_tail+1) % MAXREBOUND;
|
|
#ifdef DEBUGFILE
|
|
if (debugfile)
|
|
DebugPrintpacket("GETLOCAL");
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
if (!netgame)
|
|
return false;
|
|
|
|
while(true)
|
|
{
|
|
//nodejustjoined = I_NetGet();
|
|
I_NetGet();
|
|
|
|
if (doomcom->remotenode == -1) // No packet received
|
|
return false;
|
|
|
|
getbytes += packetheaderlength + doomcom->datalength; // For stat
|
|
|
|
if (doomcom->remotenode >= MAXNETNODES)
|
|
{
|
|
DEBFILE(va("Received packet from node %d!\n", doomcom->remotenode));
|
|
continue;
|
|
}
|
|
|
|
nodes[doomcom->remotenode].lasttimepacketreceived = I_GetTime();
|
|
|
|
if (netbuffer->checksum != NetbufferChecksum())
|
|
{
|
|
DEBFILE("Bad packet checksum\n");
|
|
// Do not disconnect or anything, just ignore the packet.
|
|
// Bad checksums with UDP tend to happen very scarcely
|
|
// so they are not normally an issue.
|
|
continue;
|
|
}
|
|
|
|
#ifdef DEBUGFILE
|
|
if (debugfile)
|
|
DebugPrintpacket("GET");
|
|
#endif
|
|
|
|
/*// If a new node sends an unexpected packet, just ignore it
|
|
if (nodejustjoined && server
|
|
&& !(netbuffer->packettype == PT_ASKINFO
|
|
|| netbuffer->packettype == PT_SERVERINFO
|
|
|| netbuffer->packettype == PT_PLAYERINFO
|
|
|| netbuffer->packettype == PT_REQUESTFILE
|
|
|| netbuffer->packettype == PT_ASKINFOVIAMS
|
|
|| netbuffer->packettype == PT_CLIENTJOIN))
|
|
{
|
|
DEBFILE(va("New node sent an unexpected %s packet\n", packettypename[netbuffer->packettype]));
|
|
//CONS_Alert(CONS_NOTICE, "New node sent an unexpected %s packet\n", packettypename[netbuffer->packettype]);
|
|
Net_CloseConnection(doomcom->remotenode | FORCECLOSE);
|
|
continue;
|
|
}*/
|
|
|
|
// Proceed the ack and ackreturn field
|
|
if (!Processackpak())
|
|
continue; // discarded (duplicated)
|
|
|
|
// A packet with just ackreturn
|
|
if (netbuffer->packettype == PT_NOTHING)
|
|
{
|
|
GotAcks();
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static boolean Internal_Get(void)
|
|
{
|
|
doomcom->remotenode = -1;
|
|
return false;
|
|
}
|
|
|
|
FUNCNORETURN static ATTRNORETURN void Internal_Send(void)
|
|
{
|
|
I_Error("Send without netgame\n");
|
|
}
|
|
|
|
static void Internal_FreeNodenum(INT32 nodenum)
|
|
{
|
|
(void)nodenum;
|
|
}
|
|
|
|
static char *I_NetSplitAddress(char *host, char **port)
|
|
{
|
|
boolean v4 = (host[0] != '[');
|
|
|
|
host = strtok(host, v4 ? ":" : "[]");
|
|
|
|
if (port)
|
|
*port = strtok(NULL, ":");
|
|
|
|
return host;
|
|
}
|
|
|
|
SINT8 I_NetMakeNode(const char *hostname)
|
|
{
|
|
SINT8 newnode = -1;
|
|
if (I_NetMakeNodewPort)
|
|
{
|
|
char *localhostname = strdup(hostname);
|
|
char *port;
|
|
if (!localhostname)
|
|
return newnode;
|
|
|
|
// retrieve portnum from address!
|
|
hostname = I_NetSplitAddress(localhostname, &port);
|
|
|
|
newnode = I_NetMakeNodewPort(hostname, port);
|
|
free(localhostname);
|
|
}
|
|
return newnode;
|
|
}
|
|
|
|
void D_SetDoomcom(void)
|
|
{
|
|
if (doomcom) return;
|
|
doomcom = Z_Calloc(sizeof (doomcom_t), PU_STATIC, NULL);
|
|
doomcom->id = DOOMCOM_ID;
|
|
doomcom->numslots = doomcom->numnodes = 1;
|
|
doomcom->gametype = 0;
|
|
doomcom->consoleplayer = 0;
|
|
doomcom->extratics = 0;
|
|
}
|
|
|
|
//
|
|
// D_CheckNetGame
|
|
// Works out player numbers among the net participants
|
|
//
|
|
boolean D_CheckNetGame(void)
|
|
{
|
|
boolean ret = false;
|
|
|
|
InitAck();
|
|
rebound_tail = rebound_head = 0;
|
|
|
|
statstarttic = I_GetTime();
|
|
|
|
I_NetGet = Internal_Get;
|
|
I_NetSend = Internal_Send;
|
|
I_NetCanSend = NULL;
|
|
I_NetCloseSocket = NULL;
|
|
I_NetFreeNodenum = Internal_FreeNodenum;
|
|
I_NetMakeNodewPort = NULL;
|
|
|
|
hardware_MAXPACKETLENGTH = MAXPACKETLENGTH;
|
|
net_bandwidth = 30000;
|
|
// I_InitNetwork sets doomcom and netgame
|
|
// check and initialize the network driver
|
|
multiplayer = false;
|
|
|
|
// only dos version with external driver will return true
|
|
netgame = I_InitNetwork();
|
|
if (!netgame && !I_NetOpenSocket)
|
|
{
|
|
D_SetDoomcom();
|
|
netgame = I_InitTcpNetwork();
|
|
}
|
|
|
|
if (netgame)
|
|
ret = true;
|
|
if (client && netgame)
|
|
netgame = false;
|
|
server = true; // WTF? server always true???
|
|
// no! The deault mode is server. Client is set elsewhere
|
|
// when the client executes connect command.
|
|
doomcom->ticdup = 1;
|
|
|
|
if (M_CheckParm("-extratic"))
|
|
{
|
|
if (M_IsNextParm())
|
|
doomcom->extratics = (INT16)atoi(M_GetNextParm());
|
|
else
|
|
doomcom->extratics = 1;
|
|
CONS_Printf(M_GetText("Set extratics to %d\n"), doomcom->extratics);
|
|
}
|
|
|
|
if (M_CheckParm("-bandwidth"))
|
|
{
|
|
if (M_IsNextParm())
|
|
{
|
|
net_bandwidth = atoi(M_GetNextParm());
|
|
if (net_bandwidth < 1000)
|
|
net_bandwidth = 1000;
|
|
if (net_bandwidth > 100000)
|
|
hardware_MAXPACKETLENGTH = MAXPACKETLENGTH;
|
|
CONS_Printf(M_GetText("Network bandwidth set to %d\n"), net_bandwidth);
|
|
}
|
|
else
|
|
I_Error("usage: -bandwidth <byte_per_sec>");
|
|
}
|
|
|
|
software_MAXPACKETLENGTH = hardware_MAXPACKETLENGTH;
|
|
if (M_CheckParm("-packetsize"))
|
|
{
|
|
if (M_IsNextParm())
|
|
{
|
|
INT32 p = atoi(M_GetNextParm());
|
|
if (p < 75)
|
|
p = 75;
|
|
if (p > hardware_MAXPACKETLENGTH)
|
|
p = hardware_MAXPACKETLENGTH;
|
|
software_MAXPACKETLENGTH = (UINT16)p;
|
|
}
|
|
else
|
|
I_Error("usage: -packetsize <bytes_per_packet>");
|
|
}
|
|
|
|
if (netgame)
|
|
multiplayer = true;
|
|
|
|
if (doomcom->id != DOOMCOM_ID)
|
|
I_Error("Doomcom buffer invalid!");
|
|
if (doomcom->numnodes > MAXNETNODES)
|
|
I_Error("Too many nodes (%d), max:%d", doomcom->numnodes, MAXNETNODES);
|
|
|
|
netbuffer = (doomdata_t *)(void *)&doomcom->data;
|
|
holepunchpacket = (holepunch_t *)(void *)&doomcom->data;
|
|
|
|
#ifdef DEBUGFILE
|
|
if (M_CheckParm("-debugfile"))
|
|
{
|
|
char filename[21];
|
|
INT32 k = doomcom->consoleplayer - 1;
|
|
if (M_IsNextParm())
|
|
k = atoi(M_GetNextParm()) - 1;
|
|
while (!debugfile && k < MAXPLAYERS)
|
|
{
|
|
k++;
|
|
sprintf(filename, "debug%d.txt", k);
|
|
debugfile = fopen(va("%s" PATHSEP "%s", srb2home, filename), "w");
|
|
}
|
|
if (debugfile)
|
|
CONS_Printf(M_GetText("debug output to: %s\n"), va("%s" PATHSEP "%s", srb2home, filename));
|
|
else
|
|
CONS_Alert(CONS_WARNING, M_GetText("cannot debug output to file %s!\n"), va("%s" PATHSEP "%s", srb2home, filename));
|
|
}
|
|
#endif
|
|
|
|
D_ClientServerInit();
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct pingcell
|
|
{
|
|
INT32 num;
|
|
INT32 ms;
|
|
INT32 f;
|
|
};
|
|
|
|
static int pingcellcmp(const void *va, const void *vb)
|
|
{
|
|
const struct pingcell *a, *b;
|
|
a = va;
|
|
b = vb;
|
|
return ( a->ms - b->ms );
|
|
}
|
|
|
|
/*
|
|
New ping command formatted nicely to present ping in
|
|
ascending order. And with equally spaced columns.
|
|
The caller's ping is presented at the bottom too, for
|
|
convenience.
|
|
*/
|
|
|
|
void Command_Ping_f(void)
|
|
{
|
|
struct pingcell pingv[MAXPLAYERS];
|
|
INT32 pingc;
|
|
|
|
int name_width = 0;
|
|
int f_width = 0;
|
|
int ms_width = 0;
|
|
|
|
int n;
|
|
INT32 i;
|
|
|
|
pingc = 0;
|
|
for (i = 1; i < MAXPLAYERS; ++i)
|
|
{
|
|
if (playeringame[i])
|
|
{
|
|
INT32 ms;
|
|
|
|
n = strlen(player_names[i]);
|
|
if (n > name_width)
|
|
name_width = n;
|
|
|
|
n = playerpingtable[i];
|
|
if (n > f_width)
|
|
f_width = n;
|
|
|
|
ms = (INT32)(playerpingtable[i] * (1000.00f / TICRATE));
|
|
n = ms;
|
|
if (n > ms_width)
|
|
ms_width = n;
|
|
|
|
pingv[pingc].num = i;
|
|
pingv[pingc].f = playerpingtable[i];
|
|
pingv[pingc].ms = ms;
|
|
pingc++;
|
|
}
|
|
}
|
|
|
|
if (f_width < 10) f_width = 1;
|
|
else if (f_width < 100) f_width = 2;
|
|
else f_width = 3;
|
|
|
|
if (ms_width < 10) ms_width = 1;
|
|
else if (ms_width < 100) ms_width = 2;
|
|
else ms_width = 3;
|
|
|
|
qs22j(pingv, pingc, sizeof (struct pingcell), &pingcellcmp);
|
|
|
|
for (i = 0; i < pingc; ++i)
|
|
{
|
|
CONS_Printf("%02d : %-*s %*d frames (%*d ms)\n",
|
|
pingv[i].num,
|
|
name_width, player_names[pingv[i].num],
|
|
f_width, pingv[i].f,
|
|
ms_width, pingv[i].ms);
|
|
}
|
|
|
|
if (!server && playeringame[consoleplayer])
|
|
{
|
|
CONS_Printf("\nYour ping is %d frames (%d ms)\n", playerpingtable[consoleplayer], (INT32)(playerpingtable[i] * (1000.00f / TICRATE)));
|
|
}
|
|
}
|
|
|
|
void D_CloseConnection(void)
|
|
{
|
|
INT32 i;
|
|
|
|
if (netgame)
|
|
{
|
|
// wait the ackreturn with timout of 5 Sec
|
|
Net_WaitAllAckReceived(5);
|
|
|
|
// close all connection
|
|
for (i = 0; i < MAXNETNODES; i++)
|
|
Net_CloseConnection(i|FORCECLOSE);
|
|
|
|
InitAck();
|
|
|
|
if (I_NetCloseSocket)
|
|
I_NetCloseSocket();
|
|
|
|
I_NetGet = Internal_Get;
|
|
I_NetSend = Internal_Send;
|
|
I_NetCanSend = NULL;
|
|
I_NetCloseSocket = NULL;
|
|
I_NetFreeNodenum = Internal_FreeNodenum;
|
|
I_NetMakeNodewPort = NULL;
|
|
netgame = false;
|
|
addedtogame = false;
|
|
}
|
|
|
|
D_ResetTiccmds();
|
|
}
|