From 0a8ed97c6043bfb23c2813cc0983284b225e7390 Mon Sep 17 00:00:00 2001
From: Tim Felgentreff <timfelgentreff@gmail.com>
Date: Fri, 27 Nov 2020 14:36:54 +0100
Subject: [PATCH] use a more compatible method to get my own address

This was after discussion with other developers on the BNetDocs chat. Since the
/netinfo command is a pvpgn specific thing, we instead advertise a fake game
when logging in and check what the server says our ip:port are.
---
 src/network/online_service.cpp | 62 +++++++++++++++++++---------------
 1 file changed, 34 insertions(+), 28 deletions(-)

diff --git a/src/network/online_service.cpp b/src/network/online_service.cpp
index 1c6700e41..4061632c6 100644
--- a/src/network/online_service.cpp
+++ b/src/network/online_service.cpp
@@ -747,10 +747,23 @@ public:
     }
 
     void requestExternalAddress() {
-        // uses the /netinfo command to get which ip:port the server sees from us
-        if (!externalAddress.isValid()) {
-            sendText("/netinfo", true);
+        // start advertising a fake game so we can see our external address in the gamelist
+        if (!requestedAddress) {
             requestedAddress = true;
+            BNCSOutputStream msg(0x1c);
+            msg.serialize32(0x10000000);
+            msg.serialize32(0x00); // uptime
+            msg.serialize16(0x0300); // game type
+            msg.serialize16(0x0100); // sub game type
+            msg.serialize32(0xff); // provider version constant
+            msg.serialize32(0x00); // not ladder
+            msg.serialize(""); // game name
+            msg.serialize(""); // password
+            std::string statstring = ",,,0x04,0x00,0x0a,0x01,0x1234,0x4000,";
+            statstring += getUsername() + "\rudp\r";
+            msg.serialize(statstring.c_str());
+            msg.flush(getTCPSocket());
+            DebugPrint("TCP Sent: 0x1c STARTADVEX\n");
         }
     }
 
@@ -1016,10 +1029,22 @@ public:
     void setGamelist(std::vector<Game*> games) {
         // before we are able to join any game, we should try to get our own
         // external address to help NAT traversal
-        requestExternalAddress();
         for (const auto value : this->games) {
             delete value;
         }
+        if (requestedAddress && !externalAddress.isValid()) {
+            for (int i = 0; i < games.size(); i++) {
+                const auto game = games[i];
+                if (game->getCreator() == getUsername() && game->getMap() == "udp") {
+                    // our fake game, remove and break;
+                    games.erase(games.begin() + i);
+                    externalAddress = game->getHost();
+                    DebugPrint("My external address is %s\n" _C_ externalAddress.toString().c_str());
+                    stopAdvertising();
+                    break;
+                }
+            }
+        }
         this->games = games;
         if (SetGames != NULL) {
           SetGames->pushPreamble();
@@ -1075,24 +1100,6 @@ public:
     std::queue<std::string> *getInfo() { return &info; }
 
     void showInfo(std::string arg) {
-        if (requestedAddress) {
-            // we have requested our external address from the server
-            DebugPrint("Requested Address Info: %s\n" _C_ arg.c_str());
-            if (arg.find("Client UDP: ") != std::string::npos) {
-                unsigned int a, b, c, d, ip, port;
-                unsigned char prefix[256]; // longer than any BNet message can be
-                int res = sscanf(arg.c_str(), "%s %s %d.%d.%d.%d:%d", prefix, prefix, &a, &b, &c, &d, &port);
-                if (res == 7 && a < 255 && b < 255 && c < 255 && d < 255 && port > 1024) {
-                    ip = a | b << 8 | c << 16 | d << 24;
-                    externalAddress = CHost(ip, port);
-                    DebugPrint("My external address is %s\n" _C_ externalAddress.toString().c_str());
-                }
-            }
-            if (arg.find("Game UDP: ") != std::string::npos) {
-                // this is the last line in the /netinfo response
-                requestedAddress = false;
-            }
-        }
         std::string infoStr = arg;
         info.push(infoStr);
         if (ShowInfo != NULL) {
@@ -1347,8 +1354,8 @@ public:
             ctx->refreshChannels();
         }
 
-        if ((ticks % 1000) == 0) {
-            // ~1000 frames @ ~50fps ~= 20 seconds
+        if ((ticks % 500) == 0) {
+            // ~1000 frames @ ~50fps ~= 10 seconds
             ctx->refreshGames();
         }
 
@@ -1450,7 +1457,7 @@ private:
 
 
     void handleFriendlist(Context *ctx) {
-        uint32_t cnt = ctx->getMsgIStream()->read32();
+        uint8_t cnt = ctx->getMsgIStream()->read8();
         std::vector<Friend*> friends;
         while (cnt--) {
             std::string user = ctx->getMsgIStream()->readString();
@@ -1540,7 +1547,7 @@ private:
             ctx->showInfo("Channel restricted");
             break;
         case 0x12: // general info text
-            ctx->showInfo("~<" + text + "~>");
+            ctx->showInfo(text);
             break;
         case 0x13: // error message
             ctx->showError(text);
@@ -1587,6 +1594,7 @@ class S2C_ENTERCHAT : public NetworkState {
             }
 
             ctx->requestExtraUserInfo(ctx->getUsername());
+            ctx->requestExternalAddress();
 
             ctx->setState(new S2C_CHATEVENT());
         }
@@ -1610,8 +1618,6 @@ class C2S_ENTERCHAT : public NetworkState {
         join.flush(ctx->getTCPSocket());
         DebugPrint("TCP Sent: 0x0c JOINCHANNEL\n");
 
-        ctx->refreshChannels();
-
         // TODO: maybe send 0x45 SID_NETGAMEPORT to report our gameport on pvpgn
         // to whatever the user specified on the cmdline?