From f7e5dfe13a04c7a6776f1506b66771e22aecdc51 Mon Sep 17 00:00:00 2001 From: HeatXD <45072324+HeatXD@users.noreply.github.com> Date: Wed, 12 Apr 2023 15:42:11 +0200 Subject: [PATCH 01/11] fix comment. --- src/core/netplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index 2026c8190..1271b2f60 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -199,7 +199,7 @@ void Netplay::UpdateThrottlePeriod() void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval) { - // threshold to what is is with correcting for. + // we need a threshold since sub frame values are not worth correcting for. if (frame_delta <= 1.0f) return; From fb47a2be448d4f7f12b747d745fdc96e655219be Mon Sep 17 00:00:00 2001 From: HeatXD <45072324+HeatXD@users.noreply.github.com> Date: Fri, 14 Apr 2023 02:09:29 +0200 Subject: [PATCH 02/11] Netplay TimeSync: Forgot to take the absolute value --- src/core/netplay.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index 1271b2f60..8183ac3d9 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -199,14 +199,13 @@ void Netplay::UpdateThrottlePeriod() void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval) { - // we need a threshold since sub frame values are not worth correcting for. - if (frame_delta <= 1.0f) + // we need a threshold since low advantage frames values are not worth correcting for. + if (std::abs(frame_delta) <= 1.0f) return; - - float total_time = frame_delta * s_frame_period; // Distribute the frame difference over the next N * 0.8 frames. // only part of the interval time is used since we want to come back to normal speed. // otherwise we will keep spiraling into unplayable gameplay. + float total_time = frame_delta * s_frame_period; float added_time_per_frame = -(total_time / (static_cast(update_interval) * 0.8f)); float iterations_per_frame = 1.0f / s_frame_period; From 31a1339ace64cb949b3be699ce2101861c58f150 Mon Sep 17 00:00:00 2001 From: HeatXD <45072324+HeatXD@users.noreply.github.com> Date: Tue, 18 Apr 2023 13:38:18 +0200 Subject: [PATCH 03/11] Update netplay.cpp --- src/core/netplay.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index da3edb3e3..027d3698d 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -176,6 +176,9 @@ void Netplay::SetSettings() si.SetIntValue("Main", "RunaheadFrameCount", 0); si.SetBoolValue("Main", "RewindEnable", false); + // no block linking, it degrades loading performance + si.SetBoolValue("CPU", "RecompilerBlockLinking", false); + Host::Internal::SetNetplaySettingsLayer(&si); } @@ -201,17 +204,19 @@ void Netplay::UpdateThrottlePeriod() void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval) { // we need a threshold since low advantage frames values are not worth correcting for. - if (std::abs(frame_delta) <= 1.0f) + if (std::abs(frame_delta ) < 3.0f) return; - // Distribute the frame difference over the next N * 0.8 frames. + // only account for half the distance + // Distribute the frame difference over the next N * 0.75 frames. // only part of the interval time is used since we want to come back to normal speed. // otherwise we will keep spiraling into unplayable gameplay. float total_time = frame_delta * s_frame_period; - float added_time_per_frame = -(total_time / (static_cast(update_interval) * 0.8f)); + float mun_timesync_frames = update_interval * 0.75f; + float added_time_per_frame = -(total_time / mun_timesync_frames); float iterations_per_frame = 1.0f / s_frame_period; - + s_target_speed = (s_frame_period + added_time_per_frame) * iterations_per_frame; - s_next_timesync_recovery_frame = CurrentFrame() + static_cast(std::ceil(static_cast(update_interval) * 0.8f)); + s_next_timesync_recovery_frame = CurrentFrame() + static_cast(std::ceil(mun_timesync_frames)); UpdateThrottlePeriod(); @@ -270,8 +275,9 @@ void Netplay::AdvanceFrame(u16 checksum) void Netplay::RunFrame() { + // housekeeping + ggpo_idle(s_ggpo); // run game - bool need_idle = true; auto result = GGPO_OK; int disconnect_flags = 0; Netplay::Input inputs[2] = {}; @@ -290,13 +296,8 @@ void Netplay::RunFrame() // enable again when rolling back done SPU::SetAudioOutputMuted(false); NetplayAdvanceFrame(inputs, disconnect_flags); - // coming here means that the system doesnt need to idle anymore - need_idle = false; } } - // allow ggpo to do housekeeping if needed - if (need_idle) - ggpo_idle(s_ggpo); } s32 Netplay::CurrentFrame() @@ -371,10 +372,9 @@ void Netplay::StartNetplaySession(s32 local_handle, u16 local_port, std::string& s_game_path = std::move(game_path); // create session int result = Netplay::Start(local_handle, local_port, remote_addr, remote_port, input_delay, MAX_ROLLBACK_FRAMES); + // notify that the session failed if (result != GGPO_OK) - { Log_ErrorPrintf("Failed to Create Netplay Session! Error: %d", result); - } } void Netplay::StopNetplaySession() From df4e21a170004829b11c328258481c0f54738394 Mon Sep 17 00:00:00 2001 From: HeatXD <45072324+HeatXD@users.noreply.github.com> Date: Wed, 19 Apr 2023 21:43:24 +0200 Subject: [PATCH 04/11] Qt Loop / Netplay: Fix netplay session getting stuck when shutting down the mainwindow --- src/core/netplay.cpp | 10 +++------- src/duckstation-qt/qthost.cpp | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index 027d3698d..992e551d9 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -176,7 +176,7 @@ void Netplay::SetSettings() si.SetIntValue("Main", "RunaheadFrameCount", 0); si.SetBoolValue("Main", "RewindEnable", false); - // no block linking, it degrades loading performance + // no block linking, it degrades savestate loading performance si.SetBoolValue("CPU", "RecompilerBlockLinking", false); Host::Internal::SetNetplaySettingsLayer(&si); @@ -203,18 +203,14 @@ void Netplay::UpdateThrottlePeriod() void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval) { - // we need a threshold since low advantage frames values are not worth correcting for. - if (std::abs(frame_delta ) < 3.0f) - return; - // only account for half the distance // Distribute the frame difference over the next N * 0.75 frames. // only part of the interval time is used since we want to come back to normal speed. // otherwise we will keep spiraling into unplayable gameplay. - float total_time = frame_delta * s_frame_period; + float total_time = (frame_delta / 8) * s_frame_period; float mun_timesync_frames = update_interval * 0.75f; float added_time_per_frame = -(total_time / mun_timesync_frames); float iterations_per_frame = 1.0f / s_frame_period; - + s_target_speed = (s_frame_period + added_time_per_frame) * iterations_per_frame; s_next_timesync_recovery_frame = CurrentFrame() + static_cast(std::ceil(mun_timesync_frames)); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index e9e906b28..2520a6b53 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1466,7 +1466,7 @@ void EmuThread::run() // main loop while (!m_shutdown_flag) { - if (Netplay::IsActive()) + if (Netplay::IsActive() && System::IsRunning()) { Netplay::ExecuteNetplay(); } From f89965b57053bf8a6a6c84cce5b016264ce9dfc1 Mon Sep 17 00:00:00 2001 From: HeatXD <45072324+HeatXD@users.noreply.github.com> Date: Sun, 30 Apr 2023 00:08:54 +0200 Subject: [PATCH 05/11] Netplay: First Attempt at creating a rolling checksum for desync detection --- src/core/netplay.cpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index 992e551d9..a9678702a 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -13,6 +13,9 @@ #include "system.h" #include #include +#include +#include "host.h" + Log_SetChannel(Netplay); #ifdef _WIN32 @@ -57,6 +60,9 @@ static void InitializeFramePacing(); static void HandleTimeSyncEvent(float frame_delta, int update_interval); static void Throttle(); +// Desync Detection +static void GenerateChecksumForFrame(int* checksum, int frame, unsigned char* buffer, int buffer_size); + ////////////////////////////////////////////////////////////////////////// // Variables ////////////////////////////////////////////////////////////////////////// @@ -107,8 +113,8 @@ s32 Netplay::Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ld ggpo_start_session(&s_ggpo, &cb, "Duckstation-Netplay", 2, sizeof(Netplay::Input), lport, MAX_ROLLBACK_FRAMES); // result = ggpo_start_synctest(&s_ggpo, &cb, (char*)"asdf", 2, sizeof(Netplay::Input), 1); - ggpo_set_disconnect_timeout(s_ggpo, 3000); - ggpo_set_disconnect_notify_start(s_ggpo, 1000); + ggpo_set_disconnect_timeout(s_ggpo, 10000); + ggpo_set_disconnect_notify_start(s_ggpo, 2000); for (int i = 1; i <= 2; i++) { @@ -206,7 +212,7 @@ void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval) // Distribute the frame difference over the next N * 0.75 frames. // only part of the interval time is used since we want to come back to normal speed. // otherwise we will keep spiraling into unplayable gameplay. - float total_time = (frame_delta / 8) * s_frame_period; + float total_time = (frame_delta * s_frame_period) / 4; float mun_timesync_frames = update_interval * 0.75f; float added_time_per_frame = -(total_time / mun_timesync_frames); float iterations_per_frame = 1.0f / s_frame_period; @@ -264,6 +270,15 @@ void Netplay::Throttle() } } +void Netplay::GenerateChecksumForFrame(int* checksum, int frame, unsigned char* buffer, int buffer_size) +{ + u32 sliding_window_size = 4096; + u32 num_pages = buffer_size / sliding_window_size; + u32 start_position = (frame % num_pages) * sliding_window_size; + *checksum = XXH32(buffer + start_position, sliding_window_size, frame); + Log_VerbosePrintf("check: f:%d c:%u", frame, *checksum); +} + void Netplay::AdvanceFrame(u16 checksum) { ggpo_advance_frame(s_ggpo, checksum); @@ -314,7 +329,7 @@ Netplay::Input Netplay::ReadLocalInput() Netplay::Input inp{0}; for (u32 i = 0; i < (u32)DigitalController::Button::Count; i++) { - if (s_net_input[0][i] >= 0.25f) + if (s_net_input[0][i] >= 0.4f) inp.button_data |= 1 << i; } return inp; @@ -425,6 +440,7 @@ bool Netplay::NpBeginGameCb(void* ctx, const char* game_name) while (System::GetInternalFrameNumber() < 2) System::RunFrame(); SPU::SetAudioOutputMuted(false); + // Set Initial Frame Pacing InitializeFramePacing(); return true; } @@ -457,8 +473,13 @@ bool Netplay::NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* ch return false; } + Netplay::GenerateChecksumForFrame(checksum, frame, + reinterpret_cast(our_buffer.get()->state_stream.get()->GetMemoryPointer()), + our_buffer.get()->state_stream.get()->GetMemorySize()); + *len = sizeof(System::MemorySaveState); *buffer = reinterpret_cast(our_buffer.release()); + return true; } @@ -521,6 +542,7 @@ bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev) sprintf(buff, "Netplay Desync Detected!: Frame: %d, L:%u, R:%u", ev->u.desync.nFrameOfDesync, ev->u.desync.ourCheckSum, ev->u.desync.remoteChecksum); msg = buff; + Host::AddKeyedOSDMessage("Netplay", msg); break; default: sprintf(buff, "Netplay Event Code: %d", ev->code); From 4b60d69cf791561f38ccb5c217db2c6b69de4fbd Mon Sep 17 00:00:00 2001 From: HeatXD <45072324+HeatXD@users.noreply.github.com> Date: Tue, 2 May 2023 01:50:10 +0200 Subject: [PATCH 06/11] GGPO/Netplay: modified desync detection to allow retrieval of checksums from savestates instead of passing it trough a param. --- dep/ggpo-x/src/backends/p2p.cpp | 25 ++++++++++++------------- dep/ggpo-x/src/sync.h | 3 ++- src/core/netplay.cpp | 23 +++++++++++------------ 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/dep/ggpo-x/src/backends/p2p.cpp b/dep/ggpo-x/src/backends/p2p.cpp index 210ae2e35..4224270c5 100644 --- a/dep/ggpo-x/src/backends/p2p.cpp +++ b/dep/ggpo-x/src/backends/p2p.cpp @@ -428,21 +428,23 @@ GGPOErrorCode Peer2PeerBackend::SetManualNetworkPolling(bool value) GGPOErrorCode Peer2PeerBackend::IncrementFrame(uint16_t checksum1) -{ +{ auto currentFrame = _sync.GetFrameCount(); + _sync.IncrementFrame(); + checksum1 = _sync.GetLastSavedFrame().checksum; char buf[256]; uint16_t cSum = checksum1; - Log("End of frame (%d)...\n", _sync.GetFrameCount()); + Log("End of frame (%d)...\n", currentFrame); static int maxDif = 0; - if (_pendingCheckSums.count(_sync.GetFrameCount())) + if (_pendingCheckSums.count(currentFrame)) { auto max = _pendingCheckSums.rbegin()->first; auto diff = max - currentFrame; maxDif = max(maxDif, diff); - int oldChecksum = _pendingCheckSums[_sync.GetFrameCount()]; - _pendingCheckSums[_sync.GetFrameCount()] = cSum; - sprintf_s<256>(buf, "Replace local checksum for frame %d: %d with %d, newest frame is %d, max diff %d\n", _sync.GetFrameCount(), oldChecksum, _pendingCheckSums[_sync.GetFrameCount()], max, maxDif); - + int oldChecksum = _pendingCheckSums[currentFrame]; + _pendingCheckSums[currentFrame] = cSum; + sprintf_s<256>(buf, "Replace local checksum for frame %d: %d with %d, newest frame is %d, max diff %d\n", + currentFrame, oldChecksum, _pendingCheckSums[currentFrame], max, maxDif); if (currentFrame <= _confirmedCheckSumFrame) { @@ -461,16 +463,13 @@ Peer2PeerBackend::IncrementFrame(uint16_t checksum1) } else { - sprintf_s<256>(buf, "Added local checksum for frame %d: %d\n", _sync.GetFrameCount(), cSum); + sprintf_s<256>(buf, "Added local checksum for frame %d: %d\n", currentFrame, cSum); //OutputDebugStringA(buf); } - _pendingCheckSums[_sync.GetFrameCount()]= cSum ; + _pendingCheckSums[currentFrame] = cSum; - - - _sync.IncrementFrame(); - DoPoll(); + DoPoll(); PollSyncEvents(); return GGPO_OK; diff --git a/dep/ggpo-x/src/sync.h b/dep/ggpo-x/src/sync.h index 76308ae2b..64e8353fd 100644 --- a/dep/ggpo-x/src/sync.h +++ b/dep/ggpo-x/src/sync.h @@ -61,7 +61,8 @@ public: bool GetEvent(Event& e); int MaxPredictionFrames() const { return _max_prediction_frames; } protected: - friend SyncTestBackend; + friend class SyncTestBackend; + friend class Peer2PeerBackend; struct SavedFrame { byte* buf; diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index a9678702a..037d0a191 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -7,6 +7,7 @@ #include "common/timer.h" #include "digital_controller.h" #include "ggponet.h" +#include "host.h" #include "host_settings.h" #include "pad.h" #include "spu.h" @@ -14,7 +15,6 @@ #include #include #include -#include "host.h" Log_SetChannel(Netplay); @@ -84,7 +84,6 @@ static float s_target_speed = 1.0f; static Common::Timer::Value s_frame_period = 0; static Common::Timer::Value s_next_frame_time = 0; static s32 s_next_timesync_recovery_frame = -1; - } // namespace Netplay // Netplay Impl @@ -113,8 +112,8 @@ s32 Netplay::Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ld ggpo_start_session(&s_ggpo, &cb, "Duckstation-Netplay", 2, sizeof(Netplay::Input), lport, MAX_ROLLBACK_FRAMES); // result = ggpo_start_synctest(&s_ggpo, &cb, (char*)"asdf", 2, sizeof(Netplay::Input), 1); - ggpo_set_disconnect_timeout(s_ggpo, 10000); - ggpo_set_disconnect_notify_start(s_ggpo, 2000); + ggpo_set_disconnect_timeout(s_ggpo, 3000); + ggpo_set_disconnect_notify_start(s_ggpo, 1000); for (int i = 1; i <= 2; i++) { @@ -212,7 +211,7 @@ void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval) // Distribute the frame difference over the next N * 0.75 frames. // only part of the interval time is used since we want to come back to normal speed. // otherwise we will keep spiraling into unplayable gameplay. - float total_time = (frame_delta * s_frame_period) / 4; + float total_time = (frame_delta * s_frame_period) / 8; float mun_timesync_frames = update_interval * 0.75f; float added_time_per_frame = -(total_time / mun_timesync_frames); float iterations_per_frame = 1.0f / s_frame_period; @@ -272,11 +271,11 @@ void Netplay::Throttle() void Netplay::GenerateChecksumForFrame(int* checksum, int frame, unsigned char* buffer, int buffer_size) { - u32 sliding_window_size = 4096; - u32 num_pages = buffer_size / sliding_window_size; - u32 start_position = (frame % num_pages) * sliding_window_size; + const u32 sliding_window_size = 4096 * 4; // 4 pages. + const u32 num_group_of_pages = buffer_size / sliding_window_size; + const u32 start_position = (frame % num_group_of_pages) * sliding_window_size; *checksum = XXH32(buffer + start_position, sliding_window_size, frame); - Log_VerbosePrintf("check: f:%d c:%u", frame, *checksum); + // Log_InfoPrintf("check: f:%d c:%u", frame, *checksum); } void Netplay::AdvanceFrame(u16 checksum) @@ -473,9 +472,9 @@ bool Netplay::NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* ch return false; } - Netplay::GenerateChecksumForFrame(checksum, frame, - reinterpret_cast(our_buffer.get()->state_stream.get()->GetMemoryPointer()), - our_buffer.get()->state_stream.get()->GetMemorySize()); + GenerateChecksumForFrame(checksum, frame, + reinterpret_cast(our_buffer.get()->state_stream.get()->GetMemoryPointer()), + our_buffer.get()->state_stream.get()->GetMemorySize()); *len = sizeof(System::MemorySaveState); *buffer = reinterpret_cast(our_buffer.release()); From f544a250d32068aae19e8a5663f5a558163013a8 Mon Sep 17 00:00:00 2001 From: HeatXD <45072324+HeatXD@users.noreply.github.com> Date: Tue, 2 May 2023 01:52:06 +0200 Subject: [PATCH 07/11] Netplay Desync Detection: for some games CDROM async readahead seems to cause desyncs (most notable during testing were the tekken games) so for now disabling it during netplay games seems like the only option. --- src/core/netplay.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index 037d0a191..47ddd7873 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -184,6 +184,10 @@ void Netplay::SetSettings() // no block linking, it degrades savestate loading performance si.SetBoolValue("CPU", "RecompilerBlockLinking", false); + // for some games CDROM async readahead seems to cause desyncs (most notable during testing were the tekken games) + // so for now disabling it seems like the only option + si.SetIntValue("CDROM", "ReadaheadSectors", 0); + Host::Internal::SetNetplaySettingsLayer(&si); } @@ -538,11 +542,12 @@ bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev) HandleTimeSyncEvent(ev->u.timesync.frames_ahead, ev->u.timesync.timeSyncPeriodInFrames); break; case GGPOEventCode::GGPO_EVENTCODE_DESYNC: - sprintf(buff, "Netplay Desync Detected!: Frame: %d, L:%u, R:%u", ev->u.desync.nFrameOfDesync, + sprintf(buff, "Possible Desync Detected: Frame: %d, L:%u, R:%u", ev->u.desync.nFrameOfDesync, ev->u.desync.ourCheckSum, ev->u.desync.remoteChecksum); msg = buff; - Host::AddKeyedOSDMessage("Netplay", msg); - break; + Log_InfoPrintf("%s", msg.c_str()); + Host::AddKeyedOSDMessage("Netplay", msg, 5); + return true; default: sprintf(buff, "Netplay Event Code: %d", ev->code); msg = buff; From ffe70067c2bd59a980a16eb527856b45828faba5 Mon Sep 17 00:00:00 2001 From: HeatXD <45072324+HeatXD@users.noreply.github.com> Date: Tue, 2 May 2023 21:35:22 +0200 Subject: [PATCH 08/11] Netplay: Ok After more testing it doesnt seem to be the CDROM. so im reveting this change. its weird tho its seems to be fine in the first session created but after when a new session is created it is prone to desync. this doesnt happen when starting the session with an identical savestate for both players tho. GGPO: Changed the size of the checksums from u16 to u32. --- dep/ggpo-x/include/ggponet.h | 4 ++-- dep/ggpo-x/src/backends/p2p.cpp | 7 +++---- dep/ggpo-x/src/backends/p2p.h | 6 +++--- dep/ggpo-x/src/game_input.h | 2 +- dep/ggpo-x/src/network/udp_msg.h | 2 +- dep/ggpo-x/src/network/udp_proto.cpp | 4 ++-- dep/ggpo-x/src/network/udp_proto.h | 4 ++-- src/core/netplay.cpp | 14 +++++--------- 8 files changed, 19 insertions(+), 24 deletions(-) diff --git a/dep/ggpo-x/include/ggponet.h b/dep/ggpo-x/include/ggponet.h index 188dfcaba..cb306fa1b 100644 --- a/dep/ggpo-x/include/ggponet.h +++ b/dep/ggpo-x/include/ggponet.h @@ -196,8 +196,8 @@ typedef struct { } chat; struct { int nFrameOfDesync; - uint16_t ourCheckSum; - uint16_t remoteChecksum; + uint32_t ourCheckSum; + uint32_t remoteChecksum; } desync; } u; } GGPOEvent; diff --git a/dep/ggpo-x/src/backends/p2p.cpp b/dep/ggpo-x/src/backends/p2p.cpp index 4224270c5..0bc8a388b 100644 --- a/dep/ggpo-x/src/backends/p2p.cpp +++ b/dep/ggpo-x/src/backends/p2p.cpp @@ -431,9 +431,8 @@ Peer2PeerBackend::IncrementFrame(uint16_t checksum1) { auto currentFrame = _sync.GetFrameCount(); _sync.IncrementFrame(); - checksum1 = _sync.GetLastSavedFrame().checksum; + uint32 cSum = _sync.GetLastSavedFrame().checksum; char buf[256]; - uint16_t cSum = checksum1; Log("End of frame (%d)...\n", currentFrame); static int maxDif = 0; if (_pendingCheckSums.count(currentFrame)) @@ -510,7 +509,7 @@ Peer2PeerBackend::PollUdpProtocolEvents(void) //} } -void Peer2PeerBackend::CheckRemoteChecksum(int framenumber, uint16 cs) +void Peer2PeerBackend::CheckRemoteChecksum(int framenumber, uint32 cs) { if (framenumber <= _sync.MaxPredictionFrames()) return; @@ -521,7 +520,7 @@ void Peer2PeerBackend::CheckRemoteChecksum(int framenumber, uint16 cs) int Peer2PeerBackend::HowFarBackForChecksums()const { - return 16; + return 32; }/* uint16 Peer2PeerBackend::GetChecksumForConfirmedFrame(int frameNumber) const { diff --git a/dep/ggpo-x/src/backends/p2p.h b/dep/ggpo-x/src/backends/p2p.h index 100df7dc6..7835a0cb9 100644 --- a/dep/ggpo-x/src/backends/p2p.h +++ b/dep/ggpo-x/src/backends/p2p.h @@ -82,11 +82,11 @@ protected: int nFrame; int checkSum;; }; - std::map _pendingCheckSums; - std::map _confirmedCheckSums; + std::map _pendingCheckSums; + std::map _confirmedCheckSums; // uint16 GetChecksumForConfirmedFrame(int frameNumber) const; - void CheckRemoteChecksum(int framenumber, uint16 cs); + void CheckRemoteChecksum(int framenumber, uint32 cs); int HowFarBackForChecksums()const; int _confirmedCheckSumFrame = -500; void CheckDesync(); diff --git a/dep/ggpo-x/src/game_input.h b/dep/ggpo-x/src/game_input.h index a19b834ba..834a0d7f3 100644 --- a/dep/ggpo-x/src/game_input.h +++ b/dep/ggpo-x/src/game_input.h @@ -24,7 +24,7 @@ struct GameInput { int frame; int size; /* size in bytes of the entire input for all players */ char bits[GAMEINPUT_MAX_BYTES * GAMEINPUT_MAX_PLAYERS]; - uint16 checksum; + uint32 checksum; bool is_null() { return frame == NullFrame; } void init(int frame, char *bits, int size, int offset); void init(int frame, char *bits, int size); diff --git a/dep/ggpo-x/src/network/udp_msg.h b/dep/ggpo-x/src/network/udp_msg.h index e21d24f2a..1a4b6e4ff 100644 --- a/dep/ggpo-x/src/network/udp_msg.h +++ b/dep/ggpo-x/src/network/udp_msg.h @@ -67,7 +67,7 @@ struct UdpMsg int ack_frame:31; uint16 num_bits; - uint16 checksum16; + uint32 checksum32; uint8 input_size; // XXX: shouldn't be in every single packet! uint8 bits[MAX_COMPRESSED_BITS]; /* must be last */ } input; diff --git a/dep/ggpo-x/src/network/udp_proto.cpp b/dep/ggpo-x/src/network/udp_proto.cpp index bfee20fde..1197ddccb 100644 --- a/dep/ggpo-x/src/network/udp_proto.cpp +++ b/dep/ggpo-x/src/network/udp_proto.cpp @@ -134,7 +134,7 @@ UdpProtocol::SendPendingOutput() ASSERT(last.frame == -1 || last.frame + 1 == msg->u.input.start_frame); for (j = 0; j < _pending_output.size(); j++) { GameInput ¤t = _pending_output.item(j); - msg->u.input.checksum16 = current.checksum; + msg->u.input.checksum32 = current.checksum; if (memcmp(current.bits, last.bits, current.size) != 0) { ASSERT((GAMEINPUT_MAX_BYTES * GAMEINPUT_MAX_PLAYERS * 8) < (1 << BITVECTOR_NIBBLE_SIZE)); for (i = 0; i < current.size * 8; i++) { @@ -627,7 +627,7 @@ UdpProtocol::OnInput(UdpMsg *msg, int len) char desc[1024]; ASSERT(currentFrame == _last_received_input.frame + 1); _last_received_input.frame = currentFrame; - _last_received_input.checksum = msg->u.input.checksum16; + _last_received_input.checksum = msg->u.input.checksum32; /* * Send the event to the emualtor */ diff --git a/dep/ggpo-x/src/network/udp_proto.h b/dep/ggpo-x/src/network/udp_proto.h index 14947e27f..086beda1c 100644 --- a/dep/ggpo-x/src/network/udp_proto.h +++ b/dep/ggpo-x/src/network/udp_proto.h @@ -94,8 +94,8 @@ public: void ApplyToEvents(std::function cb); void StartPollLoop(); void EndPollLoop(); - std::map _remoteCheckSums; - std::map _remoteCheckSumsThisFrame; + std::map _remoteCheckSums; + std::map _remoteCheckSumsThisFrame; protected: enum State { Syncing, diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index 47ddd7873..a052c03c6 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -112,7 +112,7 @@ s32 Netplay::Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ld ggpo_start_session(&s_ggpo, &cb, "Duckstation-Netplay", 2, sizeof(Netplay::Input), lport, MAX_ROLLBACK_FRAMES); // result = ggpo_start_synctest(&s_ggpo, &cb, (char*)"asdf", 2, sizeof(Netplay::Input), 1); - ggpo_set_disconnect_timeout(s_ggpo, 3000); + ggpo_set_disconnect_timeout(s_ggpo, 2000); ggpo_set_disconnect_notify_start(s_ggpo, 1000); for (int i = 1; i <= 2; i++) @@ -184,10 +184,6 @@ void Netplay::SetSettings() // no block linking, it degrades savestate loading performance si.SetBoolValue("CPU", "RecompilerBlockLinking", false); - // for some games CDROM async readahead seems to cause desyncs (most notable during testing were the tekken games) - // so for now disabling it seems like the only option - si.SetIntValue("CDROM", "ReadaheadSectors", 0); - Host::Internal::SetNetplaySettingsLayer(&si); } @@ -215,7 +211,7 @@ void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval) // Distribute the frame difference over the next N * 0.75 frames. // only part of the interval time is used since we want to come back to normal speed. // otherwise we will keep spiraling into unplayable gameplay. - float total_time = (frame_delta * s_frame_period) / 8; + float total_time = (frame_delta * s_frame_period) / 4; float mun_timesync_frames = update_interval * 0.75f; float added_time_per_frame = -(total_time / mun_timesync_frames); float iterations_per_frame = 1.0f / s_frame_period; @@ -279,7 +275,7 @@ void Netplay::GenerateChecksumForFrame(int* checksum, int frame, unsigned char* const u32 num_group_of_pages = buffer_size / sliding_window_size; const u32 start_position = (frame % num_group_of_pages) * sliding_window_size; *checksum = XXH32(buffer + start_position, sliding_window_size, frame); - // Log_InfoPrintf("check: f:%d c:%u", frame, *checksum); + Log_VerbosePrintf("Netplay Checksum: f:%d wf:%d c:%u", frame, frame % num_group_of_pages, *checksum); } void Netplay::AdvanceFrame(u16 checksum) @@ -332,7 +328,7 @@ Netplay::Input Netplay::ReadLocalInput() Netplay::Input inp{0}; for (u32 i = 0; i < (u32)DigitalController::Button::Count; i++) { - if (s_net_input[0][i] >= 0.4f) + if (s_net_input[0][i] >= 0.25f) inp.button_data |= 1 << i; } return inp; @@ -432,7 +428,7 @@ bool Netplay::NpBeginGameCb(void* ctx, const char* game_name) System::ShutdownSystem(false); // fast boot the selected game and wait for the other player auto param = SystemBootParameters(s_game_path); - param.override_fast_boot = true; + // param.save_state = EmuFolders::SaveStates + "/SLUS-00402_2.sav"; if (!System::BootSystem(param)) { StopNetplaySession(); From e2b907f5b05f69fa9e9840489143170dd99d3711 Mon Sep 17 00:00:00 2001 From: HeatXD <45072324+HeatXD@users.noreply.github.com> Date: Tue, 2 May 2023 21:39:27 +0200 Subject: [PATCH 09/11] Netplay: Removed unused param AvancedFrame --- src/core/netplay.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index a052c03c6..10fd0165c 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -15,7 +15,6 @@ #include #include #include - Log_SetChannel(Netplay); #ifdef _WIN32 @@ -48,7 +47,7 @@ static void SetSettings(); // l = local, r = remote static s32 Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ldelay, u32 pred); -static void AdvanceFrame(u16 checksum = 0); +static void AdvanceFrame(); static void RunFrame(); static s32 CurrentFrame(); @@ -278,9 +277,9 @@ void Netplay::GenerateChecksumForFrame(int* checksum, int frame, unsigned char* Log_VerbosePrintf("Netplay Checksum: f:%d wf:%d c:%u", frame, frame % num_group_of_pages, *checksum); } -void Netplay::AdvanceFrame(u16 checksum) +void Netplay::AdvanceFrame() { - ggpo_advance_frame(s_ggpo, checksum); + ggpo_advance_frame(s_ggpo, 0); } void Netplay::RunFrame() From 438fd768a990757b2e072ec0d3e02c4d9cd5826c Mon Sep 17 00:00:00 2001 From: HeatXD <45072324+HeatXD@users.noreply.github.com> Date: Wed, 3 May 2023 23:40:55 +0200 Subject: [PATCH 10/11] Netplay: Still figuring out games tend to desync after the first boot of a game. --- src/core/netplay.cpp | 9 +++++++-- src/core/system.cpp | 12 ++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index 10fd0165c..fe7db7ca5 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -182,6 +182,8 @@ void Netplay::SetSettings() // no block linking, it degrades savestate loading performance si.SetBoolValue("CPU", "RecompilerBlockLinking", false); + // not sure its needed but enabled for now. TODO + si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", true); Host::Internal::SetNetplaySettingsLayer(&si); } @@ -427,14 +429,17 @@ bool Netplay::NpBeginGameCb(void* ctx, const char* game_name) System::ShutdownSystem(false); // fast boot the selected game and wait for the other player auto param = SystemBootParameters(s_game_path); - // param.save_state = EmuFolders::SaveStates + "/SLUS-00402_2.sav"; + param.override_fast_boot = true; if (!System::BootSystem(param)) { StopNetplaySession(); return false; } - // Fast Forward to Game Start SPU::SetAudioOutputMuted(true); + // Load savestate if available + std::string save = EmuFolders::SaveStates + "/netplay/" + System::GetRunningSerial() + ".sav"; + System::LoadState(save.c_str()); + // Fast Forward to Game Start if needed. while (System::GetInternalFrameNumber() < 2) System::RunFrame(); SPU::SetAudioOutputMuted(false); diff --git a/src/core/system.cpp b/src/core/system.cpp index 606db61d4..bc715b1ec 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1752,6 +1752,18 @@ bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di cpu_overclock_active ? Settings::CPUOverclockFractionToPercent(cpu_overclock_numerator, cpu_overclock_denominator) : 100u); + + // during netplay if a file savestate is loaded set + // the overclocks to the same value as the savestate + // file savestates are usually only loaded at game start + if (Netplay::IsActive() && !is_memory_state) + { + g_settings.cpu_overclock_enable = true; + g_settings.cpu_overclock_active = cpu_overclock_active; + g_settings.cpu_overclock_numerator = cpu_overclock_numerator; + g_settings.cpu_overclock_denominator = cpu_overclock_denominator; + } + UpdateOverclock(); } From d5c3d82e67110ad94cd781b43b8845a9c75fa8f3 Mon Sep 17 00:00:00 2001 From: HeatXD <45072324+HeatXD@users.noreply.github.com> Date: Fri, 5 May 2023 04:59:39 +0200 Subject: [PATCH 11/11] Netplay: Desync detection state dumping for debugging. Netplay: Update the overclock ratio if the loaded file savestate had an active overclock. GGPO: Make ggpo report the frame that should be freed --- dep/ggpo-x/include/ggponet.h | 2 +- dep/ggpo-x/src/backends/p2p.cpp | 2 +- dep/ggpo-x/src/backends/synctest.cpp | 2 +- dep/ggpo-x/src/sync.cpp | 4 +- src/core/netplay.cpp | 79 +++++++++++++++++++++------- src/core/system.cpp | 11 ++-- 6 files changed, 73 insertions(+), 27 deletions(-) diff --git a/dep/ggpo-x/include/ggponet.h b/dep/ggpo-x/include/ggponet.h index cb306fa1b..8888d3372 100644 --- a/dep/ggpo-x/include/ggponet.h +++ b/dep/ggpo-x/include/ggponet.h @@ -248,7 +248,7 @@ typedef struct { * free_buffer - Frees a game state allocated in save_game_state. You * should deallocate the memory contained in the buffer. */ - void (__cdecl *free_buffer)(void* context, void *buffer); + void (__cdecl *free_buffer)(void* context, void *buffer, int frame); /* * advance_frame - Called during a rollback. You should advance your game diff --git a/dep/ggpo-x/src/backends/p2p.cpp b/dep/ggpo-x/src/backends/p2p.cpp index 0bc8a388b..ce3fdcc58 100644 --- a/dep/ggpo-x/src/backends/p2p.cpp +++ b/dep/ggpo-x/src/backends/p2p.cpp @@ -520,7 +520,7 @@ void Peer2PeerBackend::CheckRemoteChecksum(int framenumber, uint32 cs) int Peer2PeerBackend::HowFarBackForChecksums()const { - return 32; + return _sync.MaxPredictionFrames() + 2; }/* uint16 Peer2PeerBackend::GetChecksumForConfirmedFrame(int frameNumber) const { diff --git a/dep/ggpo-x/src/backends/synctest.cpp b/dep/ggpo-x/src/backends/synctest.cpp index c82a9c60d..a8fbe0884 100644 --- a/dep/ggpo-x/src/backends/synctest.cpp +++ b/dep/ggpo-x/src/backends/synctest.cpp @@ -158,7 +158,7 @@ SyncTestBackend::IncrementFrame(uint16_t cs) RaiseSyncError("Checksum for frame %d does not match saved (%d != %d)", frame, checksum, info.checksum); } printf("Checksum %08d for frame %d matches.\n", checksum, info.frame); - _callbacks.free_buffer(_callbacks.context, info.buf); + _callbacks.free_buffer(_callbacks.context, info.buf, info.frame); } _last_verified = frame; _rollingback = false; diff --git a/dep/ggpo-x/src/sync.cpp b/dep/ggpo-x/src/sync.cpp index 6053d0eb2..a4e69c725 100644 --- a/dep/ggpo-x/src/sync.cpp +++ b/dep/ggpo-x/src/sync.cpp @@ -24,7 +24,7 @@ Sync::~Sync() * structure so we can efficently copy frames via weak references. */ for (int i = 0; i < _savedstate.frames.size(); i++) { - _callbacks.free_buffer(_callbacks.context, _savedstate.frames[i].buf); + _callbacks.free_buffer(_callbacks.context, _savedstate.frames[i].buf, _savedstate.frames[i].frame); } delete [] _input_queues; _input_queues = NULL; @@ -204,7 +204,7 @@ Sync::SaveCurrentFrame() */ SavedFrame *state = &_savedstate.frames[_savedstate.head]; if (state->buf) { - _callbacks.free_buffer(_callbacks.context, state->buf); + _callbacks.free_buffer(_callbacks.context, state->buf, state->frame); state->buf = NULL; } state->frame = _framecount; diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index fe7db7ca5..48577c10b 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -34,7 +34,7 @@ static bool NpAdvFrameCb(void* ctx, int flags); static bool NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* checksum, int frame); static bool NpLoadFrameCb(void* ctx, unsigned char* buffer, int len, int rb_frames, int frame_to_load); static bool NpBeginGameCb(void* ctx, const char* game_name); -static void NpFreeBuffCb(void* ctx, void* buffer); +static void NpFreeBuffCb(void* ctx, void* buffer, int frame); static bool NpOnEventCb(void* ctx, GGPOEvent* ev); static Input ReadLocalInput(); @@ -61,7 +61,7 @@ static void Throttle(); // Desync Detection static void GenerateChecksumForFrame(int* checksum, int frame, unsigned char* buffer, int buffer_size); - +static void GenerateDesyncReport(s32 desync_frame); ////////////////////////////////////////////////////////////////////////// // Variables ////////////////////////////////////////////////////////////////////////// @@ -83,6 +83,7 @@ static float s_target_speed = 1.0f; static Common::Timer::Value s_frame_period = 0; static Common::Timer::Value s_next_frame_time = 0; static s32 s_next_timesync_recovery_frame = -1; + } // namespace Netplay // Netplay Impl @@ -182,7 +183,7 @@ void Netplay::SetSettings() // no block linking, it degrades savestate loading performance si.SetBoolValue("CPU", "RecompilerBlockLinking", false); - // not sure its needed but enabled for now. TODO + // not sure its needed but enabled for now... TODO si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", true); Host::Internal::SetNetplaySettingsLayer(&si); @@ -276,9 +277,41 @@ void Netplay::GenerateChecksumForFrame(int* checksum, int frame, unsigned char* const u32 num_group_of_pages = buffer_size / sliding_window_size; const u32 start_position = (frame % num_group_of_pages) * sliding_window_size; *checksum = XXH32(buffer + start_position, sliding_window_size, frame); - Log_VerbosePrintf("Netplay Checksum: f:%d wf:%d c:%u", frame, frame % num_group_of_pages, *checksum); + // Log_VerbosePrintf("Netplay Checksum: f:%d wf:%d c:%u", frame, frame % num_group_of_pages, *checksum); } +void Netplay::GenerateDesyncReport(s32 desync_frame) +{ + std::string path = "\\netplaylogs\\desync_frame_" + std::to_string(desync_frame) + "_p" + + std::to_string(s_local_handle) + "_" + System::GetRunningSerial() + "_.txt"; + std::string filename = EmuFolders::Dumps + path; + + std::unique_ptr stream = + ByteStream::OpenFile(filename.c_str(), BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_TRUNCATE | + BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED); + if (!stream) + { + Log_VerbosePrint("desync log creation failed to create stream"); + return; + } + + if (!ByteStream::WriteBinaryToStream(stream.get(), + s_save_buffer_pool.back().get()->state_stream.get()->GetMemoryPointer(), + s_save_buffer_pool.back().get()->state_stream.get()->GetMemorySize())) + { + Log_VerbosePrint("desync log creation failed to write the stream"); + stream->Discard(); + return; + } + /* stream->Write(s_save_buffer_pool.back().get()->state_stream.get()->GetMemoryPointer(), + s_save_buffer_pool.back().get()->state_stream.get()->GetMemorySize());*/ + + stream->Commit(); + + Log_VerbosePrintf("desync log created for frame %d", desync_frame); +} + + void Netplay::AdvanceFrame() { ggpo_advance_frame(s_ggpo, 0); @@ -386,6 +419,12 @@ void Netplay::StartNetplaySession(s32 local_handle, u16 local_port, std::string& // notify that the session failed if (result != GGPO_OK) Log_ErrorPrintf("Failed to Create Netplay Session! Error: %d", result); + else + { + // Load savestate if available + std::string save = EmuFolders::SaveStates + "/netplay/" + System::GetRunningSerial() + ".sav"; + System::LoadState(save.c_str()); + } } void Netplay::StopNetplaySession() @@ -436,9 +475,6 @@ bool Netplay::NpBeginGameCb(void* ctx, const char* game_name) return false; } SPU::SetAudioOutputMuted(true); - // Load savestate if available - std::string save = EmuFolders::SaveStates + "/netplay/" + System::GetRunningSerial() + ".sav"; - System::LoadState(save.c_str()); // Fast Forward to Game Start if needed. while (System::GetInternalFrameNumber() < 2) System::RunFrame(); @@ -460,25 +496,27 @@ bool Netplay::NpAdvFrameCb(void* ctx, int flags) bool Netplay::NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* checksum, int frame) { SaveStateBuffer our_buffer; - if (s_save_buffer_pool.empty()) + // min size is 2 because otherwise the desync logger doesnt have enough time to dump the state. + if (s_save_buffer_pool.size() < 2) { our_buffer = std::make_unique(); } else { - our_buffer = std::move(s_save_buffer_pool.back()); - s_save_buffer_pool.pop_back(); + our_buffer = std::move(s_save_buffer_pool.front()); + s_save_buffer_pool.pop_front(); } if (!System::SaveMemoryState(our_buffer.get())) { - s_save_buffer_pool.push_back(std::move(our_buffer)); + s_save_buffer_pool.push_front(std::move(our_buffer)); return false; } - GenerateChecksumForFrame(checksum, frame, - reinterpret_cast(our_buffer.get()->state_stream.get()->GetMemoryPointer()), - our_buffer.get()->state_stream.get()->GetMemorySize()); + // desync detection + const u32 state_size = our_buffer.get()->state_stream.get()->GetMemorySize(); + unsigned char* state = reinterpret_cast(our_buffer.get()->state_stream.get()->GetMemoryPointer()); + GenerateChecksumForFrame(checksum, frame, state, state_size); *len = sizeof(System::MemorySaveState); *buffer = reinterpret_cast(our_buffer.release()); @@ -494,8 +532,9 @@ bool Netplay::NpLoadFrameCb(void* ctx, unsigned char* buffer, int len, int rb_fr return System::LoadMemoryState(*reinterpret_cast(buffer)); } -void Netplay::NpFreeBuffCb(void* ctx, void* buffer) +void Netplay::NpFreeBuffCb(void* ctx, void* buffer, int frame) { + // Log_VerbosePrintf("Reuse Buffer: %d", frame); SaveStateBuffer our_buffer(reinterpret_cast(buffer)); s_save_buffer_pool.push_back(std::move(our_buffer)); } @@ -503,7 +542,7 @@ void Netplay::NpFreeBuffCb(void* ctx, void* buffer) bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev) { char buff[128]; - std::string msg; + std::string msg, filename; switch (ev->code) { case GGPOEventCode::GGPO_EVENTCODE_CONNECTED_TO_PEER: @@ -542,11 +581,13 @@ bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev) HandleTimeSyncEvent(ev->u.timesync.frames_ahead, ev->u.timesync.timeSyncPeriodInFrames); break; case GGPOEventCode::GGPO_EVENTCODE_DESYNC: - sprintf(buff, "Possible Desync Detected: Frame: %d, L:%u, R:%u", ev->u.desync.nFrameOfDesync, - ev->u.desync.ourCheckSum, ev->u.desync.remoteChecksum); + sprintf(buff, "Desync Detected: Current Frame: %d, Desync Frame: %d, Diff: %d, L:%u, R:%u", CurrentFrame(), + ev->u.desync.nFrameOfDesync, CurrentFrame() - ev->u.desync.nFrameOfDesync, ev->u.desync.ourCheckSum, + ev->u.desync.remoteChecksum); msg = buff; - Log_InfoPrintf("%s", msg.c_str()); + GenerateDesyncReport(ev->u.desync.nFrameOfDesync); Host::AddKeyedOSDMessage("Netplay", msg, 5); + return true; default: sprintf(buff, "Netplay Event Code: %d", ev->code); diff --git a/src/core/system.cpp b/src/core/system.cpp index bc715b1ec..3704ba65f 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1756,12 +1756,17 @@ bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di // during netplay if a file savestate is loaded set // the overclocks to the same value as the savestate // file savestates are usually only loaded at game start - if (Netplay::IsActive() && !is_memory_state) + if (Netplay::IsActive() && !is_memory_state && cpu_overclock_active) { - g_settings.cpu_overclock_enable = true; - g_settings.cpu_overclock_active = cpu_overclock_active; + g_settings.cpu_overclock_enable = cpu_overclock_active; g_settings.cpu_overclock_numerator = cpu_overclock_numerator; g_settings.cpu_overclock_denominator = cpu_overclock_denominator; + + g_settings.UpdateOverclockActive(); + + Host::AddFormattedOSDMessage(10.0f, + Host::TranslateString("OSDMessage", "WARNING: CPU overclock was changed to (%u%%) to match netplay savestate."), + g_settings.cpu_overclock_enable ? g_settings.GetCPUOverclockPercent() : 100u ); } UpdateOverclock();