Project Alice
Loading...
Searching...
No Matches
network.cpp
Go to the documentation of this file.
1#ifdef _WIN64 // WINDOWS
2#ifndef _MSC_VER
3#include <unistd.h>
4#endif
5#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
6#ifndef WINSOCK2_IMPORTED
7#define WINSOCK2_IMPORTED
8#include <winsock2.h>
9#include <ws2tcpip.h>
10#endif
11#include <windows.h>
12#include <natupnp.h>
13#include <iphlpapi.h>
14#include <Mstcpip.h>
15#include <ip2string.h>
16#include "pcp.h"
17#else // NIX
18#include <netinet/in.h>
19#include <sys/socket.h>
20#include <netdb.h>
21#include <arpa/inet.h>
22#include <stdio.h>
23#include <unistd.h>
24#endif // ...
25#include <string_view>
26#include "system_state.hpp"
27#include "commands.hpp"
28#include "SPSCQueue.h"
29#include "network.hpp"
30#include "serialization.hpp"
31#include "SHA512.hpp"
32#include "gui_error_window.hpp"
34
35#define ZSTD_STATIC_LINKING_ONLY
36#define XXH_NAMESPACE ZSTD_
37#include "zstd.h"
38
39#ifdef _WIN64
40#pragma comment(lib, "Iphlpapi.lib")
41#pragma comment(lib, "ntdll.lib")
42#endif
43
44#include <json.hpp>
45using json = nlohmann::json;
46
47namespace network {
48
49//
50// platform specific
51//
52
54 std::string address;
55 bool ipv6 = false;
56};
57
59
61
63#ifdef _WIN64
64 if(started)
65 return;
66
67 internal_wait.lock();
68 started = true;
69 do_forwarding = std::thread{ [&]() {
70 //setup forwarding
71 if(!SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)))
72 return;
73
74
75 std::vector<local_addresses> found_locals;
76
77 // try to figure out what the computer's local address is
78 {
79 IP_ADAPTER_ADDRESSES* adapter_addresses(NULL);
80 IP_ADAPTER_ADDRESSES* adapter(NULL);
81
82 // Start with a 16 KB buffer and resize if needed -
83 // multiple attempts in case interfaces change while
84 // we are in the middle of querying them.
85 DWORD adapter_addresses_buffer_size = 16 * 1024;
86 for(int attempts = 0; attempts != 3; ++attempts) {
87 adapter_addresses = (IP_ADAPTER_ADDRESSES*)malloc(adapter_addresses_buffer_size);
88 assert(adapter_addresses);
89
90 DWORD error = GetAdaptersAddresses(
91 AF_UNSPEC,
92 GAA_FLAG_SKIP_ANYCAST |
93 GAA_FLAG_SKIP_MULTICAST |
94 GAA_FLAG_SKIP_DNS_SERVER |
95 GAA_FLAG_SKIP_FRIENDLY_NAME,
96 NULL,
97 adapter_addresses,
98 &adapter_addresses_buffer_size);
99
100 if(ERROR_SUCCESS == error) {
101 // We're done here, people!
102 break;
103 } else if(ERROR_BUFFER_OVERFLOW == error) {
104 // Try again with the new size
105 free(adapter_addresses);
106 adapter_addresses = NULL;
107
108 continue;
109 } else {
110 // Unexpected error code - log and throw
111 free(adapter_addresses);
112 adapter_addresses = NULL;
113
114 // @todo
115 }
116 }
117
118 // Iterate through all of the adapters
119 for(adapter = adapter_addresses; NULL != adapter; adapter = adapter->Next) {
120 // Skip loopback adapters
121 if(IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType) {
122 continue;
123 }
124
125 // Parse all IPv4 and IPv6 addresses
126 for(
127 IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress;
128 NULL != address;
129 address = address->Next) {
130 auto family = address->Address.lpSockaddr->sa_family;
131 if(address->DadState != NldsPreferred && address->DadState != IpDadStatePreferred)
132 continue;
133
134 if(AF_INET == family) {
135 // IPv4
136 SOCKADDR_IN* ipv4 = reinterpret_cast<SOCKADDR_IN*>(address->Address.lpSockaddr);
137
138 char str_buffer[INET_ADDRSTRLEN] = { 0 };
139 inet_ntop(AF_INET, &(ipv4->sin_addr), str_buffer, INET_ADDRSTRLEN);
140 found_locals.push_back(local_addresses{ std::string(str_buffer), false});
141 } else if(AF_INET6 == family) {
142 // IPv6
143 SOCKADDR_IN6* ipv6 = reinterpret_cast<SOCKADDR_IN6*>(address->Address.lpSockaddr);
144
145 char str_buffer[INET6_ADDRSTRLEN] = { 0 };
146 inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN);
147
148 std::string ipv6_str(str_buffer);
149
150 // Detect and skip non-external addresses
151 bool is_link_local(false);
152 bool is_special_use(false);
153
154 if(0 == ipv6_str.find("fe")) {
155 char c = ipv6_str[2];
156 if(c == '8' || c == '9' || c == 'a' || c == 'b') {
157 is_link_local = true;
158 }
159 } else if(0 == ipv6_str.find("2001:0:")) {
160 is_special_use = true;
161 }
162
163 if(!(is_link_local || is_special_use)) {
164 found_locals.push_back(local_addresses{ std::string(ipv6_str), true });
165 }
166 } else {
167 // Skip all other types of addresses
168 continue;
169 }
170 }
171 }
172
173 // Cleanup
174 free(adapter_addresses);
175 adapter_addresses = NULL;
176 }
177
178 // try to add port mapping
179 bool mapped_ports_with_upnp = false;
180 if(found_locals.size() != 0) {
181 IUPnPNAT* nat_interface = nullptr;
182 IStaticPortMappingCollection* port_mappings = nullptr;
183 IStaticPortMapping* opened_port = nullptr;
184
185 BSTR proto = SysAllocString(L"TCP");
186 BSTR desc = SysAllocString(L"Project Alice Host");
187 auto tmpwstr = simple_fs::utf8_to_native(found_locals[0].address);
188 BSTR local_host = SysAllocString(tmpwstr.c_str());
189 VARIANT_BOOL enabled = VARIANT_TRUE;
190
191 if(SUCCEEDED(CoCreateInstance(__uuidof(UPnPNAT), NULL, CLSCTX_ALL, __uuidof(IUPnPNAT), (void**)&nat_interface)) && nat_interface) {
192 if(SUCCEEDED(nat_interface->get_StaticPortMappingCollection(&port_mappings)) && port_mappings) {
193 if(SUCCEEDED(port_mappings->Add(default_server_port, proto, default_server_port, local_host, enabled, desc, &opened_port)) && opened_port) {
194 mapped_ports_with_upnp = true;
195 }
196 }
197 }
198
199 pcp_ctx_t* pcp_obj = nullptr;
200 if(!mapped_ports_with_upnp) {
201 pcp_obj = pcp_init(ENABLE_AUTODISCOVERY, NULL);
202
203 sockaddr_storage source_ip;
204 sockaddr_storage ext_ip;
205
206 WORD wVersionRequested;
207 WSADATA wsaData;
208
209 wVersionRequested = MAKEWORD(2, 2);
210 auto err = WSAStartup(wVersionRequested, &wsaData);
211
212 memset(&source_ip, 0, sizeof(source_ip));
213 memset(&ext_ip, 0, sizeof(ext_ip));
214
215 if(found_locals[0].ipv6 == false) {
216 ((sockaddr_in*)(&source_ip))->sin_port = 1984;
217 ((sockaddr_in*)(&source_ip))->sin_addr.s_addr = inet_addr(found_locals[0].address.c_str());
218 ((sockaddr_in*)(&source_ip))->sin_family = AF_INET;
219
220 ((sockaddr_in*)(&ext_ip))->sin_port = 1984;
221 ((sockaddr_in*)(&ext_ip))->sin_family = AF_INET;
222 } else {
223 ((sockaddr_in6*)(&source_ip))->sin6_port = 1984;
224 PCSTR term = nullptr;
225 RtlIpv6StringToAddressA(found_locals[0].address.c_str(), &term, &(((sockaddr_in6*)(&source_ip))->sin6_addr));
226 ((sockaddr_in6*)(&source_ip))->sin6_family = AF_INET6;
227
228 ((sockaddr_in6*)(&ext_ip))->sin6_port = 1984;
229 ((sockaddr_in6*)(&ext_ip))->sin6_family = AF_INET6;
230 }
231
232 auto flow = pcp_new_flow(pcp_obj, (sockaddr*)&source_ip, nullptr, (sockaddr*)&ext_ip, IPPROTO_TCP, 3600, nullptr);
233 if(flow)
234 pcp_wait(flow, 10000, 0);
235
236 }
237
238 // wait for destructor
239 internal_wait.lock();
240
241 if(pcp_obj) {
242 pcp_terminate(pcp_obj, 0);
243 }
244
245 //cleanup forwarding
246 if(port_mappings)
247 port_mappings->Remove(default_server_port, proto);
248
249 if(opened_port)
250 opened_port->Release();
251 if(port_mappings)
252 port_mappings->Release();
253 if(nat_interface)
254 nat_interface->Release();
255
256 SysFreeString(proto);
257 SysFreeString(local_host);
258 SysFreeString(desc);
259
260 internal_wait.unlock();
261 }
262 CoUninitialize();
263 } };
264#else
265#endif
266}
267
269#ifdef _WIN64
270 if(started) {
271 internal_wait.unlock();
272 do_forwarding.join();
273 }
274#else
275#endif
276}
277
278std::string get_last_error_msg() {
279#ifdef _WIN64
280 auto err = WSAGetLastError();
281 LPTSTR err_buf = nullptr;
282 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, err, 0, (LPTSTR)&err_buf, 0, nullptr);
283 native_string err_text = err_buf;
284 LocalFree(err_buf);
285
286 std::string err_msg;
287 if(err == WSAECONNRESET) {
288 err_msg += "Host was lost ";
289 } else if(err == WSAEHOSTDOWN) {
290 err_msg += "Host is down ";
291 } else if(err == WSAEHOSTUNREACH) {
292 err_msg += "Host is unreachable ";
293 } else {
294 err_msg += "Network issue ocurred ";
295 }
296 err_msg += "Technical details: ";
297 err_msg += std::to_string(err);
298 err_msg += " => ";
300 return err_msg;
301#else
302 return std::string("Dummy");
303#endif
304}
305
306static int internal_socket_recv(socket_t socket_fd, void *data, size_t n) {
307#ifdef _WIN64
308 u_long has_pending = 0;
309 auto r = ioctlsocket(socket_fd, FIONREAD, &has_pending);
310 if(has_pending)
311 return static_cast<int>(recv(socket_fd, reinterpret_cast<char *>(data), static_cast<int>(n), 0));
312 return 0;
313#else
314 return recv(socket_fd, data, n, MSG_DONTWAIT);
315#endif
316}
317
318static int internal_socket_send(socket_t socket_fd, const void *data, size_t n) {
319#ifdef _WIN64
320 return static_cast<int>(send(socket_fd, reinterpret_cast<const char *>(data), static_cast<int>(n), 0));
321#else
322 return send(socket_fd, data, n, MSG_NOSIGNAL);
323#endif
324}
325
326template<typename F>
327static int socket_recv(socket_t socket_fd, void* data, size_t len, size_t* m, F&& func) {
328 while(*m < len) {
329 int r = internal_socket_recv(socket_fd, reinterpret_cast<uint8_t*>(data) + *m, len - *m);
330 if(r > 0) {
331 *m += static_cast<size_t>(r);
332 } else if(r < 0) { // error
333#ifdef _WIN32
334 int err = WSAGetLastError();
335 if(err == WSAENOBUFS || err == WSAEWOULDBLOCK) {
336 return 0;
337 }
338 return err;
339#else
340 return r;
341#endif
342 } else if(r == 0) {
343 break;
344 }
345 }
346 // Did we receive a command?
347 if(*m >= len) {
348 assert(*m <= len);
349 *m = 0; // reset
350 func();
351 return 0;
352 }
353 // No data received
354 return -1;
355}
356
357static int socket_send(socket_t socket_fd, std::vector<char>& buffer) {
358 while(!buffer.empty()) {
359 int r = internal_socket_send(socket_fd, buffer.data(), buffer.size());
360 if(r > 0) {
361 buffer.erase(buffer.begin(), buffer.begin() + static_cast<size_t>(r));
362 } else if(r < 0) {
363#ifdef _WIN32
364 int err = WSAGetLastError();
365 if(err == WSAENOBUFS || err == WSAEWOULDBLOCK) {
366 return 0;
367 }
368 return err;
369#else
370 return r;
371#endif
372 } else if(r == 0) {
373 break;
374 }
375 }
376 return 0;
377}
378
379static void socket_add_to_send_queue(std::vector<char>& buffer, const void *data, size_t n) {
380 buffer.resize(buffer.size() + n);
381 std::memcpy(buffer.data() + buffer.size() - n, data, n);
382}
383
384static void socket_shutdown(socket_t socket_fd) {
385 if(socket_fd > 0) {
386#ifdef _WIN64
387 shutdown(socket_fd, SD_BOTH);
388 closesocket(socket_fd);
389#else
390 shutdown(socket_fd, SHUT_RDWR);
391 close(socket_fd);
392#endif
393 }
394}
395
396static socket_t socket_init_server(bool as_v6, struct sockaddr_storage& server_address) {
397 socket_t socket_fd = static_cast<socket_t>(socket(as_v6 ? AF_INET6 : AF_INET, SOCK_STREAM, IPPROTO_TCP));
398#ifdef _WIN64
399 if(socket_fd == static_cast<socket_t>(INVALID_SOCKET)) {
400 window::emit_error_message("Network socket error: " + get_last_error_msg(), true);
401 }
402#else
403 if(socket_fd < 0)
404 std::abort();
405#endif
406 struct timeval timeout;
407 timeout.tv_sec = 60;
408 timeout.tv_usec = 0;
409 int opt = 1;
410#ifdef _WIN64
411 if(setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt))) {
412 window::emit_error_message("Network setsockopt [reuseaddr] error: " + get_last_error_msg(), true);
413 }
414 if(setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) {
415 window::emit_error_message("Network setsockopt [rcvtimeo] error: " + get_last_error_msg(), true);
416 }
417 if(setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout)) < 0) {
418 window::emit_error_message("Network setsockopt [sndtimeo] error: " + get_last_error_msg(), true);
419 }
420#else
421 if(setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
422 std::abort();
423 }
424 if(setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
425 std::abort();
426 }
427 if(setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) {
428 std::abort();
429 }
430#endif
431 if(as_v6) {
432 struct sockaddr_in6 v6_server_address;
433 v6_server_address.sin6_addr = IN6ADDR_ANY_INIT;
434 v6_server_address.sin6_family = AF_INET6;
435 v6_server_address.sin6_port = htons(default_server_port);
436 std::memcpy(&server_address, &v6_server_address, sizeof(v6_server_address));
437 } else {
438 struct sockaddr_in v4_server_address;
439 v4_server_address.sin_addr.s_addr = INADDR_ANY;
440 v4_server_address.sin_family = AF_INET;
441 v4_server_address.sin_port = htons(default_server_port);
442 std::memcpy(&server_address, &v4_server_address, sizeof(v4_server_address));
443 }
444 if(bind(socket_fd, (struct sockaddr*)&server_address, as_v6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in)) < 0) {
445 window::emit_error_message("Network bind error: " + get_last_error_msg(), true);
446 }
447 if(listen(socket_fd, SOMAXCONN) < 0) {
448 window::emit_error_message("Network listen error: " + get_last_error_msg(), true);
449 }
450#ifdef _WIN64
451 u_long mode = 1; // 1 to enable non-blocking socket
452 ioctlsocket(socket_fd, FIONBIO, &mode);
453#endif
454 return socket_fd;
455}
456
457static socket_t socket_init_client(bool& as_v6, struct sockaddr_storage& client_address, const char *ip_address) {
458 struct addrinfo hints;
459 std::memset(&hints, 0, sizeof(hints));
460 hints.ai_family = AF_UNSPEC;
461 hints.ai_socktype = SOCK_STREAM;
462 hints.ai_protocol = IPPROTO_TCP;
463 struct addrinfo* result = NULL;
464 if(getaddrinfo(ip_address, "1984", &hints, &result) != 0) {
465 window::emit_error_message("Network getaddrinfo error: " + get_last_error_msg(), true);
466 }
467 as_v6 = false;
468 bool found = false;
469 for(struct addrinfo* ptr = result; ptr != NULL; ptr = ptr->ai_next) {
470 if(ptr->ai_socktype == SOCK_STREAM && ptr->ai_protocol == IPPROTO_TCP) {
471 if(ptr->ai_family == AF_INET || ptr->ai_family == AF_INET6) {
472 as_v6 = ptr->ai_family == AF_INET6;
473 std::memcpy(&client_address, ptr->ai_addr, sizeof(sockaddr));
474 found = true;
475 }
476 }
477 }
478 freeaddrinfo(result);
479 if(!found) {
480 window::emit_error_message("No suitable host found", true);
481 }
482 socket_t socket_fd = static_cast<socket_t>(socket(as_v6 ? AF_INET6 : AF_INET, SOCK_STREAM, IPPROTO_TCP));
483#ifdef _WIN64
484 if(socket_fd == static_cast<socket_t>(INVALID_SOCKET)) {
485 window::emit_error_message("Network socket error: " + get_last_error_msg(), true);
486 }
487#else
488 if(socket_fd < 0) {
489 window::emit_error_message("Network socket error: " + get_last_error_msg(), true);
490 }
491#endif
492 if(connect(socket_fd, (const struct sockaddr*)&client_address, as_v6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in)) < 0) {
493 window::emit_error_message("Network connect error: " + get_last_error_msg(), true);
494 }
495 return socket_fd;
496}
497
498//
499// non-platform specific
500//
501
502void clear_socket(sys::state& state, client_data& client) {
503 socket_shutdown(client.socket_fd);
504 client.socket_fd = 0;
505 client.send_buffer.clear();
506 client.early_send_buffer.clear();
507 client.total_sent_bytes = 0;
508 client.save_stream_size = 0;
509 client.save_stream_offset = 0;
510 client.playing_as = dcon::nation_id{};
511 client.recv_count = 0;
512 client.handshake = true;
513 client.last_seen = sys::date{};
514}
515
516static void disconnect_client(sys::state& state, client_data& client, bool graceful) {
517 if(command::can_notify_player_leaves(state, client.playing_as, graceful)) {
518 command::notify_player_leaves(state, client.playing_as, graceful);
519 }
520#ifndef NDEBUG
521 state.console_log("server:disconnectclient | country:" + std::to_string(client.playing_as.index()));
522 log_player_nations(state);
523#endif
524 clear_socket(state, client);
525}
526
528 auto msg = std::string{};
529 for(const auto n : state.world.in_nation)
530 if(state.world.nation_get_is_player_controlled(n))
531 state.console_log("player controlled: " + std::to_string(n.id.index()));
532
533 for(const auto p : state.world.in_mp_player) {
534 auto data = p.get_nickname();
535 auto nickname = sys::player_name{ data };
536 auto nation = p.get_player_nation().get_nation();
537
538 state.console_log("player id: " + std::to_string(p.id.index()) + " nickname: " + nickname.to_string() + " nation:" + std::to_string(nation.id.index()));
539 }
540}
541
542static std::map<int, std::string> readableCommandTypes = {
543 {0,"invalid"},
544{1,"change_nat_focus"},
545{2,"start_research"},
546{3,"make_leader"},
547{4,"begin_province_building_construction"},
548{5,"increase_relations"},
549{6,"decrease_relations"},
550{7,"begin_factory_building_construction"},
551{8,"begin_naval_unit_construction"},
552{9,"cancel_naval_unit_construction"},
553{10,"change_factory_settings"},
554{11,"delete_factory"},
555{12,"make_vassal"},
556{13,"release_and_play_nation"},
557{14,"war_subsidies"},
558{15,"cancel_war_subsidies"},
559{16,"change_budget"},
560{17,"start_election"},
561{18,"change_influence_priority"},
562{19,"discredit_advisors"},
563{20,"expel_advisors"},
564{21,"ban_embassy"},
565{22,"increase_opinion"},
566{23,"decrease_opinion"},
567{24,"add_to_sphere"},
568{25,"remove_from_sphere"},
569{26,"upgrade_colony_to_state"},
570{27,"invest_in_colony"},
571{28,"abandon_colony"},
572{29,"finish_colonization"},
573{30,"intervene_in_war"},
574{31,"suppress_movement"},
575{32,"civilize_nation"},
576{33,"appoint_ruling_party"},
577{34,"change_issue_option"},
578{35,"change_reform_option"},
579{36,"become_interested_in_crisis"},
580{37,"take_sides_in_crisis"},
581{38,"begin_land_unit_construction"},
582{39,"cancel_land_unit_construction"},
583{40,"change_stockpile_settings"},
584{41,"take_decision"},
585{42,"make_n_event_choice"},
586{43,"make_f_n_event_choice"},
587{44,"make_p_event_choice"},
588{45,"make_f_p_event_choice"},
589{46,"fabricate_cb"},
590{47,"cancel_cb_fabrication"},
591{48,"ask_for_military_access"},
592{49,"ask_for_alliance"},
593{50,"call_to_arms"},
594{51,"respond_to_diplomatic_message"},
595{52,"cancel_military_access"},
596{53,"cancel_alliance"},
597{54,"cancel_given_military_access"},
598{55,"declare_war"},
599{56,"add_war_goal"},
600{58,"start_peace_offer"},
601{59,"add_peace_offer_term"},
602{60,"send_peace_offer"},
603{61,"move_army"},
604{62,"move_navy"},
605{63,"embark_army"},
606{64,"merge_armies"},
607{65,"merge_navies"},
608{66,"split_army"},
609{67,"split_navy"},
610{68,"delete_army"},
611{69,"delete_navy"},
612{70,"designate_split_regiments"},
613{71,"designate_split_ships"},
614{72,"naval_retreat"},
615{73,"land_retreat"},
616{74,"start_crisis_peace_offer"},
617{75,"invite_to_crisis"},
618{76,"add_wargoal_to_crisis_offer"},
619{77,"send_crisis_peace_offer"},
620{78,"change_admiral"},
621{79,"change_general"},
622{80,"toggle_mobilization"},
623{81,"give_military_access"},
624{82,"set_rally_point"},
625{83,"save_game"},
626{84,"cancel_factory_building_construction"},
627{85,"disband_undermanned"},
628{86,"even_split_army"},
629{87,"even_split_navy"},
630{88,"toggle_hunt_rebels"},
631{89,"toggle_select_province"},
632{90,"toggle_immigrator_province"},
633{91,"state_transfer"},
634{92,"release_subject"},
635{93,"enable_debt"},
636{94,"move_capital"},
637{95,"toggle_unit_ai_control"},
638{96,"toggle_mobilized_is_ai_controlled"},
639{97,"toggle_interested_in_alliance"},
640{98,"pbutton_script"},
641{99,"nbutton_script"},
642{100,"set_factory_type_priority"},
643{101,"crisis_add_wargoal" },
644{102,"change_unit_type" },
645{106,"notify_player_ban"},
646{107,"notify_player_kick"},
647{108,"notify_player_picks_nation"},
648{109,"notify_player_joins"},
649{110,"notify_player_leaves"},
650{111,"notify_player_oos"},
651{112,"notify_save_loaded"},
652{113,"notify_start_game"},
653{114,"notify_stop_game"},
654{115,"notify_pause_game"},
655{116,"notify_reload"},
656{120,"advance_tick"},
657{121,"chat_message"},
658{122,"network_inactivity_ping"},
659{255,"console_command"},
660};
661
662dcon::mp_player_id create_mp_player(sys::state& state, sys::player_name& name, sys::player_password_raw& password) {
663 auto p = state.world.create_mp_player();
664 state.world.mp_player_set_nickname(p, name.data);
665
666 auto salt = sys::player_password_salt{}.from_string_view(std::to_string(int32_t(rand())));
667 auto hash = sys::player_password_hash{}.from_string_view(sha512.hash(password.to_string() + salt.to_string()));
668 state.world.mp_player_set_password_hash(p, hash.data);
669 state.world.mp_player_set_password_salt(p, salt.data);
670
671 return p;
672}
673
674dcon::mp_player_id load_mp_player(sys::state& state, sys::player_name& name, sys::player_password_hash& password_hash, sys::player_password_salt& password_salt) {
675 auto p = state.world.create_mp_player();
676 state.world.mp_player_set_nickname(p, name.data);
677 state.world.mp_player_set_password_hash(p, password_hash.data);
678 state.world.mp_player_set_password_salt(p, password_salt.data);
679
680 return p;
681}
682
683void update_mp_player_password(sys::state& state, dcon::mp_player_id player_id, sys::player_password_raw& password) {
684 auto salt = sys::player_password_salt{}.from_string_view(std::to_string(int32_t(rand())));
685 auto hash = sys::player_password_hash{}.from_string_view(sha512.hash(password.to_string() + salt.to_string()));
686 state.world.mp_player_set_password_hash(player_id, hash.data);
687 state.world.mp_player_set_password_salt(player_id, salt.data);
688}
689
690dcon::mp_player_id find_mp_player(sys::state& state, sys::player_name name) {
691 for(const auto p : state.world.in_mp_player) {
692 auto nickname = p.get_nickname();
693
694 if(std::equal(std::begin(nickname), std::end(nickname), std::begin(name.data))) {
695 return p;
696 }
697 }
698
699 return dcon::mp_player_id{};
700}
701
702dcon::mp_player_id find_country_player(sys::state& state, dcon::nation_id nation) {
703 return state.world.nation_get_mp_player_from_player_nation(nation);
704}
705
706// Reassign player to his previous nation if any
707static dcon::nation_id get_player_nation(sys::state& state, sys::player_name name) {
708
709 auto p = find_mp_player(state, name);
710 if(p) {
711 return state.world.mp_player_get_nation_from_player_nation(p);
712 }
713
714 return dcon::nation_id{ };
715}
716
717static dcon::nation_id choose_nation_for_player(sys::state& state) {
718 // On join or hotjoin give the client a "joining" nation, basically a temporal nation choosen
719 // "randomly" that is tied to the client iself
720
721 for(auto n : state.nations_by_rank)
722 if(!state.world.nation_get_is_player_controlled(n) && state.world.nation_get_owned_province_count(n) > 0) {
723 bool is_taken = false;
724 for(auto& client : state.network_state.clients) {
725 if(client.playing_as == n) {
726 is_taken = true;
727 break;
728 }
729 }
730 if(!is_taken) {
731 return n;
732 }
733 }
734 return dcon::nation_id{ };
735}
736
738 /* Send our client handshake back */
740 hshake.nickname = state.network_state.nickname;
741 hshake.player_password = state.network_state.player_password;
742 std::memcpy(hshake.lobby_password, state.network_state.lobby_password, sizeof(hshake.lobby_password));
743 socket_add_to_send_queue(state.network_state.send_buffer, &hshake, sizeof(hshake));
744
745#ifndef NDEBUG
746 state.console_log("client:send:handshake");
747#endif
748}
749
751 int r = socket_recv(state.network_state.socket_fd, &state.network_state.s_hshake, sizeof(state.network_state.s_hshake), &state.network_state.recv_count, [&]() {
752 if(!state.scenario_checksum.is_equal(state.network_state.s_hshake.scenario_checksum)) {
753 bool found_match = false;
754 // Find a scenario with a matching checksum
755 auto dir = simple_fs::get_or_create_scenario_directory();
756 for(const auto& uf : simple_fs::list_files(dir, NATIVE(".bin"))) {
757 auto f = simple_fs::open_file(uf);
758 if(f) {
759 auto contents = simple_fs::view_contents(*f);
760 sys::scenario_header scen_header;
761 if(contents.file_size > sizeof(sys::scenario_header)) {
762 sys::read_scenario_header(reinterpret_cast<const uint8_t*>(contents.data), scen_header);
763 if(!scen_header.checksum.is_equal(state.network_state.s_hshake.scenario_checksum))
764 continue; // Same checksum
765 if(scen_header.version != sys::scenario_file_version)
766 continue; // Same version of scenario
767 if(sys::try_read_scenario_and_save_file(state, simple_fs::get_file_name(uf))) {
768 state.fill_unsaved_data();
769 found_match = true;
770 break;
771 }
772 }
773 }
774 }
775 if(!found_match) {
776 std::string msg = "Could not find a scenario with a matching checksum!"
777 "This is most likely a false positive, so just ask the host for their"
778 "scenario file and it should work. Or you haven't clicked on 'Make scenario'!";
779 msg += "\n";
780 msg += "Host should give you the scenario from:\n"
781 "'My Documents\\Project Alice\\scenarios\\<Most recent scenario>'";
782 msg += "And you place it on:\n"
783 "'My Documents\\Project Alice\\scenarios\\'\n";
784
785 window::emit_error_message(msg.c_str(), true);
786 }
787 }
788 state.session_host_checksum = state.network_state.s_hshake.save_checksum;
789 state.game_seed = state.network_state.s_hshake.seed;
790 state.local_player_nation = state.network_state.s_hshake.assigned_nation;
791 state.world.nation_set_is_player_controlled(state.local_player_nation, true);
792
793#ifndef NDEBUG
794 state.console_log("client:recv:handshake");
795#endif
796
797 state.network_state.handshake = false;
798
799 //update map
800 state.map_state.set_selected_province(dcon::province_id{});
801 state.map_state.unhandled_province_selection = true;
802 });
803
804 return r;
805}
806
808 /* Tell the client their assigned nation */
809 auto plnation = get_player_nation(state, client.hshake_buffer.nickname);
810 if(plnation) {
811 client.playing_as = plnation;
812 } else {
813 client.playing_as = choose_nation_for_player(state);
814 }
815 assert(client.playing_as);
816
817 /* Send it data so the client is in sync with everyone else! */
819 hshake.seed = state.game_seed;
820 hshake.assigned_nation = client.playing_as;
821 hshake.scenario_checksum = state.scenario_checksum;
822 hshake.save_checksum = state.get_save_checksum();
823 socket_add_to_send_queue(client.early_send_buffer, &hshake, sizeof(hshake));
824#ifndef NDEBUG
825 state.console_log("host:send:handshake | assignednation:" + std::to_string(client.playing_as.index()));
826#endif
827
828 /* Exit from handshake mode */
829 client.handshake = false;
830}
831
832void init(sys::state& state) {
833 if(state.network_mode == sys::network_mode_type::single_player)
834 return; // Do nothing in singleplayer
835
836 state.network_state.finished = false;
837#ifdef _WIN64
838 WSADATA data;
839 if(WSAStartup(MAKEWORD(2, 2), &data) != 0) {
840 window::emit_error_message("WSA startup error: " + get_last_error_msg(), true);
841 }
842#endif
843 if(state.network_mode == sys::network_mode_type::host) {
844 state.network_state.socket_fd = socket_init_server(state.network_state.as_v6, state.network_state.address);
845 } else {
846 assert(state.network_state.ip_address.size() > 0);
847 state.network_state.socket_fd = socket_init_client(state.network_state.as_v6, state.network_state.address, state.network_state.ip_address.c_str());
848 }
849
850 // Host must have an already selected nation, to prevent issues...
851 if(state.network_mode == sys::network_mode_type::host) {
852 load_player_nations(state);
853
854 auto nid = get_player_nation(state, state.network_state.nickname);
855 state.local_player_nation = nid ? nid : choose_nation_for_player(state);
856
857 assert(bool(state.local_player_nation));
858
859 /* Materialize it into a command we send to new clients who connect and have to replay everything... */
861 memset(&c, 0, sizeof(c));
863 c.source = state.local_player_nation;
864 c.data.notify_join.player_name = state.network_state.nickname;
865 c.data.notify_join.player_password = state.network_state.player_password;
866 state.network_state.outgoing_commands.push(c);
867 }
868 else if(state.network_mode == sys::network_mode_type::client) {
869 /* Send our client's handshake */
871 }
872}
873
874static uint8_t* write_network_compressed_section(uint8_t* ptr_out, uint8_t const* ptr_in, uint32_t uncompressed_size) {
875 uint32_t decompressed_length = uncompressed_size;
876 uint32_t section_length = uint32_t(ZSTD_compress(ptr_out + sizeof(uint32_t) * 2, ZSTD_compressBound(uncompressed_size), ptr_in,
877 uncompressed_size, ZSTD_maxCLevel())); // write compressed data
878 memcpy(ptr_out, &section_length, sizeof(uint32_t));
879 memcpy(ptr_out + sizeof(uint32_t), &decompressed_length, sizeof(uint32_t));
880 return ptr_out + sizeof(uint32_t) * 2 + section_length;
881}
882
883template<typename T>
884static uint8_t const* with_network_decompressed_section(uint8_t const* ptr_in, T const& function) {
885 uint32_t section_length = 0;
886 uint32_t decompressed_length = 0;
887 memcpy(&section_length, ptr_in, sizeof(uint32_t));
888 memcpy(&decompressed_length, ptr_in + sizeof(uint32_t), sizeof(uint32_t));
889 auto temp_buffer = std::unique_ptr<uint8_t[]>(new uint8_t[decompressed_length]);
890 ZSTD_decompress(temp_buffer.get(), decompressed_length, ptr_in + sizeof(uint32_t) * 2, section_length);
891 function(temp_buffer.get(), decompressed_length);
892 return ptr_in + sizeof(uint32_t) * 2 + section_length;
893}
894
895void notify_player_joins(sys::state& state, sys::player_name name, dcon::nation_id nation, sys::player_password_raw password) {
896 // Tell all clients about this client
898 memset(&c, 0, sizeof(c));
900
901 c.source = nation;
903 c.data.notify_join.player_password = password;
904
905 for(auto cl : state.network_state.clients) {
906 if(!cl.is_active() || cl.playing_as == nation) {
907 continue;
908 }
909 socket_add_to_send_queue(cl.send_buffer, &c, sizeof(c));
910 }
911 command::execute_command(state, c);
912#ifndef NDEBUG
913 state.console_log("host:broadcast:cmd | type:notify_player_joins nation:" + std::to_string(nation.index()));
914#endif
916}
917
918void notify_player_joins(sys::state& state, network::client_data& client) {
919 notify_player_joins(state, client.hshake_buffer.nickname, client.playing_as, client.hshake_buffer.player_password);
920}
921
923 for(const auto n : state.world.in_nation) {
924 if(n.get_is_player_controlled()) {
925 // Tell new client about all other clients
926
928 memset(&c, 0, sizeof(c));
930 c.source = n;
931 auto p = find_country_player(state, n);
932 auto nickname = state.world.mp_player_get_nickname(p);
934 socket_add_to_send_queue(client.send_buffer, &c, sizeof(c));
935#ifndef NDEBUG
936 state.console_log("host:send:cmd | type:notify_player_joins | to:" + std::to_string(client.playing_as.index()) + " | target nation:" + std::to_string(n.id.index())
937 + " | nickname: " + c.data.notify_join.player_name.to_string());
938#endif
939 }
940 }
941}
942
943void send_savegame(sys::state& state, network::client_data& client, bool hotjoin = false) {
944 std::vector<char> tmp = client.send_buffer;
945 client.send_buffer.clear();
946
947 /* Send the savefile to the newly connected client (if not a new game) */
948 {
950 memset(&c, 0, sizeof(command::payload));
952 c.source = state.local_player_nation;
953 c.data.notify_save_loaded.target = client.playing_as;
954 network::broadcast_save_to_clients(state, c, state.network_state.current_save_buffer.get(), state.network_state.current_save_length, state.network_state.current_save_checksum);
955#ifndef NDEBUG
956 state.console_log("host:broadcast:cmd | (new->save_loaded) | checksum: " + state.network_state.current_save_checksum.to_string()
957 + " | target: " + std::to_string(c.data.notify_save_loaded.target.index()));
958 log_player_nations(state);
959#endif
960 }
961
962 if(hotjoin) {
963 /* Reload clients */
964 std::vector<dcon::nation_id> players;
965 for(const auto n : state.world.in_nation)
966 if(state.world.nation_get_is_player_controlled(n))
967 players.push_back(n);
968 dcon::nation_id old_local_player_nation = state.local_player_nation;
969 state.local_player_nation = dcon::nation_id{ };
970 /* Then reload as if we loaded the save data */
971 state.preload();
972 with_network_decompressed_section(state.network_state.current_save_buffer.get(), [&state](uint8_t const* ptr_in, uint32_t length) {
973 read_save_section(ptr_in, ptr_in + length, state);
974 });
975 state.fill_unsaved_data();
976 for(const auto n : players)
977 state.world.nation_set_is_player_controlled(n, true);
978 state.local_player_nation = old_local_player_nation;
979 assert(state.world.nation_get_is_player_controlled(state.local_player_nation));
980 { /* Reload all the other clients except the newly connected one */
982 memset(&c, 0, sizeof(command::payload));
984 c.source = state.local_player_nation;
985 c.data.notify_reload.checksum = state.get_save_checksum();
986 for(auto& other_client : state.network_state.clients) {
987 if(other_client.playing_as != client.playing_as && other_client.is_active()) {
988 socket_add_to_send_queue(other_client.send_buffer, &c, sizeof(c));
989#ifndef NDEBUG
990 state.console_log("host:send:cmd: (new->reload) | to:" + std::to_string(other_client.playing_as.index()));
991#endif
992 }
993 }
994 }
995 }
996
997 auto old_size = client.send_buffer.size();
998 client.send_buffer.resize(old_size + tmp.size());
999 std::memcpy(client.send_buffer.data() + old_size, tmp.data(), tmp.size());
1000}
1001
1002void notify_start_game(sys::state& state, network::client_data& client) {
1003 // notify_start_game
1005 memset(&c, 0, sizeof(c));
1007 c.source = state.local_player_nation;
1008 socket_add_to_send_queue(client.send_buffer, &c, sizeof(c));
1009#ifndef NDEBUG
1010 state.console_log("host:send:cmd | (new->start_game) to:" + std::to_string(client.playing_as.index()));
1011#endif
1012}
1013
1014bool client_data::is_banned(sys::state& state) const {
1015 if(state.network_state.as_v6) {
1016 auto sa = (struct sockaddr_in6 const*)&address;
1017 return std::find_if(state.network_state.v6_banlist.begin(), state.network_state.v6_banlist.end(), [&](auto const& a) {
1018 return std::memcmp(&sa->sin6_addr, &a, sizeof(a)) == 0;
1019 }) != state.network_state.v6_banlist.end();
1020 } else {
1021 auto sa = (struct sockaddr_in const*)&address;
1022 return std::find_if(state.network_state.v4_banlist.begin(), state.network_state.v4_banlist.end(), [&](auto const& a) {
1023 return std::memcmp(&sa->sin_addr, &a, sizeof(a)) == 0;
1024 }) != state.network_state.v4_banlist.end();
1025 }
1026}
1027
1028static void send_post_handshake_commands(sys::state& state, network::client_data& client) {
1029 std::vector<char> tmp = client.send_buffer;
1030 client.send_buffer.clear();
1031
1032 bool paused = false;
1033
1034 if(state.current_scene.starting_scene) {
1035 /* Lobby - existing savegame */
1036 notify_player_joins(state, client);
1037 if(!state.network_state.is_new_game) {
1039 send_savegame(state, client, true);
1040 }
1041 notify_player_joins_discovery(state, client);
1042
1043 } else if(state.current_scene.game_in_progress) {
1044 notify_player_joins(state, client);
1045 if(!state.network_state.is_new_game) {
1046 paused = pause_game(state);
1048 send_savegame(state, client, true);
1049 }
1050 notify_player_joins_discovery(state, client);
1051
1052 notify_start_game(state, client);
1053 }
1054
1055 auto old_size = client.send_buffer.size();
1056 client.send_buffer.resize(old_size + tmp.size());
1057 std::memcpy(client.send_buffer.data() + old_size, tmp.data(), tmp.size());
1058
1059 if(paused) {
1060 unpause_game(state);
1061 }
1062}
1063
1065 pause_game(state);
1066#ifndef NDEBUG
1067 state.console_log("host:full_reset_after_oos");
1069#endif
1070 /* Reload clients */
1071 if(!state.network_state.is_new_game) {
1072 std::vector<dcon::nation_id> players;
1073 for(const auto n : state.world.in_nation)
1074 if(state.world.nation_get_is_player_controlled(n))
1075 players.push_back(n);
1076 dcon::nation_id old_local_player_nation = state.local_player_nation;
1077 state.local_player_nation = dcon::nation_id{ };
1079 /* Then reload as if we loaded the save data */
1080 state.preload();
1081 with_network_decompressed_section(state.network_state.current_save_buffer.get(), [&state](uint8_t const* ptr_in, uint32_t length) {
1082 read_save_section(ptr_in, ptr_in + length, state);
1083 });
1084 state.fill_unsaved_data();
1085 for(const auto n : players)
1086 state.world.nation_set_is_player_controlled(n, true);
1087 state.local_player_nation = old_local_player_nation;
1088 assert(state.world.nation_get_is_player_controlled(state.local_player_nation));
1089 { /* Reload all the clients */
1091 memset(&c, 0, sizeof(command::payload));
1093 c.source = state.local_player_nation;
1094 c.data.notify_reload.checksum = state.get_save_checksum();
1095 for(auto& other_client : state.network_state.clients) {
1096 if(other_client.is_active()) {
1097 socket_add_to_send_queue(other_client.send_buffer, &c, sizeof(c));
1098#ifndef NDEBUG
1099 state.console_log("host:send:cmd | (new->reload) to:" + std::to_string(other_client.playing_as.index()) +
1100 "| checksum: " + c.data.notify_reload.checksum.to_string());
1101#endif
1102 }
1103 }
1104 }
1105 { /* Send the savefile to all clients */
1107 memset(&c, 0, sizeof(command::payload));
1109 c.source = state.local_player_nation;
1110 network::broadcast_save_to_clients(state, c, state.network_state.current_save_buffer.get(), state.network_state.current_save_length, state.network_state.current_save_checksum);
1111#ifndef NDEBUG
1112 state.console_log("host:broadcast:cmd | (new->save_loaded)");
1113#endif
1114 }
1115 }
1116 {
1118 memset(&c, 0, sizeof(c));
1120 c.source = state.local_player_nation;
1121
1122 broadcast_to_clients(state, c);
1123#ifndef NDEBUG
1124 state.console_log("host:broadcast:cmd | (new->start_game)");
1125#endif
1126 }
1127
1128 unpause_game(state);
1129}
1130
1132 auto r = socket_recv(client.socket_fd, &client.hshake_buffer, sizeof(client.hshake_buffer), &client.recv_count, [&]() {
1133#ifndef NDEBUG
1134 state.console_log("host:recv:handshake | nickname: " + client.hshake_buffer.nickname.to_string());
1135#endif
1136 // Check lobby password
1137 if(std::memcmp(client.hshake_buffer.lobby_password, state.network_state.lobby_password, sizeof(state.network_state.lobby_password)) != 0) {
1138 disconnect_client(state, client, false);
1139 return;
1140 }
1141
1142 // Don't allow two players with the same nickname
1143 for(auto& c : state.network_state.clients) {
1144 if(!c.is_active()) {
1145 continue;
1146 }
1147
1148 if(c.hshake_buffer.nickname.to_string_view() == client.hshake_buffer.nickname.to_string_view() && c.socket_fd != client.socket_fd) {
1149 disconnect_client(state, client, false);
1150 return;
1151 }
1152 }
1153
1154 // Check player password
1155 for(auto pl : state.world.in_mp_player) {
1156 auto nickname_1 = sys::player_name{ pl.get_nickname() }.to_string();
1157 auto nickname_2 = client.hshake_buffer.nickname.to_string();
1158 auto hash_1 = sys::player_password_hash{ pl.get_password_hash() };
1159 auto password_2 = client.hshake_buffer.player_password.to_string();
1160 auto salt = sys::player_password_salt{ pl.get_password_salt() }.to_string();
1161 auto hash_2 = sys::player_password_hash{}.from_string_view(sha512.hash(password_2 + salt));
1162
1163 // If no password is set we allow player to set new password with this connection
1164 if(nickname_1 == nickname_2 && !hash_1.empty() && hash_1.to_string() != hash_2.to_string()) {
1165 disconnect_client(state, client, false);
1166 return;
1167 }
1168 else if(nickname_1 == nickname_2) {
1169 break;
1170 }
1171 }
1172
1173 server_send_handshake(state, client);
1174 send_post_handshake_commands(state, client);
1175 state.game_state_updated.store(true, std::memory_order::release);
1176 });
1177
1178 return r;
1179}
1180
1182 int r = socket_recv(client.socket_fd, &client.recv_buffer, sizeof(client.recv_buffer), &client.recv_count, [&]() {
1183 switch(client.recv_buffer.type) {
1184 case command::command_type::invalid:
1185 case command::command_type::notify_player_ban:
1186 case command::command_type::notify_player_kick:
1187 case command::command_type::notify_save_loaded:
1188 case command::command_type::notify_reload:
1189 case command::command_type::advance_tick:
1190 case command::command_type::notify_start_game:
1191 case command::command_type::notify_stop_game:
1192 case command::command_type::notify_pause_game:
1193 case command::command_type::notify_player_joins:
1194 case command::command_type::save_game:
1195 break; // has to be valid/sendable by client
1196 default:
1197 /* Has to be from the nation of the client proper - and early
1198 discard invalid commands */
1199 if(client.recv_buffer.source == client.playing_as
1200 && command::can_perform_command(state, client.recv_buffer)) {
1201 state.network_state.outgoing_commands.push(client.recv_buffer);
1202 }
1203 break;
1204 }
1205#ifndef NDEBUG
1206 state.console_log("host:recv:client_cmd | from:" + std::to_string(client.playing_as.index()) + " type:" + readableCommandTypes[uint32_t(client.recv_buffer.type)]);
1207#endif
1208 });
1209
1210 return r;
1211}
1212
1213static void receive_from_clients(sys::state& state) {
1214
1215 for(auto& client : state.network_state.clients) {
1216 if(!client.is_active())
1217 continue;
1218 int r = 0;
1219 if(client.handshake) {
1220 r = server_process_handshake(state, client);
1221 if(r > 0) { // error
1222#if !defined(NDEBUG) && defined(_WIN32)
1223 state.console_log("host:disconnect | in-receive err=" + std::to_string(int32_t(r)) + "::" + get_last_error_msg() + " from:" + std::to_string(client.playing_as.index()));
1224#endif
1225 network::disconnect_client(state, client, false);
1226 }
1227
1228 return;
1229 }
1230
1231 int commandspertick = 0;
1232 while(r == 0 && commandspertick < 10) {
1233 r = server_process_commands(state, client);
1234 commandspertick++;
1235 }
1236
1237 if(r > 0) { // error
1238#if !defined(NDEBUG) && defined(_WIN32)
1239 state.console_log("host:disconnect | in-receive err=" + std::to_string(int32_t(r)) + "::" + get_last_error_msg() + " from:" + std::to_string(client.playing_as.index()));
1240#endif
1241 network::disconnect_client(state, client, false);
1242 }
1243 }
1244}
1245
1247 state.console_log("Pausing the game");
1248
1249 if(state.actual_game_speed == 0) {
1250 return false;
1251 }
1252
1253 state.ui_state.held_game_speed = state.actual_game_speed.load();
1254 state.actual_game_speed = 0;
1255 if(state.network_mode == sys::network_mode_type::host) {
1256 command::notify_pause_game(state, state.local_player_nation);
1257 }
1258 return true;
1259}
1260
1262 state.console_log("Unpausing the game");
1263
1264 state.actual_game_speed = state.ui_state.held_game_speed;
1265 return true;
1266}
1267
1269 /* A save lock will be set when we load a save, naturally loading a save implies
1270 that we have done preload/fill_unsaved so we will skip doing that again, to save a
1271 bit of sanity on our miserable CPU */
1272 size_t length = sizeof_save_section(state);
1273 auto save_buffer = std::unique_ptr<uint8_t[]>(new uint8_t[length]);
1274 /* Clear the player nation since it is part of the savegame */
1275 std::vector<dcon::nation_id> players;
1276 for(const auto n : state.world.in_nation)
1277 if(state.world.nation_get_is_player_controlled(n))
1278 players.push_back(n);
1279 dcon::nation_id old_local_player_nation = state.local_player_nation;
1280 state.local_player_nation = dcon::nation_id{ };
1281 assert(state.local_player_nation == dcon::nation_id{ });
1282 write_save_section(save_buffer.get(), state); //writeoff data
1283 // this is an upper bound, since compacting the data may require less space
1284 state.network_state.current_save_buffer.reset(new uint8_t[ZSTD_compressBound(length) + sizeof(uint32_t) * 2]);
1285 auto buffer_position = write_network_compressed_section(state.network_state.current_save_buffer.get(), save_buffer.get(), uint32_t(length));
1286 state.network_state.current_save_length = uint32_t(buffer_position - state.network_state.current_save_buffer.get());
1287 state.network_state.current_save_checksum = state.get_save_checksum();
1288
1289 /* Then reload as if we loaded the save data */
1290 state.preload();
1291 with_network_decompressed_section(state.network_state.current_save_buffer.get(), [&state](uint8_t const* ptr_in, uint32_t length) {
1292 read_save_section(ptr_in, ptr_in + length, state);
1293 });
1294 state.fill_unsaved_data();
1295 for(const auto n : players)
1296 state.world.nation_set_is_player_controlled(n, true);
1297 state.local_player_nation = old_local_player_nation;
1298 assert(state.world.nation_get_is_player_controlled(state.local_player_nation));
1299}
1300
1302 assert(length > 0);
1305 for(auto& client : state.network_state.clients) {
1306 if(!client.is_active())
1307 continue;
1308 bool send_full = (client.playing_as == c.data.notify_save_loaded.target) || (!c.data.notify_save_loaded.target);
1309 if(send_full && !state.network_state.is_new_game) {
1310 /* And then we have to first send the command payload itself */
1311 client.save_stream_size = size_t(length);
1312 c.data.notify_save_loaded.length = size_t(length);
1313 socket_add_to_send_queue(client.send_buffer, &c, sizeof(c));
1314 /* And then the bulk payload! */
1315 client.save_stream_offset = client.total_sent_bytes + client.send_buffer.size();
1316 socket_add_to_send_queue(client.send_buffer, buffer, size_t(length));
1317#ifndef NDEBUG
1318 state.console_log("host:send:save | to" + std::to_string(client.playing_as.index()) + " len: " + std::to_string(uint32_t(length)));
1319#endif
1320 }
1321 }
1322}
1323
1326 return;
1328
1329 c.data.notify_join.player_password = sys::player_name{}; // Never send password to clients
1330 /* Propagate to all the clients */
1331 for(auto& client : state.network_state.clients) {
1332 if(client.is_active()) {
1333 socket_add_to_send_queue(client.send_buffer, &c, sizeof(c));
1334 }
1335 }
1336}
1337
1338static void accept_new_clients(sys::state& state) {
1339 /* Check if any new clients are to join us */
1340 fd_set rfds;
1341 FD_ZERO(&rfds);
1342 FD_SET(state.network_state.socket_fd, &rfds);
1343 struct timeval tv{};
1344 tv.tv_sec = 0;
1345 tv.tv_usec = 1000;
1346 if(select(socket_t(int(state.network_state.socket_fd) + 1), &rfds, nullptr, nullptr, &tv) <= 0)
1347 return;
1348
1349 // Find available slot for client
1350 for(auto& client : state.network_state.clients) {
1351 if(client.is_active())
1352 continue;
1353 socklen_t addr_len = state.network_state.as_v6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);
1354 client.socket_fd = accept(state.network_state.socket_fd, (struct sockaddr*)&client.address, &addr_len);
1355 client.last_seen = state.current_date;
1356 if(client.is_banned(state)) {
1357 disconnect_client(state, client, false);
1358 break;
1359 }
1360 if(state.current_scene.final_scene) {
1361 disconnect_client(state, client, false);
1362 break;
1363 }
1364 return;
1365 }
1366}
1367
1369 /* An issue that arose in multiplayer is that the UI was loading the savefile
1370 directly, while the game state loop was running, this was fine with the
1371 assumption that commands weren't executed while the save was being loaded
1372 HOWEVER in multiplayer this is often the case, so we have to block all
1373 commands until the savefile is finished loading
1374 This way, we're able to effectively and safely queue commands until we
1375 can receive them AFTER loading the savefile. */
1376 if(state.network_state.save_slock.load(std::memory_order::acquire) == true)
1377 return;
1378
1379 if(state.network_state.finished)
1380 return;
1381
1382 bool command_executed = false;
1383 if(state.network_mode == sys::network_mode_type::host) {
1384 accept_new_clients(state); // accept new connections
1385 receive_from_clients(state); // receive new commands
1386
1387 // send the commands of the server to all the clients
1388 auto* c = state.network_state.outgoing_commands.front();
1389 while(c) {
1390 if(!command::is_console_command(c->type)) {
1391 // Generate checksum on the spot
1393 if(state.current_date.to_ymd(state.start_date).day == 1 || state.cheat_data.daily_oos_check) {
1394 c->data.advance_tick.checksum = state.get_save_checksum();
1395 }
1396 }
1397#ifndef NDEBUG
1398 state.console_log("host:send:cmd | from " + std::to_string(c->source.index()) + " type:" + readableCommandTypes[(uint32_t(c->type))]);
1399#endif
1400 command::execute_command(state, *c);
1401 broadcast_to_clients(state, *c);
1402 command_executed = true;
1403 }
1404 state.network_state.outgoing_commands.pop();
1405 c = state.network_state.outgoing_commands.front();
1406 }
1407
1408 // Clear lost sockets
1409 if(state.current_date.to_ymd(state.start_date).day == 1 || state.cheat_data.daily_oos_check) {
1410 for(auto& client : state.network_state.clients) {
1411 if(!client.is_active())
1412 continue;
1413
1414 if(state.current_scene.game_in_progress && state.current_date.value > state.host_settings.alice_lagging_behind_days_to_drop && state.current_date.value - client.last_seen.value > state.host_settings.alice_lagging_behind_days_to_drop) {
1415 if(state.host_settings.alice_persistent_server_mode != 1) {
1416 disconnect_client(state, client, true);
1417 }
1418 else {
1419 clear_socket(state, client);
1420 }
1421 }
1422 }
1423 }
1424
1425 for(auto& client : state.network_state.clients) {
1426 if(!client.is_active())
1427 continue;
1428 if(client.early_send_buffer.size() > 0) {
1429 size_t old_size = client.early_send_buffer.size();
1430 int r = socket_send(client.socket_fd, client.early_send_buffer);
1431 if(r > 0) { // error
1432#if !defined(NDEBUG) && defined(_WIN32)
1433 state.console_log("host:disconnect | in-send-EARLY err=" + std::to_string(int32_t(r)) + "::" + get_last_error_msg());
1434#endif
1435 disconnect_client(state, client, false);
1436 continue;
1437 }
1438 client.total_sent_bytes += old_size - client.early_send_buffer.size();
1439#ifndef NDEBUG
1440 if(old_size != client.early_send_buffer.size())
1441 state.console_log("host:send:stats | [EARLY] to:" + std::to_string(client.playing_as.index()) + "total:" + std::to_string(uint32_t(client.total_sent_bytes)) + " bytes");
1442#endif
1443 } else if(client.send_buffer.size() > 0) {
1444 size_t old_size = client.send_buffer.size();
1445 int r = socket_send(client.socket_fd, client.send_buffer);
1446 if(r > 0) { // error
1447#if !defined(NDEBUG) && defined(_WIN32)
1448 state.console_log("host:disconnect | in-send-INGAME err=" + std::to_string(int32_t(r)) + "::" + get_last_error_msg());
1449#endif
1450 disconnect_client(state, client, false);
1451 continue;
1452 }
1453 client.total_sent_bytes += old_size - client.send_buffer.size();
1454#ifndef NDEBUG
1455 if(old_size != client.send_buffer.size())
1456 state.console_log("host:send:stats | [SEND] to:" +std::to_string(client.playing_as.index()) + "total:" + std::to_string(uint32_t(client.total_sent_bytes)) + " bytes");
1457#endif
1458 }
1459 }
1460 } else if(state.network_mode == sys::network_mode_type::client) {
1461 if(state.network_state.handshake) {
1462 int r = client_process_handshake(state);
1463 if(r > 0) { // error
1464 ui::popup_error_window(state, "Network Error", "Network client handshake receive error: " + get_last_error_msg());
1465 network::finish(state, false);
1466 return;
1467 }
1468 } else if(state.network_state.save_stream) {
1469 int r = socket_recv(state.network_state.socket_fd, state.network_state.save_data.data(), state.network_state.save_data.size(),
1470 &state.network_state.recv_count, [&]() {
1471#ifndef NDEBUG
1472 state.console_log("client:recv:save | len=" + std::to_string(uint32_t(state.network_state.save_data.size())));
1473#endif
1474
1475 dcon::nation_id old_local_player_nation = state.local_player_nation;
1476 state.local_player_nation = dcon::nation_id{ };
1477 state.preload();
1478 with_network_decompressed_section(state.network_state.save_data.data(), [&state](uint8_t const* ptr_in, uint32_t length) {
1479 read_save_section(ptr_in, ptr_in + length, state);
1480 });
1481 state.fill_unsaved_data();
1482
1483#ifndef NDEBUG
1484 auto save_checksum = state.get_save_checksum();
1485 assert(save_checksum.is_equal(state.session_host_checksum));
1486 state.console_log("client:loadsave | checksum:" + state.session_host_checksum.to_string() + "| localchecksum: " + save_checksum.to_string());
1487 log_player_nations(state);
1488#endif
1489
1490 state.local_player_nation = old_local_player_nation;
1491 assert(state.world.nation_get_is_player_controlled(state.local_player_nation));
1492
1493 state.railroad_built.store(true, std::memory_order::release);
1494 state.game_state_updated.store(true, std::memory_order::release);
1495 state.network_state.save_data.clear();
1496 state.network_state.save_stream = false; // go back to normal command loop stuff
1497 });
1498 if(r > 0) { // error
1499 ui::popup_error_window(state, "Network Error", "Network client save stream receive error: " + get_last_error_msg());
1500 network::finish(state, false);
1501 return;
1502 }
1503 } else {
1504 // receive commands from the server and immediately execute them
1505 int r = socket_recv(state.network_state.socket_fd, &state.network_state.recv_buffer, sizeof(state.network_state.recv_buffer), &state.network_state.recv_count, [&]() {
1506
1507#ifndef NDEBUG
1508 state.console_log("client:recv:cmd | from:" + std::to_string(state.network_state.recv_buffer.source.index()) + "type:" + readableCommandTypes[uint32_t(state.network_state.recv_buffer.type)]);
1509#endif
1510
1511 command::execute_command(state, state.network_state.recv_buffer);
1512 command_executed = true;
1513 // start save stream!
1514 if(state.network_state.recv_buffer.type == command::command_type::notify_save_loaded) {
1515 uint32_t save_size = state.network_state.recv_buffer.data.notify_save_loaded.length;
1516 state.network_state.save_stream = true;
1517 assert(save_size > 0);
1518 if(save_size >= 32 * 1000 * 1000) { // 32 MB
1519 ui::popup_error_window(state, "Network Error", "Network client save stream too big: " + get_last_error_msg());
1520 network::finish(state, false);
1521 return;
1522 }
1523 state.network_state.save_data.resize(static_cast<size_t>(save_size));
1524 }
1525
1526 });
1527 if(r > 0) { // error
1528 ui::popup_error_window(state, "Network Error", "Network client command receive error: " + get_last_error_msg());
1529 network::finish(state, false);
1530 return;
1531 }
1532 // send the outgoing commands to the server and flush the entire queue
1533 auto* c = state.network_state.outgoing_commands.front();
1534 while(c) {
1535#ifndef NDEBUG
1536 state.console_log("client:send:cmd | type:" + readableCommandTypes[uint32_t(c->type)]);
1537#endif
1538 if(c->type == command::command_type::save_game) {
1539 command::execute_command(state, *c);
1540 command_executed = true;
1541 } else {
1542 socket_add_to_send_queue(state.network_state.send_buffer, c, sizeof(*c));
1543 }
1544 state.network_state.outgoing_commands.pop();
1545 c = state.network_state.outgoing_commands.front();
1546 }
1547 }
1548 /* Do not send commands while we're on save stream mode! */
1549 if(!state.network_state.save_stream) {
1550 if(socket_send(state.network_state.socket_fd, state.network_state.send_buffer) != 0) { // error
1551 ui::popup_error_window(state, "Network Error", "Network client command send error: " + get_last_error_msg());
1552 network::finish(state, false);
1553 return;
1554 }
1555 }
1556 assert(state.network_state.early_send_buffer.empty()); //do not use the early send buffer
1557 }
1558
1559 if(command_executed) {
1560 if(state.network_state.out_of_sync && !state.network_state.reported_oos) {
1561 command::notify_player_oos(state, state.local_player_nation);
1562 state.network_state.reported_oos = true;
1563 }
1564 state.game_state_updated.store(true, std::memory_order::release);
1565 }
1566}
1567
1568void finish(sys::state& state, bool notify_host) {
1569 if(state.network_mode == sys::network_mode_type::single_player)
1570 return; // Do nothing in singleplayer
1571
1572 state.network_state.finished = true;
1573 if(notify_host && state.network_mode == sys::network_mode_type::client) {
1574 if(!state.network_state.save_stream) {
1575 // send the outgoing commands to the server and flush the entire queue
1576 {
1577 auto* c = state.network_state.outgoing_commands.front();
1578 while(c) {
1579 if(c->type == command::command_type::save_game) {
1580 command::execute_command(state, *c);
1581 } else {
1582 socket_add_to_send_queue(state.network_state.send_buffer, c, sizeof(*c));
1583 }
1584 state.network_state.outgoing_commands.pop();
1585 c = state.network_state.outgoing_commands.front();
1586 }
1587 }
1589 memset(&c, 0, sizeof(c));
1591 c.source = state.local_player_nation;
1592 c.data.notify_leave.make_ai = (state.host_settings.alice_place_ai_upon_disconnection == 1);
1593 socket_add_to_send_queue(state.network_state.send_buffer, &c, sizeof(c));
1594#ifndef NDEBUG
1595 state.console_log("client:send:cmd | type:notify_player_leaves");
1596#endif
1597 while(state.network_state.send_buffer.size() > 0) {
1598 if(socket_send(state.network_state.socket_fd, state.network_state.send_buffer) != 0) { // error
1599 state.console_log("Network client command send error: " + get_last_error_msg());
1600 //ui::popup_error_window(state, "Network Error", "Network client command send error: " + get_last_error_msg());
1601 break;
1602 }
1603 }
1604 }
1605 }
1606
1607 socket_shutdown(state.network_state.socket_fd);
1608#ifdef _WIN64
1609 WSACleanup();
1610#endif
1611}
1612
1614 auto p = find_mp_player(state, name);
1615 if(p) {
1616
1617 auto c = state.world.mp_player_get_nation_from_player_nation(p);
1618
1619 if(c) {
1620 state.world.nation_set_is_player_controlled(c, false);
1621 }
1622 auto rel = state.world.mp_player_get_player_nation(p);
1623 state.world.delete_player_nation(rel);
1624 state.world.delete_mp_player(p);
1625 }
1626}
1627
1628void kick_player(sys::state& state, client_data& client) {
1629 socket_shutdown(client.socket_fd);
1630
1631 clear_socket(state, client);
1632}
1633
1634void ban_player(sys::state& state, client_data& client) {
1635 socket_shutdown(client.socket_fd);
1636
1637 remove_player(state, client.hshake_buffer.nickname);
1638
1639 clear_socket(state, client);
1640
1641 if(state.network_state.as_v6) {
1642 auto sa = (struct sockaddr_in6*)&client.address;
1643 state.network_state.v6_banlist.push_back(sa->sin6_addr);
1644 } else {
1645 auto sa = (struct sockaddr_in*)&client.address;
1646 state.network_state.v4_banlist.push_back(sa->sin_addr);
1647 }
1648}
1649
1650void switch_player(sys::state& state, dcon::nation_id new_n, dcon::nation_id old_n) {
1651 auto p = find_country_player(state, old_n);
1652 state.world.force_create_player_nation(new_n, p);
1653
1654 if(state.network_mode == sys::network_mode_type::host) {
1655 for(auto& client : state.network_state.clients) {
1656 if(!client.is_active())
1657 continue;
1658 if(client.playing_as == old_n) {
1659 client.playing_as = new_n;
1660 }
1661 }
1662
1663 write_player_nations(state);
1664 }
1665}
1666
1668 load_player_nations(state);
1669
1670 auto n = choose_nation_for_player(state);
1671 state.local_player_nation = n;
1672 assert(bool(state.local_player_nation));
1673 state.world.nation_set_is_player_controlled(n, true);
1674
1676 memset(&c, 0, sizeof(c));
1678 c.source = n;
1679 c.data.notify_join.player_name = state.network_state.nickname;
1680 state.local_player_nation = c.source;
1681 state.network_state.outgoing_commands.push(c);
1682
1683 log_player_nations(state);
1684}
1685
1687 auto settings_location = simple_fs::get_or_create_settings_directory();
1688 auto settings_file = open_file(settings_location, NATIVE("host_settings.json"));
1689 if(settings_file) {
1690 auto content = view_contents(*settings_file);
1691 json data = json::parse(content.data);
1692
1693 if(!data["alice_expose_webui"].empty()) \
1694 state.host_settings.alice_expose_webui = data["alice_expose_webui"];
1695
1696#define HS_LOAD(x, y) \
1697if(!data[x].empty()) \
1698state.host_settings.y = data[x]
1699
1700 HS_LOAD("alice_expose_webui", alice_expose_webui);
1701 HS_LOAD("alice_lagging_behind_days_to_drop", alice_lagging_behind_days_to_drop);
1702 HS_LOAD("alice_persistent_server_mode", alice_persistent_server_mode);
1703 HS_LOAD("alice_place_ai_upon_disconnection", alice_place_ai_upon_disconnection);
1704 HS_LOAD("alice_persistent_server_pause", alice_persistent_server_pause);
1705 HS_LOAD("alice_persistent_server_unpause", alice_persistent_server_unpause);
1706 }
1707}
1708
1709// Used primarily to create JSON file with default values
1711 auto settings_location = simple_fs::get_or_create_settings_directory();
1712 auto settings_file = open_file(settings_location, NATIVE("host_settings.json"));
1713 if(!settings_file) {
1714
1715#define HS_SAVE(x, y) \
1716data[x] = state.host_settings.y
1717
1718 json data = json::object();
1719
1720 HS_SAVE("alice_expose_webui", alice_expose_webui);
1721 HS_SAVE("alice_lagging_behind_days_to_drop", alice_lagging_behind_days_to_drop);
1722 HS_SAVE("alice_persistent_server_mode", alice_persistent_server_mode);
1723 HS_SAVE("alice_place_ai_upon_disconnection", alice_place_ai_upon_disconnection);
1724 HS_SAVE("alice_persistent_server_pause", alice_persistent_server_pause);
1725 HS_SAVE("alice_persistent_server_unpause", alice_persistent_server_unpause);
1726
1727 std::string res = data.dump();
1728
1729 simple_fs::write_file(settings_location, NATIVE("host_settings.json"), res.data(), uint32_t(res.length()));
1730 }
1731}
1732
1733}
Header file describing the function signatures and the constants of the SHA512 Algorithm.
namespace for Niels Lohmann
Definition: json.hpp:19499
static JSON_HEDLEY_WARN_UNUSED_RESULT basic_json object(initializer_list_t init={})
explicitly create an object from an initializer list
Definition: json.hpp:20413
string_t dump(const int indent=-1, const char indent_char=' ', const bool ensure_ascii=false, const error_handler_t error_handler=error_handler_t::strict) const
serialization
Definition: json.hpp:20672
std::string hash(const std::string input)
Definition: SHA512.hpp:107
#define assert(condition)
Definition: debug.h:74
#define INVALID_SOCKET
Definition: httplib.h:230
int socket_t
Definition: httplib.h:228
bool is_console_command(command_type t)
Definition: commands.cpp:21
bool can_notify_player_leaves(sys::state &state, dcon::nation_id source, bool make_ai)
Definition: commands.cpp:4959
void notify_start_game(sys::state &state, dcon::nation_id source)
Definition: commands.cpp:5239
void notify_player_joins(sys::state &state, dcon::nation_id source, sys::player_name &name)
Definition: commands.cpp:4901
void execute_command(sys::state &state, payload &c)
Definition: commands.cpp:5666
void accept(sys::state &state, message const &m)
void broadcast_save_to_clients(sys::state &state, command::payload &c, uint8_t const *buffer, uint32_t length, sys::checksum_key const &k)
Definition: network.cpp:1301
dcon::mp_player_id find_country_player(sys::state &state, dcon::nation_id nation)
Definition: network.cpp:702
bool pause_game(sys::state &state)
Definition: network.cpp:1246
void place_host_player_after_saveload(sys::state &state)
Definition: network.cpp:1667
void write_network_save(sys::state &state)
Definition: network.cpp:1268
void update_mp_player_password(sys::state &state, dcon::mp_player_id player_id, sys::player_password_raw &password)
Definition: network.cpp:683
std::string get_last_error_msg()
Definition: network.cpp:278
dcon::mp_player_id find_mp_player(sys::state &state, sys::player_name name)
Definition: network.cpp:690
void load_host_settings(sys::state &state)
Definition: network.cpp:1686
int client_process_handshake(sys::state &state)
Definition: network.cpp:750
void clear_socket(sys::state &state, client_data &client)
Definition: network.cpp:502
void broadcast_to_clients(sys::state &state, command::payload &c)
Definition: network.cpp:1324
int server_process_commands(sys::state &state, network::client_data &client)
Definition: network.cpp:1181
SHA512 sha512
Definition: network.cpp:60
void notify_player_joins_discovery(sys::state &state, network::client_data &client)
Definition: network.cpp:922
bool unpause_game(sys::state &state)
Definition: network.cpp:1261
void client_send_handshake(sys::state &state)
Definition: network.cpp:737
dcon::mp_player_id create_mp_player(sys::state &state, sys::player_name &name, sys::player_password_raw &password)
Definition: network.cpp:662
void send_savegame(sys::state &state, network::client_data &client, bool hotjoin=false)
Definition: network.cpp:943
dcon::mp_player_id load_mp_player(sys::state &state, sys::player_name &name, sys::player_password_hash &password_hash, sys::player_password_salt &password_salt)
Definition: network.cpp:674
void load_player_nations(sys::state &state) noexcept
void kick_player(sys::state &state, client_data &client)
Definition: network.cpp:1628
constexpr short default_server_port
Definition: network.hpp:26
void finish(sys::state &state, bool notify_host)
Definition: network.cpp:1568
void switch_player(sys::state &state, dcon::nation_id new_n, dcon::nation_id old_n)
Definition: network.cpp:1650
int server_process_handshake(sys::state &state, network::client_data &client)
Definition: network.cpp:1131
void ban_player(sys::state &state, client_data &client)
Definition: network.cpp:1634
void save_host_settings(sys::state &state)
Definition: network.cpp:1710
void send_and_receive_commands(sys::state &state)
Definition: network.cpp:1368
void init(sys::state &state)
Definition: network.cpp:832
void server_send_handshake(sys::state &state, network::client_data &client)
Definition: network.cpp:807
void remove_player(sys::state &state, sys::player_name name)
Definition: network.cpp:1613
void full_reset_after_oos(sys::state &state)
Definition: network.cpp:1064
void log_player_nations(sys::state &state)
Definition: network.cpp:527
void write_player_nations(sys::state &state) noexcept
native_string utf8_to_native(std::string_view data_in)
void write_file(directory const &dir, native_string_view file_name, char const *file_data, uint32_t file_size)
directory get_or_create_settings_directory()
std::string native_to_utf8(native_string_view data_in)
void popup_error_window(sys::state &state, std::string_view title, std::string_view body)
void send(sys::state &state, element_base *parent, T value)
T select(bool v, T a, T b)
void emit_error_message(std::string const &content, bool fatal)
Definition: window_nix.cpp:355
#define NATIVE(X)
std::string native_string
#define HS_SAVE(x, y)
#define HS_LOAD(x, y)
uint uint32_t
uchar uint8_t
#define ENABLE_AUTODISCOVERY
Definition: pcp.h:120
pcp_ctx_t * pcp_init(uint8_t autodiscovery, pcp_socket_vt_t *socket_vt)
Definition: pcp_api.c:93
pcp_fstate_e pcp_wait(pcp_flow_t *flow, int timeout, int exit_on_partial_res)
Definition: pcp_api.c:187
pcp_flow_t * pcp_new_flow(pcp_ctx_t *ctx, struct sockaddr *src_addr, struct sockaddr *dst_addr, struct sockaddr *ext_addr, uint8_t protocol, uint32_t lifetime, void *userdata)
Definition: pcp_api.c:389
void pcp_terminate(pcp_ctx_t *ctx, int close_flows)
Definition: pcp_api.c:662
#define inet_ntop
#define MSG_DONTWAIT
sys::player_name player_name
Definition: commands.hpp:465
sys::player_password_raw player_password
Definition: commands.hpp:466
sys::checksum_key checksum
Definition: commands.hpp:474
command_type type
Definition: commands.hpp:549
union command::payload::dtype data
dcon::nation_id source
Definition: commands.hpp:548
sys::player_name nickname
Definition: network.hpp:35
sys::player_password_raw player_password
Definition: network.hpp:36
dcon::nation_id assigned_nation
Definition: network.hpp:45
sys::checksum_key save_checksum
Definition: network.hpp:43
sys::checksum_key scenario_checksum
Definition: network.hpp:42
std::string to_string() noexcept
player_value< _Size > from_string_view(std::string_view sv) noexcept
std::string to_string() noexcept
std::array< uint8_t, _Size > data
Holds important data about the game world, state, and other data regarding windowing,...
notify_reload_data notify_reload
Definition: commands.hpp:537
notify_save_loaded_data notify_save_loaded
Definition: commands.hpp:536
notify_leaves_data notify_leave
Definition: commands.hpp:540
notify_joins_data notify_join
Definition: commands.hpp:539
void err_msg(const char *,...)
size_t ZSTD_compressBound(size_t srcSize)
Definition: zstd_compress.c:69
int ZSTD_maxCLevel(void)
size_t ZSTD_compress(void *dst, size_t dstCapacity, const void *src, size_t srcSize, int compressionLevel)
size_t ZSTD_decompress(void *dst, size_t dstCapacity, const void *src, size_t srcSize)