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()
|
||||
{
|
||||
// Currently only runahead, will also be used for netplay.
|
||||
return g_settings.IsRunaheadEnabled();
|
||||
return g_settings.IsRunaheadEnabled() || Netplay::Session::IsActive();
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "mdec.h"
|
||||
#include "memory_card.h"
|
||||
#include "multitap.h"
|
||||
#include "netplay.h"
|
||||
#include "pad.h"
|
||||
#include "pgxp.h"
|
||||
#include "psf_loader.h"
|
||||
|
@ -207,6 +208,8 @@ static std::deque<MemorySaveState> s_runahead_states;
|
|||
static bool s_runahead_replay_pending = false;
|
||||
static u32 s_runahead_frames = 0;
|
||||
|
||||
static std::deque<MemorySaveState> s_netplay_states;
|
||||
|
||||
static TinyString GetTimestampStringForFileName()
|
||||
{
|
||||
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
|
||||
Host::PumpMessagesOnCPUThread();
|
||||
if (!IsValid())
|
||||
if (!IsValid() || Netplay::Session::IsActive())
|
||||
return;
|
||||
|
||||
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()
|
||||
{
|
||||
Assert(!IsShutdown());
|
||||
|
@ -4431,3 +4466,163 @@ void System::SetTimerResolutionIncreased(bool enabled)
|
|||
timeEndPeriod(1);
|
||||
#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
|
||||
#include "common/timer.h"
|
||||
#include "netplay.h"
|
||||
#include "settings.h"
|
||||
#include "timing_event.h"
|
||||
#include "types.h"
|
||||
|
@ -19,11 +20,10 @@ class Controller;
|
|||
struct CheatCode;
|
||||
class CheatList;
|
||||
|
||||
namespace BIOS
|
||||
{
|
||||
namespace BIOS {
|
||||
struct ImageInfo;
|
||||
struct Hash;
|
||||
}
|
||||
} // namespace BIOS
|
||||
|
||||
struct SystemBootParameters
|
||||
{
|
||||
|
@ -227,6 +227,9 @@ bool SaveResumeState();
|
|||
/// Runs the VM until the CPU execution is canceled.
|
||||
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).
|
||||
void RecreateSystem();
|
||||
|
||||
|
@ -452,6 +455,11 @@ void UpdateMemorySaveStateSettings();
|
|||
bool LoadRewindState(u32 skip_saves = 0, bool consume_state = true);
|
||||
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 Host {
|
||||
|
@ -500,4 +508,6 @@ bool IsFullscreen();
|
|||
|
||||
/// Alters fullscreen state of hosting application.
|
||||
void SetFullscreen(bool enabled);
|
||||
// netplay
|
||||
void OnNetplayMessage(std::string& message);
|
||||
} // namespace Host
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
#ifdef _WIN32
|
||||
#include "common/windows_headers.h"
|
||||
#include <Dbt.h>
|
||||
#include <WinSock2.h>
|
||||
#endif
|
||||
|
||||
Log_SetChannel(MainWindow);
|
||||
|
@ -105,6 +106,12 @@ MainWindow::MainWindow() : QMainWindow(nullptr)
|
|||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
s_use_central_widget = DisplayContainer::isRunningOnWayland();
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
// Setup WinSock
|
||||
WSADATA wd = {};
|
||||
WSAStartup(MAKEWORD(2, 2), &wd);
|
||||
#endif
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
|
@ -119,6 +126,8 @@ MainWindow::~MainWindow()
|
|||
|
||||
#ifdef _WIN32
|
||||
unregisterForDeviceNotifications();
|
||||
// Cleanup WinSock
|
||||
WSACleanup();
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
FrontendCommon::RemoveThemeChangeHandler(this);
|
||||
|
@ -1556,9 +1565,13 @@ void MainWindow::setupAdditionalUi()
|
|||
m_status_fps_widget->hide();
|
||||
|
||||
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_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->addAction(m_ui.actionSettings);
|
||||
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_fps_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()
|
||||
|
@ -2089,6 +2103,9 @@ void MainWindow::connectSignals()
|
|||
addThemeToMenu(tr("Dark Fusion (Blue)"), QStringLiteral("darkfusionblue"));
|
||||
addThemeToMenu(tr("QDarkStyle"), QStringLiteral("qdarkstyle"));
|
||||
updateMenuSelectedTheme();
|
||||
|
||||
// Netplay UI , TODO
|
||||
connect(m_ui.actionCreateNetplaySession, &QAction::triggered, this, &MainWindow::onNetplaySessionCreated);
|
||||
}
|
||||
|
||||
void MainWindow::addThemeToMenu(const QString& name, const QString& key)
|
||||
|
@ -2752,6 +2769,28 @@ void MainWindow::onCPUDebuggerClosed()
|
|||
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()
|
||||
{
|
||||
QtUtils::OpenURL(this, QUrl::fromLocalFile(QString::fromStdString(EmuFolders::DataRoot)));
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "displaywidget.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "ui_mainwindow.h"
|
||||
#include <netplaywidget.h>
|
||||
|
||||
class QLabel;
|
||||
class QThread;
|
||||
|
@ -89,6 +90,7 @@ public:
|
|||
ALWAYS_INLINE QLabel* getStatusResolutionWidget() const { return m_status_resolution_widget; }
|
||||
ALWAYS_INLINE QLabel* getStatusFPSWidget() const { return m_status_fps_widget; }
|
||||
ALWAYS_INLINE QLabel* getStatusVPSWidget() const { return m_status_vps_widget; }
|
||||
ALWAYS_INLINE QLabel* getStatusPingWidget() const { return m_status_ping_widget; }
|
||||
|
||||
public Q_SLOTS:
|
||||
/// Updates debug menu visibility (hides if disabled).
|
||||
|
@ -168,6 +170,8 @@ private Q_SLOTS:
|
|||
void openCPUDebugger();
|
||||
void onCPUDebuggerClosed();
|
||||
|
||||
void onNetplaySessionCreated();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
@ -262,6 +266,7 @@ private:
|
|||
QLabel* m_status_renderer_widget = nullptr;
|
||||
QLabel* m_status_fps_widget = nullptr;
|
||||
QLabel* m_status_vps_widget = nullptr;
|
||||
QLabel* m_status_ping_widget = nullptr;
|
||||
QLabel* m_status_resolution_widget = nullptr;
|
||||
|
||||
QMenu* m_settings_toolbar_menu = nullptr;
|
||||
|
@ -273,6 +278,7 @@ private:
|
|||
MemoryCardEditorDialog* m_memory_card_editor_dialog = nullptr;
|
||||
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
|
||||
DebuggerWindow* m_debugger_window = nullptr;
|
||||
NetplayWidget* m_netplay_window = nullptr;
|
||||
|
||||
std::string m_current_game_title;
|
||||
std::string m_current_game_serial;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<string>DuckStation</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
|
||||
</property>
|
||||
<widget class="QStackedWidget" name="mainContainer"/>
|
||||
|
@ -27,7 +27,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>22</height>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuSystem">
|
||||
|
@ -237,8 +237,15 @@
|
|||
<addaction name="separator"/>
|
||||
<addaction name="actionOpenDataDirectory"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuNetplay">
|
||||
<property name="title">
|
||||
<string>Netplay</string>
|
||||
</property>
|
||||
<addaction name="actionCreateNetplaySession"/>
|
||||
</widget>
|
||||
<addaction name="menuSystem"/>
|
||||
<addaction name="menuSettings"/>
|
||||
<addaction name="menuNetplay"/>
|
||||
<addaction name="menu_View"/>
|
||||
<addaction name="menu_Tools"/>
|
||||
<addaction name="menuDebug"/>
|
||||
|
@ -472,7 +479,7 @@
|
|||
</action>
|
||||
<action name="actionGitHubRepository">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/github.png</normaloff>:/icons/github.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -481,7 +488,7 @@
|
|||
</action>
|
||||
<action name="actionIssueTracker">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/IssueTracker.png</normaloff>:/icons/IssueTracker.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -490,7 +497,7 @@
|
|||
</action>
|
||||
<action name="actionDiscordServer">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/discord.png</normaloff>:/icons/discord.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -508,7 +515,7 @@
|
|||
</action>
|
||||
<action name="actionAboutQt">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/QT.png</normaloff>:/icons/QT.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -517,7 +524,7 @@
|
|||
</action>
|
||||
<action name="actionAbout">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/duck_64.png</normaloff>:/icons/duck_64.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -976,6 +983,11 @@
|
|||
<string>Cover Downloader</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCreateNetplaySession">
|
||||
<property name="text">
|
||||
<string>Create Session</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<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();
|
||||
}
|
||||
|
||||
void Host::OnNetplayMessage(std::string& message)
|
||||
{
|
||||
QString msg(message.c_str());
|
||||
emit g_emu_thread->onNetplayMessage(msg);
|
||||
}
|
||||
|
||||
void EmuThread::stopFullscreenUI()
|
||||
{
|
||||
if (!isOnThread())
|
||||
|
@ -1065,6 +1071,48 @@ void EmuThread::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)
|
||||
{
|
||||
callback();
|
||||
|
@ -1416,7 +1464,10 @@ void EmuThread::run()
|
|||
{
|
||||
if (System::IsRunning())
|
||||
{
|
||||
System::Execute();
|
||||
if (Netplay::Session::IsActive())
|
||||
System::ExecuteNetplay();
|
||||
else
|
||||
System::Execute();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1659,6 +1710,14 @@ void EmuThread::updatePerformanceCounters()
|
|||
m_last_speed = speed;
|
||||
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()
|
||||
|
|
|
@ -143,6 +143,7 @@ Q_SIGNALS:
|
|||
void achievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
|
||||
void achievementsChallengeModeChanged();
|
||||
void cheatEnabled(quint32 index, bool enabled);
|
||||
void onNetplayMessage(const QString& message);
|
||||
|
||||
public Q_SLOTS:
|
||||
void setDefaultSettings(bool system = true, bool controller = true);
|
||||
|
@ -187,6 +188,10 @@ public Q_SLOTS:
|
|||
void setCheatEnabled(quint32 index, bool enabled);
|
||||
void applyCheat(quint32 index);
|
||||
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:
|
||||
void stopInThread();
|
||||
|
@ -232,6 +237,7 @@ private:
|
|||
float m_last_video_fps = std::numeric_limits<float>::infinity();
|
||||
u32 m_last_render_width = 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;
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "core/controller.h"
|
||||
#include "core/host.h"
|
||||
#include "core/system.h"
|
||||
#include "core/netplay.h"
|
||||
#include "imgui_manager.h"
|
||||
#include "input_source.h"
|
||||
|
||||
|
@ -713,6 +714,12 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
|
|||
if (!System::IsValid())
|
||||
return;
|
||||
|
||||
if (Netplay::Session::IsActive())
|
||||
{
|
||||
Netplay::Session::CollectInput(pad_index, bind_index, value);
|
||||
return;
|
||||
}
|
||||
|
||||
Controller* c = System::GetController(pad_index);
|
||||
if (c)
|
||||
c->SetBindState(bind_index, value);
|
||||
|
|
Loading…
Reference in New Issue