diff options
87 files changed, 2043 insertions, 1096 deletions
diff --git a/appveyor.yml b/appveyor.yml index c07559479..2a4a15d40 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,6 +20,8 @@ install: - git submodule update --init --recursive before_build: + # Xamarin log spam workaround + - del "C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.targets\ImportAfter\Xamarin.Common.targets" - mkdir build - cd build - cmake -G "Visual Studio 14 2015 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 .. diff --git a/externals/boost b/externals/boost -Subproject f005c955f8147a29667aa0b65257abc3dd520b0 +Subproject 351972396392c97a659b9a02f34ce9269293d21 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1e1245160..a45439481 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(common) add_subdirectory(core) add_subdirectory(video_core) add_subdirectory(audio_core) +add_subdirectory(input_common) add_subdirectory(tests) if (ENABLE_SDL2) add_subdirectory(citra) diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt index ecb5d2dfe..47231ba71 100644 --- a/src/citra/CMakeLists.txt +++ b/src/citra/CMakeLists.txt @@ -18,7 +18,7 @@ create_directory_groups(${SRCS} ${HEADERS}) include_directories(${SDL2_INCLUDE_DIR}) add_executable(citra ${SRCS} ${HEADERS}) -target_link_libraries(citra core video_core audio_core common) +target_link_libraries(citra core video_core audio_core common input_common) target_link_libraries(citra ${SDL2_LIBRARY} ${OPENGL_gl_LIBRARY} inih glad) if (MSVC) target_link_libraries(citra getopt) diff --git a/src/citra/config.cpp b/src/citra/config.cpp index fac1c9a0e..a4162e9ad 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -8,8 +8,10 @@ #include "citra/default_ini.h" #include "common/file_util.h" #include "common/logging/log.h" +#include "common/param_package.h" #include "config.h" #include "core/settings.h" +#include "input_common/main.h" Config::Config() { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. @@ -37,25 +39,40 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) { return true; } -static const std::array<int, Settings::NativeInput::NUM_INPUTS> defaults = { - // directly mapped keys - SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_Q, SDL_SCANCODE_W, - SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_B, SDL_SCANCODE_T, - SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, - SDL_SCANCODE_L, - - // indirectly mapped keys - SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D, +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_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, + }, +}}; + void Config::ReadValues() { // Controls - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - Settings::values.input_mappings[Settings::NativeInput::All[i]] = - sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[i]); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + Settings::values.buttons[i] = + sdl2_config->Get("Controls", Settings::NativeButton::mapping[i], default_param); + if (Settings::values.buttons[i].empty()) + Settings::values.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.analogs[i] = + sdl2_config->Get("Controls", Settings::NativeAnalog::mapping[i], default_param); + if (Settings::values.analogs[i].empty()) + Settings::values.analogs[i] = default_param; } - Settings::values.pad_circle_modifier_scale = - (float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.5); // Core Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); @@ -77,6 +94,23 @@ void Config::ReadValues() { Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger("Layout", "layout_option", 0)); Settings::values.swap_screen = sdl2_config->GetBoolean("Layout", "swap_screen", false); + Settings::values.custom_layout = sdl2_config->GetBoolean("Layout", "custom_layout", false); + Settings::values.custom_top_left = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_left", 0)); + Settings::values.custom_top_top = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_top", 0)); + Settings::values.custom_top_right = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_right", 400)); + Settings::values.custom_top_bottom = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_bottom", 240)); + Settings::values.custom_bottom_left = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_left", 40)); + Settings::values.custom_bottom_top = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_top", 240)); + Settings::values.custom_bottom_right = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_right", 360)); + Settings::values.custom_bottom_bottom = + static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_bottom", 480)); // Audio Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 435ba6f00..084372df4 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -8,34 +8,47 @@ namespace DefaultINI { const char* sdl2_config_file = R"( [Controls] -pad_start = -pad_select = -pad_home = -pad_dup = -pad_ddown = -pad_dleft = -pad_dright = -pad_a = -pad_b = -pad_x = -pad_y = -pad_l = -pad_r = -pad_zl = -pad_zr = -pad_cup = -pad_cdown = -pad_cleft = -pad_cright = -pad_circle_up = -pad_circle_down = -pad_circle_left = -pad_circle_right = -pad_circle_modifier = - -# The applied modifier scale to circle pad. -# Must be in range of 0.0-1.0. Defaults to 0.5 -pad_circle_modifier_scale = +# 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 avaible: +# - "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 +# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right" +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_zl= +button_zr= +button_home= + +# for analog input, the following devices are avaible: +# - "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= [Core] # Whether to use the Just-In-Time (JIT) compiler for CPU emulation @@ -71,6 +84,21 @@ bg_green = # 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen layout_option = +# Toggle custom layout (using the settings below) on or off. +# 0 (default): Off , 1: On +custom_layout = + +# Screen placement when using Custom layout option +# 0x, 0y is the top left corner of the render window. +custom_top_left = +custom_top_top = +custom_top_right = +custom_top_bottom = +custom_bottom_left = +custom_bottom_top = +custom_bottom_right = +custom_bottom_bottom = + #Whether to toggle frame limiter on or off. # 0: Off , 1 (default): On toggle_framelimit = diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 00d00905a..6bc0b0d00 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -12,9 +12,9 @@ #include "common/logging/log.h" #include "common/scm_rev.h" #include "common/string_util.h" -#include "core/frontend/key_map.h" -#include "core/hle/service/hid/hid.h" #include "core/settings.h" +#include "input_common/keyboard.h" +#include "input_common/main.h" #include "video_core/video_core.h" void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { @@ -40,9 +40,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { if (state == SDL_PRESSED) { - KeyMap::PressKey(*this, {key, keyboard_id}); + InputCommon::GetKeyboard()->PressKey(key); } else if (state == SDL_RELEASED) { - KeyMap::ReleaseKey(*this, {key, keyboard_id}); + InputCommon::GetKeyboard()->ReleaseKey(key); } } @@ -57,9 +57,8 @@ void EmuWindow_SDL2::OnResize() { } EmuWindow_SDL2::EmuWindow_SDL2() { - keyboard_id = KeyMap::NewDeviceId(); + InputCommon::Init(); - ReloadSetKeymaps(); motion_emu = std::make_unique<Motion::MotionEmu>(*this); SDL_SetMainReady(); @@ -117,6 +116,7 @@ EmuWindow_SDL2::~EmuWindow_SDL2() { SDL_GL_DeleteContext(gl_context); SDL_Quit(); motion_emu = nullptr; + InputCommon::Shutdown(); } void EmuWindow_SDL2::SwapBuffers() { @@ -169,15 +169,6 @@ void EmuWindow_SDL2::DoneCurrent() { SDL_GL_MakeCurrent(render_window, nullptr); } -void EmuWindow_SDL2::ReloadSetKeymaps() { - KeyMap::ClearKeyMapping(keyboard_id); - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - KeyMap::SetKeyMapping( - {Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id}, - KeyMap::mapping_targets[i]); - } -} - void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest( const std::pair<unsigned, unsigned>& minimal_size) { diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h index b1cbf16d7..1ce2991f7 100644 --- a/src/citra/emu_window/emu_window_sdl2.h +++ b/src/citra/emu_window/emu_window_sdl2.h @@ -31,9 +31,6 @@ public: /// Whether the window is still open, and a close request hasn't yet been sent bool IsOpen() const; - /// Load keymap from configuration - void ReloadSetKeymaps() override; - private: /// Called by PollEvents when a key is pressed or released. void OnKeyEvent(int key, u8 state); @@ -61,9 +58,6 @@ private: /// The OpenGL context associated with the window SDL_GLContext gl_context; - /// Device id of keyboard for use with KeyMap - int keyboard_id; - /// Motion sensors emulation std::unique_ptr<Motion::MotionEmu> motion_emu; }; diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 15a6ccf9a..3e6106f0a 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -3,7 +3,14 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) set(SRCS - config.cpp + configuration/config.cpp + configuration/configure_audio.cpp + configuration/configure_debug.cpp + configuration/configure_dialog.cpp + configuration/configure_general.cpp + configuration/configure_graphics.cpp + configuration/configure_input.cpp + configuration/configure_system.cpp debugger/callstack.cpp debugger/disassembler.cpp debugger/graphics/graphics.cpp @@ -19,13 +26,6 @@ set(SRCS util/spinbox.cpp util/util.cpp bootmanager.cpp - configure_audio.cpp - configure_debug.cpp - configure_dialog.cpp - configure_general.cpp - configure_graphics.cpp - configure_system.cpp - configure_input.cpp game_list.cpp hotkeys.cpp main.cpp @@ -35,7 +35,14 @@ set(SRCS ) set(HEADERS - config.h + configuration/config.h + configuration/configure_audio.h + configuration/configure_debug.h + configuration/configure_dialog.h + configuration/configure_general.h + configuration/configure_graphics.h + configuration/configure_input.h + configuration/configure_system.h debugger/callstack.h debugger/disassembler.h debugger/graphics/graphics.h @@ -52,13 +59,6 @@ set(HEADERS util/spinbox.h util/util.h bootmanager.h - configure_audio.h - configure_debug.h - configure_dialog.h - configure_general.h - configure_graphics.h - configure_system.h - configure_input.h game_list.h game_list_p.h hotkeys.h @@ -67,16 +67,16 @@ set(HEADERS ) set(UIS + configuration/configure.ui + configuration/configure_audio.ui + configuration/configure_debug.ui + configuration/configure_general.ui + configuration/configure_graphics.ui + configuration/configure_input.ui + configuration/configure_system.ui debugger/callstack.ui debugger/disassembler.ui debugger/registers.ui - configure.ui - configure_audio.ui - configure_debug.ui - configure_general.ui - configure_graphics.ui - configure_system.ui - configure_input.ui hotkeys.ui main.ui ) @@ -97,7 +97,7 @@ if (APPLE) else() add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) endif() -target_link_libraries(citra-qt core video_core audio_core common) +target_link_libraries(citra-qt core video_core audio_core common input_common) target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 69d18cf0c..bae576d6a 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -13,7 +13,9 @@ #include "common/scm_rev.h" #include "common/string_util.h" #include "core/core.h" -#include "core/frontend/key_map.h" +#include "core/settings.h" +#include "input_common/keyboard.h" +#include "input_common/main.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/video_core.h" @@ -99,14 +101,17 @@ private: }; GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) - : QWidget(parent), child(nullptr), keyboard_id(0), emu_thread(emu_thread) { + : QWidget(parent), child(nullptr), emu_thread(emu_thread) { std::string window_title = Common::StringFromFormat("Citra %s| %s-%s", Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); - keyboard_id = KeyMap::NewDeviceId(); - ReloadSetKeymaps(); + InputCommon::Init(); +} + +GRenderWindow::~GRenderWindow() { + InputCommon::Shutdown(); } void GRenderWindow::moveContext() { @@ -197,11 +202,11 @@ void GRenderWindow::closeEvent(QCloseEvent* event) { } void GRenderWindow::keyPressEvent(QKeyEvent* event) { - KeyMap::PressKey(*this, {event->key(), keyboard_id}); + InputCommon::GetKeyboard()->PressKey(event->key()); } void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { - KeyMap::ReleaseKey(*this, {event->key(), keyboard_id}); + InputCommon::GetKeyboard()->ReleaseKey(event->key()); } void GRenderWindow::mousePressEvent(QMouseEvent* event) { @@ -230,13 +235,9 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { motion_emu->EndTilt(); } -void GRenderWindow::ReloadSetKeymaps() { - KeyMap::ClearKeyMapping(keyboard_id); - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - KeyMap::SetKeyMapping( - {Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id}, - KeyMap::mapping_targets[i]); - } +void GRenderWindow::focusOutEvent(QFocusEvent* event) { + QWidget::focusOutEvent(event); + InputCommon::GetKeyboard()->ReleaseAllKeys(); } void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) { diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 7dac1c480..9d39f1af8 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -104,6 +104,7 @@ class GRenderWindow : public QWidget, public EmuWindow { public: GRenderWindow(QWidget* parent, EmuThread* emu_thread); + ~GRenderWindow(); // EmuWindow implementation void SwapBuffers() override; @@ -127,7 +128,7 @@ public: void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; - void ReloadSetKeymaps() override; + void focusOutEvent(QFocusEvent* event) override; void OnClientAreaResized(unsigned width, unsigned height); @@ -152,9 +153,6 @@ private: QByteArray geometry; - /// Device id of keyboard for use with KeyMap - int keyboard_id; - EmuThread* emu_thread; /// Motion sensors emulation diff --git a/src/citra_qt/config.cpp b/src/citra_qt/configuration/config.cpp index 5fe57dfa2..0b9b73f9e 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -3,9 +3,10 @@ // Refer to the license.txt file included. #include <QSettings> -#include "citra_qt/config.h" +#include "citra_qt/configuration/config.h" #include "citra_qt/ui_settings.h" #include "common/file_util.h" +#include "input_common/main.h" Config::Config() { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. @@ -16,25 +17,46 @@ Config::Config() { Reload(); } -const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> Config::defaults = { - // directly mapped keys - Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2, - Qt::Key_M, Qt::Key_N, Qt::Key_B, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, Qt::Key_I, - Qt::Key_K, Qt::Key_J, Qt::Key_L, - - // indirectly mapped keys - Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D, +const std::array<int, Settings::NativeButton::NumButtons> Config::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_1, Qt::Key_2, Qt::Key_B, }; +const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ + { + Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D, + }, + { + Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, Qt::Key_D, + }, +}}; + void Config::ReadValues() { qt_config->beginGroup("Controls"); - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - Settings::values.input_mappings[Settings::NativeInput::All[i]] = - qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i]) - .toInt(); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + Settings::values.buttons[i] = + qt_config + ->value(Settings::NativeButton::mapping[i], QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (Settings::values.buttons[i].empty()) + Settings::values.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.analogs[i] = + qt_config + ->value(Settings::NativeAnalog::mapping[i], QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (Settings::values.analogs[i].empty()) + Settings::values.analogs[i] = default_param; } - Settings::values.pad_circle_modifier_scale = - qt_config->value("pad_circle_modifier_scale", 0.5).toFloat(); + qt_config->endGroup(); qt_config->beginGroup("Core"); @@ -57,6 +79,15 @@ void Config::ReadValues() { Settings::values.layout_option = static_cast<Settings::LayoutOption>(qt_config->value("layout_option").toInt()); Settings::values.swap_screen = qt_config->value("swap_screen", false).toBool(); + Settings::values.custom_layout = qt_config->value("custom_layout", false).toBool(); + Settings::values.custom_top_left = qt_config->value("custom_top_left", 0).toInt(); + Settings::values.custom_top_top = qt_config->value("custom_top_top", 0).toInt(); + Settings::values.custom_top_right = qt_config->value("custom_top_right", 400).toInt(); + Settings::values.custom_top_bottom = qt_config->value("custom_top_bottom", 240).toInt(); + Settings::values.custom_bottom_left = qt_config->value("custom_bottom_left", 40).toInt(); + Settings::values.custom_bottom_top = qt_config->value("custom_bottom_top", 240).toInt(); + Settings::values.custom_bottom_right = qt_config->value("custom_bottom_right", 360).toInt(); + Settings::values.custom_bottom_bottom = qt_config->value("custom_bottom_bottom", 480).toInt(); qt_config->endGroup(); qt_config->beginGroup("Audio"); @@ -155,12 +186,14 @@ void Config::ReadValues() { void Config::SaveValues() { qt_config->beginGroup("Controls"); - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]), - Settings::values.input_mappings[Settings::NativeInput::All[i]]); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + qt_config->setValue(QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(Settings::values.buttons[i])); + } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(Settings::values.analogs[i])); } - qt_config->setValue("pad_circle_modifier_scale", - (double)Settings::values.pad_circle_modifier_scale); qt_config->endGroup(); qt_config->beginGroup("Core"); @@ -183,6 +216,15 @@ void Config::SaveValues() { qt_config->beginGroup("Layout"); qt_config->setValue("layout_option", static_cast<int>(Settings::values.layout_option)); qt_config->setValue("swap_screen", Settings::values.swap_screen); + qt_config->setValue("custom_layout", Settings::values.custom_layout); + qt_config->setValue("custom_top_left", Settings::values.custom_top_left); + qt_config->setValue("custom_top_top", Settings::values.custom_top_top); + qt_config->setValue("custom_top_right", Settings::values.custom_top_right); + qt_config->setValue("custom_top_bottom", Settings::values.custom_top_bottom); + qt_config->setValue("custom_bottom_left", Settings::values.custom_bottom_left); + qt_config->setValue("custom_bottom_top", Settings::values.custom_bottom_top); + qt_config->setValue("custom_bottom_right", Settings::values.custom_bottom_right); + qt_config->setValue("custom_bottom_bottom", Settings::values.custom_bottom_bottom); qt_config->endGroup(); qt_config->beginGroup("Audio"); diff --git a/src/citra_qt/config.h b/src/citra_qt/configuration/config.h index 79c901804..cbf745ea2 100644 --- a/src/citra_qt/config.h +++ b/src/citra_qt/configuration/config.h @@ -4,6 +4,7 @@ #pragma once +#include <array> #include <string> #include <QVariant> #include "core/settings.h" @@ -23,5 +24,7 @@ public: void Reload(); void Save(); - static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults; + + static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; + static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; }; diff --git a/src/citra_qt/configure.ui b/src/citra_qt/configuration/configure.ui index 28b4a3b90..85e206e42 100644 --- a/src/citra_qt/configure.ui +++ b/src/citra_qt/configuration/configure.ui @@ -64,37 +64,37 @@ <customwidget> <class>ConfigureGeneral</class> <extends>QWidget</extends> - <header>configure_general.h</header> + <header>configuration/configure_general.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureSystem</class> <extends>QWidget</extends> - <header>configure_system.h</header> + <header>configuration/configure_system.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureAudio</class> <extends>QWidget</extends> - <header>configure_audio.h</header> + <header>configuration/configure_audio.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureDebug</class> <extends>QWidget</extends> - <header>configure_debug.h</header> + <header>configuration/configure_debug.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureInput</class> <extends>QWidget</extends> - <header>configure_input.h</header> + <header>configuration/configure_input.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureGraphics</class> <extends>QWidget</extends> - <header>configure_graphics.h</header> + <header>configuration/configure_graphics.h</header> <container>1</container> </customwidget> </customwidgets> diff --git a/src/citra_qt/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index 3ddcf9232..3fd1d127a 100644 --- a/src/citra_qt/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -6,7 +6,7 @@ #include "audio_core/audio_core.h" #include "audio_core/sink.h" #include "audio_core/sink_details.h" -#include "citra_qt/configure_audio.h" +#include "citra_qt/configuration/configure_audio.h" #include "core/settings.h" #include "ui_configure_audio.h" diff --git a/src/citra_qt/configure_audio.h b/src/citra_qt/configuration/configure_audio.h index 8190e694f..8190e694f 100644 --- a/src/citra_qt/configure_audio.h +++ b/src/citra_qt/configuration/configure_audio.h diff --git a/src/citra_qt/configure_audio.ui b/src/citra_qt/configuration/configure_audio.ui index dd870eb61..dd870eb61 100644 --- a/src/citra_qt/configure_audio.ui +++ b/src/citra_qt/configuration/configure_audio.ui diff --git a/src/citra_qt/configure_debug.cpp b/src/citra_qt/configuration/configure_debug.cpp index dcc398eee..263f73f38 100644 --- a/src/citra_qt/configure_debug.cpp +++ b/src/citra_qt/configuration/configure_debug.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_debug.h" +#include "citra_qt/configuration/configure_debug.h" #include "core/settings.h" #include "ui_configure_debug.h" diff --git a/src/citra_qt/configure_debug.h b/src/citra_qt/configuration/configure_debug.h index d167eb996..d167eb996 100644 --- a/src/citra_qt/configure_debug.h +++ b/src/citra_qt/configuration/configure_debug.h diff --git a/src/citra_qt/configure_debug.ui b/src/citra_qt/configuration/configure_debug.ui index bbbb0e3f4..bbbb0e3f4 100644 --- a/src/citra_qt/configure_debug.ui +++ b/src/citra_qt/configuration/configure_debug.ui diff --git a/src/citra_qt/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index 525a7cc4e..dfc8c03a7 100644 --- a/src/citra_qt/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -2,8 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/config.h" -#include "citra_qt/configure_dialog.h" +#include "citra_qt/configuration/config.h" +#include "citra_qt/configuration/configure_dialog.h" #include "core/settings.h" #include "ui_configure.h" diff --git a/src/citra_qt/configure_dialog.h b/src/citra_qt/configuration/configure_dialog.h index 21fa1f501..21fa1f501 100644 --- a/src/citra_qt/configure_dialog.h +++ b/src/citra_qt/configuration/configure_dialog.h diff --git a/src/citra_qt/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp index ac90a6df4..a21176c34 100644 --- a/src/citra_qt/configure_general.cpp +++ b/src/citra_qt/configuration/configure_general.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_general.h" +#include "citra_qt/configuration/configure_general.h" #include "citra_qt/ui_settings.h" #include "core/core.h" #include "core/settings.h" diff --git a/src/citra_qt/configure_general.h b/src/citra_qt/configuration/configure_general.h index 447552d8c..447552d8c 100644 --- a/src/citra_qt/configure_general.h +++ b/src/citra_qt/configuration/configure_general.h diff --git a/src/citra_qt/configure_general.ui b/src/citra_qt/configuration/configure_general.ui index c739605a4..c739605a4 100644 --- a/src/citra_qt/configure_general.ui +++ b/src/citra_qt/configuration/configure_general.ui diff --git a/src/citra_qt/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index 54f799b47..b5a5ab1e1 100644 --- a/src/citra_qt/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_graphics.h" +#include "citra_qt/configuration/configure_graphics.h" #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics.h" @@ -14,6 +14,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) this->setConfiguration(); ui->toggle_vsync->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + + ui->layoutBox->setEnabled(!Settings::values.custom_layout); } ConfigureGraphics::~ConfigureGraphics() {} diff --git a/src/citra_qt/configure_graphics.h b/src/citra_qt/configuration/configure_graphics.h index 5497a55f7..5497a55f7 100644 --- a/src/citra_qt/configure_graphics.h +++ b/src/citra_qt/configuration/configure_graphics.h diff --git a/src/citra_qt/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index a091f4c60..228f2a869 100644 --- a/src/citra_qt/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui @@ -126,7 +126,7 @@ </layout> </item> <item> - <widget class="QGroupBox" name="groupBox2"> + <widget class="QGroupBox" name="layoutBox"> <property name="title"> <string>Layout</string> </property> diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp new file mode 100644 index 000000000..daac9b63a --- /dev/null +++ b/src/citra_qt/configuration/configure_input.cpp @@ -0,0 +1,205 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> +#include <QTimer> +#include "citra_qt/configuration/config.h" +#include "citra_qt/configuration/configure_input.h" +#include "common/param_package.h" +#include "input_common/main.h" + +const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM> + ConfigureInput::analog_sub_buttons{{ + "up", "down", "left", "right", "modifier", + }}; + +static QString getKeyName(int key_code) { + switch (key_code) { + case Qt::Key_Shift: + return QObject::tr("Shift"); + case Qt::Key_Control: + return QObject::tr("Ctrl"); + case Qt::Key_Alt: + return QObject::tr("Alt"); + case Qt::Key_Meta: + return ""; + default: + return QKeySequence(key_code).toString(); + } +} + +static void SetButtonKey(int key, Common::ParamPackage& button_param) { + button_param = Common::ParamPackage{InputCommon::GenerateKeyboardParam(key)}; +} + +static void SetAnalogKey(int key, Common::ParamPackage& analog_param, + const std::string& button_name) { + if (analog_param.Get("engine", "") != "analog_from_button") { + analog_param = { + {"engine", "analog_from_button"}, {"modifier_scale", "0.5"}, + }; + } + analog_param.Set(button_name, InputCommon::GenerateKeyboardParam(key)); +} + +ConfigureInput::ConfigureInput(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), + timer(std::make_unique<QTimer>()) { + + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + + button_map = { + ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, ui->buttonDpadUp, + ui->buttonDpadDown, ui->buttonDpadLeft, ui->buttonDpadRight, ui->buttonL, ui->buttonR, + ui->buttonStart, ui->buttonSelect, ui->buttonZL, ui->buttonZR, ui->buttonHome, + }; + + analog_map = {{ + { + ui->buttonCircleUp, ui->buttonCircleDown, ui->buttonCircleLeft, ui->buttonCircleRight, + ui->buttonCircleMod, + }, + { + ui->buttonCStickUp, ui->buttonCStickDown, ui->buttonCStickLeft, ui->buttonCStickRight, + nullptr, + }, + }}; + + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + if (button_map[button_id]) + connect(button_map[button_id], &QPushButton::released, [=]() { + handleClick(button_map[button_id], + [=](int key) { SetButtonKey(key, buttons_param[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++) { + if (analog_map[analog_id][sub_button_id] != nullptr) { + connect(analog_map[analog_id][sub_button_id], &QPushButton::released, [=]() { + handleClick(analog_map[analog_id][sub_button_id], [=](int key) { + SetAnalogKey(key, analogs_param[analog_id], + analog_sub_buttons[sub_button_id]); + }); + }); + } + } + } + + connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); + + timer->setSingleShot(true); + connect(timer.get(), &QTimer::timeout, [this]() { + releaseKeyboard(); + releaseMouse(); + key_setter = boost::none; + updateButtonLabels(); + }); + + this->loadConfiguration(); + + // TODO(wwylele): enable these when the input emulation for them is implemented + ui->buttonZL->setEnabled(false); + ui->buttonZR->setEnabled(false); + ui->buttonHome->setEnabled(false); + ui->buttonCStickUp->setEnabled(false); + ui->buttonCStickDown->setEnabled(false); + ui->buttonCStickLeft->setEnabled(false); + ui->buttonCStickRight->setEnabled(false); +} + +void ConfigureInput::applyConfiguration() { + std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.buttons.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + + Settings::Apply(); +} + +void ConfigureInput::loadConfiguration() { + std::transform(Settings::values.buttons.begin(), Settings::values.buttons.end(), + buttons_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + std::transform(Settings::values.analogs.begin(), Settings::values.analogs.end(), + analogs_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + updateButtonLabels(); +} + +void ConfigureInput::restoreDefaults() { + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + SetButtonKey(Config::default_buttons[button_id], buttons_param[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++) { + SetAnalogKey(Config::default_analogs[analog_id][sub_button_id], + analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + } + } + updateButtonLabels(); + applyConfiguration(); +} + +void ConfigureInput::updateButtonLabels() { + QString non_keyboard(tr("[non-keyboard]")); + + auto KeyToText = [&non_keyboard](const Common::ParamPackage& param) { + if (param.Get("engine", "") != "keyboard") { + return non_keyboard; + } else { + return getKeyName(param.Get("code", 0)); + } + }; + + for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { + button_map[button]->setText(KeyToText(buttons_param[button])); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + if (analogs_param[analog_id].Get("engine", "") != "analog_from_button") { + for (QPushButton* button : analog_map[analog_id]) { + if (button) + button->setText(non_keyboard); + } + } else { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + Common::ParamPackage param( + analogs_param[analog_id].Get(analog_sub_buttons[sub_button_id], "")); + if (analog_map[analog_id][sub_button_id]) + analog_map[analog_id][sub_button_id]->setText(KeyToText(param)); + } + } + } +} + +void ConfigureInput::handleClick(QPushButton* button, std::function<void(int)> new_key_setter) { + button->setText(tr("[press key]")); + button->setFocus(); + + key_setter = new_key_setter; + + grabKeyboard(); + grabMouse(); + timer->start(5000); // Cancel after 5 seconds +} + +void ConfigureInput::keyPressEvent(QKeyEvent* event) { + releaseKeyboard(); + releaseMouse(); + + if (!key_setter || !event) + return; + + if (event->key() != Qt::Key_Escape) + (*key_setter)(event->key()); + + updateButtonLabels(); + key_setter = boost::none; + timer->stop(); +} diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h new file mode 100644 index 000000000..c950fbcb4 --- /dev/null +++ b/src/citra_qt/configuration/configure_input.h @@ -0,0 +1,69 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <functional> +#include <memory> +#include <string> +#include <QKeyEvent> +#include <QWidget> +#include <boost/optional.hpp> +#include "common/param_package.h" +#include "core/settings.h" +#include "ui_configure_input.h" + +class QPushButton; +class QString; +class QTimer; + +namespace Ui { +class ConfigureInput; +} + +class ConfigureInput : public QWidget { + Q_OBJECT + +public: + explicit ConfigureInput(QWidget* parent = nullptr); + + /// Save all button configurations to settings file + void applyConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureInput> ui; + + std::unique_ptr<QTimer> timer; + + /// This will be the the setting function when an input is awaiting configuration. + boost::optional<std::function<void(int)>> key_setter; + + std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; + std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; + + static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; + + /// Each button input is represented by a QPushButton. + std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; + + /// Each analog input is represented by five QPushButtons which represents up, down, left, right + /// and modifier + std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> + analog_map; + + static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; + + /// Load configuration settings. + void loadConfiguration(); + /// Restore all buttons to their default values. + void restoreDefaults(); + /// Update UI to reflect current configuration. + void updateButtonLabels(); + + /// Called when the button was pressed. + void handleClick(QPushButton* button, std::function<void(int)> new_key_setter); + /// Handle key press events. + void keyPressEvent(QKeyEvent* event) override; +}; diff --git a/src/citra_qt/configure_input.ui b/src/citra_qt/configuration/configure_input.ui index 2760787e5..2760787e5 100644 --- a/src/citra_qt/configure_input.ui +++ b/src/citra_qt/configuration/configure_input.ui diff --git a/src/citra_qt/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index 040185e82..a3a9015a4 100644 --- a/src/citra_qt/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_system.h" +#include "citra_qt/configuration/configure_system.h" #include "citra_qt/ui_settings.h" #include "core/core.h" #include "core/hle/service/cfg/cfg.h" diff --git a/src/citra_qt/configure_system.h b/src/citra_qt/configuration/configure_system.h index db0ead13c..db0ead13c 100644 --- a/src/citra_qt/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h diff --git a/src/citra_qt/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index cc54fa37f..cc54fa37f 100644 --- a/src/citra_qt/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui diff --git a/src/citra_qt/configure_input.cpp b/src/citra_qt/configure_input.cpp deleted file mode 100644 index c29652f32..000000000 --- a/src/citra_qt/configure_input.cpp +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <memory> -#include <utility> -#include <QTimer> -#include "citra_qt/config.h" -#include "citra_qt/configure_input.h" - -static QString getKeyName(Qt::Key key_code) { - switch (key_code) { - case Qt::Key_Shift: - return QObject::tr("Shift"); - case Qt::Key_Control: - return QObject::tr("Ctrl"); - case Qt::Key_Alt: - return QObject::tr("Alt"); - case Qt::Key_Meta: - return ""; - default: - return QKeySequence(key_code).toString(); - } -} - -ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), - timer(std::make_unique<QTimer>()) { - - ui->setupUi(this); - setFocusPolicy(Qt::ClickFocus); - - button_map = { - {Settings::NativeInput::Values::A, ui->buttonA}, - {Settings::NativeInput::Values::B, ui->buttonB}, - {Settings::NativeInput::Values::X, ui->buttonX}, - {Settings::NativeInput::Values::Y, ui->buttonY}, - {Settings::NativeInput::Values::L, ui->buttonL}, - {Settings::NativeInput::Values::R, ui->buttonR}, - {Settings::NativeInput::Values::ZL, ui->buttonZL}, - {Settings::NativeInput::Values::ZR, ui->buttonZR}, - {Settings::NativeInput::Values::START, ui->buttonStart}, - {Settings::NativeInput::Values::SELECT, ui->buttonSelect}, - {Settings::NativeInput::Values::HOME, ui->buttonHome}, - {Settings::NativeInput::Values::DUP, ui->buttonDpadUp}, - {Settings::NativeInput::Values::DDOWN, ui->buttonDpadDown}, - {Settings::NativeInput::Values::DLEFT, ui->buttonDpadLeft}, - {Settings::NativeInput::Values::DRIGHT, ui->buttonDpadRight}, - {Settings::NativeInput::Values::CUP, ui->buttonCStickUp}, - {Settings::NativeInput::Values::CDOWN, ui->buttonCStickDown}, - {Settings::NativeInput::Values::CLEFT, ui->buttonCStickLeft}, - {Settings::NativeInput::Values::CRIGHT, ui->buttonCStickRight}, - {Settings::NativeInput::Values::CIRCLE_UP, ui->buttonCircleUp}, - {Settings::NativeInput::Values::CIRCLE_DOWN, ui->buttonCircleDown}, - {Settings::NativeInput::Values::CIRCLE_LEFT, ui->buttonCircleLeft}, - {Settings::NativeInput::Values::CIRCLE_RIGHT, ui->buttonCircleRight}, - {Settings::NativeInput::Values::CIRCLE_MODIFIER, ui->buttonCircleMod}, - }; - - for (const auto& entry : button_map) { - const Settings::NativeInput::Values input_id = entry.first; - connect(entry.second, &QPushButton::released, - [this, input_id]() { handleClick(input_id); }); - } - - connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); - - timer->setSingleShot(true); - connect(timer.get(), &QTimer::timeout, [this]() { - releaseKeyboard(); - releaseMouse(); - current_input_id = boost::none; - updateButtonLabels(); - }); - - this->loadConfiguration(); -} - -void ConfigureInput::applyConfiguration() { - for (const auto& input_id : Settings::NativeInput::All) { - const size_t index = static_cast<size_t>(input_id); - Settings::values.input_mappings[index] = static_cast<int>(key_map[input_id]); - } - Settings::Apply(); -} - -void ConfigureInput::loadConfiguration() { - for (const auto& input_id : Settings::NativeInput::All) { - const size_t index = static_cast<size_t>(input_id); - key_map[input_id] = static_cast<Qt::Key>(Settings::values.input_mappings[index]); - } - updateButtonLabels(); -} - -void ConfigureInput::restoreDefaults() { - for (const auto& input_id : Settings::NativeInput::All) { - const size_t index = static_cast<size_t>(input_id); - key_map[input_id] = static_cast<Qt::Key>(Config::defaults[index].toInt()); - } - updateButtonLabels(); - applyConfiguration(); -} - -void ConfigureInput::updateButtonLabels() { - for (const auto& input_id : Settings::NativeInput::All) { - button_map[input_id]->setText(getKeyName(key_map[input_id])); - } -} - -void ConfigureInput::handleClick(Settings::NativeInput::Values input_id) { - QPushButton* button = button_map[input_id]; - button->setText(tr("[press key]")); - button->setFocus(); - - current_input_id = input_id; - - grabKeyboard(); - grabMouse(); - timer->start(5000); // Cancel after 5 seconds -} - -void ConfigureInput::keyPressEvent(QKeyEvent* event) { - releaseKeyboard(); - releaseMouse(); - - if (!current_input_id || !event) - return; - - if (event->key() != Qt::Key_Escape) - setInput(*current_input_id, static_cast<Qt::Key>(event->key())); - - updateButtonLabels(); - current_input_id = boost::none; - timer->stop(); -} - -void ConfigureInput::setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed) { - // Remove duplicates - for (auto& pair : key_map) { - if (pair.second == key_pressed) - pair.second = Qt::Key_unknown; - } - - key_map[input_id] = key_pressed; -} diff --git a/src/citra_qt/configure_input.h b/src/citra_qt/configure_input.h deleted file mode 100644 index bc343db83..000000000 --- a/src/citra_qt/configure_input.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <QKeyEvent> -#include <QWidget> -#include <boost/optional.hpp> -#include "core/settings.h" -#include "ui_configure_input.h" - -class QPushButton; -class QString; -class QTimer; - -namespace Ui { -class ConfigureInput; -} - -class ConfigureInput : public QWidget { - Q_OBJECT - -public: - explicit ConfigureInput(QWidget* parent = nullptr); - - /// Save all button configurations to settings file - void applyConfiguration(); - -private: - std::unique_ptr<Ui::ConfigureInput> ui; - - /// This input is currently awaiting configuration. - /// (i.e.: its corresponding QPushButton has been pressed.) - boost::optional<Settings::NativeInput::Values> current_input_id; - std::unique_ptr<QTimer> timer; - - /// Each input is represented by a QPushButton. - std::map<Settings::NativeInput::Values, QPushButton*> button_map; - /// Each input is configured to respond to the press of a Qt::Key. - std::map<Settings::NativeInput::Values, Qt::Key> key_map; - - /// Load configuration settings. - void loadConfiguration(); - /// Restore all buttons to their default values. - void restoreDefaults(); - /// Update UI to reflect current configuration. - void updateButtonLabels(); - - /// Called when the button corresponding to input_id was pressed. - void handleClick(Settings::NativeInput::Values input_id); - /// Handle key press events. - void keyPressEvent(QKeyEvent* event) override; - /// Configure input input_id to respond to key key_pressed. - void setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed); -}; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index fd51659b9..73b4dd34f 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -14,8 +14,8 @@ #include <QtGui> #include <QtWidgets> #include "citra_qt/bootmanager.h" -#include "citra_qt/config.h" -#include "citra_qt/configure_dialog.h" +#include "citra_qt/configuration/config.h" +#include "citra_qt/configuration/configure_dialog.h" #include "citra_qt/debugger/callstack.h" #include "citra_qt/debugger/disassembler.h" #include "citra_qt/debugger/graphics/graphics.h" @@ -612,7 +612,6 @@ void GMainWindow::OnConfigure() { auto result = configureDialog.exec(); if (result == QDialog::Accepted) { configureDialog.applyConfiguration(); - render_window->ReloadSetKeymaps(); config->Save(); } } diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 8a6170257..13277a5c2 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -35,6 +35,7 @@ set(SRCS memory_util.cpp microprofile.cpp misc.cpp + param_package.cpp scm_rev.cpp string_util.cpp symbols.cpp @@ -66,6 +67,7 @@ set(HEADERS memory_util.h microprofile.h microprofileui.h + param_package.h platform.h quaternion.h scm_rev.h diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index df234c225..5ab036b34 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -118,8 +118,7 @@ bool IsDirectory(const std::string& filename) { #endif if (result < 0) { - LOG_WARNING(Common_Filesystem, "stat failed on %s: %s", filename.c_str(), - GetLastErrorMsg()); + LOG_DEBUG(Common_Filesystem, "stat failed on %s: %s", filename.c_str(), GetLastErrorMsg()); return false; } @@ -129,12 +128,12 @@ bool IsDirectory(const std::string& filename) { // Deletes a given filename, return true on success // Doesn't supports deleting a directory bool Delete(const std::string& filename) { - LOG_INFO(Common_Filesystem, "file %s", filename.c_str()); + LOG_TRACE(Common_Filesystem, "file %s", filename.c_str()); // Return true because we care about the file no // being there, not the actual delete. if (!Exists(filename)) { - LOG_WARNING(Common_Filesystem, "%s does not exist", filename.c_str()); + LOG_DEBUG(Common_Filesystem, "%s does not exist", filename.c_str()); return true; } @@ -169,8 +168,7 @@ bool CreateDir(const std::string& path) { return true; DWORD error = GetLastError(); if (error == ERROR_ALREADY_EXISTS) { - LOG_WARNING(Common_Filesystem, "CreateDirectory failed on %s: already exists", - path.c_str()); + LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on %s: already exists", path.c_str()); return true; } LOG_ERROR(Common_Filesystem, "CreateDirectory failed on %s: %i", path.c_str(), error); @@ -182,7 +180,7 @@ bool CreateDir(const std::string& path) { int err = errno; if (err == EEXIST) { - LOG_WARNING(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str()); + LOG_DEBUG(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str()); return true; } @@ -197,7 +195,7 @@ bool CreateFullPath(const std::string& fullPath) { LOG_TRACE(Common_Filesystem, "path %s", fullPath.c_str()); if (FileUtil::Exists(fullPath)) { - LOG_WARNING(Common_Filesystem, "path exists %s", fullPath.c_str()); + LOG_DEBUG(Common_Filesystem, "path exists %s", fullPath.c_str()); return true; } @@ -229,7 +227,7 @@ bool CreateFullPath(const std::string& fullPath) { // Deletes a directory filename, returns true on success bool DeleteDir(const std::string& filename) { - LOG_INFO(Common_Filesystem, "directory %s", filename.c_str()); + LOG_TRACE(Common_Filesystem, "directory %s", filename.c_str()); // check if a directory if (!FileUtil::IsDirectory(filename)) { @@ -693,6 +691,8 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new paths[D_USER_IDX] = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; if (!FileUtil::IsDirectory(paths[D_USER_IDX])) { paths[D_USER_IDX] = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP; + } else { + LOG_INFO(Common_Filesystem, "Using the local user directory"); } paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP; diff --git a/src/common/framebuffer_layout.cpp b/src/common/framebuffer_layout.cpp index 46c008d9c..a2a0e7dad 100644 --- a/src/common/framebuffer_layout.cpp +++ b/src/common/framebuffer_layout.cpp @@ -6,6 +6,7 @@ #include "common/assert.h" #include "common/framebuffer_layout.h" +#include "core/settings.h" #include "video_core/video_core.h" namespace Layout { @@ -135,4 +136,22 @@ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool swapped res.bottom_screen = swapped ? large_screen : small_screen; return res; } + +FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) { + ASSERT(width > 0); + ASSERT(height > 0); + + FramebufferLayout res{width, height, true, true, {}, {}}; + + MathUtil::Rectangle<unsigned> top_screen{ + Settings::values.custom_top_left, Settings::values.custom_top_top, + Settings::values.custom_top_right, Settings::values.custom_top_bottom}; + MathUtil::Rectangle<unsigned> bot_screen{ + Settings::values.custom_bottom_left, Settings::values.custom_bottom_top, + Settings::values.custom_bottom_right, Settings::values.custom_bottom_bottom}; + + res.top_screen = top_screen; + res.bottom_screen = bot_screen; + return res; +} } diff --git a/src/common/framebuffer_layout.h b/src/common/framebuffer_layout.h index a125646a3..f1df5c55a 100644 --- a/src/common/framebuffer_layout.h +++ b/src/common/framebuffer_layout.h @@ -44,4 +44,12 @@ FramebufferLayout SingleFrameLayout(unsigned width, unsigned height, bool is_swa * @return Newly created FramebufferLayout object with default screen regions initialized */ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool is_swapped); + +/** + * Factory method for constructing a custom FramebufferLayout + * @param width Window framebuffer width in pixels + * @param height Window framebuffer height in pixels + * @return Newly created FramebufferLayout object with default screen regions initialized + */ +FramebufferLayout CustomFrameLayout(unsigned width, unsigned height); } diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 737e1d57f..42f6a9918 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -71,6 +71,7 @@ namespace Log { CLS(Audio) \ SUB(Audio, DSP) \ SUB(Audio, Sink) \ + CLS(Input) \ CLS(Loader) // GetClassName is a macro defined by Windows.h, grrr... diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 4b0f8ff03..1b905f66c 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -89,6 +89,7 @@ enum class Class : ClassType { Audio_DSP, ///< The HLE implementation of the DSP Audio_Sink, ///< Emulator audio output backend Loader, ///< ROM loader + Input, ///< Input emulation Count ///< Total number of logging classes }; diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp new file mode 100644 index 000000000..3a6ef8c27 --- /dev/null +++ b/src/common/param_package.cpp @@ -0,0 +1,120 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <vector> +#include "common/logging/log.h" +#include "common/param_package.h" +#include "common/string_util.h" + +namespace Common { + +constexpr char KEY_VALUE_SEPARATOR = ':'; +constexpr char PARAM_SEPARATOR = ','; +constexpr char ESCAPE_CHARACTER = '$'; +const std::string KEY_VALUE_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '0'}; +const std::string PARAM_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '1'}; +const std::string ESCAPE_CHARACTER_ESCAPE{ESCAPE_CHARACTER, '2'}; + +ParamPackage::ParamPackage(const std::string& serialized) { + std::vector<std::string> pairs; + Common::SplitString(serialized, PARAM_SEPARATOR, pairs); + + for (const std::string& pair : pairs) { + std::vector<std::string> key_value; + Common::SplitString(pair, KEY_VALUE_SEPARATOR, key_value); + if (key_value.size() != 2) { + LOG_ERROR(Common, "invalid key pair %s", pair.c_str()); + continue; + } + + for (std::string& part : key_value) { + part = Common::ReplaceAll(part, KEY_VALUE_SEPARATOR_ESCAPE, {KEY_VALUE_SEPARATOR}); + part = Common::ReplaceAll(part, PARAM_SEPARATOR_ESCAPE, {PARAM_SEPARATOR}); + part = Common::ReplaceAll(part, ESCAPE_CHARACTER_ESCAPE, {ESCAPE_CHARACTER}); + } + + Set(key_value[0], key_value[1]); + } +} + +ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : data(list) {} + +std::string ParamPackage::Serialize() const { + if (data.empty()) + return ""; + + std::string result; + + for (const auto& pair : data) { + std::array<std::string, 2> key_value{{pair.first, pair.second}}; + for (std::string& part : key_value) { + part = Common::ReplaceAll(part, {ESCAPE_CHARACTER}, ESCAPE_CHARACTER_ESCAPE); + part = Common::ReplaceAll(part, {PARAM_SEPARATOR}, PARAM_SEPARATOR_ESCAPE); + part = Common::ReplaceAll(part, {KEY_VALUE_SEPARATOR}, KEY_VALUE_SEPARATOR_ESCAPE); + } + result += key_value[0] + KEY_VALUE_SEPARATOR + key_value[1] + PARAM_SEPARATOR; + } + + result.pop_back(); // discard the trailing PARAM_SEPARATOR + return result; +} + +std::string ParamPackage::Get(const std::string& key, const std::string& default_value) const { + auto pair = data.find(key); + if (pair == data.end()) { + LOG_DEBUG(Common, "key %s not found", key.c_str()); + return default_value; + } + + return pair->second; +} + +int ParamPackage::Get(const std::string& key, int default_value) const { + auto pair = data.find(key); + if (pair == data.end()) { + LOG_DEBUG(Common, "key %s not found", key.c_str()); + return default_value; + } + + try { + return std::stoi(pair->second); + } catch (const std::logic_error&) { + LOG_ERROR(Common, "failed to convert %s to int", pair->second.c_str()); + return default_value; + } +} + +float ParamPackage::Get(const std::string& key, float default_value) const { + auto pair = data.find(key); + if (pair == data.end()) { + LOG_DEBUG(Common, "key %s not found", key.c_str()); + return default_value; + } + + try { + return std::stof(pair->second); + } catch (const std::logic_error&) { + LOG_ERROR(Common, "failed to convert %s to float", pair->second.c_str()); + return default_value; + } +} + +void ParamPackage::Set(const std::string& key, const std::string& value) { + data[key] = value; +} + +void ParamPackage::Set(const std::string& key, int value) { + data[key] = std::to_string(value); +} + +void ParamPackage::Set(const std::string& key, float value) { + data[key] = std::to_string(value); +} + +bool ParamPackage::Has(const std::string& key) const { + return data.find(key) != data.end(); +} + +} // namespace Common diff --git a/src/common/param_package.h b/src/common/param_package.h new file mode 100644 index 000000000..c4c11b221 --- /dev/null +++ b/src/common/param_package.h @@ -0,0 +1,40 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <initializer_list> +#include <string> +#include <unordered_map> + +namespace Common { + +/// A string-based key-value container supporting serializing to and deserializing from a string +class ParamPackage { +public: + using DataType = std::unordered_map<std::string, std::string>; + + ParamPackage() = default; + explicit ParamPackage(const std::string& serialized); + ParamPackage(std::initializer_list<DataType::value_type> list); + ParamPackage(const ParamPackage& other) = default; + ParamPackage(ParamPackage&& other) = default; + + ParamPackage& operator=(const ParamPackage& other) = default; + ParamPackage& operator=(ParamPackage&& other) = default; + + std::string Serialize() const; + std::string Get(const std::string& key, const std::string& default_value) const; + int Get(const std::string& key, int default_value) const; + float Get(const std::string& key, float default_value) const; + void Set(const std::string& key, const std::string& value); + void Set(const std::string& key, int value); + void Set(const std::string& key, float value); + bool Has(const std::string& key) const; + +private: + DataType data; +}; + +} // namespace Common diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index 370ae2c80..2cb3ab9cc 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -8,9 +8,9 @@ #include "common/common_types.h" #include "cpu_detect.h" -namespace Common { - -#ifndef _MSC_VER +#ifdef _MSC_VER +#include <intrin.h> +#else #if defined(__DragonFly__) || defined(__FreeBSD__) // clang-format off @@ -37,13 +37,15 @@ static inline void __cpuid(int info[4], int function_id) { } #define _XCR_XFEATURE_ENABLED_MASK 0 -static u64 _xgetbv(u32 index) { +static inline u64 _xgetbv(u32 index) { u32 eax, edx; __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index)); return ((u64)edx << 32) | eax; } -#endif // ifndef _MSC_VER +#endif // _MSC_VER + +namespace Common { // Detects the various CPU features static CPUCaps Detect() { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ffd67f074..61a0b1cc3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -34,7 +34,6 @@ set(SRCS frontend/camera/factory.cpp frontend/camera/interface.cpp frontend/emu_window.cpp - frontend/key_map.cpp frontend/motion_emu.cpp gdbstub/gdbstub.cpp hle/config_mem.cpp @@ -218,7 +217,7 @@ set(HEADERS frontend/camera/factory.h frontend/camera/interface.h frontend/emu_window.h - frontend/key_map.h + frontend/input.h frontend/motion_emu.h gdbstub/gdbstub.h hle/config_mem.h diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index dd2fb167f..f454e7840 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp @@ -173,7 +173,7 @@ Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low) { ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location, bool shared) : shared(shared), mount_point(GetExtDataContainerPath(mount_location, shared)) { - LOG_INFO(Service_FS, "Directory %s set as base for ExtSaveData.", mount_point.c_str()); + LOG_DEBUG(Service_FS, "Directory %s set as base for ExtSaveData.", mount_point.c_str()); } bool ArchiveFactory_ExtSaveData::Initialize() { diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index 72ff05c65..679909d06 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -306,7 +306,7 @@ u64 SDMCArchive::GetFreeBytes() const { ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory) : sdmc_directory(sdmc_directory) { - LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str()); + LOG_DEBUG(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str()); } bool ArchiveFactory_SDMC::Initialize() { diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp index 2aafc9b1d..244aef48a 100644 --- a/src/core/file_sys/archive_sdmcwriteonly.cpp +++ b/src/core/file_sys/archive_sdmcwriteonly.cpp @@ -32,7 +32,7 @@ ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory ArchiveFactory_SDMCWriteOnly::ArchiveFactory_SDMCWriteOnly(const std::string& mount_point) : sdmc_directory(mount_point) { - LOG_INFO(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str()); + LOG_DEBUG(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str()); } bool ArchiveFactory_SDMCWriteOnly::Initialize() { diff --git a/src/core/file_sys/archive_source_sd_savedata.cpp b/src/core/file_sys/archive_source_sd_savedata.cpp index e01357891..f31a68038 100644 --- a/src/core/file_sys/archive_source_sd_savedata.cpp +++ b/src/core/file_sys/archive_source_sd_savedata.cpp @@ -39,7 +39,7 @@ std::string GetSaveDataMetadataPath(const std::string& mount_location, u64 progr ArchiveSource_SDSaveData::ArchiveSource_SDSaveData(const std::string& sdmc_directory) : mount_point(GetSaveDataContainerPath(sdmc_directory)) { - LOG_INFO(Service_FS, "Directory %s set as SaveData.", mount_point.c_str()); + LOG_DEBUG(Service_FS, "Directory %s set as SaveData.", mount_point.c_str()); } ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(u64 program_id) { diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index a155b657d..5fdb3a7e8 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -7,33 +7,9 @@ #include "common/assert.h" #include "core/core.h" #include "core/frontend/emu_window.h" -#include "core/frontend/key_map.h" +#include "core/settings.h" #include "video_core/video_core.h" -void EmuWindow::ButtonPressed(Service::HID::PadState pad) { - pad_state.hex |= pad.hex; -} - -void EmuWindow::ButtonReleased(Service::HID::PadState pad) { - pad_state.hex &= ~pad.hex; -} - -void EmuWindow::CirclePadUpdated(float x, float y) { - constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position - - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - circle_pad_x = static_cast<s16>(x * MAX_CIRCLEPAD_POS); - circle_pad_y = static_cast<s16>(y * MAX_CIRCLEPAD_POS); -} - /** * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout * @param layout FramebufferLayout object describing the framebuffer size and screen positions @@ -113,17 +89,21 @@ void EmuWindow::GyroscopeChanged(float x, float y, float z) { void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) { Layout::FramebufferLayout layout; - switch (Settings::values.layout_option) { - case Settings::LayoutOption::SingleScreen: - layout = Layout::SingleFrameLayout(width, height, Settings::values.swap_screen); - break; - case Settings::LayoutOption::LargeScreen: - layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen); - break; - case Settings::LayoutOption::Default: - default: - layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen); - break; + if (Settings::values.custom_layout == true) { + layout = Layout::CustomFrameLayout(width, height); + } else { + switch (Settings::values.layout_option) { + case Settings::LayoutOption::SingleScreen: + layout = Layout::SingleFrameLayout(width, height, Settings::values.swap_screen); + break; + case Settings::LayoutOption::LargeScreen: + layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen); + break; + case Settings::LayoutOption::Default: + default: + layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen); + break; + } } NotifyFramebufferLayoutChanged(layout); } diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 1ba64c92b..36f2667fa 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -10,7 +10,6 @@ #include "common/common_types.h" #include "common/framebuffer_layout.h" #include "common/math_util.h" -#include "core/hle/service/hid/hid.h" /** * Abstraction class used to provide an interface between emulation code and the frontend @@ -52,30 +51,6 @@ public: /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread virtual void DoneCurrent() = 0; - virtual void ReloadSetKeymaps() = 0; - - /** - * Signals a button press action to the HID module. - * @param pad_state indicates which button to press - * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad. - */ - void ButtonPressed(Service::HID::PadState pad_state); - - /** - * Signals a button release action to the HID module. - * @param pad_state indicates which button to press - * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad. - */ - void ButtonReleased(Service::HID::PadState pad_state); - - /** - * Signals a circle pad change action to the HID module. - * @param x new x-coordinate of the circle pad, in the range [-1.0, 1.0] - * @param y new y-coordinate of the circle pad, in the range [-1.0, 1.0] - * @note the coordinates will be normalized if the radius is larger than 1 - */ - void CirclePadUpdated(float x, float y); - /** * Signal that a touch pressed event has occurred (e.g. mouse click pressed) * @param framebuffer_x Framebuffer x-coordinate that was pressed @@ -115,27 +90,6 @@ public: void GyroscopeChanged(float x, float y, float z); /** - * Gets the current pad state (which buttons are pressed). - * @note This should be called by the core emu thread to get a state set by the window thread. - * @note This doesn't include analog input like circle pad direction - * @todo Fix this function to be thread-safe. - * @return PadState object indicating the current pad state - */ - Service::HID::PadState GetPadState() const { - return pad_state; - } - - /** - * Gets the current circle pad state. - * @note This should be called by the core emu thread to get a state set by the window thread. - * @todo Fix this function to be thread-safe. - * @return std::tuple of (x, y), where `x` and `y` are the circle pad coordinates - */ - std::tuple<s16, s16> GetCirclePadState() const { - return std::make_tuple(circle_pad_x, circle_pad_y); - } - - /** * Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed). * @note This should be called by the core emu thread to get a state set by the window thread. * @todo Fix this function to be thread-safe. @@ -230,11 +184,8 @@ protected: // TODO: Find a better place to set this. config.min_client_area_size = std::make_pair(400u, 480u); active_config = config; - pad_state.hex = 0; touch_x = 0; touch_y = 0; - circle_pad_x = 0; - circle_pad_y = 0; touch_pressed = false; accel_x = 0; accel_y = -512; @@ -304,9 +255,6 @@ private: u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320) u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240) - s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156) - s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156) - std::mutex accel_mutex; s16 accel_x; ///< Accelerometer X-axis value in native 3DS units s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units @@ -321,6 +269,4 @@ private: * Clip the provided coordinates to be inside the touchscreen area. */ std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y); - - Service::HID::PadState pad_state; }; diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h new file mode 100644 index 000000000..0a5713dc0 --- /dev/null +++ b/src/core/frontend/input.h @@ -0,0 +1,110 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> +#include <tuple> +#include <unordered_map> +#include <utility> +#include "common/logging/log.h" +#include "common/param_package.h" + +namespace Input { + +/// An abstract class template for an input device (a button, an analog input, etc.). +template <typename StatusType> +class InputDevice { +public: + virtual ~InputDevice() = default; + virtual StatusType GetStatus() const { + return {}; + } +}; + +/// An abstract class template for a factory that can create input devices. +template <typename InputDeviceType> +class Factory { +public: + virtual ~Factory() = default; + virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0; +}; + +namespace Impl { + +template <typename InputDeviceType> +using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>; + +template <typename InputDeviceType> +struct FactoryList { + static FactoryListType<InputDeviceType> list; +}; + +template <typename InputDeviceType> +FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list; + +} // namespace Impl + +/** + * Registers an input device factory. + * @tparam InputDeviceType the type of input devices the factory can create + * @param name the name of the factory. Will be used to match the "engine" parameter when creating + * a device + * @param factory the factory object to register + */ +template <typename InputDeviceType> +void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) { + auto pair = std::make_pair(name, std::move(factory)); + if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) { + LOG_ERROR(Input, "Factory %s already registered", name.c_str()); + } +} + +/** + * Unregisters an input device factory. + * @tparam InputDeviceType the type of input devices the factory can create + * @param name the name of the factory to unregister + */ +template <typename InputDeviceType> +void UnregisterFactory(const std::string& name) { + if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) { + LOG_ERROR(Input, "Factory %s not registered", name.c_str()); + } +} + +/** + * Create an input device from given paramters. + * @tparam InputDeviceType the type of input devices to create + * @param params a serialized ParamPackage string contains all parameters for creating the device + */ +template <typename InputDeviceType> +std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) { + const Common::ParamPackage package(params); + const std::string engine = package.Get("engine", "null"); + const auto& factory_list = Impl::FactoryList<InputDeviceType>::list; + const auto pair = factory_list.find(engine); + if (pair == factory_list.end()) { + if (engine != "null") { + LOG_ERROR(Input, "Unknown engine name: %s", engine.c_str()); + } + return std::make_unique<InputDeviceType>(); + } + return pair->second->Create(package); +} + +/** + * A button device is an input device that returns bool as status. + * true for pressed; false for released. + */ +using ButtonDevice = InputDevice<bool>; + +/** + * An analog device is an input device that returns a tuple of x and y coordinates as status. The + * coordinates are within the unit circle. x+ is defined as right direction, and y+ is defined as up + * direction + */ +using AnalogDevice = InputDevice<std::tuple<float, float>>; + +} // namespace Input diff --git a/src/core/frontend/key_map.cpp b/src/core/frontend/key_map.cpp deleted file mode 100644 index 15f0e079c..000000000 --- a/src/core/frontend/key_map.cpp +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <map> -#include "core/frontend/emu_window.h" -#include "core/frontend/key_map.h" - -namespace KeyMap { - -// TODO (wwylele): currently we treat c-stick as four direction buttons -// and map it directly to EmuWindow::ButtonPressed. -// It should go the analog input way like circle pad does. -const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets = {{ - Service::HID::PAD_A, - Service::HID::PAD_B, - Service::HID::PAD_X, - Service::HID::PAD_Y, - Service::HID::PAD_L, - Service::HID::PAD_R, - Service::HID::PAD_ZL, - Service::HID::PAD_ZR, - Service::HID::PAD_START, - Service::HID::PAD_SELECT, - Service::HID::PAD_NONE, - Service::HID::PAD_UP, - Service::HID::PAD_DOWN, - Service::HID::PAD_LEFT, - Service::HID::PAD_RIGHT, - Service::HID::PAD_C_UP, - Service::HID::PAD_C_DOWN, - Service::HID::PAD_C_LEFT, - Service::HID::PAD_C_RIGHT, - - IndirectTarget::CirclePadUp, - IndirectTarget::CirclePadDown, - IndirectTarget::CirclePadLeft, - IndirectTarget::CirclePadRight, - IndirectTarget::CirclePadModifier, -}}; - -static std::map<HostDeviceKey, KeyTarget> key_map; -static int next_device_id = 0; - -static bool circle_pad_up = false; -static bool circle_pad_down = false; -static bool circle_pad_left = false; -static bool circle_pad_right = false; -static bool circle_pad_modifier = false; - -static void UpdateCirclePad(EmuWindow& emu_window) { - constexpr float SQRT_HALF = 0.707106781f; - int x = 0, y = 0; - - if (circle_pad_right) - ++x; - if (circle_pad_left) - --x; - if (circle_pad_up) - ++y; - if (circle_pad_down) - --y; - - float modifier = circle_pad_modifier ? Settings::values.pad_circle_modifier_scale : 1.0f; - emu_window.CirclePadUpdated(x * modifier * (y == 0 ? 1.0f : SQRT_HALF), - y * modifier * (x == 0 ? 1.0f : SQRT_HALF)); -} - -int NewDeviceId() { - return next_device_id++; -} - -void SetKeyMapping(HostDeviceKey key, KeyTarget target) { - key_map[key] = target; -} - -void ClearKeyMapping(int device_id) { - auto iter = key_map.begin(); - while (iter != key_map.end()) { - if (iter->first.device_id == device_id) - key_map.erase(iter++); - else - ++iter; - } -} - -void PressKey(EmuWindow& emu_window, HostDeviceKey key) { - auto target = key_map.find(key); - if (target == key_map.end()) - return; - - if (target->second.direct) { - emu_window.ButtonPressed({{target->second.target.direct_target_hex}}); - } else { - switch (target->second.target.indirect_target) { - case IndirectTarget::CirclePadUp: - circle_pad_up = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadDown: - circle_pad_down = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadLeft: - circle_pad_left = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadRight: - circle_pad_right = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadModifier: - circle_pad_modifier = true; - UpdateCirclePad(emu_window); - break; - } - } -} - -void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key) { - auto target = key_map.find(key); - if (target == key_map.end()) - return; - - if (target->second.direct) { - emu_window.ButtonReleased({{target->second.target.direct_target_hex}}); - } else { - switch (target->second.target.indirect_target) { - case IndirectTarget::CirclePadUp: - circle_pad_up = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadDown: - circle_pad_down = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadLeft: - circle_pad_left = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadRight: - circle_pad_right = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadModifier: - circle_pad_modifier = false; - UpdateCirclePad(emu_window); - break; - } - } -} -} diff --git a/src/core/frontend/key_map.h b/src/core/frontend/key_map.h deleted file mode 100644 index 040794578..000000000 --- a/src/core/frontend/key_map.h +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <tuple> -#include "core/hle/service/hid/hid.h" - -class EmuWindow; - -namespace KeyMap { - -/** - * Represents key mapping targets that are not real 3DS buttons. - * They will be handled by KeyMap and translated to 3DS input. - */ -enum class IndirectTarget { - CirclePadUp, - CirclePadDown, - CirclePadLeft, - CirclePadRight, - CirclePadModifier, -}; - -/** - * Represents a key mapping target. It can be a PadState that represents real 3DS buttons, - * or an IndirectTarget. - */ -struct KeyTarget { - bool direct; - union { - u32 direct_target_hex; - IndirectTarget indirect_target; - } target; - - KeyTarget() : direct(true) { - target.direct_target_hex = 0; - } - - KeyTarget(Service::HID::PadState pad) : direct(true) { - target.direct_target_hex = pad.hex; - } - - KeyTarget(IndirectTarget i) : direct(false) { - target.indirect_target = i; - } -}; - -/** - * Represents a key for a specific host device. - */ -struct HostDeviceKey { - int key_code; - int device_id; ///< Uniquely identifies a host device - - bool operator<(const HostDeviceKey& other) const { - return std::tie(key_code, device_id) < std::tie(other.key_code, other.device_id); - } - - bool operator==(const HostDeviceKey& other) const { - return std::tie(key_code, device_id) == std::tie(other.key_code, other.device_id); - } -}; - -extern const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets; - -/** - * Generates a new device id, which uniquely identifies a host device within KeyMap. - */ -int NewDeviceId(); - -/** - * Maps a device-specific key to a target (a PadState or an IndirectTarget). - */ -void SetKeyMapping(HostDeviceKey key, KeyTarget target); - -/** - * Clears all key mappings belonging to one device. - */ -void ClearKeyMapping(int device_id); - -/** - * Maps a key press action and call the corresponding function in EmuWindow - */ -void PressKey(EmuWindow& emu_window, HostDeviceKey key); - -/** - * Maps a key release action and call the corresponding function in EmuWindow - */ -void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key); -} diff --git a/src/core/hle/function_wrappers.h b/src/core/hle/function_wrappers.h index 7875971ce..f6eb900f0 100644 --- a/src/core/hle/function_wrappers.h +++ b/src/core/hle/function_wrappers.h @@ -256,9 +256,9 @@ void Wrap() { func(((s64)PARAM(1) << 32) | PARAM(0)); } -template <void func(const char*)> +template <void func(const char*, int len)> void Wrap() { - func((char*)Memory::GetPointer(PARAM(0))); + func((char*)Memory::GetPointer(PARAM(0)), PARAM(1)); } template <void func(u8)> diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index cd9a5863d..3a5d481a5 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -10,7 +10,8 @@ namespace Kernel { -static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of header +/// Offset into command buffer of header +static const int kCommandHeaderOffset = 0x80; /** * Returns a pointer to the command buffer in the current thread's TLS @@ -26,8 +27,8 @@ inline u32* GetCommandBuffer(const int offset = 0) { offset); } -static const int kStaticBuffersOffset = - 0x100; ///< Offset into static buffers, relative to command buffer header +/// Offset into static buffers, relative to command buffer header +static const int kStaticBuffersOffset = 0x100; /** * Returns a pointer to the static buffers area in the current thread's TLS diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 323158bb5..06c4c5a85 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -27,6 +27,24 @@ public: DEBUG_ASSERT_MSG(index == TotalSize(), "Operations do not match the header (cmd 0x%x)", header.raw); } + + void Skip(unsigned size_in_words, bool set_to_null) { + if (set_to_null) + memset(cmdbuf + index, 0, size_in_words * sizeof(u32)); + index += size_in_words; + } + + /** + * @brief Retrieves the address of a static buffer, used when a buffer is needed for output + * @param buffer_id The index of the static buffer + * @param data_size If non-null, will store the size of the buffer + */ + VAddr PeekStaticBuffer(u8 buffer_id, size_t* data_size = nullptr) const { + u32* static_buffer = cmdbuf + Kernel::kStaticBuffersOffset / sizeof(u32) + buffer_id * 2; + if (data_size) + *data_size = StaticBufferDescInfo{static_buffer[0]}.size; + return static_buffer[1]; + } }; class RequestBuilder : public RequestHelperBase { @@ -50,14 +68,8 @@ public: template <typename T> void Push(T value); - void Push(u32 value) { - cmdbuf[index++] = value; - } template <typename First, typename... Other> - void Push(const First& first_value, const Other&... other_values) { - Push(first_value); - Push(other_values...); - } + void Push(const First& first_value, const Other&... other_values); /** * @brief Copies the content of the given trivially copyable class to the buffer as a normal @@ -65,59 +77,96 @@ public: * @note: The input class must be correctly packed/padded to fit hardware layout. */ template <typename T> - void PushRaw(const T& value) { - static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable"); - std::memcpy(cmdbuf + index, &value, sizeof(T)); - index += (sizeof(T) + 3) / 4; // round up to word length - } + void PushRaw(const T& value); // TODO : ensure that translate params are added after all regular params template <typename... H> - void PushCopyHandles(H... handles) { - Push(CopyHandleDesc(sizeof...(H))); - Push(static_cast<Kernel::Handle>(handles)...); - } + void PushCopyHandles(H... handles); template <typename... H> - void PushMoveHandles(H... handles) { - Push(MoveHandleDesc(sizeof...(H))); - Push(static_cast<Kernel::Handle>(handles)...); - } + void PushMoveHandles(H... handles); - void PushCurrentPIDHandle() { - Push(CallingPidDesc()); - Push(u32(0)); - } + void PushCurrentPIDHandle(); - void PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id) { - Push(StaticBufferDesc(size, buffer_id)); - Push(buffer_vaddr); - } + void PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id); - void PushMappedBuffer(VAddr buffer_vaddr, u32 size, MappedBufferPermissions perms) { - Push(MappedBufferDesc(size, perms)); - Push(buffer_vaddr); - } + void PushMappedBuffer(VAddr buffer_vaddr, u32 size, MappedBufferPermissions perms); }; /// Push /// template <> -inline void RequestBuilder::Push<u32>(u32 value) { - Push(value); +inline void RequestBuilder::Push(u32 value) { + cmdbuf[index++] = value; +} + +template <typename T> +void RequestBuilder::PushRaw(const T& value) { + static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable"); + std::memcpy(cmdbuf + index, &value, sizeof(T)); + index += (sizeof(T) + 3) / 4; // round up to word length } template <> -inline void RequestBuilder::Push<u64>(u64 value) { +inline void RequestBuilder::Push(u8 value) { + PushRaw(value); +} + +template <> +inline void RequestBuilder::Push(u16 value) { + PushRaw(value); +} + +template <> +inline void RequestBuilder::Push(u64 value) { Push(static_cast<u32>(value)); Push(static_cast<u32>(value >> 32)); } template <> -inline void RequestBuilder::Push<ResultCode>(ResultCode value) { +inline void RequestBuilder::Push(bool value) { + Push(static_cast<u8>(value)); +} + +template <> +inline void RequestBuilder::Push(ResultCode value) { Push(value.raw); } +template <typename First, typename... Other> +void RequestBuilder::Push(const First& first_value, const Other&... other_values) { + Push(first_value); + Push(other_values...); +} + +template <typename... H> +inline void RequestBuilder::PushCopyHandles(H... handles) { + Push(CopyHandleDesc(sizeof...(H))); + Push(static_cast<Kernel::Handle>(handles)...); +} + +template <typename... H> +inline void RequestBuilder::PushMoveHandles(H... handles) { + Push(MoveHandleDesc(sizeof...(H))); + Push(static_cast<Kernel::Handle>(handles)...); +} + +inline void RequestBuilder::PushCurrentPIDHandle() { + Push(CallingPidDesc()); + Push(u32(0)); +} + +inline void RequestBuilder::PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id) { + Push(StaticBufferDesc(size, buffer_id)); + Push(buffer_vaddr); +} + +inline void RequestBuilder::PushMappedBuffer(VAddr buffer_vaddr, u32 size, + MappedBufferPermissions perms) { + Push(MappedBufferDesc(size, perms)); + Push(buffer_vaddr); +} + class RequestParser : public RequestHelperBase { public: RequestParser(u32* command_buffer, Header command_header) @@ -185,24 +234,60 @@ public: */ template <typename T> void PopRaw(T& value); + + /** + * @brief Reads the next normal parameters as a struct, by copying it into a new value + * @note: The output class must be correctly packed/padded to fit hardware layout. + */ + template <typename T> + T PopRaw(); }; /// Pop /// template <> -inline u32 RequestParser::Pop<u32>() { +inline u32 RequestParser::Pop() { return cmdbuf[index++]; } +template <typename T> +void RequestParser::PopRaw(T& value) { + static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable"); + std::memcpy(&value, cmdbuf + index, sizeof(T)); + index += (sizeof(T) + 3) / 4; // round up to word length +} + +template <typename T> +T RequestParser::PopRaw() { + T value; + PopRaw(value); + return value; +} + +template <> +inline u8 RequestParser::Pop() { + return PopRaw<u8>(); +} + template <> -inline u64 RequestParser::Pop<u64>() { +inline u16 RequestParser::Pop() { + return PopRaw<u16>(); +} + +template <> +inline u64 RequestParser::Pop() { const u64 lsw = Pop<u32>(); const u64 msw = Pop<u32>(); return msw << 32 | lsw; } template <> -inline ResultCode RequestParser::Pop<ResultCode>() { +inline bool RequestParser::Pop() { + return Pop<u8>() != 0; +} + +template <> +inline ResultCode RequestParser::Pop() { return ResultCode{Pop<u32>()}; } @@ -265,11 +350,4 @@ inline VAddr RequestParser::PopMappedBuffer(size_t* data_size, return Pop<VAddr>(); } -template <typename T> -void RequestParser::PopRaw(T& value) { - static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable"); - std::memcpy(&value, cmdbuf + index, sizeof(T)); - index += (sizeof(T) + 3) / 4; // round up to word length -} - } // namespace IPC diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index e57b19c2d..366d1eacf 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -49,13 +49,13 @@ void SendParameter(const MessageParameter& parameter) { } void Initialize(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - u32 flags = cmd_buff[2]; - - cmd_buff[2] = IPC::CopyHandleDesc(2); - cmd_buff[3] = Kernel::g_handle_table.Create(notification_event).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(parameter_event).MoveFrom(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2, 2, 0); // 0x20080 + u32 app_id = rp.Pop<u32>(); + u32 flags = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 3); + rb.Push(RESULT_SUCCESS); + rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).MoveFrom(), + Kernel::g_handle_table.Create(parameter_event).MoveFrom()); // TODO(bunnei): Check if these events are cleared every time Initialize is called. notification_event->Clear(); @@ -64,18 +64,16 @@ void Initialize(Service::Interface* self) { ASSERT_MSG((nullptr != lock), "Cannot initialize without lock"); lock->Release(); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_DEBUG(Service_APT, "called app_id=0x%08X, flags=0x%08X", app_id, flags); } void GetSharedFont(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000 + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); if (!shared_font_mem) { LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds"); - cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2); - cmd_buff[1] = -1; // TODO: Find the right error code + rb.Push<u32>(-1); // TODO: Find the right error code + rb.Skip(1 + 2, true); return; } @@ -87,103 +85,110 @@ void GetSharedFont(Service::Interface* self) { BCFNT::RelocateSharedFont(shared_font_mem, target_address); shared_font_relocated = true; } - cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + rb.Push(RESULT_SUCCESS); // No error // Since the SharedMemory interface doesn't provide the address at which the memory was // allocated, the real APT service calculates this address by scanning the entire address space // (using svcQueryMemory) and searches for an allocation of the same size as the Shared Font. - cmd_buff[2] = target_address; - cmd_buff[3] = IPC::CopyHandleDesc(); - cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom(); + rb.Push(target_address); + rb.PushCopyHandles(Kernel::g_handle_table.Create(shared_font_mem).MoveFrom()); } void NotifyToWait(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x43, 1, 0); // 0x430040 + u32 app_id = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) app_id=%u", app_id); } void GetLockHandle(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1, 1, 0); // 0x10040 + // Bits [0:2] are the applet type (System, Library, etc) // Bit 5 tells the application that there's a pending APT parameter, // this will cause the app to wait until parameter_event is signaled. - u32 applet_attributes = cmd_buff[1]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - cmd_buff[2] = applet_attributes; // Applet Attributes, this value is passed to Enable. - cmd_buff[3] = 0; // Least significant bit = power button state - cmd_buff[4] = IPC::CopyHandleDesc(); - cmd_buff[5] = Kernel::g_handle_table.Create(lock).MoveFrom(); - - LOG_WARNING(Service_APT, "(STUBBED) called handle=0x%08X applet_attributes=0x%08X", cmd_buff[5], + u32 applet_attributes = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(applet_attributes); // Applet Attributes, this value is passed to Enable. + rb.Push<u32>(0); // Least significant bit = power button state + Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).MoveFrom(); + rb.PushCopyHandles(handle_copy); + + LOG_WARNING(Service_APT, "(STUBBED) called handle=0x%08X applet_attributes=0x%08X", handle_copy, applet_attributes); } void Enable(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 attributes = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - parameter_event->Signal(); // Let the application know that it has been started + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3, 1, 0); // 0x30040 + u32 attributes = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error + parameter_event->Signal(); // Let the application know that it has been started LOG_WARNING(Service_APT, "(STUBBED) called attributes=0x%08X", attributes); } void GetAppletManInfo(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 unk = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; - cmd_buff[3] = 0; - cmd_buff[4] = static_cast<u32>(AppletId::HomeMenu); // Home menu AppID - cmd_buff[5] = static_cast<u32>(AppletId::Application); // TODO(purpasmart96): Do this correctly + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 1, 0); // 0x50040 + u32 unk = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push<u32>(0); + rb.Push<u32>(0); + rb.Push(static_cast<u32>(AppletId::HomeMenu)); // Home menu AppID + rb.Push(static_cast<u32>(AppletId::Application)); // TODO(purpasmart96): Do this correctly LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X", unk); } void IsRegistered(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 1, 0); // 0x90040 + u32 app_id = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error // TODO(Subv): An application is considered "registered" if it has already called APT::Enable // handle this properly once we implement multiprocess support. - cmd_buff[2] = 0; // Set to not registered by default + bool is_registered = false; // Set to not registered by default if (app_id == static_cast<u32>(AppletId::AnyLibraryApplet)) { - cmd_buff[2] = HLE::Applets::IsLibraryAppletRunning() ? 1 : 0; + is_registered = HLE::Applets::IsLibraryAppletRunning(); } else if (auto applet = HLE::Applets::Applet::Get(static_cast<AppletId>(app_id))) { - cmd_buff[2] = 1; // Set to registered + is_registered = true; // Set to registered } + rb.Push(is_registered); + LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); } void InquireNotification(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = static_cast<u32>(SignalType::None); // Signal type + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xB, 1, 0); // 0xB0040 + u32 app_id = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(static_cast<u32>(SignalType::None)); // Signal type LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); } void SendParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 src_app_id = cmd_buff[1]; - u32 dst_app_id = cmd_buff[2]; - u32 signal_type = cmd_buff[3]; - u32 buffer_size = cmd_buff[4]; - u32 value = cmd_buff[5]; - u32 handle = cmd_buff[6]; - u32 size = cmd_buff[7]; - u32 buffer = cmd_buff[8]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xC, 4, 4); // 0xC0104 + u32 src_app_id = rp.Pop<u32>(); + u32 dst_app_id = rp.Pop<u32>(); + u32 signal_type = rp.Pop<u32>(); + u32 buffer_size = rp.Pop<u32>(); + Kernel::Handle handle = rp.PopHandle(); + size_t size; + VAddr buffer = rp.PopStaticBuffer(&size); std::shared_ptr<HLE::Applets::Applet> dest_applet = HLE::Applets::Applet::Get(static_cast<AppletId>(dst_app_id)); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + if (dest_applet == nullptr) { LOG_ERROR(Service_APT, "Unknown applet id=0x%08X", dst_app_id); - cmd_buff[1] = -1; // TODO(Subv): Find the right error code + rb.Push<u32>(-1); // TODO(Subv): Find the right error code return; } @@ -195,88 +200,104 @@ void SendParameter(Service::Interface* self) { param.buffer.resize(buffer_size); Memory::ReadBlock(buffer, param.buffer.data(), param.buffer.size()); - cmd_buff[1] = dest_applet->ReceiveParameter(param).raw; + rb.Push(dest_applet->ReceiveParameter(param)); - LOG_WARNING( - Service_APT, - "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," - "buffer_size=0x%08X, value=0x%08X, handle=0x%08X, size=0x%08X, in_param_buffer_ptr=0x%08X", - src_app_id, dst_app_id, signal_type, buffer_size, value, handle, size, buffer); + LOG_WARNING(Service_APT, + "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," + "buffer_size=0x%08X, handle=0x%08X, size=0x%08zX, in_param_buffer_ptr=0x%08X", + src_app_id, dst_app_id, signal_type, buffer_size, handle, size, buffer); } void ReceiveParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - u32 buffer_size = cmd_buff[2]; - VAddr buffer = cmd_buff[0x104 >> 2]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = next_parameter.sender_id; - cmd_buff[3] = next_parameter.signal; // Signal type - cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size - cmd_buff[5] = 0x10; - cmd_buff[6] = 0; - if (next_parameter.object != nullptr) - cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom(); - cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2; - cmd_buff[8] = buffer; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xD, 2, 0); // 0xD0080 + u32 app_id = rp.Pop<u32>(); + u32 buffer_size = rp.Pop<u32>(); + + size_t static_buff_size; + VAddr buffer = rp.PeekStaticBuffer(0, &static_buff_size); + if (buffer_size > static_buff_size) + LOG_WARNING( + Service_APT, + "buffer_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)", + buffer_size, static_buff_size); + + IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(next_parameter.sender_id); + rb.Push(next_parameter.signal); // Signal type + ASSERT_MSG(next_parameter.buffer.size() <= buffer_size, "Input static buffer is too small !"); + rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size + + rb.PushMoveHandles((next_parameter.object != nullptr) + ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom() + : 0); + rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0); Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size()); - LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); + LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size); } void GlanceParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - u32 buffer_size = cmd_buff[2]; - VAddr buffer = cmd_buff[0x104 >> 2]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xE, 2, 0); // 0xE0080 + u32 app_id = rp.Pop<u32>(); + u32 buffer_size = rp.Pop<u32>(); + + size_t static_buff_size; + VAddr buffer = rp.PeekStaticBuffer(0, &static_buff_size); + if (buffer_size > static_buff_size) + LOG_WARNING( + Service_APT, + "buffer_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)", + buffer_size, static_buff_size); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = next_parameter.sender_id; - cmd_buff[3] = next_parameter.signal; // Signal type - cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size - cmd_buff[5] = 0x10; - cmd_buff[6] = 0; - if (next_parameter.object != nullptr) - cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom(); - cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2; - cmd_buff[8] = buffer; + IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(next_parameter.sender_id); + rb.Push(next_parameter.signal); // Signal type + ASSERT_MSG(next_parameter.buffer.size() <= buffer_size, "Input static buffer is too small !"); + rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size - Memory::WriteBlock(buffer, next_parameter.buffer.data(), - std::min(static_cast<size_t>(buffer_size), next_parameter.buffer.size())); + rb.PushCopyHandles((next_parameter.object != nullptr) + ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom() + : 0); + rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0); - LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); + Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size()); + + LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size); } void CancelParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 flag1 = cmd_buff[1]; - u32 unk = cmd_buff[2]; - u32 flag2 = cmd_buff[3]; - u32 app_id = cmd_buff[4]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xF, 4, 0); // 0xF0100 - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 1; // Set to Success + u32 check_sender = rp.Pop<u32>(); + u32 sender_appid = rp.Pop<u32>(); + u32 check_receiver = rp.Pop<u32>(); + u32 receiver_appid = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(true); // Set to Success - LOG_WARNING(Service_APT, - "(STUBBED) called flag1=0x%08X, unk=0x%08X, flag2=0x%08X, app_id=0x%08X", flag1, - unk, flag2, app_id); + LOG_WARNING(Service_APT, "(STUBBED) called check_sender=0x%08X, sender_appid=0x%08X, " + "check_receiver=0x%08X, receiver_appid=0x%08X", + check_sender, sender_appid, check_receiver, receiver_appid); } void PrepareToStartApplication(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 title_info1 = cmd_buff[1]; - u32 title_info2 = cmd_buff[2]; - u32 title_info3 = cmd_buff[3]; - u32 title_info4 = cmd_buff[4]; - u32 flags = cmd_buff[5]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x15, 5, 0); // 0x00150140 + u32 title_info1 = rp.Pop<u32>(); + u32 title_info2 = rp.Pop<u32>(); + u32 title_info3 = rp.Pop<u32>(); + u32 title_info4 = rp.Pop<u32>(); + u32 flags = rp.Pop<u32>(); if (flags & 0x00000100) { unknown_ns_state_field = 1; } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) called title_info1=0x%08X, title_info2=0x%08X, title_info3=0x%08X," @@ -285,172 +306,188 @@ void PrepareToStartApplication(Service::Interface* self) { } void StartApplication(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 buffer1_size = cmd_buff[1]; - u32 buffer2_size = cmd_buff[2]; - u32 flag = cmd_buff[3]; - u32 size1 = cmd_buff[4]; - u32 buffer1_ptr = cmd_buff[5]; - u32 size2 = cmd_buff[6]; - u32 buffer2_ptr = cmd_buff[7]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1B, 3, 4); // 0x001B00C4 + u32 buffer1_size = rp.Pop<u32>(); + u32 buffer2_size = rp.Pop<u32>(); + u32 flag = rp.Pop<u32>(); + size_t size1; + VAddr buffer1_ptr = rp.PopStaticBuffer(&size1); + size_t size2; + VAddr buffer2_ptr = rp.PopStaticBuffer(&size2); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) called buffer1_size=0x%08X, buffer2_size=0x%08X, flag=0x%08X," - "size1=0x%08X, buffer1_ptr=0x%08X, size2=0x%08X, buffer2_ptr=0x%08X", + "size1=0x%08zX, buffer1_ptr=0x%08X, size2=0x%08zX, buffer2_ptr=0x%08X", buffer1_size, buffer2_size, flag, size1, buffer1_ptr, size2, buffer2_ptr); } void AppletUtility(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x4B, 3, 2); // 0x004B00C2 // These are from 3dbrew - I'm not really sure what they're used for. - u32 command = cmd_buff[1]; - u32 buffer1_size = cmd_buff[2]; - u32 buffer2_size = cmd_buff[3]; - u32 buffer1_addr = cmd_buff[5]; - u32 buffer2_addr = cmd_buff[65]; + u32 utility_command = rp.Pop<u32>(); + u32 input_size = rp.Pop<u32>(); + u32 output_size = rp.Pop<u32>(); + VAddr input_addr = rp.PopStaticBuffer(); + + VAddr output_addr = rp.PeekStaticBuffer(0); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, - "(STUBBED) called command=0x%08X, buffer1_size=0x%08X, buffer2_size=0x%08X, " - "buffer1_addr=0x%08X, buffer2_addr=0x%08X", - command, buffer1_size, buffer2_size, buffer1_addr, buffer2_addr); + "(STUBBED) called command=0x%08X, input_size=0x%08X, output_size=0x%08X, " + "input_addr=0x%08X, output_addr=0x%08X", + utility_command, input_size, output_size, input_addr, output_addr); } void SetAppCpuTimeLimit(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 value = cmd_buff[1]; - cpu_percent = cmd_buff[2]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x4F, 2, 0); // 0x4F0080 + u32 value = rp.Pop<u32>(); + cpu_percent = rp.Pop<u32>(); if (value != 1) { LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) called cpu_percent=%u, value=%u", cpu_percent, value); } void GetAppCpuTimeLimit(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 value = cmd_buff[1]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x50, 1, 0); // 0x500040 + u32 value = rp.Pop<u32>(); if (value != 1) { LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = cpu_percent; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(cpu_percent); LOG_WARNING(Service_APT, "(STUBBED) called value=%u", value); } void PrepareToStartLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - AppletId applet_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x18, 1, 0); // 0x180040 + AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); auto applet = HLE::Applets::Applet::Get(applet_id); if (applet) { LOG_WARNING(Service_APT, "applet has already been started id=%08X", applet_id); - cmd_buff[1] = RESULT_SUCCESS.raw; + rb.Push(RESULT_SUCCESS); } else { - cmd_buff[1] = HLE::Applets::Applet::Create(applet_id).raw; + rb.Push(HLE::Applets::Applet::Create(applet_id)); } LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id); } void PreloadLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - AppletId applet_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x16, 1, 0); // 0x160040 + AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); auto applet = HLE::Applets::Applet::Get(applet_id); if (applet) { LOG_WARNING(Service_APT, "applet has already been started id=%08X", applet_id); - cmd_buff[1] = RESULT_SUCCESS.raw; + rb.Push(RESULT_SUCCESS); } else { - cmd_buff[1] = HLE::Applets::Applet::Create(applet_id).raw; + rb.Push(HLE::Applets::Applet::Create(applet_id)); } LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id); } void StartLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - AppletId applet_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1E, 2, 4); // 0x1E0084 + AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>()); std::shared_ptr<HLE::Applets::Applet> applet = HLE::Applets::Applet::Get(applet_id); LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id); if (applet == nullptr) { LOG_ERROR(Service_APT, "unknown applet id=%08X", applet_id); - cmd_buff[1] = -1; // TODO(Subv): Find the right error code + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0, false); + rb.Push<u32>(-1); // TODO(Subv): Find the right error code return; } - size_t buffer_size = cmd_buff[2]; - VAddr buffer_addr = cmd_buff[6]; + size_t buffer_size = rp.Pop<u32>(); + Kernel::Handle handle = rp.PopHandle(); + VAddr buffer_addr = rp.PopStaticBuffer(); AppletStartupParameter parameter; - parameter.object = Kernel::g_handle_table.GetGeneric(cmd_buff[4]); + parameter.object = Kernel::g_handle_table.GetGeneric(handle); parameter.buffer.resize(buffer_size); Memory::ReadBlock(buffer_addr, parameter.buffer.data(), parameter.buffer.size()); - cmd_buff[1] = applet->Start(parameter).raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(applet->Start(parameter)); } void CancelLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 exiting = cmd_buff[1] & 0xFF; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3B, 1, 0); // 0x003B0040 + bool exiting = rp.Pop<bool>(); - cmd_buff[1] = 1; // TODO: Find the return code meaning + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push<u32>(1); // TODO: Find the return code meaning - LOG_WARNING(Service_APT, "(STUBBED) called exiting=%u", exiting); + LOG_WARNING(Service_APT, "(STUBBED) called exiting=%d", exiting); } void SetScreenCapPostPermission(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x55, 1, 0); // 0x00550040 - screen_capture_post_permission = static_cast<ScreencapPostPermission>(cmd_buff[1] & 0xF); + screen_capture_post_permission = static_cast<ScreencapPostPermission>(rp.Pop<u32>() & 0xF); - cmd_buff[0] = IPC::MakeHeader(0x55, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) screen_capture_post_permission=%u", screen_capture_post_permission); } void GetScreenCapPostPermission(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x56, 0, 0); // 0x00560000 - cmd_buff[0] = IPC::MakeHeader(0x56, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = static_cast<u32>(screen_capture_post_permission); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(static_cast<u32>(screen_capture_post_permission)); LOG_WARNING(Service_APT, "(STUBBED) screen_capture_post_permission=%u", screen_capture_post_permission); } void GetAppletInfo(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - auto app_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x6, 1, 0); // 0x60040 + auto app_id = static_cast<AppletId>(rp.Pop<u32>()); if (auto applet = HLE::Applets::Applet::Get(app_id)) { // TODO(Subv): Get the title id for the current applet and write it in the response[2-3] - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[4] = static_cast<u32>(Service::FS::MediaType::NAND); - cmd_buff[5] = 1; // Registered - cmd_buff[6] = 1; // Loaded - cmd_buff[7] = 0; // Applet Attributes + IPC::RequestBuilder rb = rp.MakeBuilder(7, 0); + rb.Push(RESULT_SUCCESS); + u64 title_id = 0; + rb.Push(title_id); + rb.Push(static_cast<u32>(Service::FS::MediaType::NAND)); + rb.Push(true); // Registered + rb.Push(true); // Loaded + rb.Push<u32>(0); // Applet Attributes } else { - cmd_buff[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Applet, - ErrorSummary::NotFound, ErrorLevel::Status) - .raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, + ErrorLevel::Status)); } LOG_WARNING(Service_APT, "(stubbed) called appid=%u", app_id); } void GetStartupArgument(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 parameter_size = cmd_buff[1]; - StartupArgumentType startup_argument_type = static_cast<StartupArgumentType>(cmd_buff[2]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x51, 2, 0); // 0x00510080 + u32 parameter_size = rp.Pop<u32>(); + StartupArgumentType startup_argument_type = static_cast<StartupArgumentType>(rp.Pop<u8>()); if (parameter_size >= 0x300) { LOG_ERROR( @@ -460,7 +497,14 @@ void GetStartupArgument(Service::Interface* self) { return; } - u32 addr = cmd_buff[65]; + size_t static_buff_size; + VAddr addr = rp.PeekStaticBuffer(0, &static_buff_size); + if (parameter_size > static_buff_size) + LOG_WARNING( + Service_APT, + "parameter_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)", + parameter_size, static_buff_size); + if (addr && parameter_size) { Memory::ZeroBlock(addr, parameter_size); } @@ -468,8 +512,10 @@ void GetStartupArgument(Service::Interface* self) { LOG_WARNING(Service_APT, "(stubbed) called startup_argument_type=%u , parameter_size=0x%08x", startup_argument_type, parameter_size); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + rb.PushStaticBuffer(addr, parameter_size, 0); } void Wrap(Service::Interface* self) { @@ -554,7 +600,7 @@ void Unwrap(Service::Interface* self) { // Decrypts the ciphertext using AES-CCM auto pdata = HW::AES::DecryptVerifyCCM(cipher, nonce, HW::AES::KeySlotID::APTWrap); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); if (!pdata.empty()) { // Splits the plaintext and put the nonce in between Memory::WriteBlock(output, pdata.data(), nonce_offset); @@ -574,25 +620,25 @@ void Unwrap(Service::Interface* self) { } void CheckNew3DSApp(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x101, 0, 0); // 0x01010000 + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); if (unknown_ns_state_field) { - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); } else { - PTM::CheckNew3DS(self); + PTM::CheckNew3DS(rb); } - cmd_buff[0] = IPC::MakeHeader(0x101, 2, 0); LOG_WARNING(Service_APT, "(STUBBED) called"); } void CheckNew3DS(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x102, 0, 0); // 0x01020000 + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); - PTM::CheckNew3DS(self); + PTM::CheckNew3DS(rb); - cmd_buff[0] = IPC::MakeHeader(0x102, 2, 0); LOG_WARNING(Service_APT, "(STUBBED) called"); } diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 6f13cde27..4ddb1bc90 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -3,6 +3,8 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> +#include <cryptopp/sha.h> #include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" @@ -176,14 +178,29 @@ void SecureInfoGetRegion(Service::Interface* self) { } void GenHashConsoleUnique(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id_salt = cmd_buff[1]; - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0x33646D6F ^ (app_id_salt & 0xFFFFF); // 3dmoo hash - cmd_buff[3] = 0x6F534841 ^ (app_id_salt & 0xFFFFF); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0); + const u32 app_id_salt = rp.Pop<u32>() & 0x000FFFFF; + + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + + std::array<u8, 12> buffer; + const ResultCode result = GetConfigInfoBlock(ConsoleUniqueID2BlockID, 8, 2, buffer.data()); + rb.Push(result); + if (result.IsSuccess()) { + std::memcpy(&buffer[8], &app_id_salt, sizeof(u32)); + std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash; + CryptoPP::SHA256().CalculateDigest(hash.data(), buffer.data(), sizeof(buffer)); + u32 low, high; + memcpy(&low, &hash[hash.size() - 8], sizeof(u32)); + memcpy(&high, &hash[hash.size() - 4], sizeof(u32)); + rb.Push(low); + rb.Push(high); + } else { + rb.Push<u32>(0); + rb.Push<u32>(0); + } - LOG_WARNING(Service_CFG, "(STUBBED) called app_id_salt=0x%X", app_id_salt); + LOG_DEBUG(Service_CFG, "called app_id_salt=0x%X", app_id_salt); } void GetRegionCanadaUSA(Service::Interface* self) { diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index fb3acb507..b19e831fe 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2,10 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <atomic> #include <cmath> +#include <memory> #include "common/logging/log.h" #include "core/core_timing.h" #include "core/frontend/emu_window.h" +#include "core/frontend/input.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/service/hid/hid.h" @@ -44,6 +48,11 @@ constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234; constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104; constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; +static std::atomic<bool> is_device_reload_pending; +static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> + buttons; +static std::unique_ptr<Input::AnalogDevice> circle_pad; + static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions constexpr float TAN30 = 0.577350269f; @@ -74,14 +83,48 @@ static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { return state; } +static void LoadInputDevices() { + std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, + Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END, + buttons.begin(), Input::CreateDevice<Input::ButtonDevice>); + circle_pad = Input::CreateDevice<Input::AnalogDevice>( + Settings::values.analogs[Settings::NativeAnalog::CirclePad]); +} + +static void UnloadInputDevices() { + for (auto& button : buttons) { + button.reset(); + } + circle_pad.reset(); +} + static void UpdatePadCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); - PadState state = VideoCore::g_emu_window->GetPadState(); + if (is_device_reload_pending.exchange(false)) + LoadInputDevices(); + + PadState state; + using namespace Settings::NativeButton; + state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); + state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); + state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); + state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); + state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus()); + state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus()); + state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus()); + state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus()); + state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); + state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); + state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus()); + state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus()); // Get current circle pad position and update circle pad direction - s16 circle_pad_x, circle_pad_y; - std::tie(circle_pad_x, circle_pad_y) = VideoCore::g_emu_window->GetCirclePadState(); + float circle_pad_x_f, circle_pad_y_f; + std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); + constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position + s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS); + s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS); state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex; mem->pad.current_state.hex = state.hex; @@ -313,6 +356,8 @@ void Init() { AddService(new HID_U_Interface); AddService(new HID_SPVR_Interface); + is_device_reload_pending.store(true); + using Kernel::MemoryPermission; shared_mem = SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, @@ -350,6 +395,11 @@ void Shutdown() { event_accelerometer = nullptr; event_gyroscope = nullptr; event_debug_pad = nullptr; + UnloadInputDevices(); +} + +void ReloadInputDevices() { + is_device_reload_pending.store(true); } } // namespace HID diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index c7f4ee138..b505cdcd5 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -39,13 +39,6 @@ struct PadState { BitField<10, 1, u32> x; BitField<11, 1, u32> y; - BitField<14, 1, u32> zl; - BitField<15, 1, u32> zr; - - BitField<24, 1, u32> c_right; - BitField<25, 1, u32> c_left; - BitField<26, 1, u32> c_up; - BitField<27, 1, u32> c_down; BitField<28, 1, u32> circle_right; BitField<29, 1, u32> circle_left; BitField<30, 1, u32> circle_up; @@ -183,33 +176,6 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A); #undef ASSERT_REG_POSITION #endif // !defined(_MSC_VER) -// Pre-defined PadStates for single button presses -const PadState PAD_NONE = {{0}}; -const PadState PAD_A = {{1u << 0}}; -const PadState PAD_B = {{1u << 1}}; -const PadState PAD_SELECT = {{1u << 2}}; -const PadState PAD_START = {{1u << 3}}; -const PadState PAD_RIGHT = {{1u << 4}}; -const PadState PAD_LEFT = {{1u << 5}}; -const PadState PAD_UP = {{1u << 6}}; -const PadState PAD_DOWN = {{1u << 7}}; -const PadState PAD_R = {{1u << 8}}; -const PadState PAD_L = {{1u << 9}}; -const PadState PAD_X = {{1u << 10}}; -const PadState PAD_Y = {{1u << 11}}; - -const PadState PAD_ZL = {{1u << 14}}; -const PadState PAD_ZR = {{1u << 15}}; - -const PadState PAD_C_RIGHT = {{1u << 24}}; -const PadState PAD_C_LEFT = {{1u << 25}}; -const PadState PAD_C_UP = {{1u << 26}}; -const PadState PAD_C_DOWN = {{1u << 27}}; -const PadState PAD_CIRCLE_RIGHT = {{1u << 28}}; -const PadState PAD_CIRCLE_LEFT = {{1u << 29}}; -const PadState PAD_CIRCLE_UP = {{1u << 30}}; -const PadState PAD_CIRCLE_DOWN = {{1u << 31}}; - /** * HID::GetIPCHandles service function * Inputs: @@ -297,5 +263,8 @@ void Init(); /// Shutdown HID service void Shutdown(); + +/// Reload input devices. Used when input configuration changed +void ReloadInputDevices(); } } diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 8ff808fd9..e373ed47a 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -92,8 +92,7 @@ void GetSoftwareClosedFlag(Service::Interface* self) { LOG_WARNING(Service_PTM, "(STUBBED) called"); } -void CheckNew3DS(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +void CheckNew3DS(IPC::RequestBuilder& rb) { const bool is_new_3ds = Settings::values.is_new_3ds; if (is_new_3ds) { @@ -101,12 +100,17 @@ void CheckNew3DS(Service::Interface* self) { "settings. Citra does not fully support New 3DS emulation yet!"); } - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = is_new_3ds ? 1 : 0; + rb.Push(RESULT_SUCCESS); + rb.Push(is_new_3ds); LOG_WARNING(Service_PTM, "(STUBBED) called isNew3DS = 0x%08x", static_cast<u32>(is_new_3ds)); } +void CheckNew3DS(Service::Interface* self) { + IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x40A, 0, 0); // 0x040A0000 + CheckNew3DS(rb); +} + void Init() { AddService(new PTM_Gets); AddService(new PTM_Play); @@ -134,9 +138,9 @@ void Init() { ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!"); FileSys::Path gamecoin_path("/gamecoin.dat"); + Service::FS::CreateFileInArchive(*archive_result, gamecoin_path, sizeof(GameCoin)); FileSys::Mode open_mode = {}; open_mode.write_flag.Assign(1); - open_mode.create_flag.Assign(1); // Open the file and write the default gamecoin information auto gamecoin_result = Service::FS::OpenFileFromArchive(*archive_result, gamecoin_path, open_mode); diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index a1a628012..683fb445b 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -5,6 +5,7 @@ #pragma once #include "common/common_types.h" +#include "core/hle/ipc_helpers.h" namespace Service { @@ -97,6 +98,7 @@ void GetSoftwareClosedFlag(Interface* self); * 2: u8 output: 0 = Old3DS, 1 = New3DS. */ void CheckNew3DS(Interface* self); +void CheckNew3DS(IPC::RequestBuilder& rb); /// Initialize the PTM service void Init(); diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index dcc5c3c90..530614e6f 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -362,18 +362,18 @@ static void Socket(Interface* self) { return; } - u32 socket_handle = static_cast<u32>(::socket(domain, type, protocol)); + u32 ret = static_cast<u32>(::socket(domain, type, protocol)); - if ((s32)socket_handle != SOCKET_ERROR_VALUE) - open_sockets[socket_handle] = {socket_handle, true}; + if ((s32)ret != SOCKET_ERROR_VALUE) + open_sockets[ret] = {ret, true}; int result = 0; - if ((s32)socket_handle == SOCKET_ERROR_VALUE) - result = TranslateError(GET_ERRNO); + if ((s32)ret == SOCKET_ERROR_VALUE) + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(2, 2, 0); cmd_buffer[1] = result; - cmd_buffer[2] = socket_handle; + cmd_buffer[2] = ret; } static void Bind(Interface* self) { @@ -393,15 +393,15 @@ static void Bind(Interface* self) { sockaddr sock_addr = CTRSockAddr::ToPlatform(ctr_sock_addr); - int res = ::bind(socket_handle, &sock_addr, std::max<u32>(sizeof(sock_addr), len)); + int ret = ::bind(socket_handle, &sock_addr, std::max<u32>(sizeof(sock_addr), len)); int result = 0; - if (res != 0) - result = TranslateError(GET_ERRNO); + if (ret != 0) + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(5, 2, 0); cmd_buffer[1] = result; - cmd_buffer[2] = res; + cmd_buffer[2] = ret; } static void Fcntl(Interface* self) { @@ -426,8 +426,7 @@ static void Fcntl(Interface* self) { #else int ret = ::fcntl(socket_handle, F_GETFL, 0); if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } posix_ret = 0; @@ -439,8 +438,7 @@ static void Fcntl(Interface* self) { unsigned long tmp = (ctr_arg & 4 /* O_NONBLOCK */) ? 1 : 0; int ret = ioctlsocket(socket_handle, FIONBIO, &tmp); if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } auto iter = open_sockets.find(socket_handle); @@ -449,8 +447,7 @@ static void Fcntl(Interface* self) { #else int flags = ::fcntl(socket_handle, F_GETFL, 0); if (flags == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } @@ -460,15 +457,13 @@ static void Fcntl(Interface* self) { int ret = ::fcntl(socket_handle, F_SETFL, flags); if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } #endif } else { LOG_ERROR(Service_SOC, "Unsupported command (%d) in fcntl call", ctr_cmd); - result = TranslateError(EINVAL); // TODO: Find the correct error - posix_ret = -1; + posix_ret = TranslateError(EINVAL); // TODO: Find the correct error return; } } @@ -481,7 +476,7 @@ static void Listen(Interface* self) { int ret = ::listen(socket_handle, backlog); int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(3, 2, 0); cmd_buffer[1] = result; @@ -504,7 +499,7 @@ static void Accept(Interface* self) { int result = 0; if ((s32)ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); } else { CTRSockAddr ctr_addr = CTRSockAddr::FromPlatform(addr); Memory::WriteBlock(cmd_buffer[0x104 >> 2], &ctr_addr, sizeof(ctr_addr)); @@ -545,7 +540,7 @@ static void Close(Interface* self) { int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -589,7 +584,7 @@ static void SendTo(Interface* self) { int result = 0; if (ret == SOCKET_ERROR_VALUE) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -638,7 +633,7 @@ static void RecvFrom(Interface* self) { int result = 0; int total_received = ret; if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); total_received = 0; } else { // Write only the data we received to avoid overwriting parts of the buffer with zeros @@ -673,7 +668,7 @@ static void Poll(Interface* self) { std::vector<pollfd> platform_pollfd(nfds); std::transform(ctr_fds.begin(), ctr_fds.end(), platform_pollfd.begin(), CTRPollFD::ToPlatform); - const int ret = ::poll(platform_pollfd.data(), nfds, timeout); + int ret = ::poll(platform_pollfd.data(), nfds, timeout); // Now update the output pollfd structure std::transform(platform_pollfd.begin(), platform_pollfd.end(), ctr_fds.begin(), @@ -683,7 +678,7 @@ static void Poll(Interface* self) { int result = 0; if (ret == SOCKET_ERROR_VALUE) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[1] = result; cmd_buffer[2] = ret; @@ -710,7 +705,7 @@ static void GetSockName(Interface* self) { int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -724,7 +719,7 @@ static void Shutdown(Interface* self) { int ret = ::shutdown(socket_handle, how); int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; } @@ -750,7 +745,7 @@ static void GetPeerName(Interface* self) { int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -777,7 +772,7 @@ static void Connect(Interface* self) { int ret = ::connect(socket_handle, &input_addr, sizeof(input_addr)); int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(6, 2, 0); cmd_buffer[1] = result; @@ -815,7 +810,7 @@ static void GetSockOpt(Interface* self) { int optname = TranslateSockOpt(cmd_buffer[3]); socklen_t optlen = (socklen_t)cmd_buffer[4]; - int ret = -1; + int ret = 0; int err = 0; if (optname < 0) { @@ -830,9 +825,8 @@ static void GetSockOpt(Interface* self) { // >> 2 = convert to u32 offset instead of byte offset (cmd_buffer = u32*) char* optval = reinterpret_cast<char*>(Memory::GetPointer(cmd_buffer[0x104 >> 2])); - ret = ::getsockopt(socket_handle, level, optname, optval, &optlen); - err = 0; - if (ret == SOCKET_ERROR_VALUE) { + err = ::getsockopt(socket_handle, level, optname, optval, &optlen); + if (err == SOCKET_ERROR_VALUE) { err = TranslateError(GET_ERRNO); } } @@ -849,7 +843,7 @@ static void SetSockOpt(Interface* self) { u32 level = cmd_buffer[2]; int optname = TranslateSockOpt(cmd_buffer[3]); - int ret = -1; + int ret = 0; int err = 0; if (optname < 0) { @@ -862,9 +856,8 @@ static void SetSockOpt(Interface* self) { socklen_t optlen = static_cast<socklen_t>(cmd_buffer[4]); const char* optval = reinterpret_cast<const char*>(Memory::GetPointer(cmd_buffer[8])); - ret = static_cast<u32>(::setsockopt(socket_handle, level, optname, optval, optlen)); - err = 0; - if (ret == SOCKET_ERROR_VALUE) { + err = static_cast<u32>(::setsockopt(socket_handle, level, optname, optval, optlen)); + if (err == SOCKET_ERROR_VALUE) { err = TranslateError(GET_ERRNO); } } diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index 907d9c8fa..c0837d49d 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -189,11 +189,9 @@ static void SetSpacialDithering(Interface* self) { * 2 : u8, 0 = Disabled, 1 = Enabled */ static void GetSpacialDithering(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[0] = IPC::MakeHeader(0xA, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = spacial_dithering_enabled; + IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0xA, 2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(spacial_dithering_enabled != 0); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp index d0d92487d..5978ccdd4 100644 --- a/src/core/hle/shared_page.cpp +++ b/src/core/hle/shared_page.cpp @@ -6,6 +6,7 @@ #include <cstring> #include <ctime> #include "core/core_timing.h" +#include "core/hle/service/ptm/ptm.h" #include "core/hle/shared_page.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -73,6 +74,12 @@ void Init() { // Some games wait until this value becomes 0x1, before asking running_hw shared_page.unknown_value = 0x1; + // Set to a completely full battery + shared_page.battery_state.charge_level.Assign( + static_cast<u8>(Service::PTM::ChargeLevels::CompletelyFull)); + shared_page.battery_state.is_adapter_connected.Assign(1); + shared_page.battery_state.is_charging.Assign(1); + update_time_event = CoreTiming::RegisterEvent("SharedPage::UpdateTimeCallback", UpdateTimeCallback); CoreTiming::ScheduleEvent(0, update_time_event); diff --git a/src/core/hle/shared_page.h b/src/core/hle/shared_page.h index 106e47efc..864695ae1 100644 --- a/src/core/hle/shared_page.h +++ b/src/core/hle/shared_page.h @@ -10,6 +10,7 @@ * write access, according to 3dbrew; this is not emulated) */ +#include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -29,6 +30,13 @@ struct DateTime { }; static_assert(sizeof(DateTime) == 0x20, "Datetime size is wrong"); +union BatteryState { + u8 raw; + BitField<0, 1, u8> is_adapter_connected; + BitField<1, 1, u8> is_charging; + BitField<2, 3, u8> charge_level; +}; + struct SharedPageDef { // Most of these names are taken from the 3dbrew page linked above. u32_le date_time_counter; // 0 @@ -44,7 +52,7 @@ struct SharedPageDef { INSERT_PADDING_BYTES(0x80 - 0x68); // 68 float_le sliderstate_3d; // 80 u8 ledstate_3d; // 84 - INSERT_PADDING_BYTES(1); // 85 + BatteryState battery_state; // 85 u8 unknown_value; // 86 INSERT_PADDING_BYTES(0xA0 - 0x87); // 87 u64_le menu_title_id; // A0 diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 1baa80671..2db823c61 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -467,8 +467,8 @@ static void Break(u8 break_reason) { } /// Used to output a message on a debug hardware unit - does nothing on a retail unit -static void OutputDebugString(const char* string) { - LOG_DEBUG(Debug_Emulated, "%s", string); +static void OutputDebugString(const char* string, int len) { + LOG_DEBUG(Debug_Emulated, "%.*s", len, string); } /// Get resource limit @@ -556,11 +556,21 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent break; } - if (processor_id == THREADPROCESSORID_1 || processor_id == THREADPROCESSORID_ALL || - (processor_id == THREADPROCESSORID_DEFAULT && - Kernel::g_current_process->ideal_processor == THREADPROCESSORID_1)) { - LOG_WARNING(Kernel_SVC, - "Newly created thread is allowed to be run in the SysCore, unimplemented."); + if (processor_id == THREADPROCESSORID_ALL) { + LOG_INFO(Kernel_SVC, + "Newly created thread is allowed to be run in any Core, unimplemented."); + } + + if (processor_id == THREADPROCESSORID_DEFAULT && + Kernel::g_current_process->ideal_processor == THREADPROCESSORID_1) { + LOG_WARNING( + Kernel_SVC, + "Newly created thread is allowed to be run in the SysCore (Core1), unimplemented."); + } + + if (processor_id == THREADPROCESSORID_1) { + LOG_ERROR(Kernel_SVC, + "Newly created thread must run in the SysCore (Core1), unimplemented."); } CASCADE_RESULT(SharedPtr<Thread> thread, Kernel::Thread::Create(name, entry_point, priority, diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 147bf8591..be719d74c 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -139,7 +139,7 @@ std::unique_ptr<AppLoader> GetLoader(const std::string& filename) { type = filename_type; } - LOG_INFO(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type)); + LOG_DEBUG(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type)); return GetFileLoader(std::move(file), type, filename_filename, filename); } diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 98b8259d9..1a4e3efa8 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <cinttypes> #include <cstring> #include <memory> #include "common/logging/log.h" @@ -253,7 +254,7 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... if (MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) { - LOG_WARNING(Loader, "Only loading the first (bootable) NCCH within the NCSD file!"); + LOG_DEBUG(Loader, "Only loading the first (bootable) NCCH within the NCSD file!"); ncch_offset = 0x4000; file.Seek(ncch_offset, SEEK_SET); file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); @@ -277,8 +278,8 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { priority = exheader_header.arm11_system_local_caps.priority; resource_limit_category = exheader_header.arm11_system_local_caps.resource_limit_category; - LOG_INFO(Loader, "Name: %s", exheader_header.codeset_info.name); - LOG_INFO(Loader, "Program ID: %016llX", ncch_header.program_id); + LOG_DEBUG(Loader, "Name: %s", exheader_header.codeset_info.name); + LOG_DEBUG(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); LOG_DEBUG(Loader, "Code compressed: %s", is_compressed ? "yes" : "no"); LOG_DEBUG(Loader, "Entry point: 0x%08X", entry_point); LOG_DEBUG(Loader, "Code size: 0x%08X", code_size); @@ -336,6 +337,8 @@ ResultStatus AppLoader_NCCH::Load() { if (result != ResultStatus::Success) return result; + LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); + is_loaded = true; // Set state to loaded result = LoadExec(); // Load the executable into memory for booting diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 3a32b70aa..a598f9f2f 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -4,6 +4,7 @@ #include "audio_core/audio_core.h" #include "core/gdbstub/gdbstub.h" +#include "core/hle/service/hid/hid.h" #include "settings.h" #include "video_core/video_core.h" @@ -29,6 +30,8 @@ void Apply() { AudioCore::SelectSink(values.sink_id); AudioCore::EnableStretching(values.enable_audio_stretching); + + Service::HID::ReloadInputDevices(); } } // namespace diff --git a/src/core/settings.h b/src/core/settings.h index b6c75531f..03c64c94c 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -15,67 +15,70 @@ enum class LayoutOption { Default, SingleScreen, LargeScreen, - Custom, }; -namespace NativeInput { - +namespace NativeButton { enum Values { - // directly mapped keys A, B, X, Y, + Up, + Down, + Left, + Right, L, R, + Start, + Select, + ZL, ZR, - START, - SELECT, - HOME, - DUP, - DDOWN, - DLEFT, - DRIGHT, - CUP, - CDOWN, - CLEFT, - CRIGHT, - - // indirectly mapped keys - CIRCLE_UP, - CIRCLE_DOWN, - CIRCLE_LEFT, - CIRCLE_RIGHT, - CIRCLE_MODIFIER, - - NUM_INPUTS + + Home, + + NumButtons, }; -static const std::array<const char*, NUM_INPUTS> Mapping = {{ - // directly mapped keys - "pad_a", "pad_b", "pad_x", "pad_y", "pad_l", "pad_r", "pad_zl", "pad_zr", "pad_start", - "pad_select", "pad_home", "pad_dup", "pad_ddown", "pad_dleft", "pad_dright", "pad_cup", - "pad_cdown", "pad_cleft", "pad_cright", +constexpr int BUTTON_HID_BEGIN = A; +constexpr int BUTTON_IR_BEGIN = ZL; +constexpr int BUTTON_NS_BEGIN = Home; + +constexpr int BUTTON_HID_END = BUTTON_IR_BEGIN; +constexpr int BUTTON_IR_END = BUTTON_NS_BEGIN; +constexpr int BUTTON_NS_END = NumButtons; + +constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN; +constexpr int NUM_BUTTONS_IR = BUTTON_IR_END - BUTTON_IR_BEGIN; +constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN; - // indirectly mapped keys - "pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right", - "pad_circle_modifier", +static const std::array<const char*, NumButtons> mapping = {{ + "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_zl", + "button_zr", "button_home", }}; -static const std::array<Values, NUM_INPUTS> All = {{ - A, B, X, Y, L, R, ZL, ZR, - START, SELECT, HOME, DUP, DDOWN, DLEFT, DRIGHT, CUP, - CDOWN, CLEFT, CRIGHT, CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT, CIRCLE_MODIFIER, +} // namespace NativeButton + +namespace NativeAnalog { +enum Values { + CirclePad, + CStick, + + NumAnalogs, +}; + +static const std::array<const char*, NumAnalogs> mapping = {{ + "circle_pad", "c_stick", }}; -} +} // namespace NumAnalog struct Values { // CheckNew3DS bool is_new_3ds; // Controls - std::array<int, NativeInput::NUM_INPUTS> input_mappings; - float pad_circle_modifier_scale; + std::array<std::string, NativeButton::NumButtons> buttons; + std::array<std::string, NativeAnalog::NumAnalogs> analogs; // Core bool use_cpu_jit; @@ -95,6 +98,15 @@ struct Values { LayoutOption layout_option; bool swap_screen; + bool custom_layout; + u16 custom_top_left; + u16 custom_top_top; + u16 custom_top_right; + u16 custom_top_bottom; + u16 custom_bottom_left; + u16 custom_bottom_top; + u16 custom_bottom_right; + u16 custom_bottom_bottom; float bg_red; float bg_green; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt new file mode 100644 index 000000000..cfe5caaa3 --- /dev/null +++ b/src/input_common/CMakeLists.txt @@ -0,0 +1,27 @@ +set(SRCS + analog_from_button.cpp + keyboard.cpp + main.cpp + ) + +set(HEADERS + analog_from_button.h + keyboard.h + main.h + ) + +if(SDL2_FOUND) + set(SRCS ${SRCS} sdl/sdl.cpp) + set(HEADERS ${HEADERS} sdl/sdl.h) + include_directories(${SDL2_INCLUDE_DIR}) +endif() + +create_directory_groups(${SRCS} ${HEADERS}) + +add_library(input_common STATIC ${SRCS} ${HEADERS}) +target_link_libraries(input_common common core) + +if(SDL2_FOUND) + target_link_libraries(input_common ${SDL2_LIBRARY}) + set_property(TARGET input_common APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SDL2) +endif() diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp new file mode 100755 index 000000000..e1a260762 --- /dev/null +++ b/src/input_common/analog_from_button.cpp @@ -0,0 +1,58 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "input_common/analog_from_button.h" + +namespace InputCommon { + +class Analog final : public Input::AnalogDevice { +public: + using Button = std::unique_ptr<Input::ButtonDevice>; + + Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_, + float modifier_scale_) + : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), + right(std::move(right_)), modifier(std::move(modifier_)), + modifier_scale(modifier_scale_) {} + + std::tuple<float, float> GetStatus() const override { + constexpr float SQRT_HALF = 0.707106781f; + int x = 0, y = 0; + + if (right->GetStatus()) + ++x; + if (left->GetStatus()) + --x; + if (up->GetStatus()) + ++y; + if (down->GetStatus()) + --y; + + float coef = modifier->GetStatus() ? modifier_scale : 1.0f; + return std::make_tuple(x * coef * (y == 0 ? 1.0f : SQRT_HALF), + y * coef * (x == 0 ? 1.0f : SQRT_HALF)); + } + +private: + Button up; + Button down; + Button left; + Button right; + Button modifier; + float modifier_scale; +}; + +std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) { + const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); + auto up = Input::CreateDevice<Input::ButtonDevice>(params.Get("up", null_engine)); + auto down = Input::CreateDevice<Input::ButtonDevice>(params.Get("down", null_engine)); + auto left = Input::CreateDevice<Input::ButtonDevice>(params.Get("left", null_engine)); + auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine)); + auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine)); + auto modifier_scale = params.Get("modifier_scale", 0.5f); + return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left), + std::move(right), std::move(modifier), modifier_scale); +} + +} // namespace InputCommon diff --git a/src/input_common/analog_from_button.h b/src/input_common/analog_from_button.h new file mode 100755 index 000000000..bbd583dd9 --- /dev/null +++ b/src/input_common/analog_from_button.h @@ -0,0 +1,31 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/frontend/input.h" + +namespace InputCommon { + +/** + * An analog device factory that takes direction button devices and combines them into a analog + * device. + */ +class AnalogFromButton final : public Input::Factory<Input::AnalogDevice> { +public: + /** + * Creates an analog device from direction button devices + * @param params contains parameters for creating the device: + * - "up": a serialized ParamPackage for creating a button device for up direction + * - "down": a serialized ParamPackage for creating a button device for down direction + * - "left": a serialized ParamPackage for creating a button device for left direction + * - "right": a serialized ParamPackage for creating a button device for right direction + * - "modifier": a serialized ParamPackage for creating a button device as the modifier + * - "modifier_scale": a float for the multiplier the modifier gives to the position + */ + std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; +}; + +} // namespace InputCommon diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp new file mode 100644 index 000000000..0f0d10f23 --- /dev/null +++ b/src/input_common/keyboard.cpp @@ -0,0 +1,93 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <atomic> +#include <list> +#include <mutex> +#include "input_common/keyboard.h" + +namespace InputCommon { + +class KeyButton final : public Input::ButtonDevice { +public: + explicit KeyButton(std::shared_ptr<KeyButtonList> key_button_list_) + : key_button_list(key_button_list_) {} + + ~KeyButton(); + + bool GetStatus() const override { + return status.load(); + } + + friend class KeyButtonList; + +private: + std::shared_ptr<KeyButtonList> key_button_list; + std::atomic<bool> status{false}; +}; + +struct KeyButtonPair { + int key_code; + KeyButton* key_button; +}; + +class KeyButtonList { +public: + void AddKeyButton(int key_code, KeyButton* key_button) { + std::lock_guard<std::mutex> guard(mutex); + list.push_back(KeyButtonPair{key_code, key_button}); + } + + void RemoveKeyButton(const KeyButton* key_button) { + std::lock_guard<std::mutex> guard(mutex); + list.remove_if( + [key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; }); + } + + void ChangeKeyStatus(int key_code, bool pressed) { + std::lock_guard<std::mutex> guard(mutex); + for (const KeyButtonPair& pair : list) { + if (pair.key_code == key_code) + pair.key_button->status.store(pressed); + } + } + + void ChangeAllKeyStatus(bool pressed) { + std::lock_guard<std::mutex> guard(mutex); + for (const KeyButtonPair& pair : list) { + pair.key_button->status.store(pressed); + } + } + +private: + std::mutex mutex; + std::list<KeyButtonPair> list; +}; + +Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {} + +KeyButton::~KeyButton() { + key_button_list->RemoveKeyButton(this); +} + +std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) { + int key_code = params.Get("code", 0); + std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list); + key_button_list->AddKeyButton(key_code, button.get()); + return std::move(button); +} + +void Keyboard::PressKey(int key_code) { + key_button_list->ChangeKeyStatus(key_code, true); +} + +void Keyboard::ReleaseKey(int key_code) { + key_button_list->ChangeKeyStatus(key_code, false); +} + +void Keyboard::ReleaseAllKeys() { + key_button_list->ChangeAllKeyStatus(false); +} + +} // namespace InputCommon diff --git a/src/input_common/keyboard.h b/src/input_common/keyboard.h new file mode 100644 index 000000000..861950472 --- /dev/null +++ b/src/input_common/keyboard.h @@ -0,0 +1,47 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/frontend/input.h" + +namespace InputCommon { + +class KeyButtonList; + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class Keyboard final : public Input::Factory<Input::ButtonDevice> { +public: + Keyboard(); + + /** + * Creates a button device from a keyboard key + * @param params contains parameters for creating the device: + * - "code": the code of the key to bind with the button + */ + std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; + + /** + * Sets the status of all buttons bound with the key to pressed + * @param key_code the code of the key to press + */ + void PressKey(int key_code); + + /** + * Sets the status of all buttons bound with the key to released + * @param key_code the code of the key to release + */ + void ReleaseKey(int key_code); + + void ReleaseAllKeys(); + +private: + std::shared_ptr<KeyButtonList> key_button_list; +}; + +} // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp new file mode 100644 index 000000000..699f41e6b --- /dev/null +++ b/src/input_common/main.cpp @@ -0,0 +1,63 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include "common/param_package.h" +#include "input_common/analog_from_button.h" +#include "input_common/keyboard.h" +#include "input_common/main.h" +#ifdef HAVE_SDL2 +#include "input_common/sdl/sdl.h" +#endif + +namespace InputCommon { + +static std::shared_ptr<Keyboard> keyboard; + +void Init() { + keyboard = std::make_shared<InputCommon::Keyboard>(); + Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); + Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", + std::make_shared<InputCommon::AnalogFromButton>()); +#ifdef HAVE_SDL2 + SDL::Init(); +#endif +} + +void Shutdown() { + Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); + keyboard.reset(); + Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); + +#ifdef HAVE_SDL2 + SDL::Shutdown(); +#endif +} + +Keyboard* GetKeyboard() { + return keyboard.get(); +} + +std::string GenerateKeyboardParam(int key_code) { + Common::ParamPackage param{ + {"engine", "keyboard"}, {"code", std::to_string(key_code)}, + }; + return param.Serialize(); +} + +std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, + int key_modifier, float modifier_scale) { + Common::ParamPackage circle_pad_param{ + {"engine", "analog_from_button"}, + {"up", GenerateKeyboardParam(key_up)}, + {"down", GenerateKeyboardParam(key_down)}, + {"left", GenerateKeyboardParam(key_left)}, + {"right", GenerateKeyboardParam(key_right)}, + {"modifier", GenerateKeyboardParam(key_modifier)}, + {"modifier_scale", std::to_string(modifier_scale)}, + }; + return circle_pad_param.Serialize(); +} + +} // namespace InputCommon diff --git a/src/input_common/main.h b/src/input_common/main.h new file mode 100644 index 000000000..140bbd014 --- /dev/null +++ b/src/input_common/main.h @@ -0,0 +1,29 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> + +namespace InputCommon { + +/// Initializes and registers all built-in input device factories. +void Init(); + +/// Unresisters all build-in input device factories and shut them down. +void Shutdown(); + +class Keyboard; + +/// Gets the keyboard button device factory. +Keyboard* GetKeyboard(); + +/// Generates a serialized param package for creating a keyboard button device +std::string GenerateKeyboardParam(int key_code); + +/// Generates a serialized param package for creating an analog device taking input from keyboard +std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, + int key_modifier, float modifier_scale); + +} // namespace InputCommon diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp new file mode 100644 index 000000000..ae0206909 --- /dev/null +++ b/src/input_common/sdl/sdl.cpp @@ -0,0 +1,202 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cmath> +#include <memory> +#include <string> +#include <tuple> +#include <unordered_map> +#include <SDL.h> +#include "common/math_util.h" +#include "input_common/sdl/sdl.h" + +namespace InputCommon { + +namespace SDL { + +class SDLJoystick; +class SDLButtonFactory; +class SDLAnalogFactory; +static std::unordered_map<int, std::weak_ptr<SDLJoystick>> joystick_list; +static std::shared_ptr<SDLButtonFactory> button_factory; +static std::shared_ptr<SDLAnalogFactory> analog_factory; + +static bool initialized = false; + +class SDLJoystick { +public: + explicit SDLJoystick(int joystick_index) + : joystick{SDL_JoystickOpen(joystick_index), SDL_JoystickClose} { + if (!joystick) { + LOG_ERROR(Input, "failed to open joystick %d", joystick_index); + } + } + + bool GetButton(int button) const { + if (!joystick) + return {}; + SDL_JoystickUpdate(); + return SDL_JoystickGetButton(joystick.get(), button) == 1; + } + + std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { + if (!joystick) + return {}; + SDL_JoystickUpdate(); + float x = SDL_JoystickGetAxis(joystick.get(), axis_x) / 32767.0f; + float y = SDL_JoystickGetAxis(joystick.get(), axis_y) / 32767.0f; + y = -y; // 3DS uses an y-axis inverse from SDL + + // Make sure the coordinates are in the unit circle, + // otherwise normalize it. + float r = x * x + y * y; + if (r > 1.0f) { + r = std::sqrt(r); + x /= r; + y /= r; + } + + return std::make_tuple(x, y); + } + + bool GetHatDirection(int hat, Uint8 direction) const { + return (SDL_JoystickGetHat(joystick.get(), hat) & direction) != 0; + } + +private: + std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> joystick; +}; + +class SDLButton final : public Input::ButtonDevice { +public: + explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_) + : joystick(joystick_), button(button_) {} + + bool GetStatus() const override { + return joystick->GetButton(button); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int button; +}; + +class SDLDirectionButton final : public Input::ButtonDevice { +public: + explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_) + : joystick(joystick_), hat(hat_), direction(direction_) {} + + bool GetStatus() const override { + return joystick->GetHatDirection(hat, direction); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int hat; + Uint8 direction; +}; + +class SDLAnalog final : public Input::AnalogDevice { +public: + SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_) + : joystick(joystick_), axis_x(axis_x_), axis_y(axis_y_) {} + + std::tuple<float, float> GetStatus() const override { + return joystick->GetAnalog(axis_x, axis_y); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int axis_x; + int axis_y; +}; + +static std::shared_ptr<SDLJoystick> GetJoystick(int joystick_index) { + std::shared_ptr<SDLJoystick> joystick = joystick_list[joystick_index].lock(); + if (!joystick) { + joystick = std::make_shared<SDLJoystick>(joystick_index); + joystick_list[joystick_index] = joystick; + } + return joystick; +} + +/// A button device factory that creates button devices from SDL joystick +class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> { +public: + /** + * Creates a button device from a joystick button + * @param params contains parameters for creating the device: + * - "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 + * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", + * "down", "left" or "right" + */ + std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override { + const int joystick_index = params.Get("joystick", 0); + + if (params.Has("hat")) { + const int hat = params.Get("hat", 0); + const std::string direction_name = params.Get("direction", ""); + Uint8 direction; + if (direction_name == "up") { + direction = SDL_HAT_UP; + } else if (direction_name == "down") { + direction = SDL_HAT_DOWN; + } else if (direction_name == "left") { + direction = SDL_HAT_LEFT; + } else if (direction_name == "right") { + direction = SDL_HAT_RIGHT; + } else { + direction = 0; + } + return std::make_unique<SDLDirectionButton>(GetJoystick(joystick_index), hat, + direction); + } + + const int button = params.Get("button", 0); + return std::make_unique<SDLButton>(GetJoystick(joystick_index), button); + } +}; + +/// An analog device factory that creates analog devices from SDL joystick +class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> { +public: + /** + * Creates analog device from joystick axes + * @param params contains parameters for creating the device: + * - "joystick": the index of the joystick to bind + * - "axis_x": the index of the axis to be bind as x-axis + * - "axis_y": the index of the axis to be bind as y-axis + */ + std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override { + const int joystick_index = params.Get("joystick", 0); + const int axis_x = params.Get("axis_x", 0); + const int axis_y = params.Get("axis_y", 1); + return std::make_unique<SDLAnalog>(GetJoystick(joystick_index), axis_x, axis_y); + } +}; + +void Init() { + if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { + LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: %s", SDL_GetError()); + } else { + using namespace Input; + RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>()); + RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>()); + initialized = true; + } +} + +void Shutdown() { + if (initialized) { + using namespace Input; + UnregisterFactory<ButtonDevice>("sdl"); + UnregisterFactory<AnalogDevice>("sdl"); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } +} + +} // namespace SDL +} // namespace InputCommon diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h new file mode 100644 index 000000000..3e72debcc --- /dev/null +++ b/src/input_common/sdl/sdl.h @@ -0,0 +1,19 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/frontend/input.h" + +namespace InputCommon { +namespace SDL { + +/// Initializes and registers SDL device factories +void Init(); + +/// Unresisters SDL device factories and shut them down. +void Shutdown(); + +} // namespace SDL +} // namespace InputCommon diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index b47156ca4..d1144ba77 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,6 +1,7 @@ set(SRCS glad.cpp tests.cpp + common/param_package.cpp core/file_sys/path_parser.cpp ) diff --git a/src/tests/common/param_package.cpp b/src/tests/common/param_package.cpp new file mode 100644 index 000000000..efec2cc86 --- /dev/null +++ b/src/tests/common/param_package.cpp @@ -0,0 +1,25 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <catch.hpp> +#include <math.h> +#include "common/param_package.h" + +namespace Common { + +TEST_CASE("ParamPackage", "[common]") { + ParamPackage original{ + {"abc", "xyz"}, {"def", "42"}, {"jkl", "$$:1:$2$,3"}, + }; + original.Set("ghi", 3.14f); + ParamPackage copy(original.Serialize()); + REQUIRE(copy.Get("abc", "") == "xyz"); + REQUIRE(copy.Get("def", 0) == 42); + REQUIRE(std::abs(copy.Get("ghi", 0.0f) - 3.14f) < 0.01f); + REQUIRE(copy.Get("jkl", "") == "$$:1:$2$,3"); + REQUIRE(copy.Get("mno", "uvw") == "uvw"); + REQUIRE(copy.Get("abc", 42) == 42); +} + +} // namespace Common diff --git a/src/video_core/regs_framebuffer.h b/src/video_core/regs_framebuffer.h index 366782080..9ddc79243 100644 --- a/src/video_core/regs_framebuffer.h +++ b/src/video_core/regs_framebuffer.h @@ -89,8 +89,8 @@ struct FramebufferRegs { }; union { - BitField<0, 8, BlendEquation> blend_equation_rgb; - BitField<8, 8, BlendEquation> blend_equation_a; + BitField<0, 3, BlendEquation> blend_equation_rgb; + BitField<8, 3, BlendEquation> blend_equation_a; BitField<16, 4, BlendFactor> factor_source_rgb; BitField<20, 4, BlendFactor> factor_dest_rgb; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 0818a87b3..456443e86 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -17,6 +17,7 @@ #include "common/vector_math.h" #include "core/frontend/emu_window.h" #include "core/memory.h" +#include "core/settings.h" #include "video_core/pica_state.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_state.h" |