Import fork code
Netplay Refactor: Qt Redo Netplay Window Added ggpo-x dependency. Qt/Netplay: Redone DirectIP Functionality Integrate Netplay into the Pad Runahead system Qt/Netplay: bring back in all the Netplay features besides the chat window thats still wip. Qt/Netplay Rewire Netplay chat window Qt/Netplay: Add Temporary alert stating this feature is not ready yet Settings: Apply the right settings when netplay is active Qt/Netplay: Fixed crashing causes my missing references in the gamelist also faster loading of the game at start of the session. Netplay: fixed stuttery feeling at high ping due to wrong order of operations including input in time calculations. Qt/Netplay Cleanup and fix stuttering at higher ping. Qt/Netplay Cleanup and fix stuttering at higher ping.
This commit is contained in:
parent
1c3742dc8e
commit
938981f899
|
@ -0,0 +1,265 @@
|
||||||
|
#include "netplay.h"
|
||||||
|
#include "pad.h"
|
||||||
|
#include "spu.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
|
// Netplay Impl
|
||||||
|
Netplay::Session::Session() = default;
|
||||||
|
|
||||||
|
Netplay::Session::~Session()
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Netplay::Session::Start(int32_t lhandle, uint16_t lport, std::string& raddr, uint16_t rport, int32_t ldelay,
|
||||||
|
uint32_t pred)
|
||||||
|
{
|
||||||
|
s_net_session.m_max_pred = pred;
|
||||||
|
/*
|
||||||
|
TODO: since saving every frame during rollback loses us time to do actual gamestate iterations it might be better to
|
||||||
|
hijack the update / save / load cycle to only save every confirmed frame only saving when actually needed.
|
||||||
|
*/
|
||||||
|
GGPOSessionCallbacks cb{};
|
||||||
|
|
||||||
|
cb.advance_frame = NpAdvFrameCb;
|
||||||
|
cb.save_game_state = NpSaveFrameCb;
|
||||||
|
cb.load_game_state = NpLoadFrameCb;
|
||||||
|
cb.begin_game = NpBeginGameCb;
|
||||||
|
cb.free_buffer = NpFreeBuffCb;
|
||||||
|
cb.on_event = NpOnEventCb;
|
||||||
|
|
||||||
|
GGPOErrorCode result;
|
||||||
|
|
||||||
|
result = ggpo_start_session(&s_net_session.p_ggpo, &cb, "Duckstation-Netplay", 2, sizeof(Netplay::Input), lport,
|
||||||
|
s_net_session.m_max_pred);
|
||||||
|
|
||||||
|
ggpo_set_disconnect_timeout(s_net_session.p_ggpo, 3000);
|
||||||
|
ggpo_set_disconnect_notify_start(s_net_session.p_ggpo, 1000);
|
||||||
|
|
||||||
|
for (int i = 1; i <= 2; i++)
|
||||||
|
{
|
||||||
|
GGPOPlayer player = {};
|
||||||
|
GGPOPlayerHandle handle = 0;
|
||||||
|
|
||||||
|
player.size = sizeof(GGPOPlayer);
|
||||||
|
player.player_num = i;
|
||||||
|
|
||||||
|
if (lhandle == i)
|
||||||
|
{
|
||||||
|
player.type = GGPOPlayerType::GGPO_PLAYERTYPE_LOCAL;
|
||||||
|
result = ggpo_add_player(s_net_session.p_ggpo, &player, &handle);
|
||||||
|
s_net_session.m_local_handle = handle;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
player.type = GGPOPlayerType::GGPO_PLAYERTYPE_REMOTE;
|
||||||
|
#ifdef _WIN32
|
||||||
|
strcpy_s(player.u.remote.ip_address, raddr.c_str());
|
||||||
|
#else
|
||||||
|
strcpy(player.u.remote.ip_address, raddr.c_str());
|
||||||
|
#endif
|
||||||
|
player.u.remote.port = rport;
|
||||||
|
result = ggpo_add_player(s_net_session.p_ggpo, &player, &handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ggpo_set_frame_delay(s_net_session.p_ggpo, s_net_session.m_local_handle, ldelay);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::Session::Close()
|
||||||
|
{
|
||||||
|
ggpo_close_session(s_net_session.p_ggpo);
|
||||||
|
s_net_session.p_ggpo = nullptr;
|
||||||
|
s_net_session.m_local_handle = GGPO_INVALID_HANDLE;
|
||||||
|
s_net_session.m_max_pred = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Netplay::Session::IsActive()
|
||||||
|
{
|
||||||
|
return s_net_session.p_ggpo != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::Session::RunIdle()
|
||||||
|
{
|
||||||
|
ggpo_idle(s_net_session.p_ggpo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::Session::AdvanceFrame(uint16_t checksum)
|
||||||
|
{
|
||||||
|
ggpo_advance_frame(s_net_session.p_ggpo, checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::Session::RunFrame(int32_t& waitTime)
|
||||||
|
{
|
||||||
|
// run game
|
||||||
|
auto result = GGPO_OK;
|
||||||
|
int disconnectFlags = 0;
|
||||||
|
Netplay::Input inputs[2] = {};
|
||||||
|
// add local input
|
||||||
|
if (GetLocalHandle() != GGPO_INVALID_HANDLE)
|
||||||
|
{
|
||||||
|
auto inp = ReadLocalInput();
|
||||||
|
result = AddLocalInput(inp);
|
||||||
|
}
|
||||||
|
// advance game
|
||||||
|
if (GGPO_SUCCEEDED(result))
|
||||||
|
{
|
||||||
|
result = SyncInput(inputs, &disconnectFlags);
|
||||||
|
if (GGPO_SUCCEEDED(result))
|
||||||
|
{
|
||||||
|
// enable again when rolling back done
|
||||||
|
SPU::SetAudioOutputMuted(false);
|
||||||
|
System::NetplayAdvanceFrame (inputs, disconnectFlags);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
RunIdle();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
RunIdle();
|
||||||
|
|
||||||
|
waitTime = GetTimer()->UsToWaitThisLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Netplay::Session::CurrentFrame()
|
||||||
|
{
|
||||||
|
int32_t frame;
|
||||||
|
ggpo_get_current_frame(s_net_session.p_ggpo, frame);
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::Session::CollectInput(uint32_t slot, uint32_t bind, float value)
|
||||||
|
{
|
||||||
|
s_net_session.m_net_input[slot][bind] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Netplay::Input Netplay::Session::ReadLocalInput()
|
||||||
|
{
|
||||||
|
// get controller data of the first controller (0 internally)
|
||||||
|
Netplay::Input inp{0};
|
||||||
|
for (uint32_t i = 0; i < (uint32_t)DigitalController::Button::Count; i++)
|
||||||
|
{
|
||||||
|
if (s_net_session.m_net_input[0][i] >= 0.25f)
|
||||||
|
inp.button_data |= 1 << i;
|
||||||
|
}
|
||||||
|
return inp;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string& Netplay::Session::GetGamePath()
|
||||||
|
{
|
||||||
|
return s_net_session.m_game_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::Session::SetGamePath(std::string& path)
|
||||||
|
{
|
||||||
|
s_net_session.m_game_path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::Session::SendMsg(const char* msg)
|
||||||
|
{
|
||||||
|
ggpo_client_chat(s_net_session.p_ggpo, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
GGPOErrorCode Netplay::Session::SyncInput(Netplay::Input inputs[2], int* disconnect_flags)
|
||||||
|
{
|
||||||
|
return ggpo_synchronize_input(s_net_session.p_ggpo, inputs, sizeof(Netplay::Input) * 2, disconnect_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
GGPOErrorCode Netplay::Session::AddLocalInput(Netplay::Input input)
|
||||||
|
{
|
||||||
|
return ggpo_add_local_input(s_net_session.p_ggpo, s_net_session.m_local_handle, &input, sizeof(Netplay::Input));
|
||||||
|
}
|
||||||
|
|
||||||
|
GGPONetworkStats& Netplay::Session::GetNetStats(int32_t handle)
|
||||||
|
{
|
||||||
|
ggpo_get_network_stats(s_net_session.p_ggpo, handle, &s_net_session.m_last_net_stats);
|
||||||
|
return s_net_session.m_last_net_stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Netplay::Session::GetPing()
|
||||||
|
{
|
||||||
|
const int handle = GetLocalHandle() == 1 ? 2 : 1;
|
||||||
|
ggpo_get_network_stats(s_net_session.p_ggpo, handle, &s_net_session.m_last_net_stats);
|
||||||
|
return s_net_session.m_last_net_stats.network.ping;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Netplay::Session::GetMaxPrediction()
|
||||||
|
{
|
||||||
|
return s_net_session.m_max_pred;
|
||||||
|
}
|
||||||
|
|
||||||
|
GGPOPlayerHandle Netplay::Session::GetLocalHandle()
|
||||||
|
{
|
||||||
|
return s_net_session.m_local_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::Session::SetInputs(Netplay::Input inputs[2])
|
||||||
|
{
|
||||||
|
for (u32 i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
auto cont = Pad::GetController(i);
|
||||||
|
std::bitset<sizeof(u32) * 8> buttonBits(inputs[i].button_data);
|
||||||
|
for (u32 j = 0; j < (u32)DigitalController::Button::Count; j++)
|
||||||
|
cont->SetBindState(j, buttonBits.test(j) ? 1.0f : 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Netplay::LoopTimer* Netplay::Session::GetTimer()
|
||||||
|
{
|
||||||
|
return &s_net_session.m_timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Netplay::Session::Fletcher16(uint8_t* data, int count)
|
||||||
|
{
|
||||||
|
uint16_t sum1 = 0;
|
||||||
|
uint16_t sum2 = 0;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
for (index = 0; index < count; ++index)
|
||||||
|
{
|
||||||
|
sum1 = (sum1 + data[index]) % 255;
|
||||||
|
sum2 = (sum2 + sum1) % 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (sum2 << 8) | sum1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::LoopTimer::Init(uint32_t fps, uint32_t frames_to_spread_wait)
|
||||||
|
{
|
||||||
|
m_us_per_game_loop = 1000000 / fps;
|
||||||
|
m_us_ahead = 0;
|
||||||
|
m_us_extra_to_wait = 0;
|
||||||
|
m_frames_to_spread_wait = frames_to_spread_wait;
|
||||||
|
m_last_advantage = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::LoopTimer::OnGGPOTimeSyncEvent(float frames_ahead)
|
||||||
|
{
|
||||||
|
m_last_advantage = (1000.0f * frames_ahead / 60.0f);
|
||||||
|
m_last_advantage /= 2;
|
||||||
|
if (m_last_advantage < 0)
|
||||||
|
{
|
||||||
|
int t = 0;
|
||||||
|
t++;
|
||||||
|
}
|
||||||
|
m_us_extra_to_wait = (int)(m_last_advantage * 1000);
|
||||||
|
if (m_us_extra_to_wait)
|
||||||
|
{
|
||||||
|
m_us_extra_to_wait /= m_frames_to_spread_wait;
|
||||||
|
m_wait_count = m_frames_to_spread_wait;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Netplay::LoopTimer::UsToWaitThisLoop()
|
||||||
|
{
|
||||||
|
int32_t timetoWait = m_us_per_game_loop;
|
||||||
|
if (m_wait_count)
|
||||||
|
{
|
||||||
|
timetoWait += m_us_extra_to_wait;
|
||||||
|
m_wait_count--;
|
||||||
|
if (!m_wait_count)
|
||||||
|
m_us_extra_to_wait = 0;
|
||||||
|
}
|
||||||
|
return timetoWait;
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef _NETPLAY_H
|
||||||
|
#define _NETPLAY_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <ggponet.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/timer.h"
|
||||||
|
#include "digital_controller.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// C GGPO Event Callbacks. Should be defined in system.cpp
|
||||||
|
extern "C" {
|
||||||
|
bool NpAdvFrameCb(void* ctx, int flags);
|
||||||
|
bool NpSaveFrameCb(void* ctx, uint8_t** buffer, int* len, int* checksum, int frame);
|
||||||
|
bool NpLoadFrameCb(void* ctx, uint8_t* buffer, int len, int rb_frames, int frame_to_load);
|
||||||
|
bool NpBeginGameCb(void* ctx, const char* game_name);
|
||||||
|
void NpFreeBuffCb(void* ctx, void* buffer);
|
||||||
|
bool NpOnEventCb(void* ctx, GGPOEvent* ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Netplay {
|
||||||
|
|
||||||
|
struct Input
|
||||||
|
{
|
||||||
|
uint32_t button_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoopTimer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Init(uint32_t fps, uint32_t frames_to_spread_wait);
|
||||||
|
void OnGGPOTimeSyncEvent(float frames_ahead);
|
||||||
|
// Call every loop, to get the amount of time the current iteration of gameloop should take
|
||||||
|
int32_t UsToWaitThisLoop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
float m_last_advantage = 0.0f;
|
||||||
|
int32_t m_us_per_game_loop = 0;
|
||||||
|
int32_t m_us_ahead = 0;
|
||||||
|
int32_t m_us_extra_to_wait = 0;
|
||||||
|
int32_t m_frames_to_spread_wait = 0;
|
||||||
|
int32_t m_wait_count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Session
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Session();
|
||||||
|
~Session();
|
||||||
|
// l = local, r = remote
|
||||||
|
static int32_t Start(int32_t lhandle, uint16_t lport, std::string& raddr, uint16_t rport, int32_t ldelay,
|
||||||
|
uint32_t pred);
|
||||||
|
|
||||||
|
static void Close();
|
||||||
|
static bool IsActive();
|
||||||
|
static void RunIdle();
|
||||||
|
|
||||||
|
static void AdvanceFrame(uint16_t checksum = 0);
|
||||||
|
static void RunFrame(int32_t& waitTime);
|
||||||
|
static int32_t CurrentFrame();
|
||||||
|
|
||||||
|
static void CollectInput(uint32_t slot, uint32_t bind, float value);
|
||||||
|
static Netplay::Input ReadLocalInput();
|
||||||
|
|
||||||
|
static std::string& GetGamePath();
|
||||||
|
static void SetGamePath(std::string& path);
|
||||||
|
static void SendMsg(const char* msg);
|
||||||
|
|
||||||
|
static GGPOErrorCode SyncInput(Netplay::Input inputs[2], int* disconnect_flags);
|
||||||
|
static GGPOErrorCode AddLocalInput(Netplay::Input input);
|
||||||
|
static GGPONetworkStats& GetNetStats(int32_t handle);
|
||||||
|
static int32_t GetPing();
|
||||||
|
static uint32_t GetMaxPrediction();
|
||||||
|
static GGPOPlayerHandle GetLocalHandle();
|
||||||
|
static void SetInputs(Netplay::Input inputs[2]);
|
||||||
|
|
||||||
|
static Netplay::LoopTimer* GetTimer();
|
||||||
|
static uint16_t Fletcher16(uint8_t* data, int count);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Netplay::LoopTimer m_timer;
|
||||||
|
std::string m_game_path;
|
||||||
|
uint32_t m_max_pred = 0;
|
||||||
|
|
||||||
|
GGPOPlayerHandle m_local_handle = GGPO_INVALID_HANDLE;
|
||||||
|
GGPONetworkStats m_last_net_stats{};
|
||||||
|
GGPOSession* p_ggpo = nullptr;
|
||||||
|
|
||||||
|
std::array<std::array<float, 32>, NUM_CONTROLLER_AND_CARD_PORTS> m_net_input;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Netplay
|
||||||
|
|
||||||
|
// Netplay Instance
|
||||||
|
static Netplay::Session s_net_session = Netplay::Session();
|
||||||
|
|
||||||
|
#endif // !_NETPLAY_H
|
|
@ -169,12 +169,12 @@ void Pad::Reset()
|
||||||
bool Pad::ShouldAvoidSavingToState()
|
bool Pad::ShouldAvoidSavingToState()
|
||||||
{
|
{
|
||||||
// Currently only runahead, will also be used for netplay.
|
// Currently only runahead, will also be used for netplay.
|
||||||
return g_settings.IsRunaheadEnabled();
|
return g_settings.IsRunaheadEnabled() || Netplay::Session::IsActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 Pad::GetMaximumRollbackFrames()
|
u32 Pad::GetMaximumRollbackFrames()
|
||||||
{
|
{
|
||||||
return g_settings.runahead_frames;
|
return (Netplay::Session::IsActive() ? Netplay::Session::GetMaxPrediction() : g_settings.runahead_frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Pad::DoStateController(StateWrapper& sw, u32 i)
|
bool Pad::DoStateController(StateWrapper& sw, u32 i)
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include "mdec.h"
|
#include "mdec.h"
|
||||||
#include "memory_card.h"
|
#include "memory_card.h"
|
||||||
#include "multitap.h"
|
#include "multitap.h"
|
||||||
|
#include "netplay.h"
|
||||||
#include "pad.h"
|
#include "pad.h"
|
||||||
#include "pgxp.h"
|
#include "pgxp.h"
|
||||||
#include "psf_loader.h"
|
#include "psf_loader.h"
|
||||||
|
@ -207,6 +208,8 @@ static std::deque<MemorySaveState> s_runahead_states;
|
||||||
static bool s_runahead_replay_pending = false;
|
static bool s_runahead_replay_pending = false;
|
||||||
static u32 s_runahead_frames = 0;
|
static u32 s_runahead_frames = 0;
|
||||||
|
|
||||||
|
static std::deque<MemorySaveState> s_netplay_states;
|
||||||
|
|
||||||
static TinyString GetTimestampStringForFileName()
|
static TinyString GetTimestampStringForFileName()
|
||||||
{
|
{
|
||||||
return TinyString::FromFmt("{:%Y-%m-%d_%H-%M-%S}", fmt::localtime(std::time(nullptr)));
|
return TinyString::FromFmt("{:%Y-%m-%d_%H-%M-%S}", fmt::localtime(std::time(nullptr)));
|
||||||
|
@ -1538,7 +1541,7 @@ void System::Execute()
|
||||||
|
|
||||||
// this can shut us down
|
// this can shut us down
|
||||||
Host::PumpMessagesOnCPUThread();
|
Host::PumpMessagesOnCPUThread();
|
||||||
if (!IsValid())
|
if (!IsValid() || Netplay::Session::IsActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (s_frame_step_request)
|
if (s_frame_step_request)
|
||||||
|
@ -1565,6 +1568,38 @@ void System::Execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void System::ExecuteNetplay()
|
||||||
|
{
|
||||||
|
// frame timing
|
||||||
|
s32 timeToWait;
|
||||||
|
std::chrono::steady_clock::time_point start, next, now;
|
||||||
|
start = next = now = std::chrono::steady_clock::now();
|
||||||
|
while (Netplay::Session::IsActive() && System::IsRunning())
|
||||||
|
{
|
||||||
|
now = std::chrono::steady_clock::now();
|
||||||
|
if (now >= next)
|
||||||
|
{
|
||||||
|
Netplay::Session::RunFrame(timeToWait);
|
||||||
|
next = now + std::chrono::microseconds(timeToWait);
|
||||||
|
s_next_frame_time += timeToWait;
|
||||||
|
// this can shut us down
|
||||||
|
Host::PumpMessagesOnCPUThread();
|
||||||
|
if (!IsValid() || !Netplay::Session::IsActive())
|
||||||
|
break;
|
||||||
|
|
||||||
|
const bool skip_present = g_host_display->ShouldSkipDisplayingFrame();
|
||||||
|
Host::RenderDisplay(skip_present);
|
||||||
|
if (!skip_present && g_host_display->IsGPUTimingEnabled())
|
||||||
|
{
|
||||||
|
s_accumulated_gpu_time += g_host_display->GetAndResetAccumulatedGPUTime();
|
||||||
|
s_presents_since_last_update++;
|
||||||
|
}
|
||||||
|
|
||||||
|
System::UpdatePerformanceCounters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void System::RecreateSystem()
|
void System::RecreateSystem()
|
||||||
{
|
{
|
||||||
Assert(!IsShutdown());
|
Assert(!IsShutdown());
|
||||||
|
@ -4431,3 +4466,163 @@ void System::SetTimerResolutionIncreased(bool enabled)
|
||||||
timeEndPeriod(1);
|
timeEndPeriod(1);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void System::StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port,
|
||||||
|
s32 input_delay, std::string& game_path)
|
||||||
|
{
|
||||||
|
// dont want to start a session when theres already one going on.
|
||||||
|
if (Netplay::Session::IsActive())
|
||||||
|
return;
|
||||||
|
// set game path for later loading during the begin game callback
|
||||||
|
Netplay::Session::SetGamePath(game_path);
|
||||||
|
// set netplay timer
|
||||||
|
const u32 fps = (s_region == ConsoleRegion::PAL ? 50 : 60);
|
||||||
|
Netplay::Session::GetTimer()->Init(fps, 180);
|
||||||
|
// create session
|
||||||
|
int result = Netplay::Session::Start(local_handle, local_port, remote_addr, remote_port, input_delay, 8);
|
||||||
|
if (result != GGPO_OK)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to Create Netplay Session! Error: %d", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::StopNetplaySession()
|
||||||
|
{
|
||||||
|
if (!Netplay::Session::IsActive())
|
||||||
|
return;
|
||||||
|
s_netplay_states.clear();
|
||||||
|
Netplay::Session::Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::NetplayAdvanceFrame(Netplay::Input inputs[], int disconnect_flags)
|
||||||
|
{
|
||||||
|
Netplay::Session::SetInputs(inputs);
|
||||||
|
System::DoRunFrame();
|
||||||
|
Netplay::Session::AdvanceFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NpBeginGameCb(void* ctx, const char* game_name)
|
||||||
|
{
|
||||||
|
// close system if its already running
|
||||||
|
if (System::IsValid())
|
||||||
|
System::ShutdownSystem(false);
|
||||||
|
// fast boot the selected game and wait for the other player
|
||||||
|
auto param = SystemBootParameters(Netplay::Session::GetGamePath());
|
||||||
|
param.override_fast_boot = true;
|
||||||
|
if (!System::BootSystem(param))
|
||||||
|
{
|
||||||
|
System::StopNetplaySession();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Fast Forward to Game Start
|
||||||
|
SPU::SetAudioOutputMuted(true);
|
||||||
|
while (s_internal_frame_number < 2)
|
||||||
|
System::DoRunFrame();
|
||||||
|
SPU::SetAudioOutputMuted(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NpAdvFrameCb(void* ctx, int flags)
|
||||||
|
{
|
||||||
|
Netplay::Input inputs[2] = {};
|
||||||
|
int disconnectFlags;
|
||||||
|
Netplay::Session::SyncInput(inputs, &disconnectFlags);
|
||||||
|
System::NetplayAdvanceFrame(inputs, disconnectFlags);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NpSaveFrameCb(void* ctx, uint8_t** buffer, int* len, int* checksum, int frame)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
// give ggpo something so it doesnt complain.
|
||||||
|
u8 dummyData = 43;
|
||||||
|
*len = sizeof(u8);
|
||||||
|
*buffer = (unsigned char*)malloc(*len);
|
||||||
|
if (!*buffer)
|
||||||
|
return false;
|
||||||
|
memcpy(*buffer, &dummyData, *len);
|
||||||
|
// store state for later.
|
||||||
|
int pred = Netplay::Session::GetMaxPrediction();
|
||||||
|
if (frame < pred && s_netplay_states.size() < pred)
|
||||||
|
{
|
||||||
|
MemorySaveState save;
|
||||||
|
result = System::SaveMemoryState(&save);
|
||||||
|
s_netplay_states.push_back(std::move(save));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// reuse streams
|
||||||
|
result = System::SaveMemoryState(&s_netplay_states[frame % pred]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NpLoadFrameCb(void* ctx, uint8_t* buffer, int len, int rb_frames, int frame_to_load)
|
||||||
|
{
|
||||||
|
// Disable Audio For upcoming rollback
|
||||||
|
SPU::SetAudioOutputMuted(true);
|
||||||
|
return System::LoadMemoryState(s_netplay_states[frame_to_load % Netplay::Session::GetMaxPrediction()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NpOnEventCb(void* ctx, GGPOEvent* ev)
|
||||||
|
{
|
||||||
|
char buff[128];
|
||||||
|
std::string msg;
|
||||||
|
switch (ev->code)
|
||||||
|
{
|
||||||
|
case GGPOEventCode::GGPO_EVENTCODE_CONNECTED_TO_PEER:
|
||||||
|
sprintf(buff, "Netplay Connected To Player: %d", ev->u.connected.player);
|
||||||
|
msg = buff;
|
||||||
|
break;
|
||||||
|
case GGPOEventCode::GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER:
|
||||||
|
sprintf(buff, "Netplay Synchronzing: %d/%d", ev->u.synchronizing.count, ev->u.synchronizing.total);
|
||||||
|
msg = buff;
|
||||||
|
break;
|
||||||
|
case GGPOEventCode::GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER:
|
||||||
|
sprintf(buff, "Netplay Synchronized With Player: %d", ev->u.synchronized.player);
|
||||||
|
msg = buff;
|
||||||
|
break;
|
||||||
|
case GGPOEventCode::GGPO_EVENTCODE_DISCONNECTED_FROM_PEER:
|
||||||
|
sprintf(buff, "Netplay Player: %d Disconnected", ev->u.disconnected.player);
|
||||||
|
msg = buff;
|
||||||
|
break;
|
||||||
|
case GGPOEventCode::GGPO_EVENTCODE_RUNNING:
|
||||||
|
msg = "Netplay Is Running";
|
||||||
|
break;
|
||||||
|
case GGPOEventCode::GGPO_EVENTCODE_CONNECTION_INTERRUPTED:
|
||||||
|
sprintf(buff, "Netplay Player: %d Connection Interupted, Timeout: %d", ev->u.connection_interrupted.player,
|
||||||
|
ev->u.connection_interrupted.disconnect_timeout);
|
||||||
|
msg = buff;
|
||||||
|
break;
|
||||||
|
case GGPOEventCode::GGPO_EVENTCODE_CONNECTION_RESUMED:
|
||||||
|
sprintf(buff, "Netplay Player: %d Connection Resumed", ev->u.connection_resumed.player);
|
||||||
|
msg = buff;
|
||||||
|
break;
|
||||||
|
case GGPOEventCode::GGPO_EVENTCODE_CHAT:
|
||||||
|
sprintf(buff, "%s", ev->u.chat.msg);
|
||||||
|
msg = buff;
|
||||||
|
break;
|
||||||
|
case GGPOEventCode::GGPO_EVENTCODE_TIMESYNC:
|
||||||
|
Netplay::Session::GetTimer()->OnGGPOTimeSyncEvent(ev->u.timesync.frames_ahead);
|
||||||
|
break;
|
||||||
|
case GGPOEventCode::GGPO_EVENTCODE_DESYNC:
|
||||||
|
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;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sprintf(buff, "Netplay Event Code: %d", ev->code);
|
||||||
|
msg = buff;
|
||||||
|
}
|
||||||
|
if (!msg.empty())
|
||||||
|
{
|
||||||
|
Host::OnNetplayMessage(msg);
|
||||||
|
Log_InfoPrintf("%s", msg.c_str());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NpFreeBuffCb(void* ctx, void* buffer)
|
||||||
|
{
|
||||||
|
free(buffer);
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "common/timer.h"
|
#include "common/timer.h"
|
||||||
|
#include "netplay.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "timing_event.h"
|
#include "timing_event.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
@ -19,11 +20,10 @@ class Controller;
|
||||||
struct CheatCode;
|
struct CheatCode;
|
||||||
class CheatList;
|
class CheatList;
|
||||||
|
|
||||||
namespace BIOS
|
namespace BIOS {
|
||||||
{
|
|
||||||
struct ImageInfo;
|
struct ImageInfo;
|
||||||
struct Hash;
|
struct Hash;
|
||||||
}
|
} // namespace BIOS
|
||||||
|
|
||||||
struct SystemBootParameters
|
struct SystemBootParameters
|
||||||
{
|
{
|
||||||
|
@ -227,6 +227,9 @@ bool SaveResumeState();
|
||||||
/// Runs the VM until the CPU execution is canceled.
|
/// Runs the VM until the CPU execution is canceled.
|
||||||
void Execute();
|
void Execute();
|
||||||
|
|
||||||
|
/// Runs the VM and netplay loop. when the netplay loop cancels it switches to normal execute mode.
|
||||||
|
void ExecuteNetplay();
|
||||||
|
|
||||||
/// Switches the GPU renderer by saving state, recreating the display window, and restoring state (if needed).
|
/// Switches the GPU renderer by saving state, recreating the display window, and restoring state (if needed).
|
||||||
void RecreateSystem();
|
void RecreateSystem();
|
||||||
|
|
||||||
|
@ -452,6 +455,11 @@ void UpdateMemorySaveStateSettings();
|
||||||
bool LoadRewindState(u32 skip_saves = 0, bool consume_state = true);
|
bool LoadRewindState(u32 skip_saves = 0, bool consume_state = true);
|
||||||
void SetRunaheadReplayFlag();
|
void SetRunaheadReplayFlag();
|
||||||
|
|
||||||
|
/// Netplay
|
||||||
|
void StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port, s32 input_delay,
|
||||||
|
std::string& game_path);
|
||||||
|
void StopNetplaySession();
|
||||||
|
void NetplayAdvanceFrame(Netplay::Input inputs[], int disconnect_flags);
|
||||||
} // namespace System
|
} // namespace System
|
||||||
|
|
||||||
namespace Host {
|
namespace Host {
|
||||||
|
@ -500,4 +508,6 @@ bool IsFullscreen();
|
||||||
|
|
||||||
/// Alters fullscreen state of hosting application.
|
/// Alters fullscreen state of hosting application.
|
||||||
void SetFullscreen(bool enabled);
|
void SetFullscreen(bool enabled);
|
||||||
|
// netplay
|
||||||
|
void OnNetplayMessage(std::string& message);
|
||||||
} // namespace Host
|
} // namespace Host
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include "common/windows_headers.h"
|
#include "common/windows_headers.h"
|
||||||
#include <Dbt.h>
|
#include <Dbt.h>
|
||||||
|
#include <WinSock2.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Log_SetChannel(MainWindow);
|
Log_SetChannel(MainWindow);
|
||||||
|
@ -105,6 +106,12 @@ MainWindow::MainWindow() : QMainWindow(nullptr)
|
||||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||||
s_use_central_widget = DisplayContainer::isRunningOnWayland();
|
s_use_central_widget = DisplayContainer::isRunningOnWayland();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
// Setup WinSock
|
||||||
|
WSADATA wd = {};
|
||||||
|
WSAStartup(MAKEWORD(2, 2), &wd);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
|
@ -119,6 +126,8 @@ MainWindow::~MainWindow()
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
unregisterForDeviceNotifications();
|
unregisterForDeviceNotifications();
|
||||||
|
// Cleanup WinSock
|
||||||
|
WSACleanup();
|
||||||
#endif
|
#endif
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
FrontendCommon::RemoveThemeChangeHandler(this);
|
FrontendCommon::RemoveThemeChangeHandler(this);
|
||||||
|
@ -1556,9 +1565,13 @@ void MainWindow::setupAdditionalUi()
|
||||||
m_status_fps_widget->hide();
|
m_status_fps_widget->hide();
|
||||||
|
|
||||||
m_status_vps_widget = new QLabel(m_ui.statusBar);
|
m_status_vps_widget = new QLabel(m_ui.statusBar);
|
||||||
m_status_vps_widget->setFixedSize(125, 16);
|
m_status_vps_widget->setFixedSize(120, 16);
|
||||||
m_status_vps_widget->hide();
|
m_status_vps_widget->hide();
|
||||||
|
|
||||||
|
m_status_ping_widget = new QLabel(m_ui.statusBar);
|
||||||
|
m_status_ping_widget->setFixedSize(110, 16);
|
||||||
|
m_status_ping_widget->hide();
|
||||||
|
|
||||||
m_settings_toolbar_menu = new QMenu(m_ui.toolBar);
|
m_settings_toolbar_menu = new QMenu(m_ui.toolBar);
|
||||||
m_settings_toolbar_menu->addAction(m_ui.actionSettings);
|
m_settings_toolbar_menu->addAction(m_ui.actionSettings);
|
||||||
m_settings_toolbar_menu->addAction(m_ui.actionViewGameProperties);
|
m_settings_toolbar_menu->addAction(m_ui.actionViewGameProperties);
|
||||||
|
@ -1764,6 +1777,7 @@ void MainWindow::updateStatusBarWidgetVisibility()
|
||||||
Update(m_status_resolution_widget, s_system_valid && !s_system_paused, 0);
|
Update(m_status_resolution_widget, s_system_valid && !s_system_paused, 0);
|
||||||
Update(m_status_fps_widget, s_system_valid && !s_system_paused, 0);
|
Update(m_status_fps_widget, s_system_valid && !s_system_paused, 0);
|
||||||
Update(m_status_vps_widget, s_system_valid && !s_system_paused, 0);
|
Update(m_status_vps_widget, s_system_valid && !s_system_paused, 0);
|
||||||
|
Update(m_status_ping_widget, s_system_valid && !s_system_paused && m_netplay_window != nullptr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updateWindowTitle()
|
void MainWindow::updateWindowTitle()
|
||||||
|
@ -2089,6 +2103,9 @@ void MainWindow::connectSignals()
|
||||||
addThemeToMenu(tr("Dark Fusion (Blue)"), QStringLiteral("darkfusionblue"));
|
addThemeToMenu(tr("Dark Fusion (Blue)"), QStringLiteral("darkfusionblue"));
|
||||||
addThemeToMenu(tr("QDarkStyle"), QStringLiteral("qdarkstyle"));
|
addThemeToMenu(tr("QDarkStyle"), QStringLiteral("qdarkstyle"));
|
||||||
updateMenuSelectedTheme();
|
updateMenuSelectedTheme();
|
||||||
|
|
||||||
|
// Netplay UI , TODO
|
||||||
|
connect(m_ui.actionCreateNetplaySession, &QAction::triggered, this, &MainWindow::onNetplaySessionCreated);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::addThemeToMenu(const QString& name, const QString& key)
|
void MainWindow::addThemeToMenu(const QString& name, const QString& key)
|
||||||
|
@ -2752,6 +2769,28 @@ void MainWindow::onCPUDebuggerClosed()
|
||||||
m_debugger_window = nullptr;
|
m_debugger_window = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::onNetplaySessionCreated()
|
||||||
|
{
|
||||||
|
Assert(!m_netplay_window);
|
||||||
|
|
||||||
|
m_netplay_window = new NetplayWidget(this);
|
||||||
|
m_netplay_window->setWindowIcon(windowIcon());
|
||||||
|
m_netplay_window->setWindowTitle("Netplay Session");
|
||||||
|
m_netplay_window->setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint);
|
||||||
|
m_netplay_window->show();
|
||||||
|
|
||||||
|
m_ui.menuNetplay->setDisabled(true);
|
||||||
|
|
||||||
|
connect(m_netplay_window, &NetplayWidget::finished, [this]() {
|
||||||
|
Assert(m_netplay_window);
|
||||||
|
|
||||||
|
m_netplay_window->deleteLater();
|
||||||
|
m_netplay_window = nullptr;
|
||||||
|
|
||||||
|
m_ui.menuNetplay->setDisabled(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::onToolsOpenDataDirectoryTriggered()
|
void MainWindow::onToolsOpenDataDirectoryTriggered()
|
||||||
{
|
{
|
||||||
QtUtils::OpenURL(this, QUrl::fromLocalFile(QString::fromStdString(EmuFolders::DataRoot)));
|
QtUtils::OpenURL(this, QUrl::fromLocalFile(QString::fromStdString(EmuFolders::DataRoot)));
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "displaywidget.h"
|
#include "displaywidget.h"
|
||||||
#include "settingsdialog.h"
|
#include "settingsdialog.h"
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
|
#include <netplaywidget.h>
|
||||||
|
|
||||||
class QLabel;
|
class QLabel;
|
||||||
class QThread;
|
class QThread;
|
||||||
|
@ -89,6 +90,7 @@ public:
|
||||||
ALWAYS_INLINE QLabel* getStatusResolutionWidget() const { return m_status_resolution_widget; }
|
ALWAYS_INLINE QLabel* getStatusResolutionWidget() const { return m_status_resolution_widget; }
|
||||||
ALWAYS_INLINE QLabel* getStatusFPSWidget() const { return m_status_fps_widget; }
|
ALWAYS_INLINE QLabel* getStatusFPSWidget() const { return m_status_fps_widget; }
|
||||||
ALWAYS_INLINE QLabel* getStatusVPSWidget() const { return m_status_vps_widget; }
|
ALWAYS_INLINE QLabel* getStatusVPSWidget() const { return m_status_vps_widget; }
|
||||||
|
ALWAYS_INLINE QLabel* getStatusPingWidget() const { return m_status_ping_widget; }
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
/// Updates debug menu visibility (hides if disabled).
|
/// Updates debug menu visibility (hides if disabled).
|
||||||
|
@ -168,6 +170,8 @@ private Q_SLOTS:
|
||||||
void openCPUDebugger();
|
void openCPUDebugger();
|
||||||
void onCPUDebuggerClosed();
|
void onCPUDebuggerClosed();
|
||||||
|
|
||||||
|
void onNetplaySessionCreated();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent* event) override;
|
void showEvent(QShowEvent* event) override;
|
||||||
void closeEvent(QCloseEvent* event) override;
|
void closeEvent(QCloseEvent* event) override;
|
||||||
|
@ -262,6 +266,7 @@ private:
|
||||||
QLabel* m_status_renderer_widget = nullptr;
|
QLabel* m_status_renderer_widget = nullptr;
|
||||||
QLabel* m_status_fps_widget = nullptr;
|
QLabel* m_status_fps_widget = nullptr;
|
||||||
QLabel* m_status_vps_widget = nullptr;
|
QLabel* m_status_vps_widget = nullptr;
|
||||||
|
QLabel* m_status_ping_widget = nullptr;
|
||||||
QLabel* m_status_resolution_widget = nullptr;
|
QLabel* m_status_resolution_widget = nullptr;
|
||||||
|
|
||||||
QMenu* m_settings_toolbar_menu = nullptr;
|
QMenu* m_settings_toolbar_menu = nullptr;
|
||||||
|
@ -273,6 +278,7 @@ private:
|
||||||
MemoryCardEditorDialog* m_memory_card_editor_dialog = nullptr;
|
MemoryCardEditorDialog* m_memory_card_editor_dialog = nullptr;
|
||||||
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
|
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
|
||||||
DebuggerWindow* m_debugger_window = nullptr;
|
DebuggerWindow* m_debugger_window = nullptr;
|
||||||
|
NetplayWidget* m_netplay_window = nullptr;
|
||||||
|
|
||||||
std::string m_current_game_title;
|
std::string m_current_game_title;
|
||||||
std::string m_current_game_serial;
|
std::string m_current_game_serial;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<string>DuckStation</string>
|
<string>DuckStation</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset>
|
<iconset resource="resources/resources.qrc">
|
||||||
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
|
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QStackedWidget" name="mainContainer"/>
|
<widget class="QStackedWidget" name="mainContainer"/>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>800</width>
|
<width>800</width>
|
||||||
<height>22</height>
|
<height>21</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuSystem">
|
<widget class="QMenu" name="menuSystem">
|
||||||
|
@ -237,8 +237,15 @@
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionOpenDataDirectory"/>
|
<addaction name="actionOpenDataDirectory"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuNetplay">
|
||||||
|
<property name="title">
|
||||||
|
<string>Netplay</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionCreateNetplaySession"/>
|
||||||
|
</widget>
|
||||||
<addaction name="menuSystem"/>
|
<addaction name="menuSystem"/>
|
||||||
<addaction name="menuSettings"/>
|
<addaction name="menuSettings"/>
|
||||||
|
<addaction name="menuNetplay"/>
|
||||||
<addaction name="menu_View"/>
|
<addaction name="menu_View"/>
|
||||||
<addaction name="menu_Tools"/>
|
<addaction name="menu_Tools"/>
|
||||||
<addaction name="menuDebug"/>
|
<addaction name="menuDebug"/>
|
||||||
|
@ -472,7 +479,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionGitHubRepository">
|
<action name="actionGitHubRepository">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="resources/resources.qrc">
|
||||||
<normaloff>:/icons/github.png</normaloff>:/icons/github.png</iconset>
|
<normaloff>:/icons/github.png</normaloff>:/icons/github.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -481,7 +488,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionIssueTracker">
|
<action name="actionIssueTracker">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="resources/resources.qrc">
|
||||||
<normaloff>:/icons/IssueTracker.png</normaloff>:/icons/IssueTracker.png</iconset>
|
<normaloff>:/icons/IssueTracker.png</normaloff>:/icons/IssueTracker.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -490,7 +497,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionDiscordServer">
|
<action name="actionDiscordServer">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="resources/resources.qrc">
|
||||||
<normaloff>:/icons/discord.png</normaloff>:/icons/discord.png</iconset>
|
<normaloff>:/icons/discord.png</normaloff>:/icons/discord.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -508,7 +515,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAboutQt">
|
<action name="actionAboutQt">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="resources/resources.qrc">
|
||||||
<normaloff>:/icons/QT.png</normaloff>:/icons/QT.png</iconset>
|
<normaloff>:/icons/QT.png</normaloff>:/icons/QT.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -517,7 +524,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAbout">
|
<action name="actionAbout">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="resources/resources.qrc">
|
||||||
<normaloff>:/icons/duck_64.png</normaloff>:/icons/duck_64.png</iconset>
|
<normaloff>:/icons/duck_64.png</normaloff>:/icons/duck_64.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -976,6 +983,11 @@
|
||||||
<string>Cover Downloader</string>
|
<string>Cover Downloader</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionCreateNetplaySession">
|
||||||
|
<property name="text">
|
||||||
|
<string>Create Session</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="resources/resources.qrc"/>
|
<include location="resources/resources.qrc"/>
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
#include "netplaywidget.h"
|
||||||
|
#include "ui_netplaywidget.h"
|
||||||
|
#include <QtWidgets/qmessagebox.h>
|
||||||
|
#include <common/log.h>
|
||||||
|
#include <core/controller.h>
|
||||||
|
#include <qthost.h>
|
||||||
|
|
||||||
|
Log_SetChannel(NetplayWidget);
|
||||||
|
|
||||||
|
NetplayWidget::NetplayWidget(QWidget* parent) : QDialog(parent), m_ui(new Ui::NetplayWidget)
|
||||||
|
{
|
||||||
|
m_ui->setupUi(this);
|
||||||
|
FillGameList();
|
||||||
|
SetupConnections();
|
||||||
|
SetupConstraints();
|
||||||
|
CheckControllersSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
NetplayWidget::~NetplayWidget()
|
||||||
|
{
|
||||||
|
StopSession();
|
||||||
|
delete m_ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetplayWidget::FillGameList()
|
||||||
|
{
|
||||||
|
// Get all games and fill the list later to know which game to boot.
|
||||||
|
s32 numGames = GameList::GetEntryCount();
|
||||||
|
for (s32 i = 0; i < numGames; i++)
|
||||||
|
{
|
||||||
|
const auto& entry = GameList::GetEntryByIndex(i);
|
||||||
|
std::string baseFilename = entry->path.substr(entry->path.find_last_of("/\\") + 1);
|
||||||
|
m_ui->cbSelectedGame->addItem(
|
||||||
|
QString::fromStdString("[" + entry->serial + "] " + entry->title + " | " + baseFilename));
|
||||||
|
m_available_games.push_back(entry->path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetplayWidget::SetupConnections()
|
||||||
|
{
|
||||||
|
// connect netplay window messages
|
||||||
|
connect(g_emu_thread, &EmuThread::onNetplayMessage, this, &NetplayWidget::OnMsgReceived);
|
||||||
|
// connect sending messages when the chat button has been pressed
|
||||||
|
connect(m_ui->btnSendMsg, &QPushButton::pressed, [this]() {
|
||||||
|
// check if message aint empty and the complete message ( message + name + ":" + space) is below 120 characters
|
||||||
|
auto msg = m_ui->tbNetplayChat->toPlainText().trimmed();
|
||||||
|
QString completeMsg = m_ui->lePlayerName->text().trimmed() + ": " + msg;
|
||||||
|
if (completeMsg.length() > 120)
|
||||||
|
return;
|
||||||
|
m_ui->lwChatWindow->addItem(completeMsg);
|
||||||
|
m_ui->tbNetplayChat->clear();
|
||||||
|
if (!g_emu_thread)
|
||||||
|
return;
|
||||||
|
g_emu_thread->sendNetplayMessage(completeMsg);
|
||||||
|
});
|
||||||
|
|
||||||
|
// switch between DIRECT IP and traversal options
|
||||||
|
connect(m_ui->cbConnMode, &QComboBox::currentIndexChanged, [this]() {
|
||||||
|
// zero is DIRECT IP mode
|
||||||
|
const bool action = (m_ui->cbConnMode->currentIndex() == 0 ? true : false);
|
||||||
|
m_ui->frDirectIP->setVisible(action);
|
||||||
|
m_ui->frDirectIP->setEnabled(action);
|
||||||
|
m_ui->btnStartSession->setEnabled(action);
|
||||||
|
m_ui->tabTraversal->setEnabled(!action);
|
||||||
|
m_ui->btnTraversalJoin->setEnabled(!action);
|
||||||
|
m_ui->btnTraversalHost->setEnabled(!action);
|
||||||
|
});
|
||||||
|
|
||||||
|
// actions to be taken when stopping a session.
|
||||||
|
auto fnOnStopSession = [this]() {
|
||||||
|
m_ui->btnSendMsg->setEnabled(false);
|
||||||
|
m_ui->tbNetplayChat->setEnabled(false);
|
||||||
|
m_ui->btnStopSession->setEnabled(false);
|
||||||
|
m_ui->btnStartSession->setEnabled(true);
|
||||||
|
m_ui->btnTraversalHost->setEnabled(true);
|
||||||
|
m_ui->btnTraversalJoin->setEnabled(true);
|
||||||
|
m_ui->lblHostCodeResult->setText("XXXXXXXXX-");
|
||||||
|
StopSession();
|
||||||
|
};
|
||||||
|
|
||||||
|
// check session when start button pressed if there is the needed info depending on the connection mode
|
||||||
|
auto fnCheckValid = [this, fnOnStopSession]() {
|
||||||
|
const bool action = (m_ui->cbConnMode->currentIndex() == 0 ? true : false);
|
||||||
|
if (CheckInfoValid(action))
|
||||||
|
{
|
||||||
|
m_ui->btnSendMsg->setEnabled(true);
|
||||||
|
m_ui->tbNetplayChat->setEnabled(true);
|
||||||
|
m_ui->btnStopSession->setEnabled(true);
|
||||||
|
m_ui->btnStartSession->setEnabled(false);
|
||||||
|
m_ui->btnTraversalHost->setEnabled(false);
|
||||||
|
m_ui->btnTraversalJoin->setEnabled(false);
|
||||||
|
if (!StartSession(action))
|
||||||
|
fnOnStopSession();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connect(m_ui->btnStartSession, &QPushButton::pressed, fnCheckValid);
|
||||||
|
connect(m_ui->btnTraversalJoin, &QPushButton::pressed, fnCheckValid);
|
||||||
|
connect(m_ui->btnTraversalHost, &QPushButton::pressed, fnCheckValid);
|
||||||
|
// when pressed revert back to the previous ui state so people can start a new session.
|
||||||
|
connect(m_ui->btnStopSession, &QPushButton::pressed, fnOnStopSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetplayWidget::SetupConstraints()
|
||||||
|
{
|
||||||
|
m_ui->lwChatWindow->setWordWrap(true);
|
||||||
|
m_ui->sbLocalPort->setRange(0, 65535);
|
||||||
|
m_ui->sbRemotePort->setRange(0, 65535);
|
||||||
|
m_ui->sbInputDelay->setRange(0, 10);
|
||||||
|
m_ui->leRemoteAddr->setMaxLength(15);
|
||||||
|
m_ui->lePlayerName->setMaxLength(12);
|
||||||
|
QString IpRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])";
|
||||||
|
QRegularExpression IpRegex("^" + IpRange + "(\\." + IpRange + ")" + "(\\." + IpRange + ")" + "(\\." + IpRange + ")$");
|
||||||
|
QRegularExpressionValidator* ipValidator = new QRegularExpressionValidator(IpRegex, this);
|
||||||
|
m_ui->leRemoteAddr->setValidator(ipValidator);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetplayWidget::CheckInfoValid(bool direct_ip)
|
||||||
|
{
|
||||||
|
if (!direct_ip)
|
||||||
|
{
|
||||||
|
QMessageBox errBox;
|
||||||
|
errBox.setFixedSize(500, 200);
|
||||||
|
errBox.information(this, "Netplay Session", "Traversal Mode is not supported yet!");
|
||||||
|
errBox.show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool err = false;
|
||||||
|
// check nickname, game selected and player selected.
|
||||||
|
if (m_ui->lePlayerName->text().trimmed().isEmpty() || m_ui->cbSelectedGame->currentIndex() == 0 ||
|
||||||
|
m_ui->cbLocalPlayer->currentIndex() == 0)
|
||||||
|
err = true;
|
||||||
|
// check if direct ip details have been filled in
|
||||||
|
if (direct_ip && (m_ui->leRemoteAddr->text().trimmed().isEmpty() || m_ui->sbRemotePort->value() == 0 ||
|
||||||
|
m_ui->sbLocalPort->value() == 0))
|
||||||
|
err = true;
|
||||||
|
// check if host code has been filled in
|
||||||
|
if (!direct_ip && m_ui->leHostCode->text().trimmed().isEmpty() &&
|
||||||
|
m_ui->tabTraversal->currentWidget() == m_ui->tabJoin)
|
||||||
|
err = true;
|
||||||
|
// if an err has been found throw
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
QMessageBox errBox;
|
||||||
|
errBox.setFixedSize(500, 200);
|
||||||
|
errBox.information(this, "Netplay Session", "Please fill in all the needed fields!");
|
||||||
|
errBox.show();
|
||||||
|
return !err;
|
||||||
|
}
|
||||||
|
// check if controllers are set
|
||||||
|
err = !CheckControllersSet();
|
||||||
|
// everything filled in. inverse cuz we would like to return true if the info is valid.
|
||||||
|
return !err;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetplayWidget::CheckControllersSet()
|
||||||
|
{
|
||||||
|
bool err = false;
|
||||||
|
// check whether its controllers are set right
|
||||||
|
for (u32 i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(g_settings.controller_types[i]);
|
||||||
|
if (!cinfo || cinfo->type != ControllerType::DigitalController)
|
||||||
|
{
|
||||||
|
err = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if an err has been found throw popup
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
QMessageBox errBox;
|
||||||
|
errBox.information(this, "Netplay Session",
|
||||||
|
"Please make sure the controllers are both enabled and set as Digital Controllers");
|
||||||
|
errBox.setFixedSize(500, 200);
|
||||||
|
errBox.show();
|
||||||
|
}
|
||||||
|
// controllers are set right
|
||||||
|
return !err;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetplayWidget::StartSession(bool direct_ip)
|
||||||
|
{
|
||||||
|
if (!g_emu_thread)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int localHandle = m_ui->cbLocalPlayer->currentIndex();
|
||||||
|
int inputDelay = m_ui->sbInputDelay->value();
|
||||||
|
quint16 localPort = m_ui->sbLocalPort->value();
|
||||||
|
const QString& remoteAddr = m_ui->leRemoteAddr->text();
|
||||||
|
quint16 remotePort = m_ui->sbRemotePort->value();
|
||||||
|
const QString& gamePath = QString::fromStdString(m_available_games[m_ui->cbSelectedGame->currentIndex() - 1]);
|
||||||
|
|
||||||
|
if (!direct_ip)
|
||||||
|
return false; // TODO: Handle Nat Traversal and use that information by overriding the information above.
|
||||||
|
|
||||||
|
g_emu_thread->startNetplaySession(localHandle, localPort, remoteAddr, remotePort, inputDelay, gamePath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetplayWidget::StopSession()
|
||||||
|
{
|
||||||
|
if (!g_emu_thread)
|
||||||
|
return;
|
||||||
|
g_emu_thread->stopNetplaySession();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetplayWidget::OnMsgReceived(const QString& msg)
|
||||||
|
{
|
||||||
|
m_ui->lwChatWindow->addItem(msg);
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef NETPLAYWIDGET_H
|
||||||
|
#define NETPLAYWIDGET_H
|
||||||
|
|
||||||
|
#include <QtWidgets/QDialog>
|
||||||
|
#include <frontend-common/game_list.h>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class NetplayWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetplayWidget : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NetplayWidget(QWidget* parent = nullptr);
|
||||||
|
~NetplayWidget();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void FillGameList();
|
||||||
|
void SetupConnections();
|
||||||
|
void SetupConstraints();
|
||||||
|
bool CheckInfoValid(bool direct_ip);
|
||||||
|
bool CheckControllersSet();
|
||||||
|
bool StartSession(bool direct_ip);
|
||||||
|
void StopSession();
|
||||||
|
void OnMsgReceived(const QString& msg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::NetplayWidget* m_ui;
|
||||||
|
std::vector<std::string> m_available_games;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NETPLAYWIDGET_H
|
|
@ -0,0 +1,710 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>NetplayWidget</class>
|
||||||
|
<widget class="QDialog" name="NetplayWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>700</width>
|
||||||
|
<height>448</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>700</width>
|
||||||
|
<height>448</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>700</width>
|
||||||
|
<height>448</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<property name="modal">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QFrame" name="frMain">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>701</width>
|
||||||
|
<height>461</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>701</width>
|
||||||
|
<height>461</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>701</width>
|
||||||
|
<height>461</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Raised</enum>
|
||||||
|
</property>
|
||||||
|
<widget class="Line" name="line_2">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>40</y>
|
||||||
|
<width>711</width>
|
||||||
|
<height>20</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QPlainTextEdit" name="tbNetplayChat">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>380</x>
|
||||||
|
<y>400</y>
|
||||||
|
<width>231</width>
|
||||||
|
<height>41</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>10</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="plainText">
|
||||||
|
<string>...
|
||||||
|
</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QPushButton" name="btnSendMsg">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>620</x>
|
||||||
|
<y>400</y>
|
||||||
|
<width>61</width>
|
||||||
|
<height>41</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Send</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="Line" name="line">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>350</x>
|
||||||
|
<y>50</y>
|
||||||
|
<width>21</width>
|
||||||
|
<height>411</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>9</pointsize>
|
||||||
|
<italic>false</italic>
|
||||||
|
<bold>false</bold>
|
||||||
|
<underline>false</underline>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QListWidget" name="lwChatWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>380</x>
|
||||||
|
<y>60</y>
|
||||||
|
<width>301</width>
|
||||||
|
<height>331</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="verticalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
<widget class="QFrame" name="frDirectIP">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>9</x>
|
||||||
|
<y>220</y>
|
||||||
|
<width>341</width>
|
||||||
|
<height>171</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="autoFillBackground">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Raised</enum>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="formLayoutWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>10</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>321</width>
|
||||||
|
<height>161</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QSpinBox" name="sbRemotePort">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>65535</number>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="stepType">
|
||||||
|
<enum>QAbstractSpinBox::DefaultStepType</enum>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>2000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1" colspan="2">
|
||||||
|
<widget class="QLineEdit" name="leRemoteAddr">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="lblLocalPort">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Local Port :</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="lblRemoteAddr">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Remote Address :</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="lblRemotePort">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Remote Port :</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="2">
|
||||||
|
<widget class="QPushButton" name="btnStartSession">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Start Session</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1" colspan="2">
|
||||||
|
<widget class="QSpinBox" name="sbLocalPort">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>65535</number>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="stepType">
|
||||||
|
<enum>QAbstractSpinBox::DefaultStepType</enum>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>1000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QLabel" name="lblGame">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>10</x>
|
||||||
|
<y>10</y>
|
||||||
|
<width>61</width>
|
||||||
|
<height>31</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>12</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Game : </string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QComboBox" name="cbSelectedGame">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>70</x>
|
||||||
|
<y>10</y>
|
||||||
|
<width>611</width>
|
||||||
|
<height>31</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>10</pointsize>
|
||||||
|
<bold>true</bold>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="maxVisibleItems">
|
||||||
|
<number>8</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>SELECT A GAME</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
<widget class="QPushButton" name="btnStopSession">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>100</x>
|
||||||
|
<y>400</y>
|
||||||
|
<width>161</width>
|
||||||
|
<height>41</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>10</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Stop Session</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QTabWidget" name="tabTraversal">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>20</x>
|
||||||
|
<y>220</y>
|
||||||
|
<width>321</width>
|
||||||
|
<height>171</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="tabJoin">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Join</string>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QPushButton" name="btnTraversalJoin">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>220</x>
|
||||||
|
<y>60</y>
|
||||||
|
<width>81</width>
|
||||||
|
<height>31</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Join</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="horizontalLayoutWidget_2">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>10</x>
|
||||||
|
<y>20</y>
|
||||||
|
<width>291</width>
|
||||||
|
<height>28</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblHostCode">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Host Code :</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="leHostCode">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="tabHost">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>10</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Host</string>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QPushButton" name="btnTraversalHost">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>10</x>
|
||||||
|
<y>20</y>
|
||||||
|
<width>81</width>
|
||||||
|
<height>31</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Host</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="horizontalLayoutWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>10</x>
|
||||||
|
<y>60</y>
|
||||||
|
<width>281</width>
|
||||||
|
<height>31</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblHostCode2">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Host Code :</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblHostCodeResult">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>XXXXXXXXX-</string>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="gridLayoutWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>20</x>
|
||||||
|
<y>60</y>
|
||||||
|
<width>321</width>
|
||||||
|
<height>151</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="lblPlayer">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Player :</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QComboBox" name="cbLocalPlayer">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<bold>true</bold>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>NOT SET</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>2</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="lblInputDelay">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Input Delay :</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="lePlayerName">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Duck!</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QSpinBox" name="sbInputDelay">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="stepType">
|
||||||
|
<enum>QAbstractSpinBox::DefaultStepType</enum>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="cbConnMode">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<bold>true</bold>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>DIRECT IP</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>TRAVERSAL</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="lblConnMode">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Connection Mode :</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="lblPlayerName">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>11</pointsize>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Nickname : </string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<zorder>tabTraversal</zorder>
|
||||||
|
<zorder>frDirectIP</zorder>
|
||||||
|
<zorder>line_2</zorder>
|
||||||
|
<zorder>tbNetplayChat</zorder>
|
||||||
|
<zorder>btnSendMsg</zorder>
|
||||||
|
<zorder>line</zorder>
|
||||||
|
<zorder>lwChatWindow</zorder>
|
||||||
|
<zorder>lblGame</zorder>
|
||||||
|
<zorder>cbSelectedGame</zorder>
|
||||||
|
<zorder>btnStopSession</zorder>
|
||||||
|
<zorder>gridLayoutWidget</zorder>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -461,6 +461,12 @@ void EmuThread::startFullscreenUI()
|
||||||
wakeThread();
|
wakeThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Host::OnNetplayMessage(std::string& message)
|
||||||
|
{
|
||||||
|
QString msg(message.c_str());
|
||||||
|
emit g_emu_thread->onNetplayMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
void EmuThread::stopFullscreenUI()
|
void EmuThread::stopFullscreenUI()
|
||||||
{
|
{
|
||||||
if (!isOnThread())
|
if (!isOnThread())
|
||||||
|
@ -1065,6 +1071,48 @@ void EmuThread::reloadPostProcessingShaders()
|
||||||
System::ReloadPostProcessingShaders();
|
System::ReloadPostProcessingShaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmuThread::startNetplaySession(int local_handle, quint16 local_port, const QString& remote_addr,
|
||||||
|
quint16 remote_port, int input_delay, const QString& game_path)
|
||||||
|
{
|
||||||
|
if (!isOnThread())
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "startNetplaySession", Qt::QueuedConnection, Q_ARG(int, local_handle),
|
||||||
|
Q_ARG(quint16, local_port), Q_ARG(const QString&, remote_addr),
|
||||||
|
Q_ARG(quint16, remote_port), Q_ARG(int, input_delay), Q_ARG(const QString&, game_path));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// disable block linking and disable rewind and runahead during a netplay session
|
||||||
|
g_settings.cpu_recompiler_block_linking = false;
|
||||||
|
g_settings.rewind_enable = false;
|
||||||
|
g_settings.runahead_frames = 0;
|
||||||
|
|
||||||
|
Log_WarningPrintf("Disabling block linking, runahead and rewind due to rollback.");
|
||||||
|
|
||||||
|
auto remAddr = remote_addr.trimmed().toStdString();
|
||||||
|
auto gamePath = game_path.trimmed().toStdString();
|
||||||
|
System::StartNetplaySession(local_handle, local_port, remAddr, remote_port, input_delay, gamePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuThread::sendNetplayMessage(const QString& message)
|
||||||
|
{
|
||||||
|
if (!isOnThread())
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "sendNetplayMessage", Qt::QueuedConnection, Q_ARG(const QString&, message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Netplay::Session::SendMsg(message.toStdString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuThread::stopNetplaySession()
|
||||||
|
{
|
||||||
|
if (!isOnThread())
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "stopNetplaySession", Qt::QueuedConnection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
System::StopNetplaySession();
|
||||||
|
}
|
||||||
|
|
||||||
void EmuThread::runOnEmuThread(std::function<void()> callback)
|
void EmuThread::runOnEmuThread(std::function<void()> callback)
|
||||||
{
|
{
|
||||||
callback();
|
callback();
|
||||||
|
@ -1416,7 +1464,10 @@ void EmuThread::run()
|
||||||
{
|
{
|
||||||
if (System::IsRunning())
|
if (System::IsRunning())
|
||||||
{
|
{
|
||||||
System::Execute();
|
if (Netplay::Session::IsActive())
|
||||||
|
System::ExecuteNetplay();
|
||||||
|
else
|
||||||
|
System::Execute();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1659,6 +1710,14 @@ void EmuThread::updatePerformanceCounters()
|
||||||
m_last_speed = speed;
|
m_last_speed = speed;
|
||||||
m_last_video_fps = vfps;
|
m_last_video_fps = vfps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const s32 ping = Netplay::Session::GetPing();
|
||||||
|
if (m_last_ping != ping)
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(g_main_window->getStatusPingWidget(), "setText", Qt::QueuedConnection,
|
||||||
|
Q_ARG(const QString&, tr("Netplay Ping: %1 ").arg(ping, 0, 'f', 0)));
|
||||||
|
m_last_ping = ping;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::resetPerformanceCounters()
|
void EmuThread::resetPerformanceCounters()
|
||||||
|
|
|
@ -143,6 +143,7 @@ Q_SIGNALS:
|
||||||
void achievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
|
void achievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
|
||||||
void achievementsChallengeModeChanged();
|
void achievementsChallengeModeChanged();
|
||||||
void cheatEnabled(quint32 index, bool enabled);
|
void cheatEnabled(quint32 index, bool enabled);
|
||||||
|
void onNetplayMessage(const QString& message);
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void setDefaultSettings(bool system = true, bool controller = true);
|
void setDefaultSettings(bool system = true, bool controller = true);
|
||||||
|
@ -187,6 +188,10 @@ public Q_SLOTS:
|
||||||
void setCheatEnabled(quint32 index, bool enabled);
|
void setCheatEnabled(quint32 index, bool enabled);
|
||||||
void applyCheat(quint32 index);
|
void applyCheat(quint32 index);
|
||||||
void reloadPostProcessingShaders();
|
void reloadPostProcessingShaders();
|
||||||
|
void startNetplaySession(int local_handle, quint16 local_port, const QString& remote_addr, quint16 remote_port,
|
||||||
|
int input_delay, const QString& game_path);
|
||||||
|
void stopNetplaySession();
|
||||||
|
void sendNetplayMessage(const QString& message);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void stopInThread();
|
void stopInThread();
|
||||||
|
@ -232,6 +237,7 @@ private:
|
||||||
float m_last_video_fps = std::numeric_limits<float>::infinity();
|
float m_last_video_fps = std::numeric_limits<float>::infinity();
|
||||||
u32 m_last_render_width = std::numeric_limits<u32>::max();
|
u32 m_last_render_width = std::numeric_limits<u32>::max();
|
||||||
u32 m_last_render_height = std::numeric_limits<u32>::max();
|
u32 m_last_render_height = std::numeric_limits<u32>::max();
|
||||||
|
u32 m_last_ping = std::numeric_limits<u32>::max();
|
||||||
GPURenderer m_last_renderer = GPURenderer::Count;
|
GPURenderer m_last_renderer = GPURenderer::Count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "core/controller.h"
|
#include "core/controller.h"
|
||||||
#include "core/host.h"
|
#include "core/host.h"
|
||||||
#include "core/system.h"
|
#include "core/system.h"
|
||||||
|
#include "core/netplay.h"
|
||||||
#include "imgui_manager.h"
|
#include "imgui_manager.h"
|
||||||
#include "input_source.h"
|
#include "input_source.h"
|
||||||
|
|
||||||
|
@ -713,6 +714,12 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
|
||||||
if (!System::IsValid())
|
if (!System::IsValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (Netplay::Session::IsActive())
|
||||||
|
{
|
||||||
|
Netplay::Session::CollectInput(pad_index, bind_index, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Controller* c = System::GetController(pad_index);
|
Controller* c = System::GetController(pad_index);
|
||||||
if (c)
|
if (c)
|
||||||
c->SetBindState(bind_index, value);
|
c->SetBindState(bind_index, value);
|
||||||
|
|
Loading…
Reference in New Issue