// Copyright 2010 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #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" namespace UICommon { class GameFile; } namespace NetPlay { class NetPlayUI { public: virtual ~NetPlayUI() {} virtual void BootGame(const std::string& filename) = 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 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(TraversalClient::FailureReason error) = 0; virtual void OnTraversalStateChanged(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 FindGameFile(const SyncIdentifier& sync_identifier, SyncIdentifierComparison* found = nullptr) = 0; virtual void ShowMD5Dialog(const std::string& title) = 0; virtual void SetMD5Progress(int pid, int progress) = 0; virtual void SetMD5Result(int pid, const std::string& result) = 0; virtual void AbortMD5() = 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; }; class Player { public: PlayerId pid; std::string name; std::string revision; u32 ping; SyncIdentifierComparison game_status; bool IsHost() const { return pid == 1; } }; class NetPlayClient : public 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(); void GetPlayerList(std::string& list, std::vector& pid_list); 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); 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 bool WiimoteUpdate(int _number, u8* data, const u8 size, u8 reporting_mode); bool GetNetPads(int pad_nb, bool from_vi, GCPadStatus* pad_status); u64 GetInitialRTCValue() const; void OnTraversalStateChanged() override; void OnConnectReady(ENetAddress addr) override; void OnConnectFailed(u8 reason) override; bool IsFirstInGamePad(int ingame_pad) const; int NumLocalPads() const; int InGamePadToLocalPad(int ingame_pad) const; int LocalPadToInGamePad(int localPad) const; bool PlayerHasControllerMapped(PlayerId pid) const; bool LocalPlayerHasControllerMapped() const; bool IsLocalPlayer(PlayerId pid) const; static void SendTimeBase(); bool DoAllPlayersHaveGame(); const PadMappingArray& GetPadMapping() const; const PadMappingArray& GetWiimoteMapping() const; void AdjustPadBufferSize(unsigned int size); static SyncIdentifier GetSDCardIdentifier(); protected: struct AsyncQueueEntry { sf::Packet packet; u8 channel_id; }; 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; 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 DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path); std::optional> DecompressPacketIntoBuffer(sf::Packet& packet); bool PollLocalPad(int local_pad, sf::Packet& packet); void SendPadHostPoll(PadIndex pad_num); void UpdateDevices(); void AddPadStateToPacket(int in_game_pad, const GCPadStatus& np, sf::Packet& packet); void SendWiimoteState(int in_game_pad, const NetWiimote& nw); unsigned int OnData(sf::Packet& packet); void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL); void Disconnect(); bool Connect(); void ComputeMD5(const SyncIdentifier& sync_identifier); void DisplayPlayersPing(); u32 GetPlayersMaxPing() const; 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; TraversalClient* m_traversal_client = nullptr; std::thread m_MD5_thread; bool m_should_compute_MD5 = 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; }; void NetPlay_Enable(NetPlayClient* const np); void NetPlay_Disable(); } // namespace NetPlay