diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index a0a661887..2fabfbfc7 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -81,6 +81,9 @@ static void OnStateChanged(const Network::RoomMember::State& state) { case Network::RoomMember::State::Joined: LOG_DEBUG(Network, "Successfully joined to the room"); break; + case Network::RoomMember::State::Moderator: + LOG_DEBUG(Network, "Successfully joined the room as a moderator"); + break; default: break; } diff --git a/src/citra_qt/multiplayer/chat_room.cpp b/src/citra_qt/multiplayer/chat_room.cpp index 4c4150d6a..85ac53987 100644 --- a/src/citra_qt/multiplayer/chat_room.cpp +++ b/src/citra_qt/multiplayer/chat_room.cpp @@ -306,7 +306,9 @@ void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_ void ChatRoom::OnSendChat() { if (auto room = Network::GetRoomMember().lock()) { - if (room->GetState() != Network::RoomMember::State::Joined) { + if (room->GetState() != Network::RoomMember::State::Joined && + room->GetState() != Network::RoomMember::State::Moderator) { + return; } auto message = ui->chat_message->text().toStdString(); diff --git a/src/citra_qt/multiplayer/client_room.cpp b/src/citra_qt/multiplayer/client_room.cpp index d87a3e6e1..458f8c864 100644 --- a/src/citra_qt/multiplayer/client_room.cpp +++ b/src/citra_qt/multiplayer/client_room.cpp @@ -72,9 +72,12 @@ void ClientRoomWindow::OnRoomUpdate(const Network::RoomInformation& info) { } void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) { - if (state == Network::RoomMember::State::Joined) { + if (state == Network::RoomMember::State::Joined || + state == Network::RoomMember::State::Moderator) { + ui->chat->Clear(); ui->chat->AppendStatusMessage(tr("Connected")); + SetModPerms(state == Network::RoomMember::State::Moderator); } UpdateView(); } diff --git a/src/citra_qt/multiplayer/client_room.h b/src/citra_qt/multiplayer/client_room.h index c40d324c3..584a51642 100644 --- a/src/citra_qt/multiplayer/client_room.h +++ b/src/citra_qt/multiplayer/client_room.h @@ -18,7 +18,6 @@ public: ~ClientRoomWindow(); void RetranslateUi(); - void SetModPerms(bool is_mod); public slots: void OnRoomUpdate(const Network::RoomInformation&); @@ -32,6 +31,7 @@ signals: private: void Disconnect(); void UpdateView(); + void SetModPerms(bool is_mod); QStandardItemModel* player_list; std::unique_ptr ui; diff --git a/src/citra_qt/multiplayer/direct_connect.cpp b/src/citra_qt/multiplayer/direct_connect.cpp index 3870c1e25..c14edde76 100644 --- a/src/citra_qt/multiplayer/direct_connect.cpp +++ b/src/citra_qt/multiplayer/direct_connect.cpp @@ -63,7 +63,7 @@ void DirectConnectWindow::Connect() { // Prevent the user from trying to join a room while they are already joining. if (member->GetState() == Network::RoomMember::State::Joining) { return; - } else if (member->GetState() == Network::RoomMember::State::Joined) { + } else if (member->IsConnected()) { // And ask if they want to leave the room if they are already in one. if (!NetworkMessage::WarnDisconnect()) { return; @@ -122,7 +122,9 @@ void DirectConnectWindow::OnConnection() { EndConnecting(); if (auto room_member = Network::GetRoomMember().lock()) { - if (room_member->GetState() == Network::RoomMember::State::Joined) { + if (room_member->GetState() == Network::RoomMember::State::Joined || + room_member->GetState() == Network::RoomMember::State::Moderator) { + close(); } } diff --git a/src/citra_qt/multiplayer/host_room.cpp b/src/citra_qt/multiplayer/host_room.cpp index d5f093e48..2dbc74d6c 100644 --- a/src/citra_qt/multiplayer/host_room.cpp +++ b/src/citra_qt/multiplayer/host_room.cpp @@ -113,7 +113,7 @@ void HostRoomWindow::Host() { if (auto member = Network::GetRoomMember().lock()) { if (member->GetState() == Network::RoomMember::State::Joining) { return; - } else if (member->GetState() == Network::RoomMember::State::Joined) { + } else if (member->IsConnected()) { auto parent = static_cast(parentWidget()); if (!parent->OnCloseRoom()) { close(); diff --git a/src/citra_qt/multiplayer/lobby.cpp b/src/citra_qt/multiplayer/lobby.cpp index bb1807776..3b48b3a95 100644 --- a/src/citra_qt/multiplayer/lobby.cpp +++ b/src/citra_qt/multiplayer/lobby.cpp @@ -109,7 +109,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { // Prevent the user from trying to join a room while they are already joining. if (member->GetState() == Network::RoomMember::State::Joining) { return; - } else if (member->GetState() == Network::RoomMember::State::Joined) { + } else if (member->IsConnected()) { // And ask if they want to leave the room if they are already in one. if (!NetworkMessage::WarnDisconnect()) { return; diff --git a/src/citra_qt/multiplayer/state.cpp b/src/citra_qt/multiplayer/state.cpp index b14e79f9b..75d74e189 100644 --- a/src/citra_qt/multiplayer/state.cpp +++ b/src/citra_qt/multiplayer/state.cpp @@ -89,7 +89,9 @@ void MultiplayerState::retranslateUi() { if (current_state == Network::RoomMember::State::Uninitialized) { status_text->setText(tr("Not Connected. Click here to find a room!")); - } else if (current_state == Network::RoomMember::State::Joined) { + } else if (current_state == Network::RoomMember::State::Joined || + current_state == Network::RoomMember::State::Moderator) { + status_text->setText(tr("Connected")); } else { status_text->setText(tr("Not Connected")); @@ -107,7 +109,9 @@ void MultiplayerState::retranslateUi() { void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) { LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state)); - if (state == Network::RoomMember::State::Joined) { + if (state == Network::RoomMember::State::Joined || + state == Network::RoomMember::State::Moderator) { + OnOpenNetworkRoom(); status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16)); status_text->setText(tr("Connected")); @@ -183,7 +187,9 @@ void MultiplayerState::OnAnnounceFailed(const Common::WebResult& result) { void MultiplayerState::UpdateThemedIcons() { if (show_notification) { status_icon->setPixmap(QIcon::fromTheme("connected_notification").pixmap(16)); - } else if (current_state == Network::RoomMember::State::Joined) { + } else if (current_state == Network::RoomMember::State::Joined || + current_state == Network::RoomMember::State::Moderator) { + status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16)); } else { status_icon->setPixmap(QIcon::fromTheme("disconnected").pixmap(16)); @@ -258,12 +264,6 @@ void MultiplayerState::OnOpenNetworkRoom() { connect(client_room, &ClientRoomWindow::ShowNotification, this, &MultiplayerState::ShowNotification); } - const std::string host_username = member->GetRoomInformation().host_username; - if (host_username.empty()) { - client_room->SetModPerms(false); - } else { - client_room->SetModPerms(member->GetUsername() == host_username); - } BringWidgetToFront(client_room); return; } diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 755da4f1d..bf2d408c1 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -140,7 +140,9 @@ std::list GetReceivedBeacons(const MacAddress& sender) { /// Sends a WifiPacket to the room we're currently connected to. void SendPacket(Network::WifiPacket& packet) { if (auto room_member = Network::GetRoomMember().lock()) { - if (room_member->GetState() == Network::RoomMember::State::Joined) { + if (room_member->GetState() == Network::RoomMember::State::Joined || + room_member->GetState() == Network::RoomMember::State::Moderator) { + packet.transmitter_address = room_member->GetMacAddress(); room_member->SendWifiPacket(packet); } diff --git a/src/network/room.cpp b/src/network/room.cpp index 394e8644f..5386977e5 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -155,6 +155,12 @@ public: */ void SendJoinSuccess(ENetPeer* client, MacAddress mac_address); + /** + * Notifies the member that its connection attempt was successful, + * and it is now part of the room, and it has been granted mod permissions. + */ + void SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address); + /** * Sends a IdHostKicked message telling the client that they have been kicked. */ @@ -401,7 +407,11 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { // Notify everyone that the room information has changed. BroadcastRoomInformation(); - SendJoinSuccess(event->peer, preferred_mac); + if (HasModPermission(event->peer)) { + SendJoinSuccessAsMod(event->peer, preferred_mac); + } else { + SendJoinSuccess(event->peer, preferred_mac); + } } void Room::RoomImpl::HandleModKickPacket(const ENetEvent* event) { @@ -588,10 +598,11 @@ bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const { if (sending_member == members.end()) { return false; } - if (sending_member->user_data.username != room_information.host_username) { - return false; - } - return true; + if (sending_member->user_data.moderator) // Community moderator + return true; + if (sending_member->user_data.username == room_information.host_username) // Room host + return true; + return false; } void Room::RoomImpl::SendNameCollision(ENetPeer* client) { @@ -665,6 +676,16 @@ void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) { enet_host_flush(server); } +void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address) { + Packet packet; + packet << static_cast(IdJoinSuccessAsMod); + packet << mac_address; + ENetPacket* enet_packet = + enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(client, 0, enet_packet); + enet_host_flush(server); +} + void Room::RoomImpl::SendUserKicked(ENetPeer* client) { Packet packet; packet << static_cast(IdHostKicked); diff --git a/src/network/room.h b/src/network/room.h index 3181e84d7..5781631d7 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -74,6 +74,7 @@ enum RoomMessageTypes : u8 { IdModBanListResponse, IdModPermissionDenied, IdModNoSuchUser, + IdJoinSuccessAsMod, }; /// Types of system status messages diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index 40d7e7068..8a846ee04 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -151,7 +151,7 @@ void RoomMember::RoomMemberImpl::SetError(const Error new_error) { } bool RoomMember::RoomMemberImpl::IsConnected() const { - return state == State::Joining || state == State::Joined; + return state == State::Joining || state == State::Joined || state == State::Moderator; } void RoomMember::RoomMemberImpl::MemberLoop() { @@ -176,12 +176,17 @@ void RoomMember::RoomMemberImpl::MemberLoop() { HandleRoomInformationPacket(&event); break; case IdJoinSuccess: + case IdJoinSuccessAsMod: // The join request was successful, we are now in the room. // If we joined successfully, there must be at least one client in the room: us. ASSERT_MSG(member_information.size() > 0, "We have not yet received member information."); HandleJoinPacket(&event); // Get the MAC Address for the client - SetState(State::Joined); + if (event.packet->data[0] == IdJoinSuccessAsMod) { + SetState(State::Moderator); + } else { + SetState(State::Joined); + } break; case IdModBanListResponse: HandleModBanListResponsePacket(&event); @@ -232,7 +237,7 @@ void RoomMember::RoomMemberImpl::MemberLoop() { enet_packet_destroy(event.packet); break; case ENET_EVENT_TYPE_DISCONNECT: - if (state == State::Joined) { + if (state == State::Joined || state == State::Moderator) { SetState(State::Idle); SetError(Error::LostConnection); } @@ -331,7 +336,6 @@ void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) { // Parse the MAC Address from the packet packet >> mac_address; - SetState(State::Joined); } void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { diff --git a/src/network/room_member.h b/src/network/room_member.h index 65d1c64eb..3410abac1 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -59,7 +59,8 @@ public: Uninitialized, ///< Not initialized Idle, ///< Default state (i.e. not connected) Joining, ///< The client is attempting to join a room. - Joined, ///< The client is connected to the room and is ready to send/receive packets. + Joined, ///< The client is connected to the room and is ready to send/receive packets. + Moderator, ///< The client is connnected to the room and is granted mod permissions. }; enum class Error : u8 { @@ -270,6 +271,8 @@ static const char* GetStateStr(const RoomMember::State& s) { return "Joining"; case RoomMember::State::Joined: return "Joined"; + case RoomMember::State::Moderator: + return "Moderator"; } return "Unknown"; } diff --git a/src/network/verify_user.h b/src/network/verify_user.h index 74e154331..01b9877c8 100644 --- a/src/network/verify_user.h +++ b/src/network/verify_user.h @@ -13,6 +13,7 @@ struct UserData { std::string username; std::string display_name; std::string avatar_url; + bool moderator = false; ///< Whether the user is a Citra Moderator. }; /** diff --git a/src/web_service/verify_user_jwt.cpp b/src/web_service/verify_user_jwt.cpp index fb7b7281d..27e08db9e 100644 --- a/src/web_service/verify_user_jwt.cpp +++ b/src/web_service/verify_user_jwt.cpp @@ -50,6 +50,10 @@ Network::VerifyUser::UserData VerifyUserJWT::LoadUserData(const std::string& ver if (decoded.payload().has_claim("avatarUrl")) { user_data.avatar_url = decoded.payload().get_claim_value("avatarUrl"); } + if (decoded.payload().has_claim("roles")) { + auto roles = decoded.payload().get_claim_value>("roles"); + user_data.moderator = std::find(roles.begin(), roles.end(), "moderator") != roles.end(); + } return user_data; }