diff --git a/externals/cpp-jwt b/externals/cpp-jwt new file mode 160000 index 0000000000..6e27aa4c86 --- /dev/null +++ b/externals/cpp-jwt @@ -0,0 +1 @@ +Subproject commit 6e27aa4c8671e183f11e327a2e1f556c64fdc4a9 diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index b64510957b..d09a6ddc1c 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -39,6 +39,7 @@ #include "core/frontend/applets/default_applets.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/service/am/am.h" +#include "core/hle/service/cfg/cfg.h" #include "core/loader/loader.h" #include "core/movie.h" #include "core/settings.h" @@ -336,7 +337,8 @@ int main(int argc, char** argv) { member->BindOnStateChanged(OnStateChanged); LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, nickname); - member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredMac, password); + member->Join(nickname, Service::CFG::GetConsoleIdHash(system), address.c_str(), port, 0, + Network::NoPreferredMac, password); } else { LOG_ERROR(Network, "Could not access RoomMember"); return 0; diff --git a/src/citra_qt/multiplayer/direct_connect.cpp b/src/citra_qt/multiplayer/direct_connect.cpp index 2d6e28c916..3870c1e256 100644 --- a/src/citra_qt/multiplayer/direct_connect.cpp +++ b/src/citra_qt/multiplayer/direct_connect.cpp @@ -15,6 +15,7 @@ #include "citra_qt/multiplayer/state.h" #include "citra_qt/multiplayer/validation.h" #include "citra_qt/ui_settings.h" +#include "core/hle/service/cfg/cfg.h" #include "core/settings.h" #include "network/network.h" #include "ui_direct_connect.h" @@ -97,6 +98,7 @@ void DirectConnectWindow::Connect() { if (auto room_member = Network::GetRoomMember().lock()) { auto port = UISettings::values.port.toUInt(); room_member->Join(ui->nickname->text().toStdString(), + Service::CFG::GetConsoleIdHash(Core::System::GetInstance()), ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredMac, ui->password->text().toStdString().c_str()); } diff --git a/src/citra_qt/multiplayer/host_room.cpp b/src/citra_qt/multiplayer/host_room.cpp index 1d389539d8..fa335bfaed 100644 --- a/src/citra_qt/multiplayer/host_room.cpp +++ b/src/citra_qt/multiplayer/host_room.cpp @@ -19,6 +19,7 @@ #include "citra_qt/ui_settings.h" #include "common/logging/log.h" #include "core/announce_multiplayer_session.h" +#include "core/hle/service/cfg/cfg.h" #include "core/settings.h" #include "ui_host_room.h" @@ -116,8 +117,9 @@ void HostRoomWindow::Host() { return; } } - member->Join(ui->username->text().toStdString(), "127.0.0.1", port, 0, - Network::NoPreferredMac, password); + member->Join(ui->username->text().toStdString(), + Service::CFG::GetConsoleIdHash(Core::System::GetInstance()), "127.0.0.1", port, + 0, Network::NoPreferredMac, password); // Store settings UISettings::values.room_nickname = ui->username->text(); diff --git a/src/citra_qt/multiplayer/lobby.cpp b/src/citra_qt/multiplayer/lobby.cpp index 8a56302896..6b22c277c0 100644 --- a/src/citra_qt/multiplayer/lobby.cpp +++ b/src/citra_qt/multiplayer/lobby.cpp @@ -15,6 +15,7 @@ #include "citra_qt/multiplayer/validation.h" #include "citra_qt/ui_settings.h" #include "common/logging/log.h" +#include "core/hle/service/cfg/cfg.h" #include "core/settings.h" #include "network/network.h" @@ -139,7 +140,8 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { // attempt to connect in a different thread QFuture f = QtConcurrent::run([nickname, ip, port, password] { if (auto room_member = Network::GetRoomMember().lock()) { - room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredMac, password); + room_member->Join(nickname, Service::CFG::GetConsoleIdHash(Core::System::GetInstance()), + ip.c_str(), port, 0, Network::NoPreferredMac, password); } }); watcher->setFuture(f); diff --git a/src/citra_qt/multiplayer/message.cpp b/src/citra_qt/multiplayer/message.cpp index 2489a6ffd6..e13463a212 100644 --- a/src/citra_qt/multiplayer/message.cpp +++ b/src/citra_qt/multiplayer/message.cpp @@ -38,6 +38,9 @@ const ConnectionError GENERIC_ERROR( const ConnectionError LOST_CONNECTION(QT_TR_NOOP("Connection to room lost. Try to reconnect.")); const ConnectionError MAC_COLLISION( QT_TR_NOOP("MAC address is already in use. Please choose another.")); +const ConnectionError CONSOLE_ID_COLLISION(QT_TR_NOOP( + "Your Console ID conflicted with someone else's in the room.\n\nPlease go to Emulation " + "> Configure > System to regenerate your Console ID.")); static bool WarnMessage(const std::string& title, const std::string& text) { return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()), diff --git a/src/citra_qt/multiplayer/message.h b/src/citra_qt/multiplayer/message.h index 46461c3c16..cc8e0f4a4f 100644 --- a/src/citra_qt/multiplayer/message.h +++ b/src/citra_qt/multiplayer/message.h @@ -37,6 +37,7 @@ extern const ConnectionError WRONG_PASSWORD; extern const ConnectionError GENERIC_ERROR; extern const ConnectionError LOST_CONNECTION; extern const ConnectionError MAC_COLLISION; +extern const ConnectionError CONSOLE_ID_COLLISION; /** * Shows a standard QMessageBox with a error message diff --git a/src/citra_qt/multiplayer/state.cpp b/src/citra_qt/multiplayer/state.cpp index d5c15dcb6e..d819a3d0f0 100644 --- a/src/citra_qt/multiplayer/state.cpp +++ b/src/citra_qt/multiplayer/state.cpp @@ -102,6 +102,9 @@ void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& s case Network::RoomMember::State::MacCollision: NetworkMessage::ShowError(NetworkMessage::MAC_COLLISION); break; + case Network::RoomMember::State::ConsoleIdCollision: + NetworkMessage::ShowError(NetworkMessage::CONSOLE_ID_COLLISION); + break; case Network::RoomMember::State::RoomIsFull: NetworkMessage::ShowError(NetworkMessage::ROOM_IS_FULL); break; diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index d5c08a5a7d..8c6859bb39 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -734,4 +734,21 @@ void InstallInterfaces(Core::System& system) { std::make_shared()->InstallAsService(service_manager); } +std::string GetConsoleIdHash(Core::System& system) { + u64_le console_id{}; + std::array buffer; + if (system.IsPoweredOn()) { + auto cfg = GetModule(system); + ASSERT_MSG(cfg, "CFG Module missing!"); + console_id = cfg->GetConsoleUniqueId(); + } else { + console_id = std::make_unique()->GetConsoleUniqueId(); + } + std::memcpy(buffer.data(), &console_id, sizeof(console_id)); + + std::array hash; + CryptoPP::SHA256().CalculateDigest(hash.data(), buffer.data(), sizeof(buffer)); + return fmt::format("{:02x}", fmt::join(hash.begin(), hash.end(), "")); +} + } // namespace Service::CFG diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index b069ad728d..af63c6f910 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -415,4 +415,7 @@ std::shared_ptr GetModule(Core::System& system); void InstallInterfaces(Core::System& system); +/// Convenience function for getting a SHA256 hash of the Console ID +std::string GetConsoleIdHash(Core::System& system); + } // namespace Service::CFG diff --git a/src/network/room.cpp b/src/network/room.cpp index a5c39d2b64..9ae26f524b 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -31,10 +31,11 @@ public: std::string password; ///< The password required to connect to this room. struct Member { - std::string nickname; ///< The nickname of the member. - GameInfo game_info; ///< The current game of the member - MacAddress mac_address; ///< The assigned mac address of the member. - ENetPeer* peer; ///< The remote peer. + std::string nickname; ///< The nickname of the member. + std::string console_id_hash; ///< A hash of the console ID of the member. + GameInfo game_info; ///< The current game of the member + MacAddress mac_address; ///< The assigned mac address of the member. + ENetPeer* peer; ///< The remote peer. }; using MemberList = std::vector; MemberList members; ///< Information about the members of this room @@ -69,6 +70,12 @@ public: */ bool IsValidMacAddress(const MacAddress& address) const; + /** + * Returns whether the console ID (hash) is valid, ie. isn't already taken by someone else in + * the room. + */ + bool IsValidConsoleId(const std::string& console_id_hash) const; + /** * Sends a ID_ROOM_IS_FULL message telling the client that the room is full. */ @@ -84,6 +91,12 @@ public: */ void SendMacCollision(ENetPeer* client); + /** + * Sends a IdConsoleIdCollison message telling the client that another member with the same + * console ID exists. + */ + void SendConsoleIdCollision(ENetPeer* client); + /** * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid. */ @@ -212,6 +225,9 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { std::string nickname; packet >> nickname; + std::string console_id_hash; + packet >> console_id_hash; + MacAddress preferred_mac; packet >> preferred_mac; @@ -242,6 +258,11 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { preferred_mac = GenerateMacAddress(); } + if (!IsValidConsoleId(console_id_hash)) { + SendConsoleIdCollision(event->peer); + return; + } + if (client_version != network_version) { SendVersionMismatch(event->peer); return; @@ -250,6 +271,7 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { // At this point the client is ready to be added to the room. Member member{}; member.mac_address = preferred_mac; + member.console_id_hash = console_id_hash; member.nickname = nickname; member.peer = event->peer; @@ -282,6 +304,14 @@ bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const { [&address](const auto& member) { return member.mac_address != address; }); } +bool Room::RoomImpl::IsValidConsoleId(const std::string& console_id_hash) const { + // A Console ID is valid if it is not already taken by anybody else in the room. + std::lock_guard lock(member_mutex); + return std::all_of(members.begin(), members.end(), [&console_id_hash](const auto& member) { + return member.console_id_hash != console_id_hash; + }); +} + void Room::RoomImpl::SendNameCollision(ENetPeer* client) { Packet packet; packet << static_cast(IdNameCollision); @@ -302,6 +332,16 @@ void Room::RoomImpl::SendMacCollision(ENetPeer* client) { enet_host_flush(server); } +void Room::RoomImpl::SendConsoleIdCollision(ENetPeer* client) { + Packet packet; + packet << static_cast(IdConsoleIdCollision); + + 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::SendWrongPassword(ENetPeer* client) { Packet packet; packet << static_cast(IdWrongPassword); diff --git a/src/network/room.h b/src/network/room.h index 19bb5acce3..7a73022bcb 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -59,6 +59,7 @@ enum RoomMessageTypes : u8 { IdWrongPassword, IdCloseRoom, IdRoomIsFull, + IdConsoleIdCollision, }; /// This is what a server [person creating a server] would use. diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index 971c05c181..682821aa3b 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -73,11 +73,12 @@ public: * Sends a request to the server, asking for permission to join a room with the specified * nickname and preferred mac. * @params nickname The desired nickname. + * @params console_id_hash A hash of the Console ID. * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells * @params password The password for the room * the server to assign one for us. */ - void SendJoinRequest(const std::string& nickname, + void SendJoinRequest(const std::string& nickname, const std::string& console_id_hash, const MacAddress& preferred_mac = NoPreferredMac, const std::string& password = ""); @@ -163,6 +164,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() { case IdMacCollision: SetState(State::MacCollision); break; + case IdConsoleIdCollision: + SetState(State::ConsoleIdCollision); + break; case IdVersionMismatch: SetState(State::WrongVersion); break; @@ -204,11 +208,13 @@ void RoomMember::RoomMemberImpl::Send(Packet&& packet) { } void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname, + const std::string& console_id_hash, const MacAddress& preferred_mac, const std::string& password) { Packet packet; packet << static_cast(IdJoinRequest); packet << nickname; + packet << console_id_hash; packet << preferred_mac; packet << network_version; packet << password; @@ -392,9 +398,9 @@ RoomInformation RoomMember::GetRoomInformation() const { return room_member_impl->room_information; } -void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port, - u16 client_port, const MacAddress& preferred_mac, - const std::string& password) { +void RoomMember::Join(const std::string& nick, const std::string& console_id_hash, + const char* server_addr, u16 server_port, u16 client_port, + const MacAddress& preferred_mac, const std::string& password) { // If the member is connected, kill the connection first if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) { Leave(); @@ -427,7 +433,7 @@ void RoomMember::Join(const std::string& nick, const char* server_addr, u16 serv if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) { room_member_impl->nickname = nick; room_member_impl->StartLoop(); - room_member_impl->SendJoinRequest(nick, preferred_mac, password); + room_member_impl->SendJoinRequest(nick, console_id_hash, preferred_mac, password); SendGameInfo(room_member_impl->current_game_info); } else { enet_peer_disconnect(room_member_impl->server, 0); diff --git a/src/network/room_member.h b/src/network/room_member.h index b8a648f1d6..58fca96b74 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -54,12 +54,13 @@ public: LostConnection, ///< Connection closed // Reasons why connection was rejected - NameCollision, ///< Somebody is already using this name - MacCollision, ///< Somebody is already using that mac-address - WrongVersion, ///< The room version is not the same as for this RoomMember - WrongPassword, ///< The password doesn't match the one from the Room - CouldNotConnect, ///< The room is not responding to a connection attempt - RoomIsFull ///< Room is already at the maximum number of players + NameCollision, ///< Somebody is already using this name + MacCollision, ///< Somebody is already using that mac-address + ConsoleIdCollision, ///< Somebody in the room has the same Console ID + WrongVersion, ///< The room version is not the same as for this RoomMember + WrongPassword, ///< The password doesn't match the one from the Room + CouldNotConnect, ///< The room is not responding to a connection attempt + RoomIsFull ///< Room is already at the maximum number of players }; struct MemberInformation { @@ -116,11 +117,13 @@ public: /** * Attempts to join a room at the specified address and port, using the specified nickname. - * This may fail if the username is already taken. + * A console ID hash is passed in to check console ID conflicts. + * This may fail if the username or console ID is already taken. */ - void Join(const std::string& nickname, const char* server_addr = "127.0.0.1", - const u16 server_port = DefaultRoomPort, const u16 client_port = 0, - const MacAddress& preferred_mac = NoPreferredMac, const std::string& password = ""); + void Join(const std::string& nickname, const std::string& console_id_hash, + const char* server_addr = "127.0.0.1", const u16 server_port = DefaultRoomPort, + const u16 client_port = 0, const MacAddress& preferred_mac = NoPreferredMac, + const std::string& password = ""); /** * Sends a WiFi packet to the room.