summaryrefslogtreecommitdiffstats
path: root/Tools/RCONClient/RCONClient.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Tools/RCONClient/RCONClient.cpp332
1 files changed, 332 insertions, 0 deletions
diff --git a/Tools/RCONClient/RCONClient.cpp b/Tools/RCONClient/RCONClient.cpp
new file mode 100644
index 000000000..288363a66
--- /dev/null
+++ b/Tools/RCONClient/RCONClient.cpp
@@ -0,0 +1,332 @@
+
+// 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;
+}
+
+
+
+