/* tbot - A low down dirty cheating offensive tetrinet bot * Copyright (c) 2007 Gianni Tedesco * Released under the terms of the GNU GPL v2 or later * * TODO: * o Text rendering to fields * o Realistic playing * o Implement specials so you can attack tbot * o Implement better trash talk * o Command line options for nick/server/port */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define STATE_NEW 0 #define STATE_LOBBY 1 #define STATE_GAME 2 #define MAX_PLAYER 32 #define FIELD_X 12 #define FIELD_Y 22 #define C_BLUE 1 #define C_YELLOW 2 #define C_GREEN 3 #define C_PURPLE 4 #define C_RED 5 #define FIELD_COLORS 5 #define SPECIAL_A (FIELD_COLORS + 1) #define SPECIAL_C (FIELD_COLORS + 2) #define SPECIAL_N (FIELD_COLORS + 3) #define SPECIAL_R (FIELD_COLORS + 4) #define SPECIAL_S (FIELD_COLORS + 5) #define SPECIAL_B (FIELD_COLORS + 6) #define SPECIAL_G (FIELD_COLORS + 7) #define SPECIAL_Q (FIELD_COLORS + 8) #define SPECIAL_O (FIELD_COLORS + 9) struct tstate { unsigned int state; unsigned int pnum; const char *nick; char *player[MAX_PLAYER]; char buf[1024]; char *mptr; char *eptr; int fd; }; static const char *errstr(void) { return (errno) ? strerror(errno) : "Internal error"; } /* TCP connection given host-endian ip and port */ static int do_connect(const char *ip, uint16_t h_port) { struct sockaddr_in sa; struct hostent *h; int s; printf("Looking up '%s'...\n", ip); h = gethostbyname(ip); if ( h == NULL || h->h_addrtype != AF_INET ) { free(h); return -1; } printf("It's at %s:%u\n", inet_ntoa(*(struct in_addr *)h->h_addr_list[0]), h_port); sa.sin_family = AF_INET; sa.sin_addr = *(struct in_addr *)h->h_addr_list[0]; sa.sin_port = htons(h_port); s = socket(PF_INET, SOCK_STREAM, 0); if ( s < 0 ) { fprintf(stderr, "socket: %s\n", errstr()); return -1; } if ( connect(s, (struct sockaddr *)&sa, sizeof(sa)) ) { fprintf(stderr, "connect: %s\n", errstr()); close(s); return -1; } printf("Connected OK\n"); return s; } /* send() wrapper handling short sends and error messages etc.. */ static int do_send(int s, char *buf, size_t len) { ssize_t ret; for(;;) { ret = send(s, buf, len, MSG_NOSIGNAL); if ( ret < 0 ) { fprintf(stderr, "send: %s\n", errstr()); return 0; } if ( (size_t)ret >= len ) return 1; buf += ret; len -= ret; } } /* printf style send command, we could actually use printf() for * this of course, but error handling semantics would dribble * out everywhere... */ static int sockprintf(int s, const char *fmt, ...) { static char *abuf; static size_t abuflen; int len; va_list va; char *new; int ret = 0; again: va_start(va, fmt); len = vsnprintf(abuf, abuflen, fmt, va); if ( len < 0 ) /* bug in old glibc */ len = 0; if ( (size_t)len < abuflen ) goto done; new = realloc(abuf, len + 1); if ( new == NULL ) goto done; abuf = new; abuflen = len + 1; goto again; done: ret = do_send(s, abuf, (size_t)len); va_end(va); return ret; } /* Calculate a join string using the ludicrous non-sensical frankly * faulty bizarre 'encryptification' method for no reason whatsoever * that achieves nothing other than giving programmers headaches. * But seriously? What the fuck is this shit? */ static char *joinstring(int s, char *nick) { static char jbuf[1024]; char sbuf[strlen(nick) + 18]; char ipstr[16]; struct sockaddr_in sa; char *ptr = jbuf; socklen_t slen; size_t klen, i; uint8_t r; uint8_t *ip; slen = sizeof(sa); if ( getpeername(s, (struct sockaddr *)&sa, &slen) ) return NULL; if ( sa.sin_family != AF_INET ) return NULL; snprintf(sbuf, sizeof(sbuf), "tetrisstart %s 1.13", nick); ip = (uint8_t *)&sa.sin_addr; klen = snprintf(ipstr, sizeof(ipstr), "%d", 54 * ip[0] + 41 * ip[1] + 29 * ip[2] + 17 * ip[3]); memset(jbuf, 0, sizeof(jbuf)); ptr = jbuf; r = 0; ptr += sprintf(ptr, "%02X", r); for(i = 0; sbuf[i]; i++) { r = ((r + (uint8_t)sbuf[i])%255) ^ ipstr[i % klen]; ptr += sprintf(ptr, "%02X", r); } return jbuf; } /* Easy string tokeniser */ static int easy_explode(char *str, char split, char **toks, int max_toks) { char *tmp; int tok; int state; for(tmp=str,state=tok=0; *tmp && tok < max_toks; tmp++) { if ( state == 0 ) { if ( *tmp == split ) { toks[tok++] = NULL; }else if ( !isspace(*tmp) ) { state = 1; toks[tok++] = tmp; } }else if ( state == 1 ) { if ( *tmp == split || isspace(*tmp) ) { *tmp = '\0'; state = 0; } } } return tok; } /* Clear our field (like nuke) */ static int field_zero(struct tstate *ts) { return sockprintf(ts->fd, "f %i 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\xff", ts->pnum); } /* Draw a series of points of a given colour on our field, * arguments are x, y, x, y, ..., -1*/ static int field_points(struct tstate *ts, int col, ...) { char fbuf[1024]; char *ptr = fbuf; va_list va; int x, y; if ( col >= (FIELD_COLORS + 9) ) return 0; *ptr = '\0'; ptr += sprintf(ptr, "%c", '!' + col); va_start(va, col); for(;;) { x = va_arg(va, int); if ( x == -1 ) break; if ( x < 0 ) return 0; if ( x >= FIELD_X ) return 0; y = va_arg(va, int); if ( y < 0 ) return 0; if ( y >= FIELD_Y ) return 0; ptr += sprintf(ptr, "%c%c", '3' + x, '3' + y); } va_end(va); return sockprintf(ts->fd, "f %i %s\xff", ts->pnum, fbuf); } /* Draw a single block on the field */ static int field_point(struct tstate *ts, int col, int x, int y) { if ( col >= (FIELD_COLORS + 9) ) return 0; return sockprintf(ts->fd, "f %i %c%c%c\xff", ts->pnum, '!' + col, '3' + x, '3' + y); } /* We need to know our playernum for later... */ static int cmd_playernum(struct tstate *ts, int argc, char **argv) { if ( argc != 2 ) return 0; ts->pnum = atoi(argv[1]); printf("Playernum %d\n", atoi(argv[1])); ts->state = STATE_LOBBY; return 1; } /* Keep track of players joining the game */ static int cmd_playerjoin(struct tstate *ts, int argc, char **argv) { int i; if ( argc != 3 ) return 0; i = atoi(argv[1]); i--; if ( i < 0 || i >= MAX_PLAYER ) { printf("Player %s index %i out of range\n", argv[2], i); return 0; } ts->player[i] = strdup(argv[2]); printf("Player: '%s' has index %i\n", argv[2], i); return 1; } /* Keep track of players leaving the game */ static int cmd_playerleave(struct tstate *ts, int argc, char **argv) { int i; if ( argc != 2 ) return 0; i = atoi(argv[1]); i--; if ( i < 0 || i >= MAX_PLAYER ) { printf("Player (leaving) index %i out of range\n", i); return 0; } printf("Player: '%s' left (index %i)\n", ts->player[i], i); free(ts->player[i]); ts->player[i] = NULL; return 1; } /* Insult the losers as they drop out of the game */ static int cmd_loser(struct tstate *ts, int argc, char **argv) { int i; if ( argc != 2 ) return 0; i = atoi(argv[1]); i--; if ( i < 0 || i >= MAX_PLAYER ) { printf("Player (losing) index %i out of range\n", i); return 0; } sockprintf(ts->fd, "gmsg <%s> Oh man, %s sucks balls!!\xff", ts->nick, ts->player[i]); return 1; } /* In game messages */ static int cmd_gmsg(struct tstate *ts, int argc, char **argv) { if ( argc != 3 ) return 0; printf("%s %s\n", argv[1], argv[2]); return 1; } /* Partyline messages */ static int cmd_partyline(struct tstate *ts, int argc, char **argv) { int i; if ( argc < 2 ) return 0; i = atoi(argv[1]); if ( i == 0 ) { printf("SERVER: %s\n",argv[2]); return 1; } i--; if ( i < 0 || i >= MAX_PLAYER ) { printf("Player (chatting) index %i out of range\n",i); return 0; } printf("<%s> %s\n", ts->player[i], argv[2]); return 1; } /* Insult the winner, unless it's us, then insult the losers */ static int cmd_winner(struct tstate *ts, int argc, char **argv) { int i; if ( argc != 2 ) return 0; i = atoi(argv[1]); i--; if ( i < 0 || i >= MAX_PLAYER ) { printf("Player (winning) index %i out of range\n", i); return 0; } if ( (unsigned)(i + 1) != ts->pnum ) sockprintf(ts->fd, "pline %i %s is a total cunt!\xff", ts->pnum, ts->player[i]); else sockprintf(ts->fd, "pline %i %s 0wns j00 all!\xff", ts->pnum, ts->nick); return 1; } /* write 'fuck' accross the field */ static int field_fuck(struct tstate *ts, int yofs) { /* gimme an f */ field_points(ts, C_BLUE, 0, 17 - yofs, 1, 17 - yofs, 0, 18 - yofs, 0, 19 - yofs, 1, 19 - yofs, 0, 20 - yofs, 0, 21 - yofs, -1); /* gimme a u */ field_points(ts, C_RED, 2, 17 - yofs, 2, 18 - yofs, 2, 19 - yofs, 2, 20 - yofs, 3, 21 - yofs, 4, 17 - yofs, 4, 18 - yofs, 4, 19 - yofs, 4, 20 - yofs, -1); /* gimme a c */ field_points(ts, C_GREEN, 6, 17 - yofs, 5, 18 - yofs, 5, 19 - yofs, 5, 20 - yofs, 6, 21 - yofs, 7, 18 - yofs, 7, 20 - yofs, -1); /* gimme a k */ field_points(ts, C_PURPLE, 8, 17 - yofs, 8, 18 - yofs, 8, 19 - yofs, 8, 20 - yofs, 8, 21 - yofs, 9, 19 - yofs, 10, 17 - yofs, 10, 18 - yofs, 10, 20 - yofs, 10, 21 - yofs, -1); /* whats that spell?! */ field_points(ts, C_YELLOW, 11, 17 - yofs, 11, 18 - yofs, 11, 19 - yofs, 11, 21 - yofs, -1); return 1; } /* start a game */ static int cmd_start(struct tstate *ts, int argc, char **argv) { ts->state = STATE_GAME; field_zero(ts); field_fuck(ts, 0); return 1; } /* end a game */ static int cmd_end(struct tstate *ts, int argc, char **argv) { ts->state = STATE_LOBBY; return 1; } /* Handle special block attacks */ static int cmd_special(struct tstate *ts, int argc, char **argv) { int from, to, i; if ( argc != 3 ) return 0; printf("special '%s' '%s'\n", argv[1], argv[2]); from = atoi(argv[1]); if ( argv[2][0] != 's' ) return 1; if ( argv[2][1] != ' ' ) return 1; to = atoi(argv[2] + 2); if ( (unsigned)to != ts->pnum ) return 1; from--; if ( from < 0 || from >= MAX_PLAYER ) { printf("Player (swapping) index %i out of range\n",from); return 0; } printf("%s is swapping with me?!?!?!?!\n", ts->player[from]); field_zero(ts); field_fuck(ts, 0); field_fuck(ts, 6); field_fuck(ts, 12); for(i = 0; i < FIELD_X; i+=2) field_point(ts, 1 + (rand() % 5), i, 13); for(i = 1; i < FIELD_X; i+=2) field_point(ts, 1 + (rand() % 5), i, 14); for(i = 0; i < FIELD_X; i+=2) field_point(ts, 1 + (rand() % 5), i, 15); sockprintf(ts->fd, "sb %i s %i\xff", from + 1, ts->pnum); sockprintf(ts->fd, "gmsg <%s> lolercopter %s!\xff", ts->nick, ts->player[from]); return 1; } /* They're already in game, insult them! */ static int cmd_ingame(struct tstate *ts, int argc, char **argv) { sockprintf(ts->fd, "gmsg <%s> Hurry it the fuck up, %s is here " "to give you all a damn good thrashing!\xff", ts->nick, ts->nick); return 1; } /* The game is paused/unpaused, insult people! */ static int cmd_pause(struct tstate *ts, int argc, char **argv) { int p; if ( argc != 2 ) return 0; p = atoi(argv[1]); if ( p ) { sockprintf(ts->fd, "gmsg <%s> Unpause this shit, pussy!\xff", ts->nick); }else{ sockprintf(ts->fd, "gmsg <%s> Now I can get back to " "kicking some ass!\xff", ts->nick); } return 1; } /* handle other players field updates */ static int cmd_field(struct tstate *ts, int argc, char **argv) { int i; if ( argc != 3 ) return 0; i = atoi(argv[1]); if ( (unsigned)i == ts->pnum ) return 1; i--; if ( i < 0 || i >= MAX_PLAYER ) { printf("Player (field) index %i out of range\n", i); return 0; } //printf("%s: %s\n", ts->player[i], argv[2]); if ( (rand() % 100) > 10 ) return 1; printf("Adding a line to %s (%i)\n", ts->player[i], i + 1); /* Add a line to that player */ sockprintf(ts->fd, "sb %i a %i\xff", i + 1, ts->pnum); return 1; } /* Deal with a single command from the server */ static int do_response(struct tstate *ts, char *str) { char *tok[3]; unsigned int n, i; struct { const char *cmd; int (*handle)(struct tstate *, int argc, char **argv); }cmd[]={ {"playernum", cmd_playernum}, {"playerjoin", cmd_playerjoin}, {"playerleave", cmd_playerleave}, {"pline", cmd_partyline}, {"gmsg", cmd_gmsg}, {"playerlost", cmd_loser}, {"newgame", cmd_start}, {"endgame", cmd_end}, {"playerwon", cmd_winner}, {"f", cmd_field}, {"sb", cmd_special}, {"ingame", cmd_ingame}, //{"lvl", cmd_lvl}, {"pause", cmd_pause}, {"winlist", NULL}, {"team", NULL}, }; n = easy_explode(str, ' ', tok, sizeof(tok)/sizeof(*tok)); for(i = 0; i < sizeof(cmd)/sizeof(*cmd); i++) { if ( !strcmp(cmd[i].cmd, tok[0]) ) { if ( cmd[i].handle == NULL ) return 1; cmd[i].handle(ts, n, tok); return 1; } } printf("Command '%s' not found\n", tok[0]); for(i = 0; i < n; i++) { printf(" %u. '%s'\n", i, tok[i]); } return 1; } /* Strip out multiple commands and pass them to do_response() handle * any split messages by keeping them in the buffer */ static int handle_responses(struct tstate *ts) { char *ptr; again: for(ptr = ts->mptr; ptr < ts->eptr; ptr++) { if ( *(uint8_t *)ptr == 0xff ) { *ptr = '\0'; if ( !do_response(ts, ts->mptr) ) return 0; ts->mptr = ptr + 1; goto again; } } if ( ts->mptr < ts->eptr ) { memmove(ts->buf, ts->mptr, ts->eptr - ts->mptr); ts->eptr = ts->buf + (ts->eptr - ts->mptr); ts->mptr = ts->buf; return 1; } ts->mptr = ts->eptr = ts->buf; return 1; } /* Do a recv() either blocking or non-blocking, habdling all the * data we get by passing it up the chain to handle_responses() */ static int recv_response(struct tstate *ts, int flags) { ssize_t ret; size_t blen; blen = (ts->buf + sizeof(ts->buf)) - ts->eptr; ret = recv(ts->fd, ts->eptr, blen, MSG_NOSIGNAL|flags); if ( ret < 0 ) { fprintf(stderr, "recv: %s\n", errstr()); return 0; } if ( ret == 0 ) return 0; ts->eptr += ret; return handle_responses(ts); } /* Blocking variant of the above */ static int wait_response(struct tstate *ts) { return recv_response(ts, 0); } #if 0 /* Non-blocking variant of the above */ static int peek_response(struct tstate *ts) { return recv_response(ts, MSG_DONTWAIT); } #endif /* Login to the game once connected */ static int login(struct tstate *ts) { char *js = joinstring(ts->fd, "tbot"); if ( js == NULL ) return 0; if ( !sockprintf(ts->fd, "%s\xff", js) ) return 0; while ( ts->state == STATE_NEW ) { if ( !wait_response(ts) ) return 0; } sockprintf(ts->fd, "team %i \xff", ts->pnum); return sockprintf(ts->fd, "pline %u Fuck the world!\xff", ts->pnum); } /* Some 'hurry up' trash talk to insult people arsing around * chatting in the lobby, treating it like some sort of sewing * circle... */ static void lobby_trash_talk(struct tstate *ts) { char *insult[]={ "ladies", "girlies", "you fuckers", "motherfuckers", "sons of bitches", "shit heads", }; sockprintf(ts->fd, "pline %i BOOOORED. Hurry up %s\xff", ts->pnum, insult[rand() % (sizeof(insult)/sizeof(*insult))]); } /* Bot mainloop */ static int enter_mainloop(struct tstate *ts) { for(;;) { switch(ts->state) { case STATE_NEW: if ( !login(ts) ) return 0; break; case STATE_LOBBY: while(ts->state == STATE_LOBBY) { if ( !wait_response(ts) ) return 0; if ( (rand() % 100) > 95 ) lobby_trash_talk(ts); } break; case STATE_GAME: printf("All in the game baby!!!\n"); while(ts->state == STATE_GAME) { if ( !wait_response(ts) ) return 0; } break; } } } /* Initialise bot state structure */ static void ts_init(struct tstate *ts, int fd, char *nick) { memset(ts, 0, sizeof(*ts)); ts->fd = fd; ts->nick = nick; ts->mptr = ts->eptr = ts->buf; } int main(int argc, char **argv) { struct tstate ts; char *svr; int s; /* Only 1 argument, server name/ip */ if ( argc < 2 ) svr = "127.0.0.1"; else svr = argv[1]; /* Get a socket and connect */ s = do_connect(svr, 31457); if ( s < 0 ) return EXIT_FAILURE; /* Create bot state and run the mainloop */ ts_init(&ts, s, "tbot"); if ( !enter_mainloop(&ts) ) return EXIT_FAILURE; printf("Dropped the ball!\n"); return EXIT_SUCCESS; }