@@ -23,6 +23,13 @@
#include "Core/SyncIdentifier.h"
#include "InputCommon/GCPadStatus.h"

class BootSessionData;

namespace IOS::HLE::FS
{
class FileSystem;
}

namespace UICommon
{
class GameFile;
@@ -34,7 +41,8 @@ class NetPlayUI
{
public:
virtual ~NetPlayUI() {}
virtual void BootGame(const std::string& filename) = 0;
virtual void BootGame(const std::string& filename,
std::unique_ptr<BootSessionData> boot_session_data) = 0;
virtual void StopGame() = 0;
virtual bool IsHosting() const = 0;

@@ -77,6 +85,8 @@ class NetPlayUI
const std::vector<int>& players) = 0;
virtual void HideChunkedProgressDialog() = 0;
virtual void SetChunkedProgress(int pid, u64 progress) = 0;

virtual void SetHostWiiSyncTitles(std::vector<u64> titles) = 0;
};

class Player
@@ -147,6 +157,8 @@ class NetPlayClient : public TraversalClientClient

void AdjustPadBufferSize(unsigned int size);

void SetWiiSyncData(std::unique_ptr<IOS::HLE::FS::FileSystem> fs, std::vector<u64> titles);

static SyncIdentifier GetSDCardIdentifier();

protected:
@@ -313,6 +325,9 @@ class NetPlayClient : public TraversalClientClient

u64 m_initial_rtc = 0;
u32 m_timebase_frame = 0;

std::unique_ptr<IOS::HLE::FS::FileSystem> m_wii_sync_fs;
std::vector<u64> m_wii_sync_titles;
};

void NetPlay_Enable(NetPlayClient* const np);
@@ -257,10 +257,6 @@ bool IsNetPlayRunning();
// Precondition: A netplay client instance must be present. In other words,
// IsNetPlayRunning() must be true before calling this.
const NetSettings& GetNetSettings();
IOS::HLE::FS::FileSystem* GetWiiSyncFS();
const std::vector<u64>& GetWiiSyncTitles();
void SetWiiSyncData(std::unique_ptr<IOS::HLE::FS::FileSystem> fs, const std::vector<u64>& titles);
void ClearWiiSyncData();
void SetSIPollBatching(bool state);
void SendPowerButtonEvent();
bool IsSyncingAllWiiSaves();
@@ -1819,7 +1819,7 @@ bool NetPlayServer::SyncSaveData()
}

// Set titles for host-side loading in WiiRoot
SetWiiSyncData(nullptr, titles);
m_dialog->SetHostWiiSyncTitles(std::move(titles));

SendChunkedToClients(std::move(pac), 1, "Wii Save Synchronization");
}
@@ -17,6 +17,7 @@
#include "Common/Logging/Log.h"
#include "Common/NandPaths.h"
#include "Common/StringUtil.h"
#include "Core/Boot/Boot.h"
#include "Core/CommonTitles.h"
#include "Core/Config/SessionSettings.h"
#include "Core/ConfigManager.h"
@@ -114,7 +115,8 @@ static bool CopyNandFile(FS::FileSystem* source_fs, const std::string& source_fi
return true;
}

static void InitializeDeterministicWiiSaves(FS::FileSystem* session_fs)
static void InitializeDeterministicWiiSaves(FS::FileSystem* session_fs,
const BootSessionData& boot_session_data)
{
const u64 title_id = SConfig::GetInstance().GetTitleID();
const auto configured_fs = FS::MakeFileSystem(FS::Location::Configured);
@@ -136,8 +138,8 @@ static void InitializeDeterministicWiiSaves(FS::FileSystem* session_fs)
(Movie::IsMovieActive() && !Movie::IsStartingFromClearSave()))
{
// Copy the current user's save to the Blank NAND
auto* sync_fs = NetPlay::GetWiiSyncFS();
auto& sync_titles = NetPlay::GetWiiSyncTitles();
auto* sync_fs = boot_session_data.GetWiiSyncFS();
auto& sync_titles = boot_session_data.GetWiiSyncTitles();
if (sync_fs)
{
for (const u64 title : sync_titles)
@@ -298,7 +300,8 @@ static bool CopySysmenuFilesToFS(FS::FileSystem* fs, const std::string& host_sou
}

void InitializeWiiFileSystemContents(
std::optional<DiscIO::Riivolution::SavegameRedirect> save_redirect)
std::optional<DiscIO::Riivolution::SavegameRedirect> save_redirect,
const BootSessionData& boot_session_data)
{
const auto fs = IOS::HLE::GetIOS()->GetFS();

@@ -315,7 +318,7 @@ void InitializeWiiFileSystemContents(
SysConf sysconf{fs};
sysconf.Save();

InitializeDeterministicWiiSaves(fs.get());
InitializeDeterministicWiiSaves(fs.get(), boot_session_data);
}
else if (save_redirect)
{
@@ -336,10 +339,10 @@ void InitializeWiiFileSystemContents(
}
}

void CleanUpWiiFileSystemContents()
void CleanUpWiiFileSystemContents(const BootSessionData& boot_session_data)
{
if (!WiiRootIsTemporary() || !Config::Get(Config::SESSION_SAVE_DATA_WRITABLE) ||
NetPlay::GetWiiSyncFS())
boot_session_data.GetWiiSyncFS())
{
return;
}
@@ -8,6 +8,8 @@

#include "DiscIO/RiivolutionPatcher.h"

class BootSessionData;

namespace IOS::HLE::FS
{
struct NandRedirect;
@@ -32,8 +34,9 @@ void RestoreWiiSettings(RestoreReason reason);

// Initialize or clean up the filesystem contents.
void InitializeWiiFileSystemContents(
std::optional<DiscIO::Riivolution::SavegameRedirect> save_redirect);
void CleanUpWiiFileSystemContents();
std::optional<DiscIO::Riivolution::SavegameRedirect> save_redirect,
const BootSessionData& boot_session_data);
void CleanUpWiiFileSystemContents(const BootSessionData& boot_session_data);

const std::vector<IOS::HLE::FS::NandRedirect>& GetActiveNandRedirects();
} // namespace Core
@@ -187,7 +187,8 @@ int main(int argc, char* argv[])
const std::list<std::string> paths_list = options.all("exec");
const std::vector<std::string> paths{std::make_move_iterator(std::begin(paths_list)),
std::make_move_iterator(std::end(paths_list))};
boot = BootParameters::GenerateFromFile(paths, save_state_path);
boot = BootParameters::GenerateFromFile(
paths, BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
game_specified = true;
}
else if (options.is_set("nand_title"))
@@ -204,7 +205,8 @@ int main(int argc, char* argv[])
}
else if (args.size())
{
boot = BootParameters::GenerateFromFile(args.front(), save_state_path);
boot = BootParameters::GenerateFromFile(
args.front(), BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
args.erase(args.begin());
game_specified = true;
}
@@ -197,7 +197,8 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine
const std::list<std::string> paths_list = options.all("exec");
const std::vector<std::string> paths{std::make_move_iterator(std::begin(paths_list)),
std::make_move_iterator(std::end(paths_list))};
boot = BootParameters::GenerateFromFile(paths, save_state_path);
boot = BootParameters::GenerateFromFile(
paths, BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
game_specified = true;
}
else if (options.is_set("nand_title"))
@@ -216,7 +217,8 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine
}
else if (!args.empty())
{
boot = BootParameters::GenerateFromFile(args.front(), save_state_path);
boot = BootParameters::GenerateFromFile(
args.front(), BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
game_specified = true;
}

@@ -244,8 +244,13 @@ MainWindow::MainWindow(std::unique_ptr<BootParameters> boot_parameters,

if (!movie_path.empty())
{
if (Movie::PlayInput(movie_path, &m_pending_boot->savestate_path))
std::optional<std::string> savestate_path;
if (Movie::PlayInput(movie_path, &savestate_path))
{
m_pending_boot->boot_session_data.SetSavestateData(std::move(savestate_path),
DeleteSavestateAfterBoot::No);
emit RecordingStatusChanged(true);
}
}
}

@@ -768,14 +773,16 @@ void MainWindow::Play(const std::optional<std::string>& savestate_path)
std::shared_ptr<const UICommon::GameFile> selection = m_game_list->GetSelectedGame();
if (selection)
{
StartGame(selection->GetFilePath(), ScanForSecondDisc::Yes, savestate_path);
StartGame(selection->GetFilePath(), ScanForSecondDisc::Yes,
std::make_unique<BootSessionData>(savestate_path, DeleteSavestateAfterBoot::No));
}
else
{
const QString default_path = QString::fromStdString(Config::Get(Config::MAIN_DEFAULT_ISO));
if (!default_path.isEmpty() && QFile::exists(default_path))
{
StartGame(default_path, ScanForSecondDisc::Yes, savestate_path);
StartGame(default_path, ScanForSecondDisc::Yes,
std::make_unique<BootSessionData>(savestate_path, DeleteSavestateAfterBoot::No));
}
else
{
@@ -978,43 +985,45 @@ void MainWindow::ScreenShot()
}

void MainWindow::ScanForSecondDiscAndStartGame(const UICommon::GameFile& game,
const std::optional<std::string>& savestate_path)
std::unique_ptr<BootSessionData> boot_session_data)
{
auto second_game = m_game_list->FindSecondDisc(game);

std::vector<std::string> paths = {game.GetFilePath()};
if (second_game != nullptr)
paths.push_back(second_game->GetFilePath());

StartGame(paths, savestate_path);
StartGame(paths, std::move(boot_session_data));
}

void MainWindow::StartGame(const QString& path, ScanForSecondDisc scan,
const std::optional<std::string>& savestate_path)
std::unique_ptr<BootSessionData> boot_session_data)
{
StartGame(path.toStdString(), scan, savestate_path);
StartGame(path.toStdString(), scan, std::move(boot_session_data));
}

void MainWindow::StartGame(const std::string& path, ScanForSecondDisc scan,
const std::optional<std::string>& savestate_path)
std::unique_ptr<BootSessionData> boot_session_data)
{
if (scan == ScanForSecondDisc::Yes)
{
std::shared_ptr<const UICommon::GameFile> game = m_game_list->FindGame(path);
if (game != nullptr)
{
ScanForSecondDiscAndStartGame(*game, savestate_path);
ScanForSecondDiscAndStartGame(*game, std::move(boot_session_data));
return;
}
}

StartGame(BootParameters::GenerateFromFile(path, savestate_path));
StartGame(BootParameters::GenerateFromFile(
path, boot_session_data ? std::move(*boot_session_data) : BootSessionData()));
}

void MainWindow::StartGame(const std::vector<std::string>& paths,
const std::optional<std::string>& savestate_path)
std::unique_ptr<BootSessionData> boot_session_data)
{
StartGame(BootParameters::GenerateFromFile(paths, savestate_path));
StartGame(BootParameters::GenerateFromFile(
paths, boot_session_data ? std::move(*boot_session_data) : BootSessionData()));
}

void MainWindow::StartGame(std::unique_ptr<BootParameters>&& parameters)
@@ -1363,13 +1372,15 @@ void MainWindow::NetPlayInit()
{
const auto& game_list_model = m_game_list->GetGameListModel();
m_netplay_setup_dialog = new NetPlaySetupDialog(game_list_model, this);
m_netplay_dialog = new NetPlayDialog(game_list_model);
m_netplay_dialog = new NetPlayDialog(
game_list_model,
[this](const std::string& path, std::unique_ptr<BootSessionData> boot_session_data) {
StartGame(path, ScanForSecondDisc::Yes, std::move(boot_session_data));
});
#ifdef USE_DISCORD_PRESENCE
m_netplay_discord = new DiscordHandler(this);
#endif

connect(m_netplay_dialog, &NetPlayDialog::Boot, this,
[this](const QString& path) { StartGame(path, ScanForSecondDisc::Yes); });
connect(m_netplay_dialog, &NetPlayDialog::Stop, this, &MainWindow::ForceStop);
connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit);
connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin);
@@ -1818,8 +1829,7 @@ void MainWindow::ShowRiivolutionBootWidget(const UICommon::GameFile& game)
std::vector<std::string> paths = {game.GetFilePath()};
if (second_game != nullptr)
paths.push_back(second_game->GetFilePath());
std::unique_ptr<BootParameters> boot_params =
BootParameters::GenerateFromFile(paths, std::nullopt);
std::unique_ptr<BootParameters> boot_params = BootParameters::GenerateFromFile(paths);
if (!boot_params)
return;
if (!std::holds_alternative<BootParameters::Disc>(boot_params->parameters))
@@ -15,6 +15,7 @@ class QStackedWidget;
class QString;

class BreakpointWidget;
class BootSessionData;
struct BootParameters;
class CheatsManager;
class CodeWidget;
@@ -132,13 +133,13 @@ class MainWindow final : public QMainWindow
};

void ScanForSecondDiscAndStartGame(const UICommon::GameFile& game,
const std::optional<std::string>& savestate_path = {});
std::unique_ptr<BootSessionData> boot_session_data = nullptr);
void StartGame(const QString& path, ScanForSecondDisc scan,
const std::optional<std::string>& savestate_path = {});
std::unique_ptr<BootSessionData> boot_session_data = nullptr);
void StartGame(const std::string& path, ScanForSecondDisc scan,
const std::optional<std::string>& savestate_path = {});
std::unique_ptr<BootSessionData> boot_session_data = nullptr);
void StartGame(const std::vector<std::string>& paths,
const std::optional<std::string>& savestate_path = {});
std::unique_ptr<BootSessionData> boot_session_data = nullptr);
void StartGame(std::unique_ptr<BootParameters>&& parameters);
void ShowRenderWidget();
void HideRenderWidget(bool reinit = true, bool is_exit = false);
@@ -31,6 +31,7 @@
#include "Common/Logging/Log.h"
#include "Common/TraversalClient.h"

#include "Core/Boot/Boot.h"
#include "Core/Config/GraphicsSettings.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/NetplaySettings.h"
@@ -39,6 +40,7 @@
#ifdef HAS_LIBMGBA
#include "Core/HW/GBACore.h"
#endif
#include "Core/IOS/FS/FileSystem.h"
#include "Core/NetPlayServer.h"
#include "Core/SyncIdentifier.h"

@@ -62,8 +64,10 @@
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/VideoConfig.h"

NetPlayDialog::NetPlayDialog(const GameListModel& game_list_model, QWidget* parent)
: QDialog(parent), m_game_list_model(game_list_model)
NetPlayDialog::NetPlayDialog(const GameListModel& game_list_model,
StartGameCallback start_game_callback, QWidget* parent)
: QDialog(parent), m_game_list_model(game_list_model),
m_start_game_callback(std::move(start_game_callback))
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);

@@ -682,10 +686,11 @@ void NetPlayDialog::UpdateGUI()

// NetPlayUI methods

void NetPlayDialog::BootGame(const std::string& filename)
void NetPlayDialog::BootGame(const std::string& filename,
std::unique_ptr<BootSessionData> boot_session_data)
{
m_got_stop_request = false;
emit Boot(QString::fromStdString(filename));
m_start_game_callback(filename, std::move(boot_session_data));
}

void NetPlayDialog::StopGame()
@@ -1173,3 +1178,10 @@ void NetPlayDialog::SetChunkedProgress(const int pid, const u64 progress)
m_chunked_progress_dialog->SetProgress(pid, progress);
});
}

void NetPlayDialog::SetHostWiiSyncTitles(std::vector<u64> titles)
{
auto client = Settings::Instance().GetNetPlayClient();
if (client)
client->SetWiiSyncData(nullptr, std::move(titles));
}
@@ -3,6 +3,10 @@

#pragma once

#include <functional>
#include <memory>
#include <string>

#include <QDialog>
#include <QMenuBar>

@@ -11,6 +15,7 @@
#include "DolphinQt/GameList/GameListModel.h"
#include "VideoCommon/OnScreenDisplay.h"

class BootSessionData;
class ChunkedProgressDialog;
class MD5Dialog;
class PadMappingDialog;
@@ -30,14 +35,19 @@ class NetPlayDialog : public QDialog, public NetPlay::NetPlayUI
{
Q_OBJECT
public:
explicit NetPlayDialog(const GameListModel& game_list_model, QWidget* parent = nullptr);
using StartGameCallback = std::function<void(const std::string& path,
std::unique_ptr<BootSessionData> boot_session_data)>;

explicit NetPlayDialog(const GameListModel& game_list_model,
StartGameCallback start_game_callback, QWidget* parent = nullptr);
~NetPlayDialog();

void show(std::string nickname, bool use_traversal);
void reject() override;

// NetPlayUI methods
void BootGame(const std::string& filename) override;
void BootGame(const std::string& filename,
std::unique_ptr<BootSessionData> boot_session_data) override;
void StopGame() override;
bool IsHosting() const override;

@@ -84,8 +94,10 @@ class NetPlayDialog : public QDialog, public NetPlay::NetPlayUI
const std::vector<int>& players) override;
void HideChunkedProgressDialog() override;
void SetChunkedProgress(int pid, u64 progress) override;

void SetHostWiiSyncTitles(std::vector<u64> titles) override;

signals:
void Boot(const QString& filename);
void Stop();

private:
@@ -162,4 +174,6 @@ class NetPlayDialog : public QDialog, public NetPlay::NetPlayUI
int m_player_count = 0;
int m_old_player_count = 0;
bool m_host_input_authority = false;

StartGameCallback m_start_game_callback;
};