From 6a490c03832a2d48607a99b17309faa46db0fa80 Mon Sep 17 00:00:00 2001
From: twinaphex <libretro@gmail.com>
Date: Tue, 9 Nov 2021 02:52:04 +0100
Subject: [PATCH] (cthulhu88) Add netplay chat command

---
 network/netplay/netplay.h          |   9 +-
 network/netplay/netplay_frontend.c | 203 ++++++++++++++++++++++++++++-
 network/netplay/netplay_private.h  |  10 +-
 3 files changed, 218 insertions(+), 4 deletions(-)

diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h
index d8412a6b93..f9da0f5d8c 100644
--- a/network/netplay/netplay.h
+++ b/network/netplay/netplay.h
@@ -186,7 +186,7 @@ typedef struct
 {
    netplay_t *data; /* Used while Netplay is running */
    struct netplay_room host_room; /* ptr alignment */
-   netplay_t *handshake_password;
+   netplay_t *ref;
    struct netplay_room *room_list;
    struct netplay_rooms *rooms_data;
    /* List of discovered hosts */
@@ -243,6 +243,13 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused);
  */
 void netplay_toggle_play_spectate(netplay_t *netplay);
 
+/**
+ * netplay_input_chat
+ *
+ * Opens an input menu for sending netplay chat
+ */
+void netplay_input_chat(netplay_t *netplay);
+
 /**
  * netplay_load_savestate
  * @netplay              : pointer to netplay object
diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c
index 6c186b9e56..a227b9484d 100644
--- a/network/netplay/netplay_frontend.c
+++ b/network/netplay/netplay_frontend.c
@@ -91,6 +91,9 @@
 #define FULL_MAGIC    0x46554C4C /* FULL */
 #define POKE_MAGIC    0x504F4B45 /* POKE */
 
+#define MAX_CHAT_SIZE   256
+#define CHAT_FRAME_TIME 600
+
 /*
  * AD PACKET FORMAT:
  *
@@ -891,7 +894,7 @@ static void handshake_password(void *ignore, const char *line)
    char password[8+NETPLAY_PASS_LEN]; /* 8 for salt, 128 for password */
    char hash[NETPLAY_PASS_HASH_LEN+1]; /* + NULL terminator */
    net_driver_state_t *net_st            = &networking_driver_st;
-   netplay_t *netplay                    = net_st->handshake_password;
+   netplay_t *netplay                    = net_st->ref;
    struct netplay_connection *connection = &netplay->connections[0];
 
    snprintf(password, sizeof(password), "%08lX", (unsigned long)connection->salt);
@@ -1070,7 +1073,7 @@ bool netplay_handshake_init(netplay_t *netplay,
       retroarch_menu_running();
 #endif
 
-      net_st->handshake_password = netplay;
+      net_st->ref = netplay;
 
 #ifdef HAVE_MENU
       memset(&line, 0, sizeof(line));
@@ -4400,6 +4403,174 @@ static void handle_play_spectate(netplay_t *netplay,
    }
 }
 
+static bool chat_check(netplay_t *netplay)
+{
+   if (!netplay)
+      return false;
+
+   /* Do nothing if we don't have a nickname. */
+   if (!netplay->nick[0])
+      return false;
+
+   /* Do nothing if we are not playing. */
+   if (netplay->self_mode != NETPLAY_CONNECTION_PLAYING &&
+      netplay->self_mode != NETPLAY_CONNECTION_SLAVE)
+      return false;
+
+   /* If we are the server,
+      check if someone is able to read us. */
+   if (netplay->is_server)
+   {
+      size_t i;
+      for (i = 0; i < netplay->connections_size; i++)
+      {
+         struct netplay_connection *conn = &netplay->connections[i];
+         if (conn->active &&
+               (conn->mode == NETPLAY_CONNECTION_PLAYING ||
+                  conn->mode == NETPLAY_CONNECTION_SLAVE))
+            return true;
+      }
+   }
+   /* Otherwise, just check whether our connection is active. */
+   else
+   {
+      if (netplay->connections[0].active)
+         return true;
+   }
+
+   return false;
+}
+
+static size_t format_chat(char *buf, size_t bufsz,
+      const char *nick, const char *msg)
+{
+   /* Truncate the message if necessary. */
+   snprintf(buf, bufsz, "%s: %s", nick, msg);
+
+   return strlen(buf);
+}
+
+static void relay_chat(netplay_t *netplay, const char *msg, size_t len)
+{
+   size_t i;
+   for (i = 0; i < netplay->connections_size; i++)
+   {
+      struct netplay_connection *conn = &netplay->connections[i];
+      /* Only playing clients can receive chat. */
+      if (conn->active &&
+            (conn->mode == NETPLAY_CONNECTION_PLAYING ||
+               conn->mode == NETPLAY_CONNECTION_SLAVE))
+         netplay_send_raw_cmd(netplay, conn, NETPLAY_CMD_PLAYER_CHAT, msg, len);
+   }
+   /* We don't flush. Chat is not time essential. */
+}
+
+static void send_chat(void *userdata, const char *line)
+{
+   char   chat_msg[MAX_CHAT_SIZE];
+   size_t chat_len;
+   net_driver_state_t *net_st  = &networking_driver_st;
+   netplay_t          *netplay = net_st->ref;
+
+   /* We perform the same checks,
+      just in case something has changed. */
+   if (chat_check(netplay))
+   {
+      /* For servers, we need to format and relay it ourselves. */
+      if (netplay->is_server)
+      {
+         chat_len = format_chat(chat_msg, sizeof(chat_msg), netplay->nick, line);
+
+         relay_chat(netplay, chat_msg, chat_len);
+
+         RARCH_LOG("%s\n", chat_msg);
+         runloop_msg_queue_push(chat_msg, 1, CHAT_FRAME_TIME, false, NULL,
+            MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
+      }
+      /* For clients, we just send it to the server. */
+      else
+      {
+         strlcpy(chat_msg, line, sizeof(chat_msg));
+
+         netplay_send_raw_cmd(netplay, &netplay->connections[0],
+            NETPLAY_CMD_PLAYER_CHAT, chat_msg, strlen(chat_msg));
+         /* We don't flush. Chat is not time essential. */
+      }
+   }
+
+   menu_input_dialog_end();
+   retroarch_menu_running_finished(false);
+}
+
+void netplay_input_chat(netplay_t *netplay)
+{
+#if NETPLAY_PROTOCOL_VERSION >= 6 && defined(HAVE_MENU)
+   if (chat_check(netplay))
+   {
+      menu_input_ctx_line_t chat_input = {0};
+      net_driver_state_t    *net_st    = &networking_driver_st;
+
+      retroarch_menu_running();
+
+      net_st->ref = netplay;
+
+      chat_input.label         = msg_hash_to_str(MSG_NETPLAY_ENTER_CHAT);
+      chat_input.label_setting = "no_setting";
+      chat_input.cb            = send_chat;
+
+      menu_input_dialog_start(&chat_input);
+   }
+#endif
+}
+
+/**
+ * handle_chat
+ *
+ * Handle a received chat message
+ */
+static bool handle_chat(netplay_t *netplay,
+      struct netplay_connection *connection,
+      const char *msg)
+{
+   char   chat_msg[MAX_CHAT_SIZE];
+   size_t chat_len;
+
+   if (!connection->active || !msg[0])
+      return false;
+
+   /* Client sent a chat message;
+      format it and then relay it to the other clients,
+      including the one who sent it. */
+   if (netplay->is_server)
+   {
+      /* No point displaying a chat message without a nickname. */
+      if (!connection->nick[0])
+         return false;
+
+      /* Only playing clients can send chat. */
+      if (connection->mode != NETPLAY_CONNECTION_PLAYING &&
+         connection->mode != NETPLAY_CONNECTION_SLAVE)
+         return false;
+
+      chat_len = format_chat(chat_msg, sizeof(chat_msg), connection->nick, msg);
+      msg      = chat_msg;
+
+      relay_chat(netplay, chat_msg, chat_len);
+   }
+
+   /* If we still got a message even though we are not playing,
+      ignore it! */
+   if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING ||
+      netplay->self_mode == NETPLAY_CONNECTION_SLAVE)
+   {
+      RARCH_LOG("%s\n", msg);
+      runloop_msg_queue_push(msg, 1, CHAT_FRAME_TIME, false, NULL,
+         MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
+   }
+
+   return true;
+}
+
 #undef RECV
 #define RECV(buf, sz) \
 recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), \
@@ -5320,6 +5491,31 @@ static bool netplay_get_cmd(netplay_t *netplay,
             break;
          }
 
+      #if NETPLAY_PROTOCOL_VERSION >= 6
+      case NETPLAY_CMD_PLAYER_CHAT:
+         {
+            char payload[MAX_CHAT_SIZE];
+            /* We do not send the sentinel/null character on chat messages
+               and we do not allow empty messages. */
+            if (!cmd_size || cmd_size >= sizeof(payload))
+            {
+               RARCH_ERR("NETPLAY_CMD_PLAYER_CHAT with incorrect payload size.\n");
+               return netplay_cmd_nak(netplay, connection);
+            }
+
+            RECV(payload, cmd_size)
+               return false;
+            payload[recvd] = '\0';
+
+            if (!handle_chat(netplay, connection, payload))
+            {
+               RARCH_ERR("NETPLAY_CMD_PLAYER_CHAT with invalid message or from an invalid peer.\n");
+               return netplay_cmd_nak(netplay, connection);
+            }
+         }
+         break;
+      #endif
+
       default:
          {
             unsigned char buf[1024];
@@ -7580,3 +7776,6 @@ done:
    net_st->in_netplay = false;
    return ret;
 }
+
+#undef MAX_CHAT_SIZE
+#undef CHAT_FRAME_TIME
diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h
index a34ee2f654..00a9a2785f 100644
--- a/network/netplay/netplay_private.h
+++ b/network/netplay/netplay_private.h
@@ -175,7 +175,15 @@ enum netplay_cmd
    /* CMD_CFG streamlines sending multiple
       configurations. This acknowledges
       each one individually */
-   NETPLAY_CMD_CFG_ACK        = 0x0062
+   NETPLAY_CMD_CFG_ACK        = 0x0062,
+
+   /* Chat commands */
+
+   /* Sends a player chat message.
+    * The server is responsible for formatting/truncating 
+    * the message and relaying it to all playing clients,
+    * including the one that sent the message. */
+   NETPLAY_CMD_PLAYER_CHAT    = 0x1000
 };
 
 #define NETPLAY_CMD_SYNC_BIT_PAUSED    (1U<<31)