mirror of
https://git.citron-emu.org/citron/emulator
synced 2026-01-06 18:04:22 +00:00
Merge branch 'auto-token-generation' into 'main'
feat: auto-generate multiplayer tokens See merge request citron/emulator!106
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -177,6 +178,10 @@ void ConfigureProfileManager::UpdateCurrentUser() {
|
|||||||
scene->addPixmap(
|
scene->addPixmap(
|
||||||
GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||||
ui->current_user_username->setText(username);
|
ui->current_user_username->setText(username);
|
||||||
|
|
||||||
|
// Update the token username for web service configuration
|
||||||
|
// This will be processed by ConfigureWeb::ApplyConfiguration()
|
||||||
|
Settings::values.citron_username = username.toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureProfileManager::ApplyConfiguration() {
|
void ConfigureProfileManager::ApplyConfiguration() {
|
||||||
|
|||||||
@@ -1,47 +1,22 @@
|
|||||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QtConcurrent/QtConcurrentRun>
|
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
|
#include "common/uuid.h"
|
||||||
#include "core/telemetry_session.h"
|
#include "core/telemetry_session.h"
|
||||||
#include "ui_configure_web.h"
|
#include "ui_configure_web.h"
|
||||||
#include "citron/configuration/configure_web.h"
|
#include "citron/configuration/configure_web.h"
|
||||||
#include "citron/uisettings.h"
|
#include "citron/uisettings.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)
|
ConfigureWeb::ConfigureWeb(QWidget* parent)
|
||||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
|
: QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
|
connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
|
||||||
&ConfigureWeb::RefreshTelemetryID);
|
&ConfigureWeb::RefreshTelemetryID);
|
||||||
connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
|
connect(ui->button_reset_token, &QPushButton::clicked, this, &ConfigureWeb::ResetToken);
|
||||||
connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified);
|
|
||||||
|
|
||||||
#ifndef USE_DISCORD_PRESENCE
|
#ifndef USE_DISCORD_PRESENCE
|
||||||
ui->discord_group->setVisible(false);
|
ui->discord_group->setVisible(false);
|
||||||
@@ -68,24 +43,13 @@ void ConfigureWeb::RetranslateUI() {
|
|||||||
tr("<a href='https://citron-emu.org/help/feature/telemetry/'><span style=\"text-decoration: "
|
tr("<a href='https://citron-emu.org/help/feature/telemetry/'><span style=\"text-decoration: "
|
||||||
"underline; color:#039be5;\">Learn more</span></a>"));
|
"underline; color:#039be5;\">Learn more</span></a>"));
|
||||||
|
|
||||||
ui->web_signup_link->setText(
|
|
||||||
tr("<a href='https://profile.citron-emu.org/'><span style=\"text-decoration: underline; "
|
|
||||||
"color:#039be5;\">Sign up</span></a>"));
|
|
||||||
|
|
||||||
ui->web_token_info_link->setText(
|
|
||||||
tr("<a href='https://discord.gg/citron'><span style=\"text-decoration: "
|
|
||||||
"underline; color:#039be5;\">Get Support</span></a>"));
|
|
||||||
|
|
||||||
ui->label_telemetry_id->setText(
|
ui->label_telemetry_id->setText(
|
||||||
tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
|
tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureWeb::SetConfiguration() {
|
void ConfigureWeb::SetConfiguration() {
|
||||||
ui->web_credentials_disclaimer->setWordWrap(true);
|
ui->web_credentials_disclaimer->setWordWrap(true);
|
||||||
|
|
||||||
ui->telemetry_learn_more->setOpenExternalLinks(true);
|
ui->telemetry_learn_more->setOpenExternalLinks(true);
|
||||||
ui->web_signup_link->setOpenExternalLinks(true);
|
|
||||||
ui->web_token_info_link->setOpenExternalLinks(true);
|
|
||||||
|
|
||||||
if (Settings::values.citron_username.GetValue().empty()) {
|
if (Settings::values.citron_username.GetValue().empty()) {
|
||||||
ui->username->setText(tr("Unspecified"));
|
ui->username->setText(tr("Unspecified"));
|
||||||
@@ -94,28 +58,25 @@ void ConfigureWeb::SetConfiguration() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry.GetValue());
|
ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry.GetValue());
|
||||||
ui->edit_token->setText(QString::fromStdString(GenerateDisplayToken(
|
ui->edit_token->setText(QString::fromStdString(Settings::values.citron_token.GetValue()));
|
||||||
Settings::values.citron_username.GetValue(), Settings::values.citron_token.GetValue())));
|
|
||||||
|
|
||||||
// Connect after setting the values, to avoid calling OnLoginChanged now
|
|
||||||
connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
|
|
||||||
|
|
||||||
user_verified = true;
|
|
||||||
|
|
||||||
ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence.GetValue());
|
ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence.GetValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureWeb::ApplyConfiguration() {
|
void ConfigureWeb::ApplyConfiguration() {
|
||||||
Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
|
Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
|
||||||
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
|
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
|
||||||
if (user_verified) {
|
|
||||||
Settings::values.citron_username =
|
// Username is set from the profile manager via UpdateCurrentUser()
|
||||||
UsernameFromDisplayToken(ui->edit_token->text().toStdString());
|
// Use a default value if username is still empty
|
||||||
Settings::values.citron_token = TokenFromDisplayToken(ui->edit_token->text().toStdString());
|
if (Settings::values.citron_username.GetValue().empty()) {
|
||||||
|
Settings::values.citron_username = "citron";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-generate token if empty, otherwise use the user-provided value
|
||||||
|
if (ui->edit_token->text().isEmpty()) {
|
||||||
|
Settings::values.citron_token = Common::UUID::MakeRandom().FormattedString();
|
||||||
} else {
|
} else {
|
||||||
QMessageBox::warning(
|
Settings::values.citron_token = ui->edit_token->text().toStdString();
|
||||||
this, tr("Token not verified"),
|
|
||||||
tr("Token was not verified. The change to your token has not been saved."));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,53 +86,17 @@ void ConfigureWeb::RefreshTelemetryID() {
|
|||||||
tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper()));
|
tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureWeb::OnLoginChanged() {
|
void ConfigureWeb::ResetToken() {
|
||||||
if (ui->edit_token->text().isEmpty()) {
|
// Generate a new random token
|
||||||
user_verified = true;
|
const auto new_token = Common::UUID::MakeRandom().FormattedString();
|
||||||
// Empty = no icon
|
Settings::values.citron_token = new_token;
|
||||||
ui->label_token_verified->setPixmap(QPixmap());
|
|
||||||
ui->label_token_verified->setToolTip(QString());
|
|
||||||
} else {
|
|
||||||
user_verified = false;
|
|
||||||
|
|
||||||
// Show an info icon if it's been changed, clearer than showing failure
|
// Update the UI to show the new token
|
||||||
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("info")).pixmap(16);
|
ui->edit_token->setText(QString::fromStdString(new_token));
|
||||||
ui->label_token_verified->setPixmap(pixmap);
|
|
||||||
ui->label_token_verified->setToolTip(
|
|
||||||
tr("Unverified, please click Verify before saving configuration", "Tooltip"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigureWeb::VerifyLogin() {
|
// Show visual confirmation
|
||||||
ui->button_verify_login->setDisabled(true);
|
ui->label_token_icon->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16));
|
||||||
ui->button_verify_login->setText(tr("Verifying..."));
|
ui->label_token_icon->setToolTip(tr("Token reset successfully", "Tooltip"));
|
||||||
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16));
|
|
||||||
ui->label_token_verified->setToolTip(tr("Verifying..."));
|
|
||||||
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() {
|
|
||||||
ui->button_verify_login->setEnabled(true);
|
|
||||||
ui->button_verify_login->setText(tr("Verify"));
|
|
||||||
if (verify_watcher.result()) {
|
|
||||||
user_verified = true;
|
|
||||||
|
|
||||||
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16));
|
|
||||||
ui->label_token_verified->setToolTip(tr("Verified", "Tooltip"));
|
|
||||||
ui->username->setText(
|
|
||||||
QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
|
|
||||||
} else {
|
|
||||||
ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16));
|
|
||||||
ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip"));
|
|
||||||
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."));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) {
|
void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <QFutureWatcher>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
@@ -26,14 +26,9 @@ private:
|
|||||||
void RetranslateUI();
|
void RetranslateUI();
|
||||||
|
|
||||||
void RefreshTelemetryID();
|
void RefreshTelemetryID();
|
||||||
void OnLoginChanged();
|
void ResetToken();
|
||||||
void VerifyLogin();
|
|
||||||
void OnLoginVerified();
|
|
||||||
|
|
||||||
void SetConfiguration();
|
void SetConfiguration();
|
||||||
|
|
||||||
bool user_verified = true;
|
|
||||||
QFutureWatcher<bool> verify_watcher;
|
|
||||||
|
|
||||||
std::unique_ptr<Ui::ConfigureWeb> ui;
|
std::unique_ptr<Ui::ConfigureWeb> ui;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,14 +28,14 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="web_credentials_disclaimer">
|
<widget class="QLabel" name="web_credentials_disclaimer">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>By providing your username and token, you agree to allow citron to collect additional usage data, which may include user identifying information.</string>
|
<string>This token is for hosting public rooms in a lobby and is automatically generated the first time you save settings. To change your username, rename or switch the profile. If you are currently connected to multiplayer, exit and restart the emulator for changes to apply.</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QGridLayout" name="gridLayoutCitronUsername">
|
<layout class="QGridLayout" name="gridLayoutCitronUsername">
|
||||||
<item row="2" column="3">
|
<item row="2" column="3">
|
||||||
<widget class="QPushButton" name="button_verify_login">
|
<widget class="QPushButton" name="button_reset_token">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
@@ -46,12 +46,15 @@
|
|||||||
<enum>Qt::RightToLeft</enum>
|
<enum>Qt::RightToLeft</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Verify</string>
|
<string>Reset Token</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="web_signup_link">
|
<widget class="QLabel" name="web_signup_link">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Sign up</string>
|
<string>Sign up</string>
|
||||||
</property>
|
</property>
|
||||||
@@ -68,7 +71,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="4">
|
<item row="1" column="4">
|
||||||
<widget class="QLabel" name="label_token_verified"/>
|
<widget class="QLabel" name="label_token_icon"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_username">
|
<widget class="QLabel" name="label_username">
|
||||||
@@ -82,13 +85,13 @@
|
|||||||
<property name="maxLength">
|
<property name="maxLength">
|
||||||
<number>80</number>
|
<number>80</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="echoMode">
|
|
||||||
<enum>QLineEdit::Password</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QLabel" name="web_token_info_link">
|
<widget class="QLabel" name="web_token_info_link">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>What is my token?</string>
|
<string>What is my token?</string>
|
||||||
</property>
|
</property>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <future>
|
#include <future>
|
||||||
@@ -183,10 +184,10 @@ void HostRoomWindow::Host() {
|
|||||||
if (result.result_code != WebService::WebResult::Code::Success) {
|
if (result.result_code != WebService::WebResult::Code::Success) {
|
||||||
QMessageBox::warning(
|
QMessageBox::warning(
|
||||||
this, tr("Error"),
|
this, tr("Error"),
|
||||||
tr("Failed to announce the room to the public lobby. In order to host a "
|
tr("Failed to announce the room to the public lobby. To host a room "
|
||||||
"room publicly, you must have a valid citron account configured in "
|
"publicly, you must have a generated token configured in "
|
||||||
"Emulation -> Configure -> Web. If you do not want to publish a room in "
|
"Emulation -> Configure -> Web. If you do not want to publish a room "
|
||||||
"the public lobby, then select Unlisted instead.\nDebug Message: ") +
|
"in a public lobby, then select Unlisted instead.\n\nDebug Message: ") +
|
||||||
QString::fromStdString(result.result_string),
|
QString::fromStdString(result.result_string),
|
||||||
QMessageBox::Ok);
|
QMessageBox::Ok);
|
||||||
ui->host->setEnabled(true);
|
ui->host->setEnabled(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user