@@ -192,6 +192,7 @@ class NetPlayClient : public TraversalClientClient
void SendStopGamePacket();

void SyncSaveDataResponse(bool success);
void SyncCodeResponse(bool success);
bool DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path);
std::optional<std::vector<u8>> DecompressPacketIntoBuffer(sf::Packet& packet);

@@ -226,6 +227,12 @@ class NetPlayClient : public TraversalClientClient
Common::Event m_first_pad_status_received_event;
u8 m_sync_save_data_count = 0;
u8 m_sync_save_data_success_count = 0;
u16 m_sync_gecko_codes_count = 0;
u16 m_sync_gecko_codes_success_count = 0;
bool m_sync_gecko_codes_complete = false;
u16 m_sync_ar_codes_count = 0;
u16 m_sync_ar_codes_success_count = 0;
bool m_sync_ar_codes_complete = false;

u64 m_initial_rtc = 0;
u32 m_timebase_frame = 0;
@@ -77,6 +77,7 @@ struct NetSettings
bool m_DeferEFBCopies;
bool m_StrictSettingsSync;
bool m_SyncSaveData;
bool m_SyncCodes;
std::string m_SaveDataRegion;
bool m_IsHosting;
bool m_HostInputAuthority;
@@ -145,6 +146,7 @@ enum

NP_MSG_SYNC_GC_SRAM = 0xF0,
NP_MSG_SYNC_SAVE_DATA = 0xF1,
NP_MSG_SYNC_CODES = 0xF2,
};

enum
@@ -164,6 +166,17 @@ enum
SYNC_SAVE_DATA_WII = 5
};

enum
{
SYNC_CODES_NOTIFY = 0,
SYNC_CODES_NOTIFY_GECKO = 1,
SYNC_CODES_NOTIFY_AR = 2,
SYNC_CODES_DATA_GECKO = 3,
SYNC_CODES_DATA_AR = 4,
SYNC_CODES_SUCCESS = 5,
SYNC_CODES_FAILURE = 6,
};

constexpr u32 NETPLAY_LZO_IN_LEN = 1024 * 64;
constexpr u32 NETPLAY_LZO_OUT_LEN = NETPLAY_LZO_IN_LEN + (NETPLAY_LZO_IN_LEN / 16) + 64 + 3;

@@ -29,9 +29,13 @@
#include "Common/StringUtil.h"
#include "Common/UPnP.h"
#include "Common/Version.h"
#include "Core/ActionReplay.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/NetplaySettings.h"
#include "Core/ConfigLoaders/GameConfigLoader.h"
#include "Core/ConfigManager.h"
#include "Core/GeckoCode.h"
#include "Core/GeckoCodeConfig.h"
#include "Core/HW/GCMemcard/GCMemcardDirectory.h"
#include "Core/HW/GCMemcard/GCMemcardRaw.h"
#include "Core/HW/Sram.h"
@@ -852,8 +856,11 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
m_save_data_synced_players++;
if (m_save_data_synced_players >= m_players.size() - 1)
{
m_dialog->AppendChat(GetStringT("All players synchronized."));
StartGame();
m_dialog->AppendChat(GetStringT("All players saves synchronized."));

// Saves are synced, check if codes are as well and attempt to start the game
m_saves_synced = true;
CheckSyncAndStartGame();
}
}
}
@@ -877,6 +884,48 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
}
break;

case NP_MSG_SYNC_CODES:
{
// Receive Status of Code Sync
MessageId sub_id;
packet >> sub_id;

// Check If Code Sync was successful or not
switch (sub_id)
{
case SYNC_CODES_SUCCESS:
{
if (m_start_pending)
{
if (++m_codes_synced_players >= m_players.size() - 1)
{
m_dialog->AppendChat(GetStringT("All players' codes synchronized."));

// Codes are synced, check if saves are as well and attempt to start the game
m_codes_synced = true;
CheckSyncAndStartGame();
}
}
}
break;

case SYNC_CODES_FAILURE:
{
m_dialog->AppendChat(StringFromFormat(GetStringT("%s failed to synchronize codes.").c_str(),
player.name.c_str()));
m_start_pending = false;
}
break;

default:
PanicAlertT(
"Unknown SYNC_GECKO_CODES message with id:%d received from player:%d Kicking player!",
sub_id, player.pid);
return 1;
}
}
break;

default:
PanicAlertT("Unknown message with id:%d received from player:%d Kicking player!", mid,
player.pid);
@@ -966,17 +1015,34 @@ bool NetPlayServer::DoAllPlayersHaveIPLDump() const
// called from ---GUI--- thread
bool NetPlayServer::RequestStartGame()
{
bool start_now = true;

if (m_settings.m_SyncSaveData && m_players.size() > 1)
{
start_now = false;
m_start_pending = true;
if (!SyncSaveData())
{
PanicAlertT("Error synchronizing save data!");
m_start_pending = false;
return false;
}
}

// Check To Send Codes to Clients
if (m_settings.m_SyncCodes && m_players.size() > 1)
{
start_now = false;
m_start_pending = true;
if (!SyncCodes())
{
PanicAlertT("Error synchronizing save gecko codes!");
m_start_pending = false;
return false;
}
}
else

if (start_now)
{
return StartGame();
}
@@ -1065,6 +1131,7 @@ bool NetPlayServer::StartGame()
spac << initial_rtc;
spac << m_settings.m_SyncSaveData;
spac << region;
spac << m_settings.m_SyncCodes;

SendAsyncToClients(std::move(spac));

@@ -1077,6 +1144,9 @@ bool NetPlayServer::StartGame()
// called from ---GUI--- thread
bool NetPlayServer::SyncSaveData()
{
// We're about to sync saves, so set m_saves_synced to false (waits to start game)
m_saves_synced = false;

m_save_data_synced_players = 0;

u8 save_count = 0;
@@ -1253,6 +1323,150 @@ bool NetPlayServer::SyncSaveData()
return true;
}

bool NetPlayServer::SyncCodes()
{
// Sync Codes is ticked, so set m_codes_synced to false
m_codes_synced = false;

// Get Game Path
const auto game = m_dialog->FindGameFile(m_selected_game);
if (game == nullptr)
{
PanicAlertT("Selected game doesn't exist in game list!");
return false;
}

// Find all INI files
const auto game_id = game->GetGameID();
const auto revision = game->GetRevision();
IniFile globalIni;
for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(game_id, revision))
globalIni.Load(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename, true);
IniFile localIni;
for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(game_id, revision))
localIni.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + filename, true);

// Initialize Number of Synced Players
m_codes_synced_players = 0;

// Notify Clients of Incoming Code Sync
{
sf::Packet pac;
pac << static_cast<MessageId>(NP_MSG_SYNC_CODES);
pac << static_cast<MessageId>(SYNC_CODES_NOTIFY);
SendAsyncToClients(std::move(pac));
}
// Sync Gecko Codes
{
// Create a Gecko Code Vector with just the active codes
std::vector<Gecko::GeckoCode> s_active_codes =
Gecko::SetAndReturnActiveCodes(Gecko::LoadCodes(globalIni, localIni));

// Determine Codelist Size
u16 codelines = 0;
for (const Gecko::GeckoCode& active_code : s_active_codes)
{
NOTICE_LOG(ACTIONREPLAY, "Indexing %s", active_code.name.c_str());
for (const Gecko::GeckoCode::Code& code : active_code.codes)
{
NOTICE_LOG(ACTIONREPLAY, "%08x %08x", code.address, code.data);
codelines++;
}
}

// Output codelines to send
NOTICE_LOG(ACTIONREPLAY, "Sending %d Gecko codelines", codelines);

// Send initial packet. Notify of the sync operation and total number of lines being sent.
{
sf::Packet pac;
pac << static_cast<MessageId>(NP_MSG_SYNC_CODES);
pac << static_cast<MessageId>(SYNC_CODES_NOTIFY_GECKO);
pac << codelines;
SendAsyncToClients(std::move(pac));
}

// Send entire codeset in the second packet
{
sf::Packet pac;
pac << static_cast<MessageId>(NP_MSG_SYNC_CODES);
pac << static_cast<MessageId>(SYNC_CODES_DATA_GECKO);
// Iterate through the active code vector and send each codeline
for (const Gecko::GeckoCode& active_code : s_active_codes)
{
NOTICE_LOG(ACTIONREPLAY, "Sending %s", active_code.name.c_str());
for (const Gecko::GeckoCode::Code& code : active_code.codes)
{
NOTICE_LOG(ACTIONREPLAY, "%08x %08x", code.address, code.data);
pac << code.address;
pac << code.data;
}
}
SendAsyncToClients(std::move(pac));
}
}

// Sync AR Codes
{
// Create an AR Code Vector with just the active codes
std::vector<ActionReplay::ARCode> s_active_codes =
ActionReplay::ApplyAndReturnCodes(ActionReplay::LoadCodes(globalIni, localIni));

// Determine Codelist Size
u16 codelines = 0;
for (const ActionReplay::ARCode& active_code : s_active_codes)
{
NOTICE_LOG(ACTIONREPLAY, "Indexing %s", active_code.name.c_str());
for (const ActionReplay::AREntry& op : active_code.ops)
{
NOTICE_LOG(ACTIONREPLAY, "%08x %08x", op.cmd_addr, op.value);
codelines++;
}
}

// Output codelines to send
NOTICE_LOG(ACTIONREPLAY, "Sending %d AR codelines", codelines);

// Send initial packet. Notify of the sync operation and total number of lines being sent.
{
sf::Packet pac;
pac << static_cast<MessageId>(NP_MSG_SYNC_CODES);
pac << static_cast<MessageId>(SYNC_CODES_NOTIFY_AR);
pac << codelines;
SendAsyncToClients(std::move(pac));
}

// Send entire codeset in the second packet
{
sf::Packet pac;
pac << static_cast<MessageId>(NP_MSG_SYNC_CODES);
pac << static_cast<MessageId>(SYNC_CODES_DATA_AR);
// Iterate through the active code vector and send each codeline
for (const ActionReplay::ARCode& active_code : s_active_codes)
{
NOTICE_LOG(ACTIONREPLAY, "Sending %s", active_code.name.c_str());
for (const ActionReplay::AREntry& op : active_code.ops)
{
NOTICE_LOG(ACTIONREPLAY, "%08x %08x", op.cmd_addr, op.value);
pac << op.cmd_addr;
pac << op.value;
}
}
SendAsyncToClients(std::move(pac));
}
}

return true;
}

void NetPlayServer::CheckSyncAndStartGame()
{
if (m_saves_synced && m_codes_synced)
{
StartGame();
}
}

bool NetPlayServer::CompressFileIntoPacket(const std::string& file_path, sf::Packet& packet)
{
File::IOFile file(file_path, "rb");
@@ -85,6 +85,8 @@ class NetPlayServer : public TraversalClientClient
};

bool SyncSaveData();
bool SyncCodes();
void CheckSyncAndStartGame();
bool CompressFileIntoPacket(const std::string& file_path, sf::Packet& packet);
bool CompressBufferIntoPacket(const std::vector<u8>& in_buffer, sf::Packet& packet);
void SendFirstReceivedToHost(PadMapping map, bool state);
@@ -116,6 +118,9 @@ class NetPlayServer : public TraversalClientClient
PadMappingArray m_pad_map;
PadMappingArray m_wiimote_map;
unsigned int m_save_data_synced_players = 0;
unsigned int m_codes_synced_players = 0;
bool m_saves_synced = true;
bool m_codes_synced = true;
bool m_start_pending = false;
bool m_host_input_authority = false;

@@ -21,6 +21,7 @@
#include "Common/StringUtil.h"

#include "Core/ActionReplay.h"
#include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h"
#include "Core/GeckoCode.h"
#include "Core/GeckoCodeConfig.h"
@@ -164,9 +165,18 @@ void LoadPatches()
IniFile localIni = SConfig::GetInstance().LoadLocalGameIni();

LoadPatchSection("OnFrame", s_on_frame, globalIni, localIni);
ActionReplay::LoadAndApplyCodes(globalIni, localIni);

Gecko::SetActiveCodes(Gecko::LoadCodes(globalIni, localIni));
// Check if I'm syncing Codes
if (Config::Get(Config::MAIN_CODE_SYNC_OVERRIDE))
{
Gecko::SetSyncedCodesAsActive();
ActionReplay::SetSyncedCodesAsActive();
}
else
{
Gecko::SetActiveCodes(Gecko::LoadCodes(globalIni, localIni));
ActionReplay::LoadAndApplyCodes(globalIni, localIni);
}

LoadSpeedhacks("Speedhacks", merged);
}
@@ -77,6 +77,7 @@ NetPlayDialog::NetPlayDialog(QWidget* parent)
const bool write_save_sdcard_data = Config::Get(Config::NETPLAY_WRITE_SAVE_SDCARD_DATA);
const bool load_wii_save = Config::Get(Config::NETPLAY_LOAD_WII_SAVE);
const bool sync_saves = Config::Get(Config::NETPLAY_SYNC_SAVES);
const bool sync_codes = Config::Get(Config::NETPLAY_SYNC_CODES);
const bool record_inputs = Config::Get(Config::NETPLAY_RECORD_INPUTS);
const bool reduce_polling_rate = Config::Get(Config::NETPLAY_REDUCE_POLLING_RATE);
const bool strict_settings_sync = Config::Get(Config::NETPLAY_STRICT_SETTINGS_SYNC);
@@ -86,6 +87,7 @@ NetPlayDialog::NetPlayDialog(QWidget* parent)
m_save_sd_box->setChecked(write_save_sdcard_data);
m_load_wii_box->setChecked(load_wii_save);
m_sync_save_data_box->setChecked(sync_saves);
m_sync_codes_box->setChecked(sync_codes);
m_record_input_box->setChecked(record_inputs);
m_reduce_polling_rate_box->setChecked(reduce_polling_rate);
m_strict_settings_sync_box->setChecked(strict_settings_sync);
@@ -121,6 +123,7 @@ void NetPlayDialog::CreateMainLayout()
m_reduce_polling_rate_box = new QCheckBox(tr("Reduce Polling Rate"));
m_strict_settings_sync_box = new QCheckBox(tr("Strict Settings Sync"));
m_host_input_authority_box = new QCheckBox(tr("Host Input Authority"));
m_sync_codes_box = new QCheckBox(tr("Sync Codes"));
m_buffer_label = new QLabel(tr("Buffer:"));
m_quit_button = new QPushButton(tr("Quit"));
m_splitter = new QSplitter(Qt::Horizontal);
@@ -129,6 +132,7 @@ void NetPlayDialog::CreateMainLayout()
m_game_button->setAutoDefault(false);

m_sync_save_data_box->setChecked(true);
m_sync_codes_box->setChecked(true);

auto* default_button = new QAction(tr("Calculate MD5 hash"), m_md5_button);

@@ -164,6 +168,9 @@ void NetPlayDialog::CreateMainLayout()
tr("This will sync additional graphics settings, and force everyone to the same internal "
"resolution.\nMay prevent desync in some games that use EFB reads. Please ensure everyone "
"uses the same video backend."));
m_sync_codes_box->setToolTip(tr("This will sync the client's AR and Gecko Codes with the host's. "
"The client will be sent the codes regardless "
"\nof whether or not the client has them."));
m_host_input_authority_box->setToolTip(
tr("This gives the host control over when inputs are sent to the game, effectively "
"decoupling players from each other in terms of buffering.\nThis allows players to have "
@@ -190,6 +197,7 @@ void NetPlayDialog::CreateMainLayout()
options_boxes->addWidget(m_save_sd_box);
options_boxes->addWidget(m_load_wii_box);
options_boxes->addWidget(m_sync_save_data_box);
options_boxes->addWidget(m_sync_codes_box);
options_boxes->addWidget(m_record_input_box);
options_boxes->addWidget(m_reduce_polling_rate_box);
options_boxes->addWidget(m_strict_settings_sync_box);
@@ -335,6 +343,7 @@ void NetPlayDialog::ConnectWidgets()
connect(m_save_sd_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings);
connect(m_load_wii_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings);
connect(m_sync_save_data_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings);
connect(m_sync_codes_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings);
connect(m_record_input_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings);
connect(m_reduce_polling_rate_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings);
connect(m_strict_settings_sync_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings);
@@ -451,6 +460,7 @@ void NetPlayDialog::OnStart()
settings.m_DeferEFBCopies = Config::Get(Config::GFX_HACK_DEFER_EFB_COPIES);
settings.m_StrictSettingsSync = m_strict_settings_sync_box->isChecked();
settings.m_SyncSaveData = m_sync_save_data_box->isChecked();
settings.m_SyncCodes = m_sync_codes_box->isChecked();

// Unload GameINI to restore things to normal
Config::RemoveLayer(Config::LayerType::GlobalGame);
@@ -499,6 +509,7 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal)
m_save_sd_box->setHidden(!is_hosting);
m_load_wii_box->setHidden(!is_hosting);
m_sync_save_data_box->setHidden(!is_hosting);
m_sync_codes_box->setHidden(!is_hosting);
m_reduce_polling_rate_box->setHidden(!is_hosting);
m_strict_settings_sync_box->setHidden(!is_hosting);
m_host_input_authority_box->setHidden(!is_hosting);
@@ -772,6 +783,7 @@ void NetPlayDialog::SetOptionsEnabled(bool enabled)
m_load_wii_box->setEnabled(enabled);
m_save_sd_box->setEnabled(enabled);
m_sync_save_data_box->setEnabled(enabled);
m_sync_codes_box->setEnabled(enabled);
m_assign_ports_button->setEnabled(enabled);
m_reduce_polling_rate_box->setEnabled(enabled);
m_strict_settings_sync_box->setEnabled(enabled);
@@ -965,6 +977,7 @@ void NetPlayDialog::SaveSettings()
Config::SetBase(Config::NETPLAY_WRITE_SAVE_SDCARD_DATA, m_save_sd_box->isChecked());
Config::SetBase(Config::NETPLAY_LOAD_WII_SAVE, m_load_wii_box->isChecked());
Config::SetBase(Config::NETPLAY_SYNC_SAVES, m_sync_save_data_box->isChecked());
Config::SetBase(Config::NETPLAY_SYNC_CODES, m_sync_codes_box->isChecked());
Config::SetBase(Config::NETPLAY_RECORD_INPUTS, m_record_input_box->isChecked());
Config::SetBase(Config::NETPLAY_REDUCE_POLLING_RATE, m_reduce_polling_rate_box->isChecked());
Config::SetBase(Config::NETPLAY_STRICT_SETTINGS_SYNC, m_strict_settings_sync_box->isChecked());
@@ -110,6 +110,7 @@ class NetPlayDialog : public QDialog, public NetPlay::NetPlayUI
QCheckBox* m_save_sd_box;
QCheckBox* m_load_wii_box;
QCheckBox* m_sync_save_data_box;
QCheckBox* m_sync_codes_box;
QCheckBox* m_record_input_box;
QCheckBox* m_reduce_polling_rate_box;
QCheckBox* m_strict_settings_sync_box;