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;