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 */ unsigned char ghostrecon_serverquery[] = { 0xc0,0xde,0xf1,0x11, /* const ? header */ 0x42, /* start flag */ 0x03,0x00, /* data len */ 0xe9,0x03,0x00 /* server request ?? */ }; unsigned char ghostrecon_playerquery[] = { 0xc0,0xde,0xf1,0x11, /* const ? header */ 0x42, /* start flag */ 0x06,0x00, /* data len */ 0xf5,0x03,0x00,0x78,0x30,0x63 /* player request ?? may be flag 0xf5; len 0x03,0x00; data 0x78, 0x30, 0x63 */ }; { /* GHOSTRECON PROTOCOL */ GHOSTRECON_SERVER, /* id */ "GRS", /* type_prefix */ "grs", /* type_string */ "-grs", /* type_option */ "Ghost Recon", /* game_name */ 0, /* master */ GHOSTRECON_PLAYER_DEFAULT_PORT, /* default_port */ 2, /* port_offset */ TF_QUERY_ARG, /* flags */ "gametype", /* game_rule */ "GHOSTRECON", /* template_var */ (char*) &ghostrecon_playerquery, /* status_packet */ sizeof( ghostrecon_playerquery), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_ghostrecon_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_ghostrecon_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_ghostrecon_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ send_qserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ghostrecon_packet, /* packet_func */ }, static const char GrPacketHead[] = { '\xc0', '\xde', '\xf1', '\x11' }; static const char PacketStart = '\x42'; static char Dat2Reply1_2_10[] = { '\xf4', '\x03', '\x14', '\x02', '\x0a', '\x41', '\x02', '\x0a', '\x41', '\x00', '\x00', '\x78', '\x30', '\x63' }; static char Dat2Reply1_3[] = { '\xf4', '\x03', '\x14', '\x03', '\x05', '\x41', '\x03', '\x05', '\x41', '\x00', '\x00', '\x78', '\x30', '\x63' }; static char Dat2Reply1_4[] = { '\xf4', '\x03', '\x14', '\x04', '\x00', '\x41', '\x04', '\x00', '\x41', '\x00', '\x00', '\x78', '\x30', '\x63' }; //static char HDat2[]={'\xea','\x03','\x02','\x00','\x14'}; #define SHORT_GR_LEN 75 #define LONG_GR_LEN 500 #define UNKNOWN_VERSION 0 #define VERSION_1_2_10 1 #define VERSION_1_3 2 #define VERSION_1_4 3 query_status_t deal_with_ghostrecon_packet(struct qserver *server, char *pkt, int pktlen) { char str[256], *start, *end, StartFlag, *lpszIgnoreServerPlayer; char *lpszMission; unsigned int iIgnoreServerPlayer, iDedicatedServer, iUseStartTimer; unsigned short GrPayloadLen; int i; struct player *player; int iLen, iTemp; short sLen; int iSecsPlayed; long iSpawnType; int ServerVersion = UNKNOWN_VERSION; float flStartTimerSetPoint; debug( 2, "deal_with_ghostrecon_packet %p, %d", server, pktlen ); start = pkt; end = &pkt[pktlen]; pkt[pktlen] = '\0'; /* This function walks a packet that is recieved from a ghost recon server - default from port 2348. It does quite a few sanity checks along the way as the structure is not documented. The packet is mostly binary in nature with many string fields being variable in length, ie the length is listed foloowed by that many bytes. There are two structure arrays that have an array size followed by structure size * number of elements (player name and player data). This routine walks this packet and increments a pointer "pkt" to extract the info. */ if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } /* sanity check against packet */ if (memcmp(pkt, GrPacketHead, sizeof(GrPacketHead)) != 0) { server->server_name = strdup("Unknown Packet Header"); return PKT_ERROR; } pkt += sizeof(GrPacketHead); StartFlag = pkt[0]; pkt += 1; if (StartFlag != 0x42) { server->server_name = strdup("Unknown Start Flag"); return PKT_ERROR; } /* compare packet length recieved to included size - header info */ sLen = swap_short_from_little(pkt); pkt += 2; GrPayloadLen = pktlen - sizeof(GrPacketHead) - 3; // 3 = size slen + size start flag if (sLen != GrPayloadLen) { server->server_name = strdup("Packet Size Mismatch"); return PKT_ERROR; } /* Will likely need to verify and add to this "if" construct with every patch / add-on. */ if (memcmp(pkt, Dat2Reply1_2_10, sizeof(Dat2Reply1_2_10)) == 0) { ServerVersion = VERSION_1_2_10; } else if (memcmp(pkt, Dat2Reply1_3, sizeof(Dat2Reply1_3)) == 0) { ServerVersion = VERSION_1_3; } else if (memcmp(pkt, Dat2Reply1_4, sizeof(Dat2Reply1_4)) == 0) { ServerVersion = VERSION_1_4; } if (ServerVersion == UNKNOWN_VERSION) { server->server_name = strdup("Unknown GR Version"); return PKT_ERROR; } switch (ServerVersion) { case VERSION_1_2_10: strcpy(str, "1.2.10"); pkt += sizeof(Dat2Reply1_2_10); break; case VERSION_1_3: strcpy(str, "1.3"); pkt += sizeof(Dat2Reply1_3); break; case VERSION_1_4: strcpy(str, "1.4"); pkt += sizeof(Dat2Reply1_4); break; } add_rule(server, "patch", str, NO_FLAGS); /* have player packet */ // Ghost recon has one of the player slots filled up with the server program itself. By default we will // drop the first player listed. This causes a bit of a mess here and below but makes for the best display // a user can specify -grs,ignoreserverplayer=no to override this behaviour. lpszIgnoreServerPlayer = get_param_value(server, "ignoreserverplayer", "yes"); for (i = 0; i < 4; i++) { str[i] = tolower(lpszIgnoreServerPlayer[i]); } if (strcmp(str, "yes") == 0) { iIgnoreServerPlayer = 1; } else { iIgnoreServerPlayer = 0; } pkt += 4; /* unknown */ // this is the first of many variable strings. get the length, // increment pointer over length, check for sanity, // get the string, increment the pointer over string (using length) iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { server->server_name = strdup("Server Name too Long"); return PKT_ERROR; } server->server_name = strndup(pkt, iLen); pkt += iLen; iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Map Name too Long", NO_FLAGS); return PKT_ERROR; } server->map_name = strndup(pkt, iLen); pkt += iLen; iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Mission Name too Long", NO_FLAGS); return PKT_ERROR; } /* mission does not make sense unless a coop game type. Since we dont know that now, we will save the mission and set the rule and free memory below when we know game type */ lpszMission = strndup(pkt, iLen); pkt += iLen; iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Mission Type too Long", NO_FLAGS); return PKT_ERROR; } add_nrule(server, "missiontype", pkt, iLen); pkt += iLen; if (pkt[1]) { add_rule(server, "password", "Yes", NO_FLAGS); } else { add_rule(server, "password", "No", NO_FLAGS); } pkt += 2; server->max_players = swap_long_from_little(pkt); pkt += 4; if (server->max_players > 36) { add_rule(server, "error", "Max players more then 36", NO_FLAGS); return PKT_ERROR; } server->num_players = swap_long_from_little(pkt); pkt += 4; if (server->num_players > server->max_players) { add_rule(server, "error", "More then MAX Players", NO_FLAGS); return PKT_ERROR; } if (iIgnoreServerPlayer) // skip past first player { server->num_players--; server->max_players--; iLen = swap_long_from_little(pkt); pkt += 4; pkt += iLen; } for (i = 0; i < server->num_players; i++) // read each player name { iLen = swap_long_from_little(pkt); pkt += 4; player = (struct player*)calloc(1, sizeof(struct player)); if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Player Name too Long", NO_FLAGS); return PKT_ERROR; } player->name = strndup(pkt, iLen); pkt += iLen; /* player name */ player->team = i; // tag so we can find this record when we have player dat. player->team_name = "Unassigned"; player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; player->frags = 0; player->next = server->players; server->players = player; } pkt += 17; iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Version too Long", NO_FLAGS); return PKT_ERROR; } strncpy(str, pkt, iLen); add_rule(server, "version", str, NO_FLAGS); pkt += iLen; /* version */ iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > LONG_GR_LEN)) { add_rule(server, "error", "Mods too Long", NO_FLAGS); return PKT_ERROR; } server->game = strndup(pkt, iLen); for (i = 0; i < (int)strlen(server->game) - 5; i++) // clean the "/mods/" part from every entry { if (memcmp(&server->game[i], "\\mods\\", 6) == 0) { server->game[i] = ' '; strcpy(&server->game[i + 1], &server->game[i + 6]); } } add_rule(server, "game", server->game, NO_FLAGS); pkt += iLen; /* mods */ iDedicatedServer = pkt[0]; if (iDedicatedServer) { add_rule(server, "dedicated", "Yes", NO_FLAGS); } else { add_rule(server, "dedicated", "No", NO_FLAGS); } pkt += 1; /* unknown */ iSecsPlayed = swap_float_from_little(pkt); add_rule(server, "timeplayed", play_time(iSecsPlayed, 2), NO_FLAGS); pkt += 4; /* time played */ switch (pkt[0]) { case 3: strcpy(str, "Joining"); break; case 4: strcpy(str, "Playing"); break; case 5: strcpy(str, "Debrief"); break; default: strcpy(str, "Undefined"); } add_rule(server, "status", str, NO_FLAGS); pkt += 1; pkt += 3; /* unknown */ switch (pkt[0]) { case 2: strcpy(str, "COOP"); break; case 3: strcpy(str, "SOLO"); break; case 4: strcpy(str, "TEAM"); break; default: sprintf(str, "UNKOWN %u", pkt[0]); break; } add_rule(server, "gamemode", str, NO_FLAGS); if (pkt[0] == 2) { add_rule(server, "mission", lpszMission, NO_FLAGS); } else { add_rule(server, "mission", "No Mission", NO_FLAGS); } free(lpszMission); pkt += 1; /* Game Mode */ pkt += 3; /* unknown */ iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > LONG_GR_LEN)) { add_rule(server, "error", "MOTD too Long", NO_FLAGS); return PKT_ERROR; } strncpy(str, pkt, sizeof(str)); str[sizeof(str) - 1] = 0; add_rule(server, "motd", str, NO_FLAGS); pkt += iLen; /* MOTD */ iSpawnType = swap_long_from_little(pkt); switch (iSpawnType) { case 0: strcpy(str, "None"); break; case 1: strcpy(str, "Individual"); break; case 2: strcpy(str, "Team"); break; case 3: strcpy(str, "Infinite"); break; default: strcpy(str, "Unknown"); } add_rule(server, "spawntype", str, NO_FLAGS); pkt += 4; /* spawn type */ iTemp = swap_float_from_little(pkt); add_rule(server, "gametime", play_time(iTemp, 2), NO_FLAGS); iTemp = iTemp - iSecsPlayed; if (iTemp <= 0) { iTemp = 0; } add_rule(server, "remainingtime", play_time(iTemp, 2), NO_FLAGS); pkt += 4; /* Game time */ iTemp = swap_long_from_little(pkt); if (iIgnoreServerPlayer) { iTemp--; } if (iTemp != server->num_players) { add_rule(server, "error", "Number of Players Mismatch", NO_FLAGS); } pkt += 4; /* player count 2 */ if (iIgnoreServerPlayer) { pkt += 5; // skip first player data } for (i = 0; i < server->num_players; i++) // for each player get binary data { player = server->players; // first we must find the player - lets look for the tag while (player && (player->team != i)) { player = player->next; } /* get to player - linked list is in reverse order */ if (player) { player->team = pkt[2]; switch (player->team) { case 1: player->team_name = "Red"; break; case 2: player->team_name = "Blue"; break; case 3: player->team_name = "Yellow"; break; case 4: player->team_name = "Green"; break; case 5: player->team_name = "Unassigned"; break; default: player->team_name = "Not Known"; break; } player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; player->deaths = pkt[1]; } pkt += 5; /* player data*/ } for (i = 0; i < 5; i++) { pkt += 8; /* team data who knows what they have in here */ } pkt += 1; iUseStartTimer = pkt[0]; // UseStartTimer pkt += 1; iTemp = flStartTimerSetPoint = swap_float_from_little(pkt); // Start Timer Set Point pkt += 4; if (iUseStartTimer) { add_rule(server, "usestarttime", "Yes", NO_FLAGS); add_rule(server, "starttimeset", play_time(iTemp, 2), NO_FLAGS); } else { add_rule(server, "usestarttime", "No", NO_FLAGS); add_rule(server, "starttimeset", play_time(0, 2), NO_FLAGS); } if ((ServerVersion == VERSION_1_3) || // stuff added in patch 1.3 (ServerVersion == VERSION_1_4)) { iTemp = swap_float_from_little(pkt); // Debrief Time add_rule(server, "debrieftime", play_time(iTemp, 2), NO_FLAGS); pkt += 4; iTemp = swap_float_from_little(pkt); // Respawn Min add_rule(server, "respawnmin", play_time(iTemp, 2), NO_FLAGS); pkt += 4; iTemp = swap_float_from_little(pkt); // Respawn Max add_rule(server, "respawnmax", play_time(iTemp, 2), NO_FLAGS); pkt += 4; iTemp = swap_float_from_little(pkt); // Respawn Invulnerable add_rule(server, "respawnsafe", play_time(iTemp, 2), NO_FLAGS); pkt += 4; } else { add_rule(server, "debrieftime", "Undefined", NO_FLAGS); add_rule(server, "respawnmin", "Undefined", NO_FLAGS); add_rule(server, "respawnmax", "Undefined", NO_FLAGS); add_rule(server, "respawnsafe", "Undefined", NO_FLAGS); } pkt += 4; // 4 iTemp = pkt[0]; // Spawn Count if ((iSpawnType == 1) || (iSpawnType == 2)) /* Individual or team */ { sprintf(str, "%u", iTemp); } else /* else not used */ { sprintf(str, "%u", 0); } add_rule(server, "spawncount", str, NO_FLAGS); pkt += 1; // 5 pkt += 4; // 9 iTemp = pkt[0]; // Allow Observers if (iTemp) { strcpy(str, "Yes"); } else /* else not used */ { strcpy(str, "No"); } add_rule(server, "allowobservers", str, NO_FLAGS); pkt += 1; // 10 pkt += 3; // 13 // pkt += 13; if (iUseStartTimer) { iTemp = swap_float_from_little(pkt); // Start Timer Count add_rule(server, "startwait", play_time(iTemp, 2), NO_FLAGS); } else { add_rule(server, "startwait", play_time(0, 2), NO_FLAGS); } pkt += 4; //17 iTemp = pkt[0]; // IFF switch (iTemp) { case 0: strcpy(str, "None"); break; case 1: strcpy(str, "Reticule"); break; case 2: strcpy(str, "Names"); break; default: strcpy(str, "Unknown"); break; } add_rule(server, "iff", str, NO_FLAGS); pkt += 1; // 18 iTemp = pkt[0]; // Threat Indicator if (iTemp) { add_rule(server, "ti", "ON ", NO_FLAGS); } else { add_rule(server, "ti", "OFF", NO_FLAGS); } pkt += 1; // 19 pkt += 5; // 24 iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Restrictions too Long", NO_FLAGS); return PKT_ERROR; } add_rule(server, "restrict", pkt, NO_FLAGS); pkt += iLen; /* restrictions */ pkt += 23; /* if ( ghostrecon_debug) print_packet( pkt, GrPayloadLen); */ return DONE_FORCE; }