|
1 use anyhow::{bail, Result}; |
1 use argparse::{ArgumentParser, Store}; |
2 use argparse::{ArgumentParser, Store}; |
|
3 use hedgewars_network_protocol::{ |
|
4 messages::HwProtocolMessage as ClientMessage, messages::HwServerMessage::*, parser, |
|
5 }; |
2 use ini::Ini; |
6 use ini::Ini; |
|
7 use log::{debug, info, warn}; |
3 use netbuf::Buf; |
8 use netbuf::Buf; |
4 use log::{debug, warn, info}; |
9 use std::{io::Write, net::TcpStream, process::Command, str::FromStr}; |
5 use std::{ |
10 |
6 io::Write, |
11 fn check(executable: &str, data_prefix: &str, buffer: &[String]) -> Result<Vec<String>> { |
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()?; |
12 let mut replay = tempfile::NamedTempFile::new()?; |
28 |
13 |
29 for line in buffer.split(|b| *b == '\n' as u8) { |
14 for line in buffer.into_iter() { |
30 replay.write(&base64::decode(line)?)?; |
15 replay.write(&base64::decode(line)?)?; |
31 } |
16 } |
32 |
17 |
33 let temp_file_path = replay.path(); |
18 let temp_file_path = replay.path(); |
34 |
19 |
55 .split(|b| *b == '\n' as u8) |
40 .split(|b| *b == '\n' as u8) |
56 .skip_while(|l| *l != b"WINNERS" && *l != b"DRAW"); |
41 .skip_while(|l| *l != b"WINNERS" && *l != b"DRAW"); |
57 |
42 |
58 loop { |
43 loop { |
59 match engine_lines.next() { |
44 match engine_lines.next() { |
60 Some(b"DRAW") => result.push(b"DRAW".to_vec()), |
45 Some(b"DRAW") => result.push("DRAW".to_owned()), |
61 Some(b"WINNERS") => { |
46 Some(b"WINNERS") => { |
62 result.push(b"WINNERS".to_vec()); |
47 result.push("WINNERS".to_owned()); |
63 let winners = engine_lines.next().unwrap(); |
48 let winners = engine_lines.next().unwrap(); |
64 let winners_num = u32::from_str(&String::from_utf8(winners.to_vec())?)?; |
49 let winners_num = u32::from_str(&String::from_utf8(winners.to_vec())?)?; |
65 result.push(winners.to_vec()); |
50 result.push(String::from_utf8(winners.to_vec())?); |
66 |
51 |
67 for _i in 0..winners_num { |
52 for _i in 0..winners_num { |
68 result.push(engine_lines.next().unwrap().to_vec()); |
53 result.push(String::from_utf8(engine_lines.next().unwrap().to_vec())?); |
69 } |
54 } |
70 } |
55 } |
71 Some(b"GHOST_POINTS") => { |
56 Some(b"GHOST_POINTS") => { |
72 result.push(b"GHOST_POINTS".to_vec()); |
57 result.push("GHOST_POINTS".to_owned()); |
73 let points = engine_lines.next().unwrap(); |
58 let points = engine_lines.next().unwrap(); |
74 let points_num = u32::from_str(&String::from_utf8(points.to_vec())?)? * 2; |
59 let points_num = u32::from_str(&String::from_utf8(points.to_vec())?)? * 2; |
75 result.push(points.to_vec()); |
60 result.push(String::from_utf8(points.to_vec())?); |
76 |
61 |
77 for _i in 0..points_num { |
62 for _i in 0..points_num { |
78 result.push(engine_lines.next().unwrap().to_vec()); |
63 result.push(String::from_utf8(engine_lines.next().unwrap().to_vec())?); |
79 } |
64 } |
80 } |
65 } |
81 Some(b"ACHIEVEMENT") => { |
66 Some(b"ACHIEVEMENT") => { |
82 result.push(b"ACHIEVEMENT".to_vec()); |
67 result.push("ACHIEVEMENT".to_owned()); |
83 for _i in 0..4 { |
68 for _i in 0..4 { |
84 result.push(engine_lines.next().unwrap().to_vec()); |
69 result.push(String::from_utf8(engine_lines.next().unwrap().to_vec())?); |
85 } |
70 } |
86 } |
71 } |
87 _ => break, |
72 _ => break, |
88 } |
73 } |
89 } |
74 } |
90 |
75 |
91 if result.len() > 0 { |
76 if result.len() > 0 { |
92 Ok(result) |
77 Ok(result) |
93 } else { |
78 } else { |
94 Err("no data from engine".into()) |
79 bail!("no data from engine") |
95 } |
80 } |
96 } |
81 } |
97 |
82 |
98 fn connect_and_run( |
83 fn connect_and_run( |
99 username: &str, |
84 username: &str, |
100 password: &str, |
85 password: &str, |
101 protocol_number: u32, |
86 protocol_number: u16, |
102 executable: &str, |
87 executable: &str, |
103 data_prefix: &str, |
88 data_prefix: &str, |
104 ) -> Result<(), CheckError> { |
89 ) -> Result<()> { |
105 info!("Connecting..."); |
90 info!("Connecting..."); |
106 |
91 |
107 let mut stream = TcpStream::connect("hedgewars.org:46631")?; |
92 let mut stream = TcpStream::connect("hedgewars.org:46631")?; |
108 stream.set_nonblocking(false)?; |
93 stream.set_nonblocking(false)?; |
109 |
94 |
110 let mut buf = Buf::new(); |
95 let mut buf = Buf::new(); |
111 |
96 |
112 loop { |
97 loop { |
113 buf.read_from(&mut stream)?; |
98 buf.read_from(&mut stream)?; |
114 |
99 |
115 while let Some(msg) = extract_packet(&mut buf) { |
100 while let Ok((tail, msg)) = parser::server_message(buf.as_ref()) { |
116 if msg[..].starts_with(b"CONNECTED") { |
101 buf.consume(buf.len() - tail.len()); |
117 info!("Connected"); |
102 |
118 let p = format!( |
103 match msg { |
119 "CHECKER\n{}\n{}\n{}\n\n", |
104 Connected(_, _) => { |
120 protocol_number, username, password |
105 info!("Connected"); |
121 ); |
106 stream.write( |
122 stream.write(p.as_bytes())?; |
107 ClientMessage::Checker( |
123 } else if msg[..].starts_with(b"PING") { |
108 protocol_number, |
124 stream.write(b"PONG\n\n")?; |
109 username.to_owned(), |
125 } else if msg[..].starts_with(b"LOGONPASSED") { |
110 password.to_owned(), |
126 info!("Logged in"); |
111 ) |
127 stream.write(b"READY\n\n")?; |
112 .to_raw_protocol() |
128 } else if msg[..].starts_with(b"REPLAY") { |
113 .as_bytes(), |
129 info!("Got a replay"); |
114 )?; |
130 match check(executable, data_prefix, &msg[7..]) { |
115 } |
131 Ok(result) => { |
116 Ping => { |
132 info!("Checked"); |
117 stream.write(ClientMessage::Pong.to_raw_protocol().as_bytes())?; |
133 debug!( |
118 } |
134 "Check result: [{}]", |
119 LogonPassed => { |
135 String::from_utf8_lossy(&result.join(&(',' as u8))) |
120 stream.write(ClientMessage::CheckerReady.to_raw_protocol().as_bytes())?; |
136 ); |
121 } |
137 |
122 Replay(lines) => { |
138 stream.write(b"CHECKED\nOK\n")?; |
123 info!("Got a replay"); |
139 stream.write(&result.join(&('\n' as u8)))?; |
124 match check(executable, data_prefix, &lines) { |
140 stream.write(b"\n\nREADY\n\n")?; |
125 Ok(result) => { |
|
126 info!("Checked"); |
|
127 debug!("Check result: [{:?}]", result); |
|
128 |
|
129 stream.write( |
|
130 ClientMessage::CheckedOk(result) |
|
131 .to_raw_protocol() |
|
132 .as_bytes(), |
|
133 )?; |
|
134 stream |
|
135 .write(ClientMessage::CheckerReady.to_raw_protocol().as_bytes())?; |
|
136 } |
|
137 Err(e) => { |
|
138 info!("Check failed: {:?}", e); |
|
139 stream.write( |
|
140 ClientMessage::CheckedFail("error".to_owned()) |
|
141 .to_raw_protocol() |
|
142 .as_bytes(), |
|
143 )?; |
|
144 stream |
|
145 .write(ClientMessage::CheckerReady.to_raw_protocol().as_bytes())?; |
|
146 } |
141 } |
147 } |
142 Err(e) => { |
148 } |
143 info!("Check failed: {:?}", e); |
149 Bye(message) => { |
144 stream.write(b"CHECKED\nFAIL\nerror\n\nREADY\n\n")?; |
150 warn!("Received BYE: {}", message); |
|
151 return Ok(()); |
|
152 } |
|
153 ChatMsg { nick, msg } => { |
|
154 info!("Chat [{}]: {}", nick, msg); |
|
155 } |
|
156 RoomAdd(fields) => { |
|
157 let mut l = fields.into_iter(); |
|
158 info!("Room added: {}", l.skip(1).next().unwrap()); |
|
159 } |
|
160 RoomUpdated(name, fields) => { |
|
161 let mut l = fields.into_iter(); |
|
162 let new_name = l.skip(1).next().unwrap(); |
|
163 |
|
164 if (name != new_name) { |
|
165 info!("Room renamed: {}", new_name); |
145 } |
166 } |
146 } |
167 } |
147 } else if msg[..].starts_with(b"BYE") { |
168 RoomRemove(_) => { |
148 warn!("Received BYE: {}", String::from_utf8_lossy(&msg[..])); |
169 // ignore |
149 return Ok(()); |
170 } |
150 } else if msg[..].starts_with(b"CHAT") { |
171 Error(message) => { |
151 let body = String::from_utf8_lossy(&msg[5..]); |
172 warn!("Received ERROR: {}", message); |
152 let mut l = body.lines(); |
173 return Ok(()); |
153 info!("Chat [{}]: {}", l.next().unwrap(), l.next().unwrap()); |
174 } |
154 } else if msg[..].starts_with(b"ROOM") { |
175 something => { |
155 let body = String::from_utf8_lossy(&msg[5..]); |
176 warn!("Unexpected protocol command: {:?}", something) |
156 let mut l = body.lines(); |
177 } |
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 } |
178 } |
171 } |
179 } |
172 } |
180 } |
173 } |
181 } |
174 |
182 |
175 fn get_protocol_number(executable: &str) -> std::io::Result<u32> { |
183 fn get_protocol_number(executable: &str) -> std::io::Result<u16> { |
176 let output = Command::new(executable).arg("--protocol").output()?; |
184 let output = Command::new(executable).arg("--protocol").output()?; |
177 |
185 |
178 Ok(u32::from_str(&String::from_utf8(output.stdout).unwrap().trim()).unwrap_or(55)) |
186 Ok(u16::from_str(&String::from_utf8(output.stdout).unwrap().trim()).unwrap_or(55)) |
179 } |
187 } |
180 |
188 |
181 fn main() { |
189 fn main() { |
182 stderrlog::new() |
190 stderrlog::new() |
183 .verbosity(3) |
191 .verbosity(3) |