mirror of
https://github.com/ZDoom/Raze.git
synced 2024-11-27 14:32:26 +00:00
84173ee09b
The main bulk of this is the new start screen code. To make this work in Raze some more work on the startup procedure is needed. What this does provide is support for the DOS end-of-game text screens in Duke and SW on non-Windows systems.
1121 lines
26 KiB
C++
1121 lines
26 KiB
C++
// 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"
|
|
|
|
|
|
#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)
|
|
{
|
|
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)
|
|
{
|
|
address->sin_addr.s_addr = inet_addr (target);
|
|
Printf ("Node number %d, address %s\n", doomcom.numnodes, target.GetChars());
|
|
}
|
|
else
|
|
{
|
|
hostentry = gethostbyname (target);
|
|
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
|