Netplay: Add experimental rollback netplay implementation
Co-authored-by: Jamie Meyer <45072324+HeatXD@users.noreply.github.com>
This commit is contained in:
parent
5f99fda9d7
commit
1e585e74e8
|
@ -10,6 +10,7 @@ class LayeredSettingsInterface final : public SettingsInterface
|
|||
public:
|
||||
enum Layer : u32
|
||||
{
|
||||
LAYER_NETPLAY,
|
||||
LAYER_CMDLINE,
|
||||
LAYER_GAME,
|
||||
LAYER_INPUT,
|
||||
|
@ -61,7 +62,7 @@ public:
|
|||
using SettingsInterface::GetUIntValue;
|
||||
|
||||
private:
|
||||
static constexpr Layer FIRST_LAYER = LAYER_CMDLINE;
|
||||
static constexpr Layer FIRST_LAYER = LAYER_NETPLAY;
|
||||
static constexpr Layer LAST_LAYER = LAYER_BASE;
|
||||
|
||||
std::array<SettingsInterface*, NUM_LAYERS> m_layers{};
|
||||
|
|
|
@ -79,6 +79,9 @@ add_library(core
|
|||
multitap.h
|
||||
negcon.cpp
|
||||
negcon.h
|
||||
netplay.cpp
|
||||
netplay.h
|
||||
netplay_packets.h
|
||||
pad.cpp
|
||||
pad.h
|
||||
pcdrv.cpp
|
||||
|
@ -126,6 +129,10 @@ target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
|||
target_link_libraries(core PUBLIC Threads::Threads common util zlib)
|
||||
target_link_libraries(core PRIVATE stb xxhash imgui rapidjson rcheevos)
|
||||
|
||||
if(NOT ANDROID)
|
||||
target_link_libraries(core PRIVATE enet ggpo-x)
|
||||
endif()
|
||||
|
||||
if(${CPU_ARCH} STREQUAL "x64")
|
||||
target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../../dep/xbyak/xbyak")
|
||||
target_compile_definitions(core PUBLIC "XBYAK_NO_EXCEPTION=1" "ENABLE_RECOMPILER=1" "ENABLE_MMAP_FASTMEM=1")
|
||||
|
|
|
@ -18,4 +18,9 @@
|
|||
<AdditionalIncludeDirectories Condition="'$(Platform)'=='ARM' Or '$(Platform)'=='ARM64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\vixl\include</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);ws2_32.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
</Project>
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
<ClCompile Include="multitap.cpp" />
|
||||
<ClCompile Include="guncon.cpp" />
|
||||
<ClCompile Include="negcon.cpp" />
|
||||
<ClCompile Include="netplay.cpp" />
|
||||
<ClCompile Include="pad.cpp" />
|
||||
<ClCompile Include="controller.cpp" />
|
||||
<ClCompile Include="pcdrv.cpp" />
|
||||
|
@ -129,6 +130,8 @@
|
|||
<ClInclude Include="multitap.h" />
|
||||
<ClInclude Include="guncon.h" />
|
||||
<ClInclude Include="negcon.h" />
|
||||
<ClInclude Include="netplay.h" />
|
||||
<ClInclude Include="netplay_packets.h" />
|
||||
<ClInclude Include="pad.h" />
|
||||
<ClInclude Include="controller.h" />
|
||||
<ClInclude Include="pcdrv.h" />
|
||||
|
@ -152,6 +155,12 @@
|
|||
<ProjectReference Include="..\..\dep\discord-rpc\discord-rpc.vcxproj">
|
||||
<Project>{4266505b-dbaf-484b-ab31-b53b9c8235b3}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\enet\enet.vcxproj">
|
||||
<Project>{460a096b-fcc7-465c-8e4b-434af490a9ea}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\ggpo-x\ggpo-x.vcxproj">
|
||||
<Project>{edf3634a-ce8a-4625-92bd-27bad5d30a9a}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
|
||||
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
|
||||
</ProjectReference>
|
||||
|
@ -190,6 +199,7 @@
|
|||
<Import Project="core.props" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\enet\include;$(SolutionDir)dep\ggpo-x\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>ZYDIS_DISABLE_ENCODER;ZYDIS_DISABLE_AVX512;ZYDIS_DISABLE_KNC;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'=='x64'">$(SolutionDir)dep\zydis\include;$(SolutionDir)dep\zydis\dependencies\zycore\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<ObjectFileName>$(IntDir)/%(RelativeDir)/</ObjectFileName>
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
<ClCompile Include="hotkeys.cpp" />
|
||||
<ClCompile Include="gpu_shadergen.cpp" />
|
||||
<ClCompile Include="pch.cpp" />
|
||||
<ClCompile Include="netplay.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
|
@ -124,5 +125,7 @@
|
|||
<ClInclude Include="shader_cache_version.h" />
|
||||
<ClInclude Include="gpu_shadergen.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="netplay.h" />
|
||||
<ClInclude Include="netplay_packets.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -217,6 +217,12 @@ void Host::Internal::SetInputSettingsLayer(SettingsInterface* sif)
|
|||
s_layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_INPUT, sif);
|
||||
}
|
||||
|
||||
void Host::Internal::SetNetplaySettingsLayer(SettingsInterface* sif)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
s_layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_NETPLAY, sif);
|
||||
}
|
||||
|
||||
void Host::ReportFormattedDebuggerMessage(const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
|
|
|
@ -119,5 +119,8 @@ void SetGameSettingsLayer(SettingsInterface* sif);
|
|||
|
||||
/// Sets the input profile settings layer. Called by VMManager when the game changes.
|
||||
void SetInputSettingsLayer(SettingsInterface* sif);
|
||||
|
||||
/// Sets the netplay settings layer. Use once a session is established.
|
||||
void SetNetplaySettingsLayer(SettingsInterface* sif);
|
||||
} // namespace Internal
|
||||
} // namespace Host
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "gpu.h"
|
||||
#include "host.h"
|
||||
#include "imgui_overlays.h"
|
||||
#include "netplay.h"
|
||||
#include "pgxp.h"
|
||||
#include "settings.h"
|
||||
#include "spu.h"
|
||||
|
@ -107,6 +108,17 @@ DEFINE_HOTKEY("OpenPauseMenu", TRANSLATE_NOOP("Hotkeys", "General"), TRANSLATE_N
|
|||
if (!pressed)
|
||||
FullscreenUI::OpenPauseMenu();
|
||||
})
|
||||
// Netplay on Android? Give me a break....
|
||||
DEFINE_HOTKEY("OpenNetplayChat", TRANSLATE_NOOP("Hotkeys", "Netplay"), TRANSLATE_NOOP("Hotkeys", "Open Netplay Chat"),
|
||||
[](s32 pressed) {
|
||||
if (!pressed)
|
||||
Netplay::OpenChat();
|
||||
})
|
||||
DEFINE_HOTKEY("ToggleDesyncNotifications", TRANSLATE_NOOP("Hotkeys", "Netplay"),
|
||||
TRANSLATE_NOOP("Hotkeys", "Toggle Desync Notifications"), [](s32 pressed) {
|
||||
if (!pressed)
|
||||
Netplay::ToggleDesyncNotifications();
|
||||
})
|
||||
#endif
|
||||
|
||||
DEFINE_HOTKEY("FastForward", TRANSLATE_NOOP("Hotkeys", "General"), TRANSLATE_NOOP("Hotkeys", "Fast Forward"),
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,76 @@
|
|||
// SPDX-FileCopyrightText: 2023 Connor McLaughlin <stenzek@gmail.com> and contributors.
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include <string>
|
||||
|
||||
namespace Netplay {
|
||||
|
||||
enum : s32
|
||||
{
|
||||
// Maximum number of emulated controllers.
|
||||
MAX_PLAYERS = 2,
|
||||
|
||||
// Maximum number of spectators allowed to watch the session.
|
||||
MAX_SPECTATORS = 4,
|
||||
|
||||
// Maximum netplay prediction frames
|
||||
MAX_ROLLBACK_FRAMES = 8,
|
||||
|
||||
// Maximum length of a nickname
|
||||
MAX_NICKNAME_LENGTH = 128,
|
||||
|
||||
// Maximum name of password for session
|
||||
MAX_SESSION_PASSWORD_LENGTH = 128,
|
||||
};
|
||||
|
||||
enum : u8
|
||||
{
|
||||
ENET_CHANNEL_CONTROL = 0,
|
||||
ENET_CHANNEL_GGPO = 1,
|
||||
|
||||
NUM_ENET_CHANNELS,
|
||||
};
|
||||
|
||||
bool CreateSession(std::string nickname, s32 port, s32 max_players, std::string password, int inputdelay,
|
||||
bool traversal);
|
||||
bool JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password, bool spectating,
|
||||
int inputdelay, bool traversal, const std::string& hostcode);
|
||||
|
||||
bool IsActive();
|
||||
|
||||
/// Frees up resources associated with the current netplay session.
|
||||
/// Should only be called by System::ShutdownSystem().
|
||||
void SystemDestroyed();
|
||||
|
||||
/// Runs the VM and netplay loop. when the netplay loop cancels it switches to normal execute mode.
|
||||
void ExecuteNetplay();
|
||||
|
||||
/// Called when a frame is completed (System::FrameDone()).
|
||||
void FrameDone();
|
||||
|
||||
void CollectInput(u32 slot, u32 bind, float value);
|
||||
|
||||
void SendChatMessage(const std::string_view& msg);
|
||||
|
||||
s32 GetPing();
|
||||
u32 GetMaxPrediction();
|
||||
std::string_view GetHostCode();
|
||||
|
||||
/// Updates the throttle period, call when target emulation speed changes.
|
||||
void UpdateThrottlePeriod();
|
||||
|
||||
void ToggleDesyncNotifications();
|
||||
|
||||
// OSD
|
||||
void RenderOverlays();
|
||||
void OpenChat();
|
||||
|
||||
} // namespace Netplay
|
||||
|
||||
namespace Host {
|
||||
void OnNetplaySessionOpened(bool is_host);
|
||||
void OnNetplaySessionClosed();
|
||||
} // namespace Host
|
|
@ -0,0 +1,319 @@
|
|||
// SPDX-FileCopyrightText: 2023 Connor McLaughlin <stenzek@gmail.com> and contributors.
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "bios.h"
|
||||
#include "host.h"
|
||||
#include "types.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace Netplay {
|
||||
|
||||
enum class SessionState
|
||||
{
|
||||
Inactive,
|
||||
Initializing,
|
||||
Connecting,
|
||||
Resetting,
|
||||
Running,
|
||||
ClosingSession,
|
||||
};
|
||||
|
||||
enum class ControlMessage : u32
|
||||
{
|
||||
// host->player
|
||||
ConnectResponse,
|
||||
JoinResponse,
|
||||
PreReset,
|
||||
Reset,
|
||||
ResumeSession,
|
||||
PlayerJoined,
|
||||
DropPlayer,
|
||||
CloseSession,
|
||||
|
||||
// player->host
|
||||
JoinRequest,
|
||||
ResetComplete,
|
||||
ResetRequest,
|
||||
|
||||
// bi-directional
|
||||
SetNickname,
|
||||
ChatMessage,
|
||||
};
|
||||
|
||||
enum class DropPlayerReason : u32
|
||||
{
|
||||
ConnectTimeout,
|
||||
DisconnectedFromHost,
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct ControlMessageHeader
|
||||
{
|
||||
ControlMessage type;
|
||||
u32 size;
|
||||
};
|
||||
|
||||
struct ConnectResponseMessage
|
||||
{
|
||||
ControlMessageHeader header;
|
||||
|
||||
s32 num_players;
|
||||
s32 max_players;
|
||||
u64 game_hash;
|
||||
u32 game_serial_length;
|
||||
u32 game_title_length;
|
||||
BIOS::Hash bios_hash;
|
||||
|
||||
struct
|
||||
{
|
||||
ConsoleRegion console_region;
|
||||
CPUExecutionMode cpu_execution_mode;
|
||||
u32 cpu_overclock_numerator;
|
||||
u32 cpu_overclock_denominator;
|
||||
bool cpu_overclock_enable;
|
||||
bool cpu_recompiler_memory_exceptions;
|
||||
bool cpu_recompiler_icache;
|
||||
bool gpu_disable_interlacing;
|
||||
bool gpu_force_ntsc_timings;
|
||||
bool gpu_widescreen_hack;
|
||||
bool gpu_pgxp_enable;
|
||||
bool gpu_pgxp_culling;
|
||||
bool gpu_pgxp_cpu;
|
||||
bool gpu_pgxp_preserve_proj_fp;
|
||||
bool cdrom_region_check;
|
||||
bool disable_all_enhancements;
|
||||
bool use_old_mdec_routines;
|
||||
bool bios_tty_logging;
|
||||
bool was_fast_booted;
|
||||
bool enable_8mb_ram;
|
||||
DisplayAspectRatio display_aspect_ratio;
|
||||
u16 display_aspect_ratio_custom_numerator;
|
||||
u16 display_aspect_ratio_custom_denominator;
|
||||
MultitapMode multitap_mode;
|
||||
TickCount dma_max_slice_ticks;
|
||||
TickCount dma_halt_ticks;
|
||||
u32 gpu_fifo_size;
|
||||
TickCount gpu_max_run_ahead;
|
||||
} settings;
|
||||
|
||||
// <char> * game_serial_length + game_title_length follows
|
||||
// TODO: Include the settings overlays required to match the host config.
|
||||
|
||||
bool Validate() const
|
||||
{
|
||||
return (static_cast<unsigned>(settings.console_region) < static_cast<unsigned>(ConsoleRegion::Count) &&
|
||||
static_cast<unsigned>(settings.cpu_execution_mode) < static_cast<unsigned>(CPUExecutionMode::Count) &&
|
||||
static_cast<unsigned>(settings.display_aspect_ratio) < static_cast<unsigned>(DisplayAspectRatio::Count) &&
|
||||
static_cast<unsigned>(settings.multitap_mode) < static_cast<unsigned>(MultitapMode::Count));
|
||||
}
|
||||
|
||||
std::string_view GetGameSerial() const
|
||||
{
|
||||
return std::string_view(reinterpret_cast<const char*>(this) + sizeof(ConnectResponseMessage), game_serial_length);
|
||||
}
|
||||
|
||||
std::string_view GetGameTitle() const
|
||||
{
|
||||
return std::string_view(reinterpret_cast<const char*>(this) + sizeof(ConnectResponseMessage) + game_serial_length,
|
||||
game_title_length);
|
||||
}
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::ConnectResponse; }
|
||||
};
|
||||
|
||||
struct JoinRequestMessage
|
||||
{
|
||||
enum class Mode
|
||||
{
|
||||
Player,
|
||||
Spectator,
|
||||
};
|
||||
|
||||
ControlMessageHeader header;
|
||||
|
||||
Mode mode;
|
||||
s32 requested_player_id;
|
||||
char nickname[MAX_NICKNAME_LENGTH];
|
||||
char session_password[MAX_SESSION_PASSWORD_LENGTH];
|
||||
|
||||
std::string_view GetNickname() const
|
||||
{
|
||||
const size_t len = strnlen(nickname, std::size(nickname));
|
||||
return std::string_view(nickname, len);
|
||||
}
|
||||
|
||||
std::string_view GetSessionPassword() const
|
||||
{
|
||||
const size_t len = strnlen(session_password, std::size(session_password));
|
||||
return std::string_view(session_password, len);
|
||||
}
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::JoinRequest; }
|
||||
};
|
||||
|
||||
struct JoinResponseMessage
|
||||
{
|
||||
enum class Result : u32
|
||||
{
|
||||
Success = 0,
|
||||
ServerFull,
|
||||
PlayerIDInUse,
|
||||
SessionClosed,
|
||||
InvalidPassword,
|
||||
};
|
||||
|
||||
ControlMessageHeader header;
|
||||
|
||||
Result result;
|
||||
s32 player_id;
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::JoinResponse; }
|
||||
};
|
||||
|
||||
struct PreResetMessage
|
||||
{
|
||||
ControlMessageHeader header;
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::PreReset; }
|
||||
};
|
||||
|
||||
struct ResetMessage
|
||||
{
|
||||
struct PlayerAddress
|
||||
{
|
||||
u32 host;
|
||||
u16 port;
|
||||
s16 controller_port; // -1 if not present
|
||||
char nickname[MAX_NICKNAME_LENGTH];
|
||||
|
||||
std::string_view GetNickname() const
|
||||
{
|
||||
const size_t len = strnlen(nickname, std::size(nickname));
|
||||
return std::string_view(nickname, len);
|
||||
}
|
||||
};
|
||||
|
||||
ControlMessageHeader header;
|
||||
u32 cookie;
|
||||
s32 num_players;
|
||||
PlayerAddress players[MAX_PLAYERS];
|
||||
u32 state_data_size;
|
||||
// state_data_size bytes of state data follows
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::Reset; }
|
||||
};
|
||||
|
||||
struct ResetCompleteMessage
|
||||
{
|
||||
ControlMessageHeader header;
|
||||
|
||||
u32 cookie;
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::ResetComplete; }
|
||||
};
|
||||
|
||||
struct ResumeSessionMessage
|
||||
{
|
||||
ControlMessageHeader header;
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::ResumeSession; }
|
||||
};
|
||||
|
||||
struct PlayerJoinedMessage
|
||||
{
|
||||
ControlMessageHeader header;
|
||||
s32 player_id;
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::PlayerJoined; }
|
||||
};
|
||||
|
||||
struct DropPlayerMessage
|
||||
{
|
||||
ControlMessageHeader header;
|
||||
DropPlayerReason reason;
|
||||
s32 player_id;
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::DropPlayer; }
|
||||
};
|
||||
|
||||
struct ResetRequestMessage
|
||||
{
|
||||
enum class Reason : u32
|
||||
{
|
||||
ConnectionLost,
|
||||
};
|
||||
|
||||
ControlMessageHeader header;
|
||||
Reason reason;
|
||||
s32 causing_player_id;
|
||||
|
||||
std::string ReasonToString() const
|
||||
{
|
||||
switch (reason)
|
||||
{
|
||||
case Reason::ConnectionLost:
|
||||
return fmt::format(TRANSLATE_FS("Netplay", "Connection lost to player {}."), causing_player_id);
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::ResetRequest; }
|
||||
};
|
||||
|
||||
struct CloseSessionMessage
|
||||
{
|
||||
enum class Reason : u32
|
||||
{
|
||||
HostRequest,
|
||||
HostShutdown,
|
||||
};
|
||||
|
||||
ControlMessageHeader header;
|
||||
Reason reason;
|
||||
|
||||
std::string_view ReasonToString() const
|
||||
{
|
||||
switch (reason)
|
||||
{
|
||||
case Reason::HostRequest:
|
||||
return TRANSLATE_SV("Netplay", "Session closed due to host request.");
|
||||
|
||||
case Reason::HostShutdown:
|
||||
return TRANSLATE_SV("Netplay", "Session closed due to host shutdown.");
|
||||
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::CloseSession; }
|
||||
};
|
||||
|
||||
struct SetNicknameMessage
|
||||
{
|
||||
ControlMessageHeader header;
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::SetNickname; }
|
||||
};
|
||||
|
||||
struct ChatMessage
|
||||
{
|
||||
ControlMessageHeader header;
|
||||
|
||||
std::string_view GetMessage() const
|
||||
{
|
||||
return (header.size > sizeof(ChatMessage)) ?
|
||||
std::string_view(reinterpret_cast<const char*>(this) + sizeof(ChatMessage),
|
||||
header.size - sizeof(ChatMessage)) :
|
||||
std::string_view();
|
||||
}
|
||||
|
||||
static ControlMessage MessageType() { return ControlMessage::ChatMessage; }
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
} // namespace Netplay
|
|
@ -7,6 +7,7 @@
|
|||
#include "interrupt_controller.h"
|
||||
#include "memory_card.h"
|
||||
#include "multitap.h"
|
||||
#include "netplay.h"
|
||||
#include "save_state_version.h"
|
||||
#include "system.h"
|
||||
#include "types.h"
|
||||
|
@ -159,6 +160,8 @@ void Pad::Reset()
|
|||
{
|
||||
SoftReset();
|
||||
|
||||
s_last_memory_card_transfer_frame = 0;
|
||||
|
||||
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||
{
|
||||
if (s_controllers[i])
|
||||
|
@ -175,12 +178,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::IsActive();
|
||||
}
|
||||
|
||||
u32 Pad::GetMaximumRollbackFrames()
|
||||
{
|
||||
return g_settings.runahead_frames;
|
||||
return (Netplay::IsActive() ? Netplay::GetMaxPrediction() : g_settings.runahead_frames);
|
||||
}
|
||||
|
||||
bool Pad::DoStateController(StateWrapper& sw, u32 i)
|
||||
|
@ -251,11 +254,12 @@ bool Pad::DoStateController(StateWrapper& sw, u32 i)
|
|||
|
||||
bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
|
||||
{
|
||||
const bool force_load = Netplay::IsActive();
|
||||
bool card_present_in_state = static_cast<bool>(s_memory_cards[i]);
|
||||
|
||||
sw.Do(&card_present_in_state);
|
||||
|
||||
if (card_present_in_state && !s_memory_cards[i] && g_settings.load_devices_from_save_states)
|
||||
if (card_present_in_state && !s_memory_cards[i] && g_settings.load_devices_from_save_states && !force_load)
|
||||
{
|
||||
Host::AddFormattedOSDMessage(
|
||||
20.0f,
|
||||
|
@ -269,7 +273,7 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
|
|||
|
||||
if (card_present_in_state)
|
||||
{
|
||||
if (sw.IsReading() && !g_settings.load_devices_from_save_states)
|
||||
if (sw.IsReading() && !g_settings.load_devices_from_save_states && !force_load)
|
||||
{
|
||||
// load memcard into a temporary: If the card datas match, take the one from the savestate
|
||||
// since it has other useful non-data state information. Otherwise take the user's card
|
||||
|
@ -281,7 +285,7 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (sw.IsWriting())
|
||||
if (sw.IsWriting() || force_load)
|
||||
return true; // all done as far as writes concerned.
|
||||
|
||||
if (card_from_state)
|
||||
|
@ -468,6 +472,9 @@ bool Pad::DoState(StateWrapper& sw, bool is_memory_state)
|
|||
}
|
||||
else
|
||||
{
|
||||
if (sw.IsReading())
|
||||
s_last_memory_card_transfer_frame = 0;
|
||||
|
||||
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||
{
|
||||
if ((sw.GetVersion() < 50) && (i >= 2))
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "mdec.h"
|
||||
#include "memory_card.h"
|
||||
#include "multitap.h"
|
||||
#include "netplay.h"
|
||||
#include "pad.h"
|
||||
#include "pcdrv.h"
|
||||
#include "pgxp.h"
|
||||
|
@ -71,6 +72,7 @@ Log_SetChannel(System);
|
|||
#ifdef _WIN32
|
||||
#include "common/windows_headers.h"
|
||||
#include <mmsystem.h>
|
||||
#include <WinSock2.h>
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_DISCORD_PRESENCE
|
||||
|
@ -241,6 +243,12 @@ static TinyString GetTimestampStringForFileName()
|
|||
|
||||
void System::Internal::ProcessStartup()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
// Setup WinSock
|
||||
WSADATA wd = {};
|
||||
WSAStartup(MAKEWORD(2, 2), &wd);
|
||||
#endif
|
||||
|
||||
if (!Bus::AllocateMemory())
|
||||
Panic("Failed to allocate memory for emulated bus.");
|
||||
|
||||
|
@ -266,6 +274,11 @@ void System::Internal::ProcessShutdown()
|
|||
InputManager::CloseSources();
|
||||
|
||||
Bus::ReleaseMemory();
|
||||
|
||||
#ifdef _WIN32
|
||||
// Cleanup WinSock
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
void System::Internal::IdlePollUpdate()
|
||||
|
@ -966,7 +979,7 @@ void System::ApplySettings(bool display_osd_messages)
|
|||
CheckForSettingsChanges(old_config);
|
||||
Host::CheckForSettingsChanges(old_config);
|
||||
|
||||
if (IsValid())
|
||||
if (IsValid() && !Netplay::IsActive())
|
||||
{
|
||||
ResetPerformanceCounters();
|
||||
if (s_system_executing)
|
||||
|
@ -1077,7 +1090,7 @@ void System::ResetSystem()
|
|||
|
||||
void System::PauseSystem(bool paused)
|
||||
{
|
||||
if (paused == IsPaused() || !IsValid())
|
||||
if (paused == IsPaused() || !IsValid() || Netplay::IsActive())
|
||||
return;
|
||||
|
||||
SetState(paused ? State::Paused : State::Running);
|
||||
|
@ -1117,7 +1130,7 @@ void System::PauseSystem(bool paused)
|
|||
|
||||
bool System::LoadState(const char* filename)
|
||||
{
|
||||
if (!IsValid())
|
||||
if (!IsValid() || Netplay::IsActive())
|
||||
return false;
|
||||
|
||||
if (Achievements::IsHardcoreModeActive() &&
|
||||
|
@ -1748,6 +1761,12 @@ void System::FrameDone()
|
|||
PollDiscordPresence();
|
||||
#endif
|
||||
|
||||
if (Netplay::IsActive())
|
||||
{
|
||||
Netplay::FrameDone();
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_frame_step_request)
|
||||
{
|
||||
s_frame_step_request = false;
|
||||
|
@ -1855,6 +1874,9 @@ void System::UpdateThrottlePeriod()
|
|||
}
|
||||
|
||||
ResetThrottler();
|
||||
|
||||
if (Netplay::IsActive())
|
||||
Netplay::UpdateThrottlePeriod();
|
||||
}
|
||||
|
||||
void System::ResetThrottler()
|
||||
|
@ -2650,7 +2672,7 @@ bool System::IsFastForwardEnabled()
|
|||
|
||||
void System::SetFastForwardEnabled(bool enabled)
|
||||
{
|
||||
if (!IsValid())
|
||||
if (!IsValid() || Netplay::IsActive())
|
||||
return;
|
||||
|
||||
s_fast_forward_enabled = enabled;
|
||||
|
@ -2664,7 +2686,7 @@ bool System::IsTurboEnabled()
|
|||
|
||||
void System::SetTurboEnabled(bool enabled)
|
||||
{
|
||||
if (!IsValid())
|
||||
if (!IsValid() || Netplay::IsActive())
|
||||
return;
|
||||
|
||||
s_turbo_enabled = enabled;
|
||||
|
@ -2673,7 +2695,7 @@ void System::SetTurboEnabled(bool enabled)
|
|||
|
||||
void System::SetRewindState(bool enabled)
|
||||
{
|
||||
if (!System::IsValid())
|
||||
if (!System::IsValid() || Netplay::IsActive())
|
||||
return;
|
||||
|
||||
if (!g_settings.rewind_enable)
|
||||
|
@ -2693,7 +2715,7 @@ void System::SetRewindState(bool enabled)
|
|||
|
||||
void System::DoFrameStep()
|
||||
{
|
||||
if (!IsValid())
|
||||
if (!IsValid() || Netplay::IsActive())
|
||||
return;
|
||||
|
||||
if (Achievements::IsHardcoreModeActive() && !Achievements::ConfirmHardcoreModeDisable("Frame stepping"))
|
||||
|
@ -3993,6 +4015,9 @@ void System::ShutdownSystem(bool save_resume_state)
|
|||
if (!IsValid())
|
||||
return;
|
||||
|
||||
if (Netplay::IsActive())
|
||||
Netplay::SystemDestroyed();
|
||||
|
||||
if (save_resume_state)
|
||||
SaveResumeState();
|
||||
|
||||
|
@ -4598,6 +4623,8 @@ bool System::PresentDisplay(bool allow_skip_present)
|
|||
if (!skip_present)
|
||||
{
|
||||
FullscreenUI::Render();
|
||||
if (Netplay::IsActive())
|
||||
Netplay::RenderOverlays();
|
||||
ImGuiManager::RenderTextOverlays();
|
||||
ImGuiManager::RenderOSDMessages();
|
||||
|
||||
|
|
|
@ -123,6 +123,10 @@ set(SRCS
|
|||
memorycardsettingswidget.h
|
||||
memoryviewwidget.cpp
|
||||
memoryviewwidget.h
|
||||
netplaycreatesessiondialog.ui
|
||||
netplayjoinsessiondialog.ui
|
||||
netplaydialogs.cpp
|
||||
netplaydialogs.h
|
||||
postprocessingsettingswidget.cpp
|
||||
postprocessingsettingswidget.h
|
||||
postprocessingsettingswidget.ui
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="netplaydialogs.cpp" />
|
||||
<ClCompile Include="postprocessingsettingswidget.cpp" />
|
||||
<ClCompile Include="qttranslations.cpp" />
|
||||
<ClCompile Include="qthost.cpp" />
|
||||
|
@ -89,6 +90,7 @@
|
|||
<QtMoc Include="memoryviewwidget.h" />
|
||||
<QtMoc Include="logwindow.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<QtMoc Include="netplaydialogs.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="settingwidgetbinder.h" />
|
||||
<QtMoc Include="consolesettingswidget.h" />
|
||||
|
@ -262,6 +264,7 @@
|
|||
<ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_logwindow.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_netplaydialogs.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_memorycardeditordialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_memoryviewwidget.cpp" />
|
||||
|
@ -332,6 +335,12 @@
|
|||
<QtUi Include="setupwizarddialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="netplaycreatesessiondialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="netplayjoinsessiondialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<None Include="translations\duckstation-qt_es-es.ts" />
|
||||
<None Include="translations\duckstation-qt_tr.ts" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -96,6 +96,8 @@
|
|||
<ClCompile Include="$(IntDir)moc_setupwizarddialog.cpp" />
|
||||
<ClCompile Include="logwindow.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_logwindow.cpp" />
|
||||
<ClCompile Include="netplaydialogs.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_netplaydialogs.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="qtutils.h" />
|
||||
|
@ -158,6 +160,7 @@
|
|||
<QtMoc Include="colorpickerbutton.h" />
|
||||
<QtMoc Include="setupwizarddialog.h" />
|
||||
<QtMoc Include="logwindow.h" />
|
||||
<QtMoc Include="netplaydialogs.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtUi Include="consolesettingswidget.ui" />
|
||||
|
@ -199,6 +202,8 @@
|
|||
<QtUi Include="coverdownloaddialog.ui" />
|
||||
<QtUi Include="controllerledsettingsdialog.ui" />
|
||||
<QtUi Include="setupwizarddialog.ui" />
|
||||
<QtUi Include="netplaycreatesessiondialog.ui" />
|
||||
<QtUi Include="netplayjoinsessiondialog.ui" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="qt5.natvis" />
|
||||
|
@ -266,4 +271,4 @@
|
|||
<Filter>translations</Filter>
|
||||
</QtTs>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "generalsettingswidget.h"
|
||||
#include "logwindow.h"
|
||||
#include "memorycardeditordialog.h"
|
||||
#include "netplaydialogs.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include "settingsdialog.h"
|
||||
|
@ -84,6 +85,7 @@ static bool s_use_central_widget = false;
|
|||
// UI thread VM validity.
|
||||
static bool s_system_valid = false;
|
||||
static bool s_system_paused = false;
|
||||
static bool s_netplay_active = false;
|
||||
static QString s_current_game_title;
|
||||
static QString s_current_game_serial;
|
||||
static QString s_current_game_path;
|
||||
|
@ -654,6 +656,18 @@ void MainWindow::onApplicationStateChanged(Qt::ApplicationState state)
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::onNetplaySessionOpened(bool is_host)
|
||||
{
|
||||
s_netplay_active = true;
|
||||
updateEmulationActions(false, s_system_valid, false);
|
||||
}
|
||||
|
||||
void MainWindow::onNetplaySessionClosed()
|
||||
{
|
||||
s_netplay_active = false;
|
||||
updateEmulationActions(false, s_system_valid, false);
|
||||
}
|
||||
|
||||
void MainWindow::onStartFileActionTriggered()
|
||||
{
|
||||
QString filename = QDir::toNativeSeparators(
|
||||
|
@ -1671,6 +1685,8 @@ void MainWindow::setupAdditionalUi()
|
|||
|
||||
void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevos_challenge_mode)
|
||||
{
|
||||
const bool netplay_active = s_netplay_active;
|
||||
|
||||
m_ui.actionStartFile->setDisabled(starting || running);
|
||||
m_ui.actionStartDisc->setDisabled(starting || running);
|
||||
m_ui.actionStartBios->setDisabled(starting || running);
|
||||
|
@ -1680,25 +1696,30 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevo
|
|||
|
||||
m_ui.actionPowerOff->setDisabled(starting || !running);
|
||||
m_ui.actionPowerOffWithoutSaving->setDisabled(starting || !running);
|
||||
m_ui.actionReset->setDisabled(starting || !running);
|
||||
m_ui.actionPause->setDisabled(starting || !running);
|
||||
m_ui.actionChangeDisc->setDisabled(starting || !running);
|
||||
m_ui.actionCheats->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
m_ui.actionReset->setDisabled(starting || !running || netplay_active);
|
||||
m_ui.actionPause->setDisabled(starting || !running || netplay_active);
|
||||
m_ui.actionChangeDisc->setDisabled(starting || !running || netplay_active);
|
||||
m_ui.actionCheats->setDisabled(starting || !running || cheevos_challenge_mode || netplay_active);
|
||||
m_ui.actionScreenshot->setDisabled(starting || !running);
|
||||
m_ui.menuChangeDisc->setDisabled(starting || !running);
|
||||
m_ui.menuCheats->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
m_ui.actionCheatManager->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
m_ui.actionCPUDebugger->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
m_ui.menuChangeDisc->setDisabled(starting || !running || netplay_active);
|
||||
m_ui.menuCheats->setDisabled(starting || !running || cheevos_challenge_mode || netplay_active);
|
||||
m_ui.actionCheatManager->setDisabled(starting || !running || cheevos_challenge_mode || netplay_active);
|
||||
m_ui.actionCPUDebugger->setDisabled(starting || !running || cheevos_challenge_mode || netplay_active);
|
||||
m_ui.actionDumpRAM->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
m_ui.actionDumpVRAM->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
m_ui.actionDumpSPURAM->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
|
||||
m_ui.actionSaveState->setDisabled(starting || !running);
|
||||
m_ui.menuSaveState->setDisabled(starting || !running);
|
||||
m_ui.actionLoadState->setDisabled(netplay_active);
|
||||
m_ui.menuLoadState->setDisabled(netplay_active);
|
||||
m_ui.actionSaveState->setDisabled(starting || !running || netplay_active);
|
||||
m_ui.menuSaveState->setDisabled(starting || !running || netplay_active);
|
||||
m_ui.menuWindowSize->setDisabled(starting || !running);
|
||||
|
||||
m_ui.actionViewGameProperties->setDisabled(starting || !running);
|
||||
|
||||
m_ui.actionCreateNetplaySession->setDisabled(!running || cheevos_challenge_mode || netplay_active);
|
||||
m_ui.actionJoinNetplaySession->setDisabled(cheevos_challenge_mode || netplay_active);
|
||||
|
||||
if (starting || running)
|
||||
{
|
||||
if (!m_ui.toolBar->actions().contains(m_ui.actionPowerOff))
|
||||
|
@ -2016,6 +2037,8 @@ void MainWindow::connectSignals()
|
|||
connect(g_emu_thread, &EmuThread::achievementsLoginSucceeded, this, &MainWindow::onAchievementsLoginSucceeded);
|
||||
connect(g_emu_thread, &EmuThread::achievementsChallengeModeChanged, this,
|
||||
&MainWindow::onAchievementsChallengeModeChanged);
|
||||
connect(g_emu_thread, &EmuThread::netplaySessionOpened, this, &MainWindow::onNetplaySessionOpened);
|
||||
connect(g_emu_thread, &EmuThread::netplaySessionClosed, this, &MainWindow::onNetplaySessionClosed);
|
||||
|
||||
// These need to be queued connections to stop crashing due to menus opening/closing and switching focus.
|
||||
connect(m_game_list_widget, &GameListWidget::refreshProgress, this, &MainWindow::onGameListRefreshProgress);
|
||||
|
@ -2089,6 +2112,10 @@ void MainWindow::connectSignals()
|
|||
}
|
||||
|
||||
updateMenuSelectedTheme();
|
||||
|
||||
// Netplay UI , TODO
|
||||
connect(m_ui.actionCreateNetplaySession, &QAction::triggered, this, &MainWindow::onCreateNetplaySessionClicked);
|
||||
connect(m_ui.actionJoinNetplaySession, &QAction::triggered, this, &MainWindow::onJoinNetplaySessionClicked);
|
||||
}
|
||||
|
||||
void MainWindow::setTheme(const QString& theme)
|
||||
|
@ -2550,6 +2577,7 @@ bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_sav
|
|||
|
||||
// If we don't have a serial, we can't save state.
|
||||
allow_save_to_state &= !s_current_game_serial.isEmpty();
|
||||
allow_save_to_state &= !s_netplay_active;
|
||||
save_state &= allow_save_to_state;
|
||||
|
||||
// Only confirm on UI thread because we need to display a msgbox.
|
||||
|
@ -2793,6 +2821,18 @@ void MainWindow::onCPUDebuggerClosed()
|
|||
m_debugger_window = nullptr;
|
||||
}
|
||||
|
||||
void MainWindow::onCreateNetplaySessionClicked()
|
||||
{
|
||||
CreateNetplaySessionDialog dlg(this);
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void MainWindow::onJoinNetplaySessionClicked()
|
||||
{
|
||||
JoinNetplaySessionDialog dlg(this);
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void MainWindow::onToolsOpenDataDirectoryTriggered()
|
||||
{
|
||||
QtUtils::OpenURL(this, QUrl::fromLocalFile(QString::fromStdString(EmuFolders::DataRoot)));
|
||||
|
@ -2876,7 +2916,7 @@ MainWindow::SystemLock MainWindow::pauseAndLockSystem()
|
|||
#else
|
||||
const bool was_fullscreen = false;
|
||||
#endif
|
||||
const bool was_paused = !s_system_valid || s_system_paused;
|
||||
const bool was_paused = !s_system_valid || s_system_paused || s_netplay_active;
|
||||
|
||||
// We need to switch out of exclusive fullscreen before we can display our popup.
|
||||
// However, we do not want to switch back to render-to-main, the window might have generated this event.
|
||||
|
|
|
@ -133,6 +133,8 @@ private Q_SLOTS:
|
|||
quint32 unread_messages);
|
||||
void onAchievementsChallengeModeChanged(bool enabled);
|
||||
void onApplicationStateChanged(Qt::ApplicationState state);
|
||||
void onNetplaySessionOpened(bool is_host);
|
||||
void onNetplaySessionClosed();
|
||||
|
||||
void onStartFileActionTriggered();
|
||||
void onStartDiscActionTriggered();
|
||||
|
@ -177,6 +179,9 @@ private Q_SLOTS:
|
|||
void openCPUDebugger();
|
||||
void onCPUDebuggerClosed();
|
||||
|
||||
void onCreateNetplaySessionClicked();
|
||||
void onJoinNetplaySessionClicked();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
|
|
@ -237,8 +237,17 @@
|
|||
<addaction name="separator"/>
|
||||
<addaction name="actionOpenDataDirectory"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuNetplay">
|
||||
<property name="title">
|
||||
<string>Netplay</string>
|
||||
</property>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionCreateNetplaySession"/>
|
||||
<addaction name="actionJoinNetplaySession"/>
|
||||
</widget>
|
||||
<addaction name="menuSystem"/>
|
||||
<addaction name="menuSettings"/>
|
||||
<addaction name="menuNetplay"/>
|
||||
<addaction name="menu_View"/>
|
||||
<addaction name="menu_Tools"/>
|
||||
<addaction name="menuDebug"/>
|
||||
|
@ -976,6 +985,16 @@
|
|||
<string>Cover Downloader</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCreateNetplaySession">
|
||||
<property name="text">
|
||||
<string>Create Session...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionJoinNetplaySession">
|
||||
<property name="text">
|
||||
<string>Join Session...</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NetplayCreateSessionDialog</class>
|
||||
<widget class="QDialog" name="NetplayCreateSessionDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>496</width>
|
||||
<height>302</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string comment="Window title">Create Netplay Session</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="resources/resources.qrc">:/icons/emblem-person-blue.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="Header text">Create Netplay Session</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Select a nickname and port to open your current game session to other players via netplay. A password may optionally be supplied to restrict who can join. The traversal mode option can be enabled to allow other players to join via a host code without needing to portforward.</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Nickname:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="nickname">
|
||||
<property name="text">
|
||||
<string>Netplay Host</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>128</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="port">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>31200</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Players:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="maxPlayers">
|
||||
<property name="minimum">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>8</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="maxLength">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Input Delay:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="inputDelay"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="traversal">
|
||||
<property name="text">
|
||||
<string>Enable Traversal Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,129 @@
|
|||
// SPDX-FileCopyrightText: 2023 Connor McLaughlin <stenzek@gmail.com> and contributors.
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "netplaydialogs.h"
|
||||
#include "qthost.h"
|
||||
|
||||
#include "core/netplay.h"
|
||||
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
CreateNetplaySessionDialog::CreateNetplaySessionDialog(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
connect(m_ui.maxPlayers, &QSpinBox::valueChanged, this, &CreateNetplaySessionDialog::updateState);
|
||||
connect(m_ui.port, &QSpinBox::valueChanged, this, &CreateNetplaySessionDialog::updateState);
|
||||
connect(m_ui.inputDelay, &QSpinBox::valueChanged, this, &CreateNetplaySessionDialog::updateState);
|
||||
connect(m_ui.nickname, &QLineEdit::textChanged, this, &CreateNetplaySessionDialog::updateState);
|
||||
connect(m_ui.password, &QLineEdit::textChanged, this, &CreateNetplaySessionDialog::updateState);
|
||||
|
||||
connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this,
|
||||
&CreateNetplaySessionDialog::accept);
|
||||
connect(m_ui.buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, this,
|
||||
&CreateNetplaySessionDialog::reject);
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
CreateNetplaySessionDialog::~CreateNetplaySessionDialog() = default;
|
||||
|
||||
void CreateNetplaySessionDialog::accept()
|
||||
{
|
||||
if (!validate())
|
||||
return;
|
||||
|
||||
const int players = m_ui.maxPlayers->value();
|
||||
const int port = m_ui.port->value();
|
||||
const int inputdelay = m_ui.inputDelay->value();
|
||||
const QString& nickname = m_ui.nickname->text();
|
||||
const QString& password = m_ui.password->text();
|
||||
const bool traversal = m_ui.traversal->isChecked();
|
||||
QDialog::accept();
|
||||
|
||||
g_emu_thread->createNetplaySession(nickname.trimmed(), port, players, password, inputdelay, traversal);
|
||||
}
|
||||
|
||||
bool CreateNetplaySessionDialog::validate()
|
||||
{
|
||||
const int players = m_ui.maxPlayers->value();
|
||||
const int port = m_ui.port->value();
|
||||
const int inputdelay = m_ui.inputDelay->value();
|
||||
const QString& nickname = m_ui.nickname->text();
|
||||
return (!nickname.isEmpty() && players >= 2 && players <= Netplay::MAX_PLAYERS && port > 0 && port <= 65535 &&
|
||||
inputdelay >= 0 && inputdelay <= 10);
|
||||
}
|
||||
|
||||
void CreateNetplaySessionDialog::updateState()
|
||||
{
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(validate());
|
||||
}
|
||||
|
||||
JoinNetplaySessionDialog::JoinNetplaySessionDialog(QWidget* parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
connect(m_ui.port, &QSpinBox::valueChanged, this, &JoinNetplaySessionDialog::updateState);
|
||||
connect(m_ui.inputDelay, &QSpinBox::valueChanged, this, &JoinNetplaySessionDialog::updateState);
|
||||
connect(m_ui.inputDelayTraversal, &QSpinBox::valueChanged, this, &JoinNetplaySessionDialog::updateState);
|
||||
connect(m_ui.nickname, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
|
||||
connect(m_ui.nicknameTraversal, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
|
||||
connect(m_ui.password, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
|
||||
connect(m_ui.passwordTraversal, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
|
||||
connect(m_ui.hostname, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
|
||||
connect(m_ui.hostCode, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
|
||||
connect(m_ui.tabConnectMode, &QTabWidget::currentChanged, this, &JoinNetplaySessionDialog::updateState);
|
||||
|
||||
connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this,
|
||||
&JoinNetplaySessionDialog::accept);
|
||||
connect(m_ui.buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, this,
|
||||
&JoinNetplaySessionDialog::reject);
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
JoinNetplaySessionDialog::~JoinNetplaySessionDialog() = default;
|
||||
|
||||
void JoinNetplaySessionDialog::accept()
|
||||
{
|
||||
const bool direct_mode = m_ui.tabTraversal->isHidden();
|
||||
const bool valid = direct_mode ? validate() : validateTraversal();
|
||||
if (!valid)
|
||||
return;
|
||||
|
||||
int port = m_ui.port->value();
|
||||
int inputdelay = direct_mode ? m_ui.inputDelay->value() : m_ui.inputDelayTraversal->value();
|
||||
const QString& nickname = direct_mode ? m_ui.nickname->text() : m_ui.nicknameTraversal->text();
|
||||
const QString& password = direct_mode ? m_ui.password->text() : m_ui.passwordTraversal->text();
|
||||
const QString& hostname = m_ui.hostname->text();
|
||||
const QString& hostcode = m_ui.hostCode->text();
|
||||
const bool spectating = m_ui.spectating->isChecked();
|
||||
QDialog::accept();
|
||||
|
||||
g_emu_thread->joinNetplaySession(nickname.trimmed(), hostname.trimmed(), port, password, spectating, inputdelay,
|
||||
!direct_mode, hostcode.trimmed());
|
||||
}
|
||||
|
||||
bool JoinNetplaySessionDialog::validate()
|
||||
{
|
||||
const int port = m_ui.port->value();
|
||||
const int inputdelay = m_ui.inputDelay->value();
|
||||
const QString& nickname = m_ui.nickname->text();
|
||||
const QString& hostname = m_ui.hostname->text();
|
||||
return (!nickname.isEmpty() && !hostname.isEmpty() && port > 0 && port <= 65535 && inputdelay >= 0 &&
|
||||
inputdelay <= 10);
|
||||
}
|
||||
|
||||
bool JoinNetplaySessionDialog::validateTraversal()
|
||||
{
|
||||
const int inputdelay = m_ui.inputDelayTraversal->value();
|
||||
const QString& nickname = m_ui.nicknameTraversal->text();
|
||||
const QString& hostcode = m_ui.hostCode->text();
|
||||
return (!nickname.isEmpty() && !hostcode.isEmpty() && inputdelay >= 0 && inputdelay <= 10);
|
||||
}
|
||||
|
||||
void JoinNetplaySessionDialog::updateState()
|
||||
{
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Ok)
|
||||
->setEnabled(m_ui.tabTraversal->isHidden() ? validate() : validateTraversal());
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
// SPDX-FileCopyrightText: 2023 Connor McLaughlin <stenzek@gmail.com> and contributors.
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui_netplaycreatesessiondialog.h"
|
||||
#include "ui_netplayjoinsessiondialog.h"
|
||||
|
||||
#include <QtWidgets/QDialog>
|
||||
|
||||
class CreateNetplaySessionDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CreateNetplaySessionDialog(QWidget* parent);
|
||||
~CreateNetplaySessionDialog();
|
||||
|
||||
public Q_SLOTS:
|
||||
void accept() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void updateState();
|
||||
|
||||
private:
|
||||
bool validate();
|
||||
|
||||
Ui::NetplayCreateSessionDialog m_ui;
|
||||
};
|
||||
|
||||
class JoinNetplaySessionDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
JoinNetplaySessionDialog(QWidget* parent);
|
||||
~JoinNetplaySessionDialog();
|
||||
|
||||
public Q_SLOTS:
|
||||
void accept() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void updateState();
|
||||
|
||||
private:
|
||||
bool validate();
|
||||
bool validateTraversal();
|
||||
|
||||
private:
|
||||
Ui::NetplayJoinSessionDialog m_ui;
|
||||
};
|
|
@ -0,0 +1,276 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NetplayJoinSessionDialog</class>
|
||||
<widget class="QDialog" name="NetplayJoinSessionDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>480</width>
|
||||
<height>308</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string comment="Window title">Create Netplay Session</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="resources/resources.qrc">:/icons/emblem-person-blue.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="Header text">Join Netplay Session</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Choose a nickname and enter the address/port of the netplay session you wish to join.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabConnectMode">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>190</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tabDirect">
|
||||
<attribute name="title">
|
||||
<string>Direct Mode</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Nickname:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="nickname">
|
||||
<property name="text">
|
||||
<string>Netplay Peer</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>128</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Input Delay:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="inputDelay"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Hostname:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="hostname">
|
||||
<property name="text">
|
||||
<string>localhost</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="port">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>31200</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="maxLength">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabTraversal">
|
||||
<attribute name="title">
|
||||
<string>Traversal Mode</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Nickname:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="nicknameTraversal">
|
||||
<property name="text">
|
||||
<string>Netplay Peer</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>128</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="inputDelayTraversal"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="passwordTraversal">
|
||||
<property name="maxLength">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Host Code:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="hostCode"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Input Delay:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="spectating">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable Spectator Mode</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -20,6 +20,7 @@
|
|||
#include "core/host.h"
|
||||
#include "core/imgui_overlays.h"
|
||||
#include "core/memory_card.h"
|
||||
#include "core/netplay.h"
|
||||
#include "core/spu.h"
|
||||
#include "core/system.h"
|
||||
|
||||
|
@ -106,6 +107,9 @@ static bool s_start_fullscreen_ui = false;
|
|||
static bool s_start_fullscreen_ui_fullscreen = false;
|
||||
static bool s_run_setup_wizard = false;
|
||||
|
||||
// TODO: REMOVE ME
|
||||
static int s_netplay_test = -1;
|
||||
|
||||
EmuThread* g_emu_thread;
|
||||
GDBServer* g_gdb_server;
|
||||
|
||||
|
@ -975,6 +979,62 @@ void EmuThread::updatePostProcessingSettings()
|
|||
PostProcessing::UpdateSettings();
|
||||
}
|
||||
|
||||
void EmuThread::createNetplaySession(const QString& nickname, qint32 port, qint32 max_players, const QString& password, int inputdelay, bool traversal)
|
||||
{
|
||||
if (!isOnThread())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "createNetplaySession", Qt::QueuedConnection, Q_ARG(const QString&, nickname),
|
||||
Q_ARG(qint32, port), Q_ARG(qint32, max_players), Q_ARG(const QString&, password),
|
||||
Q_ARG(int, inputdelay), Q_ARG(bool, traversal));
|
||||
return;
|
||||
}
|
||||
|
||||
// need a valid system to make a session
|
||||
if (!System::IsValid())
|
||||
return;
|
||||
|
||||
if (!Netplay::CreateSession(nickname.toStdString(), port, max_players, password.toStdString(), inputdelay, traversal))
|
||||
{
|
||||
errorReported(tr("Netplay Error"), tr("Failed to create netplay session. The log may contain more information."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void EmuThread::joinNetplaySession(const QString& nickname, const QString& hostname, qint32 port,
|
||||
const QString& password, bool spectating, int inputdelay, bool traversal,
|
||||
const QString& hostcode)
|
||||
{
|
||||
if (!isOnThread())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "joinNetplaySession", Qt::QueuedConnection, Q_ARG(const QString&, nickname),
|
||||
Q_ARG(const QString&, hostname), Q_ARG(qint32, port), Q_ARG(const QString&, password),
|
||||
Q_ARG(bool, spectating), Q_ARG(int, inputdelay), Q_ARG(bool, traversal),
|
||||
Q_ARG(const QString&, hostcode));
|
||||
return;
|
||||
}
|
||||
|
||||
setInitialState(std::nullopt);
|
||||
|
||||
if (!Netplay::JoinSession(nickname.toStdString(), hostname.toStdString(), port, password.toStdString(), spectating, inputdelay, traversal, hostcode.toStdString()))
|
||||
{
|
||||
errorReported(tr("Netplay Error"), tr("Failed to join netplay session. The log may contain more information."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Exit the event loop, we'll take it from here.
|
||||
g_emu_thread->wakeThread();
|
||||
}
|
||||
|
||||
void Host::OnNetplaySessionOpened(bool is_host)
|
||||
{
|
||||
emit g_emu_thread->netplaySessionOpened(is_host);
|
||||
}
|
||||
|
||||
void Host::OnNetplaySessionClosed()
|
||||
{
|
||||
emit g_emu_thread->netplaySessionClosed();
|
||||
}
|
||||
|
||||
void EmuThread::clearInputBindStateFromSource(InputBindingKey key)
|
||||
{
|
||||
if (!isOnThread())
|
||||
|
@ -1327,11 +1387,16 @@ void EmuThread::run()
|
|||
// bind buttons/axises
|
||||
createBackgroundControllerPollTimer();
|
||||
startBackgroundControllerPollTimer();
|
||||
setInitialState(std::nullopt);
|
||||
|
||||
// main loop
|
||||
while (!m_shutdown_flag)
|
||||
{
|
||||
if (System::IsRunning())
|
||||
if (Netplay::IsActive())
|
||||
{
|
||||
Netplay::ExecuteNetplay();
|
||||
}
|
||||
else if (System::IsRunning())
|
||||
{
|
||||
System::Execute();
|
||||
}
|
||||
|
@ -1906,7 +1971,12 @@ bool QtHost::ParseCommandLineParametersAndInitializeConfig(QApplication& app,
|
|||
|
||||
continue;
|
||||
}
|
||||
#ifdef ENABLE_RAINTEGRATION
|
||||
else if (CHECK_ARG_PARAM("-netplay"))
|
||||
{
|
||||
s_netplay_test = StringUtil::FromChars<int>(args[++i].toStdString()).value_or(0);
|
||||
continue;
|
||||
}
|
||||
#ifdef WITH_RAINTEGRATION
|
||||
else if (CHECK_ARG("-raintegration"))
|
||||
{
|
||||
Achievements::SwitchToRAIntegration();
|
||||
|
@ -2070,6 +2140,31 @@ int main(int argc, char* argv[])
|
|||
else if (!s_nogui_mode)
|
||||
main_window->startupUpdateCheck();
|
||||
|
||||
if (s_netplay_test >= 0)
|
||||
{
|
||||
Host::RunOnCPUThread([]() {
|
||||
const bool first = (s_netplay_test == 0);
|
||||
QtHost::RunOnUIThread([first]() { g_main_window->move(QPoint(first ? 300 : 1400, 500)); });
|
||||
|
||||
const int port = 31200;
|
||||
const QString remote = QStringLiteral("127.0.0.1");
|
||||
std::string game = "D:\\PSX\\chd\\padtest.chd";
|
||||
const QString nickname = QStringLiteral("NICKNAME%1").arg(s_netplay_test + 1);
|
||||
if (first)
|
||||
{
|
||||
auto params = std::make_shared<SystemBootParameters>(std::move(game));
|
||||
params->override_fast_boot = true;
|
||||
params->fast_forward_to_first_frame = true;
|
||||
g_emu_thread->bootSystem(std::move(params));
|
||||
g_emu_thread->createNetplaySession(nickname, port, 2, QString(), 0, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_emu_thread->joinNetplaySession(nickname, remote, port, QString(), false, 0, false, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This doesn't return until we exit.
|
||||
result = app.exec();
|
||||
|
||||
|
|
|
@ -147,6 +147,8 @@ Q_SIGNALS:
|
|||
void achievementsRefreshed(quint32 id, const QString& game_info_string);
|
||||
void achievementsChallengeModeChanged(bool enabled);
|
||||
void cheatEnabled(quint32 index, bool enabled);
|
||||
void netplaySessionOpened(bool is_host);
|
||||
void netplaySessionClosed();
|
||||
|
||||
public Q_SLOTS:
|
||||
void setDefaultSettings(bool system = true, bool controller = true);
|
||||
|
@ -192,6 +194,10 @@ public Q_SLOTS:
|
|||
void applyCheat(quint32 index);
|
||||
void reloadPostProcessingShaders();
|
||||
void updatePostProcessingSettings();
|
||||
void createNetplaySession(const QString& nickname, qint32 port, qint32 max_players, const QString& password,
|
||||
int inputdelay, bool traversal);
|
||||
void joinNetplaySession(const QString& nickname, const QString& hostname, qint32 port, const QString& password,
|
||||
bool spectating, int inputdelay, bool traversal, const QString& hostcode);
|
||||
void clearInputBindStateFromSource(InputBindingKey key);
|
||||
|
||||
private Q_SLOTS:
|
||||
|
@ -236,6 +242,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();
|
||||
RenderAPI m_last_render_api = RenderAPI::None;
|
||||
bool m_last_hardware_renderer = false;
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
@ -714,6 +715,12 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
|
|||
if (!System::IsValid())
|
||||
return;
|
||||
|
||||
if (Netplay::IsActive())
|
||||
{
|
||||
Netplay::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