List of network system messages
There are three types of messages with overlapping message ids:
system messages,
game messages and
control messages
This list is only covering system messages.
When the client and server exchange packets.
An integer field which usually does not exceed one byte.
Is indicating which type of message was sent.
This field is called the message id.
These message ids are defined in
src/engine/shared/protocol.h
and their payload is defined where they are sent and received.
There can be multiple messages in so called chunks in one teeworlds packet.
In the list below you will find those message.
With their name, id and payload.
Sent by the client in CClient::SendInfo()
void CClient::SendInfo()
{
CMsgPacker Msg(NETMSG_INFO);
Msg.AddString(GameClient()->NetVersion(), 128);
Msg.AddString(g_Config.m_Password, 128);
SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
}
The server respons to it in
CServer::ProcessClientPacket()
with
NETMSG_MAP_CHANGE
in a call to
SendMap(ClientID);
void CServer::ProcessClientPacket(CNetChunk *pPacket)
{
// [...]
if(Sys)
{
// system message
if(Msg == NETMSG_INFO)
{
if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && 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;
}
m_aClients[ClientID].m_State = CClient::STATE_CONNECTING;
SendMap(ClientID);
}
}
Sender: | Server |
Recipient: | Client |
Message ID: | 2 |
Response to: |
TODO |
Expected response: |
TODO |
Sent by the server in
CServer::SendMap(int ClientID)
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);
}
Sender: | Server |
Recipient: | Client |
Message ID: | 3 |
Response to: |
TODO |
Expected response: |
TODO |
Sent by the server in
CServer::ProcessClientPacket()
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);
Sender: | Server |
Recipient: | Client |
Message ID: | 4 |
Response to: |
TODO |
Expected response: |
TODO |
Argument name |
Type |
Note |
None |
Sent by the server in
CServer::SendConnectionReady()
void CServer::SendConnectionReady(int ClientID)
{
CMsgPacker Msg(NETMSG_CON_READY);
SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true);
}
Unpacked by the client in
CClient::ProcessClientPacket()
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_CON_READY)
{
GameClient()->OnConnected();
}
Sender: | Server |
Recipient: | Client |
Message ID: | 5 |
Response to: |
Updates in the world |
Expected response: |
The client acknowledges the Game tick
in the message NETMSG_INPUT
|
Argument name |
Type |
Note |
Game tick |
Int |
TODO
|
Delta tick |
Int |
The base snapshot game tick which the delta was created from.
Will be -1 for the first snapshot signaling it is a delta based on the empty snapshot.
|
Num parts |
Int |
Indicates the total amount of parts for this snapshot. The current part is set in the Part field.
The client is supposed to collect the payload and concatinate it locally and only if the last part is received it should parse the snapshot.
Where last part is defined as Part == Num Parts - 1 .
So if Num Parts is set to 2 the first
Part will be 0 and the second (last) will be 1 .
|
Part |
Int |
0 based index for Num Parts.
|
Crc |
Int |
The crc (cyclic redundancy check) is used as a checksum verifying the integrity of the snapshot.
It is all snapshot item payloads summed together into one 32 bit integer.
The reference implementation might overflow the crc and so should you to match the exact value.
More details on how to calculate the crc can be seen in the
teeframe documentation.
Important to understand is that the crc is not applied to only the snap items sent in this net message but the full snapshot.
Every snap msg is just a delta that might add or remove items from a previous snapshot.
So if the client already knows about a snapshot with 3 items and then another NETMSG_SNAP comes in with 3 additional items as payload
the crc has to be calculated on all 6 items that are now in the fully unpacked snapshot.
|
Part size |
Int |
The size of this part. Meaning the size in bytes of the next raw data field.
|
Data |
Raw |
The partial snapshot data. It should be only processed when all
Num parts are assembled together.
The snapshot data. Which is a bunch of packed ints.
The first is the amount of removed items.
The second is the amount of item deltas.
The third a unused zero byte.
Then there are "amount of removed items" repeated integers
identifying the deleted items. Followed by "amount of item deltas" repeated snap items
You can read more about snap items and the structure
of this data field in the
snap items
section.
|
Sender: | TODO |
Recipient: | TODO |
Message ID: | 6 |
Response to: |
TODO |
Expected response: |
TODO |
Sender: | Server |
Recipient: | Client |
Message ID: | 7 |
Response to: |
Updates in the world |
Expected response: |
The client acknowledges the Game tick
in the message NETMSG_INPUT
|
Argument name |
Type |
Note |
Game tick |
Int |
TODO
|
Delta tick |
Int |
The base snapshot game tick which the delta was created from.
Will be -1 for the first snapshot signaling it is a delta based on the empty snapshot.
|
Crc |
Int |
The crc is a checksum. More details here.
|
Part size |
Int |
The size of this part. Meaning the size in bytes of the next raw data field.
|
Data |
Raw |
The snapshot data. Which is a bunch of packed ints.
The first is the amount of removed items.
The second is the amount of item deltas.
The third a unused zero byte.
Then there are "amount of removed items" repeated integers
identifying the deleted items. Followed by "amount of item deltas" repeated snap items
You can read more about snap items and the structure
of this data field in the
snap items
section.
|
One full snapshot. If it does not fit into one message it will be split into
multiple parts and send as
NETMSG_SNAP instead.
Sender: | TODO |
Recipient: | TODO |
Message ID: | 8 |
Response to: |
TODO |
Expected response: |
TODO |
Sender: | Server |
Recipient: | Client |
Message ID: | 9 |
Response to: |
TODO |
Expected response: |
TODO |
Sent by the server in
CServer::ProcessClientPacket()
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);
}
Unpacked by the client in
CClient::ProcessServerPacket()
else if(Msg == NETMSG_INPUTTIMING)
{
int InputPredTick = Unpacker.GetInt();
int TimeLeft = Unpacker.GetInt();
// adjust our prediction time
int64 Target = 0;
for(int k = 0; k < 200; k++)
{
if(m_aInputs[k].m_Tick == InputPredTick)
{
Target = m_aInputs[k].m_PredictedTime + (time_get() - m_aInputs[k].m_Time);
Target = Target - (int64)(((TimeLeft-PREDICTION_MARGIN)/1000.0f)*time_freq());
break;
}
}
if(Target)
m_PredictedTime.Update(&m_InputtimeMarginGraph, Target, TimeLeft, 1);
}
Argument name |
Type |
Note |
Authed |
Int |
- 0 - off (logout)
- 1 - on (login)
|
Cmdlist |
Int |
- 0 - off (logout)
- 1 - on (login)
|
Sent on login and logout by the server to inform the client about its rcon state.
The client then activates or deactivates the remote console in the ui.
Sent by the server in
CServer::ProcessClientPacket()
on successful login.
else if(Msg == NETMSG_RCON_AUTH)
{
const char *pPw;
Unpacker.GetString(); // login name, not used
pPw = Unpacker.GetString(CUnpacker::SANITIZE_CC);
if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Error() == 0)
{
if(g_Config.m_SvRconPassword[0] == 0 && g_Config.m_SvRconModPassword[0] == 0)
{
SendRconLine(ClientID, "No rcon password set on server. Set sv_rcon_password and/or sv_rcon_mod_password to enable the remote console.");
}
else if(g_Config.m_SvRconPassword[0] && str_comp(pPw, g_Config.m_SvRconPassword) == 0)
{
CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS);
Msg.AddInt(1); //authed
Msg.AddInt(1); //cmdlist
SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
m_aClients[ClientID].m_Authed = AUTHED_ADMIN;
int SendRconCmds = Unpacker.GetInt();
if(Unpacker.Error() == 0 && SendRconCmds)
m_aClients[ClientID].m_pRconCmdToSend = Console()->FirstCommandInfo(IConsole::ACCESS_LEVEL_ADMIN, CFGFLAG_SERVER);
SendRconLine(ClientID, "Admin authentication successful. Full remote console access granted.");
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (admin)", ClientID);
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
}
else if(g_Config.m_SvRconModPassword[0] && str_comp(pPw, g_Config.m_SvRconModPassword) == 0)
{
CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS);
Msg.AddInt(1); //authed
Msg.AddInt(1); //cmdlist
SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
m_aClients[ClientID].m_Authed = AUTHED_MOD;
int SendRconCmds = Unpacker.GetInt();
if(Unpacker.Error() == 0 && SendRconCmds)
m_aClients[ClientID].m_pRconCmdToSend = Console()->FirstCommandInfo(IConsole::ACCESS_LEVEL_MOD, CFGFLAG_SERVER);
SendRconLine(ClientID, "Moderator authentication successful. Limited remote console access granted.");
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (moderator)", 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
m_ServerBan.BanAddr(m_NetServer.ClientAddr(ClientID), g_Config.m_SvRconBantime*60, "Too many remote console authentication tries");
}
}
else
{
SendRconLine(ClientID, "Wrong password.");
}
}
}
And also send by the server in
CServer::ConLogout()
when the rcon command
logout
is executed.
void CServer::ConLogout(IConsole::IResult *pResult, void *pUser)
{
CServer *pServer = (CServer *)pUser;
if(pServer->m_RconClientID >= 0 && pServer->m_RconClientID < MAX_CLIENTS &&
pServer->m_aClients[pServer->m_RconClientID].m_State != CServer::CClient::STATE_EMPTY)
{
CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS);
Msg.AddInt(0); //authed
Msg.AddInt(0); //cmdlist
pServer->SendMsgEx(&Msg, MSGFLAG_VITAL, pServer->m_RconClientID, true);
pServer->m_aClients[pServer->m_RconClientID].m_Authed = AUTHED_NO;
pServer->m_aClients[pServer->m_RconClientID].m_AuthTries = 0;
pServer->m_aClients[pServer->m_RconClientID].m_pRconCmdToSend = 0;
pServer->SendRconLine(pServer->m_RconClientID, "Logout successful.");
char aBuf[32];
str_format(aBuf, sizeof(aBuf), "ClientID=%d logged out", pServer->m_RconClientID);
pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
}
}
Unpacked by the client in
CClient::ProcessServerPacket()
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_AUTH_STATUS)
{
int Result = Unpacker.GetInt();
if(Unpacker.Error() == 0)
m_RconAuthed = Result;
int Old = m_UseTempRconCommands;
m_UseTempRconCommands = Unpacker.GetInt();
if(Unpacker.Error() != 0)
m_UseTempRconCommands = 0;
if(Old != 0 && m_UseTempRconCommands == 0)
m_pConsole->DeregisterTempAll();
}
Sender: | Server |
Recipient: | Client |
Message ID: | 11 |
Response to: |
TODO |
Expected response: |
TODO |
Sent by the server in
CServer::SendRconLine(int ClientID, const char *pLine)
void CServer::SendRconLine(int ClientID, const char *pLine)
{
CMsgPacker Msg(NETMSG_RCON_LINE);
Msg.AddString(pLine, 512);
SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
}
Sender: | Unused |
Recipient: | Unused |
Message ID: | 12 |
Response to: |
Unused |
Expected response: |
Unused |
Argument name |
Type |
Note |
Unused |
This message ID is unused
Note challenge is misspelled (here to optimize search results)
Sender: | unused |
Recipient: | unused |
Message ID: | 13 |
Response to: |
unused |
Expected response: |
unused |
Argument name |
Type |
Note |
Unused |
This message ID is unused
Sender: | Client |
Recipient: | Server |
Message ID: | 14 |
Response to: |
TODO |
Expected response: |
TODO |
Argument name |
Type |
Note |
None |
Sent by the client in
CClient::SendReady()
void CClient::SendReady()
{
CMsgPacker Msg(NETMSG_READY);
SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
}
Sender: | Client |
Recipient: | Server |
Message ID: | 15 |
Response to: |
TODO |
Expected response: |
TODO |
Argument name |
Type |
Note |
None |
Sent by the client in
CClient::SendEnterGame()
void CClient::SendEnterGame()
{
CMsgPacker Msg(NETMSG_ENTERGAME);
SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
}
Sender: | Client |
Recipient: | Server |
Message ID: | 16 |
Response to: |
TODO |
Expected response: |
TODO |
struct CNetObj_PlayerInput
{
int m_Direction;
int m_TargetX;
int m_TargetY;
int m_Jump;
int m_Fire;
int m_Hook;
int m_PlayerFlags;
int m_WantedWeapon;
int m_NextWeapon;
int m_PrevWeapon;
};
Sender: | Client |
Recipient: | Server |
Message ID: | 17 |
Response to: |
TODO |
Expected response: |
TODO |
Argument name |
Type |
Note |
Command |
String |
The rcon command like for example status or shutdown |
Sent by the client in
CClient::Rcon(const char *pCmd)
void CClient::Rcon(const char *pCmd)
{
CMsgPacker Msg(NETMSG_RCON_CMD);
Msg.AddString(pCmd, 256);
SendMsgEx(&Msg, MSGFLAG_VITAL);
}
Unpacked by the server in
CServer::ProcessClientPacket()
else if(Msg == NETMSG_RCON_CMD)
{
const char *pCmd = Unpacker.GetString();
if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && 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;
m_RconAuthLevel = m_aClients[ClientID].m_Authed;
Console()->SetAccessLevel(m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : IConsole::ACCESS_LEVEL_MOD);
Console()->ExecuteLineFlag(pCmd, CFGFLAG_SERVER);
Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN);
m_RconClientID = IServer::RCON_CID_SERV;
m_RconAuthLevel = AUTHED_ADMIN;
}
}
Sender: | Client |
Recipient: | Server |
Message ID: | 18 |
Response to: |
TODO |
Expected response: |
TODO |
Argument name |
Type |
Note |
Name |
String |
TODO |
Password |
String |
TODO |
Send rcon commands |
Int |
The official vanilla 0.6 clients uses the fixed value 1
at all times for this field.
But technically the protocol allows optig out of the rcon command list
receival. The server would not send any if this field was set to 0
|
Sent by the client in
CClient::RconAuth()
void CClient::RconAuth(const char *pName, const char *pPassword)
{
if(RconAuthed())
return;
CMsgPacker Msg(NETMSG_RCON_AUTH);
Msg.AddString(pName, 32);
Msg.AddString(pPassword, 32);
Msg.AddInt(1);
SendMsgEx(&Msg, MSGFLAG_VITAL);
}
Sender: | Client |
Recipient: | Server |
Message ID: | 19 |
Response to: |
TODO |
Expected response: |
TODO |
Sent by the client in
CClient::ProcessServerPacket()
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_DATA)
{
int Last = Unpacker.GetInt();
int MapCRC = Unpacker.GetInt();
int Chunk = Unpacker.GetInt();
int Size = Unpacker.GetInt();
const unsigned char *pData = Unpacker.GetRaw(Size);
// check fior errors
if(Unpacker.Error() || Size <= 0 || MapCRC != m_MapdownloadCrc || Chunk != m_MapdownloadChunk || !m_MapdownloadFile)
return;
io_write(m_MapdownloadFile, pData, Size);
m_MapdownloadAmount += Size;
if(Last)
{
// [..]
}
else
{
// request new chunk
m_MapdownloadChunk++;
CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA);
Msg.AddInt(m_MapdownloadChunk);
SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
if(g_Config.m_Debug)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "requested chunk %d", m_MapdownloadChunk);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", aBuf);
}
}
}
Sender: | Unused |
Recipient: | Unused |
Message ID: | 20 |
Response to: |
Unused |
Expected response: |
Unused |
Argument name |
Type |
Note |
None |
This message ID is unused
Sender: | Unused |
Recipient: | Unused |
Message ID: | 21 |
Response to: |
Unused |
Expected response: |
Unused |
Argument name |
Type |
Note |
None |
This message ID is unused
Sender: | Client |
Recipient: | Server |
Message ID: | 22 |
Response to: |
TODO |
Expected response: |
TODO |
Argument name |
Type |
Note |
None |
Unpacked by the client in
CClient::ProcessServerPacket()
else if(Msg == NETMSG_PING)
{
CMsgPacker Msg(NETMSG_PING_REPLY);
SendMsgEx(&Msg, 0);
}
Sent by the client via the
ping
console command
void CClient::Con_Ping(IConsole::IResult *pResult, void *pUserData)
{
CClient *pSelf = (CClient *)pUserData;
CMsgPacker Msg(NETMSG_PING);
pSelf->SendMsgEx(&Msg, 0);
pSelf->m_PingStartTime = time_get();
}
Unpacked by the server in
CServer::ProcessClientPacket()
else if(Msg == NETMSG_PING)
{
CMsgPacker Msg(NETMSG_PING_REPLY);
SendMsgEx(&Msg, 0, ClientID, true);
}
Sender: | Client Server |
Recipient: | Client |
Message ID: | 23 |
Response to: |
TODO |
Expected response: |
TODO |
Argument name |
Type |
Note |
None |
Sent by the server in
CServer::ProcessClientPacket()
else if(Msg == NETMSG_PING)
{
CMsgPacker Msg(NETMSG_PING_REPLY);
SendMsgEx(&Msg, 0, ClientID, true);
}
Sent by the client in
CClient::ProcessServerPacket()
else if(Msg == NETMSG_PING)
{
CMsgPacker Msg(NETMSG_PING_REPLY);
SendMsgEx(&Msg, 0);
}
Unpacked in the client side to print the ping in
CClient::ProcessServerPacket()
else if(Msg == NETMSG_PING_REPLY)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "latency %.2f", (time_get() - m_PingStartTime)*1000 / (float)time_freq());
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client/network", aBuf);
}
Sender: | Unused |
Recipient: | Unused |
Message ID: | 24 |
Response to: |
Unused |
Expected response: |
Unused |
Argument name |
Type |
Note |
None |
This message ID is unused
Sender: | Server |
Recipient: | Client |
Message ID: | 25 |
Response to: |
TODO |
Expected response: |
TODO |
Sent by the server in
CServer::SendRconCmdAdd()
void CServer::SendRconCmdAdd(const IConsole::CCommandInfo *pCommandInfo, int ClientID)
{
CMsgPacker Msg(NETMSG_RCON_CMD_ADD);
Msg.AddString(pCommandInfo->m_pName, IConsole::TEMPCMD_NAME_LENGTH);
Msg.AddString(pCommandInfo->m_pHelp, IConsole::TEMPCMD_HELP_LENGTH);
Msg.AddString(pCommandInfo->m_pParams, IConsole::TEMPCMD_PARAMS_LENGTH);
SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
}
Sender: | Server |
Recipient: | Client |
Message ID: | 26 |
Response to: |
TODO |
Expected response: |
TODO |
Sent by the server in
CServer::SendRconCmdAdd()
void CServer::SendRconCmdRem(const IConsole::CCommandInfo *pCommandInfo, int ClientID)
{
CMsgPacker Msg(NETMSG_RCON_CMD_REM);
Msg.AddString(pCommandInfo->m_pName, 256);
SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
}
Unpacked by the client in
CClient::ProcessServerPacket()
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_REM)
{
const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC);
if(Unpacker.Error() == 0)
m_pConsole->DeregisterTemp(pName);
}