commit 1b8e531f29604bb32429f1b205cd5415c84bd436 Author: Bruno Rybársky Date: Wed May 8 10:24:32 2024 +0200 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf797c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +cmake-build-debug \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..7ef7ea6 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,9 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +cmake-build-debug \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..d8e9561 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..502be43 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/mcpinger.iml b/.idea/mcpinger.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/mcpinger.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0b76fe5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5380244 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5679fd7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.29) +project(mcpinger) + +set(CMAKE_CXX_STANDARD 26) + +add_executable(mcpinger main.cpp + main.cpp + cli_parser.cpp + cli_parser.h + protocol_utils.cpp + protocol_utils.h + network_utils.cpp + network_utils.h) diff --git a/cli_parser.cpp b/cli_parser.cpp new file mode 100644 index 0000000..0b52f9a --- /dev/null +++ b/cli_parser.cpp @@ -0,0 +1,68 @@ +#include "cli_parser.h" +#include +#include + +void config::parseCommandLine(int argc, char* argv[]) { + const struct option longOptions[] = { + {"host", required_argument, nullptr, 'h'}, + {"port", required_argument, nullptr, 'p'}, + {"vhost", required_argument, nullptr, 'v'}, + {"vport", required_argument, nullptr, 'b'}, + {"version", required_argument, nullptr, 'r'}, + {"state", required_argument, nullptr, 's'}, + {"help", no_argument, nullptr, 0}, + {nullptr, 0, nullptr, 0} + }; + + int opt; + while ((opt = getopt_long(argc, argv, "h:p:v:b:r:s:", longOptions, nullptr)) != -1) { + switch (opt) { + case 'h': + hostname = optarg; + break; + case 'p': + port = std::stoi(optarg); + break; + case 'v': + virtualHostname = optarg; + break; + case 'b': + virtualPort = std::stoi(optarg); + break; + case 'r': + protocolVersion = std::stoi(optarg); + break; + case 's': + nextState = std::stoi(optarg); + break; + case 0: // --help + printHelp(); + exit(0); + default: // '?' + printHelp(); + exit(1); + } + } + + if (hostname.empty()) { + std::cerr << "Error: Hostname is required.\n"; + printHelp(); + exit(1); + } + + if (virtualHostname.empty()) { + virtualHostname = hostname; // Default to real hostname + } +} + +void config::printHelp() { + std::cout << "Usage: mcpinger [options]\n" + << "Options:\n" + << " --host Real server hostname (required)\n" + << " --port Real server port (default 25565)\n" + << " --vhost Virtual hostname (default is same as host)\n" + << " --vport Virtual port (default is same as port)\n" + << " --version Protocol version (default -1)\n" + << " --state Next state (default 1, status request)\n" + << " --help Show this help message\n"; +} diff --git a/cli_parser.h b/cli_parser.h new file mode 100644 index 0000000..28e12ff --- /dev/null +++ b/cli_parser.h @@ -0,0 +1,18 @@ +#ifndef MCPINGER_CLI_PARSER_H +#define MCPINGER_CLI_PARSER_H + +#include + +struct config { + std::string hostname; + std::string virtualHostname; + int port = 25565; + int virtualPort = 25565; + int protocolVersion = -1; + int nextState = 1; + + void parseCommandLine(int argc, char* argv[]); + static void printHelp() ; +}; + +#endif // MCPINGER_CLI_PARSER_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..b14c343 --- /dev/null +++ b/main.cpp @@ -0,0 +1,8 @@ +#include "cli_parser.h" +#include "network_utils.h" + +int main(int argc, char* argv[]) { + config config; + config.parseCommandLine(argc, argv); + return pingMinecraftServer(config.hostname, config.port, config.virtualHostname, config.virtualPort, config.protocolVersion, config.nextState); +} diff --git a/network_utils.cpp b/network_utils.cpp new file mode 100644 index 0000000..f8d7672 --- /dev/null +++ b/network_utils.cpp @@ -0,0 +1,107 @@ +#include "network_utils.h" +#include +#include +#include +#include +#include +#include +#include "protocol_utils.h" + +int sendData(int sockfd, const std::vector& data) { + ssize_t sent = send(sockfd, data.data(), data.size(), 0); + if (sent < 0) { + perror("Error sending data"); + return ERROR_SENDING_DATA; + } + return SUCCESS; +} + +int receiveData(int sockfd, std::vector& buffer) { + buffer.resize(1024); + ssize_t bytesReceived = recv(sockfd, buffer.data(), buffer.size(), 0); + if (bytesReceived < 0) { + perror("Error receiving data"); + return ERROR_RECEIVING_DATA; + } + buffer.resize(bytesReceived); + return SUCCESS; +} + +int pingMinecraftServer(const std::string& hostname, int port, const std::string& virtualHostname, int virtualPort, int protocolVersionIn, int nextStateIn) { + struct addrinfo hints{}, *res, *result; + int sockfd, err; + + (void)memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + err = getaddrinfo(hostname.c_str(), std::to_string(port).c_str(), &hints, &result); + if (err != 0) { + std::cerr << "Error resolving hostname: " << gai_strerror(err) << std::endl; + return ERROR_RESOLVING_HOSTNAME; + } + + for (res = result; res != nullptr; res = res->ai_next) { + sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sockfd == -1) continue; + + if (connect(sockfd, res->ai_addr, res->ai_addrlen) != -1) break; + + (void)close(sockfd); + } + + if (res == nullptr) { + std::cerr << "Could not connect to any address" << std::endl; + freeaddrinfo(result); + return ERROR_CONNECTION_FAILED; + } + + freeaddrinfo(result); + + // Handshake + std::vector packet; + packet.push_back(0x00U); // Packet ID for handshake + std::vector protocolVersion = encodeVarint(protocolVersionIn); + std::vector serverAddress = encodeString(virtualHostname); + std::vector serverPort = {static_cast((virtualPort >> 8) & 0xFFU), static_cast(virtualPort & 0xFFU) }; + std::vector nextState = encodeVarint(nextStateIn); + + (void)packet.insert(packet.end(), protocolVersion.begin(), protocolVersion.end()); + (void)packet.insert(packet.end(), serverAddress.begin(), serverAddress.end()); + (void)packet.insert(packet.end(), serverPort.begin(), serverPort.end()); + (void)packet.insert(packet.end(), nextState.begin(), nextState.end()); + + std::vector handshakeLength = encodeVarint(packet.size()); + (void)packet.insert(packet.begin(), handshakeLength.begin(), handshakeLength.end()); + + if (sendData(sockfd, packet) != SUCCESS) { + (void)close(sockfd); + return ERROR_SENDING_DATA; + } + + // Status request + std::vector statusRequest = {0x01U, 0x00U}; // Packet length and Packet ID for status request + if (sendData(sockfd, statusRequest) != SUCCESS) { + (void)close(sockfd); + return ERROR_SENDING_DATA; + } + + // Read status response + std::vector response; + if (receiveData(sockfd, response) != SUCCESS) { + (void)close(sockfd); + return ERROR_RECEIVING_DATA; + } + + // Extract JSON string (assuming it starts from the position of the first '{' found in the response) + auto pos = std::find(response.begin(), response.end(), '{'); + if (pos != response.end()) { + std::string json(pos, response.end()); + std::cout << json << std::endl; + } else { + std::cerr << "Failed to find JSON response in server data." << std::endl; + } + + (void)close(sockfd); + return SUCCESS; +} diff --git a/network_utils.h b/network_utils.h new file mode 100644 index 0000000..efdde9a --- /dev/null +++ b/network_utils.h @@ -0,0 +1,17 @@ +#ifndef MCPINGER_NETWORK_UTILS_H +#define MCPINGER_NETWORK_UTILS_H + +#include +#include + +#define SUCCESS 0 +#define ERROR_RESOLVING_HOSTNAME (-2) +#define ERROR_CONNECTION_FAILED (-3) +#define ERROR_SENDING_DATA (-4) +#define ERROR_RECEIVING_DATA (-5) + +int sendData(int sockfd, const std::vector& data); +int receiveData(int sockfd, std::vector& buffer); +int pingMinecraftServer(const std::string& hostname, int port, const std::string& virtualHostname, int virtualPort, int protocolVersion, int nextState); + +#endif // MCPINGER_NETWORK_UTILS_H diff --git a/protocol_utils.cpp b/protocol_utils.cpp new file mode 100644 index 0000000..68683bd --- /dev/null +++ b/protocol_utils.cpp @@ -0,0 +1,21 @@ +#include "protocol_utils.h" + +std::vector encodeVarint(int value) { + std::vector varint; + auto uValue = static_cast(value); + do { + unsigned char temp = uValue & 0x7FU; + uValue >>= 7; + if (uValue != 0) { + temp |= 0x80; + } + varint.push_back(temp); + } while (uValue != 0); + return varint; +} + +std::vector encodeString(const std::string& str) { + std::vector data = encodeVarint(str.length()); + (void)data.insert(data.end(), str.begin(), str.end()); + return data; +} diff --git a/protocol_utils.h b/protocol_utils.h new file mode 100644 index 0000000..21e390d --- /dev/null +++ b/protocol_utils.h @@ -0,0 +1,10 @@ +#ifndef MCPINGER_PROTOCOL_UTILS_H +#define MCPINGER_PROTOCOL_UTILS_H + +#include +#include + +std::vector encodeVarint(int value); +std::vector encodeString(const std::string& str); + +#endif // MCPINGER_PROTOCOL_UTILS_H