Netplay: Send session details to clients

This commit is contained in:
Stenzek 2023-05-16 01:01:11 +10:00
parent a6a7a1613c
commit e1e2dcd435
12 changed files with 383 additions and 192 deletions

View File

@ -376,6 +376,35 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
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 ImageInfo*>> results;

View File

@ -81,6 +81,9 @@ std::optional<std::vector<u8>> GetBIOSImage(ConsoleRegion region);
/// BIOS image within 512KB and 4MB will be used.
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.
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> FindBIOSImagesInDirectory(const char* directory);

View File

@ -140,6 +140,7 @@
<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" />

View File

@ -129,5 +129,6 @@
<ClInclude Include="input_types.h" />
<ClInclude Include="pcdrv.h" />
<ClInclude Include="netplay.h" />
<ClInclude Include="netplay_packets.h" />
</ItemGroup>
</Project>
</Project>

View File

@ -1,4 +1,5 @@
#include "netplay.h"
#include "bios.h"
#include "common/byte_stream.h"
#include "common/file_system.h"
#include "common/gpu_texture.h"
@ -13,9 +14,11 @@
#include "netplay_packets.h"
#include "pad.h"
#include "save_state_version.h"
#include "settings.h"
#include "spu.h"
#include "system.h"
#include <bitset>
#include <cinttypes>
#include <deque>
#include <gsl/span>
#include <xxhash.h>
@ -30,6 +33,9 @@ Log_SetChannel(Netplay);
#include "enet/enet.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 {
using SaveStateBuffer = std::unique_ptr<System::MemorySaveState>;
@ -57,7 +63,7 @@ static void SetInputs(Input inputs[2]);
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 RequestCloseSession(CloseSessionMessage::Reason reason);
@ -85,6 +91,7 @@ static void SendConnectRequest();
static void HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt);
static void HandleControlMessage(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 HandleResetCompleteMessage(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");
return false;
}
else if (!is_hosting && !CreateSystem(std::string(), false))
else if (!is_hosting && !CreateDummySystem())
{
Log_ErrorPrintf("Failed to create VM for joining session");
return false;
@ -403,27 +410,16 @@ bool Netplay::IsActive()
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
if (System::IsValid())
System::ShutdownSystem(false);
// fast boot the selected game and wait for the other player
auto param = SystemBootParameters(std::move(game_path));
param.override_fast_boot = true;
if (!System::BootSystem(param))
if (!System::BootSystem(SystemBootParameters()))
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;
}
@ -589,7 +585,7 @@ void Netplay::HandleEnetEvent(const ENetEvent* event)
if (player_id < 0)
{
// 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);
enet_packet_destroy(event->packet);
return;
@ -786,6 +782,10 @@ void Netplay::HandleControlMessage(s32 player_id, const ENetPacket* pkt)
HandleConnectResponseMessage(player_id, pkt);
break;
case ControlMessage::JoinResponse:
HandleJoinResponseMessage(player_id, pkt);
break;
case ControlMessage::Reset:
HandleResetMessage(player_id, pkt);
break;
@ -826,15 +826,125 @@ void Netplay::HandleControlMessage(s32 player_id, const ENetPacket* pkt)
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...
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)
{
const ConnectRequestMessage* msg = CheckReceivedPacket<ConnectRequestMessage>(-1, pkt);
if (!msg || msg->header.type != ControlMessage::ConnectRequest)
const JoinRequestMessage* msg = CheckReceivedPacket<JoinRequestMessage>(-1, pkt);
if (!msg || msg->header.type != ControlMessage::JoinRequest)
{
Log_WarningPrintf("Received unknown packet from unknown player");
enet_peer_reset(peer);
@ -845,13 +955,13 @@ void Netplay::HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt)
msg->requested_player_id)
.c_str());
PacketWrapper<ConnectResponseMessage> response = NewControlPacket<ConnectResponseMessage>();
PacketWrapper<JoinResponseMessage> response = NewControlPacket<JoinResponseMessage>();
response->player_id = -1;
// 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);
return;
}
@ -862,7 +972,7 @@ void Netplay::HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt)
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);
response->result = ConnectResponseMessage::Result::PlayerIDInUse;
response->result = JoinResponseMessage::Result::PlayerIDInUse;
SendControlPacket(peer, response);
return;
}
@ -872,14 +982,14 @@ void Netplay::HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt)
if (new_player_id < 0)
{
Log_ErrorPrintf("Server full, rejecting connection.");
response->result = ConnectResponseMessage::Result::ServerFull;
response->result = JoinResponseMessage::Result::ServerFull;
SendControlPacket(peer, response);
return;
}
Log_VerbosePrint(
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;
SendControlPacket(peer, response);
@ -900,19 +1010,16 @@ void Netplay::HandlePeerConnectionAsNonHost(ENetPeer* peer, s32 claimed_player_i
{
if (s_state == SessionState::Connecting)
{
if (peer == s_peers[s_host_player_id].peer)
{
SendConnectRequest();
return;
}
else
if (peer != s_peers[s_host_player_id].peer)
{
Log_ErrorPrintf(
fmt::format("Unexpected connection from {} claiming player ID {}", PeerAddressString(peer), claimed_player_id)
.c_str());
enet_peer_disconnect_now(peer, 0);
return;
}
// wait for session details
return;
}
Log_VerbosePrint(
@ -942,45 +1049,16 @@ void Netplay::HandlePeerConnectionAsNonHost(ENetPeer* peer, s32 claimed_player_i
s_peers[claimed_player_id].peer = peer;
}
void Netplay::SendConnectRequest()
{
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)
void Netplay::HandleJoinResponseMessage(s32 player_id, const ENetPacket* pkt)
{
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;
}
const ConnectResponseMessage* msg = CheckReceivedPacket<ConnectResponseMessage>(player_id, pkt);
if (msg->result != ConnectResponseMessage::Result::Success)
const JoinResponseMessage* msg = CheckReceivedPacket<JoinResponseMessage>(player_id, pkt);
if (msg->result != JoinResponseMessage::Result::Success)
{
CloseSessionWithError(
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.
// We also want to use maximum compression.
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...");
const u32 state_data_size = static_cast<u32>(state.GetPosition());
@ -1086,7 +1164,7 @@ void Netplay::Reset()
// having a different number of cycles.
// CPU::CodeCache::Flush();
state.SeekAbsolute(0);
if (!System::LoadStateFromStream(&state, true))
if (!System::LoadStateFromStream(&state, true, true))
Panic("Failed to reload host state");
s_state = SessionState::Resetting;
@ -1177,7 +1255,7 @@ void Netplay::HandleResetMessage(s32 player_id, const ENetPacket* pkt)
// Load state from packet.
Log_VerbosePrintf("Loading state from host");
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");
s_state = SessionState::Resetting;
@ -1429,9 +1507,25 @@ void Netplay::SetSettings()
si.SetStringValue(Controller::GetSettingsSection(i).c_str(), "Type",
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");
// 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.
si.SetIntValue("Main", "RunaheadFrameCount", 0);
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)
{
// 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;
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();
return false;
}
else if (IsHost())
{
// Load savestate if available and only when you are the host.
// the other peers will get state from the host
auto save_path = fmt::format("{}\\netplay\\{}.sav", EmuFolders::SaveStates, System::GetGameSerial());
System::LoadState(save_path.c_str());
}
// Load savestate if available and only when you are the host.
// the other peers will get state from the host
auto save_path = fmt::format("{}\\netplay\\{}.sav", EmuFolders::SaveStates, System::GetGameSerial());
System::LoadState(save_path.c_str());
return true;
}

View File

@ -28,8 +28,6 @@ enum : u8
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 JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password);

View File

@ -3,6 +3,7 @@
#pragma once
#include "bios.h"
#include "host.h"
#include "types.h"
@ -24,6 +25,7 @@ enum class ControlMessage : u32
{
// host->player
ConnectResponse,
JoinResponse,
Reset,
ResumeSession,
PlayerJoined,
@ -31,7 +33,7 @@ enum class ControlMessage : u32
CloseSession,
// player->host
ConnectRequest,
JoinRequest,
ResetComplete,
ResetRequest,
@ -53,7 +55,39 @@ struct ControlMessageHeader
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
{
@ -80,10 +114,10 @@ struct ConnectRequestMessage
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
{
@ -98,7 +132,7 @@ struct ConnectResponseMessage
Result result;
s32 player_id;
static ControlMessage MessageType() { return ControlMessage::ConnectResponse; }
static ControlMessage MessageType() { return ControlMessage::JoinResponse; }
};
struct ResetMessage

View File

@ -250,11 +250,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 +270,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 +282,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)

View File

@ -1364,6 +1364,44 @@ bool System::BootSystem(SystemBootParameters parameters)
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)
{
g_ticks_per_second = ScaleTicksToOverclock(MASTER_CLOCK);
@ -1942,7 +1980,7 @@ std::string System::GetMediaPathFromSaveState(const char* path)
return ret;
}
bool System::LoadStateFromStream(ByteStream* state, bool update_display)
bool System::LoadStateFromStream(ByteStream* state, bool update_display, bool ignore_media)
{
Assert(IsValid());
@ -1971,90 +2009,93 @@ bool System::LoadStateFromStream(ByteStream* state, bool update_display)
return false;
}
Common::Error error;
std::string media_filename;
std::unique_ptr<CDImage> media;
if (header.media_filename_length > 0)
if (!ignore_media)
{
media_filename.resize(header.media_filename_length);
if (!state->SeekAbsolute(header.offset_to_media_filename) ||
!state->Read2(media_filename.data(), header.media_filename_length))
Common::Error error;
std::string media_filename;
std::unique_ptr<CDImage> media;
if (header.media_filename_length > 0)
{
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)
media_filename.resize(header.media_filename_length);
if (!state->SeekAbsolute(header.offset_to_media_filename) ||
!state->Read2(media_filename.data(), header.media_filename_length))
{
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(
30.0f,
Host::TranslateString("OSDMessage", "Failed to open CD image from save state '%s': %s. Using "
"existing image '%s', this may result in instability."),
media_filename.c_str(), error.GetCodeAndMessage().GetCharArray(), old_media->GetFileName().c_str());
media = std::move(old_media);
header.media_subimage_index = media->GetCurrentSubImage();
}
else
{
Host::ReportFormattedErrorAsync(
"Error", Host::TranslateString("System", "Failed to open CD image '%s' used by save state: %s."),
media_filename.c_str(), error.GetCodeAndMessage().GetCharArray());
return false;
if (old_media)
{
Host::AddFormattedOSDMessage(
30.0f,
Host::TranslateString("OSDMessage", "Failed to open CD image from save state '%s': %s. Using "
"existing image '%s', this may result in instability."),
media_filename.c_str(), error.GetCodeAndMessage().GetCharArray(), old_media->GetFileName().c_str());
media = std::move(old_media);
header.media_subimage_index = media->GetCurrentSubImage();
}
else
{
Host::ReportFormattedErrorAsync(
"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)
{
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)))
if (media && header.version >= 51)
{
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;
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(
"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
{
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();
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
// Updating game/loading settings can turn on hardcore mode. Catch this.
if (Achievements::ChallengeModeActive())
@ -2097,7 +2138,8 @@ bool System::LoadStateFromStream(ByteStream* state, bool update_display)
}
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())
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.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();
header.offset_to_media_filename = static_cast<u32>(state->GetPosition());

View File

@ -223,6 +223,7 @@ void ApplySettings(bool display_osd_messages);
bool ReloadGameSettings(bool display_osd_messages);
bool BootSystem(SystemBootParameters parameters);
bool ReinitializeSystem(ConsoleRegion region, const char* bios_path, const char* media_path, bool fast_boot);
void PauseSystem(bool paused);
void ResetSystem();
@ -239,8 +240,9 @@ struct MemorySaveState
};
bool SaveMemoryState(MemorySaveState* mss);
bool LoadMemoryState(const MemorySaveState& mss);
bool LoadStateFromStream(ByteStream* stream, bool update_display);
bool SaveStateToStream(ByteStream* state, u32 screenshot_size = 256, u32 compression_method = 0);
bool LoadStateFromStream(ByteStream* stream, bool update_display, bool ignore_media = false);
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.
void Execute();

View File

@ -1078,14 +1078,15 @@ void EmuThread::createNetplaySession(const QString& nickname, qint32 port, qint3
return;
}
// need a valid system to make a session
if (!System::IsValid())
return;
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."));
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,
@ -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."));
return;
}
// TODO: Fix this junk.. for some reason, it stays sleeping...
g_emu_thread->wakeThread();
}
void EmuThread::runOnEmuThread(std::function<void()> callback)
@ -2228,15 +2226,22 @@ int main(int argc, char* argv[])
const bool first = (s_netplay_test == 0);
QtHost::RunOnUIThread([first]() { g_main_window->move(QPoint(first ? 300 : 1400, 500)); });
const int h = first ? 1 : 2;
const int nh = first ? 2 : 1;
const int port_base = 31200;
std::string remote = "127.0.0.1";
const int port = 31200;
const QString remote = QStringLiteral("127.0.0.1");
std::string game = "D:\\PSX\\chd\\padtest.chd";
Netplay::TestNetplaySession(h, port_base + h, remote, port_base + nh, 1, game);
// TODO: Fix this junk.. for some reason, it stays sleeping...
g_emu_thread->wakeThread();
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());
}
else
{
g_emu_thread->joinNetplaySession(nickname, remote, port, QString());
}
});
}

View File

@ -140,7 +140,6 @@ void ImGuiManager::DrawNetplayStats()
const float scale = ImGuiManager::GetGlobalScale();
const float shadow_offset = 1.0f * scale;
const float margin = 10.0f * scale;
const float spacing = 5.0f * scale;
ImFont* font = ImGuiManager::GetFixedFont();
const float position_y = ImGui::GetIO().DisplaySize.y - margin - (100.0f * scale);