From 3a18039c53ca2bc8c90bf8efb0dda969575e6394 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Fri, 8 Nov 2019 22:38:48 -0500
Subject: [PATCH 1/2] citra_qt: configure_web: Use Base64 encoded token for
 simplifying user experience.

---
 src/citra_qt/configuration/configure_web.cpp | 74 ++++++++++++++------
 src/citra_qt/configuration/configure_web.ui  | 12 +---
 2 files changed, 55 insertions(+), 31 deletions(-)

diff --git a/src/citra_qt/configuration/configure_web.cpp b/src/citra_qt/configuration/configure_web.cpp
index 2ad70ce8b..cc7ca675f 100644
--- a/src/citra_qt/configuration/configure_web.cpp
+++ b/src/citra_qt/configuration/configure_web.cpp
@@ -11,6 +11,31 @@
 #include "core/telemetry_session.h"
 #include "ui_configure_web.h"
 
+static constexpr char token_delimiter{':'};
+
+static std::string GenerateDisplayToken(const std::string& username, const std::string& token) {
+    if (username.empty() || token.empty()) {
+        return {};
+    }
+
+    const std::string unencoded_display_token{username + token_delimiter + token};
+    QByteArray b{unencoded_display_token.c_str()};
+    QByteArray b64 = b.toBase64();
+    return b64.toStdString();
+}
+
+static std::string UsernameFromDisplayToken(const std::string& display_token) {
+    const std::string unencoded_display_token{
+        QByteArray::fromBase64(display_token.c_str()).toStdString()};
+    return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter));
+}
+
+static std::string TokenFromDisplayToken(const std::string& display_token) {
+    const std::string unencoded_display_token{
+        QByteArray::fromBase64(display_token.c_str()).toStdString()};
+    return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1);
+}
+
 ConfigureWeb::ConfigureWeb(QWidget* parent)
     : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
     ui->setupUi(this);
@@ -46,11 +71,18 @@ void ConfigureWeb::SetConfiguration() {
            "underline; color:#039be5;\">What is my token?</span></a>"));
 
     ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
-    ui->edit_username->setText(QString::fromStdString(Settings::values.citra_username));
-    ui->edit_token->setText(QString::fromStdString(Settings::values.citra_token));
+
+    if (Settings::values.citra_username.empty()) {
+        ui->username->setText(tr("Unspecified"));
+    } else {
+        ui->username->setText(QString::fromStdString(Settings::values.citra_username));
+    }
+
+    ui->edit_token->setText(QString::fromStdString(
+        GenerateDisplayToken(Settings::values.citra_username, Settings::values.citra_token)));
+
     // Connect after setting the values, to avoid calling OnLoginChanged now
     connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
-    connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
     ui->label_telemetry_id->setText(
         tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
     user_verified = true;
@@ -62,12 +94,13 @@ void ConfigureWeb::ApplyConfiguration() {
     Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
     UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
     if (user_verified) {
-        Settings::values.citra_username = ui->edit_username->text().toStdString();
-        Settings::values.citra_token = ui->edit_token->text().toStdString();
+        Settings::values.citra_username =
+            UsernameFromDisplayToken(ui->edit_token->text().toStdString());
+        Settings::values.citra_token = TokenFromDisplayToken(ui->edit_token->text().toStdString());
     } else {
-        QMessageBox::warning(this, tr("Username and token not verified"),
-                             tr("Username and token were not verified. The changes to your "
-                                "username and/or token have not been saved."));
+        QMessageBox::warning(
+            this, tr("Token not verified"),
+            tr("Token was not verified. The change to your token has not been saved."));
     }
 }
 
@@ -78,17 +111,15 @@ void ConfigureWeb::RefreshTelemetryID() {
 }
 
 void ConfigureWeb::OnLoginChanged() {
-    if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) {
+    if (ui->edit_token->text().isEmpty()) {
         user_verified = true;
 
         const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
-        ui->label_username_verified->setPixmap(pixmap);
         ui->label_token_verified->setPixmap(pixmap);
     } else {
         user_verified = false;
 
         const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
-        ui->label_username_verified->setPixmap(pixmap);
         ui->label_token_verified->setPixmap(pixmap);
     }
 }
@@ -96,10 +127,11 @@ void ConfigureWeb::OnLoginChanged() {
 void ConfigureWeb::VerifyLogin() {
     ui->button_verify_login->setDisabled(true);
     ui->button_verify_login->setText(tr("Verifying..."));
-    verify_watcher.setFuture(QtConcurrent::run([username = ui->edit_username->text().toStdString(),
-                                                token = ui->edit_token->text().toStdString()] {
-        return Core::VerifyLogin(username, token);
-    }));
+    verify_watcher.setFuture(QtConcurrent::run(
+        [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()),
+         token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] {
+            return Core::VerifyLogin(username, token);
+        }));
 }
 
 void ConfigureWeb::OnLoginVerified() {
@@ -109,16 +141,16 @@ void ConfigureWeb::OnLoginVerified() {
         user_verified = true;
 
         const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
-        ui->label_username_verified->setPixmap(pixmap);
         ui->label_token_verified->setPixmap(pixmap);
+        ui->username->setText(
+            QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
     } else {
         const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
-        ui->label_username_verified->setPixmap(pixmap);
         ui->label_token_verified->setPixmap(pixmap);
-        QMessageBox::critical(
-            this, tr("Verification failed"),
-            tr("Verification failed. Check that you have entered your username and token "
-               "correctly, and that your internet connection is working."));
+        ui->username->setText(tr("Unspecified"));
+        QMessageBox::critical(this, tr("Verification failed"),
+                              tr("Verification failed. Check that you have entered your token "
+                                 "correctly, and that your internet connection is working."));
     }
 }
 
diff --git a/src/citra_qt/configuration/configure_web.ui b/src/citra_qt/configuration/configure_web.ui
index ab3559157..372297dec 100644
--- a/src/citra_qt/configuration/configure_web.ui
+++ b/src/citra_qt/configuration/configure_web.ui
@@ -55,11 +55,7 @@
            </widget>
           </item>
           <item row="0" column="1" colspan="3">
-           <widget class="QLineEdit" name="edit_username">
-            <property name="maxLength">
-             <number>36</number>
-            </property>
-           </widget>
+           <widget class="QLabel" name="username" />
           </item>
           <item row="1" column="0">
            <widget class="QLabel" name="label_token">
@@ -79,14 +75,10 @@
             </property>
            </widget>
           </item>
-          <item row="0" column="4">
-           <widget class="QLabel" name="label_username_verified">
-           </widget>
-          </item>
           <item row="1" column="1" colspan="3">
            <widget class="QLineEdit" name="edit_token">
             <property name="maxLength">
-             <number>36</number>
+             <number>80</number>
             </property>
             <property name="echoMode">
              <enum>QLineEdit::Password</enum>

From a1544d8669b53391167ef23b5b0f7fbc84449b52 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Sat, 9 Nov 2019 02:42:37 -0500
Subject: [PATCH 2/2] dedicated_room: Support single Base64 token.

---
 src/dedicated_room/CMakeLists.txt |  2 +-
 src/dedicated_room/citra-room.cpp | 40 ++++++++++++++++++++++++-------
 2 files changed, 32 insertions(+), 10 deletions(-)

diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt
index 2492bc523..2b91daa70 100644
--- a/src/dedicated_room/CMakeLists.txt
+++ b/src/dedicated_room/CMakeLists.txt
@@ -13,7 +13,7 @@ if (ENABLE_WEB_SERVICE)
     target_link_libraries(citra-room PRIVATE web_service)
 endif()
 
-target_link_libraries(citra-room PRIVATE glad)
+target_link_libraries(citra-room PRIVATE cryptopp glad)
 if (MSVC)
     target_link_libraries(citra-room PRIVATE getopt)
 endif()
diff --git a/src/dedicated_room/citra-room.cpp b/src/dedicated_room/citra-room.cpp
index 3ba3972ac..ea9262046 100644
--- a/src/dedicated_room/citra-room.cpp
+++ b/src/dedicated_room/citra-room.cpp
@@ -9,6 +9,7 @@
 #include <regex>
 #include <string>
 #include <thread>
+#include <cryptopp/base64.h>
 #include <glad/glad.h>
 
 #ifdef _WIN32
@@ -66,6 +67,24 @@ static void PrintVersion() {
 /// The magic text at the beginning of a citra-room ban list file.
 static constexpr char BanListMagic[] = "CitraRoom-BanList-1";
 
+static constexpr char token_delimiter{':'};
+
+static std::string UsernameFromDisplayToken(const std::string& display_token) {
+    std::string unencoded_display_token;
+    CryptoPP::StringSource ss(
+        display_token, true,
+        new CryptoPP::Base64Decoder(new CryptoPP::StringSink(unencoded_display_token)));
+    return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter));
+}
+
+static std::string TokenFromDisplayToken(const std::string& display_token) {
+    std::string unencoded_display_token;
+    CryptoPP::StringSource ss(
+        display_token, true,
+        new CryptoPP::Base64Decoder(new CryptoPP::StringSink(unencoded_display_token)));
+    return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1);
+}
+
 static Network::Room::BanList LoadBanList(const std::string& path) {
     std::ifstream file;
     OpenFStream(file, path, std::ios_base::in);
@@ -158,7 +177,7 @@ int main(int argc, char** argv) {
         {"password", required_argument, 0, 'w'},
         {"preferred-game", required_argument, 0, 'g'},
         {"preferred-game-id", required_argument, 0, 'i'},
-        {"username", required_argument, 0, 'u'},
+        {"username", optional_argument, 0, 'u'},
         {"token", required_argument, 0, 't'},
         {"web-api-url", required_argument, 0, 'a'},
         {"ban-list-file", required_argument, 0, 'b'},
@@ -248,10 +267,6 @@ int main(int argc, char** argv) {
                      "list.\nSet with --ban-list-file <file>\n\n";
     }
     bool announce = true;
-    if (username.empty()) {
-        announce = false;
-        std::cout << "username is empty: Hosting a private room\n\n";
-    }
     if (token.empty() && announce) {
         announce = false;
         std::cout << "token is empty: Hosting a private room\n\n";
@@ -261,10 +276,17 @@ int main(int argc, char** argv) {
         std::cout << "endpoint url is empty: Hosting a private room\n\n";
     }
     if (announce) {
-        std::cout << "Hosting a public room\n\n";
-        Settings::values.web_api_url = web_api_url;
-        Settings::values.citra_username = username;
-        Settings::values.citra_token = token;
+        if (username.empty()) {
+            std::cout << "Hosting a public room\n\n";
+            Settings::values.web_api_url = web_api_url;
+            Settings::values.citra_username = UsernameFromDisplayToken(token);
+            Settings::values.citra_token = TokenFromDisplayToken(token);
+        } else {
+            std::cout << "Hosting a public room\n\n";
+            Settings::values.web_api_url = web_api_url;
+            Settings::values.citra_username = username;
+            Settings::values.citra_token = token;
+        }
     }
     if (!announce && enable_citra_mods) {
         enable_citra_mods = false;