From 469f29350f955aa0cf5b262d08e1e99b466a6965 Mon Sep 17 00:00:00 2001 From: Vin Bertinelli Date: Thu, 4 Oct 2018 18:49:41 -0400 Subject: [PATCH] Netplay: Sync codes Adds a tickbox to the server's window to syncronize codes. Codes are temporarily sent to each client and are used for the duration of the session. Saves the "sync codes" tickbox as per PR Netplay: Properly save hosting settings #7483 --- Source/Core/Core/ActionReplay.cpp | 32 +++ Source/Core/Core/ActionReplay.h | 3 + Source/Core/Core/Config/MainSettings.cpp | 1 + Source/Core/Core/Config/MainSettings.h | 1 + Source/Core/Core/Config/NetplaySettings.cpp | 1 + Source/Core/Core/Config/NetplaySettings.h | 1 + .../ConfigLoaders/NetPlayConfigLoader.cpp | 7 + Source/Core/Core/GeckoCode.cpp | 35 +++ Source/Core/Core/GeckoCode.h | 3 + Source/Core/Core/NetPlayClient.cpp | 191 +++++++++++++++ Source/Core/Core/NetPlayClient.h | 7 + Source/Core/Core/NetPlayProto.h | 13 + Source/Core/Core/NetPlayServer.cpp | 224 +++++++++++++++++- Source/Core/Core/NetPlayServer.h | 5 + Source/Core/Core/PatchEngine.cpp | 14 +- .../Core/DolphinQt/NetPlay/NetPlayDialog.cpp | 13 + Source/Core/DolphinQt/NetPlay/NetPlayDialog.h | 1 + 17 files changed, 545 insertions(+), 7 deletions(-) diff --git a/Source/Core/Core/ActionReplay.cpp b/Source/Core/Core/ActionReplay.cpp index 15ab75fffe..5df92b3e26 100644 --- a/Source/Core/Core/ActionReplay.cpp +++ b/Source/Core/Core/ActionReplay.cpp @@ -83,6 +83,7 @@ enum // General lock. Protects codes list and internal log. static std::mutex s_lock; static std::vector s_active_codes; +static std::vector s_synced_codes; static std::vector s_internal_log; static std::atomic s_use_internal_log{false}; // pointer to the code currently being run, (used by log messages that include the code name) @@ -123,6 +124,37 @@ void ApplyCodes(const std::vector& codes) s_active_codes.shrink_to_fit(); } +void SetSyncedCodesAsActive() +{ + s_active_codes.clear(); + s_active_codes.reserve(s_synced_codes.size()); + s_active_codes = s_synced_codes; +} + +void UpdateSyncedCodes(const std::vector& codes) +{ + s_synced_codes.clear(); + s_synced_codes.reserve(codes.size()); + std::copy_if(codes.begin(), codes.end(), std::back_inserter(s_synced_codes), + [](const ARCode& code) { return code.active; }); + s_synced_codes.shrink_to_fit(); +} + +std::vector ApplyAndReturnCodes(const std::vector& codes) +{ + if (SConfig::GetInstance().bEnableCheats) + { + std::lock_guard guard(s_lock); + s_disable_logging = false; + s_active_codes.clear(); + std::copy_if(codes.begin(), codes.end(), std::back_inserter(s_active_codes), + [](const ARCode& code) { return code.active; }); + } + s_active_codes.shrink_to_fit(); + + return s_active_codes; +} + void AddCode(ARCode code) { if (!SConfig::GetInstance().bEnableCheats) diff --git a/Source/Core/Core/ActionReplay.h b/Source/Core/Core/ActionReplay.h index 2a94018ee3..1266304742 100644 --- a/Source/Core/Core/ActionReplay.h +++ b/Source/Core/Core/ActionReplay.h @@ -35,6 +35,9 @@ struct ARCode void RunAllActive(); void ApplyCodes(const std::vector& codes); +void SetSyncedCodesAsActive(); +void UpdateSyncedCodes(const std::vector& codes); +std::vector ApplyAndReturnCodes(const std::vector& codes); void AddCode(ARCode new_code); void LoadAndApplyCodes(const IniFile& global_ini, const IniFile& local_ini); diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 095ef45e2a..155c8d4933 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -44,6 +44,7 @@ const ConfigInfo MAIN_GCI_FOLDER_B_PATH_OVERRIDE{ {System::Main, "Core", "GCIFolderBPathOverride"}, ""}; const ConfigInfo MAIN_GCI_FOLDER_CURRENT_GAME_ONLY{ {System::Main, "Core", "GCIFolderCurrentGameOnly"}, false}; +const ConfigInfo MAIN_CODE_SYNC_OVERRIDE{{System::Main, "Core", "CheatSyncOverride"}, false}; const ConfigInfo MAIN_SLOT_A{{System::Main, "Core", "SlotA"}, ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER}; const ConfigInfo MAIN_SLOT_B{{System::Main, "Core", "SlotB"}, diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 05f4a6dfc7..65f4662bc7 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -41,6 +41,7 @@ extern const ConfigInfo MAIN_AGP_CART_A_PATH; extern const ConfigInfo MAIN_AGP_CART_B_PATH; extern const ConfigInfo MAIN_GCI_FOLDER_A_PATH_OVERRIDE; extern const ConfigInfo MAIN_GCI_FOLDER_B_PATH_OVERRIDE; +extern const ConfigInfo MAIN_CODE_SYNC_OVERRIDE; extern const ConfigInfo MAIN_GCI_FOLDER_CURRENT_GAME_ONLY; extern const ConfigInfo MAIN_SLOT_A; extern const ConfigInfo MAIN_SLOT_B; diff --git a/Source/Core/Core/Config/NetplaySettings.cpp b/Source/Core/Core/Config/NetplaySettings.cpp index a4235ad03e..846d22372f 100644 --- a/Source/Core/Core/Config/NetplaySettings.cpp +++ b/Source/Core/Core/Config/NetplaySettings.cpp @@ -42,6 +42,7 @@ const ConfigInfo NETPLAY_WRITE_SAVE_SDCARD_DATA{ {System::Main, "NetPlay", "WriteSaveSDCardData"}, false}; const ConfigInfo NETPLAY_LOAD_WII_SAVE{{System::Main, "NetPlay", "LoadWiiSave"}, false}; const ConfigInfo NETPLAY_SYNC_SAVES{{System::Main, "NetPlay", "SyncSaves"}, true}; +const ConfigInfo NETPLAY_SYNC_CODES{{System::Main, "NetPlay", "SyncCodes"}, true}; const ConfigInfo NETPLAY_RECORD_INPUTS{{System::Main, "NetPlay", "RecordInputs"}, false}; const ConfigInfo NETPLAY_REDUCE_POLLING_RATE{{System::Main, "NetPlay", "ReducePollingRate"}, false}; diff --git a/Source/Core/Core/Config/NetplaySettings.h b/Source/Core/Core/Config/NetplaySettings.h index 90dcec5d23..9794bcc1fa 100644 --- a/Source/Core/Core/Config/NetplaySettings.h +++ b/Source/Core/Core/Config/NetplaySettings.h @@ -37,6 +37,7 @@ extern const ConfigInfo NETPLAY_CLIENT_BUFFER_SIZE; extern const ConfigInfo NETPLAY_WRITE_SAVE_SDCARD_DATA; extern const ConfigInfo NETPLAY_LOAD_WII_SAVE; extern const ConfigInfo NETPLAY_SYNC_SAVES; +extern const ConfigInfo NETPLAY_SYNC_CODES; extern const ConfigInfo NETPLAY_RECORD_INPUTS; extern const ConfigInfo NETPLAY_REDUCE_POLLING_RATE; extern const ConfigInfo NETPLAY_STRICT_SETTINGS_SYNC; diff --git a/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp b/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp index 6aa190593c..5b3721654d 100644 --- a/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp +++ b/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp @@ -105,6 +105,13 @@ public: layer->Set(Config::MAIN_GCI_FOLDER_CURRENT_GAME_ONLY, true); } + + // Check To Override Client's Cheat Codes + if (m_settings.m_SyncCodes && !m_settings.m_IsHosting) + { + // Raise flag to use host's codes + layer->Set(Config::MAIN_CODE_SYNC_OVERRIDE, true); + } } void Save(Config::Layer* layer) override diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index 512767eb7c..eb90eda5a0 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -61,6 +61,7 @@ enum class Installation static Installation s_code_handler_installed = Installation::Uninstalled; // the currently active codes static std::vector s_active_codes; +static std::vector s_synced_codes; static std::mutex s_active_codes_lock; void SetActiveCodes(const std::vector& gcodes) @@ -79,6 +80,40 @@ void SetActiveCodes(const std::vector& gcodes) s_code_handler_installed = Installation::Uninstalled; } +void SetSyncedCodesAsActive() +{ + s_active_codes.clear(); + s_active_codes.reserve(s_synced_codes.size()); + s_active_codes = s_synced_codes; +} + +void UpdateSyncedCodes(const std::vector& gcodes) +{ + s_synced_codes.clear(); + s_synced_codes.reserve(gcodes.size()); + std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_synced_codes), + [](const GeckoCode& code) { return code.enabled; }); + s_synced_codes.shrink_to_fit(); +} + +std::vector SetAndReturnActiveCodes(const std::vector& gcodes) +{ + std::lock_guard lk(s_active_codes_lock); + + s_active_codes.clear(); + if (SConfig::GetInstance().bEnableCheats) + { + s_active_codes.reserve(gcodes.size()); + std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_active_codes), + [](const GeckoCode& code) { return code.enabled; }); + } + s_active_codes.shrink_to_fit(); + + s_code_handler_installed = Installation::Uninstalled; + + return s_active_codes; +} + // Requires s_active_codes_lock // NOTE: Refer to "codehandleronly.s" from Gecko OS. static Installation InstallCodeHandlerLocked() diff --git a/Source/Core/Core/GeckoCode.h b/Source/Core/Core/GeckoCode.h index 838ed66b0c..254c65b480 100644 --- a/Source/Core/Core/GeckoCode.h +++ b/Source/Core/Core/GeckoCode.h @@ -59,6 +59,9 @@ constexpr u32 HLE_TRAMPOLINE_ADDRESS = INSTALLER_END_ADDRESS - 4; constexpr u32 MAGIC_GAMEID = 0xD01F1BAD; void SetActiveCodes(const std::vector& gcodes); +void SetSyncedCodesAsActive(); +void UpdateSyncedCodes(const std::vector& gcodes); +std::vector SetAndReturnActiveCodes(const std::vector& gcodes); void RunCodeHandler(); void Shutdown(); void DoState(PointerWrap&); diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 8762623414..85a75085c4 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -33,8 +33,10 @@ #include "Common/StringUtil.h" #include "Common/Timer.h" #include "Common/Version.h" +#include "Core/ActionReplay.h" #include "Core/Config/NetplaySettings.h" #include "Core/ConfigManager.h" +#include "Core/GeckoCode.h" #include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI_DeviceGCController.h" @@ -549,6 +551,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> m_net_settings.m_SyncSaveData; packet >> m_net_settings.m_SaveDataRegion; + packet >> m_net_settings.m_SyncCodes; m_net_settings.m_IsHosting = m_local_player->IsHost(); m_net_settings.m_HostInputAuthority = m_host_input_authority; @@ -644,6 +647,9 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) { case SYNC_SAVE_DATA_NOTIFY: { + if (m_local_player->IsHost()) + return 0; + packet >> m_sync_save_data_count; m_sync_save_data_success_count = 0; @@ -823,6 +829,163 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) } break; + case NP_MSG_SYNC_CODES: + { + // Recieve Data Packet + MessageId sub_id; + packet >> sub_id; + + // Check Which Operation to Perform with This Packet + switch (sub_id) + { + case SYNC_CODES_NOTIFY: + { + // Set both codes as unsynced + m_sync_gecko_codes_complete = false; + m_sync_ar_codes_complete = false; + } + break; + + case SYNC_CODES_NOTIFY_GECKO: + { + // Return if this is the host + if (m_local_player->IsHost()) + return 0; + + // Receive Number of Codelines to Receive + packet >> m_sync_gecko_codes_count; + + m_sync_gecko_codes_success_count = 0; + + NOTICE_LOG(ACTIONREPLAY, "Receiving %d Gecko codelines", m_sync_gecko_codes_count); + + // Check if no codes to sync, if so return as finished + if (m_sync_gecko_codes_count == 0) + { + m_sync_gecko_codes_complete = true; + SyncCodeResponse(true); + } + else + m_dialog->AppendChat(GetStringT("Synchronizing gecko codes...")); + } + break; + + case SYNC_CODES_DATA_GECKO: + { + // Return if this is the host + if (m_local_player->IsHost()) + return 0; + + // Create a synced code vector + std::vector synced_codes; + // Create a GeckoCode + Gecko::GeckoCode gcode; + gcode = Gecko::GeckoCode(); + // Initialize gcode + gcode.name = "Synced Codes"; + gcode.enabled = true; + + // Receive code contents from packet + for (int i = 0; i < m_sync_gecko_codes_count; i++) + { + Gecko::GeckoCode::Code new_code; + packet >> new_code.address; + packet >> new_code.data; + + NOTICE_LOG(ACTIONREPLAY, "Received %08x %08x", new_code.address, new_code.data); + + gcode.codes.push_back(std::move(new_code)); + + if (++m_sync_gecko_codes_success_count >= m_sync_gecko_codes_count) + { + m_sync_gecko_codes_complete = true; + SyncCodeResponse(true); + } + } + + // Add gcode containing all codes to Gecko Code vector + synced_codes.push_back(std::move(gcode)); + + // Clear Vector if received 0 codes (match host's end when using no codes) + if (m_sync_gecko_codes_count == 0) + synced_codes.clear(); + + // Copy this to the vector located in GeckoCode.cpp + Gecko::UpdateSyncedCodes(synced_codes); + } + break; + + case SYNC_CODES_NOTIFY_AR: + { + // Return if this is the host + if (m_local_player->IsHost()) + return 0; + + // Receive Number of Codelines to Receive + packet >> m_sync_ar_codes_count; + + m_sync_ar_codes_success_count = 0; + + NOTICE_LOG(ACTIONREPLAY, "Receiving %d AR codelines", m_sync_ar_codes_count); + + // Check if no codes to sync, if so return as finished + if (m_sync_ar_codes_count == 0) + { + m_sync_ar_codes_complete = true; + SyncCodeResponse(true); + } + else + m_dialog->AppendChat(GetStringT("Synchronizing AR codes...")); + } + break; + + case SYNC_CODES_DATA_AR: + { + // Return if this is the host + if (m_local_player->IsHost()) + return 0; + + // Create a synced code vector + std::vector synced_codes; + // Create an ARCode + ActionReplay::ARCode arcode; + arcode = ActionReplay::ARCode(); + // Initialize arcode + arcode.name = "Synced Codes"; + arcode.active = true; + + // Receive code contents from packet + for (int i = 0; i < m_sync_ar_codes_count; i++) + { + ActionReplay::AREntry new_code; + packet >> new_code.cmd_addr; + packet >> new_code.value; + + NOTICE_LOG(ACTIONREPLAY, "Received %08x %08x", new_code.cmd_addr, new_code.value); + arcode.ops.push_back(new_code); + + if (++m_sync_ar_codes_success_count >= m_sync_ar_codes_count) + { + m_sync_ar_codes_complete = true; + SyncCodeResponse(true); + } + } + + // Add arcode containing all codes to AR Code vector + synced_codes.push_back(std::move(arcode)); + + // Clear Vector if received 0 codes (match host's end when using no codes) + if (m_sync_ar_codes_count == 0) + synced_codes.clear(); + + // Copy this to the vector located in ActionReplay.cpp + ActionReplay::UpdateSyncedCodes(synced_codes); + } + break; + } + } + break; + case NP_MSG_COMPUTE_MD5: { std::string file_identifier; @@ -1193,6 +1356,34 @@ void NetPlayClient::SyncSaveDataResponse(const bool success) } } +void NetPlayClient::SyncCodeResponse(const bool success) +{ + // If something failed, immediately report back that code sync failed + if (!success) + { + m_dialog->AppendChat(GetStringT("Error processing Codes.")); + + sf::Packet response_packet; + response_packet << static_cast(NP_MSG_SYNC_CODES); + response_packet << static_cast(SYNC_CODES_FAILURE); + + Send(response_packet); + return; + } + + // If both gecko and AR codes have completely finished transferring, report back as successful + if (m_sync_gecko_codes_complete && m_sync_ar_codes_complete) + { + m_dialog->AppendChat(GetStringT("Codes received!")); + + sf::Packet response_packet; + response_packet << static_cast(NP_MSG_SYNC_CODES); + response_packet << static_cast(SYNC_CODES_SUCCESS); + + Send(response_packet); + } +} + bool NetPlayClient::DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path) { u64 file_size = Common::PacketReadU64(packet); diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 5287d95ef5..a6cce7479a 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -190,6 +190,7 @@ private: 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); @@ -224,6 +225,12 @@ private: Common::Event m_first_pad_status_received_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; u64 m_initial_rtc = 0; u32 m_timebase_frame = 0; diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index 8e175de807..1539dc7f6b 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -76,6 +76,7 @@ struct NetSettings bool m_EnableGPUTextureDecoding; bool m_StrictSettingsSync; bool m_SyncSaveData; + bool m_SyncCodes; std::string m_SaveDataRegion; bool m_IsHosting; bool m_HostInputAuthority; @@ -143,6 +144,7 @@ enum NP_MSG_SYNC_GC_SRAM = 0xF0, NP_MSG_SYNC_SAVE_DATA = 0xF1, + NP_MSG_SYNC_CODES = 0xF2, }; enum @@ -162,6 +164,17 @@ enum SYNC_SAVE_DATA_WII = 5 }; +enum +{ + SYNC_CODES_NOTIFY = 0, + SYNC_CODES_NOTIFY_GECKO = 1, + SYNC_CODES_NOTIFY_AR = 2, + SYNC_CODES_DATA_GECKO = 3, + SYNC_CODES_DATA_AR = 4, + SYNC_CODES_SUCCESS = 5, + SYNC_CODES_FAILURE = 6, +}; + constexpr u32 NETPLAY_LZO_IN_LEN = 1024 * 64; constexpr u32 NETPLAY_LZO_OUT_LEN = NETPLAY_LZO_IN_LEN + (NETPLAY_LZO_IN_LEN / 16) + 64 + 3; diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 8c5fa6eb3d..26fc04b397 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -29,9 +29,13 @@ #include "Common/StringUtil.h" #include "Common/UPnP.h" #include "Common/Version.h" +#include "Core/ActionReplay.h" #include "Core/Config/MainSettings.h" #include "Core/Config/NetplaySettings.h" +#include "Core/ConfigLoaders/GameConfigLoader.h" #include "Core/ConfigManager.h" +#include "Core/GeckoCode.h" +#include "Core/GeckoCodeConfig.h" #include "Core/HW/GCMemcard/GCMemcardDirectory.h" #include "Core/HW/GCMemcard/GCMemcardRaw.h" #include "Core/HW/Sram.h" @@ -844,8 +848,11 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) m_save_data_synced_players++; if (m_save_data_synced_players >= m_players.size() - 1) { - m_dialog->AppendChat(GetStringT("All players synchronized.")); - StartGame(); + m_dialog->AppendChat(GetStringT("All players saves synchronized.")); + + // Saves are synced, check if codes are as well and attempt to start the game + m_saves_synced = true; + CheckSyncAndStartGame(); } } } @@ -869,6 +876,48 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) } break; + case NP_MSG_SYNC_CODES: + { + // Receive Status of Code Sync + MessageId sub_id; + packet >> sub_id; + + // Check If Code Sync was successful or not + switch (sub_id) + { + case SYNC_CODES_SUCCESS: + { + if (m_start_pending) + { + if (++m_codes_synced_players >= m_players.size() - 1) + { + m_dialog->AppendChat(GetStringT("All players' codes synchronized.")); + + // Codes are synced, check if saves are as well and attempt to start the game + m_codes_synced = true; + CheckSyncAndStartGame(); + } + } + } + break; + + case SYNC_CODES_FAILURE: + { + m_dialog->AppendChat(StringFromFormat(GetStringT("%s failed to synchronize codes.").c_str(), + player.name.c_str())); + m_start_pending = false; + } + break; + + default: + PanicAlertT( + "Unknown SYNC_GECKO_CODES message with id:%d received from player:%d Kicking player!", + sub_id, player.pid); + return 1; + } + } + break; + default: PanicAlertT("Unknown message with id:%d received from player:%d Kicking player!", mid, player.pid); @@ -958,17 +1007,34 @@ bool NetPlayServer::DoAllPlayersHaveIPLDump() const // called from ---GUI--- thread bool NetPlayServer::RequestStartGame() { + bool start_now = true; + if (m_settings.m_SyncSaveData && m_players.size() > 1) { + start_now = false; + m_start_pending = true; if (!SyncSaveData()) { PanicAlertT("Error synchronizing save data!"); + m_start_pending = false; return false; } - - m_start_pending = true; } - else + + // Check To Send Codes to Clients + if (m_settings.m_SyncCodes && m_players.size() > 1) + { + start_now = false; + m_start_pending = true; + if (!SyncCodes()) + { + PanicAlertT("Error synchronizing save gecko codes!"); + m_start_pending = false; + return false; + } + } + + if (start_now) { return StartGame(); } @@ -1056,6 +1122,7 @@ bool NetPlayServer::StartGame() spac << initial_rtc; spac << m_settings.m_SyncSaveData; spac << region; + spac << m_settings.m_SyncCodes; SendAsyncToClients(std::move(spac)); @@ -1068,6 +1135,9 @@ bool NetPlayServer::StartGame() // called from ---GUI--- thread bool NetPlayServer::SyncSaveData() { + // We're about to sync saves, so set m_saves_synced to false (waits to start game) + m_saves_synced = false; + m_save_data_synced_players = 0; u8 save_count = 0; @@ -1244,6 +1314,150 @@ bool NetPlayServer::SyncSaveData() return true; } +bool NetPlayServer::SyncCodes() +{ + // Sync Codes is ticked, so set m_codes_synced to false + m_codes_synced = false; + + // Get Game Path + const auto game = m_dialog->FindGameFile(m_selected_game); + if (game == nullptr) + { + PanicAlertT("Selected game doesn't exist in game list!"); + return false; + } + + // Find all INI files + const auto game_id = game->GetGameID(); + const auto revision = game->GetRevision(); + IniFile globalIni; + for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(game_id, revision)) + globalIni.Load(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename, true); + IniFile localIni; + for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(game_id, revision)) + localIni.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + filename, true); + + // Initialize Number of Synced Players + m_codes_synced_players = 0; + + // Notify Clients of Incoming Code Sync + { + sf::Packet pac; + pac << static_cast(NP_MSG_SYNC_CODES); + pac << static_cast(SYNC_CODES_NOTIFY); + SendAsyncToClients(std::move(pac)); + } + // Sync Gecko Codes + { + // Create a Gecko Code Vector with just the active codes + std::vector s_active_codes = + Gecko::SetAndReturnActiveCodes(Gecko::LoadCodes(globalIni, localIni)); + + // Determine Codelist Size + u16 codelines = 0; + for (const Gecko::GeckoCode& active_code : s_active_codes) + { + NOTICE_LOG(ACTIONREPLAY, "Indexing %s", active_code.name.c_str()); + for (const Gecko::GeckoCode::Code& code : active_code.codes) + { + NOTICE_LOG(ACTIONREPLAY, "%08x %08x", code.address, code.data); + codelines++; + } + } + + // Output codelines to send + NOTICE_LOG(ACTIONREPLAY, "Sending %d Gecko codelines", codelines); + + // Send initial packet. Notify of the sync operation and total number of lines being sent. + { + sf::Packet pac; + pac << static_cast(NP_MSG_SYNC_CODES); + pac << static_cast(SYNC_CODES_NOTIFY_GECKO); + pac << codelines; + SendAsyncToClients(std::move(pac)); + } + + // Send entire codeset in the second packet + { + sf::Packet pac; + pac << static_cast(NP_MSG_SYNC_CODES); + pac << static_cast(SYNC_CODES_DATA_GECKO); + // Iterate through the active code vector and send each codeline + for (const Gecko::GeckoCode& active_code : s_active_codes) + { + NOTICE_LOG(ACTIONREPLAY, "Sending %s", active_code.name.c_str()); + for (const Gecko::GeckoCode::Code& code : active_code.codes) + { + NOTICE_LOG(ACTIONREPLAY, "%08x %08x", code.address, code.data); + pac << code.address; + pac << code.data; + } + } + SendAsyncToClients(std::move(pac)); + } + } + + // Sync AR Codes + { + // Create an AR Code Vector with just the active codes + std::vector s_active_codes = + ActionReplay::ApplyAndReturnCodes(ActionReplay::LoadCodes(globalIni, localIni)); + + // Determine Codelist Size + u16 codelines = 0; + for (const ActionReplay::ARCode& active_code : s_active_codes) + { + NOTICE_LOG(ACTIONREPLAY, "Indexing %s", active_code.name.c_str()); + for (const ActionReplay::AREntry& op : active_code.ops) + { + NOTICE_LOG(ACTIONREPLAY, "%08x %08x", op.cmd_addr, op.value); + codelines++; + } + } + + // Output codelines to send + NOTICE_LOG(ACTIONREPLAY, "Sending %d AR codelines", codelines); + + // Send initial packet. Notify of the sync operation and total number of lines being sent. + { + sf::Packet pac; + pac << static_cast(NP_MSG_SYNC_CODES); + pac << static_cast(SYNC_CODES_NOTIFY_AR); + pac << codelines; + SendAsyncToClients(std::move(pac)); + } + + // Send entire codeset in the second packet + { + sf::Packet pac; + pac << static_cast(NP_MSG_SYNC_CODES); + pac << static_cast(SYNC_CODES_DATA_AR); + // Iterate through the active code vector and send each codeline + for (const ActionReplay::ARCode& active_code : s_active_codes) + { + NOTICE_LOG(ACTIONREPLAY, "Sending %s", active_code.name.c_str()); + for (const ActionReplay::AREntry& op : active_code.ops) + { + NOTICE_LOG(ACTIONREPLAY, "%08x %08x", op.cmd_addr, op.value); + pac << op.cmd_addr; + pac << op.value; + } + } + SendAsyncToClients(std::move(pac)); + } + } + + return true; +} + +void NetPlayServer::CheckSyncAndStartGame() +{ + if (m_saves_synced && m_codes_synced) + { + StartGame(); + } +} + bool NetPlayServer::CompressFileIntoPacket(const std::string& file_path, sf::Packet& packet) { File::IOFile file(file_path, "rb"); diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index a0c49dd04f..ce75d2605d 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -85,6 +85,8 @@ private: }; bool SyncSaveData(); + bool SyncCodes(); + void CheckSyncAndStartGame(); bool CompressFileIntoPacket(const std::string& file_path, sf::Packet& packet); bool CompressBufferIntoPacket(const std::vector& in_buffer, sf::Packet& packet); void SendFirstReceivedToHost(PadMapping map, bool state); @@ -116,6 +118,9 @@ private: PadMappingArray m_pad_map; PadMappingArray m_wiimote_map; unsigned int m_save_data_synced_players = 0; + unsigned int m_codes_synced_players = 0; + bool m_saves_synced = true; + bool m_codes_synced = true; bool m_start_pending = false; bool m_host_input_authority = false; diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index 4e12701ffd..8d1b3a4a78 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -21,6 +21,7 @@ #include "Common/StringUtil.h" #include "Core/ActionReplay.h" +#include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/GeckoCode.h" #include "Core/GeckoCodeConfig.h" @@ -164,9 +165,18 @@ void LoadPatches() IniFile localIni = SConfig::GetInstance().LoadLocalGameIni(); LoadPatchSection("OnFrame", s_on_frame, globalIni, localIni); - ActionReplay::LoadAndApplyCodes(globalIni, localIni); - Gecko::SetActiveCodes(Gecko::LoadCodes(globalIni, localIni)); + // Check if I'm syncing Codes + if (Config::Get(Config::MAIN_CODE_SYNC_OVERRIDE)) + { + Gecko::SetSyncedCodesAsActive(); + ActionReplay::SetSyncedCodesAsActive(); + } + else + { + Gecko::SetActiveCodes(Gecko::LoadCodes(globalIni, localIni)); + ActionReplay::LoadAndApplyCodes(globalIni, localIni); + } LoadSpeedhacks("Speedhacks", merged); } diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index dc6b847b61..5868b6a1b8 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -76,6 +76,7 @@ NetPlayDialog::NetPlayDialog(QWidget* parent) const bool write_save_sdcard_data = Config::Get(Config::NETPLAY_WRITE_SAVE_SDCARD_DATA); const bool load_wii_save = Config::Get(Config::NETPLAY_LOAD_WII_SAVE); const bool sync_saves = Config::Get(Config::NETPLAY_SYNC_SAVES); + const bool sync_codes = Config::Get(Config::NETPLAY_SYNC_CODES); const bool record_inputs = Config::Get(Config::NETPLAY_RECORD_INPUTS); const bool reduce_polling_rate = Config::Get(Config::NETPLAY_REDUCE_POLLING_RATE); const bool strict_settings_sync = Config::Get(Config::NETPLAY_STRICT_SETTINGS_SYNC); @@ -85,6 +86,7 @@ NetPlayDialog::NetPlayDialog(QWidget* parent) m_save_sd_box->setChecked(write_save_sdcard_data); m_load_wii_box->setChecked(load_wii_save); m_sync_save_data_box->setChecked(sync_saves); + m_sync_codes_box->setChecked(sync_codes); m_record_input_box->setChecked(record_inputs); m_reduce_polling_rate_box->setChecked(reduce_polling_rate); m_strict_settings_sync_box->setChecked(strict_settings_sync); @@ -120,6 +122,7 @@ void NetPlayDialog::CreateMainLayout() m_reduce_polling_rate_box = new QCheckBox(tr("Reduce Polling Rate")); m_strict_settings_sync_box = new QCheckBox(tr("Strict Settings Sync")); m_host_input_authority_box = new QCheckBox(tr("Host Input Authority")); + m_sync_codes_box = new QCheckBox(tr("Sync Codes")); m_buffer_label = new QLabel(tr("Buffer:")); m_quit_button = new QPushButton(tr("Quit")); m_splitter = new QSplitter(Qt::Horizontal); @@ -128,6 +131,7 @@ void NetPlayDialog::CreateMainLayout() m_game_button->setAutoDefault(false); m_sync_save_data_box->setChecked(true); + m_sync_codes_box->setChecked(true); auto* default_button = new QAction(tr("Calculate MD5 hash"), m_md5_button); @@ -163,6 +167,9 @@ void NetPlayDialog::CreateMainLayout() tr("This will sync additional graphics settings, and force everyone to the same internal " "resolution.\nMay prevent desync in some games that use EFB reads. Please ensure everyone " "uses the same video backend.")); + m_sync_codes_box->setToolTip(tr("This will sync the client's AR and Gecko Codes with the host's. " + "The client will be sent the codes regardless " + "\nof whether or not the client has them.")); m_host_input_authority_box->setToolTip( tr("This gives the host control over when inputs are sent to the game, effectively " "decoupling players from each other in terms of buffering.\nThis allows players to have " @@ -189,6 +196,7 @@ void NetPlayDialog::CreateMainLayout() options_boxes->addWidget(m_save_sd_box); options_boxes->addWidget(m_load_wii_box); options_boxes->addWidget(m_sync_save_data_box); + options_boxes->addWidget(m_sync_codes_box); options_boxes->addWidget(m_record_input_box); options_boxes->addWidget(m_reduce_polling_rate_box); options_boxes->addWidget(m_strict_settings_sync_box); @@ -333,6 +341,7 @@ void NetPlayDialog::ConnectWidgets() connect(m_save_sd_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings); connect(m_load_wii_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings); connect(m_sync_save_data_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings); + connect(m_sync_codes_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings); connect(m_record_input_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings); connect(m_reduce_polling_rate_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings); connect(m_strict_settings_sync_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings); @@ -448,6 +457,7 @@ void NetPlayDialog::OnStart() settings.m_EnableGPUTextureDecoding = Config::Get(Config::GFX_ENABLE_GPU_TEXTURE_DECODING); settings.m_StrictSettingsSync = m_strict_settings_sync_box->isChecked(); settings.m_SyncSaveData = m_sync_save_data_box->isChecked(); + settings.m_SyncCodes = m_sync_codes_box->isChecked(); // Unload GameINI to restore things to normal Config::RemoveLayer(Config::LayerType::GlobalGame); @@ -496,6 +506,7 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal) m_save_sd_box->setHidden(!is_hosting); m_load_wii_box->setHidden(!is_hosting); m_sync_save_data_box->setHidden(!is_hosting); + m_sync_codes_box->setHidden(!is_hosting); m_reduce_polling_rate_box->setHidden(!is_hosting); m_strict_settings_sync_box->setHidden(!is_hosting); m_host_input_authority_box->setHidden(!is_hosting); @@ -769,6 +780,7 @@ void NetPlayDialog::SetOptionsEnabled(bool enabled) m_load_wii_box->setEnabled(enabled); m_save_sd_box->setEnabled(enabled); m_sync_save_data_box->setEnabled(enabled); + m_sync_codes_box->setEnabled(enabled); m_assign_ports_button->setEnabled(enabled); m_reduce_polling_rate_box->setEnabled(enabled); m_strict_settings_sync_box->setEnabled(enabled); @@ -955,6 +967,7 @@ void NetPlayDialog::SaveSettings() Config::SetBase(Config::NETPLAY_WRITE_SAVE_SDCARD_DATA, m_save_sd_box->isChecked()); Config::SetBase(Config::NETPLAY_LOAD_WII_SAVE, m_load_wii_box->isChecked()); Config::SetBase(Config::NETPLAY_SYNC_SAVES, m_sync_save_data_box->isChecked()); + Config::SetBase(Config::NETPLAY_SYNC_CODES, m_sync_codes_box->isChecked()); Config::SetBase(Config::NETPLAY_RECORD_INPUTS, m_record_input_box->isChecked()); Config::SetBase(Config::NETPLAY_REDUCE_POLLING_RATE, m_reduce_polling_rate_box->isChecked()); Config::SetBase(Config::NETPLAY_STRICT_SETTINGS_SYNC, m_strict_settings_sync_box->isChecked()); diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h index 0f6537f4d5..23e630cfbf 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h @@ -109,6 +109,7 @@ private: QCheckBox* m_save_sd_box; QCheckBox* m_load_wii_box; QCheckBox* m_sync_save_data_box; + QCheckBox* m_sync_codes_box; QCheckBox* m_record_input_box; QCheckBox* m_reduce_polling_rate_box; QCheckBox* m_strict_settings_sync_box;