1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 
21 /* FIXME - just for the htons() call below */
22 #if defined(_WIN32)
23 #ifdef _XBOX
24 #include <xtl.h>
25 #else
26 #include <winsock2.h>
27 #endif
28 #elif defined(MSB_FIRST)
29 // Is already big-endian
30 #ifndef htons
31 #define htons(x) (x)
32 #define ntohs(x) (x)
33 #endif
34 #elif defined(VITA) || defined(_3DS)
35 #else
36 #include <arpa/inet.h>
37 #endif
38 
39 #include "cmd.h"
40 #include "console.h"
41 #include "net.h"
42 #include "quakedef.h"
43 #include "server.h"
44 #include "sys.h"
45 #include "zone.h"
46 
47 
48 #ifdef MSB_FIRST
49 
50 #ifndef htons
51 #define htons(A) (A)
52 #endif
53 
54 #ifndef htonl
55 #define htonl(A) (A)
56 #endif
57 
58 #ifndef ntohs
59 #define ntohs(A) (A)
60 #endif
61 
62 #ifndef ntohl
63 #define ntohl(A) (A)
64 #endif
65 
66 #else
67 
68 #ifndef htons
69 #define htons(A) ((((uint16_t)(A) & 0xff00) >> 8) | \
70 (((uint16_t)(A) & 0x00ff) << 8))
71 #endif
72 
73 #ifndef htonl
74 #define htonl(A) ((((uint32_t)(A) & 0xff000000) >> 24) | \
75 (((uint32_t)(A) & 0x00ff0000) >> 8) | \
76 (((uint32_t)(A) & 0x0000ff00) << 8) | \
77 (((uint32_t)(A) & 0x000000ff) << 24))
78 #endif
79 
80 #ifndef ntohs
81 #define ntohs htons
82 #endif
83 
84 #ifndef ntohl
85 #define ntohl htohl
86 #endif
87 #endif
88 
89 qsocket_t *net_activeSockets = NULL;
90 qsocket_t *net_freeSockets = NULL;
91 static int net_numsockets = 0;
92 
93 qboolean tcpipAvailable = false;
94 
95 int net_hostport;
96 int DEFAULTnet_hostport = 26000;
97 
98 char my_tcpip_address[NET_NAMELEN];
99 
100 static qboolean listening = false;
101 
102 qboolean slistInProgress = false;
103 qboolean slistSilent = false;
104 qboolean slistLocal = true;
105 #define IS_LOOP_DRIVER(p) ((p) == &net_drivers[0])
106 
107 static double slistStartTime;
108 static int slistLastShown;
109 
110 static void Slist_Send(void);
111 static void Slist_Poll(void);
112 static PollProcedure slistSendProcedure = { NULL, 0.0, Slist_Send };
113 static PollProcedure slistPollProcedure = { NULL, 0.0, Slist_Poll };
114 
115 sizebuf_t net_message;
116 int net_activeconnections = 0;
117 
118 int messagesSent = 0;
119 int messagesReceived = 0;
120 int unreliableMessagesSent = 0;
121 int unreliableMessagesReceived = 0;
122 
123 cvar_t net_messagetimeout = { "net_messagetimeout", "300" };
124 cvar_t hostname = { "hostname", "UNNAMED" };
125 
126 net_driver_t *net_driver;
127 double net_time;
128 
129 
130 const char *
NET_AdrToString(const netadr_t * a)131 NET_AdrToString(const netadr_t *a)
132 {
133     static char s[64];
134     const byte *b = a->ip.b;
135 
136     sprintf(s, "%i.%i.%i.%i:%i", b[0], b[1], b[2], b[3], ntohs(a->port));
137 
138     return s;
139 }
140 
141 
142 double
SetNetTime(void)143 SetNetTime(void)
144 {
145     net_time = Sys_DoubleTime();
146     return net_time;
147 }
148 
149 
150 /*
151  * ===================
152  * NET_NewQSocket
153  *
154  * Called by drivers when a new communications endpoint is required
155  * The sequence and buffer fields will be filled in properly
156  * ===================
157  */
158 qsocket_t *
NET_NewQSocket(void)159 NET_NewQSocket(void)
160 {
161     qsocket_t *sock;
162 
163     if (!net_freeSockets)
164 	return NULL;
165 
166     if (net_activeconnections >= svs.maxclients)
167 	return NULL;
168 
169     /* get one from free list */
170     sock = net_freeSockets;
171     net_freeSockets = sock->next;
172 
173     /* add it to active list */
174     sock->next = net_activeSockets;
175     net_activeSockets = sock;
176 
177     sock->disconnected = false;
178     sock->connecttime = net_time;
179     strcpy(sock->address, "UNSET ADDRESS");
180     sock->driver = net_driver;
181     sock->socket = 0;
182     sock->driverdata = NULL;
183     sock->canSend = true;
184     sock->sendNext = false;
185     sock->lastMessageTime = net_time;
186     sock->ackSequence = 0;
187     sock->sendSequence = 0;
188     sock->unreliableSendSequence = 0;
189     sock->sendMessageLength = 0;
190     sock->receiveSequence = 0;
191     sock->unreliableReceiveSequence = 0;
192     sock->receiveMessageLength = 0;
193 
194     return sock;
195 }
196 
197 
198 void
NET_FreeQSocket(qsocket_t * sock)199 NET_FreeQSocket(qsocket_t *sock)
200 {
201     qsocket_t *s;
202 
203     /* remove it from active list */
204     if (sock == net_activeSockets)
205 	net_activeSockets = net_activeSockets->next;
206     else {
207 	for (s = net_activeSockets; s; s = s->next)
208 	    if (s->next == sock) {
209 		s->next = sock->next;
210 		break;
211 	    }
212 	if (!s)
213 	    Sys_Error("%s: not active", __func__);
214     }
215 
216     /* add it to free list */
217     sock->next = net_freeSockets;
218     net_freeSockets = sock;
219     sock->disconnected = true;
220 }
221 
222 
223 static void
NET_Listen_f(void)224 NET_Listen_f(void)
225 {
226     int i;
227 
228     if (Cmd_Argc() != 2) {
229 	Con_Printf("\"listen\" is \"%u\"\n", listening ? 1 : 0);
230 	return;
231     }
232 
233     listening = Q_atoi(Cmd_Argv(1)) ? true : false;
234 
235     for (i = 0; i < net_numdrivers;i++) {
236 	net_driver = &net_drivers[i];
237 	if (net_driver->initialized == false)
238 	    continue;
239 	net_driver->Listen(listening);
240     }
241 }
242 
243 
244 static void
MaxPlayers_f(void)245 MaxPlayers_f(void)
246 {
247     int n;
248 
249     if (Cmd_Argc() != 2) {
250 	Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients);
251 	return;
252     }
253 
254     if (sv.active) {
255 	Con_Printf
256 	    ("maxplayers can not be changed while a server is running.\n");
257 	return;
258     }
259 
260     n = Q_atoi(Cmd_Argv(1));
261     if (n < 1)
262 	n = 1;
263     if (n > svs.maxclientslimit) {
264 	n = svs.maxclientslimit;
265 	Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
266     }
267 
268     if ((n == 1) && listening)
269 	Cbuf_AddText("listen 0\n");
270 
271     if ((n > 1) && (!listening))
272 	Cbuf_AddText("listen 1\n");
273 
274     svs.maxclients = n;
275     if (n == 1) {
276 	Cvar_Set("deathmatch", "0");
277 	Cvar_Set("coop", "0");
278     } else {
279 	if (coop.value)
280 	    Cvar_Set("deathmatch", "0");
281 	else
282 	    Cvar_Set("deathmatch", "1");
283     }
284 }
285 
286 
287 static void
NET_Port_f(void)288 NET_Port_f(void)
289 {
290     int n;
291 
292     if (Cmd_Argc() != 2) {
293 	Con_Printf("\"port\" is \"%u\"\n", net_hostport);
294 	return;
295     }
296 
297     n = Q_atoi(Cmd_Argv(1));
298     if (n < 1 || n > 65534) {
299 	Con_Printf("Bad value, must be between 1 and 65534\n");
300 	return;
301     }
302 
303     DEFAULTnet_hostport = n;
304     net_hostport = n;
305 
306     if (listening) {
307 	/* force a change to the new port */
308 	Cbuf_AddText("listen 0\n");
309 	Cbuf_AddText("listen 1\n");
310     }
311 }
312 
313 
314 static void
PrintSlistHeader(void)315 PrintSlistHeader(void)
316 {
317     Con_Printf("Server          Map             Users\n");
318     Con_Printf("--------------- --------------- -----\n");
319     slistLastShown = 0;
320 }
321 
322 
323 static void
PrintSlist(void)324 PrintSlist(void)
325 {
326     int n;
327 
328     for (n = slistLastShown; n < hostCacheCount; n++) {
329 	if (hostcache[n].maxusers)
330 	    Con_Printf("%-15.15s %-15.15s %2u/%2u\n", hostcache[n].name,
331 		       hostcache[n].map, hostcache[n].users,
332 		       hostcache[n].maxusers);
333 	else
334 	    Con_Printf("%-15.15s %-15.15s\n", hostcache[n].name,
335 		       hostcache[n].map);
336     }
337     slistLastShown = n;
338 }
339 
340 
341 static void
PrintSlistTrailer(void)342 PrintSlistTrailer(void)
343 {
344     if (hostCacheCount)
345 	Con_Printf("== end list ==\n\n");
346     else
347 	Con_Printf("No Quake servers found.\n\n");
348 }
349 
350 
351 void
NET_Slist_f(void)352 NET_Slist_f(void)
353 {
354     if (slistInProgress)
355 	return;
356 
357     if (!slistSilent) {
358 	Con_Printf("Looking for Quake servers...\n");
359 	PrintSlistHeader();
360     }
361 
362     slistInProgress = true;
363     slistStartTime = Sys_DoubleTime();
364 
365     SchedulePollProcedure(&slistSendProcedure, 0.0);
366     SchedulePollProcedure(&slistPollProcedure, 0.1);
367 
368     hostCacheCount = 0;
369 }
370 
371 
372 static void
Slist_Send(void)373 Slist_Send(void)
374 {
375     int i;
376 
377     for (i = 0; i < net_numdrivers; i++) {
378 	net_driver = &net_drivers[i];
379 
380 	/* Only list the loop driver if slistLocal is true */
381 	if (!slistLocal && IS_LOOP_DRIVER(net_driver))
382 	    continue;
383 	if (net_driver->initialized == false)
384 	    continue;
385 	net_driver->SearchForHosts(true);
386     }
387 
388     if ((Sys_DoubleTime() - slistStartTime) < 0.5)
389 	SchedulePollProcedure(&slistSendProcedure, 0.75);
390 }
391 
392 
393 static void
Slist_Poll(void)394 Slist_Poll(void)
395 {
396     int i;
397 
398     for (i = 0; i < net_numdrivers; i++) {
399 	net_driver = &net_drivers[i];
400 
401 	/* Only list the loop driver if slistLocal is true */
402 	if (!slistLocal && IS_LOOP_DRIVER(net_driver))
403 	    continue;
404 	if (net_driver->initialized == false)
405 	    continue;
406 	net_driver->SearchForHosts(false);
407     }
408 
409     if (!slistSilent)
410 	PrintSlist();
411 
412     if ((Sys_DoubleTime() - slistStartTime) < 1.5) {
413 	SchedulePollProcedure(&slistPollProcedure, 0.1);
414 	return;
415     }
416 
417     if (!slistSilent)
418 	PrintSlistTrailer();
419     slistInProgress = false;
420     slistSilent = false;
421     slistLocal = true;
422 }
423 
424 
425 /*
426  * ===================
427  * NET_Connect
428  * ===================
429  */
430 int hostCacheCount = 0;
431 hostcache_t hostcache[HOSTCACHESIZE];
432 
433 qsocket_t *
NET_Connect(const char * host)434 NET_Connect(const char *host)
435 {
436     qsocket_t *ret;
437     int i, n;
438     int numdrivers = net_numdrivers;
439 
440 	printf("Attempting to connect to %s\n", host);
441 
442     SetNetTime();
443 
444     if (host && *host == 0)
445 	host = NULL;
446 
447     if (host) {
448 	if (strcasecmp(host, "local") == 0) {
449 	    numdrivers = 1;
450 	    goto JustDoIt;
451 	}
452 
453 	if (hostCacheCount) {
454 	    for (n = 0; n < hostCacheCount; n++)
455 		if (strcasecmp(host, hostcache[n].name) == 0) {
456 		    host = hostcache[n].cname;
457 		    break;
458 		}
459 	    if (n < hostCacheCount)
460 		goto JustDoIt;
461 	}
462     }
463 
464     slistSilent = host ? true : false;
465     NET_Slist_f();
466 
467     while (slistInProgress)
468 	NET_Poll();
469 
470     if (host == NULL) {
471 	if (hostCacheCount != 1)
472 	    return NULL;
473 	host = hostcache[0].cname;
474 	Con_Printf("Connecting to...\n%s @ %s\n\n", hostcache[0].name, host);
475     }
476 
477     if (hostCacheCount)
478 	for (n = 0; n < hostCacheCount; n++)
479 	    if (strcasecmp(host, hostcache[n].name) == 0) {
480 		host = hostcache[n].cname;
481 		break;
482 	    }
483 
484   JustDoIt:
485     for (i = 0; i < numdrivers; i++) {
486 	net_driver = &net_drivers[i];
487 	if (net_driver->initialized == false)
488 	    continue;
489 	ret = net_driver->Connect(host);
490 	if (ret)
491 	    return ret;
492     }
493 
494     if (host) {
495 	Con_Printf("\n");
496 	PrintSlistHeader();
497 	PrintSlist();
498 	PrintSlistTrailer();
499     }
500 
501     return NULL;
502 }
503 
504 
505 /*
506  * ===================
507  * NET_CheckNewConnections
508  * ===================
509  */
510 qsocket_t *
NET_CheckNewConnections(void)511 NET_CheckNewConnections(void)
512 {
513     int i;
514     qsocket_t *ret = NULL;
515 
516     SetNetTime();
517 
518     for (i = 0; i < net_numdrivers; i++) {
519 	net_driver = &net_drivers[i];
520 	if (net_driver->initialized == false)
521 	    continue;
522 	if (!IS_LOOP_DRIVER(net_driver) && listening == false)
523 	    continue;
524 	ret = net_driver->CheckNewConnections();
525 	if (ret)
526 	    break;
527     }
528 
529     return ret;
530 }
531 
532 /*
533  * ===================
534  * NET_Close
535  * ===================
536  */
537 void
NET_Close(qsocket_t * sock)538 NET_Close(qsocket_t *sock)
539 {
540     if (!sock)
541 	return;
542 
543     if (sock->disconnected)
544 	return;
545 
546     SetNetTime();
547 
548     /* call the driver_Close function */
549     sock->driver->Close(sock);
550 
551     NET_FreeQSocket(sock);
552 }
553 
554 
555 /*
556  * =================
557  * NET_GetMessage
558  *
559  * If there is a complete message, return it in net_message
560  *
561  * returns 0 if no data is waiting
562  * returns 1 if a message was received
563  * returns -1 if connection is invalid
564  * =================
565  */
566 int
NET_GetMessage(qsocket_t * sock)567 NET_GetMessage(qsocket_t *sock)
568 {
569     int ret;
570 
571     if (!sock)
572 	return -1;
573 
574     if (sock->disconnected) {
575 	Con_Printf("%s: disconnected socket\n", __func__);
576 	return -1;
577     }
578 
579     SetNetTime();
580 
581     ret = sock->driver->QGetMessage(sock);
582 
583     /* see if this connection has timed out (not for loop) */
584     if (ret == 0 && (!IS_LOOP_DRIVER(sock->driver))) {
585 	if (net_time - sock->lastMessageTime > net_messagetimeout.value) {
586 	    NET_Close(sock);
587 	    return -1;
588 	}
589     }
590 
591     if (ret > 0) {
592 	if (!IS_LOOP_DRIVER(sock->driver)) {
593 	    sock->lastMessageTime = net_time;
594 	    if (ret == 1)
595 		messagesReceived++;
596 	    else if (ret == 2)
597 		unreliableMessagesReceived++;
598 	}
599     }
600 
601     return ret;
602 }
603 
604 
605 /*
606  * ==================
607  * NET_SendMessage
608  *
609  * Try to send a complete length+message unit over the reliable stream.
610  * returns  0 : if the message cannot be delivered reliably, but the connection
611  *	        is still considered valid
612  * returns  1 : if the message was sent properly
613  * returns -1 : if the connection died
614  * ==================
615  */
616 int
NET_SendMessage(qsocket_t * sock,sizebuf_t * data)617 NET_SendMessage(qsocket_t *sock, sizebuf_t *data)
618 {
619     int r;
620 
621     if (!sock)
622 	return -1;
623 
624     if (sock->disconnected) {
625 	Con_Printf("%s: disconnected socket\n", __func__);
626 	return -1;
627     }
628 
629     SetNetTime();
630     r = sock->driver->QSendMessage(sock, data);
631     if (r == 1 && !IS_LOOP_DRIVER(sock->driver))
632 	messagesSent++;
633 
634     return r;
635 }
636 
637 
638 int
NET_SendUnreliableMessage(qsocket_t * sock,sizebuf_t * data)639 NET_SendUnreliableMessage(qsocket_t *sock, sizebuf_t *data)
640 {
641     int r;
642 
643     if (!sock)
644 	return -1;
645 
646     if (sock->disconnected) {
647 	Con_Printf("NET_SendMessage: disconnected socket\n");
648 	return -1;
649     }
650 
651     SetNetTime();
652     r = sock->driver->SendUnreliableMessage(sock, data);
653     if (r == 1 && !IS_LOOP_DRIVER(sock->driver))
654 	unreliableMessagesSent++;
655 
656     return r;
657 }
658 
659 
660 /*
661  * ==================
662  * NET_CanSendMessage
663  *
664  * Returns true or false if the given qsocket can currently accept a
665  * message to be transmitted.
666  * ==================
667  */
668 qboolean
NET_CanSendMessage(qsocket_t * sock)669 NET_CanSendMessage(qsocket_t *sock)
670 {
671     int r;
672 
673     if (!sock)
674 	return false;
675 
676     if (sock->disconnected)
677 	return false;
678 
679     SetNetTime();
680 
681     r = sock->driver->CanSendMessage(sock);
682 
683     return r;
684 }
685 
686 
687 int
NET_SendToAll(sizebuf_t * data,double blocktime)688 NET_SendToAll(sizebuf_t *data, double blocktime)
689 {
690     double start;
691     int i;
692     int count = 0;
693     qboolean msg_init[MAX_SCOREBOARD]; /* data written */
694     qboolean msg_sent[MAX_SCOREBOARD]; /* send completed */
695 
696     for (i = 0, host_client = svs.clients; i < svs.maxclients;
697 	 i++, host_client++) {
698 	if (host_client->netconnection && host_client->active) {
699 	    /*
700 	     * Loopback driver guarantees delivery, skip checks
701 	     */
702 	    if (IS_LOOP_DRIVER(host_client->netconnection->driver)) {
703 		NET_SendMessage(host_client->netconnection, data);
704 		msg_init[i] = true;
705 		msg_sent[i] = true;
706 		continue;
707 	    }
708 	    count++;
709 	    msg_init[i] = false;
710 	    msg_sent[i] = false;
711 	} else {
712 	    msg_init[i] = true;
713 	    msg_sent[i] = true;
714 	}
715     }
716 
717     start = Sys_DoubleTime();
718     while (count) {
719 	count = 0;
720 	for (i = 0, host_client = svs.clients; i < svs.maxclients;
721 	     i++, host_client++) {
722 	    if (!msg_init[i]) {
723 		if (NET_CanSendMessage(host_client->netconnection)) {
724 		    msg_init[i] = true;
725 		    NET_SendMessage(host_client->netconnection, data);
726 		} else {
727 		    NET_GetMessage(host_client->netconnection);
728 		}
729 		count++;
730 		continue;
731 	    }
732 
733 	    if (!msg_sent[i]) {
734 		if (NET_CanSendMessage(host_client->netconnection)) {
735 		    msg_sent[i] = true;
736 		} else {
737 		    NET_GetMessage(host_client->netconnection);
738 		}
739 		count++;
740 		continue;
741 	    }
742 	}
743 	if ((Sys_DoubleTime() - start) > blocktime)
744 	    break;
745     }
746     return count;
747 }
748 
749 
750 /*
751  * ====================
752  * NET_Init
753  * ====================
754  */
755 void
NET_Init(void)756 NET_Init(void)
757 {
758     int i, num_inited;
759     int controlSocket;
760     qsocket_t *s;
761 
762     i = COM_CheckParm("-port");
763     if (!i)
764 	i = COM_CheckParm("-udpport");
765 
766     if (i) {
767 	if (i < com_argc - 1)
768 	    DEFAULTnet_hostport = Q_atoi(com_argv[i + 1]);
769 	else
770 	    Sys_Error("%s: you must specify a number after -port", __func__);
771     }
772     net_hostport = DEFAULTnet_hostport;
773 
774     if (COM_CheckParm("-listen") || cls.state == ca_dedicated)
775 	listening = true;
776     net_numsockets = svs.maxclientslimit;
777     if (cls.state != ca_dedicated)
778 	net_numsockets++;
779 
780     SetNetTime();
781 
782     for (i = 0; i < net_numsockets; i++) {
783 	s = (qsocket_t *)Hunk_AllocName(sizeof(qsocket_t), "qsocket");
784 	s->next = net_freeSockets;
785 	net_freeSockets = s;
786 	s->disconnected = true;
787     }
788 
789     /* allocate space for network message buffer */
790     SZ_Alloc(&net_message, NET_MAXMESSAGE);
791 
792     Cvar_RegisterVariable(&net_messagetimeout);
793     Cvar_RegisterVariable(&hostname);
794 
795     Cmd_AddCommand("slist", NET_Slist_f);
796     Cmd_AddCommand("listen", NET_Listen_f);
797     Cmd_AddCommand("maxplayers", MaxPlayers_f);
798     Cmd_AddCommand("port", NET_Port_f);
799 
800     /* initialize all the drivers */
801     num_inited = 0;
802     for (i = 0; i < net_numdrivers; i++) {
803 	net_driver = &net_drivers[i];
804 	controlSocket = net_driver->Init();
805 	if (controlSocket == -1)
806 	    continue;
807 	num_inited++;
808 	net_driver->initialized = true;
809 	net_driver->controlSock = controlSocket;
810 	if (listening)
811 	    net_driver->Listen(true);
812     }
813 
814     if (num_inited == 0 && cls.state == ca_dedicated)
815 	Sys_Error("Network not available!");
816 
817     if (*my_tcpip_address)
818 	Con_DPrintf("TCP/IP address %s\n", my_tcpip_address);
819 }
820 
821 /*
822  * ====================
823  * NET_Shutdown
824  * ====================
825  */
826 void
NET_Shutdown(void)827 NET_Shutdown(void)
828 {
829     int i;
830     qsocket_t *sock;
831 
832     SetNetTime();
833 
834     for (sock = net_activeSockets; sock; sock = sock->next)
835 	NET_Close(sock);
836 
837     /*
838      * shutdown the drivers
839      */
840     for (i = 0; i < net_numdrivers; i++) {
841 	net_driver = &net_drivers[i];
842 	if (net_driver->initialized == true) {
843 	    net_driver->Shutdown();
844 	    net_driver->initialized = false;
845 	}
846     }
847 }
848 
849 
850 static PollProcedure *pollProcedureList = NULL;
851 
852 void
NET_Poll(void)853 NET_Poll(void)
854 {
855     PollProcedure *pp;
856 
857     SetNetTime();
858 
859     /*
860      * FIXME - A procedure could schedule itself to the head of the list, but
861      *         wouldn't be executed until next frame/tic; problem?
862      */
863     for (pp = pollProcedureList; pp; pp = pp->next) {
864 	if (pp->nextTime > net_time)
865 	    break;
866 	pollProcedureList = pp->next;
867 #ifdef _WIN32
868 //not sure what is going on here - params is void in NQ/net.h?
869     pp->procedure();
870 #else
871 	pp->procedure(pp->arg);
872 #endif
873     }
874 }
875 
876 
877 void
SchedulePollProcedure(PollProcedure * proc,double timeOffset)878 SchedulePollProcedure(PollProcedure *proc, double timeOffset)
879 {
880     PollProcedure *pp, *prev;
881 
882     proc->nextTime = Sys_DoubleTime() + timeOffset;
883     for (pp = pollProcedureList, prev = NULL; pp; pp = pp->next) {
884 	if (pp->nextTime >= proc->nextTime)
885 	    break;
886 	prev = pp;
887     }
888 
889     if (prev == NULL) {
890 	proc->next = pollProcedureList;
891 	pollProcedureList = proc;
892 	return;
893     }
894 
895     proc->next = pp;
896     prev->next = proc;
897 }
898