1 #include "online_service.h"
2 #include "results.h"
3
4 #ifdef USE_WIN32
5 #include <winsock2.h>
6 #else
7 #include <arpa/inet.h>
8 #endif
9 #include <clocale>
10 #include <cstdio>
11 #include <cstdlib>
12 #include <cstring>
13 #include <ctime>
14 #include <ios>
15 #include <iostream>
16 #include <map>
17 #include <ostream>
18 #include <queue>
19 #include <set>
20 #include <sstream>
21 #include <stdexcept>
22 #include <string>
23 #include <tuple>
24 #ifndef USE_WIN32
25 #include <unistd.h>
26 #endif
27 #include <vector>
28
29 #ifdef USE_WIN32
30 #include <winsock2.h>
31 #else
32 #include <netinet/in.h>
33 #endif
34
35 #include "stratagus.h"
36 #include "script.h"
37 #include "map.h"
38 #include "netconnect.h"
39 #include "script.h"
40 #include "settings.h"
41 #include "tileset.h"
42 #include "cursor.h"
43 #include "font.h"
44 #include "input.h"
45 #include "stratagus.h"
46 #include "ui.h"
47 #include "video.h"
48 #include "widgets.h"
49 #include "game.h"
50 #include "parameters.h"
51 #include "assert.h"
52 #include "network.h"
53 #include "network/netsockets.h"
54 #include "util.h"
55 #include "version.h"
56
57 #include "./xsha1.h"
58
dump(uint8_t * buffer,int received_bytes)59 static void dump(uint8_t* buffer, int received_bytes) {
60 std::cout << "Raw contents >>>" << std::endl;
61 for (int i = 0; i < received_bytes; i += 8) {
62 std::cout << std::hex;
63 int j = i;
64 for (; j < received_bytes && j < (i + 8); j++) {
65 uint8_t byte = buffer[j];
66 if (byte < 0x10) {
67 std::cout << "0";
68 }
69 std::cout << (unsigned short)byte << " ";
70 }
71 for (; j < (i + 9); j++) {
72 std::cout << " "; // room for 2 hex digits and one space
73 }
74 j = i;
75 for (; j < received_bytes && j < (i + 8); j++) {
76 char c = buffer[j];
77 if (c >= 32) {
78 std::cout.write(&c, 1);
79 } else {
80 std::cout.write(".", 1);
81 }
82 }
83 std::cout << std::endl;
84 }
85 std::cout << "<<<" << std::endl;
86 }
87
88 class BNCSInputStream {
89 public:
BNCSInputStream(CTCPSocket * socket)90 BNCSInputStream(CTCPSocket *socket) {
91 this->sock = socket;
92 this->bufsize = 1024;
93 this->buffer = (char*)calloc(sizeof(char), bufsize);
94 this->received_bytes = 0;
95 this->pos = 0;
96 };
~BNCSInputStream()97 ~BNCSInputStream() {};
98
readString()99 std::string readString() {
100 if (received_bytes - pos <= 0) {
101 return "";
102 }
103 std::stringstream strstr;
104 int i = pos;
105 char c;
106 while ((c = buffer[i]) != '\0' && i < received_bytes) {
107 strstr.put(c);
108 i += 1;
109 }
110 consumeData(i + 1 - pos);
111 return strstr.str();
112 };
113
readStringlist()114 std::vector<std::string> readStringlist() {
115 std::vector<std::string> stringlist;
116 while (true) {
117 std::string nxt = readString();
118 if (nxt.empty()) {
119 break;
120 } else {
121 stringlist.push_back(nxt);
122 }
123 }
124 return stringlist;
125 };
126
readStringlist(int cnt)127 std::vector<std::string> readStringlist(int cnt) {
128 std::vector<std::string> stringlist;
129 for (; cnt >= 0; cnt--) {
130 std::string nxt = readString();
131 if (nxt.empty()) {
132 break;
133 } else {
134 stringlist.push_back(nxt);
135 }
136 }
137 return stringlist;
138 };
139
read8()140 uint8_t read8() {
141 if (pos > received_bytes) {
142 return 0;
143 }
144 uint8_t byte = buffer[pos];
145 consumeData(1);
146 return byte;
147 }
148
read16()149 uint16_t read16() {
150 if (pos + 1 > received_bytes) {
151 return 0;
152 }
153 uint16_t byte = ntohs(reinterpret_cast<uint16_t *>(buffer + pos)[0]);
154 consumeData(2);
155 return ntohs(byte);
156 }
157
read32()158 uint32_t read32() {
159 if (pos + 3 > received_bytes) {
160 return 0;
161 }
162 uint32_t byte = ntohl(reinterpret_cast<uint32_t *>(buffer + pos)[0]);
163 consumeData(4);
164 return ntohl(byte);
165 }
166
read64()167 uint64_t read64() {
168 if (pos + 7 > received_bytes) {
169 return 0;
170 }
171 uint64_t byte = reinterpret_cast<uint64_t *>(buffer + pos)[0];
172 consumeData(8);
173 return ntohl(byte & (uint32_t)-1) | ntohl(byte >> 32);
174 }
175
readBool8()176 bool readBool8() {
177 return read8() != 0;
178 }
179
readBool32()180 bool readBool32() {
181 return read32() != 0;
182 }
183
readFiletime()184 uint64_t readFiletime() {
185 return read64();
186 }
187
188 /**
189 * For debugging and development: read the entire thing as a char
190 * array. Caller must "free" the out-char
191 */
readAll(char ** out)192 int readAll(char** out) {
193 *out = (char*)calloc(received_bytes, sizeof(char));
194 strncpy(*out, buffer, received_bytes);
195 return received_bytes;
196 }
197
string32()198 std::string string32() {
199 // uint32 encoded (4-byte) string
200 uint32_t data = read32();
201 char dt[5];
202 strncpy(dt, (const char*)&data, 4);
203 dt[4] = '\0';
204 return std::string(dt);
205 };
206
207 /**
208 * To be called at the start of a message, gets the entire data into memory or returns -1.
209 */
readMessageId()210 uint8_t readMessageId() {
211 // Every BNCS message has the same header:
212 // (UINT8) Always 0xFF
213 // (UINT8) Message ID
214 // (UINT16) Message length, including this header
215 // (VOID) Message data
216 if (received_bytes < 4) {
217 // in case of retry, we may already have the first 4 bytes
218 received_bytes += this->sock->Recv(buffer + received_bytes, 4 - received_bytes);
219 }
220 if (received_bytes < 4) {
221 // didn't get the complete header yet
222 return -1;
223 }
224 if (pos != 0) {
225 debugDump();
226 finishMessage();
227 return -1;
228 }
229 uint8_t headerbyte = read8();
230 if (headerbyte != 0xff) {
231 // Likely a bug on our side. We just skip this byte.
232 debugDump();
233 memmove(buffer, buffer + 1, received_bytes - 1);
234 received_bytes -= 1;
235 pos = 0;
236 return -1;
237 }
238 uint8_t msgId = read8();
239 uint16_t len = read16();
240 // we still need to have len in total for this message, so if we have
241 // more available than len minus the current position and minus the
242 // first 4 bytes that we already consumed, we'll have enough
243 assert(pos == 4);
244 long needed = len - received_bytes;
245 if (needed > 0) {
246 if (needed >= bufsize + received_bytes) {
247 // we never shrink the buffer again while we're online
248 buffer = (char*)realloc(buffer, sizeof(char) * len + 1);
249 bufsize = len + 1;
250 }
251 received_bytes += this->sock->Recv(buffer + received_bytes, needed);
252 if (received_bytes < len) {
253 // Didn't receive full message on the socket, yet. Reset position so
254 // this method can be used to try again
255 pos = 0;
256 return -1;
257 }
258 }
259 return msgId;
260 };
261
debugDump()262 void debugDump() {
263 if (EnableDebugPrint) {
264 std::cout << "Input stream state: pos(" << pos << "), received_bytes(" << received_bytes << ")" << std::endl;
265 dump((uint8_t*)buffer, received_bytes);
266 }
267 }
268
finishMessage()269 void finishMessage() {
270 assert(pos <= received_bytes);
271 received_bytes = received_bytes - pos;
272 if (received_bytes > 0) {
273 // move the remaining received bytes to the start of the buffer, to
274 // be used for the next message
275 memmove(buffer, buffer + pos, received_bytes);
276 }
277 pos = 0;
278 }
279
280 private:
consumeData(int bytes)281 void consumeData(int bytes) {
282 if (pos + bytes > received_bytes) {
283 // XXX: This is probably a symptom of missing error handling
284 // somewhere else, but we'll try just not to crash here
285 return;
286 }
287 pos += bytes;
288 }
289
290 CTCPSocket *sock;
291 char *buffer;
292 int received_bytes;
293 int pos;
294 int bufsize;
295 };
296
297 class BNCSOutputStream {
298 public:
BNCSOutputStream(uint8_t id,bool udp=false)299 BNCSOutputStream(uint8_t id, bool udp = false) {
300 // Every BNCS message has the same header:
301 // (UINT8) Always 0xFF
302 // (UINT8) Message ID
303 // (UINT16) Message length, including this header
304 // (VOID) Message data
305 // The UDP messages are instead 4 bytes for the id, then the content
306 this->pos = 0;
307 if (udp) {
308 serialize8(id);
309 serialize8(0);
310 this->length_pos = -1;
311 } else {
312 serialize8(0xff);
313 serialize8(id);
314 this->length_pos = pos;
315 }
316 serialize16((uint16_t)0);
317 };
318
~BNCSOutputStream()319 ~BNCSOutputStream() {
320 };
321
serialize32(uint32_t data)322 void serialize32(uint32_t data) {
323 ensureSpace(sizeof(data));
324 uint32_t *view = reinterpret_cast<uint32_t *>(buf + pos);
325 *view = htonl(data);
326 pos += sizeof(data);
327 };
serialize32NativeByteOrder(uint32_t data)328 void serialize32NativeByteOrder(uint32_t data) {
329 ensureSpace(sizeof(data));
330 uint32_t *view = reinterpret_cast<uint32_t *>(buf + pos);
331 *view = data;
332 pos += sizeof(data);
333 };
serialize16(uint16_t data)334 void serialize16(uint16_t data) {
335 ensureSpace(sizeof(data));
336 uint16_t *view = reinterpret_cast<uint16_t *>(buf + pos);
337 *view = htons(data);
338 pos += sizeof(data);
339 };
serialize8(uint8_t data)340 void serialize8(uint8_t data) {
341 ensureSpace(sizeof(data));
342 *(buf + pos) = data;
343 pos++;
344 };
serializeC32(const char * str)345 void serializeC32(const char* str) {
346 assert(strlen(str) == 4);
347 uint32_t value;
348 memcpy(&value, str, 4);
349 serialize32(value);
350 };
serialize(const char * str)351 void serialize(const char* str) {
352 int len = strlen(str) + 1; // include NULL byte
353 ensureSpace(len);
354 memcpy(buf + pos, str, len);
355 pos += len;
356 };
357
flush(CTCPSocket * sock)358 int flush(CTCPSocket *sock) {
359 return sock->Send(getBuffer(), pos);
360 };
361
flush(CUDPSocket * sock,CHost * host)362 void flush(CUDPSocket *sock, CHost *host) {
363 if (sock->IsValid()) {
364 sock->Send(*host, getBuffer(), pos);
365 }
366 };
367
368 private:
getBuffer()369 uint8_t *getBuffer() {
370 // if needed, insert length to make it a valid buffer
371 if (length_pos >= 0) {
372 uint16_t *view = reinterpret_cast<uint16_t *>(buf + length_pos);
373 *view = pos;
374 }
375 return buf;
376 };
377
ensureSpace(size_t required)378 void ensureSpace(size_t required) {
379 assert(pos + required < MAX_MSG_SIZE);
380 }
381
382 static const uint32_t MAX_MSG_SIZE = 1024; // 1kb for an outgoing message should be plenty
383 uint8_t buf[MAX_MSG_SIZE];
384 unsigned int pos = 0;
385 int length_pos;
386 };
387
gameName()388 static std::string gameName() {
389 if (!FullGameName.empty()) {
390 return FullGameName;
391 } else {
392 if (!GameName.empty()) {
393 return GameName;
394 } else {
395 return Parameters::Instance.applicationName;
396 }
397 }
398 }
399
400 class Friend {
401 public:
Friend(std::string name,uint8_t status,uint8_t location,uint32_t product,std::string locationName)402 Friend(std::string name, uint8_t status, uint8_t location, uint32_t product, std::string locationName) {
403 this->name = name;
404 this->status = status;
405 this->location = location;
406 this->product = product;
407 this->locationName = locationName;
408 }
409
getStatus()410 std::string getStatus() {
411 switch (location) {
412 case 0:
413 return "offline";
414 case 1:
415 return "not in chat";
416 case 2:
417 return "in chat";
418 case 3:
419 return "in public game " + locationName;
420 case 4:
421 return "in a private game";
422 case 5:
423 return "in private game " + locationName;
424 default:
425 return "unknown";
426 }
427 }
428
getName()429 std::string getName() { return name; }
430
getProduct()431 std::string getProduct() {
432 switch (product) {
433 case 0x1:
434 case 0x2:
435 case 0x6:
436 case 0xb:
437 return "Starcraft";
438 case 0x3:
439 return "Warcraft II";
440 case 0x4:
441 case 0x5:
442 return "Diablo II";
443 case 0x7:
444 case 0x8:
445 case 0xc:
446 return "Warcraft III";
447 case 0x9:
448 case 0xa:
449 return "Diablo";
450 default:
451 return "Unknown Game";
452 }
453 }
454
455 private:
456 std::string name;
457 uint8_t status;
458 uint8_t location;
459 uint32_t product;
460 std::string locationName;
461 };
462
463 class Game {
464 public:
Game(uint32_t settings,uint16_t port,uint32_t host,uint32_t status,uint32_t time,std::string name,std::string pw,std::string stats)465 Game(uint32_t settings, uint16_t port, uint32_t host, uint32_t status, uint32_t time, std::string name, std::string pw, std::string stats) {
466 this->gameSettings = settings;
467 this->host = CHost(host, port);
468 this->gameStatus = status;
469 this->elapsedTime = time;
470 this->gameName = name;
471 this->gamePassword = pw;
472 this->gameStatstring = stats;
473 splitStatstring();
474 }
475
getHost()476 CHost getHost() { return host; }
477
isSavedGame()478 bool isSavedGame() {
479 return !gameStats[0].empty();
480 };
481
mapSize()482 std::tuple<int, int> mapSize() {
483 if (gameStats[1].empty()) {
484 return {128, 128};
485 }
486 char w = gameStats[1].at(0);
487 char h = gameStats[1].at(1);
488 return {w * 32, h * 32};
489 };
490
maxPlayers()491 int maxPlayers() {
492 if (gameStats[2].empty()) {
493 return 8;
494 } else {
495 return std::stoi(gameStats[2]) - 10;
496 }
497 };
498
getSpeed()499 int getSpeed() {
500 return std::stol(gameStats[3]);
501 }
502
getApproval()503 std::string getApproval() {
504 if (std::stol(gameStats[4]) == 0) {
505 return "Not approved";
506 } else {
507 return "Approved";
508 }
509 }
510
getGameSettings()511 std::string getGameSettings() {
512 if (gameStats[8].empty()) {
513 return "Map default";
514 }
515 long settings = std::stol(gameStats[8]);
516 std::string result;
517 if (settings & 0x200) {
518 result += " 1 worker";
519 }
520 if (settings & 0x400) {
521 result += " fixed placement";
522 }
523 switch (settings & 0x23000) {
524 case 0x01000:
525 result += " low resources";
526 break;
527 case 0x02000:
528 result += " medium resources";
529 break;
530 case 0x03000:
531 result += " high resources";
532 break;
533 case 0x20000:
534 result += " random resources";
535 break;
536 }
537 switch (settings & 0x1C000) {
538 case 0x04000:
539 result += " forest";
540 break;
541 case 0x08000:
542 result += " winter";
543 break;
544 case 0x0c000:
545 result += " wasteland";
546 break;
547 case 0x14000:
548 result += " random";
549 break;
550 case 0x1c000:
551 result += " orc swamp";
552 break;
553 }
554 return result;
555 };
556
getCreator()557 std::string getCreator() {
558 return gameStats[9];
559 };
560
getMap()561 std::string getMap() {
562 return gameStats[10];
563 };
564
getGameStatus()565 std::string getGameStatus() {
566 switch (gameStatus) {
567 case 0x0:
568 return "OK";
569 case 0x1:
570 return "No such game";
571 case 0x2:
572 return "Wrong password";
573 case 0x3:
574 return "Game full";
575 case 0x4:
576 return "Game already started";
577 case 0x5:
578 return "Spawned CD-Key not allowed";
579 case 0x6:
580 return "Too many server requests";
581 }
582 return "Unknown status";
583 }
584
getGameType()585 std::string getGameType() {
586 std::string sub("");
587 switch (gameSettings & 0xff) {
588 case 0x02:
589 return "Melee";
590 case 0x03:
591 return "Free 4 All";
592 case 0x04:
593 return "One vs One";
594 case 0x05:
595 return "CTF";
596 case 0x06:
597 switch (gameSettings & 0xffff0000) {
598 case 0x00010000:
599 return "Greed 2500 resources";
600 case 0x00020000:
601 return "Greed 5000 resources";
602 case 0x00030000:
603 return "Greed 7500 resources";
604 case 0x00040000:
605 return "Greed 10000 resources";
606 }
607 return "Greed";
608 case 0x07:
609 switch (gameSettings & 0xffff0000) {
610 case 0x00010000:
611 return "Slaughter 15 minutes";
612 case 0x00020000:
613 return "Slaughter 30 minutes";
614 case 0x00030000:
615 return "Slaughter 45 minutes";
616 case 0x00040000:
617 return "Slaughter 60 minutes";
618 }
619 return "Slaughter";
620 case 0x08:
621 return "Sudden Death";
622 case 0x09:
623 switch (gameSettings & 0xffff0000) {
624 case 0x00000000:
625 return "Ladder (Disconnect is not loss)";
626 case 0x00010000:
627 return "Ladder (Loss on Disconnect)";
628 }
629 return "Ladder";
630 case 0x0A:
631 return "Use Map Settings";
632 case 0x0B:
633 case 0x0C:
634 case 0x0D:
635 switch (gameSettings & 0xffff0000) {
636 case 0x00010000:
637 sub += " (2 teams)";
638 break;
639 case 0x00020000:
640 sub += " (3 teams)";
641 break;
642 case 0x00030000:
643 sub += " (4 teams)";
644 break;
645 }
646 switch (gameSettings & 0xff) {
647 case 0x0B:
648 return "Team Melee" + sub;
649 case 0x0C:
650 return "Team Free 4 All" + sub;
651 case 0x0D:
652 return "Team CTF" + sub;
653 }
654 case 0x0F:
655 switch (gameSettings & 0xffff0000) {
656 case 0x00010000:
657 return "Top vs Bottom (1v7)";
658 case 0x00020000:
659 return "Top vs Bottom (2v6)";
660 case 0x00030000:
661 return "Top vs Bottom (3v5)";
662 case 0x00040000:
663 return "Top vs Bottom (4v4)";
664 case 0x00050000:
665 return "Top vs Bottom (5v3)";
666 case 0x00060000:
667 return "Top vs Bottom (6v2)";
668 case 0x00070000:
669 return "Top vs Bottom (7v1)";
670 }
671 return "Top vs Bottom";
672 case 0x10:
673 return "Iron Man Ladder";
674 default:
675 return gameStats[5]; // just the code
676 }
677 }
678
679 private:
splitStatstring()680 void splitStatstring() {
681 // statstring is a comma-delimited list of values
682 int pos = 0;
683 int newpos = 0;
684 char sep[2] = {',', '\0'};
685 while (true) {
686 newpos = gameStatstring.find(sep, pos);
687 if (newpos < pos && sep[0] == ',') {
688 sep[0] = '\r';
689 continue;
690 } else if (newpos < pos) {
691 break;
692 } else {
693 gameStats.push_back(gameStatstring.substr(pos, newpos - pos));
694 }
695 pos = newpos + 1;
696 }
697 while (gameStats.size() < 10) {
698 gameStats.push_back("");
699 }
700 }
701
702 uint32_t gameSettings;
703 uint32_t languageId;
704 CHost host;
705 uint32_t gameStatus;
706 uint32_t elapsedTime;
707 std::string gameName;
708 std::string gamePassword;
709 std::string gameStatstring;
710 std::vector<std::string> gameStats;
711 };
712
713 class Context;
714 class OnlineState {
715 public:
~OnlineState()716 virtual ~OnlineState() {};
717 virtual void doOneStep(Context *ctx) = 0;
718
719 protected:
720 int send(Context *ctx, BNCSOutputStream *buf);
721 };
722
723 class Context : public OnlineContext {
724 public:
Context()725 Context() {
726 this->tcpSocket = new CTCPSocket();
727 this->istream = new BNCSInputStream(tcpSocket);
728 this->state = NULL;
729 this->host = new CHost("127.0.0.1", 6112);
730 this->clientToken = MyRand();
731 this->username = "";
732 setPassword("");
733 defaultUserKeys.push_back("profile\\location");
734 defaultUserKeys.push_back("profile\\description");
735 defaultUserKeys.push_back("record\\GAME\\0\\wins");
736 defaultUserKeys.push_back("record\\GAME\\0\\losses");
737 defaultUserKeys.push_back("record\\GAME\\0\\disconnects");
738 defaultUserKeys.push_back("record\\GAME\\0\\last game");
739 defaultUserKeys.push_back("record\\GAME\\0\\last game result");
740
741 }
742
~Context()743 ~Context() {
744 if (state != NULL) {
745 delete state;
746 }
747 delete tcpSocket;
748 delete host;
749 }
750
isConnected()751 bool isConnected() {
752 return !getCurrentChannel().empty();
753 }
754
isConnecting()755 bool isConnecting() {
756 return !isDisconnected() && !isConnected() && !username.empty() && hasPassword;
757 }
758
isDisconnected()759 bool isDisconnected() {
760 return state == NULL;
761 }
762
763 // User and UI actions
disconnect()764 void disconnect() {
765 if (isConnected()) {
766 // SID_STOPADV: according to bnetdocs.org, this is always sent when
767 // clients disconnect, regardless of state
768 BNCSOutputStream stop(0x02);
769 stop.flush(tcpSocket);
770 // SID_LEAVECHAT
771 BNCSOutputStream leave(0x10);
772 leave.flush(tcpSocket);
773 }
774 if (canDoUdp == 1) {
775 ExitNetwork1();
776 }
777 tcpSocket->Close();
778 state = NULL;
779 clientToken = MyRand();
780 username = "";
781 setPassword("");
782 }
783
sendText(std::string txt,bool silent=false)784 void sendText(std::string txt, bool silent = false) {
785 // C>S 0x0E SID_CHATCOMMAND
786 int pos = 0;
787 for (unsigned int pos = 0; pos < txt.size(); pos += 220) {
788 std::string text = txt.substr(pos, pos + 220);
789 if (pos + 220 < txt.size()) {
790 text += "...";
791 }
792 BNCSOutputStream msg(0x0e);
793 msg.serialize(text.c_str());
794 msg.flush(getTCPSocket());
795 DebugPrint("TCP Sent: 0x0e CHATCOMMAND\n");
796 }
797 if (!silent) {
798 showChat(username + ": " + txt);
799 }
800 }
801
requestExternalAddress()802 void requestExternalAddress() {
803 // start advertising a fake game so we can see our external address in the gamelist
804 if (!requestedAddress) {
805 requestedAddress = true;
806 BNCSOutputStream msg(0x1c);
807 msg.serialize32(0x10000000);
808 msg.serialize32(0x00); // uptime
809 msg.serialize16(0x0300); // game type
810 msg.serialize16(0x0100); // sub game type
811 msg.serialize32(0xff); // provider version constant
812 msg.serialize32(0x00); // not ladder
813 msg.serialize(""); // game name
814 msg.serialize(""); // password
815 std::string statstring = ",,,0x04,0x00,0x0a,0x01,0x1234,0x4000,";
816 statstring += getUsername() + "\rudp\r";
817 msg.serialize(statstring.c_str());
818 msg.flush(getTCPSocket());
819 DebugPrint("TCP Sent: 0x1c STARTADVEX\n");
820 }
821 }
822
requestExtraUserInfo(std::string username)823 void requestExtraUserInfo(std::string username) {
824 BNCSOutputStream msg(0x26);
825 msg.serialize32NativeByteOrder(1); // num accounts
826 msg.serialize32NativeByteOrder(defaultUserKeys.size()); // num keys
827 msg.serialize32NativeByteOrder((uint32_t) extendedInfoNames.size());
828 extendedInfoNames.push_back(username);
829 msg.serialize(username.c_str());
830 for (const auto& key : defaultUserKeys) {
831 msg.serialize(key.c_str());
832 }
833 msg.flush(getTCPSocket());
834 DebugPrint("TCP Sent: 0x26 USERINFO\n");
835 }
836
punchNAT(std::string username)837 void punchNAT(std::string username) {
838 if (externalAddress.isValid()) {
839 sendText("/whisper " + username + " /udppunch " + externalAddress.toString());
840 }
841 }
842
refreshChannels()843 void refreshChannels() {
844 BNCSOutputStream getlist(0x0b);
845 // identify as W2BN
846 getlist.serialize32(0x4f);
847 getlist.flush(getTCPSocket());
848 DebugPrint("TCP Sent: 0x0b CHANNELLIST\n");
849 }
850
refreshGames()851 void refreshGames() {
852 // C>S 0x09 SID_GETADVLISTEX
853 BNCSOutputStream getadvlistex(0x09);
854 getadvlistex.serialize16(0x00); // all games
855 getadvlistex.serialize16(0x01); // no sub game type
856 getadvlistex.serialize32(0xff80); // show all games
857 getadvlistex.serialize32(0x00); // reserved field
858 getadvlistex.serialize32(0xff); // return all games
859 getadvlistex.serialize(""); // no game name
860 getadvlistex.serialize(""); // no game pw
861 getadvlistex.serialize(""); // no game statstring
862 getadvlistex.flush(getTCPSocket());
863 DebugPrint("TCP Sent: 0x09 GAMELIST\n");
864 }
865
refreshFriends()866 void refreshFriends() {
867 // C>S 0x65 SID_FRIENDSLIST
868 BNCSOutputStream msg(0x65);
869 msg.flush(getTCPSocket());
870 DebugPrint("TCP Sent: 0x65 FRIENDSLIST\n");
871 }
872
joinGame(std::string username,std::string pw)873 virtual void joinGame(std::string username, std::string pw) {
874 if (!isConnected()) {
875 return;
876 }
877 // C>S 0x22 SID_NOTIFYJOIN
878 BNCSOutputStream msg(0x09);
879 msg.serializeC32("W2BN");
880 msg.serialize32(0x4f);
881 msg.serialize(gameNameFromUsername(username).c_str());
882 msg.serialize(pw.c_str());
883 msg.flush(getTCPSocket());
884 DebugPrint("TCP Sent: 0x09 NOTIFYJOIN\n");
885 }
886
leaveGame()887 virtual void leaveGame() {
888 // TODO: ?
889 }
890
startAdvertising(bool isStarted=false)891 virtual void startAdvertising(bool isStarted = false) {
892 if (!isConnected()) {
893 return;
894 }
895 BNCSOutputStream msg(0x1c);
896 int maxSlots = 0;
897 for (int i = 0; i < PlayerMax; i++) {
898 if (ServerSetupState.CompOpt[i] == 0) { // available
899 maxSlots++;
900 }
901 }
902 int joinedPlayers = 0;
903 for (int i = 1; i < PlayerMax; i++) { // skip server host
904 if (Hosts[i].PlyNr) {
905 joinedPlayers++;
906 }
907 }
908 uint32_t state = 0x10000000; // disconnect always counts as loss
909 if (joinedPlayers) {
910 state |= 0x04000000; // has players other than creator
911 }
912 if (joinedPlayers + 1 == maxSlots) {
913 state |= 0x02000000; // game is full
914 }
915 if (isStarted) {
916 state |= 0x08000000; // game in progress
917 }
918 msg.serialize32(state);
919 msg.serialize32(0x00); // uptime
920 msg.serialize16(0x0300); // game type - map settings not supported on W2BN, so use FFA
921 msg.serialize16(0x0100); // sub game type
922 msg.serialize32(0xff); // provider version constant
923 msg.serialize32(0x00); // not ladder
924 msg.serialize((gameNameFromUsername(getUsername())).c_str()); // game name
925 msg.serialize(""); // password. TODO: add support
926
927 std::stringstream statstring;
928 statstring << ","; // this game is not saved. TODO: add support
929 int w = Map.Info.MapWidth;
930 int h = Map.Info.MapWidth;
931 if (w == 128 && h == 128) {
932 statstring << ",";
933 } else {
934 statstring << std::dec << w / 32 << h / 32 << ",";
935 }
936 if (maxSlots == 8) {
937 statstring << ",";
938 } else {
939 statstring << std::dec << maxSlots + 10 << ",";
940 }
941 statstring << "0x04,"; // speed - normal (can be changed in-game anyway)
942 statstring << "0x00,"; // not an approved game
943 statstring << "0x0a,"; // game type uses map settings
944 statstring << "0x01,"; // game settings parameter - none
945 statstring << std::hex << FileChecksums << ","; // cd key checksum - we use lua files checksum
946
947 uint32_t game_settings = 0;
948 if (GameSettings.NumUnits == 1) {
949 game_settings |= 0x200;
950 }
951 if (NoRandomPlacementMultiplayer == 1) {
952 game_settings |= 0x400;
953 }
954 switch (GameSettings.Resources) {
955 case -1:
956 break;
957 case 1:
958 game_settings |= 0x1000;
959 break;
960 case 2:
961 game_settings |= 0x2000;
962 break;
963 case 3:
964 game_settings |= 0x3000;
965 break;
966 default:
967 game_settings |= 0x20000;
968 break;
969 }
970 if (Map.Tileset->Name == "Forest") {
971 game_settings |= 0x4000;
972 } else if (Map.Tileset->Name == "Winter") {
973 game_settings |= 0x8000;
974 } else if (Map.Tileset->Name == "Wasteland") {
975 game_settings |= 0xC000;
976 } else if (Map.Tileset->Name == "Orc Swamp") {
977 game_settings |= 0x1C000;
978 } else {
979 game_settings |= 0x4000; // default to forest
980 }
981 statstring << std::hex << game_settings << ",";
982
983 statstring << getUsername();
984 statstring << "\r";
985 if (!Map.Info.Filename.empty()) {
986 statstring << Map.Info.Filename;
987 } else if (!Map.Info.Description.empty()) {
988 statstring << Map.Info.Description;
989 } else {
990 statstring << "unknown map";
991 }
992 statstring << "\r";
993
994 msg.serialize(statstring.str().c_str());
995 msg.flush(getTCPSocket());
996 DebugPrint("TCP Sent: 0x1c STARTADVEX\n");
997 }
998
stopAdvertising()999 virtual void stopAdvertising() {
1000 if (!isConnected()) {
1001 return;
1002 }
1003 BNCSOutputStream msg(0x02);
1004 msg.flush(getTCPSocket());
1005 DebugPrint("TCP Sent: 0x02 STOPADVEX\n");
1006 }
1007
reportGameResult()1008 virtual void reportGameResult() {
1009 BNCSOutputStream msg(0x2c);
1010 msg.serialize32(0); // Normal game
1011 msg.serialize32NativeByteOrder(8); // number of results
1012 for (int i = 0; i < 8; i++) {
1013 if (NetLocalPlayerNumber == i) {
1014 switch (GameResult) {
1015 case GameVictory:
1016 msg.serialize32NativeByteOrder(0x01);
1017 break;
1018 case GameDefeat:
1019 msg.serialize32NativeByteOrder(0x02);
1020 break;
1021 case GameDraw:
1022 msg.serialize32NativeByteOrder(0x03);
1023 break;
1024 default: // disconnect
1025 msg.serialize32NativeByteOrder(0x04);
1026 break;
1027 }
1028 } else {
1029 // it's annoying to tease out the other results, we ignore it and let the server merge
1030 // by sending that the result is unknown/still playing
1031 msg.serialize32NativeByteOrder(0x00);
1032 }
1033 }
1034 for (int i = 0; i < 8; i++) {
1035 msg.serialize(Hosts[i].PlyName);
1036 }
1037 msg.serialize(NetworkMapName.c_str());
1038 msg.serialize(""); // TODO: transmit player scores
1039 msg.flush(getTCPSocket());
1040 DebugPrint("TCP Sent: 0x2c REPORTRESULT\n");
1041 }
1042
joinChannel(std::string name)1043 void joinChannel(std::string name) {
1044 if (isConnected()) {
1045 BNCSOutputStream join(0x0c);
1046 join.serialize32(0x02); // forced join
1047 join.serialize(name.c_str());
1048 join.flush(getTCPSocket());
1049 DebugPrint("TCP Sent: 0x0c JOINCHANNEL\n");
1050 }
1051 }
1052
sendUdpConnectionInfo()1053 void sendUdpConnectionInfo() {
1054 BNCSOutputStream conntest(0x09, true);
1055 conntest.serialize32(serverToken);
1056 conntest.serialize32(udpToken);
1057 conntest.flush(getUDPSocket(), getHost());
1058 DebugPrint("UDP Sent: 0x09 connection info\n");
1059 }
1060
1061 // UI information
setCurrentChannel(std::string name)1062 void setCurrentChannel(std::string name) {
1063 this->currentChannel = name;
1064 bool unlisted = true;
1065 for (const auto& c : channelList) {
1066 if (c == name) {
1067 unlisted = false;
1068 break;
1069 }
1070 }
1071 if (unlisted) {
1072 channelList.push_back(name);
1073 setChannels(channelList);
1074 }
1075 if (SetActiveChannel != NULL) {
1076 SetActiveChannel->pushPreamble();
1077 SetActiveChannel->pushString(name);
1078 SetActiveChannel->run();
1079 }
1080 }
1081
getCurrentChannel()1082 std::string getCurrentChannel() { return currentChannel; }
1083
setGamelist(std::vector<Game * > games)1084 void setGamelist(std::vector<Game*> games) {
1085 // before we are able to join any game, we should try to get our own
1086 // external address to help NAT traversal
1087 for (const auto value : this->games) {
1088 delete value;
1089 }
1090 if (requestedAddress && !externalAddress.isValid()) {
1091 for (unsigned int i = 0; i < games.size(); i++) {
1092 const auto game = games[i];
1093 if (game->getCreator() == getUsername() && game->getMap() == "udp") {
1094 // our fake game, remove and break;
1095 games.erase(games.begin() + i);
1096 externalAddress = game->getHost();
1097 DebugPrint("My external address is %s\n" _C_ externalAddress.toString().c_str());
1098 showInfo("Your external route is " + externalAddress.toString());
1099 stopAdvertising();
1100 break;
1101 }
1102 }
1103 }
1104 this->games = games;
1105 if (SetGames != NULL) {
1106 SetGames->pushPreamble();
1107 for (const auto value : games) {
1108 SetGames->pushTable({{"Creator", value->getCreator()},
1109 {"Host", value->getHost().toString()},
1110 {"IsSavedGame", value->isSavedGame()},
1111 {"Map", value->getMap()},
1112 {"MaxPlayers", value->maxPlayers()},
1113 {"Speed", value->getSpeed()},
1114 {"Approval", value->getApproval()},
1115 {"Settings", value->getGameSettings()},
1116 {"Status", value->getGameStatus()},
1117 {"Type", value->getGameType()}});
1118 }
1119 SetGames->run();
1120 }
1121 }
1122
getGames()1123 std::vector<Game*> getGames() { return games; }
1124
setFriendslist(std::vector<Friend * > friends)1125 void setFriendslist(std::vector<Friend*> friends) {
1126 for (const auto value : this->friends) {
1127 delete value;
1128 }
1129 this->friends = friends;
1130 if (SetFriends != NULL) {
1131 SetFriends->pushPreamble();
1132 for (const auto value : friends) {
1133 SetFriends->pushTable({ { "Name", value->getName() },
1134 { "Status", value->getStatus() },
1135 { "Product", value->getProduct() } });
1136 }
1137 SetFriends->run();
1138 }
1139 }
1140
getFriends()1141 std::vector<Friend*> getFriends() { return friends; }
1142
reportUserdata(uint32_t id,std::vector<std::string> values)1143 void reportUserdata(uint32_t id, std::vector<std::string> values) {
1144 if (ShowUserInfo != NULL) {
1145 ShowUserInfo->pushPreamble();
1146 std::map<std::string, std::variant<std::string, int>> m;
1147 m["User"] = extendedInfoNames.at(id);
1148 for (unsigned int i = 0; i < values.size(); i++) {
1149 m[defaultUserKeys.at(i)] = values.at(i);
1150 }
1151 ShowUserInfo->pushTable(m);
1152 ShowUserInfo->run();
1153 }
1154 }
1155
getInfo()1156 std::queue<std::string> *getInfo() { return &info; }
1157
showInfo(std::string arg)1158 void showInfo(std::string arg) {
1159 std::string infoStr = arg;
1160 info.push(infoStr);
1161 if (ShowInfo != NULL) {
1162 ShowInfo->pushPreamble();
1163 ShowInfo->pushString(infoStr);
1164 ShowInfo->run();
1165 }
1166 }
1167
showError(std::string arg)1168 void showError(std::string arg) {
1169 info.push("!!! " + arg + " !!!");
1170 if (ShowError != NULL) {
1171 ShowError->pushPreamble();
1172 ShowError->pushString(arg);
1173 ShowError->run();
1174 }
1175 }
1176
showChat(std::string arg)1177 void showChat(std::string arg) {
1178 info.push(arg);
1179 if (ShowChat != NULL) {
1180 ShowChat->pushPreamble();
1181 ShowChat->pushString(arg);
1182 ShowChat->run();
1183 }
1184 }
1185
addUser(std::string name)1186 void addUser(std::string name) {
1187 userList.insert(name);
1188 if (AddUser != NULL) {
1189 AddUser->pushPreamble();
1190 AddUser->pushString(name);
1191 AddUser->run();
1192 }
1193 }
1194
removeUser(std::string name)1195 void removeUser(std::string name) {
1196 userList.erase(name);
1197 if (RemoveUser != NULL) {
1198 RemoveUser->pushPreamble();
1199 RemoveUser->pushString(name);
1200 RemoveUser->run();
1201 }
1202 }
1203
getUsers()1204 std::set<std::string> getUsers() { return userList; }
1205
setChannels(std::vector<std::string> channels)1206 void setChannels(std::vector<std::string> channels) {
1207 this->channelList = channels;
1208 if (SetChannels != NULL) {
1209 SetChannels->pushPreamble();
1210 for (const auto& value : channels) {
1211 SetChannels->pushString(value);
1212 }
1213 SetChannels->run();
1214 }
1215 }
1216
getChannels()1217 std::vector<std::string> getChannels() { return channelList; }
1218
1219 // State
getUsername()1220 std::string getUsername() { return username; }
1221
setUsername(std::string arg)1222 void setUsername(std::string arg) { username = arg; }
1223
getPassword1()1224 uint32_t* getPassword1() {
1225 // we assume that any valid password has at least 1 non-null word hash
1226 for (int i = 0; i < 5; i++) {
1227 if (password[i] != 0) {
1228 return password;
1229 }
1230 }
1231 return NULL;
1232 }
1233
setPassword(std::string pw)1234 void setPassword(std::string pw) {
1235 if (pw.empty()) {
1236 memset(password, 0, sizeof(password));
1237 hasPassword = false;
1238 } else {
1239 pvpgn::sha1_hash(&password, pw.length(), pw.c_str());
1240 hasPassword = true;
1241 }
1242 }
1243
setCreateAccount(bool flag)1244 void setCreateAccount(bool flag) {
1245 createAccount = flag;
1246 }
1247
shouldCreateAccount()1248 bool shouldCreateAccount() {
1249 return createAccount;
1250 }
1251
1252 // Protocol
getHost()1253 CHost *getHost() { return host; }
1254
setHost(CHost * arg)1255 void setHost(CHost *arg) {
1256 if (host != NULL) {
1257 delete host;
1258 }
1259 host = arg;
1260 }
1261
getUDPSocket()1262 CUDPSocket *getUDPSocket() {
1263 if (!NetworkFildes.IsValid()) {
1264 if (canDoUdp == -1) {
1265 InitNetwork1();
1266 if (NetworkFildes.IsValid()) {
1267 // I started it, so I'll shut it down
1268 canDoUdp = 1;
1269 } else {
1270 // do not try again
1271 canDoUdp = 0;
1272 }
1273 }
1274 }
1275 return &NetworkFildes;
1276 }
1277
getTCPSocket()1278 CTCPSocket *getTCPSocket() { return tcpSocket; }
1279
getMsgIStream()1280 BNCSInputStream *getMsgIStream() { return istream; }
1281
doOneStep()1282 virtual void doOneStep() { if (this->state != NULL) this->state->doOneStep(this); }
1283
handleUDP(const unsigned char * buffer,int len,CHost host)1284 virtual bool handleUDP(const unsigned char *buffer, int len, CHost host) {
1285 if (host.getIp() != getHost()->getIp() || host.getPort() != getHost()->getPort()) {
1286 return false;
1287 }
1288
1289 uint8_t id = 0;
1290 if (len >= 4) {
1291 id = buffer[0];
1292 }
1293 DebugPrint("UDP Recv: 0x%x\n" _C_ id);
1294 switch (id) {
1295 case 0x05:
1296 // PKT_SERVERPING
1297 // (UINT32) 0x05 0x00 0x00 0x00
1298 // (UINT32) UDP Code
1299 {
1300 const uint32_t udpCode = reinterpret_cast<const uint32_t*>(buffer)[1];
1301 BNCSOutputStream udppingresponse(0x14);
1302 udppingresponse.serialize32(udpCode);
1303 udppingresponse.flush(getTCPSocket());
1304 DebugPrint("TCP Sent: 0x14 UDPPINGRESPONSE\n");
1305 }
1306 break;
1307 default:
1308 // unknown package, ignore
1309 break;
1310 }
1311 return true;
1312 }
1313
setState(OnlineState * newState)1314 void setState(OnlineState* newState) {
1315 assert (newState != this->state);
1316 if (this->state != NULL) {
1317 delete this->state;
1318 }
1319 this->state = newState;
1320 }
1321
1322 uint32_t clientToken;
1323 uint32_t serverToken;
1324 uint32_t udpToken;
1325
1326 LuaCallback *AddUser = NULL;
1327 LuaCallback *RemoveUser = NULL;
1328 LuaCallback *SetFriends = NULL;
1329 LuaCallback *SetGames = NULL;
1330 LuaCallback *SetChannels = NULL;
1331 LuaCallback *SetActiveChannel = NULL;
1332 LuaCallback *ShowError = NULL;
1333 LuaCallback *ShowInfo = NULL;
1334 LuaCallback *ShowChat = NULL;
1335 LuaCallback *ShowUserInfo = NULL;
1336
1337 private:
gameNameFromUsername(std::string username)1338 std::string gameNameFromUsername(std::string username) {
1339 return username + "'s game";
1340 }
1341
1342 OnlineState *state;
1343 CHost *host;
1344 int8_t canDoUdp = -1; // -1,0,1 --- not tried, doesn't work, I created it
1345 CTCPSocket *tcpSocket;
1346 BNCSInputStream *istream;
1347
1348 std::string username;
1349 uint32_t password[5]; // xsha1 hash of password
1350 bool hasPassword;
1351 bool createAccount;
1352
1353 bool requestedAddress = false;
1354 CHost externalAddress;
1355
1356 std::string lastError;
1357
1358 std::string currentChannel;
1359 std::set<std::string> userList;
1360 std::vector<std::string> channelList;
1361 std::queue<std::string> info;
1362 std::vector<Game*> games;
1363 std::vector<Friend*> friends;
1364 std::vector<std::string> extendedInfoNames;
1365 std::vector<std::string> defaultUserKeys;
1366 };
1367
send(Context * ctx,BNCSOutputStream * buf)1368 int OnlineState::send(Context *ctx, BNCSOutputStream *buf) {
1369 return buf->flush(ctx->getTCPSocket());
1370 }
1371
1372 class DisconnectedState : public OnlineState {
1373 public:
DisconnectedState(std::string message)1374 DisconnectedState(std::string message) {
1375 this->message = message;
1376 };
1377
doOneStep(Context * ctx)1378 virtual void doOneStep(Context *ctx) {
1379 std::cout << message << std::endl;
1380 ctx->disconnect();
1381 ctx->showError(message);
1382 }
1383
1384 private:
1385 bool hasPrinted;
1386 std::string message;
1387 };
1388
1389 class S2C_CHATEVENT : public OnlineState {
1390 public:
S2C_CHATEVENT()1391 S2C_CHATEVENT() {
1392 this->ticks = 0;
1393 }
1394
doOneStep(Context * ctx)1395 virtual void doOneStep(Context *ctx) {
1396 if ((ticks % 1000) == 0) {
1397 // C>S 0x07 PKT_KEEPALIVE
1398 // ~1000 frames @ ~50fps ~= 20 seconds
1399 BNCSOutputStream keepalive(0x07, true);
1400 keepalive.serialize32(ticks);
1401 keepalive.flush(ctx->getUDPSocket(), ctx->getHost());
1402 DebugPrint("UDP Sent: 0x07 PKT_KEEPALIVE\n");
1403 }
1404
1405 if ((ticks % 10000) == 0) {
1406 // ~10000 frames @ ~50fps ~= 200 seconds
1407 ctx->refreshFriends();
1408 ctx->refreshChannels();
1409 }
1410
1411 if ((ticks % 500) == 0) {
1412 // ~1000 frames @ ~50fps ~= 10 seconds
1413 ctx->refreshGames();
1414 }
1415
1416 ticks++;
1417
1418 if (ctx->getTCPSocket()->HasDataToRead(0)) {
1419 uint8_t msg = ctx->getMsgIStream()->readMessageId();
1420 if (msg == 0xff) {
1421 // try again next time
1422 return;
1423 }
1424 DebugPrint("TCP Recv: 0x%x\n" _C_ msg);
1425
1426 switch (msg) {
1427 case 0x00: // SID_NULL
1428 handleNull(ctx);
1429 break;
1430 case 0x25: // SID_PING
1431 handlePing(ctx);
1432 break;
1433 case 0x0b: // SID_CHANNELLIST
1434 ctx->setChannels(ctx->getMsgIStream()->readStringlist());
1435 break;
1436 case 0x0f: // CHATEVENT
1437 handleChatevent(ctx);
1438 break;
1439 case 0x09:
1440 // S>C 0x09 SID_GETADVLISTEX
1441 handleGamelist(ctx);
1442 break;
1443 case 0x1c:
1444 // S>C 0x1C SID_STARTADVEX3
1445 if (ctx->getMsgIStream()->read32()) {
1446 ctx->showError("Online game creation failed");
1447 }
1448 break;
1449 case 0x65:
1450 // S>C 0x65 SID_FRIENDSLIST
1451 handleFriendlist(ctx);
1452 break;
1453 case 0x26:
1454 // S>C 0x26 SID_READUSERDATA
1455 handleUserdata(ctx);
1456 break;
1457 default:
1458 // TODO:
1459 // S>C 0x68 SID_FRIENDSREMOVE
1460 // S>C 0x67 SID_FRIENDSADD
1461 std::cout << "Unhandled message ID: 0x" << std::hex << msg << std::endl;
1462 std::cout << "Raw contents >>>" << std::endl;
1463 char* out;
1464 int len = ctx->getMsgIStream()->readAll(&out);
1465 std::cout.write(out, len);
1466 std::cout << "<<<" << std::endl;
1467 free(out);
1468 }
1469
1470 ctx->getMsgIStream()->finishMessage();
1471 }
1472 }
1473
1474 private:
handleNull(Context * ctx)1475 void handleNull(Context *ctx) {
1476 BNCSOutputStream buffer(0x00);
1477 send(ctx, &buffer);
1478 DebugPrint("TCP Sent: 0x00 NULL\n");
1479 }
1480
handlePing(Context * ctx)1481 void handlePing(Context *ctx) {
1482 uint32_t pingValue = ctx->getMsgIStream()->read32();
1483 BNCSOutputStream buffer(0x25);
1484 buffer.serialize32(htonl(pingValue));
1485 send(ctx, &buffer);
1486 DebugPrint("TCP Sent: 0x25 PING\n");
1487 }
1488
handleGamelist(Context * ctx)1489 void handleGamelist(Context *ctx) {
1490 uint32_t cnt = ctx->getMsgIStream()->read32();
1491 if (cnt > 100) {
1492 cnt = 100;
1493 }
1494 std::vector<Game*> games;
1495 while (cnt--) {
1496 uint32_t settings = ctx->getMsgIStream()->read32();
1497 uint32_t lang = ctx->getMsgIStream()->read32();
1498 uint16_t addr_fam = ctx->getMsgIStream()->read16();
1499 // the port is not in network byte order, since it's sent to
1500 // directly go into a sockaddr_in struct
1501 uint16_t port = (ctx->getMsgIStream()->read8() << 8) | ctx->getMsgIStream()->read8();
1502 uint32_t ip = ctx->getMsgIStream()->read32();
1503 uint32_t sinzero1 = ctx->getMsgIStream()->read32();
1504 uint32_t sinzero2 = ctx->getMsgIStream()->read32();
1505 uint32_t status = ctx->getMsgIStream()->read32();
1506 uint32_t time = ctx->getMsgIStream()->read32();
1507 std::string name = ctx->getMsgIStream()->readString();
1508 std::string pw = ctx->getMsgIStream()->readString();
1509 std::string stat = ctx->getMsgIStream()->readString();
1510 games.push_back(new Game(settings, port, ip, status, time, name, pw, stat));
1511 }
1512 ctx->setGamelist(games);
1513 }
1514
1515
handleFriendlist(Context * ctx)1516 void handleFriendlist(Context *ctx) {
1517 uint8_t cnt = ctx->getMsgIStream()->read8();
1518 if (cnt > 100) {
1519 cnt = 100;
1520 }
1521 std::vector<Friend*> friends;
1522 while (cnt--) {
1523 std::string user = ctx->getMsgIStream()->readString();
1524 uint8_t status = ctx->getMsgIStream()->read8();
1525 uint8_t location = ctx->getMsgIStream()->read8();
1526 uint32_t product = ctx->getMsgIStream()->read32();
1527 std::string locname = ctx->getMsgIStream()->readString();
1528 friends.push_back(new Friend(user, status, location, product, locname));
1529 }
1530 ctx->setFriendslist(friends);
1531 }
1532
handleUserdata(Context * ctx)1533 void handleUserdata(Context *ctx) {
1534 uint32_t cnt = ctx->getMsgIStream()->read32();
1535 assert(cnt == 1);
1536 uint32_t keys = ctx->getMsgIStream()->read32();
1537 uint32_t reqId = ctx->getMsgIStream()->read32();
1538 std::vector<std::string> values = ctx->getMsgIStream()->readStringlist(keys);
1539 ctx->reportUserdata(reqId, values);
1540 }
1541
handleChatevent(Context * ctx)1542 void handleChatevent(Context *ctx) {
1543 uint32_t eventId = ctx->getMsgIStream()->read32();
1544 uint32_t userFlags = ctx->getMsgIStream()->read32();
1545 uint32_t ping = ctx->getMsgIStream()->read32();
1546 uint32_t ip = ctx->getMsgIStream()->read32();
1547 uint32_t acn = ctx->getMsgIStream()->read32();
1548 uint32_t reg = ctx->getMsgIStream()->read32();
1549 std::string username = ctx->getMsgIStream()->readString();
1550 std::string text = ctx->getMsgIStream()->readString();
1551 switch (eventId) {
1552 case 0x01: // sent for user that is already in channel
1553 ctx->addUser(username);
1554 break;
1555 case 0x02: // user joined channel
1556 ctx->addUser(username);
1557 ctx->showInfo(username + " joined");
1558 break;
1559 case 0x03: // user left channel
1560 ctx->removeUser(username);
1561 ctx->showInfo(username + " left");
1562 case 0x04: // recv whisper
1563 if (!text.empty()) {
1564 std::string prefix = "/udppunch ";
1565 unsigned int a, b, c, d, ip, port;
1566 if (text.size() > prefix.size() && text.rfind(prefix, 0) != std::string::npos) {
1567 int res = sscanf(text.substr(prefix.size()).c_str(), "%d.%d.%d.%d:%d", &a, &b, &c, &d, &port);
1568 if (res == 5 && a < 255 && b < 255 && c < 255 && d < 255 && port >= 1024) {
1569 ip = a | b << 8 | c << 16 | d << 24;
1570 if (NetConnectType == 1 && !GameRunning) { // the server, waiting for clients
1571 const CInitMessage_Header message(MessageInit_FromServer, ICMAYT);
1572 NetworkSendICMessage(*(ctx->getUDPSocket()), CHost(ip, port), message);
1573 DebugPrint("UDP Sent: UDP punch\n");
1574 } else {
1575 // the client will connect now and send packages, anyway.
1576 // any other state shouldn't try to udp hole punch at this stage
1577 }
1578 return;
1579 } else {
1580 // incorrect format, fall through and treat as normal whisper;
1581 }
1582 }
1583 }
1584 ctx->showChat(username + " whispers " + text);
1585 break;
1586 case 0x05: // recv chat
1587 ctx->showChat(username + ": " + text);
1588 break;
1589 case 0x06: // recv broadcast
1590 ctx->showInfo("[BROADCAST]: " + text);
1591 break;
1592 case 0x07: // channel info
1593 ctx->setCurrentChannel(text);
1594 ctx->showInfo("Joined channel " + text);
1595 break;
1596 case 0x09: // user flags update
1597 break;
1598 case 0x0a: // sent whisper
1599 break;
1600 case 0x0d: // channel full
1601 ctx->showInfo("Channel full");
1602 break;
1603 case 0x0e: // channel does not exist
1604 ctx->showInfo("Channel does not exist");
1605 break;
1606 case 0x0f: // channel is restricted
1607 ctx->showInfo("Channel restricted");
1608 break;
1609 case 0x12: // general info text
1610 ctx->showInfo(text);
1611 break;
1612 case 0x13: // error message
1613 ctx->showError(text);
1614 break;
1615 case 0x17: // emote
1616 break;
1617 }
1618 }
1619
1620 uint64_t ticks;
1621 };
1622
1623 class S2C_ENTERCHAT : public OnlineState {
doOneStep(Context * ctx)1624 virtual void doOneStep(Context *ctx) {
1625 if (ctx->getTCPSocket()->HasDataToRead(0)) {
1626 uint8_t msg = ctx->getMsgIStream()->readMessageId();
1627 if (msg == 0xff) {
1628 // try again next time
1629 return;
1630 }
1631 if (msg == 0x3a) {
1632 DebugPrint("TCP Recv: 0x3a LOGONRESPONSE\n");
1633 // pvpgn seems to send a successful logonresponse again
1634 uint32_t status = ctx->getMsgIStream()->read32();
1635 assert(status == 0);
1636 ctx->getMsgIStream()->finishMessage();
1637 return;
1638 }
1639 if (msg != 0x0a) {
1640 std::string error = std::string("Expected SID_ENTERCHAT, got msg id ");
1641 error += std::to_string(msg);
1642 ctx->setState(new DisconnectedState(error));
1643 return;
1644 }
1645 DebugPrint("TCP Recv: 0x0a ENTERCHAT\n");
1646
1647 std::string uniqueName = ctx->getMsgIStream()->readString();
1648 std::string statString = ctx->getMsgIStream()->readString();
1649 std::string accountName = ctx->getMsgIStream()->readString();
1650 ctx->getMsgIStream()->finishMessage();
1651
1652 ctx->setUsername(uniqueName);
1653 if (!statString.empty()) {
1654 ctx->showInfo("Statstring after logon: " + statString);
1655 }
1656
1657 ctx->requestExtraUserInfo(ctx->getUsername());
1658 ctx->requestExternalAddress();
1659
1660 ctx->setState(new S2C_CHATEVENT());
1661 }
1662 }
1663 };
1664
1665 class C2S_ENTERCHAT : public OnlineState {
doOneStep(Context * ctx)1666 virtual void doOneStep(Context *ctx) {
1667 // does all of enterchar, getchannellist, and first-join joinchannel
1668 BNCSOutputStream enterchat(0x0a);
1669 enterchat.serialize(ctx->getUsername().c_str());
1670 enterchat.serialize("");
1671 enterchat.flush(ctx->getTCPSocket());
1672 DebugPrint("TCP Sent: 0x0a ENTERCHAT\n");
1673
1674 ctx->refreshChannels();
1675
1676 BNCSOutputStream join(0x0c);
1677 join.serialize32(0x01); // first-join
1678 join.serialize(gameName().c_str());
1679 join.flush(ctx->getTCPSocket());
1680 DebugPrint("TCP Sent: 0x0c JOINCHANNEL\n");
1681
1682 // TODO: maybe send 0x45 SID_NETGAMEPORT to report our gameport on pvpgn
1683 // to whatever the user specified on the cmdline?
1684
1685 ctx->setState(new S2C_ENTERCHAT());
1686 }
1687 };
1688
1689 class C2S_LOGONRESPONSE2_OR_C2S_CREATEACCOUNT : public OnlineState {
1690 virtual void doOneStep(Context *ctx);
1691 };
1692
1693 class S2C_CREATEACCOUNT2 : public OnlineState {
doOneStep(Context * ctx)1694 virtual void doOneStep(Context *ctx) {
1695 if (ctx->getTCPSocket()->HasDataToRead(0)) {
1696 uint8_t msg = ctx->getMsgIStream()->readMessageId();
1697 if (msg == 0xff) {
1698 // try again next time
1699 return;
1700 }
1701 if (msg != 0x3d) {
1702 std::string error = std::string("Expected SID_CREATEACCOUNT2, got msg id ");
1703 error += std::to_string(msg);
1704 ctx->setState(new DisconnectedState(error));
1705 return;
1706 }
1707 DebugPrint("TCP Recv: 0x3d CREATEACCOUNT\n");
1708
1709 uint32_t status = ctx->getMsgIStream()->read32();
1710 std::string nameSugg = ctx->getMsgIStream()->readString();
1711 ctx->getMsgIStream()->finishMessage();
1712
1713 if (!nameSugg.empty()) {
1714 nameSugg = " (try username: " + nameSugg + ")";
1715 }
1716
1717 std::string errMsg;
1718
1719 switch (status) {
1720 case 0x00:
1721 // login into created account
1722 ctx->setState(new C2S_LOGONRESPONSE2_OR_C2S_CREATEACCOUNT());
1723 return;
1724 case 0x01:
1725 errMsg = "Name too short";
1726 break;
1727 case 0x02:
1728 errMsg = "Name contains invalid character(s)";
1729 break;
1730 case 0x03:
1731 errMsg = "Name contains banned word(s)";
1732 break;
1733 case 0x04:
1734 errMsg = "Account already exists";
1735 break;
1736 case 0x05:
1737 errMsg = "Account is still being created";
1738 break;
1739 case 0x06:
1740 errMsg = "Name does not contain enough alphanumeric characters";
1741 break;
1742 case 0x07:
1743 errMsg = "Name contained adjacent punctuation characters";
1744 break;
1745 case 0x08:
1746 errMsg = "Name contained too many punctuation characters";
1747 break;
1748 default:
1749 errMsg = "Unknown error creating account";
1750 }
1751 ctx->setState(new DisconnectedState(errMsg + nameSugg));
1752 return;
1753 }
1754 }
1755 };
1756
1757 class S2C_LOGONRESPONSE2 : public OnlineState {
doOneStep(Context * ctx)1758 virtual void doOneStep(Context *ctx) {
1759 if (ctx->getTCPSocket()->HasDataToRead(0)) {
1760 uint8_t msg = ctx->getMsgIStream()->readMessageId();
1761 if (msg == 0xff) {
1762 // try again next time
1763 return;
1764 }
1765 if (msg != 0x3a) {
1766 if (msg == 0x59) {
1767 // SID_SETEMAIL, server is asking for an email address. We ignore that.
1768 ctx->getMsgIStream()->finishMessage();
1769 return;
1770 }
1771 std::string error = std::string("Expected SID_LOGONRESPONSE2, got msg id ");
1772 error += std::to_string(msg);
1773 ctx->showError(error);
1774 ctx->getMsgIStream()->debugDump();
1775 ctx->getMsgIStream()->finishMessage();
1776 return;
1777 }
1778 DebugPrint("TCP Sent: 0x3a LOGONRESPONSE\n");
1779
1780 uint32_t status = ctx->getMsgIStream()->read32();
1781 ctx->getMsgIStream()->finishMessage();
1782 std::string errMsg;
1783
1784 switch (status) {
1785 case 0x00:
1786 // success. we will send SID_UDPPINGRESPONSE before entering chat
1787 ctx->setState(new C2S_ENTERCHAT());
1788 return;
1789 case 0x01:
1790 case 0x010000:
1791 errMsg = "Account does not exist";
1792 break;
1793 case 0x02:
1794 case 0x020000:
1795 errMsg = "Incorrect password";
1796 break;
1797 case 0x06:
1798 case 0x060000:
1799 errMsg = "Account closed: " + ctx->getMsgIStream()->readString();
1800 break;
1801 default:
1802 errMsg = "unknown logon response";
1803 break;
1804 }
1805 ctx->setState(new DisconnectedState(errMsg));
1806 }
1807 }
1808 };
1809
doOneStep(Context * ctx)1810 void C2S_LOGONRESPONSE2_OR_C2S_CREATEACCOUNT::doOneStep(Context *ctx) {
1811 std::string user = ctx->getUsername();
1812 uint32_t *pw = ctx->getPassword1(); // single-hashed for SID_LOGONRESPONSE2
1813 if (!user.empty() && pw) {
1814 if (ctx->shouldCreateAccount()) {
1815 ctx->setCreateAccount(false);
1816 BNCSOutputStream msg(0x3d);
1817 uint32_t *pw = ctx->getPassword1();
1818 for (int i = 0; i < 20; i++) {
1819 msg.serialize8(reinterpret_cast<uint8_t *>(pw)[i]);
1820 }
1821 msg.serialize(ctx->getUsername().c_str());
1822 msg.flush(ctx->getTCPSocket());
1823 DebugPrint("TCP Sent: 0x3d CREATEACOUNT\n");
1824 ctx->setState(new S2C_CREATEACCOUNT2());
1825 return;
1826 }
1827
1828 BNCSOutputStream logon(0x3a);
1829 logon.serialize32(ctx->clientToken);
1830 logon.serialize32(ctx->serverToken);
1831
1832 // Battle.net password hashes are hashed twice using XSHA-1. First, the
1833 // password is hashed by itself, then the following data is hashed again
1834 // and sent to Battle.net:
1835 // (UINT32) Client Token
1836 // (UINT32) Server Token
1837 // (UINT32)[5] First password hash
1838 // The logic below is taken straight from pvpgn
1839 struct {
1840 pvpgn::bn_int ticks;
1841 pvpgn::bn_int sessionkey;
1842 pvpgn::bn_int passhash1[5];
1843 } temp;
1844 uint32_t passhash2[5];
1845
1846 pvpgn::bn_int_set(&temp.ticks, ntohl(ctx->clientToken));
1847 pvpgn::bn_int_set(&temp.sessionkey, ntohl(ctx->serverToken));
1848 pvpgn::hash_to_bnhash((pvpgn::t_hash const *)pw, temp.passhash1);
1849 pvpgn::bnet_hash(&passhash2, sizeof(temp), &temp); /* do the double hash */
1850
1851 for (int i = 0; i < 20; i++) {
1852 logon.serialize8(reinterpret_cast<uint8_t *>(passhash2)[i]);
1853 }
1854 logon.serialize(user.c_str());
1855 logon.flush(ctx->getTCPSocket());
1856 DebugPrint("TCP Sent: 0x3a LOGIN\n");
1857
1858 ctx->setState(new S2C_LOGONRESPONSE2());
1859 }
1860 };
1861
1862 class S2C_SID_AUTH_CHECK : public OnlineState {
doOneStep(Context * ctx)1863 virtual void doOneStep(Context *ctx) {
1864 if (ctx->getTCPSocket()->HasDataToRead(0)) {
1865 uint8_t msg = ctx->getMsgIStream()->readMessageId();
1866 if (msg == 0xff) {
1867 // try again next time
1868 return;
1869 }
1870 if (msg != 0x51) {
1871 std::string error = std::string("Expected SID_AUTH_CHECK, got msg id ");
1872 error += std::to_string(msg);
1873 ctx->setState(new DisconnectedState(error));
1874 return;
1875 }
1876 DebugPrint("TCP Recv: 0x51 AUTH_CHECK\n");
1877
1878 uint32_t result = ctx->getMsgIStream()->read32();
1879 std::string info = ctx->getMsgIStream()->readString();
1880 ctx->getMsgIStream()->finishMessage();
1881
1882 switch (result) {
1883 case 0x000:
1884 // Passed challenge
1885 ctx->setState(new C2S_LOGONRESPONSE2_OR_C2S_CREATEACCOUNT());
1886 return;
1887 case 0x100:
1888 // Old game version
1889 info = "Old game version: " + info;
1890 break;
1891 case 0x101:
1892 // Invalid version
1893 info = "Invalid version: " + info;
1894 break;
1895 case 0x102:
1896 // Game version must be downgraded
1897 info = "Game version must be downgraded: " + info;
1898 break;
1899 case 0x200:
1900 // Invalid CD key
1901 info = "Invalid CD Key: " + info;
1902 break;
1903 case 0x201:
1904 // CD Key in use
1905 info = "CD Key in use: " + info;
1906 break;
1907 case 0x202:
1908 // Banned key
1909 info = "Banned key: " + info;
1910 break;
1911 case 0x203:
1912 // Wrong product
1913 info = "Wrong product: " + info;
1914 break;
1915 default:
1916 // Invalid version code
1917 info = "Invalid version code: " + info;
1918 }
1919 ctx->setState(new DisconnectedState(info));
1920 }
1921 }
1922 };
1923
1924 class S2C_SID_AUTH_INFO : public OnlineState {
doOneStep(Context * ctx)1925 virtual void doOneStep(Context *ctx) {
1926 if (ctx->getTCPSocket()->HasDataToRead(0)) {
1927 uint8_t msg = ctx->getMsgIStream()->readMessageId();
1928 if (msg == 0xff) {
1929 // try again next time
1930 return;
1931 }
1932 if (msg != 0x50) {
1933 std::string error = std::string("Expected SID_AUTH_INFO, got msg id ");
1934 error += std::to_string(msg);
1935 ctx->setState(new DisconnectedState(error));
1936 return;
1937 }
1938 DebugPrint("TCP Recv: 0x50 AUTH_INFO\n");
1939
1940 uint32_t logonType = ctx->getMsgIStream()->read32();
1941 // assert(logonType == 0x00); // only support Broken SHA-1 logon for now
1942 DebugPrint("logonType: 0x%x\n" _C_ logonType);
1943 uint32_t serverToken = ctx->getMsgIStream()->read32();
1944 ctx->serverToken = htonl(serverToken); // keep in network order
1945 uint32_t udpToken = ctx->getMsgIStream()->read32();
1946 ctx->udpToken = htonl(udpToken);
1947 uint64_t mpqFiletime = ctx->getMsgIStream()->readFiletime();
1948 std::string mpqFilename = ctx->getMsgIStream()->readString();
1949 std::string formula = ctx->getMsgIStream()->readString();
1950 ctx->getMsgIStream()->finishMessage();
1951
1952 // immediately respond with pkt_conntest2 udp msg
1953 ctx->sendUdpConnectionInfo();
1954
1955 // immediately respond with SID_AUTH_CHECK
1956 BNCSOutputStream check(0x51);
1957 check.serialize32(ctx->clientToken);
1958 // EXE version (one UINT32 value, serialized in network byte order here)
1959 check.serialize8(StratagusPatchLevel2);
1960 check.serialize8(StratagusPatchLevel);
1961 check.serialize8(StratagusMinorVersion);
1962 check.serialize8(StratagusMajorVersion);
1963 // EXE hash - we use lua file checksums
1964 check.serialize32(FileChecksums);
1965 // no CD key, not a Spawn key
1966 check.serialize32(0);
1967 check.serialize32(0);
1968 // EXE information
1969 std::string exeInfo("");
1970 exeInfo += gameName();
1971 exeInfo += " ";
1972 exeInfo += StratagusLastModifiedDate;
1973 exeInfo += " ";
1974 exeInfo += StratagusLastModifiedTime;
1975 exeInfo += " ";
1976 exeInfo += std::to_string(StratagusVersion);
1977 check.serialize(exeInfo.c_str());
1978 // Key owner name
1979 check.serialize(DESCRIPTION);
1980 check.flush(ctx->getTCPSocket());
1981 DebugPrint("TCP Sent: 0x51 AUTH_CHECK\n");
1982
1983 ctx->setState(new S2C_SID_AUTH_CHECK());
1984 }
1985 }
1986 };
1987
1988 class S2C_SID_PING : public OnlineState {
doOneStep(Context * ctx)1989 virtual void doOneStep(Context *ctx) {
1990 if (ctx->getTCPSocket()->HasDataToRead(0)) {
1991 uint8_t msg = ctx->getMsgIStream()->readMessageId();
1992 if (msg == 0xff) {
1993 // try again next time
1994 return;
1995 }
1996 if (msg != 0x25) {
1997 // not a ping
1998 std::string error = std::string("Expected SID_PING, got msg id ");
1999 error += std::to_string(msg);
2000 ctx->setState(new DisconnectedState(error));
2001 return;
2002 }
2003 DebugPrint("TCP Recv: 0x25 PING\n");
2004 uint32_t pingValue = ctx->getMsgIStream()->read32();
2005 ctx->getMsgIStream()->finishMessage();
2006
2007 // immediately respond with C2S_SID_PING
2008 BNCSOutputStream buffer(0x25);
2009 buffer.serialize32(htonl(pingValue));
2010 send(ctx, &buffer);
2011 DebugPrint("TCP Sent: 0x25 PING\n");
2012
2013 ctx->setState(new S2C_SID_AUTH_INFO());
2014 }
2015 }
2016 };
2017
2018 class ConnectState : public OnlineState {
doOneStep(Context * ctx)2019 virtual void doOneStep(Context *ctx) {
2020 // Connect
2021
2022 std::string localHost = CNetworkParameter::Instance.localHost;
2023 if (!localHost.compare("127.0.0.1")) {
2024 localHost = "0.0.0.0";
2025 }
2026 if (!ctx->getTCPSocket()->IsValid()) {
2027 if (!ctx->getTCPSocket()->Open(CHost(localHost.c_str(), CNetworkParameter::Instance.localPort))) {
2028 ctx->setState(new DisconnectedState("TCP open failed"));
2029 return;
2030 }
2031 if (!ctx->getTCPSocket()->IsValid()) {
2032 ctx->setState(new DisconnectedState("TCP not valid"));
2033 return;
2034 }
2035 }
2036 if (!ctx->getTCPSocket()->Connect(*ctx->getHost())) {
2037 if (--retries < 0) {
2038 ctx->setState(new DisconnectedState("TCP connect failed for server " + ctx->getHost()->toString()));
2039 }
2040 return;
2041 }
2042
2043 // Send proto byte
2044 ctx->getTCPSocket()->Send("\x1", 1);
2045
2046 // C>S SID_AUTH_INFO
2047 BNCSOutputStream buffer(0x50);
2048 // (UINT32) Protocol ID
2049 buffer.serialize32(0x00);
2050 // (UINT32) Platform code
2051 buffer.serializeC32("IX86");
2052 // (UINT32) Product code - we'll just use W2BN and encode what we are in
2053 // the version
2054 buffer.serializeC32("W2BN");
2055 // (UINT32) Version byte - just use the last from W2BN
2056 buffer.serialize32(0x4f);
2057 // (UINT32) Language code
2058 buffer.serialize32(0x00);
2059 // (UINT32) Local IP
2060 if (CNetworkParameter::Instance.localHost.compare("127.0.0.1")) {
2061 // user set a custom local ip, use that
2062 #ifdef USE_WIN32
2063 uint32_t addr = inet_addr(CNetworkParameter::Instance.localHost.c_str());
2064 buffer.serialize32(addr);
2065 #else
2066 struct in_addr addr;
2067 inet_aton(CNetworkParameter::Instance.localHost.c_str(), &addr);
2068 buffer.serialize32(addr.s_addr);
2069 #endif
2070 } else {
2071 unsigned long ips[20];
2072 int networkNumInterfaces = NetworkFildes.GetSocketAddresses(ips, 20);
2073 bool found_one = false;
2074 if (networkNumInterfaces) {
2075 // we can only advertise one, so take the first that fits in 32-bit and is thus presumably ipv4
2076 for (int i = 0; i < networkNumInterfaces; i++) {
2077 uint32_t ip = ips[i];
2078 if (ip == ips[i]) {
2079 found_one = true;
2080 buffer.serialize32(ip);
2081 break;
2082 }
2083 }
2084 }
2085 if (!found_one) {
2086 // use default
2087 buffer.serialize32(0x00);
2088 }
2089 }
2090 // (UINT32) Time zone bias
2091 uint32_t bias = 0;
2092 std::time_t systemtime = std::time(NULL);
2093 struct std::tm *utc = std::gmtime(&systemtime);
2094 if (utc != NULL) {
2095 std::time_t utctime_since_epoch = std::mktime(utc);
2096 if (utctime_since_epoch != -1) {
2097 struct std::tm *localtime = std::localtime(&systemtime);
2098 if (localtime != 0) {
2099 std::time_t localtime_since_epoch = std::mktime(localtime);
2100 if (localtime_since_epoch != -1) {
2101 bias = (localtime_since_epoch - utctime_since_epoch) / 60;
2102 }
2103 }
2104 }
2105 }
2106 buffer.serialize32(bias);
2107 // (UINT32) MPQ locale ID
2108 buffer.serialize32(0x00);
2109 // (UINT32) User language ID
2110 buffer.serialize32(0x00);
2111 // (STRING) Country abbreviation
2112 buffer.serialize("USA");
2113 // (STRING) Country
2114 buffer.serialize("United States");
2115
2116 send(ctx, &buffer);
2117 DebugPrint("TCP Sent: 0x50 AUTH_INFO\n");
2118 ctx->setState(new S2C_SID_PING());
2119 }
2120
2121 private:
2122 int retries = 15;
2123 };
2124
2125 static Context _ctx;
2126 OnlineContext *OnlineContextHandler = &_ctx;
2127
CclStopAdvertisingOnlineGame(lua_State * l)2128 static int CclStopAdvertisingOnlineGame(lua_State* l) {
2129 if (_ctx.isConnected()) {
2130 _ctx.stopAdvertising();
2131 lua_pushboolean(l, true);
2132 } else {
2133 lua_pushboolean(l, false);
2134 }
2135 return 1;
2136 }
2137
CclStartAdvertisingOnlineGame(lua_State * l)2138 static int CclStartAdvertisingOnlineGame(lua_State* l) {
2139 if (_ctx.isConnected()) {
2140 _ctx.startAdvertising();
2141 lua_pushboolean(l, true);
2142 } else {
2143 lua_pushboolean(l, false);
2144 }
2145 return 1;
2146 }
2147
CclSetup(lua_State * l)2148 static int CclSetup(lua_State *l) {
2149 LuaCheckArgs(l, 1);
2150 if (!lua_istable(l, 1)) {
2151 LuaError(l, "incorrect argument");
2152 }
2153
2154 for (lua_pushnil(l); lua_next(l, 1); lua_pop(l, 1)) {
2155 const char *value = LuaToString(l, -2);
2156 if (!strcmp(value, "AddUser")) {
2157 if (_ctx.AddUser) {
2158 delete _ctx.AddUser;
2159 }
2160 _ctx.AddUser = new LuaCallback(l, -1);
2161 } else if (!strcmp(value, "RemoveUser")) {
2162 if (_ctx.RemoveUser) {
2163 delete _ctx.RemoveUser;
2164 }
2165 _ctx.RemoveUser = new LuaCallback(l, -1);
2166 } else if (!strcmp(value, "SetFriends")) {
2167 if (_ctx.SetFriends) {
2168 delete _ctx.SetFriends;
2169 }
2170 _ctx.SetFriends = new LuaCallback(l, -1);
2171 if (_ctx.isConnected()) {
2172 _ctx.setFriendslist(_ctx.getFriends());
2173 }
2174 } else if (!strcmp(value, "SetGames")) {
2175 if (_ctx.SetGames) {
2176 delete _ctx.SetGames;
2177 }
2178 _ctx.SetGames = new LuaCallback(l, -1);
2179 } else if (!strcmp(value, "SetChannels")) {
2180 if (_ctx.SetChannels) {
2181 delete _ctx.SetChannels;
2182 }
2183 if (_ctx.isConnected()) {
2184 _ctx.setChannels(_ctx.getChannels());
2185 }
2186 _ctx.SetChannels = new LuaCallback(l, -1);
2187 } else if (!strcmp(value, "SetActiveChannel")) {
2188 if (_ctx.SetActiveChannel) {
2189 delete _ctx.SetActiveChannel;
2190 }
2191 _ctx.SetActiveChannel = new LuaCallback(l, -1);
2192 if (_ctx.isConnected()) {
2193 _ctx.setCurrentChannel(_ctx.getCurrentChannel());
2194 }
2195 } else if (!strcmp(value, "ShowChat")) {
2196 if (_ctx.ShowChat) {
2197 delete _ctx.ShowChat;
2198 }
2199 _ctx.ShowChat = new LuaCallback(l, -1);
2200 } else if (!strcmp(value, "ShowInfo")) {
2201 if (_ctx.ShowInfo) {
2202 delete _ctx.ShowInfo;
2203 }
2204 _ctx.ShowInfo = new LuaCallback(l, -1);
2205 } else if (!strcmp(value, "ShowError")) {
2206 if (_ctx.ShowError) {
2207 delete _ctx.ShowError;
2208 }
2209 _ctx.ShowError = new LuaCallback(l, -1);
2210 } else if (!strcmp(value, "ShowUserInfo")) {
2211 if (_ctx.ShowUserInfo) {
2212 delete _ctx.ShowUserInfo;
2213 }
2214 _ctx.ShowUserInfo = new LuaCallback(l, -1);
2215 } else {
2216 LuaError(l, "Unsupported callback: %s" _C_ value);
2217 }
2218 }
2219 return 0;
2220 }
2221
CclDisconnect(lua_State * l)2222 static int CclDisconnect(lua_State *l) {
2223 LuaCheckArgs(l, 0);
2224 _ctx.disconnect();
2225 return 0;
2226 }
2227
CclConnect(lua_State * l)2228 static int CclConnect(lua_State *l) {
2229 _ctx.disconnect();
2230 LuaCheckArgs(l, 2);
2231 const char *host = LuaToString(l, 1);
2232 int port = LuaToNumber(l, 2);
2233
2234 _ctx.setHost(new CHost(host, port));
2235 _ctx.setState(new ConnectState());
2236 return 0;
2237 }
2238
CclLogin(lua_State * l)2239 static int CclLogin(lua_State *l) {
2240 LuaCheckArgs(l, 2);
2241 _ctx.setCreateAccount(false);
2242 _ctx.setUsername(LuaToString(l, 1));
2243 _ctx.setPassword(LuaToString(l, 2));
2244 return 0;
2245 }
2246
CclSignUp(lua_State * l)2247 static int CclSignUp(lua_State *l) {
2248 LuaCheckArgs(l, 2);
2249 _ctx.setCreateAccount(true);
2250 _ctx.setUsername(LuaToString(l, 1));
2251 _ctx.setPassword(LuaToString(l, 2));
2252 return 0;
2253 }
2254
CclStep(lua_State * l)2255 static int CclStep(lua_State *l) {
2256 LuaCheckArgs(l, 0);
2257 if (_ctx.isDisconnected()) {
2258 LuaError(l, "disconnected service cannot step");
2259 } else {
2260 _ctx.doOneStep();
2261 if (_ctx.isConnecting()) {
2262 lua_pushstring(l, "connecting");
2263 } else if (_ctx.isConnected()) {
2264 lua_pushstring(l, "online");
2265 } else {
2266 LuaError(l, "unknown online state");
2267 }
2268 }
2269 return 1;
2270 }
2271
CclSendMessage(lua_State * l)2272 static int CclSendMessage(lua_State *l) {
2273 LuaCheckArgs(l, 1);
2274 if (!_ctx.isConnected()) {
2275 LuaError(l, "connect first");
2276 }
2277 _ctx.sendText(LuaToString(l, 1));
2278 return 0;
2279 }
2280
CclJoinChannel(lua_State * l)2281 static int CclJoinChannel(lua_State *l) {
2282 LuaCheckArgs(l, 1);
2283 if (!_ctx.isConnected()) {
2284 LuaError(l, "connect first");
2285 }
2286 _ctx.joinChannel(LuaToString(l, 1));
2287 return 0;
2288 }
2289
CclJoinGame(lua_State * l)2290 static int CclJoinGame(lua_State *l) {
2291 LuaCheckArgs(l, 2);
2292 if (!_ctx.isConnected()) {
2293 LuaError(l, "connect first");
2294 }
2295 _ctx.joinGame(LuaToString(l, 1), LuaToString(l, 2));
2296 return 0;
2297 }
2298
CclStatus(lua_State * l)2299 static int CclStatus(lua_State *l) {
2300 LuaCheckArgs(l, 0);
2301 if (_ctx.isConnected()) {
2302 lua_pushstring(l, "connected");
2303 } else if (_ctx.isConnecting()) {
2304 lua_pushstring(l, "connecting");
2305 } else if (_ctx.isDisconnected()) {
2306 lua_pushstring(l, "disconnected");
2307 } else {
2308 if (!_ctx.getInfo()->empty()) {
2309 lua_pushstring(l, _ctx.getInfo()->back().c_str());
2310 } else {
2311 lua_pushstring(l, "unknown error");
2312 }
2313 }
2314 return 1;
2315 }
2316
CclRequestUserInfo(lua_State * l)2317 static int CclRequestUserInfo(lua_State *l) {
2318 LuaCheckArgs(l, 1);
2319 if (!_ctx.isConnected()) {
2320 LuaError(l, "not connected");
2321 }
2322 _ctx.requestExtraUserInfo(LuaToString(l, 1));
2323 return 0;
2324 }
2325
CclPunchNAT(lua_State * l)2326 static int CclPunchNAT(lua_State *l) {
2327 LuaCheckArgs(l, 1);
2328 _ctx.punchNAT(LuaToString(l, 1));
2329 return 0;
2330 }
2331
OnlineServiceCclRegister()2332 void OnlineServiceCclRegister() {
2333 lua_createtable(Lua, 0, 3);
2334
2335 lua_pushcfunction(Lua, CclSetup);
2336 lua_setfield(Lua, -2, "setup");
2337
2338 lua_pushcfunction(Lua, CclConnect);
2339 lua_setfield(Lua, -2, "connect");
2340 lua_pushcfunction(Lua, CclLogin);
2341 lua_setfield(Lua, -2, "login");
2342 lua_pushcfunction(Lua, CclSignUp);
2343 lua_setfield(Lua, -2, "signup");
2344 lua_pushcfunction(Lua, CclDisconnect);
2345 lua_setfield(Lua, -2, "disconnect");
2346
2347 lua_pushcfunction(Lua, CclStatus);
2348 lua_setfield(Lua, -2, "status");
2349
2350 // step is called in the SDL event loop, I don't think we need to expose it
2351 // lua_pushcfunction(Lua, CclStep);
2352 // lua_setfield(Lua, -2, "step");
2353 lua_pushcfunction(Lua, CclSendMessage);
2354 lua_setfield(Lua, -2, "sendmessage");
2355 lua_pushcfunction(Lua, CclJoinChannel);
2356 lua_setfield(Lua, -2, "joinchannel");
2357 lua_pushcfunction(Lua, CclRequestUserInfo);
2358 lua_setfield(Lua, -2, "requestuserinfo");
2359 // join game is called from the netconnect code, I don't think we need to expose it
2360 // lua_pushcfunction(Lua, CclJoinGame);
2361 // lua_setfield(Lua, -2, "joingame");
2362 lua_pushcfunction(Lua, CclStartAdvertisingOnlineGame);
2363 lua_setfield(Lua, -2, "startadvertising");
2364 lua_pushcfunction(Lua, CclStopAdvertisingOnlineGame);
2365 lua_setfield(Lua, -2, "stopadvertising");
2366 lua_pushcfunction(Lua, CclPunchNAT);
2367 lua_setfield(Lua, -2, "punchNAT");
2368
2369 lua_setglobal(Lua, "OnlineService");
2370 }
2371