From 802027c5524252385a4035c8b0f7608428bbf472 Mon Sep 17 00:00:00 2001 From: mmorrison Date: Sun, 18 Mar 2018 00:54:56 -0500 Subject: [PATCH] Add support for Tribes 1: Starsiege --- README.md | 2 +- games.txt | 1 + protocols/tribes1.js | 103 ++++++++++++++++ reference/tribes/gameq.txt | 63 ---------- reference/tribes/lgsl.txt | 125 ------------------- reference/tribes/qstat.txt | 247 ------------------------------------- 6 files changed, 105 insertions(+), 436 deletions(-) create mode 100644 protocols/tribes1.js delete mode 100644 reference/tribes/gameq.txt delete mode 100644 reference/tribes/lgsl.txt delete mode 100644 reference/tribes/qstat.txt diff --git a/README.md b/README.md index 7ef52c1..3dc5a82 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,7 @@ Games List * Trackmania 2 (trackmania2) [[Additional Notes](#nadeo-shootmania--trackmania--etc)] * Trackmania Forever (trackmaniaforever) [[Additional Notes](#nadeo-shootmania--trackmania--etc)] * Tremulous (tremulous) [[Separate Query Port](#separate-query-port)] +* Tribes 1: Starsiege (tribes1) * Tribes: Vengeance (tribesvengeance) [[Separate Query Port](#separate-query-port)] * Tron 2.0 (tron20) [[Separate Query Port](#separate-query-port)] * Turok 2 (turok2) [[Separate Query Port](#separate-query-port)] @@ -356,7 +357,6 @@ Games List * Savage 2: A Tortured Soul * Sum of All Fears * Teeworlds -* Tribes 1: Starsiege * Tribes 2 * Vice City Multiplayer * World in Conflict diff --git a/games.txt b/games.txt index 97a880e..00617bc 100644 --- a/games.txt +++ b/games.txt @@ -266,6 +266,7 @@ towerunite|Tower Unite|valve trackmania2|Trackmania 2|nadeo||doc_notes=nadeo-shootmania--trackmania--etc trackmaniaforever|Trackmania Forever|nadeo||doc_notes=nadeo-shootmania--trackmania--etc tremulous|Tremulous|quake3|port_query=30720 +tribes1|Tribes 1: Starsiege|tribes1|port=28001 tribesvengeance|Tribes: Vengeance|gamespy2|port=7777,port_query_offset=1 tron20|Tron 2.0|gamespy2|port_query=27888 turok2|Turok 2|gamespy1|port_query=12880 diff --git a/protocols/tribes1.js b/protocols/tribes1.js new file mode 100644 index 0000000..65978fa --- /dev/null +++ b/protocols/tribes1.js @@ -0,0 +1,103 @@ +class Unreal2 extends require('./core') { + constructor() { + super(); + this.encoding = 'latin1'; + } + run(state) { + const queryBuffer = Buffer.from('b++'); + this.udpSend(queryBuffer,(buffer) => { + const reader = this.reader(buffer); + const header = reader.string({length:4}); + if (header !== 'c++b') { + this.fatal('Header response does not match: ' + header); + return true; + } + state.raw.gametype = this.readString(reader); + state.raw.version = this.readString(reader); + state.name = this.readString(reader); + state.raw.dedicated = !!reader.uint(1); + state.password = !!reader.uint(1); + state.raw.playerCount = reader.uint(1); + state.maxplayers = reader.uint(1); + state.raw.cpu = reader.uint(2); + state.raw.mod = this.readString(reader); + state.raw.type = this.readString(reader); + state.map = this.readString(reader); + state.raw.motd = this.readString(reader); + state.raw.teamCount = reader.uint(1); + + const teamFields = this.readFieldList(reader); + const playerFields = this.readFieldList(reader); + console.log(teamFields); + console.log(playerFields); + + state.raw.teams = []; + for(let i = 0; i < state.raw.teamCount; i++) { + const teamName = this.readString(reader); + const teamValues = this.readString(reader) + .replace(/%t/g, teamName) + .split('\t') + .map((a) => a.trim()); + + const teamInfo = {}; + for (let i = 0; i < teamValues.length && i < teamFields.length; i++) { + const key = teamFields[i]; + let value = teamValues[i]; + if (key === 'score' || key === 'players') value = parseInt(value); + teamInfo[key] = value; + } + state.raw.teams.push(teamInfo); + } + + for(let i = 0; i < state.raw.playerCount; i++) { + const ping = reader.uint(1) * 4; + const packetLoss = reader.uint(1); + const teamNum = reader.uint(1); + const name = this.readString(reader); + const valuesStr = this.readString(reader); + if (!valuesStr) continue; + const playerValues = valuesStr + .replace(/%p/g, ping) + .replace(/%l/g, packetLoss) + .replace(/%t/g, teamNum) + .replace(/%n/g, name) + .split('\t') + .map((a) => a.trim()); + + const playerInfo = {}; + for (let i = 0; i < playerValues.length && i < playerFields.length; i++) { + const key = playerFields[i]; + let value = playerValues[i]; + if (key === 'score' || key === 'ping' || key === 'pl') value = parseInt(value); + if (key === 'team') { + const teamId = parseInt(value); + if (teamId >= 0 && teamId < state.raw.teams.length && state.raw.teams[teamId].name) { + value = state.raw.teams[teamId].name; + } else { + continue; + } + } + playerInfo[key] = value; + } + state.players.push(playerInfo); + } + + this.finish(state); + return true; + }); + } + readFieldList(reader) { + return ('?'+this.readString(reader)) + .split('\t') + .map((a) => a.substr(1).toLowerCase()) + .map((a) => a === 'team name' ? 'name' : a) + .map((a) => a === 'player name' ? 'name' : a); + } + readString(reader) { + const length = reader.uint(1); + if(!length) return ''; + return reader.string({length:length}); + } +} + +module.exports = Unreal2; diff --git a/reference/tribes/gameq.txt b/reference/tribes/gameq.txt deleted file mode 100644 index cb02a30..0000000 --- a/reference/tribes/gameq.txt +++ /dev/null @@ -1,63 +0,0 @@ -. - * - * $Id: tribes.php,v 1.1 2007/07/07 14:20:21 tombuskens Exp $ - */ - -[tribes] -status = "b++" - - -require_once GAMEQ_BASE . 'Protocol.php'; - - -/** - * Tribes protocol - * - * @author Tom Buskens - * @version $Revision: 1.1 $ - */ -class GameQ_Protocol_tribes extends GameQ_Protocol -{ - public function status() - { - // Header - if ($this->p->read(4) != 'c++b') { - throw new GameQ_ParsingException($this->p); - } - - // Variables - $this->r->add('game', $this->p->readPascalString()); - $this->r->add('version', $this->p->readPascalString()); - $this->r->add('hostname', $this->p->readPascalString()); - $this->r->add('dedicated', $this->p->readInt8()); - $this->r->add('password', $this->p->readInt8()); - $this->r->add('num_players', $this->p->readInt8()); - $this->r->add('max_players', $this->p->readInt8()); - $this->r->add('cpu_lsb', $this->p->readInt8()); - $this->r->add('cpu_msb', $this->p->readInt8()); - $this->r->add('mod', $this->p->readPascalString()); - $this->r->add('gametype', $this->p->readPascalString()); - $this->r->add('map', $this->p->readPascalString()); - $this->r->add('motd', $this->p->readPascalString()); - $this->r->add('teamcount', $this->p->readInt8()); // Not sure - - // TODO: player listing - } -} -?> - diff --git a/reference/tribes/lgsl.txt b/reference/tribes/lgsl.txt deleted file mode 100644 index ca7f5c6..0000000 --- a/reference/tribes/lgsl.txt +++ /dev/null @@ -1,125 +0,0 @@ - - /*----------------------------------------------------------------------------------------------------------\ - | | - | [ LIVE GAME SERVER LIST ] [ © RICHARD PERRY FROM GREYCUBE.COM ] | - | | - | Released under the terms and conditions of the GNU General Public License Version 3 (http://gnu.org) | - | | - \-----------------------------------------------------------------------------------------------------------*/ - - - function lgsl_query_23(&$server, &$lgsl_need, &$lgsl_fp) - { -//---------------------------------------------------------+ -// REFERENCE: -// http://siteinthe.us -// http://www.tribesmasterserver.com - - fwrite($lgsl_fp, "b++"); - - $buffer = fread($lgsl_fp, 4096); - - if (!$buffer) { return FALSE; } - - $buffer = substr($buffer, 4); // REMOVE HEADER - -//---------------------------------------------------------+ - - $server['s']['game'] = lgsl_cut_pascal($buffer); - $server['e']['version'] = lgsl_cut_pascal($buffer); - $server['s']['name'] = lgsl_cut_pascal($buffer); - $server['e']['dedicated'] = ord(lgsl_cut_byte($buffer, 1)); - $server['s']['password'] = ord(lgsl_cut_byte($buffer, 1)); - $server['s']['players'] = ord(lgsl_cut_byte($buffer, 1)); - $server['s']['playersmax'] = ord(lgsl_cut_byte($buffer, 1)); - $server['e']['cpu'] = lgsl_unpack(lgsl_cut_byte($buffer, 2), "S"); - $server['e']['mod'] = lgsl_cut_pascal($buffer); - $server['e']['type'] = lgsl_cut_pascal($buffer); - $server['s']['map'] = lgsl_cut_pascal($buffer); - $server['e']['motd'] = lgsl_cut_pascal($buffer); - $server['e']['teams'] = ord(lgsl_cut_byte($buffer, 1)); - -//---------------------------------------------------------+ - - $team_field = "?".lgsl_cut_pascal($buffer); - $team_field = split("\t", $team_field); - - foreach ($team_field as $key => $value) - { - $value = substr($value, 1); - $value = strtolower($value); - $team_field[$key] = $value; - } - -//---------------------------------------------------------+ - - $player_field = "?".lgsl_cut_pascal($buffer); - $player_field = split("\t", $player_field); - - foreach ($player_field as $key => $value) - { - $value = substr($value, 1); - $value = strtolower($value); - - if ($value == "player name") { $value = "name"; } - - $player_field[$key] = $value; - } - - $player_field[] = "unknown_1"; - $player_field[] = "unknown_2"; - -//---------------------------------------------------------+ - - for ($i=0; $i<$server['e']['teams']; $i++) - { - $team_name = lgsl_cut_pascal($buffer); - $team_info = lgsl_cut_pascal($buffer); - - if (!$team_info) { continue; } - - $team_info = str_replace("%t", $team_name, $team_info); - $team_info = split("\t", $team_info); - - foreach ($team_info as $key => $value) - { - $field = $team_field[$key]; - $value = trim($value); - - if ($field == "team name") { $field = "name"; } - - $server['t'][$i][$field] = $value; - } - } - -//---------------------------------------------------------+ - - for ($i=0; $i<$server['s']['players']; $i++) - { - $player_bits = array(); - $player_bits[] = ord(lgsl_cut_byte($buffer, 1)) * 4; // %p = PING - $player_bits[] = ord(lgsl_cut_byte($buffer, 1)); // %l = PACKET LOSS - $player_bits[] = ord(lgsl_cut_byte($buffer, 1)); // %t = TEAM - $player_bits[] = lgsl_cut_pascal($buffer); // %n = PLAYER NAME - $player_info = lgsl_cut_pascal($buffer); - - if (!$player_info) { continue; } - - $player_info = str_replace(array("%p","%l","%t","%n"), $player_bits, $player_info); - $player_info = split("\t", $player_info); - - foreach ($player_info as $key => $value) - { - $field = $player_field[$key]; - $value = trim($value); - - if ($field == "team") { $value = $server['t'][$value]['name']; } - - $server['p'][$i][$field] = $value; - } - } - -//---------------------------------------------------------+ - - return TRUE; - } diff --git a/reference/tribes/qstat.txt b/reference/tribes/qstat.txt deleted file mode 100644 index 04eb5a8..0000000 --- a/reference/tribes/qstat.txt +++ /dev/null @@ -1,247 +0,0 @@ -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 */ -char tribes_info[] = { '`', '*', '*' }; -char tribes_players[] = { 'b', '*', '*' }; -/* This is what the game sends to get minimal status -{ '\020', '\03', '\377', 0, (unsigned char)0xc5, 6 }; -*/ -char tribes_info_reponse[] = { 'a', '*', '*', 'b' }; -char tribes_players_reponse[] = { 'c', '*', '*', 'b' }; -char tribes_masterquery[] = { 0x10, 0x3, '\377', 0, 0x2 }; -char tribes_master_response[] = { 0x10, 0x6 }; - - - - { - /* TRIBES */ - TRIBES_SERVER, /* id */ - "TBS", /* type_prefix */ - "tbs", /* type_string */ - "-tbs", /* type_option */ - "Tribes", /* game_name */ - 0, /* master */ - TRIBES_DEFAULT_PORT, /* default_port */ - 0, /* port_offset */ - TF_SINGLE_QUERY, /* flags */ - "game", /* game_rule */ - "TRIBES", /* template_var */ - (char*) &tribes_info, /* status_packet */ - sizeof( tribes_info), /* status_len */ - (char*) &tribes_players, /* player_packet */ - sizeof( tribes_players), /* player_len */ - (char*) &tribes_players, /* rule_packet */ - sizeof( tribes_players), /* rule_len */ - NULL, /* master_packet */ - 0, /* master_len */ - NULL, /* master_protocol */ - NULL, /* master_query */ - display_tribes_player_info, /* display_player_func */ - display_server_rules, /* display_rule_func */ - raw_display_tribes_player_info, /* display_raw_player_func */ - raw_display_server_rules, /* display_raw_rule_func */ - xml_display_tribes_player_info, /* display_xml_player_func */ - xml_display_server_rules, /* display_xml_rule_func */ - send_tribes_request_packet, /* status_query_func */ - NULL, /* rule_query_func */ - NULL, /* player_query_func */ - deal_with_tribes_packet, /* packet_func */ -}, - - - - - - -query_status_t deal_with_tribes_packet(struct qserver *server, char *rawpkt, int pktlen) -{ - unsigned char *pkt, *end; - int len, pnum, ping, packet_loss, n_teams, t; - struct player *player; - struct player **teams = NULL; - struct player **last_player = &server->players; - char buf[24]; - - debug( 2, "deal_with_tribes_packet %p, %d", server, pktlen ); - - if (server->server_name == NULL) - { - server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); - } - else - { - gettimeofday(&server->packet_time1, NULL); - } - - if (pktlen < sizeof(tribes_info_reponse)) - { - return PKT_ERROR; - } - - if (strncmp(rawpkt, tribes_players_reponse, sizeof(tribes_players_reponse)) != 0) - { - return PKT_ERROR; - } - - pkt = (unsigned char*) &rawpkt[sizeof(tribes_info_reponse)]; - - len = *pkt; /* game name: "Tribes" */ - add_nrule(server, "gamename", (char*)pkt + 1, len); - pkt += len + 1; - len = *pkt; /* version */ - add_nrule(server, "version", (char*)pkt + 1, len); - pkt += len + 1; - len = *pkt; /* server name */ - server->server_name = strndup((char*)pkt + 1, len); - pkt += len + 1; - add_rule(server, "dedicated", *pkt ? "1" : "0", NO_FLAGS); - pkt++; /* flag: dedicated server */ - add_rule(server, "needpass", *pkt ? "1" : "0", NO_FLAGS); - pkt++; /* flag: password on server */ - server->num_players = *pkt++; - server->max_players = *pkt++; - - sprintf(buf, "%u", (unsigned int)pkt[0] + (unsigned int)pkt[1] *256); - add_rule(server, "cpu", buf, NO_FLAGS); - pkt++; /* cpu speed, lsb */ - pkt++; /* cpu speed, msb */ - - len = *pkt; /* Mod (game) */ - add_nrule(server, "mods", (char*)pkt + 1, len); - pkt += len + 1; - - len = *pkt; /* game (mission): "C&H" */ - add_nrule(server, "game", (char*)pkt + 1, len); - pkt += len + 1; - - len = *pkt; /* Mission (map) */ - server->map_name = strndup((char*)pkt + 1, len); - pkt += len + 1; - - len = *pkt; /* description (contains Admin: and Email: ) */ - debug( 2, "%.*s\n", len, pkt + 1); - pkt += len + 1; - - n_teams = *pkt++; /* number of teams */ - if (n_teams == 255) - { - return PKT_ERROR; - } - sprintf(buf, "%d", n_teams); - add_rule(server, "numteams", buf, NO_FLAGS); - - len = *pkt; /* first title */ - debug( 2, "%.*s\n", len, pkt + 1); - pkt += len + 1; - - len = *pkt; /* second title */ - debug( 2, "%.*s\n", len, pkt + 1); - pkt += len + 1; - - if (n_teams > 1) - { - 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; - len = *pkt; /* team name */ - teams[t]->name = strndup((char*)pkt + 1, len); - debug( 2, "team#0 <%.*s>\n", len, pkt + 1); - pkt += len + 1; - - len = *pkt; /* team score */ - if (len > 2) - { - strncpy(buf, (char*)pkt + 1+3, len - 3); - buf[len - 3] = '\0'; - } - else - { - debug( 2, "%s score len %d\n", server->arg, len); - buf[0] = '\0'; - } - teams[t]->frags = atoi(buf); - debug( 2, "team#0 <%.*s>\n", len - 3, pkt + 1+3); - pkt += len + 1; - } - } - else - { - len = *pkt; /* DM team? */ - debug( 2, "%.*s\n", len, pkt + 1); - pkt += len + 1; - pkt++; - n_teams = 0; - } - - pnum = 0; - while ((char*)pkt < (rawpkt + pktlen)) - { - ping = (unsigned int) *pkt << 2; - pkt++; - packet_loss = *pkt; - pkt++; - debug( 2, "player#%d, team #%d\n", pnum, (int) *pkt); - pkt++; - len = *pkt; - if ((char*)pkt + len > (rawpkt + pktlen)) - { - break; - } - player = (struct player*)calloc(1, sizeof(struct player)); - player->team = pkt[ - 1]; - if (n_teams && player->team < n_teams) - { - player->team_name = teams[player->team]->name; - } - else if (player->team == 255 && n_teams) - { - player->team_name = "Unknown"; - } - player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; - player->ping = ping; - player->packet_loss = packet_loss; - player->name = strndup((char*)pkt + 1, len); - debug( 2, "player#%d, name %.*s\n", pnum, len, pkt + 1); - pkt += len + 1; - len = *pkt; - debug( 2, "player#%d, info <%.*s>\n", pnum, len, pkt + 1); - end = (unsigned char*)strchr((char*)pkt + 9, 0x9); - if (end) - { - strncpy(buf, (char*)pkt + 9, end - (pkt + 9)); - buf[end - (pkt + 9)] = '\0'; - player->frags = atoi(buf); - debug( 2, "player#%d, score <%.*s>\n", pnum, (unsigned)(end - (pkt + 9)), pkt + 9); - } - - *last_player = player; - last_player = &player->next; - - pkt += len + 1; - pnum++; - } - - for (t = n_teams; t;) - { - t--; - teams[t]->next = server->players; - server->players = teams[t]; - } - free(teams); - - return DONE_AUTO; -}