diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1e12451607ec97bea6a36065d8dd6016da68c562..a45439481c828da106938d3c42a41efa1f4f95cc 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 ecb5d2dfe7a8282d9f463e14d0f3ccc7f4a688e7..47231ba719a09749af922f2aec5b365153a3337f 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 fac1c9a0e571ad7c4291bd03c679411edc51df1c..ef1229912d49dcd7792097912be42b3274ad3a6e 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);
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index 435ba6f001de3e5e2ae2ae5fe79e0c2c6c8fa121..af9f7aa2a7e32fbea6ef804313d5253f38f48444 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
diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp
index 00d00905a6fcc3a83e7c40e94a330110763486fd..6bc0b0d0010a28ff4b3d40db73303a461bea665a 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 b1cbf16d7d29a65eff523dfad55aa5eac5997546..1ce2991f7c84f60cfb7180d51cd11d5a1c9461a6 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 15a6ccf9af0e2948a4e7f03639847e877902bd47..2b1c59a92af0fe73402099ed692c6ef30c37adf2 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -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 69d18cf0c235be54bff6a404a9d4241e4ac9a479..28264df9a82ebf72af93ab2664597a2e76a5d2ab 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,14 +235,7 @@ 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::ReloadSetKeymaps() {}
 
 void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
     NotifyClientAreaSizeChanged(std::make_pair(width, height));
diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h
index 7dac1c4806f761c71a80eb5ad839eee9b831691d..923a5b45698a63914be59c3b263c216ee4170aa1 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 ReloadSetKeymaps();
 
     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/config.cpp
index 5fe57dfa2d62d3d6df3646922915cced11ca1d6d..6ccfa15779987ad042e639cef3aabaefb164b28a 100644
--- a/src/citra_qt/config.cpp
+++ b/src/citra_qt/config.cpp
@@ -6,6 +6,7 @@
 #include "citra_qt/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");
@@ -155,12 +177,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");
diff --git a/src/citra_qt/config.h b/src/citra_qt/config.h
index 79c9018042f7610d82224da2cfb7d8d2fc91ca8f..cbf745ea2517f18774cac2ca4a0128d3791ae4e6 100644
--- a/src/citra_qt/config.h
+++ b/src/citra_qt/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_input.cpp b/src/citra_qt/configure_input.cpp
index c29652f32504756c6b31200c304756f1d32c76ee..b59713e2cc3504174107e78cc795a8cac89b5689 100644
--- a/src/citra_qt/configure_input.cpp
+++ b/src/citra_qt/configure_input.cpp
@@ -2,13 +2,21 @@
 // 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/config.h"
 #include "citra_qt/configure_input.h"
+#include "common/param_package.h"
+#include "input_common/main.h"
 
-static QString getKeyName(Qt::Key key_code) {
+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");
@@ -23,6 +31,20 @@ static QString getKeyName(Qt::Key key_code) {
     }
 }
 
+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>()) {
@@ -31,36 +53,41 @@ ConfigureInput::ConfigureInput(QWidget* parent)
     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},
+        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,
     };
 
-    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); });
+    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(); });
@@ -69,50 +96,93 @@ ConfigureInput::ConfigureInput(QWidget* parent)
     connect(timer.get(), &QTimer::timeout, [this]() {
         releaseKeyboard();
         releaseMouse();
-        current_input_id = boost::none;
+        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() {
-    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]);
-    }
+    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() {
-    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]);
-    }
+    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 (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());
+    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() {
-    for (const auto& input_id : Settings::NativeInput::All) {
-        button_map[input_id]->setText(getKeyName(key_map[input_id]));
+    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(Settings::NativeInput::Values input_id) {
-    QPushButton* button = button_map[input_id];
+void ConfigureInput::handleClick(QPushButton* button, std::function<void(int)> new_key_setter) {
     button->setText(tr("[press key]"));
     button->setFocus();
 
-    current_input_id = input_id;
+    key_setter = new_key_setter;
 
     grabKeyboard();
     grabMouse();
@@ -123,23 +193,13 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) {
     releaseKeyboard();
     releaseMouse();
 
-    if (!current_input_id || !event)
+    if (!key_setter || !event)
         return;
 
     if (event->key() != Qt::Key_Escape)
-        setInput(*current_input_id, static_cast<Qt::Key>(event->key()));
+        (*key_setter)(event->key());
 
     updateButtonLabels();
-    current_input_id = boost::none;
+    key_setter = 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
index bc343db83002625afeb6432a87b1c53832ea5560..c950fbcb433b55c2c225eae1ff005c0406c0d755 100644
--- a/src/citra_qt/configure_input.h
+++ b/src/citra_qt/configure_input.h
@@ -4,10 +4,14 @@
 
 #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"
 
@@ -31,15 +35,25 @@ public:
 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;
+    /// 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();
@@ -48,10 +62,8 @@ private:
     /// 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);
+    /// 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;
-    /// 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/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 8a61702579795b70730ebb1838f755b1f0c4a609..13277a5c2e10db3305b5bdc35c7b7d3f99000d9e 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/logging/backend.cpp b/src/common/logging/backend.cpp
index 737e1d57fb333ec186b45ca10db47bfde1e6bb0e..42f6a99188739b08a7d59f5843fed50c10c50d52 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 4b0f8ff03ff5accb7c85934121e54c1b62236690..1b905f66c50cc5e2ced3984a69596fac26f1ef60 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 0000000000000000000000000000000000000000..3a6ef8c272c0c2e1be16ff187984ccb832cbf699
--- /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 0000000000000000000000000000000000000000..c4c11b221b19f64d65e531564860fe757983456f
--- /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/core/CMakeLists.txt b/src/core/CMakeLists.txt
index ffd67f0743fee997b36b2c6ec79aeecf3ae2dfa7..61a0b1cc37a5d8b6ec0666ef1427b173c47a5727 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/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index a155b657dd7093baf8037575e0538d63744dbffb..73a44bfe71bf91f0a455ff410cfe93fdfa0d3cc0 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
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 1ba64c92b4f0d4f0e1c16c8e1f6b17acbb5b6773..36f2667fa42686a45bced0ac9c02a5eae4cffb66 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
@@ -114,27 +89,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.
@@ -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 0000000000000000000000000000000000000000..0a5713dc0669273a9fa902bd8a361cbad67271a0
--- /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 15f0e079cfd58d6e980732fb9a9547703b0b7655..0000000000000000000000000000000000000000
--- 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 040794578d4f058b70cf97d00aa712e89eeb566f..0000000000000000000000000000000000000000
--- 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/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index fb3acb507cea3f8f821ad89e54a77757501f1f75..b19e831fe9eedc46152030c1654956d1dd390422 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 c7f4ee13867adaa065b26befbe8fef7525f551ea..b505cdcd50ccb48d1e62606a6bb2b8b67cf81bfe 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/settings.cpp b/src/core/settings.cpp
index 3a32b70aa54e7419176988cba7708f8940f5f212..a598f9f2f5ab9e7bf84aa3174d50f8bf8600590d 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 b6c75531fc6343f910c9668c07fc13632cb251ab..d1a9f0da86d05f85ddf34173904686afcdc958ff 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -18,64 +18,68 @@ enum class LayoutOption {
     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;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cfe5caaa3905879ca1dab021d257133190b4f9ab
--- /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 0000000000000000000000000000000000000000..e1a260762f1b149abd2d3f7dd04c865cf6a9232f
--- /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 0000000000000000000000000000000000000000..bbd583dd93aa802838f7bd037292a85de63fef22
--- /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 0000000000000000000000000000000000000000..a8fc01f2e92dfbff7323085d6213b09ee751c13f
--- /dev/null
+++ b/src/input_common/keyboard.cpp
@@ -0,0 +1,82 @@
+// 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);
+        }
+    }
+
+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);
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/keyboard.h b/src/input_common/keyboard.h
new file mode 100644
index 0000000000000000000000000000000000000000..76359aa3057dda22ada55c2b4879a84804dd4681
--- /dev/null
+++ b/src/input_common/keyboard.h
@@ -0,0 +1,45 @@
+// 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);
+
+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 0000000000000000000000000000000000000000..699f41e6bcff4307dc6fb39cecc2a415ea03cda2
--- /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 0000000000000000000000000000000000000000..140bbd0145c934e7718d17219ff3f4d9f34aa240
--- /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 0000000000000000000000000000000000000000..ae020690902e3e68f206cdadb1e6cc67f94fdca8
--- /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 0000000000000000000000000000000000000000..3e72debcceeb7ed44ab2b9cba69a3f8502b37aaf
--- /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 b47156ca4b23b145f8eadfd1a96da7fde31cf1cb..d1144ba77c7c8ef5ed5bb541cbe2f2112b4a1813 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 0000000000000000000000000000000000000000..efec2cc861baf20cd7ac2c294aaa24a71ddeae1c
--- /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/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 0818a87b330e8a7d13989ecac9b0af85dc4a2be8..456443e86aec135a48532c0a4ca986e8228cd45c 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"