mcsrv/main.c

426 lines
12 KiB
C

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <arpa/inet.h>
#define PORT 6969
#define BUFFER_SIZE 10240
#define SEGMENT_BITS 0x7F
#define CONTINUE_BIT 0x80
#define UUID_STR_LEN (32 + 4)
#define sizeof_member(type, member) (sizeof( ((type *)0)->member ))
typedef uint8_t byte;
typedef struct MCString_view {
int len;
unsigned char* str;
} MCString_view;
typedef struct MCUUID {
byte byte[16];
char str[UUID_STR_LEN];
} MCUUID;
typedef struct File {
char* data;
int len;
} File;
byte buffer[BUFFER_SIZE] = {0};
ssize_t bytes_received = 0;
int current_byte = 0;
byte request[BUFFER_SIZE] = {0};
int req_current_byte = 0;
void print_hex(const char *str, int len)
{
for (int i = 0; i < len; i++) {
printf("0x%02x, ", (unsigned int) str[i]);
}
}
void print_str(const char *str, int len)
{
for (int i = 0; i < len; i++) {
printf("%c,", (unsigned int) str[i]);
}
printf("\n");
}
void close_connection(int client_fd)
{
bytes_received = 0;
current_byte = 0;
req_current_byte = 0;
close(client_fd);
printf("[TCP] connection closed\n");
}
byte read_byte(int client_fd)
{
if (current_byte >= bytes_received) {
bytes_received = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
current_byte = 0;
if (bytes_received > 0) {
buffer[bytes_received] = '\0';
printf("[TCP] %ld bytes reçu :\n", bytes_received);
print_hex((const char*)buffer, bytes_received > 20 ? 20 : bytes_received);
// print_str(buffer, bytes_received);
printf("%s\n[TCP] fin %ld bytes\n", bytes_received > 20 ? "..." : "", bytes_received);
}
else if (bytes_received < 0) {
perror("[ERR] Erreur lors de la réception des données");
close_connection(client_fd);
}
else {
printf("[ERR] Le client s'est déconnecté.\n");
close_connection(client_fd);
}
}
return buffer[current_byte++];
}
int read_VarInt(int client_fd)
{
int value = 0;
for (int position = 0; position < 32; position += 7) {
uint8_t current_byte = read_byte(client_fd);
value |= (current_byte & SEGMENT_BITS) << position;
if ((current_byte & CONTINUE_BIT) == 0) break;
// if (position >= 32) throw new RuntimeException("VarInt is too big");
}
return value;
}
MCString_view read_String_view(int client_fd)
{
MCString_view str = {
.len = read_VarInt(client_fd),
.str = &buffer[current_byte],
};
current_byte += str.len;
return str;
}
char byte_to_char(byte b)
{
return b >= 10 ? b-10 + 'a' : b + '0';
}
MCUUID read_UUID(int client_fd)
{
MCUUID uuid = {0};
int c = 0;
for (int i = 0; i < 4; i++) {
uuid.byte[i] = read_byte(client_fd);
uuid.str[c++] = byte_to_char(uuid.byte[i] >> 4);
uuid.str[c++] = byte_to_char((uint8_t)(uuid.byte[i] << 4) >> 4);
}
uuid.str[c++] = '-';
for (int i = 4; i < 6; i++) {
uuid.byte[i] = read_byte(client_fd);
uuid.str[c++] = byte_to_char(uuid.byte[i] >> 4);
uuid.str[c++] = byte_to_char((uint8_t)(uuid.byte[i] << 4) >> 4);
}
uuid.str[c++] = '-';
for (int i = 6; i < 8; i++) {
uuid.byte[i] = read_byte(client_fd);
uuid.str[c++] = byte_to_char(uuid.byte[i] >> 4);
uuid.str[c++] = byte_to_char((uint8_t)(uuid.byte[i] << 4) >> 4);
}
uuid.str[c++] = '-';
for (int i = 8; i < 10; i++) {
uuid.byte[i] = read_byte(client_fd);
uuid.str[c++] = byte_to_char(uuid.byte[i] >> 4);
uuid.str[c++] = byte_to_char((uint8_t)(uuid.byte[i] << 4) >> 4);
}
uuid.str[c++] = '-';
for (int i = 10; i < 16; i++) {
uuid.byte[i] = read_byte(client_fd);
uuid.str[c++] = byte_to_char(uuid.byte[i] >> 4);
uuid.str[c++] = byte_to_char((uint8_t)(uuid.byte[i] << 4) >> 4);
}
return uuid;
}
uint16_t read_UShort(int client_fd)
{
uint8_t var1 = read_byte(client_fd);
uint8_t var2 = read_byte(client_fd);
uint16_t var = (var1 << 8) + var2;
return var;
}
void write_byte(int client_fd, int value)
{
(void) client_fd;
request[req_current_byte++] = value;
}
void write_UUID(int client_fd, MCUUID value)
{
for (int i = 0; i < (int) sizeof_member(MCUUID, byte); i++) {
write_byte(client_fd, value.byte[i]);
}
}
void write_Boolean(int client_fd, bool value)
{
write_byte(client_fd, value);
}
void write_VarInt(int client_fd, int value)
{
while (true) {
if ((value & ~SEGMENT_BITS) == 0) {
write_byte(client_fd, value);
return;
}
write_byte(client_fd, (value & SEGMENT_BITS) | CONTINUE_BIT);
value >>= 7;
}
}
void write_String(int client_fd, const char* str, int len)
{
(void) client_fd;
write_VarInt(client_fd, len);
for (int i = 0; i < len; i++) {
request[req_current_byte++] = str[i];
}
}
void write_Long(int client_fd, int64_t value)
{
(void) client_fd;
request[req_current_byte] = value;
req_current_byte += 8;
}
void send_request(int client_fd)
{
write(client_fd, request, req_current_byte);
printf("[INFO] %d byte request sent: ", req_current_byte);
print_hex((const char*) request, req_current_byte > 10 ? 10 : req_current_byte);
printf("%s\n", req_current_byte > 10 ? "..." : "");
req_current_byte = 0;
}
File read_entire_file(const char *filename)
{
FILE *f = fopen(filename, "r");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
char *string = malloc(fsize);
fread(string, fsize, 1, f);
fclose(f);
return (File) {
.len = fsize,
.data = string,
};
}
void write_entire_file(const char *filename, File file)
{
FILE *f = fopen(filename, "w");
fwrite(file.data, file.len, 1, f);
fclose(f);
}
void file_free(File file)
{
free(file.data);
}
void status_protocol(int client_fd)
{
File status_rep = read_entire_file("status_response.json");
// TODO: get VarInt byte size from int, for /*str len*/
write_VarInt(client_fd, 1 /*packet id*/ + 2 /*str len*/ + status_rep.len); // packet length
write_VarInt(client_fd, 0);
write_String(client_fd, status_rep.data, status_rep.len);
send_request(client_fd);
printf("\n[STEP] Ping Status\n");
file_free(status_rep);
int request_status_len = read_VarInt(client_fd);
int request_status_id = read_VarInt(client_fd);
printf("[MCPACKET %d] packet length: %d\n", request_status_id, request_status_len);
if (request_status_id != 0 || request_status_len != 1) {
printf("[ERR] client not in the status state\n");
close_connection(client_fd);
return;
}
write_VarInt(client_fd, 1 /*packet id*/ + 8 /*Long len*/);
write_VarInt(client_fd, 1); // protocol id
write_Long(client_fd, (long)time(NULL));
send_request(client_fd);
printf("\n[STEP] Switch To Login Start\n");
int login_len = read_VarInt(client_fd);
int login_id = read_VarInt(client_fd);
printf("[MCPACKET %d] packet length: %d\n", login_id, login_len);
printf("[NOTE] je ne sais pas a quoi sert cet requete du client\n");
close_connection(client_fd);
}
void login_protocol(int client_fd)
{
printf("\n[STEP] Login Start\n");
int request_len = read_VarInt(client_fd);
int request_id = read_VarInt(client_fd);
printf("[MCPACKET %d] packet length: %d\n", request_id, request_len);
MCString_view pseudo = read_String_view(client_fd);
// NOTE: le client envoie l'uuid a la fin de la requete et je n'ai aucune
// idée de ce qu'il envoie entre le pseudo et l'uuid
current_byte = bytes_received - sizeof_member(MCUUID, byte);
MCUUID uuid = read_UUID(client_fd);
printf("[INFO] player pseudo = %.*s\n", pseudo.len, pseudo.str);
printf("[INFO] player UUID = %.*s\n", UUID_STR_LEN, uuid.str);
printf("\n[STEP] Login Success / Acknowledge\n");
write_VarInt(client_fd, 1 /*id*/ + 16 /*uuid*/ + pseudo.len + 2 /*list len & bool*/);
write_VarInt(client_fd, 2);
write_UUID(client_fd, uuid);
write_String(client_fd, (char*)pseudo.str, pseudo.len);
write_VarInt(client_fd, 0); // len list de property
// write_Boolean(client_fd, true);
write_entire_file("debug.hex", (File){(char*)request, req_current_byte});
send_request(client_fd);
request_len = read_VarInt(client_fd);
request_id = read_VarInt(client_fd);
printf("[MCPACKET %d] packet length: %d\n", request_id, request_len);
close_connection(client_fd);
}
int main()
{
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
// Création de la socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Erreur lors de la création de la socket");
exit(EXIT_FAILURE);
}
int option = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
// Configuration de l'adresse du serveur
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // Accepter les connexions depuis n'importe quelle adresse IP
server_addr.sin_port = htons(PORT);
// Liaison de la socket à l'adresse et au port
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Erreur lors du bind");
close(server_fd);
exit(EXIT_FAILURE);
}
// Mise en écoute pour accepter des connexions
if (listen(server_fd, 5) == -1) { // 5 est le nombre maximum de connexions en file d'attente
perror("Erreur lors du listen");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("[INFO] Serveur en écoute sur le port %d...\n", PORT);
// Boucle principale pour accepter et gérer les connexions clients
while (1) {
// Accepter une connexion cliente
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_fd == -1) {
perror("Erreur lors de l'acceptation de la connexion");
continue;
}
printf("[TCP] connection %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
printf("\n[STEP] Handshake / List Ping\n");
int packet_length = read_VarInt(client_fd);
int packet_id = read_VarInt(client_fd);
printf("[MCPACKET %d] packet length: %d\n", packet_id, packet_length);
int protocol_version = read_VarInt(client_fd);
printf("[INFO] protocol version: %d\n", protocol_version);
// if (protocol_version != 760 /* mc 1.19.2 */) {
// printf("[ERR] protocol version %d non supporter, connection fermer\n", protocol_version);
// close_connection(client_fd);
// continue;
// }
MCString_view address = read_String_view(client_fd);
uint16_t port = read_UShort(client_fd);
printf("[INFO] server address from client: %.*s:%hu\n", address.len, address.str, port);
int next_state = read_VarInt(client_fd);
printf("[INFO] next state: %d\n", next_state);
switch (next_state) {
case 1: // status
status_protocol(client_fd);
break;
case 2: // login
login_protocol(client_fd);
break;
case 3: // transfer
printf("[TODO] transfer protocol not programmed\n");
close_connection(client_fd);
break;
default:
printf("[ERR] state n°%d not supported\n", next_state);
close_connection(client_fd);
continue;
}
}
// Fermeture de la socket du serveur
close(server_fd);
return 0;
}