Refactor color handling and add error handling to UTF-8 code

This commit is contained in:
Tiiffi
2025-11-21 15:53:39 +02:00
parent 1518d96475
commit 142479f9ee

123
mcrcon.c
View File

@@ -30,6 +30,7 @@
#include <signal.h> #include <signal.h>
#include <errno.h> #include <errno.h>
#include <unistd.h> #include <unistd.h>
#include <ctype.h>
#ifdef _WIN32 #ifdef _WIN32
#include <winsock2.h> #include <winsock2.h>
@@ -456,6 +457,69 @@ rc_packet *net_recv_packet(int sd)
return &packet; return &packet;
} }
/* NOTE: At least Bukkit/Spigot servers are sending formatting via rcon but not sure how common this
* behaviour is. This implementation tries to print colors but is omitting "bold" in "bright" colors
* because bold tag should be manually reset and Bukkit/Spigot is not doing this.
* References: https://minecraft.fandom.com/wiki/Formatting_codes
* https://en.wikipedia.org/wiki/ANSI_escape_code
*/
void set_color(int c)
{
c = tolower(c);
#ifdef _WIN32
// Map color Minecraft color codes to WinAPI colors
if (c >= '0' && c <= '9') {
c -= '0';
}
else if (c >= 'a' && c <= 'f') {
c -= 87;
}
else return;
SetConsoleTextAttribute(console_handle, c);
#else
/* NOTE: Using "bold" for bright colors for now. This is not correct exactly
* because it resets other formattings and must be reseted manually.
* This should match Bukkit/Spigot server console output though
*/
const char *ansi_escape;
switch (c)
{
case '0': ansi_escape = "\033[30m"; break; // Black
case '1': ansi_escape = "\033[34m"; break; // Blue
case '2': ansi_escape = "\033[32m"; break; // Green
case '3': ansi_escape = "\033[36m"; break; // Cyan
case '4': ansi_escape = "\033[31m"; break; // Red
case '5': ansi_escape = "\033[35m"; break; // Magenta
case '6': ansi_escape = "\033[33m"; break; // Yellow
case '7': ansi_escape = "\033[37m"; break; // White
case '8': ansi_escape = "\033[1;90m"; break; // Bright black (gray)
case '9': ansi_escape = "\033[1;94m"; break; // Bright blue
case 'a': ansi_escape = "\033[1;92m"; break; // Bright green
case 'b': ansi_escape = "\033[1;96m"; break; // Bright cyan
case 'c': ansi_escape = "\033[1;91m"; break; // Bright red
case 'd': ansi_escape = "\033[1;95m"; break; // Bright magenta
case 'e': ansi_escape = "\033[1;93m"; break; // Bright yellow
case 'f': ansi_escape = "\033[1;97m"; break; // Bright white
case 'k': ansi_escape = "\033[08m"; break; // Concealed text
case 'l': ansi_escape = "\033[01m"; break; // Bold text
case 'm': ansi_escape = "\033[09m"; break; // Strikethrough text
case 'n': ansi_escape = "\033[03m"; break; // Italic text
case 'o': ansi_escape = "\033[04m"; break; // Underlined text
case 0: // fallthrough
case 'r': ansi_escape = "\033[00m"; break; // Reset all attributes
default: return;
}
fputs(ansi_escape, stdout);
#endif
}
// NOTE: Old version!
void print_color(int color) void print_color(int color)
{ {
// sh compatible color array // sh compatible color array
@@ -480,11 +544,11 @@ void print_color(int color)
"\033[4m" /* 16 UNDERLINE 0x6e */ "\033[4m" /* 16 UNDERLINE 0x6e */
}; };
if (color == 0 || color == 0x72) { if (color == 0 || color == 'r') {
fputs("\033[0m", stdout); // cancel color fputs("\033[0m", stdout); // cancel color
} }
else else
#endif #endif
{ {
if (color >= 0x61 && color <= 0x66) color -= 0x57; if (color >= 0x61 && color <= 0x66) color -= 0x57;
else if (color >= 0x30 && color <= 0x39) else if (color >= 0x30 && color <= 0x39)
@@ -497,7 +561,7 @@ void print_color(int color)
fputs(colors[color], stdout); fputs(colors[color], stdout);
#else #else
SetConsoleTextAttribute(console_handle, color); SetConsoleTextAttribute(console_handle, color);
#endif #endif
} }
} }
@@ -523,7 +587,7 @@ void packet_print(rc_packet *packet)
} }
} }
int default_color = 0; int default_color = 'r';
#ifdef _WIN32 #ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO console_info; CONSOLE_SCREEN_BUFFER_INFO console_info;
@@ -538,15 +602,14 @@ void packet_print(rc_packet *packet)
for (i = 0; data[i] != 0; ++i) for (i = 0; data[i] != 0; ++i)
{ {
if (data[i] == 0x0A) { // Check for '§' (utf-8 section sign). Bukkit is using this for colors.
print_color(default_color); if(data[i] == 0xc2 && data[i + 1] == 0xa7) {
}
else if(data[i] == 0xc2 && data[i + 1] == 0xa7) {
// Disable new line fixes if Bukkit colors are detected // Disable new line fixes if Bukkit colors are detected
colors_detected = true; colors_detected = true;
i += 2; i += 2;
if (flag_disable_colors == 0) { if (flag_disable_colors == 0) {
print_color(data[i]); //print_color(data[i]);
set_color(data[i]);
} }
continue; continue;
} }
@@ -558,7 +621,9 @@ void packet_print(rc_packet *packet)
putchar(data[i]); putchar(data[i]);
} }
print_color(default_color); // cancel coloring
set_color(default_color); // reset color
//print_color(default_color); // cancel coloring
// print newline if string has no newline // print newline if string has no newline
if (data[i - 1] != '\n') { if (data[i - 1] != '\n') {
@@ -770,7 +835,6 @@ int run_terminal_mode(int sock)
#ifdef _WIN32 #ifdef _WIN32
char *utf8_getline(char *buf, int size, FILE *stream) char *utf8_getline(char *buf, int size, FILE *stream)
{ {
// Widechar fgets
wchar_t in[MAX_COMMAND_LENGTH] = {0}; wchar_t in[MAX_COMMAND_LENGTH] = {0};
wchar_t *result = fgetws(in, MAX_COMMAND_LENGTH, stream); wchar_t *result = fgetws(in, MAX_COMMAND_LENGTH, stream);
@@ -778,34 +842,27 @@ char *utf8_getline(char *buf, int size, FILE *stream)
return NULL; return NULL;
} }
// Calculates UTF-8 buffer size size_t length = wcslen(in);
if (length > 0 && in[length - 1] == L'\n') {
in[length - 1] = L'\0';
}
// Calculate UTF-8 buffer size
int required_size = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL); int required_size = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL);
if (size < required_size) { if (required_size > size || required_size == 0) {
// TODO: Proper error handling & reporting log_error("Widechar to UTF-8 conversion failed.\n");
return NULL; exit(EXIT_FAILURE);
} }
if (WideCharToMultiByte(CP_UTF8, 0, in, -1, buf, size, NULL, NULL) == 0) { if (WideCharToMultiByte(CP_UTF8, 0, in, -1, buf, size, NULL, NULL) == 0) {
// TODO: Proper error handling & reporting log_error("Widechar to UTF-8 conversion failed.\n");
return NULL; exit(EXIT_FAILURE);
} }
return buf; return buf;
} }
#endif #endif
/*
void flush_input(void)
{
#ifdef _WIN32
// NOTE: Undefined behaviour in C standard but Windows allows it
fflush(stdin);
#else
__fpurge(stdin);
#endif
}
*/
// gets line from stdin and deals with rubbish left in the input buffer // gets line from stdin and deals with rubbish left in the input buffer
int get_line(char *buffer, int bsize) int get_line(char *buffer, int bsize)
{ {
@@ -820,23 +877,19 @@ int get_line(char *buffer, int bsize)
log_error("Error %d: %s\n", errno, strerror(errno)); log_error("Error %d: %s\n", errno, strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
// EOF
putchar('\n'); putchar('\n');
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }
// remove unwanted characters from the buffer // remove unwanted characters from the buffer
buffer[strcspn(buffer, "\r\n")] = '\0'; buffer[strcspn(buffer, "\r\n")] = '\0';
#ifdef _WIN32
// NOTE: Undefined behaviour in C standard but Windows allows it
fflush(stdin);
#else
int len = strlen(buffer); int len = strlen(buffer);
if (len == bsize - 1) { if (len == bsize - 1) {
int ch; int ch;
while ((ch = getchar()) != '\n' && ch != EOF); while ((ch = getchar()) != '\n' && ch != EOF);
} }
#endif
return len; return len;
} }