Outils pour utilisateurs

Outils du site


misc:minecraft_client

Un client MineCraft

Un client Minecraft non fonctionnel car il lis mal les packets dans le buffer, néamoins il est capable de se connecter et peut servir de base à ceux qui veulent développer un client en C. Pour plus d'infos sur le developpement MineCraft : http://wiki.vg/

Ça peut aussi servir à ceux qui veulent savoir comment programmer un socket TCP en C.

Changelog

  • 21/12/11 MàJ pour MC 1.0.0
mcclient.c
		/***********************************************\
		* 		MCCLIENT .C			*
		*   Minecraft client exemple in C by Mr-Hide	*
		*						*
		* 1 	set the username, hostname and 		*
		*	port at the end of this file		*
		*						*
		* 2	gcc -o mcclient mcclient.c		*
		*						*
		* 3	...					*
		*						*
		* 4	PROFITS $$$				*
		\***********************************************/
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <signal.h> /* To avoid SIGPIPE on write on broken socket */
 
	/* HEADERS : */
#define KEEPALIVE 	0x00 /* This packet may be sent to keep the connection alive */
#define LOGINREQUEST	0x01 /* Sent by the client after the handshake to finish logging in */
#define HANDSHAKE 	0x02 /* This is the first packet sent when the client connects and is used for Authentication. */
#define CHATMESSAGE	0x03 /* A message from the client to the server, or the server to the client */
#define TIMEUPDATE	0x04 /* Time is based on ticks, where 20 ticks happen every second. There are 24000 ticks in a day, making Minecraft days exactly 20 minutes long.
				The time of day is based on the timestamp modulo 24000. 0 is sunrise, 6000 is noon, 12000 is sunset, and 18000 is midnight.
				The default SMP server increments the time by 20 every second.  */
#define DISCONNECT	0xFF /* Disconnection or Kick */
 
#define BUFSIZE 1024
/* Hex int of Minecraft's current protocol (currently 22) */
#define PROTOC_V 0x00000016
 
/* Java Char is 2bytes and Strings in packets are prefixed with a short equals strlen of the string */
typedef struct str_String16 {
	char* bytes;
	int size;
}* String16;
 
String16 toString16(char *msg) {
	int i;
	String16 res = malloc(sizeof(struct str_String16));
	if (res != NULL) {
		res->size = strlen(msg)*2+2;
		res->bytes = calloc(res->size, sizeof(char));
		res->bytes[1] = strlen(msg); /*FIXME cast 1int -> 2char */
		for (i=0 ; i<strlen(msg) ; i++) {
			res->bytes[2*i+2] = 0;
			res->bytes[2*i+3] = msg[i];
		}
	}
	return res;
}
 
char* toString(char* string16Bytes) {
	int i, size;
	char *res = NULL;
	size = string16Bytes[1];
	if (size != 0) {
		res = malloc((size+1)*sizeof(char));
		if (res != NULL) {
			for (i=0; i<size ; i++) {
				res[i] = string16Bytes[2*i+3];
			}
			res[size] = 0;
		}
	}
	return res;
}
 
/* used to send an int */
void intToByteArray(unsigned int n, unsigned char *array) {
	/* int in mc are 4 bytes */
	int i;
	for (i=3 ; i>=0 ; i--) {
		array[i] = n % 0x0100;
		n = n / 0x100;
	}
}
 
int mcclient(char *hostname, int portno, char *username) {
	int sockfd, n;
	struct sockaddr_in serveraddr;
	struct hostent *server;
	unsigned char buf[BUFSIZE];
	/* Datas exchange */
	char* msg;
	String16 string16;
	/* vars for the select routine */
	fd_set rfds;
	struct timeval tv;
	int retval;
	/* Disables SIGPIPE */
	struct sigaction act;
 
	act.sa_handler=SIG_IGN;
	sigemptyset(&act.sa_mask);
	act.sa_flags=0;
	sigaction(SIGPIPE, &act, NULL); 
 
	/* (int) socket = socket(typeInternet, tcp, protocol=0) */
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) { 
		perror("ERROR opening socket");
		return errno;
	}
 
	/* gethostbyname: get the server's DNS entry */
	server = gethostbyname(hostname);
	if (server == NULL) {
		fprintf(stderr,"ERROR, no such host as %s\n", hostname);
		retval = -1;
	} else {
 
		/* build the server's Internet address */
		bzero((char *) &serveraddr, sizeof(serveraddr));
		serveraddr.sin_family = AF_INET;
		bcopy((char *)server->h_addr, (char *)&serveraddr.sin_addr.s_addr, server->h_length);
		serveraddr.sin_port = htons(portno);
 
		/* connect: create a connection with the server */
		if (connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) { 
			perror("ERROR connecting");
			retval = errno;
		} else {
			printf("Connected to %s:%d\n", hostname, portno);
 
			/* sending handshake */
			bzero(buf, BUFSIZE);
			string16 = toString16(username);
			buf[0] = HANDSHAKE;
			for (n=0 ; n<string16->size ; n++) {
				buf[n+1] = string16->bytes[n];
			}
			/* send the message line to the server */
			n = write(sockfd, buf, string16->size+1);
			free(string16);
			printf("Handshake send ...\n");
 
			if (n < 0) {
				perror("ERROR writing to socket");
				retval = errno;
			} else {
 
				FD_ZERO(&rfds);
				FD_SET(sockfd, &rfds);
 
				for(;;) {
					/* Wait up to 5 seconds. */
					tv.tv_sec = 5; /* in the loop cuz select modify this var, use pselect instead ? */
					tv.tv_usec = 0;
 
					retval = select(sockfd+1, &rfds, NULL, NULL, &tv);
					if (retval == -1) {
						perror("select()");
						retval = errno;
						break;
					} else if (retval) {
						bzero(buf, BUFSIZE);
						/* print the server's reply */
						n = read(sockfd, buf, BUFSIZE);
						if (n < 0) { 
							perror("ERROR reading from socket");
							retval = errno;
							break;
						} else {
							printf("packet rcv type 0x%.2x\n", buf[0]);
							if (buf[0] == KEEPALIVE) {
								bzero(buf, BUFSIZE);
								printf("\t rcv ping : sending Pong !\n");
								n = write(sockfd, buf, 1); /* we must reply 0x00 to keep the connection alive */
								if (n < 0) {
									perror("ERROR writing to socket");
									retval = errno;
									break;
								}
							} else if (buf[0] == LOGINREQUEST) {
								/* Connection allowed ! \o/ TODO : do some cool stuff here */
							} else if (buf[0] == HANDSHAKE) {
								msg = toString((char *)buf+1); /* reading the hash */
								printf("\tserver reply hash : %s\n", msg);
								if (strlen(msg) == 1) {
									bzero(buf, BUFSIZE);
									buf[0] = LOGINREQUEST;
									if (msg[0] == '-') {
										printf("\nSending login request\n");
										intToByteArray(PROTOC_V, buf+1);
										string16 = toString16(username);
										for (n=0 ; n<string16->size ; n++) {
											buf[n+5] = string16->bytes[n];
										}
										n = write(sockfd, buf, string16->size+21);
										if (n < 0) {
											perror("ERROR writing to socket");
											retval = errno;
											return;
										}
									} else if (msg[0] == '+') {
										/* TODO login with minecraft.net */
									}
								}
								free (msg);
							} else if (buf[0] == DISCONNECT) {
								msg = toString((char *)buf+1); /* reading the reason */
								printf("\tDisconnected, reason : %s\n", msg);
								free (msg);
								break;
							} else if (buf[0] == CHATMESSAGE) {
								msg = toString((char *)buf+1);
								printf("\t%s\n", msg);
								free (msg);
							}
						}
					} else {
						printf("Read Timeout\n");
						break;
					}
				}
			}
		}
	}
 
	close(sockfd);
	return retval;
}
 
int main(){
	return mcclient("mrhide.eu", 25565, "Morphix");
}
misc/minecraft_client.txt · Dernière modification: 2011/12/21 15:56 par mrhide