diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index e4b4a67d86..55ed606517 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -122,6 +122,7 @@ #define GC_MEMCARD_NETPLAY "NetPlayTemp" #define GBA_BIOS "gba_bios.bin" +#define GBA_SAVE_NETPLAY "NetPlayTemp" #define WII_STATE "state.dat" diff --git a/Source/Core/Core/Config/NetplaySettings.cpp b/Source/Core/Core/Config/NetplaySettings.cpp index fb7bec18c7..a584117e9d 100644 --- a/Source/Core/Core/Config/NetplaySettings.cpp +++ b/Source/Core/Core/Config/NetplaySettings.cpp @@ -57,5 +57,6 @@ const Info NETPLAY_NETWORK_MODE{{System::Main, "NetPlay", "NetworkM "fixeddelay"}; const Info NETPLAY_SYNC_ALL_WII_SAVES{{System::Main, "NetPlay", "SyncAllWiiSaves"}, false}; const Info NETPLAY_GOLF_MODE_OVERLAY{{System::Main, "NetPlay", "GolfModeOverlay"}, true}; +const Info NETPLAY_HIDE_REMOTE_GBAS{{System::Main, "NetPlay", "HideRemoteGBAs"}, false}; } // namespace Config diff --git a/Source/Core/Core/Config/NetplaySettings.h b/Source/Core/Core/Config/NetplaySettings.h index 1704840cbc..f55e7fd090 100644 --- a/Source/Core/Core/Config/NetplaySettings.h +++ b/Source/Core/Core/Config/NetplaySettings.h @@ -50,5 +50,6 @@ extern const Info NETPLAY_STRICT_SETTINGS_SYNC; extern const Info NETPLAY_NETWORK_MODE; extern const Info NETPLAY_SYNC_ALL_WII_SAVES; extern const Info NETPLAY_GOLF_MODE_OVERLAY; +extern const Info NETPLAY_HIDE_REMOTE_GBAS; } // namespace Config diff --git a/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp b/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp index 6ba4702bac..1ea56af04c 100644 --- a/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp +++ b/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp @@ -133,6 +133,11 @@ public: layer->Set(Config::SESSION_GCI_FOLDER_CURRENT_GAME_ONLY, true); } + for (size_t i = 0; i < m_settings.m_GBARomPaths.size(); ++i) + { + layer->Set(Config::MAIN_GBA_ROM_PATHS[i], m_settings.m_GBARomPaths[i]); + } + // Check To Override Client's Cheat Codes if (m_settings.m_SyncCodes && !m_settings.m_IsHosting) { diff --git a/Source/Core/Core/HW/GBACore.cpp b/Source/Core/Core/HW/GBACore.cpp index 38ef42f1ba..7916cbfdc0 100644 --- a/Source/Core/Core/HW/GBACore.cpp +++ b/Source/Core/Core/HW/GBACore.cpp @@ -219,7 +219,8 @@ bool Core::Start(u64 gc_ticks) m_core->getGameTitle(m_core, game_title.data()); m_game_title = game_title.data(); - m_save_path = GetSavePath(m_rom_path, m_device_number); + m_save_path = NetPlay::IsNetPlayRunning() ? NetPlay::GetGBASavePath(m_device_number) : + GetSavePath(m_rom_path, m_device_number); if (!m_save_path.empty() && !LoadSave(m_save_path.c_str())) return false; } diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index dd66fe6366..4212d3031e 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -33,12 +33,18 @@ #include "Common/Version.h" #include "Core/ActionReplay.h" +#include "Core/Config/MainSettings.h" #include "Core/Config/NetplaySettings.h" #include "Core/Config/SessionSettings.h" #include "Core/ConfigManager.h" #include "Core/GeckoCode.h" #include "Core/HW/EXI/EXI_DeviceIPL.h" +#ifdef HAS_LIBMGBA +#include "Core/HW/GBACore.h" +#endif +#include "Core/HW/GBAPad.h" #include "Core/HW/GCMemcard/GCMemcard.h" +#include "Core/HW/GCPad.h" #include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI_Device.h" #include "Core/HW/SI/SI_DeviceGCController.h" @@ -71,7 +77,7 @@ static std::mutex crit_netplay_client; static NetPlayClient* netplay_client = nullptr; static std::unique_ptr s_wii_sync_fs; static std::vector s_wii_sync_titles; -static bool s_si_poll_batching; +static bool s_si_poll_batching = false; // called from ---GUI--- thread NetPlayClient::~NetPlayClient() @@ -463,6 +469,35 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) } break; + case NP_MSG_GBA_CONFIG: + { + for (size_t i = 0; i < m_gba_config.size(); ++i) + { + auto& config = m_gba_config[i]; + const auto old_config = config; + + packet >> config.enabled >> config.has_rom >> config.title; + for (auto& data : config.hash) + packet >> data; + + if (std::tie(config.has_rom, config.title, config.hash) != + std::tie(old_config.has_rom, old_config.title, old_config.hash)) + { + m_dialog->OnMsgChangeGBARom(static_cast(i), config); + m_net_settings.m_GBARomPaths[i] = + config.has_rom ? + m_dialog->FindGBARomPath(config.hash, config.title, static_cast(i)) : + ""; + } + } + + SendGameStatus(); + UpdateDevices(); + + m_dialog->Update(); + } + break; + case NP_MSG_WIIMOTE_MAPPING: { for (PlayerId& mapping : m_wiimote_map) @@ -482,8 +517,12 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> map; GCPadStatus pad; - packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> - pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + packet >> pad.button; + if (!m_gba_config.at(map).enabled) + { + packet >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> + pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + } // Trusting server for good map value (>=0 && <4) // add to pad buffer @@ -501,8 +540,12 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> map; GCPadStatus pad; - packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> - pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + packet >> pad.button; + if (!m_gba_config.at(map).enabled) + { + packet >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> + pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + } // Trusting server for good map value (>=0 && <4) // write to last status @@ -602,14 +645,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) // update gui m_dialog->OnMsgChangeGame(m_selected_game, netplay_name); - sf::Packet game_status_packet; - game_status_packet << static_cast(NP_MSG_GAME_STATUS); - - SyncIdentifierComparison result; - m_dialog->FindGameFile(m_selected_game, &result); - - game_status_packet << static_cast(result); - Send(game_status_packet); + SendGameStatus(); sf::Packet client_capabilities_packet; client_capabilities_packet << static_cast(NP_MSG_CLIENT_CAPABILITIES); @@ -739,6 +775,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> m_net_settings.m_GolfMode; packet >> m_net_settings.m_UseFMA; + packet >> m_net_settings.m_HideRemoteGBAs; m_net_settings.m_IsHosting = m_local_player->IsHost(); m_net_settings.m_HostInputAuthority = m_host_input_authority; @@ -1055,6 +1092,29 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) } break; + case SYNC_SAVE_DATA_GBA: + { + if (m_local_player->IsHost()) + return 0; + + u8 slot; + packet >> slot; + + const std::string path = + fmt::format("{}{}{}.sav", File::GetUserPath(D_GBAUSER_IDX), GBA_SAVE_NETPLAY, slot + 1); + if (File::Exists(path) && !File::Delete(path)) + { + PanicAlertFmtT("Failed to delete NetPlay GBA{0} save file. Verify your write permissions.", + slot + 1); + SyncSaveDataResponse(false); + return 0; + } + + const bool success = DecompressPacketIntoFile(packet, path); + SyncSaveDataResponse(success); + } + break; + default: PanicAlertFmtT("Unknown SYNC_SAVE_DATA message received with id: {0}", sub_id); break; @@ -1411,26 +1471,13 @@ void NetPlayClient::GetPlayerList(std::string& list, std::vector& pid_list) std::ostringstream ss; - const auto enumerate_player_controller_mappings = [&ss](const PadMappingArray& mappings, - const Player& player) { - for (size_t i = 0; i < mappings.size(); i++) - { - if (mappings[i] == player.pid) - ss << i + 1; - else - ss << '-'; - } - }; - for (const auto& entry : m_players) { const Player& player = entry.second; - ss << player.name << "[" << static_cast(player.pid) << "] : " << player.revision << " | "; + ss << player.name << "[" << static_cast(player.pid) << "] : " << player.revision << " | " + << GetPlayerMappingString(player.pid, m_pad_map, m_gba_config, m_wiimote_map) << " |\n"; - enumerate_player_controller_mappings(m_pad_map, player); - enumerate_player_controller_mappings(m_wiimote_map, player); - - ss << " |\nPing: " << player.ping << "ms\n"; + ss << "Ping: " << player.ping << "ms\n"; ss << "Status: "; switch (player.game_status) @@ -1492,8 +1539,12 @@ void NetPlayClient::AddPadStateToPacket(const int in_game_pad, const GCPadStatus sf::Packet& packet) { packet << static_cast(in_game_pad); - packet << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX - << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; + packet << pad.button; + if (!m_gba_config[in_game_pad].enabled) + { + packet << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX + << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; + } } // called from ---CPU--- thread @@ -1648,11 +1699,13 @@ void NetPlayClient::UpdateDevices() for (auto player_id : m_pad_map) { - // Use local controller types for local controllers if they are compatible - // Only GCController-like controllers are supported, GBA and similar - // exotic devices are not supported on netplay. - if (player_id == m_local_player->pid) + if (m_gba_config[pad].enabled && player_id > 0) { + SerialInterface::ChangeDevice(SerialInterface::SIDEVICE_GC_GBA_EMULATED, pad); + } + else if (player_id == m_local_player->pid) + { + // Use local controller types for local controllers if they are compatible if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[local_pad])) { SerialInterface::ChangeDevice(SConfig::GetInstance().m_SIDevice[local_pad], pad); @@ -1968,21 +2021,22 @@ bool NetPlayClient::WiimoteUpdate(int _number, u8* data, const std::size_t size, bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet) { - GCPadStatus pad_status; - - switch (SConfig::GetInstance().m_SIDevice[local_pad]) - { - case SerialInterface::SIDEVICE_WIIU_ADAPTER: - pad_status = GCAdapter::Input(local_pad); - break; - case SerialInterface::SIDEVICE_GC_CONTROLLER: - default: - pad_status = Pad::GetStatus(local_pad); - break; - } - const int ingame_pad = LocalPadToInGamePad(local_pad); bool data_added = false; + GCPadStatus pad_status; + + if (m_gba_config[ingame_pad].enabled) + { + pad_status = Pad::GetGBAStatus(local_pad); + } + else if (SConfig::GetInstance().m_SIDevice[local_pad] == SerialInterface::SIDEVICE_WIIU_ADAPTER) + { + pad_status = GCAdapter::Input(local_pad); + } + else + { + pad_status = Pad::GetStatus(local_pad); + } if (m_host_input_authority) { @@ -2234,6 +2288,26 @@ bool NetPlayClient::IsLocalPlayer(const PlayerId pid) const return pid == m_local_player->pid; } +void NetPlayClient::SendGameStatus() +{ + sf::Packet packet; + packet << static_cast(NP_MSG_GAME_STATUS); + + SyncIdentifierComparison result; + m_dialog->FindGameFile(m_selected_game, &result); + for (size_t i = 0; i < 4; ++i) + { + if (m_gba_config[i].enabled && m_gba_config[i].has_rom && + m_net_settings.m_GBARomPaths[i].empty()) + { + result = SyncIdentifierComparison::DifferentGame; + } + } + + packet << static_cast(result); + Send(packet); +} + void NetPlayClient::SendTimeBase() { std::lock_guard lk(crit_netplay_client); @@ -2309,6 +2383,11 @@ const PadMappingArray& NetPlayClient::GetPadMapping() const return m_pad_map; } +const GBAConfigArray& NetPlayClient::GetGBAConfig() const +{ + return m_gba_config; +} + const PadMappingArray& NetPlayClient::GetWiimoteMapping() const { return m_wiimote_map; @@ -2325,6 +2404,32 @@ SyncIdentifier NetPlayClient::GetSDCardIdentifier() return SyncIdentifier{{}, "sd", {}, {}, {}, {}}; } +std::string GetPlayerMappingString(PlayerId pid, const PadMappingArray& pad_map, + const GBAConfigArray& gba_config, + const PadMappingArray& wiimote_map) +{ + std::vector gc_slots, gba_slots, wiimote_slots; + for (size_t i = 0; i < pad_map.size(); ++i) + { + if (pad_map[i] == pid && !gba_config[i].enabled) + gc_slots.push_back(i + 1); + if (pad_map[i] == pid && gba_config[i].enabled) + gba_slots.push_back(i + 1); + if (wiimote_map[i] == pid) + wiimote_slots.push_back(i + 1); + } + std::vector groups; + for (const auto& [group_name, slots] : + {std::make_pair("GC", &gc_slots), std::make_pair("GBA", &gba_slots), + std::make_pair("Wii", &wiimote_slots)}) + { + if (!slots->empty()) + groups.emplace_back(fmt::format("{}{}", group_name, fmt::join(*slots, ","))); + } + std::string res = fmt::format("{}", fmt::join(groups, "|")); + return res.empty() ? "None" : res; +} + bool IsNetPlayRunning() { return netplay_client != nullptr; @@ -2401,6 +2506,60 @@ void SetupWiimotes() } } +std::string GetGBASavePath(int pad_num) +{ + std::lock_guard lk(crit_netplay_client); + + if (!netplay_client || NetPlay::GetNetSettings().m_IsHosting) + { +#ifdef HAS_LIBMGBA + std::string rom_path = Config::Get(Config::MAIN_GBA_ROM_PATHS[pad_num]); + return HW::GBA::Core::GetSavePath(rom_path, pad_num); +#else + return {}; +#endif + } + + if (!NetPlay::GetNetSettings().m_SyncSaveData) + return {}; + + return fmt::format("{}{}{}.sav", File::GetUserPath(D_GBAUSER_IDX), GBA_SAVE_NETPLAY, pad_num + 1); +} + +PadDetails GetPadDetails(int pad_num) +{ + std::lock_guard lk(crit_netplay_client); + + PadDetails res{.local_pad = 4}; + if (!netplay_client) + return res; + + auto pad_map = netplay_client->GetPadMapping(); + if (pad_map[pad_num] <= 0) + return res; + + for (auto player : netplay_client->GetPlayers()) + { + if (player->pid == pad_map[pad_num]) + res.player_name = player->name; + } + + int local_pad = 0; + int non_local_pad = 0; + for (int i = 0; i < pad_num; ++i) + { + if (netplay_client->IsLocalPlayer(pad_map[i])) + ++local_pad; + else + ++non_local_pad; + } + res.is_local = netplay_client->IsLocalPlayer(pad_map[pad_num]); + res.local_pad = res.is_local ? local_pad : netplay_client->NumLocalPads() + non_local_pad; + res.hide_gba = !res.is_local && netplay_client->GetNetSettings().m_HideRemoteGBAs && + netplay_client->LocalPlayerHasControllerMapped(); + return res; +} + void NetPlay_Enable(NetPlayClient* const np) { std::lock_guard lk(crit_netplay_client); diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 8cb2d7953e..1be8702b20 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -43,6 +43,7 @@ public: 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; @@ -62,6 +63,8 @@ public: 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 ShowMD5Dialog(const std::string& title) = 0; virtual void SetMD5Progress(int pid, int progress) = 0; virtual void SetMD5Result(int pid, const std::string& result) = 0; @@ -139,6 +142,7 @@ public: bool DoAllPlayersHaveGame(); const PadMappingArray& GetPadMapping() const; + const GBAConfigArray& GetGBAConfig() const; const PadMappingArray& GetWiimoteMapping() const; void AdjustPadBufferSize(unsigned int size); @@ -199,8 +203,9 @@ protected: u32 m_current_game = 0; - PadMappingArray m_pad_map; - PadMappingArray m_wiimote_map; + PadMappingArray m_pad_map{}; + GBAConfigArray m_gba_config{}; + PadMappingArray m_wiimote_map{}; bool m_is_recording = false; @@ -231,6 +236,7 @@ private: void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL); void Disconnect(); bool Connect(); + void SendGameStatus(); void ComputeMD5(const SyncIdentifier& sync_identifier); void DisplayPlayersPing(); u32 GetPlayersMaxPing() const; diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index 1b547eda80..4b7da05d80 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include "Common/CommonTypes.h" @@ -97,10 +98,12 @@ struct NetSettings std::array m_WiimoteExtension; bool m_GolfMode; bool m_UseFMA; + bool m_HideRemoteGBAs; // These aren't sent over the network directly bool m_IsHosting; bool m_HostInputAuthority; + std::array m_GBARomPaths; }; struct NetTraversalConfig @@ -136,6 +139,7 @@ enum NP_MSG_PAD_MAPPING = 0x61, NP_MSG_PAD_BUFFER = 0x62, NP_MSG_PAD_HOST_DATA = 0x63, + NP_MSG_GBA_CONFIG = 0x64, NP_MSG_WIIMOTE_DATA = 0x70, NP_MSG_WIIMOTE_MAPPING = 0x71, @@ -191,7 +195,8 @@ enum SYNC_SAVE_DATA_FAILURE = 2, SYNC_SAVE_DATA_RAW = 3, SYNC_SAVE_DATA_GCI = 4, - SYNC_SAVE_DATA_WII = 5 + SYNC_SAVE_DATA_WII = 5, + SYNC_SAVE_DATA_GBA = 6 }; enum @@ -225,7 +230,26 @@ using PlayerId = u8; using FrameNum = u32; using PadIndex = s8; using PadMappingArray = std::array; +struct GBAConfig +{ + bool enabled; + bool has_rom; + std::string title; + std::array hash; +}; +using GBAConfigArray = std::array; +struct PadDetails +{ + std::string player_name; + bool is_local; + int local_pad; + bool hide_gba; +}; + +std::string GetPlayerMappingString(PlayerId pid, const PadMappingArray& pad_map, + const GBAConfigArray& gba_config, + const PadMappingArray& wiimote_map); bool IsNetPlayRunning(); // Precondition: A netplay client instance must be present. In other words, // IsNetPlayRunning() must be true before calling this. @@ -238,4 +262,6 @@ void SetSIPollBatching(bool state); void SendPowerButtonEvent(); bool IsSyncingAllWiiSaves(); void SetupWiimotes(); +std::string GetGBASavePath(int pad_num); +PadDetails GetPadDetails(int pad_num); } // namespace NetPlay diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index f0dae94923..efc46683e9 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -40,6 +40,9 @@ #include "Core/ConfigManager.h" #include "Core/GeckoCode.h" #include "Core/GeckoCodeConfig.h" +#ifdef HAS_LIBMGBA +#include "Core/HW/GBACore.h" +#endif #include "Core/HW/GCMemcard/GCMemcard.h" #include "Core/HW/GCMemcard/GCMemcardDirectory.h" #include "Core/HW/GCMemcard/GCMemcardRaw.h" @@ -119,6 +122,7 @@ NetPlayServer::NetPlayServer(const u16 port, const bool forward_port, NetPlayUI* } m_pad_map.fill(0); + m_gba_config.fill({}); m_wiimote_map.fill(0); if (traversal_config.use_traversal) @@ -480,6 +484,7 @@ unsigned int NetPlayServer::OnConnect(ENetPeer* socket, sf::Packet& rpac) std::lock_guard lkp(m_crit.players); m_players.emplace(*PeerPlayerId(player.socket), std::move(player)); UpdatePadMapping(); // sync pad mappings with everyone + UpdateGBAConfig(); UpdateWiimoteMapping(); } @@ -530,12 +535,14 @@ unsigned int NetPlayServer::OnDisconnect(const Client& player) // alert other players of disconnect SendToClients(spac); - for (PlayerId& mapping : m_pad_map) + for (size_t i = 0; i < m_pad_map.size(); ++i) { - if (mapping == pid) + if (m_pad_map[i] == pid) { - mapping = 0; + m_pad_map[i] = 0; + m_gba_config[i].enabled = false; UpdatePadMapping(); + UpdateGBAConfig(); } } @@ -557,6 +564,11 @@ PadMappingArray NetPlayServer::GetPadMapping() const return m_pad_map; } +GBAConfigArray NetPlayServer::GetGBAConfig() const +{ + return m_gba_config; +} + PadMappingArray NetPlayServer::GetWiimoteMapping() const { return m_wiimote_map; @@ -569,6 +581,26 @@ void NetPlayServer::SetPadMapping(const PadMappingArray& mappings) UpdatePadMapping(); } +// called from ---GUI--- thread +void NetPlayServer::SetGBAConfig(const GBAConfigArray& mappings, bool update_rom) +{ +#ifdef HAS_LIBMGBA + m_gba_config = mappings; + if (update_rom) + { + for (size_t i = 0; i < m_gba_config.size(); ++i) + { + auto& config = m_gba_config[i]; + if (!config.enabled) + continue; + std::string rom_path = Config::Get(Config::MAIN_GBA_ROM_PATHS[i]); + config.has_rom = HW::GBA::Core::GetRomInfo(rom_path.c_str(), config.hash, config.title); + } + } +#endif + UpdateGBAConfig(); +} + // called from ---GUI--- thread void NetPlayServer::SetWiimoteMapping(const PadMappingArray& mappings) { @@ -588,6 +620,20 @@ void NetPlayServer::UpdatePadMapping() SendToClients(spac); } +// called from ---GUI--- thread and ---NETPLAY--- thread +void NetPlayServer::UpdateGBAConfig() +{ + sf::Packet spac; + spac << static_cast(NP_MSG_GBA_CONFIG); + for (const auto& config : m_gba_config) + { + spac << config.enabled << config.has_rom << config.title; + for (auto& data : config.hash) + spac << data; + } + SendToClients(spac); +} + // called from ---NETPLAY--- thread void NetPlayServer::UpdateWiimoteMapping() { @@ -751,12 +797,16 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) } GCPadStatus pad; - packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> - pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + packet >> pad.button; + spac << map << pad.button; + if (!m_gba_config.at(map).enabled) + { + packet >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> + pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; - spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY - << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight - << pad.isConnected; + spac << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX + << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; + } } if (m_host_input_authority) @@ -787,12 +837,16 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) packet >> map; GCPadStatus pad; - packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> - pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + packet >> pad.button; + spac << map << pad.button; + if (!m_gba_config.at(map).enabled) + { + packet >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> + pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; - spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY - << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight - << pad.isConnected; + spac << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX + << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; + } } SendToClients(spac, player.pid); @@ -1316,6 +1370,7 @@ bool NetPlayServer::SetupNetSettings() Config::Get(Config::NETPLAY_SYNC_ALL_WII_SAVES) && Config::Get(Config::NETPLAY_SYNC_SAVES); settings.m_GolfMode = Config::Get(Config::NETPLAY_NETWORK_MODE) == "golf"; settings.m_UseFMA = DoAllPlayersHaveHardwareFMA(); + settings.m_HideRemoteGBAs = Config::Get(Config::NETPLAY_HIDE_REMOTE_GBAS); // Unload GameINI to restore things to normal Config::RemoveLayer(Config::LayerType::GlobalGame); @@ -1501,6 +1556,7 @@ bool NetPlayServer::StartGame() spac << m_settings.m_GolfMode; spac << m_settings.m_UseFMA; + spac << m_settings.m_HideRemoteGBAs; SendAsyncToClients(std::move(spac)); @@ -1556,6 +1612,12 @@ bool NetPlayServer::SyncSaveData() save_count++; } + for (const auto& config : m_gba_config) + { + if (config.enabled && config.has_rom) + save_count++; + } + { sf::Packet pac; pac << static_cast(NP_MSG_SYNC_SAVE_DATA); @@ -1758,6 +1820,36 @@ bool NetPlayServer::SyncSaveData() SendChunkedToClients(std::move(pac), 1, "Wii Save Synchronization"); } + for (size_t i = 0; i < m_gba_config.size(); ++i) + { + if (m_gba_config[i].enabled && m_gba_config[i].has_rom) + { + sf::Packet pac; + pac << static_cast(NP_MSG_SYNC_SAVE_DATA); + pac << static_cast(SYNC_SAVE_DATA_GBA); + pac << static_cast(i); + + std::string path; +#ifdef HAS_LIBMGBA + path = HW::GBA::Core::GetSavePath(Config::Get(Config::MAIN_GBA_ROM_PATHS[i]), + static_cast(i)); +#endif + if (File::Exists(path)) + { + if (!CompressFileIntoPacket(path, pac)) + return false; + } + else + { + // No file, so we'll say the size is 0 + pac << sf::Uint64{0}; + } + + SendChunkedToClients(std::move(pac), 1, + fmt::format("GBA{} Save File Synchronization", i + 1)); + } + } + return true; } diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index 26c7c6e20f..87c1d46f63 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -58,6 +58,9 @@ public: PadMappingArray GetPadMapping() const; void SetPadMapping(const PadMappingArray& mappings); + GBAConfigArray GetGBAConfig() const; + void SetGBAConfig(const GBAConfigArray& configs, bool update_rom); + PadMappingArray GetWiimoteMapping() const; void SetWiimoteMapping(const PadMappingArray& mappings); @@ -134,6 +137,7 @@ private: void OnConnectReady(ENetAddress) override {} void OnConnectFailed(TraversalConnectFailedReason) override {} void UpdatePadMapping(); + void UpdateGBAConfig(); void UpdateWiimoteMapping(); std::vector> GetInterfaceListInternal() const; void ChunkedDataThreadFunc(); @@ -153,6 +157,7 @@ private: u32 m_current_game = 0; unsigned int m_target_buffer_size = 0; PadMappingArray m_pad_map; + GBAConfigArray m_gba_config; PadMappingArray m_wiimote_map; unsigned int m_save_data_synced_players = 0; unsigned int m_codes_synced_players = 0; diff --git a/Source/Core/DolphinQt/GBAWidget.cpp b/Source/Core/DolphinQt/GBAWidget.cpp index df9423a67f..291720e99e 100644 --- a/Source/Core/DolphinQt/GBAWidget.cpp +++ b/Source/Core/DolphinQt/GBAWidget.cpp @@ -58,6 +58,17 @@ GBAWidget::GBAWidget(std::weak_ptr core, int device_number, m_is_local_pad(true), m_volume(0), m_muted(false), m_force_disconnect(false) { bool visible = true; + if (NetPlay::IsNetPlayRunning()) + { + NetPlay::PadDetails details = NetPlay::GetPadDetails(m_device_number); + if (details.local_pad < 4) + { + m_netplayer_name = details.player_name; + m_is_local_pad = details.is_local; + m_local_pad = details.local_pad; + visible = !details.hide_gba; + } + } setWindowIcon(Resources::GetAppIcon()); setAcceptDrops(true); diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index d60eedc819..518eca5c23 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,9 @@ #include "Core/Config/NetplaySettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" +#ifdef HAS_LIBMGBA +#include "Core/HW/GBACore.h" +#endif #include "Core/NetPlayServer.h" #include "Core/SyncIdentifier.h" @@ -47,6 +51,7 @@ #include "DolphinQt/QtUtils/RunOnObject.h" #include "DolphinQt/Resources.h" #include "DolphinQt/Settings.h" +#include "DolphinQt/Settings/GameCubePane.h" #include "UICommon/DiscordPresence.h" #include "UICommon/GameFile.h" @@ -171,6 +176,8 @@ void NetPlayDialog::CreateMainLayout() m_record_input_action->setCheckable(true); m_golf_mode_overlay_action = m_other_menu->addAction(tr("Show Golf Mode Overlay")); m_golf_mode_overlay_action->setCheckable(true); + m_hide_remote_gbas_action = m_other_menu->addAction(tr("Hide Remote GBAs")); + m_hide_remote_gbas_action->setCheckable(true); m_game_button->setDefault(false); m_game_button->setAutoDefault(false); @@ -279,6 +286,7 @@ void NetPlayDialog::ConnectWidgets() m_pad_mapping->exec(); Settings::Instance().GetNetPlayServer()->SetPadMapping(m_pad_mapping->GetGCPadArray()); + Settings::Instance().GetNetPlayServer()->SetGBAConfig(m_pad_mapping->GetGBAArray(), true); Settings::Instance().GetNetPlayServer()->SetWiimoteMapping(m_pad_mapping->GetWiimoteArray()); }); @@ -365,6 +373,7 @@ void NetPlayDialog::ConnectWidgets() connect(m_golf_mode_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); connect(m_golf_mode_overlay_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); connect(m_fixed_delay_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); + connect(m_hide_remote_gbas_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); } void NetPlayDialog::SendMessage(const std::string& msg) @@ -471,6 +480,11 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal) m_data_menu->menuAction()->setVisible(is_hosting); m_network_menu->menuAction()->setVisible(is_hosting); m_md5_menu->menuAction()->setVisible(is_hosting); +#ifdef HAS_LIBMGBA + m_hide_remote_gbas_action->setVisible(is_hosting); +#else + m_hide_remote_gbas_action->setVisible(false); +#endif m_start_button->setHidden(!is_hosting); m_kick_button->setHidden(!is_hosting); m_assign_ports_button->setHidden(!is_hosting); @@ -570,20 +584,6 @@ void NetPlayDialog::UpdateGUI() {tr("Player"), tr("Game Status"), tr("Ping"), tr("Mapping"), tr("Revision")}); m_players_list->setRowCount(m_player_count); - const auto get_mapping_string = [](const NetPlay::Player* player, - const NetPlay::PadMappingArray& array) { - std::string str; - for (size_t i = 0; i < array.size(); i++) - { - if (player->pid == array[i]) - str += std::to_string(i + 1); - else - str += '-'; - } - - return '|' + str + '|'; - }; - static const std::map player_status{ {NetPlay::SyncIdentifierComparison::SameGame, tr("OK")}, {NetPlay::SyncIdentifierComparison::DifferentVersion, tr("Wrong Version")}, @@ -599,9 +599,9 @@ void NetPlayDialog::UpdateGUI() player_status.at(p->game_status) : QStringLiteral("?")); auto* ping_item = new QTableWidgetItem(QStringLiteral("%1 ms").arg(p->ping)); - auto* mapping_item = new QTableWidgetItem( - QString::fromStdString(get_mapping_string(p, client->GetPadMapping()) + - get_mapping_string(p, client->GetWiimoteMapping()))); + auto* mapping_item = + new QTableWidgetItem(QString::fromStdString(NetPlay::GetPlayerMappingString( + p->pid, client->GetPadMapping(), client->GetGBAConfig(), client->GetWiimoteMapping()))); auto* revision_item = new QTableWidgetItem(QString::fromStdString(p->revision)); for (auto* item : {name_item, status_item, ping_item, mapping_item, revision_item}) @@ -743,6 +743,20 @@ void NetPlayDialog::OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifi DisplayMessage(tr("Game changed to \"%1\"").arg(qname), "magenta"); } +void NetPlayDialog::OnMsgChangeGBARom(int pad, const NetPlay::GBAConfig& config) +{ + if (config.has_rom) + { + DisplayMessage( + tr("GBA%1 ROM changed to \"%2\"").arg(pad + 1).arg(QString::fromStdString(config.title)), + "magenta"); + } + else + { + DisplayMessage(tr("GBA%1 ROM disabled").arg(pad + 1), "magenta"); + } +} + void NetPlayDialog::GameStatusChanged(bool running) { QueueOnObject(this, [this, running] { SetOptionsEnabled(!running); }); @@ -976,6 +990,51 @@ NetPlayDialog::FindGameFile(const NetPlay::SyncIdentifier& sync_identifier, return nullptr; } +std::string NetPlayDialog::FindGBARomPath(const std::array& hash, std::string_view title, + int device_number) +{ +#ifdef HAS_LIBMGBA + auto result = RunOnObject(this, [&, this] { + std::string rom_path; + std::array rom_hash; + std::string rom_title; + for (size_t i = device_number; i < static_cast(device_number) + 4; ++i) + { + rom_path = Config::Get(Config::MAIN_GBA_ROM_PATHS[i % 4]); + if (!rom_path.empty() && HW::GBA::Core::GetRomInfo(rom_path.c_str(), rom_hash, rom_title) && + rom_hash == hash && rom_title == title) + { + return rom_path; + } + } + while (!(rom_path = GameCubePane::GetOpenGBARom(title)).empty()) + { + if (HW::GBA::Core::GetRomInfo(rom_path.c_str(), rom_hash, rom_title)) + { + if (rom_hash == hash && rom_title == title) + return rom_path; + ModalMessageBox::critical( + this, tr("Error"), + QString::fromStdString(Common::FmtFormatT( + "Mismatched ROMs\n" + "Selected: {0}\n- Title: {1}\n- Hash: {2:02X}\n" + "Expected:\n- Title: {3}\n- Hash: {4:02X}", + rom_path, rom_title, fmt::join(rom_hash, ""), title, fmt::join(hash, "")))); + } + else + { + ModalMessageBox::critical( + this, tr("Error"), tr("%1 is not a valid ROM").arg(QString::fromStdString(rom_path))); + } + } + return std::string(); + }); + if (result) + return *result; +#endif + return {}; +} + void NetPlayDialog::LoadSettings() { const int buffer_size = Config::Get(Config::NETPLAY_BUFFER_SIZE); @@ -987,6 +1046,7 @@ void NetPlayDialog::LoadSettings() const bool strict_settings_sync = Config::Get(Config::NETPLAY_STRICT_SETTINGS_SYNC); const bool sync_all_wii_saves = Config::Get(Config::NETPLAY_SYNC_ALL_WII_SAVES); const bool golf_mode_overlay = Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY); + const bool hide_remote_gbas = Config::Get(Config::NETPLAY_HIDE_REMOTE_GBAS); m_buffer_size_box->setValue(buffer_size); m_save_sd_action->setChecked(write_save_sdcard_data); @@ -997,6 +1057,7 @@ void NetPlayDialog::LoadSettings() m_strict_settings_sync_action->setChecked(strict_settings_sync); m_sync_all_wii_saves_action->setChecked(sync_all_wii_saves); m_golf_mode_overlay_action->setChecked(golf_mode_overlay); + m_hide_remote_gbas_action->setChecked(hide_remote_gbas); const std::string network_mode = Config::Get(Config::NETPLAY_NETWORK_MODE); @@ -1036,6 +1097,7 @@ void NetPlayDialog::SaveSettings() Config::SetBase(Config::NETPLAY_STRICT_SETTINGS_SYNC, m_strict_settings_sync_action->isChecked()); Config::SetBase(Config::NETPLAY_SYNC_ALL_WII_SAVES, m_sync_all_wii_saves_action->isChecked()); Config::SetBase(Config::NETPLAY_GOLF_MODE_OVERLAY, m_golf_mode_overlay_action->isChecked()); + Config::SetBase(Config::NETPLAY_HIDE_REMOTE_GBAS, m_hide_remote_gbas_action->isChecked()); std::string network_mode; if (m_fixed_delay_action->isChecked()) diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h index 51394e7993..34a5d7d8bc 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h @@ -46,6 +46,7 @@ public: void OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifier, const std::string& netplay_name) override; + void OnMsgChangeGBARom(int pad, const NetPlay::GBAConfig& config) override; void OnMsgStartGame() override; void OnMsgStopGame() override; void OnMsgPowerButton() override; @@ -68,6 +69,8 @@ public: std::shared_ptr FindGameFile(const NetPlay::SyncIdentifier& sync_identifier, NetPlay::SyncIdentifierComparison* found = nullptr) override; + std::string FindGBARomPath(const std::array& hash, std::string_view title, + int device_number) override; void LoadSettings(); void SaveSettings(); @@ -138,6 +141,7 @@ private: QAction* m_golf_mode_action; QAction* m_golf_mode_overlay_action; QAction* m_fixed_delay_action; + QAction* m_hide_remote_gbas_action; QPushButton* m_quit_button; QSplitter* m_splitter; QActionGroup* m_network_mode_group; diff --git a/Source/Core/DolphinQt/NetPlay/PadMappingDialog.cpp b/Source/Core/DolphinQt/NetPlay/PadMappingDialog.cpp index aa57a1a913..90836a5db0 100644 --- a/Source/Core/DolphinQt/NetPlay/PadMappingDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/PadMappingDialog.cpp @@ -3,6 +3,7 @@ #include "DolphinQt/NetPlay/PadMappingDialog.h" +#include #include #include #include @@ -31,15 +32,19 @@ void PadMappingDialog::CreateWidgets() for (unsigned int i = 0; i < m_wii_boxes.size(); i++) { m_gc_boxes[i] = new QComboBox; + m_gba_boxes[i] = new QCheckBox(tr("GBA Port %1").arg(i + 1)); m_wii_boxes[i] = new QComboBox; m_main_layout->addWidget(new QLabel(tr("GC Port %1").arg(i + 1)), 0, i); m_main_layout->addWidget(m_gc_boxes[i], 1, i); - m_main_layout->addWidget(new QLabel(tr("Wii Remote %1").arg(i + 1)), 2, i); - m_main_layout->addWidget(m_wii_boxes[i], 3, i); +#ifdef HAS_LIBMGBA + m_main_layout->addWidget(m_gba_boxes[i], 2, i); +#endif + m_main_layout->addWidget(new QLabel(tr("Wii Remote %1").arg(i + 1)), 3, i); + m_main_layout->addWidget(m_wii_boxes[i], 4, i); } - m_main_layout->addWidget(m_button_box, 4, 0, 1, -1); + m_main_layout->addWidget(m_button_box, 5, 0, 1, -1); setLayout(m_main_layout); } @@ -55,6 +60,11 @@ void PadMappingDialog::ConnectWidgets() &PadMappingDialog::OnMappingChanged); } } + for (const auto& checkbox : m_gba_boxes) + { + connect(checkbox, qOverload(&QCheckBox::stateChanged), this, + &PadMappingDialog::OnMappingChanged); + } } int PadMappingDialog::exec() @@ -64,6 +74,7 @@ int PadMappingDialog::exec() // Load Settings m_players = client->GetPlayers(); m_pad_mapping = server->GetPadMapping(); + m_gba_config = server->GetGBAConfig(); m_wii_mapping = server->GetWiimoteMapping(); QStringList players; @@ -93,6 +104,13 @@ int PadMappingDialog::exec() } } + for (size_t i = 0; i < m_gba_boxes.size(); i++) + { + const QSignalBlocker blocker(m_gba_boxes[i]); + + m_gba_boxes[i]->setChecked(m_gba_config[i].enabled); + } + return QDialog::exec(); } @@ -101,6 +119,11 @@ NetPlay::PadMappingArray PadMappingDialog::GetGCPadArray() return m_pad_mapping; } +NetPlay::GBAConfigArray PadMappingDialog::GetGBAArray() +{ + return m_gba_config; +} + NetPlay::PadMappingArray PadMappingDialog::GetWiimoteArray() { return m_wii_mapping; @@ -114,6 +137,7 @@ void PadMappingDialog::OnMappingChanged() int wii_id = m_wii_boxes[i]->currentIndex(); m_pad_mapping[i] = gc_id > 0 ? m_players[gc_id - 1]->pid : 0; + m_gba_config[i].enabled = m_gba_boxes[i]->isChecked(); m_wii_mapping[i] = wii_id > 0 ? m_players[wii_id - 1]->pid : 0; } } diff --git a/Source/Core/DolphinQt/NetPlay/PadMappingDialog.h b/Source/Core/DolphinQt/NetPlay/PadMappingDialog.h index 5123e75c29..cd7a61c2bf 100644 --- a/Source/Core/DolphinQt/NetPlay/PadMappingDialog.h +++ b/Source/Core/DolphinQt/NetPlay/PadMappingDialog.h @@ -7,6 +7,7 @@ #include "Core/NetPlayProto.h" +class QCheckBox; class QGridLayout; class QComboBox; class QDialogButtonBox; @@ -25,6 +26,7 @@ public: int exec() override; NetPlay::PadMappingArray GetGCPadArray(); + NetPlay::GBAConfigArray GetGBAArray(); NetPlay::PadMappingArray GetWiimoteArray(); private: @@ -34,10 +36,12 @@ private: void OnMappingChanged(); NetPlay::PadMappingArray m_pad_mapping; + NetPlay::GBAConfigArray m_gba_config; NetPlay::PadMappingArray m_wii_mapping; QGridLayout* m_main_layout; std::array m_gc_boxes; + std::array m_gba_boxes; std::array m_wii_boxes; std::vector m_players; QDialogButtonBox* m_button_box; diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index fcaa4af794..8f9f57c052 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -542,6 +542,10 @@ void GameCubePane::SaveSettings() Config::SetBaseOrCurrent(Config::MAIN_GBA_ROM_PATHS[i], m_gba_rom_edits[i]->text().toStdString()); } + + auto server = Settings::Instance().GetNetPlayServer(); + if (server) + server->SetGBAConfig(server->GetGBAConfig(), true); } #endif