diff --git a/README.md b/README.md
index 49f8d5706..7c591338f 100755
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 yuzu emulator early access
 =============
 
-This is the source code for early-access 3990.
+This is the source code for early-access 3992.
 
 ## Legal Notice
 
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 483d20f3b..c9a516b02 100755
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -167,6 +167,11 @@ protected:
      */
     std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const;
 
+    /**
+     * Clip the provided coordinates to be inside the touchscreen area.
+     */
+    std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
+
     WindowSystemInfo window_info;
 
     bool strict_context_required = false;
@@ -181,11 +186,6 @@ private:
         // By default, ignore this request and do nothing.
     }
 
-    /**
-     * Clip the provided coordinates to be inside the touchscreen area.
-     */
-    std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
-
     Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout
 
     u32 client_area_width;  ///< Current client width, should be set by window impl.
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index f16a5a83f..99a510267 100755
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -509,9 +509,11 @@ void EmulatedController::ReloadInput() {
         });
     }
     turbo_button_state = 0;
+    is_initalized = true;
 }
 
 void EmulatedController::UnloadInput() {
+    is_initalized = false;
     for (auto& button : button_devices) {
         button.reset();
     }
@@ -1207,6 +1209,9 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
 }
 
 bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
+    if (!is_initalized) {
+        return false;
+    }
     if (device_index >= output_devices.size()) {
         return false;
     }
@@ -1242,6 +1247,10 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
     const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
     const auto& player = Settings::values.players.GetValue()[player_index];
 
+    if (!is_initalized) {
+        return false;
+    }
+
     if (!player.vibration_enabled) {
         return false;
     }
@@ -1261,6 +1270,10 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
     EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) {
     LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index);
 
+    if (!is_initalized) {
+        return Common::Input::DriverResult::InvalidHandle;
+    }
+
     auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)];
     auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
     auto& nfc_output_device = output_devices[3];
@@ -1306,6 +1319,10 @@ bool EmulatedController::SetCameraFormat(
     Core::IrSensor::ImageTransferProcessorFormat camera_format) {
     LOG_INFO(Service_HID, "Set camera format {}", camera_format);
 
+    if (!is_initalized) {
+        return false;
+    }
+
     auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
     auto& camera_output_device = output_devices[2];
 
@@ -1329,6 +1346,11 @@ void EmulatedController::SetRingParam(Common::ParamPackage param) {
 }
 
 bool EmulatedController::HasNfc() const {
+
+    if (!is_initalized) {
+        return false;
+    }
+
     const auto& nfc_output_device = output_devices[3];
 
     switch (npad_type) {
@@ -1366,6 +1388,10 @@ bool EmulatedController::RemoveNfcHandle() {
 }
 
 bool EmulatedController::StartNfcPolling() {
+    if (!is_initalized) {
+        return false;
+    }
+
     auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
     auto& nfc_virtual_output_device = output_devices[3];
 
@@ -1377,6 +1403,10 @@ bool EmulatedController::StartNfcPolling() {
 }
 
 bool EmulatedController::StopNfcPolling() {
+    if (!is_initalized) {
+        return false;
+    }
+
     auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
     auto& nfc_virtual_output_device = output_devices[3];
 
@@ -1388,6 +1418,10 @@ bool EmulatedController::StopNfcPolling() {
 }
 
 bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
+    if (!is_initalized) {
+        return false;
+    }
+
     auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
     auto& nfc_virtual_output_device = output_devices[3];
 
@@ -1400,6 +1434,10 @@ bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
 
 bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request,
                                         Common::Input::MifareRequest& out_data) {
+    if (!is_initalized) {
+        return false;
+    }
+
     auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
     auto& nfc_virtual_output_device = output_devices[3];
 
@@ -1412,6 +1450,10 @@ bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& requ
 }
 
 bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) {
+    if (!is_initalized) {
+        return false;
+    }
+
     auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
     auto& nfc_virtual_output_device = output_devices[3];
 
@@ -1423,6 +1465,10 @@ bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& req
 }
 
 bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
+    if (!is_initalized) {
+        return false;
+    }
+
     auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
     auto& nfc_virtual_output_device = output_devices[3];
 
@@ -1434,6 +1480,10 @@ bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
 }
 
 void EmulatedController::SetLedPattern() {
+    if (!is_initalized) {
+        return;
+    }
+
     for (auto& device : output_devices) {
         if (!device) {
             continue;
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index ad93463a0..3370ad35e 100755
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -559,6 +559,7 @@ private:
     NpadStyleTag supported_style_tag{NpadStyleSet::All};
     bool is_connected{false};
     bool is_configuring{false};
+    bool is_initalized{false};
     bool system_buttons_enabled{true};
     f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
     u32 turbo_button_state{0};
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index 861bf3c8c..0a2fedc13 100755
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -19,19 +19,8 @@
 
 namespace Service::Set {
 
-namespace {
-constexpr u64 SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET = 0x05;
-
-enum class GetFirmwareVersionType {
-    Version1,
-    Version2,
-};
-
-void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
-                            GetFirmwareVersionType type) {
-    ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100,
-               "FirmwareVersion output buffer must be 0x100 bytes in size!");
-
+Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
+                              GetFirmwareVersionType type) {
     constexpr u64 FirmwareVersionSystemDataId = 0x0100000000000809;
     auto& fsc = system.GetFileSystemController();
 
@@ -52,39 +41,34 @@ void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
             FileSys::SystemArchive::SynthesizeSystemArchive(FirmwareVersionSystemDataId));
     }
 
-    const auto early_exit_failure = [&ctx](std::string_view desc, Result code) {
+    const auto early_exit_failure = [](std::string_view desc, Result code) {
         LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
                   desc);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(code);
+        return code;
     };
 
     const auto ver_file = romfs->GetFile("file");
     if (ver_file == nullptr) {
-        early_exit_failure("The system version archive didn't contain the file 'file'.",
-                           FileSys::ERROR_INVALID_ARGUMENT);
-        return;
+        return early_exit_failure("The system version archive didn't contain the file 'file'.",
+                                  FileSys::ERROR_INVALID_ARGUMENT);
     }
 
     auto data = ver_file->ReadAllBytes();
-    if (data.size() != 0x100) {
-        early_exit_failure("The system version file 'file' was not the correct size.",
-                           FileSys::ERROR_OUT_OF_BOUNDS);
-        return;
+    if (data.size() != sizeof(FirmwareVersionFormat)) {
+        return early_exit_failure("The system version file 'file' was not the correct size.",
+                                  FileSys::ERROR_OUT_OF_BOUNDS);
     }
 
+    std::memcpy(&out_firmware, data.data(), sizeof(FirmwareVersionFormat));
+
     // If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will
     // zero out the REVISION_MINOR field.
     if (type == GetFirmwareVersionType::Version1) {
-        data[SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET] = 0;
+        out_firmware.revision_minor = 0;
     }
 
-    ctx.WriteBuffer(data);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    return ResultSuccess;
 }
-} // Anonymous namespace
 
 void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
@@ -98,12 +82,32 @@ void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
 
 void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) {
     LOG_DEBUG(Service_SET, "called");
-    GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version1);
+
+    FirmwareVersionFormat firmware_data{};
+    const auto result =
+        GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version1);
+
+    if (result.IsSuccess()) {
+        ctx.WriteBuffer(firmware_data);
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
 }
 
 void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) {
     LOG_DEBUG(Service_SET, "called");
-    GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version2);
+
+    FirmwareVersionFormat firmware_data{};
+    const auto result =
+        GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version2);
+
+    if (result.IsSuccess()) {
+        ctx.WriteBuffer(firmware_data);
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
 }
 
 void SET_SYS::GetAccountSettings(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index 64e94e3b3..f12a2cbbf 100755
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -4,6 +4,7 @@
 #pragma once
 
 #include "common/uuid.h"
+#include "core/hle/result.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/time/clock_types.h"
 
@@ -12,6 +13,29 @@ class System;
 }
 
 namespace Service::Set {
+enum class LanguageCode : u64;
+enum class GetFirmwareVersionType {
+    Version1,
+    Version2,
+};
+
+struct FirmwareVersionFormat {
+    u8 major;
+    u8 minor;
+    u8 micro;
+    INSERT_PADDING_BYTES(1);
+    u8 revision_major;
+    u8 revision_minor;
+    INSERT_PADDING_BYTES(2);
+    std::array<char, 0x20> platform;
+    std::array<u8, 0x40> version_hash;
+    std::array<char, 0x18> display_version;
+    std::array<char, 0x80> display_title;
+};
+static_assert(sizeof(FirmwareVersionFormat) == 0x100, "FirmwareVersionFormat is an invalid size");
+
+Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
+                              GetFirmwareVersionType type);
 
 class SET_SYS final : public ServiceFramework<SET_SYS> {
 public:
diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h
index 85cbd5a16..643fd9dc0 100755
--- a/src/core/hle/service/time/clock_types.h
+++ b/src/core/hle/service/time/clock_types.h
@@ -11,6 +11,11 @@
 #include "core/hle/service/time/errors.h"
 #include "core/hle/service/time/time_zone_types.h"
 
+// Defined by WinBase.h on Windows
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+
 namespace Service::Time::Clock {
 
 enum class TimeType : u8 {
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index fc6c8ebcf..3d13c695f 100755
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -265,33 +265,33 @@ std::string Device::GetVendorName() const {
     if (vendor_name == "Intel") {
         // For Mesa, `Intel` is an overloaded vendor string that could mean crocus or iris.
         // Simply return `INTEL` for those as well as the Windows driver.
-        return "INTEL";
+        return "Intel";
     }
     if (vendor_name == "Intel Open Source Technology Center") {
-        return "I965";
+        return "i965";
     }
     if (vendor_name == "Mesa Project") {
-        return "I915";
+        return "i915";
     }
     if (vendor_name == "Mesa/X.org") {
         // This vendor string is overloaded between llvmpipe, softpipe, and virgl, so just return
         // MESA instead of one of those driver names.
-        return "MESA";
+        return "Mesa";
     }
     if (vendor_name == "AMD") {
-        return "RADEONSI";
+        return "RadeonSI";
     }
     if (vendor_name == "nouveau") {
-        return "NOUVEAU";
+        return "Nouveau";
     }
     if (vendor_name == "X.Org") {
         return "R600";
     }
     if (vendor_name == "Collabora Ltd") {
-        return "ZINK";
+        return "Zink";
     }
     if (vendor_name == "Intel Corporation") {
-        return "OPENSWR";
+        return "OpenSWR";
     }
     if (vendor_name == "Microsoft Corporation") {
         return "D3D12";
@@ -300,7 +300,7 @@ std::string Device::GetVendorName() const {
         // Mesa's tegra driver reports `NVIDIA`. Only present in this list because the default
         // strategy would have returned `NVIDIA` here for this driver, the same result as the
         // proprietary driver.
-        return "TEGRA";
+        return "Tegra";
     }
     return vendor_name;
 }
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 0e5fdd099..97894944e 100755
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1785,8 +1785,22 @@ ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
     : VideoCommon::ImageViewBase{info, view_info, gpu_addr_},
       buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {}
 
-ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams& params)
-    : VideoCommon::ImageViewBase{params} {}
+ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageViewParams& params)
+    : VideoCommon::ImageViewBase{params}, device{&runtime.device} {
+    if (device->HasNullDescriptor()) {
+        return;
+    }
+
+    // Handle fallback for devices without nullDescriptor
+    ImageInfo info{};
+    info.format = PixelFormat::A8B8G8R8_UNORM;
+
+    null_image = MakeImage(*device, runtime.memory_allocator, info, {});
+    image_handle = *null_image;
+    for (u32 i = 0; i < Shader::NUM_TEXTURE_TYPES; i++) {
+        image_views[i] = MakeView(VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_ASPECT_COLOR_BIT);
+    }
+}
 
 ImageView::~ImageView() = default;
 
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 4471ec74f..84038f223 100755
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -267,6 +267,7 @@ private:
     vk::ImageView depth_view;
     vk::ImageView stencil_view;
     vk::ImageView color_view;
+    vk::Image null_image;
     VkImage image_handle = VK_NULL_HANDLE;
     VkImageView render_target = VK_NULL_HANDLE;
     VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 619731935..3ad16830c 100755
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -847,11 +847,41 @@ std::string Device::GetDriverName() const {
     case VK_DRIVER_ID_NVIDIA_PROPRIETARY:
         return "NVIDIA";
     case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS:
-        return "INTEL";
+        return "Intel";
     case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA:
         return "ANV";
+    case VK_DRIVER_ID_IMAGINATION_PROPRIETARY:
+        return "PowerVR";
+    case VK_DRIVER_ID_QUALCOMM_PROPRIETARY:
+        return "Qualcomm";
+    case VK_DRIVER_ID_ARM_PROPRIETARY:
+        return "Mali";
+    case VK_DRIVER_ID_GOOGLE_SWIFTSHADER:
+        return "SwiftShader";
+    case VK_DRIVER_ID_BROADCOM_PROPRIETARY:
+        return "Broadcom";
     case VK_DRIVER_ID_MESA_LLVMPIPE:
-        return "LAVAPIPE";
+        return "Lavapipe";
+    case VK_DRIVER_ID_MOLTENVK:
+        return "MoltenVK";
+    case VK_DRIVER_ID_VERISILICON_PROPRIETARY:
+        return "Vivante";
+    case VK_DRIVER_ID_MESA_TURNIP:
+        return "Turnip";
+    case VK_DRIVER_ID_MESA_V3DV:
+        return "V3DV";
+    case VK_DRIVER_ID_MESA_PANVK:
+        return "PanVK";
+    case VK_DRIVER_ID_MESA_VENUS:
+        return "Venus";
+    case VK_DRIVER_ID_MESA_DOZEN:
+        return "Dozen";
+    case VK_DRIVER_ID_MESA_NVK:
+        return "NVK";
+    case VK_DRIVER_ID_IMAGINATION_OPEN_SOURCE_MESA:
+        return "PVR";
+    // case VK_DRIVER_ID_MESA_AGXV:
+    //     return "Asahi";
     default:
         return properties.driver.driverName;
     }
@@ -869,7 +899,8 @@ bool Device::ShouldBoostClocks() const {
         driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA ||
         driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP;
 
-    const bool is_steam_deck = vendor_id == 0x1002 && device_id == 0x163F;
+    const bool is_steam_deck = (vendor_id == 0x1002 && device_id == 0x163F) ||
+                               (vendor_id == 0x1002 && device_id == 0x1435);
 
     const bool is_debugging = this->HasDebuggingToolAttached();
 
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 32474c1d1..69e7fe7f8 100755
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -30,7 +30,6 @@
 #include <QSize>
 #include <QStringLiteral>
 #include <QSurfaceFormat>
-#include <QTimer>
 #include <QWindow>
 #include <QtCore/qobjectdefs.h>
 
@@ -66,6 +65,8 @@ class QObject;
 class QPaintEngine;
 class QSurface;
 
+constexpr int default_mouse_constrain_timeout = 10;
+
 EmuThread::EmuThread(Core::System& system) : m_system{system} {}
 
 EmuThread::~EmuThread() = default;
@@ -304,6 +305,9 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
             Qt::QueuedConnection);
     connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection);
     connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &GMainWindow::OnTasStateChanged);
+
+    mouse_constrain_timer.setInterval(default_mouse_constrain_timeout);
+    connect(&mouse_constrain_timer, &QTimer::timeout, this, &GRenderWindow::ConstrainMouse);
 }
 
 void GRenderWindow::ExecuteProgram(std::size_t program_index) {
@@ -393,6 +397,22 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
     QWidget::closeEvent(event);
 }
 
+void GRenderWindow::leaveEvent(QEvent* event) {
+    if (Settings::values.mouse_panning) {
+        const QRect& rect = QWidget::geometry();
+        QPoint position = QCursor::pos();
+
+        qint32 x = qBound(rect.left(), position.x(), rect.right());
+        qint32 y = qBound(rect.top(), position.y(), rect.bottom());
+        // Only start the timer if the mouse has left the window bound.
+        // The leave event is also triggered when the window looses focus.
+        if (x != position.x() || y != position.y()) {
+            mouse_constrain_timer.start();
+        }
+        event->accept();
+    }
+}
+
 int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) {
     static constexpr std::array<std::pair<Qt::Key, Settings::NativeKeyboard::Keys>, 106> key_map = {
         std::pair<Qt::Key, Settings::NativeKeyboard::Keys>{Qt::Key_A, Settings::NativeKeyboard::A},
@@ -658,10 +678,19 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
     input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
     input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y);
 
+    // Center mouse for mouse panning
     if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
         QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
     }
 
+    // Constrain mouse for mouse emulation with mouse panning
+    if (Settings::values.mouse_panning && Settings::values.mouse_enabled) {
+        const auto [clamped_mouse_x, clamped_mouse_y] = ClipToTouchScreen(x, y);
+        QCursor::setPos(mapToGlobal(
+            QPoint{static_cast<int>(clamped_mouse_x), static_cast<int>(clamped_mouse_y)}));
+    }
+
+    mouse_constrain_timer.stop();
     emit MouseActivity();
 }
 
@@ -675,6 +704,31 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
     input_subsystem->GetMouse()->ReleaseButton(button);
 }
 
+void GRenderWindow::ConstrainMouse() {
+    if (emu_thread == nullptr || !Settings::values.mouse_panning) {
+        mouse_constrain_timer.stop();
+        return;
+    }
+    if (!this->isActiveWindow()) {
+        mouse_constrain_timer.stop();
+        return;
+    }
+
+    if (Settings::values.mouse_enabled) {
+        const auto pos = mapFromGlobal(QCursor::pos());
+        const int new_pos_x = std::clamp(pos.x(), 0, width());
+        const int new_pos_y = std::clamp(pos.y(), 0, height());
+
+        QCursor::setPos(mapToGlobal(QPoint{new_pos_x, new_pos_y}));
+        return;
+    }
+
+    const int center_x = width() / 2;
+    const int center_y = height() / 2;
+
+    QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
+}
+
 void GRenderWindow::wheelEvent(QWheelEvent* event) {
     const int x = event->angleDelta().x();
     const int y = event->angleDelta().y();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 1597b482b..1af0d316c 100755
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -17,6 +17,7 @@
 #include <QString>
 #include <QStringList>
 #include <QThread>
+#include <QTimer>
 #include <QWidget>
 #include <qglobal.h>
 #include <qnamespace.h>
@@ -38,7 +39,6 @@ class QMouseEvent;
 class QObject;
 class QResizeEvent;
 class QShowEvent;
-class QTimer;
 class QTouchEvent;
 class QWheelEvent;
 
@@ -166,6 +166,7 @@ public:
     std::pair<u32, u32> ScaleTouch(const QPointF& pos) const;
 
     void closeEvent(QCloseEvent* event) override;
+    void leaveEvent(QEvent* event) override;
 
     void resizeEvent(QResizeEvent* event) override;
 
@@ -229,6 +230,7 @@ private:
     void TouchBeginEvent(const QTouchEvent* event);
     void TouchUpdateEvent(const QTouchEvent* event);
     void TouchEndEvent();
+    void ConstrainMouse();
 
     void RequestCameraCapture();
     void OnCameraCapture(int requestId, const QImage& img);
@@ -268,6 +270,8 @@ private:
     std::unique_ptr<QTimer> camera_timer;
 #endif
 
+    QTimer mouse_constrain_timer;
+
     Core::System& system;
 
 protected:
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 3288400ab..e88f4338c 100755
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -47,6 +47,7 @@
 #include "core/hle/service/am/applet_ae.h"
 #include "core/hle/service/am/applet_oe.h"
 #include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/set/set_sys.h"
 #include "yuzu/multiplayer/state.h"
 #include "yuzu/util/controller_navigation.h"
 
@@ -186,7 +187,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
 #endif
 
 constexpr int default_mouse_hide_timeout = 2500;
-constexpr int default_mouse_center_timeout = 10;
 constexpr int default_input_update_timeout = 1;
 
 constexpr size_t CopyBufferSize = 1_MiB;
@@ -436,9 +436,6 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
     connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
     connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor);
 
-    mouse_center_timer.setInterval(default_mouse_center_timeout);
-    connect(&mouse_center_timer, &QTimer::timeout, this, &GMainWindow::CenterMouseCursor);
-
     update_input_timer.setInterval(default_input_update_timeout);
     connect(&update_input_timer, &QTimer::timeout, this, &GMainWindow::UpdateInputDrivers);
     update_input_timer.start();
@@ -1048,7 +1045,12 @@ void GMainWindow::InitializeWidgets() {
         statusBar()->addPermanentWidget(label);
     }
 
-    // TODO (flTobi): Add the widget when multiplayer is fully implemented
+    firmware_label = new QLabel();
+    firmware_label->setObjectName(QStringLiteral("FirmwareLabel"));
+    firmware_label->setVisible(false);
+    firmware_label->setFocusPolicy(Qt::NoFocus);
+    statusBar()->addPermanentWidget(firmware_label);
+
     statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
     statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
 
@@ -1370,14 +1372,6 @@ void GMainWindow::InitializeHotkeys() {
         }
     });
     connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
-        if (Settings::values.mouse_enabled) {
-            Settings::values.mouse_panning = false;
-            QMessageBox::warning(
-                this, tr("Emulated mouse is enabled"),
-                tr("Real mouse input and mouse panning are incompatible. Please disable the "
-                   "emulated mouse in input advanced settings to allow mouse panning."));
-            return;
-        }
         Settings::values.mouse_panning = !Settings::values.mouse_panning;
         if (Settings::values.mouse_panning) {
             render_window->installEventFilter(render_window);
@@ -2165,6 +2159,10 @@ void GMainWindow::OnEmulationStopped() {
     emu_frametime_label->setVisible(false);
     renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan);
 
+    if (!firmware_label->text().isEmpty()) {
+        firmware_label->setVisible(true);
+    }
+
     current_game_path.clear();
 
     // When closing the game, destroy the GLWindow to clear the context after the game is closed
@@ -4591,6 +4589,7 @@ void GMainWindow::UpdateStatusBar() {
     emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue());
     game_fps_label->setVisible(true);
     emu_frametime_label->setVisible(true);
+    firmware_label->setVisible(false);
 }
 
 void GMainWindow::UpdateGPUAccuracyButton() {
@@ -4700,26 +4699,10 @@ void GMainWindow::ShowMouseCursor() {
     }
 }
 
-void GMainWindow::CenterMouseCursor() {
-    if (emu_thread == nullptr || !Settings::values.mouse_panning) {
-        mouse_center_timer.stop();
-        return;
-    }
-    if (!this->isActiveWindow()) {
-        mouse_center_timer.stop();
-        return;
-    }
-    const int center_x = render_window->width() / 2;
-    const int center_y = render_window->height() / 2;
-
-    QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
-}
-
 void GMainWindow::OnMouseActivity() {
     if (!Settings::values.mouse_panning) {
         ShowMouseCursor();
     }
-    mouse_center_timer.stop();
 }
 
 void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
@@ -4810,6 +4793,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
                "games."));
     }
 
+    SetFirmwareVersion();
+
     if (behavior == ReinitializeKeyBehavior::Warning) {
         game_list->PopulateAsync(UISettings::values.game_dirs);
     }
@@ -4837,7 +4822,7 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
 }
 
 bool GMainWindow::CheckFirmwarePresence() {
-    constexpr u64 MiiEditId = 0x0100000000001009ull;
+    constexpr u64 MiiEditId = static_cast<u64>(Service::AM::Applets::AppletProgramId::MiiEdit);
 
     auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
     if (!bis_system) {
@@ -4852,6 +4837,28 @@ bool GMainWindow::CheckFirmwarePresence() {
     return true;
 }
 
+void GMainWindow::SetFirmwareVersion() {
+    Service::Set::FirmwareVersionFormat firmware_data{};
+    const auto result = Service::Set::GetFirmwareVersionImpl(
+        firmware_data, *system, Service::Set::GetFirmwareVersionType::Version2);
+
+    if (result.IsError() || !CheckFirmwarePresence()) {
+        LOG_INFO(Frontend, "Installed firmware: No firmware available");
+        firmware_label->setVisible(false);
+        return;
+    }
+
+    firmware_label->setVisible(true);
+
+    const std::string display_version(firmware_data.display_version.data());
+    const std::string display_title(firmware_data.display_title.data());
+
+    LOG_INFO(Frontend, "Installed firmware: {}", display_title);
+
+    firmware_label->setText(QString::fromStdString(display_version));
+    firmware_label->setToolTip(QString::fromStdString(display_title));
+}
+
 bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
                                         u64* selected_title_id, u8* selected_content_record_type) {
     using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
@@ -4996,22 +5003,6 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
     AcceptDropEvent(event);
 }
 
-void GMainWindow::leaveEvent(QEvent* event) {
-    if (Settings::values.mouse_panning) {
-        const QRect& rect = geometry();
-        QPoint position = QCursor::pos();
-
-        qint32 x = qBound(rect.left(), position.x(), rect.right());
-        qint32 y = qBound(rect.top(), position.y(), rect.bottom());
-        // Only start the timer if the mouse has left the window bound.
-        // The leave event is also triggered when the window looses focus.
-        if (x != position.x() || y != position.y()) {
-            mouse_center_timer.start();
-        }
-        event->accept();
-    }
-}
-
 bool GMainWindow::ConfirmChangeGame() {
     if (emu_thread == nullptr)
         return true;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index b11903b15..275b43250 100755
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -451,13 +451,13 @@ private:
     void UpdateInputDrivers();
     void HideMouseCursor();
     void ShowMouseCursor();
-    void CenterMouseCursor();
     void OpenURL(const QUrl& url);
     void LoadTranslation();
     void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
     bool CheckDarkMode();
     bool CheckSystemArchiveDecryption();
     bool CheckFirmwarePresence();
+    void SetFirmwareVersion();
     void ConfigureFilesystemProvider(const std::string& filepath);
     /**
      * Open (or not) the right confirm dialog based on current setting and game exit lock
@@ -512,6 +512,7 @@ private:
     QLabel* game_fps_label = nullptr;
     QLabel* emu_frametime_label = nullptr;
     QLabel* tas_label = nullptr;
+    QLabel* firmware_label = nullptr;
     QPushButton* gpu_accuracy_button = nullptr;
     QPushButton* renderer_status_button = nullptr;
     QPushButton* dock_status_button = nullptr;
@@ -533,7 +534,6 @@ private:
     bool auto_paused = false;
     bool auto_muted = false;
     QTimer mouse_hide_timer;
-    QTimer mouse_center_timer;
     QTimer update_input_timer;
 
     QString startup_icon_theme;
@@ -590,5 +590,4 @@ protected:
     void dropEvent(QDropEvent* event) override;
     void dragEnterEvent(QDragEnterEvent* event) override;
     void dragMoveEvent(QDragMoveEvent* event) override;
-    void leaveEvent(QEvent* event) override;
 };