LICENSE: The Artistic License 2.0 /* * qstat.h * by Steve Jankowski * steve@qstat.org * http://www.qstat.org * * Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski */ /* TRIBES 2 */ #define TRIBES2_QUERY_GAME_TYPES 2 #define TRIBES2_QUERY_MASTER 6 #define TRIBES2_QUERY_PING 14 #define TRIBES2_QUERY_INFO 18 #define TRIBES2_RESPONSE_GAME_TYPES 4 #define TRIBES2_RESPONSE_MASTER 8 #define TRIBES2_RESPONSE_PING 16 #define TRIBES2_RESPONSE_INFO 20 #define TRIBES2_NO_COMPRESS 2 #define TRIBES2_DEFAULT_PACKET_INDEX 255 #define TRIBES2_STATUS_DEDICATED (1<<0) #define TRIBES2_STATUS_PASSWORD (1<<1) #define TRIBES2_STATUS_LINUX (1<<2) #define TRIBES2_STATUS_TOURNAMENT (1<<3) #define TRIBES2_STATUS_NOALIAS (1<<4) #define TRIBES2_STATUS_TEAMDAMAGE (1<<5) #define TRIBES2_STATUS_TOURNAMENT_VER3 (1<<6) #define TRIBES2_STATUS_NOALIAS_VER3 (1<<7) char tribes2_game_types_request[] = { TRIBES2_QUERY_GAME_TYPES, 0, 1,2,3,4 }; char tribes2_ping[] = { TRIBES2_QUERY_PING, TRIBES2_NO_COMPRESS, 1,2,3,4 }; char tribes2_info[] = { TRIBES2_QUERY_INFO, TRIBES2_NO_COMPRESS, 1,2,3,4 }; unsigned char tribes2_masterquery[] = { TRIBES2_QUERY_MASTER, 128, /* <= build 22228, this was 0 */ 11,12,13,14, 255, 3, 'a', 'n', 'y', 3, 'a', 'n', 'y', 0, 255, /* min/max players */ 0xff, 0xff, 0xff, 0xff, /* region mask */ 0, 0, 0, 0, /* build version */ 0, /* status */ 255, /* max bots */ 0, 0, /* min cpu */ 0 /* # buddies */ }; #define TRIBES2_ID_OFFSET 2 { /* TRIBES 2 */ TRIBES2_SERVER, /* id */ "T2S", /* type_prefix */ "t2s", /* type_string */ "-t2s", /* type_option */ "Tribes 2", /* game_name */ 0, /* master */ TRIBES2_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ 0, /* flags */ "game", /* game_rule */ "TRIBES2", /* template_var */ (char*) &tribes2_ping, /* status_packet */ sizeof( tribes2_ping), /* status_len */ (char*) &tribes2_info, /* player_packet */ sizeof( tribes2_info), /* player_len */ (char*) NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_tribes2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_tribes2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_tribes2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ send_tribes2_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_tribes2_packet, /* packet_func */ }, query_status_t send_tribes2_request_packet(struct qserver *server) { int rc; if (server->flags &FLAG_BROADCAST && server->server_name == NULL) { rc = send_broadcast(server, server->type->status_packet, server->type->status_len); } else if (server->server_name == NULL) { rc = send(server->fd, server->type->status_packet, server->type->status_len, 0); } else { rc = send(server->fd, server->type->player_packet, server->type->player_len, 0); } if (rc == SOCKET_ERROR) { return send_error( server, rc ); } register_send(server); return rc; } void get_tribes2_player_type(struct player *player) { char *name = player->name; for (; *name; name++) { switch (*name) { case 0x8: player->type_flag = PLAYER_TYPE_NORMAL; continue; case 0xc: player->type_flag = PLAYER_TYPE_ALIAS; continue; case 0xe: player->type_flag = PLAYER_TYPE_BOT; continue; case 0xb: break; default: continue; } name++; if (isprint(*name)) { char *n = name; for (; isprint(*n); n++) ; player->tribe_tag = strndup(name, n - name); name = n; } if (! *name) { break; } } } query_status_t deal_with_tribes2_packet(struct qserver *server, char *pkt, int pktlen) { char str[256], *pktstart = pkt, *term, *start; unsigned int minimum_net_protocol, build_version, i, t, len, s, status; unsigned int net_protocol; unsigned short cpu_speed; int n_teams = 0, n_players; struct player **teams = NULL, *player; struct player **last_player = &server->players; int query_version; debug( 2, "deal_with_tribes2_packet %p, %d", server, pktlen ); pkt[pktlen] = '\0'; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } /* else gettimeofday( &server->packet_time1, NULL); */ if (pkt[0] == TRIBES2_RESPONSE_PING) { if (pkt[6] < 4 || pkt[6] > 12 || strncmp(pkt + 7, "VER", 3) != 0) { return PKT_ERROR; } strncpy(str, pkt + 10, pkt[6] - 3); str[pkt[6] - 3] = '\0'; query_version = atoi(str); add_nrule(server, "queryversion", pkt + 7, pkt[6]); pkt += 7+pkt[6]; server->protocol_version = query_version; if (query_version != 3 && query_version != 5) { server->server_name = strdup("Unknown query version"); return PKT_ERROR; } if (query_version == 5) { net_protocol = swap_long_from_little(pkt); sprintf(str, "%u", net_protocol); add_rule(server, "net_protocol", str, NO_FLAGS); pkt += 4; } minimum_net_protocol = swap_long_from_little(pkt); sprintf(str, "%u", minimum_net_protocol); add_rule(server, "minimum_net_protocol", str, NO_FLAGS); pkt += 4; build_version = swap_long_from_little(pkt); sprintf(str, "%u", build_version); add_rule(server, "build_version", str, NO_FLAGS); pkt += 4; server->server_name = strndup(pkt + 1, *(unsigned char*)(pkt)); /* Always send the player request because the ping packet * contains very little information */ send_player_request_packet(server); return 0; } else if (pkt[0] != TRIBES2_RESPONSE_INFO) { return PKT_ERROR; } pkt += 6; for (i = 0; i < *(unsigned char*)pkt; i++) if (!isprint(pkt[i + 1])) { return PKT_ERROR; } add_nrule(server, server->type->game_rule, pkt + 1, *(unsigned char*)pkt); server->game = strndup(pkt + 1, *(unsigned char*)pkt); pkt += *pkt + 1; add_nrule(server, "mission", pkt + 1, *(unsigned char*)pkt); pkt += *pkt + 1; server->map_name = strndup(pkt + 1, *(unsigned char*)pkt); pkt += *pkt + 1; status = *(unsigned char*)pkt; sprintf(str, "%u", status); add_rule(server, "status", str, NO_FLAGS); if (status &TRIBES2_STATUS_DEDICATED) { add_rule(server, "dedicated", "1", NO_FLAGS); } if (status &TRIBES2_STATUS_PASSWORD) { add_rule(server, "password", "1", NO_FLAGS); } if (status &TRIBES2_STATUS_LINUX) { add_rule(server, "linux", "1", NO_FLAGS); } if (status &TRIBES2_STATUS_TEAMDAMAGE) { add_rule(server, "teamdamage", "1", NO_FLAGS); } if (server->protocol_version == 3) { if (status &TRIBES2_STATUS_TOURNAMENT_VER3) { add_rule(server, "tournament", "1", NO_FLAGS); } if (status &TRIBES2_STATUS_NOALIAS_VER3) { add_rule(server, "no_aliases", "1", NO_FLAGS); } } else { if (status &TRIBES2_STATUS_TOURNAMENT) { add_rule(server, "tournament", "1", NO_FLAGS); } if (status &TRIBES2_STATUS_NOALIAS) { add_rule(server, "no_aliases", "1", NO_FLAGS); } } pkt++; server->num_players = *(unsigned char*)pkt; pkt++; server->max_players = *(unsigned char*)pkt; pkt++; sprintf(str, "%u", *(unsigned char*)pkt); add_rule(server, "bot_count", str, NO_FLAGS); pkt++; cpu_speed = swap_short_from_little(pkt); sprintf(str, "%hu", cpu_speed); add_rule(server, "cpu_speed", str, NO_FLAGS); pkt += 2; if (strcmp(server->server_name, "VER3") == 0) { free(server->server_name); server->server_name = strndup(pkt + 1, *(unsigned char*)pkt); } else { add_nrule(server, "info", pkt + 1, *(unsigned char*)pkt); } pkt += *(unsigned char*)pkt + 1; len = swap_short_from_little(pkt); pkt += 2; start = pkt; if (len + (pkt - pktstart) > pktlen) { len -= (len + (pkt - pktstart)) - pktlen; } if (len == 0 || pkt - pktstart >= pktlen) { goto info_done; } term = strchr(pkt, 0xa); if (!term) { goto info_done; } *term = '\0'; n_teams = atoi(pkt); sprintf(str, "%d", n_teams); add_rule(server, "numteams", str, NO_FLAGS); pkt = term + 1; if (pkt - pktstart >= pktlen) { goto info_done; } teams = (struct player **)calloc(1, sizeof(struct player*) * n_teams); for (t = 0; t < n_teams; t++) { teams[t] = (struct player*)calloc(1, sizeof(struct player)); teams[t]->number = TRIBES_TEAM; teams[t]->team = t; /* team name */ term = strchr(pkt, 0x9); if (!term) { n_teams = t; goto info_done; } teams[t]->name = strndup(pkt, term - pkt); pkt = term + 1; term = strchr(pkt, 0xa); if (!term) { n_teams = t; goto info_done; } *term = '\0'; teams[t]->frags = atoi(pkt); pkt = term + 1; if (pkt - pktstart >= pktlen) { goto info_done; } } term = strchr(pkt, 0xa); if (!term || term - start >= len) { goto info_done; } *term = '\0'; n_players = atoi(pkt); pkt = term + 1; for (i = 0; i < n_players && pkt - start < len; i++) { pkt++; /* skip first byte (0x10) */ if (pkt - start >= len) { break; } player = (struct player*)calloc(1, sizeof(struct player)); term = strchr(pkt, 0x11); if (!term || term - start >= len) { free(player); break; } player->name = strndup(pkt, term - pkt); get_tribes2_player_type(player); pkt = term + 1; pkt++; /* skip 0x9 */ if (pkt - start >= len) { break; } term = strchr(pkt, 0x9); if (!term || term - start >= len) { free(player->name); free(player); break; } for (t = 0; t < n_teams; t++) { if (term - pkt == strlen(teams[t]->name) && strncmp(pkt, teams[t]->name, term - pkt) == 0) { break; } } if (t == n_teams) { player->team = - 1; player->team_name = "Unassigned"; } else { player->team = t; player->team_name = teams[t]->name; } player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; pkt = term + 1; for (s = 0; *pkt != 0xa && pkt - start < len; pkt++) { str[s++] = *pkt; } str[s] = '\0'; player->frags = atoi(str); if (*pkt == 0xa) { pkt++; } *last_player = player; last_player = &player->next; } info_done: for (t = n_teams; t;) { t--; teams[t]->next = server->players; server->players = teams[t]; } if (teams) { free(teams); } return DONE_FORCE; }