3 Commits

Author SHA1 Message Date
b11429d418 Enable input/output buffering and fflush only explicitly
As suggested in old pull request: #39
2024-12-04 16:33:49 +02:00
e9f8b0e76f Remove Valve protocol checks and set select() timeout to 1.5 seconds
Use the same method for handling multipacket responses across all servers.
2024-12-04 16:30:53 +02:00
c607925b2e Implement select() loop to receive all incoming packets:
Send a "multipacket guard" - an empty packet with an invalid 'cmd' field
and a unique packet ID to trigger a reply from the server once the previous
command's reply has been fully sent.

Valve returns an empty payload, while Minecraft includes an error message in the payload.

This workaround ensures that all packets related to the last valid command
are received from the server, avoiding the need to wait for the select() timeout.
2024-12-04 12:58:49 +02:00

View File

@ -95,7 +95,6 @@ void packet_print(rc_packet *packet);
bool rcon_auth(int sock, char *passwd);
int rcon_command(int sock, char *command);
// =============================================
// GLOBAL VARIABLES
// =============================================
@ -104,6 +103,7 @@ static int flag_silent_mode = 0;
static int flag_disable_colors = 0;
static int flag_wait_seconds = 0;
static int global_connection_alive = 1;
static bool global_valve_protocol = false;
static int global_rsock;
#ifdef _WIN32
@ -166,10 +166,6 @@ int main(int argc, char *argv[])
if (!port) port = "25575";
if (!host) host = "localhost";
// disable output buffering (https://github.com/Tiiffi/mcrcon/pull/39)
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
if(argc < 1 && pass == NULL) usage();
// default getopt error handler enabled
@ -527,6 +523,7 @@ void packet_print(rc_packet *packet)
if (data[i - 1] != 10 && data[i - 1] != 13) {
putchar('\n');
}
fflush(stdout);
}
rc_packet *packet_build(int id, int cmd, char s[static 1])
@ -573,6 +570,7 @@ receive:
* so we have to check packet type and try again if necessary.
*/
if (packet->cmd != RCON_AUTH_RESPONSE) {
global_valve_protocol = true;
goto receive;
}
@ -580,7 +578,8 @@ receive:
return packet->id == -1 ? false : true;
}
// TODO: Add proper error handling and reporting!
// TODO: Create function that sends two packets in one send() call
// This is important so multipacket guard packet is sent right after real packet
int rcon_command(int sock, char *command)
{
rc_packet *packet = packet_build(RCON_PID, RCON_EXEC_COMMAND, command);
@ -594,22 +593,60 @@ int rcon_command(int sock, char *command)
return 0;
}
// Workaround to handle valve multipacket responses
// This one does not require using select()
packet = packet_build(0xBADA55, 0xBADA55, "");
if (packet == NULL) {
log_error("Error: packet build() failed!\n");
return 0;
}
if (!net_send_packet(sock, packet)) {
log_error("Error: net_send_packet() failed!\n");
return 0;
}
// initialize stuff for select()
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(sock, &read_fds);
// Set 1.5 second timeout in case there is no response for multipacket guard
struct timeval timeout = {0};
timeout.tv_sec = 1;
timeout.tv_usec = 500000;
int incoming = 0;
do {
packet = net_recv_packet(sock);
if (packet == NULL) {
log_error("Error: net_recv_packet() failed!\n");
return 0;
}
if (packet->id != RCON_PID) {
// Check for packet id and multipacket guard id
if (packet->id != RCON_PID && packet->id != 0xBADA55) {
log_error("Error: invalid packet id!\n");
return 0;
}
if (packet->id == 0xBADA55) break;
if (!flag_silent_mode) {
if (packet->size > 10)
packet_print(packet);
}
int result = select(sock + 1, &read_fds, NULL, NULL, &timeout);
if (result == -1) {
log_error("Error: select() failed!\n");
return 0;
}
incoming = (result > 0 && FD_ISSET(sock, &read_fds));
}
while(incoming);
return 1;
}
@ -645,6 +682,7 @@ int run_terminal_mode(int sock)
while (global_connection_alive) {
putchar('>');
fflush(stdout);
int len = get_line(command, MAX_COMMAND_LENGTH);
if (len < 1) continue;