From dbb76ef9fefa90a1e24acc42ba0421980df89379 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 24 Nov 2013 14:35:35 +0100 Subject: RCONClient: Initial implementation. Fix #79. --- Tools/RCONClient/.gitignore | 7 + Tools/RCONClient/Globals.cpp | 10 ++ Tools/RCONClient/Globals.h | 208 +++++++++++++++++++++++ Tools/RCONClient/RCONClient.cpp | 332 +++++++++++++++++++++++++++++++++++++ Tools/RCONClient/RCONClient.sln | 20 +++ Tools/RCONClient/RCONClient.vcproj | 287 ++++++++++++++++++++++++++++++++ 6 files changed, 864 insertions(+) create mode 100644 Tools/RCONClient/.gitignore create mode 100644 Tools/RCONClient/Globals.cpp create mode 100644 Tools/RCONClient/Globals.h create mode 100644 Tools/RCONClient/RCONClient.cpp create mode 100644 Tools/RCONClient/RCONClient.sln create mode 100644 Tools/RCONClient/RCONClient.vcproj (limited to 'Tools/RCONClient') diff --git a/Tools/RCONClient/.gitignore b/Tools/RCONClient/.gitignore new file mode 100644 index 000000000..e124797c5 --- /dev/null +++ b/Tools/RCONClient/.gitignore @@ -0,0 +1,7 @@ +Debug/ +logs/ +Release/ +*.suo +*.user +*.ncb +*.sdf diff --git a/Tools/RCONClient/Globals.cpp b/Tools/RCONClient/Globals.cpp new file mode 100644 index 000000000..13c6ae709 --- /dev/null +++ b/Tools/RCONClient/Globals.cpp @@ -0,0 +1,10 @@ + +// Globals.cpp + +// This file is used for precompiled header generation in MSVC environments + +#include "Globals.h" + + + + diff --git a/Tools/RCONClient/Globals.h b/Tools/RCONClient/Globals.h new file mode 100644 index 000000000..a3a2f2846 --- /dev/null +++ b/Tools/RCONClient/Globals.h @@ -0,0 +1,208 @@ + +// Globals.h + +// This file gets included from every module in the project, so that global symbols may be introduced easily +// Also used for precompiled header generation in MSVC environments + + + + + +// Compiler-dependent stuff: +#if defined(_MSC_VER) + // MSVC produces warning C4481 on the override keyword usage, so disable the warning altogether + #pragma warning(disable:4481) + + // Disable some warnings that we don't care about: + #pragma warning(disable:4100) + + #define OBSOLETE __declspec(deprecated) + + // No alignment needed in MSVC + #define ALIGN_8 + #define ALIGN_16 + +#elif defined(__GNUC__) + + // TODO: Can GCC explicitly mark classes as abstract (no instances can be created)? + #define abstract + + // TODO: Can GCC mark virtual methods as overriding (forcing them to have a virtual function of the same signature in the base class) + #define override + + #define OBSOLETE __attribute__((deprecated)) + + #define ALIGN_8 __attribute__((aligned(8))) + #define ALIGN_16 __attribute__((aligned(16))) + + // Some portability macros :) + #define stricmp strcasecmp + +#else + + #error "You are using an unsupported compiler, you might need to #define some stuff here for your compiler" + + /* + // Copy and uncomment this into another #elif section based on your compiler identification + + // Explicitly mark classes as abstract (no instances can be created) + #define abstract + + // Mark virtual methods as overriding (forcing them to have a virtual function of the same signature in the base class) + #define override + + // Mark functions as obsolete, so that their usage results in a compile-time warning + #define OBSOLETE + + // Mark types / variables for alignment. Do the platforms need it? + #define ALIGN_8 + #define ALIGN_16 + */ + +#endif + + + + + +// Integral types with predefined sizes: +typedef long long Int64; +typedef int Int32; +typedef short Int16; + +typedef unsigned long long UInt64; +typedef unsigned int UInt32; +typedef unsigned short UInt16; + + + + + +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for any class that shouldn't allow copying itself +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName &); \ + void operator=(const TypeName &) + +// A macro that is used to mark unused function parameters, to avoid pedantic warnings in gcc +#define UNUSED(X) (void)(X) + + + + +// OS-dependent stuff: +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + + #define _WIN32_WINNT 0x501 // We want to target WinXP and higher + + #include + #include + #include // IPv6 stuff + + // Windows SDK defines min and max macros, messing up with our std::min and std::max usage + #undef min + #undef max + + // Windows SDK defines GetFreeSpace as a constant, probably a Win16 API remnant + #ifdef GetFreeSpace + #undef GetFreeSpace + #endif // GetFreeSpace +#else + #include + #include // for mkdir + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include + #include + #include + #include + #include + #include +#if !defined(ANDROID_NDK) + #include +#endif +#endif + +#if !defined(ANDROID_NDK) + #define USE_SQUIRREL +#endif + +#if defined(ANDROID_NDK) + #define FILE_IO_PREFIX "/sdcard/mcserver/" +#else + #define FILE_IO_PREFIX "" +#endif + + + + + +// CRT stuff: +#include +#include +#include +#include +#include +#include + + + + + +// STL stuff: +#include +#include +#include +#include +#include +#include +#include + + + + + +// Common headers (part 1, without macros): +#include "StringUtils.h" +#include "OSSupport/CriticalSection.h" +#include "OSSupport/File.h" +#include "MCLogger.h" + + + + + +// Common definitions: + +/// Evaluates to the number of elements in an array (compile-time!) +#define ARRAYCOUNT(X) (sizeof(X) / sizeof(*(X))) + +/// Allows arithmetic expressions like "32 KiB" (but consider using parenthesis around it, "(32 KiB)" ) +#define KiB * 1024 + +/// Faster than (int)floorf((float)x / (float)div) +#define FAST_FLOOR_DIV( x, div ) ( (x) < 0 ? (((int)x / div) - 1) : ((int)x / div) ) + +// Own version of assert() that writes failed assertions to the log for review +#ifdef NDEBUG + #define ASSERT(x) ((void)0) +#else + #define ASSERT assert +#endif + +// Pretty much the same as ASSERT() but stays in Release builds +#define VERIFY( x ) ( !!(x) || ( LOGERROR("Verification failed: %s, file %s, line %i", #x, __FILE__, __LINE__ ), exit(1), 0 ) ) + + + + + 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; +} + + + + diff --git a/Tools/RCONClient/RCONClient.sln b/Tools/RCONClient/RCONClient.sln new file mode 100644 index 000000000..0a8596e43 --- /dev/null +++ b/Tools/RCONClient/RCONClient.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RCONClient", "RCONClient.vcproj", "{1A48B032-07D0-4DDD-8362-66C0FC7F7849}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A48B032-07D0-4DDD-8362-66C0FC7F7849}.Debug|Win32.ActiveCfg = Debug|Win32 + {1A48B032-07D0-4DDD-8362-66C0FC7F7849}.Debug|Win32.Build.0 = Debug|Win32 + {1A48B032-07D0-4DDD-8362-66C0FC7F7849}.Release|Win32.ActiveCfg = Release|Win32 + {1A48B032-07D0-4DDD-8362-66C0FC7F7849}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Tools/RCONClient/RCONClient.vcproj b/Tools/RCONClient/RCONClient.vcproj new file mode 100644 index 000000000..59d0b3b74 --- /dev/null +++ b/Tools/RCONClient/RCONClient.vcproj @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3