From 83ef84815689632de732f35c55c398999e2a18e6 Mon Sep 17 00:00:00 2001
From: Tim Felgentreff <timfelgentreff@gmail.com>
Date: Thu, 26 Nov 2020 16:19:55 +0100
Subject: [PATCH] request our own external address in the beginning so we can
 udp punch without help

---
 src/network/online_service.cpp | 61 ++++++++++++++++++++++++----------
 1 file changed, 44 insertions(+), 17 deletions(-)

diff --git a/src/network/online_service.cpp b/src/network/online_service.cpp
index 8a59a5215..d64568e39 100644
--- a/src/network/online_service.cpp
+++ b/src/network/online_service.cpp
@@ -728,7 +728,7 @@ public:
         setPassword("");
     }
 
-    void sendText(std::string txt) {
+    void sendText(std::string txt, bool silent = false) {
         // C>S 0x0E SID_CHATCOMMAND
         int pos = 0;
         for (unsigned int pos = 0; pos < txt.size(); pos += 220) {
@@ -740,7 +740,15 @@ public:
             msg.serialize(text.c_str());
             msg.flush(getTCPSocket());
         }
-        showChat(username + ": " + txt);
+        if (!silent) {
+            showChat(username + ": " + txt);
+        }
+    }
+
+    void requestExternalAddress() {
+        // uses the /netinfo command to get which ip:port the server sees from us
+        sendText("/netinfo", true);
+        requestedAddress = true;
     }
 
     void requestExtraUserInfo(std::string username) {
@@ -757,7 +765,9 @@ public:
     }
 
     void punchNAT(std::string username) {
-        sendText("/udppunch " + username);
+        if (externalAddress.isValid()) {
+            sendText("/whisper " + username + " /udppunch " + externalAddress.toString());
+        }
     }
 
     void refreshChannels() {
@@ -1049,6 +1059,30 @@ 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("Server TCP: ") != std::string::npos || arg.find("Client TCP: ") != std::string::npos) {
+                // ignore
+                return;
+            }
+            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());
+                }
+                return;
+            }
+            if (arg.find("Game UDP: ") != std::string::npos) {
+                // this is the last line in the /netinfo response
+                requestedAddress = false;
+                return;
+            }
+        }
         std::string infoStr = arg;
         info.push(infoStr);
         if (ShowInfo != NULL) {
@@ -1243,6 +1277,9 @@ private:
     bool hasPassword;
     bool createAccount;
 
+    bool requestedAddress = false;
+    CHost externalAddress;
+
     std::string lastError;
 
     std::string currentChannel;
@@ -1302,6 +1339,10 @@ public:
             ctx->refreshGames();
         }
 
+        if (ticks == 50) {
+            ctx->requestExternalAddress();
+        }
+
         ticks++;
 
         if (ctx->getTCPSocket()->HasDataToRead(0)) {
@@ -1531,8 +1572,6 @@ class S2C_ENTERCHAT : public NetworkState {
             }
 
             ctx->requestExtraUserInfo(ctx->getUsername());
-            // send again
-            ctx->sendUdpConnectionInfo();
 
             ctx->setState(new S2C_CHATEVENT());
         }
@@ -1691,18 +1730,6 @@ class S2C_LOGONRESPONSE2 : public NetworkState {
             }
         }
     }
-
-private:
-    void createAccount(Context *ctx) {
-        BNCSOutputStream msg(0x3d);
-        uint32_t *pw = ctx->getPassword1();
-        for (int i = 0; i < 20; i++) {
-            msg.serialize8(reinterpret_cast<uint8_t*>(pw)[i]);
-        }
-        msg.serialize(ctx->getUsername().c_str());
-        msg.flush(ctx->getTCPSocket());
-        ctx->setState(new S2C_CREATEACCOUNT2());
-    }
 };
 
 void C2S_LOGONRESPONSE2_OR_C2S_CREATEACCOUNT::doOneStep(Context *ctx) {