diff options
Diffstat (limited to 'src/engine/server')
| -rw-r--r-- | src/engine/server/es_register.cpp | 271 | ||||
| -rw-r--r-- | src/engine/server/es_server.cpp | 1969 | ||||
| -rw-r--r-- | src/engine/server/register.cpp | 264 | ||||
| -rw-r--r-- | src/engine/server/register.h | 48 | ||||
| -rw-r--r-- | src/engine/server/server.cpp | 1400 | ||||
| -rw-r--r-- | src/engine/server/server.h | 195 |
6 files changed, 1907 insertions, 2240 deletions
diff --git a/src/engine/server/es_register.cpp b/src/engine/server/es_register.cpp deleted file mode 100644 index 0eb5dba5..00000000 --- a/src/engine/server/es_register.cpp +++ /dev/null @@ -1,271 +0,0 @@ -#include <string.h> -#include <base/system.h> -#include <engine/e_network.h> -#include <engine/e_config.h> -#include <engine/e_engine.h> - -#include <mastersrv/mastersrv.h> - -extern CNetServer *m_pNetServer; - -enum -{ - REGISTERSTATE_START=0, - REGISTERSTATE_UPDATE_ADDRS, - REGISTERSTATE_QUERY_COUNT, - REGISTERSTATE_HEARTBEAT, - REGISTERSTATE_REGISTERED, - REGISTERSTATE_ERROR -}; - -static int register_state = REGISTERSTATE_START; -static int64 register_state_start = 0; -static int register_first = 1; -static int register_count = 0; - -static void register_new_state(int state) -{ - register_state = state; - register_state_start = time_get(); -} - -static void register_send_fwcheckresponse(NETADDR *pAddr) -{ - 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; - CNetChunk Packet; - - mem_copy(data, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)); - - 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; - m_pNetServer->Send(&Packet); -} - -static void register_send_count_request(NETADDR Addr) -{ - 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 -{ - NETADDR addr; - int count; - int valid; - int64 last_send; -} MASTERSERVER_INFO; - -static MASTERSERVER_INFO masterserver_info[MAX_MASTERSERVERS] = {{{0}}}; -static int register_registered_server = -1; - -void register_update() -{ - int64 now = time_get(); - int64 freq = time_freq(); - - if(!config.sv_register) - return; - - mastersrv_update(); - - if(register_state == REGISTERSTATE_START) - { - register_count = 0; - register_first = 1; - register_new_state(REGISTERSTATE_UPDATE_ADDRS); - mastersrv_refresh_addresses(); - dbg_msg("register", "refreshing ip addresses"); - } - else if(register_state == REGISTERSTATE_UPDATE_ADDRS) - { - register_registered_server = -1; - - if(!mastersrv_refreshing()) - { - int i; - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - NETADDR addr = mastersrv_get(i); - masterserver_info[i].addr = addr; - masterserver_info[i].count = 0; - - if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3]) - masterserver_info[i].valid = 0; - else - { - masterserver_info[i].valid = 1; - masterserver_info[i].count = -1; - masterserver_info[i].last_send = 0; - } - } - - dbg_msg("register", "fetching server counts"); - register_new_state(REGISTERSTATE_QUERY_COUNT); - } - } - else if(register_state == REGISTERSTATE_QUERY_COUNT) - { - int i; - int left = 0; - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - if(!masterserver_info[i].valid) - continue; - - if(masterserver_info[i].count == -1) - { - left++; - if(masterserver_info[i].last_send+freq < now) - { - masterserver_info[i].last_send = now; - register_send_count_request(masterserver_info[i].addr); - } - } - } - - /* check if we are done or timed out */ - if(left == 0 || now > register_state_start+freq*3) - { - /* choose server */ - int best = -1; - int i; - for(i = 0; i < MAX_MASTERSERVERS; i++) - { - if(!masterserver_info[i].valid || masterserver_info[i].count == -1) - continue; - - if(best == -1 || masterserver_info[i].count < masterserver_info[best].count) - best = i; - } - - /* server chosen */ - register_registered_server = best; - if(register_registered_server == -1) - { - dbg_msg("register", "WARNING: No master servers. Retrying in 60 seconds"); - register_new_state(REGISTERSTATE_ERROR); - } - else - { - dbg_msg("register", "choosen '%s' as master, sending heartbeats", mastersrv_name(register_registered_server)); - masterserver_info[register_registered_server].last_send = 0; - register_new_state(REGISTERSTATE_HEARTBEAT); - } - } - } - else if(register_state == REGISTERSTATE_HEARTBEAT) - { - /* check if we should send heartbeat */ - if(now > masterserver_info[register_registered_server].last_send+freq*15) - { - masterserver_info[register_registered_server].last_send = now; - register_send_heartbeat(masterserver_info[register_registered_server].addr); - } - - if(now > register_state_start+freq*60) - { - dbg_msg("register", "WARNING: Master server is not responding, switching master"); - register_new_state(REGISTERSTATE_START); - } - } - else if(register_state == REGISTERSTATE_REGISTERED) - { - if(register_first) - dbg_msg("register", "server registered"); - - register_first = 0; - - /* check if we should send new heartbeat again */ - if(now > register_state_start+freq) - { - if(register_count == 120) /* redo the whole process after 60 minutes to balance out the master servers */ - register_new_state(REGISTERSTATE_START); - else - { - register_count++; - register_new_state(REGISTERSTATE_HEARTBEAT); - } - } - } - else if(register_state == REGISTERSTATE_ERROR) - { - /* check for restart */ - if(now > register_state_start+freq*60) - register_new_state(REGISTERSTATE_START); - } -} - -static void register_got_count(CNetChunk *pChunk) -{ - unsigned char *pData = (unsigned char *)pChunk->m_pData; - int Count = (pData[sizeof(SERVERBROWSE_COUNT)]<<8) | pData[sizeof(SERVERBROWSE_COUNT)+1]; - - for(int i = 0; i < MAX_MASTERSERVERS; i++) - { - if(net_addr_comp(&masterserver_info[i].addr, &pChunk->m_Address) == 0) - { - masterserver_info[i].count = Count; - break; - } - } -} - -int register_process_packet(CNetChunk *pPacket) -{ - if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWCHECK) && - memcmp(pPacket->m_pData, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) - { - register_send_fwcheckresponse(&pPacket->m_Address); - return 1; - } - 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(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(pPacket->m_DataSize == sizeof(SERVERBROWSE_COUNT)+2 && - memcmp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) - { - register_got_count(pPacket); - return 1; - } - - return 0; -} diff --git a/src/engine/server/es_server.cpp b/src/engine/server/es_server.cpp deleted file mode 100644 index cc0e6e0f..00000000 --- a/src/engine/server/es_server.cpp +++ /dev/null @@ -1,1969 +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 - - -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; -} - diff --git a/src/engine/server/register.cpp b/src/engine/server/register.cpp new file mode 100644 index 00000000..959b9288 --- /dev/null +++ b/src/engine/server/register.cpp @@ -0,0 +1,264 @@ +#include <base/system.h> +#include <engine/shared/network.h> +#include <engine/shared/config.h> +#include <engine/shared/engine.h> +#include <engine/masterserver.h> + +#include <mastersrv/mastersrv.h> + +#include "register.h" + +CRegister::CRegister() +{ + m_pNetServer = 0; + m_pMasterServer = 0; + + m_RegisterState = REGISTERSTATE_START; + m_RegisterStateStart = 0; + m_RegisterFirst = 1; + m_RegisterCount = 0; + + mem_zero(m_aMasterserverInfo, sizeof(m_aMasterserverInfo)); + m_RegisterRegisteredServer = -1; +} + +void CRegister::RegisterNewState(int State) +{ + m_RegisterState = State; + m_RegisterStateStart = time_get(); +} + +void CRegister::RegisterSendFwcheckresponse(NETADDR *pAddr) +{ + 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); +} + +void CRegister::RegisterSendHeartbeat(NETADDR Addr) +{ + static unsigned char aData[sizeof(SERVERBROWSE_HEARTBEAT) + 2]; + unsigned short Port = g_Config.m_SvPort; + CNetChunk Packet; + + mem_copy(aData, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)); + + Packet.m_ClientID = -1; + Packet.m_Address = Addr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = sizeof(SERVERBROWSE_HEARTBEAT) + 2; + Packet.m_pData = &aData; + + // supply the set port that the master can use if it has problems + if(g_Config.m_SvExternalPort) + Port = g_Config.m_SvExternalPort; + aData[sizeof(SERVERBROWSE_HEARTBEAT)] = Port >> 8; + aData[sizeof(SERVERBROWSE_HEARTBEAT)+1] = Port&0xff; + m_pNetServer->Send(&Packet); +} + +void CRegister::RegisterSendCountRequest(NETADDR Addr) +{ + 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); +} + +void CRegister::RegisterGotCount(CNetChunk *pChunk) +{ + unsigned char *pData = (unsigned char *)pChunk->m_pData; + int Count = (pData[sizeof(SERVERBROWSE_COUNT)]<<8) | pData[sizeof(SERVERBROWSE_COUNT)+1]; + + for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + { + if(net_addr_comp(&m_aMasterserverInfo[i].m_Addr, &pChunk->m_Address) == 0) + { + m_aMasterserverInfo[i].m_Count = Count; + break; + } + } +} + +void CRegister::Init(CNetServer *pNetServer, IEngineMasterServer *pMasterServer) +{ + m_pNetServer = pNetServer; + m_pMasterServer = pMasterServer; +} + +void CRegister::RegisterUpdate() +{ + int64 Now = time_get(); + int64 Freq = time_freq(); + + if(!g_Config.m_SvRegister) + return; + + m_pMasterServer->Update(); + + if(m_RegisterState == REGISTERSTATE_START) + { + m_RegisterCount = 0; + m_RegisterFirst = 1; + RegisterNewState(REGISTERSTATE_UPDATE_ADDRS); + m_pMasterServer->RefreshAddresses(); + dbg_msg("register", "refreshing ip addresses"); + } + else if(m_RegisterState == REGISTERSTATE_UPDATE_ADDRS) + { + m_RegisterRegisteredServer = -1; + + if(!m_pMasterServer->IsRefreshing()) + { + int i; + for(i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + { + NETADDR addr = m_pMasterServer->GetAddr(i); + m_aMasterserverInfo[i].m_Addr = addr; + m_aMasterserverInfo[i].m_Count = 0; + + if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3]) + m_aMasterserverInfo[i].m_Valid = 0; + else + { + m_aMasterserverInfo[i].m_Valid = 1; + m_aMasterserverInfo[i].m_Count = -1; + m_aMasterserverInfo[i].m_LastSend = 0; + } + } + + dbg_msg("register", "fetching server counts"); + RegisterNewState(REGISTERSTATE_QUERY_COUNT); + } + } + else if(m_RegisterState == REGISTERSTATE_QUERY_COUNT) + { + int Left = 0; + for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + { + if(!m_aMasterserverInfo[i].m_Valid) + continue; + + if(m_aMasterserverInfo[i].m_Count == -1) + { + Left++; + if(m_aMasterserverInfo[i].m_LastSend+Freq < Now) + { + m_aMasterserverInfo[i].m_LastSend = Now; + RegisterSendCountRequest(m_aMasterserverInfo[i].m_Addr); + } + } + } + + // check if we are done or timed out + if(Left == 0 || Now > m_RegisterStateStart+Freq*3) + { + // choose server + int Best = -1; + int i; + for(i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + { + if(!m_aMasterserverInfo[i].m_Valid || m_aMasterserverInfo[i].m_Count == -1) + continue; + + if(Best == -1 || m_aMasterserverInfo[i].m_Count < m_aMasterserverInfo[Best].m_Count) + Best = i; + } + + // server chosen + m_RegisterRegisteredServer = Best; + if(m_RegisterRegisteredServer == -1) + { + dbg_msg("register", "WARNING: No master servers. Retrying in 60 seconds"); + RegisterNewState(REGISTERSTATE_ERROR); + } + else + { + dbg_msg("register", "choosen '%s' as master, sending heartbeats", m_pMasterServer->GetName(m_RegisterRegisteredServer)); + m_aMasterserverInfo[m_RegisterRegisteredServer].m_LastSend = 0; + RegisterNewState(REGISTERSTATE_HEARTBEAT); + } + } + } + else if(m_RegisterState == REGISTERSTATE_HEARTBEAT) + { + // check if we should send heartbeat + if(Now > m_aMasterserverInfo[m_RegisterRegisteredServer].m_LastSend+Freq*15) + { + m_aMasterserverInfo[m_RegisterRegisteredServer].m_LastSend = Now; + RegisterSendHeartbeat(m_aMasterserverInfo[m_RegisterRegisteredServer].m_Addr); + } + + if(Now > m_RegisterStateStart+Freq*60) + { + dbg_msg("register", "WARNING: Master server is not responding, switching master"); + RegisterNewState(REGISTERSTATE_START); + } + } + else if(m_RegisterState == REGISTERSTATE_REGISTERED) + { + if(m_RegisterFirst) + dbg_msg("register", "server registered"); + + m_RegisterFirst = 0; + + // check if we should send new heartbeat again + if(Now > m_RegisterStateStart+Freq) + { + if(m_RegisterCount == 120) // redo the whole process after 60 minutes to balance out the master servers + RegisterNewState(REGISTERSTATE_START); + else + { + m_RegisterCount++; + RegisterNewState(REGISTERSTATE_HEARTBEAT); + } + } + } + else if(m_RegisterState == REGISTERSTATE_ERROR) + { + // check for restart + if(Now > m_RegisterStateStart+Freq*60) + RegisterNewState(REGISTERSTATE_START); + } +} + +int CRegister::RegisterProcessPacket(CNetChunk *pPacket) +{ + if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWCHECK) && + mem_comp(pPacket->m_pData, SERVERBROWSE_FWCHECK, sizeof(SERVERBROWSE_FWCHECK)) == 0) + { + RegisterSendFwcheckresponse(&pPacket->m_Address); + return 1; + } + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWOK) && + mem_comp(pPacket->m_pData, SERVERBROWSE_FWOK, sizeof(SERVERBROWSE_FWOK)) == 0) + { + if(m_RegisterFirst) + dbg_msg("register", "no firewall/nat problems detected"); + RegisterNewState(REGISTERSTATE_REGISTERED); + return 1; + } + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_FWERROR) && + mem_comp(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.", g_Config.m_SvPort); + RegisterNewState(REGISTERSTATE_ERROR); + return 1; + } + else if(pPacket->m_DataSize == sizeof(SERVERBROWSE_COUNT)+2 && + mem_comp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) + { + RegisterGotCount(pPacket); + return 1; + } + + return 0; +} diff --git a/src/engine/server/register.h b/src/engine/server/register.h new file mode 100644 index 00000000..a800ec1e --- /dev/null +++ b/src/engine/server/register.h @@ -0,0 +1,48 @@ +#ifndef ENGINE_SERVER_REGISTER_H +#define ENGINE_SERVER_REGISTER_H + +class CRegister +{ + enum + { + REGISTERSTATE_START=0, + REGISTERSTATE_UPDATE_ADDRS, + REGISTERSTATE_QUERY_COUNT, + REGISTERSTATE_HEARTBEAT, + REGISTERSTATE_REGISTERED, + REGISTERSTATE_ERROR + }; + + struct CMasterserverInfo + { + NETADDR m_Addr; + int m_Count; + int m_Valid; + int64 m_LastSend; + }; + + class CNetServer *m_pNetServer; + class IEngineMasterServer *m_pMasterServer; + + int m_RegisterState; + int64 m_RegisterStateStart; + int m_RegisterFirst; + int m_RegisterCount; + + class CMasterserverInfo m_aMasterserverInfo[IMasterServer::MAX_MASTERSERVERS]; + int m_RegisterRegisteredServer; + + void RegisterNewState(int State); + void RegisterSendFwcheckresponse(NETADDR *pAddr); + void RegisterSendHeartbeat(NETADDR Addr); + void RegisterSendCountRequest(NETADDR Addr); + void RegisterGotCount(class CNetChunk *pChunk); + +public: + CRegister(); + void Init(class CNetServer *pNetServer, class IEngineMasterServer *pMasterServer); + void RegisterUpdate(); + int RegisterProcessPacket(class CNetChunk *pPacket); +}; + +#endif diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp new file mode 100644 index 00000000..45cec1e4 --- /dev/null +++ b/src/engine/server/server.cpp @@ -0,0 +1,1400 @@ +// copyright (c) 2007 magnus auvinen, see licence.txt for more info + +#include <base/system.h> + +#include <engine/shared/config.h> +#include <engine/shared/engine.h> + +#include <engine/shared/protocol.h> +#include <engine/shared/snapshot.h> + +#include <engine/shared/compression.h> + +#include <engine/shared/network.h> +#include <engine/shared/config.h> +#include <engine/shared/packer.h> +#include <engine/shared/datafile.h> +#include <engine/shared/demorec.h> + +#include <engine/server.h> +#include <engine/map.h> +#include <engine/console.h> +#include <engine/storage.h> +#include <engine/masterserver.h> +#include <engine/config.h> + +#include <mastersrv/mastersrv.h> + +#include "register.h" +#include "server.h" + +#if defined(CONF_FAMILY_WINDOWS) + #define _WIN32_WINNT 0x0500 + #define NOGDI + #include <windows.h> +#endif + +static const char *StrLtrim(const char *pStr) +{ + while(*pStr && *pStr <= 32) + pStr++; + return pStr; +} + +static void StrRtrim(char *pStr) +{ + int i = str_length(pStr); + while(i >= 0) + { + if(pStr[i] > 32) + break; + pStr[i] = 0; + i--; + } +} + + +static int StrAllnum(const char *pStr) +{ + while(*pStr) + { + if(!(*pStr >= '0' && *pStr <= '9')) + return 0; + pStr++; + } + return 1; +} + +CSnapIDPool::CSnapIDPool() +{ + Reset(); +} + +void CSnapIDPool::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 CSnapIDPool::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 CSnapIDPool::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 CSnapIDPool::TimeoutIDs() +{ + // process timed ids + while(m_FirstTimed != -1) + RemoveFirstTimeout(); +} + +void CSnapIDPool::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; + } +} + +void CServer::CClient::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; +} + +CServer::CServer() : m_DemoRecorder(&m_SnapshotDelta) +{ + m_TickSpeed = SERVER_TICK_SPEED; + + m_pGameServer = 0; + + m_CurrentGameTick = 0; + m_RunServer = 1; + + mem_zero(m_aBrowseinfoGametype, sizeof(m_aBrowseinfoGametype)); + m_BrowseinfoProgression = -1; + + m_pCurrentMapData = 0; + m_CurrentMapSize = 0; + + Init(); +} + + +int CServer::TrySetClientName(int ClientID, const char *pName) +{ + char aTrimmedName[64]; + + // trim the name + str_copy(aTrimmedName, StrLtrim(pName), sizeof(aTrimmedName)); + StrRtrim(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(int i = 0; i < MAX_CLIENTS; i++) + if(i != ClientID && m_aClients[i].m_State >= CClient::STATE_READY) + { + if(str_comp(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 CServer::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 CServer::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 CServer::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 CServer::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 CServer::Tick() +{ + return m_CurrentGameTick; +}*/ + +int64 CServer::TickStartTime(int Tick) +{ + return m_GameStartTime + (time_freq()*Tick)/SERVER_TICK_SPEED; +} + +/*int CServer::TickSpeed() +{ + return SERVER_TICK_SPEED; +}*/ + +int CServer::Init() +{ + for(int 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_CurrentGameTick = 0; + + return 0; +} + +int CServer::GetClientInfo(int ClientID, CClientInfo *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->m_pName = m_aClients[ClientID].m_aName; + pInfo->m_Latency = m_aClients[ClientID].m_Latency; + return 1; + } + return 0; +} + +void CServer::GetClientIP(int ClientID, char *pIPString, int Size) +{ + if(ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME) + { + NETADDR Addr = m_NetServer.ClientAddr(ClientID); + str_format(pIPString, Size, "%d.%d.%d.%d", Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]); + } +} + + +int *CServer::LatestInput(int ClientId, int *size) +{ + if(ClientId < 0 || ClientId >= MAX_CLIENTS || m_aClients[ClientId].m_State < CServer::CClient::STATE_READY) + return 0; + return m_aClients[ClientId].m_LatestInput.m_aData; +} + +const char *CServer::ClientName(int ClientId) +{ + if(ClientId < 0 || ClientId >= MAX_CLIENTS || m_aClients[ClientId].m_State < CServer::CClient::STATE_READY) + return "(invalid client)"; + return m_aClients[ClientId].m_aName; +} + +bool CServer::ClientIngame(int ClientID) +{ + return ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME; +} + +int CServer::SendMsg(CMsgPacker *pMsg, int Flags, int ClientId) +{ + return SendMsgEx(pMsg, Flags, ClientId, false); +} + +int CServer::SendMsgEx(CMsgPacker *pMsg, int Flags, int ClientID, bool System) +{ + CNetChunk Packet; + if(!pMsg) + return -1; + + mem_zero(&Packet, sizeof(CNetChunk)); + + Packet.m_ClientID = ClientID; + Packet.m_pData = pMsg->Data(); + Packet.m_DataSize = pMsg->Size(); + + // HACK: modify the message id in the packet and store the system flag + *((unsigned char*)Packet.m_pData) <<= 1; + if(System) + *((unsigned char*)Packet.m_pData) |= 1; + + if(Flags&MSGFLAG_VITAL) + Packet.m_Flags |= NETSENDFLAG_VITAL; + if(Flags&MSGFLAG_FLUSH) + Packet.m_Flags |= NETSENDFLAG_FLUSH; + + // write message to demo recorder + if(!(Flags&MSGFLAG_NORECORD)) + m_DemoRecorder.RecordMessage(pMsg->Data(), pMsg->Size()); + + if(!(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 CServer::DoSnapshot() +{ + GameServer()->OnPreSnap(); + + // create snapshot for demo recording + if(m_DemoRecorder.IsRecording()) + { + char aData[CSnapshot::MAX_SIZE]; + int SnapshotSize; + + // build snap and possibly add some messages + m_SnapshotBuilder.Init(); + GameServer()->OnSnap(-1); + SnapshotSize = m_SnapshotBuilder.Finish(aData); + + // write snapshot + m_DemoRecorder.RecordSnapshot(Tick(), aData, SnapshotSize); + } + + // create snapshots for all clients + for(int 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 aData[CSnapshot::MAX_SIZE]; + char aDeltaData[CSnapshot::MAX_SIZE]; + char aCompData[CSnapshot::MAX_SIZE]; + int SnapshotSize; + int Crc; + static CSnapshot EmptySnap; + CSnapshot *pDeltashot = &EmptySnap; + int DeltashotSize; + int DeltaTick = -1; + int DeltaSize; + + m_SnapshotBuilder.Init(); + + GameServer()->OnSnap(i); + + // finish snapshot + SnapshotSize = m_SnapshotBuilder.Finish(aData); + Crc = ((CSnapshot*)aData)->Crc(); + + // remove old snapshos + // keep 3 seconds worth of snapshots + m_aClients[i].m_Snapshots.PurgeUntil(m_CurrentGameTick-SERVER_TICK_SPEED*3); + + // save it the snapshot + m_aClients[i].m_Snapshots.Add(m_CurrentGameTick, time_get(), SnapshotSize, aData, 0); + + // find snapshot that we can preform delta against + EmptySnap.Clear(); + + { + DeltashotSize = m_aClients[i].m_Snapshots.Get(m_aClients[i].m_LastAckedSnapshot, 0, &pDeltashot, 0); + if(DeltashotSize >= 0) + DeltaTick = 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 + DeltaSize = m_SnapshotDelta.CreateDelta(pDeltashot, (CSnapshot*)aData, aDeltaData); + + if(DeltaSize) + { + // compress it + int SnapshotSize; + const int MaxSize = MAX_SNAPSHOT_PACKSIZE; + int NumPackets; + + SnapshotSize = CVariableInt::Compress(aDeltaData, DeltaSize, aCompData); + NumPackets = (SnapshotSize+MaxSize-1)/MaxSize; + + for(int n = 0, Left = SnapshotSize; Left; n++) + { + int Chunk = Left < MaxSize ? Left : MaxSize; + Left -= Chunk; + + if(NumPackets == 1) + { + CMsgPacker Msg(NETMSG_SNAPSINGLE); + Msg.AddInt(m_CurrentGameTick); + Msg.AddInt(m_CurrentGameTick-DeltaTick); + Msg.AddInt(Crc); + Msg.AddInt(Chunk); + Msg.AddRaw(&aCompData[n*MaxSize], Chunk); + SendMsgEx(&Msg, MSGFLAG_FLUSH, i, true); + } + else + { + CMsgPacker Msg(NETMSG_SNAP); + Msg.AddInt(m_CurrentGameTick); + Msg.AddInt(m_CurrentGameTick-DeltaTick); + Msg.AddInt(NumPackets); + Msg.AddInt(n); + Msg.AddInt(Crc); + Msg.AddInt(Chunk); + Msg.AddRaw(&aCompData[n*MaxSize], Chunk); + SendMsgEx(&Msg, MSGFLAG_FLUSH, i, true); + } + } + } + else + { + CMsgPacker Msg(NETMSG_SNAPEMPTY); + Msg.AddInt(m_CurrentGameTick); + Msg.AddInt(m_CurrentGameTick-DeltaTick); + SendMsgEx(&Msg, MSGFLAG_FLUSH, i, true); + } + } + } + + GameServer()->OnPostSnap(); +} + + +int CServer::NewClientCallback(int ClientId, void *pUser) +{ + CServer *pThis = (CServer *)pUser; + pThis->m_aClients[ClientId].m_State = CClient::STATE_AUTH; + pThis->m_aClients[ClientId].m_aName[0] = 0; + pThis->m_aClients[ClientId].m_aClan[0] = 0; + pThis->m_aClients[ClientId].m_Authed = 0; + pThis->m_aClients[ClientId].Reset(); + return 0; +} + +int CServer::DelClientCallback(int ClientId, void *pUser) +{ + CServer *pThis = (CServer *)pUser; + + // notify the mod about the drop + if(pThis->m_aClients[ClientId].m_State >= CClient::STATE_READY) + pThis->GameServer()->OnClientDrop(ClientId); + + pThis->m_aClients[ClientId].m_State = CClient::STATE_EMPTY; + pThis->m_aClients[ClientId].m_aName[0] = 0; + pThis->m_aClients[ClientId].m_aClan[0] = 0; + pThis->m_aClients[ClientId].m_Authed = 0; + pThis->m_aClients[ClientId].m_Snapshots.PurgeAll(); + return 0; +} + +void CServer::SendMap(int ClientId) +{ + CMsgPacker Msg(NETMSG_MAP_CHANGE); + Msg.AddString(g_Config.m_SvMap, 0); + Msg.AddInt(m_CurrentMapCrc); + SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientId, true); +} + +void CServer::SendRconLine(int ClientId, const char *pLine) +{ + CMsgPacker Msg(NETMSG_RCON_LINE); + Msg.AddString(pLine, 512); + SendMsgEx(&Msg, MSGFLAG_VITAL, ClientId, true); +} + +void CServer::SendRconLineAuthed(const char *pLine, void *pUser) +{ + CServer *pThis = (CServer *)pUser; + static volatile int ReentryGuard = 0; + int i; + + if(ReentryGuard) return; + ReentryGuard++; + + 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); + } + + ReentryGuard--; +} + +void CServer::ProcessClientPacket(CNetChunk *pPacket) +{ + int ClientId = pPacket->m_ClientID; + NETADDR Addr; + CUnpacker Unpacker; + Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize); + + // unpack msgid and system flag + int Msg = Unpacker.GetInt(); + int Sys = Msg&1; + Msg >>= 1; + + if(Unpacker.Error()) + return; + + if(m_aClients[ClientId].m_State == CClient::STATE_AUTH) + { + if(Sys && Msg == NETMSG_INFO) + { + char aVersion[64]; + const char *pPassword; + str_copy(aVersion, Unpacker.GetString(), 64); + if(str_comp(aVersion, GameServer()->NetVersion()) != 0) + { + // OH FUCK! wrong version, drop him + char aReason[256]; + str_format(aReason, sizeof(aReason), "wrong version. server is running '%s' and client '%s'.", GameServer()->NetVersion(), aVersion); + m_NetServer.Drop(ClientId, aReason); + return; + } + + str_copy(m_aClients[ClientId].m_aName, Unpacker.GetString(), MAX_NAME_LENGTH); + str_copy(m_aClients[ClientId].m_aClan, Unpacker.GetString(), MAX_CLANNAME_LENGTH); + pPassword = Unpacker.GetString(); + + if(g_Config.m_Password[0] != 0 && str_comp(g_Config.m_Password, pPassword) != 0) + { + // wrong password + m_NetServer.Drop(ClientId, "wrong password"); + return; + } + + m_aClients[ClientId].m_State = CClient::STATE_CONNECTING; + SendMap(ClientId); + } + } + else + { + if(Sys) + { + // system message + if(Msg == NETMSG_REQUEST_MAP_DATA) + { + int Chunk = Unpacker.GetInt(); + int ChunkSize = 1024-128; + int Offset = Chunk * ChunkSize; + int Last = 0; + + // drop faulty map data requests + if(Chunk < 0 || Offset > m_CurrentMapSize) + return; + + if(Offset+ChunkSize >= m_CurrentMapSize) + { + ChunkSize = m_CurrentMapSize-Offset; + if(ChunkSize < 0) + ChunkSize = 0; + Last = 1; + } + + CMsgPacker Msg(NETMSG_MAP_DATA); + Msg.AddInt(Last); + Msg.AddInt(m_CurrentMapSize); + Msg.AddInt(ChunkSize); + Msg.AddRaw(&m_pCurrentMapData[Offset], ChunkSize); + SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientId, true); + + if(g_Config.m_Debug) + dbg_msg("server", "sending chunk %d with size %d", Chunk, ChunkSize); + } + else if(Msg == NETMSG_READY) + { + if(m_aClients[ClientId].m_State == CClient::STATE_CONNECTING) + { + Addr = m_NetServer.ClientAddr(ClientId); + + dbg_msg("server", "player is ready. ClientId=%x ip=%d.%d.%d.%d", + ClientId, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]); + m_aClients[ClientId].m_State = CClient::STATE_READY; + GameServer()->OnClientConnected(ClientId); + } + } + else if(Msg == NETMSG_ENTERGAME) + { + if(m_aClients[ClientId].m_State == CClient::STATE_READY) + { + Addr = m_NetServer.ClientAddr(ClientId); + + dbg_msg("server", "player has entered the game. ClientId=%x ip=%d.%d.%d.%d", + ClientId, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]); + m_aClients[ClientId].m_State = CClient::STATE_INGAME; + GameServer()->OnClientEnter(ClientId); + } + } + else if(Msg == NETMSG_INPUT) + { + CClient::CInput *pInput; + int64 TagTime; + + m_aClients[ClientId].m_LastAckedSnapshot = Unpacker.GetInt(); + int IntendedTick = Unpacker.GetInt(); + int Size = Unpacker.GetInt(); + + // check for errors + if(Unpacker.Error() || Size/4 > MAX_INPUT_SIZE) + return; + + if(m_aClients[ClientId].m_LastAckedSnapshot > 0) + m_aClients[ClientId].m_SnapRate = CClient::SNAPRATE_FULL; + + if(m_aClients[ClientId].m_Snapshots.Get(m_aClients[ClientId].m_LastAckedSnapshot, &TagTime, 0, 0) >= 0) + m_aClients[ClientId].m_Latency = (int)(((time_get()-TagTime)*1000)/time_freq()); + + // add message to report the input timing + // skip packets that are old + if(IntendedTick > m_aClients[ClientId].m_LastInputTick) + { + int TimeLeft = ((TickStartTime(IntendedTick)-time_get())*1000) / time_freq(); + + CMsgPacker Msg(NETMSG_INPUTTIMING); + Msg.AddInt(IntendedTick); + Msg.AddInt(TimeLeft); + SendMsgEx(&Msg, 0, ClientId, true); + } + + m_aClients[ClientId].m_LastInputTick = IntendedTick; + + pInput = &m_aClients[ClientId].m_aInputs[m_aClients[ClientId].m_CurrentInput]; + + if(IntendedTick <= Tick()) + IntendedTick = Tick()+1; + + pInput->m_GameTick = IntendedTick; + + for(int i = 0; i < Size/4; i++) + pInput->m_aData[i] = Unpacker.GetInt(); + + mem_copy(m_aClients[ClientId].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE*sizeof(int)); + + m_aClients[ClientId].m_CurrentInput++; + m_aClients[ClientId].m_CurrentInput %= 200; + + // call the mod with the fresh input data + if(m_aClients[ClientId].m_State == CClient::STATE_INGAME) + GameServer()->OnClientDirectInput(ClientId, m_aClients[ClientId].m_LatestInput.m_aData); + } + else if(Msg == NETMSG_RCON_CMD) + { + const char *pCmd = Unpacker.GetString(); + + if(Unpacker.Error() == 0 && m_aClients[ClientId].m_Authed) + { + dbg_msg("server", "ClientId=%d rcon='%s'", ClientId, pCmd); + Console()->ExecuteLine(pCmd); + } + } + else if(Msg == NETMSG_RCON_AUTH) + { + const char *pPw; + Unpacker.GetString(); // login name, not used + pPw = Unpacker.GetString(); + + if(Unpacker.Error() == 0) + { + if(g_Config.m_SvRconPassword[0] == 0) + { + SendRconLine(ClientId, "No rcon password set on server. Set sv_rcon_password to enable the remote console."); + } + else if(str_comp(pPw, g_Config.m_SvRconPassword) == 0) + { + CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS); + Msg.AddInt(1); + SendMsgEx(&Msg, MSGFLAG_VITAL, ClientId, true); + + m_aClients[ClientId].m_Authed = 1; + SendRconLine(ClientId, "Authentication successful. Remote console access granted."); + dbg_msg("server", "ClientId=%d authed", ClientId); + } + else + { + SendRconLine(ClientId, "Wrong password."); + } + } + } + else if(Msg == NETMSG_PING) + { + CMsgPacker Msg(NETMSG_PING_REPLY); + SendMsgEx(&Msg, 0, ClientId, true); + } + else + { + char aHex[] = "0123456789ABCDEF"; + char aBuf[512]; + + for(int b = 0; b < pPacket->m_DataSize && b < 32; b++) + { + aBuf[b*3] = aHex[((const unsigned char *)pPacket->m_pData)[b]>>4]; + aBuf[b*3+1] = aHex[((const unsigned char *)pPacket->m_pData)[b]&0xf]; + aBuf[b*3+2] = ' '; + aBuf[b*3+3] = 0; + } + + dbg_msg("server", "strange message ClientId=%d msg=%d data_size=%d", ClientId, Msg, pPacket->m_DataSize); + dbg_msg("server", "%s", aBuf); + + } + } + else + { + // game message + if(m_aClients[ClientId].m_State >= CClient::STATE_READY) + GameServer()->OnMessage(Msg, &Unpacker, ClientId); + } + } +} + +void CServer::SendServerInfo(NETADDR *pAddr, int Token) +{ + CNetChunk Packet; + CPacker p; + char aBuf[128]; + + // count the players + int PlayerCount = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + PlayerCount++; + } + + p.Reset(); + + if(Token >= 0) + { + // new token based format + p.AddRaw(SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); + str_format(aBuf, sizeof(aBuf), "%d", Token); + p.AddString(aBuf, 6); + } + else + { + // old format + p.AddRaw(SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)); + } + + p.AddString(GameServer()->Version(), 32); + p.AddString(g_Config.m_SvName, 64); + p.AddString(g_Config.m_SvMap, 32); + + // gametype + p.AddString(m_aBrowseinfoGametype, 16); + + // flags + int i = 0; + if(g_Config.m_Password[0]) // password set + i |= SERVER_FLAG_PASSWORD; + str_format(aBuf, sizeof(aBuf), "%d", i); + p.AddString(aBuf, 2); + + // progression + str_format(aBuf, sizeof(aBuf), "%d", m_BrowseinfoProgression); + p.AddString(aBuf, 4); + + str_format(aBuf, sizeof(aBuf), "%d", PlayerCount); p.AddString(aBuf, 3); // num players + str_format(aBuf, sizeof(aBuf), "%d", m_NetServer.MaxClients()); p.AddString(aBuf, 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(aBuf, sizeof(aBuf), "%d", m_aClients[i].m_Score); p.AddString(aBuf, 6); // player score + } + } + + + Packet.m_ClientID = -1; + Packet.m_Address = *pAddr; + Packet.m_Flags = NETSENDFLAG_CONNLESS; + Packet.m_DataSize = p.Size(); + Packet.m_pData = p.Data(); + m_NetServer.Send(&Packet); +} + +void CServer::UpdateServerInfo() +{ + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + { + NETADDR Addr = m_NetServer.ClientAddr(i); + SendServerInfo(&Addr, -1); // SERVERBROWSE_OLD_INFO + } + } +} + +int CServer::BanAdd(NETADDR Addr, int Seconds) +{ + return m_NetServer.BanAdd(Addr, Seconds); +} + +int CServer::BanRemove(NETADDR Addr) +{ + return m_NetServer.BanRemove(Addr); +} + + +void CServer::PumpNetwork() +{ + CNetChunk Packet; + + m_NetServer.Update(); + + // process packets + while(m_NetServer.Recv(&Packet)) + { + if(Packet.m_ClientID == -1) + { + // stateless + if(!m_Register.RegisterProcessPacket(&Packet)) + { + if(Packet.m_DataSize == sizeof(SERVERBROWSE_GETINFO)+1 && + mem_comp(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) && + mem_comp(Packet.m_pData, SERVERBROWSE_OLD_GETINFO, sizeof(SERVERBROWSE_OLD_GETINFO)) == 0) + { + SendServerInfo(&Packet.m_Address, -1); + } + } + } + else + ProcessClientPacket(&Packet); + } +} + +int CServer::LoadMap(const char *pMapName) +{ + //DATAFILE *df; + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName); + + /*df = datafile_load(buf); + if(!df) + return 0;*/ + + if(!m_pMap->Load(aBuf)) + return 0; + + // stop recording when we change map + m_DemoRecorder.Stop(); + + // reinit snapshot ids + m_IDPool.TimeoutIDs(); + + // get the crc of the map + m_CurrentMapCrc = m_pMap->Crc(); + dbg_msg("server", "%s crc is %08x", aBuf, m_CurrentMapCrc); + + str_copy(m_aCurrentMap, pMapName, sizeof(m_aCurrentMap)); + //map_set(df); + + // load compelate map into memory for download + { + IOHANDLE File = Storage()->OpenFile(aBuf, 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; +} + +void CServer::InitEngine(const char *pAppname) +{ + m_Engine.Init(pAppname); +} + +void CServer::InitRegister(CNetServer *pNetServer, IEngineMasterServer *pMasterServer) +{ + m_Register.Init(pNetServer, pMasterServer); +} + +int CServer::Run() +{ + m_pGameServer = Kernel()->RequestInterface<IGameServer>(); + m_pMap = Kernel()->RequestInterface<IEngineMap>(); + m_pStorage = Kernel()->RequestInterface<IStorage>(); + + //snap_init_id(); + net_init(); + + // + Console()->RegisterPrintCallback(SendRconLineAuthed, this); + + // load map + if(!LoadMap(g_Config.m_SvMap)) + { + dbg_msg("server", "failed to load map. mapname='%s'", g_Config.m_SvMap); + return -1; + } + + // start server + // TODO: IPv6 support + NETADDR BindAddr; + if(g_Config.m_SvBindaddr[0] && net_host_lookup(g_Config.m_SvBindaddr, &BindAddr, NETTYPE_IPV4) == 0) + { + // sweet! + BindAddr.port = g_Config.m_SvPort; + } + else + { + mem_zero(&BindAddr, sizeof(BindAddr)); + BindAddr.port = g_Config.m_SvPort; + } + + + if(!m_NetServer.Open(BindAddr, g_Config.m_SvMaxClients, 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'", g_Config.m_SvName); + + GameServer()->OnInit(); + dbg_msg("server", "version %s", GameServer()->NetVersion()); + + // start game + { + int64 ReportTime = time_get(); + int ReportInterval = 3; + + m_Lastheartbeat = 0; + m_GameStartTime = time_get(); + + if(g_Config.m_Debug) + dbg_msg("server", "baseline memory usage %dk", mem_stats()->allocated/1024); + + while(m_RunServer) + { + int64 t = time_get(); + int NewTicks = 0; + + // load new map TODO: don't poll this + if(str_comp(g_Config.m_SvMap, m_aCurrentMap) != 0 || g_Config.m_SvMapReload) + { + g_Config.m_SvMapReload = 0; + + // load map + if(LoadMap(g_Config.m_SvMap)) + { + // new map loaded + GameServer()->OnShutdown(); + + for(int 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_CurrentGameTick = 0; + Kernel()->ReregisterInterface(GameServer()); + GameServer()->OnInit(); + } + else + { + dbg_msg("server", "failed to load map. mapname='%s'", g_Config.m_SvMap); + str_copy(g_Config.m_SvMap, m_aCurrentMap, sizeof(g_Config.m_SvMap)); + } + } + + while(t > TickStartTime(m_CurrentGameTick+1)) + { + m_CurrentGameTick++; + NewTicks++; + + // apply new input + for(int c = 0; c < MAX_CLIENTS; c++) + { + if(m_aClients[c].m_State == CClient::STATE_EMPTY) + continue; + for(int i = 0; i < 200; i++) + { + if(m_aClients[c].m_aInputs[i].m_GameTick == Tick()) + { + if(m_aClients[c].m_State == CClient::STATE_INGAME) + GameServer()->OnClientPredictedInput(c, m_aClients[c].m_aInputs[i].m_aData); + break; + } + } + } + + GameServer()->OnTick(); + } + + // snap game + if(NewTicks) + { + if(g_Config.m_SvHighBandwidth || (m_CurrentGameTick%2) == 0) + DoSnapshot(); + } + + // master server stuff + m_Register.RegisterUpdate(); + + PumpNetwork(); + + if(ReportTime < time_get()) + { + if(g_Config.m_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); + } + } + // disconnect all clients on shutdown + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(m_aClients[i].m_State != CClient::STATE_EMPTY) + m_NetServer.Drop(i, "server shutdown"); + } + + GameServer()->OnShutdown(); + m_pMap->Unload(); + + if(m_pCurrentMapData) + mem_free(m_pCurrentMapData); + return 0; +} + +void CServer::ConKick(IConsole::IResult *pResult, void *pUser) +{ + ((CServer *)pUser)->Kick(pResult->GetInteger(0), "kicked by console"); +} + +void CServer::ConBan(IConsole::IResult *pResult, void *pUser) +{ + NETADDR Addr; + char aAddrStr[128]; + const char *pStr = pResult->GetString(0); + int Minutes = 30; + + if(pResult->NumArguments() > 1) + Minutes = pResult->GetInteger(1); + + if(net_addr_from_str(&Addr, pStr) == 0) + ((CServer *)pUser)->BanAdd(Addr, Minutes*60); + else if(StrAllnum(pStr)) + { + int ClientId = str_toint(pStr); + + if(ClientId < 0 || ClientId >= MAX_CLIENTS || ((CServer *)pUser)->m_aClients[ClientId].m_State == CClient::STATE_EMPTY) + { + dbg_msg("server", "invalid client id"); + return; + } + + NETADDR Addr = ((CServer *)pUser)->m_NetServer.ClientAddr(ClientId); + ((CServer *)pUser)->BanAdd(Addr, Minutes*60); + } + + Addr.port = 0; + net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); + + if(Minutes) + dbg_msg("server", "banned %s for %d minutes", aAddrStr, Minutes); + else + dbg_msg("server", "banned %s for life", aAddrStr); +} + +void CServer::ConUnban(IConsole::IResult *pResult, void *pUser) +{ + NETADDR Addr; + const char *pStr = pResult->GetString(0); + + if(net_addr_from_str(&Addr, pStr) == 0) + ((CServer *)pUser)->BanRemove(Addr); + else + dbg_msg("server", "invalid network address"); +} + +void CServer::ConBans(IConsole::IResult *pResult, void *pUser) +{ + unsigned Now = time_timestamp(); + char aBuf[1024]; + CServer* pServer = (CServer *)pUser; + + int Num = pServer->m_NetServer.BanNum(); + for(int i = 0; i < Num; i++) + { + CNetServer::CBanInfo Info; + pServer->m_NetServer.BanGet(i, &Info); + NETADDR Addr = Info.m_Addr; + + if(Info.m_Expires == -1) + { + str_format(aBuf, sizeof(aBuf), "#%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; + str_format(aBuf, sizeof(aBuf), "#%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); + } + pServer->Console()->Print(aBuf); + dbg_msg("server", "%s", aBuf); + } + str_format(aBuf, sizeof(aBuf), "%d ban(s)", Num); + pServer->Console()->Print(aBuf); + dbg_msg("server", "%s", aBuf); +} + +void CServer::ConStatus(IConsole::IResult *pResult, void *pUser) +{ + int i; + NETADDR Addr; + char aBuf[1024]; + CServer* pServer = (CServer *)pUser; + + for(i = 0; i < MAX_CLIENTS; i++) + { + if(pServer->m_aClients[i].m_State == CClient::STATE_INGAME) + { + Addr = pServer->m_NetServer.ClientAddr(i); + str_format(aBuf, sizeof(aBuf), "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, + pServer->m_aClients[i].m_aName, pServer->m_aClients[i].m_Score); + pServer->Console()->Print(aBuf); + dbg_msg("server", "%s", aBuf); + } + } +} + +void CServer::ConShutdown(IConsole::IResult *pResult, void *pUser) +{ + ((CServer *)pUser)->m_RunServer = 0; +} + +void CServer::ConRecord(IConsole::IResult *pResult, void *pUser) +{ + char aFilename[512]; + str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pResult->GetString(0)); + ((CServer *)pUser)->m_DemoRecorder.Start(((CServer *)pUser)->Storage(), aFilename, ((CServer *)pUser)->GameServer()->NetVersion(), ((CServer *)pUser)->m_aCurrentMap, ((CServer *)pUser)->m_CurrentMapCrc, "server"); +} + +void CServer::ConStopRecord(IConsole::IResult *pResult, void *pUser) +{ + ((CServer *)pUser)->m_DemoRecorder.Stop(); +} + +void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments()) + ((CServer *)pUserData)->UpdateServerInfo(); +} + +void CServer::RegisterCommands() +{ + m_pConsole = Kernel()->RequestInterface<IConsole>(); + + Console()->Register("kick", "i", CFGFLAG_SERVER, ConKick, this, ""); + Console()->Register("ban", "s?i", CFGFLAG_SERVER, ConBan, this, ""); + Console()->Register("unban", "s", CFGFLAG_SERVER, ConUnban, this, ""); + Console()->Register("bans", "", CFGFLAG_SERVER, ConBans, this, ""); + Console()->Register("status", "", CFGFLAG_SERVER, ConStatus, this, ""); + Console()->Register("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, ""); + + Console()->Register("record", "s", CFGFLAG_SERVER, ConRecord, this, ""); + Console()->Register("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, ""); + + Console()->Chain("sv_name", ConchainSpecialInfoupdate, this); + Console()->Chain("password", ConchainSpecialInfoupdate, this); +} + + +int CServer::SnapNewID() +{ + return m_IDPool.NewID(); +} + +void CServer::SnapFreeID(int ID) +{ + m_IDPool.FreeID(ID); +} + + +void *CServer::SnapNewItem(int Type, int Id, int Size) +{ + dbg_assert(Type >= 0 && Type <=0xffff, "incorrect type"); + dbg_assert(Id >= 0 && Id <=0xffff, "incorrect id"); + return m_SnapshotBuilder.NewItem(Type, Id, Size); +} + +void CServer::SnapSetStaticsize(int ItemType, int Size) +{ + m_SnapshotDelta.SetStaticsize(ItemType, Size); +} + +static CServer *CreateServer() { return new CServer(); } + +int main(int argc, const char **argv) // ignore_convention +{ +#if defined(CONF_FAMILY_WINDOWS) + for(int i = 1; i < argc; i++) // ignore_convention + { + if(str_comp("-s", argv[i]) == 0 || str_comp("--silent", argv[i]) == 0) // ignore_convention + { + ShowWindow(GetConsoleWindow(), SW_HIDE); + break; + } + } +#endif + + // init the engine + dbg_msg("server", "starting..."); + CServer *pServer = CreateServer(); + pServer->InitEngine("Teeworlds"); + + IKernel *pKernel = IKernel::Create(); + + // create the components + IEngineMap *pEngineMap = CreateEngineMap(); + IGameServer *pGameServer = CreateGameServer(); + IConsole *pConsole = CreateConsole(); + IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer(); + IStorage *pStorage = CreateStorage("Teeworlds", argv[0]); // ignore_convention + IConfig *pConfig = CreateConfig(); + + pServer->InitRegister(&pServer->m_NetServer, pEngineMasterServer); + + { + bool RegisterFail = false; + + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pServer); // register as both + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMap*>(pEngineMap)); // register as both + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMap*>(pEngineMap)); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pGameServer); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfig); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMasterServer*>(pEngineMasterServer)); // register as both + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMasterServer*>(pEngineMasterServer)); + + if(RegisterFail) + return -1; + } + + pConfig->Init(); + pEngineMasterServer->Init(pServer->Engine()); + pEngineMasterServer->Load(); + + // register all console commands + pServer->RegisterCommands(); + pGameServer->OnConsoleInit(); + + // execute autoexec file + pConsole->ExecuteFile("autoexec.cfg"); + + // parse the command line arguments + if(argc > 1) // ignore_convention + pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention + + // run the server + pServer->Run(); + + // free + delete pServer; + delete pKernel; + delete pEngineMap; + delete pGameServer; + delete pConsole; + delete pEngineMasterServer; + delete pStorage; + delete pConfig; + return 0; +} + diff --git a/src/engine/server/server.h b/src/engine/server/server.h new file mode 100644 index 00000000..6904085a --- /dev/null +++ b/src/engine/server/server.h @@ -0,0 +1,195 @@ +#ifndef ENGINE_SERVER_SERVER_H +#define ENGINE_SERVER_SERVER_H + +#include <engine/server.h> + +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(); + + void Reset(); + void RemoveFirstTimeout(); + int NewID(); + void TimeoutIDs(); + void FreeID(int Id); +}; + +class CServer : public IServer +{ + class IGameServer *m_pGameServer; + class IConsole *m_pConsole; + class IStorage *m_pStorage; +public: + class IGameServer *GameServer() { return m_pGameServer; } + class IConsole *Console() { return m_pConsole; } + class IStorage *Storage() { return m_pStorage; } + class CEngine *Engine() { return &m_Engine; } + + 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(); + }; + + CClient m_aClients[MAX_CLIENTS]; + + CSnapshotDelta m_SnapshotDelta; + CSnapshotBuilder m_SnapshotBuilder; + CSnapIDPool m_IDPool; + CNetServer m_NetServer; + + IEngineMap *m_pMap; + + int64 m_GameStartTime; + //int m_CurrentGameTick; + 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; + + CDemoRecorder m_DemoRecorder; + CEngine m_Engine; + CRegister m_Register; + + CServer(); + + int TrySetClientName(int ClientID, const char *pName); + + virtual void SetClientName(int ClientID, const char *pName); + virtual void SetClientScore(int ClientID, int Score); + virtual void SetBrowseInfo(const char *pGameType, int Progression); + + void Kick(int ClientID, const char *pReason); + + //int Tick() + int64 TickStartTime(int Tick); + //int TickSpeed() + + int Init(); + + int GetClientInfo(int ClientID, CClientInfo *pInfo); + void GetClientIP(int ClientID, char *pIPString, int Size); + const char *ClientName(int ClientId); + bool ClientIngame(int ClientID); + + int *LatestInput(int ClientId, int *size); + + virtual int SendMsg(CMsgPacker *pMsg, int Flags, int ClientId); + int SendMsgEx(CMsgPacker *pMsg, int Flags, int ClientID, bool System); + + void DoSnapshot(); + + static int NewClientCallback(int ClientId, void *pUser); + static int DelClientCallback(int ClientId, void *pUser); + + void SendMap(int ClientId); + void SendRconLine(int ClientId, const char *pLine); + static void SendRconLineAuthed(const char *pLine, void *pUser); + + void ProcessClientPacket(CNetChunk *pPacket); + + void SendServerInfo(NETADDR *pAddr, int Token); + void UpdateServerInfo(); + + int BanAdd(NETADDR Addr, int Seconds); + int BanRemove(NETADDR Addr); + + + void PumpNetwork(); + + int LoadMap(const char *pMapName); + + void InitEngine(const char *pAppname); + void InitRegister(CNetServer *pNetServer, IEngineMasterServer *pMasterServer); + int Run(); + + static void ConKick(IConsole::IResult *pResult, void *pUser); + static void ConBan(IConsole::IResult *pResult, void *pUser); + static void ConUnban(IConsole::IResult *pResult, void *pUser); + static void ConBans(IConsole::IResult *pResult, void *pUser); + static void ConStatus(IConsole::IResult *pResult, void *pUser); + static void ConShutdown(IConsole::IResult *pResult, void *pUser); + static void ConRecord(IConsole::IResult *pResult, void *pUser); + static void ConStopRecord(IConsole::IResult *pResult, void *pUser); + static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + + void RegisterCommands(); + + + virtual int SnapNewID(); + virtual void SnapFreeID(int ID); + virtual void *SnapNewItem(int Type, int Id, int Size); + void SnapSetStaticsize(int ItemType, int Size); +}; + +#endif |