Compare commits
1 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fdefd7064 |
8
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "enet"]
|
||||
path = externals/enet
|
||||
url = https://github.com/lsalzman/enet.git
|
||||
[submodule "inih"]
|
||||
path = externals/inih/inih
|
||||
url = https://github.com/benhoyt/inih.git
|
||||
@@ -42,10 +39,7 @@
|
||||
url = https://github.com/yhirose/cpp-httplib.git
|
||||
[submodule "ffmpeg"]
|
||||
path = externals/ffmpeg/ffmpeg
|
||||
url = https://github.com/FFmpeg/FFmpeg.git
|
||||
url = https://git.ffmpeg.org/ffmpeg.git
|
||||
[submodule "vcpkg"]
|
||||
path = externals/vcpkg
|
||||
url = https://github.com/Microsoft/vcpkg.git
|
||||
[submodule "cpp-jwt"]
|
||||
path = externals/cpp-jwt
|
||||
url = https://github.com/arun11299/cpp-jwt.git
|
||||
|
||||
9
dist/license.md
vendored
@@ -3,9 +3,6 @@ The icons in this folder and its subfolders have the following licenses:
|
||||
Icon Name | License | Origin/Author
|
||||
--- | --- | ---
|
||||
qt_themes/default/icons/16x16/checked.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/failed.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
|
||||
@@ -13,24 +10,18 @@ qt_themes/default/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.
|
||||
qt_themes/default/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/no_avatar.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/default/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
|
||||
qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/no_avatar.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||
qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/qdarkstyle/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
|
||||
qt_themes/colorful/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
|
||||
qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
|
||||
|
||||
BIN
dist/qt_themes/colorful/icons/16x16/connected.png
vendored
|
Before Width: | Height: | Size: 362 B |
|
Before Width: | Height: | Size: 607 B |
BIN
dist/qt_themes/colorful/icons/16x16/disconnected.png
vendored
|
Before Width: | Height: | Size: 784 B |
3
dist/qt_themes/colorful/style.qrc
vendored
@@ -1,9 +1,6 @@
|
||||
<RCC>
|
||||
<qresource prefix="icons/colorful">
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
||||
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
||||
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
|
||||
4
dist/qt_themes/colorful_dark/style.qrc
vendored
@@ -1,15 +1,11 @@
|
||||
<RCC>
|
||||
<qresource prefix="icons/colorful_dark">
|
||||
<file alias="16x16/connected.png">../colorful/icons/16x16/connected.png</file>
|
||||
<file alias="16x16/connected_notification.png">../colorful/icons/16x16/connected_notification.png</file>
|
||||
<file alias="16x16/disconnected.png">../colorful/icons/16x16/disconnected.png</file>
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
|
||||
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
|
||||
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/plus.png">../colorful/icons/48x48/plus.png</file>
|
||||
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
|
||||
<file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
|
||||
|
||||
4
dist/qt_themes/default/default.qrc
vendored
@@ -4,14 +4,10 @@
|
||||
<file alias="16x16/checked.png">icons/16x16/checked.png</file>
|
||||
<file alias="16x16/failed.png">icons/16x16/failed.png</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
||||
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
||||
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
||||
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
<file alias="48x48/star.png">icons/48x48/star.png</file>
|
||||
|
||||
BIN
dist/qt_themes/default/icons/16x16/connected.png
vendored
|
Before Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 517 B |
BIN
dist/qt_themes/default/icons/16x16/disconnected.png
vendored
|
Before Width: | Height: | Size: 306 B |
BIN
dist/qt_themes/default/icons/48x48/no_avatar.png
vendored
|
Before Width: | Height: | Size: 588 B |
BIN
dist/qt_themes/qdarkstyle/icons/16x16/connected.png
vendored
|
Before Width: | Height: | Size: 397 B |
|
Before Width: | Height: | Size: 526 B |
|
Before Width: | Height: | Size: 444 B |
BIN
dist/qt_themes/qdarkstyle/icons/48x48/no_avatar.png
vendored
|
Before Width: | Height: | Size: 708 B |
4
dist/qt_themes/qdarkstyle/style.qrc
vendored
@@ -1,15 +1,11 @@
|
||||
<RCC>
|
||||
<qresource prefix="icons/qdarkstyle">
|
||||
<file alias="index.theme">icons/index.theme</file>
|
||||
<file alias="16x16/connected.png">icons/16x16/connected.png</file>
|
||||
<file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file>
|
||||
<file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file>
|
||||
<file alias="16x16/lock.png">icons/16x16/lock.png</file>
|
||||
<file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file>
|
||||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
<file alias="48x48/star.png">icons/48x48/star.png</file>
|
||||
|
||||
9
externals/CMakeLists.txt
vendored
@@ -73,10 +73,6 @@ if (YUZU_USE_EXTERNAL_SDL2)
|
||||
add_library(SDL2 ALIAS SDL2-static)
|
||||
endif()
|
||||
|
||||
# ENet
|
||||
add_subdirectory(enet)
|
||||
target_include_directories(enet INTERFACE ./enet/include)
|
||||
|
||||
# Cubeb
|
||||
if(ENABLE_CUBEB)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||
@@ -116,11 +112,6 @@ if (ENABLE_WEB_SERVICE)
|
||||
if (WIN32)
|
||||
target_link_libraries(httplib INTERFACE crypt32 cryptui ws2_32)
|
||||
endif()
|
||||
|
||||
# cpp-jwt
|
||||
add_library(cpp-jwt INTERFACE)
|
||||
target_include_directories(cpp-jwt INTERFACE ./cpp-jwt/include)
|
||||
target_compile_definitions(cpp-jwt INTERFACE CPP_JWT_USE_VENDORED_NLOHMANN_JSON)
|
||||
endif()
|
||||
|
||||
# Opus
|
||||
|
||||
1
externals/cpp-jwt
vendored
1
externals/enet
vendored
2
externals/ffmpeg/ffmpeg
vendored
@@ -156,7 +156,6 @@ add_subdirectory(common)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(audio_core)
|
||||
add_subdirectory(video_core)
|
||||
add_subdirectory(network)
|
||||
add_subdirectory(input_common)
|
||||
add_subdirectory(shader_recompiler)
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
add_library(common STATIC
|
||||
algorithm.h
|
||||
alignment.h
|
||||
announce_multiplayer_room.h
|
||||
assert.cpp
|
||||
assert.h
|
||||
atomic_helpers.h
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "web_service/web_result.h"
|
||||
|
||||
namespace AnnounceMultiplayerRoom {
|
||||
|
||||
using MacAddress = std::array<u8, 6>;
|
||||
|
||||
struct GameInfo {
|
||||
std::string name{""};
|
||||
u64 id{0};
|
||||
};
|
||||
|
||||
struct Member {
|
||||
std::string username;
|
||||
std::string nickname;
|
||||
std::string display_name;
|
||||
std::string avatar_url;
|
||||
MacAddress mac_address;
|
||||
GameInfo game;
|
||||
};
|
||||
|
||||
struct RoomInformation {
|
||||
std::string name; ///< Name of the server
|
||||
std::string description; ///< Server description
|
||||
u32 member_slots; ///< Maximum number of members in this room
|
||||
u16 port; ///< The port of this room
|
||||
GameInfo preferred_game; ///< Game to advertise that you want to play
|
||||
std::string host_username; ///< Forum username of the host
|
||||
bool enable_yuzu_mods; ///< Allow yuzu Moderators to moderate on this room
|
||||
};
|
||||
|
||||
struct Room {
|
||||
RoomInformation information;
|
||||
|
||||
std::string id;
|
||||
std::string verify_uid; ///< UID used for verification
|
||||
std::string ip;
|
||||
u32 net_version;
|
||||
bool has_password;
|
||||
|
||||
std::vector<Member> members;
|
||||
};
|
||||
using RoomList = std::vector<Room>;
|
||||
|
||||
/**
|
||||
* A AnnounceMultiplayerRoom interface class. A backend to submit/get to/from a web service should
|
||||
* implement this interface.
|
||||
*/
|
||||
class Backend {
|
||||
public:
|
||||
virtual ~Backend() = default;
|
||||
|
||||
/**
|
||||
* Sets the Information that gets used for the announce
|
||||
* @param uid The Id of the room
|
||||
* @param name The name of the room
|
||||
* @param description The room description
|
||||
* @param port The port of the room
|
||||
* @param net_version The version of the libNetwork that gets used
|
||||
* @param has_password True if the room is passowrd protected
|
||||
* @param preferred_game The preferred game of the room
|
||||
* @param preferred_game_id The title id of the preferred game
|
||||
*/
|
||||
virtual 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 GameInfo& preferred_game) = 0;
|
||||
/**
|
||||
* Adds a player information to the data that gets announced
|
||||
* @param nickname The nickname of the player
|
||||
* @param mac_address The MAC Address of the player
|
||||
* @param game_id The title id of the game the player plays
|
||||
* @param game_name The name of the game the player plays
|
||||
*/
|
||||
virtual void AddPlayer(const Member& member) = 0;
|
||||
|
||||
/**
|
||||
* Updates the data in the announce service. Re-register the room when required.
|
||||
* @result The result of the update attempt
|
||||
*/
|
||||
virtual WebService::WebResult Update() = 0;
|
||||
|
||||
/**
|
||||
* Registers the data in the announce service
|
||||
* @result The result of the register attempt. When the result code is Success, A global Guid of
|
||||
* the room which may be used for verification will be in the result's returned_data.
|
||||
*/
|
||||
virtual WebService::WebResult Register() = 0;
|
||||
|
||||
/**
|
||||
* Empties the stored players
|
||||
*/
|
||||
virtual void ClearPlayers() = 0;
|
||||
|
||||
/**
|
||||
* Get the room information from the announce service
|
||||
* @result A list of all rooms the announce service has
|
||||
*/
|
||||
virtual RoomList GetRoomList() = 0;
|
||||
|
||||
/**
|
||||
* Sends a delete message to the announce service
|
||||
*/
|
||||
virtual void Delete() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Empty implementation of AnnounceMultiplayerRoom interface that drops all data. Used when a
|
||||
* functional backend implementation is not available.
|
||||
*/
|
||||
class NullBackend : public Backend {
|
||||
public:
|
||||
~NullBackend() = default;
|
||||
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 GameInfo& /*preferred_game*/) override {}
|
||||
void AddPlayer(const Member& /*member*/) override {}
|
||||
WebService::WebResult Update() override {
|
||||
return WebService::WebResult{WebService::WebResult::Code::NoWebservice,
|
||||
"WebService is missing", ""};
|
||||
}
|
||||
WebService::WebResult Register() override {
|
||||
return WebService::WebResult{WebService::WebResult::Code::NoWebservice,
|
||||
"WebService is missing", ""};
|
||||
}
|
||||
void ClearPlayers() override {}
|
||||
RoomList GetRoomList() override {
|
||||
return RoomList{};
|
||||
}
|
||||
|
||||
void Delete() override {}
|
||||
};
|
||||
|
||||
} // namespace AnnounceMultiplayerRoom
|
||||
@@ -1,6 +1,4 @@
|
||||
add_library(core STATIC
|
||||
announce_multiplayer_session.cpp
|
||||
announce_multiplayer_session.h
|
||||
arm/arm_interface.h
|
||||
arm/arm_interface.cpp
|
||||
arm/cpu_interrupt_handler.cpp
|
||||
@@ -716,11 +714,6 @@ add_library(core STATIC
|
||||
hle/service/vi/vi_u.h
|
||||
hle/service/wlan/wlan.cpp
|
||||
hle/service/wlan/wlan.h
|
||||
internal_network/network.cpp
|
||||
internal_network/network.h
|
||||
internal_network/network_interface.cpp
|
||||
internal_network/network_interface.h
|
||||
internal_network/sockets.h
|
||||
loader/deconstructed_rom_directory.cpp
|
||||
loader/deconstructed_rom_directory.h
|
||||
loader/elf.cpp
|
||||
@@ -748,6 +741,11 @@ add_library(core STATIC
|
||||
memory/dmnt_cheat_vm.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
network/network.cpp
|
||||
network/network.h
|
||||
network/network_interface.cpp
|
||||
network/network_interface.h
|
||||
network/sockets.h
|
||||
perf_stats.cpp
|
||||
perf_stats.h
|
||||
reporter.cpp
|
||||
@@ -782,7 +780,7 @@ endif()
|
||||
|
||||
create_target_directory_groups(core)
|
||||
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus)
|
||||
if (MINGW)
|
||||
target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <vector>
|
||||
#include "announce_multiplayer_session.h"
|
||||
#include "common/announce_multiplayer_room.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/settings.h"
|
||||
#include "network/network.h"
|
||||
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
#include "web_service/announce_room_json.h"
|
||||
#endif
|
||||
|
||||
namespace Core {
|
||||
|
||||
// Time between room is announced to web_service
|
||||
static constexpr std::chrono::seconds announce_time_interval(15);
|
||||
|
||||
AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& room_network_)
|
||||
: room_network{room_network_} {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
|
||||
Settings::values.yuzu_username.GetValue(),
|
||||
Settings::values.yuzu_token.GetValue());
|
||||
#else
|
||||
backend = std::make_unique<AnnounceMultiplayerRoom::NullBackend>();
|
||||
#endif
|
||||
}
|
||||
|
||||
WebService::WebResult AnnounceMultiplayerSession::Register() {
|
||||
std::shared_ptr<Network::Room> room = room_network.GetRoom().lock();
|
||||
if (!room) {
|
||||
return WebService::WebResult{WebService::WebResult::Code::LibError,
|
||||
"Network is not initialized", ""};
|
||||
}
|
||||
if (room->GetState() != Network::Room::State::Open) {
|
||||
return WebService::WebResult{WebService::WebResult::Code::LibError, "Room is not open", ""};
|
||||
}
|
||||
UpdateBackendData(room);
|
||||
WebService::WebResult result = backend->Register();
|
||||
if (result.result_code != WebService::WebResult::Code::Success) {
|
||||
return result;
|
||||
}
|
||||
LOG_INFO(WebService, "Room has been registered");
|
||||
room->SetVerifyUID(result.returned_data);
|
||||
registered = true;
|
||||
return WebService::WebResult{WebService::WebResult::Code::Success, "", ""};
|
||||
}
|
||||
|
||||
void AnnounceMultiplayerSession::Start() {
|
||||
if (announce_multiplayer_thread) {
|
||||
Stop();
|
||||
}
|
||||
shutdown_event.Reset();
|
||||
announce_multiplayer_thread =
|
||||
std::make_unique<std::thread>(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this);
|
||||
}
|
||||
|
||||
void AnnounceMultiplayerSession::Stop() {
|
||||
if (announce_multiplayer_thread) {
|
||||
shutdown_event.Set();
|
||||
announce_multiplayer_thread->join();
|
||||
announce_multiplayer_thread.reset();
|
||||
backend->Delete();
|
||||
registered = false;
|
||||
}
|
||||
}
|
||||
|
||||
AnnounceMultiplayerSession::CallbackHandle AnnounceMultiplayerSession::BindErrorCallback(
|
||||
std::function<void(const WebService::WebResult&)> function) {
|
||||
std::lock_guard lock(callback_mutex);
|
||||
auto handle = std::make_shared<std::function<void(const WebService::WebResult&)>>(function);
|
||||
error_callbacks.insert(handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void AnnounceMultiplayerSession::UnbindErrorCallback(CallbackHandle handle) {
|
||||
std::lock_guard lock(callback_mutex);
|
||||
error_callbacks.erase(handle);
|
||||
}
|
||||
|
||||
AnnounceMultiplayerSession::~AnnounceMultiplayerSession() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room> room) {
|
||||
Network::RoomInformation room_information = room->GetRoomInformation();
|
||||
std::vector<AnnounceMultiplayerRoom::Member> memberlist = room->GetRoomMemberList();
|
||||
backend->SetRoomInformation(room_information.name, room_information.description,
|
||||
room_information.port, room_information.member_slots,
|
||||
Network::network_version, room->HasPassword(),
|
||||
room_information.preferred_game);
|
||||
backend->ClearPlayers();
|
||||
for (const auto& member : memberlist) {
|
||||
backend->AddPlayer(member);
|
||||
}
|
||||
}
|
||||
|
||||
void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() {
|
||||
// Invokes all current bound error callbacks.
|
||||
const auto ErrorCallback = [this](WebService::WebResult result) {
|
||||
std::lock_guard<std::mutex> lock(callback_mutex);
|
||||
for (auto callback : error_callbacks) {
|
||||
(*callback)(result);
|
||||
}
|
||||
};
|
||||
|
||||
if (!registered) {
|
||||
WebService::WebResult result = Register();
|
||||
if (result.result_code != WebService::WebResult::Code::Success) {
|
||||
ErrorCallback(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto update_time = std::chrono::steady_clock::now();
|
||||
std::future<WebService::WebResult> future;
|
||||
while (!shutdown_event.WaitUntil(update_time)) {
|
||||
update_time += announce_time_interval;
|
||||
std::shared_ptr<Network::Room> room = room_network.GetRoom().lock();
|
||||
if (!room) {
|
||||
break;
|
||||
}
|
||||
if (room->GetState() != Network::Room::State::Open) {
|
||||
break;
|
||||
}
|
||||
UpdateBackendData(room);
|
||||
WebService::WebResult result = backend->Update();
|
||||
if (result.result_code != WebService::WebResult::Code::Success) {
|
||||
ErrorCallback(result);
|
||||
}
|
||||
if (result.result_string == "404") {
|
||||
registered = false;
|
||||
// Needs to register the room again
|
||||
WebService::WebResult register_result = Register();
|
||||
if (register_result.result_code != WebService::WebResult::Code::Success) {
|
||||
ErrorCallback(register_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnnounceMultiplayerRoom::RoomList AnnounceMultiplayerSession::GetRoomList() {
|
||||
return backend->GetRoomList();
|
||||
}
|
||||
|
||||
bool AnnounceMultiplayerSession::IsRunning() const {
|
||||
return announce_multiplayer_thread != nullptr;
|
||||
}
|
||||
|
||||
void AnnounceMultiplayerSession::UpdateCredentials() {
|
||||
ASSERT_MSG(!IsRunning(), "Credentials can only be updated when session is not running");
|
||||
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
|
||||
Settings::values.yuzu_username.GetValue(),
|
||||
Settings::values.yuzu_token.GetValue());
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
@@ -1,98 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include "common/announce_multiplayer_room.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
namespace Network {
|
||||
class Room;
|
||||
class RoomNetwork;
|
||||
} // namespace Network
|
||||
|
||||
namespace Core {
|
||||
|
||||
/**
|
||||
* Instruments AnnounceMultiplayerRoom::Backend.
|
||||
* Creates a thread that regularly updates the room information and submits them
|
||||
* An async get of room information is also possible
|
||||
*/
|
||||
class AnnounceMultiplayerSession {
|
||||
public:
|
||||
using CallbackHandle = std::shared_ptr<std::function<void(const WebService::WebResult&)>>;
|
||||
AnnounceMultiplayerSession(Network::RoomNetwork& room_network_);
|
||||
~AnnounceMultiplayerSession();
|
||||
|
||||
/**
|
||||
* Allows to bind a function that will get called if the announce encounters an error
|
||||
* @param function The function that gets called
|
||||
* @return A handle that can be used the unbind the function
|
||||
*/
|
||||
CallbackHandle BindErrorCallback(std::function<void(const WebService::WebResult&)> function);
|
||||
|
||||
/**
|
||||
* Unbind a function from the error callbacks
|
||||
* @param handle The handle for the function that should get unbind
|
||||
*/
|
||||
void UnbindErrorCallback(CallbackHandle handle);
|
||||
|
||||
/**
|
||||
* Registers a room to web services
|
||||
* @return The result of the registration attempt.
|
||||
*/
|
||||
WebService::WebResult Register();
|
||||
|
||||
/**
|
||||
* Starts the announce of a room to web services
|
||||
*/
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* Stops the announce to web services
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
/**
|
||||
* Returns a list of all room information the backend got
|
||||
* @param func A function that gets executed when the async get finished, e.g. a signal
|
||||
* @return a list of rooms received from the web service
|
||||
*/
|
||||
AnnounceMultiplayerRoom::RoomList GetRoomList();
|
||||
|
||||
/**
|
||||
* Whether the announce session is still running
|
||||
*/
|
||||
bool IsRunning() const;
|
||||
|
||||
/**
|
||||
* Recreates the backend, updating the credentials.
|
||||
* This can only be used when the announce session is not running.
|
||||
*/
|
||||
void UpdateCredentials();
|
||||
|
||||
private:
|
||||
void UpdateBackendData(std::shared_ptr<Network::Room> room);
|
||||
void AnnounceMultiplayerLoop();
|
||||
|
||||
Common::Event shutdown_event;
|
||||
std::mutex callback_mutex;
|
||||
std::set<CallbackHandle> error_callbacks;
|
||||
std::unique_ptr<std::thread> announce_multiplayer_thread;
|
||||
|
||||
/// Backend interface that logs fields
|
||||
std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend;
|
||||
|
||||
std::atomic_bool registered = false; ///< Whether the room has been registered
|
||||
|
||||
Network::RoomNetwork& room_network;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -154,10 +154,9 @@ void ARM_Interface::Run() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle syscalls and scheduling (this may change the current thread/core)
|
||||
// Handle syscalls and scheduling (this may change the current thread)
|
||||
if (Has(hr, svc_call)) {
|
||||
Kernel::Svc::Call(system, GetSvcNumber());
|
||||
break;
|
||||
}
|
||||
if (Has(hr, break_loop) || !uses_wall_clock) {
|
||||
break;
|
||||
|
||||
@@ -43,15 +43,14 @@
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/hle/service/time/time_manager.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
#include "core/network/network.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/reporter.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "core/tools/freezer.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
@@ -131,7 +130,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
|
||||
struct System::Impl {
|
||||
explicit Impl(System& system)
|
||||
: kernel{system}, fs_controller{system}, memory{system}, hid_core{}, room_network{},
|
||||
: kernel{system}, fs_controller{system}, memory{system}, hid_core{},
|
||||
cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {}
|
||||
|
||||
SystemResultStatus Run() {
|
||||
@@ -316,17 +315,6 @@ struct System::Impl {
|
||||
GetAndResetPerfStats();
|
||||
perf_stats->BeginSystemFrame();
|
||||
|
||||
std::string name = "Unknown Game";
|
||||
if (app_loader->ReadTitle(name) != Loader::ResultStatus::Success) {
|
||||
LOG_ERROR(Core, "Failed to read title for ROM (Error {})", load_result);
|
||||
}
|
||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info;
|
||||
game_info.name = name;
|
||||
game_info.id = program_id;
|
||||
room_member->SendGameInfo(game_info);
|
||||
}
|
||||
|
||||
status = SystemResultStatus::Success;
|
||||
return status;
|
||||
}
|
||||
@@ -374,11 +362,6 @@ struct System::Impl {
|
||||
memory.Reset();
|
||||
applet_manager.ClearAll();
|
||||
|
||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info{};
|
||||
room_member->SendGameInfo(game_info);
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Shutdown OK");
|
||||
}
|
||||
|
||||
@@ -451,8 +434,6 @@ struct System::Impl {
|
||||
std::unique_ptr<AudioCore::AudioCore> audio_core;
|
||||
Core::Memory::Memory memory;
|
||||
Core::HID::HIDCore hid_core;
|
||||
Network::RoomNetwork room_network;
|
||||
|
||||
CpuManager cpu_manager;
|
||||
std::atomic_bool is_powered_on{};
|
||||
bool exit_lock = false;
|
||||
@@ -898,14 +879,6 @@ const Core::Debugger& System::GetDebugger() const {
|
||||
return *impl->debugger;
|
||||
}
|
||||
|
||||
Network::RoomNetwork& System::GetRoomNetwork() {
|
||||
return impl->room_network;
|
||||
}
|
||||
|
||||
const Network::RoomNetwork& System::GetRoomNetwork() const {
|
||||
return impl->room_network;
|
||||
}
|
||||
|
||||
void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) {
|
||||
impl->execute_program_callback = std::move(callback);
|
||||
}
|
||||
|
||||
@@ -97,10 +97,6 @@ namespace Core::HID {
|
||||
class HIDCore;
|
||||
}
|
||||
|
||||
namespace Network {
|
||||
class RoomNetwork;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Interface;
|
||||
@@ -383,12 +379,6 @@ public:
|
||||
[[nodiscard]] Core::Debugger& GetDebugger();
|
||||
[[nodiscard]] const Core::Debugger& GetDebugger() const;
|
||||
|
||||
/// Gets a mutable reference to the Room Network.
|
||||
[[nodiscard]] Network::RoomNetwork& GetRoomNetwork();
|
||||
|
||||
/// Gets an immutable reference to the Room Network.
|
||||
[[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
|
||||
|
||||
void SetExitLock(bool locked);
|
||||
[[nodiscard]] bool GetExitLock() const;
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_manager.h"
|
||||
#include "core/hle/kernel/k_interrupt_manager.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
@@ -50,6 +49,14 @@ void CpuManager::GuestThreadFunction() {
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::GuestRewindFunction() {
|
||||
if (is_multicore) {
|
||||
MultiCoreRunGuestLoop();
|
||||
} else {
|
||||
SingleCoreRunGuestLoop();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::IdleThreadFunction() {
|
||||
if (is_multicore) {
|
||||
MultiCoreRunIdleThread();
|
||||
@@ -62,21 +69,21 @@ void CpuManager::ShutdownThreadFunction() {
|
||||
ShutdownThread();
|
||||
}
|
||||
|
||||
void CpuManager::HandleInterrupt() {
|
||||
auto& kernel = system.Kernel();
|
||||
auto core_index = kernel.CurrentPhysicalCoreIndex();
|
||||
|
||||
Kernel::KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_index));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// MultiCore ///
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void CpuManager::MultiCoreRunGuestThread() {
|
||||
// Similar to UserModeThreadStarter in HOS
|
||||
auto& kernel = system.Kernel();
|
||||
kernel.CurrentScheduler()->OnThreadStart();
|
||||
auto* thread = kernel.CurrentScheduler()->GetSchedulerCurrentThread();
|
||||
auto& host_context = thread->GetHostContext();
|
||||
host_context->SetRewindPoint([this] { GuestRewindFunction(); });
|
||||
MultiCoreRunGuestLoop();
|
||||
}
|
||||
|
||||
void CpuManager::MultiCoreRunGuestLoop() {
|
||||
auto& kernel = system.Kernel();
|
||||
|
||||
while (true) {
|
||||
auto* physical_core = &kernel.CurrentPhysicalCore();
|
||||
@@ -84,26 +91,18 @@ void CpuManager::MultiCoreRunGuestThread() {
|
||||
physical_core->Run();
|
||||
physical_core = &kernel.CurrentPhysicalCore();
|
||||
}
|
||||
|
||||
HandleInterrupt();
|
||||
{
|
||||
Kernel::KScopedDisableDispatch dd(kernel);
|
||||
physical_core->ArmInterface().ClearExclusiveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::MultiCoreRunIdleThread() {
|
||||
// Not accurate to HOS. Remove this entire method when singlecore is removed.
|
||||
// See notes in KScheduler::ScheduleImpl for more information about why this
|
||||
// is inaccurate.
|
||||
|
||||
auto& kernel = system.Kernel();
|
||||
kernel.CurrentScheduler()->OnThreadStart();
|
||||
|
||||
while (true) {
|
||||
auto& physical_core = kernel.CurrentPhysicalCore();
|
||||
if (!physical_core.IsInterrupted()) {
|
||||
physical_core.Idle();
|
||||
}
|
||||
|
||||
HandleInterrupt();
|
||||
Kernel::KScopedDisableDispatch dd(kernel);
|
||||
kernel.CurrentPhysicalCore().Idle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,73 +113,80 @@ void CpuManager::MultiCoreRunIdleThread() {
|
||||
void CpuManager::SingleCoreRunGuestThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
kernel.CurrentScheduler()->OnThreadStart();
|
||||
auto* thread = kernel.CurrentScheduler()->GetSchedulerCurrentThread();
|
||||
auto& host_context = thread->GetHostContext();
|
||||
host_context->SetRewindPoint([this] { GuestRewindFunction(); });
|
||||
SingleCoreRunGuestLoop();
|
||||
}
|
||||
|
||||
void CpuManager::SingleCoreRunGuestLoop() {
|
||||
auto& kernel = system.Kernel();
|
||||
while (true) {
|
||||
auto* physical_core = &kernel.CurrentPhysicalCore();
|
||||
if (!physical_core->IsInterrupted()) {
|
||||
physical_core->Run();
|
||||
physical_core = &kernel.CurrentPhysicalCore();
|
||||
}
|
||||
|
||||
kernel.SetIsPhantomModeForSingleCore(true);
|
||||
system.CoreTiming().Advance();
|
||||
kernel.SetIsPhantomModeForSingleCore(false);
|
||||
|
||||
physical_core->ArmInterface().ClearExclusiveState();
|
||||
PreemptSingleCore();
|
||||
HandleInterrupt();
|
||||
auto& scheduler = kernel.Scheduler(current_core);
|
||||
scheduler.RescheduleCurrentCore();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::SingleCoreRunIdleThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
kernel.CurrentScheduler()->OnThreadStart();
|
||||
|
||||
while (true) {
|
||||
auto& physical_core = kernel.CurrentPhysicalCore();
|
||||
PreemptSingleCore(false);
|
||||
system.CoreTiming().AddTicks(1000U);
|
||||
idle_count++;
|
||||
HandleInterrupt();
|
||||
auto& scheduler = physical_core.Scheduler();
|
||||
scheduler.RescheduleCurrentCore();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::PreemptSingleCore(bool from_running_environment) {
|
||||
auto& kernel = system.Kernel();
|
||||
void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
|
||||
{
|
||||
auto& kernel = system.Kernel();
|
||||
auto& scheduler = kernel.Scheduler(current_core);
|
||||
Kernel::KThread* current_thread = scheduler.GetSchedulerCurrentThread();
|
||||
if (idle_count >= 4 || from_running_enviroment) {
|
||||
if (!from_running_enviroment) {
|
||||
system.CoreTiming().Idle();
|
||||
idle_count = 0;
|
||||
}
|
||||
kernel.SetIsPhantomModeForSingleCore(true);
|
||||
system.CoreTiming().Advance();
|
||||
kernel.SetIsPhantomModeForSingleCore(false);
|
||||
}
|
||||
current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
|
||||
system.CoreTiming().ResetTicks();
|
||||
scheduler.Unload(scheduler.GetSchedulerCurrentThread());
|
||||
|
||||
if (idle_count >= 4 || from_running_environment) {
|
||||
if (!from_running_environment) {
|
||||
system.CoreTiming().Idle();
|
||||
auto& next_scheduler = kernel.Scheduler(current_core);
|
||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), *next_scheduler.ControlContext());
|
||||
}
|
||||
|
||||
// May have changed scheduler
|
||||
{
|
||||
auto& scheduler = system.Kernel().Scheduler(current_core);
|
||||
scheduler.Reload(scheduler.GetSchedulerCurrentThread());
|
||||
if (!scheduler.IsIdle()) {
|
||||
idle_count = 0;
|
||||
}
|
||||
kernel.SetIsPhantomModeForSingleCore(true);
|
||||
system.CoreTiming().Advance();
|
||||
kernel.SetIsPhantomModeForSingleCore(false);
|
||||
}
|
||||
current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
|
||||
system.CoreTiming().ResetTicks();
|
||||
kernel.Scheduler(current_core).PreemptSingleCore();
|
||||
|
||||
// We've now been scheduled again, and we may have exchanged schedulers.
|
||||
// Reload the scheduler in case it's different.
|
||||
if (!kernel.Scheduler(current_core).IsIdle()) {
|
||||
idle_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::GuestActivate() {
|
||||
// Similar to the HorizonKernelMain callback in HOS
|
||||
auto& kernel = system.Kernel();
|
||||
auto* scheduler = kernel.CurrentScheduler();
|
||||
|
||||
scheduler->Activate();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void CpuManager::ShutdownThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
auto* thread = kernel.GetCurrentEmuThread();
|
||||
auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0;
|
||||
auto* current_thread = kernel.GetCurrentEmuThread();
|
||||
|
||||
Common::Fiber::YieldTo(thread->GetHostContext(), *core_data[core].host_context);
|
||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context);
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
@@ -212,12 +218,9 @@ void CpuManager::RunThread(std::size_t core) {
|
||||
system.GPU().ObtainContext();
|
||||
}
|
||||
|
||||
auto& kernel = system.Kernel();
|
||||
auto& scheduler = *kernel.CurrentScheduler();
|
||||
auto* thread = scheduler.GetSchedulerCurrentThread();
|
||||
Kernel::SetCurrentThread(kernel, thread);
|
||||
|
||||
Common::Fiber::YieldTo(data.host_context, *thread->GetHostContext());
|
||||
auto* current_thread = system.Kernel().CurrentScheduler()->GetIdleThread();
|
||||
Kernel::SetCurrentThread(system.Kernel(), current_thread);
|
||||
Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext());
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -50,10 +50,7 @@ public:
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
std::function<void()> GetGuestActivateFunc() {
|
||||
return [this] { GuestActivate(); };
|
||||
}
|
||||
std::function<void()> GetGuestThreadFunc() {
|
||||
std::function<void()> GetGuestThreadStartFunc() {
|
||||
return [this] { GuestThreadFunction(); };
|
||||
}
|
||||
std::function<void()> GetIdleThreadStartFunc() {
|
||||
@@ -71,19 +68,20 @@ public:
|
||||
|
||||
private:
|
||||
void GuestThreadFunction();
|
||||
void GuestRewindFunction();
|
||||
void IdleThreadFunction();
|
||||
void ShutdownThreadFunction();
|
||||
|
||||
void MultiCoreRunGuestThread();
|
||||
void MultiCoreRunGuestLoop();
|
||||
void MultiCoreRunIdleThread();
|
||||
|
||||
void SingleCoreRunGuestThread();
|
||||
void SingleCoreRunGuestLoop();
|
||||
void SingleCoreRunIdleThread();
|
||||
|
||||
static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core);
|
||||
|
||||
void GuestActivate();
|
||||
void HandleInterrupt();
|
||||
void ShutdownThread();
|
||||
void RunThread(std::size_t core);
|
||||
|
||||
|
||||
@@ -41,7 +41,12 @@ void GlobalSchedulerContext::PreemptThreads() {
|
||||
ASSERT(IsLocked());
|
||||
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||
const u32 priority = preemption_priorities[core_id];
|
||||
KScheduler::RotateScheduledQueue(kernel, core_id, priority);
|
||||
kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority);
|
||||
|
||||
// Signal an interrupt occurred. For core 3, this is a certainty, as preemption will result
|
||||
// in the rotator thread being scheduled. For cores 0-2, this is to simulate or system
|
||||
// interrupts that may have occurred.
|
||||
kernel.PhysicalCore(core_id).Interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
|
||||
namespace Kernel::KInterruptManager {
|
||||
|
||||
@@ -16,9 +15,6 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Acknowledge the interrupt.
|
||||
kernel.PhysicalCore(core_id).ClearInterrupt();
|
||||
|
||||
auto& current_thread = GetCurrentThread(kernel);
|
||||
|
||||
// If the user disable count is set, we may need to pin the current thread.
|
||||
@@ -31,9 +27,6 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) {
|
||||
// Set the interrupt flag for the thread.
|
||||
GetCurrentThread(kernel).SetInterruptFlag();
|
||||
}
|
||||
|
||||
// Request interrupt scheduling.
|
||||
kernel.CurrentScheduler()->RequestScheduleOnInterrupt();
|
||||
}
|
||||
|
||||
} // namespace Kernel::KInterruptManager
|
||||
|
||||
@@ -27,185 +27,69 @@ static void IncrementScheduledCount(Kernel::KThread* thread) {
|
||||
}
|
||||
}
|
||||
|
||||
KScheduler::KScheduler(KernelCore& kernel_) : kernel{kernel_} {
|
||||
m_switch_fiber = std::make_shared<Common::Fiber>([this] {
|
||||
while (true) {
|
||||
ScheduleImplFiber();
|
||||
void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule) {
|
||||
auto scheduler = kernel.CurrentScheduler();
|
||||
|
||||
u32 current_core{0xF};
|
||||
bool must_context_switch{};
|
||||
if (scheduler) {
|
||||
current_core = scheduler->core_id;
|
||||
// TODO(bunnei): Should be set to true when we deprecate single core
|
||||
must_context_switch = !kernel.IsPhantomModeForSingleCore();
|
||||
}
|
||||
|
||||
while (cores_pending_reschedule != 0) {
|
||||
const auto core = static_cast<u32>(std::countr_zero(cores_pending_reschedule));
|
||||
ASSERT(core < Core::Hardware::NUM_CPU_CORES);
|
||||
if (!must_context_switch || core != current_core) {
|
||||
auto& phys_core = kernel.PhysicalCore(core);
|
||||
phys_core.Interrupt();
|
||||
}
|
||||
});
|
||||
|
||||
m_state.needs_scheduling = true;
|
||||
}
|
||||
|
||||
KScheduler::~KScheduler() = default;
|
||||
|
||||
void KScheduler::SetInterruptTaskRunnable() {
|
||||
m_state.interrupt_task_runnable = true;
|
||||
m_state.needs_scheduling = true;
|
||||
}
|
||||
|
||||
void KScheduler::RequestScheduleOnInterrupt() {
|
||||
m_state.needs_scheduling = true;
|
||||
|
||||
if (CanSchedule(kernel)) {
|
||||
ScheduleOnInterrupt();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::DisableScheduling(KernelCore& kernel) {
|
||||
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0);
|
||||
GetCurrentThread(kernel).DisableDispatch();
|
||||
}
|
||||
|
||||
void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) {
|
||||
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 1);
|
||||
|
||||
auto* scheduler{kernel.CurrentScheduler()};
|
||||
|
||||
if (!scheduler || kernel.IsPhantomModeForSingleCore()) {
|
||||
KScheduler::RescheduleCores(kernel, cores_needing_scheduling);
|
||||
KScheduler::RescheduleCurrentHLEThread(kernel);
|
||||
return;
|
||||
cores_pending_reschedule &= ~(1ULL << core);
|
||||
}
|
||||
|
||||
scheduler->RescheduleOtherCores(cores_needing_scheduling);
|
||||
|
||||
if (GetCurrentThread(kernel).GetDisableDispatchCount() > 1) {
|
||||
GetCurrentThread(kernel).EnableDispatch();
|
||||
} else {
|
||||
scheduler->RescheduleCurrentCore();
|
||||
for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; ++core_id) {
|
||||
if (kernel.PhysicalCore(core_id).IsInterrupted()) {
|
||||
KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::RescheduleCurrentHLEThread(KernelCore& kernel) {
|
||||
// HACK: we cannot schedule from this thread, it is not a core thread
|
||||
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
|
||||
|
||||
// Special case to ensure dummy threads that are waiting block
|
||||
GetCurrentThread(kernel).IfDummyThreadTryWait();
|
||||
|
||||
ASSERT(GetCurrentThread(kernel).GetState() != ThreadState::Waiting);
|
||||
GetCurrentThread(kernel).EnableDispatch();
|
||||
}
|
||||
|
||||
u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
|
||||
if (IsSchedulerUpdateNeeded(kernel)) {
|
||||
return UpdateHighestPriorityThreadsImpl(kernel);
|
||||
} else {
|
||||
return 0;
|
||||
if (must_context_switch) {
|
||||
auto core_scheduler = kernel.CurrentScheduler();
|
||||
kernel.ExitSVCProfile();
|
||||
core_scheduler->RescheduleCurrentCore();
|
||||
kernel.EnterSVCProfile();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::Schedule() {
|
||||
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
|
||||
ASSERT(m_core_id == GetCurrentCoreId(kernel));
|
||||
|
||||
ScheduleImpl();
|
||||
}
|
||||
|
||||
void KScheduler::ScheduleOnInterrupt() {
|
||||
GetCurrentThread(kernel).DisableDispatch();
|
||||
Schedule();
|
||||
GetCurrentThread(kernel).EnableDispatch();
|
||||
}
|
||||
|
||||
void KScheduler::PreemptSingleCore() {
|
||||
GetCurrentThread(kernel).DisableDispatch();
|
||||
|
||||
auto* thread = GetCurrentThreadPointer(kernel);
|
||||
auto& previous_scheduler = kernel.Scheduler(thread->GetCurrentCore());
|
||||
previous_scheduler.Unload(thread);
|
||||
|
||||
Common::Fiber::YieldTo(thread->GetHostContext(), *m_switch_fiber);
|
||||
|
||||
GetCurrentThread(kernel).EnableDispatch();
|
||||
}
|
||||
|
||||
void KScheduler::RescheduleCurrentCore() {
|
||||
ASSERT(!kernel.IsPhantomModeForSingleCore());
|
||||
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
|
||||
|
||||
GetCurrentThread(kernel).EnableDispatch();
|
||||
|
||||
if (m_state.needs_scheduling.load()) {
|
||||
// Disable interrupts, and then check again if rescheduling is needed.
|
||||
// KScopedInterruptDisable intr_disable;
|
||||
|
||||
kernel.CurrentScheduler()->RescheduleCurrentCoreImpl();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::RescheduleCurrentCoreImpl() {
|
||||
// Check that scheduling is needed.
|
||||
if (m_state.needs_scheduling.load()) [[likely]] {
|
||||
GetCurrentThread(kernel).DisableDispatch();
|
||||
Schedule();
|
||||
GetCurrentThread(kernel).EnableDispatch();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id) {
|
||||
// Set core ID/idle thread/interrupt task manager.
|
||||
m_core_id = core_id;
|
||||
m_idle_thread = idle_thread;
|
||||
// m_state.idle_thread_stack = m_idle_thread->GetStackTop();
|
||||
// m_state.interrupt_task_manager = &kernel.GetInterruptTaskManager();
|
||||
|
||||
// Insert the main thread into the priority queue.
|
||||
// {
|
||||
// KScopedSchedulerLock lk{kernel};
|
||||
// GetPriorityQueue(kernel).PushBack(GetCurrentThreadPointer(kernel));
|
||||
// SetSchedulerUpdateNeeded(kernel);
|
||||
// }
|
||||
|
||||
// Bind interrupt handler.
|
||||
// kernel.GetInterruptManager().BindHandler(
|
||||
// GetSchedulerInterruptHandler(kernel), KInterruptName::Scheduler, m_core_id,
|
||||
// KInterruptController::PriorityLevel::Scheduler, false, false);
|
||||
|
||||
// Set the current thread.
|
||||
m_current_thread = main_thread;
|
||||
}
|
||||
|
||||
void KScheduler::Activate() {
|
||||
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
|
||||
|
||||
// m_state.should_count_idle = KTargetSystem::IsDebugMode();
|
||||
m_is_active = true;
|
||||
RescheduleCurrentCore();
|
||||
}
|
||||
|
||||
void KScheduler::OnThreadStart() {
|
||||
GetCurrentThread(kernel).EnableDispatch();
|
||||
}
|
||||
|
||||
u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) {
|
||||
if (KThread* prev_highest_thread = m_state.highest_priority_thread;
|
||||
prev_highest_thread != highest_thread) [[likely]] {
|
||||
if (prev_highest_thread != nullptr) [[likely]] {
|
||||
KScopedSpinLock lk{guard};
|
||||
if (KThread* prev_highest_thread = state.highest_priority_thread;
|
||||
prev_highest_thread != highest_thread) {
|
||||
if (prev_highest_thread != nullptr) {
|
||||
IncrementScheduledCount(prev_highest_thread);
|
||||
prev_highest_thread->SetLastScheduledTick(kernel.System().CoreTiming().GetCPUTicks());
|
||||
prev_highest_thread->SetLastScheduledTick(system.CoreTiming().GetCPUTicks());
|
||||
}
|
||||
if (m_state.should_count_idle) {
|
||||
if (highest_thread != nullptr) [[likely]] {
|
||||
if (state.should_count_idle) {
|
||||
if (highest_thread != nullptr) {
|
||||
if (KProcess* process = highest_thread->GetOwnerProcess(); process != nullptr) {
|
||||
process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count);
|
||||
process->SetRunningThread(core_id, highest_thread, state.idle_count);
|
||||
}
|
||||
} else {
|
||||
m_state.idle_count++;
|
||||
state.idle_count++;
|
||||
}
|
||||
}
|
||||
|
||||
m_state.highest_priority_thread = highest_thread;
|
||||
m_state.needs_scheduling = true;
|
||||
return (1ULL << m_core_id);
|
||||
state.highest_priority_thread = highest_thread;
|
||||
state.needs_scheduling.store(true);
|
||||
return (1ULL << core_id);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
|
||||
ASSERT(IsSchedulerLockedByCurrentThread(kernel));
|
||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||
|
||||
// Clear that we need to update.
|
||||
ClearSchedulerUpdateNeeded(kernel);
|
||||
@@ -214,20 +98,18 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
|
||||
KThread* top_threads[Core::Hardware::NUM_CPU_CORES];
|
||||
auto& priority_queue = GetPriorityQueue(kernel);
|
||||
|
||||
// We want to go over all cores, finding the highest priority thread and determining if
|
||||
// scheduling is needed for that core.
|
||||
/// We want to go over all cores, finding the highest priority thread and determining if
|
||||
/// scheduling is needed for that core.
|
||||
for (size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||
KThread* top_thread = priority_queue.GetScheduledFront(static_cast<s32>(core_id));
|
||||
if (top_thread != nullptr) {
|
||||
// We need to check if the thread's process has a pinned thread.
|
||||
if (KProcess* parent = top_thread->GetOwnerProcess()) {
|
||||
// Check that there's a pinned thread other than the current top thread.
|
||||
if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id));
|
||||
pinned != nullptr && pinned != top_thread) {
|
||||
// We need to prefer threads with kernel waiters to the pinned thread.
|
||||
if (top_thread->GetNumKernelWaiters() ==
|
||||
0 /* && top_thread != parent->GetExceptionThread() */) {
|
||||
// If the pinned thread is runnable, use it.
|
||||
// If the thread has no waiters, we need to check if the process has a thread pinned.
|
||||
if (top_thread->GetNumKernelWaiters() == 0) {
|
||||
if (KProcess* parent = top_thread->GetOwnerProcess(); parent != nullptr) {
|
||||
if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id));
|
||||
pinned != nullptr && pinned != top_thread) {
|
||||
// We prefer our parent's pinned thread if possible. However, we also don't
|
||||
// want to schedule un-runnable threads.
|
||||
if (pinned->GetRawState() == ThreadState::Runnable) {
|
||||
top_thread = pinned;
|
||||
} else {
|
||||
@@ -247,8 +129,7 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
|
||||
|
||||
// Idle cores are bad. We're going to try to migrate threads to each idle core in turn.
|
||||
while (idle_cores != 0) {
|
||||
const s32 core_id = static_cast<s32>(std::countr_zero(idle_cores));
|
||||
|
||||
const auto core_id = static_cast<u32>(std::countr_zero(idle_cores));
|
||||
if (KThread* suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) {
|
||||
s32 migration_candidates[Core::Hardware::NUM_CPU_CORES];
|
||||
size_t num_candidates = 0;
|
||||
@@ -269,6 +150,7 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
|
||||
// The suggested thread isn't bound to its core, so we can migrate it!
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested);
|
||||
|
||||
top_threads[core_id] = suggested;
|
||||
cores_needing_scheduling |=
|
||||
kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
|
||||
@@ -301,6 +183,7 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
|
||||
// Perform the migration.
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(candidate_core, suggested);
|
||||
|
||||
top_threads[core_id] = suggested;
|
||||
cores_needing_scheduling |=
|
||||
kernel.Scheduler(core_id).UpdateHighestPriorityThread(
|
||||
@@ -317,210 +200,24 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
|
||||
return cores_needing_scheduling;
|
||||
}
|
||||
|
||||
void KScheduler::SwitchThread(KThread* next_thread) {
|
||||
KProcess* const cur_process = kernel.CurrentProcess();
|
||||
KThread* const cur_thread = GetCurrentThreadPointer(kernel);
|
||||
|
||||
// We never want to schedule a null thread, so use the idle thread if we don't have a next.
|
||||
if (next_thread == nullptr) {
|
||||
next_thread = m_idle_thread;
|
||||
}
|
||||
|
||||
if (next_thread->GetCurrentCore() != m_core_id) {
|
||||
next_thread->SetCurrentCore(m_core_id);
|
||||
}
|
||||
|
||||
// If we're not actually switching thread, there's nothing to do.
|
||||
if (next_thread == cur_thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Next thread is now known not to be nullptr, and must not be dispatchable.
|
||||
ASSERT(next_thread->GetDisableDispatchCount() == 1);
|
||||
ASSERT(!next_thread->IsDummyThread());
|
||||
|
||||
// Update the CPU time tracking variables.
|
||||
const s64 prev_tick = m_last_context_switch_time;
|
||||
const s64 cur_tick = kernel.System().CoreTiming().GetCPUTicks();
|
||||
const s64 tick_diff = cur_tick - prev_tick;
|
||||
cur_thread->AddCpuTime(m_core_id, tick_diff);
|
||||
if (cur_process != nullptr) {
|
||||
cur_process->UpdateCPUTimeTicks(tick_diff);
|
||||
}
|
||||
m_last_context_switch_time = cur_tick;
|
||||
|
||||
// Update our previous thread.
|
||||
if (cur_process != nullptr) {
|
||||
if (!cur_thread->IsTerminationRequested() && cur_thread->GetActiveCore() == m_core_id)
|
||||
[[likely]] {
|
||||
m_state.prev_thread = cur_thread;
|
||||
} else {
|
||||
m_state.prev_thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Switch the current process, if we're switching processes.
|
||||
// if (KProcess *next_process = next_thread->GetOwnerProcess(); next_process != cur_process) {
|
||||
// KProcess::Switch(cur_process, next_process);
|
||||
// }
|
||||
|
||||
// Set the new thread.
|
||||
SetCurrentThread(kernel, next_thread);
|
||||
m_current_thread = next_thread;
|
||||
|
||||
// Set the new Thread Local region.
|
||||
// cpu::SwitchThreadLocalRegion(GetInteger(next_thread->GetThreadLocalRegionAddress()));
|
||||
}
|
||||
|
||||
void KScheduler::ScheduleImpl() {
|
||||
// First, clear the needs scheduling bool.
|
||||
m_state.needs_scheduling.store(false, std::memory_order_seq_cst);
|
||||
|
||||
// Load the appropriate thread pointers for scheduling.
|
||||
KThread* const cur_thread{GetCurrentThreadPointer(kernel)};
|
||||
KThread* highest_priority_thread{m_state.highest_priority_thread};
|
||||
|
||||
// Check whether there are runnable interrupt tasks.
|
||||
if (m_state.interrupt_task_runnable) {
|
||||
// The interrupt task is runnable.
|
||||
// We want to switch to the interrupt task/idle thread.
|
||||
highest_priority_thread = nullptr;
|
||||
}
|
||||
|
||||
// If there aren't, we want to check if the highest priority thread is the same as the current
|
||||
// thread.
|
||||
if (highest_priority_thread == cur_thread) {
|
||||
// If they're the same, then we can just return.
|
||||
return;
|
||||
}
|
||||
|
||||
// The highest priority thread is not the same as the current thread.
|
||||
// Jump to the switcher and continue executing from there.
|
||||
m_switch_cur_thread = cur_thread;
|
||||
m_switch_highest_priority_thread = highest_priority_thread;
|
||||
m_switch_from_schedule = true;
|
||||
Common::Fiber::YieldTo(cur_thread->host_context, *m_switch_fiber);
|
||||
|
||||
// Returning from ScheduleImpl occurs after this thread has been scheduled again.
|
||||
}
|
||||
|
||||
void KScheduler::ScheduleImplFiber() {
|
||||
KThread* const cur_thread{m_switch_cur_thread};
|
||||
KThread* highest_priority_thread{m_switch_highest_priority_thread};
|
||||
|
||||
// If we're not coming from scheduling (i.e., we came from SC preemption),
|
||||
// we should restart the scheduling loop directly. Not accurate to HOS.
|
||||
if (!m_switch_from_schedule) {
|
||||
goto retry;
|
||||
}
|
||||
|
||||
// Mark that we are not coming from scheduling anymore.
|
||||
m_switch_from_schedule = false;
|
||||
|
||||
// Save the original thread context.
|
||||
Unload(cur_thread);
|
||||
|
||||
// The current thread's context has been entirely taken care of.
|
||||
// Now we want to loop until we successfully switch the thread context.
|
||||
while (true) {
|
||||
// We're starting to try to do the context switch.
|
||||
// Check if the highest priority thread is null.
|
||||
if (!highest_priority_thread) {
|
||||
// The next thread is nullptr!
|
||||
|
||||
// Switch to the idle thread. Note: HOS treats idling as a special case for
|
||||
// performance. This is not *required* for yuzu's purposes, and for singlecore
|
||||
// compatibility, we can just move the logic that would go here into the execution
|
||||
// of the idle thread. If we ever remove singlecore, we should implement this
|
||||
// accurately to HOS.
|
||||
highest_priority_thread = m_idle_thread;
|
||||
}
|
||||
|
||||
// We want to try to lock the highest priority thread's context.
|
||||
// Try to take it.
|
||||
while (!highest_priority_thread->context_guard.try_lock()) {
|
||||
// The highest priority thread's context is already locked.
|
||||
// Check if we need scheduling. If we don't, we can retry directly.
|
||||
if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) {
|
||||
// If we do, another core is interfering, and we must start again.
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
|
||||
// It's time to switch the thread.
|
||||
// Switch to the highest priority thread.
|
||||
SwitchThread(highest_priority_thread);
|
||||
|
||||
// Check if we need scheduling. If we do, then we can't complete the switch and should
|
||||
// retry.
|
||||
if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) {
|
||||
// Our switch failed.
|
||||
// We should unlock the thread context, and then retry.
|
||||
highest_priority_thread->context_guard.unlock();
|
||||
goto retry;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
retry:
|
||||
|
||||
// We failed to successfully do the context switch, and need to retry.
|
||||
// Clear needs_scheduling.
|
||||
m_state.needs_scheduling.store(false, std::memory_order_seq_cst);
|
||||
|
||||
// Refresh the highest priority thread.
|
||||
highest_priority_thread = m_state.highest_priority_thread;
|
||||
}
|
||||
|
||||
// Reload the guest thread context.
|
||||
Reload(highest_priority_thread);
|
||||
|
||||
// Reload the host thread.
|
||||
Common::Fiber::YieldTo(m_switch_fiber, *highest_priority_thread->host_context);
|
||||
}
|
||||
|
||||
void KScheduler::Unload(KThread* thread) {
|
||||
auto& cpu_core = kernel.System().ArmInterface(m_core_id);
|
||||
cpu_core.SaveContext(thread->GetContext32());
|
||||
cpu_core.SaveContext(thread->GetContext64());
|
||||
// Save the TPIDR_EL0 system register in case it was modified.
|
||||
thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
|
||||
cpu_core.ClearExclusiveState();
|
||||
|
||||
// Check if the thread is terminated by checking the DPC flags.
|
||||
if ((thread->GetStackParameters().dpc_flags & static_cast<u32>(DpcFlag::Terminated)) == 0) {
|
||||
// The thread isn't terminated, so we want to unlock it.
|
||||
thread->context_guard.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::Reload(KThread* thread) {
|
||||
auto& cpu_core = kernel.System().ArmInterface(m_core_id);
|
||||
cpu_core.LoadContext(thread->GetContext32());
|
||||
cpu_core.LoadContext(thread->GetContext64());
|
||||
cpu_core.SetTlsAddress(thread->GetTLSAddress());
|
||||
cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
|
||||
cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints());
|
||||
cpu_core.ClearExclusiveState();
|
||||
}
|
||||
|
||||
void KScheduler::ClearPreviousThread(KernelCore& kernel, KThread* thread) {
|
||||
ASSERT(IsSchedulerLockedByCurrentThread(kernel));
|
||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; ++i) {
|
||||
// Get an atomic reference to the core scheduler's previous thread.
|
||||
auto& prev_thread{kernel.Scheduler(i).m_state.prev_thread};
|
||||
std::atomic_ref<KThread*> prev_thread(kernel.Scheduler(static_cast<s32>(i)).prev_thread);
|
||||
static_assert(std::atomic_ref<KThread*>::is_always_lock_free);
|
||||
|
||||
// Atomically clear the previous thread if it's our target.
|
||||
KThread* compare = thread;
|
||||
prev_thread.compare_exchange_strong(compare, nullptr, std::memory_order_seq_cst);
|
||||
prev_thread.compare_exchange_strong(compare, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state) {
|
||||
ASSERT(IsSchedulerLockedByCurrentThread(kernel));
|
||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||
|
||||
// Check if the state has changed, because if it hasn't there's nothing to do.
|
||||
const ThreadState cur_state = thread->GetRawState();
|
||||
const auto cur_state = thread->GetRawState();
|
||||
if (cur_state == old_state) {
|
||||
return;
|
||||
}
|
||||
@@ -540,12 +237,12 @@ void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, Threa
|
||||
}
|
||||
|
||||
void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority) {
|
||||
ASSERT(IsSchedulerLockedByCurrentThread(kernel));
|
||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||
|
||||
// If the thread is runnable, we want to change its priority in the queue.
|
||||
if (thread->GetRawState() == ThreadState::Runnable) {
|
||||
GetPriorityQueue(kernel).ChangePriority(old_priority,
|
||||
thread == GetCurrentThreadPointer(kernel), thread);
|
||||
thread == kernel.GetCurrentEmuThread(), thread);
|
||||
IncrementScheduledCount(thread);
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
}
|
||||
@@ -553,7 +250,7 @@ void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s3
|
||||
|
||||
void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread,
|
||||
const KAffinityMask& old_affinity, s32 old_core) {
|
||||
ASSERT(IsSchedulerLockedByCurrentThread(kernel));
|
||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||
|
||||
// If the thread is runnable, we want to change its affinity in the queue.
|
||||
if (thread->GetRawState() == ThreadState::Runnable) {
|
||||
@@ -563,14 +260,15 @@ void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 priority) {
|
||||
ASSERT(IsSchedulerLockedByCurrentThread(kernel));
|
||||
void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
|
||||
ASSERT(system.GlobalSchedulerContext().IsLocked());
|
||||
|
||||
// Get a reference to the priority queue.
|
||||
auto& kernel = system.Kernel();
|
||||
auto& priority_queue = GetPriorityQueue(kernel);
|
||||
|
||||
// Rotate the front of the queue to the end.
|
||||
KThread* top_thread = priority_queue.GetScheduledFront(core_id, priority);
|
||||
KThread* top_thread = priority_queue.GetScheduledFront(cpu_core_id, priority);
|
||||
KThread* next_thread = nullptr;
|
||||
if (top_thread != nullptr) {
|
||||
next_thread = priority_queue.MoveToScheduledBack(top_thread);
|
||||
@@ -582,7 +280,7 @@ void KScheduler::RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 prior
|
||||
|
||||
// While we have a suggested thread, try to migrate it!
|
||||
{
|
||||
KThread* suggested = priority_queue.GetSuggestedFront(core_id, priority);
|
||||
KThread* suggested = priority_queue.GetSuggestedFront(cpu_core_id, priority);
|
||||
while (suggested != nullptr) {
|
||||
// Check if the suggested thread is the top thread on its core.
|
||||
const s32 suggested_core = suggested->GetActiveCore();
|
||||
@@ -603,7 +301,7 @@ void KScheduler::RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 prior
|
||||
// to the front of the queue.
|
||||
if (top_on_suggested_core == nullptr ||
|
||||
top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) {
|
||||
suggested->SetActiveCore(core_id);
|
||||
suggested->SetActiveCore(cpu_core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested, true);
|
||||
IncrementScheduledCount(suggested);
|
||||
break;
|
||||
@@ -611,21 +309,22 @@ void KScheduler::RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 prior
|
||||
}
|
||||
|
||||
// Get the next suggestion.
|
||||
suggested = priority_queue.GetSamePriorityNext(core_id, suggested);
|
||||
suggested = priority_queue.GetSamePriorityNext(cpu_core_id, suggested);
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we might have migrated a thread with the same priority, check if we can do better.
|
||||
|
||||
{
|
||||
KThread* best_thread = priority_queue.GetScheduledFront(core_id);
|
||||
KThread* best_thread = priority_queue.GetScheduledFront(cpu_core_id);
|
||||
if (best_thread == GetCurrentThreadPointer(kernel)) {
|
||||
best_thread = priority_queue.GetScheduledNext(core_id, best_thread);
|
||||
best_thread = priority_queue.GetScheduledNext(cpu_core_id, best_thread);
|
||||
}
|
||||
|
||||
// If the best thread we can choose has a priority the same or worse than ours, try to
|
||||
// migrate a higher priority thread.
|
||||
if (best_thread != nullptr && best_thread->GetPriority() >= priority) {
|
||||
KThread* suggested = priority_queue.GetSuggestedFront(core_id);
|
||||
KThread* suggested = priority_queue.GetSuggestedFront(cpu_core_id);
|
||||
while (suggested != nullptr) {
|
||||
// If the suggestion's priority is the same as ours, don't bother.
|
||||
if (suggested->GetPriority() >= best_thread->GetPriority()) {
|
||||
@@ -644,7 +343,7 @@ void KScheduler::RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 prior
|
||||
if (top_on_suggested_core == nullptr ||
|
||||
top_on_suggested_core->GetPriority() >=
|
||||
HighestCoreMigrationAllowedPriority) {
|
||||
suggested->SetActiveCore(core_id);
|
||||
suggested->SetActiveCore(cpu_core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested, true);
|
||||
IncrementScheduledCount(suggested);
|
||||
break;
|
||||
@@ -652,7 +351,7 @@ void KScheduler::RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 prior
|
||||
}
|
||||
|
||||
// Get the next suggestion.
|
||||
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
|
||||
suggested = priority_queue.GetSuggestedNext(cpu_core_id, suggested);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -661,6 +360,64 @@ void KScheduler::RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 prior
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
}
|
||||
|
||||
bool KScheduler::CanSchedule(KernelCore& kernel) {
|
||||
return kernel.GetCurrentEmuThread()->GetDisableDispatchCount() <= 1;
|
||||
}
|
||||
|
||||
bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) {
|
||||
return kernel.GlobalSchedulerContext().scheduler_update_needed.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void KScheduler::SetSchedulerUpdateNeeded(KernelCore& kernel) {
|
||||
kernel.GlobalSchedulerContext().scheduler_update_needed.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) {
|
||||
kernel.GlobalSchedulerContext().scheduler_update_needed.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
void KScheduler::DisableScheduling(KernelCore& kernel) {
|
||||
// If we are shutting down the kernel, none of this is relevant anymore.
|
||||
if (kernel.IsShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(GetCurrentThreadPointer(kernel)->GetDisableDispatchCount() >= 0);
|
||||
GetCurrentThreadPointer(kernel)->DisableDispatch();
|
||||
}
|
||||
|
||||
void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) {
|
||||
// If we are shutting down the kernel, none of this is relevant anymore.
|
||||
if (kernel.IsShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* current_thread = GetCurrentThreadPointer(kernel);
|
||||
|
||||
ASSERT(current_thread->GetDisableDispatchCount() >= 1);
|
||||
|
||||
if (current_thread->GetDisableDispatchCount() > 1) {
|
||||
current_thread->EnableDispatch();
|
||||
} else {
|
||||
RescheduleCores(kernel, cores_needing_scheduling);
|
||||
}
|
||||
|
||||
// Special case to ensure dummy threads that are waiting block.
|
||||
current_thread->IfDummyThreadTryWait();
|
||||
}
|
||||
|
||||
u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
|
||||
if (IsSchedulerUpdateNeeded(kernel)) {
|
||||
return UpdateHighestPriorityThreadsImpl(kernel);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
KSchedulerPriorityQueue& KScheduler::GetPriorityQueue(KernelCore& kernel) {
|
||||
return kernel.GlobalSchedulerContext().priority_queue;
|
||||
}
|
||||
|
||||
void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) {
|
||||
// Validate preconditions.
|
||||
ASSERT(CanSchedule(kernel));
|
||||
@@ -680,7 +437,7 @@ void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) {
|
||||
|
||||
// Perform the yield.
|
||||
{
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
|
||||
const auto cur_state = cur_thread.GetRawState();
|
||||
if (cur_state == ThreadState::Runnable) {
|
||||
@@ -719,7 +476,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore& kernel) {
|
||||
|
||||
// Perform the yield.
|
||||
{
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
|
||||
const auto cur_state = cur_thread.GetRawState();
|
||||
if (cur_state == ThreadState::Runnable) {
|
||||
@@ -739,7 +496,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore& kernel) {
|
||||
|
||||
if (KThread* running_on_suggested_core =
|
||||
(suggested_core >= 0)
|
||||
? kernel.Scheduler(suggested_core).m_state.highest_priority_thread
|
||||
? kernel.Scheduler(suggested_core).state.highest_priority_thread
|
||||
: nullptr;
|
||||
running_on_suggested_core != suggested) {
|
||||
// If the current thread's priority is higher than our suggestion's we prefer
|
||||
@@ -807,7 +564,7 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) {
|
||||
|
||||
// Perform the yield.
|
||||
{
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
|
||||
const auto cur_state = cur_thread.GetRawState();
|
||||
if (cur_state == ThreadState::Runnable) {
|
||||
@@ -864,19 +621,223 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) {
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::RescheduleOtherCores(u64 cores_needing_scheduling) {
|
||||
if (const u64 core_mask = cores_needing_scheduling & ~(1ULL << m_core_id); core_mask != 0) {
|
||||
RescheduleCores(kernel, core_mask);
|
||||
KScheduler::KScheduler(Core::System& system_, s32 core_id_) : system{system_}, core_id{core_id_} {
|
||||
switch_fiber = std::make_shared<Common::Fiber>([this] { SwitchToCurrent(); });
|
||||
state.needs_scheduling.store(true);
|
||||
state.interrupt_task_thread_runnable = false;
|
||||
state.should_count_idle = false;
|
||||
state.idle_count = 0;
|
||||
state.idle_thread_stack = nullptr;
|
||||
state.highest_priority_thread = nullptr;
|
||||
}
|
||||
|
||||
void KScheduler::Finalize() {
|
||||
if (idle_thread) {
|
||||
idle_thread->Close();
|
||||
idle_thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::RescheduleCores(KernelCore& kernel, u64 core_mask) {
|
||||
// Send IPI
|
||||
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
||||
if (core_mask & (1ULL << i)) {
|
||||
kernel.PhysicalCore(i).Interrupt();
|
||||
}
|
||||
KScheduler::~KScheduler() {
|
||||
ASSERT(!idle_thread);
|
||||
}
|
||||
|
||||
KThread* KScheduler::GetSchedulerCurrentThread() const {
|
||||
if (auto result = current_thread.load(); result) {
|
||||
return result;
|
||||
}
|
||||
return idle_thread;
|
||||
}
|
||||
|
||||
u64 KScheduler::GetLastContextSwitchTicks() const {
|
||||
return last_context_switch_time;
|
||||
}
|
||||
|
||||
void KScheduler::RescheduleCurrentCore() {
|
||||
ASSERT(GetCurrentThread(system.Kernel()).GetDisableDispatchCount() == 1);
|
||||
|
||||
auto& phys_core = system.Kernel().PhysicalCore(core_id);
|
||||
if (phys_core.IsInterrupted()) {
|
||||
phys_core.ClearInterrupt();
|
||||
}
|
||||
|
||||
guard.Lock();
|
||||
if (state.needs_scheduling.load()) {
|
||||
Schedule();
|
||||
} else {
|
||||
GetCurrentThread(system.Kernel()).EnableDispatch();
|
||||
guard.Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::OnThreadStart() {
|
||||
SwitchContextStep2();
|
||||
}
|
||||
|
||||
void KScheduler::Unload(KThread* thread) {
|
||||
ASSERT(thread);
|
||||
|
||||
LOG_TRACE(Kernel, "core {}, unload thread {}", core_id, thread ? thread->GetName() : "nullptr");
|
||||
|
||||
if (thread->IsCallingSvc()) {
|
||||
thread->ClearIsCallingSvc();
|
||||
}
|
||||
|
||||
auto& physical_core = system.Kernel().PhysicalCore(core_id);
|
||||
if (!physical_core.IsInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Core::ARM_Interface& cpu_core = physical_core.ArmInterface();
|
||||
cpu_core.SaveContext(thread->GetContext32());
|
||||
cpu_core.SaveContext(thread->GetContext64());
|
||||
// Save the TPIDR_EL0 system register in case it was modified.
|
||||
thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
|
||||
cpu_core.ClearExclusiveState();
|
||||
|
||||
if (!thread->IsTerminationRequested() && thread->GetActiveCore() == core_id) {
|
||||
prev_thread = thread;
|
||||
} else {
|
||||
prev_thread = nullptr;
|
||||
}
|
||||
|
||||
thread->context_guard.unlock();
|
||||
}
|
||||
|
||||
void KScheduler::Reload(KThread* thread) {
|
||||
LOG_TRACE(Kernel, "core {}, reload thread {}", core_id, thread->GetName());
|
||||
|
||||
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
|
||||
cpu_core.LoadContext(thread->GetContext32());
|
||||
cpu_core.LoadContext(thread->GetContext64());
|
||||
cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints());
|
||||
cpu_core.SetTlsAddress(thread->GetTLSAddress());
|
||||
cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
|
||||
cpu_core.ClearExclusiveState();
|
||||
}
|
||||
|
||||
void KScheduler::SwitchContextStep2() {
|
||||
// Load context of new thread
|
||||
Reload(GetCurrentThreadPointer(system.Kernel()));
|
||||
|
||||
RescheduleCurrentCore();
|
||||
}
|
||||
|
||||
void KScheduler::Schedule() {
|
||||
ASSERT(GetCurrentThread(system.Kernel()).GetDisableDispatchCount() == 1);
|
||||
this->ScheduleImpl();
|
||||
}
|
||||
|
||||
void KScheduler::ScheduleImpl() {
|
||||
KThread* previous_thread = GetCurrentThreadPointer(system.Kernel());
|
||||
KThread* next_thread = state.highest_priority_thread;
|
||||
|
||||
state.needs_scheduling.store(false);
|
||||
|
||||
// We never want to schedule a null thread, so use the idle thread if we don't have a next.
|
||||
if (next_thread == nullptr) {
|
||||
next_thread = idle_thread;
|
||||
}
|
||||
|
||||
if (next_thread->GetCurrentCore() != core_id) {
|
||||
next_thread->SetCurrentCore(core_id);
|
||||
}
|
||||
|
||||
// We never want to schedule a dummy thread, as these are only used by host threads for locking.
|
||||
if (next_thread->GetThreadType() == ThreadType::Dummy) {
|
||||
ASSERT_MSG(false, "Dummy threads should never be scheduled!");
|
||||
next_thread = idle_thread;
|
||||
}
|
||||
|
||||
// If we're not actually switching thread, there's nothing to do.
|
||||
if (next_thread == current_thread.load()) {
|
||||
previous_thread->EnableDispatch();
|
||||
guard.Unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the CPU time tracking variables.
|
||||
KProcess* const previous_process = system.Kernel().CurrentProcess();
|
||||
UpdateLastContextSwitchTime(previous_thread, previous_process);
|
||||
|
||||
// Save context for previous thread
|
||||
Unload(previous_thread);
|
||||
|
||||
std::shared_ptr<Common::Fiber>* old_context;
|
||||
old_context = &previous_thread->GetHostContext();
|
||||
|
||||
// Set the new thread.
|
||||
SetCurrentThread(system.Kernel(), next_thread);
|
||||
current_thread.store(next_thread);
|
||||
|
||||
guard.Unlock();
|
||||
|
||||
Common::Fiber::YieldTo(*old_context, *switch_fiber);
|
||||
/// When a thread wakes up, the scheduler may have changed to other in another core.
|
||||
auto& next_scheduler = *system.Kernel().CurrentScheduler();
|
||||
next_scheduler.SwitchContextStep2();
|
||||
}
|
||||
|
||||
void KScheduler::SwitchToCurrent() {
|
||||
while (true) {
|
||||
{
|
||||
KScopedSpinLock lk{guard};
|
||||
current_thread.store(state.highest_priority_thread);
|
||||
state.needs_scheduling.store(false);
|
||||
}
|
||||
const auto is_switch_pending = [this] {
|
||||
KScopedSpinLock lk{guard};
|
||||
return state.needs_scheduling.load();
|
||||
};
|
||||
do {
|
||||
auto next_thread = current_thread.load();
|
||||
if (next_thread != nullptr) {
|
||||
const auto locked = next_thread->context_guard.try_lock();
|
||||
if (state.needs_scheduling.load()) {
|
||||
next_thread->context_guard.unlock();
|
||||
break;
|
||||
}
|
||||
if (next_thread->GetActiveCore() != core_id) {
|
||||
next_thread->context_guard.unlock();
|
||||
break;
|
||||
}
|
||||
if (!locked) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
auto thread = next_thread ? next_thread : idle_thread;
|
||||
SetCurrentThread(system.Kernel(), thread);
|
||||
Common::Fiber::YieldTo(switch_fiber, *thread->GetHostContext());
|
||||
} while (!is_switch_pending());
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::UpdateLastContextSwitchTime(KThread* thread, KProcess* process) {
|
||||
const u64 prev_switch_ticks = last_context_switch_time;
|
||||
const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks();
|
||||
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
|
||||
|
||||
if (thread != nullptr) {
|
||||
thread->AddCpuTime(core_id, update_ticks);
|
||||
}
|
||||
|
||||
if (process != nullptr) {
|
||||
process->UpdateCPUTimeTicks(update_ticks);
|
||||
}
|
||||
|
||||
last_context_switch_time = most_recent_switch_ticks;
|
||||
}
|
||||
|
||||
void KScheduler::Initialize() {
|
||||
idle_thread = KThread::Create(system.Kernel());
|
||||
ASSERT(KThread::InitializeIdleThread(system, idle_thread, core_id).IsSuccess());
|
||||
idle_thread->SetName(fmt::format("IdleThread:{}", core_id));
|
||||
idle_thread->EnableDispatch();
|
||||
}
|
||||
|
||||
KScopedSchedulerLock::KScopedSchedulerLock(KernelCore& kernel)
|
||||
: KScopedLock(kernel.GlobalSchedulerContext().SchedulerLock()) {}
|
||||
|
||||
KScopedSchedulerLock::~KScopedSchedulerLock() = default;
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "core/hle/kernel/k_scheduler_lock.h"
|
||||
#include "core/hle/kernel/k_scoped_lock.h"
|
||||
#include "core/hle/kernel/k_spin_lock.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
|
||||
namespace Common {
|
||||
class Fiber;
|
||||
@@ -24,150 +23,184 @@ class System;
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
class KInterruptTaskManager;
|
||||
class KProcess;
|
||||
class SchedulerLock;
|
||||
class KThread;
|
||||
class KScopedDisableDispatch;
|
||||
class KScopedSchedulerLock;
|
||||
class KScopedSchedulerLockAndSleep;
|
||||
|
||||
class KScheduler final {
|
||||
public:
|
||||
YUZU_NON_COPYABLE(KScheduler);
|
||||
YUZU_NON_MOVEABLE(KScheduler);
|
||||
|
||||
using LockType = KAbstractSchedulerLock<KScheduler>;
|
||||
|
||||
explicit KScheduler(KernelCore& kernel);
|
||||
explicit KScheduler(Core::System& system_, s32 core_id_);
|
||||
~KScheduler();
|
||||
|
||||
void Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id);
|
||||
void Activate();
|
||||
void OnThreadStart();
|
||||
void Finalize();
|
||||
|
||||
/// Reschedules to the next available thread (call after current thread is suspended)
|
||||
void RescheduleCurrentCore();
|
||||
|
||||
/// Reschedules cores pending reschedule, to be called on EnableScheduling.
|
||||
static void RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule);
|
||||
|
||||
/// The next two are for SingleCore Only.
|
||||
/// Unload current thread before preempting core.
|
||||
void Unload(KThread* thread);
|
||||
|
||||
/// Reload current thread after core preemption.
|
||||
void Reload(KThread* thread);
|
||||
|
||||
void SetInterruptTaskRunnable();
|
||||
void RequestScheduleOnInterrupt();
|
||||
void PreemptSingleCore();
|
||||
/// Gets the current running thread
|
||||
[[nodiscard]] KThread* GetSchedulerCurrentThread() const;
|
||||
|
||||
u64 GetIdleCount() {
|
||||
return m_state.idle_count;
|
||||
/// Gets the idle thread
|
||||
[[nodiscard]] KThread* GetIdleThread() const {
|
||||
return idle_thread;
|
||||
}
|
||||
|
||||
KThread* GetIdleThread() const {
|
||||
return m_idle_thread;
|
||||
/// Returns true if the scheduler is idle
|
||||
[[nodiscard]] bool IsIdle() const {
|
||||
return GetSchedulerCurrentThread() == idle_thread;
|
||||
}
|
||||
|
||||
bool IsIdle() const {
|
||||
return m_current_thread.load() == m_idle_thread;
|
||||
/// Gets the timestamp for the last context switch in ticks.
|
||||
[[nodiscard]] u64 GetLastContextSwitchTicks() const;
|
||||
|
||||
[[nodiscard]] bool ContextSwitchPending() const {
|
||||
return state.needs_scheduling.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
KThread* GetPreviousThread() const {
|
||||
return m_state.prev_thread;
|
||||
void Initialize();
|
||||
|
||||
void OnThreadStart();
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Common::Fiber>& ControlContext() {
|
||||
return switch_fiber;
|
||||
}
|
||||
|
||||
KThread* GetSchedulerCurrentThread() const {
|
||||
return m_current_thread.load();
|
||||
[[nodiscard]] const std::shared_ptr<Common::Fiber>& ControlContext() const {
|
||||
return switch_fiber;
|
||||
}
|
||||
|
||||
s64 GetLastContextSwitchTime() const {
|
||||
return m_last_context_switch_time;
|
||||
}
|
||||
[[nodiscard]] u64 UpdateHighestPriorityThread(KThread* highest_thread);
|
||||
|
||||
// Static public API.
|
||||
static bool CanSchedule(KernelCore& kernel) {
|
||||
return GetCurrentThread(kernel).GetDisableDispatchCount() == 0;
|
||||
}
|
||||
static bool IsSchedulerLockedByCurrentThread(KernelCore& kernel) {
|
||||
return kernel.GlobalSchedulerContext().scheduler_lock.IsLockedByCurrentThread();
|
||||
}
|
||||
/**
|
||||
* Takes a thread and moves it to the back of the it's priority list.
|
||||
*
|
||||
* @note This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
static void YieldWithoutCoreMigration(KernelCore& kernel);
|
||||
|
||||
static bool IsSchedulerUpdateNeeded(KernelCore& kernel) {
|
||||
return kernel.GlobalSchedulerContext().scheduler_update_needed;
|
||||
}
|
||||
static void SetSchedulerUpdateNeeded(KernelCore& kernel) {
|
||||
kernel.GlobalSchedulerContext().scheduler_update_needed = true;
|
||||
}
|
||||
static void ClearSchedulerUpdateNeeded(KernelCore& kernel) {
|
||||
kernel.GlobalSchedulerContext().scheduler_update_needed = false;
|
||||
}
|
||||
/**
|
||||
* Takes a thread and moves it to the back of the it's priority list.
|
||||
* Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
|
||||
* a better priority than the next thread in the core.
|
||||
*
|
||||
* @note This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
static void YieldWithCoreMigration(KernelCore& kernel);
|
||||
|
||||
static void DisableScheduling(KernelCore& kernel);
|
||||
static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling);
|
||||
|
||||
static u64 UpdateHighestPriorityThreads(KernelCore& kernel);
|
||||
/**
|
||||
* Takes a thread and moves it out of the scheduling queue.
|
||||
* and into the suggested queue. If no thread can be scheduled afterwards in that core,
|
||||
* a suggested thread is obtained instead.
|
||||
*
|
||||
* @note This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
static void YieldToAnyThread(KernelCore& kernel);
|
||||
|
||||
static void ClearPreviousThread(KernelCore& kernel, KThread* thread);
|
||||
|
||||
/// Notify the scheduler a thread's status has changed.
|
||||
static void OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state);
|
||||
|
||||
/// Notify the scheduler a thread's priority has changed.
|
||||
static void OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority);
|
||||
|
||||
/// Notify the scheduler a thread's core and/or affinity mask has changed.
|
||||
static void OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread,
|
||||
const KAffinityMask& old_affinity, s32 old_core);
|
||||
|
||||
static void RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 priority);
|
||||
static void RescheduleCores(KernelCore& kernel, u64 cores_needing_scheduling);
|
||||
|
||||
static void YieldWithoutCoreMigration(KernelCore& kernel);
|
||||
static void YieldWithCoreMigration(KernelCore& kernel);
|
||||
static void YieldToAnyThread(KernelCore& kernel);
|
||||
static bool CanSchedule(KernelCore& kernel);
|
||||
static bool IsSchedulerUpdateNeeded(const KernelCore& kernel);
|
||||
static void SetSchedulerUpdateNeeded(KernelCore& kernel);
|
||||
static void ClearSchedulerUpdateNeeded(KernelCore& kernel);
|
||||
static void DisableScheduling(KernelCore& kernel);
|
||||
static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling);
|
||||
[[nodiscard]] static u64 UpdateHighestPriorityThreads(KernelCore& kernel);
|
||||
|
||||
private:
|
||||
// Static private API.
|
||||
static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel) {
|
||||
return kernel.GlobalSchedulerContext().priority_queue;
|
||||
}
|
||||
static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel);
|
||||
friend class GlobalSchedulerContext;
|
||||
|
||||
static void RescheduleCurrentHLEThread(KernelCore& kernel);
|
||||
/**
|
||||
* Takes care of selecting the new scheduled threads in three steps:
|
||||
*
|
||||
* 1. First a thread is selected from the top of the priority queue. If no thread
|
||||
* is obtained then we move to step two, else we are done.
|
||||
*
|
||||
* 2. Second we try to get a suggested thread that's not assigned to any core or
|
||||
* that is not the top thread in that core.
|
||||
*
|
||||
* 3. Third is no suggested thread is found, we do a second pass and pick a running
|
||||
* thread in another core and swap it with its current thread.
|
||||
*
|
||||
* returns the cores needing scheduling.
|
||||
*/
|
||||
[[nodiscard]] static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel);
|
||||
|
||||
// Instanced private API.
|
||||
void ScheduleImpl();
|
||||
void ScheduleImplFiber();
|
||||
void SwitchThread(KThread* next_thread);
|
||||
[[nodiscard]] static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel);
|
||||
|
||||
void RotateScheduledQueue(s32 cpu_core_id, s32 priority);
|
||||
|
||||
void Schedule();
|
||||
void ScheduleOnInterrupt();
|
||||
|
||||
void RescheduleOtherCores(u64 cores_needing_scheduling);
|
||||
void RescheduleCurrentCore();
|
||||
void RescheduleCurrentCoreImpl();
|
||||
/// Switches the CPU's active thread context to that of the specified thread
|
||||
void ScheduleImpl();
|
||||
|
||||
u64 UpdateHighestPriorityThread(KThread* thread);
|
||||
/// When a thread wakes up, it must run this through it's new scheduler
|
||||
void SwitchContextStep2();
|
||||
|
||||
private:
|
||||
friend class KScopedDisableDispatch;
|
||||
/**
|
||||
* Called on every context switch to update the internal timestamp
|
||||
* This also updates the running time ticks for the given thread and
|
||||
* process using the following difference:
|
||||
*
|
||||
* ticks += most_recent_ticks - last_context_switch_ticks
|
||||
*
|
||||
* The internal tick timestamp for the scheduler is simply the
|
||||
* most recent tick count retrieved. No special arithmetic is
|
||||
* applied to it.
|
||||
*/
|
||||
void UpdateLastContextSwitchTime(KThread* thread, KProcess* process);
|
||||
|
||||
void SwitchToCurrent();
|
||||
|
||||
KThread* prev_thread{};
|
||||
std::atomic<KThread*> current_thread{};
|
||||
|
||||
KThread* idle_thread{};
|
||||
|
||||
std::shared_ptr<Common::Fiber> switch_fiber{};
|
||||
|
||||
struct SchedulingState {
|
||||
std::atomic<bool> needs_scheduling{false};
|
||||
bool interrupt_task_runnable{false};
|
||||
bool should_count_idle{false};
|
||||
u64 idle_count{0};
|
||||
KThread* highest_priority_thread{nullptr};
|
||||
void* idle_thread_stack{nullptr};
|
||||
std::atomic<KThread*> prev_thread{nullptr};
|
||||
KInterruptTaskManager* interrupt_task_manager{nullptr};
|
||||
std::atomic<bool> needs_scheduling{};
|
||||
bool interrupt_task_thread_runnable{};
|
||||
bool should_count_idle{};
|
||||
u64 idle_count{};
|
||||
KThread* highest_priority_thread{};
|
||||
void* idle_thread_stack{};
|
||||
};
|
||||
|
||||
KernelCore& kernel;
|
||||
SchedulingState m_state;
|
||||
bool m_is_active{false};
|
||||
s32 m_core_id{0};
|
||||
s64 m_last_context_switch_time{0};
|
||||
KThread* m_idle_thread{nullptr};
|
||||
std::atomic<KThread*> m_current_thread{nullptr};
|
||||
SchedulingState state;
|
||||
|
||||
std::shared_ptr<Common::Fiber> m_switch_fiber{};
|
||||
KThread* m_switch_cur_thread{};
|
||||
KThread* m_switch_highest_priority_thread{};
|
||||
bool m_switch_from_schedule{};
|
||||
Core::System& system;
|
||||
u64 last_context_switch_time{};
|
||||
const s32 core_id;
|
||||
|
||||
KSpinLock guard{};
|
||||
};
|
||||
|
||||
class KScopedSchedulerLock : public KScopedLock<KScheduler::LockType> {
|
||||
class [[nodiscard]] KScopedSchedulerLock : KScopedLock<GlobalSchedulerContext::LockType> {
|
||||
public:
|
||||
explicit KScopedSchedulerLock(KernelCore& kernel)
|
||||
: KScopedLock(kernel.GlobalSchedulerContext().scheduler_lock) {}
|
||||
~KScopedSchedulerLock() = default;
|
||||
explicit KScopedSchedulerLock(KernelCore& kernel);
|
||||
~KScopedSchedulerLock();
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -5,11 +5,9 @@
|
||||
|
||||
#include <atomic>
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/kernel/k_interrupt_manager.h"
|
||||
#include "core/hle/kernel/k_spin_lock.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
|
||||
@@ -258,18 +258,7 @@ Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_
|
||||
}
|
||||
|
||||
Result KThread::InitializeDummyThread(KThread* thread) {
|
||||
// Initialize the thread.
|
||||
R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy));
|
||||
|
||||
// Initialize emulation parameters.
|
||||
thread->stack_parameters.disable_count = 0;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result KThread::InitializeMainThread(Core::System& system, KThread* thread, s32 virt_core) {
|
||||
return InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main,
|
||||
system.GetCpuManager().GetGuestActivateFunc());
|
||||
return thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy);
|
||||
}
|
||||
|
||||
Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) {
|
||||
@@ -288,7 +277,7 @@ Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThr
|
||||
KProcess* owner) {
|
||||
system.Kernel().GlobalSchedulerContext().AddThread(thread);
|
||||
return InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner,
|
||||
ThreadType::User, system.GetCpuManager().GetGuestThreadFunc());
|
||||
ThreadType::User, system.GetCpuManager().GetGuestThreadStartFunc());
|
||||
}
|
||||
|
||||
void KThread::PostDestroy(uintptr_t arg) {
|
||||
@@ -1069,8 +1058,6 @@ void KThread::Exit() {
|
||||
// Register the thread as a work task.
|
||||
KWorkerTaskManager::AddTask(kernel, KWorkerTaskManager::WorkerType::Exit, this);
|
||||
}
|
||||
|
||||
UNREACHABLE_MSG("KThread::Exit() would return");
|
||||
}
|
||||
|
||||
Result KThread::Sleep(s64 timeout) {
|
||||
@@ -1106,8 +1093,6 @@ void KThread::IfDummyThreadTryWait() {
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(!kernel.IsPhantomModeForSingleCore());
|
||||
|
||||
// Block until we are no longer waiting.
|
||||
std::unique_lock lk(dummy_wait_lock);
|
||||
dummy_wait_cv.wait(
|
||||
@@ -1212,13 +1197,16 @@ KScopedDisableDispatch::~KScopedDisableDispatch() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetCurrentThread(kernel).GetDisableDispatchCount() <= 1) {
|
||||
auto* scheduler = kernel.CurrentScheduler();
|
||||
// Skip the reschedule if single-core, as dispatch tracking is disabled here.
|
||||
if (!Settings::values.use_multi_core.GetValue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scheduler && !kernel.IsPhantomModeForSingleCore()) {
|
||||
if (GetCurrentThread(kernel).GetDisableDispatchCount() <= 1) {
|
||||
auto scheduler = kernel.CurrentScheduler();
|
||||
|
||||
if (scheduler) {
|
||||
scheduler->RescheduleCurrentCore();
|
||||
} else {
|
||||
KScheduler::RescheduleCurrentHLEThread(kernel);
|
||||
}
|
||||
} else {
|
||||
GetCurrentThread(kernel).EnableDispatch();
|
||||
|
||||
@@ -413,9 +413,6 @@ public:
|
||||
|
||||
[[nodiscard]] static Result InitializeDummyThread(KThread* thread);
|
||||
|
||||
[[nodiscard]] static Result InitializeMainThread(Core::System& system, KThread* thread,
|
||||
s32 virt_core);
|
||||
|
||||
[[nodiscard]] static Result InitializeIdleThread(Core::System& system, KThread* thread,
|
||||
s32 virt_core);
|
||||
|
||||
@@ -483,16 +480,39 @@ public:
|
||||
return per_core_priority_queue_entry[core];
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsKernelThread() const {
|
||||
return GetActiveCore() == 3;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsDispatchTrackingDisabled() const {
|
||||
return is_single_core || IsKernelThread();
|
||||
}
|
||||
|
||||
[[nodiscard]] s32 GetDisableDispatchCount() const {
|
||||
if (IsDispatchTrackingDisabled()) {
|
||||
// TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
|
||||
return 1;
|
||||
}
|
||||
|
||||
return this->GetStackParameters().disable_count;
|
||||
}
|
||||
|
||||
void DisableDispatch() {
|
||||
if (IsDispatchTrackingDisabled()) {
|
||||
// TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0);
|
||||
this->GetStackParameters().disable_count++;
|
||||
}
|
||||
|
||||
void EnableDispatch() {
|
||||
if (IsDispatchTrackingDisabled()) {
|
||||
// TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0);
|
||||
this->GetStackParameters().disable_count--;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ struct KernelCore::Impl {
|
||||
|
||||
is_phantom_mode_for_singlecore = false;
|
||||
|
||||
InitializePhysicalCores();
|
||||
|
||||
// Derive the initial memory layout from the emulated board
|
||||
Init::InitializeSlabResourceCounts(kernel);
|
||||
DeriveInitialMemoryLayout();
|
||||
@@ -73,9 +75,9 @@ struct KernelCore::Impl {
|
||||
InitializeSystemResourceLimit(kernel, system.CoreTiming());
|
||||
InitializeMemoryLayout();
|
||||
Init::InitializeKPageBufferSlabHeap(system);
|
||||
InitializeSchedulers();
|
||||
InitializeShutdownThreads();
|
||||
InitializePreemption(kernel);
|
||||
InitializePhysicalCores();
|
||||
|
||||
RegisterHostThread();
|
||||
}
|
||||
@@ -134,6 +136,7 @@ struct KernelCore::Impl {
|
||||
shutdown_threads[core_id] = nullptr;
|
||||
}
|
||||
|
||||
schedulers[core_id]->Finalize();
|
||||
schedulers[core_id].reset();
|
||||
}
|
||||
|
||||
@@ -196,21 +199,14 @@ struct KernelCore::Impl {
|
||||
exclusive_monitor =
|
||||
Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES);
|
||||
for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
||||
const s32 core{static_cast<s32>(i)};
|
||||
|
||||
schedulers[i] = std::make_unique<Kernel::KScheduler>(system.Kernel());
|
||||
schedulers[i] = std::make_unique<Kernel::KScheduler>(system, i);
|
||||
cores.emplace_back(i, system, *schedulers[i], interrupts);
|
||||
}
|
||||
}
|
||||
|
||||
auto* main_thread{Kernel::KThread::Create(system.Kernel())};
|
||||
main_thread->SetName(fmt::format("MainThread:{}", core));
|
||||
main_thread->SetCurrentCore(core);
|
||||
ASSERT(Kernel::KThread::InitializeMainThread(system, main_thread, core).IsSuccess());
|
||||
|
||||
auto* idle_thread{Kernel::KThread::Create(system.Kernel())};
|
||||
idle_thread->SetCurrentCore(core);
|
||||
ASSERT(Kernel::KThread::InitializeIdleThread(system, idle_thread, core).IsSuccess());
|
||||
|
||||
schedulers[i]->Initialize(main_thread, idle_thread, core);
|
||||
void InitializeSchedulers() {
|
||||
for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
||||
cores[i].Scheduler().Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1113,11 +1109,10 @@ void KernelCore::Suspend(bool suspended) {
|
||||
}
|
||||
|
||||
void KernelCore::ShutdownCores() {
|
||||
KScopedSchedulerLock lk{*this};
|
||||
|
||||
for (auto* thread : impl->shutdown_threads) {
|
||||
void(thread->Run());
|
||||
}
|
||||
InterruptAllPhysicalCores();
|
||||
}
|
||||
|
||||
bool KernelCore::IsMulticore() const {
|
||||
|
||||
@@ -43,7 +43,6 @@ void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) {
|
||||
|
||||
void PhysicalCore::Run() {
|
||||
arm_interface->Run();
|
||||
arm_interface->ClearExclusiveState();
|
||||
}
|
||||
|
||||
void PhysicalCore::Idle() {
|
||||
|
||||
@@ -887,7 +887,7 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
|
||||
const auto* const current_thread = GetCurrentThreadPointer(system.Kernel());
|
||||
const bool same_thread = current_thread == thread.GetPointerUnsafe();
|
||||
|
||||
const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTime();
|
||||
const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks();
|
||||
u64 out_ticks = 0;
|
||||
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
|
||||
const u64 thread_ticks = current_thread->GetCpuTime();
|
||||
@@ -3026,6 +3026,11 @@ void Call(Core::System& system, u32 immediate) {
|
||||
}
|
||||
|
||||
kernel.ExitSVCProfile();
|
||||
|
||||
if (!thread->IsCallingSvc()) {
|
||||
auto* host_context = thread->GetHostContext().get();
|
||||
host_context->Rewind();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Kernel::Svc
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace {
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/network_interface.h"
|
||||
#include "core/network/network.h"
|
||||
#include "core/network/network_interface.h"
|
||||
|
||||
namespace Service::NIFM {
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/service/sockets/bsd.h"
|
||||
#include "core/hle/service/sockets/sockets_translate.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
#include "core/network/network.h"
|
||||
#include "core/network/sockets.h"
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class System;
|
||||
|
||||
namespace Network {
|
||||
class Socket;
|
||||
} // namespace Network
|
||||
}
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/sockets/sockets.h"
|
||||
#include "core/hle/service/sockets/sockets_translate.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/network/network.h"
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/sockets/sockets.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/network/network.h"
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/network_interface.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
#include "core/network/network.h"
|
||||
#include "core/network/network_interface.h"
|
||||
#include "core/network/sockets.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/internal_network/network_interface.h"
|
||||
#include "core/network/network_interface.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <iphlpapi.h>
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
@@ -13,7 +12,7 @@
|
||||
#endif
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/network/network.h"
|
||||
|
||||
// TODO: C++20 Replace std::vector usages with std::span
|
||||
|
||||
@@ -85,7 +85,7 @@ enum RegisterFlags : u8 {
|
||||
struct Version {};
|
||||
/**
|
||||
* Requests the server to send information about what controllers are plugged into the ports
|
||||
* In yuzu's case, we only have one controller, so for simplicity's sake, we can just send a
|
||||
* In citra's case, we only have one controller, so for simplicity's sake, we can just send a
|
||||
* request explicitly for the first controller port and leave it at that. In the future it would be
|
||||
* nice to make this configurable
|
||||
*/
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
add_library(network STATIC
|
||||
network.cpp
|
||||
network.h
|
||||
packet.cpp
|
||||
packet.h
|
||||
room.cpp
|
||||
room.h
|
||||
room_member.cpp
|
||||
room_member.h
|
||||
verify_user.cpp
|
||||
verify_user.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(network)
|
||||
|
||||
target_link_libraries(network PRIVATE common enet Boost::boost)
|
||||
@@ -1,50 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "enet/enet.h"
|
||||
#include "network/network.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
RoomNetwork::RoomNetwork() {
|
||||
m_room = std::make_shared<Room>();
|
||||
m_room_member = std::make_shared<RoomMember>();
|
||||
}
|
||||
|
||||
bool RoomNetwork::Init() {
|
||||
if (enet_initialize() != 0) {
|
||||
LOG_ERROR(Network, "Error initalizing ENet");
|
||||
return false;
|
||||
}
|
||||
m_room = std::make_shared<Room>();
|
||||
m_room_member = std::make_shared<RoomMember>();
|
||||
LOG_DEBUG(Network, "initialized OK");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::weak_ptr<Room> RoomNetwork::GetRoom() {
|
||||
return m_room;
|
||||
}
|
||||
|
||||
std::weak_ptr<RoomMember> RoomNetwork::GetRoomMember() {
|
||||
return m_room_member;
|
||||
}
|
||||
|
||||
void RoomNetwork::Shutdown() {
|
||||
if (m_room_member) {
|
||||
if (m_room_member->IsConnected())
|
||||
m_room_member->Leave();
|
||||
m_room_member.reset();
|
||||
}
|
||||
if (m_room) {
|
||||
if (m_room->GetState() == Room::State::Open)
|
||||
m_room->Destroy();
|
||||
m_room.reset();
|
||||
}
|
||||
enet_deinitialize();
|
||||
LOG_DEBUG(Network, "shutdown OK");
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
@@ -1,33 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "network/room.h"
|
||||
#include "network/room_member.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
class RoomNetwork {
|
||||
public:
|
||||
RoomNetwork();
|
||||
|
||||
/// Initializes and registers the network device, the room, and the room member.
|
||||
bool Init();
|
||||
|
||||
/// Returns a pointer to the room handle
|
||||
std::weak_ptr<Room> GetRoom();
|
||||
|
||||
/// Returns a pointer to the room member handle
|
||||
std::weak_ptr<RoomMember> GetRoomMember();
|
||||
|
||||
/// Unregisters the network device, the room, and the room member and shut them down.
|
||||
void Shutdown();
|
||||
|
||||
private:
|
||||
std::shared_ptr<RoomMember> m_room_member; ///< RoomMember (Client) for network games
|
||||
std::shared_ptr<Room> m_room; ///< Room (Server) for network games
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
@@ -1,262 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include "network/packet.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
#ifndef htonll
|
||||
static u64 htonll(u64 x) {
|
||||
return ((1 == htonl(1)) ? (x) : ((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ntohll
|
||||
static u64 ntohll(u64 x) {
|
||||
return ((1 == ntohl(1)) ? (x) : ((uint64_t)ntohl((x)&0xFFFFFFFF) << 32) | ntohl((x) >> 32));
|
||||
}
|
||||
#endif
|
||||
|
||||
void Packet::Append(const void* in_data, std::size_t size_in_bytes) {
|
||||
if (in_data && (size_in_bytes > 0)) {
|
||||
std::size_t start = data.size();
|
||||
data.resize(start + size_in_bytes);
|
||||
std::memcpy(&data[start], in_data, size_in_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void Packet::Read(void* out_data, std::size_t size_in_bytes) {
|
||||
if (out_data && CheckSize(size_in_bytes)) {
|
||||
std::memcpy(out_data, &data[read_pos], size_in_bytes);
|
||||
read_pos += size_in_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
void Packet::Clear() {
|
||||
data.clear();
|
||||
read_pos = 0;
|
||||
is_valid = true;
|
||||
}
|
||||
|
||||
const void* Packet::GetData() const {
|
||||
return !data.empty() ? &data[0] : nullptr;
|
||||
}
|
||||
|
||||
void Packet::IgnoreBytes(u32 length) {
|
||||
read_pos += length;
|
||||
}
|
||||
|
||||
std::size_t Packet::GetDataSize() const {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
bool Packet::EndOfPacket() const {
|
||||
return read_pos >= data.size();
|
||||
}
|
||||
|
||||
Packet::operator bool() const {
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(bool& out_data) {
|
||||
u8 value{};
|
||||
if (Read(value)) {
|
||||
out_data = (value != 0);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(s8& out_data) {
|
||||
Read(&out_data, sizeof(out_data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(u8& out_data) {
|
||||
Read(&out_data, sizeof(out_data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(s16& out_data) {
|
||||
s16 value{};
|
||||
Read(&value, sizeof(value));
|
||||
out_data = ntohs(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(u16& out_data) {
|
||||
u16 value{};
|
||||
Read(&value, sizeof(value));
|
||||
out_data = ntohs(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(s32& out_data) {
|
||||
s32 value{};
|
||||
Read(&value, sizeof(value));
|
||||
out_data = ntohl(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(u32& out_data) {
|
||||
u32 value{};
|
||||
Read(&value, sizeof(value));
|
||||
out_data = ntohl(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(s64& out_data) {
|
||||
s64 value{};
|
||||
Read(&value, sizeof(value));
|
||||
out_data = ntohll(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(u64& out_data) {
|
||||
u64 value{};
|
||||
Read(&value, sizeof(value));
|
||||
out_data = ntohll(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(float& out_data) {
|
||||
Read(&out_data, sizeof(out_data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(double& out_data) {
|
||||
Read(&out_data, sizeof(out_data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(char* out_data) {
|
||||
// First extract string length
|
||||
u32 length = 0;
|
||||
Read(length);
|
||||
|
||||
if ((length > 0) && CheckSize(length)) {
|
||||
// Then extract characters
|
||||
std::memcpy(out_data, &data[read_pos], length);
|
||||
out_data[length] = '\0';
|
||||
|
||||
// Update reading position
|
||||
read_pos += length;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Read(std::string& out_data) {
|
||||
// First extract string length
|
||||
u32 length = 0;
|
||||
Read(length);
|
||||
|
||||
out_data.clear();
|
||||
if ((length > 0) && CheckSize(length)) {
|
||||
// Then extract characters
|
||||
out_data.assign(&data[read_pos], length);
|
||||
|
||||
// Update reading position
|
||||
read_pos += length;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(bool in_data) {
|
||||
Write(static_cast<u8>(in_data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(s8 in_data) {
|
||||
Append(&in_data, sizeof(in_data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(u8 in_data) {
|
||||
Append(&in_data, sizeof(in_data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(s16 in_data) {
|
||||
s16 toWrite = htons(in_data);
|
||||
Append(&toWrite, sizeof(toWrite));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(u16 in_data) {
|
||||
u16 toWrite = htons(in_data);
|
||||
Append(&toWrite, sizeof(toWrite));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(s32 in_data) {
|
||||
s32 toWrite = htonl(in_data);
|
||||
Append(&toWrite, sizeof(toWrite));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(u32 in_data) {
|
||||
u32 toWrite = htonl(in_data);
|
||||
Append(&toWrite, sizeof(toWrite));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(s64 in_data) {
|
||||
s64 toWrite = htonll(in_data);
|
||||
Append(&toWrite, sizeof(toWrite));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(u64 in_data) {
|
||||
u64 toWrite = htonll(in_data);
|
||||
Append(&toWrite, sizeof(toWrite));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(float in_data) {
|
||||
Append(&in_data, sizeof(in_data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(double in_data) {
|
||||
Append(&in_data, sizeof(in_data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(const char* in_data) {
|
||||
// First insert string length
|
||||
u32 length = static_cast<u32>(std::strlen(in_data));
|
||||
Write(length);
|
||||
|
||||
// Then insert characters
|
||||
Append(in_data, length * sizeof(char));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Packet& Packet::Write(const std::string& in_data) {
|
||||
// First insert string length
|
||||
u32 length = static_cast<u32>(in_data.size());
|
||||
Write(length);
|
||||
|
||||
// Then insert characters
|
||||
if (length > 0)
|
||||
Append(in_data.c_str(), length * sizeof(std::string::value_type));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Packet::CheckSize(std::size_t size) {
|
||||
is_valid = is_valid && (read_pos + size <= data.size());
|
||||
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
@@ -1,165 +0,0 @@
|
||||
// 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 endianess
|
||||
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
|
||||
1110
src/network/room.cpp
@@ -1,151 +0,0 @@
|
||||
// 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 "network/verify_user.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
using AnnounceMultiplayerRoom::GameInfo;
|
||||
using AnnounceMultiplayerRoom::MacAddress;
|
||||
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 MAC address that tells the room we're joining to assign us a MAC address
|
||||
/// automatically.
|
||||
constexpr MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
|
||||
// 802.11 broadcast MAC address
|
||||
constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 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,
|
||||
IdWifiPacket,
|
||||
IdChatMessage,
|
||||
IdNameCollision,
|
||||
IdMacCollision,
|
||||
IdVersionMismatch,
|
||||
IdWrongPassword,
|
||||
IdCloseRoom,
|
||||
IdRoomIsFull,
|
||||
IdConsoleIdCollision,
|
||||
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
|
||||
@@ -1,696 +0,0 @@
|
||||
// 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 "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.
|
||||
|
||||
MacAddress mac_address; ///< The mac_address 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<WifiPacket> callback_set_wifi_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 mac.
|
||||
* @params nickname The desired nickname.
|
||||
* @params console_id_hash A hash of the Console ID.
|
||||
* @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells
|
||||
* @params password The password for the room
|
||||
* the server to assign one for us.
|
||||
*/
|
||||
void SendJoinRequest(const std::string& nickname_, const std::string& console_id_hash,
|
||||
const MacAddress& preferred_mac = NoPreferredMac,
|
||||
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 WifiPacket from a received ENet packet.
|
||||
* @param event The ENet event that was received.
|
||||
*/
|
||||
void HandleWifiPackets(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, 16) > 0) {
|
||||
switch (event.type) {
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
switch (event.packet->data[0]) {
|
||||
case IdWifiPacket:
|
||||
HandleWifiPackets(&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 IdMacCollision:
|
||||
SetState(State::Idle);
|
||||
SetError(Error::MacCollision);
|
||||
break;
|
||||
case IdConsoleIdCollision:
|
||||
SetState(State::Idle);
|
||||
SetError(Error::ConsoleIdCollision);
|
||||
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 std::string& console_id_hash,
|
||||
const MacAddress& preferred_mac,
|
||||
const std::string& password,
|
||||
const std::string& token) {
|
||||
Packet packet;
|
||||
packet.Write(static_cast<u8>(IdJoinRequest));
|
||||
packet.Write(nickname_);
|
||||
packet.Write(console_id_hash);
|
||||
packet.Write(preferred_mac);
|
||||
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.mac_address);
|
||||
packet.Read(member.game_info.name);
|
||||
packet.Read(member.game_info.id);
|
||||
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(mac_address);
|
||||
}
|
||||
|
||||
void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {
|
||||
WifiPacket wifi_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 WifiPacket from the packet
|
||||
u8 frame_type;
|
||||
packet.Read(frame_type);
|
||||
WifiPacket::PacketType type = static_cast<WifiPacket::PacketType>(frame_type);
|
||||
|
||||
wifi_packet.type = type;
|
||||
packet.Read(wifi_packet.channel);
|
||||
packet.Read(wifi_packet.transmitter_address);
|
||||
packet.Read(wifi_packet.destination_address);
|
||||
packet.Read(wifi_packet.data);
|
||||
|
||||
Invoke<WifiPacket>(wifi_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<WifiPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
|
||||
return callback_set_wifi_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 MacAddress& RoomMember::GetMacAddress() const {
|
||||
ASSERT_MSG(IsConnected(), "Tried to get MAC address while not connected");
|
||||
return room_member_impl->mac_address;
|
||||
}
|
||||
|
||||
RoomInformation RoomMember::GetRoomInformation() const {
|
||||
return room_member_impl->room_information;
|
||||
}
|
||||
|
||||
void RoomMember::Join(const std::string& nick, const std::string& console_id_hash,
|
||||
const char* server_addr, u16 server_port, u16 client_port,
|
||||
const MacAddress& preferred_mac, const std::string& password,
|
||||
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, console_id_hash, preferred_mac, 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::SendWifiPacket(const WifiPacket& wifi_packet) {
|
||||
Packet packet;
|
||||
packet.Write(static_cast<u8>(IdWifiPacket));
|
||||
packet.Write(static_cast<u8>(wifi_packet.type));
|
||||
packet.Write(wifi_packet.channel);
|
||||
packet.Write(wifi_packet.transmitter_address);
|
||||
packet.Write(wifi_packet.destination_address);
|
||||
packet.Write(wifi_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);
|
||||
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<WifiPacket> RoomMember::BindOnWifiPacketReceived(
|
||||
std::function<void(const WifiPacket&)> callback) {
|
||||
return room_member_impl->Bind(callback);
|
||||
}
|
||||
|
||||
RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged(
|
||||
std::function<void(const RoomInformation&)> callback) {
|
||||
return room_member_impl->Bind(callback);
|
||||
}
|
||||
|
||||
RoomMember::CallbackHandle<ChatEntry> RoomMember::BindOnChatMessageRecieved(
|
||||
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<WifiPacket>);
|
||||
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
|
||||
@@ -1,318 +0,0 @@
|
||||
// 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 "network/room.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
using AnnounceMultiplayerRoom::GameInfo;
|
||||
using AnnounceMultiplayerRoom::RoomInformation;
|
||||
|
||||
/// Information about the received WiFi packets.
|
||||
/// Acts as our own 802.11 header.
|
||||
struct WifiPacket {
|
||||
enum class PacketType : u8 {
|
||||
Beacon,
|
||||
Data,
|
||||
Authentication,
|
||||
AssociationResponse,
|
||||
Deauthentication,
|
||||
NodeMap
|
||||
};
|
||||
PacketType type; ///< The type of 802.11 frame.
|
||||
std::vector<u8> data; ///< Raw 802.11 frame data, starting at the management frame header
|
||||
/// for management frames.
|
||||
MacAddress transmitter_address; ///< Mac address of the transmitter.
|
||||
MacAddress destination_address; ///< Mac address of the receiver.
|
||||
u8 channel; ///< WiFi channel where this frame was transmitted.
|
||||
};
|
||||
|
||||
/// 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 connnected 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
|
||||
MacCollision, ///< Somebody is already using that mac-address
|
||||
ConsoleIdCollision, ///< Somebody in the room has the same Console ID
|
||||
WrongVersion, ///< The room version is not the same as for this RoomMember
|
||||
WrongPassword, ///< The password doesn't match the one from the Room
|
||||
CouldNotConnect, ///< The room is not responding to a connection attempt
|
||||
RoomIsFull, ///< Room is already at the maximum number of players
|
||||
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.
|
||||
MacAddress mac_address; ///< MAC 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 MacAddress& GetMacAddress() 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.
|
||||
* A console ID hash is passed in to check console ID conflicts.
|
||||
* This may fail if the username or console ID is already taken.
|
||||
*/
|
||||
void Join(const std::string& nickname, const std::string& console_id_hash,
|
||||
const char* server_addr = "127.0.0.1", u16 server_port = DefaultRoomPort,
|
||||
u16 client_port = 0, const MacAddress& preferred_mac = NoPreferredMac,
|
||||
const std::string& password = "", const std::string& token = "");
|
||||
|
||||
/**
|
||||
* Sends a WiFi packet to the room.
|
||||
* @param packet The WiFi packet to send.
|
||||
*/
|
||||
void SendWifiPacket(const WifiPacket& 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 wil 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 wil 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 WifiPacket is received.
|
||||
* The function wil be called everytime 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<WifiPacket> BindOnWifiPacketReceived(
|
||||
std::function<void(const WifiPacket&)> callback);
|
||||
|
||||
/**
|
||||
* Binds a function to an event that will be triggered every time the RoomInformation changes.
|
||||
* The function wil 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 wil 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> BindOnChatMessageRecieved(
|
||||
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::MacCollision:
|
||||
return "MaxCollision";
|
||||
case RoomMember::Error::ConsoleIdCollision:
|
||||
return "ConsoleIdCollision";
|
||||
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
|
||||
@@ -1,17 +0,0 @@
|
||||
// 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
|
||||
@@ -1,45 +0,0 @@
|
||||
// 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
|
||||
@@ -7,7 +7,7 @@ add_executable(tests
|
||||
common/ring_buffer.cpp
|
||||
common/unique_function.cpp
|
||||
core/core_timing.cpp
|
||||
core/internal_network/network.cpp
|
||||
core/network/network.cpp
|
||||
tests.cpp
|
||||
video_core/buffer_base.cpp
|
||||
input_common/calibration_configuration_job.cpp
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
#include "core/network/network.h"
|
||||
#include "core/network/sockets.h"
|
||||
|
||||
TEST_CASE("Network::Errors", "[core]") {
|
||||
Network::NetworkInstance network_instance; // initialize network
|
||||
@@ -1,16 +1,12 @@
|
||||
add_library(web_service STATIC
|
||||
announce_room_json.cpp
|
||||
announce_room_json.h
|
||||
telemetry_json.cpp
|
||||
telemetry_json.h
|
||||
verify_login.cpp
|
||||
verify_login.h
|
||||
verify_user_jwt.cpp
|
||||
verify_user_jwt.h
|
||||
web_backend.cpp
|
||||
web_backend.h
|
||||
web_result.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(web_service)
|
||||
target_link_libraries(web_service PRIVATE common network nlohmann_json::nlohmann_json httplib cpp-jwt)
|
||||
target_link_libraries(web_service PRIVATE common nlohmann_json::nlohmann_json httplib)
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <future>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "common/detached_tasks.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "web_service/announce_room_json.h"
|
||||
#include "web_service/web_backend.h"
|
||||
|
||||
namespace AnnounceMultiplayerRoom {
|
||||
|
||||
static void to_json(nlohmann::json& json, const Member& member) {
|
||||
if (!member.username.empty()) {
|
||||
json["username"] = member.username;
|
||||
}
|
||||
json["nickname"] = member.nickname;
|
||||
if (!member.avatar_url.empty()) {
|
||||
json["avatarUrl"] = member.avatar_url;
|
||||
}
|
||||
json["gameName"] = member.game.name;
|
||||
json["gameId"] = member.game.id;
|
||||
}
|
||||
|
||||
static void from_json(const nlohmann::json& json, Member& member) {
|
||||
member.nickname = json.at("nickname").get<std::string>();
|
||||
member.game.name = json.at("gameName").get<std::string>();
|
||||
member.game.id = json.at("gameId").get<u64>();
|
||||
try {
|
||||
member.username = json.at("username").get<std::string>();
|
||||
member.avatar_url = json.at("avatarUrl").get<std::string>();
|
||||
} catch (const nlohmann::detail::out_of_range&) {
|
||||
member.username = member.avatar_url = "";
|
||||
LOG_DEBUG(Network, "Member \'{}\' isn't authenticated", member.nickname);
|
||||
}
|
||||
}
|
||||
|
||||
static void to_json(nlohmann::json& json, const Room& room) {
|
||||
json["port"] = room.information.port;
|
||||
json["name"] = room.information.name;
|
||||
if (!room.information.description.empty()) {
|
||||
json["description"] = room.information.description;
|
||||
}
|
||||
json["preferredGameName"] = room.information.preferred_game.name;
|
||||
json["preferredGameId"] = room.information.preferred_game.id;
|
||||
json["maxPlayers"] = room.information.member_slots;
|
||||
json["netVersion"] = room.net_version;
|
||||
json["hasPassword"] = room.has_password;
|
||||
if (room.members.size() > 0) {
|
||||
nlohmann::json member_json = room.members;
|
||||
json["players"] = member_json;
|
||||
}
|
||||
}
|
||||
|
||||
static void from_json(const nlohmann::json& json, Room& room) {
|
||||
room.verify_uid = json.at("externalGuid").get<std::string>();
|
||||
room.ip = json.at("address").get<std::string>();
|
||||
room.information.name = json.at("name").get<std::string>();
|
||||
try {
|
||||
room.information.description = json.at("description").get<std::string>();
|
||||
} catch (const nlohmann::detail::out_of_range&) {
|
||||
room.information.description = "";
|
||||
LOG_DEBUG(Network, "Room \'{}\' doesn't contain a description", room.information.name);
|
||||
}
|
||||
room.information.host_username = json.at("owner").get<std::string>();
|
||||
room.information.port = json.at("port").get<u16>();
|
||||
room.information.preferred_game.name = json.at("preferredGameName").get<std::string>();
|
||||
room.information.preferred_game.id = json.at("preferredGameId").get<u64>();
|
||||
room.information.member_slots = json.at("maxPlayers").get<u32>();
|
||||
room.net_version = json.at("netVersion").get<u32>();
|
||||
room.has_password = json.at("hasPassword").get<bool>();
|
||||
try {
|
||||
room.members = json.at("players").get<std::vector<Member>>();
|
||||
} catch (const nlohmann::detail::out_of_range& e) {
|
||||
LOG_DEBUG(Network, "Out of range {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AnnounceMultiplayerRoom
|
||||
|
||||
namespace WebService {
|
||||
|
||||
void RoomJson::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 AnnounceMultiplayerRoom::GameInfo& preferred_game) {
|
||||
room.information.name = name;
|
||||
room.information.description = description;
|
||||
room.information.port = port;
|
||||
room.information.member_slots = max_player;
|
||||
room.net_version = net_version;
|
||||
room.has_password = has_password;
|
||||
room.information.preferred_game = preferred_game;
|
||||
}
|
||||
void RoomJson::AddPlayer(const AnnounceMultiplayerRoom::Member& member) {
|
||||
room.members.push_back(member);
|
||||
}
|
||||
|
||||
WebService::WebResult RoomJson::Update() {
|
||||
if (room_id.empty()) {
|
||||
LOG_ERROR(WebService, "Room must be registered to be updated");
|
||||
return WebService::WebResult{WebService::WebResult::Code::LibError,
|
||||
"Room is not registered", ""};
|
||||
}
|
||||
nlohmann::json json{{"players", room.members}};
|
||||
return client.PostJson(fmt::format("/lobby/{}", room_id), json.dump(), false);
|
||||
}
|
||||
|
||||
WebService::WebResult RoomJson::Register() {
|
||||
nlohmann::json json = room;
|
||||
auto result = client.PostJson("/lobby", json.dump(), false);
|
||||
if (result.result_code != WebService::WebResult::Code::Success) {
|
||||
return result;
|
||||
}
|
||||
auto reply_json = nlohmann::json::parse(result.returned_data);
|
||||
room = reply_json.get<AnnounceMultiplayerRoom::Room>();
|
||||
room_id = reply_json.at("id").get<std::string>();
|
||||
return WebService::WebResult{WebService::WebResult::Code::Success, "", room.verify_uid};
|
||||
}
|
||||
|
||||
void RoomJson::ClearPlayers() {
|
||||
room.members.clear();
|
||||
}
|
||||
|
||||
AnnounceMultiplayerRoom::RoomList RoomJson::GetRoomList() {
|
||||
auto reply = client.GetJson("/lobby", true).returned_data;
|
||||
if (reply.empty()) {
|
||||
return {};
|
||||
}
|
||||
return nlohmann::json::parse(reply).at("rooms").get<AnnounceMultiplayerRoom::RoomList>();
|
||||
}
|
||||
|
||||
void RoomJson::Delete() {
|
||||
if (room_id.empty()) {
|
||||
LOG_ERROR(WebService, "Room must be registered to be deleted");
|
||||
return;
|
||||
}
|
||||
Common::DetachedTasks::AddTask(
|
||||
[host{this->host}, username{this->username}, token{this->token}, room_id{this->room_id}]() {
|
||||
// create a new client here because the this->client might be destroyed.
|
||||
Client{host, username, token}.DeleteJson(fmt::format("/lobby/{}", room_id), "", false);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace WebService
|
||||
@@ -1,41 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include "common/announce_multiplayer_room.h"
|
||||
#include "web_service/web_backend.h"
|
||||
|
||||
namespace WebService {
|
||||
|
||||
/**
|
||||
* Implementation of AnnounceMultiplayerRoom::Backend that (de)serializes room information into/from
|
||||
* JSON, and submits/gets it to/from the yuzu web service
|
||||
*/
|
||||
class RoomJson : public AnnounceMultiplayerRoom::Backend {
|
||||
public:
|
||||
RoomJson(const std::string& host_, const std::string& username_, const std::string& token_)
|
||||
: client(host_, username_, token_), host(host_), username(username_), token(token_) {}
|
||||
~RoomJson() = default;
|
||||
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 AnnounceMultiplayerRoom::GameInfo& preferred_game) override;
|
||||
void AddPlayer(const AnnounceMultiplayerRoom::Member& member) override;
|
||||
WebResult Update() override;
|
||||
WebResult Register() override;
|
||||
void ClearPlayers() override;
|
||||
AnnounceMultiplayerRoom::RoomList GetRoomList() override;
|
||||
void Delete() override;
|
||||
|
||||
private:
|
||||
AnnounceMultiplayerRoom::Room room;
|
||||
Client client;
|
||||
std::string host;
|
||||
std::string username;
|
||||
std::string token;
|
||||
std::string room_id;
|
||||
};
|
||||
|
||||
} // namespace WebService
|
||||
@@ -1,67 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||
#endif
|
||||
#include <jwt/jwt.hpp>
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <system_error>
|
||||
#include "common/logging/log.h"
|
||||
#include "web_service/verify_user_jwt.h"
|
||||
#include "web_service/web_backend.h"
|
||||
#include "web_service/web_result.h"
|
||||
|
||||
namespace WebService {
|
||||
|
||||
static std::string public_key;
|
||||
std::string GetPublicKey(const std::string& host) {
|
||||
if (public_key.empty()) {
|
||||
Client client(host, "", ""); // no need for credentials here
|
||||
public_key = client.GetPlain("/jwt/external/key.pem", true).returned_data;
|
||||
if (public_key.empty()) {
|
||||
LOG_ERROR(WebService, "Could not fetch external JWT public key, verification may fail");
|
||||
} else {
|
||||
LOG_INFO(WebService, "Fetched external JWT public key (size={})", public_key.size());
|
||||
}
|
||||
}
|
||||
return public_key;
|
||||
}
|
||||
|
||||
VerifyUserJWT::VerifyUserJWT(const std::string& host) : pub_key(GetPublicKey(host)) {}
|
||||
|
||||
Network::VerifyUser::UserData VerifyUserJWT::LoadUserData(const std::string& verify_uid,
|
||||
const std::string& token) {
|
||||
const std::string audience = fmt::format("external-{}", verify_uid);
|
||||
using namespace jwt::params;
|
||||
std::error_code error;
|
||||
auto decoded =
|
||||
jwt::decode(token, algorithms({"rs256"}), error, secret(pub_key), issuer("yuzu-core"),
|
||||
aud(audience), validate_iat(true), validate_jti(true));
|
||||
if (error) {
|
||||
LOG_INFO(WebService, "Verification failed: category={}, code={}, message={}",
|
||||
error.category().name(), error.value(), error.message());
|
||||
return {};
|
||||
}
|
||||
Network::VerifyUser::UserData user_data{};
|
||||
if (decoded.payload().has_claim("username")) {
|
||||
user_data.username = decoded.payload().get_claim_value<std::string>("username");
|
||||
}
|
||||
if (decoded.payload().has_claim("displayName")) {
|
||||
user_data.display_name = decoded.payload().get_claim_value<std::string>("displayName");
|
||||
}
|
||||
if (decoded.payload().has_claim("avatarUrl")) {
|
||||
user_data.avatar_url = decoded.payload().get_claim_value<std::string>("avatarUrl");
|
||||
}
|
||||
if (decoded.payload().has_claim("roles")) {
|
||||
auto roles = decoded.payload().get_claim_value<std::vector<std::string>>("roles");
|
||||
user_data.moderator = std::find(roles.begin(), roles.end(), "moderator") != roles.end();
|
||||
}
|
||||
return user_data;
|
||||
}
|
||||
|
||||
} // namespace WebService
|
||||
@@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "network/verify_user.h"
|
||||
#include "web_service/web_backend.h"
|
||||
|
||||
namespace WebService {
|
||||
|
||||
std::string GetPublicKey(const std::string& host);
|
||||
|
||||
class VerifyUserJWT final : public Network::VerifyUser::Backend {
|
||||
public:
|
||||
VerifyUserJWT(const std::string& host);
|
||||
~VerifyUserJWT() = default;
|
||||
|
||||
Network::VerifyUser::UserData LoadUserData(const std::string& verify_uid,
|
||||
const std::string& token) override;
|
||||
|
||||
private:
|
||||
std::string pub_key;
|
||||
};
|
||||
|
||||
} // namespace WebService
|
||||
@@ -30,6 +30,8 @@ add_executable(yuzu
|
||||
applets/qt_web_browser_scripts.h
|
||||
bootmanager.cpp
|
||||
bootmanager.h
|
||||
check_vulkan.cpp
|
||||
check_vulkan.h
|
||||
compatdb.ui
|
||||
compatibility_list.cpp
|
||||
compatibility_list.h
|
||||
@@ -156,36 +158,8 @@ add_executable(yuzu
|
||||
main.cpp
|
||||
main.h
|
||||
main.ui
|
||||
multiplayer/chat_room.cpp
|
||||
multiplayer/chat_room.h
|
||||
multiplayer/chat_room.ui
|
||||
multiplayer/client_room.h
|
||||
multiplayer/client_room.cpp
|
||||
multiplayer/client_room.ui
|
||||
multiplayer/direct_connect.cpp
|
||||
multiplayer/direct_connect.h
|
||||
multiplayer/direct_connect.ui
|
||||
multiplayer/host_room.cpp
|
||||
multiplayer/host_room.h
|
||||
multiplayer/host_room.ui
|
||||
multiplayer/lobby.cpp
|
||||
multiplayer/lobby.h
|
||||
multiplayer/lobby.ui
|
||||
multiplayer/lobby_p.h
|
||||
multiplayer/message.cpp
|
||||
multiplayer/message.h
|
||||
multiplayer/moderation_dialog.cpp
|
||||
multiplayer/moderation_dialog.h
|
||||
multiplayer/moderation_dialog.ui
|
||||
multiplayer/state.cpp
|
||||
multiplayer/state.h
|
||||
multiplayer/validation.h
|
||||
startup_checks.cpp
|
||||
startup_checks.h
|
||||
uisettings.cpp
|
||||
uisettings.h
|
||||
util/clickable_label.cpp
|
||||
util/clickable_label.h
|
||||
util/controller_navigation.cpp
|
||||
util/controller_navigation.h
|
||||
util/limitable_input_dialog.cpp
|
||||
@@ -282,7 +256,7 @@ endif()
|
||||
|
||||
create_target_directory_groups(yuzu)
|
||||
|
||||
target_link_libraries(yuzu PRIVATE common core input_common network video_core)
|
||||
target_link_libraries(yuzu PRIVATE common core input_common video_core)
|
||||
target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets Qt::Multimedia)
|
||||
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
|
||||
@@ -326,10 +300,6 @@ if (USE_DISCORD_PRESENCE)
|
||||
target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(yuzu PRIVATE -DENABLE_WEB_SERVICE)
|
||||
endif()
|
||||
|
||||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
target_link_libraries(yuzu PRIVATE Qt::WebEngineCore Qt::WebEngineWidgets)
|
||||
target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE)
|
||||
|
||||
53
src/yuzu/check_vulkan.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/vulkan_common/vulkan_instance.h"
|
||||
#include "video_core/vulkan_common/vulkan_library.h"
|
||||
#include "yuzu/check_vulkan.h"
|
||||
#include "yuzu/uisettings.h"
|
||||
|
||||
constexpr char TEMP_FILE_NAME[] = "vulkan_check";
|
||||
|
||||
bool CheckVulkan() {
|
||||
if (UISettings::values.has_broken_vulkan) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Frontend, "Checking presence of Vulkan");
|
||||
|
||||
const auto fs_config_loc = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir);
|
||||
const auto temp_file_loc = fs_config_loc / TEMP_FILE_NAME;
|
||||
|
||||
if (std::filesystem::exists(temp_file_loc)) {
|
||||
LOG_WARNING(Frontend, "Detected recovery from previous failed Vulkan initialization");
|
||||
|
||||
UISettings::values.has_broken_vulkan = true;
|
||||
std::filesystem::remove(temp_file_loc);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream temp_file_handle(temp_file_loc);
|
||||
temp_file_handle.close();
|
||||
|
||||
try {
|
||||
Vulkan::vk::InstanceDispatch dld;
|
||||
const Common::DynamicLibrary library = Vulkan::OpenLibrary();
|
||||
const Vulkan::vk::Instance instance =
|
||||
Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_0);
|
||||
|
||||
} catch (const Vulkan::vk::Exception& exception) {
|
||||
LOG_ERROR(Frontend, "Failed to initialize Vulkan: {}", exception.what());
|
||||
// Don't set has_broken_vulkan to true here: we care when loading Vulkan crashes the
|
||||
// application, not when we can handle it.
|
||||
}
|
||||
|
||||
std::filesystem::remove(temp_file_loc);
|
||||
return true;
|
||||
}
|
||||
6
src/yuzu/check_vulkan.h
Normal file
@@ -0,0 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
bool CheckVulkan();
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "input_common/main.h"
|
||||
#include "network/network.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
|
||||
namespace FS = Common::FS;
|
||||
@@ -689,6 +688,12 @@ void Config::ReadRendererValues() {
|
||||
ReadGlobalSetting(Settings::values.bg_green);
|
||||
ReadGlobalSetting(Settings::values.bg_blue);
|
||||
|
||||
if (!global && UISettings::values.has_broken_vulkan &&
|
||||
Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan &&
|
||||
!Settings::values.renderer_backend.UsingGlobal()) {
|
||||
Settings::values.renderer_backend.SetGlobal(true);
|
||||
}
|
||||
|
||||
if (global) {
|
||||
ReadBasicSetting(Settings::values.renderer_debug);
|
||||
ReadBasicSetting(Settings::values.renderer_shader_feedback);
|
||||
@@ -795,7 +800,6 @@ void Config::ReadUIValues() {
|
||||
ReadPathValues();
|
||||
ReadScreenshotValues();
|
||||
ReadShortcutValues();
|
||||
ReadMultiplayerValues();
|
||||
|
||||
ReadBasicSetting(UISettings::values.single_window_mode);
|
||||
ReadBasicSetting(UISettings::values.fullscreen);
|
||||
@@ -809,6 +813,7 @@ void Config::ReadUIValues() {
|
||||
ReadBasicSetting(UISettings::values.pause_when_in_background);
|
||||
ReadBasicSetting(UISettings::values.mute_when_in_background);
|
||||
ReadBasicSetting(UISettings::values.hide_mouse);
|
||||
ReadBasicSetting(UISettings::values.has_broken_vulkan);
|
||||
ReadBasicSetting(UISettings::values.disable_web_applet);
|
||||
|
||||
qt_config->endGroup();
|
||||
@@ -862,42 +867,6 @@ void Config::ReadWebServiceValues() {
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
void Config::ReadMultiplayerValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Multiplayer"));
|
||||
|
||||
ReadBasicSetting(UISettings::values.multiplayer_nickname);
|
||||
ReadBasicSetting(UISettings::values.multiplayer_ip);
|
||||
ReadBasicSetting(UISettings::values.multiplayer_port);
|
||||
ReadBasicSetting(UISettings::values.multiplayer_room_nickname);
|
||||
ReadBasicSetting(UISettings::values.multiplayer_room_name);
|
||||
ReadBasicSetting(UISettings::values.multiplayer_room_port);
|
||||
ReadBasicSetting(UISettings::values.multiplayer_host_type);
|
||||
ReadBasicSetting(UISettings::values.multiplayer_port);
|
||||
ReadBasicSetting(UISettings::values.multiplayer_max_player);
|
||||
ReadBasicSetting(UISettings::values.multiplayer_game_id);
|
||||
ReadBasicSetting(UISettings::values.multiplayer_room_description);
|
||||
|
||||
// Read ban list back
|
||||
int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
|
||||
UISettings::values.multiplayer_ban_list.first.resize(size);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
qt_config->setArrayIndex(i);
|
||||
UISettings::values.multiplayer_ban_list.first[i] =
|
||||
ReadSetting(QStringLiteral("username")).toString().toStdString();
|
||||
}
|
||||
qt_config->endArray();
|
||||
size = qt_config->beginReadArray(QStringLiteral("ip_ban_list"));
|
||||
UISettings::values.multiplayer_ban_list.second.resize(size);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
qt_config->setArrayIndex(i);
|
||||
UISettings::values.multiplayer_ban_list.second[i] =
|
||||
ReadSetting(QStringLiteral("ip")).toString().toStdString();
|
||||
}
|
||||
qt_config->endArray();
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
void Config::ReadValues() {
|
||||
if (global) {
|
||||
ReadControlValues();
|
||||
@@ -914,7 +883,6 @@ void Config::ReadValues() {
|
||||
ReadRendererValues();
|
||||
ReadAudioValues();
|
||||
ReadSystemValues();
|
||||
ReadMultiplayerValues();
|
||||
}
|
||||
|
||||
void Config::SavePlayerValue(std::size_t player_index) {
|
||||
@@ -1064,7 +1032,6 @@ void Config::SaveValues() {
|
||||
SaveRendererValues();
|
||||
SaveAudioValues();
|
||||
SaveSystemValues();
|
||||
SaveMultiplayerValues();
|
||||
}
|
||||
|
||||
void Config::SaveAudioValues() {
|
||||
@@ -1387,7 +1354,6 @@ void Config::SaveUIValues() {
|
||||
SavePathValues();
|
||||
SaveScreenshotValues();
|
||||
SaveShortcutValues();
|
||||
SaveMultiplayerValues();
|
||||
|
||||
WriteBasicSetting(UISettings::values.single_window_mode);
|
||||
WriteBasicSetting(UISettings::values.fullscreen);
|
||||
@@ -1401,6 +1367,7 @@ void Config::SaveUIValues() {
|
||||
WriteBasicSetting(UISettings::values.pause_when_in_background);
|
||||
WriteBasicSetting(UISettings::values.mute_when_in_background);
|
||||
WriteBasicSetting(UISettings::values.hide_mouse);
|
||||
WriteBasicSetting(UISettings::values.has_broken_vulkan);
|
||||
WriteBasicSetting(UISettings::values.disable_web_applet);
|
||||
|
||||
qt_config->endGroup();
|
||||
@@ -1452,40 +1419,6 @@ void Config::SaveWebServiceValues() {
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
void Config::SaveMultiplayerValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Multiplayer"));
|
||||
|
||||
WriteBasicSetting(UISettings::values.multiplayer_nickname);
|
||||
WriteBasicSetting(UISettings::values.multiplayer_ip);
|
||||
WriteBasicSetting(UISettings::values.multiplayer_port);
|
||||
WriteBasicSetting(UISettings::values.multiplayer_room_nickname);
|
||||
WriteBasicSetting(UISettings::values.multiplayer_room_name);
|
||||
WriteBasicSetting(UISettings::values.multiplayer_room_port);
|
||||
WriteBasicSetting(UISettings::values.multiplayer_host_type);
|
||||
WriteBasicSetting(UISettings::values.multiplayer_port);
|
||||
WriteBasicSetting(UISettings::values.multiplayer_max_player);
|
||||
WriteBasicSetting(UISettings::values.multiplayer_game_id);
|
||||
WriteBasicSetting(UISettings::values.multiplayer_room_description);
|
||||
|
||||
// Write ban list
|
||||
qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
|
||||
for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) {
|
||||
qt_config->setArrayIndex(static_cast<int>(i));
|
||||
WriteSetting(QStringLiteral("username"),
|
||||
QString::fromStdString(UISettings::values.multiplayer_ban_list.first[i]));
|
||||
}
|
||||
qt_config->endArray();
|
||||
qt_config->beginWriteArray(QStringLiteral("ip_ban_list"));
|
||||
for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) {
|
||||
qt_config->setArrayIndex(static_cast<int>(i));
|
||||
WriteSetting(QStringLiteral("ip"),
|
||||
QString::fromStdString(UISettings::values.multiplayer_ban_list.second[i]));
|
||||
}
|
||||
qt_config->endArray();
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
QVariant Config::ReadSetting(const QString& name) const {
|
||||
return qt_config->value(name);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ private:
|
||||
void ReadUIGamelistValues();
|
||||
void ReadUILayoutValues();
|
||||
void ReadWebServiceValues();
|
||||
void ReadMultiplayerValues();
|
||||
|
||||
void SaveValues();
|
||||
void SavePlayerValue(std::size_t player_index);
|
||||
@@ -119,7 +118,6 @@ private:
|
||||
void SaveUIGamelistValues();
|
||||
void SaveUILayoutValues();
|
||||
void SaveWebServiceValues();
|
||||
void SaveMultiplayerValues();
|
||||
|
||||
/**
|
||||
* Reads a setting from the qt_config.
|
||||
|
||||
@@ -29,10 +29,9 @@
|
||||
|
||||
ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
|
||||
InputCommon::InputSubsystem* input_subsystem,
|
||||
Core::System& system_, bool enable_web_config)
|
||||
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()},
|
||||
registry(registry_), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_,
|
||||
this)},
|
||||
Core::System& system_)
|
||||
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, registry{registry_},
|
||||
system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_, this)},
|
||||
cpu_tab{std::make_unique<ConfigureCpu>(system_, this)},
|
||||
debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)},
|
||||
filesystem_tab{std::make_unique<ConfigureFilesystem>(this)},
|
||||
@@ -65,7 +64,6 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
|
||||
ui->tabWidget->addTab(ui_tab.get(), tr("Game List"));
|
||||
ui->tabWidget->addTab(web_tab.get(), tr("Web"));
|
||||
|
||||
web_tab->SetWebServiceConfigEnabled(enable_web_config);
|
||||
hotkeys_tab->Populate(registry);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
|
||||
@@ -41,8 +41,7 @@ class ConfigureDialog : public QDialog {
|
||||
|
||||
public:
|
||||
explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
|
||||
InputCommon::InputSubsystem* input_subsystem, Core::System& system_,
|
||||
bool enable_web_config = true);
|
||||
InputCommon::InputSubsystem* input_subsystem, Core::System& system_);
|
||||
~ConfigureDialog() override;
|
||||
|
||||
void ApplyConfiguration();
|
||||
|
||||
@@ -58,9 +58,24 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren
|
||||
UpdateBackgroundColorButton(new_bg_color);
|
||||
});
|
||||
|
||||
ui->api->setEnabled(!UISettings::values.has_broken_vulkan);
|
||||
ui->api_widget->setEnabled(!UISettings::values.has_broken_vulkan ||
|
||||
Settings::IsConfiguringGlobal());
|
||||
connect(ui->button_check_vulkan, &QAbstractButton::clicked, this, [this] {
|
||||
UISettings::values.has_broken_vulkan = false;
|
||||
|
||||
if (RetrieveVulkanDevices()) {
|
||||
ui->api->setEnabled(true);
|
||||
ui->button_check_vulkan->hide();
|
||||
|
||||
for (const auto& device : vulkan_devices) {
|
||||
ui->device->addItem(device);
|
||||
}
|
||||
} else {
|
||||
UISettings::values.has_broken_vulkan = true;
|
||||
}
|
||||
});
|
||||
|
||||
ui->api->setEnabled(!UISettings::values.has_broken_vulkan.GetValue());
|
||||
ui->button_check_vulkan->setVisible(UISettings::values.has_broken_vulkan.GetValue());
|
||||
|
||||
ui->bg_label->setVisible(Settings::IsConfiguringGlobal());
|
||||
ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());
|
||||
}
|
||||
@@ -300,7 +315,7 @@ void ConfigureGraphics::UpdateAPILayout() {
|
||||
vulkan_device = Settings::values.vulkan_device.GetValue(true);
|
||||
shader_backend = Settings::values.shader_backend.GetValue(true);
|
||||
ui->device_widget->setEnabled(false);
|
||||
ui->backend_widget->setEnabled(false);
|
||||
ui->backend_widget->setEnabled(UISettings::values.has_broken_vulkan.GetValue());
|
||||
} else {
|
||||
vulkan_device = Settings::values.vulkan_device.GetValue();
|
||||
shader_backend = Settings::values.shader_backend.GetValue();
|
||||
@@ -322,9 +337,9 @@ void ConfigureGraphics::UpdateAPILayout() {
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureGraphics::RetrieveVulkanDevices() try {
|
||||
bool ConfigureGraphics::RetrieveVulkanDevices() try {
|
||||
if (UISettings::values.has_broken_vulkan) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
using namespace Vulkan;
|
||||
@@ -340,8 +355,11 @@ void ConfigureGraphics::RetrieveVulkanDevices() try {
|
||||
const std::string name = vk::PhysicalDevice(device, dld).GetProperties().deviceName;
|
||||
vulkan_devices.push_back(QString::fromStdString(name));
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const Vulkan::vk::Exception& exception) {
|
||||
LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
|
||||
@@ -422,4 +440,11 @@ void ConfigureGraphics::SetupPerGameUI() {
|
||||
ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
|
||||
ConfigurationShared::InsertGlobalItem(
|
||||
ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true)));
|
||||
|
||||
if (UISettings::values.has_broken_vulkan) {
|
||||
ui->backend_widget->setEnabled(true);
|
||||
ConfigurationShared::SetColoredComboBox(
|
||||
ui->backend, ui->backend_widget,
|
||||
static_cast<int>(Settings::values.shader_backend.GetValue(true)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ private:
|
||||
void UpdateDeviceSelection(int device);
|
||||
void UpdateShaderBackendSelection(int backend);
|
||||
|
||||
void RetrieveVulkanDevices();
|
||||
bool RetrieveVulkanDevices();
|
||||
|
||||
void SetupPerGameUI();
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>541</width>
|
||||
<width>471</width>
|
||||
<height>759</height>
|
||||
</rect>
|
||||
</property>
|
||||
@@ -574,6 +574,13 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_check_vulkan">
|
||||
<property name="text">
|
||||
<string>Check for Working Vulkan</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/internal_network/network_interface.h"
|
||||
#include "core/network/network_interface.h"
|
||||
#include "ui_configure_network.h"
|
||||
#include "yuzu/configuration/configure_network.h"
|
||||
|
||||
|
||||
@@ -169,8 +169,3 @@ void ConfigureWeb::OnLoginVerified() {
|
||||
"correctly, and that your internet connection is working."));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) {
|
||||
ui->label_disable_info->setVisible(!enabled);
|
||||
ui->groupBoxWebConfig->setEnabled(enabled);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ public:
|
||||
~ConfigureWeb() override;
|
||||
|
||||
void ApplyConfiguration();
|
||||
void SetWebServiceConfigEnabled(bool enabled);
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent* event) override;
|
||||
|
||||
@@ -112,16 +112,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_disable_info">
|
||||
<property name="text">
|
||||
<string>Web Service configuration can only be changed when a public room isn't being hosted.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
|
||||
@@ -499,8 +499,6 @@ void GameList::DonePopulating(const QStringList& watch_list) {
|
||||
}
|
||||
item_model->sort(tree_view->header()->sortIndicatorSection(),
|
||||
tree_view->header()->sortIndicatorOrder());
|
||||
|
||||
emit PopulatingCompleted();
|
||||
}
|
||||
|
||||
void GameList::PopupContextMenu(const QPoint& menu_location) {
|
||||
@@ -754,10 +752,6 @@ void GameList::LoadCompatibilityList() {
|
||||
}
|
||||
}
|
||||
|
||||
QStandardItemModel* GameList::GetModel() const {
|
||||
return item_model;
|
||||
}
|
||||
|
||||
void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
||||
tree_view->setEnabled(false);
|
||||
|
||||
|
||||
@@ -16,14 +16,9 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "uisettings.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
class ControllerNavigation;
|
||||
class GameListWorker;
|
||||
class GameListSearchField;
|
||||
@@ -89,8 +84,6 @@ public:
|
||||
void SaveInterfaceLayout();
|
||||
void LoadInterfaceLayout();
|
||||
|
||||
QStandardItemModel* GetModel() const;
|
||||
|
||||
/// Disables events from the emulated controller
|
||||
void UnloadController();
|
||||
|
||||
@@ -115,7 +108,6 @@ signals:
|
||||
void OpenDirectory(const QString& directory);
|
||||
void AddDirectory();
|
||||
void ShowList(bool show);
|
||||
void PopulatingCompleted();
|
||||
|
||||
private slots:
|
||||
void OnItemExpanded(const QModelIndex& item);
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include "core/hle/service/am/applet_ae.h"
|
||||
#include "core/hle/service/am/applet_oe.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
#include "yuzu/multiplayer/state.h"
|
||||
#include "yuzu/util/controller_navigation.h"
|
||||
|
||||
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
|
||||
@@ -116,6 +115,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include "video_core/shader_notify.h"
|
||||
#include "yuzu/about_dialog.h"
|
||||
#include "yuzu/bootmanager.h"
|
||||
#include "yuzu/check_vulkan.h"
|
||||
#include "yuzu/compatdb.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
@@ -131,9 +131,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include "yuzu/install_dialog.h"
|
||||
#include "yuzu/loading_screen.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/startup_checks.h"
|
||||
#include "yuzu/uisettings.h"
|
||||
#include "yuzu/util/clickable_label.h"
|
||||
|
||||
using namespace Common::Literals;
|
||||
|
||||
@@ -254,7 +252,7 @@ static QString PrettyProductName() {
|
||||
return QSysInfo::prettyProductName();
|
||||
}
|
||||
|
||||
GMainWindow::GMainWindow(bool has_broken_vulkan)
|
||||
GMainWindow::GMainWindow()
|
||||
: ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
|
||||
input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
|
||||
config{std::make_unique<Config>(*system)},
|
||||
@@ -273,8 +271,6 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
|
||||
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
|
||||
discord_rpc->Update();
|
||||
|
||||
system->GetRoomNetwork().Init();
|
||||
|
||||
RegisterMetaTypes();
|
||||
|
||||
InitializeWidgets();
|
||||
@@ -356,15 +352,17 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
|
||||
|
||||
MigrateConfigFiles();
|
||||
|
||||
if (has_broken_vulkan) {
|
||||
UISettings::values.has_broken_vulkan = true;
|
||||
|
||||
QMessageBox::warning(this, tr("Broken Vulkan Installation Detected"),
|
||||
tr("Vulkan initialization failed during boot.<br><br>Click <a "
|
||||
"href='https://yuzu-emu.org/wiki/faq/"
|
||||
"#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>"
|
||||
"here for instructions to fix the issue</a>."));
|
||||
if (!CheckVulkan()) {
|
||||
config->Save();
|
||||
|
||||
QMessageBox::warning(
|
||||
this, tr("Broken Vulkan Installation Detected"),
|
||||
tr("Vulkan initialization failed on the previous boot.<br><br>Click <a "
|
||||
"href='https://yuzu-emu.org/wiki/faq/"
|
||||
"#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>here for "
|
||||
"instructions to fix the issue</a>."));
|
||||
}
|
||||
if (UISettings::values.has_broken_vulkan) {
|
||||
Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
|
||||
|
||||
renderer_status_button->setDisabled(true);
|
||||
@@ -463,7 +461,6 @@ GMainWindow::~GMainWindow() {
|
||||
if (render_window->parent() == nullptr) {
|
||||
delete render_window;
|
||||
}
|
||||
system->GetRoomNetwork().Shutdown();
|
||||
}
|
||||
|
||||
void GMainWindow::RegisterMetaTypes() {
|
||||
@@ -827,10 +824,6 @@ void GMainWindow::InitializeWidgets() {
|
||||
}
|
||||
});
|
||||
|
||||
multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room,
|
||||
ui->action_Show_Room, system->GetRoomNetwork());
|
||||
multiplayer_state->setVisible(false);
|
||||
|
||||
// Create status bar
|
||||
message_label = new QLabel();
|
||||
// Configured separately for left alignment
|
||||
@@ -863,10 +856,6 @@ void GMainWindow::InitializeWidgets() {
|
||||
statusBar()->addPermanentWidget(label);
|
||||
}
|
||||
|
||||
// TODO (flTobi): Add the widget when multiplayer is fully implemented
|
||||
// statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
|
||||
// statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
|
||||
|
||||
tas_label = new QLabel();
|
||||
tas_label->setObjectName(QStringLiteral("TASlabel"));
|
||||
tas_label->setFocusPolicy(Qt::NoFocus);
|
||||
@@ -1061,12 +1050,26 @@ void GMainWindow::InitializeHotkeys() {
|
||||
[] { Settings::values.audio_muted = !Settings::values.audio_muted; });
|
||||
connect_shortcut(QStringLiteral("Audio Volume Down"), [] {
|
||||
const auto current_volume = static_cast<int>(Settings::values.volume.GetValue());
|
||||
const auto new_volume = std::max(current_volume - 5, 0);
|
||||
int step = 5;
|
||||
if (current_volume <= 30) {
|
||||
step = 2;
|
||||
}
|
||||
if (current_volume <= 6) {
|
||||
step = 1;
|
||||
}
|
||||
const auto new_volume = std::max(current_volume - step, 0);
|
||||
Settings::values.volume.SetValue(static_cast<u8>(new_volume));
|
||||
});
|
||||
connect_shortcut(QStringLiteral("Audio Volume Up"), [] {
|
||||
const auto current_volume = static_cast<int>(Settings::values.volume.GetValue());
|
||||
const auto new_volume = std::min(current_volume + 5, 100);
|
||||
int step = 5;
|
||||
if (current_volume < 30) {
|
||||
step = 2;
|
||||
}
|
||||
if (current_volume < 6) {
|
||||
step = 1;
|
||||
}
|
||||
const auto new_volume = std::min(current_volume + step, 100);
|
||||
Settings::values.volume.SetValue(static_cast<u8>(new_volume));
|
||||
});
|
||||
connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
|
||||
@@ -1176,8 +1179,6 @@ void GMainWindow::ConnectWidgetEvents() {
|
||||
connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
|
||||
&GMainWindow::OnGameListAddDirectory);
|
||||
connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
|
||||
connect(game_list, &GameList::PopulatingCompleted,
|
||||
[this] { multiplayer_state->UpdateGameList(game_list->GetModel()); });
|
||||
|
||||
connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
|
||||
&GMainWindow::OnGameListOpenPerGameProperties);
|
||||
@@ -1195,9 +1196,6 @@ void GMainWindow::ConnectWidgetEvents() {
|
||||
connect(this, &GMainWindow::EmulationStopping, this, &GMainWindow::SoftwareKeyboardExit);
|
||||
|
||||
connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
|
||||
|
||||
connect(this, &GMainWindow::UpdateThemedIcons, multiplayer_state,
|
||||
&MultiplayerState::UpdateThemedIcons);
|
||||
}
|
||||
|
||||
void GMainWindow::ConnectMenuEvents() {
|
||||
@@ -1241,18 +1239,6 @@ void GMainWindow::ConnectMenuEvents() {
|
||||
ui->action_Reset_Window_Size_900,
|
||||
ui->action_Reset_Window_Size_1080});
|
||||
|
||||
// Multiplayer
|
||||
connect(ui->action_View_Lobby, &QAction::triggered, multiplayer_state,
|
||||
&MultiplayerState::OnViewLobby);
|
||||
connect(ui->action_Start_Room, &QAction::triggered, multiplayer_state,
|
||||
&MultiplayerState::OnCreateRoom);
|
||||
connect(ui->action_Leave_Room, &QAction::triggered, multiplayer_state,
|
||||
&MultiplayerState::OnCloseRoom);
|
||||
connect(ui->action_Connect_To_Room, &QAction::triggered, multiplayer_state,
|
||||
&MultiplayerState::OnDirectConnectToRoom);
|
||||
connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state,
|
||||
&MultiplayerState::OnOpenNetworkRoom);
|
||||
|
||||
// Tools
|
||||
connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
|
||||
ReinitializeKeyBehavior::Warning));
|
||||
@@ -2813,8 +2799,7 @@ void GMainWindow::OnConfigure() {
|
||||
const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue();
|
||||
|
||||
Settings::SetConfiguringGlobal(true);
|
||||
ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system,
|
||||
!multiplayer_state->IsHostingPublicRoom());
|
||||
ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system);
|
||||
connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this,
|
||||
&GMainWindow::OnLanguageChanged);
|
||||
|
||||
@@ -2871,11 +2856,6 @@ void GMainWindow::OnConfigure() {
|
||||
if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) {
|
||||
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
|
||||
}
|
||||
|
||||
if (!multiplayer_state->IsHostingPublicRoom()) {
|
||||
multiplayer_state->UpdateCredentials();
|
||||
}
|
||||
|
||||
emit UpdateThemedIcons();
|
||||
|
||||
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
|
||||
@@ -3696,7 +3676,6 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
|
||||
}
|
||||
|
||||
render_window->close();
|
||||
multiplayer_state->Close();
|
||||
|
||||
QWidget::closeEvent(event);
|
||||
}
|
||||
@@ -3893,7 +3872,6 @@ void GMainWindow::OnLanguageChanged(const QString& locale) {
|
||||
UISettings::values.language = locale;
|
||||
LoadTranslation();
|
||||
ui->retranslateUi(this);
|
||||
multiplayer_state->retranslateUi();
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
|
||||
@@ -3915,11 +3893,6 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
|
||||
#endif
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
bool has_broken_vulkan = false;
|
||||
if (StartupChecks(argv[0], &has_broken_vulkan)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Common::DetachedTasks detached_tasks;
|
||||
MicroProfileOnThreadCreate("Frontend");
|
||||
SCOPE_EXIT({ MicroProfileShutdown(); });
|
||||
@@ -3959,7 +3932,7 @@ int main(int argc, char* argv[]) {
|
||||
// generating shaders
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
GMainWindow main_window{has_broken_vulkan};
|
||||
GMainWindow main_window{};
|
||||
// After settings have been loaded by GMainWindow, apply the filter
|
||||
main_window.show();
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
|
||||
#include "common/announce_multiplayer_room.h"
|
||||
#include "common/common_types.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
@@ -23,7 +22,6 @@
|
||||
#endif
|
||||
|
||||
class Config;
|
||||
class ClickableLabel;
|
||||
class EmuThread;
|
||||
class GameList;
|
||||
class GImageInfo;
|
||||
@@ -33,7 +31,6 @@ class MicroProfileDialog;
|
||||
class ProfilerWidget;
|
||||
class ControllerDialog;
|
||||
class QLabel;
|
||||
class MultiplayerState;
|
||||
class QPushButton;
|
||||
class QProgressDialog;
|
||||
class WaitTreeWidget;
|
||||
@@ -121,7 +118,7 @@ class GMainWindow : public QMainWindow {
|
||||
public:
|
||||
void filterBarSetChecked(bool state);
|
||||
void UpdateUITheme();
|
||||
explicit GMainWindow(bool has_broken_vulkan);
|
||||
explicit GMainWindow();
|
||||
~GMainWindow() override;
|
||||
|
||||
bool DropAction(QDropEvent* event);
|
||||
@@ -203,8 +200,6 @@ private:
|
||||
void ConnectMenuEvents();
|
||||
void UpdateMenuState();
|
||||
|
||||
MultiplayerState* multiplayer_state = nullptr;
|
||||
|
||||
void PreventOSSleep();
|
||||
void AllowOSSleep();
|
||||
|
||||
|
||||
@@ -154,7 +154,6 @@
|
||||
<addaction name="menu_Emulation"/>
|
||||
<addaction name="menu_View"/>
|
||||
<addaction name="menu_Tools"/>
|
||||
<addaction name="menu_Multiplayer"/>
|
||||
<addaction name="menu_Help"/>
|
||||
</widget>
|
||||
<action name="action_Install_File_NAND">
|
||||
@@ -246,43 +245,6 @@
|
||||
<string>Show Status Bar</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_View_Lobby">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browse Public Game Lobby</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Start_Room">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create Room</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Leave_Room">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Leave Room</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Connect_To_Room">
|
||||
<property name="text">
|
||||
<string>Direct Connect to Room</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Show_Room">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show Current Room</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Fullscreen">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -1,491 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <future>
|
||||
#include <QColor>
|
||||
#include <QDesktopServices>
|
||||
#include <QFutureWatcher>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QLocale>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QMetaType>
|
||||
#include <QTime>
|
||||
#include <QUrl>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/announce_multiplayer_session.h"
|
||||
#include "ui_chat_room.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
#include "yuzu/multiplayer/chat_room.h"
|
||||
#include "yuzu/multiplayer/message.h"
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
#include "web_service/web_backend.h"
|
||||
#endif
|
||||
|
||||
class ChatMessage {
|
||||
public:
|
||||
explicit ChatMessage(const Network::ChatEntry& chat, Network::RoomNetwork& room_network,
|
||||
QTime ts = {}) {
|
||||
/// Convert the time to their default locale defined format
|
||||
QLocale locale;
|
||||
timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat);
|
||||
nickname = QString::fromStdString(chat.nickname);
|
||||
username = QString::fromStdString(chat.username);
|
||||
message = QString::fromStdString(chat.message);
|
||||
|
||||
// Check for user pings
|
||||
QString cur_nickname, cur_username;
|
||||
if (auto room = room_network.GetRoomMember().lock()) {
|
||||
cur_nickname = QString::fromStdString(room->GetNickname());
|
||||
cur_username = QString::fromStdString(room->GetUsername());
|
||||
}
|
||||
|
||||
// Handle pings at the beginning and end of message
|
||||
QString fixed_message = QStringLiteral(" %1 ").arg(message);
|
||||
if (fixed_message.contains(QStringLiteral(" @%1 ").arg(cur_nickname)) ||
|
||||
(!cur_username.isEmpty() &&
|
||||
fixed_message.contains(QStringLiteral(" @%1 ").arg(cur_username)))) {
|
||||
|
||||
contains_ping = true;
|
||||
} else {
|
||||
contains_ping = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainsPing() const {
|
||||
return contains_ping;
|
||||
}
|
||||
|
||||
/// Format the message using the players color
|
||||
QString GetPlayerChatMessage(u16 player) const {
|
||||
auto color = player_color[player % 16];
|
||||
QString name;
|
||||
if (username.isEmpty() || username == nickname) {
|
||||
name = nickname;
|
||||
} else {
|
||||
name = QStringLiteral("%1 (%2)").arg(nickname, username);
|
||||
}
|
||||
|
||||
QString style, text_color;
|
||||
if (ContainsPing()) {
|
||||
// Add a background color to these messages
|
||||
style = QStringLiteral("background-color: %1").arg(QString::fromStdString(ping_color));
|
||||
// Add a font color
|
||||
text_color = QStringLiteral("color='#000000'");
|
||||
}
|
||||
|
||||
return QStringLiteral("[%1] <font color='%2'><%3></font> <font style='%4' "
|
||||
"%5>%6</font>")
|
||||
.arg(timestamp, QString::fromStdString(color), name.toHtmlEscaped(), style, text_color,
|
||||
message.toHtmlEscaped());
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::array<const char*, 16> player_color = {
|
||||
{"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222",
|
||||
"#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "FFFF00"}};
|
||||
static constexpr char ping_color[] = "#FFFF00";
|
||||
|
||||
QString timestamp;
|
||||
QString nickname;
|
||||
QString username;
|
||||
QString message;
|
||||
bool contains_ping;
|
||||
};
|
||||
|
||||
class StatusMessage {
|
||||
public:
|
||||
explicit StatusMessage(const QString& msg, QTime ts = {}) {
|
||||
/// Convert the time to their default locale defined format
|
||||
QLocale locale;
|
||||
timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat);
|
||||
message = msg;
|
||||
}
|
||||
|
||||
QString GetSystemChatMessage() const {
|
||||
return QStringLiteral("[%1] <font color='%2'>* %3</font>")
|
||||
.arg(timestamp, QString::fromStdString(system_color), message);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr const char system_color[] = "#FF8C00";
|
||||
QString timestamp;
|
||||
QString message;
|
||||
};
|
||||
|
||||
class PlayerListItem : public QStandardItem {
|
||||
public:
|
||||
static const int NicknameRole = Qt::UserRole + 1;
|
||||
static const int UsernameRole = Qt::UserRole + 2;
|
||||
static const int AvatarUrlRole = Qt::UserRole + 3;
|
||||
static const int GameNameRole = Qt::UserRole + 4;
|
||||
|
||||
PlayerListItem() = default;
|
||||
explicit PlayerListItem(const std::string& nickname, const std::string& username,
|
||||
const std::string& avatar_url, const std::string& game_name) {
|
||||
setEditable(false);
|
||||
setData(QString::fromStdString(nickname), NicknameRole);
|
||||
setData(QString::fromStdString(username), UsernameRole);
|
||||
setData(QString::fromStdString(avatar_url), AvatarUrlRole);
|
||||
if (game_name.empty()) {
|
||||
setData(QObject::tr("Not playing a game"), GameNameRole);
|
||||
} else {
|
||||
setData(QString::fromStdString(game_name), GameNameRole);
|
||||
}
|
||||
}
|
||||
|
||||
QVariant data(int role) const override {
|
||||
if (role != Qt::DisplayRole) {
|
||||
return QStandardItem::data(role);
|
||||
}
|
||||
QString name;
|
||||
const QString nickname = data(NicknameRole).toString();
|
||||
const QString username = data(UsernameRole).toString();
|
||||
if (username.isEmpty() || username == nickname) {
|
||||
name = nickname;
|
||||
} else {
|
||||
name = QStringLiteral("%1 (%2)").arg(nickname, username);
|
||||
}
|
||||
return QStringLiteral("%1\n %2").arg(name, data(GameNameRole).toString());
|
||||
}
|
||||
};
|
||||
|
||||
ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ChatRoom>()) {
|
||||
ui->setupUi(this);
|
||||
|
||||
// set the item_model for player_view
|
||||
|
||||
player_list = new QStandardItemModel(ui->player_view);
|
||||
ui->player_view->setModel(player_list);
|
||||
ui->player_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
// set a header to make it look better though there is only one column
|
||||
player_list->insertColumns(0, 1);
|
||||
player_list->setHeaderData(0, Qt::Horizontal, tr("Members"));
|
||||
|
||||
ui->chat_history->document()->setMaximumBlockCount(max_chat_lines);
|
||||
|
||||
// register the network structs to use in slots and signals
|
||||
qRegisterMetaType<Network::ChatEntry>();
|
||||
qRegisterMetaType<Network::StatusMessageEntry>();
|
||||
qRegisterMetaType<Network::RoomInformation>();
|
||||
qRegisterMetaType<Network::RoomMember::State>();
|
||||
|
||||
// Connect all the widgets to the appropriate events
|
||||
connect(ui->player_view, &QTreeView::customContextMenuRequested, this,
|
||||
&ChatRoom::PopupContextMenu);
|
||||
connect(ui->chat_message, &QLineEdit::returnPressed, this, &ChatRoom::OnSendChat);
|
||||
connect(ui->chat_message, &QLineEdit::textChanged, this, &ChatRoom::OnChatTextChanged);
|
||||
connect(ui->send_message, &QPushButton::clicked, this, &ChatRoom::OnSendChat);
|
||||
}
|
||||
|
||||
ChatRoom::~ChatRoom() = default;
|
||||
|
||||
void ChatRoom::Initialize(Network::RoomNetwork* room_network_) {
|
||||
room_network = room_network_;
|
||||
// setup the callbacks for network updates
|
||||
if (auto member = room_network->GetRoomMember().lock()) {
|
||||
member->BindOnChatMessageRecieved(
|
||||
[this](const Network::ChatEntry& chat) { emit ChatReceived(chat); });
|
||||
member->BindOnStatusMessageReceived(
|
||||
[this](const Network::StatusMessageEntry& status_message) {
|
||||
emit StatusMessageReceived(status_message);
|
||||
});
|
||||
connect(this, &ChatRoom::ChatReceived, this, &ChatRoom::OnChatReceive);
|
||||
connect(this, &ChatRoom::StatusMessageReceived, this, &ChatRoom::OnStatusMessageReceive);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRoom::SetModPerms(bool is_mod) {
|
||||
has_mod_perms = is_mod;
|
||||
}
|
||||
|
||||
void ChatRoom::RetranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
void ChatRoom::Clear() {
|
||||
ui->chat_history->clear();
|
||||
block_list.clear();
|
||||
}
|
||||
|
||||
void ChatRoom::AppendStatusMessage(const QString& msg) {
|
||||
ui->chat_history->append(StatusMessage(msg).GetSystemChatMessage());
|
||||
}
|
||||
|
||||
void ChatRoom::AppendChatMessage(const QString& msg) {
|
||||
ui->chat_history->append(msg);
|
||||
}
|
||||
|
||||
void ChatRoom::SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname) {
|
||||
if (auto room = room_network->GetRoomMember().lock()) {
|
||||
auto members = room->GetMemberInformation();
|
||||
auto it = std::find_if(members.begin(), members.end(),
|
||||
[&nickname](const Network::RoomMember::MemberInformation& member) {
|
||||
return member.nickname == nickname;
|
||||
});
|
||||
if (it == members.end()) {
|
||||
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::NO_SUCH_USER);
|
||||
return;
|
||||
}
|
||||
room->SendModerationRequest(type, nickname);
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatRoom::ValidateMessage(const std::string& msg) {
|
||||
return !msg.empty();
|
||||
}
|
||||
|
||||
void ChatRoom::OnRoomUpdate(const Network::RoomInformation& info) {
|
||||
// TODO(B3N30): change title
|
||||
if (auto room_member = room_network->GetRoomMember().lock()) {
|
||||
SetPlayerList(room_member->GetMemberInformation());
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRoom::Disable() {
|
||||
ui->send_message->setDisabled(true);
|
||||
ui->chat_message->setDisabled(true);
|
||||
}
|
||||
|
||||
void ChatRoom::Enable() {
|
||||
ui->send_message->setEnabled(true);
|
||||
ui->chat_message->setEnabled(true);
|
||||
}
|
||||
|
||||
void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
|
||||
if (!ValidateMessage(chat.message)) {
|
||||
return;
|
||||
}
|
||||
if (auto room = room_network->GetRoomMember().lock()) {
|
||||
// get the id of the player
|
||||
auto members = room->GetMemberInformation();
|
||||
auto it = std::find_if(members.begin(), members.end(),
|
||||
[&chat](const Network::RoomMember::MemberInformation& member) {
|
||||
return member.nickname == chat.nickname &&
|
||||
member.username == chat.username;
|
||||
});
|
||||
if (it == members.end()) {
|
||||
LOG_INFO(Network, "Chat message received from unknown player. Ignoring it.");
|
||||
return;
|
||||
}
|
||||
if (block_list.count(chat.nickname)) {
|
||||
LOG_INFO(Network, "Chat message received from blocked player {}. Ignoring it.",
|
||||
chat.nickname);
|
||||
return;
|
||||
}
|
||||
auto player = std::distance(members.begin(), it);
|
||||
ChatMessage m(chat, *room_network);
|
||||
if (m.ContainsPing()) {
|
||||
emit UserPinged();
|
||||
}
|
||||
AppendChatMessage(m.GetPlayerChatMessage(player));
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_message) {
|
||||
QString name;
|
||||
if (status_message.username.empty() || status_message.username == status_message.nickname) {
|
||||
name = QString::fromStdString(status_message.nickname);
|
||||
} else {
|
||||
name = QStringLiteral("%1 (%2)").arg(QString::fromStdString(status_message.nickname),
|
||||
QString::fromStdString(status_message.username));
|
||||
}
|
||||
QString message;
|
||||
switch (status_message.type) {
|
||||
case Network::IdMemberJoin:
|
||||
message = tr("%1 has joined").arg(name);
|
||||
break;
|
||||
case Network::IdMemberLeave:
|
||||
message = tr("%1 has left").arg(name);
|
||||
break;
|
||||
case Network::IdMemberKicked:
|
||||
message = tr("%1 has been kicked").arg(name);
|
||||
break;
|
||||
case Network::IdMemberBanned:
|
||||
message = tr("%1 has been banned").arg(name);
|
||||
break;
|
||||
case Network::IdAddressUnbanned:
|
||||
message = tr("%1 has been unbanned").arg(name);
|
||||
break;
|
||||
}
|
||||
if (!message.isEmpty())
|
||||
AppendStatusMessage(message);
|
||||
}
|
||||
|
||||
void ChatRoom::OnSendChat() {
|
||||
if (auto room = room_network->GetRoomMember().lock()) {
|
||||
if (room->GetState() != Network::RoomMember::State::Joined &&
|
||||
room->GetState() != Network::RoomMember::State::Moderator) {
|
||||
|
||||
return;
|
||||
}
|
||||
auto message = ui->chat_message->text().toStdString();
|
||||
if (!ValidateMessage(message)) {
|
||||
return;
|
||||
}
|
||||
auto nick = room->GetNickname();
|
||||
auto username = room->GetUsername();
|
||||
Network::ChatEntry chat{nick, username, message};
|
||||
|
||||
auto members = room->GetMemberInformation();
|
||||
auto it = std::find_if(members.begin(), members.end(),
|
||||
[&chat](const Network::RoomMember::MemberInformation& member) {
|
||||
return member.nickname == chat.nickname &&
|
||||
member.username == chat.username;
|
||||
});
|
||||
if (it == members.end()) {
|
||||
LOG_INFO(Network, "Cannot find self in the player list when sending a message.");
|
||||
}
|
||||
auto player = std::distance(members.begin(), it);
|
||||
ChatMessage m(chat, *room_network);
|
||||
room->SendChatMessage(message);
|
||||
AppendChatMessage(m.GetPlayerChatMessage(player));
|
||||
ui->chat_message->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRoom::UpdateIconDisplay() {
|
||||
for (int row = 0; row < player_list->invisibleRootItem()->rowCount(); ++row) {
|
||||
QStandardItem* item = player_list->invisibleRootItem()->child(row);
|
||||
const std::string avatar_url =
|
||||
item->data(PlayerListItem::AvatarUrlRole).toString().toStdString();
|
||||
if (icon_cache.count(avatar_url)) {
|
||||
item->setData(icon_cache.at(avatar_url), Qt::DecorationRole);
|
||||
} else {
|
||||
item->setData(QIcon::fromTheme(QStringLiteral("no_avatar")).pixmap(48),
|
||||
Qt::DecorationRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRoom::SetPlayerList(const Network::RoomMember::MemberList& member_list) {
|
||||
// TODO(B3N30): Remember which row is selected
|
||||
player_list->removeRows(0, player_list->rowCount());
|
||||
for (const auto& member : member_list) {
|
||||
if (member.nickname.empty())
|
||||
continue;
|
||||
QStandardItem* name_item = new PlayerListItem(member.nickname, member.username,
|
||||
member.avatar_url, member.game_info.name);
|
||||
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
if (!icon_cache.count(member.avatar_url) && !member.avatar_url.empty()) {
|
||||
// Start a request to get the member's avatar
|
||||
const QUrl url(QString::fromStdString(member.avatar_url));
|
||||
QFuture<std::string> future = QtConcurrent::run([url] {
|
||||
WebService::Client client(
|
||||
QStringLiteral("%1://%2").arg(url.scheme(), url.host()).toStdString(), "", "");
|
||||
auto result = client.GetImage(url.path().toStdString(), true);
|
||||
if (result.returned_data.empty()) {
|
||||
LOG_ERROR(WebService, "Failed to get avatar");
|
||||
}
|
||||
return result.returned_data;
|
||||
});
|
||||
auto* future_watcher = new QFutureWatcher<std::string>(this);
|
||||
connect(future_watcher, &QFutureWatcher<std::string>::finished, this,
|
||||
[this, future_watcher, avatar_url = member.avatar_url] {
|
||||
const std::string result = future_watcher->result();
|
||||
if (result.empty())
|
||||
return;
|
||||
QPixmap pixmap;
|
||||
if (!pixmap.loadFromData(reinterpret_cast<const u8*>(result.data()),
|
||||
static_cast<uint>(result.size())))
|
||||
return;
|
||||
icon_cache[avatar_url] =
|
||||
pixmap.scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
// Update all the displayed icons with the new icon_cache
|
||||
UpdateIconDisplay();
|
||||
});
|
||||
future_watcher->setFuture(future);
|
||||
}
|
||||
#endif
|
||||
|
||||
player_list->invisibleRootItem()->appendRow(name_item);
|
||||
}
|
||||
UpdateIconDisplay();
|
||||
// TODO(B3N30): Restore row selection
|
||||
}
|
||||
|
||||
void ChatRoom::OnChatTextChanged() {
|
||||
if (ui->chat_message->text().length() > static_cast<int>(Network::MaxMessageSize))
|
||||
ui->chat_message->setText(
|
||||
ui->chat_message->text().left(static_cast<int>(Network::MaxMessageSize)));
|
||||
}
|
||||
|
||||
void ChatRoom::PopupContextMenu(const QPoint& menu_location) {
|
||||
QModelIndex item = ui->player_view->indexAt(menu_location);
|
||||
if (!item.isValid())
|
||||
return;
|
||||
|
||||
std::string nickname =
|
||||
player_list->item(item.row())->data(PlayerListItem::NicknameRole).toString().toStdString();
|
||||
|
||||
QMenu context_menu;
|
||||
|
||||
QString username = player_list->item(item.row())->data(PlayerListItem::UsernameRole).toString();
|
||||
if (!username.isEmpty()) {
|
||||
QAction* view_profile_action = context_menu.addAction(tr("View Profile"));
|
||||
connect(view_profile_action, &QAction::triggered, [username] {
|
||||
QDesktopServices::openUrl(
|
||||
QUrl(QStringLiteral("https://community.citra-emu.org/u/%1").arg(username)));
|
||||
});
|
||||
}
|
||||
|
||||
std::string cur_nickname;
|
||||
if (auto room = room_network->GetRoomMember().lock()) {
|
||||
cur_nickname = room->GetNickname();
|
||||
}
|
||||
|
||||
if (nickname != cur_nickname) { // You can't block yourself
|
||||
QAction* block_action = context_menu.addAction(tr("Block Player"));
|
||||
|
||||
block_action->setCheckable(true);
|
||||
block_action->setChecked(block_list.count(nickname) > 0);
|
||||
|
||||
connect(block_action, &QAction::triggered, [this, nickname] {
|
||||
if (block_list.count(nickname)) {
|
||||
block_list.erase(nickname);
|
||||
} else {
|
||||
QMessageBox::StandardButton result = QMessageBox::question(
|
||||
this, tr("Block Player"),
|
||||
tr("When you block a player, you will no longer receive chat messages from "
|
||||
"them.<br><br>Are you sure you would like to block %1?")
|
||||
.arg(QString::fromStdString(nickname)),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (result == QMessageBox::Yes)
|
||||
block_list.emplace(nickname);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (has_mod_perms && nickname != cur_nickname) { // You can't kick or ban yourself
|
||||
context_menu.addSeparator();
|
||||
|
||||
QAction* kick_action = context_menu.addAction(tr("Kick"));
|
||||
QAction* ban_action = context_menu.addAction(tr("Ban"));
|
||||
|
||||
connect(kick_action, &QAction::triggered, [this, nickname] {
|
||||
QMessageBox::StandardButton result =
|
||||
QMessageBox::question(this, tr("Kick Player"),
|
||||
tr("Are you sure you would like to <b>kick</b> %1?")
|
||||
.arg(QString::fromStdString(nickname)),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (result == QMessageBox::Yes)
|
||||
SendModerationRequest(Network::IdModKick, nickname);
|
||||
});
|
||||
connect(ban_action, &QAction::triggered, [this, nickname] {
|
||||
QMessageBox::StandardButton result = QMessageBox::question(
|
||||
this, tr("Ban Player"),
|
||||
tr("Are you sure you would like to <b>kick and ban</b> %1?\n\nThis would "
|
||||
"ban both their forum username and their IP address.")
|
||||
.arg(QString::fromStdString(nickname)),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (result == QMessageBox::Yes)
|
||||
SendModerationRequest(Network::IdModBan, nickname);
|
||||
});
|
||||
}
|
||||
|
||||
context_menu.exec(ui->player_view->viewport()->mapToGlobal(menu_location));
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
#include <QDialog>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStandardItemModel>
|
||||
#include <QVariant>
|
||||
#include "network/network.h"
|
||||
|
||||
namespace Ui {
|
||||
class ChatRoom;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class AnnounceMultiplayerSession;
|
||||
}
|
||||
|
||||
class ConnectionError;
|
||||
class ComboBoxProxyModel;
|
||||
|
||||
class ChatMessage;
|
||||
|
||||
class ChatRoom : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ChatRoom(QWidget* parent);
|
||||
void Initialize(Network::RoomNetwork* room_network);
|
||||
void RetranslateUi();
|
||||
void SetPlayerList(const Network::RoomMember::MemberList& member_list);
|
||||
void Clear();
|
||||
void AppendStatusMessage(const QString& msg);
|
||||
~ChatRoom();
|
||||
|
||||
void SetModPerms(bool is_mod);
|
||||
void UpdateIconDisplay();
|
||||
|
||||
public slots:
|
||||
void OnRoomUpdate(const Network::RoomInformation& info);
|
||||
void OnChatReceive(const Network::ChatEntry&);
|
||||
void OnStatusMessageReceive(const Network::StatusMessageEntry&);
|
||||
void OnSendChat();
|
||||
void OnChatTextChanged();
|
||||
void PopupContextMenu(const QPoint& menu_location);
|
||||
void Disable();
|
||||
void Enable();
|
||||
|
||||
signals:
|
||||
void ChatReceived(const Network::ChatEntry&);
|
||||
void StatusMessageReceived(const Network::StatusMessageEntry&);
|
||||
void UserPinged();
|
||||
|
||||
private:
|
||||
static constexpr u32 max_chat_lines = 1000;
|
||||
void AppendChatMessage(const QString&);
|
||||
bool ValidateMessage(const std::string&);
|
||||
void SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname);
|
||||
|
||||
bool has_mod_perms = false;
|
||||
QStandardItemModel* player_list;
|
||||
std::unique_ptr<Ui::ChatRoom> ui;
|
||||
std::unordered_set<std::string> block_list;
|
||||
std::unordered_map<std::string, QPixmap> icon_cache;
|
||||
Network::RoomNetwork* room_network;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Network::ChatEntry);
|
||||
Q_DECLARE_METATYPE(Network::StatusMessageEntry);
|
||||
Q_DECLARE_METATYPE(Network::RoomInformation);
|
||||
Q_DECLARE_METATYPE(Network::RoomMember::State);
|
||||
Q_DECLARE_METATYPE(Network::RoomMember::Error);
|
||||
@@ -1,59 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ChatRoom</class>
|
||||
<widget class="QWidget" name="ChatRoom">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>807</width>
|
||||
<height>432</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Room Window</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QTreeView" name="player_view"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="chat_history">
|
||||
<property name="undoRedoEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="chat_message">
|
||||
<property name="placeholderText">
|
||||
<string>Send Chat Message</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="send_message">
|
||||
<property name="text">
|
||||
<string>Send Message</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,115 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <future>
|
||||
#include <QColor>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QLocale>
|
||||
#include <QMetaType>
|
||||
#include <QTime>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/announce_multiplayer_session.h"
|
||||
#include "ui_client_room.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
#include "yuzu/multiplayer/client_room.h"
|
||||
#include "yuzu/multiplayer/message.h"
|
||||
#include "yuzu/multiplayer/moderation_dialog.h"
|
||||
#include "yuzu/multiplayer/state.h"
|
||||
|
||||
ClientRoomWindow::ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||
ui(std::make_unique<Ui::ClientRoom>()), room_network{room_network_} {
|
||||
ui->setupUi(this);
|
||||
ui->chat->Initialize(&room_network);
|
||||
|
||||
// setup the callbacks for network updates
|
||||
if (auto member = room_network.GetRoomMember().lock()) {
|
||||
member->BindOnRoomInformationChanged(
|
||||
[this](const Network::RoomInformation& info) { emit RoomInformationChanged(info); });
|
||||
member->BindOnStateChanged(
|
||||
[this](const Network::RoomMember::State& state) { emit StateChanged(state); });
|
||||
|
||||
connect(this, &ClientRoomWindow::RoomInformationChanged, this,
|
||||
&ClientRoomWindow::OnRoomUpdate);
|
||||
connect(this, &ClientRoomWindow::StateChanged, this, &::ClientRoomWindow::OnStateChange);
|
||||
// Update the state
|
||||
OnStateChange(member->GetState());
|
||||
} else {
|
||||
// TODO (jroweboy) network was not initialized?
|
||||
}
|
||||
|
||||
connect(ui->disconnect, &QPushButton::clicked, this, &ClientRoomWindow::Disconnect);
|
||||
ui->disconnect->setDefault(false);
|
||||
ui->disconnect->setAutoDefault(false);
|
||||
connect(ui->moderation, &QPushButton::clicked, [this] {
|
||||
ModerationDialog dialog(room_network, this);
|
||||
dialog.exec();
|
||||
});
|
||||
ui->moderation->setDefault(false);
|
||||
ui->moderation->setAutoDefault(false);
|
||||
connect(ui->chat, &ChatRoom::UserPinged, this, &ClientRoomWindow::ShowNotification);
|
||||
UpdateView();
|
||||
}
|
||||
|
||||
ClientRoomWindow::~ClientRoomWindow() = default;
|
||||
|
||||
void ClientRoomWindow::SetModPerms(bool is_mod) {
|
||||
ui->chat->SetModPerms(is_mod);
|
||||
ui->moderation->setVisible(is_mod);
|
||||
ui->moderation->setDefault(false);
|
||||
ui->moderation->setAutoDefault(false);
|
||||
}
|
||||
|
||||
void ClientRoomWindow::RetranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
ui->chat->RetranslateUi();
|
||||
}
|
||||
|
||||
void ClientRoomWindow::OnRoomUpdate(const Network::RoomInformation& info) {
|
||||
UpdateView();
|
||||
}
|
||||
|
||||
void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) {
|
||||
if (state == Network::RoomMember::State::Joined ||
|
||||
state == Network::RoomMember::State::Moderator) {
|
||||
|
||||
ui->chat->Clear();
|
||||
ui->chat->AppendStatusMessage(tr("Connected"));
|
||||
SetModPerms(state == Network::RoomMember::State::Moderator);
|
||||
}
|
||||
UpdateView();
|
||||
}
|
||||
|
||||
void ClientRoomWindow::Disconnect() {
|
||||
auto parent = static_cast<MultiplayerState*>(parentWidget());
|
||||
if (parent->OnCloseRoom()) {
|
||||
ui->chat->AppendStatusMessage(tr("Disconnected"));
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
void ClientRoomWindow::UpdateView() {
|
||||
if (auto member = room_network.GetRoomMember().lock()) {
|
||||
if (member->IsConnected()) {
|
||||
ui->chat->Enable();
|
||||
ui->disconnect->setEnabled(true);
|
||||
auto memberlist = member->GetMemberInformation();
|
||||
ui->chat->SetPlayerList(memberlist);
|
||||
const auto information = member->GetRoomInformation();
|
||||
setWindowTitle(QString(tr("%1 (%2/%3 members) - connected"))
|
||||
.arg(QString::fromStdString(information.name))
|
||||
.arg(memberlist.size())
|
||||
.arg(information.member_slots));
|
||||
ui->description->setText(QString::fromStdString(information.description));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// TODO(B3N30): can't get RoomMember*, show error and close window
|
||||
close();
|
||||
}
|
||||
|
||||
void ClientRoomWindow::UpdateIconDisplay() {
|
||||
ui->chat->UpdateIconDisplay();
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "yuzu/multiplayer/chat_room.h"
|
||||
|
||||
namespace Ui {
|
||||
class ClientRoom;
|
||||
}
|
||||
|
||||
class ClientRoomWindow : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_);
|
||||
~ClientRoomWindow();
|
||||
|
||||
void RetranslateUi();
|
||||
void UpdateIconDisplay();
|
||||
|
||||
public slots:
|
||||
void OnRoomUpdate(const Network::RoomInformation&);
|
||||
void OnStateChange(const Network::RoomMember::State&);
|
||||
|
||||
signals:
|
||||
void RoomInformationChanged(const Network::RoomInformation&);
|
||||
void StateChanged(const Network::RoomMember::State&);
|
||||
void ShowNotification();
|
||||
|
||||
private:
|
||||
void Disconnect();
|
||||
void UpdateView();
|
||||
void SetModPerms(bool is_mod);
|
||||
|
||||
QStandardItemModel* player_list;
|
||||
std::unique_ptr<Ui::ClientRoom> ui;
|
||||
Network::RoomNetwork& room_network;
|
||||
};
|
||||
@@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ClientRoom</class>
|
||||
<widget class="QWidget" name="ClientRoom">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>807</width>
|
||||
<height>432</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Room Window</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="description">
|
||||
<property name="text">
|
||||
<string>Room Description</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="moderation">
|
||||
<property name="text">
|
||||
<string>Moderation...</string>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="disconnect">
|
||||
<property name="text">
|
||||
<string>Leave Room</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ChatRoom" name="chat" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ChatRoom</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>multiplayer/chat_room.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,130 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QFuture>
|
||||
#include <QIntValidator>
|
||||
#include <QRegExpValidator>
|
||||
#include <QString>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include "common/settings.h"
|
||||
#include "network/network.h"
|
||||
#include "ui_direct_connect.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/multiplayer/client_room.h"
|
||||
#include "yuzu/multiplayer/direct_connect.h"
|
||||
#include "yuzu/multiplayer/message.h"
|
||||
#include "yuzu/multiplayer/state.h"
|
||||
#include "yuzu/multiplayer/validation.h"
|
||||
#include "yuzu/uisettings.h"
|
||||
|
||||
enum class ConnectionType : u8 { TraversalServer, IP };
|
||||
|
||||
DirectConnectWindow::DirectConnectWindow(Network::RoomNetwork& room_network_, QWidget* parent)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||
ui(std::make_unique<Ui::DirectConnect>()), room_network{room_network_} {
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
// setup the watcher for background connections
|
||||
watcher = new QFutureWatcher<void>;
|
||||
connect(watcher, &QFutureWatcher<void>::finished, this, &DirectConnectWindow::OnConnection);
|
||||
|
||||
ui->nickname->setValidator(validation.GetNickname());
|
||||
ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
|
||||
if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
|
||||
// Use yuzu Web Service user name as nickname by default
|
||||
ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
|
||||
}
|
||||
ui->ip->setValidator(validation.GetIP());
|
||||
ui->ip->setText(UISettings::values.multiplayer_ip.GetValue());
|
||||
ui->port->setValidator(validation.GetPort());
|
||||
ui->port->setText(QString::number(UISettings::values.multiplayer_port.GetValue()));
|
||||
|
||||
// TODO(jroweboy): Show or hide the connection options based on the current value of the combo
|
||||
// box. Add this back in when the traversal server support is added.
|
||||
connect(ui->connect, &QPushButton::clicked, this, &DirectConnectWindow::Connect);
|
||||
}
|
||||
|
||||
DirectConnectWindow::~DirectConnectWindow() = default;
|
||||
|
||||
void DirectConnectWindow::RetranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
void DirectConnectWindow::Connect() {
|
||||
if (!ui->nickname->hasAcceptableInput()) {
|
||||
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
|
||||
return;
|
||||
}
|
||||
if (const auto member = room_network.GetRoomMember().lock()) {
|
||||
// Prevent the user from trying to join a room while they are already joining.
|
||||
if (member->GetState() == Network::RoomMember::State::Joining) {
|
||||
return;
|
||||
} else if (member->IsConnected()) {
|
||||
// And ask if they want to leave the room if they are already in one.
|
||||
if (!NetworkMessage::WarnDisconnect()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (static_cast<ConnectionType>(ui->connection_type->currentIndex())) {
|
||||
case ConnectionType::TraversalServer:
|
||||
break;
|
||||
case ConnectionType::IP:
|
||||
if (!ui->ip->hasAcceptableInput()) {
|
||||
NetworkMessage::ErrorManager::ShowError(
|
||||
NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID);
|
||||
return;
|
||||
}
|
||||
if (!ui->port->hasAcceptableInput()) {
|
||||
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Store settings
|
||||
UISettings::values.multiplayer_nickname = ui->nickname->text();
|
||||
UISettings::values.multiplayer_ip = ui->ip->text();
|
||||
if (ui->port->isModified() && !ui->port->text().isEmpty()) {
|
||||
UISettings::values.multiplayer_port = ui->port->text().toInt();
|
||||
} else {
|
||||
UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault();
|
||||
}
|
||||
|
||||
// attempt to connect in a different thread
|
||||
QFuture<void> f = QtConcurrent::run([&] {
|
||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
||||
auto port = UISettings::values.multiplayer_port.GetValue();
|
||||
room_member->Join(ui->nickname->text().toStdString(), "",
|
||||
ui->ip->text().toStdString().c_str(), port, 0,
|
||||
Network::NoPreferredMac, ui->password->text().toStdString().c_str());
|
||||
}
|
||||
});
|
||||
watcher->setFuture(f);
|
||||
// and disable widgets and display a connecting while we wait
|
||||
BeginConnecting();
|
||||
}
|
||||
|
||||
void DirectConnectWindow::BeginConnecting() {
|
||||
ui->connect->setEnabled(false);
|
||||
ui->connect->setText(tr("Connecting"));
|
||||
}
|
||||
|
||||
void DirectConnectWindow::EndConnecting() {
|
||||
ui->connect->setEnabled(true);
|
||||
ui->connect->setText(tr("Connect"));
|
||||
}
|
||||
|
||||
void DirectConnectWindow::OnConnection() {
|
||||
EndConnecting();
|
||||
|
||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
||||
if (room_member->GetState() == Network::RoomMember::State::Joined ||
|
||||
room_member->GetState() == Network::RoomMember::State::Moderator) {
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QDialog>
|
||||
#include <QFutureWatcher>
|
||||
#include "yuzu/multiplayer/validation.h"
|
||||
|
||||
namespace Ui {
|
||||
class DirectConnect;
|
||||
}
|
||||
|
||||
class DirectConnectWindow : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DirectConnectWindow(Network::RoomNetwork& room_network_, QWidget* parent = nullptr);
|
||||
~DirectConnectWindow();
|
||||
|
||||
void RetranslateUi();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* Signalled by this widget when it is closing itself and destroying any state such as
|
||||
* connections that it might have.
|
||||
*/
|
||||
void Closed();
|
||||
|
||||
private slots:
|
||||
void OnConnection();
|
||||
|
||||
private:
|
||||
void Connect();
|
||||
void BeginConnecting();
|
||||
void EndConnecting();
|
||||
|
||||
QFutureWatcher<void>* watcher;
|
||||
std::unique_ptr<Ui::DirectConnect> ui;
|
||||
Validation validation;
|
||||
Network::RoomNetwork& room_network;
|
||||
};
|
||||
@@ -1,168 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DirectConnect</class>
|
||||
<widget class="QWidget" name="DirectConnect">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>455</width>
|
||||
<height>161</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Direct Connect</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="connection_type">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>IP Address</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="ip_container" native="true">
|
||||
<layout class="QHBoxLayout" name="ip_layout">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>IP</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="ip">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>IPv4 address of the host</p></body></html></string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>16</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Port</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="port">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Port number the host is listening on</p></body></html></string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>24872</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Nickname</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="nickname">
|
||||
<property name="maxLength">
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="password"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="connect">
|
||||
<property name="text">
|
||||
<string>Connect</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,246 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <future>
|
||||
#include <QColor>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QLocale>
|
||||
#include <QMessageBox>
|
||||
#include <QMetaType>
|
||||
#include <QTime>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/announce_multiplayer_session.h"
|
||||
#include "ui_host_room.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/multiplayer/host_room.h"
|
||||
#include "yuzu/multiplayer/message.h"
|
||||
#include "yuzu/multiplayer/state.h"
|
||||
#include "yuzu/multiplayer/validation.h"
|
||||
#include "yuzu/uisettings.h"
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
#include "web_service/verify_user_jwt.h"
|
||||
#endif
|
||||
|
||||
HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
|
||||
std::shared_ptr<Core::AnnounceMultiplayerSession> session,
|
||||
Network::RoomNetwork& room_network_)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||
ui(std::make_unique<Ui::HostRoom>()),
|
||||
announce_multiplayer_session(session), room_network{room_network_} {
|
||||
ui->setupUi(this);
|
||||
|
||||
// set up validation for all of the fields
|
||||
ui->room_name->setValidator(validation.GetRoomName());
|
||||
ui->username->setValidator(validation.GetNickname());
|
||||
ui->port->setValidator(validation.GetPort());
|
||||
ui->port->setPlaceholderText(QString::number(Network::DefaultRoomPort));
|
||||
|
||||
// Create a proxy to the game list to display the list of preferred games
|
||||
game_list = new QStandardItemModel;
|
||||
UpdateGameList(list);
|
||||
|
||||
proxy = new ComboBoxProxyModel;
|
||||
proxy->setSourceModel(game_list);
|
||||
proxy->sort(0, Qt::AscendingOrder);
|
||||
ui->game_list->setModel(proxy);
|
||||
|
||||
// Connect all the widgets to the appropriate events
|
||||
connect(ui->host, &QPushButton::clicked, this, &HostRoomWindow::Host);
|
||||
|
||||
// Restore the settings:
|
||||
ui->username->setText(UISettings::values.multiplayer_room_nickname.GetValue());
|
||||
if (ui->username->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
|
||||
// Use yuzu Web Service user name as nickname by default
|
||||
ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
|
||||
}
|
||||
ui->room_name->setText(UISettings::values.multiplayer_room_name.GetValue());
|
||||
ui->port->setText(QString::number(UISettings::values.multiplayer_room_port.GetValue()));
|
||||
ui->max_player->setValue(UISettings::values.multiplayer_max_player.GetValue());
|
||||
int index = UISettings::values.multiplayer_host_type.GetValue();
|
||||
if (index < ui->host_type->count()) {
|
||||
ui->host_type->setCurrentIndex(index);
|
||||
}
|
||||
index = ui->game_list->findData(UISettings::values.multiplayer_game_id.GetValue(),
|
||||
GameListItemPath::ProgramIdRole);
|
||||
if (index != -1) {
|
||||
ui->game_list->setCurrentIndex(index);
|
||||
}
|
||||
ui->room_description->setText(UISettings::values.multiplayer_room_description.GetValue());
|
||||
}
|
||||
|
||||
HostRoomWindow::~HostRoomWindow() = default;
|
||||
|
||||
void HostRoomWindow::UpdateGameList(QStandardItemModel* list) {
|
||||
game_list->clear();
|
||||
for (int i = 0; i < list->rowCount(); i++) {
|
||||
auto parent = list->item(i, 0);
|
||||
for (int j = 0; j < parent->rowCount(); j++) {
|
||||
game_list->appendRow(parent->child(j)->clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HostRoomWindow::RetranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
std::unique_ptr<Network::VerifyUser::Backend> HostRoomWindow::CreateVerifyBackend(
|
||||
bool use_validation) const {
|
||||
std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
|
||||
if (use_validation) {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
verify_backend =
|
||||
std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue());
|
||||
#else
|
||||
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
|
||||
#endif
|
||||
} else {
|
||||
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
|
||||
}
|
||||
return verify_backend;
|
||||
}
|
||||
|
||||
void HostRoomWindow::Host() {
|
||||
if (!ui->username->hasAcceptableInput()) {
|
||||
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
|
||||
return;
|
||||
}
|
||||
if (!ui->room_name->hasAcceptableInput()) {
|
||||
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOMNAME_NOT_VALID);
|
||||
return;
|
||||
}
|
||||
if (!ui->port->hasAcceptableInput()) {
|
||||
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID);
|
||||
return;
|
||||
}
|
||||
if (ui->game_list->currentIndex() == -1) {
|
||||
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::GAME_NOT_SELECTED);
|
||||
return;
|
||||
}
|
||||
if (auto member = room_network.GetRoomMember().lock()) {
|
||||
if (member->GetState() == Network::RoomMember::State::Joining) {
|
||||
return;
|
||||
} else if (member->IsConnected()) {
|
||||
auto parent = static_cast<MultiplayerState*>(parentWidget());
|
||||
if (!parent->OnCloseRoom()) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
ui->host->setDisabled(true);
|
||||
|
||||
const AnnounceMultiplayerRoom::GameInfo game{
|
||||
.name = ui->game_list->currentData(Qt::DisplayRole).toString().toStdString(),
|
||||
.id = ui->game_list->currentData(GameListItemPath::ProgramIdRole).toULongLong(),
|
||||
};
|
||||
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;
|
||||
Network::Room::BanList ban_list{};
|
||||
if (ui->load_ban_list->isChecked()) {
|
||||
ban_list = UISettings::values.multiplayer_ban_list;
|
||||
}
|
||||
if (auto room = room_network.GetRoom().lock()) {
|
||||
const bool created =
|
||||
room->Create(ui->room_name->text().toStdString(),
|
||||
ui->room_description->toPlainText().toStdString(), "", port, password,
|
||||
ui->max_player->value(), Settings::values.yuzu_username.GetValue(),
|
||||
game, CreateVerifyBackend(is_public), ban_list);
|
||||
if (!created) {
|
||||
NetworkMessage::ErrorManager::ShowError(
|
||||
NetworkMessage::ErrorManager::COULD_NOT_CREATE_ROOM);
|
||||
LOG_ERROR(Network, "Could not create room!");
|
||||
ui->host->setEnabled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Start the announce session if they chose Public
|
||||
if (is_public) {
|
||||
if (auto session = announce_multiplayer_session.lock()) {
|
||||
// Register the room first to ensure verify_uid is present when we connect
|
||||
WebService::WebResult result = session->Register();
|
||||
if (result.result_code != WebService::WebResult::Code::Success) {
|
||||
QMessageBox::warning(
|
||||
this, tr("Error"),
|
||||
tr("Failed to announce the room to the public lobby. In order to host a "
|
||||
"room publicly, you must have a valid yuzu account configured in "
|
||||
"Emulation -> Configure -> Web. If you do not want to publish a room in "
|
||||
"the public lobby, then select Unlisted instead.\nDebug Message: ") +
|
||||
QString::fromStdString(result.result_string),
|
||||
QMessageBox::Ok);
|
||||
ui->host->setEnabled(true);
|
||||
if (auto room = room_network.GetRoom().lock()) {
|
||||
room->Destroy();
|
||||
}
|
||||
return;
|
||||
}
|
||||
session->Start();
|
||||
} else {
|
||||
LOG_ERROR(Network, "Starting announce session failed");
|
||||
}
|
||||
}
|
||||
std::string token;
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
if (is_public) {
|
||||
WebService::Client client(Settings::values.web_api_url.GetValue(),
|
||||
Settings::values.yuzu_username.GetValue(),
|
||||
Settings::values.yuzu_token.GetValue());
|
||||
if (auto room = room_network.GetRoom().lock()) {
|
||||
token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
|
||||
}
|
||||
if (token.empty()) {
|
||||
LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
|
||||
} else {
|
||||
LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// TODO: Check what to do with this
|
||||
member->Join(ui->username->text().toStdString(), "", "127.0.0.1", port, 0,
|
||||
Network::NoPreferredMac, password, token);
|
||||
|
||||
// Store settings
|
||||
UISettings::values.multiplayer_room_nickname = ui->username->text();
|
||||
UISettings::values.multiplayer_room_name = ui->room_name->text();
|
||||
UISettings::values.multiplayer_game_id =
|
||||
ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong();
|
||||
UISettings::values.multiplayer_max_player = ui->max_player->value();
|
||||
|
||||
UISettings::values.multiplayer_host_type = ui->host_type->currentIndex();
|
||||
if (ui->port->isModified() && !ui->port->text().isEmpty()) {
|
||||
UISettings::values.multiplayer_room_port = ui->port->text().toInt();
|
||||
} else {
|
||||
UISettings::values.multiplayer_room_port = Network::DefaultRoomPort;
|
||||
}
|
||||
UISettings::values.multiplayer_room_description = ui->room_description->toPlainText();
|
||||
ui->host->setEnabled(true);
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant ComboBoxProxyModel::data(const QModelIndex& idx, int role) const {
|
||||
if (role != Qt::DisplayRole) {
|
||||
auto val = QSortFilterProxyModel::data(idx, role);
|
||||
// If its the icon, shrink it to 16x16
|
||||
if (role == Qt::DecorationRole)
|
||||
val = val.value<QImage>().scaled(16, 16, Qt::KeepAspectRatio);
|
||||
return val;
|
||||
}
|
||||
std::string filename;
|
||||
Common::SplitPath(
|
||||
QSortFilterProxyModel::data(idx, GameListItemPath::FullPathRole).toString().toStdString(),
|
||||
nullptr, &filename, nullptr);
|
||||
QString title = QSortFilterProxyModel::data(idx, GameListItemPath::TitleRole).toString();
|
||||
return title.isEmpty() ? QString::fromStdString(filename) : title;
|
||||
}
|
||||
|
||||
bool ComboBoxProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
|
||||
auto leftData = left.data(GameListItemPath::TitleRole).toString();
|
||||
auto rightData = right.data(GameListItemPath::TitleRole).toString();
|
||||
return leftData.compare(rightData) < 0;
|
||||
}
|
||||