suyu/src/yuzu/multiplayer/host_room.cpp

246 lines
10 KiB
C++

// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <future>
#include <QColor>
#include <QImage>
#include <QList>
#include <QLocale>
#include <QMessageBox>
#include <QMetaType>
#include <QTime>
#include <QtConcurrent/QtConcurrentRun>
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/announce_multiplayer_session.h"
#include "ui_host_room.h"
#include "yuzu/game_list_p.h"
#include "yuzu/main.h"
#include "yuzu/multiplayer/host_room.h"
#include "yuzu/multiplayer/message.h"
#include "yuzu/multiplayer/state.h"
#include "yuzu/multiplayer/validation.h"
#include "yuzu/uisettings.h"
#ifdef ENABLE_WEB_SERVICE
#include "web_service/verify_user_jwt.h"
#endif
HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
std::shared_ptr<Core::AnnounceMultiplayerSession> session,
Network::RoomNetwork& room_network_)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
ui(std::make_unique<Ui::HostRoom>()),
announce_multiplayer_session(session), room_network{room_network_} {
ui->setupUi(this);
// set up validation for all of the fields
ui->room_name->setValidator(validation.GetRoomName());
ui->username->setValidator(validation.GetNickname());
ui->port->setValidator(validation.GetPort());
ui->port->setPlaceholderText(QString::number(Network::DefaultRoomPort));
// Create a proxy to the game list to display the list of preferred games
game_list = new QStandardItemModel;
UpdateGameList(list);
proxy = new ComboBoxProxyModel;
proxy->setSourceModel(game_list);
proxy->sort(0, Qt::AscendingOrder);
ui->game_list->setModel(proxy);
// Connect all the widgets to the appropriate events
connect(ui->host, &QPushButton::clicked, this, &HostRoomWindow::Host);
// Restore the settings:
ui->username->setText(UISettings::values.multiplayer_room_nickname.GetValue());
if (ui->username->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
// Use yuzu Web Service user name as nickname by default
ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
}
ui->room_name->setText(UISettings::values.multiplayer_room_name.GetValue());
ui->port->setText(QString::number(UISettings::values.multiplayer_room_port.GetValue()));
ui->max_player->setValue(UISettings::values.multiplayer_max_player.GetValue());
int index = UISettings::values.multiplayer_host_type.GetValue();
if (index < ui->host_type->count()) {
ui->host_type->setCurrentIndex(index);
}
index = ui->game_list->findData(UISettings::values.multiplayer_game_id.GetValue(),
GameListItemPath::ProgramIdRole);
if (index != -1) {
ui->game_list->setCurrentIndex(index);
}
ui->room_description->setText(UISettings::values.multiplayer_room_description.GetValue());
}
HostRoomWindow::~HostRoomWindow() = default;
void HostRoomWindow::UpdateGameList(QStandardItemModel* list) {
game_list->clear();
for (int i = 0; i < list->rowCount(); i++) {
auto parent = list->item(i, 0);
for (int j = 0; j < parent->rowCount(); j++) {
game_list->appendRow(parent->child(j)->clone());
}
}
}
void HostRoomWindow::RetranslateUi() {
ui->retranslateUi(this);
}
std::unique_ptr<Network::VerifyUser::Backend> HostRoomWindow::CreateVerifyBackend(
bool use_validation) const {
std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
if (use_validation) {
#ifdef ENABLE_WEB_SERVICE
verify_backend =
std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue());
#else
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
#endif
} else {
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
}
return verify_backend;
}
void HostRoomWindow::Host() {
if (!ui->username->hasAcceptableInput()) {
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
return;
}
if (!ui->room_name->hasAcceptableInput()) {
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOMNAME_NOT_VALID);
return;
}
if (!ui->port->hasAcceptableInput()) {
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID);
return;
}
if (ui->game_list->currentIndex() == -1) {
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::GAME_NOT_SELECTED);
return;
}
if (auto member = room_network.GetRoomMember().lock()) {
if (member->GetState() == Network::RoomMember::State::Joining) {
return;
} else if (member->IsConnected()) {
auto parent = static_cast<MultiplayerState*>(parentWidget());
if (!parent->OnCloseRoom()) {
close();
return;
}
}
ui->host->setDisabled(true);
const AnnounceMultiplayerRoom::GameInfo game{
.name = ui->game_list->currentData(Qt::DisplayRole).toString().toStdString(),
.id = ui->game_list->currentData(GameListItemPath::ProgramIdRole).toULongLong(),
};
const auto port =
ui->port->isModified() ? ui->port->text().toInt() : Network::DefaultRoomPort;
const auto password = ui->password->text().toStdString();
const bool is_public = ui->host_type->currentIndex() == 0;
Network::Room::BanList ban_list{};
if (ui->load_ban_list->isChecked()) {
ban_list = UISettings::values.multiplayer_ban_list;
}
if (auto room = room_network.GetRoom().lock()) {
const bool created =
room->Create(ui->room_name->text().toStdString(),
ui->room_description->toPlainText().toStdString(), "", port, password,
ui->max_player->value(), Settings::values.yuzu_username.GetValue(),
game, CreateVerifyBackend(is_public), ban_list);
if (!created) {
NetworkMessage::ErrorManager::ShowError(
NetworkMessage::ErrorManager::COULD_NOT_CREATE_ROOM);
LOG_ERROR(Network, "Could not create room!");
ui->host->setEnabled(true);
return;
}
}
// Start the announce session if they chose Public
if (is_public) {
if (auto session = announce_multiplayer_session.lock()) {
// Register the room first to ensure verify_uid is present when we connect
WebService::WebResult result = session->Register();
if (result.result_code != WebService::WebResult::Code::Success) {
QMessageBox::warning(
this, tr("Error"),
tr("Failed to announce the room to the public lobby. In order to host a "
"room publicly, you must have a valid yuzu account configured in "
"Emulation -> Configure -> Web. If you do not want to publish a room in "
"the public lobby, then select Unlisted instead.\nDebug Message: ") +
QString::fromStdString(result.result_string),
QMessageBox::Ok);
ui->host->setEnabled(true);
if (auto room = room_network.GetRoom().lock()) {
room->Destroy();
}
return;
}
session->Start();
} else {
LOG_ERROR(Network, "Starting announce session failed");
}
}
std::string token;
#ifdef ENABLE_WEB_SERVICE
if (is_public) {
WebService::Client client(Settings::values.web_api_url.GetValue(),
Settings::values.yuzu_username.GetValue(),
Settings::values.yuzu_token.GetValue());
if (auto room = room_network.GetRoom().lock()) {
token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
}
if (token.empty()) {
LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
} else {
LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
}
}
#endif
// TODO: Check what to do with this
member->Join(ui->username->text().toStdString(), "", "127.0.0.1", port, 0,
Network::NoPreferredMac, password, token);
// Store settings
UISettings::values.multiplayer_room_nickname = ui->username->text();
UISettings::values.multiplayer_room_name = ui->room_name->text();
UISettings::values.multiplayer_game_id =
ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong();
UISettings::values.multiplayer_max_player = ui->max_player->value();
UISettings::values.multiplayer_host_type = ui->host_type->currentIndex();
if (ui->port->isModified() && !ui->port->text().isEmpty()) {
UISettings::values.multiplayer_room_port = ui->port->text().toInt();
} else {
UISettings::values.multiplayer_room_port = Network::DefaultRoomPort;
}
UISettings::values.multiplayer_room_description = ui->room_description->toPlainText();
ui->host->setEnabled(true);
close();
}
}
QVariant ComboBoxProxyModel::data(const QModelIndex& idx, int role) const {
if (role != Qt::DisplayRole) {
auto val = QSortFilterProxyModel::data(idx, role);
// If its the icon, shrink it to 16x16
if (role == Qt::DecorationRole)
val = val.value<QImage>().scaled(16, 16, Qt::KeepAspectRatio);
return val;
}
std::string filename;
Common::SplitPath(
QSortFilterProxyModel::data(idx, GameListItemPath::FullPathRole).toString().toStdString(),
nullptr, &filename, nullptr);
QString title = QSortFilterProxyModel::data(idx, GameListItemPath::TitleRole).toString();
return title.isEmpty() ? QString::fromStdString(filename) : title;
}
bool ComboBoxProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
auto leftData = left.data(GameListItemPath::TitleRole).toString();
auto rightData = right.data(GameListItemPath::TitleRole).toString();
return leftData.compare(rightData) < 0;
}