Skip to content

Commit

Permalink
Add CLI support for NetPlay join/host
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaVandaele committed Jan 20, 2025
1 parent 90eba2b commit 99085ff
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 10 deletions.
5 changes: 5 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ Options:
-b, --batch Run Dolphin without the user interface (Requires
--exec or --nand-title)
-c, --confirm Set Confirm on Stop
--netplay_host=<port>
Host a netplay session on the specified port (Requires --exec)
--netplay_join=<ip:port> OR <host code>
Join a netplay session at the specified IP address and
port or using a host code
-v VIDEO_BACKEND, --video_backend=VIDEO_BACKEND
Specify a video backend
-a AUDIO_EMULATION, --audio_emulation=AUDIO_EMULATION
Expand Down
61 changes: 55 additions & 6 deletions Source/Core/DolphinQt/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "DolphinQt/Updater.h"

#include "UICommon/CommandLineParse.h"
#include "UICommon/GameFile.h"
#include "UICommon/UICommon.h"

static bool QtMsgAlertHandler(const char* caption, const char* text, bool yes_no,
Expand Down Expand Up @@ -190,14 +191,22 @@ int main(int argc, char* argv[])

std::unique_ptr<BootParameters> boot;
bool game_specified = false;
std::optional<UICommon::GameFile> netplay_game = std::nullopt;
if (options.is_set("exec"))
{
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, BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
game_specified = true;
if (options.is_set("netplay_host"))
{
netplay_game = UICommon::GameFile(paths[0]);
}
else
{
boot = BootParameters::GenerateFromFile(
paths, BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
game_specified = true;
}
}
else if (options.is_set("nand_title"))
{
Expand All @@ -220,9 +229,48 @@ int main(int argc, char* argv[])
game_specified = true;
}

// FIXME: All those if/else-if clauses except for the last `else` SEGFAULT.
// This is "fine" since they mean to exit dolphin anyways, but it probably shouldn't do that.
int retval;

if (save_state_path && !game_specified)
if (options.is_set("netplay_join") && options.is_set("netplay_host"))
{
ModalMessageBox::critical(
nullptr, QObject::tr("Error"),
QObject::tr("The --netplay_host and --netplay_join flags are mutually exclusive."));
retval = 1;
}
else if (options.is_set("netplay_join") && game_specified)
{
ModalMessageBox::critical(
nullptr, QObject::tr("Error"),
QObject::tr("You cannot select the game for the NetPlay session you are joining."));
retval = 1;
}
else if (options.is_set("netplay_join") && save_state_path)
{
ModalMessageBox::critical(nullptr, QObject::tr("Error"),
QObject::tr("NetPlay does not support NAND titles."));
retval = 1;
}
else if (!netplay_game && options.is_set("netplay_host"))
{
ModalMessageBox::critical(nullptr, QObject::tr("Error"),
QObject::tr("You must specify a game to host with NetPlay."));
retval = 1;
}
else if (netplay_game && game_specified)
{
ModalMessageBox::critical(nullptr, QObject::tr("Error"),
QObject::tr("NetPlay does not support NAND titles."));
retval = 1;
}
else if (netplay_game && save_state_path)
{
ModalMessageBox::critical(nullptr, QObject::tr("Error"),
QObject::tr("NetPlay does not support savestates."));
retval = 1;
}
else if (save_state_path && !game_specified)
{
ModalMessageBox::critical(
nullptr, QObject::tr("Error"),
Expand Down Expand Up @@ -250,7 +298,8 @@ int main(int argc, char* argv[])
Settings::Instance().ApplyStyle();

MainWindow win{Core::System::GetInstance(), std::move(boot),
static_cast<const char*>(options.get("movie"))};
static_cast<const char*>(options.get("movie")), options.is_set("netplay_join"),
netplay_game};

#if defined(USE_ANALYTICS) && USE_ANALYTICS
if (!Config::Get(Config::MAIN_ANALYTICS_PERMISSION_ASKED))
Expand Down
13 changes: 11 additions & 2 deletions Source/Core/DolphinQt/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ static std::vector<std::string> StringListToStdVector(QStringList list)
}

MainWindow::MainWindow(Core::System& system, std::unique_ptr<BootParameters> boot_parameters,
const std::string& movie_path)
const std::string& movie_path, const bool netplay_join,
const std::optional<UICommon::GameFile> netplay_host)
: QMainWindow(nullptr), m_system(system)
{
setWindowTitle(QString::fromStdString(Common::GetScmRevStr()));
Expand Down Expand Up @@ -330,7 +331,15 @@ MainWindow::MainWindow(Core::System& system, std::unique_ptr<BootParameters> boo

Host::GetInstance()->SetMainWindowHandle(reinterpret_cast<void*>(winId()));

if (m_pending_boot != nullptr)
if (netplay_join)
{
NetPlayJoin();
}
else if (netplay_host)
{
NetPlayHost(netplay_host.value());
}
else if (m_pending_boot != nullptr)
{
StartGame(std::move(m_pending_boot));
m_pending_boot.reset();
Expand Down
3 changes: 2 additions & 1 deletion Source/Core/DolphinQt/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ class MainWindow final : public QMainWindow

public:
explicit MainWindow(Core::System& system, std::unique_ptr<BootParameters> boot_parameters,
const std::string& movie_path);
const std::string& movie_path, const bool netplay_join,
const std::optional<UICommon::GameFile> netplay_host);
~MainWindow();

WindowSystemInfo GetWindowSystemInfo() const;
Expand Down
63 changes: 62 additions & 1 deletion Source/Core/UICommon/CommandLineParse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "UICommon/CommandLineParse.h"

#include <fmt/ostream.h>
#include <list>
#include <optional>
#include <sstream>
Expand All @@ -15,14 +16,17 @@
#include "Common/StringUtil.h"
#include "Common/Version.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/NetplaySettings.h"

namespace CommandLineParse
{
class CommandLineConfigLayerLoader final : public Config::ConfigLayerLoader
{
public:
CommandLineConfigLayerLoader(const std::list<std::string>& args, const std::string& video_backend,
const std::string& audio_backend, bool batch, bool debugger)
const std::string& audio_backend,
const std::optional<u16> netplay_host,
const std::string& netplay_join, bool batch, bool debugger)
: ConfigLayerLoader(Config::LayerType::CommandLine)
{
if (!video_backend.empty())
Expand All @@ -34,6 +38,49 @@ class CommandLineConfigLayerLoader final : public Config::ConfigLayerLoader
ValueToString(audio_backend == "HLE"));
}

if (netplay_host.has_value())
{
m_values.emplace_back(Config::NETPLAY_HOST_PORT.GetLocation(),
ValueToString(netplay_host.value()));
}

if (!netplay_join.empty())
{
std::vector<std::string> join_parts = SplitString(netplay_join, ':');
if (join_parts.size() < 2)
{
// The user is submitting a host code
const std::string& host_code = join_parts[0];
m_values.emplace_back(Config::NETPLAY_TRAVERSAL_CHOICE.GetLocation(), "traversal");
m_values.emplace_back(Config::NETPLAY_HOST_CODE.GetLocation(), host_code);
}
else
{
// Ther user is submitting an IP address
const std::string& host = join_parts[0];
const std::string& port_str = join_parts[1];
if (!std::all_of(port_str.begin(), port_str.end(), ::isdigit) || port_str.length() > 5)
{
fmt::println(std::cerr, "Error: the port must be a number between 0-{}.",
std::numeric_limits<uint16_t>::max());
std::exit(EXIT_FAILURE);
}

const u64 port = std::stoul(port_str);
if (port > std::numeric_limits<uint16_t>::max() || port < 1)
{
fmt::println(std::cerr, "Error: the port must be a number between 0-{}.",
std::numeric_limits<uint16_t>::max());
std::exit(EXIT_FAILURE);
}

const u16 cast_port = static_cast<uint16_t>(port);

m_values.emplace_back(Config::NETPLAY_ADDRESS.GetLocation(), host);
m_values.emplace_back(Config::NETPLAY_CONNECT_PORT.GetLocation(), ValueToString(cast_port));
}
}

// Batch mode hides the main window, and render to main hides the render window. To avoid a
// situation where we would have no window at all, disable render to main when using batch mode.
if (batch)
Expand Down Expand Up @@ -116,6 +163,16 @@ std::unique_ptr<optparse::OptionParser> CreateParser(ParserOptions options)
.action("store_true")
.help("Run Dolphin without the user interface (Requires --exec or --nand-title)");
parser->add_option("-c", "--confirm").action("store_true").help("Set Confirm on Stop");
parser->add_option("--netplay_host")
.action("store")
.metavar("<port>")
.type("int")
.help("Host a netplay session on the specified port (Requires --exec)");
parser->add_option("--netplay_join")
.action("store")
.metavar("<ip:port> OR <host code>")
.type("string")
.help("Join a netplay session at the specified IP address and port or using a host code");
}

parser->set_defaults("video_backend", "");
Expand All @@ -137,6 +194,10 @@ static void AddConfigLayer(const optparse::Values& options)
Config::AddLayer(std::make_unique<CommandLineConfigLayerLoader>(
std::move(config_args), static_cast<const char*>(options.get("video_backend")),
static_cast<const char*>(options.get("audio_emulation")),
options.is_set("netplay_host") ?
std::optional<u16>(static_cast<u16>(options.get("netplay_host"))) :
std::nullopt,
static_cast<const char*>(options.get("netplay_join")),
static_cast<bool>(options.get("batch")), static_cast<bool>(options.get("debugger"))));
}

Expand Down

0 comments on commit 99085ff

Please sign in to comment.