358 lines
12 KiB
C++
358 lines
12 KiB
C++
// Copyright 2010 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#pragma once
|
|
|
|
#include <SFML/Network/Packet.hpp>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <span>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Event.h"
|
|
#include "Common/SPSCQueue.h"
|
|
#include "Common/TraversalClient.h"
|
|
#include "Core/NetPlayProto.h"
|
|
#include "Core/SyncIdentifier.h"
|
|
#include "InputCommon/GCPadStatus.h"
|
|
|
|
class BootSessionData;
|
|
|
|
namespace IOS::HLE::FS
|
|
{
|
|
class FileSystem;
|
|
}
|
|
|
|
namespace UICommon
|
|
{
|
|
class GameFile;
|
|
}
|
|
|
|
namespace WiimoteEmu
|
|
{
|
|
struct SerializedWiimoteState;
|
|
}
|
|
|
|
namespace NetPlay
|
|
{
|
|
class NetPlayUI
|
|
{
|
|
public:
|
|
virtual ~NetPlayUI() {}
|
|
virtual void BootGame(const std::string& filename,
|
|
std::unique_ptr<BootSessionData> boot_session_data) = 0;
|
|
virtual void StopGame() = 0;
|
|
virtual bool IsHosting() const = 0;
|
|
|
|
virtual void Update() = 0;
|
|
virtual void AppendChat(const std::string& msg) = 0;
|
|
|
|
virtual void OnMsgChangeGame(const SyncIdentifier& sync_identifier,
|
|
const std::string& netplay_name) = 0;
|
|
virtual void OnMsgChangeGBARom(int pad, const NetPlay::GBAConfig& config) = 0;
|
|
virtual void OnMsgStartGame() = 0;
|
|
virtual void OnMsgStopGame() = 0;
|
|
virtual void OnMsgPowerButton() = 0;
|
|
virtual void OnPlayerConnect(const std::string& player) = 0;
|
|
virtual void OnPlayerDisconnect(const std::string& player) = 0;
|
|
virtual void OnPadBufferChanged(u32 buffer) = 0;
|
|
virtual void OnHostInputAuthorityChanged(bool enabled) = 0;
|
|
virtual void OnDesync(u32 frame, const std::string& player) = 0;
|
|
virtual void OnConnectionLost() = 0;
|
|
virtual void OnConnectionError(const std::string& message) = 0;
|
|
virtual void OnTraversalError(Common::TraversalClient::FailureReason error) = 0;
|
|
virtual void OnTraversalStateChanged(Common::TraversalClient::State state) = 0;
|
|
virtual void OnGameStartAborted() = 0;
|
|
virtual void OnGolferChanged(bool is_golfer, const std::string& golfer_name) = 0;
|
|
|
|
virtual bool IsRecording() = 0;
|
|
virtual std::shared_ptr<const UICommon::GameFile>
|
|
FindGameFile(const SyncIdentifier& sync_identifier,
|
|
SyncIdentifierComparison* found = nullptr) = 0;
|
|
virtual std::string FindGBARomPath(const std::array<u8, 20>& hash, std::string_view title,
|
|
int device_number) = 0;
|
|
virtual void ShowGameDigestDialog(const std::string& title) = 0;
|
|
virtual void SetGameDigestProgress(int pid, int progress) = 0;
|
|
virtual void SetGameDigestResult(int pid, const std::string& result) = 0;
|
|
virtual void AbortGameDigest() = 0;
|
|
|
|
virtual void OnIndexAdded(bool success, std::string error) = 0;
|
|
virtual void OnIndexRefreshFailed(std::string error) = 0;
|
|
|
|
virtual void ShowChunkedProgressDialog(const std::string& title, u64 data_size,
|
|
const std::vector<int>& players) = 0;
|
|
virtual void HideChunkedProgressDialog() = 0;
|
|
virtual void SetChunkedProgress(int pid, u64 progress) = 0;
|
|
|
|
virtual void SetHostWiiSyncData(std::vector<u64> titles, std::string redirect_folder) = 0;
|
|
};
|
|
|
|
class Player
|
|
{
|
|
public:
|
|
PlayerId pid{};
|
|
std::string name;
|
|
std::string revision;
|
|
u32 ping = 0;
|
|
SyncIdentifierComparison game_status = SyncIdentifierComparison::Unknown;
|
|
|
|
bool IsHost() const { return pid == 1; }
|
|
};
|
|
|
|
class NetPlayClient : public Common::TraversalClientClient
|
|
{
|
|
public:
|
|
void ThreadFunc();
|
|
void SendAsync(sf::Packet&& packet, u8 channel_id = DEFAULT_CHANNEL);
|
|
|
|
NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog,
|
|
const std::string& name, const NetTraversalConfig& traversal_config);
|
|
~NetPlayClient();
|
|
|
|
std::vector<const Player*> GetPlayers();
|
|
const NetSettings& GetNetSettings() const;
|
|
|
|
// Called from the GUI thread.
|
|
bool IsConnected() const { return m_is_connected; }
|
|
bool StartGame(const std::string& path);
|
|
void InvokeStop();
|
|
bool StopGame();
|
|
void Stop();
|
|
bool ChangeGame(const std::string& game);
|
|
void SendChatMessage(const std::string& msg);
|
|
void RequestStopGame();
|
|
void SendPowerButtonEvent();
|
|
void RequestGolfControl(PlayerId pid);
|
|
void RequestGolfControl();
|
|
std::string GetCurrentGolfer();
|
|
|
|
// Send and receive pads values
|
|
struct WiimoteDataBatchEntry
|
|
{
|
|
int wiimote;
|
|
WiimoteEmu::SerializedWiimoteState* state;
|
|
};
|
|
bool WiimoteUpdate(const std::span<WiimoteDataBatchEntry>& entries);
|
|
bool GetNetPads(int pad_nb, bool from_vi, GCPadStatus* pad_status);
|
|
|
|
u64 GetInitialRTCValue() const;
|
|
|
|
void OnTraversalStateChanged() override;
|
|
void OnConnectReady(ENetAddress addr) override;
|
|
void OnConnectFailed(Common::TraversalConnectFailedReason reason) override;
|
|
|
|
bool IsFirstInGamePad(int ingame_pad) const;
|
|
int NumLocalPads() const;
|
|
int NumLocalWiimotes() const;
|
|
|
|
int InGamePadToLocalPad(int ingame_pad) const;
|
|
int LocalPadToInGamePad(int local_pad) const;
|
|
int InGameWiimoteToLocalWiimote(int ingame_wiimote) const;
|
|
int LocalWiimoteToInGameWiimote(int local_wiimote) const;
|
|
|
|
bool PlayerHasControllerMapped(PlayerId pid) const;
|
|
bool LocalPlayerHasControllerMapped() const;
|
|
bool IsLocalPlayer(PlayerId pid) const;
|
|
const PlayerId& GetLocalPlayerId() const;
|
|
|
|
static void SendTimeBase();
|
|
bool DoAllPlayersHaveGame();
|
|
|
|
const PadMappingArray& GetPadMapping() const;
|
|
const GBAConfigArray& GetGBAConfig() const;
|
|
const PadMappingArray& GetWiimoteMapping() const;
|
|
|
|
void AdjustPadBufferSize(unsigned int size);
|
|
|
|
void SetWiiSyncData(std::unique_ptr<IOS::HLE::FS::FileSystem> fs, std::vector<u64> titles,
|
|
std::string redirect_folder);
|
|
|
|
static SyncIdentifier GetSDCardIdentifier();
|
|
|
|
protected:
|
|
struct AsyncQueueEntry
|
|
{
|
|
sf::Packet packet;
|
|
u8 channel_id = 0;
|
|
};
|
|
|
|
void ClearBuffers();
|
|
|
|
struct
|
|
{
|
|
std::recursive_mutex game;
|
|
// lock order
|
|
std::recursive_mutex players;
|
|
std::recursive_mutex async_queue_write;
|
|
} m_crit;
|
|
|
|
Common::SPSCQueue<AsyncQueueEntry, false> m_async_queue;
|
|
|
|
std::array<Common::SPSCQueue<GCPadStatus>, 4> m_pad_buffer;
|
|
std::array<Common::SPSCQueue<WiimoteEmu::SerializedWiimoteState>, 4> m_wiimote_buffer;
|
|
|
|
std::array<GCPadStatus, 4> m_last_pad_status{};
|
|
std::array<bool, 4> m_first_pad_status_received{};
|
|
|
|
std::chrono::time_point<std::chrono::steady_clock> m_buffer_under_target_last;
|
|
|
|
NetPlayUI* m_dialog = nullptr;
|
|
|
|
ENetHost* m_client = nullptr;
|
|
ENetPeer* m_server = nullptr;
|
|
std::thread m_thread;
|
|
|
|
SyncIdentifier m_selected_game;
|
|
Common::Flag m_is_running{false};
|
|
Common::Flag m_do_loop{true};
|
|
|
|
// In non-host input authority mode, this is how many packets each client should
|
|
// try to keep in-flight to the other clients. In host input authority mode, this is how
|
|
// many incoming input packets need to be queued up before the client starts
|
|
// speeding up the game to drain the buffer.
|
|
unsigned int m_target_buffer_size = 20;
|
|
bool m_host_input_authority = false;
|
|
PlayerId m_current_golfer = 1;
|
|
|
|
// This bool will stall the client at the start of GetNetPads, used for switching input control
|
|
// without deadlocking. Use the correspondingly named Event to wake it up.
|
|
bool m_wait_on_input;
|
|
bool m_wait_on_input_received;
|
|
|
|
Player* m_local_player = nullptr;
|
|
|
|
u32 m_current_game = 0;
|
|
|
|
PadMappingArray m_pad_map{};
|
|
GBAConfigArray m_gba_config{};
|
|
PadMappingArray m_wiimote_map{};
|
|
|
|
bool m_is_recording = false;
|
|
|
|
private:
|
|
enum class ConnectionState
|
|
{
|
|
WaitingForTraversalClientConnection,
|
|
WaitingForTraversalClientConnectReady,
|
|
Connecting,
|
|
WaitingForHelloResponse,
|
|
Connected,
|
|
Failure
|
|
};
|
|
|
|
void SendStartGamePacket();
|
|
void SendStopGamePacket();
|
|
|
|
void SyncSaveDataResponse(bool success);
|
|
void SyncCodeResponse(bool success);
|
|
|
|
bool PollLocalPad(int local_pad, sf::Packet& packet);
|
|
void SendPadHostPoll(PadIndex pad_num);
|
|
|
|
bool AddLocalWiimoteToBuffer(int local_wiimote, const WiimoteEmu::SerializedWiimoteState& state,
|
|
sf::Packet& packet);
|
|
|
|
void UpdateDevices();
|
|
void AddPadStateToPacket(int in_game_pad, const GCPadStatus& np, sf::Packet& packet);
|
|
void AddWiimoteStateToPacket(int in_game_pad, const WiimoteEmu::SerializedWiimoteState& np,
|
|
sf::Packet& packet);
|
|
void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL);
|
|
void Disconnect();
|
|
bool Connect();
|
|
void SendGameStatus();
|
|
void ComputeGameDigest(const SyncIdentifier& sync_identifier);
|
|
void DisplayPlayersPing();
|
|
u32 GetPlayersMaxPing() const;
|
|
|
|
void OnData(sf::Packet& packet);
|
|
void OnPlayerJoin(sf::Packet& packet);
|
|
void OnPlayerLeave(sf::Packet& packet);
|
|
void OnChatMessage(sf::Packet& packet);
|
|
void OnChunkedDataStart(sf::Packet& packet);
|
|
void OnChunkedDataEnd(sf::Packet& packet);
|
|
void OnChunkedDataPayload(sf::Packet& packet);
|
|
void OnChunkedDataAbort(sf::Packet& packet);
|
|
void OnPadMapping(sf::Packet& packet);
|
|
void OnWiimoteMapping(sf::Packet& packet);
|
|
void OnGBAConfig(sf::Packet& packet);
|
|
void OnPadData(sf::Packet& packet);
|
|
void OnPadHostData(sf::Packet& packet);
|
|
void OnWiimoteData(sf::Packet& packet);
|
|
void OnPadBuffer(sf::Packet& packet);
|
|
void OnHostInputAuthority(sf::Packet& packet);
|
|
void OnGolfSwitch(sf::Packet& packet);
|
|
void OnGolfPrepare(sf::Packet& packet);
|
|
void OnChangeGame(sf::Packet& packet);
|
|
void OnGameStatus(sf::Packet& packet);
|
|
void OnStartGame(sf::Packet& packet);
|
|
void OnStopGame(sf::Packet& packet);
|
|
void OnPowerButton();
|
|
void OnPing(sf::Packet& packet);
|
|
void OnPlayerPingData(sf::Packet& packet);
|
|
void OnDesyncDetected(sf::Packet& packet);
|
|
void OnSyncSaveData(sf::Packet& packet);
|
|
void OnSyncSaveDataNotify(sf::Packet& packet);
|
|
void OnSyncSaveDataRaw(sf::Packet& packet);
|
|
void OnSyncSaveDataGCI(sf::Packet& packet);
|
|
void OnSyncSaveDataWii(sf::Packet& packet);
|
|
void OnSyncSaveDataGBA(sf::Packet& packet);
|
|
void OnSyncCodes(sf::Packet& packet);
|
|
void OnSyncCodesNotify();
|
|
void OnSyncCodesNotifyGecko(sf::Packet& packet);
|
|
void OnSyncCodesDataGecko(sf::Packet& packet);
|
|
void OnSyncCodesNotifyAR(sf::Packet& packet);
|
|
void OnSyncCodesDataAR(sf::Packet& packet);
|
|
void OnComputeGameDigest(sf::Packet& packet);
|
|
void OnGameDigestProgress(sf::Packet& packet);
|
|
void OnGameDigestResult(sf::Packet& packet);
|
|
void OnGameDigestError(sf::Packet& packet);
|
|
void OnGameDigestAbort();
|
|
|
|
bool m_is_connected = false;
|
|
ConnectionState m_connection_state = ConnectionState::Failure;
|
|
|
|
PlayerId m_pid = 0;
|
|
NetSettings m_net_settings{};
|
|
std::map<PlayerId, Player> m_players;
|
|
std::string m_host_spec;
|
|
std::string m_player_name;
|
|
bool m_connecting = false;
|
|
Common::TraversalClient* m_traversal_client = nullptr;
|
|
std::thread m_game_digest_thread;
|
|
bool m_should_compute_game_digest = false;
|
|
Common::Event m_gc_pad_event;
|
|
Common::Event m_wii_pad_event;
|
|
Common::Event m_first_pad_status_received_event;
|
|
Common::Event m_wait_on_input_event;
|
|
u8 m_sync_save_data_count = 0;
|
|
u8 m_sync_save_data_success_count = 0;
|
|
u16 m_sync_gecko_codes_count = 0;
|
|
u16 m_sync_gecko_codes_success_count = 0;
|
|
bool m_sync_gecko_codes_complete = false;
|
|
u16 m_sync_ar_codes_count = 0;
|
|
u16 m_sync_ar_codes_success_count = 0;
|
|
bool m_sync_ar_codes_complete = false;
|
|
std::unordered_map<u32, sf::Packet> m_chunked_data_receive_queue;
|
|
|
|
u64 m_initial_rtc = 0;
|
|
u32 m_timebase_frame = 0;
|
|
|
|
std::unique_ptr<IOS::HLE::FS::FileSystem> m_wii_sync_fs;
|
|
std::vector<u64> m_wii_sync_titles;
|
|
std::string m_wii_sync_redirect_folder;
|
|
};
|
|
|
|
void NetPlay_Enable(NetPlayClient* const np);
|
|
void NetPlay_Disable();
|
|
bool NetPlay_GetWiimoteData(const std::span<NetPlayClient::WiimoteDataBatchEntry>& entries);
|
|
unsigned int NetPlay_GetLocalWiimoteForSlot(unsigned int slot);
|
|
} // namespace NetPlay
|