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