// Copyright 2010 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #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 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 void OnTtlDetermined(u8 ttl) = 0; virtual bool IsRecording() = 0; virtual std::shared_ptr FindGameFile(const SyncIdentifier& sync_identifier, SyncIdentifierComparison* found = nullptr) = 0; virtual std::string FindGBARomPath(const std::array& 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& players) = 0; virtual void HideChunkedProgressDialog() = 0; virtual void SetChunkedProgress(int pid, u64 progress) = 0; virtual void SetHostWiiSyncData(std::vector 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 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& 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; void OnTtlDetermined(u8 ttl) 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 fs, std::vector 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 m_async_queue; std::array, 4> m_pad_buffer; std::array, 4> m_wiimote_buffer; std::array m_last_pad_status{}; std::array m_first_pad_status_received{}; std::chrono::time_point 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 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 m_chunked_data_receive_queue; u64 m_initial_rtc = 0; u32 m_timebase_frame = 0; std::unique_ptr m_wii_sync_fs; std::vector 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& entries); unsigned int NetPlay_GetLocalWiimoteForSlot(unsigned int slot); } // namespace NetPlay