5#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
6#ifndef WINSOCK2_IMPORTED
7#define WINSOCK2_IMPORTED
18#include <netinet/in.h>
19#include <sys/socket.h>
35#define ZSTD_STATIC_LINKING_ONLY
36#define XXH_NAMESPACE ZSTD_
40#pragma comment(lib, "Iphlpapi.lib")
41#pragma comment(lib, "ntdll.lib")
45using json = nlohmann::json;
69 do_forwarding = std::thread{ [&]() {
71 if(!SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)))
75 std::vector<local_addresses> found_locals;
79 IP_ADAPTER_ADDRESSES* adapter_addresses(NULL);
80 IP_ADAPTER_ADDRESSES* adapter(NULL);
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);
90 DWORD error = GetAdaptersAddresses(
92 GAA_FLAG_SKIP_ANYCAST |
93 GAA_FLAG_SKIP_MULTICAST |
94 GAA_FLAG_SKIP_DNS_SERVER |
95 GAA_FLAG_SKIP_FRIENDLY_NAME,
98 &adapter_addresses_buffer_size);
100 if(ERROR_SUCCESS == error) {
103 }
else if(ERROR_BUFFER_OVERFLOW == error) {
105 free(adapter_addresses);
106 adapter_addresses = NULL;
111 free(adapter_addresses);
112 adapter_addresses = NULL;
119 for(adapter = adapter_addresses; NULL != adapter; adapter = adapter->Next) {
121 if(IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType) {
127 IP_ADAPTER_UNICAST_ADDRESS* address = adapter->FirstUnicastAddress;
129 address = address->Next) {
130 auto family = address->Address.lpSockaddr->sa_family;
131 if(address->DadState != NldsPreferred && address->DadState != IpDadStatePreferred)
134 if(AF_INET == family) {
136 SOCKADDR_IN* ipv4 =
reinterpret_cast<SOCKADDR_IN*
>(address->Address.lpSockaddr);
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) {
143 SOCKADDR_IN6* ipv6 =
reinterpret_cast<SOCKADDR_IN6*
>(address->Address.lpSockaddr);
145 char str_buffer[INET6_ADDRSTRLEN] = { 0 };
146 inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN);
148 std::string ipv6_str(str_buffer);
151 bool is_link_local(
false);
152 bool is_special_use(
false);
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;
159 }
else if(0 == ipv6_str.find(
"2001:0:")) {
160 is_special_use =
true;
163 if(!(is_link_local || is_special_use)) {
164 found_locals.push_back(
local_addresses{ std::string(ipv6_str),
true });
174 free(adapter_addresses);
175 adapter_addresses = NULL;
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;
185 BSTR proto = SysAllocString(L
"TCP");
186 BSTR desc = SysAllocString(L
"Project Alice Host");
188 BSTR local_host = SysAllocString(tmpwstr.c_str());
189 VARIANT_BOOL enabled = VARIANT_TRUE;
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) {
194 mapped_ports_with_upnp =
true;
200 if(!mapped_ports_with_upnp) {
203 sockaddr_storage source_ip;
204 sockaddr_storage ext_ip;
206 WORD wVersionRequested;
209 wVersionRequested = MAKEWORD(2, 2);
210 auto err = WSAStartup(wVersionRequested, &wsaData);
212 memset(&source_ip, 0,
sizeof(source_ip));
213 memset(&ext_ip, 0,
sizeof(ext_ip));
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;
220 ((sockaddr_in*)(&ext_ip))->sin_port = 1984;
221 ((sockaddr_in*)(&ext_ip))->sin_family = AF_INET;
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;
228 ((sockaddr_in6*)(&ext_ip))->sin6_port = 1984;
229 ((sockaddr_in6*)(&ext_ip))->sin6_family = AF_INET6;
232 auto flow =
pcp_new_flow(pcp_obj, (sockaddr*)&source_ip,
nullptr, (sockaddr*)&ext_ip, IPPROTO_TCP, 3600,
nullptr);
239 internal_wait.lock();
250 opened_port->Release();
252 port_mappings->Release();
254 nat_interface->Release();
256 SysFreeString(proto);
257 SysFreeString(local_host);
260 internal_wait.unlock();
271 internal_wait.unlock();
272 do_forwarding.join();
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);
287 if(err == WSAECONNRESET) {
289 }
else if(err == WSAEHOSTDOWN) {
291 }
else if(err == WSAEHOSTUNREACH) {
292 err_msg +=
"Host is unreachable ";
294 err_msg +=
"Network issue ocurred ";
296 err_msg +=
"Technical details: ";
297 err_msg += std::to_string(err);
302 return std::string(
"Dummy");
306static int internal_socket_recv(
socket_t socket_fd,
void *data,
size_t n) {
308 u_long has_pending = 0;
309 auto r = ioctlsocket(socket_fd, FIONREAD, &has_pending);
311 return static_cast<int>(recv(socket_fd,
reinterpret_cast<char *
>(data),
static_cast<int>(n), 0));
318static int internal_socket_send(
socket_t socket_fd,
const void *data,
size_t n) {
320 return static_cast<int>(
send(socket_fd,
reinterpret_cast<const char *
>(data),
static_cast<int>(n), 0));
322 return send(socket_fd, data, n, MSG_NOSIGNAL);
327static int socket_recv(
socket_t socket_fd,
void* data,
size_t len,
size_t* m, F&& func) {
329 int r = internal_socket_recv(socket_fd,
reinterpret_cast<uint8_t*
>(data) + *m, len - *m);
331 *
m +=
static_cast<size_t>(r);
334 int err = WSAGetLastError();
335 if(err == WSAENOBUFS || err == WSAEWOULDBLOCK) {
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());
361 buffer.erase(buffer.begin(), buffer.begin() +
static_cast<size_t>(r));
364 int err = WSAGetLastError();
365 if(err == WSAENOBUFS || err == WSAEWOULDBLOCK) {
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);
384static void socket_shutdown(
socket_t socket_fd) {
387 shutdown(socket_fd, SD_BOTH);
388 closesocket(socket_fd);
390 shutdown(socket_fd, SHUT_RDWR);
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));
406 struct timeval timeout;
411 if(setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (
char*)&opt,
sizeof(opt))) {
414 if(setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (
char*)&timeout,
sizeof(timeout)) < 0) {
417 if(setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, (
char*)&timeout,
sizeof(timeout)) < 0) {
421 if(setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt,
sizeof(opt))) {
424 if(setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout,
sizeof(timeout)) < 0) {
427 if(setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, &timeout,
sizeof(timeout)) < 0) {
432 struct sockaddr_in6 v6_server_address;
433 v6_server_address.sin6_addr = IN6ADDR_ANY_INIT;
434 v6_server_address.sin6_family = AF_INET6;
436 std::memcpy(&server_address, &v6_server_address,
sizeof(v6_server_address));
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;
442 std::memcpy(&server_address, &v4_server_address,
sizeof(v4_server_address));
444 if(bind(socket_fd, (
struct sockaddr*)&server_address, as_v6 ?
sizeof(sockaddr_in6) :
sizeof(sockaddr_in)) < 0) {
447 if(listen(socket_fd, SOMAXCONN) < 0) {
452 ioctlsocket(socket_fd, FIONBIO, &mode);
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) {
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));
478 freeaddrinfo(result);
482 socket_t socket_fd =
static_cast<socket_t>(socket(as_v6 ? AF_INET6 : AF_INET, SOCK_STREAM, IPPROTO_TCP));
492 if(connect(socket_fd, (
const struct sockaddr*)&client_address, as_v6 ?
sizeof(sockaddr_in6) :
sizeof(sockaddr_in)) < 0) {
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;
516static void disconnect_client(
sys::state& state, client_data& client,
bool graceful) {
521 state.console_log(
"server:disconnectclient | country:" + std::to_string(
client.playing_as.index()));
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()));
533 for(
const auto p : state.world.in_mp_player) {
534 auto data = p.get_nickname();
536 auto nation = p.get_player_nation().get_nation();
538 state.console_log(
"player id: " + std::to_string(p.id.index()) +
" nickname: " + nickname.to_string() +
" nation:" + std::to_string(nation.id.index()));
542static std::map<int, std::string> readableCommandTypes = {
544{1,
"change_nat_focus"},
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"},
556{13,
"release_and_play_nation"},
558{15,
"cancel_war_subsidies"},
560{17,
"start_election"},
561{18,
"change_influence_priority"},
562{19,
"discredit_advisors"},
563{20,
"expel_advisors"},
565{22,
"increase_opinion"},
566{23,
"decrease_opinion"},
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"},
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"},
590{47,
"cancel_cb_fabrication"},
591{48,
"ask_for_military_access"},
592{49,
"ask_for_alliance"},
594{51,
"respond_to_diplomatic_message"},
595{52,
"cancel_military_access"},
596{53,
"cancel_alliance"},
597{54,
"cancel_given_military_access"},
600{58,
"start_peace_offer"},
601{59,
"add_peace_offer_term"},
602{60,
"send_peace_offer"},
612{70,
"designate_split_regiments"},
613{71,
"designate_split_ships"},
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"},
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"},
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"},
658{122,
"network_inactivity_ping"},
659{255,
"console_command"},
663 auto p = state.world.create_mp_player();
664 state.world.mp_player_set_nickname(p, name.data);
668 state.world.mp_player_set_password_hash(p, hash.data);
669 state.world.mp_player_set_password_salt(p, salt.data);
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);
686 state.world.mp_player_set_password_hash(player_id, hash.data);
687 state.world.mp_player_set_password_salt(player_id, salt.data);
691 for(
const auto p : state.world.in_mp_player) {
692 auto nickname = p.get_nickname();
694 if(std::equal(std::begin(nickname), std::end(nickname), std::begin(name.data))) {
699 return dcon::mp_player_id{};
703 return state.world.nation_get_mp_player_from_player_nation(nation);
711 return state.world.mp_player_get_nation_from_player_nation(p);
714 return dcon::nation_id{ };
717static dcon::nation_id choose_nation_for_player(
sys::state& state) {
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) {
734 return dcon::nation_id{ };
740 hshake.
nickname = state.network_state.nickname;
743 socket_add_to_send_queue(state.network_state.send_buffer, &hshake,
sizeof(hshake));
746 state.console_log(
"client:send:handshake");
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;
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);
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))
765 if(scen_header.version != sys::scenario_file_version)
767 if(sys::try_read_scenario_and_save_file(state, simple_fs::get_file_name(uf))) {
768 state.fill_unsaved_data();
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'!";
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";
785 window::emit_error_message(msg.c_str(), true);
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);
794 state.console_log(
"client:recv:handshake");
797 state.network_state.handshake =
false;
800 state.map_state.set_selected_province(dcon::province_id{});
801 state.map_state.unhandled_province_selection =
true;
809 auto plnation = get_player_nation(state, client.hshake_buffer.nickname);
811 client.playing_as = plnation;
813 client.playing_as = choose_nation_for_player(state);
815 assert(client.playing_as);
819 hshake.
seed = state.game_seed;
823 socket_add_to_send_queue(client.early_send_buffer, &hshake,
sizeof(hshake));
825 state.console_log(
"host:send:handshake | assignednation:" + std::to_string(client.playing_as.index()));
829 client.handshake =
false;
836 state.network_state.finished =
false;
839 if(WSAStartup(MAKEWORD(2, 2), &data) != 0) {
844 state.network_state.socket_fd = socket_init_server(state.network_state.as_v6, state.network_state.address);
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());
854 auto nid = get_player_nation(state, state.network_state.nickname);
855 state.local_player_nation = nid ? nid : choose_nation_for_player(state);
857 assert(
bool(state.local_player_nation));
861 memset(&c, 0,
sizeof(c));
863 c.
source = state.local_player_nation;
866 state.network_state.outgoing_commands.push(c);
875 uint32_t decompressed_length = uncompressed_size;
878 memcpy(ptr_out, §ion_length,
sizeof(
uint32_t));
880 return ptr_out +
sizeof(
uint32_t) * 2 + section_length;
884static uint8_t const* with_network_decompressed_section(
uint8_t const* ptr_in, T
const& function) {
887 memcpy(§ion_length, ptr_in,
sizeof(
uint32_t));
889 auto temp_buffer = std::unique_ptr<uint8_t[]>(
new uint8_t[decompressed_length]);
891 function(temp_buffer.get(), decompressed_length);
892 return ptr_in +
sizeof(
uint32_t) * 2 + section_length;
898 memset(&c, 0,
sizeof(c));
905 for(
auto cl : state.network_state.clients) {
906 if(!cl.is_active() || cl.playing_as == nation) {
909 socket_add_to_send_queue(cl.send_buffer, &c,
sizeof(c));
913 state.console_log(
"host:broadcast:cmd | type:notify_player_joins nation:" + std::to_string(nation.index()));
919 notify_player_joins(state, client.hshake_buffer.nickname, client.playing_as, client.hshake_buffer.player_password);
923 for(
const auto n : state.world.in_nation) {
924 if(n.get_is_player_controlled()) {
928 memset(&c, 0,
sizeof(c));
932 auto nickname = state.world.mp_player_get_nickname(p);
934 socket_add_to_send_queue(client.send_buffer, &c,
sizeof(c));
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())
944 std::vector<char> tmp = client.send_buffer;
945 client.send_buffer.clear();
952 c.
source = state.local_player_nation;
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);
956 state.console_log(
"host:broadcast:cmd | (new->save_loaded) | checksum: " + state.network_state.current_save_checksum.to_string()
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{ };
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);
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));
984 c.
source = state.local_player_nation;
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));
990 state.console_log(
"host:send:cmd: (new->reload) | to:" + std::to_string(other_client.playing_as.index()));
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());
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));
1010 state.console_log(
"host:send:cmd | (new->start_game) to:" + std::to_string(client.playing_as.index()));
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();
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();
1029 std::vector<char> tmp = client.send_buffer;
1030 client.send_buffer.clear();
1032 bool paused =
false;
1034 if(state.current_scene.starting_scene) {
1036 notify_player_joins(state, client);
1037 if(!state.network_state.is_new_game) {
1041 notify_player_joins_discovery(state, client);
1043 }
else if(
state.current_scene.game_in_progress) {
1045 if(!
state.network_state.is_new_game) {
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());
1067 state.console_log(
"host:full_reset_after_oos");
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{ };
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);
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));
1093 c.
source = state.local_player_nation;
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));
1099 state.console_log(
"host:send:cmd | (new->reload) to:" + std::to_string(other_client.playing_as.index()) +
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);
1112 state.console_log(
"host:broadcast:cmd | (new->save_loaded)");
1118 memset(&c, 0,
sizeof(c));
1120 c.
source = state.local_player_nation;
1124 state.console_log(
"host:broadcast:cmd | (new->start_game)");
1132 auto r = socket_recv(client.socket_fd, &client.hshake_buffer,
sizeof(client.hshake_buffer), &client.recv_count, [&]() {
1134 state.console_log(
"host:recv:handshake | nickname: " + client.hshake_buffer.nickname.to_string());
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);
1143 for(
auto& c : state.network_state.clients) {
1144 if(!c.is_active()) {
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);
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();
1159 auto password_2 = client.hshake_buffer.player_password.to_string();
1164 if(nickname_1 == nickname_2 && !hash_1.empty() && hash_1.to_string() != hash_2.to_string()) {
1165 disconnect_client(state, client, false);
1168 else if(nickname_1 == nickname_2) {
1174 send_post_handshake_commands(state, client);
1175 state.game_state_updated.store(
true, std::memory_order::release);
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:
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);
1206 state.console_log(
"host:recv:client_cmd | from:" + std::to_string(client.playing_as.index()) +
" type:" + readableCommandTypes[
uint32_t(client.recv_buffer.type)]);
1213static void receive_from_clients(
sys::state& state) {
1215 for(
auto& client :
state.network_state.clients) {
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()));
1225 network::disconnect_client(state, client,
false);
1231 int commandspertick = 0;
1232 while(r == 0 && commandspertick < 10) {
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()));
1241 network::disconnect_client(state, client,
false);
1247 state.console_log(
"Pausing the game");
1249 if(state.actual_game_speed == 0) {
1253 state.ui_state.held_game_speed = state.actual_game_speed.load();
1254 state.actual_game_speed = 0;
1262 state.console_log(
"Unpausing the game");
1264 state.actual_game_speed = state.ui_state.held_game_speed;
1272 size_t length = sizeof_save_section(state);
1273 auto save_buffer = std::unique_ptr<uint8_t[]>(
new uint8_t[length]);
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);
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();
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);
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));
1305 for(
auto& client : state.network_state.clients) {
1306 if(!client.is_active())
1309 if(send_full && !state.network_state.is_new_game) {
1311 client.save_stream_size = size_t(length);
1313 socket_add_to_send_queue(client.send_buffer, &c,
sizeof(c));
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));
1318 state.console_log(
"host:send:save | to" + std::to_string(client.playing_as.index()) +
" len: " + std::to_string(
uint32_t(length)));
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));
1338static void accept_new_clients(
sys::state& state) {
1342 FD_SET(state.network_state.socket_fd, &rfds);
1343 struct timeval tv{};
1346 if(
select(
socket_t(
int(
state.network_state.socket_fd) + 1), &rfds,
nullptr,
nullptr, &tv) <= 0)
1350 for(
auto& client :
state.network_state.clients) {
1353 socklen_t addr_len =
state.network_state.as_v6 ?
sizeof(sockaddr_in6) :
sizeof(sockaddr_in);
1356 if(
client.is_banned(state)) {
1357 disconnect_client(state, client,
false);
1360 if(
state.current_scene.final_scene) {
1361 disconnect_client(state, client,
false);
1376 if(state.network_state.save_slock.load(std::memory_order::acquire) ==
true)
1379 if(state.network_state.finished)
1382 bool command_executed =
false;
1384 accept_new_clients(state);
1385 receive_from_clients(state);
1388 auto* c = state.network_state.outgoing_commands.front();
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();
1398 state.console_log(
"host:send:cmd | from " + std::to_string(c->source.index()) +
" type:" + readableCommandTypes[(
uint32_t(c->type))]);
1402 command_executed =
true;
1404 state.network_state.outgoing_commands.pop();
1405 c = state.network_state.outgoing_commands.front();
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())
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);
1425 for(
auto& client : state.network_state.clients) {
1426 if(!client.is_active())
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);
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());
1435 disconnect_client(state, client,
false);
1438 client.total_sent_bytes += old_size - client.early_send_buffer.size();
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");
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);
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());
1450 disconnect_client(state, client,
false);
1453 client.total_sent_bytes += old_size - client.send_buffer.size();
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");
1461 if(state.network_state.handshake) {
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, [&]() {
1472 state.console_log(
"client:recv:save | len=" + std::to_string(uint32_t(state.network_state.save_data.size())));
1475 dcon::nation_id old_local_player_nation = state.local_player_nation;
1476 state.local_player_nation = dcon::nation_id{ };
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);
1481 state.fill_unsaved_data();
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());
1490 state.local_player_nation = old_local_player_nation;
1491 assert(state.world.nation_get_is_player_controlled(state.local_player_nation));
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;
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, [&]() {
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)]);
1511 command::execute_command(state, state.network_state.recv_buffer);
1512 command_executed = true;
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) {
1519 ui::popup_error_window(state,
"Network Error",
"Network client save stream too big: " + get_last_error_msg());
1520 network::finish(state, false);
1523 state.network_state.save_data.resize(static_cast<size_t>(save_size));
1533 auto* c = state.network_state.outgoing_commands.front();
1536 state.console_log(
"client:send:cmd | type:" + readableCommandTypes[
uint32_t(c->type)]);
1540 command_executed =
true;
1542 socket_add_to_send_queue(state.network_state.send_buffer, c,
sizeof(*c));
1544 state.network_state.outgoing_commands.pop();
1545 c = state.network_state.outgoing_commands.front();
1549 if(!state.network_state.save_stream) {
1550 if(socket_send(state.network_state.socket_fd, state.network_state.send_buffer) != 0) {
1551 ui::popup_error_window(state,
"Network Error",
"Network client command send error: " + get_last_error_msg());
1556 assert(
state.network_state.early_send_buffer.empty());
1559 if(command_executed) {
1560 if(
state.network_state.out_of_sync && !
state.network_state.reported_oos) {
1562 state.network_state.reported_oos =
true;
1564 state.game_state_updated.store(
true, std::memory_order::release);
1572 state.network_state.finished =
true;
1574 if(!state.network_state.save_stream) {
1577 auto* c = state.network_state.outgoing_commands.front();
1582 socket_add_to_send_queue(state.network_state.send_buffer, c,
sizeof(*c));
1584 state.network_state.outgoing_commands.pop();
1585 c = state.network_state.outgoing_commands.front();
1589 memset(&c, 0,
sizeof(c));
1591 c.
source = state.local_player_nation;
1593 socket_add_to_send_queue(state.network_state.send_buffer, &c,
sizeof(c));
1595 state.console_log(
"client:send:cmd | type:notify_player_leaves");
1597 while(state.network_state.send_buffer.size() > 0) {
1598 if(socket_send(state.network_state.socket_fd, state.network_state.send_buffer) != 0) {
1607 socket_shutdown(state.network_state.socket_fd);
1617 auto c = state.world.mp_player_get_nation_from_player_nation(p);
1620 state.world.nation_set_is_player_controlled(c,
false);
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);
1629 socket_shutdown(client.socket_fd);
1635 socket_shutdown(client.socket_fd);
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);
1645 auto sa = (
struct sockaddr_in*)&client.address;
1646 state.network_state.v4_banlist.push_back(sa->sin_addr);
1652 state.world.force_create_player_nation(new_n, p);
1655 for(
auto& client : state.network_state.clients) {
1656 if(!client.is_active())
1658 if(client.playing_as == old_n) {
1659 client.playing_as = new_n;
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);
1676 memset(&c, 0,
sizeof(c));
1680 state.local_player_nation = c.
source;
1681 state.network_state.outgoing_commands.push(c);
1688 auto settings_file = open_file(settings_location,
NATIVE(
"host_settings.json"));
1690 auto content = view_contents(*settings_file);
1691 json data = json::parse(content.data);
1693 if(!data[
"alice_expose_webui"].empty()) \
1694 state.host_settings.alice_expose_webui = data[
"alice_expose_webui"];
1696#define HS_LOAD(x, y) \
1697if(!data[x].empty()) \
1698state.host_settings.y = data[x]
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);
1712 auto settings_file = open_file(settings_location,
NATIVE(
"host_settings.json"));
1713 if(!settings_file) {
1715#define HS_SAVE(x, y) \
1716data[x] = state.host_settings.y
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);
1727 std::string res = data.
dump();
Header file describing the function signatures and the constants of the SHA512 Algorithm.
namespace for Niels Lohmann
static JSON_HEDLEY_WARN_UNUSED_RESULT basic_json object(initializer_list_t init={})
explicitly create an object from an initializer list
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
std::string hash(const std::string input)
#define assert(condition)
bool is_console_command(command_type t)
bool can_notify_player_leaves(sys::state &state, dcon::nation_id source, bool make_ai)
void notify_start_game(sys::state &state, dcon::nation_id source)
void notify_player_joins(sys::state &state, dcon::nation_id source, sys::player_name &name)
void execute_command(sys::state &state, payload &c)
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)
dcon::mp_player_id find_country_player(sys::state &state, dcon::nation_id nation)
bool pause_game(sys::state &state)
void place_host_player_after_saveload(sys::state &state)
void write_network_save(sys::state &state)
void update_mp_player_password(sys::state &state, dcon::mp_player_id player_id, sys::player_password_raw &password)
std::string get_last_error_msg()
dcon::mp_player_id find_mp_player(sys::state &state, sys::player_name name)
void load_host_settings(sys::state &state)
int client_process_handshake(sys::state &state)
void clear_socket(sys::state &state, client_data &client)
void broadcast_to_clients(sys::state &state, command::payload &c)
int server_process_commands(sys::state &state, network::client_data &client)
void notify_player_joins_discovery(sys::state &state, network::client_data &client)
bool unpause_game(sys::state &state)
void client_send_handshake(sys::state &state)
dcon::mp_player_id create_mp_player(sys::state &state, sys::player_name &name, sys::player_password_raw &password)
void send_savegame(sys::state &state, network::client_data &client, bool hotjoin=false)
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)
void load_player_nations(sys::state &state) noexcept
void kick_player(sys::state &state, client_data &client)
constexpr short default_server_port
void finish(sys::state &state, bool notify_host)
void switch_player(sys::state &state, dcon::nation_id new_n, dcon::nation_id old_n)
int server_process_handshake(sys::state &state, network::client_data &client)
void ban_player(sys::state &state, client_data &client)
void save_host_settings(sys::state &state)
void send_and_receive_commands(sys::state &state)
void init(sys::state &state)
void server_send_handshake(sys::state &state, network::client_data &client)
void remove_player(sys::state &state, sys::player_name name)
void full_reset_after_oos(sys::state &state)
void log_player_nations(sys::state &state)
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)
std::string native_string
#define ENABLE_AUTODISCOVERY
pcp_ctx_t * pcp_init(uint8_t autodiscovery, pcp_socket_vt_t *socket_vt)
pcp_fstate_e pcp_wait(pcp_flow_t *flow, int timeout, int exit_on_partial_res)
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)
void pcp_terminate(pcp_ctx_t *ctx, int close_flows)
sys::player_name player_name
sys::player_password_raw player_password
sys::checksum_key checksum
sys::checksum_key checksum
union command::payload::dtype data
sys::player_name nickname
uint8_t lobby_password[16]
sys::player_password_raw player_password
dcon::nation_id assigned_nation
sys::checksum_key save_checksum
sys::checksum_key scenario_checksum
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
notify_save_loaded_data notify_save_loaded
notify_leaves_data notify_leave
notify_joins_data notify_join
void err_msg(const char *,...)
size_t ZSTD_compressBound(size_t srcSize)
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)