host_room.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <future>
  4. #include <QColor>
  5. #include <QImage>
  6. #include <QList>
  7. #include <QLocale>
  8. #include <QMessageBox>
  9. #include <QMetaType>
  10. #include <QTime>
  11. #include <QtConcurrent/QtConcurrentRun>
  12. #include "common/logging/log.h"
  13. #include "common/settings.h"
  14. #include "core/core.h"
  15. #include "core/internal_network/network_interface.h"
  16. #include "network/announce_multiplayer_session.h"
  17. #include "ui_host_room.h"
  18. #include "yuzu/game_list_p.h"
  19. #include "yuzu/main.h"
  20. #include "yuzu/multiplayer/host_room.h"
  21. #include "yuzu/multiplayer/message.h"
  22. #include "yuzu/multiplayer/state.h"
  23. #include "yuzu/multiplayer/validation.h"
  24. #include "yuzu/uisettings.h"
  25. #ifdef ENABLE_WEB_SERVICE
  26. #include "web_service/verify_user_jwt.h"
  27. #endif
  28. HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
  29. std::shared_ptr<Core::AnnounceMultiplayerSession> session,
  30. Core::System& system_)
  31. : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
  32. ui(std::make_unique<Ui::HostRoom>()),
  33. announce_multiplayer_session(session), system{system_}, room_network{
  34. system.GetRoomNetwork()} {
  35. ui->setupUi(this);
  36. // set up validation for all of the fields
  37. ui->room_name->setValidator(validation.GetRoomName());
  38. ui->username->setValidator(validation.GetNickname());
  39. ui->port->setValidator(validation.GetPort());
  40. ui->port->setPlaceholderText(QString::number(Network::DefaultRoomPort));
  41. // Create a proxy to the game list to display the list of preferred games
  42. game_list = new QStandardItemModel;
  43. UpdateGameList(list);
  44. proxy = new ComboBoxProxyModel;
  45. proxy->setSourceModel(game_list);
  46. proxy->sort(0, Qt::AscendingOrder);
  47. ui->game_list->setModel(proxy);
  48. // Connect all the widgets to the appropriate events
  49. connect(ui->host, &QPushButton::clicked, this, &HostRoomWindow::Host);
  50. // Restore the settings:
  51. ui->username->setText(
  52. QString::fromStdString(UISettings::values.multiplayer_room_nickname.GetValue()));
  53. if (ui->username->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
  54. // Use yuzu Web Service user name as nickname by default
  55. ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
  56. }
  57. ui->room_name->setText(
  58. QString::fromStdString(UISettings::values.multiplayer_room_name.GetValue()));
  59. ui->port->setText(QString::number(UISettings::values.multiplayer_room_port.GetValue()));
  60. ui->max_player->setValue(UISettings::values.multiplayer_max_player.GetValue());
  61. int index = UISettings::values.multiplayer_host_type.GetValue();
  62. if (index < ui->host_type->count()) {
  63. ui->host_type->setCurrentIndex(index);
  64. }
  65. index = ui->game_list->findData(UISettings::values.multiplayer_game_id.GetValue(),
  66. GameListItemPath::ProgramIdRole);
  67. if (index != -1) {
  68. ui->game_list->setCurrentIndex(index);
  69. }
  70. ui->room_description->setText(
  71. QString::fromStdString(UISettings::values.multiplayer_room_description.GetValue()));
  72. }
  73. HostRoomWindow::~HostRoomWindow() = default;
  74. void HostRoomWindow::UpdateGameList(QStandardItemModel* list) {
  75. game_list->clear();
  76. for (int i = 0; i < list->rowCount(); i++) {
  77. auto parent = list->item(i, 0);
  78. for (int j = 0; j < parent->rowCount(); j++) {
  79. game_list->appendRow(parent->child(j)->clone());
  80. }
  81. }
  82. }
  83. void HostRoomWindow::RetranslateUi() {
  84. ui->retranslateUi(this);
  85. }
  86. std::unique_ptr<Network::VerifyUser::Backend> HostRoomWindow::CreateVerifyBackend(
  87. bool use_validation) const {
  88. std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
  89. if (use_validation) {
  90. #ifdef ENABLE_WEB_SERVICE
  91. verify_backend =
  92. std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue());
  93. #else
  94. verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
  95. #endif
  96. } else {
  97. verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
  98. }
  99. return verify_backend;
  100. }
  101. void HostRoomWindow::Host() {
  102. if (!Network::GetSelectedNetworkInterface()) {
  103. NetworkMessage::ErrorManager::ShowError(
  104. NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
  105. return;
  106. }
  107. if (!ui->username->hasAcceptableInput()) {
  108. NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
  109. return;
  110. }
  111. if (!ui->room_name->hasAcceptableInput()) {
  112. NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOMNAME_NOT_VALID);
  113. return;
  114. }
  115. if (!ui->port->hasAcceptableInput()) {
  116. NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID);
  117. return;
  118. }
  119. if (ui->game_list->currentIndex() == -1) {
  120. NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::GAME_NOT_SELECTED);
  121. return;
  122. }
  123. if (system.IsPoweredOn()) {
  124. if (!NetworkMessage::WarnGameRunning()) {
  125. return;
  126. }
  127. }
  128. if (auto member = room_network.GetRoomMember().lock()) {
  129. if (member->GetState() == Network::RoomMember::State::Joining) {
  130. return;
  131. } else if (member->IsConnected()) {
  132. auto parent = static_cast<MultiplayerState*>(parentWidget());
  133. if (!parent->OnCloseRoom()) {
  134. close();
  135. return;
  136. }
  137. }
  138. ui->host->setDisabled(true);
  139. const AnnounceMultiplayerRoom::GameInfo game{
  140. .name = ui->game_list->currentData(Qt::DisplayRole).toString().toStdString(),
  141. .id = ui->game_list->currentData(GameListItemPath::ProgramIdRole).toULongLong(),
  142. };
  143. const auto port =
  144. ui->port->isModified() ? ui->port->text().toInt() : Network::DefaultRoomPort;
  145. const auto password = ui->password->text().toStdString();
  146. const bool is_public = ui->host_type->currentIndex() == 0;
  147. Network::Room::BanList ban_list{};
  148. if (ui->load_ban_list->isChecked()) {
  149. ban_list = UISettings::values.multiplayer_ban_list;
  150. }
  151. if (auto room = room_network.GetRoom().lock()) {
  152. const bool created =
  153. room->Create(ui->room_name->text().toStdString(),
  154. ui->room_description->toPlainText().toStdString(), "", port, password,
  155. ui->max_player->value(), Settings::values.yuzu_username.GetValue(),
  156. game, CreateVerifyBackend(is_public), ban_list);
  157. if (!created) {
  158. NetworkMessage::ErrorManager::ShowError(
  159. NetworkMessage::ErrorManager::COULD_NOT_CREATE_ROOM);
  160. LOG_ERROR(Network, "Could not create room!");
  161. ui->host->setEnabled(true);
  162. return;
  163. }
  164. }
  165. // Start the announce session if they chose Public
  166. if (is_public) {
  167. if (auto session = announce_multiplayer_session.lock()) {
  168. // Register the room first to ensure verify_uid is present when we connect
  169. WebService::WebResult result = session->Register();
  170. if (result.result_code != WebService::WebResult::Code::Success) {
  171. QMessageBox::warning(
  172. this, tr("Error"),
  173. tr("Failed to announce the room to the public lobby. In order to host a "
  174. "room publicly, you must have a valid yuzu account configured in "
  175. "Emulation -> Configure -> Web. If you do not want to publish a room in "
  176. "the public lobby, then select Unlisted instead.\nDebug Message: ") +
  177. QString::fromStdString(result.result_string),
  178. QMessageBox::Ok);
  179. ui->host->setEnabled(true);
  180. if (auto room = room_network.GetRoom().lock()) {
  181. room->Destroy();
  182. }
  183. return;
  184. }
  185. session->Start();
  186. } else {
  187. LOG_ERROR(Network, "Starting announce session failed");
  188. }
  189. }
  190. std::string token;
  191. #ifdef ENABLE_WEB_SERVICE
  192. if (is_public) {
  193. WebService::Client client(Settings::values.web_api_url.GetValue(),
  194. Settings::values.yuzu_username.GetValue(),
  195. Settings::values.yuzu_token.GetValue());
  196. if (auto room = room_network.GetRoom().lock()) {
  197. token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
  198. }
  199. if (token.empty()) {
  200. LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
  201. } else {
  202. LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
  203. }
  204. }
  205. #endif
  206. // TODO: Check what to do with this
  207. member->Join(ui->username->text().toStdString(), "127.0.0.1", port, 0,
  208. Network::NoPreferredIP, password, token);
  209. // Store settings
  210. UISettings::values.multiplayer_room_nickname = ui->username->text().toStdString();
  211. UISettings::values.multiplayer_room_name = ui->room_name->text().toStdString();
  212. UISettings::values.multiplayer_game_id =
  213. ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong();
  214. UISettings::values.multiplayer_max_player = ui->max_player->value();
  215. UISettings::values.multiplayer_host_type = ui->host_type->currentIndex();
  216. if (ui->port->isModified() && !ui->port->text().isEmpty()) {
  217. UISettings::values.multiplayer_room_port = ui->port->text().toInt();
  218. } else {
  219. UISettings::values.multiplayer_room_port = Network::DefaultRoomPort;
  220. }
  221. UISettings::values.multiplayer_room_description =
  222. ui->room_description->toPlainText().toStdString();
  223. ui->host->setEnabled(true);
  224. emit SaveConfig();
  225. close();
  226. }
  227. }
  228. QVariant ComboBoxProxyModel::data(const QModelIndex& idx, int role) const {
  229. if (role != Qt::DisplayRole) {
  230. auto val = QSortFilterProxyModel::data(idx, role);
  231. // If its the icon, shrink it to 16x16
  232. if (role == Qt::DecorationRole)
  233. val = val.value<QImage>().scaled(16, 16, Qt::KeepAspectRatio);
  234. return val;
  235. }
  236. std::string filename;
  237. Common::SplitPath(
  238. QSortFilterProxyModel::data(idx, GameListItemPath::FullPathRole).toString().toStdString(),
  239. nullptr, &filename, nullptr);
  240. QString title = QSortFilterProxyModel::data(idx, GameListItemPath::TitleRole).toString();
  241. return title.isEmpty() ? QString::fromStdString(filename) : title;
  242. }
  243. bool ComboBoxProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
  244. auto leftData = left.data(GameListItemPath::TitleRole).toString();
  245. auto rightData = right.data(GameListItemPath::TitleRole).toString();
  246. return leftData.compare(rightData) < 0;
  247. }