SRB2/src/d_net.c
Hank Brannock f94d3a1fb0 The code in i_net.c doesn't actually seem to be used in SRB2. I was able to compile a build without it, and hosting and joining netgames worked just fine (well, as fine as they can with the current state of the netcode...).
Do we really need to keep it around? If not, I say get rid of it. It seems like useless clutter that is just going to confuse people who are trying to understand the source code.
2016-05-22 22:38:16 -04:00

1234 lines
31 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-2016 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_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"
//
// NETWORKING
//
// gametic is the tic about to be (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
// firsttictosend: is the lowest value of nettics
// client:
// neededtic: is the tic needed by the client to run the game
// firsttictosend: is used to optimize a condition
// normally maketic >= gametic > 0
#define FORCECLOSE 0x8000
tic_t connectiontimeout = (15*TICRATE);
/// \brief network packet
doomcom_t *doomcom = NULL;
/// \brief network packet data, points inside doomcom
doomdata_t *netbuffer = NULL;
FILE *debugfile = NULL; // put some net info in a file during the game
#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;
void (*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;
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;
boolean (*I_SetBanAddress) (const char *address, const char *mask) = NULL;
boolean *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
#define MAXACKTOSEND 96
#define URGENTFREESLOTENUM 10
#define ACKTOSENDTIMEOUT (TICRATE/11)
#ifndef NONET
typedef struct
{
UINT8 acknum;
UINT8 nextacknum;
UINT8 destinationnode;
tic_t senttime;
UINT16 length;
UINT16 resentnum;
union {
SINT8 raw[MAXPACKETLENGTH];
doomdata_t data;
} pak;
} ackpak_t;
#endif
typedef enum
{
CLOSE = 1, // flag is set when connection is closing
} node_flags_t;
#ifndef NONET
// table of packet that was not acknowleged can be resend (the sender window)
static ackpak_t ackpak[MAXACKPACKETS];
#endif
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;
#ifndef NEWPING
// jacobson tcp timeout evaluation algorithm (Karn variation)
fixed_t ping;
fixed_t varping;
INT32 timeout; // computed with ping and varping
#endif
} node_t;
static node_t nodes[MAXNETNODES];
#ifndef NEWPING
#define PINGDEFAULT ((200*TICRATE*FRACUNIT)/1000)
#define VARPINGDEFAULT ((50*TICRATE*FRACUNIT)/1000)
#define TIMEOUT(p,v) (p+4*v+FRACUNIT/2)>>FRACBITS;
#else
#define NODETIMEOUT 14 //What the above boiled down to...
#endif
#ifndef NONET
// 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;
}
// return a free acknum and copy netbuffer in the ackpak table
static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
{
node_t *node = &nodes[doomcom->remotenode];
INT32 i, numfreeslote = 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 packet, make sure let freeslotes so urgents packets can be sent
numfreeslote++;
if (netbuffer->packettype >= PT_CANFAIL && numfreeslote < URGENTFREESLOTENUM)
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 mean can't be sent now so try it 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;
}
// Get a ack to send in the queu 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;
#ifndef NEWPING
fixed_t trueping = (I_GetTime() - ackpak[i].senttime)<<FRACBITS;
if (ackpak[i].resentnum)
{
// +FRACUNIT/2 for round
nodes[node].ping = (nodes[node].ping*7 + trueping)/8;
nodes[node].varping = (nodes[node].varping*7 + abs(nodes[node].ping-trueping))/8;
nodes[node].timeout = TIMEOUT(nodes[node].ping,nodes[node].varping);
}
DEBFILE(va("Remove ack %d trueping %d ping %f var %f timeout %d\n",ackpak[i].acknum,trueping>>FRACBITS,(double)FIXED_TO_FLOAT(nodes[node].ping),(double)FIXED_TO_FLOAT(nodes[node].varping),nodes[node].timeout));
#else
DEBFILE(va("Remove ack %d\n",ackpak[i].acknum));
#endif
ackpak[i].acknum = 0;
if (nodes[node].flags & 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;
node_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 == node - nodes
&& 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;
}
#endif
// send special packet with only ack on it
void Net_SendAcks(INT32 node)
{
#ifdef NONET
(void)node;
#else
netbuffer->packettype = PT_NOTHING;
M_Memcpy(netbuffer->u.textcmd, nodes[node].acktosend, MAXACKTOSEND);
HSendPacket(node, false, 0, MAXACKTOSEND);
#endif
}
#ifndef NONET
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);
else
// 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
if (cmpack(ackpak[i].nextacknum, netbuffer->u.textcmd[j]) <= 0
&& ackpak[i].senttime > 0)
{
ackpak[i].senttime--; // hurry up
}
}
}
#endif
static inline void Net_ConnectionTimeout(INT32 node)
{
// 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)
{
#ifndef NONET
INT32 i;
for (i = 0; i < MAXACKPACKETS; i++)
{
const INT32 nodei = ackpak[i].destinationnode;
node_t *node = &nodes[nodei];
#ifdef NEWPING
if (ackpak[i].acknum && ackpak[i].senttime + NODETIMEOUT < I_GetTime())
#else
if (ackpak[i].acknum && ackpak[i].senttime + node->timeout < I_GetTime())
#endif
{
if (ackpak[i].resentnum > 10 && (node->flags & CLOSE))
{
DEBFILE(va("ack %d sent 10 times so connection is supposed lost: node %d\n",
i, nodei));
Net_CloseConnection(nodei | FORCECLOSE);
ackpak[i].acknum = 0;
continue;
}
#ifdef NEWPING
DEBFILE(va("Resend ack %d, %u<%d at %u\n", ackpak[i].acknum, ackpak[i].senttime,
NODETIMEOUT, I_GetTime()));
#else
DEBFILE(va("Resend ack %d, %u<%d at %u\n", ackpak[i].acknum, ackpak[i].senttime,
node->timeout, I_GetTime()));
#endif
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 & CLOSE)
&& nodes[i].lasttimepacketreceived + connectiontimeout < I_GetTime())
{
Net_ConnectionTimeout(i);
}
}
}
#endif
}
// remove last packet received ack before resending the ackret
// (the higher layer doesn't have room, or something else ....)
void Net_UnAcknowledgPacket(INT32 node)
{
#ifdef NONET
(void)node;
#else
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;
}
#endif
}
boolean Net_AllAckReceived(void)
{
#ifndef NONET
INT32 i;
for (i = 0; i < MAXACKPACKETS; i++)
if (ackpak[i].acknum)
return false;
#endif
return true;
}
// wait for all ackreturns with timeout in seconds
void Net_WaitAllAckReceived(UINT32 timeout)
{
#ifdef NONET
(void)timeout;
#else
tic_t tictac = I_GetTime();
timeout = tictac + timeout*NEWTICRATE;
HGetPacket();
while (timeout > I_GetTime() && !Net_AllAckReceived())
{
while (tictac == I_GetTime())
I_Sleep();
tictac = I_GetTime();
HGetPacket();
Net_AckTicker();
}
#endif
}
static void InitNode(INT32 node)
{
nodes[node].acktosend_head = nodes[node].acktosend_tail = 0;
#ifndef NEWPING
nodes[node].ping = PINGDEFAULT;
nodes[node].varping = VARPINGDEFAULT;
nodes[node].timeout = TIMEOUT(nodes[node].ping,nodes[node].varping);
#endif
nodes[node].firstacktosend = 0;
nodes[node].nextacknum = 1;
nodes[node].remotefirstack = 0;
nodes[node].flags = 0;
}
static void InitAck(void)
{
INT32 i;
#ifndef NONET
for (i = 0; i < MAXACKPACKETS; i++)
ackpak[i].acknum = 0;
#endif
for (i = 0; i < MAXNETNODES; i++)
InitNode(i);
}
void Net_AbortPacketType(UINT8 packettype)
{
#ifdef NONET
(void)packettype;
#else
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;
}
#endif
}
// -----------------------------------------------------------------
// end of acknowledge function
// -----------------------------------------------------------------
// remove a node, clear all ack from this node and reset askret
void Net_CloseConnection(INT32 node)
{
#ifdef NONET
(void)node;
#else
INT32 i;
boolean forceclose = (node & FORCECLOSE) != 0;
node &= ~FORCECLOSE;
if (!node)
return;
nodes[node].flags |= 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(node);
AbortSendFiles(node);
I_NetFreeNodenum(node);
#endif
}
#ifndef NONET
//
// 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);
}
#endif
#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, "]");
fprintf(debugfile, "\n");
}
static const char *packettypename[NUMPACKETTYPE] =
{
"NOTHING",
"SERVERCFG",
"CLIENTCMD",
"CLIENTMIS",
"CLIENT2CMD",
"CLIENT2MIS",
"NODEKEEPALIVE",
"NODEKEEPALIVEMIS",
"SERVERTICS",
"SERVERREFUSE",
"SERVERSHUTDOWN",
"CLIENTQUIT",
"ASKINFO",
"SERVERINFO",
"REQUESTFILE",
"ASKINFOVIAMS",
"PLAYERCONFIGS",
"FILEFRAGMENT",
"TEXTCMD",
"TEXTCMD2",
"CLIENTJOIN",
"NODETIMEOUT",
};
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:
fprintf(debugfile, " firsttic %u ply %d tics %d ntxtcmd %s\n ",
(UINT32)ExpandTics(netbuffer->u.serverpak.starttic), netbuffer->u.serverpak.numslots,
netbuffer->u.serverpak.numtics,
sizeu1((size_t)(&((UINT8 *)netbuffer)[doomcom->datalength] - (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots*netbuffer->u.serverpak.numtics])));
fprintfstring((char *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots*netbuffer->u.serverpak.numtics],(size_t)(
&((UINT8 *)netbuffer)[doomcom->datalength] - (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots*netbuffer->u.serverpak.numtics]));
break;
case PT_CLIENTCMD:
case PT_CLIENT2CMD:
case PT_CLIENTMIS:
case PT_CLIENT2MIS:
case PT_NODEKEEPALIVE:
case PT_NODEKEEPALIVEMIS:
fprintf(debugfile, " tic %4u resendfrom %u\n",
(UINT32)ExpandTics(netbuffer->u.clientpak.client_tic),
(UINT32)ExpandTics (netbuffer->u.clientpak.resendfrom));
break;
case PT_TEXTCMD:
case PT_TEXTCMD2:
fprintf(debugfile, " length %d\n ", netbuffer->u.textcmd[0]);
fprintfstring((char *)netbuffer->u.textcmd+1, netbuffer->u.textcmd[0]);
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, map %s, filenum %d, time %u \n",
netbuffer->u.serverinfo.servername, netbuffer->u.serverinfo.numberofplayer,
netbuffer->u.serverinfo.maxplayer, netbuffer->u.serverinfo.mapname,
netbuffer->u.serverinfo.fileneedednum,
(UINT32)LONG(netbuffer->u.serverinfo.time));
fprintfstring((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:
fprintf(debugfile, " fileid %d datasize %d position %u\n",
netbuffer->u.filetxpak.fileid, (UINT16)SHORT(netbuffer->u.filetxpak.size),
(UINT32)LONG(netbuffer->u.filetxpak.position));
break;
case PT_REQUESTFILE:
default: // write as a raw packet
fprintfstring((char *)netbuffer->u.textcmd,
(UINT8)((UINT8 *)netbuffer + doomcom->datalength - (UINT8 *)netbuffer->u.textcmd));
break;
}
}
#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;
}
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");
#ifdef NONET
(void)node;
(void)reliable;
(void)acknum;
#else
// do this before GetFreeAcknum because this function backup
// 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
// simulate internet :)
if (true || rand()<(INT32)RAND_MAX/5)
{
#ifdef DEBUGFILE
if (debugfile)
DebugPrintpacket("SEND");
#endif
I_NetSend();
}
#ifdef DEBUGFILE
else if (debugfile)
DebugPrintpacket("NOTSEND");
#endif
#endif // ndef NONET
return true;
}
//
// HGetPacket
// Returns false if no packet is waiting
// Check Datalength and checksum
//
boolean HGetPacket(void)
{
// 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;
#ifndef NONET
while(true)
{
I_NetGet();
if (doomcom->remotenode == -1)
return false;
getbytes += packetheaderlength + doomcom->datalength; // for stat
if (doomcom->remotenode >= MAXNETNODES)
{
DEBFILE(va("receive packet from node %d !\n", doomcom->remotenode));
continue;
}
nodes[doomcom->remotenode].lasttimepacketreceived = I_GetTime();
if (netbuffer->checksum != NetbufferChecksum())
{
DEBFILE("Bad packet checksum\n");
Net_CloseConnection(doomcom->remotenode);
continue;
}
#ifdef DEBUGFILE
if (debugfile)
DebugPrintpacket("GET");
#endif
// proceed the ack and ackreturn field
if (!Processackpak())
continue; // discarded (duplicated)
// a packet with just ackreturn
if (netbuffer->packettype == PT_NOTHING)
{
GotAcks();
continue;
}
break;
}
#endif // ndef NONET
return true;
}
static void Internal_Get(void)
{
doomcom->remotenode = -1;
}
FUNCNORETURN static ATTRNORETURN void Internal_Send(void)
{
I_Error("Send without netgame\n");
}
static void Internal_FreeNodenum(INT32 nodenum)
{
(void)nodenum;
}
SINT8 I_NetMakeNode(const char *hostname)
{
SINT8 newnode = -1;
if (I_NetMakeNodewPort)
{
char *localhostname = strdup(hostname);
char *t = localhostname;
const char *port;
if (!localhostname)
return newnode;
// retrieve portnum from address!
strtok(localhostname, ":");
port = strtok(NULL, ":");
// remove the port in the hostname as we've it already
while ((*t != ':') && (*t != '\0'))
t++;
*t = '\0';
newnode = I_NetMakeNodewPort(localhostname, 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 = false; // I_InitNetwork() is no longer used
if (!netgame && !I_NetOpenSocket)
{
D_SetDoomcom();
netgame = I_InitTcpNetwork();
}
if (netgame)
ret = true;
if (!server && 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;
#ifdef DEBUGFILE
#ifdef _arch_dreamcast
//debugfile = stderr;
if (debugfile)
CONS_Printf(M_GetText("debug output to: %s\n"), "STDERR");
#else
if (M_CheckParm("-debugfile"))
{
char filename[20];
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(filename, "w");
}
if (debugfile)
CONS_Printf(M_GetText("debug output to: %s\n"), filename);
else
CONS_Alert(CONS_WARNING, M_GetText("cannot debug output to file %s!\n"), filename);
}
#endif
#endif
D_ClientServerInit();
return ret;
}
void Command_Ping_f(void)
{
#ifndef NEWPING
if(server)
{
#endif
INT32 i;
for (i = 0; i < MAXPLAYERS;i++)
{
#ifndef NEWPING
const INT32 node = playernode[i];
if (playeringame[i] && node != 0)
CONS_Printf(M_GetText("%.2d : %s\n %d tics, %d ms.\n"), i, player_names[i],
GetLag(node), G_TicsToMilliseconds(GetLag(node)));
#else
if (playeringame[i] && i != 0)
CONS_Printf(M_GetText("%.2d : %s\n %d ms\n"), i, player_names[i], playerpingtable[i]);
#endif
}
#ifndef NEWPING
}
else
CONS_Printf(M_GetText("Only the server can use this.\n"));
#endif
}
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;
}
}