2018-01-19 14:42:21 +01:00
|
|
|
// Copyright 2017 Citra Emulator Project
|
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
#include <future>
|
|
|
|
#include <QColor>
|
|
|
|
#include <QImage>
|
|
|
|
#include <QList>
|
|
|
|
#include <QLocale>
|
2019-04-19 16:55:49 +02:00
|
|
|
#include <QMessageBox>
|
2018-01-19 14:42:21 +01:00
|
|
|
#include <QMetaType>
|
|
|
|
#include <QTime>
|
|
|
|
#include <QtConcurrent/QtConcurrentRun>
|
|
|
|
#include "citra_qt/game_list_p.h"
|
|
|
|
#include "citra_qt/main.h"
|
|
|
|
#include "citra_qt/multiplayer/host_room.h"
|
|
|
|
#include "citra_qt/multiplayer/message.h"
|
2018-04-01 08:06:48 +02:00
|
|
|
#include "citra_qt/multiplayer/state.h"
|
2018-01-19 14:42:21 +01:00
|
|
|
#include "citra_qt/multiplayer/validation.h"
|
|
|
|
#include "citra_qt/ui_settings.h"
|
|
|
|
#include "common/logging/log.h"
|
|
|
|
#include "core/announce_multiplayer_session.h"
|
2018-10-31 16:07:03 +01:00
|
|
|
#include "core/hle/service/cfg/cfg.h"
|
2018-01-19 14:42:21 +01:00
|
|
|
#include "core/settings.h"
|
2018-04-18 18:29:03 +02:00
|
|
|
#include "ui_host_room.h"
|
2018-10-27 09:49:00 +02:00
|
|
|
#ifdef ENABLE_WEB_SERVICE
|
|
|
|
#include "web_service/verify_user_jwt.h"
|
|
|
|
#endif
|
2018-01-19 14:42:21 +01:00
|
|
|
|
|
|
|
HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
|
|
|
|
std::shared_ptr<Core::AnnounceMultiplayerSession> session)
|
|
|
|
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
2018-05-22 03:53:48 +02:00
|
|
|
ui(std::make_unique<Ui::HostRoom>()), announce_multiplayer_session(session) {
|
2018-01-19 14:42:21 +01:00
|
|
|
ui->setupUi(this);
|
|
|
|
|
|
|
|
// set up validation for all of the fields
|
2018-04-18 18:29:03 +02:00
|
|
|
ui->room_name->setValidator(validation.GetRoomName());
|
|
|
|
ui->username->setValidator(validation.GetNickname());
|
|
|
|
ui->port->setValidator(validation.GetPort());
|
2018-01-19 14:42:21 +01:00
|
|
|
ui->port->setPlaceholderText(QString::number(Network::DefaultRoomPort));
|
|
|
|
|
|
|
|
// Create a proxy to the game list to display the list of preferred games
|
2018-05-22 03:53:48 +02:00
|
|
|
game_list = new QStandardItemModel;
|
2019-04-20 06:38:19 +02:00
|
|
|
UpdateGameList(list);
|
2018-05-22 03:53:48 +02:00
|
|
|
|
2018-01-19 14:42:21 +01:00
|
|
|
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
|
2019-07-22 23:28:10 +02:00
|
|
|
connect(ui->host, &QPushButton::clicked, this, &HostRoomWindow::Host);
|
2018-01-19 14:42:21 +01:00
|
|
|
|
|
|
|
// Restore the settings:
|
|
|
|
ui->username->setText(UISettings::values.room_nickname);
|
2018-07-17 09:25:00 +02:00
|
|
|
if (ui->username->text().isEmpty() && !Settings::values.citra_username.empty()) {
|
|
|
|
// Use Citra Web Service user name as nickname by default
|
|
|
|
ui->username->setText(QString::fromStdString(Settings::values.citra_username));
|
|
|
|
}
|
2018-01-19 14:42:21 +01:00
|
|
|
ui->room_name->setText(UISettings::values.room_name);
|
|
|
|
ui->port->setText(UISettings::values.room_port);
|
|
|
|
ui->max_player->setValue(UISettings::values.max_player);
|
2018-04-18 07:06:02 +02:00
|
|
|
int index = UISettings::values.host_type;
|
|
|
|
if (index < ui->host_type->count()) {
|
2018-01-19 14:42:21 +01:00
|
|
|
ui->host_type->setCurrentIndex(index);
|
|
|
|
}
|
|
|
|
index = ui->game_list->findData(UISettings::values.game_id, GameListItemPath::ProgramIdRole);
|
|
|
|
if (index != -1) {
|
|
|
|
ui->game_list->setCurrentIndex(index);
|
|
|
|
}
|
2018-04-30 09:40:51 +02:00
|
|
|
ui->room_description->setText(UISettings::values.room_description);
|
2018-01-19 14:42:21 +01:00
|
|
|
}
|
|
|
|
|
2018-04-18 18:29:03 +02:00
|
|
|
HostRoomWindow::~HostRoomWindow() = default;
|
|
|
|
|
2019-04-20 06:38:19 +02:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-09 17:08:33 +02:00
|
|
|
void HostRoomWindow::RetranslateUi() {
|
|
|
|
ui->retranslateUi(this);
|
|
|
|
}
|
|
|
|
|
2018-10-27 09:49:00 +02:00
|
|
|
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);
|
|
|
|
#else
|
|
|
|
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
|
|
|
|
}
|
|
|
|
return verify_backend;
|
|
|
|
}
|
|
|
|
|
2018-01-19 14:42:21 +01:00
|
|
|
void HostRoomWindow::Host() {
|
|
|
|
if (!ui->username->hasAcceptableInput()) {
|
|
|
|
NetworkMessage::ShowError(NetworkMessage::USERNAME_NOT_VALID);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!ui->room_name->hasAcceptableInput()) {
|
|
|
|
NetworkMessage::ShowError(NetworkMessage::ROOMNAME_NOT_VALID);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!ui->port->hasAcceptableInput()) {
|
|
|
|
NetworkMessage::ShowError(NetworkMessage::PORT_NOT_VALID);
|
|
|
|
return;
|
|
|
|
}
|
2019-04-19 17:12:30 +02:00
|
|
|
if (ui->game_list->currentIndex() == -1) {
|
|
|
|
NetworkMessage::ShowError(NetworkMessage::GAME_NOT_SELECTED);
|
|
|
|
return;
|
|
|
|
}
|
2018-01-19 14:42:21 +01:00
|
|
|
if (auto member = Network::GetRoomMember().lock()) {
|
2018-04-20 08:14:44 +02:00
|
|
|
if (member->GetState() == Network::RoomMember::State::Joining) {
|
|
|
|
return;
|
2018-12-15 10:13:46 +01:00
|
|
|
} else if (member->IsConnected()) {
|
2018-04-18 19:04:32 +02:00
|
|
|
auto parent = static_cast<MultiplayerState*>(parentWidget());
|
|
|
|
if (!parent->OnCloseRoom()) {
|
2018-01-19 14:42:21 +01:00
|
|
|
close();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ui->host->setDisabled(true);
|
|
|
|
|
|
|
|
auto game_name = ui->game_list->currentData(Qt::DisplayRole).toString();
|
|
|
|
auto game_id = ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong();
|
|
|
|
auto port = ui->port->isModified() ? ui->port->text().toInt() : Network::DefaultRoomPort;
|
|
|
|
auto password = ui->password->text().toStdString();
|
2018-10-27 09:49:00 +02:00
|
|
|
const bool is_public = ui->host_type->currentIndex() == 0;
|
2018-11-24 09:22:49 +01:00
|
|
|
Network::Room::BanList ban_list{};
|
|
|
|
if (ui->load_ban_list->isChecked()) {
|
|
|
|
ban_list = UISettings::values.ban_list;
|
|
|
|
}
|
2018-01-19 14:42:21 +01:00
|
|
|
if (auto room = Network::GetRoom().lock()) {
|
2018-10-27 09:49:00 +02:00
|
|
|
bool created = room->Create(ui->room_name->text().toStdString(),
|
|
|
|
ui->room_description->toPlainText().toStdString(), "", port,
|
2018-11-24 09:22:49 +01:00
|
|
|
password, ui->max_player->value(),
|
|
|
|
Settings::values.citra_username, game_name.toStdString(),
|
|
|
|
game_id, CreateVerifyBackend(is_public), ban_list);
|
2018-01-19 14:42:21 +01:00
|
|
|
if (!created) {
|
|
|
|
NetworkMessage::ShowError(NetworkMessage::COULD_NOT_CREATE_ROOM);
|
2018-06-29 13:18:07 +02:00
|
|
|
LOG_ERROR(Network, "Could not create room!");
|
2018-01-19 14:42:21 +01:00
|
|
|
ui->host->setEnabled(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2018-10-27 09:49:00 +02:00
|
|
|
// 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
|
2019-04-19 16:55:49 +02:00
|
|
|
Common::WebResult result = session->Register();
|
|
|
|
if (result.result_code != Common::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 Citra 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 = Network::GetRoom().lock()) {
|
|
|
|
room->Destroy();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2018-10-27 09:49:00 +02:00
|
|
|
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, Settings::values.citra_username,
|
|
|
|
Settings::values.citra_token);
|
|
|
|
if (auto 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
|
2018-10-31 16:07:03 +01:00
|
|
|
member->Join(ui->username->text().toStdString(),
|
|
|
|
Service::CFG::GetConsoleIdHash(Core::System::GetInstance()), "127.0.0.1", port,
|
2018-10-27 09:49:00 +02:00
|
|
|
0, Network::NoPreferredMac, password, token);
|
2018-01-19 14:42:21 +01:00
|
|
|
|
|
|
|
// Store settings
|
|
|
|
UISettings::values.room_nickname = ui->username->text();
|
|
|
|
UISettings::values.room_name = ui->room_name->text();
|
|
|
|
UISettings::values.game_id =
|
|
|
|
ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong();
|
|
|
|
UISettings::values.max_player = ui->max_player->value();
|
|
|
|
|
2018-04-18 07:06:02 +02:00
|
|
|
UISettings::values.host_type = ui->host_type->currentIndex();
|
2018-01-19 14:42:21 +01:00
|
|
|
UISettings::values.room_port = (ui->port->isModified() && !ui->port->text().isEmpty())
|
|
|
|
? ui->port->text()
|
|
|
|
: QString::number(Network::DefaultRoomPort);
|
2018-04-30 09:40:51 +02:00
|
|
|
UISettings::values.room_description = ui->room_description->toPlainText();
|
2018-01-19 14:42:21 +01:00
|
|
|
Settings::Apply();
|
2018-10-27 09:49:00 +02:00
|
|
|
ui->host->setEnabled(true);
|
|
|
|
close();
|
2018-01-19 14:42:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2018-05-22 03:55:19 +02:00
|
|
|
auto leftData = left.data(GameListItemPath::TitleRole).toString();
|
|
|
|
auto rightData = right.data(GameListItemPath::TitleRole).toString();
|
2018-01-19 14:42:21 +01:00
|
|
|
return leftData.compare(rightData) < 0;
|
|
|
|
}
|