Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
3
 * It is copyright by its individual contributors, as recorded in the
4
 * project's Git history.  See COPYING.txt at the top level for license
5
 * terms and a link to the Git history.
6
 */
7
/*
8
 *
9
 * Routines for managing UDP-protocol network play.
10
 *
11
 */
12
 
13
#include <stdio.h>
14
#include <string.h>
15
#include <stdlib.h>
16
#include <random>
17
 
18
#include "pstypes.h"
19
#include "window.h"
20
#include "strutil.h"
21
#include "args.h"
22
#include "timer.h"
23
#include "newmenu.h"
24
#include "key.h"
25
#include "gauges.h"
26
#include "object.h"
27
#include "dxxerror.h"
28
#include "laser.h"
29
#include "gamesave.h"
30
#include "gamemine.h"
31
#include "player.h"
32
#include "gameseq.h"
33
#include "fireball.h"
34
#include "net_udp.h"
35
#include "game.h"
36
#include "multi.h"
37
#include "endlevel.h"
38
#include "palette.h"
39
#include "cntrlcen.h"
40
#include "powerup.h"
41
#include "menu.h"
42
#include "gameseg.h"
43
#include "sounds.h"
44
#include "text.h"
45
#include "kmatrix.h"
46
#include "newdemo.h"
47
#include "multibot.h"
48
#include "state.h"
49
#include "wall.h"
50
#include "bm.h"
51
#include "effects.h"
52
#include "physics.h"
53
#include "hudmsg.h"
54
#include "switch.h"
55
#include "textures.h"
56
#include "automap.h"
57
#include "event.h"
58
#include "playsave.h"
59
#include "gamefont.h"
60
#include "rbaudio.h"
61
#include "config.h"
62
#include "vers_id.h"
63
#include "u_mem.h"
64
 
65
#include "dxxsconf.h"
66
#include "compiler-cf_assert.h"
67
#include "compiler-range_for.h"
68
#include "d_range.h"
69
#include "partial_range.h"
70
#include <array>
71
#include <utility>
72
 
73
#if defined(DXX_BUILD_DESCENT_I)
74
#define UDP_REQ_ID "D1XR" // ID string for a request packet
75
#elif defined(DXX_BUILD_DESCENT_II)
76
#define UDP_REQ_ID "D2XR" // ID string for a request packet
77
#endif
78
 
79
namespace {
80
 
81
// player position packet structure
82
struct UDP_frame_info : prohibit_void_ptr<UDP_frame_info>
83
{
84
        ubyte                           type;
85
        ubyte                           Player_num;
86
        ubyte                           connected;
87
        quaternionpos                   qpp;
88
};
89
 
90
enum class join_netgame_status_code : unsigned
91
{
92
        game_in_disallowed_state,
93
        game_has_capacity,
94
        game_is_full,
95
        game_refuses_players,
96
};
97
 
98
}
99
 
100
// Prototypes
101
static void net_udp_init();
102
static void net_udp_close();
103
static void net_udp_listen();
104
namespace dsx {
105
static int net_udp_show_game_info();
106
static int net_udp_do_join_game();
107
}
108
static void net_udp_flush();
109
namespace dsx {
110
static void net_udp_update_netgame(void);
111
static void net_udp_send_objects(void);
112
static void net_udp_send_rejoin_sync(unsigned player_num);
113
static void net_udp_do_refuse_stuff (UDP_sequence_packet *their);
114
static void net_udp_read_sync_packet(const uint8_t *data, uint_fast32_t data_len, const _sockaddr &sender_addr);
115
}
116
static void net_udp_ping_frame(fix64 time);
117
static void net_udp_process_ping(const uint8_t *data, const _sockaddr &sender_addr);
118
static void net_udp_process_pong(const uint8_t *data, const _sockaddr &sender_addr);
119
static void net_udp_read_endlevel_packet(const uint8_t *data, const _sockaddr &sender_addr);
120
static void net_udp_send_mdata(int needack, fix64 time);
121
static void net_udp_process_mdata (uint8_t *data, uint_fast32_t data_len, const _sockaddr &sender_addr, int needack);
122
static void net_udp_send_pdata();
123
static void net_udp_process_pdata (const uint8_t *data, uint_fast32_t data_len, const _sockaddr &sender_addr);
124
static void net_udp_read_pdata_packet(UDP_frame_info *pd);
125
static void net_udp_timeout_check(fix64 time);
126
static int net_udp_get_new_player_num ();
127
static void net_udp_noloss_got_ack(const uint8_t *data, uint_fast32_t data_len);
128
static void net_udp_noloss_init_mdata_queue(void);
129
static void net_udp_noloss_clear_mdata_trace(ubyte player_num);
130
static void net_udp_noloss_process_queue(fix64 time);
131
namespace dsx {
132
static void net_udp_send_extras ();
133
}
134
static void net_udp_broadcast_game_info(ubyte info_upid);
135
namespace dsx {
136
static void net_udp_process_game_info(const uint8_t *data, uint_fast32_t data_len, const _sockaddr &game_addr, int lite_info, uint16_t TrackerGameID = 0);
137
}
138
static int net_udp_start_game(void);
139
 
140
// Variables
141
static int UDP_num_sendto, UDP_len_sendto, UDP_num_recvfrom, UDP_len_recvfrom;
142
static UDP_mdata_info           UDP_MData;
143
static UDP_sequence_packet UDP_Seq;
144
static unsigned UDP_mdata_queue_highest;
145
static std::array<UDP_mdata_store, UDP_MDATA_STOR_QUEUE_SIZE> UDP_mdata_queue;
146
static std::array<UDP_mdata_check, MAX_PLAYERS> UDP_mdata_trace;
147
static UDP_sequence_packet UDP_sync_player; // For rejoin object syncing
148
static std::array<UDP_netgame_info_lite, UDP_MAX_NETGAMES> Active_udp_games;
149
static unsigned num_active_udp_games;
150
static int num_active_udp_changed;
151
static uint16_t UDP_MyPort;
152
static sockaddr_in GBcast; // global Broadcast address clients and hosts will use for lite_info exchange over LAN
153
#define UDP_BCAST_ADDR "255.255.255.255"
154
#if DXX_USE_IPv6
155
#define UDP_MCASTv6_ADDR "ff02::1"
156
static sockaddr_in6 GMcast_v6; // same for IPv6-only
157
#define dispatch_sockaddr_from  from.sin6
158
#else
159
#define dispatch_sockaddr_from  from.sin
160
#endif
161
#if DXX_USE_TRACKER
162
static _sockaddr TrackerSocket;
163
enum class TrackerAckState : uint8_t
164
{
165
        TACK_NOCONNECTION,   // No connection with tracker (yet);
166
        TACK_INTERNAL   = 1, // Got ACK on TrackerSocket
167
        TACK_EXTERNAL   = 2, // Got ACK on our game sopcket
168
        TACK_SEQCOMPL   = 3, // We had enough time to get all acks. If we missed something now, tell the user
169
};
170
static TrackerAckState TrackerAckStatus;
171
static fix64 TrackerAckTime;
172
static int udp_tracker_init();
173
static int udp_tracker_unregister();
174
namespace dsx {
175
static int udp_tracker_register();
176
static int udp_tracker_reqgames();
177
}
178
static int udp_tracker_process_game( ubyte *data, int data_len, const _sockaddr &sender_addr );
179
static void udp_tracker_process_ack( ubyte *data, int data_len, const _sockaddr &sender_addr );
180
static void udp_tracker_verify_ack_timeout();
181
static void udp_tracker_request_holepunch( uint16_t TrackerGameID );
182
static void udp_tracker_process_holepunch(uint8_t *data, unsigned data_len, const _sockaddr &sender_addr );
183
#endif
184
 
185
static fix64 StartAbortMenuTime;
186
 
187
#ifndef _WIN32
188
constexpr std::integral_constant<int, -1> INVALID_SOCKET{};
189
#endif
190
 
191
namespace dcx {
192
 
193
namespace {
194
 
195
constexpr std::integral_constant<uint32_t, 0xfffffffe> network_checksum_marker_object{};
196
 
197
class RAIIsocket
198
{
199
#ifndef _WIN32
200
        typedef int SOCKET;
201
        int closesocket(SOCKET fd)
202
        {
203
                return close(fd);
204
        }
205
#endif
206
        SOCKET s = INVALID_SOCKET;
207
public:
208
        constexpr RAIIsocket() = default;
209
        RAIIsocket(int domain, int type, int protocol) : s(socket(domain, type, protocol))
210
        {
211
        }
212
        RAIIsocket(const RAIIsocket &) = delete;
213
        RAIIsocket &operator=(const RAIIsocket &) = delete;
214
        RAIIsocket &operator=(RAIIsocket &&) = delete;
215
        ~RAIIsocket()
216
        {
217
                reset();
218
        }
219
        /* This should be a move-assignment operator=, but early versions of
220
         * gcc-4.9 mishandle synthesizing std::array<T, N>::operator=(array &&)
221
         * when the contained type is movable but not copyable.  Debian
222
         * Jessie's newest gcc is still affected (18 months after the fix
223
         * was published upstream), so use awkward syntax here to avoid the
224
         * problem.
225
         *
226
         * https://github.com/dxx-rebirth/dxx-rebirth/issues/289
227
         * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66501
228
         */
229
        void move(RAIIsocket &&r)
230
        {
231
                std::swap(s, r.s);
232
        }
233
        void reset()
234
        {
235
                if (s != INVALID_SOCKET)
236
                        closesocket(std::exchange(s, INVALID_SOCKET));
237
        }
238
        explicit operator bool() const { return s != INVALID_SOCKET; }
239
        explicit operator bool() { return static_cast<bool>(*const_cast<const RAIIsocket *>(this)); }
240
        operator SOCKET() { return s; }
241
        template <typename T> bool operator<(T) const = delete;
242
        template <typename T> bool operator<=(T) const = delete;
243
        template <typename T> bool operator>(T) const = delete;
244
        template <typename T> bool operator>=(T) const = delete;
245
        template <typename T> bool operator==(T) const = delete;
246
        template <typename T> bool operator!=(T) const = delete;
247
};
248
 
249
#ifdef DXX_HAVE_GETADDRINFO
250
class RAIIaddrinfo
251
{
252
        struct deleter
253
        {
254
                void operator()(addrinfo *p) const
255
                {
256
                        freeaddrinfo(p);
257
                }
258
        };
259
        std::unique_ptr<addrinfo, deleter> result;
260
public:
261
        int getaddrinfo(const char *node, const char *service, const addrinfo *hints)
262
        {
263
                addrinfo *p = nullptr;
264
                int r = ::getaddrinfo(node, service, hints, &p);
265
                result.reset(p);
266
                return r;
267
        }
268
        addrinfo *get() { return result.get(); }
269
        addrinfo *operator->() { return result.operator->(); }
270
};
271
#endif
272
 
273
class start_poll_menu_items
274
{
275
        /* The host must play */
276
        unsigned playercount = 1;
277
public:
278
        std::array<newmenu_item, MAX_PLAYERS + 4> m;
279
        unsigned get_player_count() const
280
        {
281
                return playercount;
282
        }
283
        void set_player_count(const unsigned c)
284
        {
285
                playercount = c;
286
        }
287
};
288
 
289
static const char *dxx_ntop(const _sockaddr &sa, std::array<char, _sockaddr::presentation_buffer_size> &dbuf)
290
{
291
#ifdef WIN32
292
#ifdef DXX_HAVE_INET_NTOP
293
        /*
294
         * Windows and inet_ntop: copy the in_addr/in6_addr to local
295
         * variables because the Microsoft prototype lacks a const
296
         * qualifier.
297
         */
298
        union {
299
                in_addr ia;
300
#if DXX_USE_IPv6
301
                in6_addr ia6;
302
#endif
303
        };
304
        const auto addr =
305
#if DXX_USE_IPv6
306
                (sa.sa.sa_family == AF_INET6)
307
                ? &(ia6 = sa.sin6.sin6_addr)
308
                :
309
#endif
310
                static_cast<void *>(&(ia = sa.sin.sin_addr));
311
#else
312
        /*
313
         * Windows and not inet_ntop: only inet_ntoa available.
314
         *
315
         * SConf check_inet_ntop_present enforces that Windows without
316
         * inet_ntop cannot enable IPv6, so the IPv4 branch must be correct
317
         * here.
318
         *
319
         * The reverse is not true.  Windows with inet_ntop might not enable
320
         * IPv6.
321
         */
322
#if DXX_USE_IPv6
323
#error "IPv6 requires inet_ntop; SConf should prevent this path"
324
#endif
325
        dbuf.back() = 0;
326
        /*
327
         * Copy the formatted string to the local buffer `dbuf` to guard
328
         * against concurrent uses of `dxx_ntop`.
329
         */
330
        return reinterpret_cast<const char *>(memcpy(dbuf.data(), inet_ntoa(sa.sin.sin_addr), dbuf.size() - 1));
331
#endif
332
#else
333
        /*
334
         * Not Windows; assume inet_ntop present.  Non-Windows platforms
335
         * declare inet_ntop with a const qualifier, so take a pointer to
336
         * the underlying data.
337
         */
338
        const auto addr =
339
#if DXX_USE_IPv6
340
                (sa.sa.sa_family == AF_INET6)
341
                ? &sa.sin6.sin6_addr
342
                :
343
#endif
344
                static_cast<const void *>(&sa.sin.sin_addr);
345
#endif
346
#if !defined(WIN32) || defined(DXX_HAVE_INET_NTOP)
347
        if (const auto r = inet_ntop(sa.sa.sa_family, addr, dbuf.data(), dbuf.size()))
348
                return r;
349
        return "address";
350
#endif
351
}
352
 
353
uint8_t get_effective_netgame_status(const d_level_unique_control_center_state &LevelUniqueControlCenterState)
354
{
355
        if (Network_status == NETSTAT_ENDLEVEL)
356
                return NETSTAT_ENDLEVEL;
357
        if (LevelUniqueControlCenterState.Control_center_destroyed)
358
                return NETSTAT_ENDLEVEL;
359
        if (Netgame.PlayTimeAllowed.count())
360
        {
361
                const auto TicksPlayTimeRemaining = Netgame.PlayTimeAllowed - ThisLevelTime;
362
                if (TicksPlayTimeRemaining.count() < i2f(30))
363
                        return NETSTAT_ENDLEVEL;
364
        }
365
        return Netgame.game_status;
366
}
367
 
368
}
369
 
370
}
371
 
372
static std::array<RAIIsocket, 2> UDP_Socket;
373
 
374
static void clear_UDP_Socket()
375
{
376
        /* This would be simply `UDP_Socket = {}`, but the contained type
377
         * has a deleted move-assignment operator= to compensate for a
378
         * gcc-4.9 bug.  See the comment in RAIIsocket for details.
379
         */
380
        range_for (auto &i, UDP_Socket)
381
                i.reset();
382
}
383
 
384
static bool operator==(const _sockaddr &l, const _sockaddr &r)
385
{
386
        return !memcmp(&l, &r, sizeof(l));
387
}
388
 
389
static bool operator!=(const _sockaddr &l, const _sockaddr &r)
390
{
391
        return !(l == r);
392
}
393
 
394
template <std::size_t N>
395
static void copy_from_ntstring(uint8_t *const buf, uint_fast32_t &len, const ntstring<N> &in)
396
{
397
        len += in.copy_out(0, reinterpret_cast<char *>(&buf[len]), N);
398
}
399
 
400
template <std::size_t N>
401
static void copy_to_ntstring(const uint8_t *const buf, uint_fast32_t &len, ntstring<N> &out)
402
{
403
        uint_fast32_t c = out.copy_if(reinterpret_cast<const char *>(&buf[len]), N);
404
        if (c < N)
405
                ++ c;
406
        len += c;
407
}
408
 
409
static void net_udp_prepare_request_game_info(std::array<uint8_t, UPID_GAME_INFO_REQ_SIZE> &buf, int lite)
410
{
411
        buf[0] = lite ? UPID_GAME_INFO_LITE_REQ : UPID_GAME_INFO_REQ;
412
        memcpy(&buf[1], UDP_REQ_ID, 4);
413
        PUT_INTEL_SHORT(&buf[5], DXX_VERSION_MAJORi);
414
        PUT_INTEL_SHORT(&buf[7], DXX_VERSION_MINORi);
415
        PUT_INTEL_SHORT(&buf[9], DXX_VERSION_MICROi);
416
        PUT_INTEL_SHORT(&buf[11], MULTI_PROTO_VERSION);
417
}
418
 
419
static void reset_UDP_MyPort()
420
{
421
        UDP_MyPort = CGameArg.MplUdpMyPort >= 1024 ? CGameArg.MplUdpMyPort : UDP_PORT_DEFAULT;
422
}
423
 
424
static bool convert_text_portstring(const std::array<char, 6> &portstring, uint16_t &outport, bool allow_privileged, bool silent)
425
{
426
        char *porterror;
427
        unsigned long myport = strtoul(&portstring[0], &porterror, 10);
428
        if (*porterror || static_cast<uint16_t>(myport) != myport || (!allow_privileged && myport < 1024))
429
        {
430
                if (!silent)
431
                        nm_messagebox(TXT_ERROR, 1, TXT_OK, "Illegal port \"%s\"", portstring.data());
432
                return false;
433
        }
434
        else
435
                outport = myport;
436
        return true;
437
}
438
 
439
namespace {
440
 
441
#if DXX_USE_IPv6
442
/* Returns true if kernel allows specifying sizeof(sockaddr_in6) for
443
 * size of a sockaddr_in.  Saves a compare+jump in application code to
444
 * pass sizeof(sockaddr_in6) and let kernel sort it out.
445
 */
446
static constexpr bool kernel_accepts_extra_sockaddr_bytes()
447
{
448
#if defined(__linux__)
449
        /* Known to work */
450
        return true;
451
#else
452
        /* Default case: not known */
453
        return false;
454
#endif
455
}
456
#endif
457
 
458
        /* Forward to static function to eliminate this pointer */
459
template <typename F>
460
class passthrough_static_apply : F
461
{
462
public:
463
#define apply_passthrough()     this->F::apply(std::forward<Args>(args)...)
464
        template <typename... Args>
465
        __attribute_always_inline()
466
                auto operator()(Args &&... args) const
467
                {
468
                        return apply_passthrough();
469
                }
470
#undef apply_passthrough
471
};
472
 
473
template <typename F>
474
class sockaddr_dispatch_t : F
475
{
476
public:
477
#define apply_sockaddr()        this->F::operator()(reinterpret_cast<sockaddr &>(from), fromlen, std::forward<Args>(args)...)
478
        template <typename... Args>
479
                auto operator()(sockaddr_in &from, socklen_t &fromlen, Args &&... args) const
480
                {
481
                        fromlen = sizeof(from);
482
                        return apply_sockaddr();
483
                }
484
#if DXX_USE_IPv6
485
        template <typename... Args>
486
                auto operator()(sockaddr_in6 &from, socklen_t &fromlen, Args &&... args) const
487
                {
488
                        fromlen = sizeof(from);
489
                        return apply_sockaddr();
490
                }
491
#endif
492
        template <typename... Args>
493
                __attribute_always_inline()
494
                auto operator()(_sockaddr &from, socklen_t &fromlen, Args &&... args) const
495
                {
496
                        return this->sockaddr_dispatch_t<F>::operator()<Args...>(dispatch_sockaddr_from, fromlen, std::forward<Args>(args)...);
497
                }
498
#undef apply_sockaddr
499
};
500
 
501
template <typename F>
502
class csockaddr_dispatch_t : F
503
{
504
public:
505
#define apply_sockaddr()        this->F::operator()(to, tolen, std::forward<Args>(args)...)
506
        template <typename... Args>
507
                auto operator()(const sockaddr &to, socklen_t tolen, Args &&... args) const
508
                {
509
                        return apply_sockaddr();
510
                }
511
#undef apply_sockaddr
512
#define apply_sockaddr(to)      this->F::operator()(reinterpret_cast<const sockaddr &>(to), sizeof(to), std::forward<Args>(args)...)
513
        template <typename... Args>
514
                auto operator()(const sockaddr_in &to, Args &&... args) const
515
                {
516
                        return apply_sockaddr(to);
517
                }
518
#if DXX_USE_IPv6
519
        template <typename... Args>
520
                auto operator()(const sockaddr_in6 &to, Args &&... args) const
521
                {
522
                        return apply_sockaddr(to);
523
                }
524
#endif
525
        template <typename... Args>
526
                auto operator()(const _sockaddr &to, Args &&... args) const
527
                {
528
#if DXX_USE_IPv6
529
                        if (kernel_accepts_extra_sockaddr_bytes() || to.sin6.sin6_family == AF_INET6)
530
                                return apply_sockaddr(to.sin6);
531
#endif
532
                        return apply_sockaddr(to.sin);
533
                }
534
#undef apply_sockaddr
535
};
536
 
537
template <typename F>
538
class socket_array_dispatch_t : F
539
{
540
public:
541
#define apply_array(B,L)        this->F::operator()(to, tolen, sock, B, L, std::forward<Args>(args)...)
542
        template <typename T, typename... Args>
543
                auto operator()(const sockaddr &to, socklen_t tolen, int sock, T *buf, uint_fast32_t buflen, Args &&... args) const
544
                {
545
                        return apply_array(buf, buflen);
546
                }
547
        template <std::size_t N, typename... Args>
548
                auto operator()(const sockaddr &to, socklen_t tolen, int sock, std::array<uint8_t, N> &buf, Args &&... args) const
549
                {
550
                        return apply_array(buf.data(), buf.size());
551
                }
552
#undef apply_array
553
};
554
 
555
/* General UDP functions - START */
556
class dxx_sendto_t
557
{
558
public:
559
        __attribute_always_inline()
560
        ssize_t operator()(const sockaddr &to, socklen_t tolen, int sockfd, const void *msg, size_t len, int flags) const
561
        {
562
                /* Fix argument order */
563
                return apply(sockfd, msg, len, flags, to, tolen);
564
        }
565
        static ssize_t apply(int sockfd, const void *msg, size_t len, int flags, const sockaddr &to, socklen_t tolen);
566
};
567
 
568
class dxx_recvfrom_t
569
{
570
public:
571
        __attribute_always_inline()
572
        ssize_t operator()(sockaddr &from, socklen_t &fromlen, int sockfd, void *msg, size_t len, int flags) const
573
        {
574
                /* Fix argument order */
575
                return apply(sockfd, msg, len, flags, from, fromlen);
576
        }
577
        static ssize_t apply(int sockfd, void *msg, size_t len, int flags, sockaddr &from, socklen_t &fromlen);
578
};
579
 
580
ssize_t dxx_sendto_t::apply(int sockfd, const void *msg, size_t len, int flags, const sockaddr &to, socklen_t tolen)
581
{
582
        ssize_t rv = sendto(sockfd, reinterpret_cast<const char *>(msg), len, flags, &to, tolen);
583
 
584
        UDP_num_sendto++;
585
        if (rv > 0)
586
                UDP_len_sendto += rv;
587
 
588
        return rv;
589
}
590
 
591
ssize_t dxx_recvfrom_t::apply(int sockfd, void *buf, size_t len, int flags, sockaddr &from, socklen_t &fromlen)
592
{
593
        ssize_t rv = recvfrom(sockfd, reinterpret_cast<char *>(buf), len, flags, &from, &fromlen);
594
 
595
        UDP_num_recvfrom++;
596
        UDP_len_recvfrom += rv;
597
 
598
        return rv;
599
}
600
 
601
constexpr csockaddr_dispatch_t<socket_array_dispatch_t<dxx_sendto_t>> dxx_sendto{};
602
constexpr sockaddr_dispatch_t<dxx_recvfrom_t> dxx_recvfrom{};
603
 
604
}
605
 
606
static void udp_traffic_stat()
607
{
608
        static fix64 last_traf_time = 0;
609
 
610
        if (timer_query() >= last_traf_time + F1_0)
611
        {
612
                last_traf_time = timer_query();
613
                con_printf(CON_DEBUG, "P#%u TRAFFIC - OUT: %fKB/s %iPPS IN: %fKB/s %iPPS",Player_num, static_cast<float>(UDP_len_sendto)/1024, UDP_num_sendto, static_cast<float>(UDP_len_recvfrom)/1024, UDP_num_recvfrom);
614
                UDP_num_sendto = UDP_len_sendto = UDP_num_recvfrom = UDP_len_recvfrom = 0;
615
        }
616
}
617
 
618
namespace {
619
 
620
class udp_dns_filladdr_t
621
{
622
public:
623
        static int apply(sockaddr &addr, socklen_t addrlen, int ai_family, const char *host, uint16_t port, bool numeric_only, bool silent);
624
};
625
 
626
// Resolve address
627
int udp_dns_filladdr_t::apply(sockaddr &addr, socklen_t addrlen, int ai_family, const char *host, uint16_t port, bool numeric_only, bool silent)
628
{
629
#ifdef DXX_HAVE_GETADDRINFO
630
        // Variables
631
        addrinfo hints{};
632
        char sPort[6];
633
 
634
        // Build the port
635
        snprintf(sPort, 6, "%hu", port);
636
 
637
        // Uncomment the following if we want ONLY what we compile for
638
        hints.ai_family = ai_family;
639
        // We are always UDP
640
        hints.ai_socktype = SOCK_DGRAM;
641
#ifdef AI_NUMERICSERV
642
        hints.ai_flags |= AI_NUMERICSERV;
643
#endif
644
#if DXX_USE_IPv6
645
        hints.ai_flags |= AI_V4MAPPED | AI_ALL;
646
#endif
647
        // Numeric address only?
648
        if (numeric_only)
649
                hints.ai_flags |= AI_NUMERICHOST;
650
 
651
        // Resolve the domain name
652
        RAIIaddrinfo result;
653
        if (result.getaddrinfo(host, sPort, &hints) != 0)
654
        {
655
                con_printf( CON_URGENT, "udp_dns_filladdr (getaddrinfo) failed for host %s", host );
656
                if (!silent)
657
                        nm_messagebox( TXT_ERROR, 1, TXT_OK, "Could not resolve address\n%s", host );
658
                addr.sa_family = AF_UNSPEC;
659
                return -1;
660
        }
661
 
662
        if (result->ai_addrlen > addrlen)
663
        {
664
                con_printf(CON_URGENT, "Address too big for host %s", host);
665
                if (!silent)
666
                        nm_messagebox(TXT_ERROR, 1, TXT_OK, "Address too big for host\n%s", host);
667
                addr.sa_family = AF_UNSPEC;
668
                return -1;
669
        }
670
        // Now copy it over
671
        memcpy(&addr, result->ai_addr, addrlen = result->ai_addrlen);
672
 
673
        /* WARNING:  NERDY CONTENT
674
         *
675
         * The above works, since result->ai_addr contains the socket family,
676
         * which is copied into our struct.  Our struct will be read for sendto
677
         * and recvfrom, using the sockaddr.sa_family member.  If we are IPv6,
678
         * this already has enough space to read into.  If we are IPv4, we will
679
         * not be able to get any IPv6 connections anyway, so we will be safe
680
         * from an overflow.  The more you know, 'cause knowledge is power!
681
         *
682
         * -- Matt
683
         */
684
 
685
        // Free memory
686
#else
687
        (void)numeric_only;
688
        sockaddr_in &sai = reinterpret_cast<sockaddr_in &>(addr);
689
        if (addrlen < sizeof(sai))
690
                return -1;
691
        const auto he = gethostbyname(host);
692
        if (!he)
693
        {
694
                con_printf(CON_URGENT, "udp_dns_filladdr (gethostbyname) failed for host %s", host);
695
                if (!silent)
696
                        nm_messagebox(TXT_ERROR, 1, TXT_OK, "Could not resolve IPv4 address\n%s", host);
697
                addr.sa_family = AF_UNSPEC;
698
                return -1;
699
        }
700
        sai = {};
701
        sai.sin_family = ai_family;
702
        sai.sin_port = htons(port);
703
        sai.sin_addr = *reinterpret_cast<const in_addr *>(he->h_addr);
704
#endif
705
        return 0;
706
}
707
 
708
template <typename F>
709
class sockaddr_resolve_family_dispatch_t : sockaddr_dispatch_t<F>
710
{
711
public:
712
#define apply_sockaddr(fromlen,family)  this->sockaddr_dispatch_t<F>::operator()(from, fromlen, family, std::forward<Args>(args)...)
713
        template <typename... Args>
714
                auto operator()(sockaddr_in &from, Args &&... args) const
715
                {
716
                        socklen_t fromlen;
717
                        return apply_sockaddr(fromlen, AF_INET);
718
                }
719
#if DXX_USE_IPv6
720
        template <typename... Args>
721
                auto operator()(sockaddr_in6 &from, Args &&... args) const
722
                {
723
                        socklen_t fromlen;
724
                        return apply_sockaddr(fromlen, AF_INET6);
725
                }
726
#endif
727
        template <typename... Args>
728
                auto operator()(_sockaddr &from, Args &&... args) const
729
                {
730
                        return this->operator()(dispatch_sockaddr_from, std::forward<Args>(args)...);
731
                }
732
#undef apply_sockaddr
733
};
734
 
735
constexpr sockaddr_resolve_family_dispatch_t<passthrough_static_apply<udp_dns_filladdr_t>> udp_dns_filladdr{};
736
 
737
}
738
 
739
static void udp_init_broadcast_addresses()
740
{
741
        udp_dns_filladdr(GBcast, UDP_BCAST_ADDR, UDP_PORT_DEFAULT, true, true);
742
#if DXX_USE_IPv6
743
        udp_dns_filladdr(GMcast_v6, UDP_MCASTv6_ADDR, UDP_PORT_DEFAULT, true, true);
744
#endif
745
}
746
 
747
// Open socket
748
static int udp_open_socket(RAIIsocket &sock, int port)
749
{
750
        int bcast = 1;
751
 
752
        // close stale socket
753
        struct _sockaddr sAddr;   // my address information
754
 
755
        sock.move(RAIIsocket(sAddr.address_family(), SOCK_DGRAM, 0));
756
        if (!sock)
757
        {
758
                con_printf(CON_URGENT,"udp_open_socket: socket creation failed (port %i)", port);
759
                nm_messagebox(TXT_ERROR,1,TXT_OK,"Port: %i\nCould not create socket.", port);
760
                return -1;
761
        }
762
        sAddr = {};
763
        sAddr.sa.sa_family = sAddr.address_family();
764
#if DXX_USE_IPv6
765
        sAddr.sin6.sin6_port = htons (port); // short, network byte order
766
        sAddr.sin6.sin6_addr = IN6ADDR_ANY_INIT; // automatically fill with my IP
767
#else
768
        sAddr.sin.sin_port = htons (port); // short, network byte order
769
        sAddr.sin.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
770
#endif
771
 
772
        if (bind(sock, &sAddr.sa, sizeof(sAddr)) < 0)
773
        {      
774
                con_printf(CON_URGENT,"udp_open_socket: bind name to socket failed (port %i)", port);
775
                nm_messagebox(TXT_ERROR,1,TXT_OK,"Port: %i\nCould not bind name to socket.", port);
776
                sock.reset();
777
                return -1;
778
        }
779
#ifdef _WIN32
780
        setsockopt(sock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char *>(&bcast), sizeof(bcast));
781
#else
782
        setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast));
783
#endif
784
        return 0;
785
}
786
 
787
#ifndef MSG_DONTWAIT
788
static int udp_general_packet_ready(RAIIsocket &sock)
789
{
790
        fd_set set;
791
        struct timeval tv;
792
 
793
        FD_ZERO(&set);
794
        FD_SET(sock, &set);
795
        tv.tv_sec = tv.tv_usec = 0;
796
        if (select(sock + 1, &set, NULL, NULL, &tv) > 0)
797
                return 1;
798
        else
799
                return 0;
800
}
801
#endif
802
 
803
// Gets some text. Returns 0 if nothing on there.
804
static int udp_receive_packet(RAIIsocket &sock, ubyte *text, int len, struct _sockaddr *sender_addr)
805
{
806
        if (!sock)
807
                return -1;
808
#ifndef MSG_DONTWAIT
809
        if (!udp_general_packet_ready(sock))
810
                return 0;
811
#endif
812
        ssize_t msglen;
813
                socklen_t clen;
814
                int flags = 0;
815
#ifdef MSG_DONTWAIT
816
                flags |= MSG_DONTWAIT;
817
#endif
818
                msglen = dxx_recvfrom(*sender_addr, clen, sock, text, len, flags);
819
 
820
                if (msglen < 0)
821
                        return 0;
822
 
823
                if ((msglen >= 0) && (msglen < len))
824
                        text[msglen] = 0;
825
        return msglen;
826
}
827
/* General UDP functions - END */
828
 
829
 
830
namespace {
831
 
832
class net_udp_request_game_info_t
833
{
834
public:
835
        static void apply(const sockaddr &to, socklen_t tolen, int lite);
836
};
837
 
838
class net_udp_send_game_info_t
839
{
840
public:
841
        static void apply(const sockaddr &target_addr, socklen_t targetlen, const _sockaddr *sender_addr, ubyte info_upid);
842
};
843
 
844
void net_udp_request_game_info_t::apply(const sockaddr &game_addr, socklen_t addrlen, int lite)
845
{
846
        std::array<uint8_t, UPID_GAME_INFO_REQ_SIZE> buf;
847
        net_udp_prepare_request_game_info(buf, lite);
848
        dxx_sendto(game_addr, addrlen, UDP_Socket[0], buf, 0);
849
}
850
 
851
constexpr csockaddr_dispatch_t<passthrough_static_apply<net_udp_request_game_info_t>> net_udp_request_game_info{};
852
constexpr csockaddr_dispatch_t<passthrough_static_apply<net_udp_send_game_info_t>> net_udp_send_game_info{};
853
 
854
struct direct_join
855
{
856
        struct _sockaddr host_addr;
857
        int connecting;
858
        fix64 start_time, last_time;
859
#if DXX_USE_TRACKER
860
        uint16_t gameid;
861
#endif
862
};
863
 
864
struct manual_join_user_inputs
865
{
866
        std::array<char, 6> hostportbuf, myportbuf;
867
        std::array<char, 128> addrbuf;
868
};
869
 
870
struct manual_join : direct_join, manual_join_user_inputs
871
{
872
        static manual_join_user_inputs s_last_inputs;
873
        std::array<newmenu_item, 7> m;
874
};
875
 
876
struct list_join : direct_join
877
{
878
        enum {
879
                entries = ((UDP_NETGAMES_PPAGE + 5) * 2) + 1,
880
        };
881
        std::array<newmenu_item, entries> m;
882
        std::array<std::array<char, 92>, entries> ljtext;
883
};
884
 
885
manual_join_user_inputs manual_join::s_last_inputs;
886
 
887
}
888
 
889
// Connect to a game host and get full info. Eventually we join!
890
static int net_udp_game_connect(direct_join *const dj)
891
{
892
        // Get full game info so we can show it.
893
 
894
        // Timeout after 10 seconds
895
        if (timer_query() >= dj->start_time + (F1_0*10))
896
        {
897
                dj->connecting = 0;
898
                std::array<char, _sockaddr::presentation_buffer_size> dbuf;
899
                const auto port =
900
#if DXX_USE_IPv6
901
                        dj->host_addr.sa.sa_family == AF_INET6
902
                        ? dj->host_addr.sin6.sin6_port
903
                        :
904
#endif
905
                        dj->host_addr.sin.sin_port;
906
                nm_messagebox(TXT_ERROR, 1, TXT_OK,
907
"No response by host.\n\n\
908
Possible reasons:\n\
909
* No game on %s (anymore)\n\
910
* Host port %hu is not open\n\
911
* Game is hosted on a different port\n\
912
* Host uses a game version\n\
913
  I do not understand", dxx_ntop(dj->host_addr, dbuf), ntohs(port));
914
                return 0;
915
        }
916
 
917
        if (Netgame.protocol.udp.valid == -1)
918
        {
919
                nm_messagebox(TXT_ERROR,1,TXT_OK,"Version mismatch! Cannot join Game.\n\nHost game version: %i.%i.%i\nHost game protocol: %i\n(%s)\n\nYour game version: " DXX_VERSION_STR "\nYour game protocol: %i\n(%s)",Netgame.protocol.udp.program_iver[0],Netgame.protocol.udp.program_iver[1],Netgame.protocol.udp.program_iver[2],Netgame.protocol.udp.program_iver[3],(Netgame.protocol.udp.program_iver[3]==0?"RELEASE VERSION":"DEVELOPMENT BUILD, BETA, etc."), MULTI_PROTO_VERSION, (MULTI_PROTO_VERSION==0?"RELEASE VERSION":"DEVELOPMENT BUILD, BETA, etc."));
920
                dj->connecting = 0;
921
                return 0;
922
        }
923
 
924
        if (timer_query() >= dj->last_time + F1_0)
925
        {
926
                net_udp_request_game_info(dj->host_addr, 0);
927
#if DXX_USE_TRACKER
928
                if (dj->gameid)
929
                        if (timer_query() >= dj->start_time + (F1_0*4))
930
                                udp_tracker_request_holepunch(dj->gameid);
931
#endif
932
                dj->last_time = timer_query();
933
        }
934
        timer_delay2(5);
935
        net_udp_listen();
936
 
937
        if (Netgame.protocol.udp.valid != 1)
938
                return 0;               // still trying to connect
939
 
940
        if (dj->connecting == 1)
941
        {
942
                if (!net_udp_show_game_info()) // show info menu and check if we join
943
                {
944
                        dj->connecting = 0;
945
                        return 0;
946
                }
947
                else
948
                {
949
                        // Get full game info again as it could have changed since we entered the info menu.
950
                        dj->connecting = 2;
951
                        Netgame.protocol.udp.valid = 0;
952
                        dj->start_time = timer_query();
953
 
954
                        return 0;
955
                }
956
        }
957
 
958
        dj->connecting = 0;
959
 
960
        return net_udp_do_join_game();
961
}
962
 
963
static int manual_join_game_handler(newmenu *const menu, const d_event &event, manual_join *const dj)
964
{
965
        newmenu_item *items = newmenu_get_items(menu);
966
 
967
        switch (event.type)
968
        {
969
                case EVENT_KEY_COMMAND:
970
                        if (dj->connecting && event_key_get(event) == KEY_ESC)
971
                        {
972
                                dj->connecting = 0;
973
                                nm_set_item_text(items[6], "");
974
                                return 1;
975
                        }
976
                        break;
977
 
978
                case EVENT_IDLE:
979
                        if (dj->connecting)
980
                        {
981
                                if (net_udp_game_connect(dj))
982
                                        return -2;      // Success!
983
                                else if (!dj->connecting)
984
                                        nm_set_item_text(items[6], "");
985
                        }
986
                        break;
987
 
988
                case EVENT_NEWMENU_SELECTED:
989
                {
990
                        int sockres = -1;
991
 
992
                        net_udp_init(); // yes, redundant call but since the menu does not know any better it would allow any IP entry as long as Netgame-entry looks okay... my head hurts...
993
                        if (!convert_text_portstring(dj->myportbuf, UDP_MyPort, false, false))
994
                                return 1;
995
                        sockres = udp_open_socket(UDP_Socket[0], UDP_MyPort);
996
                        if (sockres != 0)
997
                        {
998
                                return 1;
999
                        }
1000
                        uint16_t hostport;
1001
                        if (!convert_text_portstring(dj->hostportbuf, hostport, true, false))
1002
                                return 1;
1003
                        // Resolve address
1004
                        if (udp_dns_filladdr(dj->host_addr, &dj->addrbuf[0], hostport, false, false) < 0)
1005
                        {
1006
                                return 1;
1007
                        }
1008
                        else
1009
                        {
1010
                                dj->s_last_inputs = *dj;
1011
                                multi_new_game();
1012
                                N_players = 0;
1013
                                change_playernum_to(1);
1014
                                dj->start_time = timer_query();
1015
                                dj->last_time = 0;
1016
 
1017
                                Netgame.players[0].protocol.udp.addr = dj->host_addr;
1018
 
1019
                                dj->connecting = 1;
1020
                                nm_set_item_text(items[6], "Connecting...");
1021
                                return 1;
1022
                        }
1023
 
1024
                        break;
1025
                }
1026
 
1027
                case EVENT_WINDOW_CLOSE:
1028
                        if (!Game_wind) // they cancelled
1029
                                net_udp_close();
1030
                        std::default_delete<manual_join>()(dj);
1031
                        break;
1032
 
1033
                default:
1034
                        break;
1035
        }
1036
 
1037
        return 0;
1038
}
1039
 
1040
void net_udp_manual_join_game()
1041
{
1042
        int nitems = 0;
1043
 
1044
        auto dj = std::make_unique<manual_join>();
1045
        net_udp_init();
1046
 
1047
        reset_UDP_MyPort();
1048
 
1049
        if (dj->s_last_inputs.addrbuf[0])
1050
                dj->addrbuf = dj->s_last_inputs.addrbuf;
1051
        else
1052
                snprintf(&dj->addrbuf[0], dj->addrbuf.size(), "%s", CGameArg.MplUdpHostAddr.c_str());
1053
        if (dj->s_last_inputs.hostportbuf[0])
1054
                dj->hostportbuf = dj->s_last_inputs.hostportbuf;
1055
        else
1056
                snprintf(&dj->hostportbuf[0], dj->hostportbuf.size(), "%hu", CGameArg.MplUdpHostPort ? CGameArg.MplUdpHostPort : UDP_PORT_DEFAULT);
1057
        if (dj->s_last_inputs.myportbuf[0])
1058
                dj->myportbuf = dj->s_last_inputs.myportbuf;
1059
        else
1060
                snprintf(&dj->myportbuf[0], dj->myportbuf.size(), "%hu", UDP_MyPort);
1061
 
1062
        nitems = 0;
1063
        auto &m = dj->m;
1064
        nm_set_item_text(m[nitems++],"GAME ADDRESS OR HOSTNAME:");
1065
        nm_set_item_input(m[nitems++],dj->addrbuf);
1066
        nm_set_item_text(m[nitems++],"GAME PORT:");
1067
        nm_set_item_input(m[nitems++], dj->hostportbuf);
1068
        nm_set_item_text(m[nitems++],"MY PORT:");
1069
        nm_set_item_input(m[nitems++], dj->myportbuf);
1070
        nm_set_item_text(m[nitems++],"");
1071
 
1072
        newmenu_do1(nullptr, "ENTER GAME ADDRESS", nitems, &m[0], manual_join_game_handler, dj.release(), 0);
1073
}
1074
 
1075
static void copy_truncate_string(const grs_font &cv_font, const font_x_scaled_float strbound, std::array<char, 25> &out, const ntstring<25> &in)
1076
{
1077
        size_t k = 0, x = 0;
1078
        char thold[2];
1079
        thold[1] = 0;
1080
        const std::size_t outsize = out.size();
1081
        range_for (const char c, in)
1082
        {
1083
                if (unlikely(c == '\t'))
1084
                        continue;
1085
                if (unlikely(!c))
1086
                        break;
1087
                thold[0] = c;
1088
                int tx;
1089
                gr_get_string_size(cv_font, thold, &tx, nullptr, nullptr);
1090
                if ((x += tx) >= strbound)
1091
                {
1092
                        const std::size_t outbound = outsize - 4;
1093
                        if (k > outbound)
1094
                                k = outbound;
1095
                        out[k] = out[k + 1] = out[k + 2] = '.';
1096
                        k += 3;
1097
                        break;
1098
                }
1099
                out[k++] = c;
1100
                if (k >= outsize - 1)
1101
                        break;
1102
        }
1103
        out[k] = 0;
1104
}
1105
 
1106
static int net_udp_list_join_poll(newmenu *menu, const d_event &event, list_join *const dj)
1107
{
1108
        // Polling loop for Join Game menu
1109
        int newpage = 0;
1110
        static int NLPage = 0;
1111
        newmenu_item *menus = newmenu_get_items(menu);
1112
        switch (event.type)
1113
        {
1114
                case EVENT_WINDOW_ACTIVATED:
1115
                {
1116
                        Netgame.protocol.udp.valid = 0;
1117
                        Active_udp_games = {};
1118
                        num_active_udp_changed = 1;
1119
                        num_active_udp_games = 0;
1120
                        net_udp_request_game_info(GBcast, 1);
1121
#if DXX_USE_IPv6
1122
                        net_udp_request_game_info(GMcast_v6, 1);
1123
#endif
1124
#if DXX_USE_TRACKER
1125
                        udp_tracker_reqgames();
1126
#endif
1127
                        if (!dj->connecting) // fallback/failsafe!
1128
                                nm_set_item_text(menus[UDP_NETGAMES_PPAGE+4], "\t");
1129
                        break;
1130
                }
1131
                case EVENT_IDLE:
1132
                        if (dj->connecting)
1133
                        {
1134
                                if (net_udp_game_connect(dj))
1135
                                        return -2;      // Success!
1136
                                if (!dj->connecting) // connect wasn't successful - get rid of the message.
1137
                                        nm_set_item_text(menus[UDP_NETGAMES_PPAGE+4], "\t");
1138
                        }
1139
                        break;
1140
                case EVENT_KEY_COMMAND:
1141
                {
1142
                        int key = event_key_get(event);
1143
                        if (key == KEY_PAGEUP)
1144
                        {
1145
                                NLPage--;
1146
                                newpage++;
1147
                                if (NLPage < 0)
1148
                                        NLPage = UDP_NETGAMES_PAGES-1;
1149
                                key = 0;
1150
                                break;
1151
                        }
1152
                        if (key == KEY_PAGEDOWN)
1153
                        {
1154
                                NLPage++;
1155
                                newpage++;
1156
                                if (NLPage >= UDP_NETGAMES_PAGES)
1157
                                        NLPage = 0;
1158
                                key = 0;
1159
                                break;
1160
                        }
1161
                        if( key == KEY_F4 )
1162
                        {
1163
                                // Empty the list
1164
                                Active_udp_games = {};
1165
                                num_active_udp_changed = 1;
1166
                                num_active_udp_games = 0;
1167
 
1168
                                // Request LAN games
1169
                                net_udp_request_game_info(GBcast, 1);
1170
#if DXX_USE_IPv6
1171
                                net_udp_request_game_info(GMcast_v6, 1);
1172
#endif
1173
#if DXX_USE_TRACKER
1174
                                udp_tracker_reqgames();
1175
#endif
1176
                                // All done
1177
                                break;
1178
                        }
1179
#if DXX_USE_TRACKER
1180
                        if (key == KEY_F5)
1181
                        {
1182
                                Active_udp_games = {};
1183
                                num_active_udp_changed = 1;
1184
                                num_active_udp_games = 0;
1185
                                net_udp_request_game_info(GBcast, 1);
1186
 
1187
#if DXX_USE_IPv6
1188
                                net_udp_request_game_info(GMcast_v6, 1);
1189
#endif
1190
                                break;
1191
                        }
1192
 
1193
                        if( key == KEY_F6 )
1194
                        {
1195
                                // Zero the list
1196
                                Active_udp_games = {};
1197
                                num_active_udp_changed = 1;
1198
                                num_active_udp_games = 0;
1199
 
1200
                                // Request from the tracker
1201
                                udp_tracker_reqgames();
1202
 
1203
                                // Break off
1204
                                break;
1205
                        }
1206
#endif
1207
                        if (key == KEY_ESC)
1208
                        {
1209
                                if (dj->connecting)
1210
                                {
1211
                                        dj->connecting = 0;
1212
                                        nm_set_item_text(menus[UDP_NETGAMES_PPAGE+4], "\t");
1213
                                        return 1;
1214
                                }
1215
                                break;
1216
                        }
1217
                        break;
1218
                }
1219
                case EVENT_NEWMENU_SELECTED:
1220
                {
1221
                        auto &citem = static_cast<const d_select_event &>(event).citem;
1222
                        if (((citem+(NLPage*UDP_NETGAMES_PPAGE)) >= 4) && (((citem+(NLPage*UDP_NETGAMES_PPAGE))-3) <= num_active_udp_games))
1223
                        {
1224
                                multi_new_game();
1225
                                N_players = 0;
1226
                                change_playernum_to(1);
1227
                                dj->start_time = timer_query();
1228
                                dj->last_time = 0;
1229
                                dj->host_addr = Active_udp_games[(citem+(NLPage*UDP_NETGAMES_PPAGE))-4].game_addr;
1230
                                Netgame.players[0].protocol.udp.addr = dj->host_addr;
1231
                                dj->connecting = 1;
1232
#if DXX_USE_TRACKER
1233
                                dj->gameid = Active_udp_games[(citem+(NLPage*UDP_NETGAMES_PPAGE))-4].TrackerGameID;
1234
#endif
1235
                                nm_set_item_text(menus[UDP_NETGAMES_PPAGE+4], "\tConnecting. Please wait...");
1236
                                return 1;
1237
                        }
1238
                        else
1239
                        {
1240
                                nm_messagebox(TXT_SORRY, 1, TXT_OK, TXT_INVALID_CHOICE);
1241
                                return -1; // invalid game selected - stay in the menu
1242
                        }
1243
                        break;
1244
                }
1245
                case EVENT_WINDOW_CLOSE:
1246
                {
1247
                        std::default_delete<list_join>()(dj);
1248
                        if (!Game_wind)
1249
                        {
1250
                                net_udp_close();
1251
                                Network_status = NETSTAT_MENU;  // they cancelled
1252
                        }
1253
                        return 0;
1254
                }
1255
                default:
1256
                        break;
1257
        }
1258
 
1259
        net_udp_listen();
1260
 
1261
        if (!num_active_udp_changed && !newpage)
1262
                return 0;
1263
 
1264
        num_active_udp_changed = 0;
1265
 
1266
        // Copy the active games data into the menu options
1267
        for (int i = 0; i < UDP_NETGAMES_PPAGE; i++)
1268
        {
1269
                const auto &augi = Active_udp_games[(i + (NLPage * UDP_NETGAMES_PPAGE))];
1270
                int game_status = augi.game_status;
1271
                int nplayers = 0;
1272
                char levelname[8];
1273
 
1274
                if ((i+(NLPage*UDP_NETGAMES_PPAGE)) >= num_active_udp_games)
1275
                {
1276
                        auto &p = dj->ljtext[i];
1277
                        snprintf(&p[0], p.size(), "%d.                                                                      ", (i + (NLPage * UDP_NETGAMES_PPAGE)) + 1);
1278
                        continue;
1279
                }
1280
 
1281
                // These next two loops protect against menu skewing
1282
                // if missiontitle or gamename contain a tab
1283
 
1284
                const auto &&fspacx = FSPACX();
1285
                const auto &cv_font = *grd_curcanv->cv_font;
1286
                std::array<char, 25> MissName, GameName;
1287
                const auto &&fspacx55 = fspacx(55);
1288
                copy_truncate_string(cv_font, fspacx55, MissName, augi.mission_title);
1289
                copy_truncate_string(cv_font, fspacx55, GameName, augi.game_name);
1290
 
1291
                nplayers = augi.numconnected;
1292
 
1293
                const int levelnum = augi.levelnum;
1294
                if (levelnum < 0)
1295
                {
1296
                        cf_assert(-levelnum < MAX_SECRET_LEVELS_PER_MISSION);
1297
                        snprintf(levelname, sizeof(levelname), "S%d", -levelnum);
1298
                }
1299
                else
1300
                {
1301
                        cf_assert(levelnum < MAX_LEVELS_PER_MISSION);
1302
                        snprintf(levelname, sizeof(levelname), "%d", levelnum);
1303
                }
1304
 
1305
                const char *status;
1306
                if (game_status == NETSTAT_STARTING)
1307
                        status = "FORMING ";
1308
                else if (game_status == NETSTAT_PLAYING)
1309
                {
1310
                        if (augi.RefusePlayers)
1311
                                status = "RESTRICT";
1312
                        else if (augi.game_flag.closed)
1313
                                status = "CLOSED  ";
1314
                        else
1315
                                status = "OPEN    ";
1316
                }
1317
                else
1318
                        status = "BETWEEN ";
1319
 
1320
                unsigned gamemode = augi.gamemode;
1321
                auto &p = dj->ljtext[i];
1322
                snprintf(&p[0], p.size(), "%d.\t%.24s \t%.7s \t%3u/%u \t%.24s \t %s \t%s", (i + (NLPage * UDP_NETGAMES_PPAGE)) + 1, GameName.data(), (gamemode < std::size(GMNamesShrt)) ? GMNamesShrt[gamemode] : "INVALID", nplayers, augi.max_numplayers, MissName.data(), levelname, status);
1323
        }
1324
        return 0;
1325
}
1326
 
1327
void net_udp_list_join_game()
1328
{
1329
        auto dj = std::make_unique<list_join>();
1330
 
1331
        net_udp_init();
1332
        const auto gamemyport = CGameArg.MplUdpMyPort;
1333
        if (udp_open_socket(UDP_Socket[0], gamemyport >= 1024 ? gamemyport : UDP_PORT_DEFAULT) < 0)
1334
                return;
1335
 
1336
        if (gamemyport >= 1024 && gamemyport != UDP_PORT_DEFAULT)
1337
                if (udp_open_socket(UDP_Socket[1], UDP_PORT_DEFAULT) < 0)
1338
                        nm_messagebox(TXT_WARNING, 1, TXT_OK, "Cannot open default port!\nYou can only scan for games\nmanually.");
1339
 
1340
        // prepare broadcast address to discover games
1341
        udp_init_broadcast_addresses();
1342
 
1343
        change_playernum_to(1);
1344
        N_players = 0;
1345
        Network_send_objects = 0;
1346
        Network_sending_extras=0;
1347
        Network_rejoined=0;
1348
 
1349
        Network_status = NETSTAT_BROWSING; // We are looking at a game menu
1350
 
1351
        net_udp_flush();
1352
        net_udp_listen();  // Throw out old info
1353
 
1354
        num_active_udp_games = 0;
1355
 
1356
        Active_udp_games = {};
1357
 
1358
        gr_set_fontcolor(*grd_curcanv, BM_XRGB(15, 15, 23),-1);
1359
 
1360
        auto &m = dj->m;
1361
#if DXX_USE_TRACKER
1362
        nm_set_item_text(m[0], "\tF4/F5/F6: (Re)Scan for all/LAN/Tracker Games." );
1363
#else
1364
        nm_set_item_text(m[0], "\tF4: (Re)Scan for LAN Games." );
1365
#endif
1366
        nm_set_item_text(m[1], "\tPgUp/PgDn: Flip Pages." );
1367
        nm_set_item_text(m[2], " " );
1368
        nm_set_item_text(m[3],  "\tGAME \tMODE \t#PLYRS \tMISSION \tLEV \tSTATUS");
1369
 
1370
        for (int i = 0; i < UDP_NETGAMES_PPAGE; i++) {
1371
                auto &p = dj->ljtext[i];
1372
                nm_set_item_menu(m[i + 4], &p[0]);
1373
                snprintf(&p[0], p.size(), "%d.                                                                      ", i + 1);
1374
        }
1375
        nm_set_item_text(m[UDP_NETGAMES_PPAGE+4], "\t" );
1376
 
1377
        num_active_udp_changed = 1;
1378
        newmenu_dotiny("NETGAMES", nullptr, UDP_NETGAMES_PPAGE + 5, &m[0], 1, net_udp_list_join_poll, dj.release());
1379
}
1380
 
1381
static void net_udp_send_sequence_packet(UDP_sequence_packet seq, const _sockaddr &recv_addr)
1382
{
1383
        std::array<uint8_t, UPID_SEQUENCE_SIZE> buf;
1384
        int len = 0;
1385
        buf[0] = seq.type;                                              len++;
1386
        memcpy(&buf[len], seq.player.callsign.buffer(), CALLSIGN_LEN+1);                len += CALLSIGN_LEN+1;
1387
        buf[len] = seq.player.connected;                                len++;
1388
        buf[len] = seq.player.rank;                                     len++;
1389
        dxx_sendto(recv_addr, UDP_Socket[0], buf, 0);
1390
}
1391
 
1392
static void net_udp_receive_sequence_packet(ubyte *data, UDP_sequence_packet *seq, const _sockaddr &sender_addr)
1393
{
1394
        int len = 0;
1395
 
1396
        seq->type = data[0];                                            len++;
1397
        memcpy(seq->player.callsign.buffer(), &(data[len]), CALLSIGN_LEN+1);    len += CALLSIGN_LEN+1;
1398
        seq->player.connected = data[len];                              len++;
1399
        memcpy (&(seq->player.rank),&(data[len]),1);                    len++;
1400
 
1401
        if (multi_i_am_master())
1402
                seq->player.protocol.udp.addr = sender_addr;
1403
}
1404
 
1405
void net_udp_init()
1406
{
1407
        // So you want to play a netgame, eh?  Let's a get a few things straight
1408
 
1409
#ifdef _WIN32
1410
{
1411
        WORD wVersionRequested;
1412
        WSADATA wsaData;
1413
        wVersionRequested = MAKEWORD(2, 2);
1414
        WSACleanup();
1415
        if (WSAStartup( wVersionRequested, &wsaData))
1416
                nm_messagebox( TXT_ERROR, 1, TXT_OK, "Cannot init Winsock!"); // no break here... game will fail at socket creation anyways...
1417
}
1418
#endif
1419
 
1420
        clear_UDP_Socket();
1421
 
1422
        Netgame = {};
1423
        UDP_Seq = {};
1424
        UDP_MData = {};
1425
        net_udp_noloss_init_mdata_queue();
1426
        UDP_Seq.type = UPID_REQUEST;
1427
        UDP_Seq.player.callsign = InterfaceUniqueState.PilotName;
1428
 
1429
        UDP_Seq.player.rank=GetMyNetRanking(); 
1430
 
1431
        multi_new_game();
1432
        net_udp_flush();
1433
 
1434
#if DXX_USE_TRACKER
1435
        // Initialize the tracker info
1436
        udp_tracker_init();
1437
#endif
1438
}
1439
 
1440
void net_udp_close()
1441
{
1442
        clear_UDP_Socket();
1443
#ifdef _WIN32
1444
        WSACleanup();
1445
#endif
1446
}
1447
 
1448
// Same as above but used when player pressed ESC during kmatrix (host also does the packets for playing clients)
1449
int net_udp_kmatrix_poll2( newmenu *,const d_event &event, const unused_newmenu_userdata_t *)
1450
{
1451
        int rval = 0;
1452
 
1453
        // Polling loop for End-of-level menu
1454
        if (event.type == EVENT_WINDOW_CREATED)
1455
        {
1456
                StartAbortMenuTime=timer_query();
1457
                return 0;
1458
        }
1459
        if (event.type != EVENT_WINDOW_DRAW)
1460
                return 0;
1461
        if (timer_query() > (StartAbortMenuTime+(F1_0*3)))
1462
                rval = -2;
1463
 
1464
        net_udp_do_frame(0, 1);
1465
 
1466
        return rval;
1467
}
1468
 
1469
namespace dsx {
1470
int net_udp_endlevel(int *secret)
1471
{
1472
        // Do whatever needs to be done between levels
1473
#if defined(DXX_BUILD_DESCENT_II)
1474
        if (EMULATING_D1)
1475
#endif
1476
        {
1477
                // We do not really check if a player has actually found a secret level... yeah, I am too lazy! So just go there and pretend we did!
1478
                range_for (const auto i, unchecked_partial_range(Secret_level_table.get(), N_secret_levels))
1479
                {
1480
                        if (Current_level_num == i)
1481
                        {
1482
                                *secret = 1;
1483
                                break;
1484
                        }
1485
                }
1486
        }
1487
#if defined(DXX_BUILD_DESCENT_II)
1488
        else
1489
                *secret = 0;
1490
#endif
1491
 
1492
        Network_status = NETSTAT_ENDLEVEL; // We are between levels
1493
        net_udp_listen();
1494
        net_udp_send_endlevel_packet();
1495
 
1496
        range_for (auto &i, partial_range(Netgame.players, N_players))
1497
        {
1498
                i.LastPacketTime = timer_query();
1499
        }
1500
 
1501
        net_udp_send_endlevel_packet();
1502
        net_udp_send_endlevel_packet();
1503
 
1504
        net_udp_update_netgame();
1505
 
1506
        return(0);
1507
}
1508
}
1509
 
1510
static join_netgame_status_code net_udp_can_join_netgame(const netgame_info *const game)
1511
{
1512
        // Can this player rejoin a netgame in progress?
1513
        if (game->game_status == NETSTAT_STARTING)
1514
                return join_netgame_status_code::game_has_capacity;
1515
 
1516
        if (game->game_status != NETSTAT_PLAYING)
1517
                return join_netgame_status_code::game_in_disallowed_state;
1518
 
1519
        // Game is in progress, figure out if this guy can re-join it
1520
 
1521
        const unsigned num_players = game->numplayers;
1522
 
1523
        if (!(game->game_flag.closed)) {
1524
                // Look for player that is not connected
1525
 
1526
                if (game->numconnected==game->max_numplayers)
1527
                        return join_netgame_status_code::game_is_full;
1528
 
1529
                if (game->RefusePlayers)
1530
                        return join_netgame_status_code::game_refuses_players;
1531
 
1532
                if (num_players < game->max_numplayers)
1533
                        return join_netgame_status_code::game_has_capacity;
1534
 
1535
                if (game->numconnected<num_players)
1536
                        return join_netgame_status_code::game_has_capacity;
1537
        }
1538
 
1539
        // Search to see if we were already in this closed netgame in progress
1540
 
1541
        auto &plr = get_local_player();
1542
        for (const auto i : xrange(num_players))
1543
        {
1544
                if (plr.callsign == game->players[i].callsign && i == game->protocol.udp.your_index)
1545
                        return join_netgame_status_code::game_has_capacity;
1546
        }
1547
        return join_netgame_status_code::game_in_disallowed_state;
1548
}
1549
 
1550
// do UDP stuff to disconnect a player. Should ONLY be called from multi_disconnect_player()
1551
void net_udp_disconnect_player(int playernum)
1552
{
1553
        // A player has disconnected from the net game, take whatever steps are
1554
        // necessary 
1555
 
1556
        if (playernum == Player_num)
1557
        {
1558
                Int3(); // Weird, see Rob
1559
                return;
1560
        }
1561
 
1562
        if (VerifyPlayerJoined==playernum)
1563
                VerifyPlayerJoined=-1;
1564
 
1565
        net_udp_noloss_clear_mdata_trace(playernum);
1566
}
1567
 
1568
namespace dsx {
1569
static void net_udp_new_player(UDP_sequence_packet *const their)
1570
{
1571
        auto &Objects = LevelUniqueObjectState.Objects;
1572
        auto &vmobjptr = Objects.vmptr;
1573
        unsigned pnum = their->player.connected;
1574
 
1575
        Assert(pnum < Netgame.max_numplayers);
1576
 
1577
        if (Newdemo_state == ND_STATE_RECORDING) {
1578
                int new_player;
1579
 
1580
                if (pnum == N_players)
1581
                        new_player = 1;
1582
                else
1583
                        new_player = 0;
1584
                newdemo_record_multi_connect(pnum, new_player, their->player.callsign);
1585
        }
1586
 
1587
        auto &plr = *vmplayerptr(pnum);
1588
        plr.callsign = their->player.callsign;
1589
        Netgame.players[pnum].callsign = their->player.callsign;
1590
        Netgame.players[pnum].protocol.udp.addr = their->player.protocol.udp.addr;
1591
 
1592
        ClipRank (&their->player.rank);
1593
        Netgame.players[pnum].rank=their->player.rank;
1594
 
1595
        plr.connected = CONNECT_PLAYING;
1596
        kill_matrix[pnum] = {};
1597
        auto &objp = *vmobjptr(plr.objnum);
1598
        auto &player_info = objp.ctype.player_info;
1599
        player_info.net_killed_total = 0;
1600
        player_info.net_kills_total = 0;
1601
        player_info.mission.score = 0;
1602
        player_info.powerup_flags = {};
1603
        player_info.KillGoalCount = 0;
1604
 
1605
        if (pnum == N_players)
1606
        {
1607
                N_players++;
1608
                Netgame.numplayers = N_players;
1609
        }
1610
 
1611
        digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
1612
 
1613
        ClipRank (&their->player.rank);
1614
 
1615
        const auto &&rankstr = GetRankStringWithSpace(their->player.rank);
1616
        HUD_init_message(HM_MULTI, "%s%s'%s' %s", rankstr.first, rankstr.second, static_cast<const char *>(their->player.callsign), TXT_JOINING);
1617
 
1618
        multi_make_ghost_player(pnum);
1619
 
1620
        multi_send_score();
1621
#if defined(DXX_BUILD_DESCENT_II)
1622
        multi_sort_kill_list();
1623
#endif
1624
 
1625
        net_udp_noloss_clear_mdata_trace(pnum);
1626
}
1627
}
1628
 
1629
static void net_udp_welcome_player(UDP_sequence_packet *their)
1630
{
1631
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1632
        auto &Objects = LevelUniqueObjectState.Objects;
1633
        auto &vmobjptr = Objects.vmptr;
1634
        // Add a player to a game already in progress
1635
        WaitForRefuseAnswer=0;
1636
 
1637
        // Don't accept new players if we're ending this level.  Its safe to
1638
        // ignore since they'll request again later
1639
 
1640
        if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
1641
        {
1642
                net_udp_dump_player(their->player.protocol.udp.addr, DUMP_ENDLEVEL);
1643
                return;
1644
        }
1645
 
1646
        if (Network_send_objects || Network_sending_extras)
1647
        {
1648
                // Ignore silently, we're already responding to someone and we can't
1649
                // do more than one person at a time.  If we don't dump them they will
1650
                // re-request in a few seconds.
1651
                return;
1652
        }
1653
 
1654
        // Joining a running game will need quite a few packets on the mdata-queue, so let players only join if we have enough space.
1655
        if (Netgame.PacketLossPrevention)
1656
                if ((UDP_MDATA_STOR_QUEUE_SIZE - UDP_mdata_queue_highest) < UDP_MDATA_STOR_MIN_FREE_2JOIN)
1657
                        return;
1658
 
1659
        if (their->player.connected != Current_level_num)
1660
        {
1661
                net_udp_dump_player(their->player.protocol.udp.addr, DUMP_LEVEL);
1662
                return;
1663
        }
1664
 
1665
        unsigned player_num = UINT_MAX;
1666
        UDP_sync_player = {};
1667
        Network_player_added = 0;
1668
 
1669
        for (unsigned i = 0; i < N_players; i++)
1670
        {
1671
                if (vcplayerptr(i)->callsign == their->player.callsign &&
1672
                        their->player.protocol.udp.addr == Netgame.players[i].protocol.udp.addr)
1673
                {
1674
                        player_num = i;
1675
                        break;
1676
                }
1677
        }
1678
 
1679
        if (player_num == UINT_MAX)
1680
        {
1681
                // Player is new to this game
1682
 
1683
                if ( !(Netgame.game_flag.closed) && (N_players < Netgame.max_numplayers))
1684
                {
1685
                        // Add player in an open slot, game not full yet
1686
 
1687
                        player_num = N_players;
1688
                        Network_player_added = 1;
1689
                }
1690
                else if (Netgame.game_flag.closed)
1691
                {
1692
                        // Slots are open but game is closed
1693
                        net_udp_dump_player(their->player.protocol.udp.addr, DUMP_CLOSED);
1694
                        return;
1695
                }
1696
                else
1697
                {
1698
                        // Slots are full but game is open, see if anyone is
1699
                        // disconnected and replace the oldest player with this new one
1700
 
1701
                        int oldest_player = -1;
1702
                        fix64 oldest_time = timer_query();
1703
                        int activeplayers = 0;
1704
 
1705
                        Assert(N_players == Netgame.max_numplayers);
1706
 
1707
                        range_for (auto &i, partial_const_range(Netgame.players, Netgame.numplayers))
1708
                                if (i.connected)
1709
                                        activeplayers++;
1710
 
1711
                        if (activeplayers == Netgame.max_numplayers)
1712
                        {
1713
                                // Game is full.
1714
                                net_udp_dump_player(their->player.protocol.udp.addr, DUMP_FULL);
1715
                                return;
1716
                        }
1717
 
1718
                        for (unsigned i = 0; i < N_players; i++)
1719
                        {
1720
                                if (!vcplayerptr(i)->connected && Netgame.players[i].LastPacketTime < oldest_time)
1721
                                {
1722
                                        oldest_time = Netgame.players[i].LastPacketTime;
1723
                                        oldest_player = i;
1724
                                }
1725
                        }
1726
 
1727
                        if (oldest_player == -1)
1728
                        {
1729
                                // Everyone is still connected 
1730
                                net_udp_dump_player(their->player.protocol.udp.addr, DUMP_FULL);
1731
                                return;
1732
                        }
1733
                        else
1734
                        {
1735
                                // Found a slot!
1736
 
1737
                                player_num = oldest_player;
1738
                                Network_player_added = 1;
1739
                        }
1740
                }
1741
        }
1742
        else
1743
        {
1744
                // Player is reconnecting
1745
 
1746
                auto &plr = *vcplayerptr(player_num);
1747
                if (plr.connected)
1748
                {
1749
                        return;
1750
                }
1751
 
1752
                if (Newdemo_state == ND_STATE_RECORDING)
1753
                        newdemo_record_multi_reconnect(player_num);
1754
 
1755
                Network_player_added = 0;
1756
 
1757
                digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
1758
 
1759
                const auto &&rankstr = GetRankStringWithSpace(Netgame.players[player_num].rank);
1760
                HUD_init_message(HM_MULTI, "%s%s'%s' %s", rankstr.first, rankstr.second, static_cast<const char *>(plr.callsign), TXT_REJOIN);
1761
 
1762
                multi_send_score();
1763
 
1764
                net_udp_noloss_clear_mdata_trace(player_num);
1765
        }
1766
 
1767
        auto &obj = *vmobjptr(vcplayerptr(player_num)->objnum);
1768
        auto &player_info = obj.ctype.player_info;
1769
        player_info.KillGoalCount = 0;
1770
 
1771
        // Send updated Objects data to the new/returning player
1772
 
1773
 
1774
        UDP_sync_player = *their;
1775
        UDP_sync_player.player.connected = player_num;
1776
        Network_send_objects = 1;
1777
        Network_send_objnum = -1;
1778
        Netgame.players[player_num].LastPacketTime = timer_query();
1779
 
1780
        net_udp_send_objects();
1781
}
1782
 
1783
int net_udp_objnum_is_past(objnum_t objnum)
1784
{
1785
        // determine whether or not a given object number has already been sent
1786
        // to a re-joining player.
1787
 
1788
        int player_num = UDP_sync_player.player.connected;
1789
        int obj_mode = !((object_owner[objnum] == -1) || (object_owner[objnum] == player_num));
1790
 
1791
        if (!Network_send_objects)
1792
                return 0; // We're not sending objects to a new player
1793
 
1794
        if (obj_mode > Network_send_object_mode)
1795
                return 0;
1796
        else if (obj_mode < Network_send_object_mode)
1797
                return 1;
1798
        else if (objnum < Network_send_objnum)
1799
                return 1;
1800
        else
1801
                return 0;
1802
}
1803
 
1804
namespace dsx {
1805
 
1806
#if defined(DXX_BUILD_DESCENT_I)
1807
static void net_udp_send_door_updates(void)
1808
{
1809
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
1810
        auto &vcwallptridx = Walls.vcptridx;
1811
        // Send door status when new player joins
1812
        range_for (const auto &&p, vcwallptridx)
1813
        {
1814
                auto &w = *p;
1815
                if ((w.type == WALL_DOOR && (w.state == WALL_DOOR_OPENING || w.state == WALL_DOOR_WAITING)) || (w.type == WALL_BLASTABLE && (w.flags & WALL_BLASTED)))
1816
                        multi_send_door_open(w.segnum, w.sidenum,0);
1817
                else if (w.type == WALL_BLASTABLE && w.hps != WALL_HPS)
1818
                        multi_send_hostage_door_status(p);
1819
        }
1820
 
1821
}
1822
#elif defined(DXX_BUILD_DESCENT_II)
1823
static void net_udp_send_door_updates(const playernum_t pnum)
1824
{
1825
        // Send door status when new player joins
1826
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
1827
        auto &vcwallptridx = Walls.vcptridx;
1828
        range_for (const auto &&p, vcwallptridx)
1829
        {
1830
                auto &w = *p;
1831
                if ((w.type == WALL_DOOR && (w.state == WALL_DOOR_OPENING || w.state == WALL_DOOR_WAITING || w.state == WALL_DOOR_OPEN)) || (w.type == WALL_BLASTABLE && (w.flags & WALL_BLASTED)))
1832
                        multi_send_door_open_specific(pnum,w.segnum, w.sidenum,w.flags);
1833
                else if (w.type == WALL_BLASTABLE && w.hps != WALL_HPS)
1834
                        multi_send_hostage_door_status(p);
1835
                else
1836
                        multi_send_wall_status_specific(pnum,p,w.type,w.flags,w.state);
1837
        }
1838
}
1839
#endif
1840
 
1841
}
1842
 
1843
static void net_udp_process_monitor_vector(uint32_t vector)
1844
{
1845
        auto &Effects = LevelUniqueEffectsClipState.Effects;
1846
        auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
1847
        if (!vector)
1848
                return;
1849
        range_for (unique_segment &seg, vmsegptr)
1850
        {
1851
                int tm, ec, bm;
1852
                range_for (auto &j, seg.sides)
1853
                {
1854
                        if ( ((tm = j.tmap_num2) != 0) &&
1855
                                (ec = TmapInfo[tm & 0x3fff].eclip_num) != eclip_none &&
1856
                                (bm = Effects[ec].dest_bm_num) != ~0u)
1857
                        {
1858
                                if (vector & 1)
1859
                                {
1860
                                        j.tmap_num2 = bm | (tm&0xc000);
1861
                                }
1862
                                if (!(vector >>= 1))
1863
                                        return;
1864
                        }
1865
                }
1866
        }
1867
}
1868
 
1869
namespace {
1870
 
1871
class blown_bitmap_array
1872
{
1873
        typedef int T;
1874
        using array_t = std::array<T, 32>;
1875
        typedef array_t::const_iterator const_iterator;
1876
        array_t a;
1877
        array_t::iterator e = a.begin();
1878
public:
1879
        bool exists(T t) const
1880
        {
1881
                const_iterator ce = e;
1882
                return std::find(a.begin(), ce, t) != ce;
1883
        }
1884
        void insert_unique(T t)
1885
        {
1886
                if (exists(t))
1887
                        return;
1888
                if (e == a.end())
1889
                {
1890
                        LevelError("too many blown bitmaps; ignoring bitmap %i.", t);
1891
                        return;
1892
                }
1893
                *e = t;
1894
                ++e;
1895
        }
1896
};
1897
 
1898
}
1899
 
1900
static unsigned net_udp_create_monitor_vector(void)
1901
{
1902
        auto &Effects = LevelUniqueEffectsClipState.Effects;
1903
        auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
1904
        blown_bitmap_array blown_bitmaps;
1905
        constexpr size_t max_textures = Textures.size();
1906
        range_for (auto &i, partial_const_range(Effects, Num_effects))
1907
        {
1908
                if (i.dest_bm_num < max_textures)
1909
                {
1910
                        blown_bitmaps.insert_unique(i.dest_bm_num);
1911
                }
1912
        }
1913
        unsigned monitor_num = 0;
1914
        unsigned vector = 0;
1915
        range_for (const auto &&seg, vcsegptridx)
1916
        {
1917
                range_for (auto &j, seg->unique_segment::sides)
1918
                {
1919
                        const unsigned tm2 = j.tmap_num2;
1920
                        if (!tm2)
1921
                                continue;
1922
                        const unsigned masked_tm2 = tm2 & 0x3fff;
1923
                        const unsigned ec = TmapInfo[masked_tm2].eclip_num;
1924
                        {
1925
                                if (ec != eclip_none &&
1926
                                        Effects[ec].dest_bm_num != ~0u)
1927
                                {
1928
                                }
1929
                                else if (blown_bitmaps.exists(masked_tm2))
1930
                                {
1931
                                        if (monitor_num >= 8 * sizeof(vector))
1932
                                        {
1933
                                                LevelError("too many blown monitors; ignoring segment %hu.", seg.get_unchecked_index());
1934
                                                return vector;
1935
                                        }
1936
                                                        vector |= (1 << monitor_num);
1937
                                }
1938
                                else
1939
                                        continue;
1940
                                monitor_num++;
1941
                        }
1942
                }
1943
        }
1944
        return(vector);
1945
}
1946
 
1947
static void net_udp_stop_resync(UDP_sequence_packet *their)
1948
{
1949
        if (UDP_sync_player.player.protocol.udp.addr == their->player.protocol.udp.addr &&
1950
                (!d_stricmp(UDP_sync_player.player.callsign, their->player.callsign)) )
1951
        {
1952
                Network_send_objects = 0;
1953
                Network_sending_extras=0;
1954
                Network_rejoined=0;
1955
                Player_joining_extras=-1;
1956
                Network_send_objnum = -1;
1957
        }
1958
}
1959
 
1960
namespace dsx {
1961
void net_udp_send_objects(void)
1962
{
1963
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1964
        auto &Objects = LevelUniqueObjectState.Objects;
1965
        auto &vmobjptr = Objects.vmptr;
1966
        sbyte owner, player_num = UDP_sync_player.player.connected;
1967
        static int obj_count = 0;
1968
        int loc = 0, remote_objnum = 0, obj_count_frame = 0;
1969
        static fix64 last_send_time = 0;
1970
 
1971
        if (last_send_time + (F1_0/50) > timer_query())
1972
                return;
1973
        last_send_time = timer_query();
1974
 
1975
        // Send clear objects array trigger and send player num
1976
 
1977
        Assert(Network_send_objects != 0);
1978
        Assert(player_num >= 0);
1979
        Assert(player_num < Netgame.max_numplayers);
1980
 
1981
        if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
1982
        {
1983
                // Endlevel started before we finished sending the goods, we'll
1984
                // have to stop and try again after the level.
1985
                net_udp_dump_player(UDP_sync_player.player.protocol.udp.addr, DUMP_ENDLEVEL);
1986
                Network_send_objects = 0;
1987
                return;
1988
        }
1989
 
1990
        std::array<uint8_t, UPID_MAX_SIZE> object_buffer;
1991
        object_buffer = {};
1992
        object_buffer[0] = UPID_OBJECT_DATA;
1993
        loc = 5;
1994
 
1995
        if (Network_send_objnum == -1)
1996
        {
1997
                obj_count = 0;
1998
                Network_send_object_mode = 0;
1999
                PUT_INTEL_INT(&object_buffer[loc], -1);                       loc += 4;
2000
                object_buffer[loc] = player_num;                            loc += 1;
2001
                /* Placeholder for remote_objnum, not used here */          loc += 4;
2002
                Network_send_objnum = 0;
2003
                obj_count_frame = 1;
2004
        }
2005
 
2006
        objnum_t i;
2007
        for (i = Network_send_objnum; i <= Highest_object_index; i++)
2008
        {
2009
                const auto &&objp = vmobjptr(i);
2010
                if ((objp->type != OBJ_POWERUP) && (objp->type != OBJ_PLAYER) &&
2011
                                (objp->type != OBJ_CNTRLCEN) && (objp->type != OBJ_GHOST) &&
2012
                                (objp->type != OBJ_ROBOT) && (objp->type != OBJ_HOSTAGE)
2013
#if defined(DXX_BUILD_DESCENT_II)
2014
                                && !(objp->type == OBJ_WEAPON && get_weapon_id(objp) == weapon_id_type::PMINE_ID)
2015
#endif
2016
                                )
2017
                        continue;
2018
                if ((Network_send_object_mode == 0) && ((object_owner[i] != -1) && (object_owner[i] != player_num)))
2019
                        continue;
2020
                if ((Network_send_object_mode == 1) && ((object_owner[i] == -1) || (object_owner[i] == player_num)))
2021
                        continue;
2022
 
2023
                if ( loc + sizeof(object_rw) + 9 > UPID_MAX_SIZE-1 )
2024
                        break; // Not enough room for another object
2025
 
2026
                obj_count_frame++;
2027
                obj_count++;
2028
 
2029
                remote_objnum = objnum_local_to_remote(i, &owner);
2030
                Assert(owner == object_owner[i]);
2031
 
2032
                PUT_INTEL_INT(&object_buffer[loc], i);                        loc += 4;
2033
                object_buffer[loc] = owner;                                 loc += 1;
2034
                PUT_INTEL_INT(&object_buffer[loc], remote_objnum);            loc += 4;
2035
                // use object_rw to send objects for now. if object sometime contains some day contains something useful the client should know about, we should use it. but by now it's also easier to use object_rw because then we also do not need fix64 timer values.
2036
                multi_object_to_object_rw(vmobjptr(i), reinterpret_cast<object_rw *>(&object_buffer[loc]));
2037
                if constexpr (words_bigendian)
2038
                        object_rw_swap(reinterpret_cast<object_rw *>(&object_buffer[loc]), 1);
2039
                loc += sizeof(object_rw);
2040
        }
2041
 
2042
        if (obj_count_frame) // Send any objects we've buffered
2043
        {
2044
                Network_send_objnum = i;
2045
                PUT_INTEL_INT(&object_buffer[1], obj_count_frame);
2046
 
2047
                Assert(loc <= UPID_MAX_SIZE);
2048
 
2049
                dxx_sendto(UDP_sync_player.player.protocol.udp.addr, UDP_Socket[0], &object_buffer[0], loc, 0);
2050
        }
2051
 
2052
        if (i > Highest_object_index)
2053
        {
2054
                if (Network_send_object_mode == 0)
2055
                {
2056
                        Network_send_objnum = 0;
2057
                        Network_send_object_mode = 1; // go to next mode
2058
                }
2059
                else
2060
                {
2061
                        Assert(Network_send_object_mode == 1);
2062
 
2063
                        // Send count so other side can make sure he got them all
2064
                        object_buffer[0] = UPID_OBJECT_DATA;
2065
                        PUT_INTEL_INT(&object_buffer[1], 1);
2066
                        PUT_INTEL_INT(&object_buffer[5], network_checksum_marker_object);
2067
                        object_buffer[9] = player_num;
2068
                        PUT_INTEL_INT(&object_buffer[10], obj_count);
2069
                        dxx_sendto(UDP_sync_player.player.protocol.udp.addr, UDP_Socket[0], &object_buffer[0], 14, 0);
2070
 
2071
                        // Send sync packet which tells the player who he is and to start!
2072
                        net_udp_send_rejoin_sync(player_num);
2073
 
2074
                        // Turn off send object mode
2075
                        Network_send_objnum = -1;
2076
                        Network_send_objects = 0;
2077
                        obj_count = 0;
2078
 
2079
#if defined(DXX_BUILD_DESCENT_I)
2080
                        Network_sending_extras=3; // start to send extras
2081
#elif defined(DXX_BUILD_DESCENT_II)
2082
                        Network_sending_extras=9; // start to send extras
2083
#endif
2084
                        VerifyPlayerJoined = Player_joining_extras = player_num;
2085
 
2086
                        return;
2087
                } // mode == 1;
2088
        } // i > Highest_object_index
2089
}
2090
}
2091
 
2092
static int net_udp_verify_objects(int remote, int local)
2093
{
2094
        auto &Objects = LevelUniqueObjectState.Objects;
2095
        auto &vcobjptr = Objects.vcptr;
2096
        int nplayers = 0;
2097
 
2098
        if ((remote-local) > 10)
2099
                return(2);
2100
 
2101
        range_for (const auto &&objp, vcobjptr)
2102
        {
2103
                if (objp->type == OBJ_PLAYER || objp->type == OBJ_GHOST)
2104
                        nplayers++;
2105
        }
2106
 
2107
        if (Netgame.max_numplayers<=nplayers)
2108
                return(0);
2109
 
2110
        return(1);
2111
}
2112
 
2113
static void net_udp_read_object_packet( ubyte *data )
2114
{
2115
        auto &Objects = LevelUniqueObjectState.Objects;
2116
        auto &vmobjptridx = Objects.vmptridx;
2117
        // Object from another net player we need to sync with
2118
        sbyte obj_owner;
2119
        static int mode = 0, object_count = 0, my_pnum = 0;
2120
        int remote_objnum = 0, nobj = 0, loc = 5;
2121
 
2122
        nobj = GET_INTEL_INT(data + 1);
2123
 
2124
        for (int i = 0; i < nobj; i++)
2125
        {
2126
                const unsigned uobjnum = GET_INTEL_INT(data + loc);
2127
                objnum_t objnum = uobjnum;                         loc += 4;
2128
                obj_owner = data[loc];                                      loc += 1;
2129
                remote_objnum = GET_INTEL_INT(data + loc);                  loc += 4;
2130
 
2131
                if (objnum == object_none)
2132
                {
2133
                        // Clear object array
2134
                        init_objects();
2135
                        Network_rejoined = 1;
2136
                        my_pnum = obj_owner;
2137
                        change_playernum_to(my_pnum);
2138
                        mode = 1;
2139
                        object_count = 0;
2140
                }
2141
                else if (uobjnum == network_checksum_marker_object)
2142
                {
2143
                        // Special debug checksum marker for entire send
2144
                        if (mode == 1)
2145
                        {
2146
                                special_reset_objects(LevelUniqueObjectState);
2147
                                mode = 0;
2148
                        }
2149
                        if (remote_objnum != object_count) {
2150
                                Int3();
2151
                        }
2152
                        if (net_udp_verify_objects(remote_objnum, object_count))
2153
                        {
2154
                                // Failed to sync up 
2155
                                nm_messagebox(NULL, 1, TXT_OK, TXT_NET_SYNC_FAILED);
2156
                                Network_status = NETSTAT_MENU;                          
2157
                                return;
2158
                        }
2159
                }
2160
                else
2161
                {
2162
                        object_count++;
2163
                        if ((obj_owner == my_pnum) || (obj_owner == -1))
2164
                        {
2165
                                if (mode != 1)
2166
                                        Int3(); // SEE ROB
2167
                                objnum = remote_objnum;
2168
                        }
2169
                        else {
2170
                                if (mode == 1)
2171
                                {
2172
                                        special_reset_objects(LevelUniqueObjectState);
2173
                                        mode = 0;
2174
                                }
2175
                                objnum = obj_allocate(LevelUniqueObjectState);
2176
                        }
2177
                        if (objnum != object_none) {
2178
                                auto obj = vmobjptridx(objnum);
2179
                                if (obj->type != OBJ_NONE)
2180
                                {
2181
                                        obj_unlink(Objects.vmptr, Segments.vmptr, obj);
2182
                                        Assert(obj->segnum == segment_none);
2183
                                }
2184
                                Assert(objnum < MAX_OBJECTS);
2185
                                if constexpr (words_bigendian)
2186
                                        object_rw_swap(reinterpret_cast<object_rw *>(&data[loc]), 1);
2187
                                multi_object_rw_to_object(reinterpret_cast<object_rw *>(&data[loc]), obj);
2188
                                loc += sizeof(object_rw);
2189
                                auto segnum = obj->segnum;
2190
                                obj->attached_obj = object_none;
2191
                                if (segnum != segment_none)
2192
                                {
2193
                                        obj_link_unchecked(Objects.vmptr, obj, Segments.vmptridx(segnum));
2194
                                }
2195
                                if (obj_owner == my_pnum)
2196
                                        map_objnum_local_to_local(objnum);
2197
                                else if (obj_owner != -1)
2198
                                        map_objnum_local_to_remote(objnum, remote_objnum, obj_owner);
2199
                                else
2200
                                        object_owner[objnum] = -1;
2201
                        }
2202
                } // For a standard onbject
2203
        } // For each object in packet
2204
}
2205
 
2206
namespace dsx {
2207
 
2208
void net_udp_send_rejoin_sync(const unsigned player_num)
2209
{
2210
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
2211
        auto &Objects = LevelUniqueObjectState.Objects;
2212
        auto &vcobjptr = Objects.vcptr;
2213
        vmplayerptr(player_num)->connected = CONNECT_PLAYING; // connect the new guy
2214
        Netgame.players[player_num].LastPacketTime = timer_query();
2215
 
2216
        if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
2217
        {
2218
                // Endlevel started before we finished sending the goods, we'll
2219
                // have to stop and try again after the level.
2220
 
2221
                net_udp_dump_player(UDP_sync_player.player.protocol.udp.addr, DUMP_ENDLEVEL);
2222
 
2223
                Network_send_objects = 0;
2224
                Network_sending_extras=0;
2225
                return;
2226
        }
2227
 
2228
        if (Network_player_added)
2229
        {
2230
                UDP_sync_player.type = UPID_ADDPLAYER;
2231
                UDP_sync_player.player.connected = player_num;
2232
                net_udp_new_player(&UDP_sync_player);
2233
 
2234
                for (unsigned i = 0; i < N_players; ++i)
2235
                {
2236
                        if (i != player_num && i != Player_num && vcplayerptr(i)->connected)
2237
                                net_udp_send_sequence_packet( UDP_sync_player, Netgame.players[i].protocol.udp.addr);
2238
                }
2239
        }
2240
 
2241
        // Send sync packet to the new guy
2242
 
2243
        net_udp_update_netgame();
2244
 
2245
        // Fill in the kill list
2246
        Netgame.kills = kill_matrix;
2247
        for (unsigned j = 0; j < MAX_PLAYERS; ++j)
2248
        {
2249
                auto &objp = *vcobjptr(vcplayerptr(j)->objnum);
2250
                auto &player_info = objp.ctype.player_info;
2251
                Netgame.killed[j] = player_info.net_killed_total;
2252
                Netgame.player_kills[j] = player_info.net_kills_total;
2253
                Netgame.player_score[j] = player_info.mission.score;
2254
        }
2255
 
2256
        Netgame.level_time = get_local_player().time_level;
2257
        Netgame.monitor_vector = net_udp_create_monitor_vector();
2258
 
2259
        net_udp_send_game_info(UDP_sync_player.player.protocol.udp.addr, &UDP_sync_player.player.protocol.udp.addr, UPID_SYNC);
2260
#if defined(DXX_BUILD_DESCENT_I)
2261
        net_udp_send_door_updates();
2262
#endif
2263
 
2264
        return;
2265
}
2266
}
2267
 
2268
static void net_udp_resend_sync_due_to_packet_loss()
2269
{
2270
        auto &Objects = LevelUniqueObjectState.Objects;
2271
        auto &vcobjptr = Objects.vcptr;
2272
        if (!multi_i_am_master())
2273
                return;
2274
 
2275
        net_udp_update_netgame();
2276
 
2277
        // Fill in the kill list
2278
        Netgame.kills = kill_matrix;
2279
        for (unsigned j = 0; j < MAX_PLAYERS; ++j)
2280
        {
2281
                auto &objp = *vcobjptr(vcplayerptr(j)->objnum);
2282
                auto &player_info = objp.ctype.player_info;
2283
                Netgame.killed[j] = player_info.net_killed_total;
2284
                Netgame.player_kills[j] = player_info.net_kills_total;
2285
                Netgame.player_score[j] = player_info.mission.score;
2286
        }
2287
 
2288
        Netgame.level_time = get_local_player().time_level;
2289
        Netgame.monitor_vector = net_udp_create_monitor_vector();
2290
 
2291
        net_udp_send_game_info(UDP_sync_player.player.protocol.udp.addr, &UDP_sync_player.player.protocol.udp.addr, UPID_SYNC);
2292
}
2293
 
2294
static void net_udp_add_player(UDP_sequence_packet *p)
2295
{
2296
        auto &Objects = LevelUniqueObjectState.Objects;
2297
        auto &vmobjptr = Objects.vmptr;
2298
        range_for (auto &i, partial_range(Netgame.players, N_players))
2299
        {
2300
                if (i.protocol.udp.addr == p->player.protocol.udp.addr)
2301
                {
2302
                        i.LastPacketTime = timer_query();
2303
                        return;         // already got them
2304
                }
2305
        }
2306
 
2307
        if ( N_players >= MAX_PLAYERS )
2308
        {
2309
                return;         // too many of em
2310
        }
2311
 
2312
        ClipRank (&p->player.rank);
2313
        Netgame.players[N_players].callsign = p->player.callsign;
2314
        Netgame.players[N_players].protocol.udp.addr = p->player.protocol.udp.addr;
2315
        Netgame.players[N_players].rank=p->player.rank;
2316
        Netgame.players[N_players].connected = CONNECT_PLAYING;
2317
        auto &obj = *vmobjptr(vcplayerptr(N_players)->objnum);
2318
        auto &player_info = obj.ctype.player_info;
2319
        player_info.KillGoalCount = 0;
2320
        vmplayerptr(N_players)->connected = CONNECT_PLAYING;
2321
        Netgame.players[N_players].LastPacketTime = timer_query();
2322
        N_players++;
2323
        Netgame.numplayers = N_players;
2324
 
2325
        net_udp_send_netgame_update();
2326
}
2327
 
2328
// One of the players decided not to join the game
2329
 
2330
static void net_udp_remove_player(UDP_sequence_packet *p)
2331
{
2332
        int pn;
2333
 
2334
        pn = -1;
2335
        for (int i=0; i<N_players; i++ )
2336
        {
2337
                if (Netgame.players[i].protocol.udp.addr == p->player.protocol.udp.addr)
2338
                {
2339
                        pn = i;
2340
                        break;
2341
                }
2342
        }
2343
 
2344
        if (pn < 0 )
2345
                return;
2346
 
2347
        for (int i=pn; i<N_players-1; i++ )
2348
        {
2349
                Netgame.players[i].callsign = Netgame.players[i+1].callsign;
2350
                Netgame.players[i].protocol.udp.addr = Netgame.players[i+1].protocol.udp.addr;
2351
                Netgame.players[i].rank=Netgame.players[i+1].rank;
2352
                ClipRank (&Netgame.players[i].rank);
2353
        }
2354
 
2355
        N_players--;
2356
        Netgame.numplayers = N_players;
2357
 
2358
        net_udp_send_netgame_update();
2359
}
2360
 
2361
void net_udp_dump_player(const _sockaddr &dump_addr, int why)
2362
{
2363
        // Inform player that he was not chosen for the netgame
2364
        std::array<uint8_t, UPID_DUMP_SIZE> buf;
2365
        buf[0] = UPID_DUMP;
2366
        buf[1] = why;
2367
        dxx_sendto(dump_addr, UDP_Socket[0], buf, 0);
2368
        if (multi_i_am_master())
2369
                for (playernum_t i = 1; i < N_players; i++)
2370
                        if (dump_addr == Netgame.players[i].protocol.udp.addr)
2371
                                multi_disconnect_player(i);
2372
}
2373
 
2374
namespace dsx {
2375
void net_udp_update_netgame(void)
2376
{
2377
        auto &Objects = LevelUniqueObjectState.Objects;
2378
        auto &vcobjptr = Objects.vcptr;
2379
        // Update the netgame struct with current game variables
2380
        Netgame.numconnected=0;
2381
        range_for (auto &i, partial_const_range(Players, N_players))
2382
                if (i.connected)
2383
                        Netgame.numconnected++;
2384
 
2385
#if defined(DXX_BUILD_DESCENT_II)
2386
// This is great: D2 1.0 and 1.1 ignore upper part of the game_flags field of
2387
//      the lite_info struct when you're sitting on the join netgame screen.  We can
2388
//      "sneak" Hoard information into this field.  This is better than sending 
2389
//      another packet that could be lost in transit.
2390
 
2391
        if (HoardEquipped())
2392
        {
2393
                if (game_mode_hoard())
2394
                {
2395
                        Netgame.game_flag.hoard = 1;
2396
                        Netgame.game_flag.team_hoard = !!(Game_mode & GM_TEAM);
2397
                }
2398
                else
2399
                {
2400
                        Netgame.game_flag.hoard = 0;
2401
                        Netgame.game_flag.team_hoard = 0;
2402
                }
2403
        }
2404
#endif
2405
        if (Network_status == NETSTAT_STARTING)
2406
                return;
2407
 
2408
        Netgame.numplayers = N_players;
2409
        Netgame.game_status = Network_status;
2410
 
2411
        Netgame.kills = kill_matrix;
2412
        for (unsigned i = 0; i < MAX_PLAYERS; ++i)
2413
        {
2414
                auto &plr = *vcplayerptr(i);
2415
                Netgame.players[i].connected = plr.connected;
2416
                auto &objp = *vcobjptr(plr.objnum);
2417
                auto &player_info = objp.ctype.player_info;
2418
                Netgame.killed[i] = player_info.net_killed_total;
2419
                Netgame.player_kills[i] = player_info.net_kills_total;
2420
#if defined(DXX_BUILD_DESCENT_II)
2421
                Netgame.player_score[i] = player_info.mission.score;
2422
#endif
2423
                Netgame.net_player_flags[i] = player_info.powerup_flags;
2424
        }
2425
        Netgame.team_kills = team_kills;
2426
        Netgame.levelnum = Current_level_num;
2427
}
2428
}
2429
 
2430
/* Send an updated endlevel status to everyone (if we are host) or host (if we are client)  */
2431
void net_udp_send_endlevel_packet(void)
2432
{
2433
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
2434
        auto &Objects = LevelUniqueObjectState.Objects;
2435
        auto &vcobjptr = Objects.vcptr;
2436
        auto &vmobjptr = Objects.vmptr;
2437
        int len = 0;
2438
 
2439
        if (multi_i_am_master())
2440
        {
2441
                std::array<uint8_t, 2 + (5 * Players.size()) + sizeof(kill_matrix)> buf;
2442
                buf[len] = UPID_ENDLEVEL_H;                                                                                     len++;
2443
                buf[len] = LevelUniqueControlCenterState.Countdown_seconds_left;                                len++;
2444
 
2445
                range_for (auto &i, Players)
2446
                {
2447
                        buf[len] = i.connected;                                                         len++;
2448
                        auto &objp = *vcobjptr(i.objnum);
2449
                        auto &player_info = objp.ctype.player_info;
2450
                        PUT_INTEL_SHORT(&buf[len], player_info.net_kills_total);
2451
                        len += 2;
2452
                        PUT_INTEL_SHORT(&buf[len], player_info.net_killed_total);
2453
                        len += 2;
2454
                }
2455
 
2456
                range_for (auto &i, kill_matrix)
2457
                {
2458
                        range_for (auto &j, i)
2459
                        {
2460
                                PUT_INTEL_SHORT(&buf[len], j);                          len += 2;
2461
                        }
2462
                }
2463
 
2464
                for (unsigned i = 1; i < MAX_PLAYERS; ++i)
2465
                        if (vcplayerptr(i)->connected != CONNECT_DISCONNECTED)
2466
                                dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], buf, 0);
2467
        }
2468
        else
2469
        {
2470
                std::array<uint8_t, 8 + sizeof(kill_matrix[0])> buf;
2471
                buf[len] = UPID_ENDLEVEL_C;                                                                                     len++;
2472
                buf[len] = Player_num;                                                                                          len++;
2473
                buf[len] = get_local_player().connected;                                                        len++;
2474
                buf[len] = LevelUniqueControlCenterState.Countdown_seconds_left;                                len++;
2475
                auto &player_info = get_local_plrobj().ctype.player_info;
2476
                PUT_INTEL_SHORT(&buf[len], player_info.net_kills_total);
2477
                len += 2;
2478
                PUT_INTEL_SHORT(&buf[len], player_info.net_killed_total);
2479
                len += 2;
2480
 
2481
                range_for (auto &i, kill_matrix[Player_num])
2482
                {
2483
                        PUT_INTEL_SHORT(&buf[len], i);
2484
                        len += 2;
2485
                }
2486
 
2487
                dxx_sendto(Netgame.players[0].protocol.udp.addr, UDP_Socket[0], buf, 0);
2488
        }
2489
}
2490
 
2491
static void net_udp_send_version_deny(const _sockaddr &sender_addr)
2492
{
2493
        std::array<uint8_t, UPID_VERSION_DENY_SIZE> buf;
2494
        buf[0] = UPID_VERSION_DENY;
2495
        PUT_INTEL_SHORT(&buf[1], DXX_VERSION_MAJORi);
2496
        PUT_INTEL_SHORT(&buf[3], DXX_VERSION_MINORi);
2497
        PUT_INTEL_SHORT(&buf[5], DXX_VERSION_MICROi);
2498
        PUT_INTEL_SHORT(&buf[7], MULTI_PROTO_VERSION);
2499
        dxx_sendto(sender_addr, UDP_Socket[0], buf, 0);
2500
}
2501
 
2502
static void net_udp_process_version_deny(ubyte *data, const _sockaddr &)
2503
{
2504
        Netgame.protocol.udp.program_iver[0] = GET_INTEL_SHORT(&data[1]);
2505
        Netgame.protocol.udp.program_iver[1] = GET_INTEL_SHORT(&data[3]);
2506
        Netgame.protocol.udp.program_iver[2] = GET_INTEL_SHORT(&data[5]);
2507
        Netgame.protocol.udp.program_iver[3] = GET_INTEL_SHORT(&data[7]);
2508
        Netgame.protocol.udp.valid = -1;
2509
}
2510
 
2511
// Check request for game info. Return 1 if sucessful; -1 if version mismatch; 0 if wrong game or some other error - do not process
2512
static int net_udp_check_game_info_request(ubyte *data, int lite)
2513
{
2514
        short sender_iver[4] = { 0, 0, 0, 0 };
2515
        char sender_id[4] = "";
2516
 
2517
        memcpy(&sender_id, &(data[1]), 4);
2518
        sender_iver[0] = GET_INTEL_SHORT(&(data[5]));
2519
        sender_iver[1] = GET_INTEL_SHORT(&(data[7]));
2520
        sender_iver[2] = GET_INTEL_SHORT(&(data[9]));
2521
        if (!lite)
2522
                sender_iver[3] = GET_INTEL_SHORT(&(data[11]));
2523
 
2524
        if (memcmp(&sender_id, UDP_REQ_ID, 4))
2525
                return 0;
2526
 
2527
        if ((sender_iver[0] != DXX_VERSION_MAJORi) || (sender_iver[1] != DXX_VERSION_MINORi) || (sender_iver[2] != DXX_VERSION_MICROi) || (!lite && sender_iver[3] != MULTI_PROTO_VERSION))
2528
                return -1;
2529
 
2530
        return 1;
2531
}
2532
 
2533
namespace {
2534
 
2535
struct game_info_light
2536
{
2537
        std::array<uint8_t, UPID_GAME_INFO_LITE_SIZE_MAX> buf;
2538
};
2539
 
2540
struct game_info_heavy
2541
{
2542
        std::array<uint8_t, UPID_GAME_INFO_SIZE_MAX> buf;
2543
};
2544
 
2545
static uint_fast32_t net_udp_prepare_light_game_info(game_info_light &info)
2546
{
2547
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
2548
        uint_fast32_t len = 0;
2549
        uint8_t *const buf = info.buf.data();
2550
                buf[0] = UPID_GAME_INFO_LITE;                                                           len++;                          // 1
2551
                PUT_INTEL_SHORT(buf + len, DXX_VERSION_MAJORi);                                                 len += 2;                       // 3
2552
                PUT_INTEL_SHORT(buf + len, DXX_VERSION_MINORi);                                                 len += 2;                       // 5
2553
                PUT_INTEL_SHORT(buf + len, DXX_VERSION_MICROi);                                                 len += 2;                       // 7
2554
                PUT_INTEL_INT(buf + len, Netgame.protocol.udp.GameID);                          len += 4;                       // 11
2555
                PUT_INTEL_INT(buf + len, Netgame.levelnum);                                     len += 4;
2556
                buf[len] = Netgame.gamemode;                                                    len++;
2557
                buf[len] = Netgame.RefusePlayers;                                               len++;
2558
                buf[len] = Netgame.difficulty;                                                  len++;
2559
        const auto tmpvar = get_effective_netgame_status(LevelUniqueControlCenterState);
2560
                buf[len] = tmpvar;                                                              len++;
2561
                buf[len] = Netgame.numconnected;                                                len++;
2562
                buf[len] = Netgame.max_numplayers;                                              len++;
2563
                buf[len] = pack_game_flags(&Netgame.game_flag).value;                                                   len++;
2564
                copy_from_ntstring(buf, len, Netgame.game_name);
2565
                copy_from_ntstring(buf, len, Netgame.mission_title);
2566
                copy_from_ntstring(buf, len, Netgame.mission_name);
2567
        return len;
2568
}
2569
 
2570
static uint_fast32_t net_udp_prepare_heavy_game_info(const _sockaddr *addr, ubyte info_upid, game_info_heavy &info)
2571
{
2572
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
2573
        uint8_t *const buf = info.buf.data();
2574
        uint_fast32_t len = 0;
2575
 
2576
                buf[0] = info_upid;                                                             len++;
2577
                PUT_INTEL_SHORT(buf + len, DXX_VERSION_MAJORi);                                                 len += 2;
2578
                PUT_INTEL_SHORT(buf + len, DXX_VERSION_MINORi);                                                 len += 2;
2579
                PUT_INTEL_SHORT(buf + len, DXX_VERSION_MICROi);                                                 len += 2;
2580
                ubyte &your_index = buf[len++];
2581
                your_index = MULTI_PNUM_UNDEF;
2582
                for (int i = 0; i < Netgame.players.size(); i++)
2583
                {
2584
                        memcpy(&buf[len], Netgame.players[i].callsign.buffer(), CALLSIGN_LEN+1);        len += CALLSIGN_LEN+1;
2585
                        buf[len] = Netgame.players[i].connected;                                len++;
2586
                        buf[len] = Netgame.players[i].rank;                                     len++;
2587
                        if (addr && *addr == Netgame.players[i].protocol.udp.addr)
2588
                                your_index = i;
2589
                }
2590
                PUT_INTEL_INT(buf + len, Netgame.levelnum);                                     len += 4;
2591
                buf[len] = Netgame.gamemode;                                                    len++;
2592
                buf[len] = Netgame.RefusePlayers;                                               len++;
2593
                buf[len] = Netgame.difficulty;                                                  len++;
2594
        const auto tmpvar = get_effective_netgame_status(LevelUniqueControlCenterState);
2595
                buf[len] = tmpvar;                                                              len++;
2596
                buf[len] = Netgame.numplayers;                                                  len++;
2597
                buf[len] = Netgame.max_numplayers;                                              len++;
2598
                buf[len] = Netgame.numconnected;                                                len++;
2599
                buf[len] = pack_game_flags(&Netgame.game_flag).value;                                                   len++;
2600
                buf[len] = Netgame.team_vector;                                                 len++;
2601
                PUT_INTEL_INT(buf + len, Netgame.AllowedItems);                                 len += 4;
2602
                /* In cooperative games, never shuffle. */
2603
                PUT_INTEL_INT(&buf[len], (Game_mode & GM_MULTI_COOP) ? 0 : Netgame.ShufflePowerupSeed);                 len += 4;
2604
                buf[len] = Netgame.SecludedSpawns;                      len += 1;
2605
#if defined(DXX_BUILD_DESCENT_I)
2606
                buf[len] = Netgame.SpawnGrantedItems.mask;                      len += 1;
2607
                buf[len] = Netgame.DuplicatePowerups.get_packed_field();                        len += 1;
2608
#elif defined(DXX_BUILD_DESCENT_II)
2609
                PUT_INTEL_SHORT(buf + len, Netgame.SpawnGrantedItems.mask);                     len += 2;
2610
                PUT_INTEL_SHORT(buf + len, Netgame.DuplicatePowerups.get_packed_field());                       len += 2;
2611
                buf[len++] = Netgame.Allow_marker_view;
2612
                buf[len++] = Netgame.AlwaysLighting;
2613
                buf[len++] = Netgame.ThiefModifierFlags;
2614
                buf[len++] = Netgame.AllowGuidebot;
2615
#endif
2616
                buf[len++] = Netgame.ShowEnemyNames;
2617
                buf[len++] = Netgame.BrightPlayers;
2618
                buf[len++] = Netgame.InvulAppear;
2619
                range_for (const auto &i, Netgame.team_name)
2620
                {
2621
                        memcpy(&buf[len], static_cast<const char *>(i), (CALLSIGN_LEN+1));
2622
                        len += CALLSIGN_LEN + 1;
2623
                }
2624
                range_for (auto &i, Netgame.locations)
2625
                {
2626
                        PUT_INTEL_INT(buf + len, i);                            len += 4;
2627
                }
2628
                range_for (auto &i, Netgame.kills)
2629
                {
2630
                        range_for (auto &j, i)
2631
                        {
2632
                                PUT_INTEL_SHORT(buf + len, j);          len += 2;
2633
                        }
2634
                }
2635
                PUT_INTEL_SHORT(buf + len, Netgame.segments_checksum);                  len += 2;
2636
                PUT_INTEL_SHORT(buf + len, Netgame.team_kills[0]);                              len += 2;
2637
                PUT_INTEL_SHORT(buf + len, Netgame.team_kills[1]);                              len += 2;
2638
                range_for (auto &i, Netgame.killed)
2639
                {
2640
                        PUT_INTEL_SHORT(buf + len, i);                          len += 2;
2641
                }
2642
                range_for (auto &i, Netgame.player_kills)
2643
                {
2644
                        PUT_INTEL_SHORT(buf + len, i);                  len += 2;
2645
                }
2646
                PUT_INTEL_INT(buf + len, Netgame.KillGoal);                                     len += 4;
2647
                PUT_INTEL_INT(buf + len, Netgame.PlayTimeAllowed.count());                              len += 4;
2648
                PUT_INTEL_INT(buf + len, Netgame.level_time);                                   len += 4;
2649
                PUT_INTEL_INT(buf + len, Netgame.control_invul_time);                           len += 4;
2650
                PUT_INTEL_INT(buf + len, Netgame.monitor_vector);                               len += 4;
2651
                range_for (auto &i, Netgame.player_score)
2652
                {
2653
                        PUT_INTEL_INT(buf + len, i);                    len += 4;
2654
                }
2655
                range_for (auto &i, Netgame.net_player_flags)
2656
                {
2657
                        buf[len] = static_cast<uint8_t>(i.get_player_flags());
2658
                        len++;
2659
                }
2660
                PUT_INTEL_SHORT(buf + len, Netgame.PacketsPerSec);                              len += 2;
2661
                buf[len] = Netgame.PacketLossPrevention;                                        len++;
2662
                buf[len] = Netgame.NoFriendlyFire;                                              len++;
2663
                buf[len] = Netgame.MouselookFlags;                                              len++;
2664
                copy_from_ntstring(buf, len, Netgame.game_name);
2665
                copy_from_ntstring(buf, len, Netgame.mission_title);
2666
                copy_from_ntstring(buf, len, Netgame.mission_name);
2667
        return len;
2668
}
2669
 
2670
void net_udp_send_game_info_t::apply(const sockaddr &sender_addr, socklen_t senderlen, const _sockaddr *player_address, ubyte info_upid)
2671
{
2672
        // Send game info to someone who requested it
2673
        net_udp_update_netgame(); // Update the values in the netgame struct
2674
        union {
2675
                game_info_light light;
2676
                game_info_heavy heavy;
2677
        };
2678
        std::size_t len;
2679
        const uint8_t *info;
2680
        if (info_upid == UPID_GAME_INFO_LITE)
2681
        {
2682
                len = net_udp_prepare_light_game_info(light);
2683
                info = light.buf.data();
2684
        }
2685
        else
2686
        {
2687
                len = net_udp_prepare_heavy_game_info(player_address, info_upid, heavy);
2688
                info = heavy.buf.data();
2689
        }
2690
        dxx_sendto(sender_addr, senderlen, UDP_Socket[0], info, len, 0);
2691
}
2692
 
2693
}
2694
 
2695
static unsigned MouselookMPFlag(const unsigned game_mode)
2696
{
2697
        return (game_mode & GM_MULTI_COOP) ? MouselookMode::MPCoop : MouselookMode::MPAnarchy;
2698
}
2699
 
2700
static void net_udp_broadcast_game_info(ubyte info_upid)
2701
{
2702
        net_udp_send_game_info(GBcast, nullptr, info_upid);
2703
#if DXX_USE_IPv6
2704
        net_udp_send_game_info(GMcast_v6, nullptr, info_upid);
2705
#endif
2706
}
2707
 
2708
/* Send game info to all players in this game. Also send lite_info for people watching the netlist */
2709
void net_udp_send_netgame_update()
2710
{
2711
        for (unsigned i = 1; i < N_players; ++i)
2712
        {
2713
                if (vcplayerptr(i)->connected == CONNECT_DISCONNECTED)
2714
                        continue;
2715
                const auto &addr = Netgame.players[i].protocol.udp.addr;
2716
                net_udp_send_game_info(addr, &addr, UPID_GAME_INFO);
2717
        }
2718
        net_udp_broadcast_game_info(UPID_GAME_INFO_LITE);
2719
}
2720
 
2721
static unsigned net_udp_send_request(void)
2722
{
2723
        // Send a request to join a game 'Netgame'.  Returns 0 if we can join this
2724
        // game, non-zero if there is some problem.
2725
        auto b = Netgame.players.begin();
2726
        auto e = Netgame.players.end();
2727
        auto i = std::find_if(b, e, [](const netplayer_info &ni) { return ni.connected != 0; });
2728
        if (i == e)
2729
        {
2730
                Assert(false);
2731
                return std::distance(b, i);
2732
        }
2733
        UDP_Seq.type = UPID_REQUEST;
2734
        UDP_Seq.player.connected = Current_level_num;
2735
 
2736
        net_udp_send_sequence_packet(UDP_Seq, Netgame.players[0].protocol.udp.addr);
2737
        return std::distance(b, i);
2738
}
2739
 
2740
namespace dsx {
2741
static void net_udp_process_game_info(const uint8_t *data, uint_fast32_t, const _sockaddr &game_addr, int lite_info, uint16_t TrackerGameID)
2742
{
2743
        uint_fast32_t len = 0;
2744
        if (lite_info)
2745
        {
2746
                UDP_netgame_info_lite recv_game;
2747
 
2748
                recv_game.game_addr = game_addr;
2749
                                                                                                len++; // skip UPID byte
2750
                recv_game.program_iver[0] = GET_INTEL_SHORT(&(data[len]));                      len += 2;
2751
                recv_game.program_iver[1] = GET_INTEL_SHORT(&(data[len]));                      len += 2;
2752
                recv_game.program_iver[2] = GET_INTEL_SHORT(&(data[len]));                      len += 2;
2753
 
2754
                if ((recv_game.program_iver[0] != DXX_VERSION_MAJORi) || (recv_game.program_iver[1] != DXX_VERSION_MINORi) || (recv_game.program_iver[2] != DXX_VERSION_MICROi))
2755
                        return;
2756
 
2757
                recv_game.GameID = GET_INTEL_INT(&(data[len]));                                 len += 4;
2758
                recv_game.levelnum = GET_INTEL_INT(&(data[len]));                               len += 4;
2759
                recv_game.gamemode = data[len];                                                 len++;
2760
                recv_game.RefusePlayers = data[len];                                            len++;
2761
                recv_game.difficulty = data[len];                                               len++;
2762
                recv_game.game_status = data[len];                                              len++;
2763
                recv_game.numconnected = data[len];                                             len++;
2764
                recv_game.max_numplayers = data[len];                                           len++;
2765
                packed_game_flags p;
2766
                p.value = data[len];
2767
                recv_game.game_flag = unpack_game_flags(&p);                                            len++;
2768
                copy_to_ntstring(data, len, recv_game.game_name);
2769
                copy_to_ntstring(data, len, recv_game.mission_title);
2770
                copy_to_ntstring(data, len, recv_game.mission_name);
2771
                recv_game.TrackerGameID = TrackerGameID;
2772
 
2773
                num_active_udp_changed = 1;
2774
 
2775
                auto r = partial_range(Active_udp_games, num_active_udp_games);
2776
                auto i = std::find_if(r.begin(), r.end(), [&recv_game](const UDP_netgame_info_lite &g) { return !d_stricmp(g.game_name.data(), recv_game.game_name.data()) && g.GameID == recv_game.GameID; });
2777
                if (i == Active_udp_games.end())
2778
                {
2779
                        return;
2780
                }
2781
 
2782
                *i = std::move(recv_game);
2783
#if defined(DXX_BUILD_DESCENT_II)
2784
                // See if this is really a Hoard game
2785
                // If so, adjust all the data accordingly
2786
                if (HoardEquipped())
2787
                {
2788
                        if (i->game_flag.hoard)
2789
                        {
2790
                                i->gamemode=NETGAME_HOARD;
2791
                                i->game_status=NETSTAT_PLAYING;
2792
 
2793
                                if (i->game_flag.team_hoard)
2794
                                        i->gamemode=NETGAME_TEAM_HOARD;
2795
                                if (i->game_flag.endlevel)
2796
                                        i->game_status=NETSTAT_ENDLEVEL;
2797
                                if (i->game_flag.forming)
2798
                                        i->game_status=NETSTAT_STARTING;
2799
                        }
2800
                }
2801
#endif
2802
                if (i == r.end())
2803
                {
2804
                        if (i->numconnected)
2805
                                num_active_udp_games++;
2806
                }
2807
                else if (!i->numconnected)
2808
                {
2809
                        // Delete this game
2810
                        std::move(std::next(i), r.end(), i);
2811
                        num_active_udp_games--;
2812
                }
2813
        }
2814
        else
2815
        {
2816
                Netgame.players[0].protocol.udp.addr = game_addr;
2817
 
2818
                                                                                                len++; // skip UPID byte
2819
                Netgame.protocol.udp.program_iver[0] = GET_INTEL_SHORT(&(data[len]));           len += 2;
2820
                Netgame.protocol.udp.program_iver[1] = GET_INTEL_SHORT(&(data[len]));           len += 2;
2821
                Netgame.protocol.udp.program_iver[2] = GET_INTEL_SHORT(&(data[len]));           len += 2;
2822
                Netgame.protocol.udp.your_index = data[len]; ++len;
2823
                range_for (auto &i, Netgame.players)
2824
                {
2825
                        i.callsign.copy_lower(reinterpret_cast<const char *>(&data[len]), CALLSIGN_LEN);
2826
                        len += CALLSIGN_LEN+1;
2827
                        i.connected = data[len];                                len++;
2828
                        i.rank = data[len];                                     len++;
2829
                }
2830
                Netgame.levelnum = GET_INTEL_INT(&(data[len]));                                 len += 4;
2831
                Netgame.gamemode = data[len];                                                   len++;
2832
                Netgame.RefusePlayers = data[len];                                              len++;
2833
                Netgame.difficulty = cast_clamp_difficulty(data[len]);
2834
                len++;
2835
                Netgame.game_status = data[len];                                                len++;
2836
                Netgame.numplayers = data[len];                                                 len++;
2837
                Netgame.max_numplayers = data[len];                                             len++;
2838
                Netgame.numconnected = data[len];                                               len++;
2839
                packed_game_flags p;
2840
                p.value = data[len];
2841
                Netgame.game_flag = unpack_game_flags(&p);                                              len++;
2842
                Netgame.team_vector = data[len];                                                len++;
2843
                Netgame.AllowedItems = GET_INTEL_INT(&(data[len]));                             len += 4;
2844
                Netgame.ShufflePowerupSeed = GET_INTEL_INT(&(data[len]));               len += 4;
2845
                Netgame.SecludedSpawns = data[len];             len += 1;
2846
#if defined(DXX_BUILD_DESCENT_I)
2847
                Netgame.SpawnGrantedItems = data[len];          len += 1;
2848
                Netgame.DuplicatePowerups.set_packed_field(data[len]);                  len += 1;
2849
#elif defined(DXX_BUILD_DESCENT_II)
2850
                Netgame.SpawnGrantedItems = GET_INTEL_SHORT(&(data[len]));              len += 2;
2851
                Netgame.DuplicatePowerups.set_packed_field(GET_INTEL_SHORT(&data[len])); len += 2;
2852
                if (unlikely(map_granted_flags_to_laser_level(Netgame.SpawnGrantedItems) > MAX_SUPER_LASER_LEVEL))
2853
                        /* Bogus input - reject whole entry */
2854
                        Netgame.SpawnGrantedItems = 0;
2855
                Netgame.Allow_marker_view = data[len++];
2856
                Netgame.AlwaysLighting = data[len++];
2857
                Netgame.ThiefModifierFlags = data[len++];
2858
                Netgame.AllowGuidebot = data[len++];
2859
#endif
2860
                Netgame.ShowEnemyNames = data[len];                             len += 1;
2861
                Netgame.BrightPlayers = data[len];                              len += 1;
2862
                Netgame.InvulAppear = data[len];                                len += 1;
2863
                range_for (auto &i, Netgame.team_name)
2864
                {
2865
                        i.copy(reinterpret_cast<const char *>(&data[len]), (CALLSIGN_LEN+1));
2866
                        len += CALLSIGN_LEN + 1;
2867
                }
2868
                range_for (auto &i, Netgame.locations)
2869
                {
2870
                        i = GET_INTEL_INT(&(data[len]));                        len += 4;
2871
                }
2872
                range_for (auto &i, Netgame.kills)
2873
                {
2874
                        range_for (auto &j, i)
2875
                        {
2876
                                j = GET_INTEL_SHORT(&(data[len]));              len += 2;
2877
                        }
2878
                }
2879
                Netgame.segments_checksum = GET_INTEL_SHORT(&(data[len]));                      len += 2;
2880
                Netgame.team_kills[0] = GET_INTEL_SHORT(&(data[len]));                          len += 2;      
2881
                Netgame.team_kills[1] = GET_INTEL_SHORT(&(data[len]));                          len += 2;
2882
                range_for (auto &i, Netgame.killed)
2883
                {
2884
                        i = GET_INTEL_SHORT(&(data[len]));                      len += 2;
2885
                }
2886
                range_for (auto &i, Netgame.player_kills)
2887
                {
2888
                        i = GET_INTEL_SHORT(&(data[len]));              len += 2;
2889
                }
2890
                Netgame.KillGoal = GET_INTEL_INT(&(data[len]));                                 len += 4;
2891
                Netgame.PlayTimeAllowed = d_time_fix(GET_INTEL_INT(&data[len]));
2892
                len += 4;
2893
                Netgame.level_time = GET_INTEL_INT(&(data[len]));                               len += 4;
2894
                Netgame.control_invul_time = GET_INTEL_INT(&(data[len]));                       len += 4;
2895
                Netgame.monitor_vector = GET_INTEL_INT(&(data[len]));                           len += 4;
2896
                range_for (auto &i, Netgame.player_score)
2897
                {
2898
                        i = GET_INTEL_INT(&(data[len]));                        len += 4;
2899
                }
2900
                range_for (auto &i, Netgame.net_player_flags)
2901
                {
2902
                        i = player_flags(data[len]);
2903
                        len++;
2904
                }
2905
                Netgame.PacketsPerSec = GET_INTEL_SHORT(&(data[len]));                          len += 2;
2906
                Netgame.PacketLossPrevention = data[len];                                       len++;
2907
                Netgame.NoFriendlyFire = data[len];                                             len++;
2908
                Netgame.MouselookFlags = data[len];                                             len++;
2909
                copy_to_ntstring(data, len, Netgame.game_name);
2910
                copy_to_ntstring(data, len, Netgame.mission_title);
2911
                copy_to_ntstring(data, len, Netgame.mission_name);
2912
 
2913
                Netgame.protocol.udp.valid = 1; // This game is valid! YAY!
2914
        }
2915
}
2916
}
2917
 
2918
static void net_udp_process_dump(ubyte *data, int, const _sockaddr &sender_addr)
2919
{
2920
        // Our request for join was denied.  Tell the user why.
2921
        if (sender_addr != Netgame.players[0].protocol.udp.addr)
2922
                return;
2923
 
2924
        switch (data[1])
2925
        {
2926
                case DUMP_PKTTIMEOUT:
2927
                case DUMP_KICKED:
2928
                        if (Game_wind)
2929
                                window_set_visible(Game_wind, 0);
2930
                        if (data[1] == DUMP_PKTTIMEOUT)
2931
                                nm_messagebox(NULL, 1, TXT_OK, "You were removed from the game.\nYou failed receiving important\npackets. Sorry.");
2932
                        if (data[1] == DUMP_KICKED)
2933
                                nm_messagebox(NULL, 1, TXT_OK, "You were kicked by Host!");
2934
                        if (Game_wind)
2935
                                window_set_visible(Game_wind, 1);
2936
                        multi_quit_game = 1;
2937
                        game_leave_menus();
2938
                        break;
2939
                default:
2940
                        if (data[1] > DUMP_LEVEL) // invalid dump... heh
2941
                                break;
2942
                        Network_status = NETSTAT_MENU; // stop us from sending before message
2943
                        nm_messagebox_str(NULL, TXT_OK, NET_DUMP_STRINGS(data[1]));
2944
                        Network_status = NETSTAT_MENU;
2945
                        multi_reset_stuff();
2946
                        break;
2947
        }
2948
}
2949
 
2950
static void net_udp_process_request(UDP_sequence_packet *their)
2951
{
2952
        // Player is ready to receieve a sync packet
2953
        for (unsigned i = 0; i < N_players; ++i)
2954
                if (their->player.protocol.udp.addr == Netgame.players[i].protocol.udp.addr && !d_stricmp(their->player.callsign, Netgame.players[i].callsign))
2955
                {
2956
                        vmplayerptr(i)->connected = CONNECT_PLAYING;
2957
                        Netgame.players[i].LastPacketTime = timer_query();
2958
                        break;
2959
                }
2960
}
2961
 
2962
static void net_udp_process_packet(ubyte *data, const _sockaddr &sender_addr, int length )
2963
{
2964
        UDP_sequence_packet their{};
2965
 
2966
        switch (data[0])
2967
        {
2968
                case UPID_VERSION_DENY:
2969
                        if (multi_i_am_master() || length != UPID_VERSION_DENY_SIZE)
2970
                                break;
2971
                        net_udp_process_version_deny(data, sender_addr);
2972
                        break;
2973
                case UPID_GAME_INFO_REQ:
2974
                {
2975
                        int result = 0;
2976
                        static fix64 last_full_req_time = 0;
2977
                        if (!multi_i_am_master() || length != UPID_GAME_INFO_REQ_SIZE)
2978
                                break;
2979
                        if (timer_query() < last_full_req_time+(F1_0/2)) // answer 2 times per second max
2980
                                break;
2981
                        last_full_req_time = timer_query();
2982
                        result = net_udp_check_game_info_request(data, 0);
2983
                        if (result == -1)
2984
                                net_udp_send_version_deny(sender_addr);
2985
                        else if (result == 1)
2986
                                net_udp_send_game_info(sender_addr, &sender_addr, UPID_GAME_INFO);
2987
                        break;
2988
                }
2989
                case UPID_GAME_INFO:
2990
                        if (multi_i_am_master() || length > UPID_GAME_INFO_SIZE_MAX)
2991
                                break;
2992
                        net_udp_process_game_info(data, length, sender_addr, 0);
2993
                        break;
2994
                case UPID_GAME_INFO_LITE_REQ:
2995
                {
2996
                        static fix64 last_lite_req_time = 0;
2997
                        if (!multi_i_am_master() || length != UPID_GAME_INFO_LITE_REQ_SIZE)
2998
                                break;
2999
                        if (timer_query() < last_lite_req_time+(F1_0/8))// answer 8 times per second max
3000
                                break;
3001
                        last_lite_req_time = timer_query();
3002
                        if (net_udp_check_game_info_request(data, 1) == 1)
3003
                                net_udp_send_game_info(sender_addr, &sender_addr, UPID_GAME_INFO_LITE);
3004
                        break;
3005
                }
3006
                case UPID_GAME_INFO_LITE:
3007
                        if (multi_i_am_master() || length > UPID_GAME_INFO_LITE_SIZE_MAX)
3008
                                break;
3009
                        net_udp_process_game_info(data, length, sender_addr, 1);
3010
                        break;
3011
                case UPID_DUMP:
3012
                        if (multi_i_am_master() || Netgame.players[0].protocol.udp.addr != sender_addr || length != UPID_DUMP_SIZE)
3013
                                break;
3014
                        if ((Network_status == NETSTAT_WAITING) || (Network_status == NETSTAT_PLAYING))
3015
                                net_udp_process_dump(data, length, sender_addr);
3016
                        break;
3017
                case UPID_ADDPLAYER:
3018
                        if (multi_i_am_master() || Netgame.players[0].protocol.udp.addr != sender_addr || length != UPID_SEQUENCE_SIZE)
3019
                                break;
3020
                        net_udp_receive_sequence_packet(data, &their, sender_addr);
3021
                        net_udp_new_player(&their);
3022
                        break;
3023
                case UPID_REQUEST:
3024
                        if (!multi_i_am_master() || length != UPID_SEQUENCE_SIZE)
3025
                                break;
3026
                        net_udp_receive_sequence_packet(data, &their, sender_addr);
3027
                        if (Network_status == NETSTAT_STARTING)
3028
                        {
3029
                                // Someone wants to join our game!
3030
                                net_udp_add_player(&their);
3031
                        }
3032
                        else if (Network_status == NETSTAT_WAITING)
3033
                        {
3034
                                // Someone is ready to recieve a sync packet
3035
                                net_udp_process_request(&their);
3036
                        }
3037
                        else if (Network_status == NETSTAT_PLAYING)
3038
                        {
3039
                                // Someone wants to join a game in progress!
3040
                                if (Netgame.RefusePlayers)
3041
                                        net_udp_do_refuse_stuff (&their);
3042
                                else
3043
                                        net_udp_welcome_player(&their);
3044
                        }
3045
                        break;
3046
                case UPID_QUIT_JOINING:
3047
                        if (!multi_i_am_master() || length != UPID_SEQUENCE_SIZE)
3048
                                break;
3049
                        net_udp_receive_sequence_packet(data, &their, sender_addr);
3050
                        if (Network_status == NETSTAT_STARTING)
3051
                                net_udp_remove_player( &their );
3052
                        else if ((Network_status == NETSTAT_PLAYING) && (Network_send_objects))
3053
                                net_udp_stop_resync( &their );
3054
                        break;
3055
                case UPID_SYNC:
3056
                        if (multi_i_am_master() || length > UPID_GAME_INFO_SIZE_MAX || Network_status != NETSTAT_WAITING)
3057
                                break;
3058
                        net_udp_read_sync_packet(data, length, sender_addr);
3059
                        break;
3060
                case UPID_OBJECT_DATA:
3061
                        if (multi_i_am_master() || length > UPID_MAX_SIZE || Network_status != NETSTAT_WAITING)
3062
                                break;
3063
                        net_udp_read_object_packet(data);
3064
                        break;
3065
                case UPID_PING:
3066
                        if (multi_i_am_master() || length != UPID_PING_SIZE)
3067
                                break;
3068
                        net_udp_process_ping(data, sender_addr);
3069
                        break;
3070
                case UPID_PONG:
3071
                        if (!multi_i_am_master() || length != UPID_PONG_SIZE)
3072
                                break;
3073
                        net_udp_process_pong(data, sender_addr);
3074
                        break;
3075
                case UPID_ENDLEVEL_H:
3076
                        if ((!multi_i_am_master()) && ((Network_status == NETSTAT_ENDLEVEL) || (Network_status == NETSTAT_PLAYING)))
3077
                                net_udp_read_endlevel_packet(data, sender_addr);
3078
                        break;
3079
                case UPID_ENDLEVEL_C:
3080
                        if ((multi_i_am_master()) && ((Network_status == NETSTAT_ENDLEVEL) || (Network_status == NETSTAT_PLAYING)))
3081
                                net_udp_read_endlevel_packet(data, sender_addr);
3082
                        break;
3083
                case UPID_PDATA:
3084
                        net_udp_process_pdata( data, length, sender_addr );
3085
                        break;
3086
                case UPID_MDATA_PNORM:
3087
                        net_udp_process_mdata( data, length, sender_addr, 0 );
3088
                        break;
3089
                case UPID_MDATA_PNEEDACK:
3090
                        net_udp_process_mdata( data, length, sender_addr, 1 );
3091
                        break;
3092
                case UPID_MDATA_ACK:
3093
                        net_udp_noloss_got_ack(data, length);
3094
                        break;
3095
#if DXX_USE_TRACKER
3096
                case UPID_TRACKER_GAMEINFO:
3097
                        udp_tracker_process_game( data, length, sender_addr );
3098
                        break;
3099
                case UPID_TRACKER_ACK:
3100
                        if (!multi_i_am_master())
3101
                                break;
3102
                        udp_tracker_process_ack( data, length, sender_addr );
3103
                        break;
3104
                case UPID_TRACKER_HOLEPUNCH:
3105
                        udp_tracker_process_holepunch( data, length, sender_addr );
3106
                        break;
3107
#endif
3108
                default:
3109
                        con_printf(CON_DEBUG, "unknown packet type received - type %i", data[0]);
3110
                        break;
3111
        }
3112
}
3113
 
3114
// Packet for end of level syncing
3115
void net_udp_read_endlevel_packet(const uint8_t *data, const _sockaddr &sender_addr)
3116
{
3117
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
3118
        auto &Objects = LevelUniqueObjectState.Objects;
3119
        auto &vmobjptr = Objects.vmptr;
3120
        int len = 0;
3121
        ubyte tmpvar = 0;
3122
 
3123
        if (multi_i_am_master())
3124
        {
3125
                playernum_t pnum = data[1];
3126
 
3127
                if (Netgame.players[pnum].protocol.udp.addr != sender_addr)
3128
                        return;
3129
 
3130
                len += 2;
3131
 
3132
                if (static_cast<int>(data[len]) == CONNECT_DISCONNECTED)
3133
                        multi_disconnect_player(pnum);
3134
                vmplayerptr(pnum)->connected = data[len];                                       len++;
3135
                tmpvar = data[len];                                                     len++;
3136
                if (Network_status != NETSTAT_PLAYING && vcplayerptr(pnum)->connected == CONNECT_PLAYING && tmpvar < LevelUniqueControlCenterState.Countdown_seconds_left)
3137
                        LevelUniqueControlCenterState.Countdown_seconds_left = tmpvar;
3138
                auto &objp = *vmobjptr(vcplayerptr(pnum)->objnum);
3139
                auto &player_info = objp.ctype.player_info;
3140
                player_info.net_kills_total = GET_INTEL_SHORT(&data[len]);
3141
                len += 2;
3142
                player_info.net_killed_total = GET_INTEL_SHORT(&data[len]);
3143
                len += 2;
3144
 
3145
                range_for (auto &i, kill_matrix[pnum])
3146
                {
3147
                        i = GET_INTEL_SHORT(&(data[len]));              len += 2;
3148
                }
3149
                if (vcplayerptr(pnum)->connected)
3150
                        Netgame.players[pnum].LastPacketTime = timer_query();
3151
        }
3152
        else
3153
        {
3154
                if (Netgame.players[0].protocol.udp.addr != sender_addr)
3155
                        return;
3156
 
3157
                len++;
3158
 
3159
                tmpvar = data[len];                                                     len++;
3160
                if (Network_status != NETSTAT_PLAYING && tmpvar < LevelUniqueControlCenterState.Countdown_seconds_left)
3161
                        LevelUniqueControlCenterState.Countdown_seconds_left = tmpvar;
3162
 
3163
                for (playernum_t i = 0; i < MAX_PLAYERS; i++)
3164
                {
3165
                        if (i == Player_num)
3166
                        {
3167
                                len += 5;
3168
                                continue;
3169
                        }
3170
 
3171
                        if (static_cast<int>(data[len]) == CONNECT_DISCONNECTED)
3172
                                multi_disconnect_player(i);
3173
                        auto &objp = *vmobjptr(vcplayerptr(i)->objnum);
3174
                        auto &player_info = objp.ctype.player_info;
3175
                        vmplayerptr(i)->connected = data[len];                          len++;
3176
                        player_info.net_kills_total = GET_INTEL_SHORT(&data[len]);
3177
                        len += 2;
3178
                        player_info.net_killed_total = GET_INTEL_SHORT(&data[len]);
3179
                        len += 2;
3180
 
3181
                        if (vcplayerptr(i)->connected)
3182
                                Netgame.players[i].LastPacketTime = timer_query();
3183
                }
3184
 
3185
                for (playernum_t i = 0; i < MAX_PLAYERS; i++)
3186
                {
3187
                        for (playernum_t j = 0; j < MAX_PLAYERS; j++)
3188
                        {
3189
                                if (i != Player_num)
3190
                                {
3191
                                        kill_matrix[i][j] = GET_INTEL_SHORT(&(data[len]));
3192
                                }
3193
                                                                                        len += 2;
3194
                        }
3195
                }
3196
        }
3197
}
3198
 
3199
/*
3200
 * Polling loop waiting for sync packet to start game after having sent request
3201
 */
3202
static int net_udp_sync_poll( newmenu *,const d_event &event, const unused_newmenu_userdata_t *)
3203
{
3204
        static fix64 t1 = 0;
3205
        int rval = 0;
3206
 
3207
        if (event.type != EVENT_WINDOW_DRAW)
3208
                return 0;
3209
        net_udp_listen();
3210
 
3211
        // Leave if Host disconnects
3212
        if (Netgame.players[0].connected == CONNECT_DISCONNECTED)
3213
                rval = -2;
3214
 
3215
        if (Network_status != NETSTAT_WAITING)  // Status changed to playing, exit the menu
3216
                rval = -2;
3217
 
3218
        if (Network_status != NETSTAT_MENU && !Network_rejoined && (timer_query() > t1+F1_0*2))
3219
        {
3220
                // Poll time expired, re-send request
3221
 
3222
                t1 = timer_query();
3223
 
3224
                auto i = net_udp_send_request();
3225
                if (i >= MAX_PLAYERS)
3226
                        rval = -2;
3227
        }
3228
 
3229
        return rval;
3230
}
3231
 
3232
static int net_udp_start_poll(newmenu *, const d_event &event, start_poll_menu_items *const items)
3233
{
3234
        if (event.type != EVENT_WINDOW_DRAW)
3235
                return 0;
3236
        Assert(Network_status == NETSTAT_STARTING);
3237
 
3238
        auto &menus = items->m;
3239
        const unsigned nitems = menus.size();
3240
        menus[0].value = 1;
3241
        range_for (auto &i, partial_range(menus, N_players, nitems))
3242
                i.value = 0;
3243
 
3244
        const auto predicate = [](const newmenu_item &i) {
3245
                return i.value;
3246
        };
3247
        const auto nm = std::count_if(menus.begin(), std::next(menus.begin(), nitems), predicate);
3248
        if ( nm > Netgame.max_numplayers ) {
3249
                nm_messagebox( TXT_ERROR, 1, TXT_OK, "%s %d %s", TXT_SORRY_ONLY, Netgame.max_numplayers, TXT_NETPLAYERS_IN );
3250
                // Turn off the last player highlighted
3251
                for (int i = N_players; i > 0; i--)
3252
                        if (menus[i].value == 1)
3253
                        {
3254
                                menus[i].value = 0;
3255
                                break;
3256
                        }
3257
        }
3258
 
3259
        net_udp_listen();
3260
 
3261
        for (int i=0; i<N_players; i++ ) // fill this in always in case players change but not their numbers
3262
        {
3263
                const auto &&rankstr = GetRankStringWithSpace(Netgame.players[i].rank);
3264
                snprintf(menus[i].text, 45, "%d. %s%s%-20s", i+1, rankstr.first, rankstr.second, static_cast<const char *>(Netgame.players[i].callsign));
3265
        }
3266
 
3267
        const unsigned players_last_poll = items->get_player_count();
3268
        if (players_last_poll == Netgame.numplayers)
3269
                return 0;
3270
        items->set_player_count(Netgame.numplayers);
3271
        // A new player
3272
        if (players_last_poll < Netgame.numplayers)
3273
        {
3274
                digi_play_sample (SOUND_HUD_MESSAGE,F1_0);
3275
                if (N_players <= Netgame.max_numplayers)
3276
                        menus[N_players-1].value = 1;
3277
        }
3278
        else    // One got removed...
3279
        {
3280
                digi_play_sample (SOUND_HUD_KILL,F1_0);
3281
 
3282
                const auto j = std::min(N_players, static_cast<unsigned>(Netgame.max_numplayers));
3283
                /* Reset all the user's choices, since there is insufficient
3284
                 * integration to move the checks based on the position of the
3285
                 * departed player(s).  Without this reset, names would move up
3286
                 * one line, but checkboxes would not.
3287
                 */
3288
                range_for (auto &i, partial_range(menus, j))
3289
                        i.value = 1;
3290
                range_for (auto &i, partial_range(menus, j, N_players))
3291
                        i.value = 0;
3292
                range_for (auto &i, partial_range(menus, N_players, players_last_poll))
3293
                {
3294
                        /* The default format string is "%d. "
3295
                         * For single digit numbers, [3] is the first character
3296
                         * after the space.  For double digit numbers, [3] is the
3297
                         * space.  Users cannot see the trailing space or its
3298
                         * absence, so always overwrite [3].  This would break if
3299
                         * the menu allowed more than 99 players.
3300
                         */
3301
                        i.text[3] = 0;
3302
                        i.value = 0;
3303
                }
3304
        }
3305
        return 0;
3306
}
3307
 
3308
#if DXX_USE_TRACKER
3309
#define DXX_UDP_MENU_TRACKER_OPTION(VERB)       \
3310
        DXX_MENUITEM(VERB, CHECK, "Track this game on", opt_tracker, Netgame.Tracker) \
3311
        DXX_MENUITEM(VERB, TEXT, tracker_addr_txt, opt_tracker_addr)    \
3312
        DXX_MENUITEM(VERB, CHECK, "Enable tracker NAT hole punch", opt_tracker_nathp, TrackerNATWarned) \
3313
 
3314
#else
3315
#define DXX_UDP_MENU_TRACKER_OPTION(VERB)
3316
#endif
3317
 
3318
#if defined(DXX_BUILD_DESCENT_I)
3319
#define D2X_UDP_MENU_OPTIONS(VERB)      \
3320
 
3321
#elif defined(DXX_BUILD_DESCENT_II)
3322
#define D2X_UDP_MENU_OPTIONS(VERB)      \
3323
        DXX_MENUITEM(VERB, CHECK, "Allow Marker camera views", opt_marker_view, Netgame.Allow_marker_view)      \
3324
        DXX_MENUITEM(VERB, CHECK, "Indestructible lights", opt_light, Netgame.AlwaysLighting)   \
3325
        DXX_MENUITEM(VERB, CHECK, "Remove Thief at level start", opt_thief_presence, thief_absent)      \
3326
        DXX_MENUITEM(VERB, CHECK, "Prevent Thief Stealing Energy Weapons", opt_thief_steal_energy, thief_cannot_steal_energy_weapons)   \
3327
        DXX_MENUITEM(VERB, CHECK, "Allow Guidebot (coop only; experimental)", opt_guidebot_enabled, Netgame.AllowGuidebot)      \
3328
 
3329
#endif
3330
 
3331
constexpr std::integral_constant<unsigned, F1_0 * 60> reactor_invul_time_mini_scale{};
3332
constexpr std::integral_constant<unsigned, 5 * reactor_invul_time_mini_scale> reactor_invul_time_scale{};
3333
 
3334
#if defined(DXX_BUILD_DESCENT_I)
3335
#define D2X_DUPLICATE_POWERUP_OPTIONS(VERB)                                \
3336
 
3337
#elif defined(DXX_BUILD_DESCENT_II)
3338
#define D2X_DUPLICATE_POWERUP_OPTIONS(VERB)                                \
3339
        DXX_MENUITEM(VERB, SLIDER, extraAccessory, opt_extra_accessory, accessory, 0, (1 << packed_netduplicate_items::accessory_width) - 1)    \
3340
 
3341
#endif
3342
 
3343
#define DXX_DUPLICATE_POWERUP_OPTIONS(VERB)                                \
3344
        DXX_MENUITEM(VERB, SLIDER, extraPrimary, opt_extra_primary, primary, 0, (1 << packed_netduplicate_items::primary_width) - 1)    \
3345
        DXX_MENUITEM(VERB, SLIDER, extraSecondary, opt_extra_secondary, secondary, 0, (1 << packed_netduplicate_items::secondary_width) - 1)    \
3346
        D2X_DUPLICATE_POWERUP_OPTIONS(VERB)                                
3347
 
3348
#define DXX_UDP_MENU_OPTIONS(VERB)                                          \
3349
        DXX_MENUITEM(VERB, TEXT, "Game Options", game_label)                         \
3350
        DXX_MENUITEM(VERB, SLIDER, get_annotated_difficulty_string(Netgame.difficulty), opt_difficulty, difficulty, Difficulty_0, Difficulty_4) \
3351
        DXX_MENUITEM(VERB, SCALE_SLIDER, srinvul, opt_cinvul, Netgame.control_invul_time, 0, 10, reactor_invul_time_scale)      \
3352
        DXX_MENUITEM(VERB, SLIDER, PlayText, opt_playtime, PlayTimeAllowed, 0, 12)      \
3353
        DXX_MENUITEM(VERB, SLIDER, KillText, opt_killgoal, Netgame.KillGoal, 0, 20)     \
3354
        DXX_MENUITEM(VERB, TEXT, "", blank_1)                                     \
3355
        DXX_MENUITEM(VERB, TEXT, "Duplicate Powerups", duplicate_label)           \
3356
        DXX_DUPLICATE_POWERUP_OPTIONS(VERB)                                           \
3357
        DXX_MENUITEM(VERB, TEXT, "", blank_5)                                     \
3358
        DXX_MENUITEM(VERB, TEXT, "Spawn Options", spawn_label)                     \
3359
        DXX_MENUITEM(VERB, SLIDER, SecludedSpawnText, opt_secluded_spawns, Netgame.SecludedSpawns, 0, MAX_PLAYERS - 1)  \
3360
        DXX_MENUITEM(VERB, SLIDER, SpawnInvulnerableText, opt_start_invul, Netgame.InvulAppear, 0, 8)   \
3361
        DXX_MENUITEM(VERB, TEXT, "", blank_2)                                     \
3362
        DXX_MENUITEM(VERB, TEXT, "Object Options", powerup_label)                       \
3363
        DXX_MENUITEM(VERB, CHECK, "Shuffle powerups in anarchy games", opt_shuffle_powerups, Netgame.ShufflePowerupSeed)        \
3364
        DXX_MENUITEM(VERB, MENU, "Set Objects allowed...", opt_setpower)                 \
3365
        DXX_MENUITEM(VERB, MENU, "Set Objects granted at spawn...", opt_setgrant)       \
3366
        DXX_MENUITEM(VERB, TEXT, "", blank_3)                                     \
3367
        DXX_MENUITEM(VERB, TEXT, "Misc. Options", misc_label)                       \
3368
        DXX_MENUITEM(VERB, CHECK, TXT_SHOW_ON_MAP, opt_show_on_map, Netgame.game_flag.show_on_map)      \
3369
        D2X_UDP_MENU_OPTIONS(VERB)                                              \
3370
        DXX_MENUITEM(VERB, CHECK, "Bright player ships", opt_bright, Netgame.BrightPlayers)     \
3371
        DXX_MENUITEM(VERB, CHECK, "Show enemy names on HUD", opt_show_names, Netgame.ShowEnemyNames)    \
3372
        DXX_MENUITEM(VERB, CHECK, "No friendly fire (Team, Coop)", opt_ffire, Netgame.NoFriendlyFire)   \
3373
        DXX_MENUITEM(VERB, FCHECK, (Game_mode & GM_MULTI_COOP) ? "Allow coop mouselook" : "Allow anarchy mouselook", opt_mouselook, Netgame.MouselookFlags, MouselookMPFlag(Game_mode)) \
3374
        DXX_MENUITEM(VERB, TEXT, "", blank_4)                                     \
3375
        DXX_MENUITEM_AUTOSAVE_LABEL_INPUT(VERB) \
3376
        DXX_MENUITEM(VERB, TEXT, "", blank_6)                                     \
3377
        DXX_MENUITEM(VERB, TEXT, "Network Options", network_label)                     \
3378
        DXX_MENUITEM(VERB, TEXT, "Packets per second (" DXX_STRINGIZE_PPS(MIN_PPS) " - " DXX_STRINGIZE_PPS(MAX_PPS) ")", opt_label_pps) \
3379
        DXX_MENUITEM(VERB, INPUT, packstring, opt_packets)      \
3380
        DXX_MENUITEM(VERB, TEXT, "Network port", opt_label_port)        \
3381
        DXX_MENUITEM(VERB, INPUT, portstring, opt_port) \
3382
        DXX_UDP_MENU_TRACKER_OPTION(VERB)
3383
 
3384
#define DXX_STRINGIZE_PPS2(X)   #X
3385
#define DXX_STRINGIZE_PPS(X)    DXX_STRINGIZE_PPS2(X)
3386
 
3387
static void net_udp_set_power (void)
3388
{
3389
        newmenu_item m[multi_allow_powerup_text.size()];
3390
        for (int i = 0; i < multi_allow_powerup_text.size(); i++)
3391
        {
3392
                nm_set_item_checkbox(m[i], multi_allow_powerup_text[i], (Netgame.AllowedItems >> i) & 1);
3393
        }
3394
 
3395
        newmenu_do1( NULL, "Objects to allow", MULTI_ALLOW_POWERUP_MAX, m, unused_newmenu_subfunction, unused_newmenu_userdata, 0 );
3396
 
3397
        Netgame.AllowedItems &= ~NETFLAG_DOPOWERUP;
3398
        for (int i = 0; i < multi_allow_powerup_text.size(); i++)
3399
                if (m[i].value)
3400
                        Netgame.AllowedItems |= (1 << i);
3401
}
3402
 
3403
#if defined(DXX_BUILD_DESCENT_I)
3404
#define D2X_GRANT_POWERUP_MENU(VERB)
3405
#elif defined(DXX_BUILD_DESCENT_II)
3406
#define D2X_GRANT_POWERUP_MENU(VERB)    \
3407
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_GAUSS, opt_gauss, menu_bit_wrapper(flags, NETGRANT_GAUSS))      \
3408
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_HELIX, opt_helix, menu_bit_wrapper(flags, NETGRANT_HELIX))      \
3409
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_PHOENIX, opt_phoenix, menu_bit_wrapper(flags, NETGRANT_PHOENIX))        \
3410
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_OMEGA, opt_omega, menu_bit_wrapper(flags, NETGRANT_OMEGA))      \
3411
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_AFTERBURNER, opt_afterburner, menu_bit_wrapper(flags, NETGRANT_AFTERBURNER))    \
3412
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_AMMORACK, opt_ammo_rack, menu_bit_wrapper(flags, NETGRANT_AMMORACK))    \
3413
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_CONVERTER, opt_converter, menu_bit_wrapper(flags, NETGRANT_CONVERTER))  \
3414
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_HEADLIGHT, opt_headlight, menu_bit_wrapper(flags, NETGRANT_HEADLIGHT))  \
3415
 
3416
#endif
3417
 
3418
#define DXX_GRANT_POWERUP_MENU(VERB)    \
3419
        DXX_MENUITEM(VERB, NUMBER, "Laser level", opt_laser_level, menu_number_bias_wrapper<1>(laser_level), LASER_LEVEL_1 + 1, DXX_MAXIMUM_LASER_LEVEL + 1)    \
3420
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_QUAD, opt_quad_lasers, menu_bit_wrapper(flags, NETGRANT_QUAD))  \
3421
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_VULCAN, opt_vulcan, menu_bit_wrapper(flags, NETGRANT_VULCAN))   \
3422
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_SPREAD, opt_spreadfire, menu_bit_wrapper(flags, NETGRANT_SPREAD))       \
3423
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_PLASMA, opt_plasma, menu_bit_wrapper(flags, NETGRANT_PLASMA))   \
3424
        DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_FUSION, opt_fusion, menu_bit_wrapper(flags, NETGRANT_FUSION))   \
3425
        D2X_GRANT_POWERUP_MENU(VERB)
3426
 
3427
namespace {
3428
 
3429
class more_game_options_menu_items
3430
{
3431
        char packstring[sizeof("99")];
3432
        std::array<char, sizeof("65535")> portstring;
3433
        char srinvul[sizeof("Reactor life: 50 min")];
3434
        char PlayText[sizeof("Max time: 50 min")];
3435
        char SpawnInvulnerableText[sizeof("Invul. Time: 0.0 sec")];
3436
        char SecludedSpawnText[sizeof("Use 0 Furthest Sites")];
3437
        char KillText[sizeof("Kill goal: 000 kills")];
3438
        char extraPrimary[sizeof("Primaries: 0")];
3439
        char extraSecondary[sizeof("Secondaries: 0")];
3440
#if defined(DXX_BUILD_DESCENT_II)
3441
        char extraAccessory[sizeof("Accessories: 0")];
3442
#endif
3443
#if DXX_USE_TRACKER
3444
        char tracker_addr_txt[sizeof("65535") + 28];
3445
#endif
3446
        human_readable_mmss_time<decltype(d_gameplay_options::AutosaveInterval)::rep> AutosaveInterval;
3447
        using menu_array = std::array<newmenu_item, DXX_UDP_MENU_OPTIONS(COUNT)>;
3448
        menu_array m;
3449
        static const char *get_annotated_difficulty_string(const Difficulty_level_type d)
3450
        {
3451
                static const std::array<char[20], 5> text{{
3452
                        "Difficulty: Trainee",
3453
                        "Difficulty: Rookie",
3454
                        "Difficulty: Hotshot",
3455
                        "Difficulty: Ace",
3456
                        "Difficulty: Insane"
3457
                }};
3458
                switch (d)
3459
                {
3460
                        case Difficulty_0:
3461
                        case Difficulty_1:
3462
                        case Difficulty_2:
3463
                        case Difficulty_3:
3464
                        case Difficulty_4:
3465
                                return text[d];
3466
                        default:
3467
                                return &text[3][16];
3468
                }
3469
        }
3470
        static int handler(newmenu *, const d_event &event, more_game_options_menu_items *items);
3471
public:
3472
        menu_array &get_menu_items()
3473
        {
3474
                return m;
3475
        }
3476
        void update_difficulty_string(const Difficulty_level_type difficulty)
3477
        {
3478
                /* Cast away const because newmenu_item uses `char *text` even
3479
                 * for fields where text is treated as `const char *`.
3480
                 */
3481
                m[opt_difficulty].text = const_cast<char *>(get_annotated_difficulty_string(difficulty));
3482
        }
3483
        void update_extra_primary_string(unsigned primary)
3484
        {
3485
                snprintf(extraPrimary, sizeof(extraPrimary), "Primaries: %u", primary);
3486
        }
3487
        void update_extra_secondary_string(unsigned secondary)
3488
        {
3489
                snprintf(extraSecondary, sizeof(extraSecondary), "Secondaries: %u", secondary);
3490
        }
3491
#if defined(DXX_BUILD_DESCENT_II)
3492
        void update_extra_accessory_string(unsigned accessory)
3493
        {
3494
                snprintf(extraAccessory, sizeof(extraAccessory), "Accessories: %u", accessory);
3495
        }
3496
#endif
3497
        void update_packstring()
3498
        {
3499
                snprintf(packstring, sizeof(packstring), "%u", Netgame.PacketsPerSec);
3500
        }
3501
        void update_portstring()
3502
        {
3503
                snprintf(&portstring[0], portstring.size(), "%hu", UDP_MyPort);
3504
        }
3505
        void update_reactor_life_string(unsigned t)
3506
        {
3507
                snprintf(srinvul, sizeof(srinvul), "%s: %u %s", TXT_REACTOR_LIFE, t, TXT_MINUTES_ABBREV);
3508
        }
3509
        void update_max_play_time_string()
3510
        {
3511
                snprintf(PlayText, sizeof(PlayText), "Max time: %d %s", Netgame.PlayTimeAllowed.count() / (F1_0 * 60), TXT_MINUTES_ABBREV);
3512
        }
3513
        void update_spawn_invuln_string()
3514
        {
3515
                snprintf(SpawnInvulnerableText, sizeof(SpawnInvulnerableText), "Invul. Time: %1.1f sec", static_cast<float>(Netgame.InvulAppear) / 2);
3516
        }
3517
        void update_secluded_spawn_string()
3518
        {
3519
                const unsigned SecludedSpawns = Netgame.SecludedSpawns;
3520
                cf_assert(SecludedSpawns < MAX_PLAYERS);
3521
                snprintf(SecludedSpawnText, sizeof(SecludedSpawnText), "Use %u Furthest Sites", SecludedSpawns + 1);
3522
        }
3523
        void update_kill_goal_string()
3524
        {
3525
                snprintf(KillText, sizeof(KillText), "Kill Goal: %3d", Netgame.KillGoal * 5);
3526
        }
3527
        enum
3528
        {
3529
                DXX_UDP_MENU_OPTIONS(ENUM)
3530
        };
3531
        more_game_options_menu_items()
3532
        {
3533
                const auto difficulty = Netgame.difficulty;
3534
                update_difficulty_string(difficulty);
3535
                update_packstring();
3536
                update_portstring();
3537
                update_reactor_life_string(Netgame.control_invul_time / reactor_invul_time_mini_scale);
3538
                update_max_play_time_string();
3539
                update_spawn_invuln_string();
3540
                update_secluded_spawn_string();
3541
                update_kill_goal_string();
3542
                auto primary = Netgame.DuplicatePowerups.get_primary_count();
3543
                auto secondary = Netgame.DuplicatePowerups.get_secondary_count();
3544
#if defined(DXX_BUILD_DESCENT_II)
3545
                auto accessory = Netgame.DuplicatePowerups.get_accessory_count();
3546
                const auto thief_absent = Netgame.ThiefModifierFlags & ThiefModifier::Absent;
3547
                const auto thief_cannot_steal_energy_weapons = Netgame.ThiefModifierFlags & ThiefModifier::NoEnergyWeapons;
3548
                update_extra_accessory_string(accessory);
3549
#endif
3550
                update_extra_primary_string(primary);
3551
                update_extra_secondary_string(secondary);
3552
#if DXX_USE_TRACKER
3553
                const unsigned TrackerNATWarned = Netgame.TrackerNATWarned == TrackerNATHolePunchWarn::UserEnabledHP;
3554
#endif
3555
                const unsigned PlayTimeAllowed = std::chrono::duration_cast<std::chrono::duration<int, netgame_info::play_time_allowed_abi_ratio>>(Netgame.PlayTimeAllowed).count();
3556
                format_human_readable_time(AutosaveInterval, Netgame.MPGameplayOptions.AutosaveInterval);
3557
                DXX_UDP_MENU_OPTIONS(ADD);
3558
#if DXX_USE_TRACKER
3559
                const auto &tracker_addr = CGameArg.MplTrackerAddr;
3560
                if (tracker_addr.empty())
3561
                {
3562
                        nm_set_item_text(m[opt_tracker], "Tracker use disabled");
3563
                        nm_set_item_text(m[opt_tracker_addr], "<Tracker address not set>");
3564
                }
3565
                else
3566
                {
3567
                        snprintf(tracker_addr_txt, sizeof(tracker_addr_txt), "%s:%u", tracker_addr.c_str(), CGameArg.MplTrackerPort);
3568
                }
3569
#endif
3570
        }
3571
        void read() const
3572
        {
3573
                unsigned primary, secondary;
3574
#if defined(DXX_BUILD_DESCENT_II)
3575
                unsigned accessory;
3576
                uint8_t thief_absent;
3577
                uint8_t thief_cannot_steal_energy_weapons;
3578
#endif
3579
                uint8_t difficulty;
3580
#if DXX_USE_TRACKER
3581
                unsigned TrackerNATWarned;
3582
#endif
3583
                unsigned PlayTimeAllowed;
3584
                DXX_UDP_MENU_OPTIONS(READ);
3585
                Netgame.difficulty = cast_clamp_difficulty(difficulty);
3586
                Netgame.PlayTimeAllowed = std::chrono::duration<int, netgame_info::play_time_allowed_abi_ratio>(PlayTimeAllowed);
3587
                auto &items = Netgame.DuplicatePowerups;
3588
                items.set_primary_count(primary);
3589
                items.set_secondary_count(secondary);
3590
#if defined(DXX_BUILD_DESCENT_II)
3591
                items.set_accessory_count(accessory);
3592
                Netgame.ThiefModifierFlags =
3593
                        (thief_absent ? ThiefModifier::Absent : 0) |
3594
                        (thief_cannot_steal_energy_weapons ? ThiefModifier::NoEnergyWeapons : 0);
3595
#endif
3596
                char *p;
3597
                auto pps = strtol(packstring, &p, 10);
3598
                if (!*p)
3599
                        Netgame.PacketsPerSec = pps;
3600
#if DXX_USE_TRACKER
3601
                Netgame.TrackerNATWarned = TrackerNATWarned ? TrackerNATHolePunchWarn::UserEnabledHP : TrackerNATHolePunchWarn::UserRejectedHP;
3602
#endif
3603
                convert_text_portstring(portstring, UDP_MyPort, false, false);
3604
                parse_human_readable_time(Netgame.MPGameplayOptions.AutosaveInterval, AutosaveInterval);
3605
        }
3606
        static void net_udp_more_game_options();
3607
};
3608
 
3609
class grant_powerup_menu_items
3610
{
3611
public:
3612
        enum
3613
        {
3614
                DXX_GRANT_POWERUP_MENU(ENUM)
3615
        };
3616
        std::array<newmenu_item, DXX_GRANT_POWERUP_MENU(COUNT)> m;
3617
        grant_powerup_menu_items(const unsigned laser_level, const packed_spawn_granted_items p)
3618
        {
3619
                auto &flags = p.mask;
3620
                DXX_GRANT_POWERUP_MENU(ADD);
3621
        }
3622
        void read(packed_spawn_granted_items &p) const
3623
        {
3624
                unsigned laser_level, flags = 0;
3625
                DXX_GRANT_POWERUP_MENU(READ);
3626
                p.mask = laser_level | flags;
3627
        }
3628
};
3629
 
3630
}
3631
 
3632
static void net_udp_set_grant_power()
3633
{
3634
        const auto SpawnGrantedItems = Netgame.SpawnGrantedItems;
3635
        grant_powerup_menu_items menu{map_granted_flags_to_laser_level(SpawnGrantedItems), SpawnGrantedItems};
3636
        newmenu_do(nullptr, "Powerups granted at player spawn", menu.m, unused_newmenu_subfunction, unused_newmenu_userdata);
3637
        menu.read(Netgame.SpawnGrantedItems);
3638
}
3639
 
3640
void more_game_options_menu_items::net_udp_more_game_options()
3641
{
3642
        more_game_options_menu_items menu;
3643
        newmenu_do(nullptr, "Advanced netgame options", menu.get_menu_items(), handler, &menu);
3644
        menu.read();
3645
        if (Netgame.PacketsPerSec>MAX_PPS)
3646
        {
3647
                Netgame.PacketsPerSec=MAX_PPS;
3648
                nm_messagebox(TXT_ERROR, 1, TXT_OK, "Packet value out of range\nSetting value to %i",MAX_PPS);
3649
        }
3650
        else if (Netgame.PacketsPerSec < MIN_PPS)
3651
        {
3652
                Netgame.PacketsPerSec=MIN_PPS;
3653
                nm_messagebox(TXT_ERROR, 1, TXT_OK, "Packet value out of range\nSetting value to %i", MIN_PPS);
3654
        }
3655
        GameUniqueState.Difficulty_level = Netgame.difficulty;
3656
}
3657
 
3658
int more_game_options_menu_items::handler(newmenu *, const d_event &event, more_game_options_menu_items *items)
3659
{
3660
        switch (event.type)
3661
        {
3662
                case EVENT_NEWMENU_CHANGED:
3663
                {
3664
                        auto &citem = static_cast<const d_change_event &>(event).citem;
3665
                        auto &menus = items->get_menu_items();
3666
                        if (citem == opt_difficulty)
3667
                        {
3668
                                Netgame.difficulty = cast_clamp_difficulty(menus[opt_difficulty].value);
3669
                                items->update_difficulty_string(Netgame.difficulty);
3670
                        }
3671
                        else if (citem == opt_cinvul)
3672
                                items->update_reactor_life_string(menus[opt_cinvul].value * (reactor_invul_time_scale / reactor_invul_time_mini_scale));
3673
                        else if (citem == opt_playtime)
3674
                        {
3675
                                if (Game_mode & GM_MULTI_COOP)
3676
                                {
3677
                                        nm_messagebox ("Sorry",1,TXT_OK,"You can't change those for coop!");
3678
                                        menus[opt_playtime].value=0;
3679
                                        return 0;
3680
                                }
3681
 
3682
                                Netgame.PlayTimeAllowed = std::chrono::duration<int, netgame_info::play_time_allowed_abi_ratio>(menus[opt_playtime].value);
3683
                                items->update_max_play_time_string();
3684
                        }
3685
                        else if (citem == opt_killgoal)
3686
                        {
3687
                                if (Game_mode & GM_MULTI_COOP)
3688
                                {
3689
                                        nm_messagebox ("Sorry",1,TXT_OK,"You can't change those for coop!");
3690
                                        menus[opt_killgoal].value=0;
3691
                                        return 0;
3692
                                }
3693
 
3694
                                Netgame.KillGoal=menus[opt_killgoal].value;
3695
                                items->update_kill_goal_string();
3696
                        }
3697
                        else if(citem == opt_extra_primary)
3698
                        {
3699
                                auto primary = menus[opt_extra_primary].value;
3700
                                items->update_extra_primary_string(primary);
3701
                        }
3702
                        else if(citem == opt_extra_secondary)
3703
                        {
3704
                                auto secondary = menus[opt_extra_secondary].value;
3705
                                items->update_extra_secondary_string(secondary);
3706
                        }
3707
#if defined(DXX_BUILD_DESCENT_II)
3708
                        else if(citem == opt_extra_accessory)
3709
                        {
3710
                                auto accessory = menus[opt_extra_accessory].value;
3711
                                items->update_extra_accessory_string(accessory);
3712
                        }
3713
#endif
3714
                        else if (citem == opt_start_invul)
3715
                        {
3716
                                Netgame.InvulAppear = menus[opt_start_invul].value;
3717
                                items->update_spawn_invuln_string();
3718
                        }
3719
                        else if (citem == opt_secluded_spawns)
3720
                        {
3721
                                Netgame.SecludedSpawns = menus[opt_secluded_spawns].value;
3722
                                items->update_secluded_spawn_string();
3723
                        }
3724
                        break;
3725
                }
3726
                case EVENT_NEWMENU_SELECTED:
3727
                {
3728
                        auto &citem = static_cast<const d_select_event &>(event).citem;
3729
                        if (citem == opt_setpower)
3730
                                net_udp_set_power();
3731
                        else if (citem == opt_setgrant)
3732
                                net_udp_set_grant_power();
3733
                        else
3734
                                break;
3735
                        return 1;
3736
                }
3737
                default:
3738
                        break;
3739
        }
3740
        return 0;
3741
}
3742
 
3743
namespace {
3744
 
3745
struct param_opt
3746
{
3747
        enum {
3748
                name = 2,
3749
                label_level,
3750
                level,
3751
        };
3752
        int start_game, mode, mode_end, moreopts;
3753
        int closed, refuse, maxnet, anarchy, team_anarchy, robot_anarchy, coop, bounty;
3754
#if defined(DXX_BUILD_DESCENT_II)
3755
        int capture, hoard, team_hoard;
3756
#endif
3757
        char slevel[sizeof("S100")] = "1";
3758
        char srmaxnet[sizeof("Maximum players: 99")];
3759
        std::array<newmenu_item, 22> m;
3760
        void update_netgame_max_players()
3761
        {
3762
                Netgame.max_numplayers = m[maxnet].value + 2;
3763
                update_max_players_string();
3764
        }
3765
        void update_max_players_string()
3766
        {
3767
                const unsigned max_numplayers = Netgame.max_numplayers;
3768
                cf_assert(max_numplayers < MAX_PLAYERS);
3769
                snprintf(srmaxnet, sizeof(srmaxnet), "Maximum players: %u", max_numplayers);
3770
        }
3771
};
3772
 
3773
}
3774
 
3775
namespace dsx {
3776
static int net_udp_game_param_handler( newmenu *menu,const d_event &event, param_opt *opt )
3777
{
3778
        newmenu_item *menus = newmenu_get_items(menu);
3779
        switch (event.type)
3780
        {
3781
                case EVENT_NEWMENU_CHANGED:
3782
                {
3783
                        auto &citem = static_cast<const d_change_event &>(event).citem;
3784
#if defined(DXX_BUILD_DESCENT_I)
3785
                        if (citem == opt->team_anarchy)
3786
                        {
3787
                                menus[opt->closed].value = 1;
3788
                                menus[opt->closed-1].value = 0;
3789
                                menus[opt->closed+1].value = 0;
3790
                        }
3791
#elif defined(DXX_BUILD_DESCENT_II)
3792
                        if (((HoardEquipped() && (citem == opt->team_hoard)) || ((citem == opt->team_anarchy) || (citem == opt->capture))) && !menus[opt->closed].value && !menus[opt->refuse].value)
3793
                        {
3794
                                menus[opt->refuse].value = 1;
3795
                                menus[opt->refuse-1].value = 0;
3796
                                menus[opt->refuse-2].value = 0;
3797
                        }
3798
#endif
3799
 
3800
                        if (menus[opt->coop].value)
3801
                        {
3802
                                Netgame.game_flag.show_on_map = 1;
3803
 
3804
                                Netgame.PlayTimeAllowed = {};
3805
                                Netgame.KillGoal = 0;
3806
                        }
3807
                        if (citem == opt->level)
3808
                        {
3809
                                auto &slevel = opt->slevel;
3810
#if defined(DXX_BUILD_DESCENT_I)
3811
                                if (tolower(static_cast<unsigned>(*slevel)) == 's')
3812
                                        Netgame.levelnum = -atoi(slevel+1);
3813
                                else
3814
#endif
3815
                                        Netgame.levelnum = atoi(slevel);
3816
                        }
3817
 
3818
                        if (citem == opt->maxnet)
3819
                        {
3820
                                opt->update_netgame_max_players();
3821
                        }
3822
 
3823
                        if ((citem >= opt->mode) && (citem <= opt->mode_end))
3824
                        {
3825
                                if ( menus[opt->anarchy].value )
3826
                                        Netgame.gamemode = NETGAME_ANARCHY;
3827
 
3828
                                else if (menus[opt->team_anarchy].value) {
3829
                                        Netgame.gamemode = NETGAME_TEAM_ANARCHY;
3830
                                }
3831
#if defined(DXX_BUILD_DESCENT_II)
3832
                                else if (menus[opt->capture].value)
3833
                                        Netgame.gamemode = NETGAME_CAPTURE_FLAG;
3834
                                else if (HoardEquipped() && menus[opt->hoard].value)
3835
                                        Netgame.gamemode = NETGAME_HOARD;
3836
                                else if (HoardEquipped() && menus[opt->team_hoard].value)
3837
                                        Netgame.gamemode = NETGAME_TEAM_HOARD;
3838
#endif
3839
                                else if( menus[opt->bounty].value )
3840
                                        Netgame.gamemode = NETGAME_BOUNTY;
3841
                                else if (ANARCHY_ONLY_MISSION) {
3842
                                        int i = 0;
3843
                                        nm_messagebox(NULL, 1, TXT_OK, TXT_ANARCHY_ONLY_MISSION);
3844
                                        for (i = opt->mode; i <= opt->mode_end; i++)
3845
                                                menus[i].value = 0;
3846
                                        menus[opt->anarchy].value = 1;
3847
                                        return 0;
3848
                                }
3849
                                else if ( menus[opt->robot_anarchy].value )
3850
                                        Netgame.gamemode = NETGAME_ROBOT_ANARCHY;
3851
                                else if ( menus[opt->coop].value )
3852
                                        Netgame.gamemode = NETGAME_COOPERATIVE;
3853
                                else Int3(); // Invalid mode -- see Rob
3854
                        }
3855
 
3856
                        Netgame.game_flag.closed = menus[opt->closed].value;
3857
                        Netgame.RefusePlayers=menus[opt->refuse].value;
3858
                        break;
3859
                }
3860
                case EVENT_NEWMENU_SELECTED:
3861
                {
3862
                        auto &citem = static_cast<const d_select_event &>(event).citem;
3863
#if defined(DXX_BUILD_DESCENT_I)
3864
                        if ((Netgame.levelnum < Last_secret_level) || (Netgame.levelnum > Last_level) || (Netgame.levelnum == 0))
3865
#elif defined(DXX_BUILD_DESCENT_II)
3866
                        if ((Netgame.levelnum < 1) || (Netgame.levelnum > Last_level))
3867
#endif
3868
                        {
3869
                                auto &slevel = opt->slevel;
3870
                                nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_LEVEL_OUT_RANGE );
3871
                                strcpy(slevel, "1");
3872
                                return 1;
3873
                        }
3874
 
3875
                        if (citem==opt->moreopts)
3876
                        {
3877
                                if ( menus[opt->coop].value )
3878
                                        Game_mode=GM_MULTI_COOP;
3879
                                more_game_options_menu_items::net_udp_more_game_options();
3880
                                Game_mode=0;
3881
                                return 1;
3882
                        }
3883
                        if (citem==opt->start_game)
3884
                                return !net_udp_start_game();
3885
                        return 1;
3886
                }
3887
                default:
3888
                        break;
3889
        }
3890
 
3891
        return 0;
3892
}
3893
}
3894
 
3895
namespace dsx {
3896
window_event_result net_udp_setup_game()
3897
{
3898
        int optnum;
3899
        param_opt opt;
3900
        auto &m = opt.m;
3901
        char level_text[32];
3902
 
3903
        net_udp_init();
3904
 
3905
        multi_new_game();
3906
 
3907
        change_playernum_to(0);
3908
 
3909
        const auto &self = get_local_player();
3910
        {
3911
                range_for (auto &i, Players)
3912
                        if (&i != &self)
3913
                                i.callsign = {};
3914
        }
3915
 
3916
        Netgame.max_numplayers = MAX_PLAYERS;
3917
        Netgame.KillGoal=0;
3918
        Netgame.PlayTimeAllowed = {};
3919
#if defined(DXX_BUILD_DESCENT_I)
3920
        Netgame.RefusePlayers=0;
3921
#elif defined(DXX_BUILD_DESCENT_II)
3922
        Netgame.Allow_marker_view=1;
3923
        Netgame.ThiefModifierFlags = 0;
3924
#endif
3925
        Netgame.difficulty=PlayerCfg.DefaultDifficulty;
3926
        Netgame.PacketsPerSec=DEFAULT_PPS;
3927
        snprintf(Netgame.game_name.data(), Netgame.game_name.size(), "%s%s", static_cast<const char *>(InterfaceUniqueState.PilotName), TXT_S_GAME);
3928
        reset_UDP_MyPort();
3929
        Netgame.ShufflePowerupSeed = 0;
3930
        Netgame.BrightPlayers = 1;
3931
        Netgame.InvulAppear = 4;
3932
        Netgame.SecludedSpawns = MAX_PLAYERS - 1;
3933
        Netgame.AllowedItems = NETFLAG_DOPOWERUP;
3934
        Netgame.PacketLossPrevention = 1;
3935
        Netgame.NoFriendlyFire = 0;
3936
        Netgame.MouselookFlags = 0;
3937
 
3938
#if DXX_USE_TRACKER
3939
        Netgame.Tracker = 1;
3940
#endif
3941
 
3942
        read_netgame_profile(&Netgame);
3943
 
3944
#if defined(DXX_BUILD_DESCENT_II)
3945
        if (!HoardEquipped() && (Netgame.gamemode == NETGAME_HOARD || Netgame.gamemode == NETGAME_TEAM_HOARD)) // did we restore a hoard mode but don't have hoard installed right now? then fall back to anarchy!
3946
                Netgame.gamemode = NETGAME_ANARCHY;
3947
#endif
3948
 
3949
        Netgame.mission_name.copy_if(&*Current_mission->filename, Netgame.mission_name.size());
3950
        Netgame.mission_title = Current_mission_longname;
3951
 
3952
        Netgame.levelnum = 1;
3953
 
3954
        optnum = 0;
3955
        opt.start_game=optnum;
3956
        nm_set_item_menu(  m[optnum], "Start Game"); optnum++;
3957
        nm_set_item_text(m[optnum], TXT_DESCRIPTION); optnum++;
3958
 
3959
        nm_set_item_input(m[optnum], Netgame.game_name); optnum++;
3960
 
3961
#define DXX_LEVEL_FORMAT_LEADER "%s (1-%d"
3962
#define DXX_LEVEL_FORMAT_TRAILER        ")"
3963
#if defined(DXX_BUILD_DESCENT_I)
3964
        if (Last_secret_level == -1)
3965
                /* Exactly one secret level */
3966
                snprintf(level_text, sizeof(level_text), DXX_LEVEL_FORMAT_LEADER ", S1" DXX_LEVEL_FORMAT_TRAILER, TXT_LEVEL_, Last_level);
3967
        else if (Last_secret_level)
3968
                /* More than one secret level */
3969
                snprintf(level_text, sizeof(level_text), DXX_LEVEL_FORMAT_LEADER ", S1-S%d" DXX_LEVEL_FORMAT_TRAILER, TXT_LEVEL_, Last_level, -Last_secret_level);
3970
        else
3971
                /* No secret levels */
3972
#endif
3973
                snprintf(level_text, sizeof(level_text), DXX_LEVEL_FORMAT_LEADER DXX_LEVEL_FORMAT_TRAILER, TXT_LEVEL_, Last_level);
3974
#undef DXX_LEVEL_FORMAT_TRAILER
3975
#undef DXX_LEVEL_FORMAT_LEADER
3976
 
3977
        nm_set_item_text(m[optnum], level_text); optnum++;
3978
 
3979
        nm_set_item_input(m[optnum], opt.slevel); optnum++;
3980
        nm_set_item_text(m[optnum], TXT_OPTIONS); optnum++;
3981
 
3982
        opt.mode = optnum;
3983
        nm_set_item_radio(m[optnum], TXT_ANARCHY,(Netgame.gamemode == NETGAME_ANARCHY),0); opt.anarchy=optnum; optnum++;
3984
        nm_set_item_radio(m[optnum], TXT_TEAM_ANARCHY,(Netgame.gamemode == NETGAME_TEAM_ANARCHY),0); opt.team_anarchy=optnum; optnum++;
3985
        nm_set_item_radio(m[optnum], TXT_ANARCHY_W_ROBOTS,(Netgame.gamemode == NETGAME_ROBOT_ANARCHY),0); opt.robot_anarchy=optnum; optnum++;
3986
        nm_set_item_radio(m[optnum], TXT_COOPERATIVE,(Netgame.gamemode == NETGAME_COOPERATIVE),0); opt.coop=optnum; optnum++;
3987
#if defined(DXX_BUILD_DESCENT_II)
3988
        nm_set_item_radio(m[optnum], "Capture the flag",(Netgame.gamemode == NETGAME_CAPTURE_FLAG),0); opt.capture=optnum; optnum++;
3989
 
3990
        if (HoardEquipped())
3991
        {
3992
                nm_set_item_radio(m[optnum], "Hoard",(Netgame.gamemode == NETGAME_HOARD),0); opt.hoard=optnum; optnum++;
3993
                nm_set_item_radio(m[optnum], "Team Hoard",(Netgame.gamemode == NETGAME_TEAM_HOARD),0); opt.team_hoard=optnum; optnum++;
3994
        }
3995
        else
3996
        {
3997
                opt.hoard = opt.team_hoard = 0; // NOTE: Make sure if you use these, use them in connection with HoardEquipped() only!
3998
        }
3999
#endif
4000
        nm_set_item_radio(m[optnum], "Bounty", ( Netgame.gamemode & NETGAME_BOUNTY ), 0); opt.mode_end=opt.bounty=optnum; optnum++;
4001
 
4002
        nm_set_item_text(m[optnum], ""); optnum++;
4003
 
4004
        nm_set_item_radio(m[optnum], "Open game",(!Netgame.RefusePlayers && !Netgame.game_flag.closed),1); optnum++;
4005
        opt.closed = optnum;
4006
        nm_set_item_radio(m[optnum], TXT_CLOSED_GAME,Netgame.game_flag.closed,1); optnum++;
4007
        opt.refuse = optnum;
4008
        nm_set_item_radio(m[optnum], "Restricted Game              ",Netgame.RefusePlayers,1); optnum++;
4009
 
4010
        opt.maxnet = optnum;
4011
        opt.update_max_players_string();
4012
        nm_set_item_slider(m[optnum], opt.srmaxnet, Netgame.max_numplayers - 2, 0, Netgame.max_numplayers - 2); optnum++;
4013
 
4014
        opt.moreopts=optnum;
4015
        nm_set_item_menu(  m[optnum], "Advanced Options"); optnum++;
4016
 
4017
        Assert(optnum <= 20);
4018
 
4019
#if DXX_USE_TRACKER
4020
        if (Netgame.TrackerNATWarned == TrackerNATHolePunchWarn::Unset)
4021
        {
4022
                const unsigned choice = nm_messagebox_str("NAT Hole Punch", nm_messagebox_tie("Yes, let Internet users join", "No, I will configure my router"),
4023
"Rebirth now supports automatic\n"
4024
"NAT hole punch through the\n"
4025
"tracker.\n\n"
4026
"This allows Internet users to\n"
4027
"join your game, even if you do\n"
4028
"not configure your router for\n"
4029
"hosting.\n\n"
4030
"Do you want to use this feature?");
4031
                if (choice <= 1)
4032
                        Netgame.TrackerNATWarned = static_cast<TrackerNATHolePunchWarn>(choice + 1);
4033
        }
4034
#endif
4035
 
4036
        const int i = newmenu_do1(nullptr, TXT_NETGAME_SETUP, optnum, m.data(), net_udp_game_param_handler, &opt, opt.start_game);
4037
 
4038
        if (i < 0)
4039
                net_udp_close();
4040
 
4041
        write_netgame_profile(&Netgame);
4042
#if DXX_USE_TRACKER
4043
        /* Force off _after_ writing profile, so that command line does not
4044
         * change ngp file.
4045
         */
4046
        if (CGameArg.MplTrackerAddr.empty())
4047
                Netgame.Tracker = 0;
4048
#endif
4049
 
4050
        return (i >= 0) ? window_event_result::close : window_event_result::handled;
4051
}
4052
}
4053
 
4054
namespace dsx {
4055
static void net_udp_set_game_mode(const int gamemode)
4056
{
4057
        Show_kill_list = 1;
4058
 
4059
        if ( gamemode == NETGAME_ANARCHY )
4060
                Game_mode = GM_NETWORK;
4061
        else if ( gamemode == NETGAME_ROBOT_ANARCHY )
4062
                Game_mode = GM_NETWORK | GM_MULTI_ROBOTS;
4063
        else if ( gamemode == NETGAME_COOPERATIVE )
4064
                Game_mode = GM_NETWORK | GM_MULTI_COOP | GM_MULTI_ROBOTS;
4065
#if defined(DXX_BUILD_DESCENT_II)
4066
        else if (gamemode == NETGAME_CAPTURE_FLAG)
4067
                {
4068
                 Game_mode = GM_NETWORK | GM_TEAM | GM_CAPTURE;
4069
                 Show_kill_list=3;
4070
                }
4071
 
4072
        else if (HoardEquipped() && gamemode == NETGAME_HOARD)
4073
                 Game_mode = GM_NETWORK | GM_HOARD;
4074
        else if (HoardEquipped() && gamemode == NETGAME_TEAM_HOARD)
4075
                 {
4076
                  Game_mode = GM_NETWORK | GM_HOARD | GM_TEAM;
4077
                  Show_kill_list=3;
4078
                 }
4079
#endif
4080
        else if( gamemode == NETGAME_BOUNTY )
4081
                Game_mode = GM_NETWORK | GM_BOUNTY;
4082
        else if ( gamemode == NETGAME_TEAM_ANARCHY )
4083
        {
4084
                Game_mode = GM_NETWORK | GM_TEAM;
4085
                Show_kill_list = 3;
4086
        }
4087
        else
4088
                Int3();
4089
}
4090
}
4091
 
4092
namespace dsx {
4093
void net_udp_read_sync_packet(const uint8_t * data, uint_fast32_t data_len, const _sockaddr &sender_addr)
4094
{
4095
        auto &Objects = LevelUniqueObjectState.Objects;
4096
        auto &vmobjptr = Objects.vmptr;
4097
        auto &vmobjptridx = Objects.vmptridx;
4098
        if (data)
4099
        {
4100
                net_udp_process_game_info(data, data_len, sender_addr, 0);
4101
        }
4102
 
4103
        N_players = Netgame.numplayers;
4104
        GameUniqueState.Difficulty_level = Netgame.difficulty;
4105
        Network_status = Netgame.game_status;
4106
 
4107
        if (Netgame.segments_checksum != my_segments_checksum)
4108
        {
4109
                Network_status = NETSTAT_MENU;
4110
                net_udp_close();
4111
                nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_NETLEVEL_NMATCH);
4112
                throw multi::level_checksum_mismatch();
4113
        }
4114
 
4115
        // Discover my player number
4116
 
4117
        auto &temp_callsign = InterfaceUniqueState.PilotName;
4118
 
4119
        Player_num = MULTI_PNUM_UNDEF;
4120
 
4121
        for (unsigned i = 0; i < N_players; ++i)
4122
        {
4123
                if (i == Netgame.protocol.udp.your_index && Netgame.players[i].callsign == temp_callsign)
4124
                {
4125
                        if (Player_num!=MULTI_PNUM_UNDEF) {
4126
                                Int3(); // Hey, we've found ourselves twice
4127
                                Network_status = NETSTAT_MENU;
4128
                                return;
4129
                        }
4130
                        change_playernum_to(i);
4131
                }
4132
                auto &plr = *vmplayerptr(i);
4133
                plr.callsign = Netgame.players[i].callsign;
4134
                plr.connected = Netgame.players[i].connected;
4135
                auto &objp = *vmobjptr(plr.objnum);
4136
                auto &player_info = objp.ctype.player_info;
4137
                player_info.net_kills_total = Netgame.player_kills[i];
4138
                player_info.net_killed_total = Netgame.killed[i];
4139
                if ((Network_rejoined) || (i != Player_num))
4140
                        player_info.mission.score = Netgame.player_score[i];
4141
        }
4142
        kill_matrix = Netgame.kills;
4143
 
4144
        if (Player_num >= MAX_PLAYERS)
4145
        {
4146
                Network_status = NETSTAT_MENU;
4147
                throw multi::local_player_not_playing();
4148
        }
4149
 
4150
#if defined(DXX_BUILD_DESCENT_I)
4151
        {
4152
                auto &player_info = get_local_plrobj().ctype.player_info;
4153
                PlayerCfg.NetlifeKills -= player_info.net_kills_total;
4154
                PlayerCfg.NetlifeKilled -= player_info.net_killed_total;
4155
        }
4156
#endif
4157
 
4158
        auto &plr = get_local_player();
4159
        if (Network_rejoined)
4160
        {
4161
                net_udp_process_monitor_vector(Netgame.monitor_vector);
4162
                plr.time_level = Netgame.level_time;
4163
        }
4164
 
4165
        team_kills = Netgame.team_kills;
4166
        plr.connected = CONNECT_PLAYING;
4167
        Netgame.players[Player_num].connected = CONNECT_PLAYING;
4168
        Netgame.players[Player_num].rank=GetMyNetRanking();
4169
 
4170
        if (!Network_rejoined)
4171
        {
4172
                for (unsigned i = 0; i < NumNetPlayerPositions; ++i)
4173
                {
4174
                        const auto &&o = vmobjptridx(vcplayerptr(i)->objnum);
4175
                        const auto &p = Player_init[Netgame.locations[i]];
4176
                        o->pos = p.pos;
4177
                        o->orient = p.orient;
4178
                        obj_relink(vmobjptr, vmsegptr, o, vmsegptridx(p.segnum));
4179
                }
4180
        }
4181
 
4182
        get_local_plrobj().type = OBJ_PLAYER;
4183
 
4184
        Network_status = NETSTAT_PLAYING;
4185
        multi_sort_kill_list();
4186
}
4187
}
4188
 
4189
static int net_udp_send_sync(void)
4190
{
4191
        int np;
4192
 
4193
        // Check if there are enough starting positions
4194
        if (NumNetPlayerPositions < Netgame.max_numplayers)
4195
        {
4196
                nm_messagebox(TXT_ERROR, 1, TXT_OK, "Not enough start positions\n(set %d got %d)\nNetgame aborted", Netgame.max_numplayers, NumNetPlayerPositions);
4197
                // Tell everyone we're bailing
4198
                Netgame.numplayers = 0;
4199
                for (unsigned i = 1; i < N_players; ++i)
4200
                {
4201
                        if (vcplayerptr(i)->connected == CONNECT_DISCONNECTED)
4202
                                continue;
4203
                        const auto &addr = Netgame.players[i].protocol.udp.addr;
4204
                        net_udp_dump_player(addr, DUMP_ABORTED);
4205
                        net_udp_send_game_info(addr, &addr, UPID_GAME_INFO);
4206
                }
4207
                net_udp_broadcast_game_info(UPID_GAME_INFO_LITE);
4208
                return -1;
4209
        }
4210
 
4211
        // Randomize their starting locations...
4212
        d_srand(static_cast<fix>(timer_query()));
4213
        for (unsigned i=0; i<NumNetPlayerPositions; i++ )
4214
        {
4215
                if (vcplayerptr(i)->connected)
4216
                        vmplayerptr(i)->connected = CONNECT_PLAYING; // Get rid of endlevel connect statuses
4217
 
4218
                if (Game_mode & GM_MULTI_COOP)
4219
                        Netgame.locations[i] = i;
4220
                else {
4221
                        do
4222
                        {
4223
                                np = d_rand() % NumNetPlayerPositions;
4224
                                range_for (auto &j, partial_const_range(Netgame.locations, i))
4225
                                {
4226
                                        if (j==np)  
4227
                                        {
4228
                                                np =-1;
4229
                                                break;
4230
                                        }
4231
                                }
4232
                        } while (np<0);
4233
                        // np is a location that is not used anywhere else..
4234
                        Netgame.locations[i]=np;
4235
                }
4236
        }
4237
 
4238
        // Push current data into the sync packet
4239
 
4240
        net_udp_update_netgame();
4241
        Netgame.game_status = NETSTAT_PLAYING;
4242
        Netgame.segments_checksum = my_segments_checksum;
4243
 
4244
        for (unsigned i = 0; i < N_players; ++i)
4245
        {
4246
                if (!vcplayerptr(i)->connected || i == Player_num)
4247
                        continue;
4248
                const auto &addr = Netgame.players[i].protocol.udp.addr;
4249
                net_udp_send_game_info(addr, &addr, UPID_SYNC);
4250
        }
4251
 
4252
        net_udp_read_sync_packet(NULL, 0, Netgame.players[0].protocol.udp.addr); // Read it myself, as if I had sent it
4253
        return 0;
4254
}
4255
 
4256
static int net_udp_select_teams()
4257
{
4258
        newmenu_item m[MAX_PLAYERS+4];
4259
        int choice, opt, opt_team_b;
4260
        ubyte team_vector = 0;
4261
        int pnums[MAX_PLAYERS+2];
4262
 
4263
        // One-time initialization
4264
 
4265
        for (int i = N_players/2; i < N_players; i++) // Put first half of players on team A
4266
        {
4267
                team_vector |= (1 << i);
4268
        }
4269
 
4270
        std::array<callsign_t, 2> team_names;
4271
        team_names[0].copy(TXT_BLUE, ~0ul);
4272
        team_names[1].copy(TXT_RED, ~0ul);
4273
 
4274
        // Here comes da menu
4275
menu:
4276
        nm_set_item_input(m[0], team_names[0].buffer());
4277
 
4278
        opt = 1;
4279
        for (int i = 0; i < N_players; i++)
4280
        {
4281
                if (!(team_vector & (1 << i)))
4282
                {
4283
                        nm_set_item_menu( m[opt], Netgame.players[i].callsign); pnums[opt] = i; opt++;
4284
                }
4285
        }
4286
        opt_team_b = opt;
4287
        nm_set_item_input(m[opt], team_names[1].buffer());
4288
        opt++;
4289
        for (int i = 0; i < N_players; i++)
4290
        {
4291
                if (team_vector & (1 << i))
4292
                {
4293
                        nm_set_item_menu( m[opt], Netgame.players[i].callsign); pnums[opt] = i; opt++;
4294
                }
4295
        }
4296
        nm_set_item_text(m[opt], ""); opt++;
4297
        nm_set_item_menu( m[opt], TXT_ACCEPT); opt++;
4298
 
4299
        Assert(opt <= MAX_PLAYERS+4);
4300
 
4301
        choice = newmenu_do(NULL, TXT_TEAM_SELECTION, opt, m, unused_newmenu_subfunction, unused_newmenu_userdata);
4302
 
4303
        if (choice == opt-1)
4304
        {
4305
#if 0 // no need to wait for other players
4306
                if ((opt-2-opt_team_b < 2) || (opt_team_b == 1))
4307
                {
4308
                        nm_messagebox(NULL, 1, TXT_OK, TXT_TEAM_MUST_ONE);
4309
                        #ifdef RELEASE
4310
                        goto menu;
4311
                        #endif
4312
                }
4313
#endif
4314
                Netgame.team_vector = team_vector;
4315
                Netgame.team_name = team_names;
4316
                return 1;
4317
        }
4318
 
4319
        else if ((choice > 0) && (choice < opt_team_b)) {
4320
                team_vector |= (1 << pnums[choice]);
4321
        }
4322
        else if ((choice > opt_team_b) && (choice < opt-2)) {
4323
                team_vector &= ~(1 << pnums[choice]);
4324
        }
4325
        else if (choice == -1)
4326
                return 0;
4327
        goto menu;
4328
}
4329
 
4330
namespace dsx {
4331
static int net_udp_select_players()
4332
{
4333
        int j;
4334
        char text[MAX_PLAYERS+4][45];
4335
        char title[50];
4336
        unsigned save_nplayers;              //how may people would like to join
4337
 
4338
        if (Netgame.ShufflePowerupSeed)
4339
        {
4340
                unsigned seed = 0;
4341
                try {
4342
                        seed = std::random_device()();
4343
                        if (!seed)
4344
                                /* random_device can return any number, including zero.
4345
                                 * Rebirth treats zero specially, interpreting it as a
4346
                                 * request not to shuffle.  Prevent a zero from
4347
                                 * random_device being interpreted as a request not to
4348
                                 * shuffle.
4349
                                 */
4350
                                seed = 1;
4351
                } catch (const std::exception &e) {
4352
                        con_printf(CON_URGENT, "Failed to generate random number: %s", e.what());
4353
                        /* Fall out without setting `seed`, so that the option is
4354
                         * disabled until the user notices the message and
4355
                         * resolves the problem.
4356
                         */
4357
                }
4358
                Netgame.ShufflePowerupSeed = seed;
4359
        }
4360
 
4361
        net_udp_add_player( &UDP_Seq );
4362
        start_poll_menu_items spd;
4363
 
4364
        for (int i=0; i< MAX_PLAYERS+4; i++ ) {
4365
                snprintf(text[i], sizeof(text[i]), "%d. ", i + 1);
4366
                nm_set_item_checkbox(spd.m[i], text[i], 0);
4367
        }
4368
 
4369
        spd.m[0].value = 1;                         // Assume server will play...
4370
 
4371
        const auto &&rankstr = GetRankStringWithSpace(Netgame.players[Player_num].rank);
4372
        snprintf( text[0], sizeof(text[0]), "%d. %s%s%-20s", 1, rankstr.first, rankstr.second, static_cast<const char *>(get_local_player().callsign));
4373
 
4374
        snprintf(title, sizeof(title), "%s %d %s", TXT_TEAM_SELECT, Netgame.max_numplayers, TXT_TEAM_PRESS_ENTER);
4375
 
4376
#if DXX_USE_TRACKER
4377
        if( Netgame.Tracker )
4378
        {
4379
                TrackerAckStatus = TrackerAckState::TACK_NOCONNECTION;
4380
                TrackerAckTime = timer_query();
4381
                udp_tracker_register();
4382
        }
4383
#endif
4384
 
4385
GetPlayersAgain:
4386
        j = newmenu_do1(nullptr, title, spd.m.size(), spd.m.data(), net_udp_start_poll, &spd, 1);
4387
 
4388
        save_nplayers = N_players;
4389
 
4390
        if (j<0)
4391
        {
4392
                // Aborted!
4393
                // Dump all players and go back to menu mode
4394
abort:
4395
                // Tell everyone we're bailing
4396
                Netgame.numplayers = 0;
4397
                for (unsigned i = 1; i < save_nplayers; ++i)
4398
                {
4399
                        if (vcplayerptr(i)->connected == CONNECT_DISCONNECTED)
4400
                                continue;
4401
                        const auto &addr = Netgame.players[i].protocol.udp.addr;
4402
                        net_udp_dump_player(addr, DUMP_ABORTED);
4403
                        net_udp_send_game_info(addr, &addr, UPID_GAME_INFO);
4404
                }
4405
                net_udp_broadcast_game_info(UPID_GAME_INFO_LITE);
4406
                Netgame.numplayers = save_nplayers;
4407
 
4408
                Network_status = NETSTAT_MENU;
4409
#if DXX_USE_TRACKER
4410
                if( Netgame.Tracker )
4411
                        udp_tracker_unregister();
4412
#endif
4413
                return(0);
4414
        }
4415
        // Count number of players chosen
4416
 
4417
        N_players = 0;
4418
        range_for (auto &i, partial_const_range(spd.m, save_nplayers))
4419
        {
4420
                if (i.value)
4421
                        N_players++;
4422
        }
4423
 
4424
        if ( N_players > Netgame.max_numplayers) {
4425
                nm_messagebox( TXT_ERROR, 1, TXT_OK, "%s %d %s", TXT_SORRY_ONLY, Netgame.max_numplayers, TXT_NETPLAYERS_IN );
4426
                N_players = save_nplayers;
4427
                goto GetPlayersAgain;
4428
        }
4429
 
4430
// Let host join without Client available. Let's see if our players like that
4431
#if 0 //def RELEASE
4432
        if ( N_players < 2 )    {
4433
                nm_messagebox( TXT_ERROR, 1, TXT_OK, TXT_TEAM_ATLEAST_TWO );
4434
                N_players = save_nplayers;
4435
                goto GetPlayersAgain;
4436
        }
4437
#endif
4438
 
4439
#if 0 //def RELEASE
4440
        if ( (Netgame.gamemode == NETGAME_TEAM_ANARCHY || Netgame.gamemode == NETGAME_CAPTURE_FLAG || Netgame.gamemode == NETGAME_TEAM_HOARD) && (N_players < 2) )
4441
        {
4442
                nm_messagebox(TXT_ERROR, 1, TXT_OK, "You must select at least two\nplayers to start a team game" );
4443
                N_players = save_nplayers;
4444
                goto GetPlayersAgain;
4445
        }
4446
#endif
4447
 
4448
        // Remove players that aren't marked.
4449
        N_players = 0;
4450
        for (int i=0; i<save_nplayers; i++ )
4451
        {
4452
                if (spd.m[i].value)
4453
                {
4454
                        if (i > N_players)
4455
                        {
4456
                                Netgame.players[N_players].callsign = Netgame.players[i].callsign;
4457
                                Netgame.players[N_players].rank=Netgame.players[i].rank;
4458
                                ClipRank (&Netgame.players[N_players].rank);
4459
                        }
4460
                        vmplayerptr(N_players)->connected = CONNECT_PLAYING;
4461
                        N_players++;
4462
                }
4463
                else
4464
                {
4465
                        net_udp_dump_player(Netgame.players[i].protocol.udp.addr, DUMP_DORK);
4466
                }
4467
        }
4468
 
4469
        range_for (auto &i, partial_range(Netgame.players, N_players, Netgame.players.size()))
4470
        {
4471
                i.callsign = {};
4472
                i.rank=0;
4473
        }
4474
 
4475
#if defined(DXX_BUILD_DESCENT_I)
4476
        if (Netgame.gamemode == NETGAME_TEAM_ANARCHY)
4477
#elif defined(DXX_BUILD_DESCENT_II)
4478
        if (Netgame.gamemode == NETGAME_TEAM_ANARCHY ||
4479
            Netgame.gamemode == NETGAME_CAPTURE_FLAG ||
4480
                 Netgame.gamemode == NETGAME_TEAM_HOARD)
4481
#endif
4482
                 if (!net_udp_select_teams())
4483
                        goto abort;
4484
        return(1);
4485
}
4486
}
4487
 
4488
static int net_udp_start_game(void)
4489
{
4490
        int i;
4491
 
4492
        i = udp_open_socket(UDP_Socket[0], UDP_MyPort);
4493
 
4494
        if (i != 0)
4495
                return 0;
4496
 
4497
        if (UDP_MyPort != UDP_PORT_DEFAULT)
4498
                i = udp_open_socket(UDP_Socket[1], UDP_PORT_DEFAULT); // Default port open for Broadcasts
4499
 
4500
        if (i != 0)
4501
                return 0;
4502
 
4503
        // prepare broadcast address to announce our game
4504
        udp_init_broadcast_addresses();
4505
        d_srand(static_cast<fix>(timer_query()));
4506
        Netgame.protocol.udp.GameID=d_rand();
4507
 
4508
        N_players = 0;
4509
        Netgame.game_status = NETSTAT_STARTING;
4510
        Netgame.numplayers = 0;
4511
 
4512
        Network_status = NETSTAT_STARTING;
4513
 
4514
        net_udp_set_game_mode(Netgame.gamemode);
4515
 
4516
        Netgame.protocol.udp.your_index = 0; // I am Host. I need to know that y'know? For syncing later.
4517
 
4518
        if (!net_udp_select_players()
4519
                || StartNewLevel(Netgame.levelnum) == window_event_result::close)
4520
        {
4521
                Game_mode = GM_GAME_OVER;
4522
                return 0;       // see if we want to tweak the game we setup
4523
        }
4524
        state_set_next_autosave(GameUniqueState, Netgame.MPGameplayOptions.AutosaveInterval);
4525
        net_udp_broadcast_game_info(UPID_GAME_INFO_LITE); // game started. broadcast our current status to everyone who wants to know
4526
 
4527
        return 1;       // don't keep params menu or mission listbox (may want to join a game next time)
4528
}
4529
 
4530
static int net_udp_wait_for_sync(void)
4531
{
4532
        char text[60];
4533
        int choice=0;
4534
 
4535
        Network_status = NETSTAT_WAITING;
4536
 
4537
        std::array<newmenu_item, 2> m{{
4538
                nm_item_text(text),
4539
                nm_item_text(TXT_NET_LEAVE),
4540
        }};
4541
        auto i = net_udp_send_request();
4542
 
4543
        if (i >= MAX_PLAYERS)
4544
                return(-1);
4545
 
4546
        snprintf(text, sizeof(text), "%s\n'%s' %s", TXT_NET_WAITING, static_cast<const char *>(Netgame.players[i].callsign), TXT_NET_TO_ENTER );
4547
 
4548
        while (choice > -1)
4549
        {
4550
                timer_update();
4551
                choice=newmenu_do( NULL, TXT_WAIT, m, net_udp_sync_poll, unused_newmenu_userdata );
4552
        }
4553
 
4554
        if (Network_status != NETSTAT_PLAYING)
4555
        {
4556
                UDP_sequence_packet me{};
4557
                me.type = UPID_QUIT_JOINING;
4558
                me.player.callsign = get_local_player().callsign;
4559
                net_udp_send_sequence_packet(me, Netgame.players[0].protocol.udp.addr);
4560
                N_players = 0;
4561
                Game_mode = GM_GAME_OVER;
4562
                return(-1);     // they cancelled
4563
        }
4564
        return(0);
4565
}
4566
 
4567
static int net_udp_request_poll( newmenu *,const d_event &event, const unused_newmenu_userdata_t *)
4568
{
4569
        // Polling loop for waiting-for-requests menu
4570
        int num_ready = 0;
4571
 
4572
        if (event.type != EVENT_WINDOW_DRAW)
4573
                return 0;
4574
        net_udp_listen();
4575
        net_udp_timeout_check(timer_query());
4576
 
4577
        range_for (auto &i, partial_const_range(Players, N_players))
4578
        {
4579
                if ((i.connected == CONNECT_PLAYING) || (i.connected == CONNECT_DISCONNECTED))
4580
                        num_ready++;
4581
        }
4582
 
4583
        if (num_ready == N_players) // All players have checked in or are disconnected
4584
        {
4585
                return -2;
4586
        }
4587
 
4588
        return 0;
4589
}
4590
 
4591
static int net_udp_wait_for_requests(void)
4592
{
4593
        // Wait for other players to load the level before we send the sync
4594
        int choice;
4595
        std::array<newmenu_item, 1> m{{
4596
                nm_item_text(TXT_NET_LEAVE),
4597
        }};
4598
        Network_status = NETSTAT_WAITING;
4599
        net_udp_flush();
4600
 
4601
        get_local_player().connected = CONNECT_PLAYING;
4602
 
4603
menu:
4604
        choice = newmenu_do(NULL, TXT_WAIT, m, net_udp_request_poll, unused_newmenu_userdata);
4605
 
4606
        if (choice == -1)
4607
        {
4608
                // User aborted
4609
                choice = nm_messagebox(NULL, 3, TXT_YES, TXT_NO, TXT_START_NOWAIT, TXT_QUITTING_NOW);
4610
                if (choice == 2) {
4611
                        N_players = 1;
4612
                        return 0;
4613
                }
4614
                if (choice != 0)
4615
                        goto menu;
4616
 
4617
                // User confirmed abort
4618
 
4619
                for (unsigned i = 0; i < N_players; ++i)
4620
                {
4621
                        if (vcplayerptr(i)->connected != CONNECT_DISCONNECTED && i != Player_num)
4622
                        {
4623
                                net_udp_dump_player(Netgame.players[i].protocol.udp.addr, DUMP_ABORTED);
4624
                        }
4625
                }
4626
                return -1;
4627
        }
4628
        else if (choice != -2)
4629
                goto menu;
4630
 
4631
        return 0;
4632
}
4633
 
4634
/* Do required syncing after each level, before starting new one */
4635
window_event_result net_udp_level_sync()
4636
{
4637
        int result = 0;
4638
 
4639
        UDP_MData = {};
4640
        net_udp_noloss_init_mdata_queue();
4641
 
4642
        net_udp_flush(); // Flush any old packets
4643
 
4644
        if (N_players == 0)
4645
                result = net_udp_wait_for_sync();
4646
        else if (multi_i_am_master())
4647
        {
4648
                result = net_udp_wait_for_requests();
4649
                if (!result)
4650
                        result = net_udp_send_sync();
4651
        }
4652
        else
4653
                result = net_udp_wait_for_sync();
4654
 
4655
        if (result)
4656
        {
4657
                get_local_player().connected = CONNECT_DISCONNECTED;
4658
                net_udp_send_endlevel_packet();
4659
                show_menus();
4660
                net_udp_close();
4661
                return window_event_result::close;
4662
        }
4663
        return window_event_result::handled;
4664
}
4665
 
4666
namespace dsx {
4667
int net_udp_do_join_game()
4668
{
4669
 
4670
        if (Netgame.game_status == NETSTAT_ENDLEVEL)
4671
        {
4672
                nm_messagebox(TXT_SORRY, 1, TXT_OK, TXT_NET_GAME_BETWEEN2);
4673
                return 0;
4674
        }
4675
 
4676
        // Check for valid mission name
4677
        {
4678
                mission_entry_predicate mission_predicate;
4679
                mission_predicate.filesystem_name = Netgame.mission_name;
4680
#if defined(DXX_BUILD_DESCENT_II)
4681
                /* FIXME: This should be set to true and the version set
4682
                 * accordingly.  However, currently the host does not provide
4683
                 * the mission version to the guests.
4684
                 */
4685
                mission_predicate.check_version = false;
4686
#endif
4687
        if (const auto errstr = load_mission_by_name(mission_predicate, mission_name_type::guess))
4688
        {
4689
                nm_messagebox(nullptr, 1, TXT_OK, "%s\n\n%s", TXT_MISSION_NOT_FOUND, errstr);
4690
                return 0;
4691
        }
4692
        }
4693
 
4694
#if defined(DXX_BUILD_DESCENT_II)
4695
        if (is_D2_OEM)
4696
        {
4697
                if (Netgame.levelnum>8)
4698
                {
4699
                        nm_messagebox(NULL, 1, TXT_OK, "This OEM version only supports\nthe first 8 levels!");
4700
                        return 0;
4701
                }
4702
        }
4703
 
4704
        if (is_MAC_SHARE)
4705
        {
4706
                if (Netgame.levelnum > 4)
4707
                {
4708
                        nm_messagebox(NULL, 1, TXT_OK, "This SHAREWARE version only supports\nthe first 4 levels!");
4709
                        return 0;
4710
                }
4711
        }
4712
 
4713
        if ( !HoardEquipped() && (Netgame.gamemode == NETGAME_HOARD || Netgame.gamemode == NETGAME_TEAM_HOARD) )
4714
        {
4715
                nm_messagebox(TXT_SORRY, 1, TXT_OK, "HOARD(.ham) not installed. You can't join.");
4716
                return 0;
4717
        }
4718
 
4719
        Network_status = NETSTAT_BROWSING; // We are looking at a game menu
4720
#endif
4721
 
4722
        if (net_udp_can_join_netgame(&Netgame) == join_netgame_status_code::game_in_disallowed_state)
4723
        {
4724
                nm_messagebox(TXT_SORRY, 1, TXT_OK, Netgame.numplayers == Netgame.max_numplayers ? TXT_GAME_FULL : TXT_IN_PROGRESS);
4725
                return 0;
4726
        }
4727
 
4728
        // Choice is valid, prepare to join in
4729
        GameUniqueState.Difficulty_level = Netgame.difficulty;
4730
        change_playernum_to(1);
4731
 
4732
        net_udp_set_game_mode(Netgame.gamemode);
4733
 
4734
        return StartNewLevel(Netgame.levelnum) == window_event_result::handled;     // look ma, we're in a game!!! (If level syncing didn't fail -kreatordxx)
4735
}
4736
}
4737
 
4738
namespace dsx {
4739
void net_udp_leave_game()
4740
{
4741
        int nsave;
4742
 
4743
        net_udp_do_frame(1, 1);
4744
 
4745
        if (multi_i_am_master())
4746
        {
4747
                while (Network_sending_extras>1 && Player_joining_extras!=-1)
4748
                {
4749
                        timer_update();
4750
                        net_udp_send_extras();
4751
                }
4752
 
4753
                Netgame.numplayers = 0;
4754
                nsave=N_players;
4755
                N_players=0;
4756
                for (unsigned i = 1; i < nsave; ++i)
4757
                {
4758
                        if (vcplayerptr(i)->connected == CONNECT_DISCONNECTED)
4759
                                continue;
4760
                        const auto &addr = Netgame.players[i].protocol.udp.addr;
4761
                        net_udp_send_game_info(addr, &addr, UPID_GAME_INFO);
4762
                }
4763
                net_udp_broadcast_game_info(UPID_GAME_INFO_LITE);
4764
                N_players=nsave;
4765
#if DXX_USE_TRACKER
4766
                if( Netgame.Tracker )
4767
                        udp_tracker_unregister();
4768
#endif
4769
        }
4770
 
4771
        get_local_player().connected = CONNECT_DISCONNECTED;
4772
        change_playernum_to(0);
4773
#if defined(DXX_BUILD_DESCENT_II)
4774
        write_player_file();
4775
#endif
4776
 
4777
        net_udp_flush();
4778
        net_udp_close();
4779
}
4780
}
4781
 
4782
static void net_udp_flush(RAIIsocket &s)
4783
{
4784
        if (!s)
4785
                return;
4786
        unsigned i = 0;
4787
        struct _sockaddr sender_addr;
4788
        std::array<uint8_t, UPID_MAX_SIZE> packet;
4789
        while (udp_receive_packet(s, packet.data(), packet.size(), &sender_addr) > 0)
4790
                ++i;
4791
        if (i)
4792
                con_printf(CON_VERBOSE, "Flushed %u UDP packets from socket %i", i, static_cast<int>(s));
4793
}
4794
 
4795
void net_udp_flush()
4796
{
4797
        range_for (auto &s, UDP_Socket)
4798
                net_udp_flush(s);
4799
}
4800
 
4801
static void net_udp_listen(RAIIsocket &sock)
4802
{
4803
        if (!sock)
4804
                return;
4805
        struct _sockaddr sender_addr;
4806
        std::array<uint8_t, UPID_MAX_SIZE> packet;
4807
        for (;;)
4808
        {
4809
                const int size = udp_receive_packet(sock, packet.data(), packet.size(), &sender_addr);
4810
                if (!(size > 0))
4811
                        break;
4812
                net_udp_process_packet(packet.data(), sender_addr, size);
4813
        }
4814
}
4815
 
4816
void net_udp_listen()
4817
{
4818
        range_for (auto &s, UDP_Socket)
4819
                net_udp_listen(s);
4820
}
4821
 
4822
void net_udp_send_data(const uint8_t *const ptr, const unsigned len, const int priority)
4823
{
4824
#if DXX_HAVE_POISON_VALGRIND
4825
        VALGRIND_CHECK_MEM_IS_DEFINED(ptr, len);
4826
#endif
4827
        char check;
4828
 
4829
        if ((UDP_MData.mbuf_size+len) > UPID_MDATA_BUF_SIZE )
4830
        {
4831
                check = ptr[0];
4832
                net_udp_send_mdata(0, timer_query());
4833
                if (UDP_MData.mbuf_size != 0)
4834
                        Int3();
4835
                Assert(check == ptr[0]);
4836
                (void)check;
4837
        }
4838
 
4839
        Assert(UDP_MData.mbuf_size+len <= UPID_MDATA_BUF_SIZE);
4840
 
4841
        memcpy( &UDP_MData.mbuf[UDP_MData.mbuf_size], ptr, len );
4842
        UDP_MData.mbuf_size += len;
4843
 
4844
        if (priority)
4845
                net_udp_send_mdata((priority==2)?1:0, timer_query());
4846
}
4847
 
4848
void net_udp_timeout_check(fix64 time)
4849
{
4850
        static fix64 last_timeout_time = 0;
4851
        if (time>=last_timeout_time+F1_0)
4852
        {
4853
                // Check for player timeouts
4854
                for (playernum_t i = 0; i < N_players; i++)
4855
                {
4856
                        if ((i != Player_num) && (vcplayerptr(i)->connected != CONNECT_DISCONNECTED))
4857
                        {
4858
                                if ((Netgame.players[i].LastPacketTime == 0) || (Netgame.players[i].LastPacketTime > time))
4859
                                {
4860
                                        Netgame.players[i].LastPacketTime = time;
4861
                                }
4862
                                else if ((time - Netgame.players[i].LastPacketTime) > UDP_TIMEOUT)
4863
                                {
4864
                                        multi_disconnect_player(i);
4865
                                }
4866
                        }
4867
                }
4868
                last_timeout_time = time;
4869
        }
4870
}
4871
 
4872
namespace dsx {
4873
void net_udp_do_frame(int force, int listen)
4874
{
4875
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
4876
        static fix64 last_pdata_time = 0, last_mdata_time = 16, last_endlevel_time = 32, last_bcast_time = 48, last_resync_time = 64;
4877
 
4878
        if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
4879
                return;
4880
 
4881
        const fix64 time = timer_update();
4882
 
4883
        if (WaitForRefuseAnswer && time>(RefuseTimeLimit+(F1_0*12)))
4884
                WaitForRefuseAnswer=0;
4885
 
4886
        // Send positional update either in the regular PPS interval OR if forced
4887
        if (force || (time >= (last_pdata_time+(F1_0/Netgame.PacketsPerSec))))
4888
        {
4889
                last_pdata_time = time;
4890
                net_udp_send_pdata();
4891
#if defined(DXX_BUILD_DESCENT_II)
4892
                multi_send_thief_frame();
4893
#endif
4894
        }
4895
 
4896
        if (force || (time >= (last_mdata_time+(F1_0/10))))
4897
        {
4898
                last_mdata_time = time;
4899
                multi_send_robot_frame(0);
4900
                net_udp_send_mdata(0, time);
4901
        }
4902
 
4903
        net_udp_noloss_process_queue(time);
4904
 
4905
        if (VerifyPlayerJoined!=-1 && time >= last_resync_time+F1_0)
4906
        {
4907
                last_resync_time = time;
4908
                net_udp_resend_sync_due_to_packet_loss(); // This will resend to UDP_sync_player
4909
        }
4910
 
4911
        if (time >= last_endlevel_time + F1_0 && LevelUniqueControlCenterState.Control_center_destroyed)
4912
        {
4913
                last_endlevel_time = time;
4914
                net_udp_send_endlevel_packet();
4915
        }
4916
 
4917
        // broadcast lite_info every 10 seconds
4918
        if (multi_i_am_master() && time>=last_bcast_time+(F1_0*10))
4919
        {
4920
                last_bcast_time = time;
4921
                net_udp_broadcast_game_info(UPID_GAME_INFO_LITE);
4922
#if DXX_USE_TRACKER
4923
                if ( Netgame.Tracker )
4924
                        udp_tracker_register();
4925
#endif
4926
        }
4927
 
4928
        net_udp_ping_frame(time);
4929
#if DXX_USE_TRACKER
4930
        udp_tracker_verify_ack_timeout();
4931
#endif
4932
 
4933
        if (listen)
4934
        {
4935
                net_udp_timeout_check(time);
4936
                net_udp_listen();
4937
                if (Network_send_objects)
4938
                        net_udp_send_objects();
4939
                if (Network_sending_extras && VerifyPlayerJoined==-1)
4940
                        net_udp_send_extras();
4941
        }
4942
 
4943
        udp_traffic_stat();
4944
}
4945
}
4946
 
4947
/* CODE FOR PACKET LOSS PREVENTION - START */
4948
/* This code tries to make sure that packets with opcode UPID_MDATA_PNEEDACK aren't lost and sent and received in order. */
4949
/*
4950
 * Adds a packet to our queue. Should be called when an IMPORTANT mdata packet is created.
4951
 * player_ack is an array which should contain 0 for each player that needs to send an ACK signal.
4952
 */
4953
static void net_udp_noloss_add_queue_pkt(fix64 time, const ubyte *data, ushort data_size, ubyte pnum, ubyte player_ack[MAX_PLAYERS])
4954
{
4955
        if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
4956
                return;
4957
 
4958
        if (!Netgame.PacketLossPrevention)
4959
                return;
4960
 
4961
        if (UDP_mdata_queue_highest == UDP_MDATA_STOR_QUEUE_SIZE) // The list is full. That should not happen. But if it does, we must do something.
4962
        {
4963
                con_printf(CON_VERBOSE, "P#%u: MData store list is full!", Player_num);
4964
                if (multi_i_am_master()) // I am host. I will kick everyone who did not ACK the first packet and then remove it.
4965
                {
4966
                        for ( int i=1; i<N_players; i++ )
4967
                                if (UDP_mdata_queue[0].player_ack[i] == 0)
4968
                                        net_udp_dump_player(Netgame.players[i].protocol.udp.addr, DUMP_PKTTIMEOUT);
4969
                        std::move(std::next(UDP_mdata_queue.begin()), UDP_mdata_queue.end(), UDP_mdata_queue.begin());
4970
                        UDP_mdata_queue[UDP_MDATA_STOR_QUEUE_SIZE - 1] = {};
4971
                        UDP_mdata_queue_highest--;
4972
                }
4973
                else // I am just a client. I gotta go.
4974
                {
4975
                        Netgame.PacketLossPrevention = 0; // Disable PLP - otherwise we get stuck in an infinite loop here. NOTE: We could as well clean the whole queue to continue protect our disconnect signal bit it's not that important - we just wanna leave.
4976
                        if (Game_wind)
4977
                                window_set_visible(Game_wind, 0);
4978
                        nm_messagebox(NULL, 1, TXT_OK, "You left the game. You failed\nsending important packets.\nSorry.");
4979
                        if (Game_wind)
4980
                                window_set_visible(Game_wind, 1);
4981
                        multi_quit_game = 1;
4982
                        game_leave_menus();
4983
                }
4984
                Assert(UDP_mdata_queue_highest == (UDP_MDATA_STOR_QUEUE_SIZE - 1));
4985
        }
4986
 
4987
        con_printf(CON_VERBOSE, "P#%u: Adding MData pkt_num [%i,%i,%i,%i,%i,%i,%i,%i], type %i from P#%i to MData store list", Player_num, UDP_mdata_trace[0].pkt_num_tosend,UDP_mdata_trace[1].pkt_num_tosend,UDP_mdata_trace[2].pkt_num_tosend,UDP_mdata_trace[3].pkt_num_tosend,UDP_mdata_trace[4].pkt_num_tosend,UDP_mdata_trace[5].pkt_num_tosend,UDP_mdata_trace[6].pkt_num_tosend,UDP_mdata_trace[7].pkt_num_tosend, data[0], pnum);
4988
        UDP_mdata_queue[UDP_mdata_queue_highest].used = 1;
4989
        UDP_mdata_queue[UDP_mdata_queue_highest].pkt_initial_timestamp = time;
4990
        for (unsigned i = 0; i < MAX_PLAYERS; ++i)
4991
        {
4992
                if (i == Player_num || player_ack[i] || vcplayerptr(i)->connected == CONNECT_DISCONNECTED) // if player me, is not playing or does not require an ACK, do not add timestamp or increment pkt_num
4993
                        continue;
4994
 
4995
                UDP_mdata_queue[UDP_mdata_queue_highest].pkt_timestamp[i] = time;
4996
                UDP_mdata_queue[UDP_mdata_queue_highest].pkt_num[i] = UDP_mdata_trace[i].pkt_num_tosend;
4997
                UDP_mdata_trace[i].pkt_num_tosend++;
4998
                if (UDP_mdata_trace[i].pkt_num_tosend > UDP_MDATA_PKT_NUM_MAX)
4999
                        UDP_mdata_trace[i].pkt_num_tosend = UDP_MDATA_PKT_NUM_MIN;
5000
        }
5001
        UDP_mdata_queue[UDP_mdata_queue_highest].Player_num = pnum;
5002
        memcpy( &UDP_mdata_queue[UDP_mdata_queue_highest].player_ack, player_ack, sizeof(ubyte)*MAX_PLAYERS);
5003
        memcpy( &UDP_mdata_queue[UDP_mdata_queue_highest].data, data, sizeof(char)*data_size );
5004
        UDP_mdata_queue[UDP_mdata_queue_highest].data_size = data_size;
5005
        UDP_mdata_queue_highest++;
5006
}
5007
 
5008
/*
5009
 * We have received a MDATA packet. Send ACK response to sender!
5010
 * Make sure this packet has the expected packet number so we get them all in order. If not, reject it and await further packets.
5011
 * Also check in our UDP_mdata_trace list, if we got this packet already. If yes, return 0 so do not process it!
5012
 */
5013
static int net_udp_noloss_validate_mdata(uint32_t pkt_num, ubyte sender_pnum, const _sockaddr &sender_addr)
5014
{
5015
        ubyte pkt_sender_pnum = sender_pnum;
5016
        int len = 0;
5017
 
5018
        // If we are a client, we get all our packets from the host.
5019
        if (!multi_i_am_master())
5020
                sender_pnum = 0;
5021
 
5022
        // Check if this comes from a valid IP
5023
        if (sender_addr != Netgame.players[sender_pnum].protocol.udp.addr)
5024
                return 0;
5025
 
5026
        // Prepare the ACK (but do not send, yet)
5027
        std::array<uint8_t, 7> buf;
5028
        buf[len] = UPID_MDATA_ACK;                                                                                      len++;
5029
        buf[len] = Player_num;                                                                                          len++;
5030
        buf[len] = pkt_sender_pnum;                                                                                     len++;
5031
        PUT_INTEL_INT(&buf[len], pkt_num);                                                                              len += 4;
5032
 
5033
        // Make sure this is the packet we are expecting!
5034
        if (UDP_mdata_trace[sender_pnum].pkt_num_torecv != pkt_num)
5035
        {
5036
                range_for (auto &i, partial_const_range(UDP_mdata_trace[sender_pnum].pkt_num, static_cast<uint32_t>(UDP_MDATA_STOR_QUEUE_SIZE)))
5037
                {
5038
                        if (pkt_num == i) // We got this packet already - need to REsend ACK
5039
                        {
5040
                                con_printf(CON_VERBOSE, "P#%u: Resending MData ACK for pkt %i we already got by pnum %i",Player_num, pkt_num, sender_pnum);
5041
                                dxx_sendto(sender_addr, UDP_Socket[0], buf, 0);
5042
                                return 0;
5043
                        }
5044
                }
5045
                con_printf(CON_VERBOSE, "P#%u: Rejecting MData pkt %i - expected %i by pnum %i",Player_num, pkt_num, UDP_mdata_trace[sender_pnum].pkt_num_torecv, sender_pnum);
5046
                return 0; // Not the right packet and we haven't gotten it, yet either. So bail out and wait for the right one.
5047
        }
5048
 
5049
        con_printf(CON_VERBOSE, "P#%u: Sending MData ACK for pkt %i by pnum %i",Player_num, pkt_num, sender_pnum);
5050
        dxx_sendto(sender_addr, UDP_Socket[0], buf, 0);
5051
 
5052
        UDP_mdata_trace[sender_pnum].cur_slot++;
5053
        if (UDP_mdata_trace[sender_pnum].cur_slot >= UDP_MDATA_STOR_QUEUE_SIZE)
5054
                UDP_mdata_trace[sender_pnum].cur_slot = 0;
5055
        UDP_mdata_trace[sender_pnum].pkt_num[UDP_mdata_trace[sender_pnum].cur_slot] = pkt_num;
5056
        UDP_mdata_trace[sender_pnum].pkt_num_torecv++;
5057
        if (UDP_mdata_trace[sender_pnum].pkt_num_torecv > UDP_MDATA_PKT_NUM_MAX)
5058
                UDP_mdata_trace[sender_pnum].pkt_num_torecv = UDP_MDATA_PKT_NUM_MIN;
5059
        return 1;
5060
}
5061
 
5062
/* We got an ACK by a player. Set this player slot to positive! */
5063
void net_udp_noloss_got_ack(const uint8_t *data, uint_fast32_t data_len)
5064
{
5065
        int len = 0;
5066
        uint32_t pkt_num = 0;
5067
        ubyte sender_pnum = 0, dest_pnum = 0;
5068
 
5069
        if (data_len != 7)
5070
                return;
5071
 
5072
                                                                                                                        len++;
5073
        sender_pnum = data[len];                                                                                        len++;
5074
        dest_pnum = data[len];                                                                                          len++;
5075
        pkt_num = GET_INTEL_INT(&data[len]);                                                                            len += 4;
5076
 
5077
        for (int i = 0; i < UDP_mdata_queue_highest; i++)
5078
        {
5079
                if ((pkt_num == UDP_mdata_queue[i].pkt_num[sender_pnum]) && (dest_pnum == UDP_mdata_queue[i].Player_num))
5080
                {
5081
                        con_printf(CON_VERBOSE, "P#%u: Got MData ACK for pkt_num %i from pnum %i for pnum %i",Player_num, pkt_num, sender_pnum, dest_pnum);
5082
                        UDP_mdata_queue[i].player_ack[sender_pnum] = 1;
5083
                        break;
5084
                }
5085
        }
5086
}
5087
 
5088
/* Init/Free the queue. Call at start and end of a game or level. */
5089
void net_udp_noloss_init_mdata_queue(void)
5090
{
5091
        UDP_mdata_queue_highest=0;
5092
        con_printf(CON_VERBOSE, "P#%u: Clearing MData store/trace list",Player_num);
5093
        UDP_mdata_queue = {};
5094
        for (int i = 0; i < MAX_PLAYERS; i++)
5095
                net_udp_noloss_clear_mdata_trace(i);
5096
}
5097
 
5098
/* Reset the trace list for given player when (dis)connect happens */
5099
void net_udp_noloss_clear_mdata_trace(ubyte player_num)
5100
{
5101
        con_printf(CON_VERBOSE, "P#%u: Clearing trace list for %i",Player_num, player_num);
5102
        UDP_mdata_trace[player_num].pkt_num = {};
5103
        UDP_mdata_trace[player_num].cur_slot = 0;
5104
        UDP_mdata_trace[player_num].pkt_num_torecv = UDP_MDATA_PKT_NUM_MIN;
5105
        UDP_mdata_trace[player_num].pkt_num_tosend = UDP_MDATA_PKT_NUM_MIN;
5106
}
5107
 
5108
/*
5109
 * The main queue-process function.
5110
 * Check if we can remove a packet from queue, and check if there are packets in queue which we need to re-send
5111
 */
5112
void net_udp_noloss_process_queue(fix64 time)
5113
{
5114
        int total_len = 0;
5115
 
5116
        if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
5117
                return;
5118
 
5119
        if (!Netgame.PacketLossPrevention)
5120
                return;
5121
 
5122
        for (int queuec = 0; queuec < UDP_mdata_queue_highest; queuec++)
5123
        {
5124
                int needack = 0;
5125
 
5126
                // This might happen if we get out ACK's in the wrong order. So ignore that packet for now. It'll resolve itself.
5127
                if (!UDP_mdata_queue[queuec].used)
5128
                        continue;
5129
 
5130
                // Check if at least one connected player has not ACK'd the packet
5131
                for (unsigned plc = 0; plc < MAX_PLAYERS; ++plc)
5132
                {
5133
                        // If player is not playing anymore, we can remove him from list. Also remove *me* (even if that should have been done already). Also make sure Clients do not send to anyone else than Host
5134
                        if ((vcplayerptr(plc)->connected != CONNECT_PLAYING || plc == Player_num) || (!multi_i_am_master() && plc > 0))
5135
                                UDP_mdata_queue[queuec].player_ack[plc] = 1;
5136
 
5137
                        if (!UDP_mdata_queue[queuec].player_ack[plc])
5138
                        {
5139
                                // Resend if enough time has passed.
5140
                                if (UDP_mdata_queue[queuec].pkt_timestamp[plc] + (F1_0/4) <= time)
5141
                                {
5142
                                        ubyte buf[sizeof(UDP_mdata_info)];
5143
                                        int len = 0;
5144
 
5145
                                        con_printf(CON_VERBOSE, "P#%u: Resending pkt_num %i from pnum %i to pnum %i",Player_num, UDP_mdata_queue[queuec].pkt_num[plc], UDP_mdata_queue[queuec].Player_num, plc);
5146
 
5147
                                        UDP_mdata_queue[queuec].pkt_timestamp[plc] = time;
5148
                                        memset(&buf, 0, sizeof(UDP_mdata_info));
5149
 
5150
                                        // Prepare the packet and send it
5151
                                        buf[len] = UPID_MDATA_PNEEDACK;                                                                                                 len++;
5152
                                        buf[len] = UDP_mdata_queue[queuec].Player_num;                                                          len++;
5153
                                        PUT_INTEL_INT(buf + len, UDP_mdata_queue[queuec].pkt_num[plc]);                                 len += 4;
5154
                                        memcpy(&buf[len], UDP_mdata_queue[queuec].data.data(), sizeof(char)*UDP_mdata_queue[queuec].data_size);
5155
                                                                                                                                                                                                len += UDP_mdata_queue[queuec].data_size;
5156
                                        dxx_sendto(Netgame.players[plc].protocol.udp.addr, UDP_Socket[0], buf, len, 0);
5157
                                        total_len += len;
5158
                                }
5159
                                needack++;
5160
                        }
5161
                }
5162
 
5163
                // Check if we can remove that packet due to to it had no resend's or Timeout
5164
                if (needack==0 || (UDP_mdata_queue[queuec].pkt_initial_timestamp + UDP_TIMEOUT <= time))
5165
                {
5166
                        if (needack) // packet timed out but still not all have ack'd.
5167
                        {
5168
                                if (multi_i_am_master()) // We are host, so we kick the remaining players.
5169
                                {
5170
                                        for ( int plc=1; plc<N_players; plc++ )
5171
                                                if (UDP_mdata_queue[queuec].player_ack[plc] == 0)
5172
                                                        net_udp_dump_player(Netgame.players[plc].protocol.udp.addr, DUMP_PKTTIMEOUT);
5173
                                }
5174
                                else // We are client, so we gotta go.
5175
                                {
5176
                                        Netgame.PacketLossPrevention = 0; // Disable PLP - otherwise we get stuck in an infinite loop here. NOTE: We could as well clean the whole queue to continue protect our disconnect signal bit it's not that important - we just wanna leave.
5177
                                        if (Game_wind)
5178
                                                window_set_visible(Game_wind, 0);
5179
                                        nm_messagebox(NULL, 1, TXT_OK, "You left the game. You failed\nsending important packets.\nSorry.");
5180
                                        if (Game_wind)
5181
                                                window_set_visible(Game_wind, 1);
5182
                                        multi_quit_game = 1;
5183
                                        game_leave_menus();
5184
                                }
5185
                        }
5186
                        con_printf(CON_VERBOSE, "P#%u: Removing stored pkt_num [%i,%i,%i,%i,%i,%i,%i,%i] - missing ACKs: %i",Player_num, UDP_mdata_queue[queuec].pkt_num[0],UDP_mdata_queue[queuec].pkt_num[1],UDP_mdata_queue[queuec].pkt_num[2],UDP_mdata_queue[queuec].pkt_num[3],UDP_mdata_queue[queuec].pkt_num[4],UDP_mdata_queue[queuec].pkt_num[5],UDP_mdata_queue[queuec].pkt_num[6],UDP_mdata_queue[queuec].pkt_num[7], needack); // Just *marked* for removal. The actual process happens further below.
5187
                        UDP_mdata_queue[queuec].used = 0;
5188
                }
5189
 
5190
                // Send up to half our max packet size
5191
                if (total_len >= (UPID_MAX_SIZE/2))
5192
                        break;
5193
        }
5194
 
5195
        // Now that we are done processing the queue, actually remove all unused packets from the top of the list.
5196
        while (!UDP_mdata_queue[0].used && UDP_mdata_queue_highest > 0)
5197
        {
5198
                std::move(std::next(UDP_mdata_queue.begin()), UDP_mdata_queue.end(), UDP_mdata_queue.begin());
5199
                UDP_mdata_queue[UDP_MDATA_STOR_QUEUE_SIZE - 1] = {};
5200
                UDP_mdata_queue_highest--;
5201
        }
5202
}
5203
/* CODE FOR PACKET LOSS PREVENTION - END */
5204
 
5205
void net_udp_send_mdata_direct(const ubyte *data, int data_len, int pnum, int needack)
5206
{
5207
        ubyte buf[sizeof(UDP_mdata_info)];
5208
        ubyte pack[MAX_PLAYERS];
5209
        int len = 0;
5210
 
5211
        if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
5212
                return;
5213
 
5214
        if (!(data_len > 0))
5215
                return;
5216
 
5217
        if (!multi_i_am_master() && pnum != 0)
5218
                Error("Client sent direct data to non-Host in net_udp_send_mdata_direct()!\n");
5219
 
5220
        if (!Netgame.PacketLossPrevention)
5221
                needack = 0;
5222
 
5223
        memset(&buf, 0, sizeof(UDP_mdata_info));
5224
        memset(&pack, 1, sizeof(ubyte)*MAX_PLAYERS);
5225
 
5226
        pack[pnum] = 0;
5227
 
5228
        if (needack)
5229
                buf[len] = UPID_MDATA_PNEEDACK;
5230
        else
5231
                buf[len] = UPID_MDATA_PNORM;
5232
                                                                                                                len++;
5233
        buf[len] = Player_num;                                                                                  len++;
5234
        if (needack)
5235
        {
5236
                PUT_INTEL_INT(buf + len, UDP_mdata_trace[pnum].pkt_num_tosend);                                 len += 4;
5237
        }
5238
        memcpy(&buf[len], data, sizeof(char)*data_len);                                                         len += data_len;
5239
 
5240
        dxx_sendto(Netgame.players[pnum].protocol.udp.addr, UDP_Socket[0], buf, len, 0);
5241
 
5242
        if (needack)
5243
                net_udp_noloss_add_queue_pkt(timer_query(), data, data_len, Player_num, pack);
5244
}
5245
 
5246
void net_udp_send_mdata(int needack, fix64 time)
5247
{
5248
        ubyte buf[sizeof(UDP_mdata_info)];
5249
        ubyte pack[MAX_PLAYERS];
5250
        int len = 0;
5251
 
5252
        if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
5253
                return;
5254
 
5255
        if (!(UDP_MData.mbuf_size > 0))
5256
                return;
5257
 
5258
        if (!Netgame.PacketLossPrevention)
5259
                needack = 0;
5260
 
5261
        memset(&buf, 0, sizeof(UDP_mdata_info));
5262
        memset(&pack, 1, sizeof(ubyte)*MAX_PLAYERS);
5263
 
5264
        if (needack)
5265
                buf[len] = UPID_MDATA_PNEEDACK;
5266
        else
5267
                buf[len] = UPID_MDATA_PNORM;
5268
                                                                                                                len++;
5269
        buf[len] = Player_num;                                                                                  len++;
5270
        if (needack)                                                                                            len += 4; // we place the pkt_num later since it changes per player
5271
        memcpy(&buf[len], UDP_MData.mbuf.data(), sizeof(char)*UDP_MData.mbuf_size);
5272
        len += UDP_MData.mbuf_size;
5273
 
5274
        if (multi_i_am_master())
5275
        {
5276
                for (unsigned i = 1; i < MAX_PLAYERS; ++i)
5277
                {
5278
                        if (vcplayerptr(i)->connected == CONNECT_PLAYING)
5279
                        {
5280
                                if (needack) // assign pkt_num
5281
                                        PUT_INTEL_INT(buf + 2, UDP_mdata_trace[i].pkt_num_tosend);
5282
                                dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], buf, len, 0);
5283
                                pack[i] = 0;
5284
                        }
5285
                }
5286
        }
5287
        else
5288
        {
5289
                if (needack) // assign pkt_num
5290
                        PUT_INTEL_INT(buf + 2, UDP_mdata_trace[0].pkt_num_tosend);
5291
                dxx_sendto(Netgame.players[0].protocol.udp.addr, UDP_Socket[0], buf, len, 0);
5292
                pack[0] = 0;
5293
        }
5294
 
5295
        if (needack)
5296
                net_udp_noloss_add_queue_pkt(time, UDP_MData.mbuf.data(), UDP_MData.mbuf_size, Player_num, pack);
5297
 
5298
        // Clear UDP_MData except pkt_num. That one must not be deleted so we can clearly keep track of important packets.
5299
        UDP_MData.type = 0;
5300
        UDP_MData.Player_num = 0;
5301
        UDP_MData.mbuf_size = 0;
5302
        UDP_MData.mbuf = {};
5303
}
5304
 
5305
void net_udp_process_mdata(uint8_t *data, uint_fast32_t data_len, const _sockaddr &sender_addr, int needack)
5306
{
5307
        int pnum = data[1], dataoffset = (needack?6:2);
5308
 
5309
        // Check if packet might be bogus
5310
        if ((pnum < 0) || (data_len > sizeof(UDP_mdata_info)))
5311
                return;
5312
 
5313
        // Check if it came from valid IP
5314
        if (multi_i_am_master())
5315
        {
5316
                if (sender_addr != Netgame.players[pnum].protocol.udp.addr)
5317
                {
5318
                        return;
5319
                }
5320
        }
5321
        else
5322
        {
5323
                if (sender_addr != Netgame.players[0].protocol.udp.addr)
5324
                {
5325
                        return;
5326
                }
5327
        }
5328
 
5329
        // Add needack packet and check for possible redundancy
5330
        if (needack)
5331
        {
5332
                if (!net_udp_noloss_validate_mdata(GET_INTEL_INT(&data[2]), pnum, sender_addr))
5333
                        return;
5334
        }
5335
 
5336
        // send this to everyone else (if master)
5337
        if (multi_i_am_master())
5338
        {
5339
                ubyte pack[MAX_PLAYERS];
5340
                memset(&pack, 1, sizeof(ubyte)*MAX_PLAYERS);
5341
 
5342
                for (unsigned i = 1; i < MAX_PLAYERS; ++i)
5343
                {
5344
                        if (i != pnum && vcplayerptr(i)->connected == CONNECT_PLAYING)
5345
                        {
5346
                                if (needack)
5347
                                {
5348
                                        pack[i] = 0;
5349
                                        PUT_INTEL_INT(data + 2, UDP_mdata_trace[i].pkt_num_tosend);
5350
                                }
5351
                                dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], data, data_len, 0);
5352
 
5353
                        }
5354
                }
5355
 
5356
                if (needack)
5357
                {
5358
                        net_udp_noloss_add_queue_pkt(timer_query(), data+dataoffset, data_len-dataoffset, pnum, pack);
5359
                }
5360
        }
5361
 
5362
        // Check if we are in correct state to process the packet
5363
        if (!((Network_status == NETSTAT_PLAYING)||(Network_status == NETSTAT_ENDLEVEL) || Network_status==NETSTAT_WAITING))
5364
                return;
5365
 
5366
        // Process
5367
 
5368
        multi_process_bigdata(pnum, data+dataoffset, data_len-dataoffset );
5369
}
5370
 
5371
void net_udp_send_pdata()
5372
{
5373
        auto &Objects = LevelUniqueObjectState.Objects;
5374
        auto &vmobjptr = Objects.vmptr;
5375
        std::array<uint8_t, 3 + quaternionpos::packed_size::value> buf;
5376
        int len = 0;
5377
 
5378
        if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
5379
                return;
5380
        auto &plr = get_local_player();
5381
        if (plr.connected != CONNECT_PLAYING)
5382
                return;
5383
        if ( !( Network_status == NETSTAT_PLAYING || Network_status == NETSTAT_ENDLEVEL ) )
5384
                return;
5385
 
5386
        buf[len] = UPID_PDATA;                                                                  len++;
5387
        buf[len] = Player_num;                                                                  len++;
5388
        buf[len] = plr.connected;                                               len++;
5389
 
5390
        quaternionpos qpp{};
5391
        create_quaternionpos(qpp, vmobjptr(plr.objnum));
5392
        PUT_INTEL_SHORT(&buf[len], qpp.orient.w);                                                       len += 2;
5393
        PUT_INTEL_SHORT(&buf[len], qpp.orient.x);                                                       len += 2;
5394
        PUT_INTEL_SHORT(&buf[len], qpp.orient.y);                                                       len += 2;
5395
        PUT_INTEL_SHORT(&buf[len], qpp.orient.z);                                                       len += 2;
5396
        PUT_INTEL_INT(&buf[len], qpp.pos.x);                                                    len += 4;
5397
        PUT_INTEL_INT(&buf[len], qpp.pos.y);                                                    len += 4;
5398
        PUT_INTEL_INT(&buf[len], qpp.pos.z);                                                    len += 4;
5399
        PUT_INTEL_SHORT(&buf[len], qpp.segment);                                                        len += 2;
5400
        PUT_INTEL_INT(&buf[len], qpp.vel.x);                                                    len += 4;
5401
        PUT_INTEL_INT(&buf[len], qpp.vel.y);                                                    len += 4;
5402
        PUT_INTEL_INT(&buf[len], qpp.vel.z);                                                    len += 4;
5403
        PUT_INTEL_INT(&buf[len], qpp.rotvel.x);                                                 len += 4;
5404
        PUT_INTEL_INT(&buf[len], qpp.rotvel.y);                                                 len += 4;
5405
        PUT_INTEL_INT(&buf[len], qpp.rotvel.z);                                                 len += 4; // 46 + 3 = 49
5406
 
5407
        if (multi_i_am_master())
5408
        {
5409
                for (unsigned i = 1; i < MAX_PLAYERS; ++i)
5410
                        if (vcplayerptr(i)->connected != CONNECT_DISCONNECTED)
5411
                                dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], buf, 0);
5412
        }
5413
        else
5414
        {
5415
                dxx_sendto(Netgame.players[0].protocol.udp.addr, UDP_Socket[0], buf, 0);
5416
        }
5417
}
5418
 
5419
void net_udp_process_pdata(const uint8_t *data, uint_fast32_t data_len, const _sockaddr &sender_addr)
5420
{
5421
        UDP_frame_info pd;
5422
        int len = 0;
5423
 
5424
        if ( !( Game_mode & GM_NETWORK && ( Network_status == NETSTAT_PLAYING || Network_status == NETSTAT_ENDLEVEL ) ) )
5425
                return;
5426
 
5427
        len++;
5428
 
5429
        pd = {};
5430
 
5431
        if (data_len > sizeof(UDP_frame_info))
5432
                return;
5433
        if (data_len != UPID_PDATA_SIZE)
5434
                return;
5435
 
5436
        if (sender_addr != Netgame.players[((multi_i_am_master())?(data[len]):(0))].protocol.udp.addr)
5437
                return;
5438
 
5439
        pd.Player_num = data[len];                                                              len++;
5440
        pd.connected = data[len];                                                               len++;
5441
        pd.qpp.orient.w = GET_INTEL_SHORT(&data[len]);                                  len += 2;
5442
        pd.qpp.orient.x = GET_INTEL_SHORT(&data[len]);                                  len += 2;
5443
        pd.qpp.orient.y = GET_INTEL_SHORT(&data[len]);                                  len += 2;
5444
        pd.qpp.orient.z = GET_INTEL_SHORT(&data[len]);                                  len += 2;
5445
        pd.qpp.pos.x = GET_INTEL_INT(&data[len]);                                               len += 4;
5446
        pd.qpp.pos.y = GET_INTEL_INT(&data[len]);                                               len += 4;
5447
        pd.qpp.pos.z = GET_INTEL_INT(&data[len]);                                               len += 4;
5448
        pd.qpp.segment = GET_INTEL_SHORT(&data[len]);                                   len += 2;
5449
        pd.qpp.vel.x = GET_INTEL_INT(&data[len]);                                               len += 4;
5450
        pd.qpp.vel.y = GET_INTEL_INT(&data[len]);                                               len += 4;
5451
        pd.qpp.vel.z = GET_INTEL_INT(&data[len]);                                               len += 4;
5452
        pd.qpp.rotvel.x = GET_INTEL_INT(&data[len]);                                    len += 4;
5453
        pd.qpp.rotvel.y = GET_INTEL_INT(&data[len]);                                    len += 4;
5454
        pd.qpp.rotvel.z = GET_INTEL_INT(&data[len]);                                    len += 4;
5455
 
5456
        if (multi_i_am_master()) // I am host - must relay this packet to others!
5457
        {
5458
                const unsigned ppn = pd.Player_num;
5459
                if (ppn > 0 && ppn <= N_players && vcplayerptr(ppn)->connected == CONNECT_PLAYING) // some checking whether this packet is legal
5460
                {
5461
                        for (unsigned i = 1; i < MAX_PLAYERS; ++i)
5462
                        {
5463
                                // not to sender or disconnected/waiting players - right.
5464
                                if (i == ppn)
5465
                                        continue;
5466
                                auto &iplr = *vcplayerptr(i);
5467
                                if (iplr.connected != CONNECT_DISCONNECTED && iplr.connected != CONNECT_WAITING)
5468
                                        dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], data, data_len, 0);
5469
                        }
5470
                }
5471
        }
5472
 
5473
        net_udp_read_pdata_packet (&pd);
5474
}
5475
 
5476
void net_udp_read_pdata_packet(UDP_frame_info *pd)
5477
{
5478
        auto &Objects = LevelUniqueObjectState.Objects;
5479
        auto &vmobjptridx = Objects.vmptridx;
5480
        const unsigned TheirPlayernum = pd->Player_num;
5481
        auto &tplr = *vmplayerptr(TheirPlayernum);
5482
        const auto TheirObjnum = tplr.objnum;
5483
 
5484
        if (multi_i_am_master())
5485
        {
5486
                // latecoming player seems to successfully have synced
5487
                if ( VerifyPlayerJoined != -1 && TheirPlayernum == VerifyPlayerJoined )
5488
                        VerifyPlayerJoined=-1;
5489
                // we say that guy is disconnected so we do not want him/her in game
5490
                if (tplr.connected == CONNECT_DISCONNECTED )
5491
                        return;
5492
        }
5493
        else
5494
        {
5495
                // only by reading pdata a client can know if a player reconnected. So do that here.
5496
                // NOTE: we might do this somewhere else - maybe with a sync packet like when adding a fresh player.
5497
                if (tplr.connected == CONNECT_DISCONNECTED && pd->connected == CONNECT_PLAYING )
5498
                {
5499
                        tplr.connected = CONNECT_PLAYING;
5500
 
5501
                        if (Newdemo_state == ND_STATE_RECORDING)
5502
                                newdemo_record_multi_reconnect(TheirPlayernum);
5503
 
5504
                        digi_play_sample( SOUND_HUD_MESSAGE, F1_0);
5505
                        ClipRank (&Netgame.players[TheirPlayernum].rank);
5506
 
5507
                        const auto &&rankstr = GetRankStringWithSpace(Netgame.players[TheirPlayernum].rank);
5508
                        HUD_init_message(HM_MULTI, "%s%s'%s' %s", rankstr.first, rankstr.second, static_cast<const char *>(vcplayerptr(TheirPlayernum)->callsign), TXT_REJOIN);
5509
 
5510
                        multi_send_score();
5511
 
5512
                        net_udp_noloss_clear_mdata_trace(TheirPlayernum);
5513
                }
5514
        }
5515
 
5516
        if (vcplayerptr(TheirPlayernum)->connected != CONNECT_PLAYING || TheirPlayernum == Player_num)
5517
                return;
5518
 
5519
        if (!multi_quit_game && (TheirPlayernum >= N_players))
5520
        {
5521
                if (Network_status!=NETSTAT_WAITING)
5522
                {
5523
                        Int3(); // We missed an important packet!
5524
                        multi_consistency_error(0);
5525
                        return;
5526
                }
5527
                else
5528
                        return;
5529
        }
5530
 
5531
        const auto TheirObj = vmobjptridx(TheirObjnum);
5532
        Netgame.players[TheirPlayernum].LastPacketTime = timer_query();
5533
 
5534
        // do not read the packet unless the level is loaded.
5535
        if (vcplayerptr(Player_num)->connected == CONNECT_DISCONNECTED || vcplayerptr(Player_num)->connected == CONNECT_WAITING)
5536
                return;
5537
        //------------ Read the player's ship's object info ----------------------
5538
        extract_quaternionpos(TheirObj, pd->qpp);
5539
        if (TheirObj->movement_type == MT_PHYSICS)
5540
                set_thrust_from_velocity(TheirObj);
5541
}
5542
 
5543
#if defined(DXX_BUILD_DESCENT_II)
5544
static void net_udp_send_smash_lights (const playernum_t pnum)
5545
 {
5546
  // send the lights that have been blown out
5547
        range_for (const auto &&segp, vmsegptridx)
5548
        {
5549
                if (segp->light_subtracted)
5550
                        multi_send_light_specific(pnum, segp, segp->light_subtracted);
5551
        }
5552
 }
5553
 
5554
static void net_udp_send_fly_thru_triggers (const playernum_t pnum)
5555
 {
5556
  // send the fly thru triggers that have been disabled
5557
        auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
5558
        auto &vctrgptridx = Triggers.vcptridx;
5559
        range_for (const auto &&t, vctrgptridx)
5560
        {
5561
                if (t->flags & trigger_behavior_flags::disabled)
5562
                        multi_send_trigger_specific(pnum, t);
5563
        }
5564
 }
5565
 
5566
static void net_udp_send_player_flags()
5567
 {
5568
        for (playernum_t i=0;i<N_players;i++)
5569
        multi_send_flags(i);
5570
 }
5571
#endif
5572
 
5573
// Send the ping list in regular intervals
5574
void net_udp_ping_frame(fix64 time)
5575
{
5576
        static fix64 PingTime = 0;
5577
 
5578
        if ((PingTime + F1_0) < time)
5579
        {
5580
                std::array<uint8_t, UPID_PING_SIZE> buf;
5581
                int len = 0;
5582
 
5583
                memset(&buf, 0, sizeof(ubyte)*UPID_PING_SIZE);
5584
                buf[len] = UPID_PING;                                                   len++;
5585
                memcpy(&buf[len], &time, 8);                                            len += 8;
5586
                range_for (auto &i, partial_const_range(Netgame.players, 1u, MAX_PLAYERS))
5587
                {
5588
                        PUT_INTEL_INT(&buf[len], i.ping);               len += 4;
5589
                }
5590
 
5591
                for (unsigned i = 1; i < MAX_PLAYERS; ++i)
5592
                {
5593
                        if (vcplayerptr(i)->connected == CONNECT_DISCONNECTED)
5594
                                continue;
5595
                        dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], buf, 0);
5596
                }
5597
                PingTime = time;
5598
        }
5599
}
5600
 
5601
// Got a PING from host. Apply the pings to our players and respond to host.
5602
void net_udp_process_ping(const uint8_t *data, const _sockaddr &sender_addr)
5603
{
5604
        fix64 host_ping_time = 0;
5605
        std::array<uint8_t, UPID_PONG_SIZE> buf;
5606
        int len = 0;
5607
 
5608
        if (Netgame.players[0].protocol.udp.addr != sender_addr)
5609
                return;
5610
 
5611
                                                                                len++; // Skip UPID byte;
5612
        memcpy(&host_ping_time, &data[len], 8);                                 len += 8;
5613
        range_for (auto &i, partial_range(Netgame.players, 1u, MAX_PLAYERS))
5614
        {
5615
                i.ping = GET_INTEL_INT(&(data[len]));           len += 4;
5616
        }
5617
 
5618
        buf[0] = UPID_PONG;
5619
        buf[1] = Player_num;
5620
        memcpy(&buf[2], &host_ping_time, 8);
5621
 
5622
        dxx_sendto(sender_addr, UDP_Socket[0], buf, 0);
5623
}
5624
 
5625
// Got a PONG from a client. Check the time and add it to our players.
5626
void net_udp_process_pong(const uint8_t *data, const _sockaddr &sender_addr)
5627
{
5628
        const uint_fast32_t playernum = data[1];
5629
        if (playernum >= MAX_PLAYERS || playernum < 1)
5630
                return;
5631
        if (sender_addr != Netgame.players[playernum].protocol.udp.addr)
5632
                return;
5633
        fix64 client_pong_time;
5634
        memcpy(&client_pong_time, &data[2], 8);
5635
        const fix64 delta64 = timer_update() - client_pong_time;
5636
        const fix delta = static_cast<fix>(delta64);
5637
        fix result;
5638
        if (likely(delta64 == static_cast<fix64>(delta)))
5639
        {
5640
                result = f2i(fixmul64(delta, i2f(1000)));
5641
                const fix lower_bound = 0;
5642
                const fix upper_bound = 9999;
5643
                if (unlikely(result < lower_bound))
5644
                        result = lower_bound;
5645
                else if (unlikely(result > upper_bound))
5646
                        result = upper_bound;
5647
        }
5648
        else
5649
                result = 0;
5650
        Netgame.players[playernum].ping = result;
5651
}
5652
 
5653
namespace dsx {
5654
void net_udp_do_refuse_stuff (UDP_sequence_packet *their)
5655
{
5656
        int new_player_num;
5657
 
5658
        ClipRank (&their->player.rank);
5659
 
5660
        for (unsigned i = 0; i < MAX_PLAYERS; ++i)
5661
        {
5662
                if (!d_stricmp(vcplayerptr(i)->callsign, their->player.callsign) && their->player.protocol.udp.addr == Netgame.players[i].protocol.udp.addr)
5663
                {
5664
                        net_udp_welcome_player(their);
5665
                        return;
5666
                }
5667
        }
5668
 
5669
        if (!WaitForRefuseAnswer)
5670
        {
5671
                for (unsigned i = 0; i < MAX_PLAYERS; ++i)
5672
                {
5673
                        if (!d_stricmp(vcplayerptr(i)->callsign, their->player.callsign) && their->player.protocol.udp.addr == Netgame.players[i].protocol.udp.addr)
5674
                        {
5675
                                net_udp_welcome_player(their);
5676
                                return;
5677
                        }
5678
                }
5679
 
5680
#if defined(DXX_BUILD_DESCENT_I)
5681
                digi_play_sample (SOUND_CONTROL_CENTER_WARNING_SIREN,F1_0*2);
5682
#elif defined(DXX_BUILD_DESCENT_II)
5683
                digi_play_sample (SOUND_HUD_JOIN_REQUEST,F1_0*2);
5684
#endif
5685
 
5686
                const auto &&rankstr = GetRankStringWithSpace(their->player.rank);
5687
                if (Game_mode & GM_TEAM)
5688
                {
5689
                        HUD_init_message(HM_MULTI, "%s%s'%s' wants to join", rankstr.first, rankstr.second, static_cast<const char *>(their->player.callsign));
5690
                        HUD_init_message(HM_MULTI, "Alt-1 assigns to team %s. Alt-2 to team %s", static_cast<const char *>(Netgame.team_name[0]), static_cast<const char *>(Netgame.team_name[1]));
5691
                }
5692
                else
5693
                {
5694
                        HUD_init_message(HM_MULTI, "%s%s'%s' wants to join (accept: F6)", rankstr.first, rankstr.second, static_cast<const char *>(their->player.callsign));
5695
                }
5696
 
5697
                strcpy (RefusePlayerName,their->player.callsign);
5698
                RefuseTimeLimit=timer_query();
5699
                RefuseThisPlayer=0;
5700
                WaitForRefuseAnswer=1;
5701
        }
5702
        else
5703
        {
5704
                for (unsigned i = 0; i < MAX_PLAYERS; ++i)
5705
                {
5706
                        if (!d_stricmp(vcplayerptr(i)->callsign, their->player.callsign) && their->player.protocol.udp.addr == Netgame.players[i].protocol.udp.addr)
5707
                        {
5708
                                net_udp_welcome_player(their);
5709
                                return;
5710
                        }
5711
                }
5712
 
5713
                if (strcmp(their->player.callsign,RefusePlayerName))
5714
                        return;
5715
 
5716
                if (RefuseThisPlayer)
5717
                {
5718
                        RefuseTimeLimit=0;
5719
                        RefuseThisPlayer=0;
5720
                        WaitForRefuseAnswer=0;
5721
                        if (Game_mode & GM_TEAM)
5722
                        {
5723
                                new_player_num=net_udp_get_new_player_num ();
5724
 
5725
                                Assert (RefuseTeam==1 || RefuseTeam==2);        
5726
 
5727
                                if (RefuseTeam==1)      
5728
                                        Netgame.team_vector &=(~(1<<new_player_num));
5729
                                else
5730
                                        Netgame.team_vector |=(1<<new_player_num);
5731
                                net_udp_welcome_player(their);
5732
                                net_udp_send_netgame_update();
5733
                        }
5734
                        else
5735
                        {
5736
                                net_udp_welcome_player(their);
5737
                        }
5738
                        return;
5739
                }
5740
 
5741
                if ((timer_query()) > RefuseTimeLimit+REFUSE_INTERVAL)
5742
                {
5743
                        RefuseTimeLimit=0;
5744
                        RefuseThisPlayer=0;
5745
                        WaitForRefuseAnswer=0;
5746
                        if (!strcmp (their->player.callsign,RefusePlayerName))
5747
                        {
5748
                                net_udp_dump_player(their->player.protocol.udp.addr, DUMP_DORK);
5749
                        }
5750
                        return;
5751
                }
5752
        }
5753
}
5754
}
5755
 
5756
static int net_udp_get_new_player_num ()
5757
{
5758
        if ( N_players < Netgame.max_numplayers)
5759
                return (N_players);
5760
 
5761
        else
5762
        {
5763
                // Slots are full but game is open, see if anyone is
5764
                // disconnected and replace the oldest player with this new one
5765
 
5766
                int oldest_player = -1;
5767
                fix64 oldest_time = timer_query();
5768
 
5769
                Assert(N_players == Netgame.max_numplayers);
5770
 
5771
                for (unsigned i = 0; i < N_players; ++i)
5772
                {
5773
                        if (!vcplayerptr(i)->connected && Netgame.players[i].LastPacketTime < oldest_time)
5774
                        {
5775
                                oldest_time = Netgame.players[i].LastPacketTime;
5776
                                oldest_player = i;
5777
                        }
5778
                }
5779
                return (oldest_player);
5780
        }
5781
}
5782
 
5783
namespace dsx {
5784
void net_udp_send_extras ()
5785
{
5786
        static fix64 last_send_time = 0;
5787
 
5788
        if (last_send_time + (F1_0/50) > timer_query())
5789
                return;
5790
        last_send_time = timer_query();
5791
 
5792
        Assert (Player_joining_extras>-1);
5793
 
5794
#if defined(DXX_BUILD_DESCENT_I)
5795
        if (Network_sending_extras==3 && (Netgame.PlayTimeAllowed.count() || Netgame.KillGoal))
5796
#elif defined(DXX_BUILD_DESCENT_II)
5797
        if (Network_sending_extras==9)
5798
                net_udp_send_fly_thru_triggers(Player_joining_extras);
5799
        if (Network_sending_extras==8)
5800
                net_udp_send_door_updates(Player_joining_extras);
5801
        if (Network_sending_extras==7)
5802
                multi_send_markers();
5803
        if (Network_sending_extras==6 && (Game_mode & GM_MULTI_ROBOTS))
5804
                multi_send_stolen_items();
5805
        if (Network_sending_extras==5 && (Netgame.PlayTimeAllowed.count() || Netgame.KillGoal))
5806
#endif
5807
                multi_send_kill_goal_counts();
5808
#if defined(DXX_BUILD_DESCENT_II)
5809
        if (Network_sending_extras==4)
5810
                net_udp_send_smash_lights(Player_joining_extras);
5811
        if (Network_sending_extras==3)
5812
                net_udp_send_player_flags();    
5813
#endif
5814
        if (Network_sending_extras==2)
5815
                multi_send_player_inventory(1);
5816
        if (Network_sending_extras==1 && Game_mode & GM_BOUNTY)
5817
                multi_send_bounty();
5818
 
5819
        Network_sending_extras--;
5820
        if (!Network_sending_extras)
5821
                Player_joining_extras=-1;
5822
}
5823
}
5824
 
5825
static int show_game_info_handler(newmenu *, const d_event &event, netgame_info *netgame)
5826
{
5827
        switch (event.type)
5828
        {
5829
                case EVENT_NEWMENU_SELECTED:
5830
                {
5831
                        auto &citem = static_cast<const d_select_event &>(event).citem;
5832
                        if (citem != 1)
5833
                                return 0;
5834
                        show_netgame_info(*netgame);
5835
                        return 1;
5836
                }
5837
                default:
5838
                        return 0;
5839
        }
5840
}
5841
 
5842
namespace dsx {
5843
int net_udp_show_game_info()
5844
{
5845
        char rinfo[512];
5846
        int c;
5847
        netgame_info *netgame = &Netgame;
5848
 
5849
#if defined(DXX_BUILD_DESCENT_I)
5850
#define DXX_SECRET_LEVEL_FORMAT "%s"
5851
#define DXX_SECRET_LEVEL_PARAMETER      (netgame->levelnum >= 0 ? "" : "S"), \
5852
        netgame->levelnum < 0 ? -netgame->levelnum :    /* else portion provided by invoker */
5853
#elif defined(DXX_BUILD_DESCENT_II)
5854
#define DXX_SECRET_LEVEL_FORMAT
5855
#define DXX_SECRET_LEVEL_PARAMETER
5856
#endif
5857
        unsigned gamemode = netgame->gamemode;
5858
        unsigned players;
5859
#if defined(DXX_BUILD_DESCENT_I)
5860
        players = netgame->numplayers;
5861
#elif defined(DXX_BUILD_DESCENT_II)
5862
        players = netgame->numconnected;
5863
#endif
5864
#define GAME_INFO_FORMAT_TEXT(F)        \
5865
        F("\nConnected to\n\"%." DXX_STRINGIZE(NETGAME_NAME_LEN) "s\"\n", netgame->game_name.data())    \
5866
        F("%." DXX_STRINGIZE(MISSION_NAME_LEN) "s", netgame->mission_title.data())      \
5867
        F(" - Lvl " DXX_SECRET_LEVEL_FORMAT "%i", DXX_SECRET_LEVEL_PARAMETER netgame->levelnum) \
5868
        F("\n\nDifficulty: %s", MENU_DIFFICULTY_TEXT(netgame->difficulty))      \
5869
        F("\nGame Mode: %s", gamemode < GMNames.size() ? GMNames[gamemode] : "INVALID") \
5870
        F("\nPlayers: %u/%i", players, netgame->max_numplayers)
5871
#define EXPAND_FORMAT(A,B,...)  A
5872
#define EXPAND_ARGUMENT(A,B,...)        , B, ## __VA_ARGS__
5873
        snprintf(rinfo, std::size(rinfo), GAME_INFO_FORMAT_TEXT(EXPAND_FORMAT) GAME_INFO_FORMAT_TEXT(EXPAND_ARGUMENT));
5874
#undef GAME_INFO_FORMAT_TEXT
5875
 
5876
        std::array<newmenu_item, 2> nm_message_items{{
5877
                nm_item_menu("JOIN GAME"),
5878
                nm_item_menu("GAME INFO"),
5879
        }};
5880
        c = newmenu_do("WELCOME", rinfo, nm_message_items, show_game_info_handler, netgame);
5881
        if (c==0)
5882
                return 1;
5883
        //else if (c==1)
5884
        // handled in above callback
5885
        else
5886
                return 0;
5887
}
5888
}
5889
 
5890
/* Tracker stuff, begin! */
5891
#if DXX_USE_TRACKER
5892
 
5893
/* Tracker initialization */
5894
static int udp_tracker_init()
5895
{
5896
        if (CGameArg.MplTrackerAddr.empty())
5897
                return 0;
5898
 
5899
        TrackerAckStatus = TrackerAckState::TACK_NOCONNECTION;
5900
        TrackerAckTime = timer_query();
5901
 
5902
        const char *tracker_addr = CGameArg.MplTrackerAddr.c_str();
5903
 
5904
        // Fill the address
5905
        if (udp_dns_filladdr(TrackerSocket, tracker_addr, CGameArg.MplTrackerPort, false, true) < 0)
5906
                return -1;
5907
 
5908
        // Yay
5909
        return 0;
5910
}
5911
 
5912
/* Compares sender to tracker. Returns 1 if address matches, Returns 2 is address and port matches. */
5913
static int sender_is_tracker(const _sockaddr &sender, const _sockaddr &tracker)
5914
{
5915
        uint16_t sf, tf, sp, tp;
5916
 
5917
        sf = sender.sin.sin_family;
5918
        tf = tracker.sin.sin_family;
5919
 
5920
#if DXX_USE_IPv6
5921
        if (sf == AF_INET6)
5922
        {
5923
                if (tf == AF_INET)
5924
                {
5925
                        if (IN6_IS_ADDR_V4MAPPED(&sender.sin6.sin6_addr))
5926
                        {
5927
                                if (memcmp(&sender.sin6.sin6_addr.s6_addr[12], &tracker.sin.sin_addr, sizeof(tracker.sin.sin_addr)))
5928
                                        return 0;
5929
                                tp = tracker.sin.sin_port;
5930
                        }
5931
                        else
5932
                                return 0;
5933
                }
5934
                else if (tf == AF_INET6)
5935
                {
5936
                        if (memcmp(&sender.sin6.sin6_addr, &tracker.sin6.sin6_addr, sizeof(sender.sin6.sin6_addr)))
5937
                                return 0;
5938
                        tp = tracker.sin6.sin6_port;
5939
                }
5940
                else
5941
                        return 0;
5942
                sp = sender.sin6.sin6_port;
5943
        }
5944
        else
5945
#endif
5946
        if (sf == AF_INET)
5947
        {
5948
                if (sf != tf)
5949
                        return 0;
5950
                if (memcmp(&sender.sin.sin_addr, &tracker.sin.sin_addr, sizeof(sender.sin.sin_addr)))
5951
                        return 0;
5952
                sp = sender.sin.sin_port;
5953
                tp = tracker.sin.sin_port;
5954
        }
5955
        else
5956
                return 0;
5957
 
5958
        if (tp == sp)
5959
                return 2;
5960
        else
5961
                return 1;
5962
}
5963
 
5964
/* Unregister from the tracker */
5965
static int udp_tracker_unregister()
5966
{
5967
        std::array<uint8_t, 1> pBuf;
5968
 
5969
        pBuf[0] = UPID_TRACKER_REMOVE;
5970
 
5971
        return dxx_sendto(TrackerSocket, UDP_Socket[0], &pBuf, 1, 0);
5972
}
5973
 
5974
namespace dsx {
5975
/* Register or update (i.e. keep alive) a game on the tracker */
5976
static int udp_tracker_register()
5977
{
5978
        net_udp_update_netgame();
5979
 
5980
        game_info_light light;
5981
        int len = 1, light_len = net_udp_prepare_light_game_info(light);
5982
        std::array<uint8_t, 2 + sizeof("b=") + sizeof(UDP_REQ_ID) + sizeof("00000.00000.00000.00000,z=") + UPID_GAME_INFO_LITE_SIZE_MAX> pBuf = {};
5983
 
5984
        pBuf[0] = UPID_TRACKER_REGISTER;
5985
        len += snprintf(reinterpret_cast<char *>(&pBuf[1]), sizeof(pBuf)-1, "b=" UDP_REQ_ID DXX_VERSION_STR ".%hu,z=", MULTI_PROTO_VERSION );
5986
        memcpy(&pBuf[len], light.buf.data(), light_len);                len += light_len;
5987
 
5988
        return dxx_sendto(TrackerSocket, UDP_Socket[0], &pBuf, len, 0);
5989
}
5990
 
5991
/* Ask the tracker to send us a list of games */
5992
static int udp_tracker_reqgames()
5993
{
5994
        std::array<uint8_t, 2 + sizeof(UDP_REQ_ID) + sizeof("00000.00000.00000.00000")> pBuf = {};
5995
        int len = 1;
5996
 
5997
        pBuf[0] = UPID_TRACKER_REQGAMES;
5998
        len += snprintf(reinterpret_cast<char *>(&pBuf[1]), sizeof(pBuf)-1, UDP_REQ_ID DXX_VERSION_STR ".%hu", MULTI_PROTO_VERSION );
5999
 
6000
        return dxx_sendto(TrackerSocket, UDP_Socket[0], &pBuf, len, 0);
6001
}
6002
}
6003
 
6004
/* The tracker has sent us a game.  Let's list it. */
6005
static int udp_tracker_process_game( ubyte *data, int data_len, const _sockaddr &sender_addr )
6006
{
6007
        // Only accept data from the tracker we specified and only when we look at the netlist (i.e. NETSTAT_BROWSING)
6008
        if (!sender_is_tracker(sender_addr, TrackerSocket) || (Network_status != NETSTAT_BROWSING))
6009
                return -1;
6010
 
6011
        char *p0 = NULL, *p1 = NULL, *p2 = NULL, *p3 = NULL;
6012
        char sIP[47] = {};
6013
        std::array<char, 6> sPort{};
6014
        uint16_t iPort = 0, TrackerGameID = 0;
6015
 
6016
        // Get the IP
6017
        if ((p0 = strstr(reinterpret_cast<char *>(data), "a=")) == NULL)
6018
                return -1;
6019
        p0 +=2;
6020
        if ((p1 = strstr(p0, "/")) == NULL)
6021
                return -1;
6022
        if (p1-p0 < 1 || p1-p0 > sizeof(sIP))
6023
                return -1;
6024
        memcpy(sIP, p0, p1-p0);
6025
 
6026
        // Get the port
6027
        p1++;
6028
        if ((p2 = strstr(p1, "c=")) == NULL)
6029
                return -1;
6030
        if (p2-p1-1 < 1 || p2-p1-1 > sizeof(sPort))
6031
                return -1;
6032
        memcpy(&sPort, p1, p2-p1-1);
6033
        if (!convert_text_portstring(sPort, iPort, true, true))
6034
                return -1;
6035
 
6036
        // Get the DNS stuff
6037
        struct _sockaddr sAddr;
6038
        if(udp_dns_filladdr(sAddr, sIP, iPort, true, true) < 0)
6039
                return -1;
6040
        if (data_len < p2-reinterpret_cast<char *>(data)+2)
6041
                return -1;
6042
        TrackerGameID = GET_INTEL_SHORT(p2 + 2);
6043
        if ((p3 = strstr(reinterpret_cast<char *>(data), "z=")) == NULL)
6044
                return -1;
6045
 
6046
        // Now process the actual lite_game packet contained.
6047
        int iPos = (p3-p0+5);
6048
        net_udp_process_game_info( &data[iPos], data_len - iPos, sAddr, 1, TrackerGameID );
6049
 
6050
        return 0;
6051
}
6052
 
6053
/* Process ACK's from tracker. We will get up to 5, each internal and external */
6054
static void udp_tracker_process_ack( ubyte *data, int data_len, const _sockaddr &sender_addr )
6055
{
6056
        if(!Netgame.Tracker)
6057
                return;
6058
        if (data_len != 2)
6059
                return;
6060
        int addr_check = sender_is_tracker(sender_addr, TrackerSocket);
6061
 
6062
        switch (data[1])
6063
        {
6064
                case 0: // ack coming from the same socket we are already talking with the tracker
6065
                        if (TrackerAckStatus == TrackerAckState::TACK_NOCONNECTION && addr_check == 2)
6066
                        {
6067
                                TrackerAckStatus = TrackerAckState::TACK_INTERNAL;
6068
                                con_puts(CON_VERBOSE, "[Tracker] Got internal ACK. Your game is hosted!");
6069
                        }
6070
                        break;
6071
                case 1: // ack from another socket (same IP, different port) to see if we're reachable from the outside
6072
                        if (TrackerAckStatus <= TrackerAckState::TACK_INTERNAL && addr_check)
6073
                        {
6074
                                TrackerAckStatus = TrackerAckState::TACK_EXTERNAL;
6075
                                con_puts(CON_VERBOSE, "[Tracker] Got external ACK. Your game is hosted and game port is reachable!");
6076
                        }
6077
                        break;
6078
        }
6079
}
6080
 
6081
/* 10 seconds passed since we registered our game. If we have not received all ACK's, yet, tell user about that! */
6082
static void udp_tracker_verify_ack_timeout()
6083
{
6084
        if (!Netgame.Tracker || !multi_i_am_master() || TrackerAckTime + F1_0*10 > timer_query() || TrackerAckStatus == TrackerAckState::TACK_SEQCOMPL)
6085
                return;
6086
        if (TrackerAckStatus == TrackerAckState::TACK_NOCONNECTION)
6087
        {
6088
                TrackerAckStatus = TrackerAckState::TACK_SEQCOMPL; // set this now or we'll run into an endless loop if nm_messagebox triggers.
6089
                if (Network_status == NETSTAT_PLAYING)
6090
                        HUD_init_message(HM_MULTI, "No ACK from tracker. Please check game log.");
6091
                else
6092
                        nm_messagebox(TXT_WARNING, 1, TXT_OK, "No ACK from tracker.\nPlease check game log.");
6093
                con_puts(CON_URGENT, "[Tracker] No response from game tracker. Tracker address may be invalid or Tracker may be offline or otherwise unreachable.");
6094
        }
6095
        else if (TrackerAckStatus == TrackerAckState::TACK_INTERNAL)
6096
        {
6097
                con_puts(CON_NORMAL, "[Tracker] No external signal from game tracker.  Your game port does not seem to be reachable.");
6098
                con_puts(CON_NORMAL, Netgame.TrackerNATWarned == TrackerNATHolePunchWarn::UserEnabledHP ? "Clients will attempt hole-punching to join your game." : "Clients will only be able to join your game if specifically configured in your router.");
6099
        }
6100
        TrackerAckStatus = TrackerAckState::TACK_SEQCOMPL;
6101
}
6102
 
6103
/* We don't seem to be able to connect to a game. Ask Tracker to send hole punch request to host. */
6104
static void udp_tracker_request_holepunch( uint16_t TrackerGameID )
6105
{
6106
        std::array<uint8_t, 3> pBuf;
6107
 
6108
        pBuf[0] = UPID_TRACKER_HOLEPUNCH;
6109
        PUT_INTEL_SHORT(&pBuf[1], TrackerGameID);
6110
 
6111
        con_printf(CON_VERBOSE, "[Tracker] Sending hole-punch request for game [%i] to tracker.", TrackerGameID);
6112
        dxx_sendto(TrackerSocket, UDP_Socket[0], &pBuf, 3, 0);
6113
}
6114
 
6115
/* Tracker sent us an address from a client requesting hole punching.
6116
 * We'll simply reply with another hole punch packet and wait for them to request our game info properly. */
6117
static void udp_tracker_process_holepunch(uint8_t *const data, const unsigned data_len, const _sockaddr &sender_addr )
6118
{
6119
        if (data_len == 1 && !multi_i_am_master())
6120
        {
6121
                con_puts(CON_VERBOSE, "[Tracker] Received hole-punch pong from a host.");
6122
                return;
6123
        }
6124
        if (!Netgame.Tracker || !sender_is_tracker(sender_addr, TrackerSocket) || !multi_i_am_master())
6125
                return;
6126
        if (Netgame.TrackerNATWarned != TrackerNATHolePunchWarn::UserEnabledHP)
6127
        {
6128
                con_puts(CON_NORMAL, "Ignoring tracker hole-punch request because user disabled hole punch.");
6129
                return;
6130
        }
6131
        if (!data_len || data[data_len - 1])
6132
                return;
6133
 
6134
        auto &delimiter = "/";
6135
 
6136
        const auto p0 = strtok(reinterpret_cast<char *>(data), delimiter);
6137
        if (!p0)
6138
                return;
6139
        const auto sIP = p0 + 1;
6140
        const auto pPort = strtok(NULL, delimiter);
6141
        if (!pPort)
6142
                return;
6143
        char *porterror;
6144
        const auto myport = strtoul(pPort, &porterror, 10);
6145
        if (*porterror)
6146
                return;
6147
        const uint16_t iPort = myport;
6148
        if (iPort != myport)
6149
                return;
6150
 
6151
        // Get the DNS stuff
6152
        struct _sockaddr sAddr;
6153
        if(udp_dns_filladdr(sAddr, sIP, iPort, true, true) < 0)
6154
                return;
6155
 
6156
        std::array<uint8_t, 1> pBuf;
6157
        pBuf[0] = UPID_TRACKER_HOLEPUNCH;
6158
        dxx_sendto(sAddr, UDP_Socket[0], &pBuf, 1, 0);
6159
}
6160
#endif /* USE_TRACKER */