Last active
January 13, 2018 21:44
-
-
Save Andoryuuta/2045593e88ca2336a3f9687d9ac2e5e2 to your computer and use it in GitHub Desktop.
Renegade GSA list viewer in rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::net::UdpSocket; | |
use std::collections::HashMap; | |
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; | |
use std::io::{BufRead, BufReader, BufWriter, Read, Write}; | |
const GSA_ADDRESS: &str = "renmaster.cncnet.org:28900"; // or "master-gsa.renlist.n00b.hk:28900" | |
const EXPECTED_FIRST_RESP: &'static [u8] = b"\\basic\\\\secure\\IGNORE"; | |
const GET_HOSTS_QUERY:&'static [u8] = b"\\gamename\\ccrenegade\\enctype\\0\\validate\\TVJFLK6J\\final\\\\queryid\\1.1\\list\\cmp\\gamename\\ccrenegade\\where\\\\final\\"; | |
fn main() { | |
let hosts = get_hosts(); | |
for host in hosts { | |
println!("Querying {}", host.); | |
let q = query_host_status(host); | |
println!("{:>3}/{:<3}\t\t{:<40}\t{}:{}", q["numplayers"], q["maxplayers"], q["hostname"].trim_left(), host.ip(), q["hostport"]); | |
} | |
} | |
fn get_hosts() -> Vec<SocketAddr> { | |
// Connect to the GameSpy Arcade master server. | |
let stream = TcpStream::connect(GSA_ADDRESS).expect("Failed to connect to GSA master server"); | |
let mut reader = BufReader::new(&stream); | |
let mut writer = BufWriter::new(&stream); | |
// Check for GSA "\basic\\secure\IGNORE\" packet. | |
let mut first_packet = vec![0; EXPECTED_FIRST_RESP.len()]; | |
reader | |
.read_exact(&mut first_packet) | |
.expect("Failed reading first GSA packet"); | |
if first_packet != EXPECTED_FIRST_RESP { | |
// TODO(Andoryuuta): Make this return an error when converting into a library. | |
panic!("GSA master server didn't send the expected packet!"); | |
} | |
// Query the GameSpy master server for game hosts. | |
let _ = writer.write(GET_HOSTS_QUERY); | |
// Read in the hosts data from the server. | |
let mut hosts = Vec::new(); | |
{ | |
let mut hosts_data = vec![]; | |
// The host data packet is terminated with b"\\final\\", so just read until the first backslash. | |
let n = reader | |
.read_until(b'\\', &mut hosts_data) | |
.expect("Error reading hosts data"); | |
// Remove the delimiter byte. | |
hosts_data.truncate(n - 1); | |
assert_eq!(hosts_data.len() % 6, 0); | |
for b in hosts_data.chunks(6) { | |
let ip = Ipv4Addr::new(b[0], b[1], b[2], b[3]); | |
let port: u16 = (b[4] as u16) << 8 | (b[5] as u16); | |
let socket_address = SocketAddr::new(IpAddr::from(ip), port); | |
hosts.push(socket_address); | |
} | |
} | |
hosts | |
} | |
fn query_host_status(host:SocketAddr) -> HashMap<String, String>{ | |
// Bind and "connect" a UDP socket. | |
let sock = UdpSocket::bind("0.0.0.0:0").expect("Failed to get UDP bind address from OS"); | |
sock.connect(format!("{}:{}", host.ip(), host.port())).expect("Failed to connect to game host"); | |
// Send the status query. | |
sock.send(b"\\status\\").expect("Failed to send UDP datagram"); | |
// Read in the full response query. | |
let mut query_string = String::new(); | |
loop{ | |
let mut buf:[u8; 1024*4] = [0; 1024*4]; | |
match sock.recv(&mut buf) { | |
Ok(received) => { | |
let tmp_str = std::str::from_utf8(&buf[..received-1]).unwrap(); | |
query_string.push_str(tmp_str); | |
if tmp_str.contains("\\final\\"){ | |
break | |
} | |
}, | |
Err(e) => println!("recv function failed: {:?}", e), | |
} | |
} | |
// Split string for converting to key-value pairs. | |
let query_split: Vec<&str> = query_string.split('\\').collect(); | |
let mut query_fields = HashMap::new(); | |
// There has got to be a better way to do a loop with a specific step. | |
let mut i = 1; | |
while i < query_split.len()-1{ | |
query_fields.insert(query_split[i].to_owned(), query_split[i+1].to_owned()); | |
i += 2; | |
} | |
query_fields | |
} | |
//query : \gametype\Infantry\mapname\C&C_Gobi.mix\nextmap\C&C_Ulake_D3.mix\timeleft\00.18.22\timeelapsed\00.11.30\timelimit\00.30.00\Starting Credits\50\FDS\Dragonade 1.8.1\Website\www.mpforums.com\ts\talk.cncirc.net\irc\irc.cncirc.net #mpf-newmaps\taunts\1\veteran system\1\Crates\1\Character Refunds\1\Donate\1\Loot\1\Infinite Ammo\1\Advanced Kill Messages\1\Parachutes\1\Points Distribution\1\Extra Radio Commands\1\Taunts\1\Vehicle Ownership\1\Vehicle Shells\1\Vehicle Queue\1\Sounds and Taunts\1\hostname\ MPF UltraAOW NewMaps\gamename\ccrenegade\gamever\838\hostport\1337\password\0\numplayers\16\maxplayers\50\queryid\12912.1\player_0\Nod\score_0\3255\kills_0\47\deaths_0\53\time_0\00.11.30\ping_0\3\team_0\Nod\player_1\GDI\score_1\3380\kills_1\53\deaths_1\45\time_1\00.11.30\ping_1\3\team_1\GDI\player_2\kirou11\score_2\339\kills_2\5\deaths_2\7\time_2\00.11.30\ping_2\171\team_2\Nod\player_3\nod398\score_3\443\kills_3\7\deaths_3\12\time_3\00.11.30\ping_3\128\team_3\Nod\player_4\RIDERSRULE\score_4\406\kills_4\3\deaths_4\7\time_4\00.11.30\ping_4\58\team_4\Nod\player_5\Cereal_Killer\score_5\725\kills_5\18\deaths_5\6\time_5\00.11.30\ping_5\54\team_5\GDI\player_6\Oxdradium\score_6\211\kills_6\9\deaths_6\5\time_6\00.11.30\ping_6\142\team_6\Nod\player_7\MPFwobbly196\score_7\184\kills_7\2\deaths_7\2\time_7\00.11.30\ping_7\140\team_7\GDI\player_8\viperexcl\score_8\430\kills_8\1\deaths_8\13\time_8\00.11.30\ping_8\173\team_8\GDI\player_9\dadukas\score_9\647\kills_9\8\deaths_9\7\time_9\00.11.30\ping_9\74\team_9\GDI\player_10\Christian\score_10\588\kills_10\14\deaths_10\4\time_10\00.11.30\ping_10\81\team_10\GDI\player_11\Redz85\score_11\358\kills_11\7\deaths_11\7\time_11\00.11.30\ping_11\176\team_11\Nod\queryid\12912.2\player_12\JO_MOMMA_USA\score_12\54\kills_12\3\deaths_12\0\time_12\00.11.30\ping_12\50\team_12\Nod\player_13\testrambo\score_13\812\kills_13\4\deaths_13\5\time_13\00.11.30\ping_13\124\team_13\Nod\player_14\defries2\score_14\571\kills_14\3\deaths_14\4\time_14\00.11.30\ping_14\137\team_14\GDI\player_15\DDoSer\score_15\613\kills_15\8\deaths_15\9\time_15\00.11.30\ping_15\188\team_15\Nod\player_16\aaaaaaaa\score_16\134\kills_16\3\deaths_16\5\time_16\00.11.27\ping_16\187\team_16\GDI\player_17\unknown\score_17\16\kills_17\1\deaths_17\2\time_17\00.06.19\ping_17\246\team_17\GDI\queryid\12912.3\final\\queryid\12912.4 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment