diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index e08c3f58d2..604de6f170 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -29,6 +29,7 @@ #include "Core/Host.h" #include "Core/MemTools.h" #include "Core/Movie.h" +#include "Core/NetPlayClient.h" #include "Core/NetPlayProto.h" #include "Core/PatchEngine.h" #include "Core/State.h" @@ -129,6 +130,12 @@ void SetIsFramelimiterTempDisabled(bool disable) std::string GetStateFileName() { return s_state_filename; } void SetStateFileName(const std::string& val) { s_state_filename = val; } +void FrameUpdateOnCPUThread() +{ + if (NetPlay::IsNetPlayRunning()) + NetPlayClient::SendTimeBase(); +} + // Display messages and return values // Formatted stop message diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h index b86168f3f6..1eea2be226 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -63,6 +63,8 @@ void SetStateFileName(const std::string& val); void SetBlockStart(u32 addr); +void FrameUpdateOnCPUThread(); + bool ShouldSkipFrame(int skipped); void VideoThrottle(); void RequestRefreshInfo(); diff --git a/Source/Core/Core/Movie.cpp b/Source/Core/Core/Movie.cpp index b59ff1d78a..de457e7b16 100644 --- a/Source/Core/Core/Movie.cpp +++ b/Source/Core/Core/Movie.cpp @@ -17,6 +17,7 @@ #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/Movie.h" +#include "Core/NetPlayClient.h" #include "Core/NetPlayProto.h" #include "Core/State.h" #include "Core/DSP/DSPCore.h" @@ -139,6 +140,8 @@ std::string GetInputDisplay() void FrameUpdate() { + // TODO[comex]: This runs on the GPU thread, yet it messes with the CPU + // state directly. That's super sketchy. g_currentFrame++; if (!s_bPolled) g_currentLagCount++; diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 3f53d58a32..eeae193110 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -442,6 +442,26 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) } break; + case NP_MSG_DESYNC_DETECTED: + { + int pid_to_blame; + u32 frame; + packet >> pid_to_blame; + packet >> frame; + const char* blame_str = ""; + const char* blame_name = ""; + std::lock_guard lkp(m_crit.players); + if (pid_to_blame != -1) + { + auto it = m_players.find(pid_to_blame); + blame_str = " from player "; + blame_name = it != m_players.end() ? it->second.name.c_str() : "??"; + } + + m_dialog->AppendChat(StringFromFormat("/!\\ Possible desync detected%s%s on frame %u", blame_str, blame_name, frame)); + } + break; + default: PanicAlertT("Unknown message received with id : %d", mid); break; @@ -643,6 +663,8 @@ bool NetPlayClient::StartGame(const std::string &path) m_dialog->AppendChat(" -- STARTING GAME -- "); + m_timebase_frame = 0; + m_is_running.store(true); NetPlay_Enable(this); @@ -1044,6 +1066,20 @@ u8 NetPlayClient::LocalWiimoteToInGameWiimote(u8 local_pad) return ingame_pad; } +void NetPlayClient::SendTimeBase() +{ + std::lock_guard lk(crit_netplay_client); + + u64 timebase = SystemTimers::GetFakeTimeBase(); + + sf::Packet* spac = new sf::Packet; + *spac << (MessageId)NP_MSG_TIMEBASE; + *spac << (u32)timebase; + *spac << (u32)(timebase << 32); + *spac << netplay_client->m_timebase_frame++; + netplay_client->SendAsync(spac); +} + // stuff hacked into dolphin // called from ---CPU--- thread diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index ce5d0ee63b..c80afda9f7 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -77,6 +77,8 @@ public: u8 LocalWiimoteToInGameWiimote(u8 local_pad); + static void SendTimeBase(); + enum State { WaitingForTraversalClientConnection, @@ -141,6 +143,8 @@ private: std::string m_player_name; bool m_connecting; TraversalClient* m_traversal_client; + + u32 m_timebase_frame; }; void NetPlay_Enable(NetPlayClient* const np); diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index 7f17e0aa60..e473c660fa 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -29,7 +29,7 @@ struct Rpt : public std::vector typedef std::vector NetWiimote; -#define NETPLAY_VERSION "Dolphin NetPlay 2014-01-08" +#define NETPLAY_VERSION "Dolphin NetPlay 2015-03-10" extern u64 g_netplay_initial_gctime; @@ -53,6 +53,9 @@ enum NP_MSG_STOP_GAME = 0xA2, NP_MSG_DISABLE_GAME = 0xA3, + NP_MSG_TIMEBASE = 0xB0, + NP_MSG_DESYNC_DETECTED = 0xB1, + NP_MSG_READY = 0xD0, NP_MSG_NOT_READY = 0xD1, diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index f53793f3be..aa10e56afb 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -110,7 +110,6 @@ void NetPlayServer::ThreadFunc() while (m_do_loop) { // update pings every so many seconds - if ((m_ping_timer.GetTimeElapsed() > 1000) || m_update_pings) { m_ping_key = Common::Timer::GetTimeMs(); @@ -581,6 +580,53 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) } break; + case NP_MSG_TIMEBASE: + { + u32 x, y, frame; + packet >> x; + packet >> y; + packet >> frame; + + if (m_desync_detected) + break; + + u64 timebase = x | ((u64)y << 32); + std::vector>& timebases = m_timebase_by_frame[frame]; + timebases.emplace_back(player.pid, timebase); + if (timebases.size() >= m_players.size()) + { + // we have all records for this frame + + if (!std::all_of(timebases.begin(), timebases.end(), [&](std::pair pair){ return pair.second == timebases[0].second; })) + { + int pid_to_blame = -1; + if (timebases.size() > 2) + { + for (auto pair : timebases) + { + if (std::all_of(timebases.begin(), timebases.end(), [&](std::pair other) { + return other.first == pair.first || other.second != pair.second; + })) + { + // we are the only outlier + pid_to_blame = pair.first; + break; + } + } + } + + sf::Packet spac; + spac << (MessageId) NP_MSG_DESYNC_DETECTED; + spac << pid_to_blame; + spac << frame; + SendToClients(spac); + + m_desync_detected = true; + } + m_timebase_by_frame.erase(frame); + } + } + break; default: PanicAlertT("Unknown message with id:%d received from player:%d Kicking player!", mid, player.pid); // unknown message, kick the client @@ -635,6 +681,8 @@ void NetPlayServer::SetNetSettings(const NetSettings &settings) // called from ---GUI--- thread bool NetPlayServer::StartGame() { + m_timebase_by_frame.clear(); + m_desync_detected = false; std::lock_guard lkg(m_crit.game); m_current_game = Common::Timer::GetTimeMs(); diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index 73f8d6d85e..842c795c38 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include "Common/Timer.h" @@ -99,6 +100,9 @@ private: std::map m_players; + std::unordered_map>> m_timebase_by_frame; + bool m_desync_detected; + struct { std::recursive_mutex game; diff --git a/Source/Core/VideoCommon/PixelEngine.cpp b/Source/Core/VideoCommon/PixelEngine.cpp index 45db963d23..91662e9931 100644 --- a/Source/Core/VideoCommon/PixelEngine.cpp +++ b/Source/Core/VideoCommon/PixelEngine.cpp @@ -11,6 +11,7 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Core/ConfigManager.h" +#include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/State.h" #include "Core/HW/MMIO.h" @@ -282,6 +283,8 @@ void SetFinish_OnMainThread(u64 userdata, int cyclesLate) s_signal_finish_interrupt.store(1); UpdateInterrupts(); CommandProcessor::SetInterruptFinishWaiting(false); + + Core::FrameUpdateOnCPUThread(); } // SetToken