Created
January 14, 2026 18:08
-
-
Save hydranix/7cdd9849d1fb511417696fbb85ed6b5d to your computer and use it in GitHub Desktop.
Quick dirty way to get player list from any counter strike source server. (Don't spam it...)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <cstring> | |
| #include <iomanip> | |
| #include <iostream> | |
| #include <string> | |
| #include <vector> | |
| #ifdef _WIN32 | |
| #include <winsock2.h> | |
| #include <ws2tcpip.h> | |
| #pragma comment(lib, "Ws2_32.lib") | |
| #else | |
| #include <arpa/inet.h> | |
| #include <netinet/in.h> | |
| #include <sys/socket.h> | |
| #include <unistd.h> | |
| #endif | |
| // Function to initialize the socket library (Windows only) | |
| void initialize_sockets() | |
| { | |
| #ifdef _WIN32 | |
| WSADATA wsaData; | |
| if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) | |
| { | |
| std::cerr << "WSAStartup failed." << std::endl; | |
| exit(1); | |
| } | |
| #endif | |
| } | |
| // Function to clean up the socket library (Windows only) | |
| void cleanup_sockets() | |
| { | |
| #ifdef _WIN32 | |
| WSACleanup(); | |
| #endif | |
| } | |
| // Function to close a socket | |
| void close_socket(int sock) | |
| { | |
| #ifdef _WIN32 | |
| closesocket(sock); | |
| #else | |
| close(sock); | |
| #endif | |
| } | |
| // Helper function to read a null-terminated string from a buffer | |
| const char* read_string(const char* buffer, size_t& offset) | |
| { | |
| const char* str_start = buffer + offset; | |
| size_t len = strlen(str_start); | |
| offset += len + 1; // Move offset past the string and null terminator | |
| return str_start; | |
| } | |
| // Helper function to convert a float to a time string (e.g., 3600.0s -> 1h 0m 0s) | |
| std::string format_time(float seconds) | |
| { | |
| int hours = static_cast<int>(seconds / 3600); | |
| int minutes = static_cast<int>((seconds - hours * 3600) / 60); | |
| int secs = static_cast<int>(seconds) % 60; | |
| return std::to_string(hours) + "h " + std::to_string(minutes) + "m " + std::to_string(secs) + "s"; | |
| } | |
| int main(int argc, char* argv[]) | |
| { | |
| if (argc != 3) | |
| { | |
| std::cerr << "Usage: " << argv[0] << " <server_ip> <server_port>" << std::endl; | |
| return 1; | |
| } | |
| // Server details | |
| const char* server_ip = argv[1]; | |
| int server_port = std::stoi(argv[2]); | |
| initialize_sockets(); | |
| // Create a UDP socket | |
| int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); | |
| if (sock == -1) | |
| { | |
| std::cerr << "Failed to create socket." << std::endl; | |
| cleanup_sockets(); | |
| return 1; | |
| } | |
| // Set a timeout for the socket | |
| struct timeval tv; | |
| tv.tv_sec = 5; // 5 second timeout | |
| tv.tv_usec = 0; | |
| #ifdef _WIN32 | |
| DWORD timeout = 5000; | |
| setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); | |
| #else | |
| setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); | |
| #endif | |
| // Server address shit | |
| struct sockaddr_in server_addr; | |
| server_addr.sin_family = AF_INET; | |
| server_addr.sin_port = htons(server_port); | |
| #ifdef _WIN32 | |
| server_addr.sin_addr.s_addr = inet_addr(server_ip); | |
| #else | |
| inet_pton(AF_INET, server_ip, &server_addr.sin_addr); | |
| #endif | |
| // Request a challenge number | |
| std::cout << "Requesting challenge number..." << std::endl; | |
| const char challenge_request[] = "\xFF\xFF\xFF\xFF\x55\xFF\xFF\xFF\xFF"; | |
| if (sendto(sock, challenge_request, sizeof(challenge_request) - 1, 0, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) | |
| { | |
| std::cerr << "Failed to send challenge request." << std::endl; | |
| close_socket(sock); | |
| cleanup_sockets(); | |
| return 1; | |
| } | |
| char response_buffer[4096]; | |
| socklen_t addr_len = sizeof(server_addr); | |
| int bytes_received = recvfrom(sock, response_buffer, sizeof(response_buffer), 0, (struct sockaddr*)&server_addr, &addr_len); | |
| if (bytes_received <= 0) | |
| { | |
| std::cerr << "Failed to receive challenge response or timeout occurred." << std::endl; | |
| close_socket(sock); | |
| cleanup_sockets(); | |
| return 1; | |
| } | |
| // The challenge response should start with 0xFFFFFFFF, a header byte (0x41) and the challenge number | |
| if (bytes_received < 9 || memcmp(response_buffer, "\xFF\xFF\xFF\xFF\x41", 5) != 0) | |
| { | |
| std::cerr << "Invalid challenge response received." << std::endl; | |
| close_socket(sock); | |
| cleanup_sockets(); | |
| return 1; | |
| } | |
| int32_t challenge_number; | |
| memcpy(&challenge_number, response_buffer + 5, sizeof(int32_t)); | |
| std::cout << "Received challenge number: " << challenge_number << std::endl; | |
| // Request player information using the challenge number | |
| std::cout << "Requesting player information..." << std::endl; | |
| std::vector<char> player_request; | |
| player_request.push_back('\xFF'); | |
| player_request.push_back('\xFF'); | |
| player_request.push_back('\xFF'); | |
| player_request.push_back('\xFF'); | |
| player_request.push_back('\x55'); // A2S_PLAYER query header | |
| player_request.insert(player_request.end(), (char*)&challenge_number, (char*)&challenge_number + sizeof(int32_t)); | |
| if (sendto(sock, player_request.data(), player_request.size(), 0, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) | |
| { | |
| std::cerr << "Failed to send player info request." << std::endl; | |
| close_socket(sock); | |
| cleanup_sockets(); | |
| return 1; | |
| } | |
| // Receive player information | |
| bytes_received = recvfrom(sock, response_buffer, sizeof(response_buffer), 0, (struct sockaddr*)&server_addr, &addr_len); | |
| if (bytes_received <= 0) | |
| { | |
| std::cerr << "Failed to receive player info response or timeout occurred." << std::endl; | |
| close_socket(sock); | |
| cleanup_sockets(); | |
| return 1; | |
| } | |
| // Response format: 0xFFFFFFFF 0x44 <player_count> <player_list> | |
| if (bytes_received < 6 || memcmp(response_buffer, "\xFF\xFF\xFF\xFF\x44", 5) != 0) | |
| { | |
| std::cerr << "Invalid player info response received." << std::endl; | |
| close_socket(sock); | |
| cleanup_sockets(); | |
| return 1; | |
| } | |
| size_t offset = 5; // Skip the header | |
| uint8_t player_count = response_buffer[offset++]; | |
| std::cout << "\nFound " << static_cast<int>(player_count) << " players:" << std::endl; | |
| std::cout << "--------------------------------------------------------" << std::endl; | |
| std::cout << std::setw(4) << "ID" << std::setw(40) << "Name" << std::setw(10) << "Score" << std::setw(15) << "Playtime" << std::endl; | |
| std::cout << "--------------------------------------------------------" << std::endl; | |
| for (int i = 0; i < player_count; ++i) | |
| { | |
| // Player entry format: <index> <name> <score> <playtime> | |
| uint8_t player_index = response_buffer[offset++]; | |
| const char* player_name = read_string(response_buffer, offset); | |
| int32_t player_score; | |
| float player_playtime; | |
| memcpy(&player_score, response_buffer + offset, sizeof(int32_t)); | |
| offset += sizeof(int32_t); | |
| memcpy(&player_playtime, response_buffer + offset, sizeof(float)); | |
| offset += sizeof(float); | |
| std::cout << std::setw(4) << static_cast<int>(player_index) | |
| << std::setw(40) << player_name | |
| << std::setw(10) << player_score | |
| << std::setw(15) << format_time(player_playtime) << std::endl; | |
| } | |
| // Clean up | |
| close_socket(sock); | |
| cleanup_sockets(); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment