Netplay: Send session details to clients
This commit is contained in:
parent
a6a7a1613c
commit
e1e2dcd435
|
@ -376,6 +376,35 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
|
||||||
return fallback_image;
|
return fallback_image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string BIOS::FindBIOSPathWithHash(const char* directory, const Hash& hash)
|
||||||
|
{
|
||||||
|
FileSystem::FindResultsArray files;
|
||||||
|
FileSystem::FindFiles(directory, "*",
|
||||||
|
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS, &files);
|
||||||
|
|
||||||
|
std::string ret;
|
||||||
|
|
||||||
|
for (FILESYSTEM_FIND_DATA& fd : files)
|
||||||
|
{
|
||||||
|
if (fd.Size != BIOS_SIZE && fd.Size != BIOS_SIZE_PS2 && fd.Size != BIOS_SIZE_PS3)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string full_path(Path::Combine(directory, fd.FileName));
|
||||||
|
std::optional<Image> found_image = LoadImageFromFile(full_path.c_str());
|
||||||
|
if (!found_image)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const BIOS::Hash found_hash = GetImageHash(found_image.value());
|
||||||
|
if (found_hash == hash)
|
||||||
|
{
|
||||||
|
ret = std::move(full_path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> BIOS::FindBIOSImagesInDirectory(const char* directory)
|
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> BIOS::FindBIOSImagesInDirectory(const char* directory)
|
||||||
{
|
{
|
||||||
std::vector<std::pair<std::string, const ImageInfo*>> results;
|
std::vector<std::pair<std::string, const ImageInfo*>> results;
|
||||||
|
|
|
@ -81,6 +81,9 @@ std::optional<std::vector<u8>> GetBIOSImage(ConsoleRegion region);
|
||||||
/// BIOS image within 512KB and 4MB will be used.
|
/// BIOS image within 512KB and 4MB will be used.
|
||||||
std::optional<std::vector<u8>> FindBIOSImageInDirectory(ConsoleRegion region, const char* directory);
|
std::optional<std::vector<u8>> FindBIOSImageInDirectory(ConsoleRegion region, const char* directory);
|
||||||
|
|
||||||
|
/// Returns a BIOS image which matches the specified hash.
|
||||||
|
std::string FindBIOSPathWithHash(const char* directory, const BIOS::Hash& hash);
|
||||||
|
|
||||||
/// Returns a list of filenames and descriptions for BIOS images in a directory.
|
/// Returns a list of filenames and descriptions for BIOS images in a directory.
|
||||||
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> FindBIOSImagesInDirectory(const char* directory);
|
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> FindBIOSImagesInDirectory(const char* directory);
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,7 @@
|
||||||
<ClInclude Include="guncon.h" />
|
<ClInclude Include="guncon.h" />
|
||||||
<ClInclude Include="negcon.h" />
|
<ClInclude Include="negcon.h" />
|
||||||
<ClInclude Include="netplay.h" />
|
<ClInclude Include="netplay.h" />
|
||||||
|
<ClInclude Include="netplay_packets.h" />
|
||||||
<ClInclude Include="pad.h" />
|
<ClInclude Include="pad.h" />
|
||||||
<ClInclude Include="controller.h" />
|
<ClInclude Include="controller.h" />
|
||||||
<ClInclude Include="pcdrv.h" />
|
<ClInclude Include="pcdrv.h" />
|
||||||
|
|
|
@ -129,5 +129,6 @@
|
||||||
<ClInclude Include="input_types.h" />
|
<ClInclude Include="input_types.h" />
|
||||||
<ClInclude Include="pcdrv.h" />
|
<ClInclude Include="pcdrv.h" />
|
||||||
<ClInclude Include="netplay.h" />
|
<ClInclude Include="netplay.h" />
|
||||||
|
<ClInclude Include="netplay_packets.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -1,4 +1,5 @@
|
||||||
#include "netplay.h"
|
#include "netplay.h"
|
||||||
|
#include "bios.h"
|
||||||
#include "common/byte_stream.h"
|
#include "common/byte_stream.h"
|
||||||
#include "common/file_system.h"
|
#include "common/file_system.h"
|
||||||
#include "common/gpu_texture.h"
|
#include "common/gpu_texture.h"
|
||||||
|
@ -13,9 +14,11 @@
|
||||||
#include "netplay_packets.h"
|
#include "netplay_packets.h"
|
||||||
#include "pad.h"
|
#include "pad.h"
|
||||||
#include "save_state_version.h"
|
#include "save_state_version.h"
|
||||||
|
#include "settings.h"
|
||||||
#include "spu.h"
|
#include "spu.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
#include <cinttypes>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <gsl/span>
|
#include <gsl/span>
|
||||||
#include <xxhash.h>
|
#include <xxhash.h>
|
||||||
|
@ -30,6 +33,9 @@ Log_SetChannel(Netplay);
|
||||||
#include "enet/enet.h"
|
#include "enet/enet.h"
|
||||||
#include "ggponet.h"
|
#include "ggponet.h"
|
||||||
|
|
||||||
|
// TODO: We don't want a core->frontend-common dependency. I'll move GameList to core at some point...
|
||||||
|
#include "frontend-common/game_list.h"
|
||||||
|
|
||||||
namespace Netplay {
|
namespace Netplay {
|
||||||
|
|
||||||
using SaveStateBuffer = std::unique_ptr<System::MemorySaveState>;
|
using SaveStateBuffer = std::unique_ptr<System::MemorySaveState>;
|
||||||
|
@ -57,7 +63,7 @@ static void SetInputs(Input inputs[2]);
|
||||||
|
|
||||||
static void SetSettings();
|
static void SetSettings();
|
||||||
|
|
||||||
static bool CreateSystem(std::string game_path, bool hosting);
|
static bool CreateDummySystem();
|
||||||
static void CloseSessionWithError(const std::string_view& message);
|
static void CloseSessionWithError(const std::string_view& message);
|
||||||
static void RequestCloseSession(CloseSessionMessage::Reason reason);
|
static void RequestCloseSession(CloseSessionMessage::Reason reason);
|
||||||
|
|
||||||
|
@ -85,6 +91,7 @@ static void SendConnectRequest();
|
||||||
static void HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt);
|
static void HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt);
|
||||||
static void HandleControlMessage(s32 player_id, const ENetPacket* pkt);
|
static void HandleControlMessage(s32 player_id, const ENetPacket* pkt);
|
||||||
static void HandleConnectResponseMessage(s32 player_id, const ENetPacket* pkt);
|
static void HandleConnectResponseMessage(s32 player_id, const ENetPacket* pkt);
|
||||||
|
static void HandleJoinResponseMessage(s32 player_id, const ENetPacket* pkt);
|
||||||
static void HandleResetMessage(s32 player_id, const ENetPacket* pkt);
|
static void HandleResetMessage(s32 player_id, const ENetPacket* pkt);
|
||||||
static void HandleResetCompleteMessage(s32 player_id, const ENetPacket* pkt);
|
static void HandleResetCompleteMessage(s32 player_id, const ENetPacket* pkt);
|
||||||
static void HandleResumeSessionMessage(s32 player_id, const ENetPacket* pkt);
|
static void HandleResumeSessionMessage(s32 player_id, const ENetPacket* pkt);
|
||||||
|
@ -297,7 +304,7 @@ bool Netplay::Start(bool is_hosting, std::string nickname, const std::string& re
|
||||||
Log_ErrorPrintf("Can't host a netplay session without a valid VM");
|
Log_ErrorPrintf("Can't host a netplay session without a valid VM");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (!is_hosting && !CreateSystem(std::string(), false))
|
else if (!is_hosting && !CreateDummySystem())
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("Failed to create VM for joining session");
|
Log_ErrorPrintf("Failed to create VM for joining session");
|
||||||
return false;
|
return false;
|
||||||
|
@ -403,27 +410,16 @@ bool Netplay::IsActive()
|
||||||
return (s_state >= SessionState::Initializing && s_state <= SessionState::Running);
|
return (s_state >= SessionState::Initializing && s_state <= SessionState::Running);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Netplay::CreateSystem(std::string game_path, bool is_hosting)
|
bool Netplay::CreateDummySystem()
|
||||||
{
|
{
|
||||||
// close system if its already running
|
// close system if its already running
|
||||||
if (System::IsValid())
|
if (System::IsValid())
|
||||||
System::ShutdownSystem(false);
|
System::ShutdownSystem(false);
|
||||||
|
|
||||||
// fast boot the selected game and wait for the other player
|
// fast boot the selected game and wait for the other player
|
||||||
auto param = SystemBootParameters(std::move(game_path));
|
if (!System::BootSystem(SystemBootParameters()))
|
||||||
param.override_fast_boot = true;
|
|
||||||
if (!System::BootSystem(param))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (is_hosting)
|
|
||||||
{
|
|
||||||
// Fast Forward to Game Start if needed.
|
|
||||||
SPU::SetAudioOutputMuted(true);
|
|
||||||
while (System::GetInternalFrameNumber() < 2)
|
|
||||||
System::RunFrame();
|
|
||||||
SPU::SetAudioOutputMuted(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,7 +585,7 @@ void Netplay::HandleEnetEvent(const ENetEvent* event)
|
||||||
if (player_id < 0)
|
if (player_id < 0)
|
||||||
{
|
{
|
||||||
// If it's a new connection, we need to handle connection request messages.
|
// If it's a new connection, we need to handle connection request messages.
|
||||||
if (event->channelID == ENET_CHANNEL_CONTROL)
|
if (event->channelID == ENET_CHANNEL_CONTROL && IsHost())
|
||||||
HandleMessageFromNewPeer(event->peer, event->packet);
|
HandleMessageFromNewPeer(event->peer, event->packet);
|
||||||
enet_packet_destroy(event->packet);
|
enet_packet_destroy(event->packet);
|
||||||
return;
|
return;
|
||||||
|
@ -786,6 +782,10 @@ void Netplay::HandleControlMessage(s32 player_id, const ENetPacket* pkt)
|
||||||
HandleConnectResponseMessage(player_id, pkt);
|
HandleConnectResponseMessage(player_id, pkt);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ControlMessage::JoinResponse:
|
||||||
|
HandleJoinResponseMessage(player_id, pkt);
|
||||||
|
break;
|
||||||
|
|
||||||
case ControlMessage::Reset:
|
case ControlMessage::Reset:
|
||||||
HandleResetMessage(player_id, pkt);
|
HandleResetMessage(player_id, pkt);
|
||||||
break;
|
break;
|
||||||
|
@ -826,15 +826,125 @@ void Netplay::HandleControlMessage(s32 player_id, const ENetPacket* pkt)
|
||||||
|
|
||||||
void Netplay::HandlePeerConnectionAsHost(ENetPeer* peer)
|
void Netplay::HandlePeerConnectionAsHost(ENetPeer* peer)
|
||||||
{
|
{
|
||||||
// don't do anything until they send a connect request
|
|
||||||
// TODO: we might want to put an idle timeout here...
|
// TODO: we might want to put an idle timeout here...
|
||||||
Log_InfoPrintf(fmt::format("New peer connection from {}", PeerAddressString(peer)).c_str());
|
Log_InfoPrintf(fmt::format("New peer connection from {}", PeerAddressString(peer)).c_str());
|
||||||
|
|
||||||
|
// send them the session details
|
||||||
|
const std::string& game_title = System::GetGameTitle();
|
||||||
|
const std::string& game_serial = System::GetGameSerial();
|
||||||
|
auto pkt = NewControlPacket<ConnectResponseMessage>(
|
||||||
|
sizeof(ConnectResponseMessage) + static_cast<u32>(game_serial.length()) + static_cast<u32>(game_title.length()));
|
||||||
|
pkt->num_players = s_num_players;
|
||||||
|
pkt->max_players = MAX_PLAYERS;
|
||||||
|
pkt->console_region = System::GetRegion();
|
||||||
|
pkt->game_title_length = static_cast<u32>(game_title.length());
|
||||||
|
pkt->game_serial_length = static_cast<u32>(game_serial.length());
|
||||||
|
pkt->game_hash = System::GetGameHash();
|
||||||
|
pkt->bios_hash = System::GetBIOSHash();
|
||||||
|
pkt->was_fast_booted = System::WasFastBooted();
|
||||||
|
std::memcpy(pkt.pkt->data + sizeof(ConnectResponseMessage), game_serial.c_str(), pkt->game_serial_length);
|
||||||
|
std::memcpy(pkt.pkt->data + sizeof(ConnectResponseMessage) + pkt->game_serial_length, game_title.c_str(),
|
||||||
|
pkt->game_title_length);
|
||||||
|
SendControlPacket(peer, pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::HandleConnectResponseMessage(s32 player_id, const ENetPacket* pkt)
|
||||||
|
{
|
||||||
|
const ConnectResponseMessage* msg = CheckReceivedPacket<ConnectResponseMessage>(-1, pkt);
|
||||||
|
if (!msg || player_id != s_host_player_id)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Received unexpected connect response from player %d", player_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore these messages when reconnecting
|
||||||
|
if (s_state != SessionState::Connecting)
|
||||||
|
{
|
||||||
|
Log_DevPrintf("Ignoring connect response because we're not initially connecting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!msg->Validate())
|
||||||
|
{
|
||||||
|
CloseSessionWithError(Host::TranslateStdString("Netplay", "Cannot join session: Invalid details recieved."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log_InfoPrintf("Received session details from host: ");
|
||||||
|
Log_InfoPrintf(" Console Region: %s", Settings::GetConsoleRegionDisplayName(msg->console_region));
|
||||||
|
Log_InfoPrintf(" BIOS Hash: %s%s", msg->bios_hash.ToString().c_str(), msg->was_fast_booted ? " (fast booted)" : "");
|
||||||
|
Log_InfoPrintf(" Game Serial: %.*s", msg->game_serial_length, msg->GetGameSerial().data());
|
||||||
|
Log_InfoPrintf(" Game Title: %.*s", msg->game_title_length, msg->GetGameTitle().data());
|
||||||
|
Log_InfoPrintf(" Game Hash: %" PRIX64, msg->game_hash);
|
||||||
|
|
||||||
|
// Find a matching BIOS.
|
||||||
|
const std::string bios_path = BIOS::FindBIOSPathWithHash(EmuFolders::Bios.c_str(), msg->bios_hash);
|
||||||
|
if (bios_path.empty())
|
||||||
|
{
|
||||||
|
CloseSessionWithError(fmt::format(
|
||||||
|
Host::TranslateString("Netplay", "Cannot join session: Unable to find BIOS with hash {}.").GetCharArray(),
|
||||||
|
msg->bios_hash.ToString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the matching game.
|
||||||
|
const GameList::Entry* entry = GameList::GetEntryBySerialAndHash(msg->GetGameSerial(), msg->game_hash);
|
||||||
|
if (!entry)
|
||||||
|
{
|
||||||
|
CloseSessionWithError(fmt::format(
|
||||||
|
Host::TranslateString("Netplay", "Cannot join session: Unable to find game \"{}\".\nSerial: {}\nHash: {}")
|
||||||
|
.GetCharArray(),
|
||||||
|
msg->GetGameTitle(), msg->GetGameSerial(), System::GetGameHashId(msg->game_hash)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log_InfoPrintf("Found matching BIOS: %s", bios_path.c_str());
|
||||||
|
Log_InfoPrintf("Found matching game: %s", entry->path.c_str());
|
||||||
|
|
||||||
|
// Reboot created system with host details.
|
||||||
|
if (!System::ReinitializeSystem(msg->console_region, bios_path.c_str(), entry->path.c_str(), msg->was_fast_booted))
|
||||||
|
{
|
||||||
|
CloseSessionWithError(Host::TranslateStdString("Netplay", "Cannot join session: Failed to reinitialize system."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're ready to go, send the connection request.
|
||||||
|
SendConnectRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::SendConnectRequest()
|
||||||
|
{
|
||||||
|
DebugAssert(!IsHost());
|
||||||
|
|
||||||
|
Log_DevPrintf("Sending connect request to host with player id %d", s_player_id);
|
||||||
|
|
||||||
|
auto pkt = NewControlPacket<JoinRequestMessage>();
|
||||||
|
pkt->mode = JoinRequestMessage::Mode::Player;
|
||||||
|
pkt->requested_player_id = s_player_id;
|
||||||
|
std::memset(pkt->nickname, 0, sizeof(pkt->nickname));
|
||||||
|
std::memset(pkt->session_password, 0, sizeof(pkt->session_password));
|
||||||
|
StringUtil::Strlcpy(pkt->nickname, s_local_nickname, std::size(pkt->nickname));
|
||||||
|
SendControlPacket(s_peers[s_host_player_id].peer, pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::UpdateConnectingState()
|
||||||
|
{
|
||||||
|
if (s_reset_start_time.GetTimeSeconds() >= MAX_CONNECT_TIME)
|
||||||
|
{
|
||||||
|
CloseSessionWithError(Host::TranslateStdString("Netplay", "Timed out connecting to server."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// still waiting for connection to host..
|
||||||
|
PollEnet(Common::Timer::GetCurrentValue() + Common::Timer::ConvertMillisecondsToValue(16));
|
||||||
|
Host::DisplayLoadingScreen("Connecting to host...");
|
||||||
|
Host::PumpMessagesOnCPUThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Netplay::HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt)
|
void Netplay::HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt)
|
||||||
{
|
{
|
||||||
const ConnectRequestMessage* msg = CheckReceivedPacket<ConnectRequestMessage>(-1, pkt);
|
const JoinRequestMessage* msg = CheckReceivedPacket<JoinRequestMessage>(-1, pkt);
|
||||||
if (!msg || msg->header.type != ControlMessage::ConnectRequest)
|
if (!msg || msg->header.type != ControlMessage::JoinRequest)
|
||||||
{
|
{
|
||||||
Log_WarningPrintf("Received unknown packet from unknown player");
|
Log_WarningPrintf("Received unknown packet from unknown player");
|
||||||
enet_peer_reset(peer);
|
enet_peer_reset(peer);
|
||||||
|
@ -845,13 +955,13 @@ void Netplay::HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt)
|
||||||
msg->requested_player_id)
|
msg->requested_player_id)
|
||||||
.c_str());
|
.c_str());
|
||||||
|
|
||||||
PacketWrapper<ConnectResponseMessage> response = NewControlPacket<ConnectResponseMessage>();
|
PacketWrapper<JoinResponseMessage> response = NewControlPacket<JoinResponseMessage>();
|
||||||
response->player_id = -1;
|
response->player_id = -1;
|
||||||
|
|
||||||
// TODO: Spectators shouldn't get assigned a real player ID, they should go into a separate peer list.
|
// TODO: Spectators shouldn't get assigned a real player ID, they should go into a separate peer list.
|
||||||
if (msg->mode != ConnectRequestMessage::Mode::Player)
|
if (msg->mode != JoinRequestMessage::Mode::Player)
|
||||||
{
|
{
|
||||||
response->result = ConnectResponseMessage::Result::SessionClosed;
|
response->result = JoinResponseMessage::Result::SessionClosed;
|
||||||
SendControlPacket(peer, response);
|
SendControlPacket(peer, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -862,7 +972,7 @@ void Netplay::HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt)
|
||||||
if (msg->requested_player_id >= 0 && IsValidPlayerId(msg->requested_player_id))
|
if (msg->requested_player_id >= 0 && IsValidPlayerId(msg->requested_player_id))
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("Player ID %d is already in use, rejecting connection.", msg->requested_player_id);
|
Log_ErrorPrintf("Player ID %d is already in use, rejecting connection.", msg->requested_player_id);
|
||||||
response->result = ConnectResponseMessage::Result::PlayerIDInUse;
|
response->result = JoinResponseMessage::Result::PlayerIDInUse;
|
||||||
SendControlPacket(peer, response);
|
SendControlPacket(peer, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -872,14 +982,14 @@ void Netplay::HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt)
|
||||||
if (new_player_id < 0)
|
if (new_player_id < 0)
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("Server full, rejecting connection.");
|
Log_ErrorPrintf("Server full, rejecting connection.");
|
||||||
response->result = ConnectResponseMessage::Result::ServerFull;
|
response->result = JoinResponseMessage::Result::ServerFull;
|
||||||
SendControlPacket(peer, response);
|
SendControlPacket(peer, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log_VerbosePrint(
|
Log_VerbosePrint(
|
||||||
fmt::format("New connection from {} assigned to player ID {}", PeerAddressString(peer), new_player_id).c_str());
|
fmt::format("New connection from {} assigned to player ID {}", PeerAddressString(peer), new_player_id).c_str());
|
||||||
response->result = ConnectResponseMessage::Result::Success;
|
response->result = JoinResponseMessage::Result::Success;
|
||||||
response->player_id = new_player_id;
|
response->player_id = new_player_id;
|
||||||
SendControlPacket(peer, response);
|
SendControlPacket(peer, response);
|
||||||
|
|
||||||
|
@ -900,19 +1010,16 @@ void Netplay::HandlePeerConnectionAsNonHost(ENetPeer* peer, s32 claimed_player_i
|
||||||
{
|
{
|
||||||
if (s_state == SessionState::Connecting)
|
if (s_state == SessionState::Connecting)
|
||||||
{
|
{
|
||||||
if (peer == s_peers[s_host_player_id].peer)
|
if (peer != s_peers[s_host_player_id].peer)
|
||||||
{
|
|
||||||
SendConnectRequest();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf(
|
Log_ErrorPrintf(
|
||||||
fmt::format("Unexpected connection from {} claiming player ID {}", PeerAddressString(peer), claimed_player_id)
|
fmt::format("Unexpected connection from {} claiming player ID {}", PeerAddressString(peer), claimed_player_id)
|
||||||
.c_str());
|
.c_str());
|
||||||
enet_peer_disconnect_now(peer, 0);
|
enet_peer_disconnect_now(peer, 0);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait for session details
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log_VerbosePrint(
|
Log_VerbosePrint(
|
||||||
|
@ -942,45 +1049,16 @@ void Netplay::HandlePeerConnectionAsNonHost(ENetPeer* peer, s32 claimed_player_i
|
||||||
s_peers[claimed_player_id].peer = peer;
|
s_peers[claimed_player_id].peer = peer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Netplay::SendConnectRequest()
|
void Netplay::HandleJoinResponseMessage(s32 player_id, const ENetPacket* pkt)
|
||||||
{
|
|
||||||
DebugAssert(!IsHost());
|
|
||||||
|
|
||||||
Log_DevPrintf("Sending connect request to host with player id %d", s_player_id);
|
|
||||||
|
|
||||||
auto pkt = NewControlPacket<ConnectRequestMessage>();
|
|
||||||
pkt->mode = ConnectRequestMessage::Mode::Player;
|
|
||||||
pkt->requested_player_id = s_player_id;
|
|
||||||
std::memset(pkt->nickname, 0, sizeof(pkt->nickname));
|
|
||||||
std::memset(pkt->session_password, 0, sizeof(pkt->session_password));
|
|
||||||
StringUtil::Strlcpy(pkt->nickname, s_local_nickname, std::size(pkt->nickname));
|
|
||||||
SendControlPacket(s_peers[s_host_player_id].peer, pkt);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Netplay::UpdateConnectingState()
|
|
||||||
{
|
|
||||||
if (s_reset_start_time.GetTimeSeconds() >= MAX_CONNECT_TIME)
|
|
||||||
{
|
|
||||||
CloseSessionWithError(Host::TranslateStdString("Netplay", "Timed out connecting to server."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// still waiting for connection to host..
|
|
||||||
PollEnet(Common::Timer::GetCurrentValue() + Common::Timer::ConvertMillisecondsToValue(16));
|
|
||||||
Host::DisplayLoadingScreen("Connecting to host...");
|
|
||||||
Host::PumpMessagesOnCPUThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Netplay::HandleConnectResponseMessage(s32 player_id, const ENetPacket* pkt)
|
|
||||||
{
|
{
|
||||||
if (s_state != SessionState::Connecting)
|
if (s_state != SessionState::Connecting)
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("Received unexpected connect response from player %d", player_id);
|
Log_ErrorPrintf("Received unexpected join response from player %d", player_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectResponseMessage* msg = CheckReceivedPacket<ConnectResponseMessage>(player_id, pkt);
|
const JoinResponseMessage* msg = CheckReceivedPacket<JoinResponseMessage>(player_id, pkt);
|
||||||
if (msg->result != ConnectResponseMessage::Result::Success)
|
if (msg->result != JoinResponseMessage::Result::Success)
|
||||||
{
|
{
|
||||||
CloseSessionWithError(
|
CloseSessionWithError(
|
||||||
fmt::format("Connection rejected by server with error code {}", static_cast<u32>(msg->result)));
|
fmt::format("Connection rejected by server with error code {}", static_cast<u32>(msg->result)));
|
||||||
|
@ -1025,7 +1103,7 @@ void Netplay::Reset()
|
||||||
// TODO: This save state has the bloody path to the disc in it. We need a new save state format.
|
// TODO: This save state has the bloody path to the disc in it. We need a new save state format.
|
||||||
// We also want to use maximum compression.
|
// We also want to use maximum compression.
|
||||||
GrowableMemoryByteStream state(nullptr, System::MAX_SAVE_STATE_SIZE);
|
GrowableMemoryByteStream state(nullptr, System::MAX_SAVE_STATE_SIZE);
|
||||||
if (!System::SaveStateToStream(&state, 0, SAVE_STATE_HEADER::COMPRESSION_TYPE_ZSTD))
|
if (!System::SaveStateToStream(&state, 0, SAVE_STATE_HEADER::COMPRESSION_TYPE_ZSTD, true))
|
||||||
Panic("Failed to save state...");
|
Panic("Failed to save state...");
|
||||||
|
|
||||||
const u32 state_data_size = static_cast<u32>(state.GetPosition());
|
const u32 state_data_size = static_cast<u32>(state.GetPosition());
|
||||||
|
@ -1086,7 +1164,7 @@ void Netplay::Reset()
|
||||||
// having a different number of cycles.
|
// having a different number of cycles.
|
||||||
// CPU::CodeCache::Flush();
|
// CPU::CodeCache::Flush();
|
||||||
state.SeekAbsolute(0);
|
state.SeekAbsolute(0);
|
||||||
if (!System::LoadStateFromStream(&state, true))
|
if (!System::LoadStateFromStream(&state, true, true))
|
||||||
Panic("Failed to reload host state");
|
Panic("Failed to reload host state");
|
||||||
|
|
||||||
s_state = SessionState::Resetting;
|
s_state = SessionState::Resetting;
|
||||||
|
@ -1177,7 +1255,7 @@ void Netplay::HandleResetMessage(s32 player_id, const ENetPacket* pkt)
|
||||||
// Load state from packet.
|
// Load state from packet.
|
||||||
Log_VerbosePrintf("Loading state from host");
|
Log_VerbosePrintf("Loading state from host");
|
||||||
ReadOnlyMemoryByteStream stream(pkt->data + sizeof(ResetMessage), msg->state_data_size);
|
ReadOnlyMemoryByteStream stream(pkt->data + sizeof(ResetMessage), msg->state_data_size);
|
||||||
if (!System::LoadStateFromStream(&stream, true))
|
if (!System::LoadStateFromStream(&stream, true, true))
|
||||||
Panic("Failed to load state from host");
|
Panic("Failed to load state from host");
|
||||||
|
|
||||||
s_state = SessionState::Resetting;
|
s_state = SessionState::Resetting;
|
||||||
|
@ -1429,9 +1507,25 @@ void Netplay::SetSettings()
|
||||||
si.SetStringValue(Controller::GetSettingsSection(i).c_str(), "Type",
|
si.SetStringValue(Controller::GetSettingsSection(i).c_str(), "Type",
|
||||||
Settings::GetControllerTypeName(ControllerType::DigitalController));
|
Settings::GetControllerTypeName(ControllerType::DigitalController));
|
||||||
}
|
}
|
||||||
|
for (u32 i = MAX_PLAYERS; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||||
|
{
|
||||||
|
si.SetStringValue(Controller::GetSettingsSection(i).c_str(), "Type",
|
||||||
|
Settings::GetControllerTypeName(ControllerType::None));
|
||||||
|
}
|
||||||
|
// We want all players to have the same memory card contents.
|
||||||
|
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||||
|
{
|
||||||
|
si.SetStringValue("MemoryCards", fmt::format("Card{}Type", i + 1).c_str(),
|
||||||
|
Settings::GetMemoryCardTypeName((i == 0) ? MemoryCardType::NonPersistent : MemoryCardType::None));
|
||||||
|
}
|
||||||
|
|
||||||
// si.SetStringValue("CPU", "ExecutionMode", "Interpreter");
|
// si.SetStringValue("CPU", "ExecutionMode", "Interpreter");
|
||||||
|
|
||||||
|
// BIOS patching must be the same.
|
||||||
|
si.SetBoolValue("BIOS", "PatchTTYEnable", false);
|
||||||
|
si.SetBoolValue("BIOS", "PatchFastBoot", true);
|
||||||
|
si.SetBoolValue("CDROM", "LoadImagePatches", false);
|
||||||
|
|
||||||
// No runahead or rewind, that'd be a disaster.
|
// No runahead or rewind, that'd be a disaster.
|
||||||
si.SetIntValue("Main", "RunaheadFrameCount", 0);
|
si.SetIntValue("Main", "RunaheadFrameCount", 0);
|
||||||
si.SetBoolValue("Main", "RewindEnable", false);
|
si.SetBoolValue("Main", "RewindEnable", false);
|
||||||
|
@ -1642,30 +1736,14 @@ void Netplay::SetInputs(Netplay::Input inputs[2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Netplay::TestNetplaySession(s32 local_handle, u16 local_port, const std::string& remote_addr, u16 remote_port,
|
|
||||||
s32 input_delay, std::string game_path)
|
|
||||||
{
|
|
||||||
const bool is_hosting = (local_handle == 1);
|
|
||||||
if (!CreateSystem(std::move(game_path), is_hosting))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to create system.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create session
|
|
||||||
std::string nickname = fmt::format("NICKNAME{}", local_handle);
|
|
||||||
if (!Netplay::Start(is_hosting, std::move(nickname), remote_addr, is_hosting ? local_port : remote_port, input_delay))
|
|
||||||
{
|
|
||||||
// this'll call back to us to shut everything netplay-related down
|
|
||||||
Log_ErrorPrint("Failed to Create Netplay Session!");
|
|
||||||
System::ShutdownSystem(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std::string password)
|
bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std::string password)
|
||||||
{
|
{
|
||||||
// TODO: Password
|
// TODO: Password
|
||||||
|
|
||||||
|
// TODO: This is going to blow away our memory cards, because for sync purposes we want all clients
|
||||||
|
// to have the same data, and we don't want to trash their local memcards. We should therefore load
|
||||||
|
// the memory cards for this game (based on game/global settings), and copy that to the temp card.
|
||||||
|
|
||||||
const s32 input_delay = 1;
|
const s32 input_delay = 1;
|
||||||
|
|
||||||
if (!Netplay::Start(true, std::move(nickname), std::string(), port, input_delay))
|
if (!Netplay::Start(true, std::move(nickname), std::string(), port, input_delay))
|
||||||
|
@ -1673,13 +1751,11 @@ bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std
|
||||||
CloseSession();
|
CloseSession();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (IsHost())
|
|
||||||
{
|
// Load savestate if available and only when you are the host.
|
||||||
// Load savestate if available and only when you are the host.
|
// the other peers will get state from the host
|
||||||
// the other peers will get state from the host
|
auto save_path = fmt::format("{}\\netplay\\{}.sav", EmuFolders::SaveStates, System::GetGameSerial());
|
||||||
auto save_path = fmt::format("{}\\netplay\\{}.sav", EmuFolders::SaveStates, System::GetGameSerial());
|
System::LoadState(save_path.c_str());
|
||||||
System::LoadState(save_path.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,6 @@ enum : u8
|
||||||
NUM_ENET_CHANNELS,
|
NUM_ENET_CHANNELS,
|
||||||
};
|
};
|
||||||
|
|
||||||
void TestNetplaySession(s32 local_handle, u16 local_port, const std::string& remote_addr, u16 remote_port,
|
|
||||||
s32 input_delay, std::string game_path);
|
|
||||||
bool CreateSession(std::string nickname, s32 port, s32 max_players, std::string password);
|
bool CreateSession(std::string nickname, s32 port, s32 max_players, std::string password);
|
||||||
bool JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password);
|
bool JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "bios.h"
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ enum class ControlMessage : u32
|
||||||
{
|
{
|
||||||
// host->player
|
// host->player
|
||||||
ConnectResponse,
|
ConnectResponse,
|
||||||
|
JoinResponse,
|
||||||
Reset,
|
Reset,
|
||||||
ResumeSession,
|
ResumeSession,
|
||||||
PlayerJoined,
|
PlayerJoined,
|
||||||
|
@ -31,7 +33,7 @@ enum class ControlMessage : u32
|
||||||
CloseSession,
|
CloseSession,
|
||||||
|
|
||||||
// player->host
|
// player->host
|
||||||
ConnectRequest,
|
JoinRequest,
|
||||||
ResetComplete,
|
ResetComplete,
|
||||||
ResetRequest,
|
ResetRequest,
|
||||||
|
|
||||||
|
@ -53,7 +55,39 @@ struct ControlMessageHeader
|
||||||
u32 size;
|
u32 size;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ConnectRequestMessage
|
struct ConnectResponseMessage
|
||||||
|
{
|
||||||
|
ControlMessageHeader header;
|
||||||
|
|
||||||
|
s32 num_players;
|
||||||
|
s32 max_players;
|
||||||
|
u64 game_hash;
|
||||||
|
u32 game_serial_length;
|
||||||
|
u32 game_title_length;
|
||||||
|
ConsoleRegion console_region;
|
||||||
|
BIOS::Hash bios_hash;
|
||||||
|
bool was_fast_booted;
|
||||||
|
|
||||||
|
// <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>(console_region) < static_cast<unsigned>(ConsoleRegion::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
|
enum class Mode
|
||||||
{
|
{
|
||||||
|
@ -80,10 +114,10 @@ struct ConnectRequestMessage
|
||||||
return std::string_view(session_password, len);
|
return std::string_view(session_password, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ControlMessage MessageType() { return ControlMessage::ConnectRequest; }
|
static ControlMessage MessageType() { return ControlMessage::JoinRequest; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ConnectResponseMessage
|
struct JoinResponseMessage
|
||||||
{
|
{
|
||||||
enum class Result : u32
|
enum class Result : u32
|
||||||
{
|
{
|
||||||
|
@ -98,7 +132,7 @@ struct ConnectResponseMessage
|
||||||
Result result;
|
Result result;
|
||||||
s32 player_id;
|
s32 player_id;
|
||||||
|
|
||||||
static ControlMessage MessageType() { return ControlMessage::ConnectResponse; }
|
static ControlMessage MessageType() { return ControlMessage::JoinResponse; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ResetMessage
|
struct ResetMessage
|
||||||
|
|
|
@ -250,11 +250,12 @@ bool Pad::DoStateController(StateWrapper& sw, u32 i)
|
||||||
|
|
||||||
bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
|
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]);
|
bool card_present_in_state = static_cast<bool>(s_memory_cards[i]);
|
||||||
|
|
||||||
sw.Do(&card_present_in_state);
|
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(
|
Host::AddFormattedOSDMessage(
|
||||||
20.0f,
|
20.0f,
|
||||||
|
@ -269,7 +270,7 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
|
||||||
|
|
||||||
if (card_present_in_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
|
// 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
|
// since it has other useful non-data state information. Otherwise take the user's card
|
||||||
|
@ -281,7 +282,7 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sw.IsWriting())
|
if (sw.IsWriting() || force_load)
|
||||||
return true; // all done as far as writes concerned.
|
return true; // all done as far as writes concerned.
|
||||||
|
|
||||||
if (card_from_state)
|
if (card_from_state)
|
||||||
|
|
|
@ -1364,6 +1364,44 @@ bool System::BootSystem(SystemBootParameters parameters)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool System::ReinitializeSystem(ConsoleRegion region, const char* bios_path, const char* media_path, bool fast_boot)
|
||||||
|
{
|
||||||
|
std::optional<BIOS::Image> bios_image = FileSystem::ReadBinaryFile(bios_path);
|
||||||
|
if (!bios_image.has_value())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to read replacement BIOS at '%s'", bios_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!InsertMedia(media_path))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to insert media at '%s'", media_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the BIOS.
|
||||||
|
s_bios_hash = BIOS::GetImageHash(bios_image.value());
|
||||||
|
s_bios_image_info = BIOS::GetInfoForImage(bios_image.value(), s_bios_hash);
|
||||||
|
if (s_bios_image_info)
|
||||||
|
Log_InfoPrintf("Replacing BIOS: %s", s_bios_image_info->description);
|
||||||
|
else
|
||||||
|
Log_WarningPrintf("Replacing with an unknown BIOS: %s", s_bios_hash.ToString().c_str());
|
||||||
|
|
||||||
|
std::memcpy(Bus::g_bios, bios_image->data(), Bus::BIOS_SIZE);
|
||||||
|
|
||||||
|
if (s_bios_image_info && s_bios_image_info->patch_compatible)
|
||||||
|
BIOS::PatchBIOSEnableTTY(Bus::g_bios, Bus::BIOS_SIZE);
|
||||||
|
|
||||||
|
s_was_fast_booted = false;
|
||||||
|
if (s_bios_image_info && s_bios_image_info->patch_compatible && fast_boot)
|
||||||
|
{
|
||||||
|
BIOS::PatchBIOSFastBoot(Bus::g_bios, Bus::BIOS_SIZE);
|
||||||
|
s_was_fast_booted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool System::Initialize(bool force_software_renderer)
|
bool System::Initialize(bool force_software_renderer)
|
||||||
{
|
{
|
||||||
g_ticks_per_second = ScaleTicksToOverclock(MASTER_CLOCK);
|
g_ticks_per_second = ScaleTicksToOverclock(MASTER_CLOCK);
|
||||||
|
@ -1942,7 +1980,7 @@ std::string System::GetMediaPathFromSaveState(const char* path)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool System::LoadStateFromStream(ByteStream* state, bool update_display)
|
bool System::LoadStateFromStream(ByteStream* state, bool update_display, bool ignore_media)
|
||||||
{
|
{
|
||||||
Assert(IsValid());
|
Assert(IsValid());
|
||||||
|
|
||||||
|
@ -1971,90 +2009,93 @@ bool System::LoadStateFromStream(ByteStream* state, bool update_display)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Common::Error error;
|
if (!ignore_media)
|
||||||
std::string media_filename;
|
|
||||||
std::unique_ptr<CDImage> media;
|
|
||||||
if (header.media_filename_length > 0)
|
|
||||||
{
|
{
|
||||||
media_filename.resize(header.media_filename_length);
|
Common::Error error;
|
||||||
if (!state->SeekAbsolute(header.offset_to_media_filename) ||
|
std::string media_filename;
|
||||||
!state->Read2(media_filename.data(), header.media_filename_length))
|
std::unique_ptr<CDImage> media;
|
||||||
|
if (header.media_filename_length > 0)
|
||||||
{
|
{
|
||||||
return false;
|
media_filename.resize(header.media_filename_length);
|
||||||
}
|
if (!state->SeekAbsolute(header.offset_to_media_filename) ||
|
||||||
|
!state->Read2(media_filename.data(), header.media_filename_length))
|
||||||
std::unique_ptr<CDImage> old_media = CDROM::RemoveMedia(false);
|
|
||||||
if (old_media && old_media->GetFileName() == media_filename)
|
|
||||||
{
|
|
||||||
Log_InfoPrintf("Re-using same media '%s'", media_filename.c_str());
|
|
||||||
media = std::move(old_media);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
media = CDImage::Open(media_filename.c_str(), g_settings.cdrom_load_image_patches, &error);
|
|
||||||
if (!media)
|
|
||||||
{
|
{
|
||||||
if (old_media)
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<CDImage> old_media = CDROM::RemoveMedia(false);
|
||||||
|
if (old_media && old_media->GetFileName() == media_filename)
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Re-using same media '%s'", media_filename.c_str());
|
||||||
|
media = std::move(old_media);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
media = CDImage::Open(media_filename.c_str(), g_settings.cdrom_load_image_patches, &error);
|
||||||
|
if (!media)
|
||||||
{
|
{
|
||||||
Host::AddFormattedOSDMessage(
|
if (old_media)
|
||||||
30.0f,
|
{
|
||||||
Host::TranslateString("OSDMessage", "Failed to open CD image from save state '%s': %s. Using "
|
Host::AddFormattedOSDMessage(
|
||||||
"existing image '%s', this may result in instability."),
|
30.0f,
|
||||||
media_filename.c_str(), error.GetCodeAndMessage().GetCharArray(), old_media->GetFileName().c_str());
|
Host::TranslateString("OSDMessage", "Failed to open CD image from save state '%s': %s. Using "
|
||||||
media = std::move(old_media);
|
"existing image '%s', this may result in instability."),
|
||||||
header.media_subimage_index = media->GetCurrentSubImage();
|
media_filename.c_str(), error.GetCodeAndMessage().GetCharArray(), old_media->GetFileName().c_str());
|
||||||
}
|
media = std::move(old_media);
|
||||||
else
|
header.media_subimage_index = media->GetCurrentSubImage();
|
||||||
{
|
}
|
||||||
Host::ReportFormattedErrorAsync(
|
else
|
||||||
"Error", Host::TranslateString("System", "Failed to open CD image '%s' used by save state: %s."),
|
{
|
||||||
media_filename.c_str(), error.GetCodeAndMessage().GetCharArray());
|
Host::ReportFormattedErrorAsync(
|
||||||
return false;
|
"Error", Host::TranslateString("System", "Failed to open CD image '%s' used by save state: %s."),
|
||||||
|
media_filename.c_str(), error.GetCodeAndMessage().GetCharArray());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
UpdateRunningGame(media_filename.c_str(), media.get(), false);
|
UpdateRunningGame(media_filename.c_str(), media.get(), false);
|
||||||
|
|
||||||
if (media && header.version >= 51)
|
if (media && header.version >= 51)
|
||||||
{
|
|
||||||
const u32 num_subimages = media->HasSubImages() ? media->GetSubImageCount() : 1;
|
|
||||||
if (header.media_subimage_index >= num_subimages ||
|
|
||||||
(media->HasSubImages() && media->GetCurrentSubImage() != header.media_subimage_index &&
|
|
||||||
!media->SwitchSubImage(header.media_subimage_index, &error)))
|
|
||||||
{
|
{
|
||||||
Host::ReportFormattedErrorAsync(
|
const u32 num_subimages = media->HasSubImages() ? media->GetSubImageCount() : 1;
|
||||||
"Error",
|
if (header.media_subimage_index >= num_subimages ||
|
||||||
Host::TranslateString("System", "Failed to switch to subimage %u in CD image '%s' used by save state: %s."),
|
(media->HasSubImages() && media->GetCurrentSubImage() != header.media_subimage_index &&
|
||||||
header.media_subimage_index + 1u, media_filename.c_str(), error.GetCodeAndMessage().GetCharArray());
|
!media->SwitchSubImage(header.media_subimage_index, &error)))
|
||||||
return false;
|
{
|
||||||
|
Host::ReportFormattedErrorAsync(
|
||||||
|
"Error",
|
||||||
|
Host::TranslateString("System", "Failed to switch to subimage %u in CD image '%s' used by save state: %s."),
|
||||||
|
header.media_subimage_index + 1u, media_filename.c_str(), error.GetCodeAndMessage().GetCharArray());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Switched to subimage %u in '%s'", header.media_subimage_index, media_filename.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CDROM::Reset();
|
||||||
|
if (media)
|
||||||
|
{
|
||||||
|
const DiscRegion region = GetRegionForImage(media.get());
|
||||||
|
CDROM::InsertMedia(std::move(media), region);
|
||||||
|
if (g_settings.cdrom_load_image_to_ram)
|
||||||
|
CDROM::PrecacheMedia();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log_InfoPrintf("Switched to subimage %u in '%s'", header.media_subimage_index, media_filename.c_str());
|
CDROM::RemoveMedia(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure the correct card is loaded
|
||||||
|
if (g_settings.HasAnyPerGameMemoryCards())
|
||||||
|
UpdatePerGameMemoryCards();
|
||||||
}
|
}
|
||||||
|
|
||||||
ClearMemorySaveStates();
|
ClearMemorySaveStates();
|
||||||
|
|
||||||
CDROM::Reset();
|
|
||||||
if (media)
|
|
||||||
{
|
|
||||||
const DiscRegion region = GetRegionForImage(media.get());
|
|
||||||
CDROM::InsertMedia(std::move(media), region);
|
|
||||||
if (g_settings.cdrom_load_image_to_ram)
|
|
||||||
CDROM::PrecacheMedia();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CDROM::RemoveMedia(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure the correct card is loaded
|
|
||||||
if (g_settings.HasAnyPerGameMemoryCards())
|
|
||||||
UpdatePerGameMemoryCards();
|
|
||||||
|
|
||||||
#ifdef WITH_CHEEVOS
|
#ifdef WITH_CHEEVOS
|
||||||
// Updating game/loading settings can turn on hardcore mode. Catch this.
|
// Updating game/loading settings can turn on hardcore mode. Catch this.
|
||||||
if (Achievements::ChallengeModeActive())
|
if (Achievements::ChallengeModeActive())
|
||||||
|
@ -2097,7 +2138,8 @@ bool System::LoadStateFromStream(ByteStream* state, bool update_display)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool System::SaveStateToStream(ByteStream* state, u32 screenshot_size /* = 256 */,
|
bool System::SaveStateToStream(ByteStream* state, u32 screenshot_size /* = 256 */,
|
||||||
u32 compression_method /* = SAVE_STATE_HEADER::COMPRESSION_TYPE_NONE*/)
|
u32 compression_method /* = SAVE_STATE_HEADER::COMPRESSION_TYPE_NONE*/,
|
||||||
|
bool ignore_media /* = false*/)
|
||||||
{
|
{
|
||||||
if (IsShutdown())
|
if (IsShutdown())
|
||||||
return false;
|
return false;
|
||||||
|
@ -2114,7 +2156,7 @@ bool System::SaveStateToStream(ByteStream* state, u32 screenshot_size /* = 256 *
|
||||||
StringUtil::Strlcpy(header.title, s_running_game_title.c_str(), sizeof(header.title));
|
StringUtil::Strlcpy(header.title, s_running_game_title.c_str(), sizeof(header.title));
|
||||||
StringUtil::Strlcpy(header.serial, s_running_game_serial.c_str(), sizeof(header.serial));
|
StringUtil::Strlcpy(header.serial, s_running_game_serial.c_str(), sizeof(header.serial));
|
||||||
|
|
||||||
if (CDROM::HasMedia())
|
if (CDROM::HasMedia() && !ignore_media)
|
||||||
{
|
{
|
||||||
const std::string& media_filename = CDROM::GetMediaFileName();
|
const std::string& media_filename = CDROM::GetMediaFileName();
|
||||||
header.offset_to_media_filename = static_cast<u32>(state->GetPosition());
|
header.offset_to_media_filename = static_cast<u32>(state->GetPosition());
|
||||||
|
|
|
@ -223,6 +223,7 @@ void ApplySettings(bool display_osd_messages);
|
||||||
bool ReloadGameSettings(bool display_osd_messages);
|
bool ReloadGameSettings(bool display_osd_messages);
|
||||||
|
|
||||||
bool BootSystem(SystemBootParameters parameters);
|
bool BootSystem(SystemBootParameters parameters);
|
||||||
|
bool ReinitializeSystem(ConsoleRegion region, const char* bios_path, const char* media_path, bool fast_boot);
|
||||||
void PauseSystem(bool paused);
|
void PauseSystem(bool paused);
|
||||||
void ResetSystem();
|
void ResetSystem();
|
||||||
|
|
||||||
|
@ -239,8 +240,9 @@ struct MemorySaveState
|
||||||
};
|
};
|
||||||
bool SaveMemoryState(MemorySaveState* mss);
|
bool SaveMemoryState(MemorySaveState* mss);
|
||||||
bool LoadMemoryState(const MemorySaveState& mss);
|
bool LoadMemoryState(const MemorySaveState& mss);
|
||||||
bool LoadStateFromStream(ByteStream* stream, bool update_display);
|
bool LoadStateFromStream(ByteStream* stream, bool update_display, bool ignore_media = false);
|
||||||
bool SaveStateToStream(ByteStream* state, u32 screenshot_size = 256, u32 compression_method = 0);
|
bool SaveStateToStream(ByteStream* state, u32 screenshot_size = 256, u32 compression_method = 0,
|
||||||
|
bool ignore_media = false);
|
||||||
|
|
||||||
/// Runs the VM until the CPU execution is canceled.
|
/// Runs the VM until the CPU execution is canceled.
|
||||||
void Execute();
|
void Execute();
|
||||||
|
|
|
@ -1078,14 +1078,15 @@ void EmuThread::createNetplaySession(const QString& nickname, qint32 port, qint3
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// need a valid system to make a session
|
||||||
|
if (!System::IsValid())
|
||||||
|
return;
|
||||||
|
|
||||||
if (!Netplay::CreateSession(nickname.toStdString(), port, max_players, password.toStdString()))
|
if (!Netplay::CreateSession(nickname.toStdString(), port, max_players, password.toStdString()))
|
||||||
{
|
{
|
||||||
errorReported(tr("Netplay Error"), tr("Failed to create netplay session. The log may contain more information."));
|
errorReported(tr("Netplay Error"), tr("Failed to create netplay session. The log may contain more information."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Fix this junk.. for some reason, it stays sleeping...
|
|
||||||
g_emu_thread->wakeThread();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::joinNetplaySession(const QString& nickname, const QString& hostname, qint32 port,
|
void EmuThread::joinNetplaySession(const QString& nickname, const QString& hostname, qint32 port,
|
||||||
|
@ -1103,9 +1104,6 @@ void EmuThread::joinNetplaySession(const QString& nickname, const QString& hostn
|
||||||
errorReported(tr("Netplay Error"), tr("Failed to join netplay session. The log may contain more information."));
|
errorReported(tr("Netplay Error"), tr("Failed to join netplay session. The log may contain more information."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Fix this junk.. for some reason, it stays sleeping...
|
|
||||||
g_emu_thread->wakeThread();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::runOnEmuThread(std::function<void()> callback)
|
void EmuThread::runOnEmuThread(std::function<void()> callback)
|
||||||
|
@ -2228,15 +2226,22 @@ int main(int argc, char* argv[])
|
||||||
const bool first = (s_netplay_test == 0);
|
const bool first = (s_netplay_test == 0);
|
||||||
QtHost::RunOnUIThread([first]() { g_main_window->move(QPoint(first ? 300 : 1400, 500)); });
|
QtHost::RunOnUIThread([first]() { g_main_window->move(QPoint(first ? 300 : 1400, 500)); });
|
||||||
|
|
||||||
const int h = first ? 1 : 2;
|
const int port = 31200;
|
||||||
const int nh = first ? 2 : 1;
|
const QString remote = QStringLiteral("127.0.0.1");
|
||||||
const int port_base = 31200;
|
|
||||||
std::string remote = "127.0.0.1";
|
|
||||||
std::string game = "D:\\PSX\\chd\\padtest.chd";
|
std::string game = "D:\\PSX\\chd\\padtest.chd";
|
||||||
Netplay::TestNetplaySession(h, port_base + h, remote, port_base + nh, 1, game);
|
const QString nickname = QStringLiteral("NICKNAME%1").arg(s_netplay_test + 1);
|
||||||
|
if (first)
|
||||||
// TODO: Fix this junk.. for some reason, it stays sleeping...
|
{
|
||||||
g_emu_thread->wakeThread();
|
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());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_emu_thread->joinNetplaySession(nickname, remote, port, QString());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -140,7 +140,6 @@ void ImGuiManager::DrawNetplayStats()
|
||||||
const float scale = ImGuiManager::GetGlobalScale();
|
const float scale = ImGuiManager::GetGlobalScale();
|
||||||
const float shadow_offset = 1.0f * scale;
|
const float shadow_offset = 1.0f * scale;
|
||||||
const float margin = 10.0f * scale;
|
const float margin = 10.0f * scale;
|
||||||
const float spacing = 5.0f * scale;
|
|
||||||
ImFont* font = ImGuiManager::GetFixedFont();
|
ImFont* font = ImGuiManager::GetFixedFont();
|
||||||
const float position_y = ImGui::GetIO().DisplaySize.y - margin - (100.0f * scale);
|
const float position_y = ImGui::GetIO().DisplaySize.y - margin - (100.0f * scale);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue