1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: i_net.c,v 1.2 1997/12/29 19:50:54 pekangas Exp $
5 //
6 // Copyright (C) 1993-1996 by id Software, Inc.
7 //
8 // This source is available for distribution and/or modification
9 // only under the terms of the DOOM Source Code License as
10 // published by id Software. All rights reserved.
11 //
12 // The source is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
15 // for more details.
16 //
17 // DESCRIPTION:
18 // Low-level networking code. Uses BSD sockets for UDP networking.
19 //
20 //-----------------------------------------------------------------------------
21
22
23 /* [Petteri] Check if compiling for Win32: */
24 #if defined(__WINDOWS__) || defined(__NT__) || defined(_MSC_VER) || defined(_WIN32)
25 #ifndef __WIN32__
26 # define __WIN32__
27 #endif
28 #endif
29 /* Follow #ifdef __WIN32__ marks */
30
31 #include <stdlib.h>
32 #include <string.h>
33 #include <stdio.h>
34
35 /* [Petteri] Use Winsock for Win32: */
36 #ifdef __WIN32__
37 # define WIN32_LEAN_AND_MEAN
38 # include <windows.h>
39 # include <winsock.h>
40 #define USE_WINDOWS_DWORD
41 #else
42 # include <sys/socket.h>
43 # include <netinet/in.h>
44 # include <arpa/inet.h>
45 # include <errno.h>
46 # include <unistd.h>
47 # include <netdb.h>
48 # include <sys/ioctl.h>
49 # ifdef __sun
50 # include <fcntl.h>
51 # endif
52 #endif
53
54 #include "doomtype.h"
55 #include "i_system.h"
56 #include "d_event.h"
57 #include "d_net.h"
58 #include "m_argv.h"
59 #include "m_swap.h"
60 #include "m_crc32.h"
61 #include "d_player.h"
62 #include "templates.h"
63 #include "c_console.h"
64 #include "st_start.h"
65 #include "m_misc.h"
66 #include "doomstat.h"
67
68 #include "i_net.h"
69
70 // As per http://support.microsoft.com/kb/q192599/ the standard
71 // size for network buffers is 8k.
72 #define TRANSMIT_SIZE 8000
73
74 /* [Petteri] Get more portable: */
75 #ifndef __WIN32__
76 typedef int SOCKET;
77 #define SOCKET_ERROR -1
78 #define INVALID_SOCKET -1
79 #define closesocket close
80 #define ioctlsocket ioctl
81 #define Sleep(x) usleep (x * 1000)
82 #define WSAEWOULDBLOCK EWOULDBLOCK
83 #define WSAECONNRESET ECONNRESET
84 #define WSAGetLastError() errno
85 #endif
86
87 #ifndef IPPORT_USERRESERVED
88 #define IPPORT_USERRESERVED 5000
89 #endif
90
91 #ifdef __WIN32__
92 typedef int socklen_t;
93 #endif
94
95 //
96 // NETWORKING
97 //
98
99 static u_short DOOMPORT = (IPPORT_USERRESERVED + 29);
100 static SOCKET mysocket = INVALID_SOCKET;
101 static sockaddr_in sendaddress[MAXNETNODES];
102 static BYTE sendplayer[MAXNETNODES];
103
104 #ifdef __WIN32__
105 const char *neterror (void);
106 #else
107 #define neterror() strerror(errno)
108 #endif
109
110 enum
111 {
112 PRE_CONNECT, // Sent from guest to host for initial connection
113 PRE_KEEPALIVE,
114 PRE_DISCONNECT, // Sent from guest that aborts the game
115 PRE_ALLHERE, // Sent from host to guest when everybody has connected
116 PRE_CONACK, // Sent from host to guest to acknowledge PRE_CONNECT receipt
117 PRE_ALLFULL, // Sent from host to an unwanted guest
118 PRE_ALLHEREACK, // Sent from guest to host to acknowledge PRE_ALLHEREACK receipt
119 PRE_GO // Sent from host to guest to continue game startup
120 };
121
122 // Set PreGamePacket.fake to this so that the game rejects any pregame packets
123 // after it starts. This translates to NCMD_SETUP|NCMD_MULTI.
124 #define PRE_FAKE 0x30
125
126 struct PreGamePacket
127 {
128 BYTE Fake;
129 BYTE Message;
130 BYTE NumNodes;
131 union
132 {
133 BYTE ConsoleNum;
134 BYTE NumPresent;
135 };
136 struct
137 {
138 DWORD address;
139 WORD port;
140 BYTE player;
141 BYTE pad;
142 } machines[MAXNETNODES];
143 };
144
145 BYTE TransmitBuffer[TRANSMIT_SIZE];
146
147 //
148 // UDPsocket
149 //
UDPsocket(void)150 SOCKET UDPsocket (void)
151 {
152 SOCKET s;
153
154 // allocate a socket
155 s = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP);
156 if (s == INVALID_SOCKET)
157 I_FatalError ("can't create socket: %s", neterror ());
158
159 return s;
160 }
161
162 //
163 // BindToLocalPort
164 //
BindToLocalPort(SOCKET s,u_short port)165 void BindToLocalPort (SOCKET s, u_short port)
166 {
167 int v;
168 sockaddr_in address;
169
170 memset (&address, 0, sizeof(address));
171 address.sin_family = AF_INET;
172 address.sin_addr.s_addr = INADDR_ANY;
173 address.sin_port = htons(port);
174
175 v = bind (s, (sockaddr *)&address, sizeof(address));
176 if (v == SOCKET_ERROR)
177 I_FatalError ("BindToPort: %s", neterror ());
178 }
179
FindNode(const sockaddr_in * address)180 int FindNode (const sockaddr_in *address)
181 {
182 int i;
183
184 // find remote node number
185 for (i = 0; i<doomcom.numnodes; i++)
186 if (address->sin_addr.s_addr == sendaddress[i].sin_addr.s_addr
187 && address->sin_port == sendaddress[i].sin_port)
188 break;
189
190 if (i == doomcom.numnodes)
191 {
192 // packet is not from one of the players (new game broadcast?)
193 i = -1;
194 }
195 return i;
196 }
197
198 //
199 // PacketSend
200 //
PacketSend(void)201 void PacketSend (void)
202 {
203 int c;
204
205 // FIXME: Catch this before we've overflown the buffer. With long chat
206 // text and lots of backup tics, it could conceivably happen. (Though
207 // apparently it hasn't yet, which is good.)
208 if (doomcom.datalength > MAX_MSGLEN)
209 {
210 I_FatalError("Netbuffer overflow!");
211 }
212 assert(!(doomcom.data[0] & NCMD_COMPRESSED));
213
214 uLong size = TRANSMIT_SIZE - 1;
215 if (doomcom.datalength >= 10)
216 {
217 TransmitBuffer[0] = doomcom.data[0] | NCMD_COMPRESSED;
218 c = compress2(TransmitBuffer + 1, &size, doomcom.data + 1, doomcom.datalength - 1, 9);
219 size += 1;
220 }
221 else
222 {
223 c = -1; // Just some random error code to avoid sending the compressed buffer.
224 }
225 if (c == Z_OK && size < (uLong)doomcom.datalength)
226 {
227 // Printf("send %lu/%d\n", size, doomcom.datalength);
228 c = sendto(mysocket, (char *)TransmitBuffer, size,
229 0, (sockaddr *)&sendaddress[doomcom.remotenode],
230 sizeof(sendaddress[doomcom.remotenode]));
231 }
232 else
233 {
234 if (doomcom.datalength > TRANSMIT_SIZE)
235 {
236 I_Error("Net compression failed (zlib error %d)", c);
237 }
238 else
239 {
240 // Printf("send %d\n", doomcom.datalength);
241 c = sendto(mysocket, (char *)doomcom.data, doomcom.datalength,
242 0, (sockaddr *)&sendaddress[doomcom.remotenode],
243 sizeof(sendaddress[doomcom.remotenode]));
244 }
245 }
246 // if (c == -1)
247 // I_Error ("SendPacket error: %s",strerror(errno));
248 }
249
250
251 //
252 // PacketGet
253 //
PacketGet(void)254 void PacketGet (void)
255 {
256 int c;
257 socklen_t fromlen;
258 sockaddr_in fromaddress;
259 int node;
260
261 fromlen = sizeof(fromaddress);
262 c = recvfrom (mysocket, (char*)TransmitBuffer, TRANSMIT_SIZE, 0,
263 (sockaddr *)&fromaddress, &fromlen);
264 node = FindNode (&fromaddress);
265
266 if (node >= 0 && c == SOCKET_ERROR)
267 {
268 int err = WSAGetLastError();
269
270 if (err == WSAECONNRESET)
271 { // The remote node aborted unexpectedly, so pretend it sent an exit packet
272
273 if (StartScreen != NULL)
274 {
275 StartScreen->NetMessage ("The connection from %s was dropped.\n",
276 players[sendplayer[node]].userinfo.GetName());
277 }
278 else
279 {
280 Printf("The connection from %s was dropped.\n",
281 players[sendplayer[node]].userinfo.GetName());
282 }
283
284 doomcom.data[0] = 0x80; // NCMD_EXIT
285 c = 1;
286 }
287 else if (err != WSAEWOULDBLOCK)
288 {
289 I_Error ("GetPacket: %s", neterror ());
290 }
291 else
292 {
293 doomcom.remotenode = -1; // no packet
294 return;
295 }
296 }
297 else if (node >= 0 && c > 0)
298 {
299 doomcom.data[0] = TransmitBuffer[0] & ~NCMD_COMPRESSED;
300 if (TransmitBuffer[0] & NCMD_COMPRESSED)
301 {
302 uLongf msgsize = MAX_MSGLEN - 1;
303 int err = uncompress(doomcom.data + 1, &msgsize, TransmitBuffer + 1, c - 1);
304 // Printf("recv %d/%lu\n", c, msgsize + 1);
305 if (err != Z_OK)
306 {
307 Printf("Net decompression failed (zlib error %s)\n", M_ZLibError(err).GetChars());
308 // Pretend no packet
309 doomcom.remotenode = -1;
310 return;
311 }
312 c = msgsize + 1;
313 }
314 else
315 {
316 // Printf("recv %d\n", c);
317 memcpy(doomcom.data + 1, TransmitBuffer + 1, c - 1);
318 }
319 }
320 else if (c > 0)
321 { //The packet is not from any in-game node, so we might as well discard it.
322 // Don't show the message for disconnect notifications.
323 if (c != 2 || TransmitBuffer[0] != PRE_FAKE || TransmitBuffer[1] != PRE_DISCONNECT)
324 {
325 DPrintf("Dropped packet: Unknown host (%s:%d)\n", inet_ntoa(fromaddress.sin_addr), fromaddress.sin_port);
326 }
327 doomcom.remotenode = -1;
328 return;
329 }
330
331 doomcom.remotenode = node;
332 doomcom.datalength = (short)c;
333 }
334
PreGet(void * buffer,int bufferlen,bool noabort)335 sockaddr_in *PreGet (void *buffer, int bufferlen, bool noabort)
336 {
337 static sockaddr_in fromaddress;
338 socklen_t fromlen;
339 int c;
340
341 fromlen = sizeof(fromaddress);
342 c = recvfrom (mysocket, (char *)buffer, bufferlen, 0,
343 (sockaddr *)&fromaddress, &fromlen);
344
345 if (c == SOCKET_ERROR)
346 {
347 int err = WSAGetLastError();
348 if (err == WSAEWOULDBLOCK || (noabort && err == WSAECONNRESET))
349 return NULL; // no packet
350 I_Error ("PreGet: %s", neterror ());
351 }
352 return &fromaddress;
353 }
354
PreSend(const void * buffer,int bufferlen,const sockaddr_in * to)355 void PreSend (const void *buffer, int bufferlen, const sockaddr_in *to)
356 {
357 sendto (mysocket, (const char *)buffer, bufferlen, 0, (const sockaddr *)to, sizeof(*to));
358 }
359
BuildAddress(sockaddr_in * address,const char * name)360 void BuildAddress (sockaddr_in *address, const char *name)
361 {
362 hostent *hostentry; // host information entry
363 u_short port;
364 const char *portpart;
365 bool isnamed = false;
366 int curchar;
367 char c;
368 FString target;
369
370 address->sin_family = AF_INET;
371
372 if ( (portpart = strchr (name, ':')) )
373 {
374 target = FString(name, portpart - name);
375 port = atoi (portpart + 1);
376 if (!port)
377 {
378 Printf ("Weird port: %s (using %d)\n", portpart + 1, DOOMPORT);
379 port = DOOMPORT;
380 }
381 }
382 else
383 {
384 target = name;
385 port = DOOMPORT;
386 }
387 address->sin_port = htons(port);
388
389 for (curchar = 0; (c = target[curchar]) ; curchar++)
390 {
391 if ((c < '0' || c > '9') && c != '.')
392 {
393 isnamed = true;
394 break;
395 }
396 }
397
398 if (!isnamed)
399 {
400 address->sin_addr.s_addr = inet_addr (target);
401 Printf ("Node number %d, address %s\n", doomcom.numnodes, target.GetChars());
402 }
403 else
404 {
405 hostentry = gethostbyname (target);
406 if (!hostentry)
407 I_FatalError ("gethostbyname: couldn't find %s\n%s", target.GetChars(), neterror());
408 address->sin_addr.s_addr = *(int *)hostentry->h_addr_list[0];
409 Printf ("Node number %d, hostname %s\n",
410 doomcom.numnodes, hostentry->h_name);
411 }
412 }
413
CloseNetwork(void)414 void CloseNetwork (void)
415 {
416 if (mysocket != INVALID_SOCKET)
417 {
418 closesocket (mysocket);
419 mysocket = INVALID_SOCKET;
420 }
421 #ifdef __WIN32__
422 WSACleanup ();
423 #endif
424 }
425
StartNetwork(bool autoPort)426 void StartNetwork (bool autoPort)
427 {
428 u_long trueval = 1;
429 #ifdef __WIN32__
430 WSADATA wsad;
431
432 if (WSAStartup (0x0101, &wsad))
433 {
434 I_FatalError ("Could not initialize Windows Sockets");
435 }
436 #endif
437
438 atterm (CloseNetwork);
439
440 netgame = true;
441 multiplayer = true;
442
443 // create communication socket
444 mysocket = UDPsocket ();
445 BindToLocalPort (mysocket, autoPort ? 0 : DOOMPORT);
446 #ifndef __sun
447 ioctlsocket (mysocket, FIONBIO, &trueval);
448 #else
449 fcntl(mysocket, F_SETFL, trueval | O_NONBLOCK);
450 #endif
451 }
452
SendAbort(void)453 void SendAbort (void)
454 {
455 BYTE dis[2] = { PRE_FAKE, PRE_DISCONNECT };
456 int i, j;
457
458 if (doomcom.numnodes > 1)
459 {
460 if (consoleplayer == 0)
461 {
462 // The host needs to let everyone know
463 for (i = 1; i < doomcom.numnodes; ++i)
464 {
465 for (j = 4; j > 0; --j)
466 {
467 PreSend (dis, 2, &sendaddress[i]);
468 }
469 }
470 }
471 else
472 {
473 // Guests only need to let the host know.
474 for (i = 4; i > 0; --i)
475 {
476 PreSend (dis, 2, &sendaddress[1]);
477 }
478 }
479 }
480 }
481
SendConAck(int num_connected,int num_needed)482 static void SendConAck (int num_connected, int num_needed)
483 {
484 PreGamePacket packet;
485
486 packet.Fake = PRE_FAKE;
487 packet.Message = PRE_CONACK;
488 packet.NumNodes = num_needed;
489 packet.NumPresent = num_connected;
490 for (int node = 1; node < doomcom.numnodes; ++node)
491 {
492 PreSend (&packet, 4, &sendaddress[node]);
493 }
494 StartScreen->NetProgress (doomcom.numnodes);
495 }
496
Host_CheckForConnects(void * userdata)497 bool Host_CheckForConnects (void *userdata)
498 {
499 PreGamePacket packet;
500 int numplayers = (int)(intptr_t)userdata;
501 sockaddr_in *from;
502 int node;
503
504 while ( (from = PreGet (&packet, sizeof(packet), false)) )
505 {
506 if (packet.Fake != PRE_FAKE)
507 {
508 continue;
509 }
510 switch (packet.Message)
511 {
512 case PRE_CONNECT:
513 node = FindNode (from);
514 if (doomcom.numnodes == numplayers)
515 {
516 if (node == -1)
517 {
518 const BYTE *s_addr_bytes = (const BYTE *)&from->sin_addr;
519 StartScreen->NetMessage ("Got extra connect from %d.%d.%d.%d:%d",
520 s_addr_bytes[0], s_addr_bytes[1], s_addr_bytes[2], s_addr_bytes[3],
521 from->sin_port);
522 packet.Message = PRE_ALLFULL;
523 PreSend (&packet, 2, from);
524 }
525 }
526 else
527 {
528 if (node == -1)
529 {
530 node = doomcom.numnodes++;
531 sendaddress[node] = *from;
532 StartScreen->NetMessage ("Got connect from node %d.", node);
533 }
534
535 // Let the new guest (and everyone else) know we got their message.
536 SendConAck (doomcom.numnodes, numplayers);
537 }
538 break;
539
540 case PRE_DISCONNECT:
541 node = FindNode (from);
542 if (node >= 0)
543 {
544 StartScreen->NetMessage ("Got disconnect from node %d.", node);
545 doomcom.numnodes--;
546 while (node < doomcom.numnodes)
547 {
548 sendaddress[node] = sendaddress[node+1];
549 node++;
550 }
551
552 // Let remaining guests know that somebody left.
553 SendConAck (doomcom.numnodes, numplayers);
554 }
555 break;
556
557 case PRE_KEEPALIVE:
558 break;
559 }
560 }
561 if (doomcom.numnodes < numplayers)
562 {
563 // Send message to everyone as a keepalive
564 SendConAck(doomcom.numnodes, numplayers);
565 return false;
566 }
567
568 // It's possible somebody bailed out after all players were found.
569 // Unfortunately, this isn't guaranteed to catch all of them.
570 // Oh well. Better than nothing.
571 while ( (from = PreGet (&packet, sizeof(packet), false)) )
572 {
573 if (packet.Fake == PRE_FAKE && packet.Message == PRE_DISCONNECT)
574 {
575 node = FindNode (from);
576 if (node >= 0)
577 {
578 doomcom.numnodes--;
579 while (node < doomcom.numnodes)
580 {
581 sendaddress[node] = sendaddress[node+1];
582 node++;
583 }
584 // Let remaining guests know that somebody left.
585 SendConAck (doomcom.numnodes, numplayers);
586 }
587 break;
588 }
589 }
590 return doomcom.numnodes >= numplayers;
591 }
592
Host_SendAllHere(void * userdata)593 bool Host_SendAllHere (void *userdata)
594 {
595 int *gotack = (int *)userdata; // ackcount is at gotack[MAXNETNODES]
596 PreGamePacket packet;
597 int node;
598 sockaddr_in *from;
599
600 // Send out address information to all guests. Guests that have already
601 // acknowledged receipt effectively get just a heartbeat packet.
602 packet.Fake = PRE_FAKE;
603 packet.Message = PRE_ALLHERE;
604 for (node = 1; node < doomcom.numnodes; node++)
605 {
606 int machine, spot = 0;
607
608 packet.ConsoleNum = node;
609 if (!gotack[node])
610 {
611 for (spot = 0, machine = 1; machine < doomcom.numnodes; machine++)
612 {
613 if (node != machine)
614 {
615 packet.machines[spot].address = sendaddress[machine].sin_addr.s_addr;
616 packet.machines[spot].port = sendaddress[machine].sin_port;
617 packet.machines[spot].player = node;
618
619 spot++; // fixes problem of new address replacing existing address in
620 // array; it's supposed to increment the index before getting
621 // and storing in the packet the next address.
622 }
623 }
624 packet.NumNodes = doomcom.numnodes - 2;
625 }
626 else
627 {
628 packet.NumNodes = 0;
629 }
630 PreSend (&packet, 4 + spot*8, &sendaddress[node]);
631 }
632
633 // Check for replies.
634 while ( (from = PreGet (&packet, sizeof(packet), false)) )
635 {
636 if (packet.Fake == PRE_FAKE && packet.Message == PRE_ALLHEREACK)
637 {
638 node = FindNode (from);
639 if (node >= 0)
640 {
641 if (!gotack[node])
642 {
643 gotack[node] = true;
644 gotack[MAXNETNODES]++;
645 }
646 }
647 PreSend (&packet, 2, from);
648 }
649 }
650
651 // If everybody has replied, then this loop can end.
652 return gotack[MAXNETNODES] == doomcom.numnodes - 1;
653 }
654
HostGame(int i)655 void HostGame (int i)
656 {
657 PreGamePacket packet;
658 int numplayers;
659 int node;
660 int gotack[MAXNETNODES+1];
661
662 if ((i == Args->NumArgs() - 1) || !(numplayers = atoi (Args->GetArg(i+1))))
663 { // No player count specified, assume 2
664 numplayers = 2;
665 }
666
667 if (numplayers > MAXNETNODES)
668 {
669 I_FatalError("You cannot host a game with %d players. The limit is currently %d.", numplayers, MAXNETNODES);
670 return;
671 }
672
673 if (numplayers == 1)
674 { // Special case: Only 1 player, so don't bother starting the network
675 netgame = false;
676 multiplayer = true;
677 doomcom.id = DOOMCOM_ID;
678 doomcom.numplayers = doomcom.numnodes = 1;
679 doomcom.consoleplayer = 0;
680 return;
681 }
682
683 StartNetwork (false);
684
685 // [JC] - this computer is starting the game, therefore it should
686 // be the Net Arbitrator.
687 doomcom.consoleplayer = 0;
688 Printf ("Console player number: %d\n", doomcom.consoleplayer);
689
690 doomcom.numnodes = 1;
691
692 atterm (SendAbort);
693
694 StartScreen->NetInit ("Waiting for players", numplayers);
695
696 // Wait for numplayers-1 different connections
697 if (!StartScreen->NetLoop (Host_CheckForConnects, (void *)(intptr_t)numplayers))
698 {
699 exit (0);
700 }
701
702 // Now inform everyone of all machines involved in the game
703 memset (gotack, 0, sizeof(gotack));
704 StartScreen->NetMessage ("Sending all here.");
705 StartScreen->NetInit ("Done waiting", 1);
706
707 if (!StartScreen->NetLoop (Host_SendAllHere, (void *)gotack))
708 {
709 exit (0);
710 }
711
712 popterm ();
713
714 // Now go
715 StartScreen->NetMessage ("Go");
716 packet.Fake = PRE_FAKE;
717 packet.Message = PRE_GO;
718 for (node = 1; node < doomcom.numnodes; node++)
719 {
720 // If we send the packets eight times to each guest,
721 // hopefully at least one of them will get through.
722 for (int i = 8; i != 0; --i)
723 {
724 PreSend (&packet, 2, &sendaddress[node]);
725 }
726 }
727
728 StartScreen->NetMessage ("Total players: %d", doomcom.numnodes);
729
730 doomcom.id = DOOMCOM_ID;
731 doomcom.numplayers = doomcom.numnodes;
732
733 // On the host, each player's number is the same as its node number
734 for (i = 0; i < doomcom.numnodes; ++i)
735 {
736 sendplayer[i] = i;
737 }
738 }
739
740 // This routine is used by a guest to notify the host of its presence.
741 // Once that host acknowledges receipt of the notification, this routine
742 // is never called again.
743
Guest_ContactHost(void * userdata)744 bool Guest_ContactHost (void *userdata)
745 {
746 sockaddr_in *from;
747 PreGamePacket packet;
748
749 // Let the host know we are here.
750 packet.Fake = PRE_FAKE;
751 packet.Message = PRE_CONNECT;
752 PreSend (&packet, 2, &sendaddress[1]);
753
754 // Listen for a reply.
755 while ( (from = PreGet (&packet, sizeof(packet), true)) )
756 {
757 if (packet.Fake == PRE_FAKE && FindNode(from) == 1)
758 {
759 if (packet.Message == PRE_CONACK)
760 {
761 StartScreen->NetMessage ("Total players: %d", packet.NumNodes);
762 StartScreen->NetInit ("Waiting for other players", packet.NumNodes);
763 StartScreen->NetProgress (packet.NumPresent);
764 return true;
765 }
766 else if (packet.Message == PRE_DISCONNECT)
767 {
768 doomcom.numnodes = 0;
769 I_FatalError ("The host cancelled the game.");
770 }
771 else if (packet.Message == PRE_ALLFULL)
772 {
773 doomcom.numnodes = 0;
774 I_FatalError ("The game is full.");
775 }
776 }
777 }
778
779 // In case the progress bar could not be marqueed, bump it.
780 StartScreen->NetProgress (0);
781
782 return false;
783 }
784
Guest_WaitForOthers(void * userdata)785 bool Guest_WaitForOthers (void *userdata)
786 {
787 sockaddr_in *from;
788 PreGamePacket packet;
789
790 while ( (from = PreGet (&packet, sizeof(packet), false)) )
791 {
792 if (packet.Fake != PRE_FAKE || FindNode(from) != 1)
793 {
794 continue;
795 }
796 switch (packet.Message)
797 {
798 case PRE_CONACK:
799 StartScreen->NetProgress (packet.NumPresent);
800 break;
801
802 case PRE_ALLHERE:
803 if (doomcom.numnodes == 2)
804 {
805 int node;
806
807 doomcom.numnodes = packet.NumNodes + 2;
808 sendplayer[0] = packet.ConsoleNum; // My player number
809 doomcom.consoleplayer = packet.ConsoleNum;
810 StartScreen->NetMessage ("Console player number: %d", doomcom.consoleplayer);
811 for (node = 0; node < packet.NumNodes; node++)
812 {
813 sendaddress[node+2].sin_addr.s_addr = packet.machines[node].address;
814 sendaddress[node+2].sin_port = packet.machines[node].port;
815 sendplayer[node+2] = packet.machines[node].player;
816
817 // [JC] - fixes problem of games not starting due to
818 // no address family being assigned to nodes stored in
819 // sendaddress[] from the All Here packet.
820 sendaddress[node+2].sin_family = AF_INET;
821 }
822 }
823
824 StartScreen->NetMessage ("Received All Here, sending ACK.");
825 packet.Fake = PRE_FAKE;
826 packet.Message = PRE_ALLHEREACK;
827 PreSend (&packet, 2, &sendaddress[1]);
828 break;
829
830 case PRE_GO:
831 StartScreen->NetMessage ("Received \"Go.\"");
832 return true;
833
834 case PRE_DISCONNECT:
835 I_FatalError ("The host cancelled the game.");
836 break;
837 }
838 }
839
840 packet.Fake = PRE_FAKE;
841 packet.Message = PRE_KEEPALIVE;
842 PreSend(&packet, 2, &sendaddress[1]);
843
844 return false;
845 }
846
JoinGame(int i)847 void JoinGame (int i)
848 {
849 if ((i == Args->NumArgs() - 1) ||
850 (Args->GetArg(i+1)[0] == '-') ||
851 (Args->GetArg(i+1)[0] == '+'))
852 I_FatalError ("You need to specify the host machine's address");
853
854 StartNetwork (true);
855
856 // Host is always node 1
857 BuildAddress (&sendaddress[1], Args->GetArg(i+1));
858 sendplayer[1] = 0;
859 doomcom.numnodes = 2;
860
861 atterm (SendAbort);
862
863 // Let host know we are here
864 StartScreen->NetInit ("Contacting host", 0);
865
866 if (!StartScreen->NetLoop (Guest_ContactHost, NULL))
867 {
868 exit (0);
869 }
870
871 // Wait for everyone else to connect
872 if (!StartScreen->NetLoop (Guest_WaitForOthers, 0))
873 {
874 exit (0);
875 }
876
877 popterm ();
878
879 StartScreen->NetMessage ("Total players: %d", doomcom.numnodes);
880
881 doomcom.id = DOOMCOM_ID;
882 doomcom.numplayers = doomcom.numnodes;
883 }
884
PrivateNetOf(in_addr in)885 static int PrivateNetOf(in_addr in)
886 {
887 int addr = ntohl(in.s_addr);
888 if ((addr & 0xFFFF0000) == 0xC0A80000) // 192.168.0.0
889 {
890 return 0xC0A80000;
891 }
892 else if ((addr & 0xFFF00000) == 0xAC100000) // 172.16.0.0
893 {
894 return 0xAC100000;
895 }
896 else if ((addr & 0xFF000000) == 0x0A000000) // 10.0.0.0
897 {
898 return 0x0A000000;
899 }
900 else if ((addr & 0xFF000000) == 0x7F000000) // 127.0.0.0 (localhost)
901 {
902 return 0x7F000000;
903 }
904 // Not a private IP
905 return 0;
906 }
907
908 //
909 // NodesOnSameNetwork
910 //
911 // The best I can really do here is check if the others are on the same
912 // private network, since that means we (probably) are too.
913 //
914
NodesOnSameNetwork()915 static bool NodesOnSameNetwork()
916 {
917 int net1;
918
919 net1 = PrivateNetOf(sendaddress[1].sin_addr);
920 // Printf("net1 = %08x\n", net1);
921 if (net1 == 0)
922 {
923 return false;
924 }
925 for (int i = 2; i < doomcom.numnodes; ++i)
926 {
927 int net = PrivateNetOf(sendaddress[i].sin_addr);
928 // Printf("Net[%d] = %08x\n", i, net);
929 if (net != net1)
930 {
931 return false;
932 }
933 }
934 return true;
935 }
936
937 //
938 // I_InitNetwork
939 //
940 // Returns true if packet server mode might be a good idea.
941 //
I_InitNetwork(void)942 bool I_InitNetwork (void)
943 {
944 int i;
945 const char *v;
946
947 memset (&doomcom, 0, sizeof(doomcom));
948
949 // set up for network
950 v = Args->CheckValue ("-dup");
951 if (v)
952 {
953 doomcom.ticdup = clamp (atoi (v), 1, MAXTICDUP);
954 }
955 else
956 {
957 doomcom.ticdup = 1;
958 }
959
960 v = Args->CheckValue ("-port");
961 if (v)
962 {
963 DOOMPORT = atoi (v);
964 Printf ("using alternate port %i\n", DOOMPORT);
965 }
966
967 // parse network game options,
968 // player 1: -host <numplayers>
969 // player x: -join <player 1's address>
970 if ( (i = Args->CheckParm ("-host")) )
971 {
972 HostGame (i);
973 }
974 else if ( (i = Args->CheckParm ("-join")) )
975 {
976 JoinGame (i);
977 }
978 else
979 {
980 // single player game
981 netgame = false;
982 multiplayer = false;
983 doomcom.id = DOOMCOM_ID;
984 doomcom.numplayers = doomcom.numnodes = 1;
985 doomcom.consoleplayer = 0;
986 return false;
987 }
988 if (doomcom.numnodes < 3)
989 { // Packet server mode with only two players is effectively the same as
990 // peer-to-peer but with some slightly larger packets.
991 return false;
992 }
993 return doomcom.numnodes > 3 || !NodesOnSameNetwork();
994 }
995
996
I_NetCmd(void)997 void I_NetCmd (void)
998 {
999 if (doomcom.command == CMD_SEND)
1000 {
1001 PacketSend ();
1002 }
1003 else if (doomcom.command == CMD_GET)
1004 {
1005 PacketGet ();
1006 }
1007 else
1008 I_Error ("Bad net cmd: %i\n",doomcom.command);
1009 }
1010
1011 #ifdef __WIN32__
neterror(void)1012 const char *neterror (void)
1013 {
1014 static char neterr[16];
1015 int code;
1016
1017 switch (code = WSAGetLastError ()) {
1018 case WSAEACCES: return "EACCES";
1019 case WSAEADDRINUSE: return "EADDRINUSE";
1020 case WSAEADDRNOTAVAIL: return "EADDRNOTAVAIL";
1021 case WSAEAFNOSUPPORT: return "EAFNOSUPPORT";
1022 case WSAEALREADY: return "EALREADY";
1023 case WSAECONNABORTED: return "ECONNABORTED";
1024 case WSAECONNREFUSED: return "ECONNREFUSED";
1025 case WSAECONNRESET: return "ECONNRESET";
1026 case WSAEDESTADDRREQ: return "EDESTADDRREQ";
1027 case WSAEFAULT: return "EFAULT";
1028 case WSAEHOSTDOWN: return "EHOSTDOWN";
1029 case WSAEHOSTUNREACH: return "EHOSTUNREACH";
1030 case WSAEINPROGRESS: return "EINPROGRESS";
1031 case WSAEINTR: return "EINTR";
1032 case WSAEINVAL: return "EINVAL";
1033 case WSAEISCONN: return "EISCONN";
1034 case WSAEMFILE: return "EMFILE";
1035 case WSAEMSGSIZE: return "EMSGSIZE";
1036 case WSAENETDOWN: return "ENETDOWN";
1037 case WSAENETRESET: return "ENETRESET";
1038 case WSAENETUNREACH: return "ENETUNREACH";
1039 case WSAENOBUFS: return "ENOBUFS";
1040 case WSAENOPROTOOPT: return "ENOPROTOOPT";
1041 case WSAENOTCONN: return "ENOTCONN";
1042 case WSAENOTSOCK: return "ENOTSOCK";
1043 case WSAEOPNOTSUPP: return "EOPNOTSUPP";
1044 case WSAEPFNOSUPPORT: return "EPFNOSUPPORT";
1045 case WSAEPROCLIM: return "EPROCLIM";
1046 case WSAEPROTONOSUPPORT: return "EPROTONOSUPPORT";
1047 case WSAEPROTOTYPE: return "EPROTOTYPE";
1048 case WSAESHUTDOWN: return "ESHUTDOWN";
1049 case WSAESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT";
1050 case WSAETIMEDOUT: return "ETIMEDOUT";
1051 case WSAEWOULDBLOCK: return "EWOULDBLOCK";
1052 case WSAHOST_NOT_FOUND: return "HOST_NOT_FOUND";
1053 case WSANOTINITIALISED: return "NOTINITIALISED";
1054 case WSANO_DATA: return "NO_DATA";
1055 case WSANO_RECOVERY: return "NO_RECOVERY";
1056 case WSASYSNOTREADY: return "SYSNOTREADY";
1057 case WSATRY_AGAIN: return "TRY_AGAIN";
1058 case WSAVERNOTSUPPORTED: return "VERNOTSUPPORTED";
1059 case WSAEDISCON: return "EDISCON";
1060
1061 default:
1062 mysnprintf (neterr, countof(neterr), "%d", code);
1063 return neterr;
1064 }
1065 }
1066 #endif
1067