29 */ |
29 */ |
30 |
30 |
31 #include <time.h> |
31 #include <time.h> |
32 #include "rconsession.h" |
32 #include "rconsession.h" |
33 #include "../interface.h" |
33 #include "../interface.h" |
|
34 #include "../md5.h" |
34 BEGIN_ZFC_NAMESPACE |
35 BEGIN_ZFC_NAMESPACE |
35 |
36 |
36 // ------------------------------------------------------------------------------------------------- |
37 // ------------------------------------------------------------------------------------------------- |
37 // |
38 // |
38 RCONSession::RCONSession() : |
39 RCONSession::RCONSession() : |
39 m_state(RCON_DISCONNECTED), |
40 m_state(RCON_DISCONNECTED), |
40 m_lastPing(0), |
41 m_lastPing(0), |
41 m_adminCount(0), |
42 m_adminCount(0), |
42 m_interface(nullptr) |
43 m_interface(nullptr) |
43 { |
44 { |
44 if (not m_socket.set_blocking(false)) |
45 std::stringstream errors; |
45 { |
46 if (not m_socket.set_blocking(false, errors)) |
46 fprintf(stderr, "unable to set socket as non-blocking: %s\n", |
47 { |
47 m_socket.error_string().chars()); |
48 fprintf(stderr, "unable to set socket as non-blocking: %s\n", errors.str().data()); |
48 exit(EXIT_FAILURE); |
49 exit(EXIT_FAILURE); |
49 } |
50 } |
50 } |
51 } |
51 |
52 |
52 // ------------------------------------------------------------------------------------------------- |
53 // ------------------------------------------------------------------------------------------------- |
53 // |
54 // |
54 RCONSession::~RCONSession() {} |
55 RCONSession::~RCONSession() {} |
55 |
56 |
56 // ------------------------------------------------------------------------------------------------- |
57 // ------------------------------------------------------------------------------------------------- |
57 // |
58 // |
58 void RCONSession::connect(IPAddress address) |
59 void RCONSession::connect(net::ip_address address) |
59 { |
60 { |
60 m_address = address; |
61 m_address = address; |
61 m_state = RCON_CONNECTING; |
62 m_state = RCON_CONNECTING; |
62 m_interface->updateStatusBar(); |
63 m_interface->updateStatusBar(); |
63 sendHello(); |
64 sendHello(); |
77 m_state = RCON_DISCONNECTED; |
78 m_state = RCON_DISCONNECTED; |
78 } |
79 } |
79 |
80 |
80 // ------------------------------------------------------------------------------------------------- |
81 // ------------------------------------------------------------------------------------------------- |
81 // |
82 // |
82 void RCONSession::send(const ByteArray& packet) |
83 bool RCONSession::send(const std::vector<unsigned char>& packet) |
83 { |
84 { |
84 m_socket.send(m_address, packet); |
85 std::stringstream errors; |
|
86 const bool result = m_socket.send(m_address, packet, errors); |
|
87 if (not result) |
|
88 { |
|
89 this->m_interface->printError("Network error: %s\n", errors.str().data()); |
|
90 } |
|
91 return result; |
85 } |
92 } |
86 |
93 |
87 // ------------------------------------------------------------------------------------------------- |
94 // ------------------------------------------------------------------------------------------------- |
88 // |
95 // |
89 void RCONSession::tick() |
96 void RCONSession::tick() |
110 bumpLastPing(); |
117 bumpLastPing(); |
111 } |
118 } |
112 } |
119 } |
113 |
120 |
114 // Check for new packets in our socket |
121 // Check for new packets in our socket |
115 for (Datagram datagram; m_socket.read(datagram);) |
122 std::stringstream errors; |
116 { |
123 for (net::Datagram datagram; m_socket.read(datagram, errors);) |
|
124 { |
|
125 if (errors.tellp() > 0) |
|
126 { |
|
127 m_interface->printError("Network error: %s\n", errors.str().data()); |
|
128 errors = {}; |
|
129 } |
117 // Only process packets that originate from the game server. |
130 // Only process packets that originate from the game server. |
118 if (datagram.address == m_address) |
131 if (datagram.address == m_address) |
119 handlePacket(datagram.message); |
132 handlePacket(datagram.message); |
120 } |
133 } |
121 } |
134 } |
122 |
135 |
123 // ------------------------------------------------------------------------------------------------- |
136 // ------------------------------------------------------------------------------------------------- |
124 // |
137 // |
125 void RCONSession::handlePacket(ByteArray& message) |
138 void RCONSession::handlePacket(std::vector<unsigned char>& message) |
126 { |
139 { |
127 Bytestream stream(message); |
140 Bytestream stream(message); |
128 |
141 |
129 try |
142 try |
130 { |
143 { |
195 |
208 |
196 case SVRC_TOOMANYTABCOMPLETES: |
209 case SVRC_TOOMANYTABCOMPLETES: |
197 { |
210 { |
198 unsigned int numCompletions = stream.readShort(); |
211 unsigned int numCompletions = stream.readShort(); |
199 m_interface->print("%d completions for '%s'.\n", |
212 m_interface->print("%d completions for '%s'.\n", |
200 int(numCompletions), m_lastTabComplete.chars()); |
213 int(numCompletions), m_lastTabComplete.data()); |
201 } |
214 } |
202 break; |
215 break; |
203 |
216 |
204 case SVRC_TABCOMPLETE: |
217 case SVRC_TABCOMPLETE: |
205 { |
218 { |
206 StringList completes; |
219 std::vector<std::string> completes; |
207 completes.resize(stream.readByte()); |
220 completes.resize(stream.readByte()); |
208 |
221 |
209 for (String& completion : completes) |
222 for (std::string& completion : completes) |
210 completion = stream.readString(); |
223 completion = stream.readString(); |
211 |
224 |
212 if (completes.size() == 1) |
225 if (completes.size() == 1) |
213 { |
226 { |
214 m_interface->tabComplete(m_lastTabComplete, completes[0]); |
227 m_interface->tabComplete(m_lastTabComplete, completes[0]); |
215 } |
228 } |
216 else if (not completes.is_empty()) |
229 else if (completes.size() > 0) |
217 { |
230 { |
218 m_interface->print("Completions for '%s':\n", m_lastTabComplete.chars()); |
231 m_interface->print("Completions for '%s':\n", m_lastTabComplete.data()); |
219 |
232 |
220 for (int i : range(0, completes.size(), 8)) |
233 for (std::size_t i = 0; i < completes.size(); i += 8) |
221 { |
234 { |
222 Range<int> spliceRange(i, min(i + 8, completes.size())); |
235 const int end = min(i + 8, completes.size()); |
223 StringList splice(completes.splice(spliceRange)); |
236 std::vector<std::string> splices = splice(completes, i, end); |
224 m_interface->print("- %s\n", splice.join(", ").chars()); |
237 m_interface->print("- %s\n", join_string_list(splices, ", ").data()); |
225 } |
238 } |
226 } |
239 } |
227 } |
240 } |
228 break; |
241 break; |
229 |
242 |
230 case SVRC_WATCHINGCVAR: |
243 case SVRC_WATCHINGCVAR: |
231 m_interface->print ("You are now watching %s\n", stream.readString().chars()); |
244 m_interface->print ("You are now watching %s\n", stream.readString().data()); |
232 m_interface->print ("Its value is: %s\n", stream.readString().chars()); |
245 m_interface->print ("Its value is: %s\n", stream.readString().data()); |
233 break; |
246 break; |
234 |
247 |
235 case SVRC_ALREADYWATCHINGCVAR: |
248 case SVRC_ALREADYWATCHINGCVAR: |
236 m_interface->print ("You are already watching %s\n", stream.readString().chars()); |
249 m_interface->print ("You are already watching %s\n", stream.readString().data()); |
237 break; |
250 break; |
238 |
251 |
239 case SVRC_WATCHCVARNOTFOUND: |
252 case SVRC_WATCHCVARNOTFOUND: |
240 m_interface->print ("CVar %s not found\n", stream.readString().chars()); |
253 m_interface->print ("CVar %s not found\n", stream.readString().data()); |
241 break; |
254 break; |
242 |
255 |
243 case SVRC_CVARCHANGED: |
256 case SVRC_CVARCHANGED: |
244 { |
257 { |
245 String name = stream.readString(); |
258 String name = stream.readString(); |
246 String value = stream.readString(); |
259 String value = stream.readString(); |
247 m_interface->print ("The value of CVar %s", name.chars()); |
260 m_interface->print ("The value of CVar %s", name.data()); |
248 m_interface->print (" is now %s\n", value.chars()); |
261 m_interface->print (" is now %s\n", value.data()); |
249 |
262 |
250 // If sv_hostname changes, update the titlebar |
263 // If sv_hostname changes, update the titlebar |
251 if (name == "sv_hostname") |
264 if (name == "sv_hostname") |
252 { |
265 { |
253 m_hostname = value; |
266 m_hostname = value; |
255 } |
268 } |
256 } |
269 } |
257 break; |
270 break; |
258 |
271 |
259 case SVRC_YOUREDISCONNECTED: |
272 case SVRC_YOUREDISCONNECTED: |
260 m_interface->print ("You have been disconnected: %s\n", stream.readString().chars()); |
273 m_interface->print ("You have been disconnected: %s\n", stream.readString().data()); |
261 m_interface->disconnected(); |
274 m_interface->disconnected(); |
262 break; |
275 break; |
263 } |
276 } |
264 } |
277 } |
265 } |
278 } |
266 catch (std::exception& e) |
279 catch (std::exception& e) |
267 { |
280 { |
268 m_interface->printWarning("Couldn't process packet: %s\n", e.what()); |
281 m_interface->printWarning("Couldn't process packet: %s\n", e.what()); |
269 m_interface->printWarning("Packet contents was: %s\n", message.quote().chars()); |
282 m_interface->printWarning("Packet contents was: %s\n", quote(message).data()); |
270 m_interface->printWarning("Stream position in payload was: %d\n", stream.position()); |
283 m_interface->printWarning("Stream position in payload was: %d\n", stream.position()); |
271 } |
284 } |
272 } |
285 } |
273 |
286 |
274 void RCONSession::processServerUpdates(Bytestream& packet) |
287 void RCONSession::processServerUpdates(Bytestream& packet) |
304 } |
317 } |
305 } |
318 } |
306 |
319 |
307 // ------------------------------------------------------------------------------------------------- |
320 // ------------------------------------------------------------------------------------------------- |
308 // |
321 // |
309 UDPSocket* RCONSession::getSocket() |
322 net::UDPSocket* RCONSession::getSocket() |
310 { |
323 { |
311 return &m_socket; |
324 return &m_socket; |
312 } |
325 } |
313 |
326 |
314 // ------------------------------------------------------------------------------------------------- |
327 // ------------------------------------------------------------------------------------------------- |
315 // |
328 // |
316 void RCONSession::sendHello() |
329 void RCONSession::sendHello() |
317 { |
330 { |
318 m_interface->print("Connecting to %s...\n", m_address.to_string(IPAddress::WITH_PORT).chars()); |
331 m_interface->print("Connecting to %s...\n", net::ip_address_to_string(m_address).data()); |
319 send({CLRC_BEGINCONNECTION, RCON_PROTOCOL_VERSION}); |
332 send({CLRC_BEGINCONNECTION, RCON_PROTOCOL_VERSION}); |
320 bumpLastPing(); |
333 bumpLastPing(); |
321 } |
334 } |
322 |
335 |
323 // ------------------------------------------------------------------------------------------------- |
336 // ------------------------------------------------------------------------------------------------- |
324 // |
337 // |
325 void RCONSession::sendPassword() |
338 void RCONSession::sendPassword() |
326 { |
339 { |
327 m_interface->print("Authenticating...\n"); |
340 m_interface->print("Authenticating...\n"); |
328 ByteArray message; |
341 std::vector<unsigned char> message; |
329 Bytestream stream(message); |
342 Bytestream stream(message); |
330 stream.writeByte(CLRC_PASSWORD); |
343 stream.writeByte(CLRC_PASSWORD); |
331 stream.writeString((m_salt + m_password).md5()); |
344 stream.writeString(md5((m_salt + m_password).data())); |
332 send(message); |
345 send(message); |
333 bumpLastPing(); |
346 bumpLastPing(); |
334 } |
347 } |
335 |
348 |
336 // ------------------------------------------------------------------------------------------------- |
349 // ------------------------------------------------------------------------------------------------- |
337 // |
350 // |
338 void RCONSession::setPassword(const String& password) |
351 void RCONSession::setPassword(const std::string& password) |
339 { |
352 { |
340 m_password = password; |
353 m_password = password; |
341 } |
354 } |
342 |
355 |
343 // ------------------------------------------------------------------------------------------------- |
356 // ------------------------------------------------------------------------------------------------- |
357 } |
370 } |
358 |
371 |
359 // ------------------------------------------------------------------------------------------------- |
372 // ------------------------------------------------------------------------------------------------- |
360 // Returns true if the message was successfully sent. |
373 // Returns true if the message was successfully sent. |
361 // |
374 // |
362 bool RCONSession::sendCommand(const String& commandString) |
375 bool RCONSession::sendCommand(const std::string& commandString) |
363 { |
376 { |
364 if (m_state != RCON_CONNECTED or commandString.isEmpty()) |
377 if (m_state != RCON_CONNECTED or commandString.empty()) |
365 return false; |
378 return false; |
366 |
379 |
367 ByteArray message; |
380 std::vector<unsigned char> message; |
368 Bytestream stream(message); |
381 Bytestream stream(message); |
369 stream.writeByte(CLRC_COMMAND); |
382 stream.writeByte(CLRC_COMMAND); |
370 stream.writeString(commandString); |
383 stream.writeString(commandString); |
371 send(message); |
384 send(message); |
372 bumpLastPing(); |
385 bumpLastPing(); |
394 return m_adminCount; |
407 return m_adminCount; |
395 } |
408 } |
396 |
409 |
397 // ------------------------------------------------------------------------------------------------- |
410 // ------------------------------------------------------------------------------------------------- |
398 // |
411 // |
399 const String& RCONSession::getLevel() const |
412 const std::string& RCONSession::getLevel() const |
400 { |
413 { |
401 return m_level; |
414 return m_level; |
402 } |
415 } |
403 |
416 |
404 // ------------------------------------------------------------------------------------------------- |
417 // ------------------------------------------------------------------------------------------------- |
405 // |
418 // |
406 void RCONSession::requestTabCompletion(const String& part) |
419 void RCONSession::requestTabCompletion(const std::string& part) |
407 { |
420 { |
408 if (m_serverProtocol >= 4) |
421 if (m_serverProtocol >= 4) |
409 { |
422 { |
410 ByteArray message; |
423 std::vector<unsigned char> message; |
411 Bytestream stream(message); |
424 Bytestream stream(message); |
412 stream.writeByte(CLRC_TABCOMPLETE); |
425 stream.writeByte(CLRC_TABCOMPLETE); |
413 stream.writeString(part); |
426 stream.writeString(part); |
414 send(message); |
427 send(message); |
415 bumpLastPing(); |
428 bumpLastPing(); |
416 m_lastTabComplete = part; |
429 m_lastTabComplete = part; |
417 } |
430 } |
418 else |
431 else |
419 { |
432 { |
420 m_interface->print("This server does not support tab-completion\n", m_serverProtocol); |
433 m_interface->print("This server does not support tab-completion\n"); |
421 } |
434 } |
422 } |
435 } |
423 |
436 |
424 // ------------------------------------------------------------------------------------------------- |
437 // ------------------------------------------------------------------------------------------------- |
425 // |
438 // |
430 |
443 |
431 // ------------------------------------------------------------------------------------------------- |
444 // ------------------------------------------------------------------------------------------------- |
432 // |
445 // |
433 void RCONSession::requestWatch(const String& cvar) |
446 void RCONSession::requestWatch(const String& cvar) |
434 { |
447 { |
435 StringList cvars; |
448 const std::vector<std::string> cvars{{cvar}}; |
436 cvars.append(cvar); |
|
437 requestWatch(cvars); |
449 requestWatch(cvars); |
438 } |
450 } |
439 |
451 |
440 // ------------------------------------------------------------------------------------------------- |
452 // ------------------------------------------------------------------------------------------------- |
441 // |
453 // |
442 void RCONSession::requestWatch(const StringList& cvars) |
454 void RCONSession::requestWatch(const std::vector<std::string>& cvars) |
443 { |
455 { |
444 ByteArray message; |
456 std::vector<unsigned char> message; |
445 Bytestream stream(message); |
457 Bytestream stream(message); |
446 stream.writeByte(CLRC_WATCHCVAR); |
458 stream.writeByte(CLRC_WATCHCVAR); |
447 |
459 for (String cvar : cvars) |
448 for (const String& cvar : cvars) |
460 { |
449 stream.writeString(cvar.normalized()); |
461 normalize(cvar); |
450 |
462 stream.writeString(cvar); |
|
463 } |
451 stream.writeString(""); |
464 stream.writeString(""); |
452 send(message); |
465 send(message); |
453 } |
466 } |
454 |
467 |
455 END_ZFC_NAMESPACE |
468 END_ZFC_NAMESPACE |