raze/source/common/engine/i_net.cpp

1122 lines
26 KiB
C++
Raw Normal View History

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id: i_net.c,v 1.2 1997/12/29 19:50:54 pekangas Exp $
//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// 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, see http://www.gnu.org/licenses/
//
//
//
// Alternatively the following applies:
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//
//
// DESCRIPTION:
// Low-level networking code. Uses BSD sockets for UDP networking.
//
//-----------------------------------------------------------------------------
/* [Petteri] Check if compiling for Win32: */
#if defined(__WINDOWS__) || defined(__NT__) || defined(_MSC_VER) || defined(_WIN32)
#ifndef __WIN32__
# define __WIN32__
#endif
#endif
/* Follow #ifdef __WIN32__ marks */
#include <stdlib.h>
#include <string.h>
/* [Petteri] Use Winsock for Win32: */
#ifdef __WIN32__
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <winsock.h>
#else
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <errno.h>
# include <unistd.h>
# include <netdb.h>
# include <sys/ioctl.h>
# ifdef __sun
# include <fcntl.h>
# endif
#endif
#include "i_system.h"
#include "m_argv.h"
#include "m_crc32.h"
#include "st_start.h"
#include "engineerrors.h"
#include "cmdlib.h"
#include "printf.h"
#include "i_interface.h"
2021-10-30 08:51:03 +00:00
#include "i_net.h"
// As per http://support.microsoft.com/kb/q192599/ the standard
// size for network buffers is 8k.
#define TRANSMIT_SIZE 8000
/* [Petteri] Get more portable: */
#ifndef __WIN32__
typedef int SOCKET;
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1
#define closesocket close
#define ioctlsocket ioctl
#define Sleep(x) usleep (x * 1000)
#define WSAEWOULDBLOCK EWOULDBLOCK
#define WSAECONNRESET ECONNRESET
#define WSAGetLastError() errno
#endif
#ifndef IPPORT_USERRESERVED
#define IPPORT_USERRESERVED 5000
#endif
#ifdef __WIN32__
typedef int socklen_t;
#endif
bool netgame, multiplayer;
int consoleplayer; // i.e. myconnectindex in Build.
doomcom_t doomcom;
//
// NETWORKING
//
static u_short DOOMPORT = (IPPORT_USERRESERVED + 29);
static SOCKET mysocket = INVALID_SOCKET;
static sockaddr_in sendaddress[MAXNETNODES];
static uint8_t sendplayer[MAXNETNODES];
#ifdef __WIN32__
const char *neterror (void);
#else
#define neterror() strerror(errno)
#endif
enum
{
PRE_CONNECT, // Sent from guest to host for initial connection
PRE_KEEPALIVE,
PRE_DISCONNECT, // Sent from guest that aborts the game
PRE_ALLHERE, // Sent from host to guest when everybody has connected
PRE_CONACK, // Sent from host to guest to acknowledge PRE_CONNECT receipt
PRE_ALLFULL, // Sent from host to an unwanted guest
PRE_ALLHEREACK, // Sent from guest to host to acknowledge PRE_ALLHEREACK receipt
PRE_GO // Sent from host to guest to continue game startup
};
// Set PreGamePacket.fake to this so that the game rejects any pregame packets
// after it starts. This translates to NCMD_SETUP|NCMD_MULTI.
#define PRE_FAKE 0x30
struct PreGamePacket
{
uint8_t Fake;
uint8_t Message;
uint8_t NumNodes;
union
{
uint8_t ConsoleNum;
uint8_t NumPresent;
};
struct
{
uint32_t address;
uint16_t port;
uint8_t player;
uint8_t pad;
} machines[MAXNETNODES];
};
uint8_t TransmitBuffer[TRANSMIT_SIZE];
FString GetPlayerName(int num)
{
2020-10-03 15:04:45 +00:00
if (sysCallbacks.GetPlayerName) return sysCallbacks.GetPlayerName(sendplayer[num]);
else return FStringf("Player %d", sendplayer[num] + 1);
}
//
// UDPsocket
//
SOCKET UDPsocket (void)
{
SOCKET s;
// allocate a socket
s = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s == INVALID_SOCKET)
I_FatalError ("can't create socket: %s", neterror ());
return s;
}
//
// BindToLocalPort
//
void BindToLocalPort (SOCKET s, u_short port)
{
int v;
sockaddr_in address;
memset (&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
v = bind (s, (sockaddr *)&address, sizeof(address));
if (v == SOCKET_ERROR)
I_FatalError ("BindToPort: %s", neterror ());
}
int FindNode (const sockaddr_in *address)
{
int i;
// find remote node number
for (i = 0; i<doomcom.numnodes; i++)
if (address->sin_addr.s_addr == sendaddress[i].sin_addr.s_addr
&& address->sin_port == sendaddress[i].sin_port)
break;
if (i == doomcom.numnodes)
{
// packet is not from one of the players (new game broadcast?)
i = -1;
}
return i;
}
//
// PacketSend
//
void PacketSend (void)
{
int c;
// FIXME: Catch this before we've overflown the buffer. With long chat
// text and lots of backup tics, it could conceivably happen. (Though
// apparently it hasn't yet, which is good.)
if (doomcom.datalength > MAX_MSGLEN)
{
I_FatalError("Netbuffer overflow!");
}
assert(!(doomcom.data[0] & NCMD_COMPRESSED));
uLong size = TRANSMIT_SIZE - 1;
if (doomcom.datalength >= 10)
{
TransmitBuffer[0] = doomcom.data[0] | NCMD_COMPRESSED;
c = compress2(TransmitBuffer + 1, &size, doomcom.data + 1, doomcom.datalength - 1, 9);
size += 1;
}
else
{
c = -1; // Just some random error code to avoid sending the compressed buffer.
}
if (c == Z_OK && size < (uLong)doomcom.datalength)
{
// Printf("send %lu/%d\n", size, doomcom.datalength);
c = sendto(mysocket, (char *)TransmitBuffer, size,
0, (sockaddr *)&sendaddress[doomcom.remotenode],
sizeof(sendaddress[doomcom.remotenode]));
}
else
{
if (doomcom.datalength > TRANSMIT_SIZE)
{
I_Error("Net compression failed (zlib error %d)", c);
}
else
{
// Printf("send %d\n", doomcom.datalength);
c = sendto(mysocket, (char *)doomcom.data, doomcom.datalength,
0, (sockaddr *)&sendaddress[doomcom.remotenode],
sizeof(sendaddress[doomcom.remotenode]));
}
}
// if (c == -1)
// I_Error ("SendPacket error: %s",strerror(errno));
}
//
// PacketGet
//
void PacketGet (void)
{
int c;
socklen_t fromlen;
sockaddr_in fromaddress;
int node;
fromlen = sizeof(fromaddress);
c = recvfrom (mysocket, (char*)TransmitBuffer, TRANSMIT_SIZE, 0,
(sockaddr *)&fromaddress, &fromlen);
node = FindNode (&fromaddress);
if (node >= 0 && c == SOCKET_ERROR)
{
int err = WSAGetLastError();
if (err == WSAECONNRESET)
{ // The remote node aborted unexpectedly, so pretend it sent an exit packet
if (StartWindow != NULL)
{
I_NetMessage ("The connection from %s was dropped.\n",
GetPlayerName(node).GetChars());
}
else
{
Printf("The connection from %s was dropped.\n",
GetPlayerName(node).GetChars());
}
doomcom.data[0] = 0x80; // NCMD_EXIT
c = 1;
}
else if (err != WSAEWOULDBLOCK)
{
I_Error ("GetPacket: %s", neterror ());
}
else
{
doomcom.remotenode = -1; // no packet
return;
}
}
else if (node >= 0 && c > 0)
{
doomcom.data[0] = TransmitBuffer[0] & ~NCMD_COMPRESSED;
if (TransmitBuffer[0] & NCMD_COMPRESSED)
{
uLongf msgsize = MAX_MSGLEN - 1;
int err = uncompress(doomcom.data + 1, &msgsize, TransmitBuffer + 1, c - 1);
// Printf("recv %d/%lu\n", c, msgsize + 1);
if (err != Z_OK)
{
Printf("Net decompression failed (zlib error %s)\n", M_ZLibError(err).GetChars());
// Pretend no packet
doomcom.remotenode = -1;
return;
}
c = msgsize + 1;
}
else
{
// Printf("recv %d\n", c);
memcpy(doomcom.data + 1, TransmitBuffer + 1, c - 1);
}
}
else if (c > 0)
{ //The packet is not from any in-game node, so we might as well discard it.
// Don't show the message for disconnect notifications.
if (c != 2 || TransmitBuffer[0] != PRE_FAKE || TransmitBuffer[1] != PRE_DISCONNECT)
{
DPrintf(DMSG_WARNING, "Dropped packet: Unknown host (%s:%d)\n", inet_ntoa(fromaddress.sin_addr), fromaddress.sin_port);
}
doomcom.remotenode = -1;
return;
}
doomcom.remotenode = node;
doomcom.datalength = (short)c;
}
sockaddr_in *PreGet (void *buffer, int bufferlen, bool noabort)
{
static sockaddr_in fromaddress;
socklen_t fromlen;
int c;
fromlen = sizeof(fromaddress);
c = recvfrom (mysocket, (char *)buffer, bufferlen, 0,
(sockaddr *)&fromaddress, &fromlen);
if (c == SOCKET_ERROR)
{
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK || (noabort && err == WSAECONNRESET))
return NULL; // no packet
I_Error ("PreGet: %s", neterror ());
}
return &fromaddress;
}
void PreSend (const void *buffer, int bufferlen, const sockaddr_in *to)
{
sendto (mysocket, (const char *)buffer, bufferlen, 0, (const sockaddr *)to, sizeof(*to));
}
void BuildAddress (sockaddr_in *address, const char *name)
{
hostent *hostentry; // host information entry
u_short port;
const char *portpart;
bool isnamed = false;
int curchar;
char c;
FString target;
address->sin_family = AF_INET;
if ( (portpart = strchr (name, ':')) )
{
target = FString(name, portpart - name);
port = atoi (portpart + 1);
if (!port)
{
Printf ("Weird port: %s (using %d)\n", portpart + 1, DOOMPORT);
port = DOOMPORT;
}
}
else
{
target = name;
port = DOOMPORT;
}
address->sin_port = htons(port);
for (curchar = 0; (c = target[curchar]) ; curchar++)
{
if ((c < '0' || c > '9') && c != '.')
{
isnamed = true;
break;
}
}
if (!isnamed)
{
2023-10-07 21:48:50 +00:00
address->sin_addr.s_addr = inet_addr (target.GetChars());
Printf ("Node number %d, address %s\n", doomcom.numnodes, target.GetChars());
}
else
{
2023-10-07 21:48:50 +00:00
hostentry = gethostbyname (target.GetChars());
if (!hostentry)
I_FatalError ("gethostbyname: couldn't find %s\n%s", target.GetChars(), neterror());
address->sin_addr.s_addr = *(int *)hostentry->h_addr_list[0];
Printf ("Node number %d, hostname %s\n",
doomcom.numnodes, hostentry->h_name);
}
}
void CloseNetwork (void)
{
if (mysocket != INVALID_SOCKET)
{
closesocket (mysocket);
mysocket = INVALID_SOCKET;
}
#ifdef __WIN32__
WSACleanup ();
#endif
}
void StartNetwork (bool autoPort)
{
u_long trueval = 1;
#ifdef __WIN32__
WSADATA wsad;
if (WSAStartup (0x0101, &wsad))
{
I_FatalError ("Could not initialize Windows Sockets");
}
#endif
netgame = true;
multiplayer = true;
// create communication socket
mysocket = UDPsocket ();
BindToLocalPort (mysocket, autoPort ? 0 : DOOMPORT);
#ifndef __sun
ioctlsocket (mysocket, FIONBIO, &trueval);
#else
fcntl(mysocket, F_SETFL, trueval | O_NONBLOCK);
#endif
}
void SendAbort (void)
{
uint8_t dis[2] = { PRE_FAKE, PRE_DISCONNECT };
int i, j;
if (doomcom.numnodes > 1)
{
if (consoleplayer == 0)
{
// The host needs to let everyone know
for (i = 1; i < doomcom.numnodes; ++i)
{
for (j = 4; j > 0; --j)
{
PreSend (dis, 2, &sendaddress[i]);
}
}
}
else
{
// Guests only need to let the host know.
for (i = 4; i > 0; --i)
{
PreSend (dis, 2, &sendaddress[1]);
}
}
}
}
static void SendConAck (int num_connected, int num_needed)
{
PreGamePacket packet;
packet.Fake = PRE_FAKE;
packet.Message = PRE_CONACK;
packet.NumNodes = num_needed;
packet.NumPresent = num_connected;
for (int node = 1; node < doomcom.numnodes; ++node)
{
PreSend (&packet, 4, &sendaddress[node]);
}
I_NetProgress (doomcom.numnodes);
}
bool Host_CheckForConnects (void *userdata)
{
PreGamePacket packet;
int numplayers = (int)(intptr_t)userdata;
sockaddr_in *from;
int node;
while ( (from = PreGet (&packet, sizeof(packet), false)) )
{
if (packet.Fake != PRE_FAKE)
{
continue;
}
switch (packet.Message)
{
case PRE_CONNECT:
node = FindNode (from);
if (doomcom.numnodes == numplayers)
{
if (node == -1)
{
const uint8_t *s_addr_bytes = (const uint8_t *)&from->sin_addr;
I_NetMessage ("Got extra connect from %d.%d.%d.%d:%d",
s_addr_bytes[0], s_addr_bytes[1], s_addr_bytes[2], s_addr_bytes[3],
from->sin_port);
packet.Message = PRE_ALLFULL;
PreSend (&packet, 2, from);
}
}
else
{
if (node == -1)
{
node = doomcom.numnodes++;
sendaddress[node] = *from;
I_NetMessage ("Got connect from node %d.", node);
}
// Let the new guest (and everyone else) know we got their message.
SendConAck (doomcom.numnodes, numplayers);
}
break;
case PRE_DISCONNECT:
node = FindNode (from);
if (node >= 0)
{
I_NetMessage ("Got disconnect from node %d.", node);
doomcom.numnodes--;
while (node < doomcom.numnodes)
{
sendaddress[node] = sendaddress[node+1];
node++;
}
// Let remaining guests know that somebody left.
SendConAck (doomcom.numnodes, numplayers);
}
break;
case PRE_KEEPALIVE:
break;
}
}
if (doomcom.numnodes < numplayers)
{
// Send message to everyone as a keepalive
SendConAck(doomcom.numnodes, numplayers);
return false;
}
// It's possible somebody bailed out after all players were found.
// Unfortunately, this isn't guaranteed to catch all of them.
// Oh well. Better than nothing.
while ( (from = PreGet (&packet, sizeof(packet), false)) )
{
if (packet.Fake == PRE_FAKE && packet.Message == PRE_DISCONNECT)
{
node = FindNode (from);
if (node >= 0)
{
doomcom.numnodes--;
while (node < doomcom.numnodes)
{
sendaddress[node] = sendaddress[node+1];
node++;
}
// Let remaining guests know that somebody left.
SendConAck (doomcom.numnodes, numplayers);
}
break;
}
}
return doomcom.numnodes >= numplayers;
}
bool Host_SendAllHere (void *userdata)
{
int *gotack = (int *)userdata; // ackcount is at gotack[MAXNETNODES]
PreGamePacket packet;
int node;
sockaddr_in *from;
// Send out address information to all guests. Guests that have already
// acknowledged receipt effectively get just a heartbeat packet.
packet.Fake = PRE_FAKE;
packet.Message = PRE_ALLHERE;
for (node = 1; node < doomcom.numnodes; node++)
{
int machine, spot = 0;
packet.ConsoleNum = node;
if (!gotack[node])
{
for (spot = 0, machine = 1; machine < doomcom.numnodes; machine++)
{
if (node != machine)
{
packet.machines[spot].address = sendaddress[machine].sin_addr.s_addr;
packet.machines[spot].port = sendaddress[machine].sin_port;
packet.machines[spot].player = node;
spot++; // fixes problem of new address replacing existing address in
// array; it's supposed to increment the index before getting
// and storing in the packet the next address.
}
}
packet.NumNodes = doomcom.numnodes - 2;
}
else
{
packet.NumNodes = 0;
}
PreSend (&packet, 4 + spot*8, &sendaddress[node]);
}
// Check for replies.
while ( (from = PreGet (&packet, sizeof(packet), false)) )
{
if (packet.Fake == PRE_FAKE && packet.Message == PRE_ALLHEREACK)
{
node = FindNode (from);
if (node >= 0)
{
if (!gotack[node])
{
gotack[node] = true;
gotack[MAXNETNODES]++;
}
}
PreSend (&packet, 2, from);
}
}
// If everybody has replied, then this loop can end.
return gotack[MAXNETNODES] == doomcom.numnodes - 1;
}
bool HostGame (int i)
{
PreGamePacket packet;
int numplayers;
int node;
int gotack[MAXNETNODES+1];
if ((i == Args->NumArgs() - 1) || !(numplayers = atoi (Args->GetArg(i+1))))
{ // No player count specified, assume 2
numplayers = 2;
}
if (numplayers > MAXNETNODES)
{
I_FatalError("You cannot host a game with %d players. The limit is currently %d.", numplayers, MAXNETNODES);
}
if (numplayers == 1)
{ // Special case: Only 1 player, so don't bother starting the network
netgame = false;
multiplayer = true;
doomcom.id = DOOMCOM_ID;
doomcom.numplayers = doomcom.numnodes = 1;
doomcom.consoleplayer = 0;
return true;
}
StartNetwork (false);
// [JC] - this computer is starting the game, therefore it should
// be the Net Arbitrator.
doomcom.consoleplayer = 0;
Printf ("Console player number: %d\n", doomcom.consoleplayer);
doomcom.numnodes = 1;
I_NetInit ("Waiting for players", numplayers);
// Wait for numplayers-1 different connections
if (!I_NetLoop (Host_CheckForConnects, (void *)(intptr_t)numplayers))
{
SendAbort();
return false;
}
// Now inform everyone of all machines involved in the game
memset (gotack, 0, sizeof(gotack));
I_NetMessage ("Sending all here.");
I_NetInit ("Done waiting", 1);
if (!I_NetLoop (Host_SendAllHere, (void *)gotack))
{
SendAbort();
return false;
}
// Now go
I_NetMessage ("Go");
packet.Fake = PRE_FAKE;
packet.Message = PRE_GO;
for (node = 1; node < doomcom.numnodes; node++)
{
// If we send the packets eight times to each guest,
// hopefully at least one of them will get through.
for (int ii = 8; ii != 0; --ii)
{
PreSend (&packet, 2, &sendaddress[node]);
}
}
I_NetMessage ("Total players: %d", doomcom.numnodes);
doomcom.id = DOOMCOM_ID;
doomcom.numplayers = doomcom.numnodes;
// On the host, each player's number is the same as its node number
for (i = 0; i < doomcom.numnodes; ++i)
{
sendplayer[i] = i;
}
return true;
}
// This routine is used by a guest to notify the host of its presence.
// Once that host acknowledges receipt of the notification, this routine
// is never called again.
bool Guest_ContactHost (void *userdata)
{
sockaddr_in *from;
PreGamePacket packet;
// Let the host know we are here.
packet.Fake = PRE_FAKE;
packet.Message = PRE_CONNECT;
PreSend (&packet, 2, &sendaddress[1]);
// Listen for a reply.
while ( (from = PreGet (&packet, sizeof(packet), true)) )
{
if (packet.Fake == PRE_FAKE && FindNode(from) == 1)
{
if (packet.Message == PRE_CONACK)
{
I_NetMessage ("Total players: %d", packet.NumNodes);
I_NetInit ("Waiting for other players", packet.NumNodes);
I_NetProgress (packet.NumPresent);
return true;
}
else if (packet.Message == PRE_DISCONNECT)
{
doomcom.numnodes = 0;
I_FatalError ("The host cancelled the game.");
}
else if (packet.Message == PRE_ALLFULL)
{
doomcom.numnodes = 0;
I_FatalError ("The game is full.");
}
}
}
// In case the progress bar could not be marqueed, bump it.
I_NetProgress (0);
return false;
}
bool Guest_WaitForOthers (void *userdata)
{
sockaddr_in *from;
PreGamePacket packet;
while ( (from = PreGet (&packet, sizeof(packet), false)) )
{
if (packet.Fake != PRE_FAKE || FindNode(from) != 1)
{
continue;
}
switch (packet.Message)
{
case PRE_CONACK:
I_NetProgress (packet.NumPresent);
break;
case PRE_ALLHERE:
if (doomcom.numnodes == 2)
{
int node;
doomcom.numnodes = packet.NumNodes + 2;
sendplayer[0] = packet.ConsoleNum; // My player number
doomcom.consoleplayer = packet.ConsoleNum;
I_NetMessage ("Console player number: %d", doomcom.consoleplayer);
for (node = 0; node < packet.NumNodes; node++)
{
sendaddress[node+2].sin_addr.s_addr = packet.machines[node].address;
sendaddress[node+2].sin_port = packet.machines[node].port;
sendplayer[node+2] = packet.machines[node].player;
// [JC] - fixes problem of games not starting due to
// no address family being assigned to nodes stored in
// sendaddress[] from the All Here packet.
sendaddress[node+2].sin_family = AF_INET;
}
}
I_NetMessage ("Received All Here, sending ACK.");
packet.Fake = PRE_FAKE;
packet.Message = PRE_ALLHEREACK;
PreSend (&packet, 2, &sendaddress[1]);
break;
case PRE_GO:
I_NetMessage ("Received \"Go.\"");
return true;
case PRE_DISCONNECT:
I_FatalError ("The host cancelled the game.");
break;
}
}
packet.Fake = PRE_FAKE;
packet.Message = PRE_KEEPALIVE;
PreSend(&packet, 2, &sendaddress[1]);
return false;
}
bool JoinGame (int i)
{
if ((i == Args->NumArgs() - 1) ||
(Args->GetArg(i+1)[0] == '-') ||
(Args->GetArg(i+1)[0] == '+'))
I_FatalError ("You need to specify the host machine's address");
StartNetwork (true);
// Host is always node 1
BuildAddress (&sendaddress[1], Args->GetArg(i+1));
sendplayer[1] = 0;
doomcom.numnodes = 2;
// Let host know we are here
I_NetInit ("Contacting host", 0);
if (!I_NetLoop (Guest_ContactHost, NULL))
{
SendAbort();
return false;
}
// Wait for everyone else to connect
if (!I_NetLoop (Guest_WaitForOthers, 0))
{
SendAbort();
return false;
}
I_NetMessage ("Total players: %d", doomcom.numnodes);
doomcom.id = DOOMCOM_ID;
doomcom.numplayers = doomcom.numnodes;
return true;
}
static int PrivateNetOf(in_addr in)
{
int addr = ntohl(in.s_addr);
if ((addr & 0xFFFF0000) == 0xC0A80000) // 192.168.0.0
{
return 0xC0A80000;
}
else if ((addr & 0xFFF00000) == 0xAC100000) // 172.16.0.0
{
return 0xAC100000;
}
else if ((addr & 0xFF000000) == 0x0A000000) // 10.0.0.0
{
return 0x0A000000;
}
else if ((addr & 0xFF000000) == 0x7F000000) // 127.0.0.0 (localhost)
{
return 0x7F000000;
}
// Not a private IP
return 0;
}
//
// NodesOnSameNetwork
//
// The best I can really do here is check if the others are on the same
// private network, since that means we (probably) are too.
//
static bool NodesOnSameNetwork()
{
int net1;
net1 = PrivateNetOf(sendaddress[1].sin_addr);
// Printf("net1 = %08x\n", net1);
if (net1 == 0)
{
return false;
}
for (int i = 2; i < doomcom.numnodes; ++i)
{
int net = PrivateNetOf(sendaddress[i].sin_addr);
// Printf("Net[%d] = %08x\n", i, net);
if (net != net1)
{
return false;
}
}
return true;
}
//
// I_InitNetwork
//
// Returns true if packet server mode might be a good idea.
//
int I_InitNetwork (void)
{
int i;
const char *v;
memset (&doomcom, 0, sizeof(doomcom));
// set up for network
v = Args->CheckValue ("-dup");
if (v)
{
doomcom.ticdup = clamp<int> (atoi (v), 1, MAXTICDUP);
}
else
{
doomcom.ticdup = 1;
}
v = Args->CheckValue ("-port");
if (v)
{
DOOMPORT = atoi (v);
Printf ("using alternate port %i\n", DOOMPORT);
}
// parse network game options,
// player 1: -host <numplayers>
// player x: -join <player 1's address>
if ( (i = Args->CheckParm ("-host")) )
{
if (!HostGame (i)) return -1;
}
else if ( (i = Args->CheckParm ("-join")) )
{
if (!JoinGame (i)) return -1;
}
else
{
// single player game
netgame = false;
multiplayer = false;
doomcom.id = DOOMCOM_ID;
doomcom.numplayers = doomcom.numnodes = 1;
doomcom.consoleplayer = 0;
return false;
}
if (doomcom.numnodes < 3)
{ // Packet server mode with only two players is effectively the same as
// peer-to-peer but with some slightly larger packets.
return false;
}
return doomcom.numnodes > 3 || !NodesOnSameNetwork();
}
void I_NetCmd (void)
{
if (doomcom.command == CMD_SEND)
{
PacketSend ();
}
else if (doomcom.command == CMD_GET)
{
PacketGet ();
}
else
I_Error ("Bad net cmd: %i\n",doomcom.command);
}
void I_NetMessage(const char* text, ...)
{
// todo: use better abstraction once everything is migrated to in-game start screens.
#if defined _WIN32 || defined __APPLE__
va_list ap;
va_start(ap, text);
VPrintf(PRINT_HIGH, text, ap);
Printf("\n");
va_end(ap);
#else
FString str;
va_list argptr;
va_start(argptr, text);
str.VFormat(text, argptr);
va_end(argptr);
fprintf(stderr, "\r%-40s\n", str.GetChars());
#endif
}
// todo: later these must be dispatched by the main menu, not the start screen.
void I_NetProgress(int val)
{
StartWindow->NetProgress(val);
}
void I_NetInit(const char* msg, int num)
{
StartWindow->NetInit(msg, num);
}
bool I_NetLoop(bool (*timer_callback)(void*), void* userdata)
{
return StartWindow->NetLoop(timer_callback, userdata);
}
void I_NetDone()
{
StartWindow->NetDone();
}
#ifdef __WIN32__
const char *neterror (void)
{
static char neterr[16];
int code;
switch (code = WSAGetLastError ()) {
case WSAEACCES: return "EACCES";
case WSAEADDRINUSE: return "EADDRINUSE";
case WSAEADDRNOTAVAIL: return "EADDRNOTAVAIL";
case WSAEAFNOSUPPORT: return "EAFNOSUPPORT";
case WSAEALREADY: return "EALREADY";
case WSAECONNABORTED: return "ECONNABORTED";
case WSAECONNREFUSED: return "ECONNREFUSED";
case WSAECONNRESET: return "ECONNRESET";
case WSAEDESTADDRREQ: return "EDESTADDRREQ";
case WSAEFAULT: return "EFAULT";
case WSAEHOSTDOWN: return "EHOSTDOWN";
case WSAEHOSTUNREACH: return "EHOSTUNREACH";
case WSAEINPROGRESS: return "EINPROGRESS";
case WSAEINTR: return "EINTR";
case WSAEINVAL: return "EINVAL";
case WSAEISCONN: return "EISCONN";
case WSAEMFILE: return "EMFILE";
case WSAEMSGSIZE: return "EMSGSIZE";
case WSAENETDOWN: return "ENETDOWN";
case WSAENETRESET: return "ENETRESET";
case WSAENETUNREACH: return "ENETUNREACH";
case WSAENOBUFS: return "ENOBUFS";
case WSAENOPROTOOPT: return "ENOPROTOOPT";
case WSAENOTCONN: return "ENOTCONN";
case WSAENOTSOCK: return "ENOTSOCK";
case WSAEOPNOTSUPP: return "EOPNOTSUPP";
case WSAEPFNOSUPPORT: return "EPFNOSUPPORT";
case WSAEPROCLIM: return "EPROCLIM";
case WSAEPROTONOSUPPORT: return "EPROTONOSUPPORT";
case WSAEPROTOTYPE: return "EPROTOTYPE";
case WSAESHUTDOWN: return "ESHUTDOWN";
case WSAESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT";
case WSAETIMEDOUT: return "ETIMEDOUT";
case WSAEWOULDBLOCK: return "EWOULDBLOCK";
case WSAHOST_NOT_FOUND: return "HOST_NOT_FOUND";
case WSANOTINITIALISED: return "NOTINITIALISED";
case WSANO_DATA: return "NO_DATA";
case WSANO_RECOVERY: return "NO_RECOVERY";
case WSASYSNOTREADY: return "SYSNOTREADY";
case WSATRY_AGAIN: return "TRY_AGAIN";
case WSAVERNOTSUPPORTED: return "VERNOTSUPPORTED";
case WSAEDISCON: return "EDISCON";
default:
mysnprintf (neterr, countof(neterr), "%d", code);
return neterr;
}
}
#endif