code:
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <vector>
#include "common/common_types.h"

namespace Network {

/// A class that serializes data for network transfer. It also handles endianness
class Packet {
public:
    Packet() = default;
    ~Packet() = default;

    /**
     * Append data to the end of the packet
     * @param data        Pointer to the sequence of bytes to append
     * @param size_in_bytes Number of bytes to append
     */
    void Append(const void* data, std::size_t size_in_bytes);

    /**
     * Reads data from the current read position of the packet
     * @param out_data        Pointer where the data should get written to
     * @param size_in_bytes Number of bytes to read
     */
    void Read(void* out_data, std::size_t size_in_bytes);

    /**
     * Clear the packet
     * After calling Clear, the packet is empty.
     */
    void Clear();

    /**
     * Ignores bytes while reading
     * @param length THe number of bytes to ignore
     */
    void IgnoreBytes(u32 length);

    /**
     * Get a pointer to the data contained in the packet
     * @return Pointer to the data
     */
    const void* GetData() const;

    /**
     * This function returns the number of bytes pointed to by
     * what getData returns.
     * @return Data size, in bytes
     */
    std::size_t GetDataSize() const;

    /**
     * This function is useful to know if there is some data
     * left to be read, without actually reading it.
     * @return True if all data was read, false otherwise
     */
    bool EndOfPacket() const;

    explicit operator bool() const;

    /// Overloads of read function to read data from the packet
    Packet& Read(bool& out_data);
    Packet& Read(s8& out_data);
    Packet& Read(u8& out_data);
    Packet& Read(s16& out_data);
    Packet& Read(u16& out_data);
    Packet& Read(s32& out_data);
    Packet& Read(u32& out_data);
    Packet& Read(s64& out_data);
    Packet& Read(u64& out_data);
    Packet& Read(float& out_data);
    Packet& Read(double& out_data);
    Packet& Read(char* out_data);
    Packet& Read(std::string& out_data);
    template <typename T>
    Packet& Read(std::vector<T>& out_data);
    template <typename T, std::size_t S>
    Packet& Read(std::array<T, S>& out_data);

    /// Overloads of write function to write data into the packet
    Packet& Write(bool in_data);
    Packet& Write(s8 in_data);
    Packet& Write(u8 in_data);
    Packet& Write(s16 in_data);
    Packet& Write(u16 in_data);
    Packet& Write(s32 in_data);
    Packet& Write(u32 in_data);
    Packet& Write(s64 in_data);
    Packet& Write(u64 in_data);
    Packet& Write(float in_data);
    Packet& Write(double in_data);
    Packet& Write(const char* in_data);
    Packet& Write(const std::string& in_data);
    template <typename T>
    Packet& Write(const std::vector<T>& in_data);
    template <typename T, std::size_t S>
    Packet& Write(const std::array<T, S>& data);

private:
    /**
     * Check if the packet can extract a given number of bytes
     * This function updates accordingly the state of the packet.
     * @param size Size to check
     * @return True if size bytes can be read from the packet
     */
    bool CheckSize(std::size_t size);

    // Member data
    std::vector<char> data;   ///< Data stored in the packet
    std::size_t read_pos = 0; ///< Current reading position in the packet
    bool is_valid = true;     ///< Reading state of the packet
};

template <typename T>
Packet& Packet::Read(std::vector<T>& out_data) {
    // First extract the size
    u32 size = 0;
    Read(size);
    out_data.resize(size);

    // Then extract the data
    for (std::size_t i = 0; i < out_data.size(); ++i) {
        T character;
        Read(character);
        out_data[i] = character;
    }
    return *this;
}

template <typename T, std::size_t S>
Packet& Packet::Read(std::array<T, S>& out_data) {
    for (std::size_t i = 0; i < out_data.size(); ++i) {
        T character;
        Read(character);
        out_data[i] = character;
    }
    return *this;
}

template <typename T>
Packet& Packet::Write(const std::vector<T>& in_data) {
    // First insert the size
    Write(static_cast<u32>(in_data.size()));

    // Then insert the data
    for (std::size_t i = 0; i < in_data.size(); ++i) {
        Write(in_data[i]);
    }
    return *this;
}

template <typename T, std::size_t S>
Packet& Packet::Write(const std::array<T, S>& in_data) {
    for (std::size_t i = 0; i < in_data.size(); ++i) {
        Write(in_data[i]);
    }
    return *this;
}

} // namespace Network


code:
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "common/common_precompiled_headers.h"

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <atomic>
#include <iomanip>
#include <mutex>
#include <random>
#include <regex>
#include <shared_mutex>
#include <sstream>
#include <thread>
#include "common/logging/log.h"
#include "enet/enet.h"
#include "network/packet.h"
#include "network/room.h"
#include "network/verify_user.h"

namespace Network {

class Room::RoomImpl {
public:
    std::mt19937 random_gen; ///< Random number generator. Used for GenerateFakeIPAddress

    ENetHost* server = nullptr; ///< Network interface.

    std::atomic<State> state{State::Closed}; ///< Current state of the room.
    RoomInformation room_information;        ///< Information about this room.

    std::string verify_uid;              ///< A GUID which may be used for verification.
    mutable std::mutex verify_uid_mutex; ///< Mutex for verify_uid

    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
        IPv4Address fake_ip;  ///< The assigned fake ip address of the member.
        /// Data of the user, often including authenticated forum username.
        VerifyUser::UserData user_data;
        ENetPeer* peer; ///< The remote peer.
    };
    using MemberList = std::vector<Member>;
    MemberList members;                     ///< Information about the members of this room
    mutable std::shared_mutex member_mutex; ///< Mutex for locking the members list

    UsernameBanList username_ban_list; ///< List of banned usernames
    IPBanList ip_ban_list;             ///< List of banned IP addresses
    mutable std::mutex ban_list_mutex; ///< Mutex for the ban lists

    RoomImpl() : random_gen(std::random_device()()) {}

    /// Thread that receives and dispatches network packets
    std::unique_ptr<std::thread> room_thread;

    /// Verification backend of the room
    std::unique_ptr<VerifyUser::Backend> verify_backend;

    /// Thread function that will receive and dispatch messages until the room is destroyed.
    void ServerLoop();
    void StartLoop();

    /**
     * Parses and answers a room join request from a client.
     * Validates the uniqueness of the username and assigns the MAC address
     * that the client will use for the remainder of the connection.
     */
    void HandleJoinRequest(const ENetEvent* event);

    /**
     * Parses and answers a kick request from a client.
     * Validates the permissions and that the given user exists and then kicks the member.
     */
    void HandleModKickPacket(const ENetEvent* event);

    /**
     * Parses and answers a ban request from a client.
     * Validates the permissions and bans the user (by forum username or IP).
     */
    void HandleModBanPacket(const ENetEvent* event);

    /**
     * Parses and answers a unban request from a client.
     * Validates the permissions and unbans the address.
     */
    void HandleModUnbanPacket(const ENetEvent* event);

    /**
     * Parses and answers a get ban list request from a client.
     * Validates the permissions and returns the ban list.
     */
    void HandleModGetBanListPacket(const ENetEvent* event);

    /**
     * Returns whether the nickname is valid, ie. isn't already taken by someone else in the room.
     */
    bool IsValidNickname(const std::string& nickname) const;

    /**
     * Returns whether the fake ip address is valid, ie. isn't already taken by someone else in the
     * room.
     */
    bool IsValidFakeIPAddress(const IPv4Address& address) const;

    /**
     * Returns whether a user has mod permissions.
     */
    bool HasModPermission(const ENetPeer* client) const;

    /**
     * Sends a ID_ROOM_IS_FULL message telling the client that the room is full.
     */
    void SendRoomIsFull(ENetPeer* client);

    /**
     * Sends a ID_ROOM_NAME_COLLISION message telling the client that the name is invalid.
     */
    void SendNameCollision(ENetPeer* client);

    /**
     * Sends a ID_ROOM_IP_COLLISION message telling the client that the IP is invalid.
     */
    void SendIPCollision(ENetPeer* client);

    /**
     * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid.
     */
    void SendVersionMismatch(ENetPeer* client);

    /**
     * Sends a ID_ROOM_WRONG_PASSWORD message telling the client that the password is wrong.
     */
    void SendWrongPassword(ENetPeer* client);

    /**
     * Notifies the member that its connection attempt was successful,
     * and it is now part of the room.
     */
    void SendJoinSuccess(ENetPeer* client, IPv4Address fake_ip);

    /**
     * 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, IPv4Address fake_ip);

    /**
     * Sends a IdHostKicked message telling the client that they have been kicked.
     */
    void SendUserKicked(ENetPeer* client);

    /**
     * Sends a IdHostBanned message telling the client that they have been banned.
     */
    void SendUserBanned(ENetPeer* client);

    /**
     * Sends a IdModPermissionDenied message telling the client that they do not have mod
     * permission.
     */
    void SendModPermissionDenied(ENetPeer* client);

    /**
     * Sends a IdModNoSuchUser message telling the client that the given user could not be found.
     */
    void SendModNoSuchUser(ENetPeer* client);

    /**
     * Sends the ban list in response to a client's request for getting ban list.
     */
    void SendModBanListResponse(ENetPeer* client);

    /**
     * Notifies the members that the room is closed,
     */
    void SendCloseMessage();

    /**
     * Sends a system message to all the connected clients.
     */
    void SendStatusMessage(StatusMessageTypes type, const std::string& nickname,
                           const std::string& username, const std::string& ip);

    /**
     * Sends the information about the room, along with the list of members
     * to every connected client in the room.
     * The packet has the structure:
     * <MessageID>ID_ROOM_INFORMATION
     * <String> room_name
     * <String> room_description
     * <u32> member_slots: The max number of clients allowed in this room
     * <String> uid
     * <u16> port
     * <u32> num_members: the number of currently joined clients
     * This is followed by the following three values for each member:
     * <String> nickname of that member
     * <IPv4Address> fake_ip of that member
     * <String> game_name of that member
     */
    void BroadcastRoomInformation();

    /**
     * Generates a free MAC address to assign to a new client.
     * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32
     */
    IPv4Address GenerateFakeIPAddress();

    /**
     * Broadcasts this packet to all members except the sender.
     * @param event The ENet event containing the data
     */
    void HandleProxyPacket(const ENetEvent* event);

    /**
     * Broadcasts this packet to all members except the sender.
     * @param event The ENet event containing the data
     */
    void HandleLdnPacket(const ENetEvent* event);

    /**
     * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
     * @param event The ENet event that was received.
     */
    void HandleChatPacket(const ENetEvent* event);

    /**
     * Extracts the game name from a received ENet packet and broadcasts it.
     * @param event The ENet event that was received.
     */
    void HandleGameInfoPacket(const ENetEvent* event);

    /**
     * Removes the client from the members list if it was in it and announces the change
     * to all other clients.
     */
    void HandleClientDisconnection(ENetPeer* client);
};

// RoomImpl
void Room::RoomImpl::ServerLoop() {
    while (state != State::Closed) {
        ENetEvent event;
        if (enet_host_service(server, &event, 5) > 0) {
            switch (event.type) {
            case ENET_EVENT_TYPE_RECEIVE:
                switch (event.packet->data[0]) {
                case IdJoinRequest:
                    HandleJoinRequest(&event);
                    break;
                case IdSetGameInfo:
                    HandleGameInfoPacket(&event);
                    break;
                case IdProxyPacket:
                    HandleProxyPacket(&event);
                    break;
                case IdLdnPacket:
                    HandleLdnPacket(&event);
                    break;
                case IdChatMessage:
                    HandleChatPacket(&event);
                    break;
                // Moderation
                case IdModKick:
                    HandleModKickPacket(&event);
                    break;
                case IdModBan:
                    HandleModBanPacket(&event);
                    break;
                case IdModUnban:
                    HandleModUnbanPacket(&event);
                    break;
                case IdModGetBanList:
                    HandleModGetBanListPacket(&event);
                    break;
                }
                enet_packet_destroy(event.packet);
                break;
            case ENET_EVENT_TYPE_DISCONNECT:
                HandleClientDisconnection(event.peer);
                break;
            case ENET_EVENT_TYPE_NONE:
            case ENET_EVENT_TYPE_CONNECT:
                break;
            }
        }
    }
    // Close the connection to all members:
    SendCloseMessage();
}

void Room::RoomImpl::StartLoop() {
    room_thread = std::make_unique<std::thread>(&Room::RoomImpl::ServerLoop, this);
}

void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
    {
        std::lock_guard lock(member_mutex);
        if (members.size() >= room_information.member_slots) {
            SendRoomIsFull(event->peer);
            return;
        }
    }
    Packet packet;
    packet.Append(event->packet->data, event->packet->dataLength);
    packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
    std::string nickname;
    packet.Read(nickname);

    IPv4Address preferred_fake_ip;
    packet.Read(preferred_fake_ip);

    u32 client_version;
    packet.Read(client_version);

    std::string pass;
    packet.Read(pass);

    std::string token;
    packet.Read(token);

    if (pass != password) {
        SendWrongPassword(event->peer);
        return;
    }

    if (!IsValidNickname(nickname)) {
        SendNameCollision(event->peer);
        return;
    }

    if (preferred_fake_ip != NoPreferredIP) {
        // Verify if the preferred fake ip is available
        if (!IsValidFakeIPAddress(preferred_fake_ip)) {
            SendIPCollision(event->peer);
            return;
        }
    } else {
        // Assign a fake ip address of this client automatically
        preferred_fake_ip = GenerateFakeIPAddress();
    }

    if (client_version != network_version) {
        SendVersionMismatch(event->peer);
        return;
    }

    // At this point the client is ready to be added to the room.
    Member member{};
    member.fake_ip = preferred_fake_ip;
    member.nickname = nickname;
    member.peer = event->peer;

    std::string uid;
    {
        std::lock_guard lock(verify_uid_mutex);
        uid = verify_uid;
    }
    member.user_data = verify_backend->LoadUserData(uid, token);

    std::string ip;
    {
        std::lock_guard lock(ban_list_mutex);

        // Check username ban
        if (!member.user_data.username.empty() &&
            std::find(username_ban_list.begin(), username_ban_list.end(),
                      member.user_data.username) != username_ban_list.end()) {

            SendUserBanned(event->peer);
            return;
        }

        // Check IP ban
        std::array<char, 256> ip_raw{};
        enet_address_get_host_ip(&event->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
        ip = ip_raw.data();

        if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) != ip_ban_list.end()) {
            SendUserBanned(event->peer);
            return;
        }
    }

    // Notify everyone that the user has joined.
    SendStatusMessage(IdMemberJoin, member.nickname, member.user_data.username, ip);

    {
        std::lock_guard lock(member_mutex);
        members.push_back(std::move(member));
    }

    // Notify everyone that the room information has changed.
    BroadcastRoomInformation();
    if (HasModPermission(event->peer)) {
        SendJoinSuccessAsMod(event->peer, preferred_fake_ip);
    } else {
        SendJoinSuccess(event->peer, preferred_fake_ip);
    }
}

void Room::RoomImpl::HandleModKickPacket(const ENetEvent* event) {
    if (!HasModPermission(event->peer)) {
        SendModPermissionDenied(event->peer);
        return;
    }

    Packet packet;
    packet.Append(event->packet->data, event->packet->dataLength);
    packet.IgnoreBytes(sizeof(u8)); // Ignore the message type

    std::string nickname;
    packet.Read(nickname);

    std::string username, ip;
    {
        std::lock_guard lock(member_mutex);
        const auto target_member =
            std::find_if(members.begin(), members.end(),
                         [&nickname](const auto& member) { return member.nickname == nickname; });
        if (target_member == members.end()) {
            SendModNoSuchUser(event->peer);
            return;
        }

        // Notify the kicked member
        SendUserKicked(target_member->peer);

        username = target_member->user_data.username;

        std::array<char, 256> ip_raw{};
        enet_address_get_host_ip(&target_member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
        ip = ip_raw.data();

        enet_peer_disconnect(target_member->peer, 0);
        members.erase(target_member);
    }

    // Announce the change to all clients.
    SendStatusMessage(IdMemberKicked, nickname, username, ip);
    BroadcastRoomInformation();
}

void Room::RoomImpl::HandleModBanPacket(const ENetEvent* event) {
    if (!HasModPermission(event->peer)) {
        SendModPermissionDenied(event->peer);
        return;
    }

    Packet packet;
    packet.Append(event->packet->data, event->packet->dataLength);
    packet.IgnoreBytes(sizeof(u8)); // Ignore the message type

    std::string nickname;
    packet.Read(nickname);

    std::string username, ip;
    {
        std::lock_guard lock(member_mutex);
        const auto target_member =
            std::find_if(members.begin(), members.end(),
                         [&nickname](const auto& member) { return member.nickname == nickname; });
        if (target_member == members.end()) {
            SendModNoSuchUser(event->peer);
            return;
        }

        // Notify the banned member
        SendUserBanned(target_member->peer);

        nickname = target_member->nickname;
        username = target_member->user_data.username;

        std::array<char, 256> ip_raw{};
        enet_address_get_host_ip(&target_member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
        ip = ip_raw.data();

        enet_peer_disconnect(target_member->peer, 0);
        members.erase(target_member);
    }

    {
        std::lock_guard lock(ban_list_mutex);

        if (!username.empty()) {
            // Ban the forum username
            if (std::find(username_ban_list.begin(), username_ban_list.end(), username) ==
                username_ban_list.end()) {

                username_ban_list.emplace_back(username);
            }
        }

        // Ban the member's IP as well
        if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) == ip_ban_list.end()) {
            ip_ban_list.emplace_back(ip);
        }
    }

    // Announce the change to all clients.
    SendStatusMessage(IdMemberBanned, nickname, username, ip);
    BroadcastRoomInformation();
}

void Room::RoomImpl::HandleModUnbanPacket(const ENetEvent* event) {
    if (!HasModPermission(event->peer)) {
        SendModPermissionDenied(event->peer);
        return;
    }

    Packet packet;
    packet.Append(event->packet->data, event->packet->dataLength);
    packet.IgnoreBytes(sizeof(u8)); // Ignore the message type

    std::string address;
    packet.Read(address);

    bool unbanned = false;
    {
        std::lock_guard lock(ban_list_mutex);

        auto it = std::find(username_ban_list.begin(), username_ban_list.end(), address);
        if (it != username_ban_list.end()) {
            unbanned = true;
            username_ban_list.erase(it);
        }

        it = std::find(ip_ban_list.begin(), ip_ban_list.end(), address);
        if (it != ip_ban_list.end()) {
            unbanned = true;
            ip_ban_list.erase(it);
        }
    }

    if (unbanned) {
        SendStatusMessage(IdAddressUnbanned, address, "", "");
    } else {
        SendModNoSuchUser(event->peer);
    }
}

void Room::RoomImpl::HandleModGetBanListPacket(const ENetEvent* event) {
    if (!HasModPermission(event->peer)) {
        SendModPermissionDenied(event->peer);
        return;
    }

    SendModBanListResponse(event->peer);
}

bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const {
    // A nickname is valid if it matches the regex and is not already taken by anybody else in the
    // room.
    const std::regex nickname_regex("^[ a-zA-Z0-9._-]{4,20}$");
    if (!std::regex_match(nickname, nickname_regex))
        return false;

    std::lock_guard lock(member_mutex);
    return std::all_of(members.begin(), members.end(),
                       [&nickname](const auto& member) { return member.nickname != nickname; });
}

bool Room::RoomImpl::IsValidFakeIPAddress(const IPv4Address& address) const {
    // An IP address 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(),
                       [&address](const auto& member) { return member.fake_ip != address; });
}

bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const {
    std::lock_guard lock(member_mutex);
    const auto sending_member =
        std::find_if(members.begin(), members.end(),
                     [client](const auto& member) { return member.peer == client; });
    if (sending_member == members.end()) {
        return false;
    }
    if (room_information.enable_yuzu_mods &&
        sending_member->user_data.moderator) { // Community moderator

        return true;
    }
    if (!room_information.host_username.empty() &&
        sending_member->user_data.username == room_information.host_username) { // Room host

        return true;
    }
    return false;
}

void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
    Packet packet;
    packet.Write(static_cast<u8>(IdNameCollision));

    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::SendIPCollision(ENetPeer* client) {
    Packet packet;
    packet.Write(static_cast<u8>(IdIpCollision));

    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.Write(static_cast<u8>(IdWrongPassword));

    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::SendRoomIsFull(ENetPeer* client) {
    Packet packet;
    packet.Write(static_cast<u8>(IdRoomIsFull));

    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::SendVersionMismatch(ENetPeer* client) {
    Packet packet;
    packet.Write(static_cast<u8>(IdVersionMismatch));
    packet.Write(network_version);

    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::SendJoinSuccess(ENetPeer* client, IPv4Address fake_ip) {
    Packet packet;
    packet.Write(static_cast<u8>(IdJoinSuccess));
    packet.Write(fake_ip);
    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::SendJoinSuccessAsMod(ENetPeer* client, IPv4Address fake_ip) {
    Packet packet;
    packet.Write(static_cast<u8>(IdJoinSuccessAsMod));
    packet.Write(fake_ip);
    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.Write(static_cast<u8>(IdHostKicked));

    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::SendUserBanned(ENetPeer* client) {
    Packet packet;
    packet.Write(static_cast<u8>(IdHostBanned));

    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::SendModPermissionDenied(ENetPeer* client) {
    Packet packet;
    packet.Write(static_cast<u8>(IdModPermissionDenied));

    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::SendModNoSuchUser(ENetPeer* client) {
    Packet packet;
    packet.Write(static_cast<u8>(IdModNoSuchUser));

    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::SendModBanListResponse(ENetPeer* client) {
    Packet packet;
    packet.Write(static_cast<u8>(IdModBanListResponse));
    {
        std::lock_guard lock(ban_list_mutex);
        packet.Write(username_ban_list);
        packet.Write(ip_ban_list);
    }

    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::SendCloseMessage() {
    Packet packet;
    packet.Write(static_cast<u8>(IdCloseRoom));
    std::lock_guard lock(member_mutex);
    if (!members.empty()) {
        ENetPacket* enet_packet =
            enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
        for (auto& member : members) {
            enet_peer_send(member.peer, 0, enet_packet);
        }
    }
    enet_host_flush(server);
    for (auto& member : members) {
        enet_peer_disconnect(member.peer, 0);
    }
}

void Room::RoomImpl::SendStatusMessage(StatusMessageTypes type, const std::string& nickname,
                                       const std::string& username, const std::string& ip) {
    Packet packet;
    packet.Write(static_cast<u8>(IdStatusMessage));
    packet.Write(static_cast<u8>(type));
    packet.Write(nickname);
    packet.Write(username);
    std::lock_guard lock(member_mutex);
    if (!members.empty()) {
        ENetPacket* enet_packet =
            enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
        for (auto& member : members) {
            enet_peer_send(member.peer, 0, enet_packet);
        }
    }
    enet_host_flush(server);

    const std::string display_name =
        username.empty() ? nickname : fmt::format("{} ({})", nickname, username);

    switch (type) {
    case IdMemberJoin:
        LOG_INFO(Network, "[{}] {} has joined.", ip, display_name);
        break;
    case IdMemberLeave:
        LOG_INFO(Network, "[{}] {} has left.", ip, display_name);
        break;
    case IdMemberKicked:
        LOG_INFO(Network, "[{}] {} has been kicked.", ip, display_name);
        break;
    case IdMemberBanned:
        LOG_INFO(Network, "[{}] {} has been banned.", ip, display_name);
        break;
    case IdAddressUnbanned:
        LOG_INFO(Network, "{} has been unbanned.", display_name);
        break;
    }
}

void Room::RoomImpl::BroadcastRoomInformation() {
    Packet packet;
    packet.Write(static_cast<u8>(IdRoomInformation));
    packet.Write(room_information.name);
    packet.Write(room_information.description);
    packet.Write(room_information.member_slots);
    packet.Write(room_information.port);
    packet.Write(room_information.preferred_game.name);
    packet.Write(room_information.host_username);

    packet.Write(static_cast<u32>(members.size()));
    {
        std::lock_guard lock(member_mutex);
        for (const auto& member : members) {
            packet.Write(member.nickname);
            packet.Write(member.fake_ip);
            packet.Write(member.game_info.name);
            packet.Write(member.game_info.id);
            packet.Write(member.game_info.version);
            packet.Write(member.user_data.username);
            packet.Write(member.user_data.display_name);
            packet.Write(member.user_data.avatar_url);
        }
    }

    ENetPacket* enet_packet =
        enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
    enet_host_broadcast(server, 0, enet_packet);
    enet_host_flush(server);
}

IPv4Address Room::RoomImpl::GenerateFakeIPAddress() {
    IPv4Address result_ip{192, 168, 0, 0};
    std::uniform_int_distribution<> dis(0x01, 0xFE); // Random byte between 1 and 0xFE
    do {
        for (std::size_t i = 2; i < result_ip.size(); ++i) {
            result_ip[i] = static_cast<u8>(dis(random_gen));
        }
    } while (!IsValidFakeIPAddress(result_ip));

    return result_ip;
}

void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) {
    Packet in_packet;
    in_packet.Append(event->packet->data, event->packet->dataLength);
    in_packet.IgnoreBytes(sizeof(u8)); // Message type

    in_packet.IgnoreBytes(sizeof(u8));          // Domain
    in_packet.IgnoreBytes(sizeof(IPv4Address)); // IP
    in_packet.IgnoreBytes(sizeof(u16));         // Port

    in_packet.IgnoreBytes(sizeof(u8)); // Domain
    IPv4Address remote_ip;
    in_packet.Read(remote_ip);          // IP
    in_packet.IgnoreBytes(sizeof(u16)); // Port

    in_packet.IgnoreBytes(sizeof(u8)); // Protocol

    bool broadcast;
    in_packet.Read(broadcast); // Broadcast

    Packet out_packet;
    out_packet.Append(event->packet->data, event->packet->dataLength);
    ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
                                                 ENET_PACKET_FLAG_RELIABLE);

    const auto& destination_address = remote_ip;
    if (broadcast) { // Send the data to everyone except the sender
        std::lock_guard lock(member_mutex);
        bool sent_packet = false;
        for (const auto& member : members) {
            if (member.peer != event->peer) {
                sent_packet = true;
                enet_peer_send(member.peer, 0, enet_packet);
            }
        }

        if (!sent_packet) {
            enet_packet_destroy(enet_packet);
        }
    } else { // Send the data only to the destination client
        std::lock_guard lock(member_mutex);
        auto member = std::find_if(members.begin(), members.end(),
                                   [destination_address](const Member& member_entry) -> bool {
                                       return member_entry.fake_ip == destination_address;
                                   });
        if (member != members.end()) {
            enet_peer_send(member->peer, 0, enet_packet);
        } else {
            LOG_ERROR(Network,
                      "Attempting to send to unknown IP address: "
                      "{}.{}.{}.{}",
                      destination_address[0], destination_address[1], destination_address[2],
                      destination_address[3]);
            enet_packet_destroy(enet_packet);
        }
    }
    enet_host_flush(server);
}

void Room::RoomImpl::HandleLdnPacket(const ENetEvent* event) {
    Packet in_packet;
    in_packet.Append(event->packet->data, event->packet->dataLength);

    in_packet.IgnoreBytes(sizeof(u8)); // Message type

    in_packet.IgnoreBytes(sizeof(u8));          // LAN packet type
    in_packet.IgnoreBytes(sizeof(IPv4Address)); // Local IP

    IPv4Address remote_ip;
    in_packet.Read(remote_ip); // Remote IP

    bool broadcast;
    in_packet.Read(broadcast); // Broadcast

    Packet out_packet;
    out_packet.Append(event->packet->data, event->packet->dataLength);
    ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
                                                 ENET_PACKET_FLAG_RELIABLE);

    const auto& destination_address = remote_ip;
    if (broadcast) { // Send the data to everyone except the sender
        std::lock_guard lock(member_mutex);
        bool sent_packet = false;
        for (const auto& member : members) {
            if (member.peer != event->peer) {
                sent_packet = true;
                enet_peer_send(member.peer, 0, enet_packet);
            }
        }

        if (!sent_packet) {
            enet_packet_destroy(enet_packet);
        }
    } else {
        std::lock_guard lock(member_mutex);
        auto member = std::find_if(members.begin(), members.end(),
                                   [destination_address](const Member& member_entry) -> bool {
                                       return member_entry.fake_ip == destination_address;
                                   });
        if (member != members.end()) {
            enet_peer_send(member->peer, 0, enet_packet);
        } else {
            LOG_ERROR(Network,
                      "Attempting to send to unknown IP address: "
                      "{}.{}.{}.{}",
                      destination_address[0], destination_address[1], destination_address[2],
                      destination_address[3]);
            enet_packet_destroy(enet_packet);
        }
    }
    enet_host_flush(server);
}

void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
    Packet in_packet;
    in_packet.Append(event->packet->data, event->packet->dataLength);

    in_packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
    std::string message;
    in_packet.Read(message);
    auto CompareNetworkAddress = [event](const Member member) -> bool {
        return member.peer == event->peer;
    };

    std::lock_guard lock(member_mutex);
    const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress);
    if (sending_member == members.end()) {
        return; // Received a chat message from a unknown sender
    }

    // Limit the size of chat messages to MaxMessageSize
    message.resize(std::min(static_cast<u32>(message.size()), MaxMessageSize));

    Packet out_packet;
    out_packet.Write(static_cast<u8>(IdChatMessage));
    out_packet.Write(sending_member->nickname);
    out_packet.Write(sending_member->user_data.username);
    out_packet.Write(message);

    ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
                                                 ENET_PACKET_FLAG_RELIABLE);
    bool sent_packet = false;
    for (const auto& member : members) {
        if (member.peer != event->peer) {
            sent_packet = true;
            enet_peer_send(member.peer, 0, enet_packet);
        }
    }

    if (!sent_packet) {
        enet_packet_destroy(enet_packet);
    }

    enet_host_flush(server);

    if (sending_member->user_data.username.empty()) {
        LOG_INFO(Network, "{}: {}", sending_member->nickname, message);
    } else {
        LOG_INFO(Network, "{} ({}): {}", sending_member->nickname,
                 sending_member->user_data.username, message);
    }
}

void Room::RoomImpl::HandleGameInfoPacket(const ENetEvent* event) {
    Packet in_packet;
    in_packet.Append(event->packet->data, event->packet->dataLength);

    in_packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
    GameInfo game_info;
    in_packet.Read(game_info.name);
    in_packet.Read(game_info.id);
    in_packet.Read(game_info.version);

    {
        std::lock_guard lock(member_mutex);
        auto member = std::find_if(members.begin(), members.end(),
                                   [event](const Member& member_entry) -> bool {
                                       return member_entry.peer == event->peer;
                                   });
        if (member != members.end()) {
            member->game_info = game_info;

            const std::string display_name =
                member->user_data.username.empty()
                    ? member->nickname
                    : fmt::format("{} ({})", member->nickname, member->user_data.username);

            if (game_info.name.empty()) {
                LOG_INFO(Network, "{} is not playing", display_name);
            } else {
                LOG_INFO(Network, "{} is playing {} ({})", display_name, game_info.name,
                         game_info.version);
            }
        }
    }
    BroadcastRoomInformation();
}

void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) {
    // Remove the client from the members list.
    std::string nickname, username, ip;
    {
        std::lock_guard lock(member_mutex);
        auto member =
            std::find_if(members.begin(), members.end(), [client](const Member& member_entry) {
                return member_entry.peer == client;
            });
        if (member != members.end()) {
            nickname = member->nickname;
            username = member->user_data.username;

            std::array<char, 256> ip_raw{};
            enet_address_get_host_ip(&member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
            ip = ip_raw.data();

            members.erase(member);
        }
    }

    // Announce the change to all clients.
    enet_peer_disconnect(client, 0);
    if (!nickname.empty())
        SendStatusMessage(IdMemberLeave, nickname, username, ip);
    BroadcastRoomInformation();
}

// Room
Room::Room() : room_impl{std::make_unique<RoomImpl>()} {}

Room::~Room() = default;

bool Room::Create(const std::string& name, const std::string& description,
                  const std::string& server_address, u16 server_port, const std::string& password,
                  const u32 max_connections, const std::string& host_username,
                  const GameInfo preferred_game,
                  std::unique_ptr<VerifyUser::Backend> verify_backend,
                  const Room::BanList& ban_list, bool enable_yuzu_mods) {
    ENetAddress address;
    address.host = ENET_HOST_ANY;
    if (!server_address.empty()) {
        enet_address_set_host(&address, server_address.c_str());
    }
    address.port = server_port;

    // In order to send the room is full message to the connecting client, we need to leave one
    // slot open so enet won't reject the incoming connection without telling us
    room_impl->server = enet_host_create(&address, max_connections + 1, NumChannels, 0, 0);
    if (!room_impl->server) {
        return false;
    }
    room_impl->state = State::Open;

    room_impl->room_information.name = name;
    room_impl->room_information.description = description;
    room_impl->room_information.member_slots = max_connections;
    room_impl->room_information.port = server_port;
    room_impl->room_information.preferred_game = preferred_game;
    room_impl->room_information.host_username = host_username;
    room_impl->room_information.enable_yuzu_mods = enable_yuzu_mods;
    room_impl->password = password;
    room_impl->verify_backend = std::move(verify_backend);
    room_impl->username_ban_list = ban_list.first;
    room_impl->ip_ban_list = ban_list.second;

    room_impl->StartLoop();
    return true;
}

Room::State Room::GetState() const {
    return room_impl->state;
}

const RoomInformation& Room::GetRoomInformation() const {
    return room_impl->room_information;
}

std::string Room::GetVerifyUID() const {
    std::lock_guard lock(room_impl->verify_uid_mutex);
    return room_impl->verify_uid;
}

Room::BanList Room::GetBanList() const {
    std::lock_guard lock(room_impl->ban_list_mutex);
    return {room_impl->username_ban_list, room_impl->ip_ban_list};
}

std::vector<Member> Room::GetRoomMemberList() const {
    std::vector<Member> member_list;
    std::lock_guard lock(room_impl->member_mutex);
    for (const auto& member_impl : room_impl->members) {
        Member member;
        member.nickname = member_impl.nickname;
        member.username = member_impl.user_data.username;
        member.display_name = member_impl.user_data.display_name;
        member.avatar_url = member_impl.user_data.avatar_url;
        member.fake_ip = member_impl.fake_ip;
        member.game = member_impl.game_info;
        member_list.push_back(member);
    }
    return member_list;
}

bool Room::HasPassword() const {
    return !room_impl->password.empty();
}

void Room::SetVerifyUID(const std::string& uid) {
    std::lock_guard lock(room_impl->verify_uid_mutex);
    room_impl->verify_uid = uid;
}

void Room::Destroy() {
    room_impl->state = State::Closed;
    room_impl->room_thread->join();
    room_impl->room_thread.reset();

    if (room_impl->server) {
        enet_host_destroy(room_impl->server);
    }
    room_impl->room_information = {};
    room_impl->server = nullptr;
    {
        std::lock_guard lock(room_impl->member_mutex);
        room_impl->members.clear();
    }
    room_impl->room_information.member_slots = 0;
    room_impl->room_information.name.clear();
}

} // namespace Network


code:
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <memory>
#include <string>
#include <vector>
#include "common/announce_multiplayer_room.h"
#include "common/common_types.h"
#include "common/socket_types.h"
#include "network/verify_user.h"

namespace Network {

using AnnounceMultiplayerRoom::GameInfo;
using AnnounceMultiplayerRoom::Member;
using AnnounceMultiplayerRoom::RoomInformation;

constexpr u32 network_version = 1; ///< The version of this Room and RoomMember

constexpr u16 DefaultRoomPort = 24872;

constexpr u32 MaxMessageSize = 500;

/// Maximum number of concurrent connections allowed to this room.
static constexpr u32 MaxConcurrentConnections = 254;

constexpr std::size_t NumChannels = 1; // Number of channels used for the connection

/// A special IP address that tells the room we're joining to assign us a IP address
/// automatically.
constexpr IPv4Address NoPreferredIP = {0xFF, 0xFF, 0xFF, 0xFF};

// The different types of messages that can be sent. The first byte of each packet defines the type
enum RoomMessageTypes : u8 {
    IdJoinRequest = 1,
    IdJoinSuccess,
    IdRoomInformation,
    IdSetGameInfo,
    IdProxyPacket,
    IdLdnPacket,
    IdChatMessage,
    IdNameCollision,
    IdIpCollision,
    IdVersionMismatch,
    IdWrongPassword,
    IdCloseRoom,
    IdRoomIsFull,
    IdStatusMessage,
    IdHostKicked,
    IdHostBanned,
    /// Moderation requests
    IdModKick,
    IdModBan,
    IdModUnban,
    IdModGetBanList,
    // Moderation responses
    IdModBanListResponse,
    IdModPermissionDenied,
    IdModNoSuchUser,
    IdJoinSuccessAsMod,
};

/// Types of system status messages
enum StatusMessageTypes : u8 {
    IdMemberJoin = 1,  ///< Member joining
    IdMemberLeave,     ///< Member leaving
    IdMemberKicked,    ///< A member is kicked from the room
    IdMemberBanned,    ///< A member is banned from the room
    IdAddressUnbanned, ///< A username / ip address is unbanned from the room
};

/// This is what a server [person creating a server] would use.
class Room final {
public:
    enum class State : u8 {
        Open,   ///< The room is open and ready to accept connections.
        Closed, ///< The room is not opened and can not accept connections.
    };

    Room();
    ~Room();

    /**
     * Gets the current state of the room.
     */
    State GetState() const;

    /**
     * Gets the room information of the room.
     */
    const RoomInformation& GetRoomInformation() const;

    /**
     * Gets the verify UID of this room.
     */
    std::string GetVerifyUID() const;

    /**
     * Gets a list of the mbmers connected to the room.
     */
    std::vector<Member> GetRoomMemberList() const;

    /**
     * Checks if the room is password protected
     */
    bool HasPassword() const;

    using UsernameBanList = std::vector<std::string>;
    using IPBanList = std::vector<std::string>;

    using BanList = std::pair<UsernameBanList, IPBanList>;

    /**
     * Creates the socket for this room. Will bind to default address if
     * server is empty string.
     */
    bool Create(const std::string& name, const std::string& description = "",
                const std::string& server = "", u16 server_port = DefaultRoomPort,
                const std::string& password = "",
                const u32 max_connections = MaxConcurrentConnections,
                const std::string& host_username = "", const GameInfo = {},
                std::unique_ptr<VerifyUser::Backend> verify_backend = nullptr,
                const BanList& ban_list = {}, bool enable_yuzu_mods = false);

    /**
     * Sets the verification GUID of the room.
     */
    void SetVerifyUID(const std::string& uid);

    /**
     * Gets the ban list (including banned forum usernames and IPs) of the room.
     */
    BanList GetBanList() const;

    /**
     * Destroys the socket
     */
    void Destroy();

private:
    class RoomImpl;
    std::unique_ptr<RoomImpl> room_impl;
};

} // namespace Network


code:
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <atomic>
#include <list>
#include <mutex>
#include <set>
#include <thread>
#include "common/assert.h"
#include "common/socket_types.h"
#include "enet/enet.h"
#include "network/packet.h"
#include "network/room_member.h"

namespace Network {

constexpr u32 ConnectionTimeoutMs = 5000;

class RoomMember::RoomMemberImpl {
public:
    ENetHost* client = nullptr; ///< ENet network interface.
    ENetPeer* server = nullptr; ///< The server peer the client is connected to

    /// Information about the clients connected to the same room as us.
    MemberList member_information;
    /// Information about the room we're connected to.
    RoomInformation room_information;

    /// The current game name, id and version
    GameInfo current_game_info;

    std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
    void SetState(const State new_state);
    void SetError(const Error new_error);
    bool IsConnected() const;

    std::string nickname; ///< The nickname of this member.

    std::string username;              ///< The username of this member.
    mutable std::mutex username_mutex; ///< Mutex for locking username.

    IPv4Address fake_ip; ///< The fake ip of this member.

    std::mutex network_mutex; ///< Mutex that controls access to the `client` variable.
    /// Thread that receives and dispatches network packets
    std::unique_ptr<std::thread> loop_thread;
    std::mutex send_list_mutex;  ///< Mutex that controls access to the `send_list` variable.
    std::list<Packet> send_list; ///< A list that stores all packets to send the async

    template <typename T>
    using CallbackSet = std::set<CallbackHandle<T>>;
    std::mutex callback_mutex; ///< The mutex used for handling callbacks

    class Callbacks {
    public:
        template <typename T>
        CallbackSet<T>& Get();

    private:
        CallbackSet<ProxyPacket> callback_set_proxy_packet;
        CallbackSet<LDNPacket> callback_set_ldn_packet;
        CallbackSet<ChatEntry> callback_set_chat_messages;
        CallbackSet<StatusMessageEntry> callback_set_status_messages;
        CallbackSet<RoomInformation> callback_set_room_information;
        CallbackSet<State> callback_set_state;
        CallbackSet<Error> callback_set_error;
        CallbackSet<Room::BanList> callback_set_ban_list;
    };
    Callbacks callbacks; ///< All CallbackSets to all events

    void MemberLoop();

    void StartLoop();

    /**
     * Sends data to the room. It will be send on channel 0 with flag RELIABLE
     * @param packet The data to send
     */
    void Send(Packet&& packet);

    /**
     * Sends a request to the server, asking for permission to join a room with the specified
     * nickname and preferred fake ip.
     * @params nickname The desired nickname.
     * @params preferred_fake_ip The preferred IP address to use in the room, the NoPreferredIP
     * tells
     * @params password The password for the room
     * the server to assign one for us.
     */
    void SendJoinRequest(const std::string& nickname_,
                         const IPv4Address& preferred_fake_ip = NoPreferredIP,
                         const std::string& password = "", const std::string& token = "");

    /**
     * Extracts a MAC Address from a received ENet packet.
     * @param event The ENet event that was received.
     */
    void HandleJoinPacket(const ENetEvent* event);
    /**
     * Extracts RoomInformation and MemberInformation from a received ENet packet.
     * @param event The ENet event that was received.
     */
    void HandleRoomInformationPacket(const ENetEvent* event);

    /**
     * Extracts a ProxyPacket from a received ENet packet.
     * @param event The ENet event that was received.
     */
    void HandleProxyPackets(const ENetEvent* event);

    /**
     * Extracts an LdnPacket from a received ENet packet.
     * @param event The ENet event that was received.
     */
    void HandleLdnPackets(const ENetEvent* event);

    /**
     * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
     * @param event The ENet event that was received.
     */
    void HandleChatPacket(const ENetEvent* event);

    /**
     * Extracts a system message entry from a received ENet packet and adds it to the system message
     * queue.
     * @param event The ENet event that was received.
     */
    void HandleStatusMessagePacket(const ENetEvent* event);

    /**
     * Extracts a ban list request response from a received ENet packet.
     * @param event The ENet event that was received.
     */
    void HandleModBanListResponsePacket(const ENetEvent* event);

    /**
     * Disconnects the RoomMember from the Room
     */
    void Disconnect();

    template <typename T>
    void Invoke(const T& data);

    template <typename T>
    CallbackHandle<T> Bind(std::function<void(const T&)> callback);
};

// RoomMemberImpl
void RoomMember::RoomMemberImpl::SetState(const State new_state) {
    if (state != new_state) {
        state = new_state;
        Invoke<State>(state);
    }
}

void RoomMember::RoomMemberImpl::SetError(const Error new_error) {
    Invoke<Error>(new_error);
}

bool RoomMember::RoomMemberImpl::IsConnected() const {
    return state == State::Joining || state == State::Joined || state == State::Moderator;
}

void RoomMember::RoomMemberImpl::MemberLoop() {
    // Receive packets while the connection is open
    while (IsConnected()) {
        std::lock_guard lock(network_mutex);
        ENetEvent event;
        if (enet_host_service(client, &event, 5) > 0) {
            switch (event.type) {
            case ENET_EVENT_TYPE_RECEIVE:
                switch (event.packet->data[0]) {
                case IdProxyPacket:
                    HandleProxyPackets(&event);
                    break;
                case IdLdnPacket:
                    HandleLdnPackets(&event);
                    break;
                case IdChatMessage:
                    HandleChatPacket(&event);
                    break;
                case IdStatusMessage:
                    HandleStatusMessagePacket(&event);
                    break;
                case IdRoomInformation:
                    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
                    if (event.packet->data[0] == IdJoinSuccessAsMod) {
                        SetState(State::Moderator);
                    } else {
                        SetState(State::Joined);
                    }
                    break;
                case IdModBanListResponse:
                    HandleModBanListResponsePacket(&event);
                    break;
                case IdRoomIsFull:
                    SetState(State::Idle);
                    SetError(Error::RoomIsFull);
                    break;
                case IdNameCollision:
                    SetState(State::Idle);
                    SetError(Error::NameCollision);
                    break;
                case IdIpCollision:
                    SetState(State::Idle);
                    SetError(Error::IpCollision);
                    break;
                case IdVersionMismatch:
                    SetState(State::Idle);
                    SetError(Error::WrongVersion);
                    break;
                case IdWrongPassword:
                    SetState(State::Idle);
                    SetError(Error::WrongPassword);
                    break;
                case IdCloseRoom:
                    SetState(State::Idle);
                    SetError(Error::LostConnection);
                    break;
                case IdHostKicked:
                    SetState(State::Idle);
                    SetError(Error::HostKicked);
                    break;
                case IdHostBanned:
                    SetState(State::Idle);
                    SetError(Error::HostBanned);
                    break;
                case IdModPermissionDenied:
                    SetError(Error::PermissionDenied);
                    break;
                case IdModNoSuchUser:
                    SetError(Error::NoSuchUser);
                    break;
                }
                enet_packet_destroy(event.packet);
                break;
            case ENET_EVENT_TYPE_DISCONNECT:
                if (state == State::Joined || state == State::Moderator) {
                    SetState(State::Idle);
                    SetError(Error::LostConnection);
                }
                break;
            case ENET_EVENT_TYPE_NONE:
                break;
            case ENET_EVENT_TYPE_CONNECT:
                // The ENET_EVENT_TYPE_CONNECT event can not possibly happen here because we're
                // already connected
                ASSERT_MSG(false, "Received unexpected connect event while already connected");
                break;
            }
        }
        std::list<Packet> packets;
        {
            std::lock_guard send_lock(send_list_mutex);
            packets.swap(send_list);
        }
        for (const auto& packet : packets) {
            ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
                                                        ENET_PACKET_FLAG_RELIABLE);
            enet_peer_send(server, 0, enetPacket);
        }
        enet_host_flush(client);
    }
    Disconnect();
};

void RoomMember::RoomMemberImpl::StartLoop() {
    loop_thread = std::make_unique<std::thread>(&RoomMember::RoomMemberImpl::MemberLoop, this);
}

void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
    std::lock_guard lock(send_list_mutex);
    send_list.push_back(std::move(packet));
}

void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname_,
                                                 const IPv4Address& preferred_fake_ip,
                                                 const std::string& password,
                                                 const std::string& token) {
    Packet packet;
    packet.Write(static_cast<u8>(IdJoinRequest));
    packet.Write(nickname_);
    packet.Write(preferred_fake_ip);
    packet.Write(network_version);
    packet.Write(password);
    packet.Write(token);
    Send(std::move(packet));
}

void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* event) {
    Packet packet;
    packet.Append(event->packet->data, event->packet->dataLength);

    // Ignore the first byte, which is the message id.
    packet.IgnoreBytes(sizeof(u8)); // Ignore the message type

    RoomInformation info{};
    packet.Read(info.name);
    packet.Read(info.description);
    packet.Read(info.member_slots);
    packet.Read(info.port);
    packet.Read(info.preferred_game.name);
    packet.Read(info.host_username);
    room_information.name = info.name;
    room_information.description = info.description;
    room_information.member_slots = info.member_slots;
    room_information.port = info.port;
    room_information.preferred_game = info.preferred_game;
    room_information.host_username = info.host_username;

    u32 num_members;
    packet.Read(num_members);
    member_information.resize(num_members);

    for (auto& member : member_information) {
        packet.Read(member.nickname);
        packet.Read(member.fake_ip);
        packet.Read(member.game_info.name);
        packet.Read(member.game_info.id);
        packet.Read(member.game_info.version);
        packet.Read(member.username);
        packet.Read(member.display_name);
        packet.Read(member.avatar_url);

        {
            std::lock_guard lock(username_mutex);
            if (member.nickname == nickname) {
                username = member.username;
            }
        }
    }
    Invoke(room_information);
}

void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {
    Packet packet;
    packet.Append(event->packet->data, event->packet->dataLength);

    // Ignore the first byte, which is the message id.
    packet.IgnoreBytes(sizeof(u8)); // Ignore the message type

    // Parse the MAC Address from the packet
    packet.Read(fake_ip);
}

void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) {
    ProxyPacket proxy_packet{};
    Packet packet;
    packet.Append(event->packet->data, event->packet->dataLength);

    // Ignore the first byte, which is the message id.
    packet.IgnoreBytes(sizeof(u8)); // Ignore the message type

    // Parse the ProxyPacket from the packet
    u8 local_family;
    packet.Read(local_family);
    proxy_packet.local_endpoint.family = static_cast<Domain>(local_family);
    packet.Read(proxy_packet.local_endpoint.ip);
    packet.Read(proxy_packet.local_endpoint.portno);

    u8 remote_family;
    packet.Read(remote_family);
    proxy_packet.remote_endpoint.family = static_cast<Domain>(remote_family);
    packet.Read(proxy_packet.remote_endpoint.ip);
    packet.Read(proxy_packet.remote_endpoint.portno);

    u8 protocol_type;
    packet.Read(protocol_type);
    proxy_packet.protocol = static_cast<Protocol>(protocol_type);

    packet.Read(proxy_packet.broadcast);
    packet.Read(proxy_packet.data);

    Invoke<ProxyPacket>(proxy_packet);
}

void RoomMember::RoomMemberImpl::HandleLdnPackets(const ENetEvent* event) {
    LDNPacket ldn_packet{};
    Packet packet;
    packet.Append(event->packet->data, event->packet->dataLength);

    // Ignore the first byte, which is the message id.
    packet.IgnoreBytes(sizeof(u8)); // Ignore the message type

    u8 packet_type;
    packet.Read(packet_type);
    ldn_packet.type = static_cast<LDNPacketType>(packet_type);

    packet.Read(ldn_packet.local_ip);
    packet.Read(ldn_packet.remote_ip);
    packet.Read(ldn_packet.broadcast);

    packet.Read(ldn_packet.data);

    Invoke<LDNPacket>(ldn_packet);
}

void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
    Packet packet;
    packet.Append(event->packet->data, event->packet->dataLength);

    // Ignore the first byte, which is the message id.
    packet.IgnoreBytes(sizeof(u8));

    ChatEntry chat_entry{};
    packet.Read(chat_entry.nickname);
    packet.Read(chat_entry.username);
    packet.Read(chat_entry.message);
    Invoke<ChatEntry>(chat_entry);
}

void RoomMember::RoomMemberImpl::HandleStatusMessagePacket(const ENetEvent* event) {
    Packet packet;
    packet.Append(event->packet->data, event->packet->dataLength);

    // Ignore the first byte, which is the message id.
    packet.IgnoreBytes(sizeof(u8));

    StatusMessageEntry status_message_entry{};
    u8 type{};
    packet.Read(type);
    status_message_entry.type = static_cast<StatusMessageTypes>(type);
    packet.Read(status_message_entry.nickname);
    packet.Read(status_message_entry.username);
    Invoke<StatusMessageEntry>(status_message_entry);
}

void RoomMember::RoomMemberImpl::HandleModBanListResponsePacket(const ENetEvent* event) {
    Packet packet;
    packet.Append(event->packet->data, event->packet->dataLength);

    // Ignore the first byte, which is the message id.
    packet.IgnoreBytes(sizeof(u8));

    Room::BanList ban_list = {};
    packet.Read(ban_list.first);
    packet.Read(ban_list.second);
    Invoke<Room::BanList>(ban_list);
}

void RoomMember::RoomMemberImpl::Disconnect() {
    member_information.clear();
    room_information.member_slots = 0;
    room_information.name.clear();

    if (!server) {
        return;
    }
    enet_peer_disconnect(server, 0);

    ENetEvent event;
    while (enet_host_service(client, &event, ConnectionTimeoutMs) > 0) {
        switch (event.type) {
        case ENET_EVENT_TYPE_RECEIVE:
            enet_packet_destroy(event.packet); // Ignore all incoming data
            break;
        case ENET_EVENT_TYPE_DISCONNECT:
            server = nullptr;
            return;
        case ENET_EVENT_TYPE_NONE:
        case ENET_EVENT_TYPE_CONNECT:
            break;
        }
    }
    // didn't disconnect gracefully force disconnect
    enet_peer_reset(server);
    server = nullptr;
}

template <>
RoomMember::RoomMemberImpl::CallbackSet<ProxyPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
    return callback_set_proxy_packet;
}

template <>
RoomMember::RoomMemberImpl::CallbackSet<LDNPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
    return callback_set_ldn_packet;
}

template <>
RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
    return callback_set_state;
}

template <>
RoomMember::RoomMemberImpl::CallbackSet<RoomMember::Error>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
    return callback_set_error;
}

template <>
RoomMember::RoomMemberImpl::CallbackSet<RoomInformation>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
    return callback_set_room_information;
}

template <>
RoomMember::RoomMemberImpl::CallbackSet<ChatEntry>& RoomMember::RoomMemberImpl::Callbacks::Get() {
    return callback_set_chat_messages;
}

template <>
RoomMember::RoomMemberImpl::CallbackSet<StatusMessageEntry>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
    return callback_set_status_messages;
}

template <>
RoomMember::RoomMemberImpl::CallbackSet<Room::BanList>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
    return callback_set_ban_list;
}

template <typename T>
void RoomMember::RoomMemberImpl::Invoke(const T& data) {
    std::lock_guard lock(callback_mutex);
    CallbackSet<T> callback_set = callbacks.Get<T>();
    for (auto const& callback : callback_set) {
        (*callback)(data);
    }
}

template <typename T>
RoomMember::CallbackHandle<T> RoomMember::RoomMemberImpl::Bind(
    std::function<void(const T&)> callback) {
    std::lock_guard lock(callback_mutex);
    CallbackHandle<T> handle;
    handle = std::make_shared<std::function<void(const T&)>>(callback);
    callbacks.Get<T>().insert(handle);
    return handle;
}

// RoomMember
RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {}

RoomMember::~RoomMember() {
    ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected");
    if (room_member_impl->loop_thread) {
        Leave();
    }
}

RoomMember::State RoomMember::GetState() const {
    return room_member_impl->state;
}

const RoomMember::MemberList& RoomMember::GetMemberInformation() const {
    return room_member_impl->member_information;
}

const std::string& RoomMember::GetNickname() const {
    return room_member_impl->nickname;
}

const std::string& RoomMember::GetUsername() const {
    std::lock_guard lock(room_member_impl->username_mutex);
    return room_member_impl->username;
}

const IPv4Address& RoomMember::GetFakeIpAddress() const {
    ASSERT_MSG(IsConnected(), "Tried to get fake ip address while not connected");
    return room_member_impl->fake_ip;
}

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 IPv4Address& preferred_fake_ip,
                      const std::string& password, const std::string& token) {
    // If the member is connected, kill the connection first
    if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) {
        Leave();
    }
    // If the thread isn't running but the ptr still exists, reset it
    else if (room_member_impl->loop_thread) {
        room_member_impl->loop_thread.reset();
    }

    if (!room_member_impl->client) {
        room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
        ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
    }

    room_member_impl->SetState(State::Joining);

    ENetAddress address{};
    enet_address_set_host(&address, server_addr);
    address.port = server_port;
    room_member_impl->server =
        enet_host_connect(room_member_impl->client, &address, NumChannels, 0);

    if (!room_member_impl->server) {
        room_member_impl->SetState(State::Idle);
        room_member_impl->SetError(Error::UnknownError);
        return;
    }

    ENetEvent event{};
    int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs);
    if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
        room_member_impl->nickname = nick;
        room_member_impl->StartLoop();
        room_member_impl->SendJoinRequest(nick, preferred_fake_ip, password, token);
        SendGameInfo(room_member_impl->current_game_info);
    } else {
        enet_peer_disconnect(room_member_impl->server, 0);
        room_member_impl->SetState(State::Idle);
        room_member_impl->SetError(Error::CouldNotConnect);
    }
}

bool RoomMember::IsConnected() const {
    return room_member_impl->IsConnected();
}

void RoomMember::SendProxyPacket(const ProxyPacket& proxy_packet) {
    Packet packet;
    packet.Write(static_cast<u8>(IdProxyPacket));

    packet.Write(static_cast<u8>(proxy_packet.local_endpoint.family));
    packet.Write(proxy_packet.local_endpoint.ip);
    packet.Write(proxy_packet.local_endpoint.portno);

    packet.Write(static_cast<u8>(proxy_packet.remote_endpoint.family));
    packet.Write(proxy_packet.remote_endpoint.ip);
    packet.Write(proxy_packet.remote_endpoint.portno);

    packet.Write(static_cast<u8>(proxy_packet.protocol));
    packet.Write(proxy_packet.broadcast);
    packet.Write(proxy_packet.data);

    room_member_impl->Send(std::move(packet));
}

void RoomMember::SendLdnPacket(const LDNPacket& ldn_packet) {
    Packet packet;
    packet.Write(static_cast<u8>(IdLdnPacket));

    packet.Write(static_cast<u8>(ldn_packet.type));

    packet.Write(ldn_packet.local_ip);
    packet.Write(ldn_packet.remote_ip);
    packet.Write(ldn_packet.broadcast);

    packet.Write(ldn_packet.data);

    room_member_impl->Send(std::move(packet));
}

void RoomMember::SendChatMessage(const std::string& message) {
    Packet packet;
    packet.Write(static_cast<u8>(IdChatMessage));
    packet.Write(message);
    room_member_impl->Send(std::move(packet));
}

void RoomMember::SendGameInfo(const GameInfo& game_info) {
    room_member_impl->current_game_info = game_info;
    if (!IsConnected())
        return;

    Packet packet;
    packet.Write(static_cast<u8>(IdSetGameInfo));
    packet.Write(game_info.name);
    packet.Write(game_info.id);
    packet.Write(game_info.version);
    room_member_impl->Send(std::move(packet));
}

void RoomMember::SendModerationRequest(RoomMessageTypes type, const std::string& nickname) {
    ASSERT_MSG(type == IdModKick || type == IdModBan || type == IdModUnban,
               "type is not a moderation request");
    if (!IsConnected())
        return;

    Packet packet;
    packet.Write(static_cast<u8>(type));
    packet.Write(nickname);
    room_member_impl->Send(std::move(packet));
}

void RoomMember::RequestBanList() {
    if (!IsConnected())
        return;

    Packet packet;
    packet.Write(static_cast<u8>(IdModGetBanList));
    room_member_impl->Send(std::move(packet));
}

RoomMember::CallbackHandle<RoomMember::State> RoomMember::BindOnStateChanged(
    std::function<void(const RoomMember::State&)> callback) {
    return room_member_impl->Bind(callback);
}

RoomMember::CallbackHandle<RoomMember::Error> RoomMember::BindOnError(
    std::function<void(const RoomMember::Error&)> callback) {
    return room_member_impl->Bind(callback);
}

RoomMember::CallbackHandle<ProxyPacket> RoomMember::BindOnProxyPacketReceived(
    std::function<void(const ProxyPacket&)> callback) {
    return room_member_impl->Bind(callback);
}

RoomMember::CallbackHandle<LDNPacket> RoomMember::BindOnLdnPacketReceived(
    std::function<void(const LDNPacket&)> callback) {
    return room_member_impl->Bind(std::move(callback));
}

RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged(
    std::function<void(const RoomInformation&)> callback) {
    return room_member_impl->Bind(callback);
}

RoomMember::CallbackHandle<ChatEntry> RoomMember::BindOnChatMessageReceived(
    std::function<void(const ChatEntry&)> callback) {
    return room_member_impl->Bind(callback);
}

RoomMember::CallbackHandle<StatusMessageEntry> RoomMember::BindOnStatusMessageReceived(
    std::function<void(const StatusMessageEntry&)> callback) {
    return room_member_impl->Bind(callback);
}

RoomMember::CallbackHandle<Room::BanList> RoomMember::BindOnBanListReceived(
    std::function<void(const Room::BanList&)> callback) {
    return room_member_impl->Bind(callback);
}

template <typename T>
void RoomMember::Unbind(CallbackHandle<T> handle) {
    std::lock_guard lock(room_member_impl->callback_mutex);
    room_member_impl->callbacks.Get<T>().erase(handle);
}

void RoomMember::Leave() {
    room_member_impl->SetState(State::Idle);
    room_member_impl->loop_thread->join();
    room_member_impl->loop_thread.reset();

    enet_host_destroy(room_member_impl->client);
    room_member_impl->client = nullptr;
}

template void RoomMember::Unbind(CallbackHandle<ProxyPacket>);
template void RoomMember::Unbind(CallbackHandle<LDNPacket>);
template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
template void RoomMember::Unbind(CallbackHandle<ChatEntry>);
template void RoomMember::Unbind(CallbackHandle<StatusMessageEntry>);
template void RoomMember::Unbind(CallbackHandle<Room::BanList>);

} // namespace Network


code:
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "common/announce_multiplayer_room.h"
#include "common/common_types.h"
#include "common/socket_types.h"
#include "network/room.h"

namespace Network {

using AnnounceMultiplayerRoom::GameInfo;
using AnnounceMultiplayerRoom::RoomInformation;

enum class LDNPacketType : u8 {
    Scan,
    ScanResp,
    Connect,
    SyncNetwork,
    Disconnect,
    DestroyNetwork,
};

struct LDNPacket {
    LDNPacketType type;
    IPv4Address local_ip;
    IPv4Address remote_ip;
    bool broadcast;
    std::vector<u8> data;
};

/// Information about the received proxy packets.
struct ProxyPacket {
    SockAddrIn local_endpoint;
    SockAddrIn remote_endpoint;
    Protocol protocol;
    bool broadcast;
    std::vector<u8> data;
};

/// Represents a chat message.
struct ChatEntry {
    std::string nickname; ///< Nickname of the client who sent this message.
    /// Web services username of the client who sent this message, can be empty.
    std::string username;
    std::string message; ///< Body of the message.
};

/// Represents a system status message.
struct StatusMessageEntry {
    StatusMessageTypes type; ///< Type of the message
    /// Subject of the message. i.e. the user who is joining/leaving/being banned, etc.
    std::string nickname;
    std::string username;
};

/**
 * This is what a client [person joining a server] would use.
 * It also has to be used if you host a game yourself (You'd create both, a Room and a
 * RoomMembership for yourself)
 */
class RoomMember final {
public:
    enum class State : u8 {
        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.
        Moderator, ///< The client is connected to the room and is granted mod permissions.
    };

    enum class Error : u8 {
        // Reasons why connection was closed
        LostConnection, ///< Connection closed
        HostKicked,     ///< Kicked by the host

        // Reasons why connection was rejected
        UnknownError,    ///< Some error [permissions to network device missing or something]
        NameCollision,   ///< Somebody is already using this name
        IpCollision,     ///< Somebody is already using that fake-ip-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
        HostBanned,      ///< The user is banned by the host

        // Reasons why moderation request failed
        PermissionDenied, ///< The user does not have mod permissions
        NoSuchUser,       ///< The nickname the user attempts to kick/ban does not exist
    };

    struct MemberInformation {
        std::string nickname;     ///< Nickname of the member.
        std::string username;     ///< The web services username of the member. Can be empty.
        std::string display_name; ///< The web services display name of the member. Can be empty.
        std::string avatar_url;   ///< Url to the member's avatar. Can be empty.
        GameInfo game_info;  ///< Name of the game they're currently playing, or empty if they're
                             /// not playing anything.
        IPv4Address fake_ip; ///< Fake Ip address associated with this member.
    };
    using MemberList = std::vector<MemberInformation>;

    // The handle for the callback functions
    template <typename T>
    using CallbackHandle = std::shared_ptr<std::function<void(const T&)>>;

    /**
     * Unbinds a callback function from the events.
     * @param handle The connection handle to disconnect
     */
    template <typename T>
    void Unbind(CallbackHandle<T> handle);

    RoomMember();
    ~RoomMember();

    /**
     * Returns the status of our connection to the room.
     */
    State GetState() const;

    /**
     * Returns information about the members in the room we're currently connected to.
     */
    const MemberList& GetMemberInformation() const;

    /**
     * Returns the nickname of the RoomMember.
     */
    const std::string& GetNickname() const;

    /**
     * Returns the username of the RoomMember.
     */
    const std::string& GetUsername() const;

    /**
     * Returns the MAC address of the RoomMember.
     */
    const IPv4Address& GetFakeIpAddress() const;

    /**
     * Returns information about the room we're currently connected to.
     */
    RoomInformation GetRoomInformation() const;

    /**
     * Returns whether we're connected to a server or not.
     */
    bool IsConnected() const;

    /**
     * Attempts to join a room at the specified address and port, using the specified nickname.
     */
    void Join(const std::string& nickname, const char* server_addr = "127.0.0.1",
              u16 server_port = DefaultRoomPort, u16 client_port = 0,
              const IPv4Address& preferred_fake_ip = NoPreferredIP,
              const std::string& password = "", const std::string& token = "");

    /**
     * Sends a Proxy packet to the room.
     * @param packet The WiFi packet to send.
     */
    void SendProxyPacket(const ProxyPacket& packet);

    /**
     * Sends an LDN packet to the room.
     * @param packet The WiFi packet to send.
     */
    void SendLdnPacket(const LDNPacket& packet);

    /**
     * Sends a chat message to the room.
     * @param message The contents of the message.
     */
    void SendChatMessage(const std::string& message);

    /**
     * Sends the current game info to the room.
     * @param game_info The game information.
     */
    void SendGameInfo(const GameInfo& game_info);

    /**
     * Sends a moderation request to the room.
     * @param type Moderation request type.
     * @param nickname The subject of the request. (i.e. the user you want to kick/ban)
     */
    void SendModerationRequest(RoomMessageTypes type, const std::string& nickname);

    /**
     * Attempts to retrieve ban list from the room.
     * If success, the ban list callback would be called. Otherwise an error would be emitted.
     */
    void RequestBanList();

    /**
     * Binds a function to an event that will be triggered every time the State of the member
     * changed. The function will be called every time the event is triggered. The callback function
     * must not bind or unbind a function. Doing so will cause a deadlock
     * @param callback The function to call
     * @return A handle used for removing the function from the registered list
     */
    CallbackHandle<State> BindOnStateChanged(std::function<void(const State&)> callback);

    /**
     * Binds a function to an event that will be triggered every time an error happened. The
     * function will be called every time the event is triggered. The callback function must not
     * bind or unbind a function. Doing so will cause a deadlock
     * @param callback The function to call
     * @return A handle used for removing the function from the registered list
     */
    CallbackHandle<Error> BindOnError(std::function<void(const Error&)> callback);

    /**
     * Binds a function to an event that will be triggered every time a ProxyPacket is received.
     * The function will be called every time the event is triggered.
     * The callback function must not bind or unbind a function. Doing so will cause a deadlock
     * @param callback The function to call
     * @return A handle used for removing the function from the registered list
     */
    CallbackHandle<ProxyPacket> BindOnProxyPacketReceived(
        std::function<void(const ProxyPacket&)> callback);

    /**
     * Binds a function to an event that will be triggered every time an LDNPacket is received.
     * The function will be called every time the event is triggered.
     * The callback function must not bind or unbind a function. Doing so will cause a deadlock
     * @param callback The function to call
     * @return A handle used for removing the function from the registered list
     */
    CallbackHandle<LDNPacket> BindOnLdnPacketReceived(
        std::function<void(const LDNPacket&)> callback);

    /**
     * Binds a function to an event that will be triggered every time the RoomInformation changes.
     * The function will be called every time the event is triggered.
     * The callback function must not bind or unbind a function. Doing so will cause a deadlock
     * @param callback The function to call
     * @return A handle used for removing the function from the registered list
     */
    CallbackHandle<RoomInformation> BindOnRoomInformationChanged(
        std::function<void(const RoomInformation&)> callback);

    /**
     * Binds a function to an event that will be triggered every time a ChatMessage is received.
     * The function will be called every time the event is triggered.
     * The callback function must not bind or unbind a function. Doing so will cause a deadlock
     * @param callback The function to call
     * @return A handle used for removing the function from the registered list
     */
    CallbackHandle<ChatEntry> BindOnChatMessageReceived(
        std::function<void(const ChatEntry&)> callback);

    /**
     * Binds a function to an event that will be triggered every time a StatusMessage is
     * received. The function will be called every time the event is triggered. The callback
     * function must not bind or unbind a function. Doing so will cause a deadlock
     * @param callback The function to call
     * @return A handle used for removing the function from the registered list
     */
    CallbackHandle<StatusMessageEntry> BindOnStatusMessageReceived(
        std::function<void(const StatusMessageEntry&)> callback);

    /**
     * Binds a function to an event that will be triggered every time a requested ban list
     * received. The function will be called every time the event is triggered. The callback
     * function must not bind or unbind a function. Doing so will cause a deadlock
     * @param callback The function to call
     * @return A handle used for removing the function from the registered list
     */
    CallbackHandle<Room::BanList> BindOnBanListReceived(
        std::function<void(const Room::BanList&)> callback);

    /**
     * Leaves the current room.
     */
    void Leave();

private:
    class RoomMemberImpl;
    std::unique_ptr<RoomMemberImpl> room_member_impl;
};

inline const char* GetStateStr(const RoomMember::State& s) {
    switch (s) {
    case RoomMember::State::Uninitialized:
        return "Uninitialized";
    case RoomMember::State::Idle:
        return "Idle";
    case RoomMember::State::Joining:
        return "Joining";
    case RoomMember::State::Joined:
        return "Joined";
    case RoomMember::State::Moderator:
        return "Moderator";
    }
    return "Unknown";
}

inline const char* GetErrorStr(const RoomMember::Error& e) {
    switch (e) {
    case RoomMember::Error::LostConnection:
        return "LostConnection";
    case RoomMember::Error::HostKicked:
        return "HostKicked";
    case RoomMember::Error::UnknownError:
        return "UnknownError";
    case RoomMember::Error::NameCollision:
        return "NameCollision";
    case RoomMember::Error::IpCollision:
        return "IpCollision";
    case RoomMember::Error::WrongVersion:
        return "WrongVersion";
    case RoomMember::Error::WrongPassword:
        return "WrongPassword";
    case RoomMember::Error::CouldNotConnect:
        return "CouldNotConnect";
    case RoomMember::Error::RoomIsFull:
        return "RoomIsFull";
    case RoomMember::Error::HostBanned:
        return "HostBanned";
    case RoomMember::Error::PermissionDenied:
        return "PermissionDenied";
    case RoomMember::Error::NoSuchUser:
        return "NoSuchUser";
    default:
        return "Unknown";
    }
}

} // namespace Network


code:
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "network/verify_user.h"

namespace Network::VerifyUser {

Backend::~Backend() = default;

NullBackend::~NullBackend() = default;

UserData NullBackend::LoadUserData([[maybe_unused]] const std::string& verify_uid,
                                   [[maybe_unused]] const std::string& token) {
    return {};
}

} // namespace Network::VerifyUser


code:
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <string>
#include "common/logging/log.h"

namespace Network::VerifyUser {

struct UserData {
    std::string username;
    std::string display_name;
    std::string avatar_url;
    bool moderator = false; ///< Whether the user is a yuzu Moderator.
};

/**
 * A backend used for verifying users and loading user data.
 */
class Backend {
public:
    virtual ~Backend();

    /**
     * Verifies the given token and loads the information into a UserData struct.
     * @param verify_uid A GUID that may be used for verification.
     * @param token A token that contains user data and verification data. The format and content is
     * decided by backends.
     */
    virtual UserData LoadUserData(const std::string& verify_uid, const std::string& token) = 0;
};

/**
 * A null backend where the token is ignored.
 * No verification is performed here and the function returns an empty UserData.
 */
class NullBackend final : public Backend {
public:
    ~NullBackend();

    UserData LoadUserData(const std::string& verify_uid, const std::string& token) override;
};

} // namespace Network::VerifyUser


code:
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later

add_library(shader_recompiler STATIC
    backend/bindings.h
    backend/glasm/emit_glasm.cpp
    backend/glasm/emit_glasm.h
    backend/glasm/emit_glasm_barriers.cpp
    backend/glasm/emit_glasm_bitwise_conversion.cpp
    backend/glasm/emit_glasm_composite.cpp
    backend/glasm/emit_glasm_context_get_set.cpp
    backend/glasm/emit_glasm_control_flow.cpp
    backend/glasm/emit_glasm_convert.cpp
    backend/glasm/emit_glasm_floating_point.cpp
    backend/glasm/emit_glasm_image.cpp
    backend/glasm/emit_glasm_instructions.h
    backend/glasm/emit_glasm_integer.cpp
    backend/glasm/emit_glasm_logical.cpp
    backend/glasm/emit_glasm_memory.cpp
    backend/glasm/emit_glasm_not_implemented.cpp
    backend/glasm/emit_glasm_select.cpp
    backend/glasm/emit_glasm_shared_memory.cpp
    backend/glasm/emit_glasm_special.cpp
    backend/glasm/emit_glasm_undefined.cpp
    backend/glasm/emit_glasm_warp.cpp
    backend/glasm/glasm_emit_context.cpp
    backend/glasm/glasm_emit_context.h
    backend/glasm/reg_alloc.cpp
    backend/glasm/reg_alloc.h
    backend/glsl/emit_glsl.cpp
    backend/glsl/emit_glsl.h
    backend/glsl/emit_glsl_atomic.cpp
    backend/glsl/emit_glsl_barriers.cpp
    backend/glsl/emit_glsl_bitwise_conversion.cpp
    backend/glsl/emit_glsl_composite.cpp
    backend/glsl/emit_glsl_context_get_set.cpp
    backend/glsl/emit_glsl_control_flow.cpp
    backend/glsl/emit_glsl_convert.cpp
    backend/glsl/emit_glsl_floating_point.cpp
    backend/glsl/emit_glsl_image.cpp
    backend/glsl/emit_glsl_instructions.h
    backend/glsl/emit_glsl_integer.cpp
    backend/glsl/emit_glsl_logical.cpp
    backend/glsl/emit_glsl_memory.cpp
    backend/glsl/emit_glsl_not_implemented.cpp
    backend/glsl/emit_glsl_select.cpp
    backend/glsl/emit_glsl_shared_memory.cpp
    backend/glsl/emit_glsl_special.cpp
    backend/glsl/emit_glsl_undefined.cpp
    backend/glsl/emit_glsl_warp.cpp
    backend/glsl/glsl_emit_context.cpp
    backend/glsl/glsl_emit_context.h
    backend/glsl/var_alloc.cpp
    backend/glsl/var_alloc.h
    backend/spirv/emit_spirv.cpp
    backend/spirv/emit_spirv.h
    backend/spirv/emit_spirv_atomic.cpp
    backend/spirv/emit_spirv_barriers.cpp
    backend/spirv/emit_spirv_bitwise_conversion.cpp
    backend/spirv/emit_spirv_composite.cpp
    backend/spirv/emit_spirv_context_get_set.cpp
    backend/spirv/emit_spirv_control_flow.cpp
    backend/spirv/emit_spirv_convert.cpp
    backend/spirv/emit_spirv_floating_point.cpp
    backend/spirv/emit_spirv_image.cpp
    backend/spirv/emit_spirv_image_atomic.cpp
    backend/spirv/emit_spirv_instructions.h
    backend/spirv/emit_spirv_integer.cpp
    backend/spirv/emit_spirv_logical.cpp
    backend/spirv/emit_spirv_memory.cpp
    backend/spirv/emit_spirv_select.cpp
    backend/spirv/emit_spirv_shared_memory.cpp
    backend/spirv/emit_spirv_special.cpp
    backend/spirv/emit_spirv_undefined.cpp
    backend/spirv/emit_spirv_warp.cpp
    backend/spirv/spirv_emit_context.cpp
    backend/spirv/spirv_emit_context.h
    environment.h
    exception.h
    frontend/ir/abstract_syntax_list.h
    frontend/ir/attribute.cpp
    frontend/ir/attribute.h
    frontend/ir/basic_block.cpp
    frontend/ir/basic_block.h
    frontend/ir/breadth_first_search.h
    frontend/ir/condition.cpp
    frontend/ir/condition.h
    frontend/ir/flow_test.cpp
    frontend/ir/flow_test.h
    frontend/ir/ir_emitter.cpp
    frontend/ir/ir_emitter.h
    frontend/ir/microinstruction.cpp
    frontend/ir/modifiers.h
    frontend/ir/opcodes.cpp
    frontend/ir/opcodes.h
    frontend/ir/opcodes.inc
    frontend/ir/patch.cpp
    frontend/ir/patch.h
    frontend/ir/post_order.cpp
    frontend/ir/post_order.h
    frontend/ir/pred.h
    frontend/ir/program.cpp
    frontend/ir/program.h
    frontend/ir/reg.h
    frontend/ir/type.cpp
    frontend/ir/type.h
    frontend/ir/value.cpp
    frontend/ir/value.h
    frontend/maxwell/control_flow.cpp
    frontend/maxwell/control_flow.h
    frontend/maxwell/decode.cpp
    frontend/maxwell/decode.h
    frontend/maxwell/indirect_branch_table_track.cpp
    frontend/maxwell/indirect_branch_table_track.h
    frontend/maxwell/instruction.h
    frontend/maxwell/location.h
    frontend/maxwell/maxwell.inc
    frontend/maxwell/opcodes.cpp
    frontend/maxwell/opcodes.h
    frontend/maxwell/structured_control_flow.cpp
    frontend/maxwell/structured_control_flow.h
    frontend/maxwell/translate/impl/atomic_operations_global_memory.cpp
    frontend/maxwell/translate/impl/atomic_operations_shared_memory.cpp
    frontend/maxwell/translate/impl/attribute_memory_to_physical.cpp
    frontend/maxwell/translate/impl/barrier_operations.cpp
    frontend/maxwell/translate/impl/bitfield_extract.cpp
    frontend/maxwell/translate/impl/bitfield_insert.cpp
    frontend/maxwell/translate/impl/branch_indirect.cpp
    frontend/maxwell/translate/impl/common_encoding.h
    frontend/maxwell/translate/impl/common_funcs.cpp
    frontend/maxwell/translate/impl/common_funcs.h
    frontend/maxwell/translate/impl/condition_code_set.cpp
    frontend/maxwell/translate/impl/double_add.cpp
    frontend/maxwell/translate/impl/double_compare_and_set.cpp
    frontend/maxwell/translate/impl/double_fused_multiply_add.cpp
    frontend/maxwell/translate/impl/double_min_max.cpp
    frontend/maxwell/translate/impl/double_multiply.cpp
    frontend/maxwell/translate/impl/double_set_predicate.cpp
    frontend/maxwell/translate/impl/exit_program.cpp
    frontend/maxwell/translate/impl/find_leading_one.cpp
    frontend/maxwell/translate/impl/floating_point_add.cpp
    frontend/maxwell/translate/impl/floating_point_compare.cpp
    frontend/maxwell/translate/impl/floating_point_compare_and_set.cpp
    frontend/maxwell/translate/impl/floating_point_conversion_floating_point.cpp
    frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp
    frontend/maxwell/translate/impl/floating_point_fused_multiply_add.cpp
    frontend/maxwell/translate/impl/floating_point_min_max.cpp
    frontend/maxwell/translate/impl/floating_point_multi_function.cpp
    frontend/maxwell/translate/impl/floating_point_multiply.cpp
    frontend/maxwell/translate/impl/floating_point_range_reduction.cpp
    frontend/maxwell/translate/impl/floating_point_set_predicate.cpp
    frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp
    frontend/maxwell/translate/impl/half_floating_point_add.cpp
    frontend/maxwell/translate/impl/half_floating_point_fused_multiply_add.cpp
    frontend/maxwell/translate/impl/half_floating_point_helper.cpp
    frontend/maxwell/translate/impl/half_floating_point_helper.h
    frontend/maxwell/translate/impl/half_floating_point_multiply.cpp
    frontend/maxwell/translate/impl/half_floating_point_set.cpp
    frontend/maxwell/translate/impl/half_floating_point_set_predicate.cpp
    frontend/maxwell/translate/impl/impl.cpp
    frontend/maxwell/translate/impl/impl.h
    frontend/maxwell/translate/impl/integer_add.cpp
    frontend/maxwell/translate/impl/integer_add_three_input.cpp
    frontend/maxwell/translate/impl/integer_compare.cpp
    frontend/maxwell/translate/impl/integer_compare_and_set.cpp
    frontend/maxwell/translate/impl/integer_floating_point_conversion.cpp
    frontend/maxwell/translate/impl/integer_funnel_shift.cpp
    frontend/maxwell/translate/impl/integer_minimum_maximum.cpp
    frontend/maxwell/translate/impl/integer_popcount.cpp
    frontend/maxwell/translate/impl/integer_scaled_add.cpp
    frontend/maxwell/translate/impl/integer_set_predicate.cpp
    frontend/maxwell/translate/impl/integer_shift_left.cpp
    frontend/maxwell/translate/impl/integer_shift_right.cpp
    frontend/maxwell/translate/impl/integer_short_multiply_add.cpp
    frontend/maxwell/translate/impl/integer_to_integer_conversion.cpp
    frontend/maxwell/translate/impl/internal_stage_buffer_entry_read.cpp
    frontend/maxwell/translate/impl/load_constant.cpp
    frontend/maxwell/translate/impl/load_constant.h
    frontend/maxwell/translate/impl/load_effective_address.cpp
    frontend/maxwell/translate/impl/load_store_attribute.cpp
    frontend/maxwell/translate/impl/load_store_local_shared.cpp
    frontend/maxwell/translate/impl/load_store_memory.cpp
    frontend/maxwell/translate/impl/logic_operation.cpp
    frontend/maxwell/translate/impl/logic_operation_three_input.cpp
    frontend/maxwell/translate/impl/move_predicate_to_register.cpp
    frontend/maxwell/translate/impl/move_register.cpp
    frontend/maxwell/translate/impl/move_register_to_predicate.cpp
    frontend/maxwell/translate/impl/move_special_register.cpp
    frontend/maxwell/translate/impl/not_implemented.cpp
    frontend/maxwell/translate/impl/output_geometry.cpp
    frontend/maxwell/translate/impl/pixel_load.cpp
    frontend/maxwell/translate/impl/predicate_set_predicate.cpp
    frontend/maxwell/translate/impl/predicate_set_register.cpp
    frontend/maxwell/translate/impl/select_source_with_predicate.cpp
    frontend/maxwell/translate/impl/surface_atomic_operations.cpp
    frontend/maxwell/translate/impl/surface_load_store.cpp
    frontend/maxwell/translate/impl/texture_fetch.cpp
    frontend/maxwell/translate/impl/texture_fetch_swizzled.cpp
    frontend/maxwell/translate/impl/texture_gather.cpp
    frontend/maxwell/translate/impl/texture_gather_swizzled.cpp
    frontend/maxwell/translate/impl/texture_gradient.cpp
    frontend/maxwell/translate/impl/texture_load.cpp
    frontend/maxwell/translate/impl/texture_load_swizzled.cpp
    frontend/maxwell/translate/impl/texture_mipmap_level.cpp
    frontend/maxwell/translate/impl/texture_query.cpp
    frontend/maxwell/translate/impl/video_helper.cpp
    frontend/maxwell/translate/impl/video_helper.h
    frontend/maxwell/translate/impl/video_minimum_maximum.cpp
    frontend/maxwell/translate/impl/video_multiply_add.cpp
    frontend/maxwell/translate/impl/video_set_predicate.cpp
    frontend/maxwell/translate/impl/vote.cpp
    frontend/maxwell/translate/impl/warp_shuffle.cpp
    frontend/maxwell/translate/translate.cpp
    frontend/maxwell/translate/translate.h
    frontend/maxwell/translate_program.cpp
    frontend/maxwell/translate_program.h
    host_translate_info.h
    ir_opt/collect_shader_info_pass.cpp
    ir_opt/conditional_barrier_pass.cpp
    ir_opt/constant_propagation_pass.cpp
    ir_opt/dead_code_elimination_pass.cpp
    ir_opt/dual_vertex_pass.cpp
    ir_opt/global_memory_to_storage_buffer_pass.cpp
    ir_opt/identity_removal_pass.cpp
    ir_opt/layer_pass.cpp
    ir_opt/lower_fp16_to_fp32.cpp
    ir_opt/lower_fp64_to_fp32.cpp
    ir_opt/lower_int64_to_int32.cpp
    ir_opt/passes.h
    ir_opt/position_pass.cpp
    ir_opt/rescaling_pass.cpp
    ir_opt/ssa_rewrite_pass.cpp
    ir_opt/texture_pass.cpp
    ir_opt/vendor_workaround_pass.cpp
    ir_opt/verification_pass.cpp
    object_pool.h
    precompiled_headers.h
    profile.h
    program_header.h
    runtime_info.h
    shader_info.h
    varying_state.h
)

target_link_libraries(shader_recompiler PUBLIC common fmt::fmt sirit)

if (MSVC)
    target_compile_options(shader_recompiler PRIVATE
        /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
        /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
        /we4800 # Implicit conversion from 'type' to bool. Possible information loss
    )
else()
    target_compile_options(shader_recompiler PRIVATE
        -Werror=conversion

        # Bracket depth determines maximum size of a fold expression in Clang since 9c9974c3ccb6.
        # And this in turns limits the size of a std::array.
        $<$<CXX_COMPILER_ID:Clang>:-fbracket-depth=1024>
        $<$<CXX_COMPILER_ID:AppleClang>:-fbracket-depth=1024>
    )
endif()

create_target_directory_groups(shader_recompiler)

if (YUZU_USE_PRECOMPILED_HEADERS)
    target_precompile_headers(shader_recompiler PRIVATE precompiled_headers.h)
endif()