diff options
Diffstat (limited to 'src/engine/server')
| -rw-r--r-- | src/engine/server/es_register.cpp (renamed from src/engine/server/es_register.c) | 83 | ||||
| -rw-r--r-- | src/engine/server/es_server.c | 1380 | ||||
| -rw-r--r-- | src/engine/server/es_server.cpp | 1969 |
3 files changed, 2010 insertions, 1422 deletions
diff --git a/src/engine/server/es_register.c b/src/engine/server/es_register.cpp index b3ac70a6..0eb5dba5 100644 --- a/src/engine/server/es_register.c +++ b/src/engine/server/es_register.cpp @@ -6,7 +6,7 @@ #include <mastersrv/mastersrv.h> -extern NETSERVER *net; +extern CNetServer *m_pNetServer; enum { @@ -29,48 +29,48 @@ static void register_new_state(int state) register_state_start = time_get(); } -static void register_send_fwcheckresponse(NETADDR *addr) +static void register_send_fwcheckresponse(NETADDR *pAddr) { - NETCHUNK packet; - packet.client_id = -1; - packet.address = *addr; - packet.flags = NETSENDFLAG_CONNLESS; - packet.data_size = sizeof(SERVERBROWSE_FWRESPONSE); - packet.data = SERVERBROWSE_FWRESPONSE; - netserver_send(net, &packet); + CNetChunk Packet; + Packet.m_ClientID = -1; + Packet.m_Address = *pAddr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_FWRESPONSE); + Packet.m_pData = SERVERBROWSE_FWRESPONSE; + m_pNetServer->Send(&Packet); } static void register_send_heartbeat(NETADDR addr) { static unsigned char data[sizeof(SERVERBROWSE_HEARTBEAT) + 2]; unsigned short port = config.sv_port; - NETCHUNK packet; + CNetChunk Packet; mem_copy(data, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)); - packet.client_id = -1; - packet.address = addr; - packet.flags = NETSENDFLAG_CONNLESS; - packet.data_size = sizeof(SERVERBROWSE_HEARTBEAT) + 2; - packet.data = &data; + Packet.m_ClientID = -1; + Packet.m_Address = addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_HEARTBEAT) + 2; + Packet.m_pData = &data; /* supply the set port that the master can use if it has problems */ if(config.sv_external_port) port = config.sv_external_port; data[sizeof(SERVERBROWSE_HEARTBEAT)] = port >> 8; data[sizeof(SERVERBROWSE_HEARTBEAT)+1] = port&0xff; - netserver_send(net, &packet); + m_pNetServer->Send(&Packet); } -static void register_send_count_request(NETADDR addr) +static void register_send_count_request(NETADDR Addr) { - NETCHUNK packet; - packet.client_id = -1; - packet.address = addr; - packet.flags = NETSENDFLAG_CONNLESS; - packet.data_size = sizeof(SERVERBROWSE_GETCOUNT); - packet.data = SERVERBROWSE_GETCOUNT; - netserver_send(net, &packet); + CNetChunk Packet; + Packet.m_ClientID = -1; + Packet.m_Address = Addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_GETCOUNT); + Packet.m_pData = SERVERBROWSE_GETCOUNT; + m_pNetServer->Send(&Packet); } typedef struct @@ -221,50 +221,49 @@ void register_update() } } -static void register_got_count(NETCHUNK *p) +static void register_got_count(CNetChunk *pChunk) { - unsigned char *data = (unsigned char *)p->data; - int count = (data[sizeof(SERVERBROWSE_COUNT)]<<8) | data[sizeof(SERVERBROWSE_COUNT)+1]; - int i; + unsigned char *pData = (unsigned char *)pChunk->m_pData; + int Count = (pData[sizeof(SERVERBROWSE_COUNT)]<<8) | pData[sizeof(SERVERBROWSE_COUNT)+1]; - for(i = 0; i < MAX_MASTERSERVERS; i++) + for(int i = 0; i < MAX_MASTERSERVERS; i++) { - if(net_addr_comp(&masterserver_info[i].addr, &p->address) == 0) + if(net_addr_comp(&masterserver_info[i].addr, &pChunk->m_Address) == 0) { - masterserver_info[i].count = count; + masterserver_info[i].count = Count; break; } } } -int register_process_packet(NETCHUNK *packet) +int register_process_packet(CNetChunk *pPacket) { - if(packet->data_size == sizeof(SERVERBROWSE_FWCHECK) && - memcmp(packet->data, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) + if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWCHECK) && + memcmp(pPacket->m_pData, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) { - register_send_fwcheckresponse(&packet->address); + register_send_fwcheckresponse(&pPacket->m_Address); return 1; } - else if(packet->data_size == sizeof(SERVERBROWSE_FWOK) && - memcmp(packet->data, SERVERBROWSE_FWOK, sizeof(SERVERBROWSE_FWOK)) == 0) + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWOK) && + memcmp(pPacket->m_pData, SERVERBROWSE_FWOK, sizeof(SERVERBROWSE_FWOK)) == 0) { if(register_first) dbg_msg("register", "no firewall/nat problems detected"); register_new_state(REGISTERSTATE_REGISTERED); return 1; } - else if(packet->data_size == sizeof(SERVERBROWSE_FWERROR) && - memcmp(packet->data, SERVERBROWSE_FWERROR, sizeof(SERVERBROWSE_FWERROR)) == 0) + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWERROR) && + memcmp(pPacket->m_pData, SERVERBROWSE_FWERROR, sizeof(SERVERBROWSE_FWERROR)) == 0) { dbg_msg("register", "ERROR: the master server reports that clients can not connect to this server."); dbg_msg("register", "ERROR: configure your firewall/nat to let through udp on port %d.", config.sv_port); register_new_state(REGISTERSTATE_ERROR); return 1; } - else if(packet->data_size == sizeof(SERVERBROWSE_COUNT)+2 && - memcmp(packet->data, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_COUNT)+2 && + memcmp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) { - register_got_count(packet); + register_got_count(pPacket); return 1; } diff --git a/src/engine/server/es_server.c b/src/engine/server/es_server.c deleted file mode 100644 index 8763351d..00000000 --- a/src/engine/server/es_server.c +++ /dev/null @@ -1,1380 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include <stdio.h> -#include <string.h> -#include <stdlib.h> - -#include <base/system.h> - -#include <engine/e_config.h> -#include <engine/e_engine.h> -#include <engine/e_server_interface.h> - -#include <engine/e_protocol.h> -#include <engine/e_snapshot.h> - -#include <engine/e_compression.h> - -#include <engine/e_network.h> -#include <engine/e_config.h> -#include <engine/e_packer.h> -#include <engine/e_datafile.h> -#include <engine/e_demorec.h> - -#include <mastersrv/mastersrv.h> - -#if defined(CONF_FAMILY_WINDOWS) - #define _WIN32_WINNT 0x0500 - #include <windows.h> -#endif - -static SNAPBUILD builder; - -static int64 game_start_time; -static int current_tick = 0; -static int run_server = 1; - -static char browseinfo_gametype[16] = {0}; -static int browseinfo_progression = -1; - -static int64 lastheartbeat; -/*static NETADDR4 master_server;*/ - -static char current_map[64]; -static int current_map_crc; -static unsigned char *current_map_data = 0; -static int current_map_size = 0; - -void *snap_new_item(int type, int id, int size) -{ - dbg_assert(type >= 0 && type <=0xffff, "incorrect type"); - dbg_assert(id >= 0 && id <=0xffff, "incorrect id"); - return snapbuild_new_item(&builder, type, id, size); -} - -typedef struct -{ - short next; - short state; /* 0 = free, 1 = alloced, 2 = timed */ - int timeout; -} SNAP_ID; - -static const int MAX_IDS = 16*1024; /* should be lowered */ -static SNAP_ID snap_ids[16*1024]; -static int snap_first_free_id; -static int snap_first_timed_id; -static int snap_last_timed_id; -static int snap_id_usage; -static int snap_id_inusage; -static int snap_id_inited = 0; - -enum -{ - SRVCLIENT_STATE_EMPTY = 0, - SRVCLIENT_STATE_AUTH, - SRVCLIENT_STATE_CONNECTING, - SRVCLIENT_STATE_READY, - SRVCLIENT_STATE_INGAME, - - SRVCLIENT_SNAPRATE_INIT=0, - SRVCLIENT_SNAPRATE_FULL, - SRVCLIENT_SNAPRATE_RECOVER -}; - -typedef struct -{ - int data[MAX_INPUT_SIZE]; - int game_tick; /* the tick that was chosen for the input */ -} CLIENT_INPUT; - -/* */ -typedef struct -{ - /* connection state info */ - int state; - int latency; - int snap_rate; - - int last_acked_snapshot; - int last_input_tick; - SNAPSTORAGE snapshots; - - CLIENT_INPUT latestinput; - CLIENT_INPUT inputs[200]; /* TODO: handle input better */ - int current_input; - - char name[MAX_NAME_LENGTH]; - char clan[MAX_CLANNAME_LENGTH]; - int score; - int authed; -} CLIENT; - -static CLIENT clients[MAX_CLIENTS]; -NETSERVER *net; - -static void snap_init_id() -{ - int i; - if(snap_id_inited) - return; - - for(i = 0; i < MAX_IDS; i++) - { - snap_ids[i].next = i+1; - snap_ids[i].state = 0; - } - - snap_ids[MAX_IDS-1].next = -1; - snap_first_free_id = 0; - snap_first_timed_id = -1; - snap_last_timed_id = -1; - snap_id_usage = 0; - snap_id_inusage = 0; - - snap_id_inited = 1; -} - -static void snap_remove_first_timeout() -{ - int next_timed = snap_ids[snap_first_timed_id].next; - - /* add it to the free list */ - snap_ids[snap_first_timed_id].next = snap_first_free_id; - snap_ids[snap_first_timed_id].state = 0; - snap_first_free_id = snap_first_timed_id; - - /* remove it from the timed list */ - snap_first_timed_id = next_timed; - if(snap_first_timed_id == -1) - snap_last_timed_id = -1; - - snap_id_usage--; -} - -int snap_new_id() -{ - int id; - int64 now = time_get(); - if(!snap_id_inited) - snap_init_id(); - - /* process timed ids */ - while(snap_first_timed_id != -1 && snap_ids[snap_first_timed_id].timeout < now) - snap_remove_first_timeout(); - - id = snap_first_free_id; - dbg_assert(id != -1, "id error"); - snap_first_free_id = snap_ids[snap_first_free_id].next; - snap_ids[id].state = 1; - snap_id_usage++; - snap_id_inusage++; - return id; -} - -void snap_timeout_ids() -{ - /* process timed ids */ - while(snap_first_timed_id != -1) - snap_remove_first_timeout(); -} - -void snap_free_id(int id) -{ - dbg_assert(snap_ids[id].state == 1, "id is not alloced"); - - snap_id_inusage--; - snap_ids[id].state = 2; - snap_ids[id].timeout = time_get()+time_freq()*5; - snap_ids[id].next = -1; - - if(snap_last_timed_id != -1) - { - snap_ids[snap_last_timed_id].next = id; - snap_last_timed_id = id; - } - else - { - snap_first_timed_id = id; - snap_last_timed_id = id; - } -} - -int *server_latestinput(int client_id, int *size) -{ - if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) - return 0; - return clients[client_id].latestinput.data; -} - -const char *server_clientname(int client_id) -{ - if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) - return "(invalid client)"; - return clients[client_id].name; -} - -static const char *str_ltrim(const char *str) -{ - while(*str && *str <= 32) - str++; - return str; -} - -static void str_rtrim(char *str) -{ - int i = str_length(str); - while(i >= 0) - { - if(str[i] > 32) - break; - str[i] = 0; - i--; - } -} - -static int server_try_setclientname(int client_id, const char *name) -{ - int i; - char trimmed_name[64]; - - /* trim the name */ - str_copy(trimmed_name, str_ltrim(name), sizeof(trimmed_name)); - str_rtrim(trimmed_name); - dbg_msg("", "'%s' -> '%s'", name, trimmed_name); - name = trimmed_name; - - - /* check for empty names */ - if(!name[0]) - return -1; - - /* make sure that two clients doesn't have the same name */ - for(i = 0; i < MAX_CLIENTS; i++) - if(i != client_id && clients[i].state >= SRVCLIENT_STATE_READY) - { - if(strcmp(name, clients[i].name) == 0) - return -1; - } - - /* set the client name */ - str_copy(clients[client_id].name, name, MAX_NAME_LENGTH); - return 0; -} - -void server_setclientname(int client_id, const char *name) -{ - char nametry[MAX_NAME_LENGTH]; - int i; - if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) - return; - - if(!name) - return; - - str_copy(nametry, name, MAX_NAME_LENGTH); - if(server_try_setclientname(client_id, nametry)) - { - /* auto rename */ - for(i = 1;; i++) - { - str_format(nametry, MAX_NAME_LENGTH, "(%d)%s", i, name); - if(server_try_setclientname(client_id, nametry) == 0) - break; - } - } -} - -void server_setclientscore(int client_id, int score) -{ - if(client_id < 0 || client_id > MAX_CLIENTS || clients[client_id].state < SRVCLIENT_STATE_READY) - return; - clients[client_id].score = score; -} - -void server_setbrowseinfo(const char *game_type, int progression) -{ - str_copy(browseinfo_gametype, game_type, sizeof(browseinfo_gametype)); - browseinfo_progression = progression; - if(browseinfo_progression > 100) - browseinfo_progression = 100; - if(browseinfo_progression < -1) - browseinfo_progression = -1; -} - -void server_kick(int client_id, const char *reason) -{ - if(client_id < 0 || client_id > MAX_CLIENTS) - return; - - if(clients[client_id].state != SRVCLIENT_STATE_EMPTY) - netserver_drop(net, client_id, reason); -} - -int server_tick() -{ - return current_tick; -} - -int64 server_tick_start_time(int tick) -{ - return game_start_time + (time_freq()*tick)/SERVER_TICK_SPEED; -} - -int server_tickspeed() -{ - return SERVER_TICK_SPEED; -} - -int server_init() -{ - int i; - for(i = 0; i < MAX_CLIENTS; i++) - { - clients[i].state = SRVCLIENT_STATE_EMPTY; - clients[i].name[0] = 0; - clients[i].clan[0] = 0; - snapstorage_init(&clients[i].snapshots); - } - - current_tick = 0; - - return 0; -} - -int server_getclientinfo(int client_id, CLIENT_INFO *info) -{ - dbg_assert(client_id >= 0 && client_id < MAX_CLIENTS, "client_id is not valid"); - dbg_assert(info != 0, "info can not be null"); - - if(clients[client_id].state == SRVCLIENT_STATE_INGAME) - { - info->name = clients[client_id].name; - info->latency = clients[client_id].latency; - return 1; - } - return 0; -} - -int server_send_msg(int client_id) -{ - const MSG_INFO *info = msg_get_info(); - NETCHUNK packet; - if(!info) - return -1; - - mem_zero(&packet, sizeof(NETCHUNK)); - - packet.client_id = client_id; - packet.data = info->data; - packet.data_size = info->size; - - if(info->flags&MSGFLAG_VITAL) - packet.flags |= NETSENDFLAG_VITAL; - if(info->flags&MSGFLAG_FLUSH) - packet.flags |= NETSENDFLAG_FLUSH; - - /* write message to demo recorder */ - if(!(info->flags&MSGFLAG_NORECORD)) - demorec_record_message(info->data, info->size); - - if(!(info->flags&MSGFLAG_NOSEND)) - { - if(client_id == -1) - { - /* broadcast */ - int i; - for(i = 0; i < MAX_CLIENTS; i++) - if(clients[i].state == SRVCLIENT_STATE_INGAME) - { - packet.client_id = i; - netserver_send(net, &packet); - } - } - else - netserver_send(net, &packet); - } - return 0; -} - -static void server_do_snap() -{ - int i; - - { - static PERFORMACE_INFO scope = {"presnap", 0}; - perf_start(&scope); - mods_presnap(); - perf_end(); - } - - /* create snapshot for demo recording */ - if(demorec_isrecording()) - { - char data[MAX_SNAPSHOT_SIZE]; - int snapshot_size; - - /* build snap and possibly add some messages */ - snapbuild_init(&builder); - mods_snap(-1); - snapshot_size = snapbuild_finish(&builder, data); - - /* write snapshot */ - demorec_record_snapshot(server_tick(), data, snapshot_size); - } - - /* create snapshots for all clients */ - for(i = 0; i < MAX_CLIENTS; i++) - { - /* client must be ingame to recive snapshots */ - if(clients[i].state != SRVCLIENT_STATE_INGAME) - continue; - - /* this client is trying to recover, don't spam snapshots */ - if(clients[i].snap_rate == SRVCLIENT_SNAPRATE_RECOVER && (server_tick()%50) != 0) - continue; - - /* this client is trying to recover, don't spam snapshots */ - if(clients[i].snap_rate == SRVCLIENT_SNAPRATE_INIT && (server_tick()%10) != 0) - continue; - - { - char data[MAX_SNAPSHOT_SIZE]; - char deltadata[MAX_SNAPSHOT_SIZE]; - char compdata[MAX_SNAPSHOT_SIZE]; - int snapshot_size; - int crc; - static SNAPSHOT emptysnap; - SNAPSHOT *deltashot = &emptysnap; - int deltashot_size; - int delta_tick = -1; - int deltasize; - static PERFORMACE_INFO scope = {"build", 0}; - perf_start(&scope); - - snapbuild_init(&builder); - - { - static PERFORMACE_INFO scope = {"modsnap", 0}; - perf_start(&scope); - mods_snap(i); - perf_end(); - } - - /* finish snapshot */ - snapshot_size = snapbuild_finish(&builder, data); - crc = snapshot_crc((SNAPSHOT*)data); - - /* remove old snapshos */ - /* keep 3 seconds worth of snapshots */ - snapstorage_purge_until(&clients[i].snapshots, current_tick-SERVER_TICK_SPEED*3); - - /* save it the snapshot */ - snapstorage_add(&clients[i].snapshots, current_tick, time_get(), snapshot_size, data, 0); - - /* find snapshot that we can preform delta against */ - emptysnap.data_size = 0; - emptysnap.num_items = 0; - - { - deltashot_size = snapstorage_get(&clients[i].snapshots, clients[i].last_acked_snapshot, 0, &deltashot, 0); - if(deltashot_size >= 0) - delta_tick = clients[i].last_acked_snapshot; - else - { - /* no acked package found, force client to recover rate */ - if(clients[i].snap_rate == SRVCLIENT_SNAPRATE_FULL) - clients[i].snap_rate = SRVCLIENT_SNAPRATE_RECOVER; - } - } - - /* create delta */ - { - static PERFORMACE_INFO scope = {"delta", 0}; - perf_start(&scope); - deltasize = snapshot_create_delta(deltashot, (SNAPSHOT*)data, deltadata); - perf_end(); - } - - - if(deltasize) - { - /* compress it */ - int snapshot_size; - const int max_size = MAX_SNAPSHOT_PACKSIZE; - int numpackets; - int n, left; - - { - static PERFORMACE_INFO scope = {"compress", 0}; - /*char buffer[512];*/ - perf_start(&scope); - snapshot_size = intpack_compress(deltadata, deltasize, compdata); - - /*str_hex(buffer, sizeof(buffer), compdata, snapshot_size); - dbg_msg("", "deltasize=%d -> %d : %s", deltasize, snapshot_size, buffer);*/ - - perf_end(); - } - - numpackets = (snapshot_size+max_size-1)/max_size; - - for(n = 0, left = snapshot_size; left; n++) - { - int chunk = left < max_size ? left : max_size; - left -= chunk; - - if(numpackets == 1) - msg_pack_start_system(NETMSG_SNAPSINGLE, MSGFLAG_FLUSH); - else - msg_pack_start_system(NETMSG_SNAP, MSGFLAG_FLUSH); - - msg_pack_int(current_tick); - msg_pack_int(current_tick-delta_tick); /* compressed with */ - - if(numpackets != 1) - { - msg_pack_int(numpackets); - msg_pack_int(n); - } - - msg_pack_int(crc); - msg_pack_int(chunk); - msg_pack_raw(&compdata[n*max_size], chunk); - msg_pack_end(); - server_send_msg(i); - } - } - else - { - msg_pack_start_system(NETMSG_SNAPEMPTY, MSGFLAG_FLUSH); - msg_pack_int(current_tick); - msg_pack_int(current_tick-delta_tick); /* compressed with */ - msg_pack_end(); - server_send_msg(i); - } - - perf_end(); - } - } - - mods_postsnap(); -} - - -static void reset_client(int cid) -{ - /* reset input */ - int i; - for(i = 0; i < 200; i++) - clients[cid].inputs[i].game_tick = -1; - clients[cid].current_input = 0; - mem_zero(&clients[cid].latestinput, sizeof(clients[cid].latestinput)); - - snapstorage_purge_all(&clients[cid].snapshots); - clients[cid].last_acked_snapshot = -1; - clients[cid].last_input_tick = -1; - clients[cid].snap_rate = SRVCLIENT_SNAPRATE_INIT; - clients[cid].score = 0; - -} - -static int new_client_callback(int cid, void *user) -{ - clients[cid].state = SRVCLIENT_STATE_AUTH; - clients[cid].name[0] = 0; - clients[cid].clan[0] = 0; - clients[cid].authed = 0; - reset_client(cid); - return 0; -} - -static int del_client_callback(int cid, void *user) -{ - /* notify the mod about the drop */ - if(clients[cid].state >= SRVCLIENT_STATE_READY) - mods_client_drop(cid); - - clients[cid].state = SRVCLIENT_STATE_EMPTY; - clients[cid].name[0] = 0; - clients[cid].clan[0] = 0; - clients[cid].authed = 0; - snapstorage_purge_all(&clients[cid].snapshots); - return 0; -} - -static void server_send_map(int cid) -{ - msg_pack_start_system(NETMSG_MAP_CHANGE, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_string(config.sv_map, 0); - msg_pack_int(current_map_crc); - msg_pack_end(); - server_send_msg(cid); -} - -static void server_send_rcon_line(int cid, const char *line) -{ - msg_pack_start_system(NETMSG_RCON_LINE, MSGFLAG_VITAL); - msg_pack_string(line, 512); - msg_pack_end(); - server_send_msg(cid); -} - -static void server_send_rcon_line_authed(const char *line, void *user_data) -{ - static volatile int reentry_guard = 0; - int i; - - if(reentry_guard) return; - reentry_guard++; - - for(i = 0; i < MAX_CLIENTS; i++) - { - if(clients[i].state != SRVCLIENT_STATE_EMPTY && clients[i].authed) - server_send_rcon_line(i, line); - } - - reentry_guard--; -} - -static void server_process_client_packet(NETCHUNK *packet) -{ - int cid = packet->client_id; - NETADDR addr; - - int sys; - int msg = msg_unpack_start(packet->data, packet->data_size, &sys); - - if(clients[cid].state == SRVCLIENT_STATE_AUTH) - { - if(sys && msg == NETMSG_INFO) - { - char version[64]; - const char *password; - str_copy(version, msg_unpack_string(), 64); - if(strcmp(version, mods_net_version()) != 0) - { - /* OH FUCK! wrong version, drop him */ - char reason[256]; - str_format(reason, sizeof(reason), "wrong version. server is running '%s' and client '%s'.", mods_net_version(), version); - netserver_drop(net, cid, reason); - return; - } - - str_copy(clients[cid].name, msg_unpack_string(), MAX_NAME_LENGTH); - str_copy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH); - password = msg_unpack_string(); - - if(config.password[0] != 0 && strcmp(config.password, password) != 0) - { - /* wrong password */ - netserver_drop(net, cid, "wrong password"); - return; - } - - clients[cid].state = SRVCLIENT_STATE_CONNECTING; - server_send_map(cid); - } - } - else - { - if(sys) - { - /* system message */ - if(msg == NETMSG_REQUEST_MAP_DATA) - { - int chunk = msg_unpack_int(); - int chunk_size = 1024-128; - int offset = chunk * chunk_size; - int last = 0; - - /* drop faulty map data requests */ - if(chunk < 0 || offset > current_map_size) - return; - - if(offset+chunk_size >= current_map_size) - { - chunk_size = current_map_size-offset; - if(chunk_size < 0) - chunk_size = 0; - last = 1; - } - - msg_pack_start_system(NETMSG_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); - msg_pack_int(last); - msg_pack_int(current_map_size); - msg_pack_int(chunk_size); - msg_pack_raw(¤t_map_data[offset], chunk_size); - msg_pack_end(); - server_send_msg(cid); - - if(config.debug) - dbg_msg("server", "sending chunk %d with size %d", chunk, chunk_size); - } - else if(msg == NETMSG_READY) - { - if(clients[cid].state == SRVCLIENT_STATE_CONNECTING) - { - netserver_client_addr(net, cid, &addr); - - dbg_msg("server", "player is ready. cid=%x ip=%d.%d.%d.%d", - cid, - addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3] - ); - clients[cid].state = SRVCLIENT_STATE_READY; - mods_connected(cid); - } - } - else if(msg == NETMSG_ENTERGAME) - { - if(clients[cid].state == SRVCLIENT_STATE_READY) - { - netserver_client_addr(net, cid, &addr); - - dbg_msg("server", "player has entered the game. cid=%x ip=%d.%d.%d.%d", - cid, - addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3] - ); - clients[cid].state = SRVCLIENT_STATE_INGAME; - mods_client_enter(cid); - } - } - else if(msg == NETMSG_INPUT) - { - int tick, size, i; - CLIENT_INPUT *input; - int64 tagtime; - - clients[cid].last_acked_snapshot = msg_unpack_int(); - tick = msg_unpack_int(); - size = msg_unpack_int(); - - /* check for errors */ - if(msg_unpack_error() || size/4 > MAX_INPUT_SIZE) - return; - - if(clients[cid].last_acked_snapshot > 0) - clients[cid].snap_rate = SRVCLIENT_SNAPRATE_FULL; - - if(snapstorage_get(&clients[cid].snapshots, clients[cid].last_acked_snapshot, &tagtime, 0, 0) >= 0) - clients[cid].latency = (int)(((time_get()-tagtime)*1000)/time_freq()); - - /* add message to report the input timing */ - /* skip packets that are old */ - if(tick > clients[cid].last_input_tick) - { - int time_left = ((server_tick_start_time(tick)-time_get())*1000) / time_freq(); - msg_pack_start_system(NETMSG_INPUTTIMING, 0); - msg_pack_int(tick); - msg_pack_int(time_left); - msg_pack_end(); - server_send_msg(cid); - } - - clients[cid].last_input_tick = tick; - - input = &clients[cid].inputs[clients[cid].current_input]; - - if(tick <= server_tick()) - tick = server_tick()+1; - - input->game_tick = tick; - - for(i = 0; i < size/4; i++) - input->data[i] = msg_unpack_int(); - - mem_copy(clients[cid].latestinput.data, input->data, MAX_INPUT_SIZE*sizeof(int)); - - clients[cid].current_input++; - clients[cid].current_input %= 200; - - /* call the mod with the fresh input data */ - if(clients[cid].state == SRVCLIENT_STATE_INGAME) - mods_client_direct_input(cid, clients[cid].latestinput.data); - } - else if(msg == NETMSG_RCON_CMD) - { - const char *cmd = msg_unpack_string(); - - if(msg_unpack_error() == 0 && clients[cid].authed) - { - dbg_msg("server", "cid=%d rcon='%s'", cid, cmd); - console_execute_line(cmd); - } - } - else if(msg == NETMSG_RCON_AUTH) - { - const char *pw; - msg_unpack_string(); /* login name, not used */ - pw = msg_unpack_string(); - - if(msg_unpack_error() == 0) - { - if(config.sv_rcon_password[0] == 0) - { - server_send_rcon_line(cid, "No rcon password set on server. Set sv_rcon_password to enable the remote console."); - } - else if(strcmp(pw, config.sv_rcon_password) == 0) - { - msg_pack_start_system(NETMSG_RCON_AUTH_STATUS, MSGFLAG_VITAL); - msg_pack_int(1); - msg_pack_end(); - server_send_msg(cid); - - clients[cid].authed = 1; - server_send_rcon_line(cid, "Authentication successful. Remote console access granted."); - dbg_msg("server", "cid=%d authed", cid); - } - else - { - server_send_rcon_line(cid, "Wrong password."); - } - } - } - else if(msg == NETMSG_PING) - { - msg_pack_start_system(NETMSG_PING_REPLY, 0); - msg_pack_end(); - server_send_msg(cid); - } - else - { - char hex[] = "0123456789ABCDEF"; - char buf[512]; - int b; - - for(b = 0; b < packet->data_size && b < 32; b++) - { - buf[b*3] = hex[((const unsigned char *)packet->data)[b]>>4]; - buf[b*3+1] = hex[((const unsigned char *)packet->data)[b]&0xf]; - buf[b*3+2] = ' '; - buf[b*3+3] = 0; - } - - dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, packet->data_size); - dbg_msg("server", "%s", buf); - - } - } - else - { - /* game message */ - if(clients[cid].state >= SRVCLIENT_STATE_READY) - mods_message(msg, cid); - } - } -} - - -int server_ban_add(NETADDR addr, int seconds) -{ - return netserver_ban_add(net, addr, seconds); -} - -int server_ban_remove(NETADDR addr) -{ - return netserver_ban_remove(net, addr); -} - -static void server_send_serverinfo(NETADDR *addr, int token) -{ - NETCHUNK packet; - PACKER p; - char buf[128]; - - /* count the players */ - int player_count = 0; - int i; - for(i = 0; i < MAX_CLIENTS; i++) - { - if(clients[i].state != SRVCLIENT_STATE_EMPTY) - player_count++; - } - - packer_reset(&p); - - if(token >= 0) - { - /* new token based format */ - packer_add_raw(&p, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); - str_format(buf, sizeof(buf), "%d", token); - packer_add_string(&p, buf, 6); - } - else - { - /* old format */ - packer_add_raw(&p, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)); - } - - packer_add_string(&p, mods_version(), 32); - packer_add_string(&p, config.sv_name, 64); - packer_add_string(&p, config.sv_map, 32); - - /* gametype */ - packer_add_string(&p, browseinfo_gametype, 16); - - /* flags */ - i = 0; - if(config.password[0]) /* password set */ - i |= SRVFLAG_PASSWORD; - str_format(buf, sizeof(buf), "%d", i); - packer_add_string(&p, buf, 2); - - /* progression */ - str_format(buf, sizeof(buf), "%d", browseinfo_progression); - packer_add_string(&p, buf, 4); - - str_format(buf, sizeof(buf), "%d", player_count); packer_add_string(&p, buf, 3); /* num players */ - str_format(buf, sizeof(buf), "%d", netserver_max_clients(net)); packer_add_string(&p, buf, 3); /* max players */ - - for(i = 0; i < MAX_CLIENTS; i++) - { - if(clients[i].state != SRVCLIENT_STATE_EMPTY) - { - packer_add_string(&p, clients[i].name, 48); /* player name */ - str_format(buf, sizeof(buf), "%d", clients[i].score); packer_add_string(&p, buf, 6); /* player score */ - } - } - - - packet.client_id = -1; - packet.address = *addr; - packet.flags = NETSENDFLAG_CONNLESS; - packet.data_size = packer_size(&p); - packet.data = packer_data(&p); - netserver_send(net, &packet); -} - -extern int register_process_packet(NETCHUNK *packet); -extern int register_update(); - -static void server_pump_network() -{ - NETCHUNK packet; - - netserver_update(net); - - /* process packets */ - while(netserver_recv(net, &packet)) - { - if(packet.client_id == -1) - { - /* stateless */ - if(!register_process_packet(&packet)) - { - if(packet.data_size == sizeof(SERVERBROWSE_GETINFO)+1 && - memcmp(packet.data, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) - { - server_send_serverinfo(&packet.address, ((unsigned char *)packet.data)[sizeof(SERVERBROWSE_GETINFO)]); - } - - - if(packet.data_size == sizeof(SERVERBROWSE_OLD_GETINFO) && - memcmp(packet.data, SERVERBROWSE_OLD_GETINFO, sizeof(SERVERBROWSE_OLD_GETINFO)) == 0) - { - server_send_serverinfo(&packet.address, -1); - } - } - } - else - server_process_client_packet(&packet); - } -} - -static int server_load_map(const char *mapname) -{ - DATAFILE *df; - char buf[512]; - str_format(buf, sizeof(buf), "maps/%s.map", mapname); - df = datafile_load(buf); - if(!df) - return 0; - - /* stop recording when we change map */ - demorec_record_stop(); - - /* reinit snapshot ids */ - snap_timeout_ids(); - - /* get the crc of the map */ - current_map_crc = datafile_crc(buf); - dbg_msg("server", "%s crc is %08x", buf, current_map_crc); - - str_copy(current_map, mapname, sizeof(current_map)); - map_set(df); - - /* load compelate map into memory for download */ - { - IOHANDLE file = engine_openfile(buf, IOFLAG_READ); - current_map_size = (int)io_length(file); - if(current_map_data) - mem_free(current_map_data); - current_map_data = (unsigned char *)mem_alloc(current_map_size, 1); - io_read(file, current_map_data, current_map_size); - io_close(file); - } - return 1; -} - -static int server_run() -{ - NETADDR bindaddr; - - snap_init_id(); - net_init(); - - /* */ - console_register_print_callback(server_send_rcon_line_authed, 0); - - /* load map */ - if(!server_load_map(config.sv_map)) - { - dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); - return -1; - } - - /* start server */ - /* TODO: IPv6 support */ - if(config.sv_bindaddr[0] && net_host_lookup(config.sv_bindaddr, &bindaddr, NETTYPE_IPV4) == 0) - { - /* sweet! */ - bindaddr.port = config.sv_port; - } - else - { - mem_zero(&bindaddr, sizeof(bindaddr)); - bindaddr.port = config.sv_port; - } - - net = netserver_open(bindaddr, config.sv_max_clients, 0); - if(!net) - { - dbg_msg("server", "couldn't open socket. port might already be in use"); - return -1; - } - - netserver_set_callbacks(net, new_client_callback, del_client_callback, 0); - - dbg_msg("server", "server name is '%s'", config.sv_name); - - mods_init(); - dbg_msg("server", "version %s", mods_net_version()); - - /* start game */ - { - int64 reporttime = time_get(); - int reportinterval = 3; - - lastheartbeat = 0; - game_start_time = time_get(); - - if(config.debug) - dbg_msg("server", "baseline memory usage %dk", mem_stats()->allocated/1024); - - while(run_server) - { - static PERFORMACE_INFO rootscope = {"root", 0}; - int64 t = time_get(); - int new_ticks = 0; - - perf_start(&rootscope); - - /* load new map TODO: don't poll this */ - if(strcmp(config.sv_map, current_map) != 0 || config.sv_map_reload) - { - config.sv_map_reload = 0; - - /* load map */ - if(server_load_map(config.sv_map)) - { - int c; - - /* new map loaded */ - mods_shutdown(); - - for(c = 0; c < MAX_CLIENTS; c++) - { - if(clients[c].state == SRVCLIENT_STATE_EMPTY) - continue; - - server_send_map(c); - reset_client(c); - clients[c].state = SRVCLIENT_STATE_CONNECTING; - } - - game_start_time = time_get(); - current_tick = 0; - mods_init(); - } - else - { - dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); - config_set_sv_map(&config, current_map); - } - } - - while(t > server_tick_start_time(current_tick+1)) - { - current_tick++; - new_ticks++; - - /* apply new input */ - { - static PERFORMACE_INFO scope = {"input", 0}; - int c, i; - - perf_start(&scope); - - for(c = 0; c < MAX_CLIENTS; c++) - { - if(clients[c].state == SRVCLIENT_STATE_EMPTY) - continue; - for(i = 0; i < 200; i++) - { - if(clients[c].inputs[i].game_tick == server_tick()) - { - if(clients[c].state == SRVCLIENT_STATE_INGAME) - mods_client_predicted_input(c, clients[c].inputs[i].data); - break; - } - } - } - - perf_end(); - } - - /* progress game */ - { - static PERFORMACE_INFO scope = {"tick", 0}; - perf_start(&scope); - mods_tick(); - perf_end(); - } - } - - /* snap game */ - if(new_ticks) - { - if(config.sv_high_bandwidth || (current_tick%2) == 0) - { - static PERFORMACE_INFO scope = {"snap", 0}; - perf_start(&scope); - server_do_snap(); - perf_end(); - } - } - - /* master server stuff */ - register_update(); - - - { - static PERFORMACE_INFO scope = {"net", 0}; - perf_start(&scope); - server_pump_network(); - perf_end(); - } - - perf_end(); - - if(reporttime < time_get()) - { - if(config.debug) - { - /* - static NETSTATS prev_stats; - NETSTATS stats; - netserver_stats(net, &stats); - - perf_next(); - - if(config.dbg_pref) - perf_dump(&rootscope); - - dbg_msg("server", "send=%8d recv=%8d", - (stats.send_bytes - prev_stats.send_bytes)/reportinterval, - (stats.recv_bytes - prev_stats.recv_bytes)/reportinterval); - - prev_stats = stats; - */ - } - - reporttime += time_freq()*reportinterval; - } - - /* wait for incomming data */ - net_socket_read_wait(netserver_socket(net), 5); - } - } - - mods_shutdown(); - map_unload(); - - if(current_map_data) - mem_free(current_map_data); - return 0; -} - -static void con_kick(void *result, void *user_data) -{ - server_kick(console_arg_int(result, 0), "kicked by console"); -} - -static int str_allnum(const char *str) -{ - while(*str) - { - if(!(*str >= '0' && *str <= '9')) - return 0; - str++; - } - return 1; -} - -static void con_ban(void *result, void *user_data) -{ - NETADDR addr; - char addrstr[128]; - const char *str = console_arg_string(result, 0); - int minutes = 30; - - if(console_arg_num(result) > 1) - minutes = console_arg_int(result, 1); - - if(net_addr_from_str(&addr, str) == 0) - server_ban_add(addr, minutes*60); - else if(str_allnum(str)) - { - NETADDR addr; - int cid = atoi(str); - - if(cid < 0 || cid > MAX_CLIENTS || clients[cid].state == SRVCLIENT_STATE_EMPTY) - { - dbg_msg("server", "invalid client id"); - return; - } - - netserver_client_addr(net, cid, &addr); - server_ban_add(addr, minutes*60); - } - - addr.port = 0; - net_addr_str(&addr, addrstr, sizeof(addrstr)); - - if(minutes) - dbg_msg("server", "banned %s for %d minutes", addrstr, minutes); - else - dbg_msg("server", "banned %s for life", addrstr); -} - -static void con_unban(void *result, void *user_data) -{ - NETADDR addr; - const char *str = console_arg_string(result, 0); - - if(net_addr_from_str(&addr, str) == 0) - server_ban_remove(addr); - else - dbg_msg("server", "invalid network address"); -} - -static void con_bans(void *result, void *user_data) -{ - int i; - unsigned now = time_timestamp(); - NETBANINFO info; - NETADDR addr; - int num = netserver_ban_num(net); - for(i = 0; i < num; i++) - { - unsigned t; - netserver_ban_get(net, i, &info); - addr = info.addr; - - if(info.expires == 0xffffffff) - { - dbg_msg("server", "#%d %d.%d.%d.%d for life", i, addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3]); - } - else - { - t = info.expires - now; - dbg_msg("server", "#%d %d.%d.%d.%d for %d minutes and %d seconds", i, addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], t/60, t%60); - } - } - dbg_msg("server", "%d ban(s)", num); -} - -static void con_status(void *result, void *user_data) -{ - int i; - NETADDR addr; - for(i = 0; i < MAX_CLIENTS; i++) - { - if(clients[i].state == SRVCLIENT_STATE_INGAME) - { - netserver_client_addr(net, i, &addr); - dbg_msg("server", "id=%d addr=%d.%d.%d.%d:%d name='%s' score=%d", - i, addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port, - clients[i].name, clients[i].score); - } - } -} - -static void con_shutdown(void *result, void *user_data) -{ - run_server = 0; -} - -static void con_record(void *result, void *user_data) -{ - char filename[512]; - str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(result, 0)); - demorec_record_start(filename, mods_net_version(), current_map, current_map_crc, "server"); -} - -static void con_stoprecord(void *result, void *user_data) -{ - demorec_record_stop(); -} - -static void server_register_commands() -{ - MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, con_kick, 0, ""); - MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, con_ban, 0, ""); - MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, con_unban, 0, ""); - MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, con_bans, 0, ""); - MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, con_status, 0, ""); - MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, con_shutdown, 0, ""); - - MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_SERVER, con_record, 0, ""); - MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, con_stoprecord, 0, ""); -} - -int main(int argc, char **argv) -{ -#if defined(CONF_FAMILY_WINDOWS) - int i; - for(i = 1; i < argc; i++) - { - if(strcmp("-s", argv[i]) == 0 || strcmp("--silent", argv[i]) == 0) - { - ShowWindow(GetConsoleWindow(), SW_HIDE); - break; - } - } -#endif - - /* init the engine */ - dbg_msg("server", "starting..."); - engine_init("Teeworlds"); - - /* register all console commands */ - server_register_commands(); - mods_console_init(); - - /* parse the command line arguments */ - engine_parse_arguments(argc, argv); - - /* run the server */ - server_run(); - return 0; -} - diff --git a/src/engine/server/es_server.cpp b/src/engine/server/es_server.cpp new file mode 100644 index 00000000..cc0e6e0f --- /dev/null +++ b/src/engine/server/es_server.cpp @@ -0,0 +1,1969 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <base/system.h> + +#include <engine/e_config.h> +#include <engine/e_engine.h> +#include <engine/e_server_interface.h> + +#include <engine/e_protocol.h> +#include <engine/e_snapshot.h> + +#include <engine/e_compression.h> + +#include <engine/e_network.h> +#include <engine/e_config.h> +#include <engine/e_packer.h> +#include <engine/e_datafile.h> +#include <engine/e_demorec.h> + +#include <mastersrv/mastersrv.h> + +#if defined(CONF_FAMILY_WINDOWS) + #define _WIN32_WINNT 0x0500 + #include <windows.h> +#endif + + +extern int register_process_packet(CNetChunk *pPacket); +extern int register_update(); + +static const char *str_ltrim(const char *str) +{ + while(*str && *str <= 32) + str++; + return str; +} + +static void str_rtrim(char *str) +{ + int i = str_length(str); + while(i >= 0) + { + if(str[i] > 32) + break; + str[i] = 0; + i--; + } +} + + +class CSnapIDPool +{ + enum + { + MAX_IDS = 16*1024, + }; + + class CID + { + public: + short m_Next; + short m_State; /* 0 = free, 1 = alloced, 2 = timed */ + int m_Timeout; + }; + + CID m_aIDs[MAX_IDS]; + + int m_FirstFree; + int m_FirstTimed; + int m_LastTimed; + int m_Usage; + int m_InUsage; + +public: + + CSnapIDPool() + { + Reset(); + } + + void Reset() + { + for(int i = 0; i < MAX_IDS; i++) + { + m_aIDs[i].m_Next = i+1; + m_aIDs[i].m_State = 0; + } + + m_aIDs[MAX_IDS-1].m_Next = -1; + m_FirstFree = 0; + m_FirstTimed = -1; + m_LastTimed = -1; + m_Usage = 0; + m_InUsage = 0; + } + + + void RemoveFirstTimeout() + { + int NextTimed = m_aIDs[m_FirstTimed].m_Next; + + /* add it to the free list */ + m_aIDs[m_FirstTimed].m_Next = m_FirstFree; + m_aIDs[m_FirstTimed].m_State = 0; + m_FirstFree = m_FirstTimed; + + /* remove it from the timed list */ + m_FirstTimed = NextTimed; + if(m_FirstTimed == -1) + m_LastTimed = -1; + + m_Usage--; + } + + int NewID() + { + int64 now = time_get(); + + /* process timed ids */ + while(m_FirstTimed != -1 && m_aIDs[m_FirstTimed].m_Timeout < now) + RemoveFirstTimeout(); + + int id = m_FirstFree; + dbg_assert(id != -1, "id error"); + m_FirstFree = m_aIDs[m_FirstFree].m_Next; + m_aIDs[id].m_State = 1; + m_Usage++; + m_InUsage++; + return id; + } + + void TimeoutIDs() + { + /* process timed ids */ + while(m_FirstTimed != -1) + RemoveFirstTimeout(); + } + + void FreeID(int id) + { + dbg_assert(m_aIDs[id].m_State == 1, "id is not alloced"); + + m_InUsage--; + m_aIDs[id].m_State = 2; + m_aIDs[id].m_Timeout = time_get()+time_freq()*5; + m_aIDs[id].m_Next = -1; + + if(m_LastTimed != -1) + { + m_aIDs[m_LastTimed].m_Next = id; + m_LastTimed = id; + } + else + { + m_FirstTimed = id; + m_LastTimed = id; + } + } +}; + +#if 0 +class IGameServer +{ +public: + /* + Function: mods_console_init + TODO + */ + virtual void ConsoleInit(); + + /* + Function: Init + Called when the server is started. + + Remarks: + It's called after the map is loaded so all map items are available. + */ + void Init() = 0; + + /* + Function: Shutdown + Called when the server quits. + + Remarks: + Should be used to clean up all resources used. + */ + void Shutdown() = 0; + + /* + Function: ClientEnter + Called when a client has joined the game. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS. + + Remarks: + It's called when the client is finished loading and should enter gameplay. + */ + void ClientEnter(int cid) = 0; + + /* + Function: ClientDrop + Called when a client drops from the server. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS + */ + void ClientDrop(int cid) = 0; + + /* + Function: ClientDirectInput + Called when the server recives new input from a client. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS. + input - Pointer to the input data. + size - Size of the data. (NOT IMPLEMENTED YET) + */ + void ClientDirectInput(int cid, void *input) = 0; + + /* + Function: ClientPredictedInput + Called when the server applys the predicted input on the client. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS. + input - Pointer to the input data. + size - Size of the data. (NOT IMPLEMENTED YET) + */ + void ClientPredictedInput(int cid, void *input) = 0; + + + /* + Function: Tick + Called with a regular interval to progress the gameplay. + + Remarks: + The SERVER_TICK_SPEED tells the number of ticks per second. + */ + void Tick() = 0; + + /* + Function: Presnap + Called before the server starts to construct snapshots for the clients. + */ + void Presnap() = 0; + + /* + Function: Snap + Called to create the snapshot for a client. + + Arguments: + cid - Client ID. Is 0 - MAX_CLIENTS. + + Remarks: + The game should make a series of calls to <snap_new_item> to construct + the snapshot for the client. + */ + void Snap(int cid) = 0; + + /* + Function: PostSnap + Called after the server is done sending the snapshots. + */ + void PostSnap() = 0; + + /* + Function: ClientConnected + TODO + + Arguments: + arg1 - desc + + Returns: + + See Also: + <other_func> + */ + void ClientConnected(int client_id) = 0; + + + /* + Function: NetVersion + TODO + + Arguments: + arg1 - desc + + Returns: + + See Also: + <other_func> + */ + const char *NetVersion() = 0; + + /* + Function: Version + TODO + + Arguments: + arg1 - desc + + Returns: + + See Also: + <other_func> + */ + const char *Version() = 0; + + /* + Function: Message + TODO + + Arguments: + arg1 - desc + + Returns: + + See Also: + <other_func> + */ + void Message(int msg, int client_id) = 0; +}; + +#endif +/* +class IServer +{ +public: + BanAdd + BanRemove + TickSpeed + Tick + Kick + SetBrowseInfo + SetClientScore + SetClientName + GetClientInfo + LatestInput + ClientName + + SendMessage() + + Map + + virtual int NewSnapID() = 0; + virtual int FreeSnapID(int i) = 0; +};*/ + +class CServer +{ +public: + /* */ + class CClient + { + public: + + enum + { + STATE_EMPTY = 0, + STATE_AUTH, + STATE_CONNECTING, + STATE_READY, + STATE_INGAME, + + SNAPRATE_INIT=0, + SNAPRATE_FULL, + SNAPRATE_RECOVER + }; + + class CInput + { + public: + int m_aData[MAX_INPUT_SIZE]; + int m_GameTick; /* the tick that was chosen for the input */ + }; + + /* connection state info */ + int m_State; + int m_Latency; + int m_SnapRate; + + int m_LastAckedSnapshot; + int m_LastInputTick; + CSnapshotStorage m_Snapshots; + + CInput m_LatestInput; + CInput m_aInputs[200]; /* TODO: handle input better */ + int m_CurrentInput; + + char m_aName[MAX_NAME_LENGTH]; + char m_aClan[MAX_CLANNAME_LENGTH]; + int m_Score; + int m_Authed; + + void Reset() + { + /* reset input */ + for(int i = 0; i < 200; i++) + m_aInputs[i].m_GameTick = -1; + m_CurrentInput = 0; + mem_zero(&m_LatestInput, sizeof(m_LatestInput)); + + m_Snapshots.PurgeAll(); + m_LastAckedSnapshot = -1; + m_LastInputTick = -1; + m_SnapRate = CClient::SNAPRATE_INIT; + m_Score = 0; + } + }; + + CClient m_aClients[MAX_CLIENTS]; + + CSnapshotBuilder m_SnapshotBuilder; + CSnapIDPool m_IDPool; + CNetServer m_NetServer; + + int64 m_GameStartTime; + int m_CurrentTick; + int m_RunServer; + + char m_aBrowseinfoGametype[16]; + int m_BrowseinfoProgression; + + int64 m_Lastheartbeat; + /*static NETADDR4 master_server;*/ + + char m_aCurrentMap[64]; + int m_CurrentMapCrc; + unsigned char *m_pCurrentMapData; + int m_CurrentMapSize; + + CServer() + { + m_CurrentTick = 0; + m_RunServer = 1; + + mem_zero(m_aBrowseinfoGametype, sizeof(m_aBrowseinfoGametype)); + m_BrowseinfoProgression = -1; + + m_pCurrentMapData = 0; + m_CurrentMapSize = 0; + } + + + int TrySetClientName(int ClientID, const char *pName) + { + int i; + char aTrimmedName[64]; + + /* trim the name */ + str_copy(aTrimmedName, str_ltrim(pName), sizeof(aTrimmedName)); + str_rtrim(aTrimmedName); + dbg_msg("", "'%s' -> '%s'", pName, aTrimmedName); + pName = aTrimmedName; + + + /* check for empty names */ + if(!pName[0]) + return -1; + + /* make sure that two clients doesn't have the same name */ + for(i = 0; i < MAX_CLIENTS; i++) + if(i != ClientID && m_aClients[i].m_State >= CClient::STATE_READY) + { + if(strcmp(pName, m_aClients[i].m_aName) == 0) + return -1; + } + + /* set the client name */ + str_copy(m_aClients[ClientID].m_aName, pName, MAX_NAME_LENGTH); + return 0; + } + + + + void SetClientName(int ClientID, const char *pName) + { + if(ClientID < 0 || ClientID > MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) + return; + + if(!pName) + return; + + char aNameTry[MAX_NAME_LENGTH]; + str_copy(aNameTry, pName, MAX_NAME_LENGTH); + if(TrySetClientName(ClientID, aNameTry)) + { + /* auto rename */ + for(int i = 1;; i++) + { + str_format(aNameTry, MAX_NAME_LENGTH, "(%d)%s", i, pName); + if(TrySetClientName(ClientID, aNameTry) == 0) + break; + } + } + } + + void SetClientScore(int ClientID, int Score) + { + if(ClientID < 0 || ClientID > MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) + return; + m_aClients[ClientID].m_Score = Score; + } + + void SetBrowseInfo(const char *pGameType, int Progression) + { + str_copy(m_aBrowseinfoGametype, pGameType, sizeof(m_aBrowseinfoGametype)); + m_BrowseinfoProgression = Progression; + if(m_BrowseinfoProgression > 100) + m_BrowseinfoProgression = 100; + if(m_BrowseinfoProgression < -1) + m_BrowseinfoProgression = -1; + } + + void Kick(int ClientID, const char *pReason) + { + if(ClientID < 0 || ClientID > MAX_CLIENTS) + return; + + if(m_aClients[ClientID].m_State != CClient::STATE_EMPTY) + m_NetServer.Drop(ClientID, pReason); + } + + int Tick() + { + return m_CurrentTick; + } + + int64 TickStartTime(int Tick) + { + return m_GameStartTime + (time_freq()*Tick)/SERVER_TICK_SPEED; + } + + int TickSpeed() + { + return SERVER_TICK_SPEED; + } + + int Init() + { + int i; + for(i = 0; i < MAX_CLIENTS; i++) + { + m_aClients[i].m_State = CClient::STATE_EMPTY; + m_aClients[i].m_aName[0] = 0; + m_aClients[i].m_aClan[0] = 0; + m_aClients[i].m_Snapshots.Init(); + } + + m_CurrentTick = 0; + + return 0; + } + + int GetClientInfo(int ClientID, CLIENT_INFO *pInfo) + { + dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid"); + dbg_assert(pInfo != 0, "info can not be null"); + + if(m_aClients[ClientID].m_State == CClient::STATE_INGAME) + { + pInfo->name = m_aClients[ClientID].m_aName; + pInfo->latency = m_aClients[ClientID].m_Latency; + return 1; + } + return 0; + } + + int SendMsg(int ClientID) + { + const MSG_INFO *pInfo = msg_get_info(); + CNetChunk Packet; + if(!pInfo) + return -1; + + mem_zero(&Packet, sizeof(CNetChunk)); + + Packet.m_ClientID = ClientID; + Packet.m_pData = pInfo->data; + Packet.m_DataSize = pInfo->size; + + if(pInfo->flags&MSGFLAG_VITAL) + Packet.m_Flags |= NETSENDFLAG_VITAL; + if(pInfo->flags&MSGFLAG_FLUSH) + Packet.m_Flags |= NETSENDFLAG_FLUSH; + + /* write message to demo recorder */ + if(!(pInfo->flags&MSGFLAG_NORECORD)) + demorec_record_message(pInfo->data, pInfo->size); + + if(!(pInfo->flags&MSGFLAG_NOSEND)) + { + if(ClientID == -1) + { + /* broadcast */ + int i; + for(i = 0; i < MAX_CLIENTS; i++) + if(m_aClients[i].m_State == CClient::STATE_INGAME) + { + Packet.m_ClientID = i; + m_NetServer.Send(&Packet); + } + } + else + m_NetServer.Send(&Packet); + } + return 0; + } + + void DoSnapshot() + { + int i; + + { + static PERFORMACE_INFO scope = {"presnap", 0}; + perf_start(&scope); + mods_presnap(); + perf_end(); + } + + /* create snapshot for demo recording */ + if(demorec_isrecording()) + { + char data[CSnapshot::MAX_SIZE]; + int snapshot_size; + + /* build snap and possibly add some messages */ + m_SnapshotBuilder.Init(); + mods_snap(-1); + snapshot_size = m_SnapshotBuilder.Finish(data); + + /* write snapshot */ + demorec_record_snapshot(Tick(), data, snapshot_size); + } + + /* create snapshots for all clients */ + for(i = 0; i < MAX_CLIENTS; i++) + { + /* client must be ingame to recive snapshots */ + if(m_aClients[i].m_State != CClient::STATE_INGAME) + continue; + + /* this client is trying to recover, don't spam snapshots */ + if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_RECOVER && (Tick()%50) != 0) + continue; + + /* this client is trying to recover, don't spam snapshots */ + if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_INIT && (Tick()%10) != 0) + continue; + + { + char data[CSnapshot::MAX_SIZE]; + char deltadata[CSnapshot::MAX_SIZE]; + char compdata[CSnapshot::MAX_SIZE]; + int snapshot_size; + int crc; + static CSnapshot emptysnap; + CSnapshot *deltashot = &emptysnap; + int deltashot_size; + int delta_tick = -1; + int deltasize; + static PERFORMACE_INFO scope = {"build", 0}; + perf_start(&scope); + + m_SnapshotBuilder.Init(); + + { + static PERFORMACE_INFO scope = {"modsnap", 0}; + perf_start(&scope); + mods_snap(i); + perf_end(); + } + + /* finish snapshot */ + snapshot_size = m_SnapshotBuilder.Finish(data); + crc = ((CSnapshot*)data)->Crc(); + + /* remove old snapshos */ + /* keep 3 seconds worth of snapshots */ + m_aClients[i].m_Snapshots.PurgeUntil(m_CurrentTick-SERVER_TICK_SPEED*3); + + /* save it the snapshot */ + m_aClients[i].m_Snapshots.Add(m_CurrentTick, time_get(), snapshot_size, data, 0); + + /* find snapshot that we can preform delta against */ + emptysnap.m_DataSize = 0; + emptysnap.m_NumItems = 0; + + { + deltashot_size = m_aClients[i].m_Snapshots.Get(m_aClients[i].m_LastAckedSnapshot, 0, &deltashot, 0); + if(deltashot_size >= 0) + delta_tick = m_aClients[i].m_LastAckedSnapshot; + else + { + /* no acked package found, force client to recover rate */ + if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_FULL) + m_aClients[i].m_SnapRate = CClient::SNAPRATE_RECOVER; + } + } + + /* create delta */ + { + static PERFORMACE_INFO scope = {"delta", 0}; + perf_start(&scope); + deltasize = CSnapshot::CreateDelta(deltashot, (CSnapshot*)data, deltadata); + perf_end(); + } + + + if(deltasize) + { + /* compress it */ + int snapshot_size; + const int max_size = MAX_SNAPSHOT_PACKSIZE; + int numpackets; + int n, left; + + { + static PERFORMACE_INFO scope = {"compress", 0}; + /*char buffer[512];*/ + perf_start(&scope); + snapshot_size = intpack_compress(deltadata, deltasize, compdata); + + /*str_hex(buffer, sizeof(buffer), compdata, snapshot_size); + dbg_msg("", "deltasize=%d -> %d : %s", deltasize, snapshot_size, buffer);*/ + + perf_end(); + } + + numpackets = (snapshot_size+max_size-1)/max_size; + + for(n = 0, left = snapshot_size; left; n++) + { + int chunk = left < max_size ? left : max_size; + left -= chunk; + + if(numpackets == 1) + msg_pack_start_system(NETMSG_SNAPSINGLE, MSGFLAG_FLUSH); + else + msg_pack_start_system(NETMSG_SNAP, MSGFLAG_FLUSH); + + msg_pack_int(m_CurrentTick); + msg_pack_int(m_CurrentTick-delta_tick); /* compressed with */ + + if(numpackets != 1) + { + msg_pack_int(numpackets); + msg_pack_int(n); + } + + msg_pack_int(crc); + msg_pack_int(chunk); + msg_pack_raw(&compdata[n*max_size], chunk); + msg_pack_end(); + SendMsg(i); + } + } + else + { + msg_pack_start_system(NETMSG_SNAPEMPTY, MSGFLAG_FLUSH); + msg_pack_int(m_CurrentTick); + msg_pack_int(m_CurrentTick-delta_tick); /* compressed with */ + msg_pack_end(); + SendMsg(i); + } + + perf_end(); + } + } + + mods_postsnap(); + } + + + static int NewClientCallback(int cid, void *pUser) + { + CServer *pThis = (CServer *)pUser; + pThis->m_aClients[cid].m_State = CClient::STATE_AUTH; + pThis->m_aClients[cid].m_aName[0] = 0; + pThis->m_aClients[cid].m_aClan[0] = 0; + pThis->m_aClients[cid].m_Authed = 0; + pThis->m_aClients[cid].Reset(); + return 0; + } + + static int DelClientCallback(int cid, void *pUser) + { + CServer *pThis = (CServer *)pUser; + + /* notify the mod about the drop */ + if(pThis->m_aClients[cid].m_State >= CClient::STATE_READY) + mods_client_drop(cid); + + pThis->m_aClients[cid].m_State = CClient::STATE_EMPTY; + pThis->m_aClients[cid].m_aName[0] = 0; + pThis->m_aClients[cid].m_aClan[0] = 0; + pThis->m_aClients[cid].m_Authed = 0; + pThis->m_aClients[cid].m_Snapshots.PurgeAll(); + return 0; + } + + void SendMap(int cid) + { + msg_pack_start_system(NETMSG_MAP_CHANGE, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_string(config.sv_map, 0); + msg_pack_int(m_CurrentMapCrc); + msg_pack_end(); + server_send_msg(cid); + } + + void SendRconLine(int cid, const char *pLine) + { + msg_pack_start_system(NETMSG_RCON_LINE, MSGFLAG_VITAL); + msg_pack_string(pLine, 512); + msg_pack_end(); + server_send_msg(cid); + } + + static void SendRconLineAuthed(const char *pLine, void *pUser) + { + CServer *pThis = (CServer *)pUser; + static volatile int reentry_guard = 0; + int i; + + if(reentry_guard) return; + reentry_guard++; + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY && pThis->m_aClients[i].m_Authed) + pThis->SendRconLine(i, pLine); + } + + reentry_guard--; + } + + void ProcessClientPacket(CNetChunk *pPacket) + { + int cid = pPacket->m_ClientID; + NETADDR addr; + + int sys; + int msg = msg_unpack_start(pPacket->m_pData, pPacket->m_DataSize, &sys); + + if(m_aClients[cid].m_State == CClient::STATE_AUTH) + { + if(sys && msg == NETMSG_INFO) + { + char version[64]; + const char *password; + str_copy(version, msg_unpack_string(), 64); + if(strcmp(version, mods_net_version()) != 0) + { + /* OH FUCK! wrong version, drop him */ + char reason[256]; + str_format(reason, sizeof(reason), "wrong version. server is running '%s' and client '%s'.", mods_net_version(), version); + m_NetServer.Drop(cid, reason); + return; + } + + str_copy(m_aClients[cid].m_aName, msg_unpack_string(), MAX_NAME_LENGTH); + str_copy(m_aClients[cid].m_aClan, msg_unpack_string(), MAX_CLANNAME_LENGTH); + password = msg_unpack_string(); + + if(config.password[0] != 0 && strcmp(config.password, password) != 0) + { + /* wrong password */ + m_NetServer.Drop(cid, "wrong password"); + return; + } + + m_aClients[cid].m_State = CClient::STATE_CONNECTING; + SendMap(cid); + } + } + else + { + if(sys) + { + /* system message */ + if(msg == NETMSG_REQUEST_MAP_DATA) + { + int chunk = msg_unpack_int(); + int chunk_size = 1024-128; + int offset = chunk * chunk_size; + int last = 0; + + /* drop faulty map data requests */ + if(chunk < 0 || offset > m_CurrentMapSize) + return; + + if(offset+chunk_size >= m_CurrentMapSize) + { + chunk_size = m_CurrentMapSize-offset; + if(chunk_size < 0) + chunk_size = 0; + last = 1; + } + + msg_pack_start_system(NETMSG_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_int(last); + msg_pack_int(m_CurrentMapSize); + msg_pack_int(chunk_size); + msg_pack_raw(&m_pCurrentMapData[offset], chunk_size); + msg_pack_end(); + server_send_msg(cid); + + if(config.debug) + dbg_msg("server", "sending chunk %d with size %d", chunk, chunk_size); + } + else if(msg == NETMSG_READY) + { + if(m_aClients[cid].m_State == CClient::STATE_CONNECTING) + { + addr = m_NetServer.ClientAddr(cid); + + dbg_msg("server", "player is ready. cid=%x ip=%d.%d.%d.%d", + cid, + addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3] + ); + m_aClients[cid].m_State = CClient::STATE_READY; + mods_connected(cid); + } + } + else if(msg == NETMSG_ENTERGAME) + { + if(m_aClients[cid].m_State == CClient::STATE_READY) + { + addr = m_NetServer.ClientAddr(cid); + + dbg_msg("server", "player has entered the game. cid=%x ip=%d.%d.%d.%d", + cid, + addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3] + ); + m_aClients[cid].m_State = CClient::STATE_INGAME; + mods_client_enter(cid); + } + } + else if(msg == NETMSG_INPUT) + { + int tick, size, i; + CClient::CInput *pInput; + int64 tagtime; + + m_aClients[cid].m_LastAckedSnapshot = msg_unpack_int(); + tick = msg_unpack_int(); + size = msg_unpack_int(); + + /* check for errors */ + if(msg_unpack_error() || size/4 > MAX_INPUT_SIZE) + return; + + if(m_aClients[cid].m_LastAckedSnapshot > 0) + m_aClients[cid].m_SnapRate = CClient::SNAPRATE_FULL; + + if(m_aClients[cid].m_Snapshots.Get(m_aClients[cid].m_LastAckedSnapshot, &tagtime, 0, 0) >= 0) + m_aClients[cid].m_Latency = (int)(((time_get()-tagtime)*1000)/time_freq()); + + /* add message to report the input timing */ + /* skip packets that are old */ + if(tick > m_aClients[cid].m_LastInputTick) + { + int time_left = ((TickStartTime(tick)-time_get())*1000) / time_freq(); + msg_pack_start_system(NETMSG_INPUTTIMING, 0); + msg_pack_int(tick); + msg_pack_int(time_left); + msg_pack_end(); + server_send_msg(cid); + } + + m_aClients[cid].m_LastInputTick = tick; + + pInput = &m_aClients[cid].m_aInputs[m_aClients[cid].m_CurrentInput]; + + if(tick <= server_tick()) + tick = server_tick()+1; + + pInput->m_GameTick = tick; + + for(i = 0; i < size/4; i++) + pInput->m_aData[i] = msg_unpack_int(); + + mem_copy(m_aClients[cid].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE*sizeof(int)); + + m_aClients[cid].m_CurrentInput++; + m_aClients[cid].m_CurrentInput %= 200; + + /* call the mod with the fresh input data */ + if(m_aClients[cid].m_State == CClient::STATE_INGAME) + mods_client_direct_input(cid, m_aClients[cid].m_LatestInput.m_aData); + } + else if(msg == NETMSG_RCON_CMD) + { + const char *cmd = msg_unpack_string(); + + if(msg_unpack_error() == 0 && m_aClients[cid].m_Authed) + { + dbg_msg("server", "cid=%d rcon='%s'", cid, cmd); + console_execute_line(cmd); + } + } + else if(msg == NETMSG_RCON_AUTH) + { + const char *pw; + msg_unpack_string(); /* login name, not used */ + pw = msg_unpack_string(); + + if(msg_unpack_error() == 0) + { + if(config.sv_rcon_password[0] == 0) + { + SendRconLine(cid, "No rcon password set on server. Set sv_rcon_password to enable the remote console."); + } + else if(strcmp(pw, config.sv_rcon_password) == 0) + { + msg_pack_start_system(NETMSG_RCON_AUTH_STATUS, MSGFLAG_VITAL); + msg_pack_int(1); + msg_pack_end(); + server_send_msg(cid); + + m_aClients[cid].m_Authed = 1; + SendRconLine(cid, "Authentication successful. Remote console access granted."); + dbg_msg("server", "cid=%d authed", cid); + } + else + { + SendRconLine(cid, "Wrong password."); + } + } + } + else if(msg == NETMSG_PING) + { + msg_pack_start_system(NETMSG_PING_REPLY, 0); + msg_pack_end(); + server_send_msg(cid); + } + else + { + char hex[] = "0123456789ABCDEF"; + char buf[512]; + int b; + + for(b = 0; b < pPacket->m_DataSize && b < 32; b++) + { + buf[b*3] = hex[((const unsigned char *)pPacket->m_pData)[b]>>4]; + buf[b*3+1] = hex[((const unsigned char *)pPacket->m_pData)[b]&0xf]; + buf[b*3+2] = ' '; + buf[b*3+3] = 0; + } + + dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, pPacket->m_DataSize); + dbg_msg("server", "%s", buf); + + } + } + else + { + /* game message */ + if(m_aClients[cid].m_State >= CClient::STATE_READY) + mods_message(msg, cid); + } + } + } + + void SendServerInfo(NETADDR *addr, int token) + { + CNetChunk Packet; + CPacker p; + char buf[128]; + + /* count the players */ + int player_count = 0; + int i; + for(i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + player_count++; + } + + p.Reset(); + + if(token >= 0) + { + /* new token based format */ + p.AddRaw(SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); + str_format(buf, sizeof(buf), "%d", token); + p.AddString(buf, 6); + } + else + { + /* old format */ + p.AddRaw(SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)); + } + + p.AddString(mods_version(), 32); + p.AddString(config.sv_name, 64); + p.AddString(config.sv_map, 32); + + /* gametype */ + p.AddString(m_aBrowseinfoGametype, 16); + + /* flags */ + i = 0; + if(config.password[0]) /* password set */ + i |= SRVFLAG_PASSWORD; + str_format(buf, sizeof(buf), "%d", i); + p.AddString(buf, 2); + + /* progression */ + str_format(buf, sizeof(buf), "%d", m_BrowseinfoProgression); + p.AddString(buf, 4); + + str_format(buf, sizeof(buf), "%d", player_count); p.AddString(buf, 3); /* num players */ + str_format(buf, sizeof(buf), "%d", m_NetServer.MaxClients()); p.AddString(buf, 3); /* max players */ + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + { + p.AddString(m_aClients[i].m_aName, 48); /* player name */ + str_format(buf, sizeof(buf), "%d", m_aClients[i].m_Score); p.AddString(buf, 6); /* player score */ + } + } + + + Packet.m_ClientID = -1; + Packet.m_Address = *addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = p.Size(); + Packet.m_pData = p.Data(); + m_NetServer.Send(&Packet); + } + + int BanAdd(NETADDR Addr, int Seconds) + { + return m_NetServer.BanAdd(Addr, Seconds); + } + + int BanRemove(NETADDR Addr) + { + return m_NetServer.BanRemove(Addr); + } + + + void PumpNetwork() + { + CNetChunk Packet; + + m_NetServer.Update(); + + /* process packets */ + while(m_NetServer.Recv(&Packet)) + { + if(Packet.m_ClientID == -1) + { + /* stateless */ + if(!register_process_packet(&Packet)) + { + if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO)+1 && + memcmp(Packet.m_pData, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) + { + SendServerInfo(&Packet.m_Address, ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO)]); + } + + + if(Packet.m_DataSize == sizeof(SERVERBROWSE_OLD_GETINFO) && + memcmp(Packet.m_pData, SERVERBROWSE_OLD_GETINFO, sizeof(SERVERBROWSE_OLD_GETINFO)) == 0) + { + SendServerInfo(&Packet.m_Address, -1); + } + } + } + else + ProcessClientPacket(&Packet); + } + } + + int LoadMap(const char *pMapName) + { + DATAFILE *df; + char buf[512]; + str_format(buf, sizeof(buf), "maps/%s.map", pMapName); + df = datafile_load(buf); + if(!df) + return 0; + + /* stop recording when we change map */ + demorec_record_stop(); + + /* reinit snapshot ids */ + m_IDPool.TimeoutIDs(); + + /* get the crc of the map */ + m_CurrentMapCrc = datafile_crc(buf); + dbg_msg("server", "%s crc is %08x", buf, m_CurrentMapCrc); + + str_copy(m_aCurrentMap, pMapName, sizeof(m_aCurrentMap)); + map_set(df); + + /* load compelate map into memory for download */ + { + IOHANDLE file = engine_openfile(buf, IOFLAG_READ); + m_CurrentMapSize = (int)io_length(file); + if(m_pCurrentMapData) + mem_free(m_pCurrentMapData); + m_pCurrentMapData = (unsigned char *)mem_alloc(m_CurrentMapSize, 1); + io_read(file, m_pCurrentMapData, m_CurrentMapSize); + io_close(file); + } + return 1; + } + + int Run() + { + NETADDR bindaddr; + + //snap_init_id(); + net_init(); + + /* */ + console_register_print_callback(SendRconLineAuthed, this); + + /* load map */ + if(!LoadMap(config.sv_map)) + { + dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); + return -1; + } + + /* start server */ + /* TODO: IPv6 support */ + if(config.sv_bindaddr[0] && net_host_lookup(config.sv_bindaddr, &bindaddr, NETTYPE_IPV4) == 0) + { + /* sweet! */ + bindaddr.port = config.sv_port; + } + else + { + mem_zero(&bindaddr, sizeof(bindaddr)); + bindaddr.port = config.sv_port; + } + + + if(!m_NetServer.Open(bindaddr, config.sv_max_clients, 0)) + { + dbg_msg("server", "couldn't open socket. port might already be in use"); + return -1; + } + + m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this); + + dbg_msg("server", "server name is '%s'", config.sv_name); + + mods_init(); + dbg_msg("server", "version %s", mods_net_version()); + + /* start game */ + { + int64 reporttime = time_get(); + int reportinterval = 3; + + m_Lastheartbeat = 0; + m_GameStartTime = time_get(); + + if(config.debug) + dbg_msg("server", "baseline memory usage %dk", mem_stats()->allocated/1024); + + while(m_RunServer) + { + static PERFORMACE_INFO rootscope = {"root", 0}; + int64 t = time_get(); + int new_ticks = 0; + + perf_start(&rootscope); + + /* load new map TODO: don't poll this */ + if(strcmp(config.sv_map, m_aCurrentMap) != 0 || config.sv_map_reload) + { + config.sv_map_reload = 0; + + /* load map */ + if(LoadMap(config.sv_map)) + { + int c; + + /* new map loaded */ + mods_shutdown(); + + for(c = 0; c < MAX_CLIENTS; c++) + { + if(m_aClients[c].m_State == CClient::STATE_EMPTY) + continue; + + SendMap(c); + m_aClients[c].Reset(); + m_aClients[c].m_State = CClient::STATE_CONNECTING; + } + + m_GameStartTime = time_get(); + m_CurrentTick = 0; + mods_init(); + } + else + { + dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); + config_set_sv_map(&config, m_aCurrentMap); + } + } + + while(t > TickStartTime(m_CurrentTick+1)) + { + m_CurrentTick++; + new_ticks++; + + /* apply new input */ + { + static PERFORMACE_INFO scope = {"input", 0}; + int c, i; + + perf_start(&scope); + + for(c = 0; c < MAX_CLIENTS; c++) + { + if(m_aClients[c].m_State == CClient::STATE_EMPTY) + continue; + for(i = 0; i < 200; i++) + { + if(m_aClients[c].m_aInputs[i].m_GameTick == server_tick()) + { + if(m_aClients[c].m_State == CClient::STATE_INGAME) + mods_client_predicted_input(c, m_aClients[c].m_aInputs[i].m_aData); + break; + } + } + } + + perf_end(); + } + + /* progress game */ + { + static PERFORMACE_INFO scope = {"tick", 0}; + perf_start(&scope); + mods_tick(); + perf_end(); + } + } + + /* snap game */ + if(new_ticks) + { + if(config.sv_high_bandwidth || (m_CurrentTick%2) == 0) + { + static PERFORMACE_INFO scope = {"snap", 0}; + perf_start(&scope); + DoSnapshot(); + perf_end(); + } + } + + /* master server stuff */ + register_update(); + + + { + static PERFORMACE_INFO scope = {"net", 0}; + perf_start(&scope); + PumpNetwork(); + perf_end(); + } + + perf_end(); + + if(reporttime < time_get()) + { + if(config.debug) + { + /* + static NETSTATS prev_stats; + NETSTATS stats; + netserver_stats(net, &stats); + + perf_next(); + + if(config.dbg_pref) + perf_dump(&rootscope); + + dbg_msg("server", "send=%8d recv=%8d", + (stats.send_bytes - prev_stats.send_bytes)/reportinterval, + (stats.recv_bytes - prev_stats.recv_bytes)/reportinterval); + + prev_stats = stats; + */ + } + + reporttime += time_freq()*reportinterval; + } + + /* wait for incomming data */ + net_socket_read_wait(m_NetServer.Socket(), 5); + } + } + + mods_shutdown(); + map_unload(); + + if(m_pCurrentMapData) + mem_free(m_pCurrentMapData); + return 0; + } + + static void ConKick(void *pResult, void *pUser) + { + ((CServer *)pUser)->Kick(console_arg_int(pResult, 0), "kicked by console"); + } + + static int str_allnum(const char *str) + { + while(*str) + { + if(!(*str >= '0' && *str <= '9')) + return 0; + str++; + } + return 1; + } + + static void ConBan(void *pResult, void *pUser) + { + NETADDR addr; + char addrstr[128]; + const char *str = console_arg_string(pResult, 0); + int minutes = 30; + + if(console_arg_num(pResult) > 1) + minutes = console_arg_int(pResult, 1); + + if(net_addr_from_str(&addr, str) == 0) + ((CServer *)pUser)->BanAdd(addr, minutes*60); + else if(str_allnum(str)) + { + NETADDR addr; + int cid = atoi(str); + + if(cid < 0 || cid > MAX_CLIENTS || ((CServer *)pUser)->m_aClients[cid].m_State == CClient::STATE_EMPTY) + { + dbg_msg("server", "invalid client id"); + return; + } + + addr = ((CServer *)pUser)->m_NetServer.ClientAddr(cid); + ((CServer *)pUser)->BanAdd(addr, minutes*60); + } + + addr.port = 0; + net_addr_str(&addr, addrstr, sizeof(addrstr)); + + if(minutes) + dbg_msg("server", "banned %s for %d minutes", addrstr, minutes); + else + dbg_msg("server", "banned %s for life", addrstr); + } + + static void ConUnban(void *result, void *pUser) + { + NETADDR addr; + const char *str = console_arg_string(result, 0); + + if(net_addr_from_str(&addr, str) == 0) + ((CServer *)pUser)->BanRemove(addr); + else + dbg_msg("server", "invalid network address"); + } + + static void ConBans(void *pResult, void *pUser) + { + unsigned now = time_timestamp(); + + int num = ((CServer *)pUser)->m_NetServer.BanNum(); + for(int i = 0; i < num; i++) + { + CNetServer::CBanInfo Info; + ((CServer *)pUser)->m_NetServer.BanGet(i, &Info); + NETADDR Addr = Info.m_Addr; + + if(Info.m_Expires == -1) + { + dbg_msg("server", "#%d %d.%d.%d.%d for life", i, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]); + } + else + { + unsigned t = Info.m_Expires - now; + dbg_msg("server", "#%d %d.%d.%d.%d for %d minutes and %d seconds", i, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3], t/60, t%60); + } + } + dbg_msg("server", "%d ban(s)", num); + } + + static void ConStatus(void *pResult, void *pUser) + { + int i; + NETADDR addr; + for(i = 0; i < MAX_CLIENTS; i++) + { + if(((CServer *)pUser)->m_aClients[i].m_State == CClient::STATE_INGAME) + { + addr = ((CServer *)pUser)->m_NetServer.ClientAddr(i); + dbg_msg("server", "id=%d addr=%d.%d.%d.%d:%d name='%s' score=%d", + i, addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port, + ((CServer *)pUser)->m_aClients[i].m_aName, ((CServer *)pUser)->m_aClients[i].m_Score); + } + } + } + + static void ConShutdown(void *pResult, void *pUser) + { + ((CServer *)pUser)->m_RunServer = 0; + } + + static void ConRecord(void *pResult, void *pUser) + { + char filename[512]; + str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(pResult, 0)); + demorec_record_start(filename, mods_net_version(), ((CServer *)pUser)->m_aCurrentMap, ((CServer *)pUser)->m_CurrentMapCrc, "server"); + } + + static void ConStopRecord(void *pResult, void *pUser) + { + demorec_record_stop(); + } + + + void RegisterCommands() + { + MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, ConKick, this, ""); + MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, ConBan, this, ""); + MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, ConUnban, this, ""); + MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, ConBans, this, ""); + MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, ConStatus, this, ""); + MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, ""); + + MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_SERVER, ConRecord, this, ""); + MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, ""); + } + +}; + + +// UGLY UGLY HACK for now +CServer g_Server; +CNetServer *m_pNetServer; + +int server_tick() { return g_Server.Tick(); } +int server_tickspeed() { return g_Server.TickSpeed(); } +int snap_new_id() { return g_Server.m_IDPool.NewID(); } +void snap_free_id(int id) { return g_Server.m_IDPool.FreeID(id); } +int server_send_msg(int client_id) { return g_Server.SendMsg(client_id); } +void server_setbrowseinfo(const char *game_type, int progression) { g_Server.SetBrowseInfo(game_type, progression); } +void server_setclientname(int client_id, const char *name) { g_Server.SetClientName(client_id, name); } +void server_setclientscore(int client_id, int score) { g_Server.SetClientScore(client_id, score); } + +int server_getclientinfo(int client_id, CLIENT_INFO *info) { return g_Server.GetClientInfo(client_id, info); } + +void *snap_new_item(int type, int id, int size) +{ + dbg_assert(type >= 0 && type <=0xffff, "incorrect type"); + dbg_assert(id >= 0 && id <=0xffff, "incorrect id"); + return g_Server.m_SnapshotBuilder.NewItem(type, id, size); +} + +int *server_latestinput(int client_id, int *size) +{ + if(client_id < 0 || client_id > MAX_CLIENTS || g_Server.m_aClients[client_id].m_State < CServer::CClient::STATE_READY) + return 0; + return g_Server.m_aClients[client_id].m_LatestInput.m_aData; +} + +const char *server_clientname(int client_id) +{ + if(client_id < 0 || client_id > MAX_CLIENTS || g_Server.m_aClients[client_id].m_State < CServer::CClient::STATE_READY) + return "(invalid client)"; + return g_Server.m_aClients[client_id].m_aName; +} + +#if 0 + + +static void reset_client(int cid) +{ + /* reset input */ + int i; + for(i = 0; i < 200; i++) + m_aClients[cid].m_aInputs[i].m_GameTick = -1; + m_aClients[cid].m_CurrentInput = 0; + mem_zero(&m_aClients[cid].m_Latestinput, sizeof(m_aClients[cid].m_Latestinput)); + + m_aClients[cid].m_Snapshots.PurgeAll(); + m_aClients[cid].m_LastAckedSnapshot = -1; + m_aClients[cid].m_LastInputTick = -1; + m_aClients[cid].m_SnapRate = CClient::SNAPRATE_INIT; + m_aClients[cid].m_Score = 0; + +} + + +static int new_client_callback(int cid, void *user) +{ + m_aClients[cid].state = CClient::STATE_AUTH; + m_aClients[cid].name[0] = 0; + m_aClients[cid].clan[0] = 0; + m_aClients[cid].authed = 0; + reset_client(cid); + return 0; +} + +static int del_client_callback(int cid, void *user) +{ + /* notify the mod about the drop */ + if(m_aClients[cid].state >= CClient::STATE_READY) + mods_client_drop(cid); + + m_aClients[cid].state = CClient::STATE_EMPTY; + m_aClients[cid].name[0] = 0; + m_aClients[cid].clan[0] = 0; + m_aClients[cid].authed = 0; + m_aClients[cid].snapshots.PurgeAll(); + return 0; +} +static void server_send_map(int cid) +{ + msg_pack_start_system(NETMSG_MAP_CHANGE, MSGFLAG_VITAL|MSGFLAG_FLUSH); + msg_pack_string(config.sv_map, 0); + msg_pack_int(current_map_crc); + msg_pack_end(); + server_send_msg(cid); +} + +static void server_send_rcon_line(int cid, const char *line) +{ + msg_pack_start_system(NETMSG_RCON_LINE, MSGFLAG_VITAL); + msg_pack_string(line, 512); + msg_pack_end(); + server_send_msg(cid); +} + +static void server_send_rcon_line_authed(const char *line, void *user_data) +{ + static volatile int reentry_guard = 0; + int i; + + if(reentry_guard) return; + reentry_guard++; + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].state != CClient::STATE_EMPTY && m_aClients[i].authed) + server_send_rcon_line(i, line); + } + + reentry_guard--; +} + +static void server_pump_network() +{ + CNetChunk Packet; + + m_NetServer.Update(); + + /* process packets */ + while(m_NetServer.Recv(&Packet)) + { + if(Packet.m_ClientID == -1) + { + /* stateless */ + if(!register_process_packet(&Packet)) + { + if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO)+1 && + memcmp(Packet.m_pData, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)) == 0) + { + server_send_serverinfo(&Packet.m_Address, ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO)]); + } + + + if(Packet.m_DataSize == sizeof(SERVERBROWSE_OLD_GETINFO) && + memcmp(Packet.m_pData, SERVERBROWSE_OLD_GETINFO, sizeof(SERVERBROWSE_OLD_GETINFO)) == 0) + { + server_send_serverinfo(&Packet.m_Address, -1); + } + } + } + else + server_process_client_packet(&Packet); + } +} + +static int server_load_map(const char *mapname) +{ + DATAFILE *df; + char buf[512]; + str_format(buf, sizeof(buf), "maps/%s.map", mapname); + df = datafile_load(buf); + if(!df) + return 0; + + /* stop recording when we change map */ + demorec_record_stop(); + + /* reinit snapshot ids */ + snap_timeout_ids(); + + /* get the crc of the map */ + current_map_crc = datafile_crc(buf); + dbg_msg("server", "%s crc is %08x", buf, current_map_crc); + + str_copy(current_map, mapname, sizeof(current_map)); + map_set(df); + + /* load compelate map into memory for download */ + { + IOHANDLE file = engine_openfile(buf, IOFLAG_READ); + current_map_size = (int)io_length(file); + if(current_map_data) + mem_free(current_map_data); + current_map_data = (unsigned char *)mem_alloc(current_map_size, 1); + io_read(file, current_map_data, current_map_size); + io_close(file); + } + return 1; +} + +static int server_run() +{ + NETADDR bindaddr; + + snap_init_id(); + net_init(); + + /* */ + console_register_print_callback(server_send_rcon_line_authed, 0); + + /* load map */ + if(!server_load_map(config.sv_map)) + { + dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); + return -1; + } + + /* start server */ + /* TODO: IPv6 support */ + if(config.sv_bindaddr[0] && net_host_lookup(config.sv_bindaddr, &bindaddr, NETTYPE_IPV4) == 0) + { + /* sweet! */ + bindaddr.port = config.sv_port; + } + else + { + mem_zero(&bindaddr, sizeof(bindaddr)); + bindaddr.port = config.sv_port; + } + + + if(!m_NetServer.Open(bindaddr, config.sv_max_clients, 0)) + { + dbg_msg("server", "couldn't open socket. port might already be in use"); + return -1; + } + + m_NetServer.SetCallbacks(new_client_callback, del_client_callback, this); + + dbg_msg("server", "server name is '%s'", config.sv_name); + + mods_init(); + dbg_msg("server", "version %s", mods_net_version()); + + /* start game */ + { + int64 reporttime = time_get(); + int reportinterval = 3; + + lastheartbeat = 0; + game_start_time = time_get(); + + if(config.debug) + dbg_msg("server", "baseline memory usage %dk", mem_stats()->allocated/1024); + + while(run_server) + { + static PERFORMACE_INFO rootscope = {"root", 0}; + int64 t = time_get(); + int new_ticks = 0; + + perf_start(&rootscope); + + /* load new map TODO: don't poll this */ + if(strcmp(config.sv_map, current_map) != 0 || config.sv_map_reload) + { + config.sv_map_reload = 0; + + /* load map */ + if(server_load_map(config.sv_map)) + { + int c; + + /* new map loaded */ + mods_shutdown(); + + for(c = 0; c < MAX_CLIENTS; c++) + { + if(m_aClients[c].state == CClient::STATE_EMPTY) + continue; + + server_send_map(c); + reset_client(c); + m_aClients[c].state = CClient::STATE_CONNECTING; + } + + game_start_time = time_get(); + current_tick = 0; + mods_init(); + } + else + { + dbg_msg("server", "failed to load map. mapname='%s'", config.sv_map); + config_set_sv_map(&config, current_map); + } + } + + while(t > server_tick_start_time(current_tick+1)) + { + current_tick++; + new_ticks++; + + /* apply new input */ + { + static PERFORMACE_INFO scope = {"input", 0}; + int c, i; + + perf_start(&scope); + + for(c = 0; c < MAX_CLIENTS; c++) + { + if(m_aClients[c].state == CClient::STATE_EMPTY) + continue; + for(i = 0; i < 200; i++) + { + if(m_aClients[c].inputs[i].game_tick == server_tick()) + { + if(m_aClients[c].state == CClient::STATE_INGAME) + mods_client_predicted_input(c, m_aClients[c].inputs[i].data); + break; + } + } + } + + perf_end(); + } + + /* progress game */ + { + static PERFORMACE_INFO scope = {"tick", 0}; + perf_start(&scope); + mods_tick(); + perf_end(); + } + } + + /* snap game */ + if(new_ticks) + { + if(config.sv_high_bandwidth || (current_tick%2) == 0) + { + static PERFORMACE_INFO scope = {"snap", 0}; + perf_start(&scope); + server_do_snap(); + perf_end(); + } + } + + /* master server stuff */ + register_update(); + + + { + static PERFORMACE_INFO scope = {"net", 0}; + perf_start(&scope); + server_pump_network(); + perf_end(); + } + + perf_end(); + + if(reporttime < time_get()) + { + if(config.debug) + { + /* + static NETSTATS prev_stats; + NETSTATS stats; + netserver_stats(net, &stats); + + perf_next(); + + if(config.dbg_pref) + perf_dump(&rootscope); + + dbg_msg("server", "send=%8d recv=%8d", + (stats.send_bytes - prev_stats.send_bytes)/reportinterval, + (stats.recv_bytes - prev_stats.recv_bytes)/reportinterval); + + prev_stats = stats; + */ + } + + reporttime += time_freq()*reportinterval; + } + + /* wait for incomming data */ + net_socket_read_wait(m_NetServer.Socket(), 5); + } + } + + mods_shutdown(); + map_unload(); + + if(current_map_data) + mem_free(current_map_data); + return 0; +} + + +#endif + +int main(int argc, char **argv) +{ + + m_pNetServer = &g_Server.m_NetServer; +#if defined(CONF_FAMILY_WINDOWS) + int i; + for(i = 1; i < argc; i++) + { + if(strcmp("-s", argv[i]) == 0 || strcmp("--silent", argv[i]) == 0) + { + ShowWindow(GetConsoleWindow(), SW_HIDE); + break; + } + } +#endif + + /* init the engine */ + dbg_msg("server", "starting..."); + engine_init("Teeworlds"); + + /* register all console commands */ + + g_Server.RegisterCommands(); + mods_console_init(); + + /* parse the command line arguments */ + engine_parse_arguments(argc, argv); + + /* run the server */ + g_Server.Run(); + return 0; +} + |