/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "register.h" #include "server.h" #if defined(CONF_FAMILY_WINDOWS) #define _WIN32_WINNT 0x0501 #define WIN32_LEAN_AND_MEAN #include #endif #include #include #include "../shared/linereader.h" #include static const char SERVER_BANMASTERFILE[] = "banmasters.cfg"; static const char *StrLtrim(const char *pStr) { while(*pStr && *pStr >= 0 && *pStr <= 32) pStr++; return pStr; } static void StrRtrim(char *pStr) { int i = str_length(pStr); while(i >= 0) { if(pStr[i] < 0 || 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"); if(ID == -1) return ID; 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) { if(ID < 0) return; 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; m_pCurrentMapData = 0; m_CurrentMapSize = 0; m_MapReload = 0; m_RconClientID = -1; Init(); } int CServer::TrySetClientName(int ClientID, const char *pName) { char aTrimmedName[64]; // trim the name str_copy(aTrimmedName, StrLtrim(pName), sizeof(aTrimmedName)); StrRtrim(aTrimmedName); // check if new and old name are the same if(m_aClients[ClientID].m_aName[0] && str_comp(m_aClients[ClientID].m_aName, aTrimmedName) == 0) return 0; char aBuf[256]; str_format(aBuf, sizeof(aBuf), "'%s' -> '%s'", pName, aTrimmedName); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); 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::SetClientClan(int ClientID, const char *pClan) { if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY || !pClan) return; str_copy(m_aClients[ClientID].m_aClan, pClan, MAX_CLAN_LENGTH); } void CServer::SetClientCountry(int ClientID, int Country) { if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY) return; m_aClients[ClientID].m_Country = Country; } 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::Kick(int ClientID, const char *pReason) { if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CClient::STATE_EMPTY) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "invalid client id to kick"); return; } else if(m_RconClientID == ClientID) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't kick yourself"); return; } 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_Country = -1; m_aClients[i].m_Snapshots.Init(); } m_CurrentGameTick = 0; m_AnnouncementLastLine = 0; memset(m_aPrevStates, CClient::STATE_EMPTY, MAX_CLIENTS * sizeof(int)); return 0; } int CServer::IsAuthed(int ClientID) { return m_aClients[ClientID].m_Authed; } 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::GetClientAddr(int ClientID, char *pAddrStr, int Size) { if(ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME) { NETADDR Addr = m_NetServer.ClientAddr(ClientID); Addr.port = 0; net_addr_str(&Addr, pAddrStr, Size); } } 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_EMPTY) return "(invalid client)"; if(m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME) return m_aClients[ClientID].m_aName; else return "(connecting client)"; } const char *CServer::ClientClan(int ClientID) { if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) return ""; if(m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME) return m_aClients[ClientID].m_aClan; else return ""; } int CServer::ClientCountry(int ClientID) { if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) return -1; if(m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME) return m_aClients[ClientID].m_Country; else return -1; } 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]; CSnapshot *pData = (CSnapshot*)aData; // Fix compiler warning for strict-aliasing 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(pData); Crc = pData->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, pData, 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, pData, 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_Country = -1; pThis->m_aClients[ClientID].m_Authed = 0; pThis->m_aClients[ClientID].m_AuthTries = 0; memset(&pThis->m_aClients[ClientID].m_Addr, 0, sizeof(NETADDR)); pThis->m_aClients[ClientID].Reset(); return 0; } int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser) { CServer *pThis = (CServer *)pUser; NETADDR Addr = pThis->m_NetServer.ClientAddr(ClientID); char aAddrStr[NETADDR_MAXSTRSIZE]; net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "client dropped. cid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason); pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); // notify the mod about the drop if(pThis->m_aClients[ClientID].m_State >= CClient::STATE_READY) pThis->GameServer()->OnClientDrop(ClientID, pReason); 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_Country = -1; pThis->m_aClients[ClientID].m_Authed = 0; pThis->m_aClients[ClientID].m_AuthTries = 0; pThis->m_aPrevStates[ClientID] = CClient::STATE_EMPTY; memset(&pThis->m_aClients[ClientID].m_Addr, 0, sizeof(NETADDR)); pThis->m_aClients[ClientID].m_Snapshots.PurgeAll(); return 0; } void CServer::SendMap(int ClientID) { CMsgPacker Msg(NETMSG_MAP_CHANGE); Msg.AddString(GetMapName(), 0); Msg.AddInt(m_CurrentMapCrc); Msg.AddInt(m_CurrentMapSize); SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true); } void CServer::SendConnectionReady(int ClientID) { CMsgPacker Msg(NETMSG_CON_READY); 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(Sys) { // system message if(Msg == NETMSG_INFO) { if(m_aClients[ClientID].m_State == CClient::STATE_AUTH) { const char *pVersion = Unpacker.GetString(CUnpacker::SANITIZE_CC); if(str_comp(pVersion, GameServer()->NetVersion()) != 0) { // wrong version char aReason[256]; str_format(aReason, sizeof(aReason), "Wrong version. Server is running '%s' and client '%s'", GameServer()->NetVersion(), pVersion); m_NetServer.Drop(ClientID, aReason); return; } const char *pPassword = Unpacker.GetString(CUnpacker::SANITIZE_CC); if(g_Config.m_Password[0] != 0 && str_comp(g_Config.m_Password, pPassword) != 0) { // wrong password m_NetServer.Drop(ClientID, "Wrong password"); return; } // reserved slot if(ClientID >= (g_Config.m_SvMaxClients - g_Config.m_SvReservedSlots) && g_Config.m_SvReservedSlotsPass[0] != 0 && strcmp(g_Config.m_SvReservedSlotsPass, pPassword) != 0) { m_NetServer.Drop(ClientID, "This server is full"); return; } m_aClients[ClientID].m_State = CClient::STATE_CONNECTING; SendMap(ClientID); } } else 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_CurrentMapCrc); Msg.AddInt(Chunk); Msg.AddInt(ChunkSize); Msg.AddRaw(&m_pCurrentMapData[Offset], ChunkSize); SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true); if(g_Config.m_Debug) { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "sending chunk %d with size %d", Chunk, ChunkSize); Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBuf); } } else if(Msg == NETMSG_READY) { if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTING) { Addr = m_NetServer.ClientAddr(ClientID); char aAddrStr[NETADDR_MAXSTRSIZE]; net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "player is ready. ClientID=%x addr=%s", ClientID, aAddrStr); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); m_aClients[ClientID].m_State = CClient::STATE_READY; GameServer()->OnClientConnected(ClientID); GameServer()->OnSetAuthed(ClientID, m_aClients[ClientID].m_Authed); SendConnectionReady(ClientID); } } else if(Msg == NETMSG_ENTERGAME) { if(m_aClients[ClientID].m_State == CClient::STATE_READY && GameServer()->IsClientReady(ClientID)) { Addr = m_NetServer.ClientAddr(ClientID); m_aClients[ClientID].m_Addr = Addr; char aAddrStr[NETADDR_MAXSTRSIZE]; net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "player has entered the game. ClientID=%x addr=%s", ClientID, aAddrStr); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); 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) { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "ClientID=%d rcon='%s'", ClientID, pCmd); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); m_RconClientID = ClientID; Console()->ExecuteLine(pCmd); m_RconClientID = -1; }*/ if(Unpacker.Error() == 0) { char aBuf[256]; if(m_aClients[ClientID].m_Authed >= 0) { str_format(aBuf, sizeof(aBuf), "'%s' ClientID=%d Level=%d Rcon='%s'", ClientName(ClientID), ClientID, m_aClients[ClientID].m_Authed, pCmd); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); m_RconClientID = ClientID; RconResponseInfo Info; Info.m_Server = this; Info.m_ClientID = ClientID; Console()->ExecuteLine(pCmd, ClientID, m_aClients[ClientID].m_Authed, SendRconResponse, &Info); m_RconClientID = -1; } } } else if(Msg == NETMSG_RCON_AUTH) { const char *pPw; Unpacker.GetString(); // login name, not used pPw = Unpacker.GetString(CUnpacker::SANITIZE_CC); if(Unpacker.Error() == 0) CheckPass(ClientID,pPw); /*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."); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "ClientID=%d authed", ClientID); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } else if(g_Config.m_SvRconMaxTries) { m_aClients[ClientID].m_AuthTries++; char aBuf[128]; str_format(aBuf, sizeof(aBuf), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, g_Config.m_SvRconMaxTries); SendRconLine(ClientID, aBuf); if(m_aClients[ClientID].m_AuthTries >= g_Config.m_SvRconMaxTries) { if(!g_Config.m_SvRconBantime) m_NetServer.Drop(ClientID, "Too many remote console authentication tries"); else { NETADDR Addr = m_NetServer.ClientAddr(ClientID); BanAdd(Addr, g_Config.m_SvRconBantime*60, "Too many remote console authentication tries"); } } } else { SendRconLine(ClientID, "Wrong password."); } }*/ } else if(Msg == NETMSG_PING) { CMsgPacker Msg(NETMSG_PING_REPLY); SendMsgEx(&Msg, 0, ClientID, true); } else { if(g_Config.m_Debug) { 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; } char aBufMsg[256]; str_format(aBufMsg, sizeof(aBufMsg), "strange message ClientID=%d msg=%d data_size=%d", ClientID, Msg, pPacket->m_DataSize); Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBufMsg); Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", 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, ClientCount = 0; for(int i = 0; i < MAX_CLIENTS; i++) { if(m_aClients[i].m_State != CClient::STATE_EMPTY) { if(GameServer()->IsClientPlayer(i)) PlayerCount++; ClientCount++; } } p.Reset(); p.AddRaw(SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)); str_format(aBuf, sizeof(aBuf), "%d", Token); p.AddString(aBuf, 6); p.AddString(GameServer()->Version(), 32); p.AddString(g_Config.m_SvName, 64); p.AddString(GetMapName(), 32); // gametype p.AddString(GameServer()->GameType(), 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); str_format(aBuf, sizeof(aBuf), "%d", PlayerCount); p.AddString(aBuf, 3); // num players str_format(aBuf, sizeof(aBuf), "%d", m_NetServer.MaxClients()-g_Config.m_SvSpectatorSlots-g_Config.m_SvReservedSlots); p.AddString(aBuf, 3); // max players str_format(aBuf, sizeof(aBuf), "%d", ClientCount); p.AddString(aBuf, 3); // num clients str_format(aBuf, sizeof(aBuf), "%d", m_NetServer.MaxClients()); p.AddString(aBuf, 3); // max clients for(i = 0; i < MAX_CLIENTS; i++) { if(m_aClients[i].m_State != CClient::STATE_EMPTY) { p.AddString(ClientName(i), MAX_NAME_LENGTH); // client name p.AddString(ClientClan(i), MAX_CLAN_LENGTH); // client clan str_format(aBuf, sizeof(aBuf), "%d", m_aClients[i].m_Country); p.AddString(aBuf, 6); // client country str_format(aBuf, sizeof(aBuf), "%d", m_aClients[i].m_Score); p.AddString(aBuf, 6); // client score str_format(aBuf, sizeof(aBuf), "%d", GameServer()->IsClientPlayer(i)?1:0); p.AddString(aBuf, 2); // is player? } } 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); } } } int CServer::BanAdd(NETADDR Addr, int Seconds, const char *pReason) { Addr.port = 0; char aAddrStr[128]; net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); char aBuf[256]; if(Seconds) str_format(aBuf, sizeof(aBuf), "banned %s for %d minutes", aAddrStr, Seconds/60); else str_format(aBuf, sizeof(aBuf), "banned %s for life", aAddrStr); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); return m_NetServer.BanAdd(Addr, Seconds, pReason); } 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)]); } // Ban Master if(Packet.m_DataSize >= (int)sizeof(BANMASTER_IPOK) && mem_comp(Packet.m_pData, BANMASTER_IPOK, sizeof(BANMASTER_IPOK)) == 0 && m_NetServer.BanmasterCheck(&Packet.m_Address) != -1) { } if(Packet.m_DataSize >= (int)sizeof(BANMASTER_IPBAN) && mem_comp(Packet.m_pData, BANMASTER_IPBAN, sizeof(BANMASTER_IPBAN)) == 0 && g_Config.m_SvGlobalBantime && m_NetServer.BanmasterCheck(&Packet.m_Address) != -1) { CUnpacker Up; char aIp[32]; char aReason[256]; NETADDR Addr; Up.Reset((unsigned char*)Packet.m_pData + sizeof(BANMASTER_IPBAN), Packet.m_DataSize - sizeof(BANMASTER_IPBAN)); str_copy(aIp, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(aIp)); str_copy(aReason, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(aReason)); if (net_addr_from_str(&Addr, aIp)) { dbg_msg("globalbans", "dropped weird message from banmaster"); return; } m_NetServer.BanAdd(Addr, g_Config.m_SvGlobalBantime * 60, aReason); dbg_msg("globalbans", "added ban, ip=%d.%d.%d.%d, reason=\"%s\"", Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3], aReason); } } } else ProcessClientPacket(&Packet); } } char *CServer::GetMapName() { // get the name of the map without his path char *pMapShortName = &g_Config.m_SvMap[0]; for(int i = 0; i < str_length(g_Config.m_SvMap)-1; i++) { if(g_Config.m_SvMap[i] == '/' || g_Config.m_SvMap[i] == '\\') pMapShortName = &g_Config.m_SvMap[i+1]; } return pMapShortName; } 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;*/ // check for valid standard map if(!m_MapChecker.ReadAndValidateMap(Storage(), aBuf, IStorage::TYPE_ALL)) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "mapchecker", "invalid standard map"); 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(); char aBufMsg[256]; str_format(aBufMsg, sizeof(aBufMsg), "%s crc is %08x", aBuf, m_CurrentMapCrc); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBufMsg); 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, IStorage::TYPE_ALL); 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); } for(int i=0; iRequestInterface(); m_pMap = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); // Console()->RegisterPrintCallback(SendRconLineAuthed, this); Console()->RegisterClientOnlineCallback(ClientOnline, this); Console()->RegisterCompareClientsCallback(CompareClients, 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 NETADDR BindAddr; if(g_Config.m_SvBindaddr[0] && net_host_lookup(g_Config.m_SvBindaddr, &BindAddr, NETTYPE_ALL) == 0) { // sweet! BindAddr.port = g_Config.m_SvPort; } else { mem_zero(&BindAddr, sizeof(BindAddr)); BindAddr.type = NETTYPE_ALL; BindAddr.port = g_Config.m_SvPort; } if(!m_NetServer.Open(BindAddr, g_Config.m_SvMaxClients, g_Config.m_SvMaxClientsPerIP, 0)) { dbg_msg("server", "couldn't open socket. port might already be in use"); return -1; } m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this); Console()->ExecuteFile(SERVER_BANMASTERFILE, -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "server name is '%s'", g_Config.m_SvName); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); GameServer()->OnInit(); str_format(aBuf, sizeof(aBuf), "version %s", GameServer()->NetVersion()); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); // process pending commands m_pConsole->StoreCommands(false, -1); // start game { int64 ReportTime = time_get(); int ReportInterval = 3; m_Lastheartbeat = 0; m_GameStartTime = time_get(); if(g_Config.m_Debug) { str_format(aBuf, sizeof(aBuf), "baseline memory usage %dk", mem_stats()->allocated/1024); Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBuf); } 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 || m_MapReload) { m_MapReload = 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_AUTH) 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(); UpdateServerInfo(); } else { str_format(aBuf, sizeof(aBuf), "failed to load map. mapname='%s'", g_Config.m_SvMap); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); 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(BindAddr.type); 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, int ClientID) { int Victim = pResult->GetVictim(); if(pResult->NumArguments() >= 1) { char aBuf[128]; str_format(aBuf, sizeof(aBuf), "Kicked (%s)", pResult->GetString(0)); ((CServer *)pUser)->Kick(Victim, aBuf); } else ((CServer *)pUser)->Kick(Victim, "Kicked by console"); } void CServer::ConBan(IConsole::IResult *pResult, void *pUser, int ClientID) { NETADDR Addr; CServer *pServer = (CServer *)pUser; const char *pStr = pResult->GetString(0); int Minutes = 30; const char *pReason = "No reason given"; if(pResult->NumArguments() > 1) Minutes = pResult->GetInteger(1); if(pResult->NumArguments() > 2) pReason = pResult->GetString(2); if(net_addr_from_str(&Addr, pStr) == 0) { if(pServer->m_RconClientID >= 0 && pServer->m_RconClientID < MAX_CLIENTS && pServer->m_aClients[pServer->m_RconClientID].m_State != CClient::STATE_EMPTY) { NETADDR AddrCheck = pServer->m_NetServer.ClientAddr(pServer->m_RconClientID); Addr.port = AddrCheck.port = 0; if(net_addr_comp(&Addr, &AddrCheck) == 0) { pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't ban yourself"); return; } } pServer->BanAdd(Addr, Minutes*60, pReason); } else if(StrAllnum(pStr)) { int Victim = str_toint(pStr); if(Victim < 0 || Victim >= MAX_CLIENTS || pServer->m_aClients[Victim].m_State == CClient::STATE_EMPTY) { pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "invalid client id"); return; } else if(pServer->m_RconClientID == Victim) { pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't ban yourself"); return; } if (ClientID != -1 && ((CServer *)pUser)->m_aClients[ClientID].m_Authed <= ((CServer *)pUser)->m_aClients[Victim].m_Authed) return; Addr = pServer->m_NetServer.ClientAddr(Victim); pServer->BanAdd(Addr, Minutes*60, pReason); } else { pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "invalid network address to ban"); return; } } void CServer::ConUnban(IConsole::IResult *pResult, void *pUser, int ClientID) { NETADDR Addr; CServer *pServer = (CServer *)pUser; const char *pStr = pResult->GetString(0); if(net_addr_from_str(&Addr, pStr) == 0 && !pServer->BanRemove(Addr)) { char aAddrStr[NETADDR_MAXSTRSIZE]; net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "unbanned %s", aAddrStr); pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } else if(StrAllnum(pStr)) { int BanIndex = str_toint(pStr); CNetServer::CBanInfo Info; if(BanIndex < 0 || !pServer->m_NetServer.BanGet(BanIndex, &Info)) pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "invalid ban index"); else if(!pServer->BanRemove(Info.m_Addr)) { char aAddrStr[NETADDR_MAXSTRSIZE]; net_addr_str(&Info.m_Addr, aAddrStr, sizeof(aAddrStr)); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "unbanned %s", aAddrStr); pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } } else pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "invalid network address"); } void CServer::ConBans(IConsole::IResult *pResult, void *pUser, int ClientID) { unsigned Now = time_timestamp(); char aBuf[1024]; char aAddrStr[NETADDR_MAXSTRSIZE]; 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; net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); if(Info.m_Expires == -1) { str_format(aBuf, sizeof(aBuf), "#%i %s for life", i, aAddrStr); } else { unsigned t = Info.m_Expires - Now; str_format(aBuf, sizeof(aBuf), "#%i %s for %d minutes and %d seconds", i, aAddrStr, t/60, t%60); } pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf); } str_format(aBuf, sizeof(aBuf), "%d ban(s)", Num); pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf); } void CServer::ConStatus(IConsole::IResult *pResult, void *pUser, int ClientID) { int i; NETADDR Addr; char aBuf[1024]; char aAddrStr[NETADDR_MAXSTRSIZE]; CServer* pServer = (CServer *)pUser; for(i = 0; i < MAX_CLIENTS; i++) { if(pServer->m_aClients[i].m_State != CClient::STATE_EMPTY) { Addr = pServer->m_NetServer.ClientAddr(i); net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); if(pServer->m_aClients[i].m_State == CClient::STATE_INGAME) str_format(aBuf, sizeof(aBuf), "id=%d addr=%s name='%s' score=%d", i, aAddrStr, pServer->m_aClients[i].m_aName, pServer->m_aClients[i].m_Score); else str_format(aBuf, sizeof(aBuf), "id=%d addr=%s connecting", i, aAddrStr); pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf); } } } void CServer::ConShutdown(IConsole::IResult *pResult, void *pUser, int ClientID) { ((CServer *)pUser)->m_RunServer = 0; } void CServer::ConRecord(IConsole::IResult *pResult, void *pUser, int ClientID) { CServer* pServer = (CServer *)pUser; char aFilename[128]; if(pResult->NumArguments()) str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pResult->GetString(0)); else { char aDate[20]; str_timestamp(aDate, sizeof(aDate)); str_format(aFilename, sizeof(aFilename), "demos/demo_%s.demo", aDate); } pServer->m_DemoRecorder.Start(pServer->Storage(), pServer->Console(), aFilename, pServer->GameServer()->NetVersion(), pServer->m_aCurrentMap, pServer->m_CurrentMapCrc, "server"); } void CServer::ConStopRecord(IConsole::IResult *pResult, void *pUser, int ClientID) { ((CServer *)pUser)->m_DemoRecorder.Stop(); } void CServer::ConMapReload(IConsole::IResult *pResult, void *pUser, int ClientID) { ((CServer *)pUser)->m_MapReload = 1; } void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData, -1); if(pResult->NumArguments()) ((CServer *)pUserData)->UpdateServerInfo(); } void CServer::ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData, -1); if(pResult->NumArguments()) ((CServer *)pUserData)->m_NetServer.SetMaxClientsPerIP(pResult->GetInteger(0)); } void CServer::RegisterCommands() { m_pConsole = Kernel()->RequestInterface(); Console()->Register("kick", "v?r", CFGFLAG_SERVER, ConKick, this, "", IConsole::CONSOLELEVEL_MODERATOR); Console()->Register("ban", "s?ir", CFGFLAG_SERVER|CFGFLAG_STORE, ConBan, this, "", IConsole::CONSOLELEVEL_MODERATOR); Console()->Register("unban", "s", CFGFLAG_SERVER|CFGFLAG_STORE, ConUnban, this, "", IConsole::CONSOLELEVEL_MODERATOR); Console()->Register("bans", "", CFGFLAG_SERVER|CFGFLAG_STORE, ConBans, this, "", IConsole::CONSOLELEVEL_MODERATOR); Console()->Register("status", "", CFGFLAG_SERVER, ConStatus, this, "", IConsole::CONSOLELEVEL_MODERATOR); Console()->Register("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, "", IConsole::CONSOLELEVEL_ADMIN); Console()->Register("record", "?s", CFGFLAG_SERVER|CFGFLAG_STORE, ConRecord, this, "", IConsole::CONSOLELEVEL_ADMIN); Console()->Register("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, "", IConsole::CONSOLELEVEL_ADMIN); Console()->Register("reload", "", CFGFLAG_SERVER, ConMapReload, this, "", IConsole::CONSOLELEVEL_ADMIN); Console()->Chain("sv_name", ConchainSpecialInfoupdate, this); Console()->Chain("password", ConchainSpecialInfoupdate, this); Console()->Chain("sv_max_clients_per_ip", ConchainMaxclientsperipUpdate, this); Console()->Register("add_banmaster", "s", CFGFLAG_SERVER, ConAddBanmaster, this, "", IConsole::CONSOLELEVEL_ADMIN); Console()->Register("banmasters", "", CFGFLAG_SERVER, ConBanmasters, this, "", IConsole::CONSOLELEVEL_ADMIN); Console()->Register("clear_banmasters", "", CFGFLAG_SERVER, ConClearBanmasters, this, "", IConsole::CONSOLELEVEL_ADMIN); Console()->Register("login", "?s", CFGFLAG_SERVER, ConLogin, this, "Allows you access to rcon if no password is given, or changes your level if a password is given", IConsole::CONSOLELEVEL_USER); Console()->Register("auth", "?s", CFGFLAG_SERVER, ConLogin, this, "Allows you access to rcon if no password is given, or changes your level if a password is given", IConsole::CONSOLELEVEL_USER); Console()->Register("cmdlist", "?i", CFGFLAG_SERVER, ConCmdList, this, "Shows you the commands available for your remote console access. Specify the level if you want to see other level's commands", IConsole::CONSOLELEVEL_USER); } 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 ID < 0 ? 0 : 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 CServer *pServer = CreateServer(); IKernel *pKernel = IKernel::Create(); // create the components IEngine *pEngine = CreateEngine("Teeworlds"); IEngineMap *pEngineMap = CreateEngineMap(); IGameServer *pGameServer = CreateGameServer(); IConsole *pConsole = CreateConsole(CFGFLAG_SERVER); IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer(); IStorage *pStorage = CreateStorage("Teeworlds", argc, argv); // ignore_convention IConfig *pConfig = CreateConfig(); pServer->InitRegister(&pServer->m_NetServer, pEngineMasterServer, pConsole); { bool RegisterFail = false; RegisterFail = RegisterFail || !pKernel->RegisterInterface(pServer); // register as both RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine); RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap)); // register as both RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(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(pEngineMasterServer)); // register as both RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMasterServer)); if(RegisterFail) return -1; } pEngine->Init(); pConfig->Init(); pEngineMasterServer->Init(); pEngineMasterServer->Load(); // register all console commands pServer->RegisterCommands(); pGameServer->OnConsoleInit(); // execute autoexec file pConsole->ExecuteFile("autoexec.cfg", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); // parse the command line arguments if(argc > 1) // ignore_convention pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention // restore empty config strings to their defaults pConfig->RestoreStrings(); pEngine->InitLogfile(); // run the server dbg_msg("server", "starting..."); pServer->Run(); // free delete pServer; delete pKernel; delete pEngineMap; delete pGameServer; delete pConsole; delete pEngineMasterServer; delete pStorage; delete pConfig; return 0; } // DDRace void CServer::GetClientAddr(int ClientID, NETADDR *pAddr) { if(ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME) *pAddr = m_NetServer.ClientAddr(ClientID); } void DDRaceTunesReset(CConsole* pConsole) { pConsole->ExecuteLine("tune_reset", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); pConsole->ExecuteLine("tune gun_speed 1400", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); pConsole->ExecuteLine("tune shotgun_curvature 0", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); pConsole->ExecuteLine("tune shotgun_speed 500", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); pConsole->ExecuteLine("tune shotgun_speeddiff 0", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); pConsole->ExecuteLine("tune gun_curvature 0", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); pConsole->ExecuteLine("sv_hit 1", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); pConsole->ExecuteLine("sv_npc 0", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); pConsole->ExecuteLine("sv_phook 1", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); pConsole->ExecuteLine("sv_endless_drag 0", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); pConsole->ExecuteLine("sv_old_laser 0", -1, IConsole::CONSOLELEVEL_CONFIG, 0, 0); } void CServer::SetRconLevel(int ClientID, int Level) { char aBuf[128]; Level = clamp(Level, (int)IConsole::CONSOLELEVEL_USER, (int)IConsole::CONSOLELEVEL_ADMIN); if(Level > IConsole::CONSOLELEVEL_USER) { str_format(aBuf, sizeof(aBuf), "%s set to level %d. ClientID=%x ip=%d.%d.%d.%d",ClientName(ClientID), Level, ClientID, m_aClients[ClientID].m_Addr.ip[0], m_aClients[ClientID].m_Addr.ip[1], m_aClients[ClientID].m_Addr.ip[2], m_aClients[ClientID].m_Addr.ip[3]); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS); Msg.AddInt(1); SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); m_aClients[ClientID].m_Authed = Level; GameServer()->OnSetAuthed(ClientID, m_aClients[ClientID].m_Authed); } else if(Level == IConsole::CONSOLELEVEL_USER) { str_format(aBuf, sizeof(aBuf), "%s set to level %d. ClientID=%x ip=%d.%d.%d.%d",ClientName(ClientID), Level, ClientID, m_aClients[ClientID].m_Addr.ip[0], m_aClients[ClientID].m_Addr.ip[1], m_aClients[ClientID].m_Addr.ip[2], m_aClients[ClientID].m_Addr.ip[3]); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS); Msg.AddInt(0); SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); m_aClients[ClientID].m_Authed = Level; } } void CServer::CheckPass(int ClientID, const char *pPassword) { if(pPassword[0] != 0) { if(g_Config.m_SvRconPasswordModer[0] == 0 && g_Config.m_SvRconPasswordAdmin[0] == 0) { SendRconLine(ClientID, "No rcon password set on server. Set sv_admin_pass/sv_mod_pass/sv_helper_pass to enable the remote console."); } else { /*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); }*/ int Level = IConsole::CONSOLELEVEL_USER; if(str_comp(pPassword, g_Config.m_SvRconPasswordModer) == 0) { Level = IConsole::CONSOLELEVEL_MODERATOR; } else if(str_comp(pPassword, g_Config.m_SvRconPasswordAdmin) == 0) { Level = IConsole::CONSOLELEVEL_ADMIN; } if(Level > IConsole::CONSOLELEVEL_USER) { char buf[128]="Authentication successful. Remote console access granted for ClientID=%d with level=%d"; SetRconLevel(ClientID, Level); str_format(buf,sizeof(buf),buf,ClientID,Level); SendRconLine(ClientID, buf); dbg_msg("server", "'%s' ClientID=%d authed with Level=%d", ClientName(ClientID), ClientID, Level); } else if(g_Config.m_SvRconMaxTries) { m_aClients[ClientID].m_AuthTries++; char aBuf[128]; str_format(aBuf, sizeof(aBuf), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, g_Config.m_SvRconMaxTries); SendRconLine(ClientID, aBuf); if(m_aClients[ClientID].m_AuthTries >= g_Config.m_SvRconMaxTries) { if(!g_Config.m_SvRconBantime) m_NetServer.Drop(ClientID, "Too many remote console authentication tries"); else { NETADDR Addr = m_NetServer.ClientAddr(ClientID); BanAdd(Addr, g_Config.m_SvRconBantime*60, "Too many remote console authentication tries"); } } } } } /*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."); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "ClientID=%d authed", ClientID); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); }*/ else { char aBuf[128]="Authentication successful. Remote console access granted for ClientID=%d with level=%d"; SetRconLevel(ClientID,0); str_format(aBuf, sizeof(aBuf), aBuf, ClientID, 0); SendRconLine(ClientID, aBuf); dbg_msg("server", "'%s' ClientID=%d authed with Level=%d", ClientName(ClientID), ClientID, 0); } } char *CServer::GetAnnouncementLine(char const *pFileName) { IOHANDLE File = m_pStorage->OpenFile(pFileName, IOFLAG_READ, IStorage::TYPE_ALL); if(File) { std::vector v; char *pLine; CLineReader *lr = new CLineReader(); lr->Init(File); while((pLine = lr->Get())) if(str_length(pLine)) if(pLine[0]!='#') v.push_back(pLine); if(v.size() == 1) { m_AnnouncementLastLine = 0; } else if(!g_Config.m_SvAnnouncementRandom) { if(m_AnnouncementLastLine >= v.size()) m_AnnouncementLastLine %= v.size(); } else { unsigned Rand; do Rand = rand() % v.size(); while(Rand == m_AnnouncementLastLine); m_AnnouncementLastLine = Rand; } return v[m_AnnouncementLastLine]; } return 0; } void CServer::ConAddBanmaster(IConsole::IResult *pResult, void *pUser, int ClientID) { CServer *pServer = (CServer *)pUser; int Result = pServer->m_NetServer.BanmasterAdd(pResult->GetString(0)); if(Result == 0) pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server/banmaster", "succesfully added banmaster"); else if (Result == 1) pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server/banmaster", "invalid address for banmaster / net lookup failed"); else pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server/banmaster", "too many banmasters"); } void CServer::ConBanmasters(IConsole::IResult *pResult, void *pUser, int ClientID) { CServer *pServer = (CServer *)pUser; int NumBanmasters = pServer->m_NetServer.BanmasterNum(); char aBuf[128]; char aIpString[64]; for(int i = 0; i < NumBanmasters; i++) { NETADDR *pBanmaster = pServer->m_NetServer.BanmasterGet(i); net_addr_str(pBanmaster, aIpString, sizeof(aIpString)); str_format(aBuf, sizeof(aBuf), "%d: %s", i, aIpString); pResult->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server/banmaster", aBuf); } } void CServer::ConClearBanmasters(IConsole::IResult *pResult, void *pUser, int ClientID) { CServer *pServer = (CServer *)pUser; pServer->m_NetServer.BanmastersClear(); } void CServer::SendRconResponse(const char *pLine, void *pUser) { RconResponseInfo *pInfo = (RconResponseInfo *)pUser; CServer *pThis = pInfo->m_Server; static volatile int ReentryGuard = 0; if(ReentryGuard) return; ReentryGuard++; if(pThis->m_aClients[pInfo->m_ClientID].m_State != CClient::STATE_EMPTY) pThis->SendRconLine(pInfo->m_ClientID, pLine); ReentryGuard--; } void CServer::ConCmdList(IConsole::IResult *pResult, void *pUserData, int ClientID) { CServer *pSelf = (CServer *)pUserData; if(pResult->NumArguments() == 0) pSelf->Console()->List(pResult, (pSelf->m_aClients[ClientID].m_Authed != 0) ? pSelf->m_aClients[ClientID].m_Authed : -1, CFGFLAG_SERVER); else if (pResult->GetInteger(0) == 0) pSelf->Console()->List(pResult, -1, CFGFLAG_SERVER); else pSelf->Console()->List(pResult, pResult->GetInteger(0), CFGFLAG_SERVER); } void CServer::ConLogin(IConsole::IResult *pResult, void *pUser, int ClientID) { if(pResult->NumArguments()) ((CServer *)pUser)->CheckPass(ClientID, pResult->GetString(0)); else ((CServer *)pUser)->SetRconLevel(ClientID, 0); } bool CServer::CompareClients(int ClientLevel, int Victim, void *pUser) { CServer* pSelf = (CServer *)pUser; if(!ClientOnline(Victim, pSelf)) return false; return clamp(ClientLevel, 0, 3) > clamp(pSelf->m_aClients[Victim].m_Authed, 0, 3); } bool CServer::ClientOnline(int ClientID, void *pUser) { CServer* pSelf = (CServer *)pUser; if(ClientID < 0 || ClientID >= MAX_CLIENTS) return false; return pSelf->m_aClients[ClientID].m_State != CClient::STATE_EMPTY; } void CServer::SetClientAuthed(int ClientID, int Level) { if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CClient::STATE_READY) return; m_aClients[ClientID].m_Authed = Level; } 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]); } }