mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-02-07 13:13:32 +01:00
ac28b89fa5
When I added the software FMA path in 2c38d64 and made us use it when determinism is enabled, I was assuming that either the performance impact of software FMA wouldn't be too large or CPUs that were too old to have FMA instructions were too slow to run Dolphin well anyway. This was wrong. To give an example, the netplay performance went from 60 FPS to 30 FPS in one case. This change makes netplay clients negotiate whether FMA should be used. If all clients use an x64 CPU that supports FMA, or AArch64, then FMA is enabled, and otherwise FMA is disabled. In other words, we sacrifice accuracy if needed to avoid massive slowdown, but not otherwise. When not using netplay, whether to enable FMA is simply based on whether the host CPU supports it. The only remaining case where the software FMA path gets used under normal circumstances is when an input recording is created on a CPU with FMA support and then played back on a CPU without. This is not an especially common scenario (though it can happen), and TASers are generally less picky about performance and more picky about accuracy than other users anyway. With this change, FMA desyncs are avoided between AArch64 and modern x64 CPUs (unlike before 2c38d64), but we do get FMA desyncs between AArch64 and old x64 CPUs (like before 2c38d64). This desync can be avoided by adding a non-FMA path to JitArm64 as an option, which I will wait with for another pull request so that we can get the performance regression fixed as quickly as possible. https://bugs.dolphin-emu.org/issues/12542
2569 lines
65 KiB
C++
2569 lines
65 KiB
C++
// Copyright 2010 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include "Core/NetPlayClient.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
#include <thread>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
#include <fmt/format.h>
|
|
#include <lzo/lzo1x.h>
|
|
#include <mbedtls/md5.h>
|
|
|
|
#include "Common/Assert.h"
|
|
#include "Common/CommonPaths.h"
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/ENetUtil.h"
|
|
#include "Common/FileUtil.h"
|
|
#include "Common/IOFile.h"
|
|
#include "Common/Logging/Log.h"
|
|
#include "Common/MD5.h"
|
|
#include "Common/MsgHandler.h"
|
|
#include "Common/NandPaths.h"
|
|
#include "Common/QoSSession.h"
|
|
#include "Common/SFMLHelper.h"
|
|
#include "Common/StringUtil.h"
|
|
#include "Common/Timer.h"
|
|
#include "Common/Version.h"
|
|
|
|
#include "Core/ActionReplay.h"
|
|
#include "Core/Config/NetplaySettings.h"
|
|
#include "Core/Config/SessionSettings.h"
|
|
#include "Core/ConfigManager.h"
|
|
#include "Core/GeckoCode.h"
|
|
#include "Core/HW/EXI/EXI_DeviceIPL.h"
|
|
#include "Core/HW/SI/SI.h"
|
|
#include "Core/HW/SI/SI_Device.h"
|
|
#include "Core/HW/SI/SI_DeviceGCController.h"
|
|
#include "Core/HW/Sram.h"
|
|
#include "Core/HW/WiiSave.h"
|
|
#include "Core/HW/WiiSaveStructs.h"
|
|
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
|
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
|
#include "Core/IOS/FS/FileSystem.h"
|
|
#include "Core/IOS/FS/HostBackend/FS.h"
|
|
#include "Core/IOS/USB/Bluetooth/BTEmu.h"
|
|
#include "Core/IOS/Uids.h"
|
|
#include "Core/Movie.h"
|
|
#include "Core/PowerPC/PowerPC.h"
|
|
#include "Core/SyncIdentifier.h"
|
|
|
|
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
|
|
#include "InputCommon/GCAdapter.h"
|
|
#include "InputCommon/InputConfig.h"
|
|
#include "UICommon/GameFile.h"
|
|
#include "VideoCommon/OnScreenDisplay.h"
|
|
#include "VideoCommon/VideoConfig.h"
|
|
|
|
namespace NetPlay
|
|
{
|
|
using namespace WiimoteCommon;
|
|
|
|
static std::mutex crit_netplay_client;
|
|
static NetPlayClient* netplay_client = nullptr;
|
|
static std::unique_ptr<IOS::HLE::FS::FileSystem> s_wii_sync_fs;
|
|
static std::vector<u64> s_wii_sync_titles;
|
|
static bool s_si_poll_batching;
|
|
|
|
// called from ---GUI--- thread
|
|
NetPlayClient::~NetPlayClient()
|
|
{
|
|
// not perfect
|
|
if (m_is_running.IsSet())
|
|
StopGame();
|
|
|
|
if (m_is_connected)
|
|
{
|
|
m_should_compute_MD5 = false;
|
|
m_dialog->AbortMD5();
|
|
if (m_MD5_thread.joinable())
|
|
m_MD5_thread.join();
|
|
m_do_loop.Clear();
|
|
m_thread.join();
|
|
|
|
m_chunked_data_receive_queue.clear();
|
|
m_dialog->HideChunkedProgressDialog();
|
|
}
|
|
|
|
if (m_server)
|
|
{
|
|
Disconnect();
|
|
}
|
|
|
|
if (g_MainNetHost.get() == m_client)
|
|
{
|
|
g_MainNetHost.release();
|
|
}
|
|
if (m_client)
|
|
{
|
|
enet_host_destroy(m_client);
|
|
m_client = nullptr;
|
|
}
|
|
|
|
if (m_traversal_client)
|
|
{
|
|
ReleaseTraversalClient();
|
|
}
|
|
}
|
|
|
|
// called from ---GUI--- thread
|
|
NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog,
|
|
const std::string& name, const NetTraversalConfig& traversal_config)
|
|
: m_dialog(dialog), m_player_name(name)
|
|
{
|
|
ClearBuffers();
|
|
|
|
if (!traversal_config.use_traversal)
|
|
{
|
|
// Direct Connection
|
|
m_client = enet_host_create(nullptr, 1, CHANNEL_COUNT, 0, 0);
|
|
|
|
if (m_client == nullptr)
|
|
{
|
|
m_dialog->OnConnectionError(_trans("Could not create client."));
|
|
return;
|
|
}
|
|
|
|
ENetAddress addr;
|
|
enet_address_set_host(&addr, address.c_str());
|
|
addr.port = port;
|
|
|
|
m_server = enet_host_connect(m_client, &addr, CHANNEL_COUNT, 0);
|
|
|
|
if (m_server == nullptr)
|
|
{
|
|
m_dialog->OnConnectionError(_trans("Could not create peer."));
|
|
return;
|
|
}
|
|
|
|
ENetEvent netEvent;
|
|
int net = enet_host_service(m_client, &netEvent, 5000);
|
|
if (net > 0 && netEvent.type == ENET_EVENT_TYPE_CONNECT)
|
|
{
|
|
if (Connect())
|
|
{
|
|
m_client->intercept = ENetUtil::InterceptCallback;
|
|
m_thread = std::thread(&NetPlayClient::ThreadFunc, this);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_dialog->OnConnectionError(_trans("Could not communicate with host."));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (address.size() > NETPLAY_CODE_SIZE)
|
|
{
|
|
m_dialog->OnConnectionError(
|
|
_trans("The host code is too long.\nPlease recheck that you have the correct code."));
|
|
return;
|
|
}
|
|
|
|
if (!EnsureTraversalClient(traversal_config.traversal_host, traversal_config.traversal_port))
|
|
return;
|
|
m_client = g_MainNetHost.get();
|
|
|
|
m_traversal_client = g_TraversalClient.get();
|
|
|
|
// If we were disconnected in the background, reconnect.
|
|
if (m_traversal_client->HasFailed())
|
|
m_traversal_client->ReconnectToServer();
|
|
m_traversal_client->m_Client = this;
|
|
m_host_spec = address;
|
|
m_connection_state = ConnectionState::WaitingForTraversalClientConnection;
|
|
OnTraversalStateChanged();
|
|
m_connecting = true;
|
|
|
|
Common::Timer connect_timer;
|
|
connect_timer.Start();
|
|
|
|
while (m_connecting)
|
|
{
|
|
ENetEvent netEvent;
|
|
if (m_traversal_client)
|
|
m_traversal_client->HandleResends();
|
|
|
|
while (enet_host_service(m_client, &netEvent, 4) > 0)
|
|
{
|
|
sf::Packet rpac;
|
|
switch (netEvent.type)
|
|
{
|
|
case ENET_EVENT_TYPE_CONNECT:
|
|
m_server = netEvent.peer;
|
|
if (Connect())
|
|
{
|
|
m_connection_state = ConnectionState::Connected;
|
|
m_thread = std::thread(&NetPlayClient::ThreadFunc, this);
|
|
}
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (connect_timer.GetTimeElapsed() > 5000)
|
|
break;
|
|
}
|
|
m_dialog->OnConnectionError(_trans("Could not communicate with host."));
|
|
}
|
|
}
|
|
|
|
bool NetPlayClient::Connect()
|
|
{
|
|
// send connect message
|
|
sf::Packet packet;
|
|
packet << Common::scm_rev_git_str;
|
|
packet << Common::netplay_dolphin_ver;
|
|
packet << m_player_name;
|
|
Send(packet);
|
|
enet_host_flush(m_client);
|
|
sf::Packet rpac;
|
|
// TODO: make this not hang
|
|
ENetEvent netEvent;
|
|
if (enet_host_service(m_client, &netEvent, 5000) > 0 && netEvent.type == ENET_EVENT_TYPE_RECEIVE)
|
|
{
|
|
rpac.append(netEvent.packet->data, netEvent.packet->dataLength);
|
|
enet_packet_destroy(netEvent.packet);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
MessageId error;
|
|
rpac >> error;
|
|
|
|
// got error message
|
|
if (error)
|
|
{
|
|
switch (error)
|
|
{
|
|
case CON_ERR_SERVER_FULL:
|
|
m_dialog->OnConnectionError(_trans("The server is full."));
|
|
break;
|
|
case CON_ERR_VERSION_MISMATCH:
|
|
m_dialog->OnConnectionError(
|
|
_trans("The server and client's NetPlay versions are incompatible."));
|
|
break;
|
|
case CON_ERR_GAME_RUNNING:
|
|
m_dialog->OnConnectionError(_trans("The game is currently running."));
|
|
break;
|
|
case CON_ERR_NAME_TOO_LONG:
|
|
m_dialog->OnConnectionError(_trans("Nickname is too long."));
|
|
break;
|
|
default:
|
|
m_dialog->OnConnectionError(_trans("The server sent an unknown error message."));
|
|
break;
|
|
}
|
|
|
|
Disconnect();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
rpac >> m_pid;
|
|
|
|
Player player;
|
|
player.name = m_player_name;
|
|
player.pid = m_pid;
|
|
player.revision = Common::netplay_dolphin_ver;
|
|
|
|
// add self to player list
|
|
m_players[m_pid] = player;
|
|
m_local_player = &m_players[m_pid];
|
|
|
|
m_dialog->Update();
|
|
|
|
m_is_connected = true;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static void ReceiveSyncIdentifier(sf::Packet& spac, SyncIdentifier& sync_identifier)
|
|
{
|
|
// We use a temporary variable here due to a potential long vs long long mismatch
|
|
sf::Uint64 dol_elf_size;
|
|
spac >> dol_elf_size;
|
|
sync_identifier.dol_elf_size = dol_elf_size;
|
|
|
|
spac >> sync_identifier.game_id;
|
|
spac >> sync_identifier.revision;
|
|
spac >> sync_identifier.disc_number;
|
|
spac >> sync_identifier.is_datel;
|
|
|
|
for (u8& x : sync_identifier.sync_hash)
|
|
spac >> x;
|
|
}
|
|
|
|
// called from ---NETPLAY--- thread
|
|
unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
|
{
|
|
MessageId mid;
|
|
packet >> mid;
|
|
|
|
INFO_LOG_FMT(NETPLAY, "Got server message: {:x}", mid);
|
|
|
|
switch (mid)
|
|
{
|
|
case NP_MSG_PLAYER_JOIN:
|
|
{
|
|
Player player;
|
|
packet >> player.pid;
|
|
packet >> player.name;
|
|
packet >> player.revision;
|
|
|
|
INFO_LOG_FMT(NETPLAY, "Player {} ({}) using {} joined", player.name, player.pid,
|
|
player.revision);
|
|
|
|
{
|
|
std::lock_guard lkp(m_crit.players);
|
|
m_players[player.pid] = player;
|
|
}
|
|
|
|
m_dialog->OnPlayerConnect(player.name);
|
|
|
|
m_dialog->Update();
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_PLAYER_LEAVE:
|
|
{
|
|
PlayerId pid;
|
|
packet >> pid;
|
|
|
|
{
|
|
std::lock_guard lkp(m_crit.players);
|
|
const auto it = m_players.find(pid);
|
|
if (it == m_players.end())
|
|
break;
|
|
|
|
const auto& player = it->second;
|
|
INFO_LOG_FMT(NETPLAY, "Player {} ({}) left", player.name, pid);
|
|
m_dialog->OnPlayerDisconnect(player.name);
|
|
m_players.erase(m_players.find(pid));
|
|
}
|
|
|
|
m_dialog->Update();
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_CHAT_MESSAGE:
|
|
{
|
|
PlayerId pid;
|
|
packet >> pid;
|
|
std::string msg;
|
|
packet >> msg;
|
|
|
|
// don't need lock to read in this thread
|
|
const Player& player = m_players[pid];
|
|
|
|
INFO_LOG_FMT(NETPLAY, "Player {} ({}) wrote: {}", player.name, player.pid, msg);
|
|
|
|
// add to gui
|
|
std::ostringstream ss;
|
|
ss << player.name << '[' << (char)(pid + '0') << "]: " << msg;
|
|
|
|
m_dialog->AppendChat(ss.str());
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_CHUNKED_DATA_START:
|
|
{
|
|
u32 cid;
|
|
packet >> cid;
|
|
std::string title;
|
|
packet >> title;
|
|
u64 data_size = Common::PacketReadU64(packet);
|
|
|
|
m_chunked_data_receive_queue.emplace(cid, sf::Packet{});
|
|
|
|
std::vector<int> players;
|
|
players.push_back(m_local_player->pid);
|
|
m_dialog->ShowChunkedProgressDialog(title, data_size, players);
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_CHUNKED_DATA_END:
|
|
{
|
|
u32 cid;
|
|
packet >> cid;
|
|
|
|
if (m_chunked_data_receive_queue.count(cid))
|
|
{
|
|
OnData(m_chunked_data_receive_queue[cid]);
|
|
m_chunked_data_receive_queue.erase(cid);
|
|
m_dialog->HideChunkedProgressDialog();
|
|
|
|
sf::Packet complete_packet;
|
|
complete_packet << static_cast<MessageId>(NP_MSG_CHUNKED_DATA_COMPLETE);
|
|
complete_packet << cid;
|
|
Send(complete_packet, CHUNKED_DATA_CHANNEL);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_CHUNKED_DATA_PAYLOAD:
|
|
{
|
|
u32 cid;
|
|
packet >> cid;
|
|
|
|
if (m_chunked_data_receive_queue.count(cid))
|
|
{
|
|
while (!packet.endOfPacket())
|
|
{
|
|
u8 byte;
|
|
packet >> byte;
|
|
m_chunked_data_receive_queue[cid] << byte;
|
|
}
|
|
|
|
m_dialog->SetChunkedProgress(m_local_player->pid,
|
|
m_chunked_data_receive_queue[cid].getDataSize());
|
|
|
|
sf::Packet progress_packet;
|
|
progress_packet << static_cast<MessageId>(NP_MSG_CHUNKED_DATA_PROGRESS);
|
|
progress_packet << cid;
|
|
progress_packet << sf::Uint64{m_chunked_data_receive_queue[cid].getDataSize()};
|
|
Send(progress_packet, CHUNKED_DATA_CHANNEL);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_CHUNKED_DATA_ABORT:
|
|
{
|
|
u32 cid;
|
|
packet >> cid;
|
|
|
|
if (m_chunked_data_receive_queue.count(cid))
|
|
{
|
|
m_chunked_data_receive_queue.erase(cid);
|
|
m_dialog->HideChunkedProgressDialog();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_PAD_MAPPING:
|
|
{
|
|
for (PlayerId& mapping : m_pad_map)
|
|
{
|
|
packet >> mapping;
|
|
}
|
|
|
|
UpdateDevices();
|
|
|
|
m_dialog->Update();
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_WIIMOTE_MAPPING:
|
|
{
|
|
for (PlayerId& mapping : m_wiimote_map)
|
|
{
|
|
packet >> mapping;
|
|
}
|
|
|
|
m_dialog->Update();
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_PAD_DATA:
|
|
{
|
|
while (!packet.endOfPacket())
|
|
{
|
|
PadIndex map;
|
|
packet >> map;
|
|
|
|
GCPadStatus pad;
|
|
packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >>
|
|
pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected;
|
|
|
|
// Trusting server for good map value (>=0 && <4)
|
|
// add to pad buffer
|
|
m_pad_buffer.at(map).Push(pad);
|
|
m_gc_pad_event.Set();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_PAD_HOST_DATA:
|
|
{
|
|
while (!packet.endOfPacket())
|
|
{
|
|
PadIndex map;
|
|
packet >> map;
|
|
|
|
GCPadStatus pad;
|
|
packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >>
|
|
pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected;
|
|
|
|
// Trusting server for good map value (>=0 && <4)
|
|
// write to last status
|
|
m_last_pad_status[map] = pad;
|
|
|
|
if (!m_first_pad_status_received[map])
|
|
{
|
|
m_first_pad_status_received[map] = true;
|
|
m_first_pad_status_received_event.Set();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_WIIMOTE_DATA:
|
|
{
|
|
PadIndex map;
|
|
WiimoteInput nw;
|
|
u8 size;
|
|
packet >> map >> nw.report_id >> size;
|
|
nw.data.resize(size);
|
|
for (auto& byte : nw.data)
|
|
packet >> byte;
|
|
|
|
// Trusting server for good map value (>=0 && <4)
|
|
// add to Wiimote buffer
|
|
m_wiimote_buffer.at(map).Push(nw);
|
|
m_wii_pad_event.Set();
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_PAD_BUFFER:
|
|
{
|
|
u32 size = 0;
|
|
packet >> size;
|
|
|
|
m_target_buffer_size = size;
|
|
m_dialog->OnPadBufferChanged(size);
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_HOST_INPUT_AUTHORITY:
|
|
{
|
|
packet >> m_host_input_authority;
|
|
m_dialog->OnHostInputAuthorityChanged(m_host_input_authority);
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_GOLF_SWITCH:
|
|
{
|
|
PlayerId pid;
|
|
packet >> pid;
|
|
|
|
const PlayerId previous_golfer = m_current_golfer;
|
|
m_current_golfer = pid;
|
|
m_dialog->OnGolferChanged(m_local_player->pid == pid, pid != 0 ? m_players[pid].name : "");
|
|
|
|
if (m_local_player->pid == previous_golfer)
|
|
{
|
|
sf::Packet spac;
|
|
spac << static_cast<MessageId>(NP_MSG_GOLF_RELEASE);
|
|
Send(spac);
|
|
}
|
|
else if (m_local_player->pid == pid)
|
|
{
|
|
sf::Packet spac;
|
|
spac << static_cast<MessageId>(NP_MSG_GOLF_ACQUIRE);
|
|
Send(spac);
|
|
|
|
// Pads are already calibrated so we can just ignore this
|
|
m_first_pad_status_received.fill(true);
|
|
|
|
m_wait_on_input = false;
|
|
m_wait_on_input_event.Set();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_GOLF_PREPARE:
|
|
{
|
|
m_wait_on_input_received = true;
|
|
m_wait_on_input = true;
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_CHANGE_GAME:
|
|
{
|
|
std::string netplay_name;
|
|
{
|
|
std::lock_guard lkg(m_crit.game);
|
|
ReceiveSyncIdentifier(packet, m_selected_game);
|
|
packet >> netplay_name;
|
|
}
|
|
|
|
INFO_LOG_FMT(NETPLAY, "Game changed to {}", netplay_name);
|
|
|
|
// update gui
|
|
m_dialog->OnMsgChangeGame(m_selected_game, netplay_name);
|
|
|
|
sf::Packet game_status_packet;
|
|
game_status_packet << static_cast<MessageId>(NP_MSG_GAME_STATUS);
|
|
|
|
SyncIdentifierComparison result;
|
|
m_dialog->FindGameFile(m_selected_game, &result);
|
|
|
|
game_status_packet << static_cast<u32>(result);
|
|
Send(game_status_packet);
|
|
|
|
sf::Packet client_capabilities_packet;
|
|
client_capabilities_packet << static_cast<MessageId>(NP_MSG_CLIENT_CAPABILITIES);
|
|
client_capabilities_packet << ExpansionInterface::CEXIIPL::HasIPLDump();
|
|
client_capabilities_packet << Config::Get(Config::SESSION_USE_FMA);
|
|
Send(client_capabilities_packet);
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_GAME_STATUS:
|
|
{
|
|
PlayerId pid;
|
|
packet >> pid;
|
|
|
|
{
|
|
std::lock_guard lkp(m_crit.players);
|
|
Player& player = m_players[pid];
|
|
u32 status;
|
|
packet >> status;
|
|
player.game_status = static_cast<SyncIdentifierComparison>(status);
|
|
}
|
|
|
|
m_dialog->Update();
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_START_GAME:
|
|
{
|
|
{
|
|
std::lock_guard lkg(m_crit.game);
|
|
packet >> m_current_game;
|
|
packet >> m_net_settings.m_CPUthread;
|
|
|
|
INFO_LOG_FMT(NETPLAY, "Start of game {}", m_selected_game.game_id);
|
|
|
|
{
|
|
std::underlying_type_t<PowerPC::CPUCore> core;
|
|
if (packet >> core)
|
|
m_net_settings.m_CPUcore = static_cast<PowerPC::CPUCore>(core);
|
|
else
|
|
m_net_settings.m_CPUcore = PowerPC::CPUCore::CachedInterpreter;
|
|
}
|
|
|
|
packet >> m_net_settings.m_EnableCheats;
|
|
packet >> m_net_settings.m_SelectedLanguage;
|
|
packet >> m_net_settings.m_OverrideRegionSettings;
|
|
packet >> m_net_settings.m_DSPEnableJIT;
|
|
packet >> m_net_settings.m_DSPHLE;
|
|
packet >> m_net_settings.m_WriteToMemcard;
|
|
packet >> m_net_settings.m_RAMOverrideEnable;
|
|
packet >> m_net_settings.m_Mem1Size;
|
|
packet >> m_net_settings.m_Mem2Size;
|
|
|
|
{
|
|
std::underlying_type_t<DiscIO::Region> tmp;
|
|
packet >> tmp;
|
|
m_net_settings.m_FallbackRegion = static_cast<DiscIO::Region>(tmp);
|
|
}
|
|
|
|
packet >> m_net_settings.m_AllowSDWrites;
|
|
packet >> m_net_settings.m_CopyWiiSave;
|
|
packet >> m_net_settings.m_OCEnable;
|
|
packet >> m_net_settings.m_OCFactor;
|
|
|
|
for (auto& device : m_net_settings.m_EXIDevice)
|
|
{
|
|
int tmp;
|
|
packet >> tmp;
|
|
device = static_cast<ExpansionInterface::TEXIDevices>(tmp);
|
|
}
|
|
|
|
for (u32& value : m_net_settings.m_SYSCONFSettings)
|
|
packet >> value;
|
|
|
|
packet >> m_net_settings.m_EFBAccessEnable;
|
|
packet >> m_net_settings.m_BBoxEnable;
|
|
packet >> m_net_settings.m_ForceProgressive;
|
|
packet >> m_net_settings.m_EFBToTextureEnable;
|
|
packet >> m_net_settings.m_XFBToTextureEnable;
|
|
packet >> m_net_settings.m_DisableCopyToVRAM;
|
|
packet >> m_net_settings.m_ImmediateXFBEnable;
|
|
packet >> m_net_settings.m_EFBEmulateFormatChanges;
|
|
packet >> m_net_settings.m_SafeTextureCacheColorSamples;
|
|
packet >> m_net_settings.m_PerfQueriesEnable;
|
|
packet >> m_net_settings.m_FPRF;
|
|
packet >> m_net_settings.m_AccurateNaNs;
|
|
packet >> m_net_settings.m_DisableICache;
|
|
packet >> m_net_settings.m_SyncOnSkipIdle;
|
|
packet >> m_net_settings.m_SyncGPU;
|
|
packet >> m_net_settings.m_SyncGpuMaxDistance;
|
|
packet >> m_net_settings.m_SyncGpuMinDistance;
|
|
packet >> m_net_settings.m_SyncGpuOverclock;
|
|
packet >> m_net_settings.m_JITFollowBranch;
|
|
packet >> m_net_settings.m_FastDiscSpeed;
|
|
packet >> m_net_settings.m_MMU;
|
|
packet >> m_net_settings.m_Fastmem;
|
|
packet >> m_net_settings.m_SkipIPL;
|
|
packet >> m_net_settings.m_LoadIPLDump;
|
|
packet >> m_net_settings.m_VertexRounding;
|
|
packet >> m_net_settings.m_InternalResolution;
|
|
packet >> m_net_settings.m_EFBScaledCopy;
|
|
packet >> m_net_settings.m_FastDepthCalc;
|
|
packet >> m_net_settings.m_EnablePixelLighting;
|
|
packet >> m_net_settings.m_WidescreenHack;
|
|
packet >> m_net_settings.m_ForceFiltering;
|
|
packet >> m_net_settings.m_MaxAnisotropy;
|
|
packet >> m_net_settings.m_ForceTrueColor;
|
|
packet >> m_net_settings.m_DisableCopyFilter;
|
|
packet >> m_net_settings.m_DisableFog;
|
|
packet >> m_net_settings.m_ArbitraryMipmapDetection;
|
|
packet >> m_net_settings.m_ArbitraryMipmapDetectionThreshold;
|
|
packet >> m_net_settings.m_EnableGPUTextureDecoding;
|
|
packet >> m_net_settings.m_DeferEFBCopies;
|
|
packet >> m_net_settings.m_EFBAccessTileSize;
|
|
packet >> m_net_settings.m_EFBAccessDeferInvalidation;
|
|
packet >> m_net_settings.m_StrictSettingsSync;
|
|
|
|
m_initial_rtc = Common::PacketReadU64(packet);
|
|
|
|
packet >> m_net_settings.m_SyncSaveData;
|
|
packet >> m_net_settings.m_SaveDataRegion;
|
|
packet >> m_net_settings.m_SyncCodes;
|
|
packet >> m_net_settings.m_SyncAllWiiSaves;
|
|
|
|
for (int& extension : m_net_settings.m_WiimoteExtension)
|
|
packet >> extension;
|
|
|
|
packet >> m_net_settings.m_GolfMode;
|
|
packet >> m_net_settings.m_UseFMA;
|
|
|
|
m_net_settings.m_IsHosting = m_local_player->IsHost();
|
|
m_net_settings.m_HostInputAuthority = m_host_input_authority;
|
|
}
|
|
|
|
m_dialog->OnMsgStartGame();
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_STOP_GAME:
|
|
case NP_MSG_DISABLE_GAME:
|
|
{
|
|
INFO_LOG_FMT(NETPLAY, "Game stopped");
|
|
|
|
StopGame();
|
|
m_dialog->OnMsgStopGame();
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_POWER_BUTTON:
|
|
{
|
|
m_dialog->OnMsgPowerButton();
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_PING:
|
|
{
|
|
u32 ping_key = 0;
|
|
packet >> ping_key;
|
|
|
|
sf::Packet response_packet;
|
|
response_packet << static_cast<MessageId>(NP_MSG_PONG);
|
|
response_packet << ping_key;
|
|
|
|
Send(response_packet);
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_PLAYER_PING_DATA:
|
|
{
|
|
PlayerId pid;
|
|
packet >> pid;
|
|
|
|
{
|
|
std::lock_guard lkp(m_crit.players);
|
|
Player& player = m_players[pid];
|
|
packet >> player.ping;
|
|
}
|
|
|
|
DisplayPlayersPing();
|
|
m_dialog->Update();
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_DESYNC_DETECTED:
|
|
{
|
|
int pid_to_blame;
|
|
u32 frame;
|
|
packet >> pid_to_blame;
|
|
packet >> frame;
|
|
|
|
std::string player = "??";
|
|
std::lock_guard lkp(m_crit.players);
|
|
{
|
|
auto it = m_players.find(pid_to_blame);
|
|
if (it != m_players.end())
|
|
player = it->second.name;
|
|
}
|
|
|
|
INFO_LOG_FMT(NETPLAY, "Player {} ({}) desynced!", player, pid_to_blame);
|
|
|
|
m_dialog->OnDesync(frame, player);
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_SYNC_GC_SRAM:
|
|
{
|
|
const size_t sram_settings_len = sizeof(g_SRAM) - offsetof(Sram, settings);
|
|
u8 sram[sram_settings_len];
|
|
for (u8& cell : sram)
|
|
{
|
|
packet >> cell;
|
|
}
|
|
|
|
{
|
|
std::lock_guard lkg(m_crit.game);
|
|
memcpy(&g_SRAM.settings, sram, sram_settings_len);
|
|
g_SRAM_netplay_initialized = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_SYNC_SAVE_DATA:
|
|
{
|
|
MessageId sub_id;
|
|
packet >> sub_id;
|
|
|
|
switch (sub_id)
|
|
{
|
|
case SYNC_SAVE_DATA_NOTIFY:
|
|
{
|
|
if (m_local_player->IsHost())
|
|
return 0;
|
|
|
|
packet >> m_sync_save_data_count;
|
|
m_sync_save_data_success_count = 0;
|
|
|
|
if (m_sync_save_data_count == 0)
|
|
SyncSaveDataResponse(true);
|
|
else
|
|
m_dialog->AppendChat(Common::GetStringT("Synchronizing save data..."));
|
|
}
|
|
break;
|
|
|
|
case SYNC_SAVE_DATA_RAW:
|
|
{
|
|
if (m_local_player->IsHost())
|
|
return 0;
|
|
|
|
bool is_slot_a;
|
|
std::string region;
|
|
bool mc251;
|
|
packet >> is_slot_a >> region >> mc251;
|
|
|
|
// This check is mainly intended to filter out characters which have special meanings in paths
|
|
if (region != JAP_DIR && region != USA_DIR && region != EUR_DIR)
|
|
{
|
|
SyncSaveDataResponse(false);
|
|
return 0;
|
|
}
|
|
|
|
const std::string path = File::GetUserPath(D_GCUSER_IDX) + GC_MEMCARD_NETPLAY +
|
|
(is_slot_a ? "A." : "B.") + region + (mc251 ? ".251" : "") + ".raw";
|
|
if (File::Exists(path) && !File::Delete(path))
|
|
{
|
|
PanicAlertFmtT("Failed to delete NetPlay memory card. Verify your write permissions.");
|
|
SyncSaveDataResponse(false);
|
|
return 0;
|
|
}
|
|
|
|
const bool success = DecompressPacketIntoFile(packet, path);
|
|
SyncSaveDataResponse(success);
|
|
}
|
|
break;
|
|
|
|
case SYNC_SAVE_DATA_GCI:
|
|
{
|
|
if (m_local_player->IsHost())
|
|
return 0;
|
|
|
|
bool is_slot_a;
|
|
u8 file_count;
|
|
packet >> is_slot_a >> file_count;
|
|
|
|
const std::string path = File::GetUserPath(D_GCUSER_IDX) + GC_MEMCARD_NETPLAY DIR_SEP +
|
|
fmt::format("Card {}", is_slot_a ? 'A' : 'B');
|
|
|
|
if ((File::Exists(path) && !File::DeleteDirRecursively(path + DIR_SEP)) ||
|
|
!File::CreateFullPath(path + DIR_SEP))
|
|
{
|
|
PanicAlertFmtT("Failed to reset NetPlay GCI folder. Verify your write permissions.");
|
|
SyncSaveDataResponse(false);
|
|
return 0;
|
|
}
|
|
|
|
for (u8 i = 0; i < file_count; i++)
|
|
{
|
|
std::string file_name;
|
|
packet >> file_name;
|
|
|
|
if (!Common::IsFileNameSafe(file_name) ||
|
|
!DecompressPacketIntoFile(packet, path + DIR_SEP + file_name))
|
|
{
|
|
SyncSaveDataResponse(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
SyncSaveDataResponse(true);
|
|
}
|
|
break;
|
|
|
|
case SYNC_SAVE_DATA_WII:
|
|
{
|
|
if (m_local_player->IsHost())
|
|
return 0;
|
|
|
|
const std::string path = File::GetUserPath(D_USER_IDX) + "Wii" GC_MEMCARD_NETPLAY DIR_SEP;
|
|
|
|
if (File::Exists(path) && !File::DeleteDirRecursively(path))
|
|
{
|
|
PanicAlertFmtT("Failed to reset NetPlay NAND folder. Verify your write permissions.");
|
|
SyncSaveDataResponse(false);
|
|
return 0;
|
|
}
|
|
|
|
auto temp_fs = std::make_unique<IOS::HLE::FS::HostFileSystem>(path);
|
|
std::vector<u64> titles;
|
|
|
|
const IOS::HLE::FS::Modes fs_modes = {IOS::HLE::FS::Mode::ReadWrite,
|
|
IOS::HLE::FS::Mode::ReadWrite,
|
|
IOS::HLE::FS::Mode::ReadWrite};
|
|
|
|
// Read the Mii data
|
|
bool mii_data;
|
|
packet >> mii_data;
|
|
if (mii_data)
|
|
{
|
|
auto buffer = DecompressPacketIntoBuffer(packet);
|
|
|
|
temp_fs->CreateFullPath(IOS::PID_KERNEL, IOS::PID_KERNEL, "/shared2/menu/FaceLib/", 0,
|
|
fs_modes);
|
|
auto file = temp_fs->CreateAndOpenFile(IOS::PID_KERNEL, IOS::PID_KERNEL,
|
|
Common::GetMiiDatabasePath(), fs_modes);
|
|
|
|
if (!buffer || !file || !file->Write(buffer->data(), buffer->size()))
|
|
{
|
|
PanicAlertFmtT("Failed to write Mii data.");
|
|
SyncSaveDataResponse(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Read the saves
|
|
u32 save_count;
|
|
packet >> save_count;
|
|
for (u32 n = 0; n < save_count; n++)
|
|
{
|
|
u64 title_id = Common::PacketReadU64(packet);
|
|
titles.push_back(title_id);
|
|
temp_fs->CreateFullPath(IOS::PID_KERNEL, IOS::PID_KERNEL,
|
|
Common::GetTitleDataPath(title_id) + '/', 0, fs_modes);
|
|
auto save = WiiSave::MakeNandStorage(temp_fs.get(), title_id);
|
|
|
|
bool exists;
|
|
packet >> exists;
|
|
if (!exists)
|
|
continue;
|
|
|
|
// Header
|
|
WiiSave::Header header;
|
|
packet >> header.tid;
|
|
packet >> header.banner_size;
|
|
packet >> header.permissions;
|
|
packet >> header.unk1;
|
|
for (u8& byte : header.md5)
|
|
packet >> byte;
|
|
packet >> header.unk2;
|
|
for (size_t i = 0; i < header.banner_size; i++)
|
|
packet >> header.banner[i];
|
|
|
|
// BkHeader
|
|
WiiSave::BkHeader bk_header;
|
|
packet >> bk_header.size;
|
|
packet >> bk_header.magic;
|
|
packet >> bk_header.ngid;
|
|
packet >> bk_header.number_of_files;
|
|
packet >> bk_header.size_of_files;
|
|
packet >> bk_header.unk1;
|
|
packet >> bk_header.unk2;
|
|
packet >> bk_header.total_size;
|
|
for (u8& byte : bk_header.unk3)
|
|
packet >> byte;
|
|
packet >> bk_header.tid;
|
|
for (u8& byte : bk_header.mac_address)
|
|
packet >> byte;
|
|
|
|
// Files
|
|
std::vector<WiiSave::Storage::SaveFile> files;
|
|
for (u32 i = 0; i < bk_header.number_of_files; i++)
|
|
{
|
|
WiiSave::Storage::SaveFile file;
|
|
packet >> file.mode >> file.attributes;
|
|
{
|
|
u8 tmp;
|
|
packet >> tmp;
|
|
file.type = static_cast<WiiSave::Storage::SaveFile::Type>(tmp);
|
|
}
|
|
packet >> file.path;
|
|
|
|
if (file.type == WiiSave::Storage::SaveFile::Type::File)
|
|
{
|
|
auto buffer = DecompressPacketIntoBuffer(packet);
|
|
if (!buffer)
|
|
{
|
|
SyncSaveDataResponse(false);
|
|
return 0;
|
|
}
|
|
|
|
file.data = std::move(*buffer);
|
|
}
|
|
|
|
files.push_back(std::move(file));
|
|
}
|
|
|
|
if (!save->WriteHeader(header) || !save->WriteBkHeader(bk_header) ||
|
|
!save->WriteFiles(files))
|
|
{
|
|
PanicAlertFmtT("Failed to write Wii save.");
|
|
SyncSaveDataResponse(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
SetWiiSyncData(std::move(temp_fs), titles);
|
|
SyncSaveDataResponse(true);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
PanicAlertFmtT("Unknown SYNC_SAVE_DATA message received with id: {0}", sub_id);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_SYNC_CODES:
|
|
{
|
|
// Recieve Data Packet
|
|
MessageId sub_id;
|
|
packet >> sub_id;
|
|
|
|
// Check Which Operation to Perform with This Packet
|
|
switch (sub_id)
|
|
{
|
|
case SYNC_CODES_NOTIFY:
|
|
{
|
|
// Set both codes as unsynced
|
|
m_sync_gecko_codes_complete = false;
|
|
m_sync_ar_codes_complete = false;
|
|
}
|
|
break;
|
|
|
|
case SYNC_CODES_NOTIFY_GECKO:
|
|
{
|
|
// Return if this is the host
|
|
if (m_local_player->IsHost())
|
|
return 0;
|
|
|
|
// Receive Number of Codelines to Receive
|
|
packet >> m_sync_gecko_codes_count;
|
|
|
|
m_sync_gecko_codes_success_count = 0;
|
|
|
|
NOTICE_LOG_FMT(ACTIONREPLAY, "Receiving {} Gecko codelines", m_sync_gecko_codes_count);
|
|
|
|
// Check if no codes to sync, if so return as finished
|
|
if (m_sync_gecko_codes_count == 0)
|
|
{
|
|
m_sync_gecko_codes_complete = true;
|
|
SyncCodeResponse(true);
|
|
}
|
|
else
|
|
{
|
|
m_dialog->AppendChat(Common::GetStringT("Synchronizing Gecko codes..."));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SYNC_CODES_DATA_GECKO:
|
|
{
|
|
// Return if this is the host
|
|
if (m_local_player->IsHost())
|
|
return 0;
|
|
|
|
// Create a synced code vector
|
|
std::vector<Gecko::GeckoCode> synced_codes;
|
|
// Create a GeckoCode
|
|
Gecko::GeckoCode gcode;
|
|
gcode = Gecko::GeckoCode();
|
|
// Initialize gcode
|
|
gcode.name = "Synced Codes";
|
|
gcode.enabled = true;
|
|
|
|
// Receive code contents from packet
|
|
for (int i = 0; i < m_sync_gecko_codes_count; i++)
|
|
{
|
|
Gecko::GeckoCode::Code new_code;
|
|
packet >> new_code.address;
|
|
packet >> new_code.data;
|
|
|
|
NOTICE_LOG_FMT(ACTIONREPLAY, "Received {:08x} {:08x}", new_code.address, new_code.data);
|
|
|
|
gcode.codes.push_back(std::move(new_code));
|
|
|
|
if (++m_sync_gecko_codes_success_count >= m_sync_gecko_codes_count)
|
|
{
|
|
m_sync_gecko_codes_complete = true;
|
|
SyncCodeResponse(true);
|
|
}
|
|
}
|
|
|
|
// Add gcode containing all codes to Gecko Code vector
|
|
synced_codes.push_back(std::move(gcode));
|
|
|
|
// Clear Vector if received 0 codes (match host's end when using no codes)
|
|
if (m_sync_gecko_codes_count == 0)
|
|
synced_codes.clear();
|
|
|
|
// Copy this to the vector located in GeckoCode.cpp
|
|
Gecko::UpdateSyncedCodes(synced_codes);
|
|
}
|
|
break;
|
|
|
|
case SYNC_CODES_NOTIFY_AR:
|
|
{
|
|
// Return if this is the host
|
|
if (m_local_player->IsHost())
|
|
return 0;
|
|
|
|
// Receive Number of Codelines to Receive
|
|
packet >> m_sync_ar_codes_count;
|
|
|
|
m_sync_ar_codes_success_count = 0;
|
|
|
|
NOTICE_LOG_FMT(ACTIONREPLAY, "Receiving {} AR codelines", m_sync_ar_codes_count);
|
|
|
|
// Check if no codes to sync, if so return as finished
|
|
if (m_sync_ar_codes_count == 0)
|
|
{
|
|
m_sync_ar_codes_complete = true;
|
|
SyncCodeResponse(true);
|
|
}
|
|
else
|
|
{
|
|
m_dialog->AppendChat(Common::GetStringT("Synchronizing AR codes..."));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SYNC_CODES_DATA_AR:
|
|
{
|
|
// Return if this is the host
|
|
if (m_local_player->IsHost())
|
|
return 0;
|
|
|
|
// Create a synced code vector
|
|
std::vector<ActionReplay::ARCode> synced_codes;
|
|
// Create an ARCode
|
|
ActionReplay::ARCode arcode;
|
|
arcode = ActionReplay::ARCode();
|
|
// Initialize arcode
|
|
arcode.name = "Synced Codes";
|
|
arcode.enabled = true;
|
|
|
|
// Receive code contents from packet
|
|
for (int i = 0; i < m_sync_ar_codes_count; i++)
|
|
{
|
|
ActionReplay::AREntry new_code;
|
|
packet >> new_code.cmd_addr;
|
|
packet >> new_code.value;
|
|
|
|
NOTICE_LOG_FMT(ACTIONREPLAY, "Received {:08x} {:08x}", new_code.cmd_addr, new_code.value);
|
|
arcode.ops.push_back(new_code);
|
|
|
|
if (++m_sync_ar_codes_success_count >= m_sync_ar_codes_count)
|
|
{
|
|
m_sync_ar_codes_complete = true;
|
|
SyncCodeResponse(true);
|
|
}
|
|
}
|
|
|
|
// Add arcode containing all codes to AR Code vector
|
|
synced_codes.push_back(std::move(arcode));
|
|
|
|
// Clear Vector if received 0 codes (match host's end when using no codes)
|
|
if (m_sync_ar_codes_count == 0)
|
|
synced_codes.clear();
|
|
|
|
// Copy this to the vector located in ActionReplay.cpp
|
|
ActionReplay::UpdateSyncedCodes(synced_codes);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_COMPUTE_MD5:
|
|
{
|
|
SyncIdentifier sync_identifier;
|
|
ReceiveSyncIdentifier(packet, sync_identifier);
|
|
|
|
ComputeMD5(sync_identifier);
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_MD5_PROGRESS:
|
|
{
|
|
PlayerId pid;
|
|
int progress;
|
|
packet >> pid;
|
|
packet >> progress;
|
|
|
|
m_dialog->SetMD5Progress(pid, progress);
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_MD5_RESULT:
|
|
{
|
|
PlayerId pid;
|
|
std::string result;
|
|
packet >> pid;
|
|
packet >> result;
|
|
|
|
m_dialog->SetMD5Result(pid, result);
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_MD5_ERROR:
|
|
{
|
|
PlayerId pid;
|
|
std::string error;
|
|
packet >> pid;
|
|
packet >> error;
|
|
|
|
m_dialog->SetMD5Result(pid, error);
|
|
}
|
|
break;
|
|
|
|
case NP_MSG_MD5_ABORT:
|
|
{
|
|
m_should_compute_MD5 = false;
|
|
m_dialog->AbortMD5();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
PanicAlertFmtT("Unknown message received with id : {0}", mid);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void NetPlayClient::Send(const sf::Packet& packet, const u8 channel_id)
|
|
{
|
|
ENetPacket* epac =
|
|
enet_packet_create(packet.getData(), packet.getDataSize(), ENET_PACKET_FLAG_RELIABLE);
|
|
enet_peer_send(m_server, channel_id, epac);
|
|
}
|
|
|
|
void NetPlayClient::DisplayPlayersPing()
|
|
{
|
|
if (!g_ActiveConfig.bShowNetPlayPing)
|
|
return;
|
|
|
|
OSD::AddTypedMessage(OSD::MessageType::NetPlayPing, fmt::format("Ping: {}", GetPlayersMaxPing()),
|
|
OSD::Duration::SHORT, OSD::Color::CYAN);
|
|
}
|
|
|
|
u32 NetPlayClient::GetPlayersMaxPing() const
|
|
{
|
|
return std::max_element(
|
|
m_players.begin(), m_players.end(),
|
|
[](const auto& a, const auto& b) { return a.second.ping < b.second.ping; })
|
|
->second.ping;
|
|
}
|
|
|
|
void NetPlayClient::Disconnect()
|
|
{
|
|
ENetEvent netEvent;
|
|
m_connecting = false;
|
|
m_connection_state = ConnectionState::Failure;
|
|
if (m_server)
|
|
enet_peer_disconnect(m_server, 0);
|
|
else
|
|
return;
|
|
|
|
while (enet_host_service(m_client, &netEvent, 3000) > 0)
|
|
{
|
|
switch (netEvent.type)
|
|
{
|
|
case ENET_EVENT_TYPE_RECEIVE:
|
|
enet_packet_destroy(netEvent.packet);
|
|
break;
|
|
case ENET_EVENT_TYPE_DISCONNECT:
|
|
m_server = nullptr;
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
// didn't disconnect gracefully force disconnect
|
|
enet_peer_reset(m_server);
|
|
m_server = nullptr;
|
|
}
|
|
|
|
void NetPlayClient::SendAsync(sf::Packet&& packet, const u8 channel_id)
|
|
{
|
|
{
|
|
std::lock_guard lkq(m_crit.async_queue_write);
|
|
m_async_queue.Push(AsyncQueueEntry{std::move(packet), channel_id});
|
|
}
|
|
ENetUtil::WakeupThread(m_client);
|
|
}
|
|
|
|
// called from ---NETPLAY--- thread
|
|
void NetPlayClient::ThreadFunc()
|
|
{
|
|
Common::QoSSession qos_session;
|
|
if (Config::Get(Config::NETPLAY_ENABLE_QOS))
|
|
{
|
|
qos_session = Common::QoSSession(m_server);
|
|
|
|
if (qos_session.Successful())
|
|
{
|
|
m_dialog->AppendChat(
|
|
Common::GetStringT("Quality of Service (QoS) was successfully enabled."));
|
|
}
|
|
else
|
|
{
|
|
m_dialog->AppendChat(Common::GetStringT("Quality of Service (QoS) couldn't be enabled."));
|
|
}
|
|
}
|
|
|
|
while (m_do_loop.IsSet())
|
|
{
|
|
ENetEvent netEvent;
|
|
int net;
|
|
if (m_traversal_client)
|
|
m_traversal_client->HandleResends();
|
|
net = enet_host_service(m_client, &netEvent, 250);
|
|
while (!m_async_queue.Empty())
|
|
{
|
|
{
|
|
auto& e = m_async_queue.Front();
|
|
Send(e.packet, e.channel_id);
|
|
}
|
|
m_async_queue.Pop();
|
|
}
|
|
if (net > 0)
|
|
{
|
|
sf::Packet rpac;
|
|
switch (netEvent.type)
|
|
{
|
|
case ENET_EVENT_TYPE_RECEIVE:
|
|
rpac.append(netEvent.packet->data, netEvent.packet->dataLength);
|
|
OnData(rpac);
|
|
|
|
enet_packet_destroy(netEvent.packet);
|
|
break;
|
|
case ENET_EVENT_TYPE_DISCONNECT:
|
|
m_dialog->OnConnectionLost();
|
|
|
|
if (m_is_running.IsSet())
|
|
StopGame();
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Disconnect();
|
|
return;
|
|
}
|
|
|
|
// called from ---GUI--- thread
|
|
void NetPlayClient::GetPlayerList(std::string& list, std::vector<int>& pid_list)
|
|
{
|
|
std::lock_guard lkp(m_crit.players);
|
|
|
|
std::ostringstream ss;
|
|
|
|
const auto enumerate_player_controller_mappings = [&ss](const PadMappingArray& mappings,
|
|
const Player& player) {
|
|
for (size_t i = 0; i < mappings.size(); i++)
|
|
{
|
|
if (mappings[i] == player.pid)
|
|
ss << i + 1;
|
|
else
|
|
ss << '-';
|
|
}
|
|
};
|
|
|
|
for (const auto& entry : m_players)
|
|
{
|
|
const Player& player = entry.second;
|
|
ss << player.name << "[" << static_cast<int>(player.pid) << "] : " << player.revision << " | ";
|
|
|
|
enumerate_player_controller_mappings(m_pad_map, player);
|
|
enumerate_player_controller_mappings(m_wiimote_map, player);
|
|
|
|
ss << " |\nPing: " << player.ping << "ms\n";
|
|
ss << "Status: ";
|
|
|
|
switch (player.game_status)
|
|
{
|
|
case SyncIdentifierComparison::SameGame:
|
|
ss << "ready";
|
|
break;
|
|
|
|
case SyncIdentifierComparison::DifferentVersion:
|
|
ss << "wrong game version";
|
|
break;
|
|
|
|
case SyncIdentifierComparison::DifferentGame:
|
|
ss << "game missing";
|
|
break;
|
|
|
|
default:
|
|
ss << "unknown";
|
|
break;
|
|
}
|
|
|
|
ss << "\n\n";
|
|
|
|
pid_list.push_back(player.pid);
|
|
}
|
|
|
|
list = ss.str();
|
|
}
|
|
|
|
// called from ---GUI--- thread
|
|
std::vector<const Player*> NetPlayClient::GetPlayers()
|
|
{
|
|
std::lock_guard lkp(m_crit.players);
|
|
std::vector<const Player*> players;
|
|
|
|
for (const auto& pair : m_players)
|
|
players.push_back(&pair.second);
|
|
|
|
return players;
|
|
}
|
|
|
|
const NetSettings& NetPlayClient::GetNetSettings() const
|
|
{
|
|
return m_net_settings;
|
|
}
|
|
|
|
// called from ---GUI--- thread
|
|
void NetPlayClient::SendChatMessage(const std::string& msg)
|
|
{
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_CHAT_MESSAGE);
|
|
packet << msg;
|
|
|
|
SendAsync(std::move(packet));
|
|
}
|
|
|
|
// called from ---CPU--- thread
|
|
void NetPlayClient::AddPadStateToPacket(const int in_game_pad, const GCPadStatus& pad,
|
|
sf::Packet& packet)
|
|
{
|
|
packet << static_cast<PadIndex>(in_game_pad);
|
|
packet << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX
|
|
<< pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected;
|
|
}
|
|
|
|
// called from ---CPU--- thread
|
|
void NetPlayClient::SendWiimoteState(const int in_game_pad, const WiimoteInput& nw)
|
|
{
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_WIIMOTE_DATA);
|
|
packet << static_cast<PadIndex>(in_game_pad);
|
|
packet << static_cast<u8>(nw.report_id);
|
|
packet << static_cast<u8>(nw.data.size());
|
|
packet.append(nw.data.data(), nw.data.size());
|
|
SendAsync(std::move(packet));
|
|
}
|
|
|
|
// called from ---GUI--- thread
|
|
void NetPlayClient::SendStartGamePacket()
|
|
{
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_START_GAME);
|
|
packet << m_current_game;
|
|
|
|
SendAsync(std::move(packet));
|
|
}
|
|
|
|
// called from ---GUI--- thread
|
|
void NetPlayClient::SendStopGamePacket()
|
|
{
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_STOP_GAME);
|
|
|
|
SendAsync(std::move(packet));
|
|
}
|
|
|
|
// called from ---GUI--- thread
|
|
bool NetPlayClient::StartGame(const std::string& path)
|
|
{
|
|
std::lock_guard lkg(m_crit.game);
|
|
SendStartGamePacket();
|
|
|
|
if (m_is_running.IsSet())
|
|
{
|
|
PanicAlertFmtT("Game is already running!");
|
|
return false;
|
|
}
|
|
|
|
m_timebase_frame = 0;
|
|
m_current_golfer = 1;
|
|
m_wait_on_input = false;
|
|
|
|
m_is_running.Set();
|
|
NetPlay_Enable(this);
|
|
|
|
ClearBuffers();
|
|
|
|
m_first_pad_status_received.fill(false);
|
|
|
|
if (m_dialog->IsRecording())
|
|
{
|
|
if (Movie::IsReadOnly())
|
|
Movie::SetReadOnly(false);
|
|
|
|
u8 controllers_mask = 0;
|
|
for (unsigned int i = 0; i < 4; ++i)
|
|
{
|
|
if (m_pad_map[i] > 0)
|
|
controllers_mask |= (1 << i);
|
|
if (m_wiimote_map[i] > 0)
|
|
controllers_mask |= (1 << (i + 4));
|
|
}
|
|
Movie::BeginRecordingInput(controllers_mask);
|
|
}
|
|
|
|
for (unsigned int i = 0; i < 4; ++i)
|
|
{
|
|
WiimoteCommon::SetSource(i,
|
|
m_wiimote_map[i] > 0 ? WiimoteSource::Emulated : WiimoteSource::None);
|
|
}
|
|
|
|
// boot game
|
|
m_dialog->BootGame(path);
|
|
|
|
UpdateDevices();
|
|
|
|
return true;
|
|
}
|
|
|
|
void NetPlayClient::SyncSaveDataResponse(const bool success)
|
|
{
|
|
m_dialog->AppendChat(success ? Common::GetStringT("Data received!") :
|
|
Common::GetStringT("Error processing data."));
|
|
|
|
if (success)
|
|
{
|
|
if (++m_sync_save_data_success_count >= m_sync_save_data_count)
|
|
{
|
|
sf::Packet response_packet;
|
|
response_packet << static_cast<MessageId>(NP_MSG_SYNC_SAVE_DATA);
|
|
response_packet << static_cast<MessageId>(SYNC_SAVE_DATA_SUCCESS);
|
|
|
|
Send(response_packet);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sf::Packet response_packet;
|
|
response_packet << static_cast<MessageId>(NP_MSG_SYNC_SAVE_DATA);
|
|
response_packet << static_cast<MessageId>(SYNC_SAVE_DATA_FAILURE);
|
|
|
|
Send(response_packet);
|
|
}
|
|
}
|
|
|
|
void NetPlayClient::SyncCodeResponse(const bool success)
|
|
{
|
|
// If something failed, immediately report back that code sync failed
|
|
if (!success)
|
|
{
|
|
m_dialog->AppendChat(Common::GetStringT("Error processing codes."));
|
|
|
|
sf::Packet response_packet;
|
|
response_packet << static_cast<MessageId>(NP_MSG_SYNC_CODES);
|
|
response_packet << static_cast<MessageId>(SYNC_CODES_FAILURE);
|
|
|
|
Send(response_packet);
|
|
return;
|
|
}
|
|
|
|
// If both gecko and AR codes have completely finished transferring, report back as successful
|
|
if (m_sync_gecko_codes_complete && m_sync_ar_codes_complete)
|
|
{
|
|
m_dialog->AppendChat(Common::GetStringT("Codes received!"));
|
|
|
|
sf::Packet response_packet;
|
|
response_packet << static_cast<MessageId>(NP_MSG_SYNC_CODES);
|
|
response_packet << static_cast<MessageId>(SYNC_CODES_SUCCESS);
|
|
|
|
Send(response_packet);
|
|
}
|
|
}
|
|
|
|
bool NetPlayClient::DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path)
|
|
{
|
|
u64 file_size = Common::PacketReadU64(packet);
|
|
|
|
if (file_size == 0)
|
|
return true;
|
|
|
|
File::IOFile file(file_path, "wb");
|
|
if (!file)
|
|
{
|
|
PanicAlertFmtT("Failed to open file \"{0}\". Verify your write permissions.", file_path);
|
|
return false;
|
|
}
|
|
|
|
std::vector<u8> in_buffer(NETPLAY_LZO_OUT_LEN);
|
|
std::vector<u8> out_buffer(NETPLAY_LZO_IN_LEN);
|
|
|
|
while (true)
|
|
{
|
|
u32 cur_len = 0; // number of bytes to read
|
|
lzo_uint new_len = 0; // number of bytes to write
|
|
|
|
packet >> cur_len;
|
|
if (!cur_len)
|
|
break; // We reached the end of the data stream
|
|
|
|
for (size_t j = 0; j < cur_len; j++)
|
|
{
|
|
packet >> in_buffer[j];
|
|
}
|
|
|
|
if (lzo1x_decompress(in_buffer.data(), cur_len, out_buffer.data(), &new_len, nullptr) !=
|
|
LZO_E_OK)
|
|
{
|
|
PanicAlertFmtT("Internal LZO Error - decompression failed");
|
|
return false;
|
|
}
|
|
|
|
if (!file.WriteBytes(out_buffer.data(), new_len))
|
|
{
|
|
PanicAlertFmtT("Error writing file: {0}", file_path);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::optional<std::vector<u8>> NetPlayClient::DecompressPacketIntoBuffer(sf::Packet& packet)
|
|
{
|
|
u64 size = Common::PacketReadU64(packet);
|
|
|
|
std::vector<u8> out_buffer(size);
|
|
|
|
if (size == 0)
|
|
return out_buffer;
|
|
|
|
std::vector<u8> in_buffer(NETPLAY_LZO_OUT_LEN);
|
|
|
|
lzo_uint i = 0;
|
|
while (true)
|
|
{
|
|
u32 cur_len = 0; // number of bytes to read
|
|
lzo_uint new_len = 0; // number of bytes to write
|
|
|
|
packet >> cur_len;
|
|
if (!cur_len)
|
|
break; // We reached the end of the data stream
|
|
|
|
for (size_t j = 0; j < cur_len; j++)
|
|
{
|
|
packet >> in_buffer[j];
|
|
}
|
|
|
|
if (lzo1x_decompress(in_buffer.data(), cur_len, &out_buffer[i], &new_len, nullptr) != LZO_E_OK)
|
|
{
|
|
PanicAlertFmtT("Internal LZO Error - decompression failed");
|
|
return {};
|
|
}
|
|
|
|
i += new_len;
|
|
}
|
|
|
|
return out_buffer;
|
|
}
|
|
|
|
// called from ---GUI--- thread
|
|
bool NetPlayClient::ChangeGame(const std::string&)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// called from ---NETPLAY--- thread
|
|
void NetPlayClient::UpdateDevices()
|
|
{
|
|
u8 local_pad = 0;
|
|
u8 pad = 0;
|
|
|
|
for (auto player_id : m_pad_map)
|
|
{
|
|
// Use local controller types for local controllers if they are compatible
|
|
// Only GCController-like controllers are supported, GBA and similar
|
|
// exotic devices are not supported on netplay.
|
|
if (player_id == m_local_player->pid)
|
|
{
|
|
if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[local_pad]))
|
|
{
|
|
SerialInterface::ChangeDevice(SConfig::GetInstance().m_SIDevice[local_pad], pad);
|
|
|
|
if (SConfig::GetInstance().m_SIDevice[local_pad] == SerialInterface::SIDEVICE_WIIU_ADAPTER)
|
|
{
|
|
GCAdapter::ResetDeviceType(local_pad);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SerialInterface::ChangeDevice(SerialInterface::SIDEVICE_GC_CONTROLLER, pad);
|
|
}
|
|
local_pad++;
|
|
}
|
|
else if (player_id > 0)
|
|
{
|
|
SerialInterface::ChangeDevice(SerialInterface::SIDEVICE_GC_CONTROLLER, pad);
|
|
}
|
|
else
|
|
{
|
|
SerialInterface::ChangeDevice(SerialInterface::SIDEVICE_NONE, pad);
|
|
}
|
|
pad++;
|
|
}
|
|
}
|
|
|
|
// called from ---NETPLAY--- thread
|
|
void NetPlayClient::ClearBuffers()
|
|
{
|
|
// clear pad buffers, Clear method isn't thread safe
|
|
for (unsigned int i = 0; i < 4; ++i)
|
|
{
|
|
while (m_pad_buffer[i].Size())
|
|
m_pad_buffer[i].Pop();
|
|
|
|
while (m_wiimote_buffer[i].Size())
|
|
m_wiimote_buffer[i].Pop();
|
|
}
|
|
}
|
|
|
|
// called from ---NETPLAY--- thread
|
|
void NetPlayClient::OnTraversalStateChanged()
|
|
{
|
|
const TraversalClient::State state = m_traversal_client->GetState();
|
|
|
|
if (m_connection_state == ConnectionState::WaitingForTraversalClientConnection &&
|
|
state == TraversalClient::State::Connected)
|
|
{
|
|
m_connection_state = ConnectionState::WaitingForTraversalClientConnectReady;
|
|
m_traversal_client->ConnectToClient(m_host_spec);
|
|
}
|
|
else if (m_connection_state != ConnectionState::Failure &&
|
|
state == TraversalClient::State::Failure)
|
|
{
|
|
Disconnect();
|
|
m_dialog->OnTraversalError(m_traversal_client->GetFailureReason());
|
|
}
|
|
m_dialog->OnTraversalStateChanged(state);
|
|
}
|
|
|
|
// called from ---NETPLAY--- thread
|
|
void NetPlayClient::OnConnectReady(ENetAddress addr)
|
|
{
|
|
if (m_connection_state == ConnectionState::WaitingForTraversalClientConnectReady)
|
|
{
|
|
m_connection_state = ConnectionState::Connecting;
|
|
enet_host_connect(m_client, &addr, CHANNEL_COUNT, 0);
|
|
}
|
|
}
|
|
|
|
// called from ---NETPLAY--- thread
|
|
void NetPlayClient::OnConnectFailed(TraversalConnectFailedReason reason)
|
|
{
|
|
m_connecting = false;
|
|
m_connection_state = ConnectionState::Failure;
|
|
switch (reason)
|
|
{
|
|
case TraversalConnectFailedReason::ClientDidntRespond:
|
|
PanicAlertFmtT("Traversal server timed out connecting to the host");
|
|
break;
|
|
case TraversalConnectFailedReason::ClientFailure:
|
|
PanicAlertFmtT("Server rejected traversal attempt");
|
|
break;
|
|
case TraversalConnectFailedReason::NoSuchClient:
|
|
PanicAlertFmtT("Invalid host");
|
|
break;
|
|
default:
|
|
PanicAlertFmtT("Unknown error {0:x}", reason);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// called from ---CPU--- thread
|
|
bool NetPlayClient::GetNetPads(const int pad_nb, const bool batching, GCPadStatus* pad_status)
|
|
{
|
|
// The interface for this is extremely silly.
|
|
//
|
|
// Imagine a physical device that links three GameCubes together
|
|
// and emulates NetPlay that way. Which GameCube controls which
|
|
// in-game controllers can be configured on the device (m_pad_map)
|
|
// but which sockets on each individual GameCube should be used
|
|
// to control which players? The solution that Dolphin uses is
|
|
// that we hardcode the knowledge that they go in order, so if
|
|
// you have a 3P game with three GameCubes, then every single
|
|
// controller should be plugged into slot 1.
|
|
//
|
|
// If you have a 4P game, then one of the GameCubes will have
|
|
// a controller plugged into slot 1, and another in slot 2.
|
|
//
|
|
// The slot number is the "local" pad number, and what player
|
|
// it actually means is the "in-game" pad number.
|
|
|
|
// When the 1st in-game pad is polled and batching is set, the
|
|
// others will be polled as well. To reduce latency, we poll all
|
|
// local controllers at once and then send the status to the other
|
|
// clients.
|
|
//
|
|
// Batching is enabled when polled from VI. If batching is not
|
|
// enabled, the poll is probably from MMIO, which can poll any
|
|
// specific pad arbitrarily. In this case, we poll just that pad
|
|
// and send it.
|
|
|
|
// When here when told to so we don't deadlock in certain situations
|
|
while (m_wait_on_input)
|
|
{
|
|
if (!m_is_running.IsSet())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_wait_on_input_received)
|
|
{
|
|
// Tell the server we've acknowledged the message
|
|
sf::Packet spac;
|
|
spac << static_cast<MessageId>(NP_MSG_GOLF_PREPARE);
|
|
Send(spac);
|
|
|
|
m_wait_on_input_received = false;
|
|
}
|
|
|
|
m_wait_on_input_event.Wait();
|
|
}
|
|
|
|
if (IsFirstInGamePad(pad_nb) && batching)
|
|
{
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_PAD_DATA);
|
|
|
|
bool send_packet = false;
|
|
const int num_local_pads = NumLocalPads();
|
|
for (int local_pad = 0; local_pad < num_local_pads; local_pad++)
|
|
{
|
|
send_packet = PollLocalPad(local_pad, packet) || send_packet;
|
|
}
|
|
|
|
if (send_packet)
|
|
SendAsync(std::move(packet));
|
|
|
|
if (m_host_input_authority)
|
|
SendPadHostPoll(-1);
|
|
}
|
|
|
|
if (!batching)
|
|
{
|
|
int local_pad = InGamePadToLocalPad(pad_nb);
|
|
if (local_pad < 4)
|
|
{
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_PAD_DATA);
|
|
if (PollLocalPad(local_pad, packet))
|
|
SendAsync(std::move(packet));
|
|
}
|
|
|
|
if (m_host_input_authority)
|
|
SendPadHostPoll(pad_nb);
|
|
}
|
|
|
|
if (m_host_input_authority)
|
|
{
|
|
if (m_local_player->pid != m_current_golfer)
|
|
{
|
|
// CoreTiming acts funny and causes what looks like frame skip if
|
|
// we toggle the emulation speed too quickly, so to prevent this
|
|
// we wait until the buffer has been over for at least 1 second.
|
|
|
|
const bool buffer_over_target = m_pad_buffer[pad_nb].Size() > m_target_buffer_size + 1;
|
|
if (!buffer_over_target)
|
|
m_buffer_under_target_last = std::chrono::steady_clock::now();
|
|
|
|
std::chrono::duration<double> time_diff =
|
|
std::chrono::steady_clock::now() - m_buffer_under_target_last;
|
|
if (time_diff.count() >= 1.0 || !buffer_over_target)
|
|
{
|
|
// run fast if the buffer is overfilled, otherwise run normal speed
|
|
SConfig::GetInstance().m_EmulationSpeed = buffer_over_target ? 0.0f : 1.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Set normal speed when we're the host, otherwise it can get stuck at unlimited
|
|
SConfig::GetInstance().m_EmulationSpeed = 1.0f;
|
|
}
|
|
}
|
|
|
|
// Now, we either use the data pushed earlier, or wait for the
|
|
// other clients to send it to us
|
|
while (m_pad_buffer[pad_nb].Size() == 0)
|
|
{
|
|
if (!m_is_running.IsSet())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_gc_pad_event.Wait();
|
|
}
|
|
|
|
m_pad_buffer[pad_nb].Pop(*pad_status);
|
|
|
|
if (Movie::IsRecordingInput())
|
|
{
|
|
Movie::RecordInput(pad_status, pad_nb);
|
|
Movie::InputUpdate();
|
|
}
|
|
else
|
|
{
|
|
Movie::CheckPadStatus(pad_status, pad_nb);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
u64 NetPlayClient::GetInitialRTCValue() const
|
|
{
|
|
return m_initial_rtc;
|
|
}
|
|
|
|
// called from ---CPU--- thread
|
|
bool NetPlayClient::WiimoteUpdate(int _number, u8* data, const std::size_t size, u8 reporting_mode)
|
|
{
|
|
WiimoteInput nw;
|
|
nw.report_id = reporting_mode;
|
|
{
|
|
std::lock_guard lkp(m_crit.players);
|
|
|
|
// Only send data, if this Wiimote is mapped to this player
|
|
if (m_wiimote_map[_number] == m_local_player->pid)
|
|
{
|
|
nw.data.assign(data, data + size);
|
|
|
|
// TODO: add a seperate setting for wiimote buffer?
|
|
while (m_wiimote_buffer[_number].Size() <= m_target_buffer_size * 200 / 120)
|
|
{
|
|
// add to buffer
|
|
m_wiimote_buffer[_number].Push(nw);
|
|
|
|
SendWiimoteState(_number, nw);
|
|
}
|
|
}
|
|
|
|
} // unlock players
|
|
|
|
while (m_wiimote_buffer[_number].Size() == 0)
|
|
{
|
|
if (!m_is_running.IsSet())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// wait for receiving thread to push some data
|
|
m_wii_pad_event.Wait();
|
|
}
|
|
|
|
m_wiimote_buffer[_number].Pop(nw);
|
|
|
|
// If the reporting mode has changed, we just need to pop through the buffer,
|
|
// until we reach a good input
|
|
if (nw.report_id != reporting_mode)
|
|
{
|
|
u32 tries = 0;
|
|
while (nw.report_id != reporting_mode)
|
|
{
|
|
while (m_wiimote_buffer[_number].Size() == 0)
|
|
{
|
|
if (!m_is_running.IsSet())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// wait for receiving thread to push some data
|
|
m_wii_pad_event.Wait();
|
|
}
|
|
|
|
m_wiimote_buffer[_number].Pop(nw);
|
|
|
|
++tries;
|
|
if (tries > m_target_buffer_size * 200 / 120)
|
|
break;
|
|
}
|
|
|
|
// If it still mismatches, it surely desynced
|
|
if (nw.report_id != reporting_mode)
|
|
{
|
|
PanicAlertFmtT("Netplay has desynced. There is no way to recover from this.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ASSERT(nw.data.size() == size);
|
|
std::copy(nw.data.begin(), nw.data.end(), data);
|
|
return true;
|
|
}
|
|
|
|
bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet)
|
|
{
|
|
GCPadStatus pad_status;
|
|
|
|
switch (SConfig::GetInstance().m_SIDevice[local_pad])
|
|
{
|
|
case SerialInterface::SIDEVICE_WIIU_ADAPTER:
|
|
pad_status = GCAdapter::Input(local_pad);
|
|
break;
|
|
case SerialInterface::SIDEVICE_GC_CONTROLLER:
|
|
default:
|
|
pad_status = Pad::GetStatus(local_pad);
|
|
break;
|
|
}
|
|
|
|
const int ingame_pad = LocalPadToInGamePad(local_pad);
|
|
bool data_added = false;
|
|
|
|
if (m_host_input_authority)
|
|
{
|
|
if (m_local_player->pid != m_current_golfer)
|
|
{
|
|
// add to packet
|
|
AddPadStateToPacket(ingame_pad, pad_status, packet);
|
|
data_added = true;
|
|
}
|
|
else
|
|
{
|
|
// set locally
|
|
m_last_pad_status[ingame_pad] = pad_status;
|
|
m_first_pad_status_received[ingame_pad] = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// adjust the buffer either up or down
|
|
// inserting multiple padstates or dropping states
|
|
while (m_pad_buffer[ingame_pad].Size() <= m_target_buffer_size)
|
|
{
|
|
// add to buffer
|
|
m_pad_buffer[ingame_pad].Push(pad_status);
|
|
|
|
// add to packet
|
|
AddPadStateToPacket(ingame_pad, pad_status, packet);
|
|
data_added = true;
|
|
}
|
|
}
|
|
|
|
return data_added;
|
|
}
|
|
|
|
void NetPlayClient::SendPadHostPoll(const PadIndex pad_num)
|
|
{
|
|
// Here we handle polling for the Host Input Authority and Golf modes. Pad data is "polled" from
|
|
// the most recent data received for the given pad. Passing pad_num < 0 will poll all assigned
|
|
// pads (used for batched polls), while 0..3 will poll the respective pad (used for MMIO polls).
|
|
// See GetNetPads for more details.
|
|
//
|
|
// If the local buffer is non-empty, we skip actually buffering and sending new pad data, this way
|
|
// don't end up with permanent local latency. It does create a period of time where no inputs are
|
|
// accepted, but under typical circumstances this is not noticeable.
|
|
//
|
|
// Additionally, we wait until some actual pad data has been received before buffering and sending
|
|
// it, otherwise controllers get calibrated wrongly with the default values of GCPadStatus.
|
|
|
|
if (m_local_player->pid != m_current_golfer)
|
|
return;
|
|
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_PAD_HOST_DATA);
|
|
|
|
if (pad_num < 0)
|
|
{
|
|
for (size_t i = 0; i < m_pad_map.size(); i++)
|
|
{
|
|
if (m_pad_map[i] <= 0)
|
|
continue;
|
|
|
|
while (!m_first_pad_status_received[i])
|
|
{
|
|
if (!m_is_running.IsSet())
|
|
return;
|
|
|
|
m_first_pad_status_received_event.Wait();
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < m_pad_map.size(); i++)
|
|
{
|
|
if (m_pad_map[i] == 0 || m_pad_buffer[i].Size() > 0)
|
|
continue;
|
|
|
|
const GCPadStatus& pad_status = m_last_pad_status[i];
|
|
m_pad_buffer[i].Push(pad_status);
|
|
AddPadStateToPacket(static_cast<int>(i), pad_status, packet);
|
|
}
|
|
}
|
|
else if (m_pad_map[pad_num] != 0)
|
|
{
|
|
while (!m_first_pad_status_received[pad_num])
|
|
{
|
|
if (!m_is_running.IsSet())
|
|
return;
|
|
|
|
m_first_pad_status_received_event.Wait();
|
|
}
|
|
|
|
if (m_pad_buffer[pad_num].Size() == 0)
|
|
{
|
|
const GCPadStatus& pad_status = m_last_pad_status[pad_num];
|
|
m_pad_buffer[pad_num].Push(pad_status);
|
|
AddPadStateToPacket(pad_num, pad_status, packet);
|
|
}
|
|
}
|
|
|
|
SendAsync(std::move(packet));
|
|
}
|
|
|
|
// called from ---GUI--- thread and ---NETPLAY--- thread (client side)
|
|
bool NetPlayClient::StopGame()
|
|
{
|
|
m_is_running.Clear();
|
|
|
|
// stop waiting for input
|
|
m_gc_pad_event.Set();
|
|
m_wii_pad_event.Set();
|
|
m_first_pad_status_received_event.Set();
|
|
m_wait_on_input_event.Set();
|
|
|
|
NetPlay_Disable();
|
|
|
|
// stop game
|
|
m_dialog->StopGame();
|
|
|
|
ClearWiiSyncData();
|
|
|
|
return true;
|
|
}
|
|
|
|
// called from ---GUI--- thread
|
|
void NetPlayClient::Stop()
|
|
{
|
|
if (!m_is_running.IsSet())
|
|
return;
|
|
|
|
m_is_running.Clear();
|
|
|
|
// stop waiting for input
|
|
m_gc_pad_event.Set();
|
|
m_wii_pad_event.Set();
|
|
m_first_pad_status_received_event.Set();
|
|
m_wait_on_input_event.Set();
|
|
|
|
// Tell the server to stop if we have a pad mapped in game.
|
|
if (LocalPlayerHasControllerMapped())
|
|
SendStopGamePacket();
|
|
else
|
|
StopGame();
|
|
}
|
|
|
|
void NetPlayClient::RequestStopGame()
|
|
{
|
|
// Tell the server to stop if we have a pad mapped in game.
|
|
if (LocalPlayerHasControllerMapped())
|
|
SendStopGamePacket();
|
|
}
|
|
|
|
void NetPlayClient::SendPowerButtonEvent()
|
|
{
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_POWER_BUTTON);
|
|
SendAsync(std::move(packet));
|
|
}
|
|
|
|
void NetPlayClient::RequestGolfControl(const PlayerId pid)
|
|
{
|
|
if (!m_host_input_authority || !m_net_settings.m_GolfMode)
|
|
return;
|
|
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_GOLF_REQUEST);
|
|
packet << pid;
|
|
SendAsync(std::move(packet));
|
|
}
|
|
|
|
void NetPlayClient::RequestGolfControl()
|
|
{
|
|
RequestGolfControl(m_local_player->pid);
|
|
}
|
|
|
|
// called from ---GUI--- thread
|
|
std::string NetPlayClient::GetCurrentGolfer()
|
|
{
|
|
std::lock_guard lkp(m_crit.players);
|
|
if (m_players.count(m_current_golfer))
|
|
return m_players[m_current_golfer].name;
|
|
return "";
|
|
}
|
|
|
|
// called from ---GUI--- thread
|
|
bool NetPlayClient::LocalPlayerHasControllerMapped() const
|
|
{
|
|
return PlayerHasControllerMapped(m_local_player->pid);
|
|
}
|
|
|
|
bool NetPlayClient::IsFirstInGamePad(int ingame_pad) const
|
|
{
|
|
return std::none_of(m_pad_map.begin(), m_pad_map.begin() + ingame_pad,
|
|
[](auto mapping) { return mapping > 0; });
|
|
}
|
|
|
|
int NetPlayClient::NumLocalPads() const
|
|
{
|
|
return static_cast<int>(std::count_if(m_pad_map.begin(), m_pad_map.end(), [this](auto mapping) {
|
|
return mapping == m_local_player->pid;
|
|
}));
|
|
}
|
|
|
|
int NetPlayClient::InGamePadToLocalPad(int ingame_pad) const
|
|
{
|
|
// not our pad
|
|
if (m_pad_map[ingame_pad] != m_local_player->pid)
|
|
return 4;
|
|
|
|
int local_pad = 0;
|
|
int pad = 0;
|
|
|
|
for (; pad < ingame_pad; pad++)
|
|
{
|
|
if (m_pad_map[pad] == m_local_player->pid)
|
|
local_pad++;
|
|
}
|
|
|
|
return local_pad;
|
|
}
|
|
|
|
int NetPlayClient::LocalPadToInGamePad(int local_pad) const
|
|
{
|
|
// Figure out which in-game pad maps to which local pad.
|
|
// The logic we have here is that the local slots always
|
|
// go in order.
|
|
int local_pad_count = -1;
|
|
int ingame_pad = 0;
|
|
for (; ingame_pad < 4; ingame_pad++)
|
|
{
|
|
if (m_pad_map[ingame_pad] == m_local_player->pid)
|
|
local_pad_count++;
|
|
|
|
if (local_pad_count == local_pad)
|
|
break;
|
|
}
|
|
|
|
return ingame_pad;
|
|
}
|
|
|
|
bool NetPlayClient::PlayerHasControllerMapped(const PlayerId pid) const
|
|
{
|
|
const auto mapping_matches_player_id = [pid](const PlayerId& mapping) { return mapping == pid; };
|
|
|
|
return std::any_of(m_pad_map.begin(), m_pad_map.end(), mapping_matches_player_id) ||
|
|
std::any_of(m_wiimote_map.begin(), m_wiimote_map.end(), mapping_matches_player_id);
|
|
}
|
|
|
|
bool NetPlayClient::IsLocalPlayer(const PlayerId pid) const
|
|
{
|
|
return pid == m_local_player->pid;
|
|
}
|
|
|
|
void NetPlayClient::SendTimeBase()
|
|
{
|
|
std::lock_guard lk(crit_netplay_client);
|
|
|
|
if (netplay_client->m_timebase_frame % 60 == 0)
|
|
{
|
|
const sf::Uint64 timebase = SystemTimers::GetFakeTimeBase();
|
|
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_TIMEBASE);
|
|
packet << timebase;
|
|
packet << netplay_client->m_timebase_frame;
|
|
|
|
netplay_client->SendAsync(std::move(packet));
|
|
}
|
|
|
|
netplay_client->m_timebase_frame++;
|
|
}
|
|
|
|
bool NetPlayClient::DoAllPlayersHaveGame()
|
|
{
|
|
std::lock_guard lkp(m_crit.players);
|
|
|
|
return std::all_of(std::begin(m_players), std::end(m_players), [](auto entry) {
|
|
return entry.second.game_status == SyncIdentifierComparison::SameGame;
|
|
});
|
|
}
|
|
|
|
void NetPlayClient::ComputeMD5(const SyncIdentifier& sync_identifier)
|
|
{
|
|
if (m_should_compute_MD5)
|
|
return;
|
|
|
|
m_dialog->ShowMD5Dialog(sync_identifier.game_id);
|
|
m_should_compute_MD5 = true;
|
|
|
|
std::string file;
|
|
if (sync_identifier == GetSDCardIdentifier())
|
|
file = File::GetUserPath(F_WIISDCARD_IDX);
|
|
else if (auto game = m_dialog->FindGameFile(sync_identifier))
|
|
file = game->GetFilePath();
|
|
|
|
if (file.empty() || !File::Exists(file))
|
|
{
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_MD5_ERROR);
|
|
packet << "file not found";
|
|
Send(packet);
|
|
return;
|
|
}
|
|
|
|
if (m_MD5_thread.joinable())
|
|
m_MD5_thread.join();
|
|
m_MD5_thread = std::thread([this, file]() {
|
|
std::string sum = MD5::MD5Sum(file, [&](int progress) {
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_MD5_PROGRESS);
|
|
packet << progress;
|
|
SendAsync(std::move(packet));
|
|
|
|
return m_should_compute_MD5;
|
|
});
|
|
|
|
sf::Packet packet;
|
|
packet << static_cast<MessageId>(NP_MSG_MD5_RESULT);
|
|
packet << sum;
|
|
SendAsync(std::move(packet));
|
|
});
|
|
}
|
|
|
|
const PadMappingArray& NetPlayClient::GetPadMapping() const
|
|
{
|
|
return m_pad_map;
|
|
}
|
|
|
|
const PadMappingArray& NetPlayClient::GetWiimoteMapping() const
|
|
{
|
|
return m_wiimote_map;
|
|
}
|
|
|
|
void NetPlayClient::AdjustPadBufferSize(const unsigned int size)
|
|
{
|
|
m_target_buffer_size = size;
|
|
m_dialog->OnPadBufferChanged(size);
|
|
}
|
|
|
|
SyncIdentifier NetPlayClient::GetSDCardIdentifier()
|
|
{
|
|
return SyncIdentifier{{}, "sd", {}, {}, {}, {}};
|
|
}
|
|
|
|
bool IsNetPlayRunning()
|
|
{
|
|
return netplay_client != nullptr;
|
|
}
|
|
|
|
const NetSettings& GetNetSettings()
|
|
{
|
|
ASSERT(IsNetPlayRunning());
|
|
return netplay_client->GetNetSettings();
|
|
}
|
|
|
|
IOS::HLE::FS::FileSystem* GetWiiSyncFS()
|
|
{
|
|
return s_wii_sync_fs.get();
|
|
}
|
|
|
|
const std::vector<u64>& GetWiiSyncTitles()
|
|
{
|
|
return s_wii_sync_titles;
|
|
}
|
|
|
|
void SetWiiSyncData(std::unique_ptr<IOS::HLE::FS::FileSystem> fs, const std::vector<u64>& titles)
|
|
{
|
|
s_wii_sync_fs = std::move(fs);
|
|
s_wii_sync_titles.insert(s_wii_sync_titles.end(), titles.begin(), titles.end());
|
|
}
|
|
|
|
void ClearWiiSyncData()
|
|
{
|
|
// We're just assuming it will always be here because it is
|
|
const std::string path = File::GetUserPath(D_USER_IDX) + "Wii" GC_MEMCARD_NETPLAY DIR_SEP;
|
|
if (File::Exists(path))
|
|
File::DeleteDirRecursively(path);
|
|
|
|
s_wii_sync_fs.reset();
|
|
s_wii_sync_titles.clear();
|
|
}
|
|
|
|
void SetSIPollBatching(bool state)
|
|
{
|
|
s_si_poll_batching = state;
|
|
}
|
|
|
|
void SendPowerButtonEvent()
|
|
{
|
|
ASSERT(IsNetPlayRunning());
|
|
netplay_client->SendPowerButtonEvent();
|
|
}
|
|
|
|
bool IsSyncingAllWiiSaves()
|
|
{
|
|
std::lock_guard lk(crit_netplay_client);
|
|
|
|
if (netplay_client)
|
|
return netplay_client->GetNetSettings().m_SyncAllWiiSaves;
|
|
|
|
return false;
|
|
}
|
|
|
|
void SetupWiimotes()
|
|
{
|
|
ASSERT(IsNetPlayRunning());
|
|
const NetSettings& netplay_settings = netplay_client->GetNetSettings();
|
|
const PadMappingArray& wiimote_map = netplay_client->GetWiimoteMapping();
|
|
for (size_t i = 0; i < netplay_settings.m_WiimoteExtension.size(); i++)
|
|
{
|
|
if (wiimote_map[i] > 0)
|
|
{
|
|
static_cast<ControllerEmu::Attachments*>(
|
|
static_cast<WiimoteEmu::Wiimote*>(Wiimote::GetConfig()->GetController(int(i)))
|
|
->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Attachments))
|
|
->SetSelectedAttachment(netplay_settings.m_WiimoteExtension[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NetPlay_Enable(NetPlayClient* const np)
|
|
{
|
|
std::lock_guard lk(crit_netplay_client);
|
|
netplay_client = np;
|
|
}
|
|
|
|
void NetPlay_Disable()
|
|
{
|
|
std::lock_guard lk(crit_netplay_client);
|
|
netplay_client = nullptr;
|
|
}
|
|
} // namespace NetPlay
|
|
|
|
// stuff hacked into dolphin
|
|
|
|
// called from ---CPU--- thread
|
|
// Actual Core function which is called on every frame
|
|
bool SerialInterface::CSIDevice_GCController::NetPlay_GetInput(int pad_num, GCPadStatus* status)
|
|
{
|
|
std::lock_guard lk(NetPlay::crit_netplay_client);
|
|
|
|
if (NetPlay::netplay_client)
|
|
return NetPlay::netplay_client->GetNetPads(pad_num, NetPlay::s_si_poll_batching, status);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool WiimoteEmu::Wiimote::NetPlay_GetWiimoteData(int wiimote, u8* data, u8 size, u8 reporting_mode)
|
|
{
|
|
std::lock_guard lk(NetPlay::crit_netplay_client);
|
|
|
|
if (NetPlay::netplay_client)
|
|
return NetPlay::netplay_client->WiimoteUpdate(wiimote, data, size, reporting_mode);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Sync the info whether a button was pressed or not. Used for the reconnect on button press feature
|
|
bool Wiimote::NetPlay_GetButtonPress(int wiimote, bool pressed)
|
|
{
|
|
std::lock_guard lk(NetPlay::crit_netplay_client);
|
|
|
|
// Use the reporting mode 0 for the button pressed event, the real ones start at RT_REPORT_CORE
|
|
static const u8 BUTTON_PRESS_REPORTING_MODE = 0;
|
|
|
|
if (NetPlay::netplay_client)
|
|
{
|
|
std::array<u8, 1> data = {u8(pressed)};
|
|
if (NetPlay::netplay_client->WiimoteUpdate(wiimote, data.data(), data.size(),
|
|
BUTTON_PRESS_REPORTING_MODE))
|
|
{
|
|
return data[0];
|
|
}
|
|
PanicAlertFmtT("Netplay has desynced in NetPlay_GetButtonPress()");
|
|
return false;
|
|
}
|
|
|
|
return pressed;
|
|
}
|
|
|
|
// called from ---CPU--- thread
|
|
// so all players' games get the same time
|
|
//
|
|
// also called from ---GUI--- thread when starting input recording
|
|
u64 ExpansionInterface::CEXIIPL::NetPlay_GetEmulatedTime()
|
|
{
|
|
std::lock_guard lk(NetPlay::crit_netplay_client);
|
|
|
|
if (NetPlay::netplay_client)
|
|
return NetPlay::netplay_client->GetInitialRTCValue();
|
|
|
|
return 0;
|
|
}
|
|
|
|
// called from ---CPU--- thread
|
|
// return the local pad num that should rumble given a ingame pad num
|
|
int SerialInterface::CSIDevice_GCController::NetPlay_InGamePadToLocalPad(int numPAD)
|
|
{
|
|
std::lock_guard lk(NetPlay::crit_netplay_client);
|
|
|
|
if (NetPlay::netplay_client)
|
|
return NetPlay::netplay_client->InGamePadToLocalPad(numPAD);
|
|
|
|
return numPAD;
|
|
}
|