mirror of
https://github.com/yuzu-emu/yuzu-mainline.git
synced 2024-12-12 17:54:36 +01:00
common: multiplayer: Use GameInfo type
This commit is contained in:
parent
4b404191cf
commit
899c8bb330
@ -15,14 +15,18 @@ namespace AnnounceMultiplayerRoom {
|
|||||||
|
|
||||||
using MacAddress = std::array<u8, 6>;
|
using MacAddress = std::array<u8, 6>;
|
||||||
|
|
||||||
|
struct GameInfo {
|
||||||
|
std::string name{""};
|
||||||
|
u64 id{0};
|
||||||
|
};
|
||||||
|
|
||||||
struct Member {
|
struct Member {
|
||||||
std::string username;
|
std::string username;
|
||||||
std::string nickname;
|
std::string nickname;
|
||||||
std::string display_name;
|
std::string display_name;
|
||||||
std::string avatar_url;
|
std::string avatar_url;
|
||||||
MacAddress mac_address;
|
MacAddress mac_address;
|
||||||
std::string game_name;
|
GameInfo game;
|
||||||
u64 game_id;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RoomInformation {
|
struct RoomInformation {
|
||||||
@ -30,17 +34,11 @@ struct RoomInformation {
|
|||||||
std::string description; ///< Server description
|
std::string description; ///< Server description
|
||||||
u32 member_slots; ///< Maximum number of members in this room
|
u32 member_slots; ///< Maximum number of members in this room
|
||||||
u16 port; ///< The port of this room
|
u16 port; ///< The port of this room
|
||||||
std::string preferred_game; ///< Game to advertise that you want to play
|
GameInfo preferred_game; ///< Game to advertise that you want to play
|
||||||
u64 preferred_game_id; ///< Title ID for the advertised game
|
|
||||||
std::string host_username; ///< Forum username of the host
|
std::string host_username; ///< Forum username of the host
|
||||||
bool enable_yuzu_mods; ///< Allow yuzu Moderators to moderate on this room
|
bool enable_yuzu_mods; ///< Allow yuzu Moderators to moderate on this room
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GameInfo {
|
|
||||||
std::string name{""};
|
|
||||||
u64 id{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Room {
|
struct Room {
|
||||||
RoomInformation information;
|
RoomInformation information;
|
||||||
|
|
||||||
@ -75,8 +73,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void SetRoomInformation(const std::string& name, const std::string& description,
|
virtual void SetRoomInformation(const std::string& name, const std::string& description,
|
||||||
const u16 port, const u32 max_player, const u32 net_version,
|
const u16 port, const u32 max_player, const u32 net_version,
|
||||||
const bool has_password, const std::string& preferred_game,
|
const bool has_password, const GameInfo& preferred_game) = 0;
|
||||||
const u64 preferred_game_id) = 0;
|
|
||||||
/**
|
/**
|
||||||
* Adds a player information to the data that gets announced
|
* Adds a player information to the data that gets announced
|
||||||
* @param nickname The nickname of the player
|
* @param nickname The nickname of the player
|
||||||
@ -125,8 +122,8 @@ public:
|
|||||||
~NullBackend() = default;
|
~NullBackend() = default;
|
||||||
void SetRoomInformation(const std::string& /*name*/, const std::string& /*description*/,
|
void SetRoomInformation(const std::string& /*name*/, const std::string& /*description*/,
|
||||||
const u16 /*port*/, const u32 /*max_player*/, const u32 /*net_version*/,
|
const u16 /*port*/, const u32 /*max_player*/, const u32 /*net_version*/,
|
||||||
const bool /*has_password*/, const std::string& /*preferred_game*/,
|
const bool /*has_password*/,
|
||||||
const u64 /*preferred_game_id*/) override {}
|
const GameInfo& /*preferred_game*/) override {}
|
||||||
void AddPlayer(const Member& /*member*/) override {}
|
void AddPlayer(const Member& /*member*/) override {}
|
||||||
WebService::WebResult Update() override {
|
WebService::WebResult Update() override {
|
||||||
return WebService::WebResult{WebService::WebResult::Code::NoWebservice,
|
return WebService::WebResult{WebService::WebResult::Code::NoWebservice,
|
||||||
|
@ -89,10 +89,10 @@ AnnounceMultiplayerSession::~AnnounceMultiplayerSession() {
|
|||||||
void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room> room) {
|
void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room> room) {
|
||||||
Network::RoomInformation room_information = room->GetRoomInformation();
|
Network::RoomInformation room_information = room->GetRoomInformation();
|
||||||
std::vector<AnnounceMultiplayerRoom::Member> memberlist = room->GetRoomMemberList();
|
std::vector<AnnounceMultiplayerRoom::Member> memberlist = room->GetRoomMemberList();
|
||||||
backend->SetRoomInformation(
|
backend->SetRoomInformation(room_information.name, room_information.description,
|
||||||
room_information.name, room_information.description, room_information.port,
|
room_information.port, room_information.member_slots,
|
||||||
room_information.member_slots, Network::network_version, room->HasPassword(),
|
Network::network_version, room->HasPassword(),
|
||||||
room_information.preferred_game, room_information.preferred_game_id);
|
room_information.preferred_game);
|
||||||
backend->ClearPlayers();
|
backend->ClearPlayers();
|
||||||
for (const auto& member : memberlist) {
|
for (const auto& member : memberlist) {
|
||||||
backend->AddPlayer(member);
|
backend->AddPlayer(member);
|
||||||
|
@ -811,7 +811,7 @@ void Room::RoomImpl::BroadcastRoomInformation() {
|
|||||||
packet << room_information.description;
|
packet << room_information.description;
|
||||||
packet << room_information.member_slots;
|
packet << room_information.member_slots;
|
||||||
packet << room_information.port;
|
packet << room_information.port;
|
||||||
packet << room_information.preferred_game;
|
packet << room_information.preferred_game.name;
|
||||||
packet << room_information.host_username;
|
packet << room_information.host_username;
|
||||||
|
|
||||||
packet << static_cast<u32>(members.size());
|
packet << static_cast<u32>(members.size());
|
||||||
@ -1013,7 +1013,7 @@ Room::~Room() = default;
|
|||||||
bool Room::Create(const std::string& name, const std::string& description,
|
bool Room::Create(const std::string& name, const std::string& description,
|
||||||
const std::string& server_address, u16 server_port, const std::string& password,
|
const std::string& server_address, u16 server_port, const std::string& password,
|
||||||
const u32 max_connections, const std::string& host_username,
|
const u32 max_connections, const std::string& host_username,
|
||||||
const std::string& preferred_game, u64 preferred_game_id,
|
const GameInfo preferred_game,
|
||||||
std::unique_ptr<VerifyUser::Backend> verify_backend,
|
std::unique_ptr<VerifyUser::Backend> verify_backend,
|
||||||
const Room::BanList& ban_list, bool enable_yuzu_mods) {
|
const Room::BanList& ban_list, bool enable_yuzu_mods) {
|
||||||
ENetAddress address;
|
ENetAddress address;
|
||||||
@ -1036,7 +1036,6 @@ bool Room::Create(const std::string& name, const std::string& description,
|
|||||||
room_impl->room_information.member_slots = max_connections;
|
room_impl->room_information.member_slots = max_connections;
|
||||||
room_impl->room_information.port = server_port;
|
room_impl->room_information.port = server_port;
|
||||||
room_impl->room_information.preferred_game = preferred_game;
|
room_impl->room_information.preferred_game = preferred_game;
|
||||||
room_impl->room_information.preferred_game_id = preferred_game_id;
|
|
||||||
room_impl->room_information.host_username = host_username;
|
room_impl->room_information.host_username = host_username;
|
||||||
room_impl->room_information.enable_yuzu_mods = enable_yuzu_mods;
|
room_impl->room_information.enable_yuzu_mods = enable_yuzu_mods;
|
||||||
room_impl->password = password;
|
room_impl->password = password;
|
||||||
@ -1076,8 +1075,7 @@ std::vector<Member> Room::GetRoomMemberList() const {
|
|||||||
member.display_name = member_impl.user_data.display_name;
|
member.display_name = member_impl.user_data.display_name;
|
||||||
member.avatar_url = member_impl.user_data.avatar_url;
|
member.avatar_url = member_impl.user_data.avatar_url;
|
||||||
member.mac_address = member_impl.mac_address;
|
member.mac_address = member_impl.mac_address;
|
||||||
member.game_name = member_impl.game_info.name;
|
member.game = member_impl.game_info;
|
||||||
member.game_id = member_impl.game_info.id;
|
|
||||||
member_list.push_back(member);
|
member_list.push_back(member);
|
||||||
}
|
}
|
||||||
return member_list;
|
return member_list;
|
||||||
|
@ -125,8 +125,7 @@ public:
|
|||||||
const std::string& server = "", u16 server_port = DefaultRoomPort,
|
const std::string& server = "", u16 server_port = DefaultRoomPort,
|
||||||
const std::string& password = "",
|
const std::string& password = "",
|
||||||
const u32 max_connections = MaxConcurrentConnections,
|
const u32 max_connections = MaxConcurrentConnections,
|
||||||
const std::string& host_username = "", const std::string& preferred_game = "",
|
const std::string& host_username = "", const GameInfo = {},
|
||||||
u64 preferred_game_id = 0,
|
|
||||||
std::unique_ptr<VerifyUser::Backend> verify_backend = nullptr,
|
std::unique_ptr<VerifyUser::Backend> verify_backend = nullptr,
|
||||||
const BanList& ban_list = {}, bool enable_yuzu_mods = false);
|
const BanList& ban_list = {}, bool enable_yuzu_mods = false);
|
||||||
|
|
||||||
|
@ -303,7 +303,7 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev
|
|||||||
packet >> info.description;
|
packet >> info.description;
|
||||||
packet >> info.member_slots;
|
packet >> info.member_slots;
|
||||||
packet >> info.port;
|
packet >> info.port;
|
||||||
packet >> info.preferred_game;
|
packet >> info.preferred_game.name;
|
||||||
packet >> info.host_username;
|
packet >> info.host_username;
|
||||||
room_information.name = info.name;
|
room_information.name = info.name;
|
||||||
room_information.description = info.description;
|
room_information.description = info.description;
|
||||||
|
@ -19,14 +19,14 @@ static void to_json(nlohmann::json& json, const Member& member) {
|
|||||||
if (!member.avatar_url.empty()) {
|
if (!member.avatar_url.empty()) {
|
||||||
json["avatarUrl"] = member.avatar_url;
|
json["avatarUrl"] = member.avatar_url;
|
||||||
}
|
}
|
||||||
json["gameName"] = member.game_name;
|
json["gameName"] = member.game.name;
|
||||||
json["gameId"] = member.game_id;
|
json["gameId"] = member.game.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void from_json(const nlohmann::json& json, Member& member) {
|
static void from_json(const nlohmann::json& json, Member& member) {
|
||||||
member.nickname = json.at("nickname").get<std::string>();
|
member.nickname = json.at("nickname").get<std::string>();
|
||||||
member.game_name = json.at("gameName").get<std::string>();
|
member.game.name = json.at("gameName").get<std::string>();
|
||||||
member.game_id = json.at("gameId").get<u64>();
|
member.game.id = json.at("gameId").get<u64>();
|
||||||
try {
|
try {
|
||||||
member.username = json.at("username").get<std::string>();
|
member.username = json.at("username").get<std::string>();
|
||||||
member.avatar_url = json.at("avatarUrl").get<std::string>();
|
member.avatar_url = json.at("avatarUrl").get<std::string>();
|
||||||
@ -42,8 +42,8 @@ static void to_json(nlohmann::json& json, const Room& room) {
|
|||||||
if (!room.information.description.empty()) {
|
if (!room.information.description.empty()) {
|
||||||
json["description"] = room.information.description;
|
json["description"] = room.information.description;
|
||||||
}
|
}
|
||||||
json["preferredGameName"] = room.information.preferred_game;
|
json["preferredGameName"] = room.information.preferred_game.name;
|
||||||
json["preferredGameId"] = room.information.preferred_game_id;
|
json["preferredGameId"] = room.information.preferred_game.id;
|
||||||
json["maxPlayers"] = room.information.member_slots;
|
json["maxPlayers"] = room.information.member_slots;
|
||||||
json["netVersion"] = room.net_version;
|
json["netVersion"] = room.net_version;
|
||||||
json["hasPassword"] = room.has_password;
|
json["hasPassword"] = room.has_password;
|
||||||
@ -65,8 +65,8 @@ static void from_json(const nlohmann::json& json, Room& room) {
|
|||||||
}
|
}
|
||||||
room.information.host_username = json.at("owner").get<std::string>();
|
room.information.host_username = json.at("owner").get<std::string>();
|
||||||
room.information.port = json.at("port").get<u16>();
|
room.information.port = json.at("port").get<u16>();
|
||||||
room.information.preferred_game = json.at("preferredGameName").get<std::string>();
|
room.information.preferred_game.name = json.at("preferredGameName").get<std::string>();
|
||||||
room.information.preferred_game_id = json.at("preferredGameId").get<u64>();
|
room.information.preferred_game.id = json.at("preferredGameId").get<u64>();
|
||||||
room.information.member_slots = json.at("maxPlayers").get<u32>();
|
room.information.member_slots = json.at("maxPlayers").get<u32>();
|
||||||
room.net_version = json.at("netVersion").get<u32>();
|
room.net_version = json.at("netVersion").get<u32>();
|
||||||
room.has_password = json.at("hasPassword").get<bool>();
|
room.has_password = json.at("hasPassword").get<bool>();
|
||||||
@ -83,8 +83,8 @@ namespace WebService {
|
|||||||
|
|
||||||
void RoomJson::SetRoomInformation(const std::string& name, const std::string& description,
|
void RoomJson::SetRoomInformation(const std::string& name, const std::string& description,
|
||||||
const u16 port, const u32 max_player, const u32 net_version,
|
const u16 port, const u32 max_player, const u32 net_version,
|
||||||
const bool has_password, const std::string& preferred_game,
|
const bool has_password,
|
||||||
const u64 preferred_game_id) {
|
const AnnounceMultiplayerRoom::GameInfo& preferred_game) {
|
||||||
room.information.name = name;
|
room.information.name = name;
|
||||||
room.information.description = description;
|
room.information.description = description;
|
||||||
room.information.port = port;
|
room.information.port = port;
|
||||||
@ -92,7 +92,6 @@ void RoomJson::SetRoomInformation(const std::string& name, const std::string& de
|
|||||||
room.net_version = net_version;
|
room.net_version = net_version;
|
||||||
room.has_password = has_password;
|
room.has_password = has_password;
|
||||||
room.information.preferred_game = preferred_game;
|
room.information.preferred_game = preferred_game;
|
||||||
room.information.preferred_game_id = preferred_game_id;
|
|
||||||
}
|
}
|
||||||
void RoomJson::AddPlayer(const AnnounceMultiplayerRoom::Member& member) {
|
void RoomJson::AddPlayer(const AnnounceMultiplayerRoom::Member& member) {
|
||||||
room.members.push_back(member);
|
room.members.push_back(member);
|
||||||
|
@ -22,8 +22,7 @@ public:
|
|||||||
~RoomJson() = default;
|
~RoomJson() = default;
|
||||||
void SetRoomInformation(const std::string& name, const std::string& description, const u16 port,
|
void SetRoomInformation(const std::string& name, const std::string& description, const u16 port,
|
||||||
const u32 max_player, const u32 net_version, const bool has_password,
|
const u32 max_player, const u32 net_version, const bool has_password,
|
||||||
const std::string& preferred_game,
|
const AnnounceMultiplayerRoom::GameInfo& preferred_game) override;
|
||||||
const u64 preferred_game_id) override;
|
|
||||||
void AddPlayer(const AnnounceMultiplayerRoom::Member& member) override;
|
void AddPlayer(const AnnounceMultiplayerRoom::Member& member) override;
|
||||||
WebResult Update() override;
|
WebResult Update() override;
|
||||||
WebResult Register() override;
|
WebResult Register() override;
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
namespace WebService {
|
namespace WebService {
|
||||||
|
|
||||||
|
std::string GetPublicKey(const std::string& host);
|
||||||
|
|
||||||
class VerifyUserJWT final : public Network::VerifyUser::Backend {
|
class VerifyUserJWT final : public Network::VerifyUser::Backend {
|
||||||
public:
|
public:
|
||||||
VerifyUserJWT(const std::string& host);
|
VerifyUserJWT(const std::string& host);
|
||||||
|
@ -132,21 +132,24 @@ void HostRoomWindow::Host() {
|
|||||||
}
|
}
|
||||||
ui->host->setDisabled(true);
|
ui->host->setDisabled(true);
|
||||||
|
|
||||||
auto game_name = ui->game_list->currentData(Qt::DisplayRole).toString();
|
const AnnounceMultiplayerRoom::GameInfo game{
|
||||||
auto game_id = ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong();
|
.name = ui->game_list->currentData(Qt::DisplayRole).toString().toStdString(),
|
||||||
auto port = ui->port->isModified() ? ui->port->text().toInt() : Network::DefaultRoomPort;
|
.id = ui->game_list->currentData(GameListItemPath::ProgramIdRole).toULongLong(),
|
||||||
auto password = ui->password->text().toStdString();
|
};
|
||||||
|
const auto port =
|
||||||
|
ui->port->isModified() ? ui->port->text().toInt() : Network::DefaultRoomPort;
|
||||||
|
const auto password = ui->password->text().toStdString();
|
||||||
const bool is_public = ui->host_type->currentIndex() == 0;
|
const bool is_public = ui->host_type->currentIndex() == 0;
|
||||||
Network::Room::BanList ban_list{};
|
Network::Room::BanList ban_list{};
|
||||||
if (ui->load_ban_list->isChecked()) {
|
if (ui->load_ban_list->isChecked()) {
|
||||||
ban_list = UISettings::values.multiplayer_ban_list;
|
ban_list = UISettings::values.multiplayer_ban_list;
|
||||||
}
|
}
|
||||||
if (auto room = Network::GetRoom().lock()) {
|
if (auto room = Network::GetRoom().lock()) {
|
||||||
bool created = room->Create(
|
const bool created =
|
||||||
ui->room_name->text().toStdString(),
|
room->Create(ui->room_name->text().toStdString(),
|
||||||
ui->room_description->toPlainText().toStdString(), "", port, password,
|
ui->room_description->toPlainText().toStdString(), "", port, password,
|
||||||
ui->max_player->value(), Settings::values.yuzu_username.GetValue(),
|
ui->max_player->value(), Settings::values.yuzu_username.GetValue(),
|
||||||
game_name.toStdString(), game_id, CreateVerifyBackend(is_public), ban_list);
|
game, CreateVerifyBackend(is_public), ban_list);
|
||||||
if (!created) {
|
if (!created) {
|
||||||
NetworkMessage::ErrorManager::ShowError(
|
NetworkMessage::ErrorManager::ShowError(
|
||||||
NetworkMessage::ErrorManager::COULD_NOT_CREATE_ROOM);
|
NetworkMessage::ErrorManager::COULD_NOT_CREATE_ROOM);
|
||||||
|
@ -214,7 +214,7 @@ void Lobby::OnRefreshLobby() {
|
|||||||
for (int r = 0; r < game_list->rowCount(); ++r) {
|
for (int r = 0; r < game_list->rowCount(); ++r) {
|
||||||
auto index = game_list->index(r, 0);
|
auto index = game_list->index(r, 0);
|
||||||
auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong();
|
auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong();
|
||||||
if (game_id != 0 && room.information.preferred_game_id == game_id) {
|
if (game_id != 0 && room.information.preferred_game.id == game_id) {
|
||||||
smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>();
|
smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,8 +223,8 @@ void Lobby::OnRefreshLobby() {
|
|||||||
for (auto member : room.members) {
|
for (auto member : room.members) {
|
||||||
QVariant var;
|
QVariant var;
|
||||||
var.setValue(LobbyMember{QString::fromStdString(member.username),
|
var.setValue(LobbyMember{QString::fromStdString(member.username),
|
||||||
QString::fromStdString(member.nickname), member.game_id,
|
QString::fromStdString(member.nickname), member.game.id,
|
||||||
QString::fromStdString(member.game_name)});
|
QString::fromStdString(member.game.name)});
|
||||||
members.append(var);
|
members.append(var);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,8 +232,9 @@ void Lobby::OnRefreshLobby() {
|
|||||||
auto row = QList<QStandardItem*>({
|
auto row = QList<QStandardItem*>({
|
||||||
first_item,
|
first_item,
|
||||||
new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)),
|
new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)),
|
||||||
new LobbyItemGame(room.information.preferred_game_id,
|
new LobbyItemGame(room.information.preferred_game.id,
|
||||||
QString::fromStdString(room.information.preferred_game), smdh_icon),
|
QString::fromStdString(room.information.preferred_game.name),
|
||||||
|
smdh_icon),
|
||||||
new LobbyItemHost(QString::fromStdString(room.information.host_username),
|
new LobbyItemHost(QString::fromStdString(room.information.host_username),
|
||||||
QString::fromStdString(room.ip), room.information.port,
|
QString::fromStdString(room.ip), room.information.port,
|
||||||
QString::fromStdString(room.verify_UID)),
|
QString::fromStdString(room.verify_UID)),
|
||||||
|
@ -105,12 +105,12 @@ struct Values {
|
|||||||
// multiplayer settings
|
// multiplayer settings
|
||||||
Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"};
|
Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"};
|
||||||
Settings::Setting<QString> multiplayer_ip{{}, "ip"};
|
Settings::Setting<QString> multiplayer_ip{{}, "ip"};
|
||||||
Settings::SwitchableSetting<uint> multiplayer_port{24872, 0, 65535, "port"};
|
Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, 65535, "port"};
|
||||||
Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"};
|
Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"};
|
||||||
Settings::Setting<QString> multiplayer_room_name{{}, "room_name"};
|
Settings::Setting<QString> multiplayer_room_name{{}, "room_name"};
|
||||||
Settings::SwitchableSetting<uint> multiplayer_max_player{8, 0, 8, "max_player"};
|
Settings::SwitchableSetting<uint, true> multiplayer_max_player{8, 0, 8, "max_player"};
|
||||||
Settings::SwitchableSetting<uint> multiplayer_room_port{24872, 0, 65535, "room_port"};
|
Settings::SwitchableSetting<uint, true> multiplayer_room_port{24872, 0, 65535, "room_port"};
|
||||||
Settings::SwitchableSetting<uint> multiplayer_host_type{0, 0, 1, "host_type"};
|
Settings::SwitchableSetting<uint, true> multiplayer_host_type{0, 0, 1, "host_type"};
|
||||||
Settings::Setting<qulonglong> multiplayer_game_id{{}, "game_id"};
|
Settings::Setting<qulonglong> multiplayer_game_id{{}, "game_id"};
|
||||||
Settings::Setting<QString> multiplayer_room_description{{}, "room_description"};
|
Settings::Setting<QString> multiplayer_room_description{{}, "room_description"};
|
||||||
std::pair<std::vector<std::string>, std::vector<std::string>> multiplayer_ban_list;
|
std::pair<std::vector<std::string>, std::vector<std::string>> multiplayer_ban_list;
|
||||||
|
Loading…
Reference in New Issue
Block a user