rust/hedgewars-checker/src/main.rs
branchui-scaling
changeset 15288 c4fd2813b127
parent 14401 694c96fb3a06
equal deleted inserted replaced
13395:0135e64c6c66 15288:c4fd2813b127
       
     1 use argparse::{ArgumentParser, Store};
       
     2 use ini::Ini;
       
     3 use netbuf::Buf;
       
     4 use log::{debug, warn, info};
       
     5 use std::{
       
     6     io::Write,
       
     7     net::TcpStream,
       
     8     process::Command,
       
     9     str::FromStr
       
    10 };
       
    11 
       
    12 type CheckError = Box<std::error::Error>;
       
    13 
       
    14 fn extract_packet(buf: &mut Buf) -> Option<netbuf::Buf> {
       
    15     let packet_end = (&buf[..]).windows(2).position(|window| window == b"\n\n")?;
       
    16 
       
    17     let mut tail = buf.split_off(packet_end);
       
    18 
       
    19     std::mem::swap(&mut tail, buf);
       
    20 
       
    21     buf.consume(2);
       
    22 
       
    23     Some(tail)
       
    24 }
       
    25 
       
    26 fn check(executable: &str, data_prefix: &str, buffer: &[u8]) -> Result<Vec<Vec<u8>>, CheckError> {
       
    27     let mut replay = tempfile::NamedTempFile::new()?;
       
    28 
       
    29     for line in buffer.split(|b| *b == '\n' as u8) {
       
    30         replay.write(&base64::decode(line)?)?;
       
    31     }
       
    32 
       
    33     let temp_file_path = replay.path();
       
    34 
       
    35     let mut home_dir = dirs::home_dir().unwrap();
       
    36     home_dir.push(".hedgewars");
       
    37 
       
    38     debug!("Checking replay in {}", temp_file_path.to_string_lossy());
       
    39 
       
    40     let output = Command::new(executable)
       
    41         .arg("--user-prefix")
       
    42         .arg(&home_dir)
       
    43         .arg("--prefix")
       
    44         .arg(data_prefix)
       
    45         .arg("--nomusic")
       
    46         .arg("--nosound")
       
    47         .arg("--stats-only")
       
    48         .arg(temp_file_path)
       
    49         .output()?;
       
    50 
       
    51     let mut result = Vec::new();
       
    52 
       
    53     let mut engine_lines = output
       
    54         .stderr
       
    55         .split(|b| *b == '\n' as u8)
       
    56         .skip_while(|l| *l != b"WINNERS" && *l != b"DRAW");
       
    57 
       
    58     loop {
       
    59         match engine_lines.next() {
       
    60             Some(b"DRAW") => result.push(b"DRAW".to_vec()),
       
    61             Some(b"WINNERS") => {
       
    62                 result.push(b"WINNERS".to_vec());
       
    63                 let winners = engine_lines.next().unwrap();
       
    64                 let winners_num = u32::from_str(&String::from_utf8(winners.to_vec())?)?;
       
    65                 result.push(winners.to_vec());
       
    66 
       
    67                 for _i in 0..winners_num {
       
    68                     result.push(engine_lines.next().unwrap().to_vec());
       
    69                 }
       
    70             }
       
    71             Some(b"GHOST_POINTS") => {
       
    72                 result.push(b"GHOST_POINTS".to_vec());
       
    73                 let points = engine_lines.next().unwrap();
       
    74                 let points_num = u32::from_str(&String::from_utf8(points.to_vec())?)? * 2;
       
    75                 result.push(points.to_vec());
       
    76 
       
    77                 for _i in 0..points_num {
       
    78                     result.push(engine_lines.next().unwrap().to_vec());
       
    79                 }
       
    80             }
       
    81             Some(b"ACHIEVEMENT") => {
       
    82                 result.push(b"ACHIEVEMENT".to_vec());
       
    83                 for _i in 0..4 {
       
    84                     result.push(engine_lines.next().unwrap().to_vec());
       
    85                 }
       
    86             }
       
    87             _ => break,
       
    88         }
       
    89     }
       
    90 
       
    91     if result.len() > 0 {
       
    92         Ok(result)
       
    93     } else {
       
    94         Err("no data from engine".into())
       
    95     }
       
    96 }
       
    97 
       
    98 fn connect_and_run(
       
    99     username: &str,
       
   100     password: &str,
       
   101     protocol_number: u32,
       
   102     executable: &str,
       
   103     data_prefix: &str,
       
   104 ) -> Result<(), CheckError> {
       
   105     info!("Connecting...");
       
   106 
       
   107     let mut stream = TcpStream::connect("hedgewars.org:46631")?;
       
   108     stream.set_nonblocking(false)?;
       
   109 
       
   110     let mut buf = Buf::new();
       
   111 
       
   112     loop {
       
   113         buf.read_from(&mut stream)?;
       
   114 
       
   115         while let Some(msg) = extract_packet(&mut buf) {
       
   116             if msg[..].starts_with(b"CONNECTED") {
       
   117                 info!("Connected");
       
   118                 let p = format!(
       
   119                     "CHECKER\n{}\n{}\n{}\n\n",
       
   120                     protocol_number, username, password
       
   121                 );
       
   122                 stream.write(p.as_bytes())?;
       
   123             } else if msg[..].starts_with(b"PING") {
       
   124                 stream.write(b"PONG\n\n")?;
       
   125             } else if msg[..].starts_with(b"LOGONPASSED") {
       
   126                 info!("Logged in");
       
   127                 stream.write(b"READY\n\n")?;
       
   128             } else if msg[..].starts_with(b"REPLAY") {
       
   129                 info!("Got a replay");
       
   130                 match check(executable, data_prefix, &msg[7..]) {
       
   131                     Ok(result) => {
       
   132                         info!("Checked");
       
   133                         debug!(
       
   134                             "Check result: [{}]",
       
   135                             String::from_utf8_lossy(&result.join(&(',' as u8)))
       
   136                         );
       
   137 
       
   138                         stream.write(b"CHECKED\nOK\n")?;
       
   139                         stream.write(&result.join(&('\n' as u8)))?;
       
   140                         stream.write(b"\n\nREADY\n\n")?;
       
   141                     }
       
   142                     Err(e) => {
       
   143                         info!("Check failed: {:?}", e);
       
   144                         stream.write(b"CHECKED\nFAIL\nerror\n\nREADY\n\n")?;
       
   145                     }
       
   146                 }
       
   147             } else if msg[..].starts_with(b"BYE") {
       
   148                 warn!("Received BYE: {}", String::from_utf8_lossy(&msg[..]));
       
   149                 return Ok(());
       
   150             } else if msg[..].starts_with(b"CHAT") {
       
   151                 let body = String::from_utf8_lossy(&msg[5..]);
       
   152                 let mut l = body.lines();
       
   153                 info!("Chat [{}]: {}", l.next().unwrap(), l.next().unwrap());
       
   154             } else if msg[..].starts_with(b"ROOM") {
       
   155                 let body = String::from_utf8_lossy(&msg[5..]);
       
   156                 let mut l = body.lines();
       
   157                 if let Some(action) = l.next() {
       
   158                     if action == "ADD" {
       
   159                         info!("Room added: {}", l.skip(1).next().unwrap());
       
   160                     }
       
   161                 }
       
   162             } else if msg[..].starts_with(b"ERROR") {
       
   163                 warn!("Received ERROR: {}", String::from_utf8_lossy(&msg[..]));
       
   164                 return Ok(());
       
   165             } else {
       
   166                 warn!(
       
   167                     "Unknown protocol command: {}",
       
   168                     String::from_utf8_lossy(&msg[..])
       
   169                 )
       
   170             }
       
   171         }
       
   172     }
       
   173 }
       
   174 
       
   175 fn get_protocol_number(executable: &str) -> std::io::Result<u32> {
       
   176     let output = Command::new(executable).arg("--protocol").output()?;
       
   177 
       
   178     Ok(u32::from_str(&String::from_utf8(output.stdout).unwrap().trim()).unwrap_or(55))
       
   179 }
       
   180 
       
   181 fn main() {
       
   182     stderrlog::new()
       
   183         .verbosity(3)
       
   184         .timestamp(stderrlog::Timestamp::Second)
       
   185         .module(module_path!())
       
   186         .init()
       
   187         .unwrap();
       
   188 
       
   189     let mut frontend_settings = dirs::home_dir().unwrap();
       
   190     frontend_settings.push(".hedgewars/settings.ini");
       
   191 
       
   192     let i = Ini::load_from_file(frontend_settings.to_str().unwrap()).unwrap();
       
   193     let username = i.get_from(Some("net"), "nick").unwrap();
       
   194     let password = i.get_from(Some("net"), "passwordhash").unwrap();
       
   195 
       
   196     let mut exe = "/usr/local/bin/hwengine".to_string();
       
   197     let mut prefix = "/usr/local/share/hedgewars/Data".to_string();
       
   198     {
       
   199         let mut ap = ArgumentParser::new();
       
   200         ap.set_description("Game replay checker for hedgewars.");
       
   201         ap.refer(&mut exe)
       
   202             .add_option(&["--exe"], Store, "Path to hwengine executable");
       
   203         ap.refer(&mut prefix)
       
   204             .add_option(&["--prefix"], Store, "Path main Data dir");
       
   205         ap.parse_args_or_exit();
       
   206     }
       
   207 
       
   208     info!("Executable: {}", exe);
       
   209     info!("Data dir: {}", prefix);
       
   210 
       
   211     let protocol_number = get_protocol_number(&exe.as_str()).unwrap_or_default();
       
   212 
       
   213     info!("Using protocol number {}", protocol_number);
       
   214 
       
   215     connect_and_run(&username, &password, protocol_number, &exe, &prefix).unwrap();
       
   216 }
       
   217 
       
   218 #[cfg(test)]
       
   219 #[test]
       
   220 fn test() {
       
   221     let mut buf = Buf::new();
       
   222     buf.extend(b"Hell");
       
   223     if let Some(_) = extract_packet(&mut buf) {
       
   224         assert!(false)
       
   225     }
       
   226 
       
   227     buf.extend(b"o\n\nWorld");
       
   228 
       
   229     let packet2 = extract_packet(&mut buf).unwrap();
       
   230     assert_eq!(&buf[..], b"World");
       
   231     assert_eq!(&packet2[..], b"Hello");
       
   232 
       
   233     if let Some(_) = extract_packet(&mut buf) {
       
   234         assert!(false)
       
   235     }
       
   236 }