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