Merged the SDL and Qt frontends together into a single unified executable (#508 1/2)

This commit is contained in:
OpenSauce04 2024-10-28 18:27:00 +00:00
parent 48447fc8c5
commit 563089d792
44 changed files with 2587 additions and 210 deletions

View File

@ -57,6 +57,7 @@ else()
endif()
option(ENABLE_SDL2 "Enable using SDL2" ON)
CMAKE_DEPENDENT_OPTION(ENABLE_SDL2_FRONTEND "Enable the SDL2 frontend" ON "ENABLE_SDL2;NOT ANDROID" OFF)
option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF)
# Set bundled qt as dependent options.
@ -87,6 +88,17 @@ option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
option(LIME3DS_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
option(LIME3DS_WARNINGS_AS_ERRORS "Enable warnings as errors" ON)
# Pass the following values to C++ land
if (ENABLE_QT)
add_definitions(-DENABLE_QT)
endif()
if (ENABLE_QT_TRANSLATION)
add_definitions(-DENABLE_QT_TRANSLATION)
endif()
if (ENABLE_SDL2_FRONTEND)
add_definitions(-DENABLE_SDL2_FRONTEND)
endif()
include(Lime3DSHandleSystemLibs)
if (LIME3DS_USE_PRECOMPILED_HEADERS)
@ -420,19 +432,18 @@ endif()
add_subdirectory(src)
# Set lime-qt project or lime project as default StartUp Project in Visual Studio depending on whether QT is enabled or not
if(ENABLE_QT)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT lime-qt)
endif()
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT lime)
# Create target for outputting distributable bundles.
# Not supported for mobile platforms as distributables are built differently.
if (NOT ANDROID)
include(BundleTarget)
if (ENABLE_QT)
bundle_target(lime-qt)
qt_bundle_target(lime)
elseif (ENABLE_SDL2_FRONTEND)
bundle_target(lime)
endif()
if (ENABLE_DEDICATED_ROOM)
bundle_target(lime-room)
endif()

View File

@ -308,22 +308,17 @@ else()
# Adds a target to the bundle target, packing in required libraries.
# If in_place is true, the bundling will be done in-place as part of the specified target.
function(bundle_target_internal target_name in_place)
function(bundle_target_internal target_name in_place bundle_qt)
# Create base bundle target if it does not exist.
if (NOT in_place AND NOT TARGET bundle)
create_base_bundle_target()
endif()
set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
if (target_name MATCHES ".*qt")
set(bundle_qt ON)
if (APPLE)
if (bundle_qt AND APPLE)
# For Qt targets on Apple, expect an app bundle.
set(bundle_executable_path "$<TARGET_BUNDLE_DIR:${target_name}>")
endif()
else()
set(bundle_qt OFF)
endif()
# Build a list of library search paths from prefix paths.
foreach(prefix_path IN LISTS CMAKE_FIND_ROOT_PATH CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH)
@ -364,11 +359,21 @@ else()
# Adds a target to the bundle target, packing in required libraries.
function(bundle_target target_name)
bundle_target_internal("${target_name}" OFF)
bundle_target_internal("${target_name}" OFF OFF)
endfunction()
# Same as bundle_target, but also bundles Qt libraries
function(qt_bundle_target target_name)
bundle_target_internal("${target_name}" OFF ON)
endfunction()
# Bundles the target in-place, packing in required libraries.
function(bundle_target_in_place target_name)
bundle_target_internal("${target_name}" ON)
bundle_target_internal("${target_name}" ON OFF)
endfunction()
# Same as bundle_target_in_place, but also bundles Qt libraries
function(qt_bundle_target_in_place target_name)
bundle_target_internal("${target_name}" ON ON)
endfunction()
endif()

View File

@ -180,10 +180,18 @@ if (ENABLE_TESTS)
add_subdirectory(tests)
endif()
if (ENABLE_SDL2_FRONTEND)
add_subdirectory(lime_sdl)
endif()
if (ENABLE_QT)
add_subdirectory(lime_qt)
endif()
if (ENABLE_QT OR ENABLE_SDL2_FRONTEND)
add_subdirectory(lime)
endif()
if (ENABLE_DEDICATED_ROOM)
add_subdirectory(lime_room)
endif()

View File

@ -38,7 +38,7 @@ enum class Class : u8 {
Core_ARM11, ///< ARM11 CPU core
Core_Timing, ///< CoreTiming functions
Core_Cheats, ///< Cheat functions
Config, ///< Emulator configuration (including commandline)
Config, ///< Emulator configuration
Debug, ///< Debugging tools
Debug_Emulated, ///< Debug messages from the emulated programs
Debug_GPU, ///< GPU debugging tools

68
src/lime/CMakeLists.txt Normal file
View File

@ -0,0 +1,68 @@
add_executable(lime
lime.rc
main.cpp
)
set_target_properties(lime PROPERTIES OUTPUT_NAME "lime3ds")
if (APPLE)
set(DIST_DIR "../../dist/apple")
set(APPLE_RESOURCES
"${DIST_DIR}/lime.icns"
"${DIST_DIR}/LaunchScreen.storyboard"
"${DIST_DIR}/launch_logo.png"
)
target_sources(lime PRIVATE ${APPLE_RESOURCES})
# Define app bundle metadata.
include(GenerateBuildInfo)
set_target_properties(lime PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST "${DIST_DIR}/Info.plist.in"
MACOSX_BUNDLE_BUNDLE_NAME "Lime3DS"
MACOSX_BUNDLE_GUI_IDENTIFIER "io.github.lime3ds.Lime3DS"
MACOSX_BUNDLE_BUNDLE_VERSION "${BUILD_VERSION}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${BUILD_FULLNAME}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${BUILD_FULLNAME}"
MACOSX_BUNDLE_ICON_FILE "lime.icns"
RESOURCE "${APPLE_RESOURCES}"
)
endif()
target_link_libraries(lime PRIVATE fmt)
if (ENABLE_SDL2_FRONTEND)
target_link_libraries(lime PRIVATE lime_sdl)
endif()
if (ENABLE_QT)
target_link_libraries(lime PRIVATE lime_qt)
target_link_libraries(lime PRIVATE Boost::boost Qt6::Widgets)
endif()
if (ENABLE_QT AND UNIX AND NOT APPLE)
target_link_libraries(lime PRIVATE Qt6::DBus gamemode)
endif()
if (ENABLE_QT AND USE_DISCORD_PRESENCE)
target_link_libraries(lime PRIVATE discord-rpc)
endif()
if(WIN32)
# compile as a win32 gui application instead of a console application
if(MSVC)
set_target_properties(lime PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
elseif(MINGW)
set_target_properties(lime PROPERTIES LINK_FLAGS_RELEASE "-mwindows")
endif()
endif()
# Bundle in-place on MSVC so dependencies can be resolved by builds.
if (ENABLE_QT AND MSVC)
include(BundleTarget)
qt_bundle_target_in_place(lime)
endif()
if(UNIX AND NOT APPLE)
install(TARGETS lime RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()

29
src/lime/common_strings.h Normal file
View File

@ -0,0 +1,29 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
namespace Common {
constexpr char help_string[] =
"Usage: {} [options] <file path>\n"
"-d, --dump-video [path] Dump video recording of emulator playback to the given file path\n"
"-f, --fullscreen Start in fullscreen mode\n"
"-g, --gdbport [port] Enable gdb stub on the given port\n"
"-h, --help Display this help and exit\n"
"-i, --install [path] Install a CIA file at the given path\n"
"-p, --movie-play [path] Play a TAS movie located at the given path\n"
"-r, --movie-record [path] Record a TAS movie to the given file path\n"
"-a, --movie-record-author [author] Set the author for the recorded TAS movie (to be used "
"alongside --movie-record)\n"
"-n, --no-gui Use the lightweight SDL frontend instead of the usual Qt "
"frontend\n"
"-m, --multiplayer [nick:password@address:port] Nickname, password, address and port for "
"multiplayer\n"
"-v, --version Output version information and exit\n"
"-w, --windowed Start in windowed mode";
}

45
src/lime/main.cpp Normal file
View File

@ -0,0 +1,45 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <iostream>
#ifdef ENABLE_QT
#include "lime_qt/lime_qt.h"
#endif
#ifdef ENABLE_SDL2_FRONTEND
#include "lime_sdl/lime_sdl.h"
#endif
#ifdef _WIN32
extern "C" {
// tells Nvidia drivers to use the dedicated GPU by default on laptops with switchable graphics
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
}
#endif
int main(int argc, char* argv[]) {
#if ENABLE_QT
bool no_gui = false;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--no-gui") == 0 || strcmp(argv[i], "-n") == 0) {
no_gui = true;
}
}
if (!no_gui) {
LaunchQtFrontend(argc, argv);
return 0;
}
#endif
#if ENABLE_SDL2_FRONTEND
LaunchSdlFrontend(argc, argv);
#else
std::cout << "Cannot use SDL frontend as it was excluded at compile time. Exiting."
<< std::endl;
return -1;
#endif
return 0;
}

View File

@ -7,7 +7,7 @@ if (POLICY CMP0071)
cmake_policy(SET CMP0071 NEW)
endif()
add_executable(lime-qt
add_library(lime_qt STATIC EXCLUDE_FROM_ALL
aboutdialog.cpp
aboutdialog.h
aboutdialog.ui
@ -27,7 +27,6 @@ add_executable(lime-qt
camera/qt_camera_base.h
camera/qt_multimedia_camera.cpp
camera/qt_multimedia_camera.h
lime-qt.rc
configuration/config.cpp
configuration/config.h
configuration/configure.ui
@ -136,11 +135,11 @@ add_executable(lime-qt
game_list_worker.h
hotkeys.cpp
hotkeys.h
lime_qt.cpp
lime_qt.h
loading_screen.cpp
loading_screen.h
loading_screen.ui
main.cpp
main.h
main.ui
movie/movie_play_dialog.cpp
movie/movie_play_dialog.h
@ -191,8 +190,6 @@ add_executable(lime-qt
util/util.h
)
set_target_properties(lime-qt PROPERTIES OUTPUT_NAME "lime3ds")
file(GLOB COMPAT_LIST
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
@ -200,12 +197,12 @@ file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*)
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*)
if (ENABLE_QT_UPDATER)
target_sources(lime-qt PRIVATE
target_sources(lime_qt PRIVATE
updater/updater.cpp
updater/updater.h
updater/updater_p.h
)
target_compile_definitions(lime-qt PUBLIC ENABLE_QT_UPDATER)
target_compile_definitions(lime_qt PUBLIC ENABLE_QT_UPDATER)
endif()
if (ENABLE_QT_TRANSLATION)
@ -214,13 +211,13 @@ if (ENABLE_QT_TRANSLATION)
# Update source TS file if enabled
if (GENERATE_QT_TRANSLATION)
get_target_property(QT_SRCS lime-qt SOURCES)
get_target_property(QT_INCLUDES lime-qt INCLUDE_DIRECTORIES)
qt_add_lupdate(lime-qt TS_FILES ${LIME3DS_QT_LANGUAGES}/en.ts
get_target_property(QT_SRCS lime_qt SOURCES)
get_target_property(QT_INCLUDES lime_qt INCLUDE_DIRECTORIES)
qt_add_lupdate(lime_qt TS_FILES ${LIME3DS_QT_LANGUAGES}/en.ts
SOURCES ${QT_SRCS} ${UIS}
INCLUDE_DIRECTORIES ${QT_INCLUDES}
NO_GLOBAL_TARGET)
add_custom_target(translation ALL DEPENDS lime-qt_lupdate)
add_custom_target(translation ALL DEPENDS lime_qt_lupdate)
endif()
# Find all TS files except en.ts
@ -228,7 +225,7 @@ if (ENABLE_QT_TRANSLATION)
list(REMOVE_ITEM LANGUAGES_TS ${LIME3DS_QT_LANGUAGES}/en.ts)
# Compile TS files to QM files
qt_add_lrelease(lime-qt TS_FILES ${LANGUAGES_TS} NO_GLOBAL_TARGET QM_FILES_OUTPUT_VARIABLE LANGUAGES_QM)
qt_add_lrelease(lime_qt TS_FILES ${LANGUAGES_TS} NO_GLOBAL_TARGET QM_FILES_OUTPUT_VARIABLE LANGUAGES_QM)
# Build a QRC file from the QM file list
set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
@ -245,7 +242,7 @@ else()
set(LANGUAGES)
endif()
target_sources(lime-qt
target_sources(lime_qt
PRIVATE
${COMPAT_LIST}
${ICONS}
@ -253,66 +250,38 @@ target_sources(lime-qt
${THEMES}
)
if (APPLE)
set(DIST_DIR "../../dist/apple")
set(APPLE_RESOURCES
"${DIST_DIR}/lime.icns"
"${DIST_DIR}/LaunchScreen.storyboard"
"${DIST_DIR}/launch_logo.png"
)
target_sources(lime-qt PRIVATE ${APPLE_RESOURCES})
# Define app bundle metadata.
include(GenerateBuildInfo)
set_target_properties(lime-qt PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST "${DIST_DIR}/Info.plist.in"
MACOSX_BUNDLE_BUNDLE_NAME "Lime3DS"
MACOSX_BUNDLE_GUI_IDENTIFIER "io.github.lime3ds.Lime3DS"
MACOSX_BUNDLE_BUNDLE_VERSION "${BUILD_VERSION}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${BUILD_FULLNAME}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${BUILD_FULLNAME}"
MACOSX_BUNDLE_ICON_FILE "lime.icns"
RESOURCE "${APPLE_RESOURCES}"
)
elseif(WIN32)
# compile as a win32 gui application instead of a console application
target_link_libraries(lime-qt PRIVATE Qt6::EntryPointImplementation)
if(MSVC)
set_target_properties(lime-qt PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
elseif(MINGW)
set_target_properties(lime-qt PROPERTIES LINK_FLAGS_RELEASE "-mwindows")
endif()
if (WIN32)
target_link_libraries(lime_qt PRIVATE Qt6::EntryPointImplementation)
endif()
if(ENABLE_SDL2)
target_link_libraries(lime-qt PRIVATE SDL2::SDL2)
target_compile_definitions(lime-qt PRIVATE HAVE_SDL2)
target_link_libraries(lime_qt PRIVATE SDL2::SDL2)
target_compile_definitions(lime_qt PRIVATE HAVE_SDL2)
endif()
create_target_directory_groups(lime-qt)
create_target_directory_groups(lime_qt)
target_link_libraries(lime-qt PRIVATE audio_core lime_common lime_core input_common network video_core)
target_link_libraries(lime-qt PRIVATE Boost::boost nihstro-headers Qt6::Widgets Qt6::Multimedia Qt6::Concurrent)
target_link_libraries(lime-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
target_link_libraries(lime_qt PRIVATE audio_core lime_common lime_core input_common network video_core)
target_link_libraries(lime_qt PRIVATE Boost::boost nihstro-headers Qt6::Widgets Qt6::Multimedia Qt6::Concurrent)
target_link_libraries(lime_qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (ENABLE_OPENGL)
target_link_libraries(lime-qt PRIVATE glad)
target_link_libraries(lime_qt PRIVATE glad)
endif()
if (ENABLE_VULKAN)
target_link_libraries(lime-qt PRIVATE vulkan-headers)
target_link_libraries(lime_qt PRIVATE vulkan-headers)
endif()
if (NOT WIN32)
target_include_directories(lime-qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
target_include_directories(lime_qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
endif()
if (UNIX AND NOT APPLE)
target_link_libraries(lime-qt PRIVATE Qt6::DBus gamemode)
target_link_libraries(lime_qt PRIVATE Qt6::DBus gamemode)
endif()
target_compile_definitions(lime-qt PRIVATE
target_compile_definitions(lime_qt PRIVATE
# Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created.
-DQT_USE_QSTRINGBUILDER
@ -332,28 +301,18 @@ target_compile_definitions(lime-qt PRIVATE
)
if (USE_DISCORD_PRESENCE)
target_sources(lime-qt PUBLIC
target_sources(lime_qt PUBLIC
discord_impl.cpp
discord_impl.h
)
target_link_libraries(lime-qt PRIVATE discord-rpc)
target_compile_definitions(lime-qt PRIVATE -DUSE_DISCORD_PRESENCE)
target_link_libraries(lime_qt PRIVATE discord-rpc)
target_compile_definitions(lime_qt PRIVATE -DUSE_DISCORD_PRESENCE)
endif()
if (ENABLE_WEB_SERVICE)
target_link_libraries(lime-qt PRIVATE web_service)
endif()
if(UNIX AND NOT APPLE)
install(TARGETS lime-qt RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
target_link_libraries(lime_qt PRIVATE web_service)
endif()
if (LIME3DS_USE_PRECOMPILED_HEADERS)
target_precompile_headers(lime-qt PRIVATE precompiled_headers.h)
endif()
# Bundle in-place on MSVC so dependencies can be resolved by builds.
if (MSVC)
include(BundleTarget)
bundle_target_in_place(lime-qt)
target_precompile_headers(lime_qt PRIVATE precompiled_headers.h)
endif()

View File

@ -20,7 +20,7 @@
#include "input_common/main.h"
#include "input_common/motion_emu.h"
#include "lime_qt/bootmanager.h"
#include "lime_qt/main.h"
#include "lime_qt/lime_qt.h"
#include "video_core/custom_textures/custom_tex_manager.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"

View File

@ -7,7 +7,7 @@
#include <QMediaDevices>
#include <QThread>
#include "lime_qt/camera/qt_multimedia_camera.h"
#include "lime_qt/main.h"
#include "lime_qt/lime_qt.h"
#if defined(__APPLE__)
#include "common/apple_authorization.h"

View File

@ -15,24 +15,24 @@
#include "network/network.h"
#include "network/network_settings.h"
Config::Config(const std::string& config_name, ConfigType config_type) : type{config_type} {
QtConfig::QtConfig(const std::string& config_name, ConfigType config_type) : type{config_type} {
global = config_type == ConfigType::GlobalConfig;
Initialize(config_name);
}
Config::~Config() {
QtConfig::~QtConfig() {
if (global) {
Save();
}
}
const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
const std::array<int, Settings::NativeButton::NumButtons> QtConfig::default_buttons = {
Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_T, Qt::Key_G,
Qt::Key_F, Qt::Key_H, Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N,
Qt::Key_O, Qt::Key_P, Qt::Key_1, Qt::Key_2, Qt::Key_B, Qt::Key_V,
};
const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> QtConfig::default_analogs{{
{
Qt::Key_Up,
Qt::Key_Down,
@ -54,7 +54,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:
// This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
const std::array<UISettings::Shortcut, 35> Config::default_hotkeys {{
const std::array<UISettings::Shortcut, 35> QtConfig::default_hotkeys {{
{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
{QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
@ -93,7 +93,7 @@ const std::array<UISettings::Shortcut, 35> Config::default_hotkeys {{
}};
// clang-format on
void Config::Initialize(const std::string& config_name) {
void QtConfig::Initialize(const std::string& config_name) {
const std::string fs_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir);
const std::string config_file = fmt::format("{}.ini", config_name);
@ -120,7 +120,7 @@ void Config::Initialize(const std::string& config_name) {
// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant, nor
// can it implicitly convert a QVariant back to a {std::,Q}string
template <>
void Config::ReadBasicSetting(Settings::Setting<std::string>& setting) {
void QtConfig::ReadBasicSetting(Settings::Setting<std::string>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const auto default_value = QString::fromStdString(setting.GetDefault());
if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
@ -131,7 +131,7 @@ void Config::ReadBasicSetting(Settings::Setting<std::string>& setting) {
}
template <typename Type, bool ranged>
void Config::ReadBasicSetting(Settings::Setting<Type, ranged>& setting) {
void QtConfig::ReadBasicSetting(Settings::Setting<Type, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const Type default_value = setting.GetDefault();
if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
@ -150,7 +150,7 @@ void Config::ReadBasicSetting(Settings::Setting<Type, ranged>& setting) {
}
template <typename Type, bool ranged>
void Config::ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting) {
void QtConfig::ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting) {
QString name = QString::fromStdString(setting.GetLabel());
const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
setting.SetGlobal(use_global);
@ -168,7 +168,7 @@ void Config::ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& settin
}
template <>
void Config::ReadGlobalSetting(Settings::SwitchableSetting<std::string>& setting) {
void QtConfig::ReadGlobalSetting(Settings::SwitchableSetting<std::string>& setting) {
QString name = QString::fromStdString(setting.GetLabel());
const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
setting.SetGlobal(use_global);
@ -181,7 +181,7 @@ void Config::ReadGlobalSetting(Settings::SwitchableSetting<std::string>& setting
// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant
template <>
void Config::WriteBasicSetting(const Settings::Setting<std::string>& setting) {
void QtConfig::WriteBasicSetting(const Settings::Setting<std::string>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const std::string& value = setting.GetValue();
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
@ -190,7 +190,7 @@ void Config::WriteBasicSetting(const Settings::Setting<std::string>& setting) {
// Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable
template <>
void Config::WriteBasicSetting(const Settings::Setting<u16>& setting) {
void QtConfig::WriteBasicSetting(const Settings::Setting<u16>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const u16& value = setting.GetValue();
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
@ -198,7 +198,7 @@ void Config::WriteBasicSetting(const Settings::Setting<u16>& setting) {
}
template <typename Type, bool ranged>
void Config::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting) {
void QtConfig::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const Type value = setting.GetValue();
qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
@ -210,7 +210,7 @@ void Config::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting) {
}
template <typename Type, bool ranged>
void Config::WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting) {
void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const Type& value = setting.GetValue(global);
if (!global) {
@ -227,7 +227,7 @@ void Config::WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>&
}
template <>
void Config::WriteGlobalSetting(const Settings::SwitchableSetting<std::string>& setting) {
void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting<std::string>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const std::string& value = setting.GetValue(global);
if (!global) {
@ -241,7 +241,7 @@ void Config::WriteGlobalSetting(const Settings::SwitchableSetting<std::string>&
// Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable
template <>
void Config::WriteGlobalSetting(const Settings::SwitchableSetting<u16, true>& setting) {
void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting<u16, true>& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
const u16& value = setting.GetValue(global);
if (!global) {
@ -253,7 +253,7 @@ void Config::WriteGlobalSetting(const Settings::SwitchableSetting<u16, true>& se
}
}
void Config::ReadValues() {
void QtConfig::ReadValues() {
if (global) {
ReadControlValues();
ReadCameraValues();
@ -273,7 +273,7 @@ void Config::ReadValues() {
ReadUtilityValues();
}
void Config::ReadAudioValues() {
void QtConfig::ReadAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
ReadGlobalSetting(Settings::values.audio_emulation);
@ -291,7 +291,7 @@ void Config::ReadAudioValues() {
qt_config->endGroup();
}
void Config::ReadCameraValues() {
void QtConfig::ReadCameraValues() {
using namespace Service::CAM;
qt_config->beginGroup(QStringLiteral("Camera"));
@ -325,7 +325,7 @@ void Config::ReadCameraValues() {
qt_config->endGroup();
}
void Config::ReadControlValues() {
void QtConfig::ReadControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
ReadBasicSetting(Settings::values.use_artic_base_controller);
@ -442,7 +442,7 @@ void Config::ReadControlValues() {
qt_config->endGroup();
}
void Config::ReadUtilityValues() {
void QtConfig::ReadUtilityValues() {
qt_config->beginGroup(QStringLiteral("Utility"));
ReadGlobalSetting(Settings::values.dump_textures);
@ -453,7 +453,7 @@ void Config::ReadUtilityValues() {
qt_config->endGroup();
}
void Config::ReadCoreValues() {
void QtConfig::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
ReadGlobalSetting(Settings::values.cpu_clock_percentage);
@ -466,7 +466,7 @@ void Config::ReadCoreValues() {
qt_config->endGroup();
}
void Config::ReadDataStorageValues() {
void QtConfig::ReadDataStorageValues() {
qt_config->beginGroup(QStringLiteral("Data Storage"));
ReadBasicSetting(Settings::values.use_virtual_sd);
@ -485,7 +485,7 @@ void Config::ReadDataStorageValues() {
qt_config->endGroup();
}
void Config::ReadDebuggingValues() {
void QtConfig::ReadDebuggingValues() {
qt_config->beginGroup(QStringLiteral("Debugging"));
// Intentionally not using the QT default setting as this is intended to be changed in the ini
@ -505,7 +505,7 @@ void Config::ReadDebuggingValues() {
qt_config->endGroup();
}
void Config::ReadLayoutValues() {
void QtConfig::ReadLayoutValues() {
qt_config->beginGroup(QStringLiteral("Layout"));
ReadGlobalSetting(Settings::values.render_3d);
@ -550,7 +550,7 @@ void Config::ReadLayoutValues() {
qt_config->endGroup();
}
void Config::ReadMiscellaneousValues() {
void QtConfig::ReadMiscellaneousValues() {
qt_config->beginGroup(QStringLiteral("Miscellaneous"));
ReadBasicSetting(Settings::values.log_filter);
@ -559,7 +559,7 @@ void Config::ReadMiscellaneousValues() {
qt_config->endGroup();
}
void Config::ReadMultiplayerValues() {
void QtConfig::ReadMultiplayerValues() {
qt_config->beginGroup(QStringLiteral("Multiplayer"));
UISettings::values.nickname = ReadSetting(QStringLiteral("nickname"), QString{}).toString();
@ -610,7 +610,7 @@ void Config::ReadMultiplayerValues() {
qt_config->endGroup();
}
void Config::ReadPathValues() {
void QtConfig::ReadPathValues() {
qt_config->beginGroup(QStringLiteral("Paths"));
ReadGlobalSetting(UISettings::values.screenshot_path);
@ -662,7 +662,7 @@ void Config::ReadPathValues() {
qt_config->endGroup();
}
void Config::ReadRendererValues() {
void QtConfig::ReadRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
ReadGlobalSetting(Settings::values.graphics_api);
@ -691,7 +691,7 @@ void Config::ReadRendererValues() {
qt_config->endGroup();
}
void Config::ReadShortcutValues() {
void QtConfig::ReadShortcutValues() {
qt_config->beginGroup(QStringLiteral("Shortcuts"));
for (const auto& [name, group, shortcut] : default_hotkeys) {
@ -712,7 +712,7 @@ void Config::ReadShortcutValues() {
qt_config->endGroup();
}
void Config::ReadSystemValues() {
void QtConfig::ReadSystemValues() {
qt_config->beginGroup(QStringLiteral("System"));
ReadGlobalSetting(Settings::values.is_new_3ds);
@ -738,7 +738,7 @@ const QString DEFAULT_VIDEO_ENCODER_OPTIONS =
QStringLiteral("quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1");
const QString DEFAULT_AUDIO_ENCODER_OPTIONS = QStringLiteral("");
void Config::ReadVideoDumpingValues() {
void QtConfig::ReadVideoDumpingValues() {
qt_config->beginGroup(QStringLiteral("VideoDumping"));
Settings::values.output_format =
@ -775,7 +775,7 @@ void Config::ReadVideoDumpingValues() {
qt_config->endGroup();
}
void Config::ReadUIValues() {
void QtConfig::ReadUIValues() {
qt_config->beginGroup(QStringLiteral("UI"));
ReadPathValues();
@ -811,7 +811,7 @@ void Config::ReadUIValues() {
qt_config->endGroup();
}
void Config::ReadUIGameListValues() {
void QtConfig::ReadUIGameListValues() {
qt_config->beginGroup(QStringLiteral("GameList"));
ReadBasicSetting(UISettings::values.game_list_icon_size);
@ -837,7 +837,7 @@ void Config::ReadUIGameListValues() {
qt_config->endGroup();
}
void Config::ReadUILayoutValues() {
void QtConfig::ReadUILayoutValues() {
qt_config->beginGroup(QStringLiteral("UILayout"));
UISettings::values.geometry = ReadSetting(QStringLiteral("geometry")).toByteArray();
@ -853,7 +853,7 @@ void Config::ReadUILayoutValues() {
qt_config->endGroup();
}
void Config::ReadUpdaterValues() {
void QtConfig::ReadUpdaterValues() {
qt_config->beginGroup(QStringLiteral("Updater"));
ReadBasicSetting(UISettings::values.check_for_update_on_start);
@ -862,7 +862,7 @@ void Config::ReadUpdaterValues() {
qt_config->endGroup();
}
void Config::ReadWebServiceValues() {
void QtConfig::ReadWebServiceValues() {
qt_config->beginGroup(QStringLiteral("WebService"));
NetSettings::values.web_api_url =
@ -877,7 +877,7 @@ void Config::ReadWebServiceValues() {
qt_config->endGroup();
}
void Config::SaveValues() {
void QtConfig::SaveValues() {
if (global) {
SaveControlValues();
SaveCameraValues();
@ -898,7 +898,7 @@ void Config::SaveValues() {
qt_config->sync();
}
void Config::SaveAudioValues() {
void QtConfig::SaveAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
WriteGlobalSetting(Settings::values.audio_emulation);
@ -916,7 +916,7 @@ void Config::SaveAudioValues() {
qt_config->endGroup();
}
void Config::SaveCameraValues() {
void QtConfig::SaveCameraValues() {
using namespace Service::CAM;
qt_config->beginGroup(QStringLiteral("Camera"));
@ -946,7 +946,7 @@ void Config::SaveCameraValues() {
qt_config->endGroup();
}
void Config::SaveControlValues() {
void QtConfig::SaveControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
WriteBasicSetting(Settings::values.use_artic_base_controller);
@ -1007,7 +1007,7 @@ void Config::SaveControlValues() {
qt_config->endGroup();
}
void Config::SaveUtilityValues() {
void QtConfig::SaveUtilityValues() {
qt_config->beginGroup(QStringLiteral("Utility"));
WriteGlobalSetting(Settings::values.dump_textures);
@ -1018,7 +1018,7 @@ void Config::SaveUtilityValues() {
qt_config->endGroup();
}
void Config::SaveCoreValues() {
void QtConfig::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
WriteGlobalSetting(Settings::values.cpu_clock_percentage);
@ -1031,7 +1031,7 @@ void Config::SaveCoreValues() {
qt_config->endGroup();
}
void Config::SaveDataStorageValues() {
void QtConfig::SaveDataStorageValues() {
qt_config->beginGroup(QStringLiteral("Data Storage"));
WriteBasicSetting(Settings::values.use_virtual_sd);
@ -1046,7 +1046,7 @@ void Config::SaveDataStorageValues() {
qt_config->endGroup();
}
void Config::SaveDebuggingValues() {
void QtConfig::SaveDebuggingValues() {
qt_config->beginGroup(QStringLiteral("Debugging"));
// Intentionally not using the QT default setting as this is intended to be changed in the ini
@ -1064,7 +1064,7 @@ void Config::SaveDebuggingValues() {
qt_config->endGroup();
}
void Config::SaveLayoutValues() {
void QtConfig::SaveLayoutValues() {
qt_config->beginGroup(QStringLiteral("Layout"));
WriteGlobalSetting(Settings::values.render_3d);
@ -1108,7 +1108,7 @@ void Config::SaveLayoutValues() {
qt_config->endGroup();
}
void Config::SaveMiscellaneousValues() {
void QtConfig::SaveMiscellaneousValues() {
qt_config->beginGroup(QStringLiteral("Miscellaneous"));
WriteBasicSetting(Settings::values.log_filter);
@ -1117,7 +1117,7 @@ void Config::SaveMiscellaneousValues() {
qt_config->endGroup();
}
void Config::SaveMultiplayerValues() {
void QtConfig::SaveMultiplayerValues() {
qt_config->beginGroup(QStringLiteral("Multiplayer"));
WriteSetting(QStringLiteral("nickname"), UISettings::values.nickname, QString{});
@ -1160,7 +1160,7 @@ void Config::SaveMultiplayerValues() {
qt_config->endGroup();
}
void Config::SavePathValues() {
void QtConfig::SavePathValues() {
qt_config->beginGroup(QStringLiteral("Paths"));
WriteGlobalSetting(UISettings::values.screenshot_path);
@ -1188,7 +1188,7 @@ void Config::SavePathValues() {
qt_config->endGroup();
}
void Config::SaveRendererValues() {
void QtConfig::SaveRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
WriteGlobalSetting(Settings::values.graphics_api);
@ -1218,7 +1218,7 @@ void Config::SaveRendererValues() {
qt_config->endGroup();
}
void Config::SaveShortcutValues() {
void QtConfig::SaveShortcutValues() {
qt_config->beginGroup(QStringLiteral("Shortcuts"));
// Lengths of UISettings::values.shortcuts & default_hotkeys are same.
@ -1238,7 +1238,7 @@ void Config::SaveShortcutValues() {
qt_config->endGroup();
}
void Config::SaveSystemValues() {
void QtConfig::SaveSystemValues() {
qt_config->beginGroup(QStringLiteral("System"));
WriteGlobalSetting(Settings::values.is_new_3ds);
@ -1258,7 +1258,7 @@ void Config::SaveSystemValues() {
qt_config->endGroup();
}
void Config::SaveVideoDumpingValues() {
void QtConfig::SaveVideoDumpingValues() {
qt_config->beginGroup(QStringLiteral("VideoDumping"));
WriteSetting(QStringLiteral("output_format"),
@ -1285,7 +1285,7 @@ void Config::SaveVideoDumpingValues() {
qt_config->endGroup();
}
void Config::SaveUIValues() {
void QtConfig::SaveUIValues() {
qt_config->beginGroup(QStringLiteral("UI"));
SavePathValues();
@ -1320,7 +1320,7 @@ void Config::SaveUIValues() {
qt_config->endGroup();
}
void Config::SaveUIGameListValues() {
void QtConfig::SaveUIGameListValues() {
qt_config->beginGroup(QStringLiteral("GameList"));
WriteBasicSetting(UISettings::values.game_list_icon_size);
@ -1346,7 +1346,7 @@ void Config::SaveUIGameListValues() {
qt_config->endGroup();
}
void Config::SaveUILayoutValues() {
void QtConfig::SaveUILayoutValues() {
qt_config->beginGroup(QStringLiteral("UILayout"));
WriteSetting(QStringLiteral("geometry"), UISettings::values.geometry);
@ -1360,7 +1360,7 @@ void Config::SaveUILayoutValues() {
qt_config->endGroup();
}
void Config::SaveUpdaterValues() {
void QtConfig::SaveUpdaterValues() {
qt_config->beginGroup(QStringLiteral("Updater"));
WriteBasicSetting(UISettings::values.check_for_update_on_start);
@ -1369,7 +1369,7 @@ void Config::SaveUpdaterValues() {
qt_config->endGroup();
}
void Config::SaveWebServiceValues() {
void QtConfig::SaveWebServiceValues() {
qt_config->beginGroup(QStringLiteral("WebService"));
WriteSetting(QStringLiteral("web_api_url"),
@ -1383,11 +1383,11 @@ void Config::SaveWebServiceValues() {
qt_config->endGroup();
}
QVariant Config::ReadSetting(const QString& name) const {
QVariant QtConfig::ReadSetting(const QString& name) const {
return qt_config->value(name);
}
QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const {
QVariant QtConfig::ReadSetting(const QString& name, const QVariant& default_value) const {
QVariant result;
if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
result = default_value;
@ -1397,22 +1397,22 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value)
return result;
}
void Config::WriteSetting(const QString& name, const QVariant& value) {
void QtConfig::WriteSetting(const QString& name, const QVariant& value) {
qt_config->setValue(name, value);
}
void Config::WriteSetting(const QString& name, const QVariant& value,
void QtConfig::WriteSetting(const QString& name, const QVariant& value,
const QVariant& default_value) {
qt_config->setValue(name + QStringLiteral("/default"), value == default_value);
qt_config->setValue(name, value);
}
void Config::Reload() {
void QtConfig::Reload() {
ReadValues();
// To apply default value changes
SaveValues();
}
void Config::Save() {
void QtConfig::Save() {
SaveValues();
}

View File

@ -13,13 +13,13 @@
class QSettings;
class Config {
class QtConfig {
public:
enum class ConfigType : u32 { GlobalConfig, PerGameConfig };
explicit Config(const std::string& config_name = "qt-config",
explicit QtConfig(const std::string& config_name = "qt-config",
ConfigType config_type = ConfigType::GlobalConfig);
~Config();
~QtConfig();
void Reload();
void Save();

View File

@ -162,7 +162,7 @@ void ConfigureHotkeys::RestoreDefaults() {
for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
model->item(r, 0)
->child(r2, hotkey_column)
->setText(Config::default_hotkeys[r2].shortcut.keyseq);
->setText(QtConfig::default_hotkeys[r2].shortcut.keyseq);
}
}
}
@ -198,7 +198,7 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) {
void ConfigureHotkeys::RestoreHotkey(QModelIndex index) {
const QKeySequence& default_key_sequence = QKeySequence::fromString(
Config::default_hotkeys[index.row()].shortcut.keyseq, QKeySequence::NativeText);
QtConfig::default_hotkeys[index.row()].shortcut.keyseq, QKeySequence::NativeText);
const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence);
if (key_sequence_used && default_key_sequence != QKeySequence(model->data(index).toString())) {

View File

@ -228,8 +228,9 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.addAction(tr("Restore Default"), this, [&] {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
buttons_param[button_id] =
Common::ParamPackage{InputCommon::GenerateKeyboardParam(
QtConfig::default_buttons[button_id])};
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
ApplyConfiguration();
Settings::SaveProfile(ui->profile->currentIndex());
@ -268,7 +269,7 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
});
context_menu.addAction(tr("Restore Default"), this, [&] {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
Config::default_analogs[analog_id][sub_button_id])};
QtConfig::default_analogs[analog_id][sub_button_id])};
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
@ -347,8 +348,8 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs;
analog_id++) {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
Config::default_analogs[analog_id]
[static_cast<u32>(AnalogSubButtons::modifier)])};
QtConfig::default_analogs[analog_id][static_cast<u32>(
AnalogSubButtons::modifier)])};
SetAnalogButton(params, analogs_param[analog_id], "modifier");
ui->buttonCircleMod->setText(
AnalogToText(analogs_param[analog_id], "modifier"));
@ -464,13 +465,13 @@ void ConfigureInput::LoadConfiguration() {
void ConfigureInput::RestoreDefaults() {
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
InputCommon::GenerateKeyboardParam(QtConfig::default_buttons[button_id])};
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
Config::default_analogs[analog_id][sub_button_id])};
QtConfig::default_analogs[analog_id][sub_button_id])};
SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
}
analogs_param[analog_id].Set("modifier_scale", 0.5f);

View File

@ -31,7 +31,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const QString
filename{file_name.toStdString()}, title_id{title_id_}, system{system_} {
const auto config_file_name = title_id == 0 ? std::string(FileUtil::GetFilename(filename))
: fmt::format("{:016X}", title_id);
game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
game_config = std::make_unique<QtConfig>(config_file_name, QtConfig::ConfigType::PerGameConfig);
const bool is_powered_on = system.IsPoweredOn();
audio_tab = std::make_unique<ConfigureAudio>(is_powered_on, this);

View File

@ -65,7 +65,7 @@ private:
QGraphicsScene* scene;
std::unique_ptr<Config> game_config;
std::unique_ptr<QtConfig> game_config;
Core::System& system;

View File

@ -35,7 +35,7 @@
#include "lime_qt/game_list.h"
#include "lime_qt/game_list_p.h"
#include "lime_qt/game_list_worker.h"
#include "lime_qt/main.h"
#include "lime_qt/lime_qt.h"
#include "lime_qt/uisettings.h"
#include "qcursor.h"

View File

@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <clocale>
#include <iostream>
#include <memory>
#include <optional>
#include <thread>
@ -24,7 +25,6 @@
#include <shlobj.h>
#include <windows.h>
#else
#include <iostream>
#include <getopt.h>
#endif
#ifdef __unix__
@ -44,6 +44,7 @@
#include "common/memory_detect.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "lime/common_strings.h"
#include "lime_qt/aboutdialog.h"
#include "lime_qt/applets/mii_selector.h"
#include "lime_qt/applets/swkbd.h"
@ -70,8 +71,8 @@
#include "lime_qt/dumping/dumping_dialog.h"
#include "lime_qt/game_list.h"
#include "lime_qt/hotkeys.h"
#include "lime_qt/lime_qt.h"
#include "lime_qt/loading_screen.h"
#include "lime_qt/main.h"
#include "lime_qt/movie/movie_play_dialog.h"
#include "lime_qt/movie/movie_record_dialog.h"
#include "lime_qt/multiplayer/state.h"
@ -116,13 +117,6 @@
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
#ifdef _WIN32
extern "C" {
// tells Nvidia drivers to use the dedicated GPU by default on laptops with switchable graphics
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
}
#endif
#ifdef HAVE_SDL2
#include <SDL.h>
#endif
@ -175,7 +169,7 @@ GMainWindow::GMainWindow(Core::System& system_)
Debugger::ToggleConsole();
this->config = std::make_unique<Config>();
this->config = std::make_unique<QtConfig>();
QStringList args = QApplication::arguments();
QString game_path;
@ -188,7 +182,7 @@ GMainWindow::GMainWindow(Core::System& system_)
}
// Dump video
if (args[i] == QStringLiteral("-d")) {
if (args[i] == QStringLiteral("--dump-video") || args[i] == QStringLiteral("-d")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
@ -202,13 +196,13 @@ GMainWindow::GMainWindow(Core::System& system_)
}
// Launch game in fullscreen mode
if (args[i] == QStringLiteral("-f")) {
if (args[i] == QStringLiteral("--fullscreen") || args[i] == QStringLiteral("-f")) {
fullscreen_override = true;
continue;
}
// Enable GDB stub
if (args[i] == QStringLiteral("-g")) {
if (args[i] == QStringLiteral("--gdbport") || args[i] == QStringLiteral("-g")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
@ -217,24 +211,12 @@ GMainWindow::GMainWindow(Core::System& system_)
continue;
}
if (args[i] == QStringLiteral("-h")) {
const std::string help_string =
std::string("Usage: ") + args[0].toStdString() +
" [options] <file path>\n"
"-d [path] Dump video recording of emulator playback to the given file path\n"
"-g [port] Enable gdb stub on the given port\n"
"-f Start in fullscreen mode\n"
"-h Display this help and exit\n"
"-i [path] Install a CIA file at the given path\n"
"-p [path] Play a TAS movie located at the given path\n"
"-r [path] Record a TAS movie to the given file path\n"
"-v Output version information and exit";
ShowCommandOutput("Help", help_string);
if (args[i] == QStringLiteral("--help") || args[i] == QStringLiteral("-h")) {
ShowCommandOutput("Help", fmt::format(Common::help_string, args[0].toStdString()));
exit(0);
}
if (args[i] == QStringLiteral("-i")) {
if (args[i] == QStringLiteral("--install") || args[i] == QStringLiteral("-i")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
@ -267,7 +249,7 @@ GMainWindow::GMainWindow(Core::System& system_)
exit(0);
}
if (args[i] == QStringLiteral("-p")) {
if (args[i] == QStringLiteral("--movie-play") || args[i] == QStringLiteral("-p")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
@ -276,7 +258,7 @@ GMainWindow::GMainWindow(Core::System& system_)
continue;
}
if (args[i] == QStringLiteral("-r")) {
if (args[i] == QStringLiteral("--movie-record") || args[i] == QStringLiteral("-r")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
@ -285,7 +267,25 @@ GMainWindow::GMainWindow(Core::System& system_)
continue;
}
if (args[i] == QStringLiteral("-v")) {
if (args[i] == QStringLiteral("--movie-record-author") || args[i] == QStringLiteral("-a")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
movie_record_author = args[++i];
continue;
}
if (args[i] == QStringLiteral("--multiplayer") || args[i] == QStringLiteral("-m")) {
std::cout << "Warning: The --multiplayer option is not yet implemented for the Qt "
"frontend; Ignoring."
<< std::endl;
if (i < args.size() - 1 && !args[i + 1].startsWith(QChar::fromLatin1('-'))) {
i++;
}
continue;
}
if (args[i] == QStringLiteral("--version") || args[i] == QStringLiteral("-v")) {
const std::string version_string =
std::string("Lime3DS ") + Common::g_scm_branch + " " + Common::g_scm_desc;
ShowCommandOutput("Version", version_string);
@ -293,7 +293,7 @@ GMainWindow::GMainWindow(Core::System& system_)
}
// Launch game in windowed mode
if (args[i] == QStringLiteral("-w")) {
if (args[i] == QStringLiteral("--windowed") || args[i] == QStringLiteral("-w")) {
fullscreen_override = false;
continue;
}
@ -1468,7 +1468,7 @@ void GMainWindow::BootGame(const QString& filename) {
const std::string config_file_name =
title_id == 0 ? name : fmt::format("{:016X}", title_id);
LOG_INFO(Frontend, "Loading per game config file for title {}", config_file_name);
Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
QtConfig per_game_config(config_file_name, QtConfig::ConfigType::PerGameConfig);
}
// Artic Base Server cannot accept a client multiple times, so multiple loaders are not
@ -3722,7 +3722,7 @@ static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() {
#endif
}
int main(int argc, char* argv[]) {
void LaunchQtFrontend(int argc, char* argv[]) {
Common::DetachedTasks detached_tasks;
MicroProfileOnThreadCreate("Frontend");
SCOPE_EXIT({ MicroProfileShutdown(); });
@ -3748,6 +3748,19 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
// Required when using .qrc resources from within a static library.
// See https://doc.qt.io/qt-5/resources.html#using-resources-in-a-library
Q_INIT_RESOURCE(compatibility_list);
Q_INIT_RESOURCE(theme_colorful);
Q_INIT_RESOURCE(theme_colorful_dark);
Q_INIT_RESOURCE(theme_colorful_midnight_blue);
Q_INIT_RESOURCE(theme_default);
Q_INIT_RESOURCE(theme_qdarkstyle);
Q_INIT_RESOURCE(theme_qdarkstyle_midnight_blue);
#ifdef ENABLE_QT_TRANSLATION
Q_INIT_RESOURCE(languages);
#endif
// Qt changes the locale and causes issues in float conversion using std::to_string() when
// generating shaders
setlocale(LC_ALL, "C");
@ -3777,5 +3790,5 @@ int main(int argc, char* argv[]) {
int result = app.exec();
detached_tasks.WaitForAllTasks();
return result;
exit(result);
}

View File

@ -25,7 +25,7 @@
#include <filesystem>
class AboutDialog;
class Config;
class QtConfig;
class ClickableLabel;
class EmuThread;
class GameList;
@ -86,6 +86,8 @@ namespace Service::FS {
enum class MediaType : u32;
}
void LaunchQtFrontend(int argc, char* argv[]);
class GMainWindow : public QMainWindow {
Q_OBJECT
@ -348,7 +350,7 @@ private:
bool message_label_used_for_movie = false;
MultiplayerState* multiplayer_state = nullptr;
std::unique_ptr<Config> config;
std::unique_ptr<QtConfig> config;
// Whether emulation is currently running in Lime3DS.
bool emulation_running = false;

View File

@ -9,7 +9,7 @@
#include <QString>
#include <QtConcurrent/QtConcurrentRun>
#include "core/hle/service/cfg/cfg.h"
#include "lime_qt/main.h"
#include "lime_qt/lime_qt.h"
#include "lime_qt/multiplayer/direct_connect.h"
#include "lime_qt/multiplayer/message.h"
#include "lime_qt/multiplayer/validation.h"

View File

@ -8,7 +8,7 @@
#include "common/logging/log.h"
#include "core/hle/service/cfg/cfg.h"
#include "lime_qt/game_list_p.h"
#include "lime_qt/main.h"
#include "lime_qt/lime_qt.h"
#include "lime_qt/multiplayer/lobby.h"
#include "lime_qt/multiplayer/lobby_p.h"
#include "lime_qt/multiplayer/message.h"

View File

@ -0,0 +1,48 @@
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
add_library(lime_sdl STATIC EXCLUDE_FROM_ALL
config.cpp
config.h
default_ini.h
emu_window/emu_window_sdl2.cpp
emu_window/emu_window_sdl2.h
lime_sdl.cpp
precompiled_headers.h
resource.h
)
if (ENABLE_SOFTWARE_RENDERER)
target_sources(lime_sdl PRIVATE
emu_window/emu_window_sdl2_sw.cpp
emu_window/emu_window_sdl2_sw.h
)
endif()
if (ENABLE_OPENGL)
target_sources(lime_sdl PRIVATE
emu_window/emu_window_sdl2_gl.cpp
emu_window/emu_window_sdl2_gl.h
)
endif()
if (ENABLE_VULKAN)
target_sources(lime_sdl PRIVATE
emu_window/emu_window_sdl2_vk.cpp
emu_window/emu_window_sdl2_vk.h
)
endif()
create_target_directory_groups(lime_sdl)
target_link_libraries(lime_sdl PRIVATE lime_common lime_core input_common network)
target_link_libraries(lime_sdl PRIVATE inih)
if (MSVC)
target_link_libraries(lime_sdl PRIVATE getopt)
endif()
target_link_libraries(lime_sdl PRIVATE ${PLATFORM_LIBRARIES} SDL2::SDL2 Threads::Threads)
if (ENABLE_OPENGL)
target_link_libraries(lime_sdl PRIVATE glad)
endif()
if (LIME3DS_USE_PRECOMPILED_HEADERS)
target_precompile_headers(lime_sdl PRIVATE precompiled_headers.h)
endif()

383
src/lime_sdl/config.cpp Normal file
View File

@ -0,0 +1,383 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <iomanip>
#include <memory>
#include <sstream>
#include <type_traits>
#include <INIReader.h>
#include <SDL.h>
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/hle/service/service.h"
#include "input_common/main.h"
#include "input_common/udp/client.h"
#include "lime_sdl/config.h"
#include "lime_sdl/default_ini.h"
#include "network/network_settings.h"
SdlConfig::SdlConfig() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
sdl2_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-config.ini";
sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
Reload();
}
SdlConfig::~SdlConfig() = default;
bool SdlConfig::LoadINI(const std::string& default_contents, bool retry) {
const std::string& location = this->sdl2_config_loc;
if (sdl2_config->ParseError() < 0) {
if (retry) {
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
FileUtil::CreateFullPath(location);
FileUtil::WriteStringToFile(true, location, default_contents);
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
return LoadINI(default_contents, false);
}
LOG_ERROR(Config, "Failed.");
return false;
}
LOG_INFO(Config, "Successfully loaded {}", location);
return true;
}
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons = {
SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T, SDL_SCANCODE_G,
SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_M, SDL_SCANCODE_N,
SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B,
};
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{
{
SDL_SCANCODE_UP,
SDL_SCANCODE_DOWN,
SDL_SCANCODE_LEFT,
SDL_SCANCODE_RIGHT,
SDL_SCANCODE_D,
},
{
SDL_SCANCODE_I,
SDL_SCANCODE_K,
SDL_SCANCODE_J,
SDL_SCANCODE_L,
SDL_SCANCODE_D,
},
}};
template <>
void SdlConfig::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
std::string setting_value = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault());
if (setting_value.empty()) {
setting_value = setting.GetDefault();
}
setting = std::move(setting_value);
}
template <>
void SdlConfig::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
}
template <typename Type, bool ranged>
void SdlConfig::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
if constexpr (std::is_floating_point_v<Type>) {
setting = static_cast<Type>(
sdl2_config->GetReal(group, setting.GetLabel(), setting.GetDefault()));
} else {
setting = static_cast<Type>(sdl2_config->GetInteger(
group, setting.GetLabel(), static_cast<long>(setting.GetDefault())));
}
}
void SdlConfig::ReadValues() {
// Controls
// TODO: add multiple input profile support
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
Settings::values.current_input_profile.buttons[i] =
sdl2_config->GetString("Controls", Settings::NativeButton::mapping[i], default_param);
if (Settings::values.current_input_profile.buttons[i].empty())
Settings::values.current_input_profile.buttons[i] = default_param;
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
Settings::values.current_input_profile.analogs[i] =
sdl2_config->GetString("Controls", Settings::NativeAnalog::mapping[i], default_param);
if (Settings::values.current_input_profile.analogs[i].empty())
Settings::values.current_input_profile.analogs[i] = default_param;
}
Settings::values.current_input_profile.motion_device = sdl2_config->GetString(
"Controls", "motion_device",
"engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0");
Settings::values.current_input_profile.touch_device =
sdl2_config->GetString("Controls", "touch_device", "engine:emu_window");
Settings::values.current_input_profile.udp_input_address = sdl2_config->GetString(
"Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR);
Settings::values.current_input_profile.udp_input_port =
static_cast<u16>(sdl2_config->GetInteger("Controls", "udp_input_port",
InputCommon::CemuhookUDP::DEFAULT_PORT));
// Core
ReadSetting("Core", Settings::values.use_cpu_jit);
ReadSetting("Core", Settings::values.cpu_clock_percentage);
// Renderer
ReadSetting("Renderer", Settings::values.graphics_api);
ReadSetting("Renderer", Settings::values.physical_device);
ReadSetting("Renderer", Settings::values.spirv_shader_gen);
ReadSetting("Renderer", Settings::values.async_shader_compilation);
ReadSetting("Renderer", Settings::values.async_presentation);
ReadSetting("Renderer", Settings::values.use_gles);
ReadSetting("Renderer", Settings::values.use_hw_shader);
ReadSetting("Renderer", Settings::values.shaders_accurate_mul);
ReadSetting("Renderer", Settings::values.use_shader_jit);
ReadSetting("Renderer", Settings::values.resolution_factor);
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
ReadSetting("Renderer", Settings::values.frame_limit);
ReadSetting("Renderer", Settings::values.use_vsync_new);
ReadSetting("Renderer", Settings::values.texture_filter);
ReadSetting("Renderer", Settings::values.texture_sampling);
ReadSetting("Renderer", Settings::values.mono_render_option);
ReadSetting("Renderer", Settings::values.render_3d);
ReadSetting("Renderer", Settings::values.factor_3d);
ReadSetting("Renderer", Settings::values.pp_shader_name);
ReadSetting("Renderer", Settings::values.anaglyph_shader_name);
ReadSetting("Renderer", Settings::values.filter_mode);
ReadSetting("Renderer", Settings::values.bg_red);
ReadSetting("Renderer", Settings::values.bg_green);
ReadSetting("Renderer", Settings::values.bg_blue);
// Layout
ReadSetting("Layout", Settings::values.layout_option);
ReadSetting("Layout", Settings::values.swap_screen);
ReadSetting("Layout", Settings::values.upright_screen);
ReadSetting("Layout", Settings::values.large_screen_proportion);
ReadSetting("Layout", Settings::values.custom_top_x);
ReadSetting("Layout", Settings::values.custom_top_y);
ReadSetting("Layout", Settings::values.custom_top_width);
ReadSetting("Layout", Settings::values.custom_top_height);
ReadSetting("Layout", Settings::values.custom_bottom_x);
ReadSetting("Layout", Settings::values.custom_bottom_y);
ReadSetting("Layout", Settings::values.custom_bottom_width);
ReadSetting("Layout", Settings::values.custom_bottom_height);
ReadSetting("Layout", Settings::values.custom_second_layer_opacity);
ReadSetting("Layout", Settings::values.screen_top_stretch);
ReadSetting("Layout", Settings::values.screen_top_leftright_padding);
ReadSetting("Layout", Settings::values.screen_top_topbottom_padding);
ReadSetting("Layout", Settings::values.screen_bottom_stretch);
ReadSetting("Layout", Settings::values.screen_bottom_leftright_padding);
ReadSetting("Layout", Settings::values.screen_bottom_topbottom_padding);
ReadSetting("Layout", Settings::values.portrait_layout_option);
ReadSetting("Layout", Settings::values.custom_portrait_top_x);
ReadSetting("Layout", Settings::values.custom_portrait_top_y);
ReadSetting("Layout", Settings::values.custom_portrait_top_width);
ReadSetting("Layout", Settings::values.custom_portrait_top_height);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_x);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_y);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_width);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_height);
// Utility
ReadSetting("Utility", Settings::values.dump_textures);
ReadSetting("Utility", Settings::values.custom_textures);
ReadSetting("Utility", Settings::values.preload_textures);
ReadSetting("Utility", Settings::values.async_custom_loading);
// Audio
ReadSetting("Audio", Settings::values.audio_emulation);
ReadSetting("Audio", Settings::values.enable_audio_stretching);
ReadSetting("Audio", Settings::values.enable_realtime_audio);
ReadSetting("Audio", Settings::values.volume);
ReadSetting("Audio", Settings::values.output_type);
ReadSetting("Audio", Settings::values.output_device);
ReadSetting("Audio", Settings::values.input_type);
ReadSetting("Audio", Settings::values.input_device);
// Data Storage
ReadSetting("Data Storage", Settings::values.use_virtual_sd);
ReadSetting("Data Storage", Settings::values.use_custom_storage);
if (Settings::values.use_custom_storage) {
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir,
sdl2_config->GetString("Data Storage", "nand_directory", ""));
FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir,
sdl2_config->GetString("Data Storage", "sdmc_directory", ""));
}
// System
ReadSetting("System", Settings::values.is_new_3ds);
ReadSetting("System", Settings::values.lle_applets);
ReadSetting("System", Settings::values.region_value);
ReadSetting("System", Settings::values.init_clock);
{
std::tm t;
t.tm_sec = 1;
t.tm_min = 0;
t.tm_hour = 0;
t.tm_mday = 1;
t.tm_mon = 0;
t.tm_year = 100;
t.tm_isdst = 0;
std::istringstream string_stream(
sdl2_config->GetString("System", "init_time", "2000-01-01 00:00:01"));
string_stream >> std::get_time(&t, "%Y-%m-%d %H:%M:%S");
if (string_stream.fail()) {
LOG_ERROR(Config, "Failed To parse init_time. Using 2000-01-01 00:00:01");
}
Settings::values.init_time =
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch())
.count();
}
ReadSetting("System", Settings::values.init_ticks_type);
ReadSetting("System", Settings::values.init_ticks_override);
ReadSetting("System", Settings::values.plugin_loader_enabled);
ReadSetting("System", Settings::values.allow_plugin_loader);
{
constexpr const char* default_init_time_offset = "0 00:00:00";
std::string offset_string =
sdl2_config->GetString("System", "init_time_offset", default_init_time_offset);
std::size_t sep_index = offset_string.find(' ');
if (sep_index == std::string::npos) {
LOG_ERROR(Config, "Failed to parse init_time_offset. Using 0 00:00:00");
offset_string = default_init_time_offset;
sep_index = offset_string.find(' ');
}
std::string day_string = offset_string.substr(0, sep_index);
long long days = 0;
try {
days = std::stoll(day_string);
} catch (std::logic_error&) {
LOG_ERROR(Config, "Failed to parse days in init_time_offset. Using 0");
days = 0;
}
long long days_in_seconds = days * 86400;
std::tm t;
t.tm_sec = 0;
t.tm_min = 0;
t.tm_hour = 0;
t.tm_mday = 1;
t.tm_mon = 0;
t.tm_year = 100;
t.tm_isdst = 0;
std::istringstream string_stream(offset_string.substr(sep_index + 1));
string_stream >> std::get_time(&t, "%H:%M:%S");
if (string_stream.fail()) {
LOG_ERROR(Config,
"Failed to parse hours, minutes and seconds in init_time_offset. 00:00:00");
}
auto time_offset =
std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch();
auto secs = std::chrono::duration_cast<std::chrono::seconds>(time_offset).count();
Settings::values.init_time_offset = static_cast<long long>(secs) + days_in_seconds;
}
// Camera
using namespace Service::CAM;
Settings::values.camera_name[OuterRightCamera] =
sdl2_config->GetString("Camera", "camera_outer_right_name", "blank");
Settings::values.camera_config[OuterRightCamera] =
sdl2_config->GetString("Camera", "camera_outer_right_config", "");
Settings::values.camera_flip[OuterRightCamera] =
sdl2_config->GetInteger("Camera", "camera_outer_right_flip", 0);
Settings::values.camera_name[InnerCamera] =
sdl2_config->GetString("Camera", "camera_inner_name", "blank");
Settings::values.camera_config[InnerCamera] =
sdl2_config->GetString("Camera", "camera_inner_config", "");
Settings::values.camera_flip[InnerCamera] =
sdl2_config->GetInteger("Camera", "camera_inner_flip", 0);
Settings::values.camera_name[OuterLeftCamera] =
sdl2_config->GetString("Camera", "camera_outer_left_name", "blank");
Settings::values.camera_config[OuterLeftCamera] =
sdl2_config->GetString("Camera", "camera_outer_left_config", "");
Settings::values.camera_flip[OuterLeftCamera] =
sdl2_config->GetInteger("Camera", "camera_outer_left_flip", 0);
// Miscellaneous
ReadSetting("Miscellaneous", Settings::values.log_filter);
// Apply the log_filter setting as the logger has already been initialized
// and doesn't pick up the filter on its own.
Common::Log::Filter filter;
filter.ParseFilterString(Settings::values.log_filter.GetValue());
Common::Log::SetGlobalFilter(filter);
// Debugging
Settings::values.record_frame_times =
sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
ReadSetting("Debugging", Settings::values.renderer_debug);
ReadSetting("Debugging", Settings::values.use_gdbstub);
ReadSetting("Debugging", Settings::values.gdbstub_port);
for (const auto& service_module : Service::service_module_map) {
bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false);
Settings::values.lle_modules.emplace(service_module.name, use_lle);
}
// Web Service
NetSettings::values.web_api_url =
sdl2_config->GetString("WebService", "web_api_url", "https://api.citra-emu.org");
NetSettings::values.lime3ds_username =
sdl2_config->GetString("WebService", "lime3ds_username", "");
NetSettings::values.lime3ds_token = sdl2_config->GetString("WebService", "lime3ds_token", "");
// Video Dumping
Settings::values.output_format =
sdl2_config->GetString("Video Dumping", "output_format", "webm");
Settings::values.format_options = sdl2_config->GetString("Video Dumping", "format_options", "");
Settings::values.video_encoder =
sdl2_config->GetString("Video Dumping", "video_encoder", "libvpx-vp9");
// Options for variable bit rate live streaming taken from here:
// https://developers.google.com/media/vp9/live-encoding
std::string default_video_options;
if (Settings::values.video_encoder == "libvpx-vp9") {
default_video_options =
"quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1";
}
Settings::values.video_encoder_options =
sdl2_config->GetString("Video Dumping", "video_encoder_options", default_video_options);
Settings::values.video_bitrate =
sdl2_config->GetInteger("Video Dumping", "video_bitrate", 2500000);
Settings::values.audio_encoder =
sdl2_config->GetString("Video Dumping", "audio_encoder", "libvorbis");
Settings::values.audio_encoder_options =
sdl2_config->GetString("Video Dumping", "audio_encoder_options", "");
Settings::values.audio_bitrate =
sdl2_config->GetInteger("Video Dumping", "audio_bitrate", 64000);
}
void SdlConfig::Reload() {
LoadINI(DefaultINI::sdl2_config_file);
ReadValues();
}

35
src/lime_sdl/config.h Normal file
View File

@ -0,0 +1,35 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include "common/settings.h"
class INIReader;
class SdlConfig {
std::unique_ptr<INIReader> sdl2_config;
std::string sdl2_config_loc;
bool LoadINI(const std::string& default_contents = "", bool retry = true);
void ReadValues();
public:
SdlConfig();
~SdlConfig();
void Reload();
private:
/**
* Applies a value read from the sdl2_config to a Setting.
*
* @param group The name of the INI group
* @param setting The yuzu setting to modify
*/
template <typename Type, bool ranged>
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
};

398
src/lime_sdl/default_ini.h Normal file
View File

@ -0,0 +1,398 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace DefaultINI {
const char* sdl2_config_file = R"(
[Controls]
# The input devices and parameters for each 3DS native input
# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
# for button input, the following devices are available:
# - "keyboard" (default) for keyboard input. Required parameters:
# - "code": the code of the key to bind
# - "sdl" for joystick input using SDL. Required parameters:
# - "joystick": the index of the joystick to bind
# - "button"(optional): the index of the button to bind
# - "hat"(optional): the index of the hat to bind as direction buttons
# - "axis"(optional): the index of the axis to bind
# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
# triggered if the axis value crosses
# - "direction"(only used for axis): "+" means the button is triggered when the axis value
# is greater than the threshold; "-" means the button is triggered when the axis value
# is smaller than the threshold
button_a=
button_b=
button_x=
button_y=
button_up=
button_down=
button_left=
button_right=
button_l=
button_r=
button_start=
button_select=
button_debug=
button_gpio14=
button_zl=
button_zr=
button_home=
# for analog input, the following devices are available:
# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
# - "up", "down", "left", "right": sub-devices for each direction.
# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
# - "modifier": sub-devices as a modifier.
# - "modifier_scale": a float number representing the applied modifier scale to the analog input.
# Must be in range of 0.0-1.0. Defaults to 0.5
# - "sdl" for joystick input using SDL. Required parameters:
# - "joystick": the index of the joystick to bind
# - "axis_x": the index of the axis to bind as x-axis (default to 0)
# - "axis_y": the index of the axis to bind as y-axis (default to 1)
circle_pad=
c_stick=
# for motion input, the following devices are available:
# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
# - "update_period": update period in milliseconds (default to 100)
# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
# - "tilt_clamp": the max value of the tilt angle in degrees (default to 90)
# - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol
motion_device=
# for touch input, the following devices are available:
# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required
# - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol
# - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system
touch_device=
# Most desktop operating systems do not expose a way to poll the motion state of the controllers
# so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly
# from a controller device to the client program. Lime3DS has a client that can connect and read
# from any cemuhook compatible motion program.
# IPv4 address of the udp input server (Default "127.0.0.1")
udp_input_address=
# Port of the udp input server. (Default 26760)
udp_input_port=
# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0)
udp_pad_index=
[Core]
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
# 0: Interpreter (slow), 1 (default): JIT (fast)
use_cpu_jit =
# Change the Clock Frequency of the emulated 3DS CPU.
# Underclocking can increase the performance of the game at the risk of freezing.
# Overclocking may fix lag that happens on console, but also comes with the risk of freezing.
# Range is any positive integer (but we suspect 25 - 400 is a good idea) Default is 100
cpu_clock_percentage =
[Renderer]
# Whether to render using OpenGL or Software
# 0: Software, 1: OpenGL (default), 2: Vulkan
graphics_api =
# Whether to render using GLES or OpenGL
# 0 (default): OpenGL, 1: GLES
use_gles =
# Whether to use hardware shaders to emulate 3DS shaders
# 0: Software, 1 (default): Hardware
use_hw_shader =
# Whether to use accurate multiplication in hardware shaders
# 0: Off (Faster, but causes issues in some games) 1: On (Default. Slower, but correct)
shaders_accurate_mul =
# Whether to use the Just-In-Time (JIT) compiler for shader emulation
# 0: Interpreter (slow), 1 (default): JIT (fast)
use_shader_jit =
# Forces VSync on the display thread. Usually doesn't impact performance, but on some drivers it can
# so only turn this off if you notice a speed difference.
# 0: Off, 1 (default): On
use_vsync_new =
# Reduce stuttering by storing and loading generated shaders to disk
# 0: Off, 1 (default. On)
use_disk_shader_cache =
# Resolution scale factor
# 0: Auto (scales resolution to window size), 1: Native 3DS screen resolution, Otherwise a scale
# factor for the 3DS resolution
resolution_factor =
# Texture filter
# 0: None, 1: Anime4K, 2: Bicubic, 3: Nearest Neighbor, 4: ScaleForce, 5: xBRZ
texture_filter =
# Limits the speed of the game to run no faster than this value as a percentage of target speed.
# Will not have an effect if unthrottled is enabled.
# 5 - 995: Speed limit as a percentage of target game speed. 0 for unthrottled. 100 (default)
frame_limit =
# Overrides the frame limiter to use frame_limit_alternate instead of frame_limit.
# 0: Off (default), 1: On
use_frame_limit_alternate =
# Alternate speed limit to be used instead of frame_limit if use_frame_limit_alternate is enabled
# 5 - 995: Speed limit as a percentage of target game speed. 0 for unthrottled. 200 (default)
frame_limit_alternate =
# The clear color for the renderer. What shows up on the sides of the bottom screen.
# Must be in range of 0.0-1.0. Defaults to 0.0 for all.
bg_red =
bg_blue =
bg_green =
# Whether and how Stereoscopic 3D should be rendered
# 0 (default): Off, 1: Side by Side, 2: Reverse Side by Side, 3: Anaglyph, 4: Interlaced, 5: Reverse Interlaced
render_3d =
# Change 3D Intensity
# 0 - 100: Intensity. 0 (default)
factor_3d =
# Change Default Eye to Render When in Monoscopic Mode
# 0 (default): Left, 1: Right
mono_render_option =
# The name of the post processing shader to apply.
# Loaded from shaders if render_3d is off or side by side.
pp_shader_name =
# The name of the shader to apply when render_3d is anaglyph.
# Loaded from shaders/anaglyph
anaglyph_shader_name =
# Whether to enable linear filtering or not
# This is required for some shaders to work correctly
# 0: Nearest, 1 (default): Linear
filter_mode =
[Layout]
# Layout for the screen inside the render window.
# 0 (default): Default Above/Below Screen
# 1: Single Screen Only
# 2: Large Screen Small Screen
# 3: Side by Side
# 4: Separate Windows
# 5: Hybrid Screen
# 6: Custom Layout
layout_option =
# Screen placement when using Custom layout option
# 0x, 0y is the top left corner of the render window.
custom_top_x =
custom_top_y =
custom_top_width =
custom_top_height =
custom_bottom_x =
custom_bottom_y =
custom_bottom_width =
custom_bottom_height =
# Opacity of second layer when using custom layout option (bottom screen unless swapped)
custom_second_layer_opacity =
# Swaps the prominent screen with the other screen.
# For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen.
# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent
swap_screen =
# Toggle upright orientation, for book style games.
# 0 (default): Off, 1: On
upright_screen =
# The proportion between the large and small screens when playing in Large Screen Small Screen layout.
# Must be a real value between 1.0 and 16.0. Default is 4
large_screen_proportion =
# Dumps textures as PNG to dump/textures/[Title ID]/.
# 0 (default): Off, 1: On
dump_textures =
# Reads PNG files from load/textures/[Title ID]/ and replaces textures.
# 0 (default): Off, 1: On
custom_textures =
# Loads all custom textures into memory before booting.
# 0 (default): Off, 1: On
preload_textures =
# Loads custom textures asynchronously with background threads.
# 0: Off, 1 (default): On
async_custom_loading =
[Audio]
# Whether or not to enable DSP LLE
# 0 (default): No, 1: Yes
enable_dsp_lle =
# Whether or not to run DSP LLE on a different thread
# 0 (default): No, 1: Yes
enable_dsp_lle_thread =
# Whether or not to enable the audio-stretching post-processing effect.
# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter,
# at the cost of increasing audio latency.
# 0: No, 1 (default): Yes
enable_audio_stretching =
# Scales audio playback speed to account for drops in emulation framerate
# 0 (default): No, 1: Yes
enable_realtime_audio =
# Output volume.
# 1.0 (default): 100%, 0.0; mute
volume =
# Which audio output type to use.
# 0 (default): Auto-select, 1: No audio output, 2: Cubeb (if available), 3: OpenAL (if available), 4: SDL2 (if available)
output_type =
# Which audio output device to use.
# auto (default): Auto-select
output_device =
# Which audio input type to use.
# 0 (default): Auto-select, 1: No audio input, 2: Static noise, 3: Cubeb (if available), 4: OpenAL (if available)
input_type =
# Which audio input device to use.
# auto (default): Auto-select
input_device =
[Data Storage]
# Whether to create a virtual SD card.
# 1 (default): Yes, 0: No
use_virtual_sd =
# Whether to use custom storage locations
# 1: Yes, 0 (default): No
use_custom_storage =
# The path of the virtual SD card directory.
# empty (default) will use the user_path
sdmc_directory =
# The path of NAND directory.
# empty (default) will use the user_path
nand_directory =
[System]
# The system model that Lime3DS will try to emulate
# 0: Old 3DS, 1: New 3DS (default)
is_new_3ds =
# Whether to use LLE system applets, if installed
# 0 (default): No, 1: Yes
lle_applets =
# The system region that Lime3DS will use during emulation
# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
region_value =
# The clock to use when lime3ds starts
# 0: System clock (default), 1: fixed time
init_clock =
# Time used when init_clock is set to fixed_time in the format %Y-%m-%d %H:%M:%S
# set to fixed time. Default 2000-01-01 00:00:01
# Note: 3DS can only handle times later then Jan 1 2000
init_time =
# The system ticks count to use when lime3ds starts
# 0: Random (default), 1: Fixed
init_ticks_type =
# Tick count to use when init_ticks_type is set to Fixed.
# Defaults to 0.
init_ticks_override =
[Camera]
# Which camera engine to use for the right outer camera
# blank (default): a dummy camera that always returns black image
camera_outer_right_name =
# A config string for the right outer camera. Its meaning is defined by the camera engine
camera_outer_right_config =
# The image flip to apply
# 0: None (default), 1: Horizontal, 2: Vertical, 3: Reverse
camera_outer_right_flip =
# ... for the left outer camera
camera_outer_left_name =
camera_outer_left_config =
camera_outer_left_flip =
# ... for the inner camera
camera_inner_name =
camera_inner_config =
camera_inner_flip =
[Miscellaneous]
# A filter which removes logs below a certain logging level.
# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
log_filter = *:Info
[Debugging]
# Record frame time data, can be found in the log directory. Boolean value
record_frame_times =
# Port for listening to GDB connections.
use_gdbstub=false
gdbstub_port=24689
# Whether to enable additional debugging information during emulation
# 0 (default): Off, 1: On
renderer_debug =
# To LLE a service module add "LLE\<module name>=true"
[WebService]
# URL for Web API
web_api_url = https://api.citra-emu.org
# Username and token for Lime3DS Web Service
# See https://profile.citra-emu.org/ for more info
lime3ds_username =
lime3ds_token =
[Video Dumping]
# Format of the video to output, default: webm
output_format =
# Options passed to the muxer (optional)
# This is a param package, format: [key1]:[value1],[key2]:[value2],...
format_options =
# Video encoder used, default: libvpx-vp9
video_encoder =
# Options passed to the video codec (optional)
video_encoder_options =
# Video bitrate, default: 2500000
video_bitrate =
# Audio encoder used, default: libvorbis
audio_encoder =
# Options passed to the audio codec (optional)
audio_encoder_options =
# Audio bitrate, default: 64000
audio_bitrate =
)";
}

View File

@ -0,0 +1,252 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstdlib>
#include <string>
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "core/core.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
#include "lime_sdl/emu_window/emu_window_sdl2.h"
#include "network/network.h"
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
InputCommon::GetMotionEmu()->Tilt(x, y);
}
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
if (button == SDL_BUTTON_LEFT) {
if (state == SDL_PRESSED) {
TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
} else {
TouchReleased();
}
} else if (button == SDL_BUTTON_RIGHT) {
if (state == SDL_PRESSED) {
InputCommon::GetMotionEmu()->BeginTilt(x, y);
} else {
InputCommon::GetMotionEmu()->EndTilt();
}
}
}
std::pair<unsigned, unsigned> EmuWindow_SDL2::TouchToPixelPos(float touch_x, float touch_y) const {
int w, h;
SDL_GetWindowSize(render_window, &w, &h);
touch_x *= w;
touch_y *= h;
return {static_cast<unsigned>(std::max(std::round(touch_x), 0.0f)),
static_cast<unsigned>(std::max(std::round(touch_y), 0.0f))};
}
void EmuWindow_SDL2::OnFingerDown(float x, float y) {
// TODO(NeatNit): keep track of multitouch using the fingerID and a dictionary of some kind
// This isn't critical because the best we can do when we have that is to average them, like the
// 3DS does
const auto [px, py] = TouchToPixelPos(x, y);
TouchPressed(px, py);
}
void EmuWindow_SDL2::OnFingerMotion(float x, float y) {
const auto [px, py] = TouchToPixelPos(x, y);
TouchMoved(px, py);
}
void EmuWindow_SDL2::OnFingerUp() {
TouchReleased();
}
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
if (state == SDL_PRESSED) {
InputCommon::GetKeyboard()->PressKey(key);
} else if (state == SDL_RELEASED) {
InputCommon::GetKeyboard()->ReleaseKey(key);
}
}
bool EmuWindow_SDL2::IsOpen() const {
return is_open;
}
void EmuWindow_SDL2::RequestClose() {
is_open = false;
}
void EmuWindow_SDL2::OnResize() {
int width, height;
SDL_GL_GetDrawableSize(render_window, &width, &height);
UpdateCurrentFramebufferLayout(width, height);
}
void EmuWindow_SDL2::Fullscreen() {
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) {
return;
}
LOG_ERROR(Frontend, "Fullscreening failed: {}", SDL_GetError());
// Try a different fullscreening method
LOG_INFO(Frontend, "Attempting to use borderless fullscreen...");
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) {
return;
}
LOG_ERROR(Frontend, "Borderless fullscreening failed: {}", SDL_GetError());
// Fallback algorithm: Maximise window.
// Works on all systems (unless something is seriously wrong), so no fallback for this one.
LOG_INFO(Frontend, "Falling back on a maximised window...");
SDL_MaximizeWindow(render_window);
}
EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system_, bool is_secondary)
: EmuWindow(is_secondary), system(system_) {}
EmuWindow_SDL2::~EmuWindow_SDL2() {
SDL_Quit();
}
void EmuWindow_SDL2::InitializeSDL2() {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL2: {}! Exiting...", SDL_GetError());
exit(1);
}
InputCommon::Init();
Network::Init();
SDL_SetMainReady();
}
u32 EmuWindow_SDL2::GetEventWindowId(const SDL_Event& event) const {
switch (event.type) {
case SDL_WINDOWEVENT:
return event.window.windowID;
case SDL_KEYDOWN:
case SDL_KEYUP:
return event.key.windowID;
case SDL_MOUSEMOTION:
return event.motion.windowID;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
return event.button.windowID;
case SDL_MOUSEWHEEL:
return event.wheel.windowID;
case SDL_FINGERDOWN:
case SDL_FINGERMOTION:
case SDL_FINGERUP:
return event.tfinger.windowID;
case SDL_TEXTEDITING:
return event.edit.windowID;
case SDL_TEXTEDITING_EXT:
return event.editExt.windowID;
case SDL_TEXTINPUT:
return event.text.windowID;
case SDL_DROPBEGIN:
case SDL_DROPFILE:
case SDL_DROPTEXT:
case SDL_DROPCOMPLETE:
return event.drop.windowID;
case SDL_USEREVENT:
return event.user.windowID;
default:
// Event is not for any particular window, so we can just pretend it's for this one.
return render_window_id;
}
}
void EmuWindow_SDL2::PollEvents() {
SDL_Event event;
std::vector<SDL_Event> other_window_events;
// SDL_PollEvent returns 0 when there are no more events in the event queue
while (SDL_PollEvent(&event)) {
if (GetEventWindowId(event) != render_window_id) {
other_window_events.push_back(event);
continue;
}
switch (event.type) {
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_MAXIMIZED:
case SDL_WINDOWEVENT_RESTORED:
case SDL_WINDOWEVENT_MINIMIZED:
OnResize();
break;
case SDL_WINDOWEVENT_CLOSE:
RequestClose();
break;
}
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state);
break;
case SDL_MOUSEMOTION:
// ignore if it came from touch
if (event.button.which != SDL_TOUCH_MOUSEID)
OnMouseMotion(event.motion.x, event.motion.y);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// ignore if it came from touch
if (event.button.which != SDL_TOUCH_MOUSEID) {
OnMouseButton(event.button.button, event.button.state, event.button.x,
event.button.y);
}
break;
case SDL_FINGERDOWN:
OnFingerDown(event.tfinger.x, event.tfinger.y);
break;
case SDL_FINGERMOTION:
OnFingerMotion(event.tfinger.x, event.tfinger.y);
break;
case SDL_FINGERUP:
OnFingerUp();
break;
case SDL_QUIT:
RequestClose();
break;
default:
break;
}
}
for (auto& e : other_window_events) {
// This is a somewhat hacky workaround to re-emit window events meant for another window
// since SDL_PollEvent() is global but we poll events per window.
SDL_PushEvent(&e);
}
if (!is_secondary) {
UpdateFramerateCounter();
}
}
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
}
void EmuWindow_SDL2::UpdateFramerateCounter() {
const u32 current_time = SDL_GetTicks();
if (current_time > last_time + 2000) {
const auto results = system.GetAndResetPerfStats();
const auto title =
fmt::format("Lime3DS {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc, results.game_fps,
results.emulation_speed * 100.0f);
SDL_SetWindowTitle(render_window, title.c_str());
last_time = current_time;
}
}

View File

@ -0,0 +1,91 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <utility>
#include "common/common_types.h"
#include "core/frontend/emu_window.h"
union SDL_Event;
struct SDL_Window;
namespace Core {
class System;
}
class EmuWindow_SDL2 : public Frontend::EmuWindow {
public:
explicit EmuWindow_SDL2(Core::System& system_, bool is_secondary);
~EmuWindow_SDL2();
/// Initializes SDL2
static void InitializeSDL2();
/// Presents the most recent frame from the video backend
virtual void Present() {}
/// Polls window events
void PollEvents() override;
/// Whether the window is still open, and a close request hasn't yet been sent
bool IsOpen() const;
/// Close the window.
void RequestClose();
protected:
/// Gets the ID of the window an event originated from.
u32 GetEventWindowId(const SDL_Event& event) const;
/// Called by PollEvents when a key is pressed or released.
void OnKeyEvent(int key, u8 state);
/// Called by PollEvents when the mouse moves.
void OnMouseMotion(s32 x, s32 y);
/// Called by PollEvents when a mouse button is pressed or released
void OnMouseButton(u32 button, u8 state, s32 x, s32 y);
/// Translates pixel position (0..1) to pixel positions
std::pair<unsigned, unsigned> TouchToPixelPos(float touch_x, float touch_y) const;
/// Called by PollEvents when a finger starts touching the touchscreen
void OnFingerDown(float x, float y);
/// Called by PollEvents when a finger moves while touching the touchscreen
void OnFingerMotion(float x, float y);
/// Called by PollEvents when a finger stops touching the touchscreen
void OnFingerUp();
/// Called by PollEvents when any event that may cause the window to be resized occurs
void OnResize();
/// Called when user passes the fullscreen parameter flag
void Fullscreen();
/// Called when a configuration change affects the minimal size of the window
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
/// Called when polling to update framerate
void UpdateFramerateCounter();
/// Is the window still open?
bool is_open = true;
/// Internal SDL2 render window
SDL_Window* render_window;
/// Internal SDL2 window ID
u32 render_window_id{};
/// Fake hidden window for the core context
SDL_Window* dummy_window;
/// Keeps track of how often to update the title bar during gameplay
u32 last_time = 0;
Core::System& system;
};

View File

@ -0,0 +1,167 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstdlib>
#include <string>
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <glad/glad.h>
#include "common/scm_rev.h"
#include "common/settings.h"
#include "core/core.h"
#include "lime_sdl/emu_window/emu_window_sdl2_gl.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
class SDLGLContext : public Frontend::GraphicsContext {
public:
using SDL_GLContext = void*;
SDLGLContext() {
window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
context = SDL_GL_CreateContext(window);
}
~SDLGLContext() override {
SDL_GL_DeleteContext(context);
SDL_DestroyWindow(window);
}
void MakeCurrent() override {
SDL_GL_MakeCurrent(window, context);
}
void DoneCurrent() override {
SDL_GL_MakeCurrent(window, nullptr);
}
private:
SDL_Window* window;
SDL_GLContext context;
};
static SDL_Window* CreateGLWindow(const std::string& window_title, bool gles) {
if (gles) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
} else {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
}
return SDL_CreateWindow(window_title.c_str(),
SDL_WINDOWPOS_UNDEFINED, // x position
SDL_WINDOWPOS_UNDEFINED, // y position
Core::kScreenTopWidth,
Core::kScreenTopHeight + Core::kScreenBottomHeight,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
}
EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system_, bool fullscreen, bool is_secondary)
: EmuWindow_SDL2{system_, is_secondary} {
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
// Enable context sharing for the shared context
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
// Enable vsync
SDL_GL_SetSwapInterval(1);
// Enable debug context
if (Settings::values.renderer_debug) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
}
std::string window_title = fmt::format("Lime3DS {} | {}-{}", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc);
// First, try to create a context with the requested type.
render_window = CreateGLWindow(window_title, Settings::values.use_gles.GetValue());
if (render_window == nullptr) {
// On failure, fall back to context with flipped type.
render_window = CreateGLWindow(window_title, !Settings::values.use_gles.GetValue());
if (render_window == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError());
exit(1);
}
}
strict_context_required = std::strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0;
dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
if (fullscreen) {
Fullscreen();
}
window_context = SDL_GL_CreateContext(render_window);
core_context = CreateSharedContext();
last_saved_context = nullptr;
if (window_context == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError());
exit(1);
}
if (core_context == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError());
exit(1);
}
render_window_id = SDL_GetWindowID(render_window);
int profile_mask = 0;
SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profile_mask);
auto gl_load_func =
profile_mask == SDL_GL_CONTEXT_PROFILE_ES ? gladLoadGLES2Loader : gladLoadGLLoader;
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError());
exit(1);
}
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
}
EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
core_context.reset();
SDL_DestroyWindow(render_window);
SDL_GL_DeleteContext(window_context);
}
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
return std::make_unique<SDLGLContext>();
}
void EmuWindow_SDL2_GL::MakeCurrent() {
core_context->MakeCurrent();
}
void EmuWindow_SDL2_GL::DoneCurrent() {
core_context->DoneCurrent();
}
void EmuWindow_SDL2_GL::SaveContext() {
last_saved_context = SDL_GL_GetCurrentContext();
}
void EmuWindow_SDL2_GL::RestoreContext() {
SDL_GL_MakeCurrent(render_window, last_saved_context);
}
void EmuWindow_SDL2_GL::Present() {
SDL_GL_MakeCurrent(render_window, window_context);
SDL_GL_SetSwapInterval(1);
while (IsOpen()) {
system.GPU().Renderer().TryPresent(100, is_secondary);
SDL_GL_SwapWindow(render_window);
}
SDL_GL_MakeCurrent(render_window, nullptr);
}

View File

@ -0,0 +1,39 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "lime_sdl/emu_window/emu_window_sdl2.h"
struct SDL_Window;
namespace Core {
class System;
}
class EmuWindow_SDL2_GL : public EmuWindow_SDL2 {
public:
explicit EmuWindow_SDL2_GL(Core::System& system_, bool fullscreen, bool is_secondary);
~EmuWindow_SDL2_GL();
void Present() override;
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
void MakeCurrent() override;
void DoneCurrent() override;
void SaveContext() override;
void RestoreContext() override;
private:
using SDL_GLContext = void*;
/// The OpenGL context associated with the window
SDL_GLContext window_context;
/// Used by SaveContext and RestoreContext
SDL_GLContext last_saved_context;
/// The OpenGL context associated with the core
std::unique_ptr<Frontend::GraphicsContext> core_context;
};

View File

@ -0,0 +1,108 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstdlib>
#include <string>
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <SDL_rect.h>
#include "common/scm_rev.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/frontend/emu_window.h"
#include "lime_sdl/emu_window/emu_window_sdl2_sw.h"
#include "video_core/gpu.h"
#include "video_core/renderer_software/renderer_software.h"
class DummyContext : public Frontend::GraphicsContext {};
EmuWindow_SDL2_SW::EmuWindow_SDL2_SW(Core::System& system_, bool fullscreen, bool is_secondary)
: EmuWindow_SDL2{system_, is_secondary}, system{system_} {
std::string window_title = fmt::format("Lime3DS {} | {}-{}", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
SDL_CreateWindow(window_title.c_str(),
SDL_WINDOWPOS_UNDEFINED, // x position
SDL_WINDOWPOS_UNDEFINED, // y position
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight,
SDL_WINDOW_SHOWN);
if (render_window == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError());
exit(1);
}
window_surface = SDL_GetWindowSurface(render_window);
renderer = SDL_CreateSoftwareRenderer(window_surface);
if (renderer == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 software renderer: {}", SDL_GetError());
exit(1);
}
if (fullscreen) {
Fullscreen();
}
render_window_id = SDL_GetWindowID(render_window);
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
}
EmuWindow_SDL2_SW::~EmuWindow_SDL2_SW() {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(render_window);
}
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2_SW::CreateSharedContext() const {
return std::make_unique<DummyContext>();
}
void EmuWindow_SDL2_SW::Present() {
const auto layout{Layout::DefaultFrameLayout(
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight, false, false)};
using VideoCore::ScreenId;
while (IsOpen()) {
SDL_SetRenderDrawColor(renderer,
static_cast<Uint8>(Settings::values.bg_red.GetValue() * 255),
static_cast<Uint8>(Settings::values.bg_green.GetValue() * 255),
static_cast<Uint8>(Settings::values.bg_blue.GetValue() * 255), 0xFF);
SDL_RenderClear(renderer);
const auto draw_screen = [&](ScreenId screen_id) {
const auto dst_rect =
screen_id == ScreenId::TopLeft ? layout.top_screen : layout.bottom_screen;
SDL_Rect sdl_rect{static_cast<int>(dst_rect.left), static_cast<int>(dst_rect.top),
static_cast<int>(dst_rect.GetWidth()),
static_cast<int>(dst_rect.GetHeight())};
SDL_Surface* screen = LoadFramebuffer(screen_id);
SDL_BlitSurface(screen, nullptr, window_surface, &sdl_rect);
SDL_FreeSurface(screen);
};
draw_screen(ScreenId::TopLeft);
draw_screen(ScreenId::Bottom);
SDL_RenderPresent(renderer);
SDL_UpdateWindowSurface(render_window);
}
}
SDL_Surface* EmuWindow_SDL2_SW::LoadFramebuffer(VideoCore::ScreenId screen_id) {
const auto& renderer = static_cast<SwRenderer::RendererSoftware&>(system.GPU().Renderer());
const auto& info = renderer.Screen(screen_id);
const int width = static_cast<int>(info.width);
const int height = static_cast<int>(info.height);
SDL_Surface* surface =
SDL_CreateRGBSurfaceWithFormat(0, width, height, 0, SDL_PIXELFORMAT_ABGR8888);
SDL_LockSurface(surface);
std::memcpy(surface->pixels, info.pixels.data(), info.pixels.size());
SDL_UnlockSurface(surface);
return surface;
}

View File

@ -0,0 +1,43 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "lime_sdl/emu_window/emu_window_sdl2.h"
struct SDL_Renderer;
struct SDL_Surface;
namespace VideoCore {
enum class ScreenId : u32;
}
namespace Core {
class System;
}
class EmuWindow_SDL2_SW : public EmuWindow_SDL2 {
public:
explicit EmuWindow_SDL2_SW(Core::System& system, bool fullscreen, bool is_secondary);
~EmuWindow_SDL2_SW();
void Present() override;
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
void MakeCurrent() override {}
void DoneCurrent() override {}
private:
/// Loads a framebuffer to an SDL surface
SDL_Surface* LoadFramebuffer(VideoCore::ScreenId screen_id);
/// The system class.
Core::System& system;
/// The SDL software renderer
SDL_Renderer* renderer;
/// The window surface
SDL_Surface* window_surface;
};

View File

@ -0,0 +1,90 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstdlib>
#include <memory>
#include <string>
#include <SDL.h>
#include <SDL_syswm.h>
#include <fmt/format.h>
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "core/frontend/emu_window.h"
#include "lime_sdl/emu_window/emu_window_sdl2_vk.h"
class DummyContext : public Frontend::GraphicsContext {};
EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen, bool is_secondary)
: EmuWindow_SDL2{system, is_secondary} {
const std::string window_title = fmt::format("Lime3DS {} | {}-{}", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
SDL_CreateWindow(window_title.c_str(),
SDL_WINDOWPOS_UNDEFINED, // x position
SDL_WINDOWPOS_UNDEFINED, // y position
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_SysWMinfo wm;
SDL_VERSION(&wm.version);
if (SDL_GetWindowWMInfo(render_window, &wm) == SDL_FALSE) {
LOG_CRITICAL(Frontend, "Failed to get information from the window manager");
std::exit(EXIT_FAILURE);
}
if (fullscreen) {
Fullscreen();
SDL_ShowCursor(false);
}
switch (wm.subsystem) {
#ifdef SDL_VIDEO_DRIVER_WINDOWS
case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS:
window_info.type = Frontend::WindowSystemType::Windows;
window_info.render_surface = reinterpret_cast<void*>(wm.info.win.window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_X11
case SDL_SYSWM_TYPE::SDL_SYSWM_X11:
window_info.type = Frontend::WindowSystemType::X11;
window_info.display_connection = wm.info.x11.display;
window_info.render_surface = reinterpret_cast<void*>(wm.info.x11.window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_WAYLAND
case SDL_SYSWM_TYPE::SDL_SYSWM_WAYLAND:
window_info.type = Frontend::WindowSystemType::Wayland;
window_info.display_connection = wm.info.wl.display;
window_info.render_surface = wm.info.wl.surface;
break;
#endif
#ifdef SDL_VIDEO_DRIVER_COCOA
case SDL_SYSWM_TYPE::SDL_SYSWM_COCOA:
window_info.type = Frontend::WindowSystemType::MacOS;
window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(render_window));
break;
#endif
#ifdef SDL_VIDEO_DRIVER_ANDROID
case SDL_SYSWM_TYPE::SDL_SYSWM_ANDROID:
window_info.type = Frontend::WindowSystemType::Android;
window_info.render_surface = reinterpret_cast<void*>(wm.info.android.window);
break;
#endif
default:
LOG_CRITICAL(Frontend, "Window manager subsystem {} not implemented", wm.subsystem);
std::exit(EXIT_FAILURE);
break;
}
render_window_id = SDL_GetWindowID(render_window);
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
}
EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default;
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const {
return std::make_unique<DummyContext>();
}

View File

@ -0,0 +1,24 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "lime_sdl/emu_window/emu_window_sdl2.h"
namespace Frontend {
class GraphicsContext;
}
namespace Core {
class System;
}
class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
public:
explicit EmuWindow_SDL2_VK(Core::System& system_, bool fullscreen, bool is_secondary);
~EmuWindow_SDL2_VK() override;
std::unique_ptr<Frontend::GraphicsContext> CreateSharedContext() const override;
};

528
src/lime_sdl/lime_sdl.cpp Normal file
View File

@ -0,0 +1,528 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <iostream>
#include <memory>
#include <regex>
#include <string>
#include <thread>
// This needs to be included before getopt.h because the latter #defines symbols used by it
#include "common/microprofile.h"
#include "lime_sdl/config.h"
#include "lime_sdl/emu_window/emu_window_sdl2.h"
#ifdef ENABLE_OPENGL
#include "lime_sdl/emu_window/emu_window_sdl2_gl.h"
#endif
#ifdef ENABLE_SOFTWARE_RENDERER
#include "lime_sdl/emu_window/emu_window_sdl2_sw.h"
#endif
#ifdef ENABLE_VULKAN
#include "lime_sdl/emu_window/emu_window_sdl2_vk.h"
#endif
#include "SDL_messagebox.h"
#include "common/common_paths.h"
#include "common/detached_tasks.h"
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/dumping/backend.h"
#include "core/dumping/ffmpeg_backend.h"
#include "core/frontend/applets/default_applets.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/cfg/cfg.h"
#include "core/movie.h"
#include "input_common/main.h"
#include "lime/common_strings.h"
#include "network/network.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
#ifdef __unix__
#include "common/linux/gamemode.h"
#endif
#undef _UNICODE
#include <getopt.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
#include <windows.h>
#include <shellapi.h>
#endif
static void ShowCommandOutput(std::string title, std::string message) {
#ifdef _WIN32
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, title.c_str(), message.c_str(), NULL);
#else
std::cout << message << std::endl;
#endif
}
static void PrintHelp(const char* argv0) {
ShowCommandOutput("Help", fmt::format(Common::help_string, argv0));
}
static void OnStateChanged(const Network::RoomMember::State& state) {
switch (state) {
case Network::RoomMember::State::Idle:
LOG_DEBUG(Network, "Network is idle");
break;
case Network::RoomMember::State::Joining:
LOG_DEBUG(Network, "Connection sequence to room started");
break;
case Network::RoomMember::State::Joined:
LOG_DEBUG(Network, "Successfully joined to the room");
break;
case Network::RoomMember::State::Moderator:
LOG_DEBUG(Network, "Successfully joined the room as a moderator");
break;
default:
break;
}
}
static void OnNetworkError(const Network::RoomMember::Error& error) {
switch (error) {
case Network::RoomMember::Error::LostConnection:
LOG_DEBUG(Network, "Lost connection to the room");
break;
case Network::RoomMember::Error::CouldNotConnect:
LOG_ERROR(Network, "Error: Could not connect");
std::exit(1);
break;
case Network::RoomMember::Error::NameCollision:
LOG_ERROR(
Network,
"You tried to use the same nickname as another user that is connected to the Room");
std::exit(1);
break;
case Network::RoomMember::Error::MacCollision:
LOG_ERROR(Network, "You tried to use the same MAC-Address as another user that is "
"connected to the Room");
std::exit(1);
break;
case Network::RoomMember::Error::ConsoleIdCollision:
LOG_ERROR(Network, "Your Console ID conflicted with someone else in the Room");
std::exit(1);
break;
case Network::RoomMember::Error::WrongPassword:
LOG_ERROR(Network, "Room replied with: Wrong password");
std::exit(1);
break;
case Network::RoomMember::Error::WrongVersion:
LOG_ERROR(Network,
"You are using a different version than the room you are trying to connect to");
std::exit(1);
break;
case Network::RoomMember::Error::RoomIsFull:
LOG_ERROR(Network, "The room is full");
std::exit(1);
break;
case Network::RoomMember::Error::HostKicked:
LOG_ERROR(Network, "You have been kicked by the host");
break;
case Network::RoomMember::Error::HostBanned:
LOG_ERROR(Network, "You have been banned by the host");
break;
default:
LOG_ERROR(Network, "Unknown network error: {}", error);
break;
}
}
static void OnMessageReceived(const Network::ChatEntry& msg) {
std::cout << std::endl << msg.nickname << ": " << msg.message << std::endl << std::endl;
}
static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
std::string message;
switch (msg.type) {
case Network::IdMemberJoin:
message = fmt::format("{} has joined", msg.nickname);
break;
case Network::IdMemberLeave:
message = fmt::format("{} has left", msg.nickname);
break;
case Network::IdMemberKicked:
message = fmt::format("{} has been kicked", msg.nickname);
break;
case Network::IdMemberBanned:
message = fmt::format("{} has been banned", msg.nickname);
break;
case Network::IdAddressUnbanned:
message = fmt::format("{} has been unbanned", msg.nickname);
break;
}
if (!message.empty())
std::cout << std::endl << "* " << message << std::endl << std::endl;
}
/// Application entry point
void LaunchSdlFrontend(int argc, char** argv) {
Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true);
Common::Log::Start();
Common::DetachedTasks detached_tasks;
SdlConfig config;
int option_index = 0;
bool use_gdbstub = Settings::values.use_gdbstub.GetValue();
u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port.GetValue());
std::string movie_record;
std::string movie_record_author;
std::string movie_play;
std::string dump_video;
char* endarg;
#ifdef _WIN32
int argc_w;
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
if (argv_w == nullptr) {
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
exit(-1);
}
#endif
std::string filepath;
bool use_multiplayer = false;
bool fullscreen = false;
std::string nickname{};
std::string password{};
std::string address{};
u16 port = Network::DefaultRoomPort;
static struct option long_options[] = {
{"dump-video", required_argument, 0, 'd'},
{"fullscreen", no_argument, 0, 'f'},
{"gdbport", required_argument, 0, 'g'},
{"help", no_argument, 0, 'h'},
{"install", required_argument, 0, 'i'},
{"movie-play", required_argument, 0, 'p'},
{"movie-record", required_argument, 0, 'r'},
{"movie-record-author", required_argument, 0, 'a'},
{"multiplayer", required_argument, 0, 'm'},
{"version", no_argument, 0, 'v'},
{"windowed", no_argument, 0, 'w'},
{0, 0, 0, 0},
};
while (optind < argc) {
int arg = getopt_long(argc, argv, "fg:hi:p:r:m:nvw", long_options, &option_index);
if (arg != -1) {
switch (static_cast<char>(arg)) {
case 'd':
dump_video = optarg;
break;
case 'f':
fullscreen = true;
LOG_INFO(Frontend, "Starting in fullscreen mode...");
break;
case 'g':
errno = 0;
gdb_port = strtoul(optarg, &endarg, 0);
use_gdbstub = true;
if (endarg == optarg)
errno = EINVAL;
if (errno != 0) {
perror("--gdbport");
exit(1);
}
break;
case 'h':
PrintHelp(argv[0]);
exit(0);
case 'i': {
const auto cia_progress = [](std::size_t written, std::size_t total) {
LOG_INFO(Frontend, "{:02d}%", (written * 100 / total));
};
if (Service::AM::InstallCIA(std::string(optarg), cia_progress) !=
Service::AM::InstallStatus::Success)
errno = EINVAL;
if (errno != 0)
exit(1);
break;
}
case 'p':
movie_play = optarg;
break;
case 'r':
movie_record = optarg;
break;
case 'a':
movie_record_author = optarg;
break;
case 'm': {
use_multiplayer = true;
const std::string str_arg(optarg);
// regex to check if the format is nickname:password@ip:port
// with optional :password
const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
if (!std::regex_match(str_arg, re)) {
std::cout << "Wrong format for option --multiplayer\n";
PrintHelp(argv[0]);
exit(0);
}
std::smatch match;
std::regex_search(str_arg, match, re);
ASSERT(match.size() == 5);
nickname = match[1];
password = match[2];
address = match[3];
if (!match[4].str().empty())
port = std::stoi(match[4]);
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
if (!std::regex_match(nickname, nickname_re)) {
std::cout
<< "Nickname is not valid. Must be 4 to 20 alphanumeric characters.\n";
exit(0);
}
if (address.empty()) {
std::cout << "Address to room must not be empty.\n";
exit(0);
}
break;
}
case 'v':
const std::string version_string =
std::string("Lime3DS ") + Common::g_scm_branch + " " + Common::g_scm_desc;
ShowCommandOutput("Version", version_string);
exit(0);
}
} else {
#ifdef _WIN32
filepath = Common::UTF16ToUTF8(argv_w[optind]);
#else
filepath = argv[optind];
#endif
optind++;
}
}
#ifdef _WIN32
LocalFree(argv_w);
#endif
MicroProfileOnThreadCreate("EmuThread");
SCOPE_EXIT({ MicroProfileShutdown(); });
if (filepath.empty()) {
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
exit(-1);
}
if (!movie_record.empty() && !movie_play.empty()) {
LOG_CRITICAL(Frontend, "Cannot both play and record a movie");
exit(-1);
}
auto& system = Core::System::GetInstance();
auto& movie = system.Movie();
if (!movie_record.empty()) {
movie.PrepareForRecording();
}
if (!movie_play.empty()) {
movie.PrepareForPlayback(movie_play);
}
// Apply the command line arguments
Settings::values.gdbstub_port = gdb_port;
Settings::values.use_gdbstub = use_gdbstub;
system.ApplySettings();
// Register frontend applets
Frontend::RegisterDefaultApplets(system);
EmuWindow_SDL2::InitializeSDL2();
const auto create_emu_window = [&](bool fullscreen,
bool is_secondary) -> std::unique_ptr<EmuWindow_SDL2> {
const auto graphics_api = Settings::values.graphics_api.GetValue();
switch (graphics_api) {
#ifdef ENABLE_OPENGL
case Settings::GraphicsAPI::OpenGL:
return std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, is_secondary);
#endif
#ifdef ENABLE_VULKAN
case Settings::GraphicsAPI::Vulkan:
return std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen, is_secondary);
#endif
#ifdef ENABLE_SOFTWARE_RENDERER
case Settings::GraphicsAPI::Software:
return std::make_unique<EmuWindow_SDL2_SW>(system, fullscreen, is_secondary);
#endif
default:
LOG_CRITICAL(
Frontend,
"Unknown or unsupported graphics API {}, falling back to available default",
graphics_api);
#ifdef ENABLE_OPENGL
return std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, is_secondary);
#elif ENABLE_VULKAN
return std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen, is_secondary);
#elif ENABLE_SOFTWARE_RENDERER
return std::make_unique<EmuWindow_SDL2_SW>(system, fullscreen, is_secondary);
#else
// TODO: Add a null renderer backend for this, perhaps.
#error "At least one renderer must be enabled."
#endif
}
};
const auto emu_window{create_emu_window(fullscreen, false)};
const bool use_secondary_window{
Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows &&
Settings::values.graphics_api.GetValue() != Settings::GraphicsAPI::Software};
const auto secondary_window = use_secondary_window ? create_emu_window(false, true) : nullptr;
const auto scope = emu_window->Acquire();
LOG_INFO(Frontend, "Lime3DS Version: {} | {}-{}", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc);
Settings::LogSettings();
const Core::System::ResultStatus load_result{
system.Load(*emu_window, filepath, secondary_window.get())};
switch (load_result) {
case Core::System::ResultStatus::ErrorGetLoader:
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
exit(-1);
case Core::System::ResultStatus::ErrorLoader:
LOG_CRITICAL(Frontend, "Failed to load ROM!");
exit(-1);
case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted:
LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before "
"being used with Lime3DS. \n\n For more information on dumping and "
"decrypting games, please refer to: "
"https://web.archive.org/web/20240304210021/https://citra-emu.org/"
"wiki/dumping-game-cartridges/");
exit(-1);
case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:
LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported.");
exit(-1);
case Core::System::ResultStatus::ErrorNotInitialized:
LOG_CRITICAL(Frontend, "CPUCore not initialized");
exit(-1);
case Core::System::ResultStatus::ErrorSystemMode:
LOG_CRITICAL(Frontend, "Failed to determine system mode!");
exit(-1);
case Core::System::ResultStatus::Success:
break; // Expected case
default:
LOG_ERROR(Frontend, "Error while loading ROM: {}", system.GetStatusDetails());
break;
}
if (use_multiplayer) {
if (auto member = Network::GetRoomMember().lock()) {
member->BindOnChatMessageRecieved(OnMessageReceived);
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
member->BindOnStateChanged(OnStateChanged);
member->BindOnError(OnNetworkError);
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port,
nickname);
member->Join(nickname, Service::CFG::GetConsoleIdHash(system), address.c_str(), port, 0,
Network::NoPreferredMac, password);
} else {
LOG_ERROR(Network, "Could not access RoomMember");
exit(0);
}
}
if (!movie_play.empty()) {
auto metadata = movie.GetMovieMetadata(movie_play);
LOG_INFO(Movie, "Author: {}", metadata.author);
LOG_INFO(Movie, "Rerecord count: {}", metadata.rerecord_count);
LOG_INFO(Movie, "Input count: {}", metadata.input_count);
movie.StartPlayback(movie_play);
}
if (!movie_record.empty()) {
movie.StartRecording(movie_record, movie_record_author);
}
if (!dump_video.empty() && DynamicLibrary::FFmpeg::LoadFFmpeg()) {
auto& renderer = system.GPU().Renderer();
const auto layout{
Layout::FrameLayoutFromResolutionScale(renderer.GetResolutionScaleFactor())};
auto dumper = std::make_shared<VideoDumper::FFmpegBackend>(renderer);
if (dumper->StartDumping(dump_video, layout)) {
system.RegisterVideoDumper(dumper);
}
}
#ifdef __unix__
Common::Linux::StartGamemode();
#endif
std::thread main_render_thread([&emu_window] { emu_window->Present(); });
std::thread secondary_render_thread([&secondary_window] {
if (secondary_window) {
secondary_window->Present();
}
});
std::atomic_bool stop_run;
system.GPU().Renderer().Rasterizer()->LoadDiskResources(
stop_run, [](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
LOG_DEBUG(Frontend, "Loading stage {} progress {} {}", static_cast<u32>(stage), value,
total);
});
const auto secondary_is_open = [&secondary_window] {
// if the secondary window isn't created, it shouldn't affect the main loop
return secondary_window ? secondary_window->IsOpen() : true;
};
while (emu_window->IsOpen() && secondary_is_open()) {
const auto result = system.RunLoop();
switch (result) {
case Core::System::ResultStatus::ShutdownRequested:
emu_window->RequestClose();
break;
case Core::System::ResultStatus::Success:
break;
default:
LOG_ERROR(Frontend, "Error in main run loop: {}", result, system.GetStatusDetails());
break;
}
}
emu_window->RequestClose();
if (secondary_window) {
secondary_window->RequestClose();
}
main_render_thread.join();
secondary_render_thread.join();
movie.Shutdown();
auto video_dumper = system.GetVideoDumper();
if (video_dumper && video_dumper->IsDumping()) {
video_dumper->StopDumping();
}
Network::Shutdown();
InputCommon::Shutdown();
system.Shutdown();
#ifdef __unix__
Common::Linux::StopGamemode();
#endif
detached_tasks.WaitForAllTasks();
exit(0);
}

7
src/lime_sdl/lime_sdl.h Normal file
View File

@ -0,0 +1,7 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
void LaunchSdlFrontend(int argc, char** argv);

View File

@ -0,0 +1,7 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_precompiled_headers.h"

16
src/lime_sdl/resource.h Normal file
View File

@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by pcafe.rc
//
#define IDI_ICON3 103
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 105
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif