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