// RCONClient.cpp
// Implements the main app entrypoint
#include "Globals.h"
#include "OSSupport/Socket.h"
#include "ByteBuffer.h"
// If set to true, verbose messages are output to stderr. Use the "-v" or "--verbose" param to turn on
bool g_IsVerbose = false;
/// This class can read and write RCON packets to / from a connected socket
class cRCONPacketizer
{
public:
enum
{
ptCommand = 2,
ptLogin = 3,
} ;
cRCONPacketizer(cSocket & a_Socket);
/// Sends the packet to the socket and waits until the response is received.
/// Returns true if response successfully received, false if the client disconnected or protocol error.
/// Dumps the reply payload to stdout.
bool SendPacket(int a_PacketType, const AString & a_PacketPayload);
protected:
/// The socket to use for reading incoming data and writing outgoing data:
cSocket & m_Socket;
/// The RequestID of the packet that is being sent. Incremented when the reply is received
int m_RequestID;
/// Receives the full response and dumps its payload to stdout.
/// Returns true if successful, false if the client disconnected or protocol error.
bool ReceiveResponse(void);
/// Parses the received response packet and dumps its payload to stdout.
/// Returns true if successful, false on protocol error
/// Assumes that the packet length has already been read from the packet
/// If the packet is successfully parsed, increments m_RequestID
bool ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength);
} ;
cRCONPacketizer::cRCONPacketizer(cSocket & a_Socket) :
m_Socket(a_Socket),
m_RequestID(0)
{
}
bool cRCONPacketizer::SendPacket(int a_PacketType, const AString & a_PacketPayload)
{
// Send the packet:
cByteBuffer bb(a_PacketPayload.size() + 30);
bb.WriteLEInt(m_RequestID);
bb.WriteLEInt(a_PacketType);
bb.WriteBuf(a_PacketPayload.data(), a_PacketPayload.size());
bb.WriteBEShort(0); // Padding
AString Packet;
bb.ReadAll(Packet);
size_t Length = Packet.size();
if (!m_Socket.Send((const char *)&Length, 4))
{
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.",
cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
);
return false;
}
if (!m_Socket.Send(Packet.data(), Packet.size()))
{
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.",
cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
);
return false;
}
return ReceiveResponse();
}
bool cRCONPacketizer::ReceiveResponse(void)
{
// Receive the response:
cByteBuffer Buffer(64 KiB);
while (true)
{
char buf[1024];
int NumReceived = m_Socket.Receive(buf, sizeof(buf), 0);
if (NumReceived == 0)
{
fprintf(stderr, "The remote end closed the connection. Aborting.");
return false;
}
if (NumReceived < 0)
{
fprintf(stderr, "Network error while receiving response: %d, %d (%s). Aborting.",
NumReceived, cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
);
return false;
}
Buffer.Write(buf, NumReceived);
Buffer.ResetRead();
// Check if the buffer contains the full packet:
if (!Buffer.CanReadBytes(14))
{
// 14 is the minimum packet size for RCON
continue;
}
int PacketSize;
VERIFY(Buffer.ReadLEInt(PacketSize));
if (!Buffer.CanReadBytes(PacketSize))
{
// The packet is not complete yet
continue;
}
// Parse the packet
return ParsePacket(Buffer, PacketSize);
}
}
bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength)
{
// Check that the request ID is equal
bool IsValid = true;
int RequestID = 0;
VERIFY(a_Buffer.ReadLEInt(RequestID));
if (RequestID != m_RequestID)
{
if ((RequestID == -1) && (m_RequestID == 0))
{
fprintf(stderr, "Login failed. Aborting.");
IsValid = false;
// Continue, so that the payload is printed before the program aborts.
}
else
{
fprintf(stderr, "The server returned an invalid request ID, got %d, exp. %d. Aborting.", RequestID, m_RequestID);
return false;
}
}
// Check the packet type:
int PacketType = 0;
VERIFY(a_Buffer.ReadLEInt(PacketType));
if (PacketType != ptCommand)
{
fprintf(stderr, "The server returned an unknown packet type: %d. Aborting.", PacketType);
IsValid = false;
// Continue, so that the payload is printed before the program aborts.
}
AString Payload;
VERIFY(a_Buffer.ReadString(Payload, a_PacketLength - 10));
// Dump the payload to stdout, in a binary mode
fwrite(Payload.data(), Payload.size(), 1, stdout);
if (IsValid)
{
m_RequestID++;
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// main:
int RealMain(int argc, char * argv[])
{
new cMCLogger; // Create a new logger
// Parse the cmdline params for server IP, port, password and the commands to send:
AString ServerAddress, Password;
int ServerPort = -1;
AStringVector Commands;
for (int i = 1; i < argc; i++)
{
if (((NoCaseCompare(argv[i], "-s") == 0) || (NoCaseCompare(argv[i], "--server") == 0)) && (i < argc - 1))
{
ServerAddress = argv[i + 1];
i++;
continue;
}
if (((NoCaseCompare(argv[i], "-p") == 0) || (NoCaseCompare(argv[i], "--port") == 0)) && (i < argc - 1))
{
ServerPort = atoi(argv[i + 1]);
i++;
continue;
}
if (((NoCaseCompare(argv[i], "-w") == 0) || (NoCaseCompare(argv[i], "--password") == 0)) && (i < argc - 1))
{
Password = argv[i + 1];
i++;
continue;
}
if (((NoCaseCompare(argv[i], "-c") == 0) || (NoCaseCompare(argv[i], "--cmd") == 0) || (NoCaseCompare(argv[i], "--command") == 0)) && (i < argc - 1))
{
Commands.push_back(argv[i + 1]);
i++;
continue;
}
if (((NoCaseCompare(argv[i], "-f") == 0) || (NoCaseCompare(argv[i], "--file") == 0)) && (i < argc - 1))
{
i++;
cFile f(argv[i], cFile::fmRead);
if (!f.IsOpen())
{
fprintf(stderr, "Cannot read commands from file \"%s\", aborting.", argv[i]);
return 2;
}
AString cmd;
f.ReadRestOfFile(cmd);
Commands.push_back(cmd);
continue;
}
if ((NoCaseCompare(argv[i], "-v") == 0) || (NoCaseCompare(argv[i], "--verbose") == 0))
{
fprintf(stderr, "Verbose output enabled\n");
g_IsVerbose = true;
continue;
}
fprintf(stderr, "Unknown parameter: \"%s\". Aborting.", argv[i]);
return 1;
} // for i - argv[]
if (ServerAddress.empty() || (ServerPort < 0))
{
fprintf(stderr, "Server address or port not set. Use the --server and --port parameters to set them. Aborting.");
return 1;
}
// Connect:
if (cSocket::WSAStartup() != 0)
{
fprintf(stderr, "Cannot initialize network stack. Aborting\n");
return 6;
}
if (g_IsVerbose)
{
fprintf(stderr, "Connecting to \"%s:%d\"...\n", ServerAddress.c_str(), ServerPort);
}
cSocket s = cSocket::CreateSocket(cSocket::IPv4);
if (!s.ConnectIPv4(ServerAddress, (unsigned short)ServerPort))
{
fprintf(stderr, "Cannot connect to \"%s:%d\": %s\n", ServerAddress.c_str(), ServerPort, cSocket::GetLastErrorString().c_str());
return 3;
}
cRCONPacketizer Packetizer(s);
// Authenticate using the provided password:
if (!Password.empty())
{
if (g_IsVerbose)
{
fprintf(stderr, "Sending the login packet...\n");
}
if (!Packetizer.SendPacket(cRCONPacketizer::ptLogin, Password))
{
// Error message has already been printed, bail out
return 4;
}
}
else
{
if (g_IsVerbose)
{
fprintf(stderr, "No password provided, not sending a login packet.\n");
}
}
for (AStringVector::const_iterator itr = Commands.begin(), end = Commands.end(); itr != end; ++itr)
{
if (g_IsVerbose)
{
fprintf(stderr, "Sending command \"%s\"...\n", itr->c_str());
}
if (!Packetizer.SendPacket(cRCONPacketizer::ptCommand, *itr))
{
return 5;
}
}
return 0;
}
int main(int argc, char * argv[])
{
// This redirection function is only so that debugging the program is easier in MSVC - when RealMain exits, it's still possible to place a breakpoint
int res = RealMain(argc, argv);
return res;
}