mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-15 05:47:56 -07:00
dabad82219
We currently have two different code paths for initializing controllers:
Either the frontend (DolphinQt) can do it, or if the frontend doesn't do
it, the core will do it automatically when booting. Having these two
paths has caused problems in the past due to only one frontend being
tested (see de7ef47548
). I would like to get rid of the latter path to
avoid further problems like this.
307 lines
6.7 KiB
C++
307 lines
6.7 KiB
C++
// Copyright 2008 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "DolphinNoGUI/Platform.h"
|
|
|
|
#include <OptionParser.h>
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <signal.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#else
|
|
#include <Windows.h>
|
|
#endif
|
|
|
|
#include "Common/ScopeGuard.h"
|
|
#include "Common/StringUtil.h"
|
|
#include "Core/Boot/Boot.h"
|
|
#include "Core/BootManager.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/DolphinAnalytics.h"
|
|
#include "Core/Host.h"
|
|
|
|
#include "UICommon/CommandLineParse.h"
|
|
#ifdef USE_DISCORD_PRESENCE
|
|
#include "UICommon/DiscordPresence.h"
|
|
#endif
|
|
#include "UICommon/UICommon.h"
|
|
|
|
#include "InputCommon/GCAdapter.h"
|
|
|
|
#include "VideoCommon/RenderBase.h"
|
|
#include "VideoCommon/VideoBackendBase.h"
|
|
|
|
static std::unique_ptr<Platform> s_platform;
|
|
|
|
static void signal_handler(int)
|
|
{
|
|
const char message[] = "A signal was received. A second signal will force Dolphin to stop.\n";
|
|
#ifdef _WIN32
|
|
puts(message);
|
|
#else
|
|
if (write(STDERR_FILENO, message, sizeof(message)) < 0)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
s_platform->RequestShutdown();
|
|
}
|
|
|
|
std::vector<std::string> Host_GetPreferredLocales()
|
|
{
|
|
return {};
|
|
}
|
|
|
|
void Host_NotifyMapLoaded()
|
|
{
|
|
}
|
|
|
|
void Host_RefreshDSPDebuggerWindow()
|
|
{
|
|
}
|
|
|
|
bool Host_UIBlocksControllerState()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static Common::Event s_update_main_frame_event;
|
|
void Host_Message(HostMessageID id)
|
|
{
|
|
if (id == HostMessageID::WMUserStop)
|
|
s_platform->Stop();
|
|
}
|
|
|
|
void Host_UpdateTitle(const std::string& title)
|
|
{
|
|
s_platform->SetTitle(title);
|
|
}
|
|
|
|
void Host_UpdateDisasmDialog()
|
|
{
|
|
}
|
|
|
|
void Host_UpdateMainFrame()
|
|
{
|
|
s_update_main_frame_event.Set();
|
|
}
|
|
|
|
void Host_RequestRenderWindowSize(int width, int height)
|
|
{
|
|
}
|
|
|
|
bool Host_RendererHasFocus()
|
|
{
|
|
return s_platform->IsWindowFocused();
|
|
}
|
|
|
|
bool Host_RendererHasFullFocus()
|
|
{
|
|
// Mouse capturing isn't implemented
|
|
return Host_RendererHasFocus();
|
|
}
|
|
|
|
bool Host_RendererIsFullscreen()
|
|
{
|
|
return s_platform->IsWindowFullscreen();
|
|
}
|
|
|
|
void Host_YieldToUI()
|
|
{
|
|
}
|
|
|
|
void Host_TitleChanged()
|
|
{
|
|
#ifdef USE_DISCORD_PRESENCE
|
|
Discord::UpdateDiscordPresence();
|
|
#endif
|
|
}
|
|
|
|
std::unique_ptr<GBAHostInterface> Host_CreateGBAHost(std::weak_ptr<HW::GBA::Core> core)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
static std::unique_ptr<Platform> GetPlatform(const optparse::Values& options)
|
|
{
|
|
std::string platform_name = static_cast<const char*>(options.get("platform"));
|
|
|
|
#if HAVE_X11
|
|
if (platform_name == "x11" || platform_name.empty())
|
|
return Platform::CreateX11Platform();
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
if (platform_name == "fbdev" || platform_name.empty())
|
|
return Platform::CreateFBDevPlatform();
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
if (platform_name == "win32" || platform_name.empty())
|
|
return Platform::CreateWin32Platform();
|
|
#endif
|
|
|
|
if (platform_name == "headless" || platform_name.empty())
|
|
return Platform::CreateHeadlessPlatform();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
#define main app_main
|
|
#endif
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
auto parser = CommandLineParse::CreateParser(CommandLineParse::ParserOptions::OmitGUIOptions);
|
|
parser->add_option("-p", "--platform")
|
|
.action("store")
|
|
.help("Window platform to use [%choices]")
|
|
.choices({
|
|
"headless"
|
|
#ifdef __linux__
|
|
,
|
|
"fbdev"
|
|
#endif
|
|
#if HAVE_X11
|
|
,
|
|
"x11"
|
|
#endif
|
|
#ifdef _WIN32
|
|
,
|
|
"win32"
|
|
#endif
|
|
});
|
|
|
|
optparse::Values& options = CommandLineParse::ParseArguments(parser.get(), argc, argv);
|
|
std::vector<std::string> args = parser->args();
|
|
|
|
std::optional<std::string> save_state_path;
|
|
if (options.is_set("save_state"))
|
|
{
|
|
save_state_path = static_cast<const char*>(options.get("save_state"));
|
|
}
|
|
|
|
std::unique_ptr<BootParameters> boot;
|
|
bool game_specified = false;
|
|
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;
|
|
}
|
|
else if (options.is_set("nand_title"))
|
|
{
|
|
const std::string hex_string = static_cast<const char*>(options.get("nand_title"));
|
|
if (hex_string.length() != 16)
|
|
{
|
|
fprintf(stderr, "Invalid title ID\n");
|
|
parser->print_help();
|
|
return 1;
|
|
}
|
|
const u64 title_id = std::stoull(hex_string, nullptr, 16);
|
|
boot = std::make_unique<BootParameters>(BootParameters::NANDTitle{title_id});
|
|
}
|
|
else if (args.size())
|
|
{
|
|
boot = BootParameters::GenerateFromFile(
|
|
args.front(), BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
|
|
args.erase(args.begin());
|
|
game_specified = true;
|
|
}
|
|
else
|
|
{
|
|
parser->print_help();
|
|
return 0;
|
|
}
|
|
|
|
std::string user_directory;
|
|
if (options.is_set("user"))
|
|
user_directory = static_cast<const char*>(options.get("user"));
|
|
|
|
s_platform = GetPlatform(options);
|
|
if (!s_platform || !s_platform->Init())
|
|
{
|
|
fprintf(stderr, "No platform found, or failed to initialize.\n");
|
|
return 1;
|
|
}
|
|
|
|
const WindowSystemInfo wsi = s_platform->GetWindowSystemInfo();
|
|
|
|
UICommon::SetUserDirectory(user_directory);
|
|
UICommon::Init();
|
|
UICommon::InitControllers(wsi);
|
|
|
|
Common::ScopeGuard ui_common_guard([] {
|
|
UICommon::ShutdownControllers();
|
|
UICommon::Shutdown();
|
|
});
|
|
|
|
if (save_state_path && !game_specified)
|
|
{
|
|
fprintf(stderr, "A save state cannot be loaded without specifying a game to launch.\n");
|
|
return 1;
|
|
}
|
|
|
|
Core::AddOnStateChangedCallback([](Core::State state) {
|
|
if (state == Core::State::Uninitialized)
|
|
s_platform->Stop();
|
|
});
|
|
|
|
#ifdef _WIN32
|
|
signal(SIGINT, signal_handler);
|
|
signal(SIGTERM, signal_handler);
|
|
#else
|
|
// Shut down cleanly on SIGINT and SIGTERM
|
|
struct sigaction sa;
|
|
sa.sa_handler = signal_handler;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_RESETHAND;
|
|
sigaction(SIGINT, &sa, nullptr);
|
|
sigaction(SIGTERM, &sa, nullptr);
|
|
#endif
|
|
|
|
DolphinAnalytics::Instance().ReportDolphinStart("nogui");
|
|
|
|
if (!BootManager::BootCore(std::move(boot), wsi))
|
|
{
|
|
fprintf(stderr, "Could not boot the specified file\n");
|
|
return 1;
|
|
}
|
|
|
|
#ifdef USE_DISCORD_PRESENCE
|
|
Discord::UpdateDiscordPresence();
|
|
#endif
|
|
|
|
s_platform->MainLoop();
|
|
Core::Stop();
|
|
|
|
Core::Shutdown();
|
|
s_platform.reset();
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
int wmain(int, wchar_t*[], wchar_t*[])
|
|
{
|
|
std::vector<std::string> args = CommandLineToUtf8Argv(GetCommandLineW());
|
|
const int argc = static_cast<int>(args.size());
|
|
std::vector<char*> argv(args.size());
|
|
for (size_t i = 0; i < args.size(); ++i)
|
|
argv[i] = args[i].data();
|
|
|
|
return main(argc, argv.data());
|
|
}
|
|
|
|
#undef main
|
|
#endif
|