diff --git a/src/app/ControllerManager.cpp b/src/app/ControllerManager.cpp index b31d661..8c8c908 100644 --- a/src/app/ControllerManager.cpp +++ b/src/app/ControllerManager.cpp @@ -148,6 +148,15 @@ void ControllerManager::SetBackButtonsEnabled(bool enabled) { slot->trackpad.SetBackButtonsEnabled(enabled); } +void ControllerManager::SetControllerPlatform(ControllerPlatform platform) { + if (m_controllerPlatform == platform) return; + m_controllerPlatform = platform; + if (IsGameModeActive()) { + DisableGameMode(); + EnableGameMode(); + } +} + void ControllerManager::StartButtonCapture(std::function callback) { std::lock_guard lk(m_captureMutex); m_captureCallback = std::move(callback); @@ -215,6 +224,7 @@ void ControllerManager::EnableGameModeSlot(Slot& slot, bool& vigemMissingOut) { if (!slot.sc->DisableLizardMode()) return; slot.vc = std::make_unique( + m_controllerPlatform, [sc = slot.sc.get()](uint8_t largeMotor, uint8_t smallMotor) { if (!sc->IsOpen()) return; const uint16_t leftSpeed = static_cast(static_cast(largeMotor) * 257u); diff --git a/src/app/ControllerManager.h b/src/app/ControllerManager.h index 9ac21e0..f2c5dd6 100644 --- a/src/app/ControllerManager.h +++ b/src/app/ControllerManager.h @@ -1,5 +1,6 @@ #pragma once #include "BackButtonConfig.h" +#include "ControllerPlatform.h" #include #include #include @@ -25,12 +26,14 @@ public: void SetUseLeftTrackpad(bool enabled); void SetBackButtonConfig(const BackButtonConfig& cfg); void SetBackButtonsEnabled(bool enabled); + void SetControllerPlatform(ControllerPlatform platform); - bool IsConnected() const { return !m_slots.empty(); } - bool IsGameModeActive() const; - bool IsTrackpadMouseEnabled() const { return m_trackpadMouseEnabled; } - bool IsUseLeftTrackpad() const { return m_useLeftTrackpad; } - bool IsBackButtonsEnabled() const { return m_backButtonsEnabled; } + bool IsConnected() const { return !m_slots.empty(); } + bool IsGameModeActive() const; + bool IsTrackpadMouseEnabled() const { return m_trackpadMouseEnabled; } + bool IsUseLeftTrackpad() const { return m_useLeftTrackpad; } + bool IsBackButtonsEnabled() const { return m_backButtonsEnabled; } + ControllerPlatform GetControllerPlatform() const { return m_controllerPlatform; } const BackButtonConfig& GetBackButtonConfig() const { return m_backConfig; } // Called by RemapWindow when a row enters/exits listening state. @@ -55,6 +58,7 @@ private: bool m_trackpadMouseEnabled = false; bool m_useLeftTrackpad = false; bool m_backButtonsEnabled = false; + ControllerPlatform m_controllerPlatform = ControllerPlatform::Xbox; BackButtonConfig m_backConfig; std::atomic m_capturing{false}; diff --git a/src/app/ControllerPlatform.h b/src/app/ControllerPlatform.h new file mode 100644 index 0000000..624d281 --- /dev/null +++ b/src/app/ControllerPlatform.h @@ -0,0 +1,6 @@ +#pragma once + +enum class ControllerPlatform { + Xbox, + PlayStation +}; diff --git a/src/app/TrayApp.cpp b/src/app/TrayApp.cpp index e3512e0..78225ef 100644 --- a/src/app/TrayApp.cpp +++ b/src/app/TrayApp.cpp @@ -1,5 +1,6 @@ #include "TrayApp.h" #include "ControllerManager.h" +#include "ControllerPlatform.h" #include "resource.h" #include #include @@ -111,6 +112,14 @@ LRESULT TrayApp::HandleMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { m_controller->SetUseLeftTrackpad(!m_controller->IsUseLeftTrackpad()); SaveSettings(); break; + case IDM_PLATFORM_XBOX: + m_controller->SetControllerPlatform(ControllerPlatform::Xbox); + SaveSettings(); + break; + case IDM_PLATFORM_PS: + m_controller->SetControllerPlatform(ControllerPlatform::PlayStation); + SaveSettings(); + break; case IDM_STARTUP: SetStartupEnabled(!IsStartupEnabled()); break; @@ -242,6 +251,9 @@ void TrayApp::LoadSettings() { m_controller->SetTrackpadMouseEnabled(readDw(L"TrackpadMouse", 0) != 0); m_controller->SetBackButtonsEnabled (readDw(L"BackButtons", 0) != 0); m_controller->SetUseLeftTrackpad (readDw(L"UseLeftTrackpad", 0) != 0); + m_controller->SetControllerPlatform (readDw(L"ControllerPlatform", 0) != 0 + ? ControllerPlatform::PlayStation + : ControllerPlatform::Xbox); BackButtonConfig cfg; cfg.l4 = static_cast(readDw(L"BackBtnL4", static_cast(BackButtonAction::None))); @@ -265,9 +277,10 @@ void TrayApp::SaveSettings() { reinterpret_cast(&val), sizeof(val)); }; - writeDw(L"TrackpadMouse", m_controller->IsTrackpadMouseEnabled() ? 1 : 0); - writeDw(L"BackButtons", m_controller->IsBackButtonsEnabled() ? 1 : 0); - writeDw(L"UseLeftTrackpad", m_controller->IsUseLeftTrackpad() ? 1 : 0); + writeDw(L"TrackpadMouse", m_controller->IsTrackpadMouseEnabled() ? 1 : 0); + writeDw(L"BackButtons", m_controller->IsBackButtonsEnabled() ? 1 : 0); + writeDw(L"UseLeftTrackpad", m_controller->IsUseLeftTrackpad() ? 1 : 0); + writeDw(L"ControllerPlatform", m_controller->GetControllerPlatform() == ControllerPlatform::PlayStation ? 1 : 0); const auto& cfg = m_controller->GetBackButtonConfig(); writeDw(L"BackBtnL4", static_cast(cfg.l4)); @@ -307,6 +320,17 @@ void TrayApp::ShowContextMenu() { AppendMenuW(menu, MF_SEPARATOR, 0, nullptr); + bool isPS = (m_controller->GetControllerPlatform() == ControllerPlatform::PlayStation); + HMENU platformMenu = CreatePopupMenu(); + AppendMenuW(platformMenu, MF_STRING | (!isPS ? MF_CHECKED : MF_UNCHECKED), + IDM_PLATFORM_XBOX, L"Xbox"); + AppendMenuW(platformMenu, MF_STRING | (isPS ? MF_CHECKED : MF_UNCHECKED), + IDM_PLATFORM_PS, L"PlayStation"); + AppendMenuW(menu, MF_STRING | MF_POPUP, + reinterpret_cast(platformMenu), L"Controller Platform"); + + AppendMenuW(menu, MF_SEPARATOR, 0, nullptr); + AppendMenuW(menu, MF_STRING | (startupOn ? MF_CHECKED : MF_UNCHECKED), IDM_STARTUP, L"Start with Windows"); diff --git a/src/app/TrayApp.h b/src/app/TrayApp.h index 0f46600..a224460 100644 --- a/src/app/TrayApp.h +++ b/src/app/TrayApp.h @@ -43,6 +43,8 @@ private: static constexpr UINT IDM_LEFT_TRACKPAD = 1005; static constexpr UINT IDM_STARTUP = 1006; static constexpr UINT IDM_BACKBUTTONS = 1007; + static constexpr UINT IDM_PLATFORM_XBOX = 1008; + static constexpr UINT IDM_PLATFORM_PS = 1009; static constexpr UINT WM_TRAY = WM_APP + 1; static constexpr UINT TRAY_UID = 1; }; diff --git a/src/app/VirtualController.cpp b/src/app/VirtualController.cpp index ea1ee44..0bdd44f 100644 --- a/src/app/VirtualController.cpp +++ b/src/app/VirtualController.cpp @@ -21,11 +21,23 @@ static VOID X360Notification( g_notificationSink->OnRumble(largeMotor, smallMotor); } +static VOID DS4Notification( + PVIGEM_CLIENT, + PVIGEM_TARGET, + UCHAR largeMotor, + UCHAR smallMotor, + DS4_LIGHTBAR_COLOR) +{ + std::lock_guard lock(g_notificationMutex); + if (g_notificationSink) + g_notificationSink->OnRumble(largeMotor, smallMotor); +} + // --------------------------------------------------------------------------- // Report translation — 0x45 → XUSB_REPORT // --------------------------------------------------------------------------- -static void ApplyBackAction(BackButtonAction action, XUSB_REPORT& r) { +static void ApplyBackActionX360(BackButtonAction action, XUSB_REPORT& r) { switch (action) { case BackButtonAction::A: r.wButtons |= XUSB_GAMEPAD_A; break; case BackButtonAction::B: r.wButtons |= XUSB_GAMEPAD_B; break; @@ -39,12 +51,10 @@ static void ApplyBackAction(BackButtonAction action, XUSB_REPORT& r) { case BackButtonAction::DPadDown: r.wButtons |= XUSB_GAMEPAD_DPAD_DOWN; break; case BackButtonAction::DPadLeft: r.wButtons |= XUSB_GAMEPAD_DPAD_LEFT; break; case BackButtonAction::DPadRight: r.wButtons |= XUSB_GAMEPAD_DPAD_RIGHT; break; - case BackButtonAction::Menu: r.wButtons |= XUSB_GAMEPAD_START; break; - case BackButtonAction::View: r.wButtons |= XUSB_GAMEPAD_BACK; break; - case BackButtonAction::L3: r.wButtons |= XUSB_GAMEPAD_LEFT_THUMB; break; - case BackButtonAction::R3: r.wButtons |= XUSB_GAMEPAD_RIGHT_THUMB; break; - // Mouse button actions are handled via SendInput in the ReadLoop (edge-detected). - // None/mouse actions produce no XInput output here. + case BackButtonAction::Menu: r.wButtons |= XUSB_GAMEPAD_START; break; + case BackButtonAction::View: r.wButtons |= XUSB_GAMEPAD_BACK; break; + case BackButtonAction::L3: r.wButtons |= XUSB_GAMEPAD_LEFT_THUMB; break; + case BackButtonAction::R3: r.wButtons |= XUSB_GAMEPAD_RIGHT_THUMB; break; case BackButtonAction::None: case BackButtonAction::LeftMouseButton: case BackButtonAction::RightMouseButton: @@ -52,7 +62,7 @@ static void ApplyBackAction(BackButtonAction action, XUSB_REPORT& r) { } } -static XUSB_REPORT Translate(const uint8_t* buf, size_t n) { +static XUSB_REPORT TranslateX360(const uint8_t* buf, size_t n) { XUSB_REPORT r{}; if (n < 18) return r; @@ -82,10 +92,10 @@ static XUSB_REPORT Translate(const uint8_t* buf, size_t n) { if (b2 & SteamController::BTN_STEAM) r.wButtons |= XUSB_GAMEPAD_GUIDE; // D-pad - if (b1 & SteamController::BTN_DPAD_UP) r.wButtons |= XUSB_GAMEPAD_DPAD_UP; - if (b1 & SteamController::BTN_DPAD_DN) r.wButtons |= XUSB_GAMEPAD_DPAD_DOWN; - if (b1 & SteamController::BTN_DPAD_LT) r.wButtons |= XUSB_GAMEPAD_DPAD_LEFT; - if (b1 & SteamController::BTN_DPAD_RT) r.wButtons |= XUSB_GAMEPAD_DPAD_RIGHT; + if (b1 & SteamController::BTN_DPAD_UP) r.wButtons |= XUSB_GAMEPAD_DPAD_UP; + if (b1 & SteamController::BTN_DPAD_DN) r.wButtons |= XUSB_GAMEPAD_DPAD_DOWN; + if (b1 & SteamController::BTN_DPAD_LT) r.wButtons |= XUSB_GAMEPAD_DPAD_LEFT; + if (b1 & SteamController::BTN_DPAD_RT) r.wButtons |= XUSB_GAMEPAD_DPAD_RIGHT; // Triggers: 16-bit signed (0x0000–0x7FFF) → 8-bit (0–255) int16_t ltRaw, rtRaw; @@ -103,12 +113,135 @@ static XUSB_REPORT Translate(const uint8_t* buf, size_t n) { return r; } +// --------------------------------------------------------------------------- +// Report translation — 0x45 → DS4_REPORT +// --------------------------------------------------------------------------- + +// Maps signed 16-bit XInput range to DS4 unsigned byte (0=min, 128=center, 255=max). +static uint8_t s16ToU8(int16_t v) { + return static_cast((static_cast(v) + 32768) >> 8); +} + +static DS4_REPORT TranslateDS4(const uint8_t* buf, size_t n, + const BackButtonConfig& backCfg, bool backMouseEnabled) { + DS4_REPORT r{}; + // Zero-init gives sticks at 0; we set them from actual data below, and + // always call DS4_SET_DPAD at the end — so no need for DS4_REPORT_INIT. + if (n < 18) { + // Return centered sticks and D-pad none as safe default. + r.bThumbLX = r.bThumbLY = r.bThumbRX = r.bThumbRY = 0x80; + DS4_SET_DPAD(&r, DS4_BUTTON_DPAD_NONE); + return r; + } + + const uint8_t b0 = buf[2]; + const uint8_t b1 = buf[3]; + const uint8_t b2 = buf[4]; + + // Face buttons (A→Cross, B→Circle, X→Square, Y→Triangle) + if (b0 & SteamController::BTN_A) r.wButtons |= DS4_BUTTON_CROSS; + if (b0 & SteamController::BTN_B) r.wButtons |= DS4_BUTTON_CIRCLE; + if (b0 & SteamController::BTN_X) r.wButtons |= DS4_BUTTON_SQUARE; + if (b0 & SteamController::BTN_Y) r.wButtons |= DS4_BUTTON_TRIANGLE; + + // Bumpers + if (b2 & SteamController::BTN_LB) r.wButtons |= DS4_BUTTON_SHOULDER_LEFT; + if (b1 & SteamController::BTN_RB) r.wButtons |= DS4_BUTTON_SHOULDER_RIGHT; + + // Options / Share (Menu→Options, View→Share) + if (b0 & SteamController::BTN_MENU) r.wButtons |= DS4_BUTTON_OPTIONS; + if (b1 & SteamController::BTN_VIEW) r.wButtons |= DS4_BUTTON_SHARE; + + // Stick clicks + if (b1 & SteamController::BTN_LS) r.wButtons |= DS4_BUTTON_THUMB_LEFT; + if (b0 & SteamController::BTN_RS) r.wButtons |= DS4_BUTTON_THUMB_RIGHT; + + // PS / Steam button + if (b2 & SteamController::BTN_STEAM) r.bSpecial |= DS4_SPECIAL_BUTTON_PS; + + // Right trackpad click → DS4 touchpad button + if (b2 & SteamController::BTN_TP_RT) r.bSpecial |= DS4_SPECIAL_BUTTON_TOUCHPAD; + + // D-pad: accumulate all direction sources before computing the HAT. + bool dUp = (b1 & SteamController::BTN_DPAD_UP) != 0; + bool dDn = (b1 & SteamController::BTN_DPAD_DN) != 0; + bool dLt = (b1 & SteamController::BTN_DPAD_LT) != 0; + bool dRt = (b1 & SteamController::BTN_DPAD_RT) != 0; + + // Triggers: 16-bit signed (0x0000–0x7FFF) → 8-bit (0–255) + int16_t ltRaw, rtRaw; + memcpy(<Raw, buf + 6, 2); + memcpy(&rtRaw, buf + 8, 2); + r.bTriggerL = static_cast(std::clamp(ltRaw >> 7, 0, 255)); + r.bTriggerR = static_cast(std::clamp(rtRaw >> 7, 0, 255)); + + // Sticks: signed 16-bit → uint8 centered at 0x80. + // DS4 Y axis is inverted vs XInput (0 = up, 255 = down). + int16_t lx, ly, rx, ry; + memcpy(&lx, buf + 10, 2); + memcpy(&ly, buf + 12, 2); + memcpy(&rx, buf + 14, 2); + memcpy(&ry, buf + 16, 2); + r.bThumbLX = s16ToU8(lx); + r.bThumbLY = static_cast(255u - s16ToU8(ly)); + r.bThumbRX = s16ToU8(rx); + r.bThumbRY = static_cast(255u - s16ToU8(ry)); + + // Back paddles: apply to report and accumulate D-pad bits. + auto applyBack = [&](BackButtonAction action) { + switch (action) { + case BackButtonAction::A: r.wButtons |= DS4_BUTTON_CROSS; break; + case BackButtonAction::B: r.wButtons |= DS4_BUTTON_CIRCLE; break; + case BackButtonAction::X: r.wButtons |= DS4_BUTTON_SQUARE; break; + case BackButtonAction::Y: r.wButtons |= DS4_BUTTON_TRIANGLE; break; + case BackButtonAction::LB: r.wButtons |= DS4_BUTTON_SHOULDER_LEFT; break; + case BackButtonAction::RB: r.wButtons |= DS4_BUTTON_SHOULDER_RIGHT; break; + case BackButtonAction::LT: r.bTriggerL = 255; break; + case BackButtonAction::RT: r.bTriggerR = 255; break; + case BackButtonAction::DPadUp: dUp = true; break; + case BackButtonAction::DPadDown: dDn = true; break; + case BackButtonAction::DPadLeft: dLt = true; break; + case BackButtonAction::DPadRight: dRt = true; break; + case BackButtonAction::Menu: r.wButtons |= DS4_BUTTON_OPTIONS; break; + case BackButtonAction::View: r.wButtons |= DS4_BUTTON_SHARE; break; + case BackButtonAction::L3: r.wButtons |= DS4_BUTTON_THUMB_LEFT; break; + case BackButtonAction::R3: r.wButtons |= DS4_BUTTON_THUMB_RIGHT; break; + default: break; + } + }; + + if (n > 4) { + if (!backMouseEnabled && (buf[4] & SteamController::BTN_L4)) applyBack(backCfg.l4); + if (buf[4] & SteamController::BTN_L5) applyBack(backCfg.l5); + } + if (n > 2) { + if (!backMouseEnabled && (buf[2] & SteamController::BTN_R4)) applyBack(backCfg.r4); + } + if (n > 3) { + if (buf[3] & SteamController::BTN_R5) applyBack(backCfg.r5); + } + + // Resolve accumulated D-pad bits into a HAT value (handles diagonals). + DS4_DPAD_DIRECTIONS hat = DS4_BUTTON_DPAD_NONE; + if (dUp && dRt) hat = DS4_BUTTON_DPAD_NORTHEAST; + else if (dUp && dLt) hat = DS4_BUTTON_DPAD_NORTHWEST; + else if (dDn && dRt) hat = DS4_BUTTON_DPAD_SOUTHEAST; + else if (dDn && dLt) hat = DS4_BUTTON_DPAD_SOUTHWEST; + else if (dUp) hat = DS4_BUTTON_DPAD_NORTH; + else if (dRt) hat = DS4_BUTTON_DPAD_EAST; + else if (dDn) hat = DS4_BUTTON_DPAD_SOUTH; + else if (dLt) hat = DS4_BUTTON_DPAD_WEST; + DS4_SET_DPAD(&r, hat); + + return r; +} + // --------------------------------------------------------------------------- // VirtualController // --------------------------------------------------------------------------- -VirtualController::VirtualController(RumbleCallback rumbleCallback) - : m_rumbleCallback(std::move(rumbleCallback)) +VirtualController::VirtualController(ControllerPlatform platform, RumbleCallback rumbleCallback) + : m_platform(platform), m_rumbleCallback(std::move(rumbleCallback)) { m_client = vigem_alloc(); if (!m_client) { printf("[ViGEm] alloc failed\n"); return; } @@ -122,7 +255,9 @@ VirtualController::VirtualController(RumbleCallback rumbleCallback) return; } - m_target = vigem_target_x360_alloc(); + m_target = (platform == ControllerPlatform::Xbox) + ? vigem_target_x360_alloc() + : vigem_target_ds4_alloc(); if (!m_target) { printf("[ViGEm] target alloc failed\n"); return; } err = vigem_target_add(static_cast(m_client), @@ -134,14 +269,22 @@ VirtualController::VirtualController(RumbleCallback rumbleCallback) return; } - printf("[ViGEm] Virtual Xbox 360 controller connected\n"); + if (platform == ControllerPlatform::Xbox) { + printf("[ViGEm] Virtual Xbox 360 controller connected\n"); + err = vigem_target_x360_register_notification( + static_cast(m_client), + static_cast(m_target), + X360Notification); + } else { + printf("[ViGEm] Virtual DualShock 4 controller connected\n"); + err = vigem_target_ds4_register_notification( + static_cast(m_client), + static_cast(m_target), + DS4Notification); + } - err = vigem_target_x360_register_notification( - static_cast(m_client), - static_cast(m_target), - X360Notification); if (!VIGEM_SUCCESS(err)) - printf("[ViGEm] x360 notification registration failed: 0x%08X\n", err); + printf("[ViGEm] notification registration failed: 0x%08X\n", err); else { std::lock_guard lock(g_notificationMutex); g_notificationSink = this; @@ -152,7 +295,10 @@ VirtualController::VirtualController(RumbleCallback rumbleCallback) VirtualController::~VirtualController() { if (m_target) { - vigem_target_x360_unregister_notification(static_cast(m_target)); + if (m_platform == ControllerPlatform::Xbox) + vigem_target_x360_unregister_notification(static_cast(m_target)); + else + vigem_target_ds4_unregister_notification(static_cast(m_target)); std::lock_guard lock(g_notificationMutex); if (g_notificationSink == this) g_notificationSink = nullptr; @@ -171,25 +317,30 @@ VirtualController::~VirtualController() { void VirtualController::Update(const uint8_t* buf, size_t n, const BackButtonConfig& backCfg, bool backMouseEnabled) { if (!m_valid) return; - XUSB_REPORT report = Translate(buf, n); - // Inject back paddle buttons as their mapped XInput actions. - // L4 and R4 are skipped when "back buttons as mouse click" is active — - // TrackpadMouse handles them as left-mouse-button presses instead. - if (n > 4) { - if (!backMouseEnabled && (buf[4] & SteamController::BTN_L4)) ApplyBackAction(backCfg.l4, report); - if (buf[4] & SteamController::BTN_L5) ApplyBackAction(backCfg.l5, report); - } - if (n > 2) { - if (!backMouseEnabled && (buf[2] & SteamController::BTN_R4)) ApplyBackAction(backCfg.r4, report); - } - if (n > 3) { - if (buf[3] & SteamController::BTN_R5) ApplyBackAction(backCfg.r5, report); - } + if (m_platform == ControllerPlatform::Xbox) { + XUSB_REPORT report = TranslateX360(buf, n); - vigem_target_x360_update(static_cast(m_client), - static_cast(m_target), - report); + if (n > 4) { + if (!backMouseEnabled && (buf[4] & SteamController::BTN_L4)) ApplyBackActionX360(backCfg.l4, report); + if (buf[4] & SteamController::BTN_L5) ApplyBackActionX360(backCfg.l5, report); + } + if (n > 2) { + if (!backMouseEnabled && (buf[2] & SteamController::BTN_R4)) ApplyBackActionX360(backCfg.r4, report); + } + if (n > 3) { + if (buf[3] & SteamController::BTN_R5) ApplyBackActionX360(backCfg.r5, report); + } + + vigem_target_x360_update(static_cast(m_client), + static_cast(m_target), + report); + } else { + DS4_REPORT report = TranslateDS4(buf, n, backCfg, backMouseEnabled); + vigem_target_ds4_update(static_cast(m_client), + static_cast(m_target), + report); + } } void VirtualController::OnRumble(uint8_t largeMotor, uint8_t smallMotor) { diff --git a/src/app/VirtualController.h b/src/app/VirtualController.h index ca5acb8..8ed8996 100644 --- a/src/app/VirtualController.h +++ b/src/app/VirtualController.h @@ -3,12 +3,13 @@ #include #include #include "BackButtonConfig.h" +#include "ControllerPlatform.h" class VirtualController { public: using RumbleCallback = std::function; - explicit VirtualController(RumbleCallback rumbleCallback = {}); + explicit VirtualController(ControllerPlatform platform, RumbleCallback rumbleCallback = {}); ~VirtualController(); VirtualController(const VirtualController&) = delete; VirtualController& operator=(const VirtualController&) = delete; @@ -20,9 +21,10 @@ public: void OnRumble(uint8_t largeMotor, uint8_t smallMotor); private: - void* m_client = nullptr; - void* m_target = nullptr; + ControllerPlatform m_platform; + void* m_client = nullptr; + void* m_target = nullptr; RumbleCallback m_rumbleCallback; - bool m_valid = false; + bool m_valid = false; bool m_driverMissing = false; };