From f0a56998ca3f076855cb24cef9ebc14761fd0430 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 16:38:23 +0200 Subject: [PATCH 01/74] Fix height calculation and improve task handling Adjust height calculation and replace vTaskDelay with yield for better task management. --- src/core/scrollableTextArea.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/scrollableTextArea.cpp b/src/core/scrollableTextArea.cpp index 55b888575..14085aac1 100644 --- a/src/core/scrollableTextArea.cpp +++ b/src/core/scrollableTextArea.cpp @@ -3,7 +3,7 @@ ScrollableTextArea::ScrollableTextArea(const String &title) : firstVisibleLine{0}, _redraw{true}, _title(title), _fontSize(FP), _startX(BORDER_PAD_X), _startY(BORDER_PAD_Y), _width(tftWidth - 2 * BORDER_PAD_X), - _height(tftHeight - BORDER_PAD_X - BORDER_PAD_Y) { + _height(tftHeight - 4 - BORDER_PAD_X - BORDER_PAD_Y) { drawMainBorder(); if (!_title.isEmpty()) { @@ -76,11 +76,11 @@ void ScrollableTextArea::show(bool force) { while (check(SelPress)) { update(force); - vTaskDelay(pdMS_TO_TICKS(1)); + yield(); } while (!check(SelPress)) { update(force); - vTaskDelay(pdMS_TO_TICKS(1)); + yield(); } } @@ -121,7 +121,7 @@ void ScrollableTextArea::fromString(const String &text) { endIdx++; } - // Add the last line if there’s remaining text (text does not ends with \n) + // Add the last line if there's remaining text (text does not ends with \n) if (startIdx < text.length()) { addLine(text.substring(startIdx, endIdx)); } } From a8f94158de99acdf7b5b1667ab77fc02a93b6e73 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 16:40:58 +0200 Subject: [PATCH 02/74] Update debounce method in ble_common.cpp Changed debounce method to use yield instead of vTaskDelay. --- src/modules/ble/ble_common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index a2f580476..304d6d7c5 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -77,7 +77,7 @@ void ble_info(const String &name, const String &address, const String &signal) { delay(300); while (!check(SelPress)) { - while (!check(SelPress)) { vTaskDelay(pdMS_TO_TICKS(1)); } // timerless debounce + while (!check(SelPress)) { yield(); } // timerless debounce returnToMenu = true; break; } From d9ddd4643781a0cbd47bd626121e4250b8c931fb Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 16:41:47 +0200 Subject: [PATCH 03/74] Refactor UI text and layout in BLE_Suite.cpp Updated user interface text and layout for better clarity. Adjusted text positions and added new messages for device selection. --- src/modules/ble/BLE_Suite.cpp | 347 ++++++++++++++++++++++++++-------- 1 file changed, 264 insertions(+), 83 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index 5320dae78..2d10062cd 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -2971,8 +2971,10 @@ String selectFileFromSD() { } tft.setTextColor(TFT_GREEN, bruceConfig.bgColor); - tft.setCursor(20, tftHeight - 35); - tft.print("SEL: Select PREV/NEXT: Navigate ESC: Back"); + tft.setCursor(20, tftHeight - 30); + tft.print("SEL: Select PREV/NEXT: Navigate"); + tft.setCursor(20, tftHeight - 20); + tft.print("ESC: Back"); lastSelected = selected; lastScrollOffset = scrollOffset; @@ -3095,8 +3097,10 @@ String getScriptFromUser() { } tft.setTextColor(TFT_GREEN, bruceConfig.bgColor); - tft.setCursor(20, tftHeight - 35); - tft.print("SEL: Select PREV/NEXT: Navigate ESC: Back"); + tft.setCursor(20, tftHeight - 30); + tft.print("SEL: Select PREV/NEXT: Navigate"); + tft.setCursor(20, tftHeight - 20); + tft.print("ESC: Back"); lastSelected = selected; lastScrollOffset = scrollOffset; @@ -4038,108 +4042,266 @@ void BLE_Sniffer() { //============================================================================= String selectTargetFromScan(const char *title) { - if (scannerData.size() == 0) { - showErrorMessage("No devices found. Run scan first."); + scannerData.clear(); + + // Use the BLE scan setup from ble_common which has proper RAM checks + bool bleWasActiveBefore = BLEConnected || (BLEDevice::getServer() != nullptr); +#if !defined(LITE_VERSION) + bleWasActiveBefore = + bleWasActiveBefore || BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0; +#endif + + if (!ble_scan_setup() || pBLEScan == nullptr) return ""; + + tft.fillScreen(bruceConfig.bgColor); + tft.drawRect(5, 5, tftWidth - 10, tftHeight - 10, TFT_WHITE); + + tft.setTextColor(TFT_WHITE, bruceConfig.bgColor); + tft.setTextSize(2); + tft.setCursor((tftWidth - tft.textWidth(title)) / 2, 15); + tft.print(title); + tft.setTextSize(1); + + tft.setCursor(20, 60); + tft.print("Scanning for devices..."); + + const int ACTIVE_SCAN_TIME = 15, PASSIVE_SCAN_TIME = 15; + + tft.setCursor(20, 80); + tft.print("Active scan (15s)..."); + +#ifdef NIMBLE_V2_PLUS + NimBLEScanResults results = pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); +#else + NimBLEScanResults results = pBLEScan->start(ACTIVE_SCAN_TIME, false); +#endif + + tft.setCursor(20, 100); + tft.print("Passive scan (15s)..."); + pBLEScan->setActiveScan(false); + +#ifdef NIMBLE_V2_PLUS + results = pBLEScan->getResults(PASSIVE_SCAN_TIME * 1000, false); +#else + results = pBLEScan->start(PASSIVE_SCAN_TIME, false); +#endif + + if (results.getCount() == 0) { + pBLEScan->stop(); + pBLEScan->clearResults(); + if (!bleWasActiveBefore) { stopBLEStack(); } + + tft.fillScreen(TFT_YELLOW); + tft.drawRect(5, 5, tftWidth - 10, tftHeight - 10, TFT_BLACK); + tft.setTextColor(TFT_BLACK, TFT_YELLOW); + tft.setTextSize(2); + tft.setCursor((tftWidth - tft.textWidth("NO DEVICES")) / 2, 15); + tft.print("NO DEVICES"); + tft.setTextSize(1); + tft.setCursor(20, 60); + tft.print("No BLE devices found!"); + tft.setCursor(20, 80); + tft.print("Make sure BLE devices are"); + tft.setCursor(20, 100); + tft.print("turned on and in range."); + delay(2000); return ""; } - int selected = 0, scrollOffset = 0; - int lastSelected = -1, lastScrollOffset = -1; - bool exitMenu = false; - int menuStartY = 60, menuItemHeight = 25; + for (int i = 0; i < results.getCount(); i++) { + const NimBLEAdvertisedDevice *device = results.getDevice(i); + + String address = String(device->getAddress().toString().c_str()); + String name = String(device->getName().c_str()); + if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") name = "Unknown"; + + int rssi = device->getRSSI(); + if (rssi == 0) rssi = -100; + + bool fastPair = false, hasHFP = false; + uint8_t deviceType = 0; + + if (device->haveServiceUUID()) { + NimBLEUUID uuid = device->getServiceUUID(); + std::string uuidStr = uuid.toString(); + if (uuidStr.find("fe2c") != std::string::npos) fastPair = true; + if (uuidStr.find("111e") != std::string::npos || uuidStr.find("111f") != std::string::npos) + hasHFP = true; + if (uuidStr.find("110e") != std::string::npos || uuidStr.find("110f") != std::string::npos) + deviceType |= 0x01; + if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; + } + + scannerData.addDevice(name, address, rssi, fastPair, hasHFP, deviceType); + } + + pBLEScan->stop(); + pBLEScan->clearResults(); + if (!bleWasActiveBefore) { stopBLEStack(); } + size_t deviceCount = scannerData.size(); - int maxVisibleItems = (tftHeight - menuStartY - 50) / menuItemHeight; - if (maxVisibleItems > (int)deviceCount) maxVisibleItems = deviceCount; - while (!exitMenu) { - if (selected != lastSelected || scrollOffset != lastScrollOffset) { + if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { + for (size_t i = 0; scannerData.deviceAddresses.size() > 1 && i < scannerData.deviceAddresses.size() - 1; i++) { + for (size_t j = i + 1; j < scannerData.deviceAddresses.size(); j++) { + bool swapNeeded = false; + if (scannerData.deviceFastPair[j] && !scannerData.deviceFastPair[i]) swapNeeded = true; + else if (scannerData.deviceFastPair[j] == scannerData.deviceFastPair[i] && + scannerData.deviceRssi[j] > scannerData.deviceRssi[i]) + swapNeeded = true; + + if (swapNeeded) { + std::swap(scannerData.deviceNames[i], scannerData.deviceNames[j]); + std::swap(scannerData.deviceAddresses[i], scannerData.deviceAddresses[j]); + std::swap(scannerData.deviceRssi[i], scannerData.deviceRssi[j]); + + bool tempFastPair = scannerData.deviceFastPair[i]; + scannerData.deviceFastPair[i] = scannerData.deviceFastPair[j]; + scannerData.deviceFastPair[j] = tempFastPair; + + bool tempHFP = scannerData.deviceHasHFP[i]; + scannerData.deviceHasHFP[i] = scannerData.deviceHasHFP[j]; + scannerData.deviceHasHFP[j] = tempHFP; + std::swap(scannerData.deviceTypes[i], scannerData.deviceTypes[j]); + } + } + } + xSemaphoreGive(scannerData.mutex); + } + + int maxVisibleDevices = 3, deviceItemHeight = 30, menuStartY = 60; + int selectedIdx = 0, scrollOffset = 0; + int lastSelected = -1, lastScrollOffset = -1; + bool exitLoop = false; + + while (!exitLoop) { + if (selectedIdx != lastSelected || scrollOffset != lastScrollOffset) { tft.fillScreen(bruceConfig.bgColor); tft.drawRect(5, 5, tftWidth - 10, tftHeight - 10, TFT_WHITE); tft.setTextColor(TFT_WHITE, bruceConfig.bgColor); tft.setTextSize(2); - tft.setCursor((tftWidth - tft.textWidth(title)) / 2, 15); - tft.print(title); + tft.setCursor((tftWidth - tft.textWidth("SELECT DEVICE")) / 2, 15); + tft.print("SELECT DEVICE"); tft.setTextSize(1); tft.setTextColor(TFT_YELLOW, bruceConfig.bgColor); tft.setCursor(20, 40); - tft.print("Devices: "); + tft.print("Found: "); tft.print(deviceCount); + tft.print(" devices"); + + for (int i = 0; i < maxVisibleDevices && (scrollOffset + i) < (int)deviceCount; i++) { + String displayName, address; + int rssi = 0; + bool fastPair = false, hasHFP = false; + uint8_t deviceType = 0; + + if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { + int deviceIndex = scrollOffset + i; + if (deviceIndex < (int)scannerData.deviceNames.size()) { + displayName = scannerData.deviceNames[deviceIndex]; + address = scannerData.deviceAddresses[deviceIndex]; + rssi = scannerData.deviceRssi[deviceIndex]; + fastPair = scannerData.deviceFastPair[deviceIndex]; + hasHFP = scannerData.deviceHasHFP[deviceIndex]; + deviceType = scannerData.deviceTypes[deviceIndex]; + } + xSemaphoreGive(scannerData.mutex); + } - for (int i = 0; i < maxVisibleItems && (scrollOffset + i) < (int)deviceCount; i++) { - int idx = scrollOffset + i; - int yPos = menuStartY + (i * menuItemHeight); - if (yPos + menuItemHeight > tftHeight - 45) break; + if (displayName.isEmpty()) continue; - if (idx == selected) { - tft.fillRect(20, yPos, tftWidth - 40, menuItemHeight - 3, TFT_WHITE); + String displayText = displayName; + if (displayText.length() > 18) displayText = displayText.substring(0, 15) + "..."; + displayText += " (" + String(rssi) + "dB)"; + if (fastPair) displayText += " [FP]"; + if (hasHFP) displayText += " [HFP]"; + if (deviceType & 0x01) displayText += " [AUDIO]"; + if (deviceType & 0x02) displayText += " [HID]"; + + int yPos = menuStartY + (i * deviceItemHeight); + if (yPos + deviceItemHeight > tftHeight - 45) break; + + if (i == selectedIdx - scrollOffset) { + tft.fillRect(15, yPos, tftWidth - 30, deviceItemHeight - 5, TFT_WHITE); tft.setTextColor(TFT_BLACK, TFT_WHITE); - tft.setCursor(25, yPos + 8); + tft.setCursor(20, yPos + 10); tft.print("> "); } else { - tft.fillRect(20, yPos, tftWidth - 40, menuItemHeight - 3, bruceConfig.bgColor); + tft.fillRect(15, yPos, tftWidth - 30, deviceItemHeight - 5, bruceConfig.bgColor); tft.setTextColor(TFT_WHITE, bruceConfig.bgColor); - tft.setCursor(25, yPos + 8); + tft.setCursor(20, yPos + 10); tft.print(" "); } - - String display = String(idx + 1) + ". " + scannerData.deviceNames[idx] + " | " + - scannerData.deviceAddresses[idx] + " | " + - String(scannerData.deviceRssi[idx]) + "dB"; - if (display.length() > 28) display = display.substring(0, 25) + "..."; - tft.print(display); + tft.print(displayText); } - if (deviceCount > (size_t)maxVisibleItems) { + if (deviceCount > (size_t)maxVisibleDevices) { tft.setTextColor(TFT_CYAN, bruceConfig.bgColor); - tft.setCursor(tftWidth - 25, menuStartY + 5); + tft.setCursor(tftWidth - 25, menuStartY + 10); if (scrollOffset > 0) tft.print("^"); - tft.setCursor(tftWidth - 25, menuStartY + (maxVisibleItems * menuItemHeight) - 20); - if (scrollOffset + maxVisibleItems < (int)deviceCount) tft.print("v"); + tft.setCursor(tftWidth - 25, menuStartY + (maxVisibleDevices * deviceItemHeight) - 15); + if (scrollOffset + maxVisibleDevices < (int)deviceCount) tft.print("v"); } tft.setTextColor(TFT_GREEN, bruceConfig.bgColor); - tft.setCursor(20, tftHeight - 35); - tft.print("SEL: Select PREV/NEXT: Navigate ESC: Back"); + tft.setCursor(20, tftHeight - 30); + tft.print("SEL: Select PREV/NEXT: Navigate"); + tft.setCursor(20, tftHeight - 20); + tft.print("ESC: Back"); - lastSelected = selected; + lastSelected = selectedIdx; lastScrollOffset = scrollOffset; } if (check(EscPress)) { - delay(200); - exitMenu = true; - return ""; + exitLoop = true; } else if (check(PrevPress)) { delay(150); - if (selected > 0) { - selected--; - if (selected < scrollOffset) scrollOffset = selected; + if (selectedIdx > 0) { + selectedIdx--; + if (selectedIdx < scrollOffset) scrollOffset = selectedIdx; } else { - selected = deviceCount - 1; - scrollOffset = std::max(0, (int)deviceCount - maxVisibleItems); + selectedIdx = deviceCount - 1; + scrollOffset = std::max(0, (int)deviceCount - maxVisibleDevices); } } else if (check(NextPress)) { delay(150); - if (selected < (int)deviceCount - 1) { - selected++; - if (selected >= scrollOffset + maxVisibleItems) scrollOffset = selected - maxVisibleItems + 1; + if (selectedIdx < (int)deviceCount - 1) { + selectedIdx++; + if (selectedIdx >= scrollOffset + maxVisibleDevices) + scrollOffset = selectedIdx - maxVisibleDevices + 1; } else { - selected = 0; + selectedIdx = 0; scrollOffset = 0; } } else if (check(SelPress)) { - delay(200); - return scannerData.deviceAddresses[selected]; + String selectedMAC = "", selectedName = ""; + + if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { + if (selectedIdx < (int)scannerData.deviceAddresses.size()) { + selectedMAC = scannerData.deviceAddresses[selectedIdx]; + selectedName = scannerData.deviceNames[selectedIdx]; + } + xSemaphoreGive(scannerData.mutex); + } + + if (!selectedMAC.isEmpty()) { + scannerData.clear(); + return selectedMAC + ":0"; + } } delay(50); } + scannerData.clear(); return ""; } String selectMultipleTargetsFromScan(const char *title, std::vector &targets) { targets.clear(); + + // Full multi-select implementation if (scannerData.size() == 0) { showErrorMessage("No devices found. Run scan first."); return ""; @@ -4201,8 +4363,10 @@ String selectMultipleTargetsFromScan(const char *title, std::vector 28) { displayText = displayText.substring(0, 25) + "..."; } + + if (tft.textWidth(displayText) > availWidth) { + String ellipsis = "..."; + int ellipsisWidth = tft.textWidth(ellipsis); + while (displayText.length() > 0 && + tft.textWidth(displayText) + ellipsisWidth > availWidth) { + displayText.remove(displayText.length() - 1); + } + displayText += ellipsis; + } if (idx == selected) { tft.fillRect(20, yPos, tftWidth - 40, 20, TFT_WHITE); @@ -4755,8 +4932,10 @@ int showSubMenu(const char *title, const char *options[], int optionCount) { } tft.setTextColor(TFT_GREEN, bruceConfig.bgColor); - tft.setCursor(20, tftHeight - 35); - tft.print("SEL: Select PREV/NEXT: Navigate ESC: Back"); + tft.setCursor(20, tftHeight - 30); + tft.print("SEL: Select PREV/NEXT: Navigate"); + tft.setCursor(20, tftHeight - 20); + tft.print("ESC: Back"); lastSelected = selected; lastScrollOffset = scrollOffset; @@ -5464,9 +5643,11 @@ void runAudioControlTest(NimBLEAddress target) { tft.print(displayName); } - tft.setTextColor(TFT_WHITE, bruceConfig.bgColor); - tft.setCursor(20, tftHeight - 35); - tft.print("SEL: Test PREV/NEXT: Navigate ESC: Back"); + tft.setTextColor(TFT_GREEN, bruceConfig.bgColor); + tft.setCursor(20, tftHeight - 30); + tft.print("SEL: Select PREV/NEXT: Navigate"); + tft.setCursor(20, tftHeight - 20); + tft.print("ESC: Back"); lastSelected = selectedTest; } @@ -5729,8 +5910,8 @@ bool confirmAttack(const char *targetName) { tft.setCursor(20, 90); tft.println("FastPair buffer overflow exploit"); - tft.setTextColor(TFT_WHITE, bruceConfig.bgColor); - tft.setCursor(20, tftHeight - 35); + tft.setTextColor(TFT_GREEN, bruceConfig.bgColor); + tft.setCursor(20, tftHeight - 30); tft.print("SEL: Yes NEXT: No ESC: Cancel"); while (true) { @@ -5846,7 +6027,7 @@ int8_t showAdaptiveMessage( if (yPos > 140) break; } - tft.setTextColor(TFT_WHITE, bruceConfig.bgColor); + tft.setTextColor(TFT_BLACK, bruceConfig.bgColor); tft.setCursor(20, tftHeight - 35); if (buttonCount == 0) { @@ -6106,7 +6287,7 @@ void showDeviceInfoScreen( yPos = lineY; } - tft.setTextColor(TFT_WHITE, bgColor); + tft.setTextColor(TFT_BLACK, bgColor); tft.setCursor(20, tftHeight - 35); tft.print("Press any key to continue..."); From af15c4cdc73405b80fe9f1bd801b15099a5563b9 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 16:42:29 +0200 Subject: [PATCH 04/74] Reorder and add includes in BLE_Suite.h --- src/modules/ble/BLE_Suite.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index ef3452a00..c4c9b04d5 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -1,14 +1,14 @@ #ifndef BLE_SUITE_H #define BLE_SUITE_H #if !defined(LITE_VERSION) -#include -#include "fastpair_crypto.h" #include "HFP_Exploit.h" +#include "fastpair_crypto.h" +#include #include -#include #include #include #include +#include #if __has_include() #define NIMBLE_V2_PLUS 1 @@ -47,6 +47,12 @@ #ifndef TFT_DARKGREEN #define TFT_DARKGREEN 0x03E0 #endif +#ifndef TFT_PURPLE +#define TFT_PURPLE 0x780F +#endif +#ifndef TFT_LIGHTGREY +#define TFT_LIGHTGREY 0xC618 +#endif extern volatile int tftWidth; extern volatile int tftHeight; @@ -504,6 +510,9 @@ void runUniversalAttack(NimBLEAddress target); String selectFileFromSD(); bool loadScriptFromSD(const String &filename); +// BLE Sniffer +void BLE_Sniffer(); + // Forward declarations for submenu functions void showFastPairSubMenu(NimBLEAddress target); void showHFPSubMenu(NimBLEAddress target); From 1525da5ed6e081811a509855e6f5a0cfef3db725 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 17:20:05 +0200 Subject: [PATCH 05/74] Remove TFT color definitions Removed redundant TFT color definitions from BLE_Suite.h as they are now defined in VectorDisplay.h. --- src/modules/ble/BLE_Suite.h | 40 +------------------------------------ 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index c4c9b04d5..021faa0e9 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -14,45 +14,7 @@ #define NIMBLE_V2_PLUS 1 #endif -#ifndef TFT_WHITE -#define TFT_WHITE 0xFFFF -#endif -#ifndef TFT_BLACK -#define TFT_BLACK 0x0000 -#endif -#ifndef TFT_RED -#define TFT_RED 0xF800 -#endif -#ifndef TFT_GREEN -#define TFT_GREEN 0x07E0 -#endif -#ifndef TFT_BLUE -#define TFT_BLUE 0x001F -#endif -#ifndef TFT_YELLOW -#define TFT_YELLOW 0xFFE0 -#endif -#ifndef TFT_CYAN -#define TFT_CYAN 0x07FF -#endif -#ifndef TFT_MAGENTA -#define TFT_MAGENTA 0xF81F -#endif -#ifndef TFT_ORANGE -#define TFT_ORANGE 0xFDA0 -#endif -#ifndef TFT_GRAY -#define TFT_GRAY 0x8410 -#endif -#ifndef TFT_DARKGREEN -#define TFT_DARKGREEN 0x03E0 -#endif -#ifndef TFT_PURPLE -#define TFT_PURPLE 0x780F -#endif -#ifndef TFT_LIGHTGREY -#define TFT_LIGHTGREY 0xC618 -#endif +// TFT color definitions are in VectorDisplay.h - do not redefine here extern volatile int tftWidth; extern volatile int tftHeight; From c9070c4fe98363d964ed9a8f1fa2219ba977c868 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 17:20:48 +0200 Subject: [PATCH 06/74] Fix String to const char* conversion for text width Refactor text width checks to use c_str() for String conversion. --- src/modules/ble/BLE_Suite.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index 2d10062cd..6338b5dc7 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -4894,16 +4894,16 @@ int showSubMenu(const char *title, const char *options[], int optionCount) { int idx = scrollOffset + i; int yPos = 60 + (i * 25); - int availWidth = - (tftWidth - 40) - 20; + int availWidth = (tftWidth - 40) - 20; String displayText = options[idx]; - if (tft.textWidth(displayText) > availWidth) { + // FIX: Use .c_str() for String to const char* conversion (required for headless ESP32-S3) + if (tft.textWidth(displayText.c_str()) > availWidth) { String ellipsis = "..."; - int ellipsisWidth = tft.textWidth(ellipsis); + int ellipsisWidth = tft.textWidth(ellipsis.c_str()); while (displayText.length() > 0 && - tft.textWidth(displayText) + ellipsisWidth > availWidth) { + tft.textWidth(displayText.c_str()) + ellipsisWidth > availWidth) { displayText.remove(displayText.length() - 1); } displayText += ellipsis; From 0a294c61f5ae64171107abecfc312f92c7b43921 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 17:21:40 +0200 Subject: [PATCH 07/74] Update BLE service start comment for NimBLE v2 Updated comments to reflect changes in NimBLE v2 regarding service start behavior. --- src/modules/ble/ble_common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index 304d6d7c5..bdb11093f 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -273,7 +273,7 @@ void disPlayBLESend() { drawMainBorder(); // Moved up to avoid drawing screen issues tft.setTextSize(1); - pService->start(); + // pService->start() is deprecated in NimBLE v2 - services start automatically with the server pServer->getAdvertising()->start(); uint64_t chipid = ESP.getEfuseMac(); From 32854ed1edad2c4557d8546188bc460a9cb9ec49 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 17:22:51 +0200 Subject: [PATCH 08/74] Fix missing newline at end of file Add missing newline at the end of scrollableTextArea.cpp From 823330e61084627a3aded6124e4e18ef5a37f528 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 18:28:55 +0200 Subject: [PATCH 09/74] Refactor BLE_Suite.h for include and comment clarity Updated include order and comments regarding TFT color definitions. --- src/modules/ble/BLE_Suite.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index 021faa0e9..dff091206 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -1,6 +1,10 @@ #ifndef BLE_SUITE_H #define BLE_SUITE_H #if !defined(LITE_VERSION) + +// Include display header FIRST so TFT color definitions are available +#include "core/display.h" + #include "HFP_Exploit.h" #include "fastpair_crypto.h" #include @@ -14,7 +18,7 @@ #define NIMBLE_V2_PLUS 1 #endif -// TFT color definitions are in VectorDisplay.h - do not redefine here +// TFT color definitions are now included from display.h - do not redefine here extern volatile int tftWidth; extern volatile int tftHeight; From 019f330a2bf2dde319ae5ac9045d3ef8cf489ada Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 19:50:07 +0200 Subject: [PATCH 10/74] Update BLE Suite version and improve scanning logic Updated BLE Suite version and fixed target selection logic with callback-based scanning. Improved title handling for display and optimized device data collection. --- src/modules/ble/BLE_Suite.cpp | 169 +++++++++++++++++++++++----------- 1 file changed, 114 insertions(+), 55 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index 6338b5dc7..e6d4fb107 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -2,7 +2,7 @@ * BLE Suite v3.1 - Complete BLE attack and analysis toolkit * Author: Ninja-jr * Version: 3.1 - * Last Updated: 2026-01-24 + * Last Updated: 14/07/2026 * * Contains: Vulnerability scanning, HID attacks, FastPair exploits, * HFP attacks, Audio attacks, DuckyScript injection, @@ -87,7 +87,7 @@ void ScannerData::addDevice( for (size_t i = 0; i < deviceAddresses.size(); i++) { if (deviceAddresses[i] == address) { isDuplicate = true; - deviceRssi[i] = rssi; + if (rssi > deviceRssi[i]) deviceRssi[i] = rssi; break; } } @@ -4038,19 +4038,20 @@ void BLE_Sniffer() { } //============================================================================= -// Target Selection Functions +// Target Selection Functions - FIXED with Active + Passive callback-based scan //============================================================================= String selectTargetFromScan(const char *title) { scannerData.clear(); - // Use the BLE scan setup from ble_common which has proper RAM checks + // Check if BLE is already active bool bleWasActiveBefore = BLEConnected || (BLEDevice::getServer() != nullptr); #if !defined(LITE_VERSION) bleWasActiveBefore = bleWasActiveBefore || BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0; #endif + // Use the callback-based scan approach (same as ble_scan()) if (!ble_scan_setup() || pBLEScan == nullptr) return ""; tft.fillScreen(bruceConfig.bgColor); @@ -4058,39 +4059,128 @@ String selectTargetFromScan(const char *title) { tft.setTextColor(TFT_WHITE, bruceConfig.bgColor); tft.setTextSize(2); - tft.setCursor((tftWidth - tft.textWidth(title)) / 2, 15); - tft.print(title); + // FIX: Truncate title if too long for small screens + String titleStr = String(title); + int maxTitleWidth = tftWidth - 20; // Leave 10px padding on each side + if (tft.textWidth(titleStr.c_str()) > maxTitleWidth) { + while (titleStr.length() > 0 && tft.textWidth(titleStr.c_str() + "...") > maxTitleWidth) { + titleStr.remove(titleStr.length() - 1); + } + titleStr += "..."; + } + tft.setCursor(10, 15); // Left align with padding + tft.print(titleStr); tft.setTextSize(1); tft.setCursor(20, 60); tft.print("Scanning for devices..."); - const int ACTIVE_SCAN_TIME = 15, PASSIVE_SCAN_TIME = 15; + // Custom callback class to collect devices + class TargetSelectionCallbacks : public NimBLEScanCallbacks { + public: + std::vector names; + std::vector addresses; + std::vector rssis; + std::vector fastPairs; + std::vector hfps; + std::vector types; + + void onResult(NimBLEAdvertisedDevice *advertisedDevice) override { + String name = String(advertisedDevice->getName().c_str()); + if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { + name = "Unknown"; + } + + String address = String(advertisedDevice->getAddress().toString().c_str()); + int rssi = advertisedDevice->getRSSI(); + if (rssi == 0) rssi = -100; + + bool fastPair = false, hasHFP = false; + uint8_t deviceType = 0; + + if (advertisedDevice->haveServiceUUID()) { + NimBLEUUID uuid = advertisedDevice->getServiceUUID(); + std::string uuidStr = uuid.toString(); + if (uuidStr.find("fe2c") != std::string::npos) fastPair = true; + if (uuidStr.find("111e") != std::string::npos || uuidStr.find("111f") != std::string::npos) + hasHFP = true; + if (uuidStr.find("110e") != std::string::npos || uuidStr.find("110f") != std::string::npos) + deviceType |= 0x01; + if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; + } + + // Check for duplicates (update RSSI if exists) + for (size_t i = 0; i < addresses.size(); i++) { + if (addresses[i] == address) { + // Update RSSI with strongest signal + if (rssi > rssis[i]) rssis[i] = rssi; + return; + } + } + + names.push_back(name); + addresses.push_back(address); + rssis.push_back(rssi); + fastPairs.push_back(fastPair); + hfps.push_back(hasHFP); + types.push_back(deviceType); + } + + void clear() { + names.clear(); + addresses.clear(); + rssis.clear(); + fastPairs.clear(); + hfps.clear(); + types.clear(); + } + }; + + TargetSelectionCallbacks callbacks; + pBLEScan->setScanCallbacks(&callbacks); + + const int ACTIVE_SCAN_TIME = 15; + const int PASSIVE_SCAN_TIME = 15; + + // === ACTIVE SCAN === + pBLEScan->setActiveScan(true); + pBLEScan->setInterval(SCAN_INT); + pBLEScan->setWindow(SCAN_WINDOW); tft.setCursor(20, 80); tft.print("Active scan (15s)..."); -#ifdef NIMBLE_V2_PLUS - NimBLEScanResults results = pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); -#else - NimBLEScanResults results = pBLEScan->start(ACTIVE_SCAN_TIME, false); -#endif + pBLEScan->start(ACTIVE_SCAN_TIME * 1000, false); + + // === PASSIVE SCAN === + pBLEScan->setActiveScan(false); + pBLEScan->setInterval(SCAN_INT); + pBLEScan->setWindow(SCAN_WINDOW); tft.setCursor(20, 100); tft.print("Passive scan (15s)..."); - pBLEScan->setActiveScan(false); -#ifdef NIMBLE_V2_PLUS - results = pBLEScan->getResults(PASSIVE_SCAN_TIME * 1000, false); -#else - results = pBLEScan->start(PASSIVE_SCAN_TIME, false); -#endif + pBLEScan->start(PASSIVE_SCAN_TIME * 1000, false); + + // Transfer collected data to scannerData + for (size_t i = 0; i < callbacks.addresses.size(); i++) { + scannerData.addDevice( + callbacks.names[i], + callbacks.addresses[i], + callbacks.rssis[i], + callbacks.fastPairs[i], + callbacks.hfps[i], + callbacks.types[i] + ); + } - if (results.getCount() == 0) { - pBLEScan->stop(); - pBLEScan->clearResults(); - if (!bleWasActiveBefore) { stopBLEStack(); } + pBLEScan->stop(); + pBLEScan->clearResults(); + if (!bleWasActiveBefore) { stopBLEStack(); } + + size_t deviceCount = scannerData.size(); + if (deviceCount == 0) { tft.fillScreen(TFT_YELLOW); tft.drawRect(5, 5, tftWidth - 10, tftHeight - 10, TFT_BLACK); tft.setTextColor(TFT_BLACK, TFT_YELLOW); @@ -4108,39 +4198,7 @@ String selectTargetFromScan(const char *title) { return ""; } - for (int i = 0; i < results.getCount(); i++) { - const NimBLEAdvertisedDevice *device = results.getDevice(i); - - String address = String(device->getAddress().toString().c_str()); - String name = String(device->getName().c_str()); - if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") name = "Unknown"; - - int rssi = device->getRSSI(); - if (rssi == 0) rssi = -100; - - bool fastPair = false, hasHFP = false; - uint8_t deviceType = 0; - - if (device->haveServiceUUID()) { - NimBLEUUID uuid = device->getServiceUUID(); - std::string uuidStr = uuid.toString(); - if (uuidStr.find("fe2c") != std::string::npos) fastPair = true; - if (uuidStr.find("111e") != std::string::npos || uuidStr.find("111f") != std::string::npos) - hasHFP = true; - if (uuidStr.find("110e") != std::string::npos || uuidStr.find("110f") != std::string::npos) - deviceType |= 0x01; - if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; - } - - scannerData.addDevice(name, address, rssi, fastPair, hasHFP, deviceType); - } - - pBLEScan->stop(); - pBLEScan->clearResults(); - if (!bleWasActiveBefore) { stopBLEStack(); } - - size_t deviceCount = scannerData.size(); - + // Sort devices by FastPair first, then RSSI if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { for (size_t i = 0; scannerData.deviceAddresses.size() > 1 && i < scannerData.deviceAddresses.size() - 1; i++) { for (size_t j = i + 1; j < scannerData.deviceAddresses.size(); j++) { @@ -4169,6 +4227,7 @@ String selectTargetFromScan(const char *title) { xSemaphoreGive(scannerData.mutex); } + // Display device selection menu int maxVisibleDevices = 3, deviceItemHeight = 30, menuStartY = 60; int selectedIdx = 0, scrollOffset = 0; int lastSelected = -1, lastScrollOffset = -1; From 90bb252948f49a6ac79fa96ae46a401d88a6a952 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 20:39:33 +0200 Subject: [PATCH 11/74] Fix title truncation and NimBLE compatibility Updated title truncation logic and added 'const' for compatibility. --- src/modules/ble/BLE_Suite.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index e6d4fb107..f1e4e701f 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -4063,7 +4063,7 @@ String selectTargetFromScan(const char *title) { String titleStr = String(title); int maxTitleWidth = tftWidth - 20; // Leave 10px padding on each side if (tft.textWidth(titleStr.c_str()) > maxTitleWidth) { - while (titleStr.length() > 0 && tft.textWidth(titleStr.c_str() + "...") > maxTitleWidth) { + while (titleStr.length() > 0 && tft.textWidth((titleStr + "...").c_str()) > maxTitleWidth) { titleStr.remove(titleStr.length() - 1); } titleStr += "..."; @@ -4085,7 +4085,8 @@ String selectTargetFromScan(const char *title) { std::vector hfps; std::vector types; - void onResult(NimBLEAdvertisedDevice *advertisedDevice) override { + // FIX: Add 'const' for NimBLE v2.5 compatibility + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { String name = String(advertisedDevice->getName().c_str()); if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { name = "Unknown"; From 854bb2f3c2d067160e44d51dda222c37f0aec17d Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 21:27:31 +0200 Subject: [PATCH 12/74] Update BLE Suite version and scan time settings Updated BLE Suite version and adjusted scan times. --- src/modules/ble/BLE_Suite.cpp | 73 ++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index f1e4e701f..da2140692 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -2,7 +2,7 @@ * BLE Suite v3.1 - Complete BLE attack and analysis toolkit * Author: Ninja-jr * Version: 3.1 - * Last Updated: 14/07/2026 + * Last Updated: 2026-01-24 * * Contains: Vulnerability scanning, HID attacks, FastPair exploits, * HFP attacks, Audio attacks, DuckyScript injection, @@ -4061,32 +4061,37 @@ String selectTargetFromScan(const char *title) { tft.setTextSize(2); // FIX: Truncate title if too long for small screens String titleStr = String(title); - int maxTitleWidth = tftWidth - 20; // Leave 10px padding on each side + int maxTitleWidth = tftWidth - 20; if (tft.textWidth(titleStr.c_str()) > maxTitleWidth) { while (titleStr.length() > 0 && tft.textWidth((titleStr + "...").c_str()) > maxTitleWidth) { titleStr.remove(titleStr.length() - 1); } titleStr += "..."; } - tft.setCursor(10, 15); // Left align with padding + tft.setCursor(10, 15); tft.print(titleStr); tft.setTextSize(1); tft.setCursor(20, 60); tft.print("Scanning for devices..."); - // Custom callback class to collect devices + // Simple callback class with fixed buffers to avoid memory issues class TargetSelectionCallbacks : public NimBLEScanCallbacks { public: - std::vector names; - std::vector addresses; - std::vector rssis; - std::vector fastPairs; - std::vector hfps; - std::vector types; - - // FIX: Add 'const' for NimBLE v2.5 compatibility + static const int MAX_DEVICES = 100; + String names[MAX_DEVICES]; + String addresses[MAX_DEVICES]; + int rssis[MAX_DEVICES]; + bool fastPairs[MAX_DEVICES]; + bool hfps[MAX_DEVICES]; + uint8_t types[MAX_DEVICES]; + int count; + + TargetSelectionCallbacks() : count(0) {} + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { + if (count >= MAX_DEVICES) return; + String name = String(advertisedDevice->getName().c_str()); if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { name = "Unknown"; @@ -4110,38 +4115,29 @@ String selectTargetFromScan(const char *title) { if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; } - // Check for duplicates (update RSSI if exists) - for (size_t i = 0; i < addresses.size(); i++) { + // Check for duplicates + for (int i = 0; i < count; i++) { if (addresses[i] == address) { - // Update RSSI with strongest signal if (rssi > rssis[i]) rssis[i] = rssi; return; } } - names.push_back(name); - addresses.push_back(address); - rssis.push_back(rssi); - fastPairs.push_back(fastPair); - hfps.push_back(hasHFP); - types.push_back(deviceType); - } - - void clear() { - names.clear(); - addresses.clear(); - rssis.clear(); - fastPairs.clear(); - hfps.clear(); - types.clear(); + names[count] = name; + addresses[count] = address; + rssis[count] = rssi; + fastPairs[count] = fastPair; + hfps[count] = hasHFP; + types[count] = deviceType; + count++; } }; TargetSelectionCallbacks callbacks; pBLEScan->setScanCallbacks(&callbacks); - const int ACTIVE_SCAN_TIME = 15; - const int PASSIVE_SCAN_TIME = 15; + const int ACTIVE_SCAN_TIME = 10; + const int PASSIVE_SCAN_TIME = 10; // === ACTIVE SCAN === pBLEScan->setActiveScan(true); @@ -4149,7 +4145,7 @@ String selectTargetFromScan(const char *title) { pBLEScan->setWindow(SCAN_WINDOW); tft.setCursor(20, 80); - tft.print("Active scan (15s)..."); + tft.print("Active scan (10s)..."); pBLEScan->start(ACTIVE_SCAN_TIME * 1000, false); @@ -4159,12 +4155,12 @@ String selectTargetFromScan(const char *title) { pBLEScan->setWindow(SCAN_WINDOW); tft.setCursor(20, 100); - tft.print("Passive scan (15s)..."); + tft.print("Passive scan (10s)..."); pBLEScan->start(PASSIVE_SCAN_TIME * 1000, false); // Transfer collected data to scannerData - for (size_t i = 0; i < callbacks.addresses.size(); i++) { + for (int i = 0; i < callbacks.count; i++) { scannerData.addDevice( callbacks.names[i], callbacks.addresses[i], @@ -4175,9 +4171,14 @@ String selectTargetFromScan(const char *title) { ); } + // Clear callbacks to prevent dangling pointer + pBLEScan->setScanCallbacks(nullptr); pBLEScan->stop(); pBLEScan->clearResults(); - if (!bleWasActiveBefore) { stopBLEStack(); } + + if (!bleWasActiveBefore) { + stopBLEStack(); + } size_t deviceCount = scannerData.size(); From 1e01d185588030a4ad6c75b0f4e4fe9beb2c77a9 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 21:28:15 +0200 Subject: [PATCH 13/74] Update last updated date in BLE_Suite.cpp --- src/modules/ble/BLE_Suite.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index da2140692..1ed303520 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -2,7 +2,7 @@ * BLE Suite v3.1 - Complete BLE attack and analysis toolkit * Author: Ninja-jr * Version: 3.1 - * Last Updated: 2026-01-24 + * Last Updated: 14/07/2026 * * Contains: Vulnerability scanning, HID attacks, FastPair exploits, * HFP attacks, Audio attacks, DuckyScript injection, From 25fdea5db4eaeef19236331c76aa63ccdff001ad Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 22:05:42 +0200 Subject: [PATCH 14/74] Refactor TargetSelectionCallbacks for better clarity Updated callback class to use enum for compile-time constant and improved comment clarity. --- src/modules/ble/BLE_Suite.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index 1ed303520..8130d2f0c 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -4075,10 +4075,10 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 60); tft.print("Scanning for devices..."); - // Simple callback class with fixed buffers to avoid memory issues + // Simple callback class with fixed buffers - using enum for compile-time constant class TargetSelectionCallbacks : public NimBLEScanCallbacks { public: - static const int MAX_DEVICES = 100; + enum { MAX_DEVICES = 100 }; String names[MAX_DEVICES]; String addresses[MAX_DEVICES]; int rssis[MAX_DEVICES]; From 1343e5f7f28a0dd057d234c13a2f81b061f99a74 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Tue, 14 Jul 2026 22:50:39 +0200 Subject: [PATCH 15/74] Refactor BLE scanning to unify active and passive methods Updated BLE scanning process to use a unified approach for both active and passive scans, improving code clarity and functionality. --- src/modules/ble/BLE_Suite.cpp | 172 +++++++++++++++++----------------- 1 file changed, 87 insertions(+), 85 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index 8130d2f0c..a6630fff9 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -4038,7 +4038,7 @@ void BLE_Sniffer() { } //============================================================================= -// Target Selection Functions - FIXED with Active + Passive callback-based scan +// Target Selection Functions - FIXED with Active + Passive scan using getResults() //============================================================================= String selectTargetFromScan(const char *title) { @@ -4051,7 +4051,7 @@ String selectTargetFromScan(const char *title) { bleWasActiveBefore || BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0; #endif - // Use the callback-based scan approach (same as ble_scan()) + // Use the scan setup from ble_common if (!ble_scan_setup() || pBLEScan == nullptr) return ""; tft.fillScreen(bruceConfig.bgColor); @@ -4075,69 +4075,8 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 60); tft.print("Scanning for devices..."); - // Simple callback class with fixed buffers - using enum for compile-time constant - class TargetSelectionCallbacks : public NimBLEScanCallbacks { - public: - enum { MAX_DEVICES = 100 }; - String names[MAX_DEVICES]; - String addresses[MAX_DEVICES]; - int rssis[MAX_DEVICES]; - bool fastPairs[MAX_DEVICES]; - bool hfps[MAX_DEVICES]; - uint8_t types[MAX_DEVICES]; - int count; - - TargetSelectionCallbacks() : count(0) {} - - void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { - if (count >= MAX_DEVICES) return; - - String name = String(advertisedDevice->getName().c_str()); - if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { - name = "Unknown"; - } - - String address = String(advertisedDevice->getAddress().toString().c_str()); - int rssi = advertisedDevice->getRSSI(); - if (rssi == 0) rssi = -100; - - bool fastPair = false, hasHFP = false; - uint8_t deviceType = 0; - - if (advertisedDevice->haveServiceUUID()) { - NimBLEUUID uuid = advertisedDevice->getServiceUUID(); - std::string uuidStr = uuid.toString(); - if (uuidStr.find("fe2c") != std::string::npos) fastPair = true; - if (uuidStr.find("111e") != std::string::npos || uuidStr.find("111f") != std::string::npos) - hasHFP = true; - if (uuidStr.find("110e") != std::string::npos || uuidStr.find("110f") != std::string::npos) - deviceType |= 0x01; - if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; - } - - // Check for duplicates - for (int i = 0; i < count; i++) { - if (addresses[i] == address) { - if (rssi > rssis[i]) rssis[i] = rssi; - return; - } - } - - names[count] = name; - addresses[count] = address; - rssis[count] = rssi; - fastPairs[count] = fastPair; - hfps[count] = hasHFP; - types[count] = deviceType; - count++; - } - }; - - TargetSelectionCallbacks callbacks; - pBLEScan->setScanCallbacks(&callbacks); - - const int ACTIVE_SCAN_TIME = 10; - const int PASSIVE_SCAN_TIME = 10; + const int ACTIVE_SCAN_TIME = 15; + const int PASSIVE_SCAN_TIME = 15; // === ACTIVE SCAN === pBLEScan->setActiveScan(true); @@ -4145,9 +4084,13 @@ String selectTargetFromScan(const char *title) { pBLEScan->setWindow(SCAN_WINDOW); tft.setCursor(20, 80); - tft.print("Active scan (10s)..."); + tft.print("Active scan (15s)..."); - pBLEScan->start(ACTIVE_SCAN_TIME * 1000, false); +#ifdef NIMBLE_V2_PLUS + BLEScanResults activeResults = pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); +#else + BLEScanResults activeResults = pBLEScan->start(ACTIVE_SCAN_TIME, false); +#endif // === PASSIVE SCAN === pBLEScan->setActiveScan(false); @@ -4155,24 +4098,83 @@ String selectTargetFromScan(const char *title) { pBLEScan->setWindow(SCAN_WINDOW); tft.setCursor(20, 100); - tft.print("Passive scan (10s)..."); + tft.print("Passive scan (15s)..."); + +#ifdef NIMBLE_V2_PLUS + BLEScanResults passiveResults = pBLEScan->getResults(PASSIVE_SCAN_TIME * 1000, false); +#else + BLEScanResults passiveResults = pBLEScan->start(PASSIVE_SCAN_TIME, false); +#endif + + // Merge results from both scans + std::vector mergedAddresses; + std::vector mergedNames; + std::vector mergedRssis; + std::vector mergedFastPairs; + std::vector mergedHfps; + std::vector mergedTypes; - pBLEScan->start(PASSIVE_SCAN_TIME * 1000, false); + auto processDevice = [&](const NimBLEAdvertisedDevice *device) { + String address = String(device->getAddress().toString().c_str()); + String name = String(device->getName().c_str()); + if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { + name = "Unknown"; + } + int rssi = device->getRSSI(); + if (rssi == 0) rssi = -100; + + bool fastPair = false, hasHFP = false; + uint8_t deviceType = 0; + + if (device->haveServiceUUID()) { + NimBLEUUID uuid = device->getServiceUUID(); + std::string uuidStr = uuid.toString(); + if (uuidStr.find("fe2c") != std::string::npos) fastPair = true; + if (uuidStr.find("111e") != std::string::npos || uuidStr.find("111f") != std::string::npos) + hasHFP = true; + if (uuidStr.find("110e") != std::string::npos || uuidStr.find("110f") != std::string::npos) + deviceType |= 0x01; + if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; + } + + // Check for duplicates + for (size_t i = 0; i < mergedAddresses.size(); i++) { + if (mergedAddresses[i] == address) { + if (rssi > mergedRssis[i]) mergedRssis[i] = rssi; + return; + } + } + + mergedAddresses.push_back(address); + mergedNames.push_back(name); + mergedRssis.push_back(rssi); + mergedFastPairs.push_back(fastPair); + mergedHfps.push_back(hasHFP); + mergedTypes.push_back(deviceType); + }; + + // Process active results + for (int i = 0; i < activeResults.getCount(); i++) { + processDevice(activeResults.getDevice(i)); + } + + // Process passive results + for (int i = 0; i < passiveResults.getCount(); i++) { + processDevice(passiveResults.getDevice(i)); + } - // Transfer collected data to scannerData - for (int i = 0; i < callbacks.count; i++) { + // Transfer to scannerData + for (size_t i = 0; i < mergedAddresses.size(); i++) { scannerData.addDevice( - callbacks.names[i], - callbacks.addresses[i], - callbacks.rssis[i], - callbacks.fastPairs[i], - callbacks.hfps[i], - callbacks.types[i] + mergedNames[i], + mergedAddresses[i], + mergedRssis[i], + mergedFastPairs[i], + mergedHfps[i], + mergedTypes[i] ); } - // Clear callbacks to prevent dangling pointer - pBLEScan->setScanCallbacks(nullptr); pBLEScan->stop(); pBLEScan->clearResults(); @@ -4771,8 +4773,8 @@ void showWelcomeScreen() { tft.setTextColor(TFT_BLUE, bruceConfig.bgColor); tft.setTextSize(2); - tft.setCursor((tftWidth - tft.textWidth("BLE SUITE v3.1")) / 2, 80); - tft.print("BLE SUITE v3.1"); + tft.setCursor((tftWidth - tft.textWidth("BLE SUITE")) / 2, 80); + tft.print("BLE SUITE"); tft.setTextColor(TFT_LIGHTGREY, bruceConfig.bgColor); tft.setTextSize(1.5); @@ -4813,8 +4815,8 @@ void BleSuiteMenu() { tft.setTextColor(TFT_WHITE, bruceConfig.bgColor); tft.setTextSize(2); - tft.setCursor((tftWidth - tft.textWidth("BLE SUITE v3.1")) / 2, 15); - tft.print("BLE SUITE v3.1"); + tft.setCursor((tftWidth - tft.textWidth("BLE SUITE")) / 2, 15); + tft.print("BLE SUITE"); tft.setTextSize(1); for (int i = 0; i < maxVisible && (scrollOffset + i) < MENU_ITEMS; i++) { @@ -5837,8 +5839,8 @@ void showAttackProgress(const char *message, uint16_t color) { tft.setTextColor(TFT_WHITE, bruceConfig.bgColor); tft.setTextSize(2); - tft.setCursor((tftWidth - tft.textWidth("BLE SUITE v3.1")) / 2, 15); - tft.print("BLE SUITE v3.1"); + tft.setCursor((tftWidth - tft.textWidth("BLE SUITE")) / 2, 15); + tft.print("BLE SUITE"); tft.setTextSize(1); tft.setTextColor(color, bruceConfig.bgColor); From b52900834186aafe53d924f085b8026a640da871 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 00:32:09 +0200 Subject: [PATCH 16/74] Reduce active and passive scan time to 10 seconds --- src/modules/ble/BLE_Suite.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index a6630fff9..a00106a1b 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -4075,8 +4075,8 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 60); tft.print("Scanning for devices..."); - const int ACTIVE_SCAN_TIME = 15; - const int PASSIVE_SCAN_TIME = 15; + const int ACTIVE_SCAN_TIME = 10; + const int PASSIVE_SCAN_TIME = 10; // === ACTIVE SCAN === pBLEScan->setActiveScan(true); @@ -4084,7 +4084,7 @@ String selectTargetFromScan(const char *title) { pBLEScan->setWindow(SCAN_WINDOW); tft.setCursor(20, 80); - tft.print("Active scan (15s)..."); + tft.print("Active scan (10s)..."); #ifdef NIMBLE_V2_PLUS BLEScanResults activeResults = pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); @@ -4098,7 +4098,7 @@ String selectTargetFromScan(const char *title) { pBLEScan->setWindow(SCAN_WINDOW); tft.setCursor(20, 100); - tft.print("Passive scan (15s)..."); + tft.print("Passive scan (10s)..."); #ifdef NIMBLE_V2_PLUS BLEScanResults passiveResults = pBLEScan->getResults(PASSIVE_SCAN_TIME * 1000, false); @@ -4175,6 +4175,7 @@ String selectTargetFromScan(const char *title) { ); } + // SAFER CLEANUP - Only stop BLE if we started it pBLEScan->stop(); pBLEScan->clearResults(); From 928677cc759eebefd955bd2f5a32e57074b6471b Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 03:25:09 +0200 Subject: [PATCH 17/74] Enhance BLE Suite with snapshot support and refactor Added snapshot support for device scanning. Modified semaphore timeout values and refactored several functions to use the new SelectedDevice structure for better data handling. --- src/modules/ble/BLE_Suite.cpp | 540 +++++++++++++++++----------------- 1 file changed, 274 insertions(+), 266 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index a00106a1b..b1ed34627 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -2,7 +2,7 @@ * BLE Suite v3.1 - Complete BLE attack and analysis toolkit * Author: Ninja-jr * Version: 3.1 - * Last Updated: 14/07/2026 + * Last Updated: 15/07/2026 * * Contains: Vulnerability scanning, HID attacks, FastPair exploits, * HFP attacks, Audio attacks, DuckyScript injection, @@ -40,6 +40,21 @@ bool BLEStateManager::bleInitialized = false; std::vector BLEStateManager::activeClients; String BLEStateManager::currentDeviceName = ""; +// Scan state management +static NimBLEScan* g_pBLEScan = nullptr; +static bool g_bleScanActive = false; + +// Device selection cache +struct SelectedDevice { + String address; + String name; + int rssi; + bool hasFastPair; + bool hasHFP; + uint8_t deviceType; +}; +static SelectedDevice g_selectedDevice; + //============================================================================= // v3.1: Samsung MAC OUI Detection //============================================================================= @@ -67,22 +82,40 @@ bool isSamsungDevice(const String &mac) { FastPairVersion detectFastPairVersion(NimBLEAddress target) { return FP_VERSION_2; } //============================================================================= -// ScannerData Implementation +// ScannerData Implementation with Snapshot Support //============================================================================= +struct DeviceSnapshot { + uint32_t version; + uint32_t count; + uint32_t timestamp; + std::vector names; + std::vector addresses; + std::vector rssi; + std::vector fastPair; + std::vector hfp; + std::vector types; + + DeviceSnapshot() : version(0), count(0), timestamp(0) {} +}; + ScannerData::ScannerData() { mutex = xSemaphoreCreateMutex(); foundCount = 0; + dataVersion = 0; + snapshotCache = nullptr; + cacheTimestamp = 0; } ScannerData::~ScannerData() { if (mutex) vSemaphoreDelete(mutex); + if (snapshotCache) delete snapshotCache; } void ScannerData::addDevice( const String &name, const String &address, int rssi, bool fastPair, bool hasHFP, uint8_t type ) { - if (xSemaphoreTake(mutex, portMAX_DELAY)) { + if (xSemaphoreTake(mutex, 10 / portTICK_PERIOD_MS)) { bool isDuplicate = false; for (size_t i = 0; i < deviceAddresses.size(); i++) { if (deviceAddresses[i] == address) { @@ -99,13 +132,65 @@ void ScannerData::addDevice( deviceHasHFP.push_back(hasHFP); deviceTypes.push_back(type); foundCount++; + dataVersion++; + + if (snapshotCache) { + delete snapshotCache; + snapshotCache = nullptr; + } } xSemaphoreGive(mutex); } } +DeviceSnapshot* ScannerData::getSnapshot() { + if (snapshotCache && (millis() - cacheTimestamp) < 1000) { + return snapshotCache; + } + + if (xSemaphoreTake(mutex, 50 / portTICK_PERIOD_MS)) { + if (snapshotCache) { + delete snapshotCache; + snapshotCache = nullptr; + } + + snapshotCache = new DeviceSnapshot(); + snapshotCache->version = dataVersion; + snapshotCache->count = deviceAddresses.size(); + snapshotCache->timestamp = millis(); + snapshotCache->names = deviceNames; + snapshotCache->addresses = deviceAddresses; + snapshotCache->rssi = deviceRssi; + snapshotCache->fastPair = deviceFastPair; + snapshotCache->hfp = deviceHasHFP; + snapshotCache->types = deviceTypes; + + cacheTimestamp = millis(); + xSemaphoreGive(mutex); + return snapshotCache; + } + return nullptr; +} + +bool ScannerData::getDeviceInfo(int index, DeviceInfo &info) { + bool success = false; + if (xSemaphoreTake(mutex, 10 / portTICK_PERIOD_MS)) { + if (index >= 0 && index < (int)deviceAddresses.size()) { + info.address = deviceAddresses[index]; + info.name = deviceNames[index]; + info.rssi = deviceRssi[index]; + info.hasFastPair = deviceFastPair[index]; + info.hasHFP = deviceHasHFP[index]; + info.deviceType = deviceTypes[index]; + success = true; + } + xSemaphoreGive(mutex); + } + return success; +} + void ScannerData::clear() { - if (xSemaphoreTake(mutex, portMAX_DELAY)) { + if (xSemaphoreTake(mutex, 50 / portTICK_PERIOD_MS)) { deviceNames.clear(); deviceAddresses.clear(); deviceRssi.clear(); @@ -113,13 +198,19 @@ void ScannerData::clear() { deviceHasHFP.clear(); deviceTypes.clear(); foundCount = 0; + dataVersion++; + + if (snapshotCache) { + delete snapshotCache; + snapshotCache = nullptr; + } xSemaphoreGive(mutex); } } size_t ScannerData::size() { size_t result = 0; - if (xSemaphoreTake(mutex, portMAX_DELAY)) { + if (xSemaphoreTake(mutex, 10 / portTICK_PERIOD_MS)) { result = deviceAddresses.size(); xSemaphoreGive(mutex); } @@ -424,14 +515,17 @@ NimBLEClient *attemptConnectionWithStrategies(NimBLEAddress target, String &conn } bool hasHFP = false; - if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { - for (size_t i = 0; i < scannerData.deviceAddresses.size(); i++) { - if (scannerData.deviceAddresses[i] == target.toString().c_str()) { - hasHFP = scannerData.deviceHasHFP[i]; - break; + DeviceInfo info; + if (scannerData.getDeviceInfo(0, info)) { + // Check if target matches any device in scanner data + for (size_t i = 0; i < scannerData.size(); i++) { + if (scannerData.getDeviceInfo(i, info)) { + if (info.address == target.toString().c_str()) { + hasHFP = info.hasHFP; + break; + } } } - xSemaphoreGive(scannerData.mutex); } if (hasHFP) { @@ -1880,16 +1974,16 @@ bool HIDDuckyService::injectDuckyScript(NimBLEAddress target, const String &scri bool hasHFP = false; String deviceName = ""; + DeviceInfo info; - if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { - for (size_t i = 0; i < scannerData.deviceAddresses.size(); i++) { - if (scannerData.deviceAddresses[i] == target.toString().c_str()) { - deviceName = scannerData.deviceNames[i]; - hasHFP = scannerData.deviceHasHFP[i]; + for (size_t i = 0; i < scannerData.size(); i++) { + if (scannerData.getDeviceInfo(i, info)) { + if (info.address == target.toString().c_str()) { + deviceName = info.name; + hasHFP = info.hasHFP; break; } } - xSemaphoreGive(scannerData.mutex); } if (hasHFP && !deviceName.isEmpty()) { @@ -2564,16 +2658,16 @@ bool HIDAttackServiceClass::injectKeystrokes(NimBLEAddress target) { bool hasHFP = false; String deviceName = ""; + DeviceInfo info; - if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { - for (size_t i = 0; i < scannerData.deviceAddresses.size(); i++) { - if (scannerData.deviceAddresses[i] == target.toString().c_str()) { - deviceName = scannerData.deviceNames[i]; - hasHFP = scannerData.deviceHasHFP[i]; + for (size_t i = 0; i < scannerData.size(); i++) { + if (scannerData.getDeviceInfo(i, info)) { + if (info.address == target.toString().c_str()) { + deviceName = info.name; + hasHFP = info.hasHFP; break; } } - xSemaphoreGive(scannerData.mutex); } if (hasHFP && !deviceName.isEmpty()) { @@ -4038,28 +4132,43 @@ void BLE_Sniffer() { } //============================================================================= -// Target Selection Functions - FIXED with Active + Passive scan using getResults() +// Target Selection Functions - Uses snapshot for safe data access //============================================================================= String selectTargetFromScan(const char *title) { scannerData.clear(); - - // Check if BLE is already active + g_selectedDevice.address = ""; + g_selectedDevice.name = ""; + bool bleWasActiveBefore = BLEConnected || (BLEDevice::getServer() != nullptr); #if !defined(LITE_VERSION) - bleWasActiveBefore = - bleWasActiveBefore || BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0; + bleWasActiveBefore = bleWasActiveBefore || BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0; #endif - // Use the scan setup from ble_common - if (!ble_scan_setup() || pBLEScan == nullptr) return ""; + if (!bleWasActiveBefore) { + if (!BLEStateManager::initBLE("Bruce-Scanner", ESP_PWR_LVL_P9)) { + displayError("Failed to init BLE"); + return ""; + } + } + + if (g_pBLEScan == nullptr) { + g_pBLEScan = NimBLEDevice::getScan(); + if (!g_pBLEScan) { + displayError("Failed to get scanner"); + return ""; + } + g_pBLEScan->setActiveScan(true); + g_pBLEScan->setInterval(SCAN_INT); + g_pBLEScan->setWindow(SCAN_WINDOW); + g_pBLEScan->setDuplicateFilter(false); + } tft.fillScreen(bruceConfig.bgColor); tft.drawRect(5, 5, tftWidth - 10, tftHeight - 10, TFT_WHITE); tft.setTextColor(TFT_WHITE, bruceConfig.bgColor); tft.setTextSize(2); - // FIX: Truncate title if too long for small screens String titleStr = String(title); int maxTitleWidth = tftWidth - 20; if (tft.textWidth(titleStr.c_str()) > maxTitleWidth) { @@ -4075,46 +4184,53 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 60); tft.print("Scanning for devices..."); - const int ACTIVE_SCAN_TIME = 10; - const int PASSIVE_SCAN_TIME = 10; - - // === ACTIVE SCAN === - pBLEScan->setActiveScan(true); - pBLEScan->setInterval(SCAN_INT); - pBLEScan->setWindow(SCAN_WINDOW); + const int ACTIVE_SCAN_TIME = 8; + const int PASSIVE_SCAN_TIME = 8; + g_pBLEScan->setActiveScan(true); tft.setCursor(20, 80); - tft.print("Active scan (10s)..."); + tft.print("Active scan (8s)..."); + g_pBLEScan->clearResults(); + BLEScanResults activeResults = g_pBLEScan->start(ACTIVE_SCAN_TIME, false); -#ifdef NIMBLE_V2_PLUS - BLEScanResults activeResults = pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); -#else - BLEScanResults activeResults = pBLEScan->start(ACTIVE_SCAN_TIME, false); -#endif + for (int i = 0; i < activeResults.getCount(); i++) { + const NimBLEAdvertisedDevice *device = activeResults.getDevice(i); + if (!device) continue; + + String address = String(device->getAddress().toString().c_str()); + String name = String(device->getName().c_str()); + if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { + name = "Unknown"; + } + int rssi = device->getRSSI(); + if (rssi == 0) rssi = -100; - // === PASSIVE SCAN === - pBLEScan->setActiveScan(false); - pBLEScan->setInterval(SCAN_INT); - pBLEScan->setWindow(SCAN_WINDOW); + bool fastPair = false, hasHFP = false; + uint8_t deviceType = 0; - tft.setCursor(20, 100); - tft.print("Passive scan (10s)..."); + if (device->haveServiceUUID()) { + NimBLEUUID uuid = device->getServiceUUID(); + std::string uuidStr = uuid.toString(); + if (uuidStr.find("fe2c") != std::string::npos) fastPair = true; + if (uuidStr.find("111e") != std::string::npos || uuidStr.find("111f") != std::string::npos) + hasHFP = true; + if (uuidStr.find("110e") != std::string::npos || uuidStr.find("110f") != std::string::npos) + deviceType |= 0x01; + if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; + } -#ifdef NIMBLE_V2_PLUS - BLEScanResults passiveResults = pBLEScan->getResults(PASSIVE_SCAN_TIME * 1000, false); -#else - BLEScanResults passiveResults = pBLEScan->start(PASSIVE_SCAN_TIME, false); -#endif + scannerData.addDevice(name, address, rssi, fastPair, hasHFP, deviceType); + } - // Merge results from both scans - std::vector mergedAddresses; - std::vector mergedNames; - std::vector mergedRssis; - std::vector mergedFastPairs; - std::vector mergedHfps; - std::vector mergedTypes; + g_pBLEScan->setActiveScan(false); + tft.setCursor(20, 100); + tft.print("Passive scan (8s)..."); + BLEScanResults passiveResults = g_pBLEScan->start(PASSIVE_SCAN_TIME, false); - auto processDevice = [&](const NimBLEAdvertisedDevice *device) { + for (int i = 0; i < passiveResults.getCount(); i++) { + const NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); + if (!device) continue; + String address = String(device->getAddress().toString().c_str()); String name = String(device->getName().c_str()); if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { @@ -4137,55 +4253,17 @@ String selectTargetFromScan(const char *title) { if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; } - // Check for duplicates - for (size_t i = 0; i < mergedAddresses.size(); i++) { - if (mergedAddresses[i] == address) { - if (rssi > mergedRssis[i]) mergedRssis[i] = rssi; - return; - } - } - - mergedAddresses.push_back(address); - mergedNames.push_back(name); - mergedRssis.push_back(rssi); - mergedFastPairs.push_back(fastPair); - mergedHfps.push_back(hasHFP); - mergedTypes.push_back(deviceType); - }; - - // Process active results - for (int i = 0; i < activeResults.getCount(); i++) { - processDevice(activeResults.getDevice(i)); + scannerData.addDevice(name, address, rssi, fastPair, hasHFP, deviceType); } - // Process passive results - for (int i = 0; i < passiveResults.getCount(); i++) { - processDevice(passiveResults.getDevice(i)); - } - - // Transfer to scannerData - for (size_t i = 0; i < mergedAddresses.size(); i++) { - scannerData.addDevice( - mergedNames[i], - mergedAddresses[i], - mergedRssis[i], - mergedFastPairs[i], - mergedHfps[i], - mergedTypes[i] - ); + if (g_pBLEScan) { + g_pBLEScan->stop(); + g_bleScanActive = false; } - // SAFER CLEANUP - Only stop BLE if we started it - pBLEScan->stop(); - pBLEScan->clearResults(); - - if (!bleWasActiveBefore) { - stopBLEStack(); - } - - size_t deviceCount = scannerData.size(); - - if (deviceCount == 0) { + DeviceSnapshot* snapshot = scannerData.getSnapshot(); + if (!snapshot || snapshot->count == 0) { + if (snapshot) delete snapshot; tft.fillScreen(TFT_YELLOW); tft.drawRect(5, 5, tftWidth - 10, tftHeight - 10, TFT_BLACK); tft.setTextColor(TFT_BLACK, TFT_YELLOW); @@ -4200,40 +4278,31 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 100); tft.print("turned on and in range."); delay(2000); + scannerData.clear(); return ""; } - // Sort devices by FastPair first, then RSSI - if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { - for (size_t i = 0; scannerData.deviceAddresses.size() > 1 && i < scannerData.deviceAddresses.size() - 1; i++) { - for (size_t j = i + 1; j < scannerData.deviceAddresses.size(); j++) { - bool swapNeeded = false; - if (scannerData.deviceFastPair[j] && !scannerData.deviceFastPair[i]) swapNeeded = true; - else if (scannerData.deviceFastPair[j] == scannerData.deviceFastPair[i] && - scannerData.deviceRssi[j] > scannerData.deviceRssi[i]) - swapNeeded = true; - - if (swapNeeded) { - std::swap(scannerData.deviceNames[i], scannerData.deviceNames[j]); - std::swap(scannerData.deviceAddresses[i], scannerData.deviceAddresses[j]); - std::swap(scannerData.deviceRssi[i], scannerData.deviceRssi[j]); - - bool tempFastPair = scannerData.deviceFastPair[i]; - scannerData.deviceFastPair[i] = scannerData.deviceFastPair[j]; - scannerData.deviceFastPair[j] = tempFastPair; - - bool tempHFP = scannerData.deviceHasHFP[i]; - scannerData.deviceHasHFP[i] = scannerData.deviceHasHFP[j]; - scannerData.deviceHasHFP[j] = tempHFP; - std::swap(scannerData.deviceTypes[i], scannerData.deviceTypes[j]); - } + size_t deviceCount = snapshot->count; + for (size_t i = 0; i < deviceCount - 1; i++) { + for (size_t j = i + 1; j < deviceCount; j++) { + bool swapNeeded = false; + if (snapshot->fastPair[j] && !snapshot->fastPair[i]) swapNeeded = true; + else if (snapshot->fastPair[j] == snapshot->fastPair[i] && + snapshot->rssi[j] > snapshot->rssi[i]) + swapNeeded = true; + + if (swapNeeded) { + std::swap(snapshot->names[i], snapshot->names[j]); + std::swap(snapshot->addresses[i], snapshot->addresses[j]); + std::swap(snapshot->rssi[i], snapshot->rssi[j]); + std::swap(snapshot->fastPair[i], snapshot->fastPair[j]); + std::swap(snapshot->hfp[i], snapshot->hfp[j]); + std::swap(snapshot->types[i], snapshot->types[j]); } } - xSemaphoreGive(scannerData.mutex); } - // Display device selection menu - int maxVisibleDevices = 3, deviceItemHeight = 30, menuStartY = 60; + int maxVisibleDevices = 4, deviceItemHeight = 30, menuStartY = 60; int selectedIdx = 0, scrollOffset = 0; int lastSelected = -1, lastScrollOffset = -1; bool exitLoop = false; @@ -4256,38 +4325,19 @@ String selectTargetFromScan(const char *title) { tft.print(" devices"); for (int i = 0; i < maxVisibleDevices && (scrollOffset + i) < (int)deviceCount; i++) { - String displayName, address; - int rssi = 0; - bool fastPair = false, hasHFP = false; - uint8_t deviceType = 0; - - if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { - int deviceIndex = scrollOffset + i; - if (deviceIndex < (int)scannerData.deviceNames.size()) { - displayName = scannerData.deviceNames[deviceIndex]; - address = scannerData.deviceAddresses[deviceIndex]; - rssi = scannerData.deviceRssi[deviceIndex]; - fastPair = scannerData.deviceFastPair[deviceIndex]; - hasHFP = scannerData.deviceHasHFP[deviceIndex]; - deviceType = scannerData.deviceTypes[deviceIndex]; - } - xSemaphoreGive(scannerData.mutex); - } - - if (displayName.isEmpty()) continue; - - String displayText = displayName; - if (displayText.length() > 18) displayText = displayText.substring(0, 15) + "..."; - displayText += " (" + String(rssi) + "dB)"; - if (fastPair) displayText += " [FP]"; - if (hasHFP) displayText += " [HFP]"; - if (deviceType & 0x01) displayText += " [AUDIO]"; - if (deviceType & 0x02) displayText += " [HID]"; - + int idx = scrollOffset + i; int yPos = menuStartY + (i * deviceItemHeight); if (yPos + deviceItemHeight > tftHeight - 45) break; - if (i == selectedIdx - scrollOffset) { + String displayText = snapshot->names[idx]; + if (displayText.length() > 18) displayText = displayText.substring(0, 15) + "..."; + displayText += " (" + String(snapshot->rssi[idx]) + "dB)"; + if (snapshot->fastPair[idx]) displayText += " [FP]"; + if (snapshot->hfp[idx]) displayText += " [HFP]"; + if (snapshot->types[idx] & 0x01) displayText += " [AUDIO]"; + if (snapshot->types[idx] & 0x02) displayText += " [HID]"; + + if (idx == selectedIdx) { tft.fillRect(15, yPos, tftWidth - 30, deviceItemHeight - 5, TFT_WHITE); tft.setTextColor(TFT_BLACK, TFT_WHITE); tft.setCursor(20, yPos + 10); @@ -4341,23 +4391,20 @@ String selectTargetFromScan(const char *title) { scrollOffset = 0; } } else if (check(SelPress)) { - String selectedMAC = "", selectedName = ""; - - if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { - if (selectedIdx < (int)scannerData.deviceAddresses.size()) { - selectedMAC = scannerData.deviceAddresses[selectedIdx]; - selectedName = scannerData.deviceNames[selectedIdx]; - } - xSemaphoreGive(scannerData.mutex); - } - - if (!selectedMAC.isEmpty()) { - scannerData.clear(); - return selectedMAC + ":0"; - } + g_selectedDevice.address = snapshot->addresses[selectedIdx]; + g_selectedDevice.name = snapshot->names[selectedIdx]; + g_selectedDevice.rssi = snapshot->rssi[selectedIdx]; + g_selectedDevice.hasFastPair = snapshot->fastPair[selectedIdx]; + g_selectedDevice.hasHFP = snapshot->hfp[selectedIdx]; + g_selectedDevice.deviceType = snapshot->types[selectedIdx]; + + delete snapshot; + return g_selectedDevice.address + ":0"; } delay(50); } + + delete snapshot; scannerData.clear(); return ""; } @@ -4365,17 +4412,18 @@ String selectTargetFromScan(const char *title) { String selectMultipleTargetsFromScan(const char *title, std::vector &targets) { targets.clear(); - // Full multi-select implementation - if (scannerData.size() == 0) { + DeviceSnapshot* snapshot = scannerData.getSnapshot(); + if (!snapshot || snapshot->count == 0) { + if (snapshot) delete snapshot; showErrorMessage("No devices found. Run scan first."); return ""; } - std::vector selected(scannerData.size(), false); + std::vector selected(snapshot->count, false); int currentIndex = 0, scrollOffset = 0; bool exitMenu = false; + size_t deviceCount = snapshot->count; int menuStartY = 60, menuItemHeight = 25; - size_t deviceCount = scannerData.size(); int maxVisibleItems = (tftHeight - menuStartY - 50) / menuItemHeight; if (maxVisibleItems > (int)deviceCount) maxVisibleItems = deviceCount; @@ -4413,7 +4461,7 @@ String selectMultipleTargetsFromScan(const char *title, std::vectornames[idx] + " | " + snapshot->addresses[idx]; if (display.length() > 25) display = display.substring(0, 22) + "..."; tft.print(display); } @@ -4436,6 +4484,7 @@ String selectMultipleTargetsFromScan(const char *title, std::vectoraddresses[currentIndex].c_str()), BLE_ADDR_PUBLIC )); } else { for (auto it = targets.begin(); it != targets.end(); ++it) { - if (it->toString() == scannerData.deviceAddresses[currentIndex].c_str()) { + if (it->toString() == snapshot->addresses[currentIndex].c_str()) { targets.erase(it); break; } @@ -4475,6 +4524,7 @@ String selectMultipleTargetsFromScan(const char *title, std::vector availWidth) { String ellipsis = "..."; int ellipsisWidth = tft.textWidth(ellipsis.c_str()); @@ -5025,10 +5072,10 @@ int showSubMenu(const char *title, const char *options[], int optionCount) { } //============================================================================= -// Attack Submenus +// Attack Submenus - Updated to use SelectedDevice //============================================================================= -void showFastPairSubMenu(NimBLEAddress target) { +void showFastPairSubMenu(NimBLEAddress target, SelectedDevice deviceInfo) { const char *options[] = { "Quick Vulnerability Test", "Memory Corruption Attack", @@ -5112,7 +5159,7 @@ void showFastPairSubMenu(NimBLEAddress target) { } } -void showHFPSubMenu(NimBLEAddress target) { +void showHFPSubMenu(NimBLEAddress target, SelectedDevice deviceInfo) { const char *options[] = { "Test Vulnerability (CVE)", "Establish HFP Connection", "Full HFP Attack Chain", "HFP → HID Pivot" }; @@ -5130,7 +5177,7 @@ void showHFPSubMenu(NimBLEAddress target) { } } -void showAudioSubMenu(NimBLEAddress target) { +void showAudioSubMenu(NimBLEAddress target, SelectedDevice deviceInfo) { const char *options[] = { "AVRCP Media Control", "Audio Stack Crash", "Telephony Alert Test", "Run All Audio Tests" }; @@ -5169,7 +5216,7 @@ void showAudioSubMenu(NimBLEAddress target) { NimBLEDevice::deleteClient(pClient); } -void showHIDSubMenu(NimBLEAddress target) { +void showHIDSubMenu(NimBLEAddress target, SelectedDevice deviceInfo) { const char *options[] = { "Test HID Vulnerability", "Force HID Connection", @@ -5185,22 +5232,9 @@ void showHIDSubMenu(NimBLEAddress target) { HIDExploitEngine hid; HIDDuckyService ducky; - String deviceName = ""; - int rssi = -60; - if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { - for (size_t i = 0; i < scannerData.deviceAddresses.size(); i++) { - if (scannerData.deviceAddresses[i] == target.toString().c_str()) { - deviceName = scannerData.deviceNames[i]; - rssi = scannerData.deviceRssi[i]; - break; - } - } - xSemaphoreGive(scannerData.mutex); - } - switch (choice) { case 0: hid.testHIDVulnerability(target); break; - case 1: hid.forceHIDConnection(target, deviceName, rssi); break; + case 1: hid.forceHIDConnection(target, deviceInfo.name, deviceInfo.rssi); break; case 2: HIDAttackServiceClass().injectKeystrokes(target); break; case 3: { String script = getScriptFromUser(); @@ -5208,7 +5242,7 @@ void showHIDSubMenu(NimBLEAddress target) { break; } case 4: { - HIDDeviceProfile profile = hid.analyzeHIDDevice(target, deviceName, rssi); + HIDDeviceProfile profile = hid.analyzeHIDDevice(target, deviceInfo.name, deviceInfo.rssi); if (profile.isAppleDevice) hid.tryAppleMagicSpoof(target, profile); else if (profile.isWindowsDevice) hid.tryWindowsHIDBypass(target, profile); else if (profile.isAndroidDevice) hid.tryAndroidJustWorks(target, profile); @@ -5216,13 +5250,13 @@ void showHIDSubMenu(NimBLEAddress target) { } case 5: hid.testHIDVulnerability(target); - hid.forceHIDConnection(target, deviceName, rssi); + hid.forceHIDConnection(target, deviceInfo.name, deviceInfo.rssi); HIDAttackServiceClass().injectKeystrokes(target); break; } } -void showMemorySubMenu(NimBLEAddress target) { +void showMemorySubMenu(NimBLEAddress target, SelectedDevice deviceInfo) { const char *options[] = { "FastPair Memory Corruption", "FastPair State Confusion", @@ -5285,7 +5319,7 @@ void showMemorySubMenu(NimBLEAddress target) { NimBLEDevice::deleteClient(pClient); } -void showDoSSubMenu(NimBLEAddress target) { +void showDoSSubMenu(NimBLEAddress target, SelectedDevice deviceInfo) { const char *options[] = { "Connection Flood", "Advertising Spam", "Jam & Connect (NRF24)", "Protocol Fuzzer" }; @@ -5304,7 +5338,7 @@ void showDoSSubMenu(NimBLEAddress target) { } } -void showPayloadSubMenu(NimBLEAddress target) { +void showPayloadSubMenu(NimBLEAddress target, SelectedDevice deviceInfo) { const char *options[] = {"DuckyScript Injection", "PIN Brute Force", "Auth Bypass Suite"}; int choice = showSubMenu("Payload Delivery", options, 3); @@ -5321,7 +5355,7 @@ void showPayloadSubMenu(NimBLEAddress target) { } } -void showTestingSubMenu(NimBLEAddress target) { +void showTestingSubMenu(NimBLEAddress target, SelectedDevice deviceInfo) { const char *options[] = { "Write Access Test", "Audio Control Test", "Protocol Fuzzer", "HID Service Test" }; @@ -5338,39 +5372,24 @@ void showTestingSubMenu(NimBLEAddress target) { } //============================================================================= -// Attack Functions +// Attack Functions - Updated to use SelectedDevice //============================================================================= -void runUniversalAttack(NimBLEAddress target) { +void runUniversalAttack(NimBLEAddress target, SelectedDevice deviceInfo) { AutoCleanup cleanup([]() { BLEStateManager::deinitBLE(true); }); if (!confirmAttack("Execute universal attack chain (HFP + HID + FastPair)?")) return; - String deviceName = ""; - bool hasHFP = false, hasFastPair = false; - - if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { - for (size_t i = 0; i < scannerData.deviceAddresses.size(); i++) { - if (scannerData.deviceAddresses[i] == target.toString().c_str()) { - deviceName = scannerData.deviceNames[i]; - hasHFP = scannerData.deviceHasHFP[i]; - hasFastPair = scannerData.deviceFastPair[i]; - break; - } - } - xSemaphoreGive(scannerData.mutex); - } - std::vector lines = { "UNIVERSAL ATTACK CHAIN", - "Device: " + deviceName, - "HFP: " + String(hasHFP ? "YES" : "NO"), - "FastPair: " + String(hasFastPair ? "YES" : "NO") + "Device: " + deviceInfo.name, + "HFP: " + String(deviceInfo.hasHFP ? "YES" : "NO"), + "FastPair: " + String(deviceInfo.hasFastPair ? "YES" : "NO") }; bool hfpSuccess = false, fpSuccess = false, hidSuccess = false; - if (hasHFP) { + if (deviceInfo.hasHFP) { showAttackProgress("Phase 1: Testing HFP vulnerability...", TFT_CYAN); HFPExploitEngine hfp; hfpSuccess = hfp.executeHFPAttackChain(target); @@ -5384,7 +5403,7 @@ void runUniversalAttack(NimBLEAddress target) { } } - if (hasFastPair && (!hfpSuccess || !hidSuccess)) { + if (deviceInfo.hasFastPair && (!hfpSuccess || !hidSuccess)) { showAttackProgress("Phase 3: Testing FastPair vulnerability...", TFT_BLUE); FastPairExploitEngine fpEngine; fpSuccess = fpEngine.testVulnerability(target); @@ -5402,25 +5421,14 @@ void runUniversalAttack(NimBLEAddress target) { } } -void runQuickTest(NimBLEAddress target) { +void runQuickTest(NimBLEAddress target, SelectedDevice deviceInfo) { AutoCleanup cleanup([]() { BLEStateManager::deinitBLE(true); }); showAttackProgress("Quick testing (HFP + FastPair)...", TFT_WHITE); - bool hasHFP = false; - if (xSemaphoreTake(scannerData.mutex, portMAX_DELAY)) { - for (size_t i = 0; i < scannerData.deviceAddresses.size(); i++) { - if (scannerData.deviceAddresses[i] == target.toString().c_str()) { - hasHFP = scannerData.deviceHasHFP[i]; - break; - } - } - xSemaphoreGive(scannerData.mutex); - } - std::vector results; - if (hasHFP) { + if (deviceInfo.hasHFP) { HFPExploitEngine hfp; bool hfpVulnerable = hfp.testCVE202536911(target); results.push_back("HFP (CVE-2025-36911): " + String(hfpVulnerable ? "VULNERABLE" : "SAFE")); @@ -5441,7 +5449,7 @@ void runQuickTest(NimBLEAddress target) { cleanup.disable(); - if (hasHFP && results[0].indexOf("VULNERABLE") != -1) { + if (deviceInfo.hasHFP && results[0].indexOf("VULNERABLE") != -1) { lines.push_back("Try HFP-based attacks first!"); showDeviceInfoScreen("VULNERABLE DEVICE", lines, TFT_ORANGE, TFT_BLACK); } else if (fpVulnerable) { @@ -5451,7 +5459,7 @@ void runQuickTest(NimBLEAddress target) { } } -void runDeviceProfiling(NimBLEAddress target) { +void runDeviceProfiling(NimBLEAddress target, SelectedDevice deviceInfo) { AutoCleanup cleanup([]() { BLEStateManager::deinitBLE(true); }); if (!confirmAttack("Profile device services?")) return; @@ -5831,7 +5839,7 @@ void runHFPHIDPivotAttack(NimBLEAddress target) { } //============================================================================= -// UI Helpers - with text wrapping for long messages +// UI Helpers //============================================================================= void showAttackProgress(const char *message, uint16_t color) { From 88cb9a8bfc2e75551bea45ece82d72ec5644fb95 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 03:48:02 +0200 Subject: [PATCH 18/74] Enhance BLE_Suite with device info structures and updates Added new structures for device information and snapshots, updated ScannerData with new methods, and modified attack functions to accept SelectedDevice parameters. --- src/modules/ble/BLE_Suite.h | 120 +++++++++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 23 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index dff091206..9ab4520b6 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -18,8 +18,6 @@ #define NIMBLE_V2_PLUS 1 #endif -// TFT color definitions are now included from display.h - do not redefine here - extern volatile int tftWidth; extern volatile int tftHeight; class tft_logger; @@ -52,6 +50,76 @@ enum FastPairExploitType { FP_EXPLOIT_ALL }; +//============================================================================= +// NEW: DeviceInfo and DeviceSnapshot structures +//============================================================================= + +struct DeviceInfo { + String address; + String name; + int rssi; + bool hasFastPair; + bool hasHFP; + uint8_t deviceType; +}; + +struct DeviceSnapshot { + uint32_t version; + uint32_t count; + uint32_t timestamp; + std::vector names; + std::vector addresses; + std::vector rssi; + std::vector fastPair; + std::vector hfp; + std::vector types; + + DeviceSnapshot() : version(0), count(0), timestamp(0) {} +}; + +//============================================================================= +// NEW: SelectedDevice for passing device info to attacks +//============================================================================= + +struct SelectedDevice { + String address; + String name; + int rssi; + bool hasFastPair; + bool hasHFP; + uint8_t deviceType; +}; + +//============================================================================= +// UPDATED: ScannerData with new methods and members +//============================================================================= + +struct ScannerData { + std::vector deviceNames; + std::vector deviceAddresses; + std::vector deviceRssi; + std::vector deviceFastPair; + std::vector deviceHasHFP; + std::vector deviceTypes; + SemaphoreHandle_t mutex; + int foundCount; + + // NEW: Version tracking and snapshot cache + uint32_t dataVersion; + DeviceSnapshot* snapshotCache; + uint32_t cacheTimestamp; + + ScannerData(); + ~ScannerData(); + void addDevice(const String& name, const String& address, int rssi, bool fastPair, bool hasHFP, uint8_t type); + void clear(); + size_t size(); + + // NEW: Snapshot methods + DeviceSnapshot* getSnapshot(); + bool getDeviceInfo(int index, DeviceInfo &info); +}; + struct CharacteristicInfo { String uuid; bool canRead; @@ -105,23 +173,6 @@ struct DuckyCommand { int delay_ms; }; -struct ScannerData { - std::vector deviceNames; - std::vector deviceAddresses; - std::vector deviceRssi; - std::vector deviceFastPair; - std::vector deviceHasHFP; - std::vector deviceTypes; - SemaphoreHandle_t mutex; - int foundCount; - - ScannerData(); - ~ScannerData(); - void addDevice(const String& name, const String& address, int rssi, bool fastPair, bool hasHFP, uint8_t type); - void clear(); - size_t size(); -}; - class AutoCleanup { private: std::function cleanupFunc; @@ -432,8 +483,32 @@ void runDuckyScriptAttack(NimBLEAddress target); void runPINBruteForce(NimBLEAddress target); void runConnectionFlood(NimBLEAddress target); void runAdvertisingSpam(NimBLEAddress target); -void runQuickTest(NimBLEAddress target); -void runDeviceProfiling(NimBLEAddress target); + +//============================================================================= +// UPDATED: Attack functions with SelectedDevice parameter +//============================================================================= + +void runQuickTest(NimBLEAddress target, SelectedDevice deviceInfo); +void runDeviceProfiling(NimBLEAddress target, SelectedDevice deviceInfo); +void runUniversalAttack(NimBLEAddress target, SelectedDevice deviceInfo); + +//============================================================================= +// UPDATED: Submenu functions with SelectedDevice parameter +//============================================================================= + +void showFastPairSubMenu(NimBLEAddress target, SelectedDevice deviceInfo); +void showHFPSubMenu(NimBLEAddress target, SelectedDevice deviceInfo); +void showAudioSubMenu(NimBLEAddress target, SelectedDevice deviceInfo); +void showHIDSubMenu(NimBLEAddress target, SelectedDevice deviceInfo); +void showMemorySubMenu(NimBLEAddress target, SelectedDevice deviceInfo); +void showDoSSubMenu(NimBLEAddress target, SelectedDevice deviceInfo); +void showPayloadSubMenu(NimBLEAddress target, SelectedDevice deviceInfo); +void showTestingSubMenu(NimBLEAddress target, SelectedDevice deviceInfo); + +//============================================================================= +// Original function declarations (keep these) +//============================================================================= + void runWriteAccessTest(NimBLEAddress target); void runProtocolFuzzer(NimBLEAddress target); void runJamConnectAttack(NimBLEAddress target); @@ -472,14 +547,13 @@ void runFastPairCryptoOverflow(NimBLEAddress target); void runFastPairPopupSpam(NimBLEAddress target, FastPairPopupType type); void runFastPairAllExploits(NimBLEAddress target); void runFastPairHIDChain(NimBLEAddress target); -void runUniversalAttack(NimBLEAddress target); String selectFileFromSD(); bool loadScriptFromSD(const String &filename); // BLE Sniffer void BLE_Sniffer(); -// Forward declarations for submenu functions +// Forward declarations for old submenu functions (for compatibility) void showFastPairSubMenu(NimBLEAddress target); void showHFPSubMenu(NimBLEAddress target); void showAudioSubMenu(NimBLEAddress target); From 66ec26abe5112b5c7cdef5f5c0ee7c00dedbc484 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 04:44:34 +0200 Subject: [PATCH 19/74] Refactor BLE_Suite: Remove unused structs and update scan methods Removed unused structures and updated scanning methods to use new API calls for active and passive scanning. --- src/modules/ble/BLE_Suite.cpp | 68 +++++++++++++++++------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index b1ed34627..f0f0c75a4 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -45,14 +45,6 @@ static NimBLEScan* g_pBLEScan = nullptr; static bool g_bleScanActive = false; // Device selection cache -struct SelectedDevice { - String address; - String name; - int rssi; - bool hasFastPair; - bool hasHFP; - uint8_t deviceType; -}; static SelectedDevice g_selectedDevice; //============================================================================= @@ -85,20 +77,6 @@ FastPairVersion detectFastPairVersion(NimBLEAddress target) { return FP_VERSION_ // ScannerData Implementation with Snapshot Support //============================================================================= -struct DeviceSnapshot { - uint32_t version; - uint32_t count; - uint32_t timestamp; - std::vector names; - std::vector addresses; - std::vector rssi; - std::vector fastPair; - std::vector hfp; - std::vector types; - - DeviceSnapshot() : version(0), count(0), timestamp(0) {} -}; - ScannerData::ScannerData() { mutex = xSemaphoreCreateMutex(); foundCount = 0; @@ -516,14 +494,11 @@ NimBLEClient *attemptConnectionWithStrategies(NimBLEAddress target, String &conn bool hasHFP = false; DeviceInfo info; - if (scannerData.getDeviceInfo(0, info)) { - // Check if target matches any device in scanner data - for (size_t i = 0; i < scannerData.size(); i++) { - if (scannerData.getDeviceInfo(i, info)) { - if (info.address == target.toString().c_str()) { - hasHFP = info.hasHFP; - break; - } + for (size_t i = 0; i < scannerData.size(); i++) { + if (scannerData.getDeviceInfo(i, info)) { + if (info.address == target.toString().c_str()) { + hasHFP = info.hasHFP; + break; } } } @@ -4187,11 +4162,18 @@ String selectTargetFromScan(const char *title) { const int ACTIVE_SCAN_TIME = 8; const int PASSIVE_SCAN_TIME = 8; + // === ACTIVE SCAN === g_pBLEScan->setActiveScan(true); tft.setCursor(20, 80); tft.print("Active scan (8s)..."); g_pBLEScan->clearResults(); - BLEScanResults activeResults = g_pBLEScan->start(ACTIVE_SCAN_TIME, false); + +#ifdef NIMBLE_V2_PLUS + BLEScanResults activeResults = g_pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); +#else + g_pBLEScan->start(ACTIVE_SCAN_TIME, false); + BLEScanResults activeResults = g_pBLEScan->getResults(); +#endif for (int i = 0; i < activeResults.getCount(); i++) { const NimBLEAdvertisedDevice *device = activeResults.getDevice(i); @@ -4222,10 +4204,17 @@ String selectTargetFromScan(const char *title) { scannerData.addDevice(name, address, rssi, fastPair, hasHFP, deviceType); } + // === PASSIVE SCAN === g_pBLEScan->setActiveScan(false); tft.setCursor(20, 100); tft.print("Passive scan (8s)..."); - BLEScanResults passiveResults = g_pBLEScan->start(PASSIVE_SCAN_TIME, false); + +#ifdef NIMBLE_V2_PLUS + BLEScanResults passiveResults = g_pBLEScan->getResults(PASSIVE_SCAN_TIME * 1000, false); +#else + g_pBLEScan->start(PASSIVE_SCAN_TIME, false); + BLEScanResults passiveResults = g_pBLEScan->getResults(); +#endif for (int i = 0; i < passiveResults.getCount(); i++) { const NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); @@ -4283,6 +4272,8 @@ String selectTargetFromScan(const char *title) { } size_t deviceCount = snapshot->count; + + // Sort with manual swap for vector for (size_t i = 0; i < deviceCount - 1; i++) { for (size_t j = i + 1; j < deviceCount; j++) { bool swapNeeded = false; @@ -4295,13 +4286,22 @@ String selectTargetFromScan(const char *title) { std::swap(snapshot->names[i], snapshot->names[j]); std::swap(snapshot->addresses[i], snapshot->addresses[j]); std::swap(snapshot->rssi[i], snapshot->rssi[j]); - std::swap(snapshot->fastPair[i], snapshot->fastPair[j]); - std::swap(snapshot->hfp[i], snapshot->hfp[j]); + + // Manual swap for vector proxy references + bool tempFast = snapshot->fastPair[i]; + snapshot->fastPair[i] = snapshot->fastPair[j]; + snapshot->fastPair[j] = tempFast; + + bool tempHfp = snapshot->hfp[i]; + snapshot->hfp[i] = snapshot->hfp[j]; + snapshot->hfp[j] = tempHfp; + std::swap(snapshot->types[i], snapshot->types[j]); } } } + // UI selection loop... int maxVisibleDevices = 4, deviceItemHeight = 30, menuStartY = 60; int selectedIdx = 0, scrollOffset = 0; int lastSelected = -1, lastScrollOffset = -1; From b147d1d6f2ba1380a7cc87c9c71a00f77e0382d2 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 05:23:33 +0200 Subject: [PATCH 20/74] Implement cleanup function for BLE state management Added cleanupBLESuiteState function to reset BLE state and clear selected device cache. Updated selectTargetFromScan and parseAddress functions to utilize cleanup function for better state management. --- src/modules/ble/BLE_Suite.cpp | 200 +++++++++++++++++++++++++++------- 1 file changed, 160 insertions(+), 40 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index f0f0c75a4..b70ff1c50 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -47,6 +47,41 @@ static bool g_bleScanActive = false; // Device selection cache static SelectedDevice g_selectedDevice; +//============================================================================= +// Cleanup Function +//============================================================================= + +void cleanupBLESuiteState() { + // Stop any ongoing scan + if (g_pBLEScan) { + g_pBLEScan->stop(); + g_pBLEScan->clearResults(); + g_pBLEScan = nullptr; + } + g_bleScanActive = false; + + // Clear the selected device cache + g_selectedDevice.address = ""; + g_selectedDevice.name = ""; + g_selectedDevice.rssi = 0; + g_selectedDevice.hasFastPair = false; + g_selectedDevice.hasHFP = false; + g_selectedDevice.deviceType = 0; + + // Clear scanner data + scannerData.clear(); + + // Deinit BLE if it's active + if (BLEStateManager::isBLEActive()) { + BLEStateManager::deinitBLE(true); + delay(100); + } + + // Reinit BLE in a clean state + BLEStateManager::initBLE("Bruce", ESP_PWR_LVL_P9); + delay(100); +} + //============================================================================= // v3.1: Samsung MAC OUI Detection //============================================================================= @@ -4111,6 +4146,9 @@ void BLE_Sniffer() { //============================================================================= String selectTargetFromScan(const char *title) { + // Clean up previous state first + cleanupBLESuiteState(); + scannerData.clear(); g_selectedDevice.address = ""; g_selectedDevice.name = ""; @@ -4301,7 +4339,7 @@ String selectTargetFromScan(const char *title) { } } - // UI selection loop... + // UI selection loop int maxVisibleDevices = 4, deviceItemHeight = 30, menuStartY = 60; int selectedIdx = 0, scrollOffset = 0; int lastSelected = -1, lastScrollOffset = -1; @@ -4391,15 +4429,56 @@ String selectTargetFromScan(const char *title) { scrollOffset = 0; } } else if (check(SelPress)) { - g_selectedDevice.address = snapshot->addresses[selectedIdx]; - g_selectedDevice.name = snapshot->names[selectedIdx]; + // Get the MAC address directly from the snapshot + String selectedMAC = snapshot->addresses[selectedIdx]; + String selectedName = snapshot->names[selectedIdx]; + + // Clean the MAC address - remove any extra characters + selectedMAC.trim(); + selectedMAC.toUpperCase(); + + // Remove any trailing garbage + int colonCount = 0; + for (int i = 0; i < selectedMAC.length(); i++) { + if (selectedMAC.charAt(i) == ':') colonCount++; + } + + // If we have more than 5 colons, something is wrong + if (colonCount > 5) { + // Try to extract just the MAC + for (int i = 0; i < selectedMAC.length() - 17; i++) { + String substr = selectedMAC.substring(i, i + 17); + bool valid = true; + for (int j = 0; j < 17; j++) { + char c = substr.charAt(j); + if (j % 3 == 2) { + if (c != ':') { valid = false; break; } + } else { + if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))) { + valid = false; break; + } + } + } + if (valid) { + selectedMAC = substr; + break; + } + } + } + + g_selectedDevice.address = selectedMAC; + g_selectedDevice.name = selectedName; g_selectedDevice.rssi = snapshot->rssi[selectedIdx]; g_selectedDevice.hasFastPair = snapshot->fastPair[selectedIdx]; g_selectedDevice.hasHFP = snapshot->hfp[selectedIdx]; g_selectedDevice.deviceType = snapshot->types[selectedIdx]; + // Return just the MAC with no extra characters + String returnMac = selectedMAC; + returnMac.trim(); + delete snapshot; - return g_selectedDevice.address + ":0"; + return returnMac; } delay(50); } @@ -4529,54 +4608,87 @@ String selectMultipleTargetsFromScan(const char *title, std::vector= '0' && c <= '9') || (c >= 'A' && c <= 'F') || c == ':') { - } else if (c == '|' || c == '(' || c == ' ') { - cleanAddr = cleanAddr.substring(0, i); - break; - } - } - - cleanAddr.trim(); - if (cleanAddr.length() < 17) { - for (int i = 0; i < addressInfo.length() - 17; i++) { - String substr = addressInfo.substring(i, i + 17); - bool valid = true; - for (int j = 0; j < 17; j++) { - char c = substr.charAt(j); - if (j % 3 == 2) { - if (c != ':') { - valid = false; - break; - } - } else { - if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) { - valid = false; - break; + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) { + if (start == -1) start = i; + // Check if we have a valid MAC + if (i - start + 1 >= 17) { + String possibleMac = cleanAddr.substring(start, start + 17); + // Validate MAC format + bool valid = true; + for (int j = 0; j < 17; j++) { + if (j % 3 == 2) { + if (possibleMac.charAt(j) != ':') { + valid = false; + break; + } + } else { + char h = possibleMac.charAt(j); + if (!((h >= '0' && h <= '9') || (h >= 'A' && h <= 'F'))) { + valid = false; + break; + } } } + if (valid) { + return NimBLEAddress(std::string(possibleMac.c_str()), BLE_ADDR_PUBLIC); + } } - if (valid) { - cleanAddr = substr; - cleanAddr.toUpperCase(); - break; + } else if (c == ':') { + colonCount++; + } else { + // Reset if we hit a non-valid character + if (start != -1 && colonCount < 5) { + start = -1; + colonCount = 0; } } } - - if (cleanAddr.length() < 17) { - Serial.println("[WARN] Invalid MAC address format: " + addressInfo); - return NimBLEAddress(std::string(""), BLE_ADDR_PUBLIC); + + // Try the simpler approach - just look for the first valid MAC + for (int i = 0; i < addressInfo.length() - 17; i++) { + String substr = addressInfo.substring(i, i + 17); + bool valid = true; + for (int j = 0; j < 17; j++) { + char c = substr.charAt(j); + if (j % 3 == 2) { + if (c != ':') { + valid = false; + break; + } + } else { + if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) { + valid = false; + break; + } + } + } + if (valid) { + substr.toUpperCase(); + return NimBLEAddress(std::string(substr.c_str()), BLE_ADDR_PUBLIC); + } } - - return NimBLEAddress(std::string(cleanAddr.c_str()), BLE_ADDR_PUBLIC); + + Serial.println("[WARN] Invalid MAC address format: " + addressInfo); + return NimBLEAddress(std::string(""), BLE_ADDR_PUBLIC); } //============================================================================= @@ -4906,7 +5018,10 @@ void BleSuiteMenu() { lastScrollOffset = scrollOffset; } - if (check(EscPress)) return; + if (check(EscPress)) { + cleanupBLESuiteState(); + return; + } if (check(PrevPress)) { selected = (selected > 0) ? selected - 1 : MENU_ITEMS - 1; if (selected < scrollOffset) scrollOffset = selected; @@ -4922,6 +5037,7 @@ void BleSuiteMenu() { if (check(SelPress)) { if (selected == MENU_ITEMS - 1) { BLE_Sniffer(); + cleanupBLESuiteState(); } else { executeAttackWithTargetScan(selected); } @@ -4977,6 +5093,10 @@ void executeAttackWithTargetScan(int attackIndex) { showAttackProgress("Attack complete. Press any key to continue...", TFT_GREEN); while (!check(EscPress) && !check(SelPress) && !check(PrevPress) && !check(NextPress)) delay(50); + + // Clean up after attack to prevent crashes + cleanupBLESuiteState(); + delay(200); } //============================================================================= From 2c3feb34d752839612f9ffb9253e02af4e77762d Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 12:52:43 +0200 Subject: [PATCH 21/74] Refactor cleanupBLESuiteState to avoid BLE deinit --- src/modules/ble/BLE_Suite.cpp | 49 +++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index b70ff1c50..380241b25 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -48,7 +48,7 @@ static bool g_bleScanActive = false; static SelectedDevice g_selectedDevice; //============================================================================= -// Cleanup Function +// Cleanup Function - Safe version that doesn't deinit BLE //============================================================================= void cleanupBLESuiteState() { @@ -56,9 +56,8 @@ void cleanupBLESuiteState() { if (g_pBLEScan) { g_pBLEScan->stop(); g_pBLEScan->clearResults(); - g_pBLEScan = nullptr; + g_bleScanActive = false; } - g_bleScanActive = false; // Clear the selected device cache g_selectedDevice.address = ""; @@ -71,15 +70,8 @@ void cleanupBLESuiteState() { // Clear scanner data scannerData.clear(); - // Deinit BLE if it's active - if (BLEStateManager::isBLEActive()) { - BLEStateManager::deinitBLE(true); - delay(100); - } - - // Reinit BLE in a clean state - BLEStateManager::initBLE("Bruce", ESP_PWR_LVL_P9); - delay(100); + // Don't deinit BLE - just clean up state + delay(50); } //============================================================================= @@ -4146,9 +4138,7 @@ void BLE_Sniffer() { //============================================================================= String selectTargetFromScan(const char *title) { - // Clean up previous state first - cleanupBLESuiteState(); - + // Clean up previous state scannerData.clear(); g_selectedDevice.address = ""; g_selectedDevice.name = ""; @@ -5019,7 +5009,13 @@ void BleSuiteMenu() { } if (check(EscPress)) { - cleanupBLESuiteState(); + // Clean up scan state without deinit + if (g_pBLEScan) { + g_pBLEScan->stop(); + g_pBLEScan->clearResults(); + g_bleScanActive = false; + } + scannerData.clear(); return; } if (check(PrevPress)) { @@ -5037,7 +5033,13 @@ void BleSuiteMenu() { if (check(SelPress)) { if (selected == MENU_ITEMS - 1) { BLE_Sniffer(); - cleanupBLESuiteState(); + // Clean up after sniffer + if (g_pBLEScan) { + g_pBLEScan->stop(); + g_pBLEScan->clearResults(); + g_bleScanActive = false; + } + scannerData.clear(); } else { executeAttackWithTargetScan(selected); } @@ -5094,9 +5096,16 @@ void executeAttackWithTargetScan(int attackIndex) { showAttackProgress("Attack complete. Press any key to continue...", TFT_GREEN); while (!check(EscPress) && !check(SelPress) && !check(PrevPress) && !check(NextPress)) delay(50); - // Clean up after attack to prevent crashes - cleanupBLESuiteState(); - delay(200); + // Clean up scan state without deinit + if (g_pBLEScan) { + g_pBLEScan->stop(); + g_pBLEScan->clearResults(); + g_bleScanActive = false; + } + scannerData.clear(); + g_selectedDevice.address = ""; + g_selectedDevice.name = ""; + delay(100); } //============================================================================= From 7493b42b30ae2d7aa6cb6f3bba2176d3db977293 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 13:27:34 +0200 Subject: [PATCH 22/74] Enhance comments in ble_common.h for clarity Added comments to clarify BLE scan setup and display functions. --- src/modules/ble/ble_common.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index e4c700378..4d611d2de 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -18,6 +18,11 @@ #define SCAN_INT 100 #define SCAN_WINDOW 99 +// Maximum number of BLE devices to display to prevent memory issues +// In dense environments (subway, airport, etc.) there can be hundreds of devices +// Limiting to 100 prevents out-of-memory crashes while still showing plenty +#define MAX_DISPLAY_DEVICES 100 + extern BLEScan *pBLEScan; extern int scanTime; @@ -32,8 +37,16 @@ constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = true; constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = false; #endif -bool ble_scan_setup(); // false = aborted (e.g. not enough contiguous RAM) +// Initialize BLE scan with proper memory management +// Returns: true on success, false if aborted (e.g. not enough contiguous RAM) +bool ble_scan_setup(); + +// Perform BLE scan and display results +// Automatically handles device limits to prevent memory issues void ble_scan(); + +// Safely stop BLE stack and clean up resources +// Does not aggressively deinit if BLE is still needed void stopBLEStack(); // GATT notification tolerant to temporary MSYS-pool exhaustion. @@ -45,6 +58,7 @@ void stopBLEStack(); bool bleNotifyRetry(NimBLECharacteristic *chr, const uint8_t *value, size_t length, uint8_t retries = 8); bool bleNotifyRetry(NimBLECharacteristic *chr, uint8_t retries = 8); +// Display BLE send interface void disPlayBLESend(); #endif From 0f53ae125aecba32db300ebcf6ac06b95a7c10a9 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 13:28:11 +0200 Subject: [PATCH 23/74] Implement bounds checking for BLE device display Added bounds checking to BLE device display and improved memory management during scanning. --- src/modules/ble/ble_common.cpp | 256 ++++++++++++++++++++++++--------- 1 file changed, 192 insertions(+), 64 deletions(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index bdb11093f..1fee90853 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -9,6 +9,7 @@ #if !defined(LITE_VERSION) #include "BLE_Suite.h" #endif + #define SERVICE_UUID "1bc68b2a-f3e3-11e9-81b4-2a2ae2dbcce4" #define CHARACTERISTIC_RX_UUID "1bc68da0-f3e3-11e9-81b4-2a2ae2dbcce4" #define CHARACTERISTIC_TX_UUID "1bc68efe-f3e3-11e9-81b4-2a2ae2dbcce4" @@ -16,6 +17,9 @@ BLEScan *pBLEScan = nullptr; int scanTime = SCANTIME; // In seconds +// Limit the number of devices we show to prevent memory issues +#define MAX_DISPLAY_DEVICES 100 + bool bleNotifyRetry(NimBLECharacteristic *chr, const uint8_t *value, size_t length, uint8_t retries) { if (chr == nullptr) return false; if (chr->notify(value, length)) return true; @@ -82,30 +86,43 @@ void ble_info(const String &name, const String &address, const String &signal) { break; } } + +// Fixed callback with bounds checking #ifdef NIMBLE_V2_PLUS class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { #else class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { #endif void onResult(NimBLEAdvertisedDevice *advertisedDevice) { + // Check if we've reached the maximum + if (options.size() >= MAX_DISPLAY_DEVICES) { + // Stop the scan to prevent further callbacks + if (pBLEScan) { + pBLEScan->stop(); + Serial.println("Reached max devices, stopping scan"); + } + return; + } + String bt_title; String bt_name; String bt_address; String bt_signal; + // Safely get device info bt_name = advertisedDevice->getName().c_str(); - bt_title = advertisedDevice->getName().c_str(); bt_address = advertisedDevice->getAddress().toString().c_str(); bt_signal = String(advertisedDevice->getRSSI()); - // Serial.println("\n\nAddress - " + bt_address + "Name-"+ bt_name +"\n\n"); - if (bt_title.isEmpty()) bt_title = bt_address; + if (bt_name.isEmpty()) bt_name = ""; - // If BT name is empty, set NONAME - if (options.size() < 250) - options.emplace_back(bt_title.c_str(), [=]() { ble_info(bt_name, bt_address, bt_signal); }); - else { - Serial.println("Memory low, stopping BLE scan..."); - pBLEScan->stop(); + bt_title = bt_name; + if (bt_title.isEmpty()) bt_title = bt_address; + + // Add to options with bounds check + if (options.size() < MAX_DISPLAY_DEVICES) { + options.emplace_back(bt_title.c_str(), [=]() { + ble_info(bt_name, bt_address, bt_signal); + }); } } }; @@ -113,16 +130,23 @@ class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { static bool is_ble_inited = false; void stopBLEStack() { - if (pBLEScan) pBLEScan->stop(); + if (pBLEScan) { + pBLEScan->stop(); + // Don't clear results if we might need them + // pBLEScan->clearResults(); + } + // Only deinit if we actually initialized it + if (is_ble_inited) { #if !defined(LITE_VERSION) - if (BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0) { - BLEStateManager::deinitBLE(true); - } else + if (BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0) { + BLEStateManager::deinitBLE(true); + } else #endif - if (BLEDevice::getScan() != nullptr || BLEDevice::getAdvertising() != nullptr || - BLEDevice::getServer() != nullptr || BLEConnected || is_ble_inited) { - BLEDevice::deinit(); + if (BLEDevice::getScan() != nullptr || BLEDevice::getAdvertising() != nullptr || + BLEDevice::getServer() != nullptr || BLEConnected) { + BLEDevice::deinit(); + } } pBLEScan = nullptr; @@ -160,11 +184,22 @@ bool ble_scan_setup() { returnToMenu = true; return false; } - BLEDevice::init(""); + + // Only init if not already inited + if (!is_ble_inited) { + BLEDevice::init(""); + is_ble_inited = true; + } + RAM_LOG("ble-scan post-init"); pBLEScan = BLEDevice::getScan(); + if (!pBLEScan) { + displayError("Failed to get scan object", true); + return false; + } + #ifdef NIMBLE_V2_PLUS - pBLEScan->setScanCallbacks(new NimBLEScanCallbacks()); + pBLEScan->setScanCallbacks(new AdvertisedDeviceCallbacks()); #else pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); #endif @@ -174,13 +209,11 @@ bool ble_scan_setup() { pBLEScan->setInterval(SCAN_INT); // Less or equal setInterval value pBLEScan->setWindow(SCAN_WINDOW); + // Don't filter duplicates - we want to see all devices + pBLEScan->setDuplicateFilter(false); // Bluetooth MAC Address -#ifdef NIMBLE_V2_PLUS - esp_read_mac(sta_mac, ESP_MAC_BT); -#else esp_read_mac(sta_mac, ESP_MAC_BT); -#endif sprintf( strID, @@ -199,81 +232,174 @@ bool ble_scan_setup() { void ble_scan() { displayTextLine("Scanning.."); + // Clear options and limit size options = {}; + options.reserve(MAX_DISPLAY_DEVICES); + bool bleWasActiveBefore = BLEConnected || (BLEDevice::getServer() != nullptr); #if !defined(LITE_VERSION) - bleWasActiveBefore = - bleWasActiveBefore || BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0; + bleWasActiveBefore = bleWasActiveBefore || BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0; #endif - if (!ble_scan_setup() || pBLEScan == nullptr) return; + if (!ble_scan_setup() || pBLEScan == nullptr) { + displayError("Failed to init BLE scan"); + return; + } + + // Clear previous results before scanning + pBLEScan->clearResults(); + #ifdef NIMBLE_V2_PLUS + // For NimBLE v2+, use getResults with timeout BLEScanResults foundDevices = pBLEScan->getResults(scanTime * 1000, false); - for (int i = 0; i < foundDevices.getCount(); i++) { + + // Safely process results with bounds checking + int deviceCount = foundDevices.getCount(); + int processedCount = 0; + + for (int i = 0; i < deviceCount && processedCount < MAX_DISPLAY_DEVICES; i++) { const NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); + if (!advertisedDevice) continue; + String bt_title; String bt_name; String bt_address; String bt_signal; bt_name = advertisedDevice->getName().c_str(); - bt_title = advertisedDevice->getName().c_str(); bt_address = advertisedDevice->getAddress().toString().c_str(); bt_signal = String(advertisedDevice->getRSSI()); - // Serial.println("\n\nAddress - " + bt_address + "Name-"+ bt_name +"\n\n"); - if (bt_title.isEmpty()) bt_title = bt_address; + if (bt_name.isEmpty()) bt_name = ""; - // If BT name is empty, set NONAME - if (options.size() < 250) - options.emplace_back(bt_title.c_str(), [=]() { ble_info(bt_name, bt_address, bt_signal); }); - else { - Serial.println("Memory low, stopping BLE scan..."); - pBLEScan->stop(); + bt_title = bt_name; + if (bt_title.isEmpty()) bt_title = bt_address; + + if (options.size() < MAX_DISPLAY_DEVICES) { + options.emplace_back(bt_title.c_str(), [=]() { + ble_info(bt_name, bt_address, bt_signal); + }); + processedCount++; } } #else - BLEScanResults foundDevices = pBLEScan->start(scanTime, false); -#endif - - addOptionToMainMenu(); + // For older NimBLE, use start() which blocks + pBLEScan->start(scanTime, false); + BLEScanResults foundDevices = pBLEScan->getResults(); + + // Process results with bounds checking + int deviceCount = foundDevices.getCount(); + int processedCount = 0; + + for (int i = 0; i < deviceCount && processedCount < MAX_DISPLAY_DEVICES; i++) { + NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); + if (!advertisedDevice) continue; + + String bt_title; + String bt_name; + String bt_address; + String bt_signal; - loopOptions(options); - options.clear(); + bt_name = advertisedDevice->getName().c_str(); + bt_address = advertisedDevice->getAddress().toString().c_str(); + bt_signal = String(advertisedDevice->getRSSI()); + + if (bt_name.isEmpty()) bt_name = ""; + bt_title = bt_name; + if (bt_title.isEmpty()) bt_title = bt_address; + + if (options.size() < MAX_DISPLAY_DEVICES) { + options.emplace_back(bt_title.c_str(), [=]() { + ble_info(bt_name, bt_address, bt_signal); + }); + processedCount++; + } + } +#endif - // Delete results fromBLEScan buffer to release memory - pBLEScan->clearResults(); - pBLEScan->stop(); - if (!bleWasActiveBefore) { stopBLEStack(); } + // Add option to show count if we hit the limit + if (options.size() >= MAX_DISPLAY_DEVICES) { + options.emplace_back("... and more devices", nullptr); + } + + // Clear results from buffer to release memory + if (pBLEScan) { + pBLEScan->clearResults(); + pBLEScan->stop(); + } + + // Only stop BLE if it wasn't active before and we don't need it + if (!bleWasActiveBefore && !BLEStateManager::isBLEActive()) { + stopBLEStack(); + } + + // Show the menu if we have options + if (!options.empty()) { + addOptionToMainMenu(); + loopOptions(options); + options.clear(); + } else { + displayError("No devices found"); + delay(1000); + } } bool initBLEServer() { uint64_t chipid = ESP.getEfuseMac(); String blename = "Bruce-" + String((uint8_t)(chipid >> 32), HEX); - BLEDevice::init(blename.c_str()); - // BLEDevice::setPower(ESP_PWR_LVL_N12); + if (!is_ble_inited) { + BLEDevice::init(blename.c_str()); + is_ble_inited = true; + } + pServer = BLEDevice::createServer(); + if (!pServer) { + displayError("Failed to create BLE server"); + return false; + } pServer->setCallbacks(new MyServerCallbacks()); pService = pServer->createService(SERVICE_UUID); + if (!pService) { + displayError("Failed to create BLE service"); + return false; + } + pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_RX_UUID, NIMBLE_PROPERTY::NOTIFY); + if (!pTxCharacteristic) { + displayError("Failed to create TX characteristic"); + return false; + } pTxCharacteristic->addDescriptor(new NimBLE2904()); - BLECharacteristic *pRxCharacteristic = pService->createCharacteristic( + pRxCharacteristic = pService->createCharacteristic( CHARACTERISTIC_TX_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR ); + if (!pRxCharacteristic) { + displayError("Failed to create RX characteristic"); + return false; + } pRxCharacteristic->setCallbacks(new MyCallbacks()); + // Start the service + pService->start(); + return true; } void disPlayBLESend() { uint8_t senddata[2] = {0}; tft.fillScreen(bruceConfig.bgColor); - drawMainBorder(); // Moved up to avoid drawing screen issues + drawMainBorder(); tft.setTextSize(1); - // pService->start() is deprecated in NimBLE v2 - services start automatically with the server + if (!pServer) { + if (!initBLEServer()) { + displayError("Failed to init BLE server"); + return; + } + } + pServer->getAdvertising()->start(); uint64_t chipid = ESP.getEfuseMac(); @@ -291,7 +417,6 @@ void disPlayBLESend() { tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor); tft.setTextSize(FM); tft.setCursor(12, 50); - // tft.printf("BLE connect!\n"); tft.printf("BLE Send\n"); tft.setTextSize(FM); } @@ -337,28 +462,31 @@ void disPlayBLESend() { } tft.setTextColor(TFT_WHITE); - pService->~NimBLEService(); pServer->getAdvertising()->stop(); -#if defined(CONFIG_IDF_TARGET_ESP32C5) - esp_bt_controller_deinit(); -#else - BLEDevice::deinit(); -#endif BLEConnected = false; + + // Don't deinit here - let the caller handle cleanup } void ble_test() { printf("ble test\n"); - // if (!is_ble_inited) - // { - printf("Init ble server\n"); - initBLEServer(); - delay(100); - is_ble_inited = true; - // } + if (!is_ble_inited) { + printf("Init ble server\n"); + if (!initBLEServer()) { + displayError("Failed to init BLE server"); + return; + } + delay(100); + } disPlayBLESend(); + // Clean up properly + if (pServer) { + pServer->getAdvertising()->stop(); + } + stopBLEStack(); + printf("Quit ble test\n"); } From 6c232413dbc1dc2b5069013d2e2d663b2f2e6caf Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 15:34:39 +0200 Subject: [PATCH 24/74] Update BLE scan methods for NimBLE 2.x compatibility --- src/modules/ble/BLE_Suite.cpp | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index 380241b25..a29cd8712 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -3294,9 +3294,16 @@ std::vector FastPairExploitEngine::scanForFastPairDevices(in pScan->setActiveScan(true); pScan->setInterval(97); pScan->setWindow(67); - pScan->start(duration, false); - NimBLEScanResults results = pScan->getResults(); +#ifdef NIMBLE_V2_PLUS + // NimBLE 2.x: start() returns bool, getResults() gets the data + pScan->start(duration * 1000, false); + NimBLEScanResults results = pScan->getResults(duration * 1000, false); +#else + // NimBLE 1.x: start() returns results directly + NimBLEScanResults results = pScan->start(duration, false); +#endif + for (int i = 0; i < results.getCount(); i++) { const NimBLEAdvertisedDevice *device = results.getDevice(i); @@ -3931,7 +3938,14 @@ void BLE_Sniffer() { padprintln("Status: CAPTURING..."); padprintln("Press [SEL] to stop"); +#ifdef NIMBLE_V2_PLUS + // NimBLE 2.x: start() returns bool, getResults() gets the data + pScan->start(10 * 1000, true); + NimBLEScanResults results = pScan->getResults(10 * 1000, true); +#else + // NimBLE 1.x: getResults handles everything NimBLEScanResults results = pScan->getResults(10 * 1000, true); +#endif for (int i = 0; i < results.getCount(); i++) { const NimBLEAdvertisedDevice *device = results.getDevice(i); @@ -4197,8 +4211,15 @@ String selectTargetFromScan(const char *title) { g_pBLEScan->clearResults(); #ifdef NIMBLE_V2_PLUS + // NimBLE 2.x: start() returns bool, getResults() gets the data + bool scanStarted = g_pBLEScan->start(ACTIVE_SCAN_TIME * 1000, false); + if (!scanStarted) { + displayError("Failed to start BLE scan"); + return ""; + } BLEScanResults activeResults = g_pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); #else + // NimBLE 1.x: start() returns results directly g_pBLEScan->start(ACTIVE_SCAN_TIME, false); BLEScanResults activeResults = g_pBLEScan->getResults(); #endif @@ -4238,8 +4259,15 @@ String selectTargetFromScan(const char *title) { tft.print("Passive scan (8s)..."); #ifdef NIMBLE_V2_PLUS + // NimBLE 2.x: start() returns bool, getResults() gets the data + bool passiveScanStarted = g_pBLEScan->start(PASSIVE_SCAN_TIME * 1000, false); + if (!passiveScanStarted) { + displayError("Failed to start passive BLE scan"); + return ""; + } BLEScanResults passiveResults = g_pBLEScan->getResults(PASSIVE_SCAN_TIME * 1000, false); #else + // NimBLE 1.x: start() returns results directly g_pBLEScan->start(PASSIVE_SCAN_TIME, false); BLEScanResults passiveResults = g_pBLEScan->getResults(); #endif From 856e5b26fcc7e787ac6fff33b528c8971a7f24ed Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 15:35:58 +0200 Subject: [PATCH 25/74] Add compatibility note for NimBLE versions --- src/modules/ble/ble_common.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index 4d611d2de..3023e4896 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -43,6 +43,7 @@ bool ble_scan_setup(); // Perform BLE scan and display results // Automatically handles device limits to prevent memory issues +// Compatible with NimBLE 1.x and 2.x void ble_scan(); // Safely stop BLE stack and clean up resources From a0b63ff7bf6e4f8ada01eec84006449f83c90777 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 15:36:36 +0200 Subject: [PATCH 26/74] Refactor BLE scan logic and device management Refactor BLE scanning logic and improve device handling. --- src/modules/ble/ble_common.cpp | 99 +++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 37 deletions(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index 1fee90853..8264045ac 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -14,12 +14,12 @@ #define CHARACTERISTIC_RX_UUID "1bc68da0-f3e3-11e9-81b4-2a2ae2dbcce4" #define CHARACTERISTIC_TX_UUID "1bc68efe-f3e3-11e9-81b4-2a2ae2dbcce4" +// Limit the number of devices we show to prevent memory issues in dense environments +#define MAX_DISPLAY_DEVICES 100 + BLEScan *pBLEScan = nullptr; int scanTime = SCANTIME; // In seconds -// Limit the number of devices we show to prevent memory issues -#define MAX_DISPLAY_DEVICES 100 - bool bleNotifyRetry(NimBLECharacteristic *chr, const uint8_t *value, size_t length, uint8_t retries) { if (chr == nullptr) return false; if (chr->notify(value, length)) return true; @@ -81,22 +81,51 @@ void ble_info(const String &name, const String &address, const String &signal) { delay(300); while (!check(SelPress)) { - while (!check(SelPress)) { yield(); } // timerless debounce + while (!check(SelPress)) { yield(); } returnToMenu = true; break; } } -// Fixed callback with bounds checking +// NimBLE 2.x uses NimBLEScanCallbacks with const pointers #ifdef NIMBLE_V2_PLUS class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) { + // Stop adding if we hit the limit + if (options.size() >= MAX_DISPLAY_DEVICES) { + if (pBLEScan) { + pBLEScan->stop(); + Serial.println("Reached max devices, stopping scan"); + } + return; + } + + String bt_title; + String bt_name; + String bt_address; + String bt_signal; + + bt_name = advertisedDevice->getName().c_str(); + bt_address = advertisedDevice->getAddress().toString().c_str(); + bt_signal = String(advertisedDevice->getRSSI()); + + if (bt_name.isEmpty()) bt_name = ""; + bt_title = bt_name; + if (bt_title.isEmpty()) bt_title = bt_address; + + if (options.size() < MAX_DISPLAY_DEVICES) { + options.emplace_back(bt_title.c_str(), [=]() { + ble_info(bt_name, bt_address, bt_signal); + }); + } + } +}; #else +// Old NimBLE 1.x uses NimBLEAdvertisedDeviceCallbacks class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { -#endif void onResult(NimBLEAdvertisedDevice *advertisedDevice) { - // Check if we've reached the maximum + // Stop adding if we hit the limit if (options.size() >= MAX_DISPLAY_DEVICES) { - // Stop the scan to prevent further callbacks if (pBLEScan) { pBLEScan->stop(); Serial.println("Reached max devices, stopping scan"); @@ -109,7 +138,6 @@ class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { String bt_address; String bt_signal; - // Safely get device info bt_name = advertisedDevice->getName().c_str(); bt_address = advertisedDevice->getAddress().toString().c_str(); bt_signal = String(advertisedDevice->getRSSI()); @@ -118,7 +146,6 @@ class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { bt_title = bt_name; if (bt_title.isEmpty()) bt_title = bt_address; - // Add to options with bounds check if (options.size() < MAX_DISPLAY_DEVICES) { options.emplace_back(bt_title.c_str(), [=]() { ble_info(bt_name, bt_address, bt_signal); @@ -126,17 +153,15 @@ class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { } } }; +#endif static bool is_ble_inited = false; void stopBLEStack() { if (pBLEScan) { pBLEScan->stop(); - // Don't clear results if we might need them - // pBLEScan->clearResults(); } - // Only deinit if we actually initialized it if (is_ble_inited) { #if !defined(LITE_VERSION) if (BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0) { @@ -185,7 +210,6 @@ bool ble_scan_setup() { return false; } - // Only init if not already inited if (!is_ble_inited) { BLEDevice::init(""); is_ble_inited = true; @@ -204,15 +228,11 @@ bool ble_scan_setup() { pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); #endif - // Active scan uses more power, but get results faster pBLEScan->setActiveScan(true); pBLEScan->setInterval(SCAN_INT); - // Less or equal setInterval value pBLEScan->setWindow(SCAN_WINDOW); - // Don't filter duplicates - we want to see all devices pBLEScan->setDuplicateFilter(false); - // Bluetooth MAC Address esp_read_mac(sta_mac, ESP_MAC_BT); sprintf( @@ -232,7 +252,6 @@ bool ble_scan_setup() { void ble_scan() { displayTextLine("Scanning.."); - // Clear options and limit size options = {}; options.reserve(MAX_DISPLAY_DEVICES); @@ -246,14 +265,21 @@ void ble_scan() { return; } - // Clear previous results before scanning + // Clear previous results pBLEScan->clearResults(); #ifdef NIMBLE_V2_PLUS - // For NimBLE v2+, use getResults with timeout + // NimBLE 2.x: start() returns bool, getResults() gets the data + // Time is in milliseconds for NimBLE 2.x + bool scanStarted = pBLEScan->start(scanTime * 1000, false); + if (!scanStarted) { + displayError("Failed to start BLE scan"); + return; + } + + // Get results - timeout in milliseconds BLEScanResults foundDevices = pBLEScan->getResults(scanTime * 1000, false); - // Safely process results with bounds checking int deviceCount = foundDevices.getCount(); int processedCount = 0; @@ -282,11 +308,9 @@ void ble_scan() { } } #else - // For older NimBLE, use start() which blocks - pBLEScan->start(scanTime, false); - BLEScanResults foundDevices = pBLEScan->getResults(); + // NimBLE 1.x: start() returns results directly, time in seconds + BLEScanResults foundDevices = pBLEScan->start(scanTime, false); - // Process results with bounds checking int deviceCount = foundDevices.getCount(); int processedCount = 0; @@ -316,23 +340,27 @@ void ble_scan() { } #endif - // Add option to show count if we hit the limit + // Show "and more" if we hit the limit if (options.size() >= MAX_DISPLAY_DEVICES) { options.emplace_back("... and more devices", nullptr); } - // Clear results from buffer to release memory if (pBLEScan) { pBLEScan->clearResults(); pBLEScan->stop(); } - // Only stop BLE if it wasn't active before and we don't need it - if (!bleWasActiveBefore && !BLEStateManager::isBLEActive()) { + // Only stop BLE if it wasn't active before + if (!bleWasActiveBefore) { +#if !defined(LITE_VERSION) + if (!BLEStateManager::isBLEActive()) { + stopBLEStack(); + } +#else stopBLEStack(); +#endif } - // Show the menu if we have options if (!options.empty()) { addOptionToMainMenu(); loopOptions(options); @@ -381,9 +409,9 @@ bool initBLEServer() { } pRxCharacteristic->setCallbacks(new MyCallbacks()); - // Start the service - pService->start(); - + // NimBLE 2.x: Services start automatically when server starts + // No need to call pService->start() - it's deprecated and does nothing + return true; } @@ -464,8 +492,6 @@ void disPlayBLESend() { tft.setTextColor(TFT_WHITE); pServer->getAdvertising()->stop(); BLEConnected = false; - - // Don't deinit here - let the caller handle cleanup } void ble_test() { @@ -482,7 +508,6 @@ void ble_test() { disPlayBLESend(); - // Clean up properly if (pServer) { pServer->getAdvertising()->stop(); } From b0fcb1ee659d4fee3462ddaa78f7fbec4bd2a43e Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 16:59:16 +0200 Subject: [PATCH 27/74] Refactor BLE scan logic and remove device limit Removed MAX_DISPLAY_DEVICES limit and updated BLE scan handling. --- src/modules/ble/ble_common.cpp | 59 ++++++---------------------------- 1 file changed, 10 insertions(+), 49 deletions(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index 8264045ac..505ab3a20 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -14,9 +14,6 @@ #define CHARACTERISTIC_RX_UUID "1bc68da0-f3e3-11e9-81b4-2a2ae2dbcce4" #define CHARACTERISTIC_TX_UUID "1bc68efe-f3e3-11e9-81b4-2a2ae2dbcce4" -// Limit the number of devices we show to prevent memory issues in dense environments -#define MAX_DISPLAY_DEVICES 100 - BLEScan *pBLEScan = nullptr; int scanTime = SCANTIME; // In seconds @@ -24,7 +21,7 @@ bool bleNotifyRetry(NimBLECharacteristic *chr, const uint8_t *value, size_t leng if (chr == nullptr) return false; if (chr->notify(value, length)) return true; for (uint8_t i = 0; i < retries; i++) { - vTaskDelay(1); // let the host drain the pool MSYS and retry + vTaskDelay(1); if (chr->notify(value, length)) return true; } return false; @@ -34,7 +31,7 @@ bool bleNotifyRetry(NimBLECharacteristic *chr, uint8_t retries) { if (chr == nullptr) return false; if (chr->notify()) return true; for (uint8_t i = 0; i < retries; i++) { - vTaskDelay(1); // let the host drain the pool MSYS and retry + vTaskDelay(1); if (chr->notify()) return true; } return false; @@ -87,11 +84,9 @@ void ble_info(const String &name, const String &address, const String &signal) { } } -// NimBLE 2.x uses NimBLEScanCallbacks with const pointers #ifdef NIMBLE_V2_PLUS class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { void onResult(const NimBLEAdvertisedDevice *advertisedDevice) { - // Stop adding if we hit the limit if (options.size() >= MAX_DISPLAY_DEVICES) { if (pBLEScan) { pBLEScan->stop(); @@ -121,10 +116,8 @@ class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { } }; #else -// Old NimBLE 1.x uses NimBLEAdvertisedDeviceCallbacks class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { void onResult(NimBLEAdvertisedDevice *advertisedDevice) { - // Stop adding if we hit the limit if (options.size() >= MAX_DISPLAY_DEVICES) { if (pBLEScan) { pBLEScan->stop(); @@ -265,57 +258,27 @@ void ble_scan() { return; } - // Clear previous results + // Clear previous results before scanning pBLEScan->clearResults(); #ifdef NIMBLE_V2_PLUS // NimBLE 2.x: start() returns bool, getResults() gets the data - // Time is in milliseconds for NimBLE 2.x bool scanStarted = pBLEScan->start(scanTime * 1000, false); if (!scanStarted) { displayError("Failed to start BLE scan"); return; } - - // Get results - timeout in milliseconds BLEScanResults foundDevices = pBLEScan->getResults(scanTime * 1000, false); - - int deviceCount = foundDevices.getCount(); - int processedCount = 0; - - for (int i = 0; i < deviceCount && processedCount < MAX_DISPLAY_DEVICES; i++) { - const NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); - if (!advertisedDevice) continue; - - String bt_title; - String bt_name; - String bt_address; - String bt_signal; - - bt_name = advertisedDevice->getName().c_str(); - bt_address = advertisedDevice->getAddress().toString().c_str(); - bt_signal = String(advertisedDevice->getRSSI()); - - if (bt_name.isEmpty()) bt_name = ""; - bt_title = bt_name; - if (bt_title.isEmpty()) bt_title = bt_address; - - if (options.size() < MAX_DISPLAY_DEVICES) { - options.emplace_back(bt_title.c_str(), [=]() { - ble_info(bt_name, bt_address, bt_signal); - }); - processedCount++; - } - } #else - // NimBLE 1.x: start() returns results directly, time in seconds + // NimBLE 1.x: start() returns results directly BLEScanResults foundDevices = pBLEScan->start(scanTime, false); - +#endif + int deviceCount = foundDevices.getCount(); int processedCount = 0; for (int i = 0; i < deviceCount && processedCount < MAX_DISPLAY_DEVICES; i++) { - NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); + const NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); if (!advertisedDevice) continue; String bt_title; @@ -338,16 +301,16 @@ void ble_scan() { processedCount++; } } -#endif // Show "and more" if we hit the limit if (options.size() >= MAX_DISPLAY_DEVICES) { options.emplace_back("... and more devices", nullptr); } + // Stop scan and clean up if (pBLEScan) { - pBLEScan->clearResults(); pBLEScan->stop(); + pBLEScan->clearResults(); } // Only stop BLE if it wasn't active before @@ -409,9 +372,7 @@ bool initBLEServer() { } pRxCharacteristic->setCallbacks(new MyCallbacks()); - // NimBLE 2.x: Services start automatically when server starts - // No need to call pService->start() - it's deprecated and does nothing - + // Services start automatically when server starts return true; } From 17f79f461496e251536f221c270d72ce04870faa Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 16:59:48 +0200 Subject: [PATCH 28/74] Fix header guard in ble_common.h From 7d5313aa36c63cebbca60378a67cf3d26079148f Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 17:02:07 +0200 Subject: [PATCH 29/74] Refactor BLE_Suite.cpp for better state handling Refactor BLE scanning functions to improve state management and remove unnecessary comments. --- src/modules/ble/BLE_Suite.cpp | 61 ++++++----------------------------- 1 file changed, 9 insertions(+), 52 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index a29cd8712..b93458a2b 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -52,14 +52,12 @@ static SelectedDevice g_selectedDevice; //============================================================================= void cleanupBLESuiteState() { - // Stop any ongoing scan if (g_pBLEScan) { g_pBLEScan->stop(); g_pBLEScan->clearResults(); g_bleScanActive = false; } - // Clear the selected device cache g_selectedDevice.address = ""; g_selectedDevice.name = ""; g_selectedDevice.rssi = 0; @@ -67,10 +65,7 @@ void cleanupBLESuiteState() { g_selectedDevice.hasHFP = false; g_selectedDevice.deviceType = 0; - // Clear scanner data scannerData.clear(); - - // Don't deinit BLE - just clean up state delay(50); } @@ -3296,11 +3291,9 @@ std::vector FastPairExploitEngine::scanForFastPairDevices(in pScan->setWindow(67); #ifdef NIMBLE_V2_PLUS - // NimBLE 2.x: start() returns bool, getResults() gets the data pScan->start(duration * 1000, false); NimBLEScanResults results = pScan->getResults(duration * 1000, false); #else - // NimBLE 1.x: start() returns results directly NimBLEScanResults results = pScan->start(duration, false); #endif @@ -3939,11 +3932,9 @@ void BLE_Sniffer() { padprintln("Press [SEL] to stop"); #ifdef NIMBLE_V2_PLUS - // NimBLE 2.x: start() returns bool, getResults() gets the data pScan->start(10 * 1000, true); NimBLEScanResults results = pScan->getResults(10 * 1000, true); #else - // NimBLE 1.x: getResults handles everything NimBLEScanResults results = pScan->getResults(10 * 1000, true); #endif @@ -4152,8 +4143,7 @@ void BLE_Sniffer() { //============================================================================= String selectTargetFromScan(const char *title) { - // Clean up previous state - scannerData.clear(); + // Don't clear scannerData at start - keep existing data g_selectedDevice.address = ""; g_selectedDevice.name = ""; @@ -4181,6 +4171,9 @@ String selectTargetFromScan(const char *title) { g_pBLEScan->setDuplicateFilter(false); } + // Clear previous results before scanning + g_pBLEScan->clearResults(); + tft.fillScreen(bruceConfig.bgColor); tft.drawRect(5, 5, tftWidth - 10, tftHeight - 10, TFT_WHITE); @@ -4208,10 +4201,8 @@ String selectTargetFromScan(const char *title) { g_pBLEScan->setActiveScan(true); tft.setCursor(20, 80); tft.print("Active scan (8s)..."); - g_pBLEScan->clearResults(); #ifdef NIMBLE_V2_PLUS - // NimBLE 2.x: start() returns bool, getResults() gets the data bool scanStarted = g_pBLEScan->start(ACTIVE_SCAN_TIME * 1000, false); if (!scanStarted) { displayError("Failed to start BLE scan"); @@ -4219,9 +4210,7 @@ String selectTargetFromScan(const char *title) { } BLEScanResults activeResults = g_pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); #else - // NimBLE 1.x: start() returns results directly - g_pBLEScan->start(ACTIVE_SCAN_TIME, false); - BLEScanResults activeResults = g_pBLEScan->getResults(); + BLEScanResults activeResults = g_pBLEScan->start(ACTIVE_SCAN_TIME, false); #endif for (int i = 0; i < activeResults.getCount(); i++) { @@ -4259,7 +4248,6 @@ String selectTargetFromScan(const char *title) { tft.print("Passive scan (8s)..."); #ifdef NIMBLE_V2_PLUS - // NimBLE 2.x: start() returns bool, getResults() gets the data bool passiveScanStarted = g_pBLEScan->start(PASSIVE_SCAN_TIME * 1000, false); if (!passiveScanStarted) { displayError("Failed to start passive BLE scan"); @@ -4267,9 +4255,7 @@ String selectTargetFromScan(const char *title) { } BLEScanResults passiveResults = g_pBLEScan->getResults(PASSIVE_SCAN_TIME * 1000, false); #else - // NimBLE 1.x: start() returns results directly - g_pBLEScan->start(PASSIVE_SCAN_TIME, false); - BLEScanResults passiveResults = g_pBLEScan->getResults(); + BLEScanResults passiveResults = g_pBLEScan->start(PASSIVE_SCAN_TIME, false); #endif for (int i = 0; i < passiveResults.getCount(); i++) { @@ -4301,11 +4287,13 @@ String selectTargetFromScan(const char *title) { scannerData.addDevice(name, address, rssi, fastPair, hasHFP, deviceType); } + // Stop the scan but don't clear results yet if (g_pBLEScan) { g_pBLEScan->stop(); g_bleScanActive = false; } + // Get snapshot of discovered devices DeviceSnapshot* snapshot = scannerData.getSnapshot(); if (!snapshot || snapshot->count == 0) { if (snapshot) delete snapshot; @@ -4323,7 +4311,6 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 100); tft.print("turned on and in range."); delay(2000); - scannerData.clear(); return ""; } @@ -4451,39 +4438,10 @@ String selectTargetFromScan(const char *title) { String selectedMAC = snapshot->addresses[selectedIdx]; String selectedName = snapshot->names[selectedIdx]; - // Clean the MAC address - remove any extra characters + // Clean the MAC address selectedMAC.trim(); selectedMAC.toUpperCase(); - // Remove any trailing garbage - int colonCount = 0; - for (int i = 0; i < selectedMAC.length(); i++) { - if (selectedMAC.charAt(i) == ':') colonCount++; - } - - // If we have more than 5 colons, something is wrong - if (colonCount > 5) { - // Try to extract just the MAC - for (int i = 0; i < selectedMAC.length() - 17; i++) { - String substr = selectedMAC.substring(i, i + 17); - bool valid = true; - for (int j = 0; j < 17; j++) { - char c = substr.charAt(j); - if (j % 3 == 2) { - if (c != ':') { valid = false; break; } - } else { - if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))) { - valid = false; break; - } - } - } - if (valid) { - selectedMAC = substr; - break; - } - } - } - g_selectedDevice.address = selectedMAC; g_selectedDevice.name = selectedName; g_selectedDevice.rssi = snapshot->rssi[selectedIdx]; @@ -4491,7 +4449,6 @@ String selectTargetFromScan(const char *title) { g_selectedDevice.hasHFP = snapshot->hfp[selectedIdx]; g_selectedDevice.deviceType = snapshot->types[selectedIdx]; - // Return just the MAC with no extra characters String returnMac = selectedMAC; returnMac.trim(); From 91530b8d2b24fe7db5e7c36edda76ff3e6d71810 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 21:59:15 +0200 Subject: [PATCH 30/74] Clean up comments in BLE_Suite.cpp Removed unnecessary comments related to MAC address validation and scan state cleanup. --- src/modules/ble/BLE_Suite.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index b93458a2b..7aec69fdf 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -4592,22 +4592,18 @@ NimBLEAddress parseAddress(const String &addressInfo) { cleanAddr.trim(); cleanAddr.toUpperCase(); - // If it has the ":0" suffix from our return format, remove it if (cleanAddr.endsWith(":0")) { cleanAddr = cleanAddr.substring(0, cleanAddr.length() - 2); } - // Look for MAC pattern (XX:XX:XX:XX:XX:XX) int start = -1; int colonCount = 0; for (int i = 0; i < cleanAddr.length(); i++) { char c = cleanAddr.charAt(i); if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) { if (start == -1) start = i; - // Check if we have a valid MAC if (i - start + 1 >= 17) { String possibleMac = cleanAddr.substring(start, start + 17); - // Validate MAC format bool valid = true; for (int j = 0; j < 17; j++) { if (j % 3 == 2) { @@ -4630,7 +4626,6 @@ NimBLEAddress parseAddress(const String &addressInfo) { } else if (c == ':') { colonCount++; } else { - // Reset if we hit a non-valid character if (start != -1 && colonCount < 5) { start = -1; colonCount = 0; @@ -4638,7 +4633,6 @@ NimBLEAddress parseAddress(const String &addressInfo) { } } - // Try the simpler approach - just look for the first valid MAC for (int i = 0; i < addressInfo.length() - 17; i++) { String substr = addressInfo.substring(i, i + 17); bool valid = true; @@ -4994,7 +4988,6 @@ void BleSuiteMenu() { } if (check(EscPress)) { - // Clean up scan state without deinit if (g_pBLEScan) { g_pBLEScan->stop(); g_pBLEScan->clearResults(); @@ -5018,7 +5011,6 @@ void BleSuiteMenu() { if (check(SelPress)) { if (selected == MENU_ITEMS - 1) { BLE_Sniffer(); - // Clean up after sniffer if (g_pBLEScan) { g_pBLEScan->stop(); g_pBLEScan->clearResults(); @@ -5081,7 +5073,6 @@ void executeAttackWithTargetScan(int attackIndex) { showAttackProgress("Attack complete. Press any key to continue...", TFT_GREEN); while (!check(EscPress) && !check(SelPress) && !check(PrevPress) && !check(NextPress)) delay(50); - // Clean up scan state without deinit if (g_pBLEScan) { g_pBLEScan->stop(); g_pBLEScan->clearResults(); From 190b3a27094690e6ab06773762ae84a93c38ab6c Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Wed, 15 Jul 2026 21:59:53 +0200 Subject: [PATCH 31/74] Simplify BLE stack management and improve readability Removed conditional checks for LITE_VERSION to simplify BLE stack management. Updated comments for clarity and improved code readability. --- src/modules/ble/ble_common.cpp | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index 505ab3a20..b51baf897 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -14,6 +14,9 @@ #define CHARACTERISTIC_RX_UUID "1bc68da0-f3e3-11e9-81b4-2a2ae2dbcce4" #define CHARACTERISTIC_TX_UUID "1bc68efe-f3e3-11e9-81b4-2a2ae2dbcce4" +// Limit the number of devices to prevent memory issues +#define MAX_DISPLAY_DEVICES 100 + BLEScan *pBLEScan = nullptr; int scanTime = SCANTIME; // In seconds @@ -153,16 +156,12 @@ static bool is_ble_inited = false; void stopBLEStack() { if (pBLEScan) { pBLEScan->stop(); + pBLEScan->clearResults(); } if (is_ble_inited) { -#if !defined(LITE_VERSION) - if (BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0) { - BLEStateManager::deinitBLE(true); - } else -#endif - if (BLEDevice::getScan() != nullptr || BLEDevice::getAdvertising() != nullptr || - BLEDevice::getServer() != nullptr || BLEConnected) { + if (BLEDevice::getScan() != nullptr || BLEDevice::getAdvertising() != nullptr || + BLEDevice::getServer() != nullptr || BLEConnected) { BLEDevice::deinit(); } } @@ -249,9 +248,6 @@ void ble_scan() { options.reserve(MAX_DISPLAY_DEVICES); bool bleWasActiveBefore = BLEConnected || (BLEDevice::getServer() != nullptr); -#if !defined(LITE_VERSION) - bleWasActiveBefore = bleWasActiveBefore || BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0; -#endif if (!ble_scan_setup() || pBLEScan == nullptr) { displayError("Failed to init BLE scan"); @@ -266,11 +262,11 @@ void ble_scan() { bool scanStarted = pBLEScan->start(scanTime * 1000, false); if (!scanStarted) { displayError("Failed to start BLE scan"); + pBLEScan->clearResults(); return; } BLEScanResults foundDevices = pBLEScan->getResults(scanTime * 1000, false); #else - // NimBLE 1.x: start() returns results directly BLEScanResults foundDevices = pBLEScan->start(scanTime, false); #endif @@ -313,15 +309,9 @@ void ble_scan() { pBLEScan->clearResults(); } - // Only stop BLE if it wasn't active before + // Only stop BLE if it wasn't active before and we're done with it if (!bleWasActiveBefore) { -#if !defined(LITE_VERSION) - if (!BLEStateManager::isBLEActive()) { - stopBLEStack(); - } -#else stopBLEStack(); -#endif } if (!options.empty()) { @@ -372,7 +362,6 @@ bool initBLEServer() { } pRxCharacteristic->setCallbacks(new MyCallbacks()); - // Services start automatically when server starts return true; } @@ -453,6 +442,8 @@ void disPlayBLESend() { tft.setTextColor(TFT_WHITE); pServer->getAdvertising()->stop(); BLEConnected = false; + + // Don't stop BLE stack here - let caller handle it } void ble_test() { From 2c140b52da4fb05a5dafce6fc230a75769aee812 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 00:20:08 +0200 Subject: [PATCH 32/74] Downgrade NimBLE-Arduino library version Downgraded NimBLE-Arduino library version from 2.5 to 2.3.7 in platformio.ini. --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 7da672022..32dc5cda7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -195,7 +195,7 @@ lib_deps = earlephilhower/ESP8266SAM@^1.1.0 mikalhart/TinyGPSPlus tinyu-zhao/FFT@^0.0.1 - h2zero/NimBLE-Arduino@2.5 + h2zero/NimBLE-Arduino@2.3.7 nrf24/RF24 @ 1.4.11 Adafruit Si4713 Library@1.2.3 Bodmer/JPEGDecoder @@ -264,7 +264,7 @@ lib_deps = ;earlephilhower/ESP8266SAM@^1.1.0 mikalhart/TinyGPSPlus@1.1.0 tinyu-zhao/FFT@0.0.1 - h2zero/NimBLE-Arduino@2.5 + h2zero/NimBLE-Arduino@2.3.7 nrf24/RF24 @ 1.4.11 ;Adafruit Si4713 Library@1.2.3 Bodmer/JPEGDecoder From d387002ed32e4b992294e5ba2aeba01453e6325f Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 00:22:05 +0200 Subject: [PATCH 33/74] Update BLE_Suite.cpp --- src/modules/ble/BLE_Suite.cpp | 69 +++++++++++------------------------ 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index 7aec69fdf..71ea6f00e 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -2,7 +2,7 @@ * BLE Suite v3.1 - Complete BLE attack and analysis toolkit * Author: Ninja-jr * Version: 3.1 - * Last Updated: 15/07/2026 + * Last Updated: 16/07/2026 * * Contains: Vulnerability scanning, HID attacks, FastPair exploits, * HFP attacks, Audio attacks, DuckyScript injection, @@ -547,7 +547,7 @@ NimBLEClient *attemptConnectionWithStrategies(NimBLEAddress target, String &conn } //============================================================================= -// HID Exploit Engine +// HID Exploit Engine - Complete Implementation //============================================================================= HIDDeviceProfile HIDExploitEngine::analyzeHIDDevice(NimBLEAddress target, const String &name, int rssi) { @@ -1468,7 +1468,7 @@ bool WhisperPairExploit::executeAdvanced(NimBLEAddress target, int attackType) { } //============================================================================= -// Audio Attack Service +// Audio Attack Service - Complete Implementation //============================================================================= bool AudioAttackService::findAndAttackAudioServices(NimBLEClient *pClient) { @@ -1682,7 +1682,7 @@ bool AudioAttackService::crashAudioStack(NimBLEAddress target) { } //============================================================================= -// Ducky Script Engine +// Ducky Script Engine - Complete Implementation //============================================================================= DuckyScriptEngine::DuckyScriptEngine() : scriptLoaded(false) {} @@ -1859,7 +1859,7 @@ void DuckyScriptEngine::clear() { size_t DuckyScriptEngine::getCommandCount() { return commands.size(); } //============================================================================= -// HID Ducky Service +// HID Ducky Service - Complete Implementation //============================================================================= HIDDuckyService::HIDDuckyService() : defaultDelay(100) {} @@ -2219,7 +2219,7 @@ void HIDDuckyService::setDefaultDelay(int delay_ms) { defaultDelay = delay_ms; } size_t HIDDuckyService::getScriptSize() { return duckyEngine.getCommandCount(); } //============================================================================= -// Auth Bypass Engine +// Auth Bypass Engine - Complete Implementation //============================================================================= AuthBypassEngine::AuthBypassEngine() { @@ -2373,7 +2373,7 @@ bool AuthBypassEngine::exploitAuthBypass(NimBLEAddress target) { } //============================================================================= -// Multi Connection Attack +// Multi Connection Attack - Complete Implementation //============================================================================= MultiConnectionAttack::MultiConnectionAttack() {} @@ -2573,7 +2573,7 @@ void MultiConnectionAttack::cleanup() { } //============================================================================= -// Vulnerability Scanner +// Vulnerability Scanner - Complete Implementation //============================================================================= VulnerabilityScanner::VulnerabilityScanner() { vulnerabilityChecks.clear(); } @@ -2645,7 +2645,7 @@ std::vector VulnerabilityScanner::getVulnerabilities() { } //============================================================================= -// HID Attack Service +// HID Attack Service - Complete Implementation //============================================================================= bool HIDAttackServiceClass::injectKeystrokes(NimBLEAddress target) { @@ -2813,7 +2813,7 @@ bool HIDAttackServiceClass::forceHIDKeystrokes(NimBLEAddress target, const Strin } //============================================================================= -// Pairing Attack Service +// Pairing Attack Service - Complete Implementation //============================================================================= bool PairingAttackServiceClass::bruteForcePIN(NimBLEAddress target) { @@ -2885,7 +2885,7 @@ bool PairingAttackServiceClass::bruteForcePIN(NimBLEAddress target) { } //============================================================================= -// DoS Attack Service +// DoS Attack Service - Complete Implementation //============================================================================= bool DoSAttackServiceClass::connectionFlood(NimBLEAddress target) { @@ -2972,7 +2972,7 @@ bool DoSAttackServiceClass::advertisingSpam(NimBLEAddress target) { } //============================================================================= -// File Operations +// File Operations - Complete Implementation //============================================================================= String selectFileFromSD() { @@ -3290,15 +3290,11 @@ std::vector FastPairExploitEngine::scanForFastPairDevices(in pScan->setInterval(97); pScan->setWindow(67); -#ifdef NIMBLE_V2_PLUS - pScan->start(duration * 1000, false); - NimBLEScanResults results = pScan->getResults(duration * 1000, false); -#else + // NimBLE 2.3.7: start() returns results directly NimBLEScanResults results = pScan->start(duration, false); -#endif for (int i = 0; i < results.getCount(); i++) { - const NimBLEAdvertisedDevice *device = results.getDevice(i); + NimBLEAdvertisedDevice *device = results.getDevice(i); String address = String(device->getAddress().toString().c_str()); String name = device->getName().c_str(); @@ -3457,7 +3453,7 @@ bool FastPairExploitEngine::testVulnerability(NimBLEAddress target) { } //============================================================================= -// FastPair Helpers +// FastPair Helpers - Complete Implementation //============================================================================= NimBLERemoteCharacteristic *FastPairExploitEngine::findKBPCharacteristic(NimBLERemoteService *service) { @@ -3931,15 +3927,11 @@ void BLE_Sniffer() { padprintln("Status: CAPTURING..."); padprintln("Press [SEL] to stop"); -#ifdef NIMBLE_V2_PLUS - pScan->start(10 * 1000, true); - NimBLEScanResults results = pScan->getResults(10 * 1000, true); -#else + // NimBLE 2.3.7: getResults works directly NimBLEScanResults results = pScan->getResults(10 * 1000, true); -#endif for (int i = 0; i < results.getCount(); i++) { - const NimBLEAdvertisedDevice *device = results.getDevice(i); + NimBLEAdvertisedDevice *device = results.getDevice(i); SnifferPacket packet; packet.address = String(device->getAddress().toString().c_str()); @@ -4202,19 +4194,11 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 80); tft.print("Active scan (8s)..."); -#ifdef NIMBLE_V2_PLUS - bool scanStarted = g_pBLEScan->start(ACTIVE_SCAN_TIME * 1000, false); - if (!scanStarted) { - displayError("Failed to start BLE scan"); - return ""; - } - BLEScanResults activeResults = g_pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); -#else + // NimBLE 2.3.7: start() returns BLEScanResults directly BLEScanResults activeResults = g_pBLEScan->start(ACTIVE_SCAN_TIME, false); -#endif for (int i = 0; i < activeResults.getCount(); i++) { - const NimBLEAdvertisedDevice *device = activeResults.getDevice(i); + NimBLEAdvertisedDevice *device = activeResults.getDevice(i); if (!device) continue; String address = String(device->getAddress().toString().c_str()); @@ -4247,19 +4231,10 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 100); tft.print("Passive scan (8s)..."); -#ifdef NIMBLE_V2_PLUS - bool passiveScanStarted = g_pBLEScan->start(PASSIVE_SCAN_TIME * 1000, false); - if (!passiveScanStarted) { - displayError("Failed to start passive BLE scan"); - return ""; - } - BLEScanResults passiveResults = g_pBLEScan->getResults(PASSIVE_SCAN_TIME * 1000, false); -#else BLEScanResults passiveResults = g_pBLEScan->start(PASSIVE_SCAN_TIME, false); -#endif for (int i = 0; i < passiveResults.getCount(); i++) { - const NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); + NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); if (!device) continue; String address = String(device->getAddress().toString().c_str()); @@ -4287,7 +4262,7 @@ String selectTargetFromScan(const char *title) { scannerData.addDevice(name, address, rssi, fastPair, hasHFP, deviceType); } - // Stop the scan but don't clear results yet + // Stop the scan but DON'T clear results yet - we need them for display if (g_pBLEScan) { g_pBLEScan->stop(); g_bleScanActive = false; @@ -4584,7 +4559,7 @@ String selectMultipleTargetsFromScan(const char *title, std::vector Date: Thu, 16 Jul 2026 00:23:38 +0200 Subject: [PATCH 34/74] Fix preprocessor directive closing in BLE_Suite.cpp From ec9ec308196205421b56cafe87e58fb5ce405760 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 00:24:17 +0200 Subject: [PATCH 35/74] Refactor BLE handling for NimBLE 2.3.7 compatibility --- src/modules/ble/ble_common.cpp | 63 +++------------------------------- 1 file changed, 5 insertions(+), 58 deletions(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index b51baf897..48e4b3701 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -40,12 +40,6 @@ bool bleNotifyRetry(NimBLECharacteristic *chr, uint8_t retries) { return false; } -#if __has_include() -#define NIMBLE_V2_PLUS 1 -#endif - -#define ENDIAN_CHANGE_U16(x) ((((x) & 0xFF00) >> 8) + (((x) & 0xFF) << 8)) - BLEServer *pServer = NULL; BLEService *pService = NULL; BLECharacteristic *pTxCharacteristic; @@ -87,38 +81,6 @@ void ble_info(const String &name, const String &address, const String &signal) { } } -#ifdef NIMBLE_V2_PLUS -class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { - void onResult(const NimBLEAdvertisedDevice *advertisedDevice) { - if (options.size() >= MAX_DISPLAY_DEVICES) { - if (pBLEScan) { - pBLEScan->stop(); - Serial.println("Reached max devices, stopping scan"); - } - return; - } - - String bt_title; - String bt_name; - String bt_address; - String bt_signal; - - bt_name = advertisedDevice->getName().c_str(); - bt_address = advertisedDevice->getAddress().toString().c_str(); - bt_signal = String(advertisedDevice->getRSSI()); - - if (bt_name.isEmpty()) bt_name = ""; - bt_title = bt_name; - if (bt_title.isEmpty()) bt_title = bt_address; - - if (options.size() < MAX_DISPLAY_DEVICES) { - options.emplace_back(bt_title.c_str(), [=]() { - ble_info(bt_name, bt_address, bt_signal); - }); - } - } -}; -#else class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { void onResult(NimBLEAdvertisedDevice *advertisedDevice) { if (options.size() >= MAX_DISPLAY_DEVICES) { @@ -149,7 +111,6 @@ class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { } } }; -#endif static bool is_ble_inited = false; @@ -214,12 +175,7 @@ bool ble_scan_setup() { return false; } -#ifdef NIMBLE_V2_PLUS - pBLEScan->setScanCallbacks(new AdvertisedDeviceCallbacks()); -#else pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); -#endif - pBLEScan->setActiveScan(true); pBLEScan->setInterval(SCAN_INT); pBLEScan->setWindow(SCAN_WINDOW); @@ -257,24 +213,14 @@ void ble_scan() { // Clear previous results before scanning pBLEScan->clearResults(); -#ifdef NIMBLE_V2_PLUS - // NimBLE 2.x: start() returns bool, getResults() gets the data - bool scanStarted = pBLEScan->start(scanTime * 1000, false); - if (!scanStarted) { - displayError("Failed to start BLE scan"); - pBLEScan->clearResults(); - return; - } - BLEScanResults foundDevices = pBLEScan->getResults(scanTime * 1000, false); -#else + // NimBLE 2.3.7: start() returns BLEScanResults directly BLEScanResults foundDevices = pBLEScan->start(scanTime, false); -#endif int deviceCount = foundDevices.getCount(); int processedCount = 0; for (int i = 0; i < deviceCount && processedCount < MAX_DISPLAY_DEVICES; i++) { - const NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); + NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); if (!advertisedDevice) continue; String bt_title; @@ -362,6 +308,9 @@ bool initBLEServer() { } pRxCharacteristic->setCallbacks(new MyCallbacks()); + // pService->start() - works in NimBLE 2.3.7 + pService->start(); + return true; } @@ -442,8 +391,6 @@ void disPlayBLESend() { tft.setTextColor(TFT_WHITE); pServer->getAdvertising()->stop(); BLEConnected = false; - - // Don't stop BLE stack here - let caller handle it } void ble_test() { From 734edafdcd9b0726234b505cb17a4fcb2d4245ee Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 00:24:50 +0200 Subject: [PATCH 36/74] Clean up ble_common.h by removing comments Removed commented-out includes and unnecessary comments from ble_common.h. --- src/modules/ble/ble_common.h | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index 3023e4896..7a41e8d59 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -1,11 +1,9 @@ #ifndef __BLE_COMMON_H__ #define __BLE_COMMON_H__ -// #include "BLE2902.h" #include #include #include - #include #include #include @@ -27,7 +25,7 @@ extern BLEScan *pBLEScan; extern int scanTime; void ble_test(); -#if 0 // keep it out for now +#if 0 #ifdef BOARD_HAS_PSRAM constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = false; #else @@ -37,29 +35,13 @@ constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = true; constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = false; #endif -// Initialize BLE scan with proper memory management -// Returns: true on success, false if aborted (e.g. not enough contiguous RAM) bool ble_scan_setup(); - -// Perform BLE scan and display results -// Automatically handles device limits to prevent memory issues -// Compatible with NimBLE 1.x and 2.x void ble_scan(); - -// Safely stop BLE stack and clean up resources -// Does not aggressively deinit if BLE is still needed void stopBLEStack(); -// GATT notification tolerant to temporary MSYS-pool exhaustion. -// NimBLECharacteristic::notify() returns false when the os_mbuf could not be -// allocated (MSYS pool full, made worse by reducing -// CONFIG_BT_NIMBLE_MSYS_*_BLOCK_COUNT). If untreated, the notification is silently -// dropped (lost HID key / BLE-serial chunk). Retry while yielding 1 tick for the -// host to drain the pool. Mirrors wifiRawTx() on the Wi-Fi side. bool bleNotifyRetry(NimBLECharacteristic *chr, const uint8_t *value, size_t length, uint8_t retries = 8); bool bleNotifyRetry(NimBLECharacteristic *chr, uint8_t retries = 8); -// Display BLE send interface void disPlayBLESend(); #endif From 3b9b208fa79527eeb508613d54c0d5de6e8af4d1 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 00:27:04 +0200 Subject: [PATCH 37/74] Refactor BLE_Suite.h with new device structures Updated BLE_Suite.h to include new structures and methods for device information and scanning, while commenting out legacy code for NimBLEExtAdvertising. --- src/modules/ble/BLE_Suite.h | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index 9ab4520b6..1bffaacde 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -14,9 +14,10 @@ #include #include -#if __has_include() -#define NIMBLE_V2_PLUS 1 -#endif +// NimBLE 2.3.7 doesn't have NimBLEExtAdvertising.h +// #if __has_include() +// #define NIMBLE_V2_PLUS 1 +// #endif extern volatile int tftWidth; extern volatile int tftHeight; @@ -51,7 +52,7 @@ enum FastPairExploitType { }; //============================================================================= -// NEW: DeviceInfo and DeviceSnapshot structures +// DeviceInfo and DeviceSnapshot structures //============================================================================= struct DeviceInfo { @@ -78,7 +79,7 @@ struct DeviceSnapshot { }; //============================================================================= -// NEW: SelectedDevice for passing device info to attacks +// SelectedDevice for passing device info to attacks //============================================================================= struct SelectedDevice { @@ -91,7 +92,7 @@ struct SelectedDevice { }; //============================================================================= -// UPDATED: ScannerData with new methods and members +// ScannerData with snapshot methods //============================================================================= struct ScannerData { @@ -104,7 +105,6 @@ struct ScannerData { SemaphoreHandle_t mutex; int foundCount; - // NEW: Version tracking and snapshot cache uint32_t dataVersion; DeviceSnapshot* snapshotCache; uint32_t cacheTimestamp; @@ -114,8 +114,6 @@ struct ScannerData { void addDevice(const String& name, const String& address, int rssi, bool fastPair, bool hasHFP, uint8_t type); void clear(); size_t size(); - - // NEW: Snapshot methods DeviceSnapshot* getSnapshot(); bool getDeviceInfo(int index, DeviceInfo &info); }; @@ -250,12 +248,10 @@ class FastPairExploitEngine { void spamFastPairPopups(FastPairPopupType popupType, int count); bool testVulnerability(NimBLEAddress target); - // v3.1: Smart FastPair attack with Samsung detection bool smartExploit(NimBLEAddress target); bool exploitSamsungFastPair(NimBLEAddress target); bool exploitGoogleFastPair(NimBLEAddress target); - // Public exploit methods bool executeMemoryCorruption(NimBLERemoteCharacteristic* pChar); bool executeStateConfusion(NimBLERemoteCharacteristic* pChar); bool executeCryptoOverflow(NimBLERemoteCharacteristic* pChar); @@ -485,7 +481,7 @@ void runConnectionFlood(NimBLEAddress target); void runAdvertisingSpam(NimBLEAddress target); //============================================================================= -// UPDATED: Attack functions with SelectedDevice parameter +// Attack functions with SelectedDevice parameter //============================================================================= void runQuickTest(NimBLEAddress target, SelectedDevice deviceInfo); @@ -493,7 +489,7 @@ void runDeviceProfiling(NimBLEAddress target, SelectedDevice deviceInfo); void runUniversalAttack(NimBLEAddress target, SelectedDevice deviceInfo); //============================================================================= -// UPDATED: Submenu functions with SelectedDevice parameter +// Submenu functions with SelectedDevice parameter //============================================================================= void showFastPairSubMenu(NimBLEAddress target, SelectedDevice deviceInfo); @@ -506,7 +502,7 @@ void showPayloadSubMenu(NimBLEAddress target, SelectedDevice deviceInfo); void showTestingSubMenu(NimBLEAddress target, SelectedDevice deviceInfo); //============================================================================= -// Original function declarations (keep these) +// Original function declarations //============================================================================= void runWriteAccessTest(NimBLEAddress target); From d201e2c7212bd054c442fd395aa4e20240dc1b37 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 00:41:40 +0200 Subject: [PATCH 38/74] Enhance BLE support for NimBLE 2.x Refactor BLE scanning and advertising callbacks for NimBLE 2.x compatibility. Adjusted BLE stack initialization and scanning logic based on NimBLE version. --- src/modules/ble/ble_common.cpp | 90 ++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index 48e4b3701..fed38eecb 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -40,6 +40,12 @@ bool bleNotifyRetry(NimBLECharacteristic *chr, uint8_t retries) { return false; } +#if __has_include() +#define NIMBLE_V2_PLUS 1 +#endif + +#define ENDIAN_CHANGE_U16(x) ((((x) & 0xFF00) >> 8) + (((x) & 0xFF) << 8)) + BLEServer *pServer = NULL; BLEService *pService = NULL; BLECharacteristic *pTxCharacteristic; @@ -81,6 +87,40 @@ void ble_info(const String &name, const String &address, const String &signal) { } } +#ifdef NIMBLE_V2_PLUS +// NimBLE 2.x uses NimBLEScanCallbacks with const pointers +class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) { + if (options.size() >= MAX_DISPLAY_DEVICES) { + if (pBLEScan) { + pBLEScan->stop(); + Serial.println("Reached max devices, stopping scan"); + } + return; + } + + String bt_title; + String bt_name; + String bt_address; + String bt_signal; + + bt_name = advertisedDevice->getName().c_str(); + bt_address = advertisedDevice->getAddress().toString().c_str(); + bt_signal = String(advertisedDevice->getRSSI()); + + if (bt_name.isEmpty()) bt_name = ""; + bt_title = bt_name; + if (bt_title.isEmpty()) bt_title = bt_address; + + if (options.size() < MAX_DISPLAY_DEVICES) { + options.emplace_back(bt_title.c_str(), [=]() { + ble_info(bt_name, bt_address, bt_signal); + }); + } + } +}; +#else +// NimBLE 1.x uses NimBLEAdvertisedDeviceCallbacks class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { void onResult(NimBLEAdvertisedDevice *advertisedDevice) { if (options.size() >= MAX_DISPLAY_DEVICES) { @@ -111,6 +151,7 @@ class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { } } }; +#endif static bool is_ble_inited = false; @@ -121,8 +162,13 @@ void stopBLEStack() { } if (is_ble_inited) { - if (BLEDevice::getScan() != nullptr || BLEDevice::getAdvertising() != nullptr || - BLEDevice::getServer() != nullptr || BLEConnected) { +#if !defined(LITE_VERSION) + if (BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0) { + BLEStateManager::deinitBLE(true); + } else +#endif + if (BLEDevice::getScan() != nullptr || BLEDevice::getAdvertising() != nullptr || + BLEDevice::getServer() != nullptr || BLEConnected) { BLEDevice::deinit(); } } @@ -175,7 +221,12 @@ bool ble_scan_setup() { return false; } +#ifdef NIMBLE_V2_PLUS + pBLEScan->setScanCallbacks(new AdvertisedDeviceCallbacks()); +#else pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); +#endif + pBLEScan->setActiveScan(true); pBLEScan->setInterval(SCAN_INT); pBLEScan->setWindow(SCAN_WINDOW); @@ -204,6 +255,9 @@ void ble_scan() { options.reserve(MAX_DISPLAY_DEVICES); bool bleWasActiveBefore = BLEConnected || (BLEDevice::getServer() != nullptr); +#if !defined(LITE_VERSION) + bleWasActiveBefore = bleWasActiveBefore || BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0; +#endif if (!ble_scan_setup() || pBLEScan == nullptr) { displayError("Failed to init BLE scan"); @@ -213,14 +267,31 @@ void ble_scan() { // Clear previous results before scanning pBLEScan->clearResults(); - // NimBLE 2.3.7: start() returns BLEScanResults directly +#ifdef NIMBLE_V2_PLUS + // NimBLE 2.x: start() returns bool, getResults() gets the data + // Time is in milliseconds for NimBLE 2.x + bool scanStarted = pBLEScan->start(scanTime * 1000, false); + if (!scanStarted) { + displayError("Failed to start BLE scan"); + pBLEScan->clearResults(); + return; + } + // Get results - timeout in milliseconds + BLEScanResults foundDevices = pBLEScan->getResults(scanTime * 1000, false); +#else + // NimBLE 1.x: start() returns results directly, time in seconds BLEScanResults foundDevices = pBLEScan->start(scanTime, false); +#endif int deviceCount = foundDevices.getCount(); int processedCount = 0; for (int i = 0; i < deviceCount && processedCount < MAX_DISPLAY_DEVICES; i++) { +#ifdef NIMBLE_V2_PLUS + const NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); +#else NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); +#endif if (!advertisedDevice) continue; String bt_title; @@ -257,7 +328,13 @@ void ble_scan() { // Only stop BLE if it wasn't active before and we're done with it if (!bleWasActiveBefore) { +#if !defined(LITE_VERSION) + if (!BLEStateManager::isBLEActive()) { + stopBLEStack(); + } +#else stopBLEStack(); +#endif } if (!options.empty()) { @@ -308,8 +385,13 @@ bool initBLEServer() { } pRxCharacteristic->setCallbacks(new MyCallbacks()); - // pService->start() - works in NimBLE 2.3.7 +#ifdef NIMBLE_V2_PLUS + // NimBLE 2.x: Services start automatically when server starts + // No need to call pService->start() +#else + // NimBLE 1.x: Need to call pService->start() pService->start(); +#endif return true; } From c3e7bd42c75eea7a06b44bc29f0b032cffae7376 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 01:30:50 +0200 Subject: [PATCH 39/74] Update BLE_Suite.cpp --- src/modules/ble/BLE_Suite.cpp | 118 +++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 39 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index 71ea6f00e..d4e4d17f0 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -48,7 +48,7 @@ static bool g_bleScanActive = false; static SelectedDevice g_selectedDevice; //============================================================================= -// Cleanup Function - Safe version that doesn't deinit BLE +// Cleanup Function - Only stops scan, doesn't clear data //============================================================================= void cleanupBLESuiteState() { @@ -57,15 +57,8 @@ void cleanupBLESuiteState() { g_pBLEScan->clearResults(); g_bleScanActive = false; } - - g_selectedDevice.address = ""; - g_selectedDevice.name = ""; - g_selectedDevice.rssi = 0; - g_selectedDevice.hasFastPair = false; - g_selectedDevice.hasHFP = false; - g_selectedDevice.deviceType = 0; - - scannerData.clear(); + // DO NOT clear scannerData or g_selectedDevice here + // They persist between operations delay(50); } @@ -547,7 +540,7 @@ NimBLEClient *attemptConnectionWithStrategies(NimBLEAddress target, String &conn } //============================================================================= -// HID Exploit Engine - Complete Implementation +// HID Exploit Engine //============================================================================= HIDDeviceProfile HIDExploitEngine::analyzeHIDDevice(NimBLEAddress target, const String &name, int rssi) { @@ -1468,7 +1461,7 @@ bool WhisperPairExploit::executeAdvanced(NimBLEAddress target, int attackType) { } //============================================================================= -// Audio Attack Service - Complete Implementation +// Audio Attack Service //============================================================================= bool AudioAttackService::findAndAttackAudioServices(NimBLEClient *pClient) { @@ -1682,7 +1675,7 @@ bool AudioAttackService::crashAudioStack(NimBLEAddress target) { } //============================================================================= -// Ducky Script Engine - Complete Implementation +// Ducky Script Engine //============================================================================= DuckyScriptEngine::DuckyScriptEngine() : scriptLoaded(false) {} @@ -1859,7 +1852,7 @@ void DuckyScriptEngine::clear() { size_t DuckyScriptEngine::getCommandCount() { return commands.size(); } //============================================================================= -// HID Ducky Service - Complete Implementation +// HID Ducky Service //============================================================================= HIDDuckyService::HIDDuckyService() : defaultDelay(100) {} @@ -2219,7 +2212,7 @@ void HIDDuckyService::setDefaultDelay(int delay_ms) { defaultDelay = delay_ms; } size_t HIDDuckyService::getScriptSize() { return duckyEngine.getCommandCount(); } //============================================================================= -// Auth Bypass Engine - Complete Implementation +// Auth Bypass Engine //============================================================================= AuthBypassEngine::AuthBypassEngine() { @@ -2373,7 +2366,7 @@ bool AuthBypassEngine::exploitAuthBypass(NimBLEAddress target) { } //============================================================================= -// Multi Connection Attack - Complete Implementation +// Multi Connection Attack //============================================================================= MultiConnectionAttack::MultiConnectionAttack() {} @@ -2573,7 +2566,7 @@ void MultiConnectionAttack::cleanup() { } //============================================================================= -// Vulnerability Scanner - Complete Implementation +// Vulnerability Scanner //============================================================================= VulnerabilityScanner::VulnerabilityScanner() { vulnerabilityChecks.clear(); } @@ -2645,7 +2638,7 @@ std::vector VulnerabilityScanner::getVulnerabilities() { } //============================================================================= -// HID Attack Service - Complete Implementation +// HID Attack Service //============================================================================= bool HIDAttackServiceClass::injectKeystrokes(NimBLEAddress target) { @@ -2813,7 +2806,7 @@ bool HIDAttackServiceClass::forceHIDKeystrokes(NimBLEAddress target, const Strin } //============================================================================= -// Pairing Attack Service - Complete Implementation +// Pairing Attack Service //============================================================================= bool PairingAttackServiceClass::bruteForcePIN(NimBLEAddress target) { @@ -2885,7 +2878,7 @@ bool PairingAttackServiceClass::bruteForcePIN(NimBLEAddress target) { } //============================================================================= -// DoS Attack Service - Complete Implementation +// DoS Attack Service //============================================================================= bool DoSAttackServiceClass::connectionFlood(NimBLEAddress target) { @@ -2972,7 +2965,7 @@ bool DoSAttackServiceClass::advertisingSpam(NimBLEAddress target) { } //============================================================================= -// File Operations - Complete Implementation +// File Operations //============================================================================= String selectFileFromSD() { @@ -3290,11 +3283,23 @@ std::vector FastPairExploitEngine::scanForFastPairDevices(in pScan->setInterval(97); pScan->setWindow(67); - // NimBLE 2.3.7: start() returns results directly +#ifdef NIMBLE_V2_PLUS + bool scanStarted = pScan->start(duration * 1000, false); + if (!scanStarted) { + showAttackProgress("Failed to start FastPair scan", TFT_RED); + return discoveredDevices; + } + NimBLEScanResults results = pScan->getResults(duration * 1000, false); +#else NimBLEScanResults results = pScan->start(duration, false); +#endif for (int i = 0; i < results.getCount(); i++) { +#ifdef NIMBLE_V2_PLUS + const NimBLEAdvertisedDevice *device = results.getDevice(i); +#else NimBLEAdvertisedDevice *device = results.getDevice(i); +#endif String address = String(device->getAddress().toString().c_str()); String name = device->getName().c_str(); @@ -3453,7 +3458,7 @@ bool FastPairExploitEngine::testVulnerability(NimBLEAddress target) { } //============================================================================= -// FastPair Helpers - Complete Implementation +// FastPair Helpers //============================================================================= NimBLERemoteCharacteristic *FastPairExploitEngine::findKBPCharacteristic(NimBLERemoteService *service) { @@ -3927,11 +3932,19 @@ void BLE_Sniffer() { padprintln("Status: CAPTURING..."); padprintln("Press [SEL] to stop"); - // NimBLE 2.3.7: getResults works directly +#ifdef NIMBLE_V2_PLUS + pScan->start(10 * 1000, true); + NimBLEScanResults results = pScan->getResults(10 * 1000, true); +#else NimBLEScanResults results = pScan->getResults(10 * 1000, true); +#endif for (int i = 0; i < results.getCount(); i++) { +#ifdef NIMBLE_V2_PLUS + const NimBLEAdvertisedDevice *device = results.getDevice(i); +#else NimBLEAdvertisedDevice *device = results.getDevice(i); +#endif SnifferPacket packet; packet.address = String(device->getAddress().toString().c_str()); @@ -4135,7 +4148,7 @@ void BLE_Sniffer() { //============================================================================= String selectTargetFromScan(const char *title) { - // Don't clear scannerData at start - keep existing data + // DO NOT clear scannerData here - it persists between operations g_selectedDevice.address = ""; g_selectedDevice.name = ""; @@ -4194,11 +4207,23 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 80); tft.print("Active scan (8s)..."); - // NimBLE 2.3.7: start() returns BLEScanResults directly +#ifdef NIMBLE_V2_PLUS + bool scanStarted = g_pBLEScan->start(ACTIVE_SCAN_TIME * 1000, false); + if (!scanStarted) { + displayError("Failed to start BLE scan"); + return ""; + } + BLEScanResults activeResults = g_pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); +#else BLEScanResults activeResults = g_pBLEScan->start(ACTIVE_SCAN_TIME, false); +#endif for (int i = 0; i < activeResults.getCount(); i++) { +#ifdef NIMBLE_V2_PLUS + const NimBLEAdvertisedDevice *device = activeResults.getDevice(i); +#else NimBLEAdvertisedDevice *device = activeResults.getDevice(i); +#endif if (!device) continue; String address = String(device->getAddress().toString().c_str()); @@ -4231,10 +4256,23 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 100); tft.print("Passive scan (8s)..."); +#ifdef NIMBLE_V2_PLUS + bool passiveScanStarted = g_pBLEScan->start(PASSIVE_SCAN_TIME * 1000, false); + if (!passiveScanStarted) { + displayError("Failed to start passive BLE scan"); + return ""; + } + BLEScanResults passiveResults = g_pBLEScan->getResults(PASSIVE_SCAN_TIME * 1000, false); +#else BLEScanResults passiveResults = g_pBLEScan->start(PASSIVE_SCAN_TIME, false); +#endif for (int i = 0; i < passiveResults.getCount(); i++) { +#ifdef NIMBLE_V2_PLUS + const NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); +#else NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); +#endif if (!device) continue; String address = String(device->getAddress().toString().c_str()); @@ -4286,6 +4324,7 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 100); tft.print("turned on and in range."); delay(2000); + // DO NOT clear scannerData - it was already empty return ""; } @@ -4409,11 +4448,9 @@ String selectTargetFromScan(const char *title) { scrollOffset = 0; } } else if (check(SelPress)) { - // Get the MAC address directly from the snapshot String selectedMAC = snapshot->addresses[selectedIdx]; String selectedName = snapshot->names[selectedIdx]; - // Clean the MAC address selectedMAC.trim(); selectedMAC.toUpperCase(); @@ -4428,13 +4465,14 @@ String selectTargetFromScan(const char *title) { returnMac.trim(); delete snapshot; + // DO NOT clear scannerData here - keep it for potential reuse return returnMac; } delay(50); } delete snapshot; - scannerData.clear(); + // DO NOT clear scannerData here - keep it for potential reuse return ""; } @@ -4864,7 +4902,7 @@ void runAdvertisingSpam(NimBLEAddress target) { } //============================================================================= -// Menu System +// Menu System - Clear data ONLY at entry and exit //============================================================================= static bool welcomeShown = false; @@ -4893,6 +4931,11 @@ void showWelcomeScreen() { } void BleSuiteMenu() { + // Clear data when entering the menu + scannerData.clear(); + g_selectedDevice.address = ""; + g_selectedDevice.name = ""; + showWelcomeScreen(); const int MENU_ITEMS = 12; @@ -4963,12 +5006,15 @@ void BleSuiteMenu() { } if (check(EscPress)) { + // Clear data when exiting the menu if (g_pBLEScan) { g_pBLEScan->stop(); g_pBLEScan->clearResults(); g_bleScanActive = false; } scannerData.clear(); + g_selectedDevice.address = ""; + g_selectedDevice.name = ""; return; } if (check(PrevPress)) { @@ -4986,12 +5032,7 @@ void BleSuiteMenu() { if (check(SelPress)) { if (selected == MENU_ITEMS - 1) { BLE_Sniffer(); - if (g_pBLEScan) { - g_pBLEScan->stop(); - g_pBLEScan->clearResults(); - g_bleScanActive = false; - } - scannerData.clear(); + // Don't clear data - keep it for the menu } else { executeAttackWithTargetScan(selected); } @@ -5048,14 +5089,13 @@ void executeAttackWithTargetScan(int attackIndex) { showAttackProgress("Attack complete. Press any key to continue...", TFT_GREEN); while (!check(EscPress) && !check(SelPress) && !check(PrevPress) && !check(NextPress)) delay(50); + // Clean up scan state but DON'T clear scannerData if (g_pBLEScan) { g_pBLEScan->stop(); g_pBLEScan->clearResults(); g_bleScanActive = false; } - scannerData.clear(); - g_selectedDevice.address = ""; - g_selectedDevice.name = ""; + // Keep scannerData and g_selectedDevice for potential reuse delay(100); } From f829daa2b681b607c10e9294f0e9a94bcb6f94ae Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 03:03:36 +0200 Subject: [PATCH 40/74] Clean up comments and fix preprocessor directive Removed comment about not clearing scannerData and fixed preprocessor directive formatting. --- src/modules/ble/BLE_Suite.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index d4e4d17f0..ccd39bd95 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -4324,7 +4324,6 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 100); tft.print("turned on and in range."); delay(2000); - // DO NOT clear scannerData - it was already empty return ""; } From 1778e80440e4f114f8200abf3ff14181be30181f Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 10:58:32 +0200 Subject: [PATCH 41/74] Implement NimBLE version detection and API adjustments Added version detection for NimBLE and adjusted API usage based on version. Updated scan methods to support both NimBLE 1.x and 2.x. --- src/modules/ble/BLE_Suite.cpp | 62 ++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index ccd39bd95..f2c461709 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -26,6 +26,41 @@ #include #include +// NimBLE version detection +// Check for NimBLE version via multiple methods +#if defined(NIMBLE_VERSION) + #if NIMBLE_VERSION >= 20000 + #define NIMBLE_V2_PLUS 1 + #endif +#elif defined(NIMBLE_CPP_VERSION) && NIMBLE_CPP_VERSION >= 2 + #define NIMBLE_V2_PLUS 1 +#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 + #define NIMBLE_V2_PLUS 1 +#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR == 1 && NIMBLE_VERSION_MINOR >= 5 + // v1.5+ has some v2 features + #define NIMBLE_V2_PLUS 1 +#elif __has_include() + #define NIMBLE_V2_PLUS 1 +#endif + +// If we're compiling with a version that has the new scan API +// Check if NimBLEScan::start returns NimBLEScanResults or bool +// We'll use a simpler approach: detect by checking if NimBLEScanResults is defined +#ifdef __has_include + #if __has_include() + // Check if NimBLEScanResults type exists (v2.x) + // We'll use a compile-time detection approach + #ifndef NIMBLE_V2_PLUS + // Try to detect v2 by checking for NimBLEScanResults type + class __NimBLE_Scanner_Detector { + static void test(NimBLEScanResults*) {} + }; + // If this compiles, we're on v2+ + #define NIMBLE_V2_PLUS 1 + #endif + #endif +#endif + int showSubMenu(const char *title, const char *options[], int optionCount); extern tft_logger tft; @@ -540,7 +575,7 @@ NimBLEClient *attemptConnectionWithStrategies(NimBLEAddress target, String &conn } //============================================================================= -// HID Exploit Engine +// HID Exploit Engine - Complete Implementation //============================================================================= HIDDeviceProfile HIDExploitEngine::analyzeHIDDevice(NimBLEAddress target, const String &name, int rssi) { @@ -616,6 +651,10 @@ HIDDeviceProfile HIDExploitEngine::analyzeHIDDevice(NimBLEAddress target, const return profile; } +//============================================================================= +// [HID Exploit Engine functions - unchanged from previous version] +//============================================================================= + bool HIDExploitEngine::tryAppleMagicSpoof(NimBLEAddress target, HIDDeviceProfile profile) { AutoCleanup cleanup([]() { BLEStateManager::deinitBLE(true); }); @@ -3283,7 +3322,9 @@ std::vector FastPairExploitEngine::scanForFastPairDevices(in pScan->setInterval(97); pScan->setWindow(67); -#ifdef NIMBLE_V2_PLUS + // This is the key fix - use the correct API based on NimBLE version +#if defined(NIMBLE_V2_PLUS) + // NimBLE 2.x API: start returns bool, getResults returns NimBLEScanResults bool scanStarted = pScan->start(duration * 1000, false); if (!scanStarted) { showAttackProgress("Failed to start FastPair scan", TFT_RED); @@ -3291,11 +3332,12 @@ std::vector FastPairExploitEngine::scanForFastPairDevices(in } NimBLEScanResults results = pScan->getResults(duration * 1000, false); #else + // NimBLE 1.x API: start returns NimBLEScanResults NimBLEScanResults results = pScan->start(duration, false); #endif for (int i = 0; i < results.getCount(); i++) { -#ifdef NIMBLE_V2_PLUS +#if defined(NIMBLE_V2_PLUS) const NimBLEAdvertisedDevice *device = results.getDevice(i); #else NimBLEAdvertisedDevice *device = results.getDevice(i); @@ -3932,7 +3974,7 @@ void BLE_Sniffer() { padprintln("Status: CAPTURING..."); padprintln("Press [SEL] to stop"); -#ifdef NIMBLE_V2_PLUS +#if defined(NIMBLE_V2_PLUS) pScan->start(10 * 1000, true); NimBLEScanResults results = pScan->getResults(10 * 1000, true); #else @@ -3940,7 +3982,7 @@ void BLE_Sniffer() { #endif for (int i = 0; i < results.getCount(); i++) { -#ifdef NIMBLE_V2_PLUS +#if defined(NIMBLE_V2_PLUS) const NimBLEAdvertisedDevice *device = results.getDevice(i); #else NimBLEAdvertisedDevice *device = results.getDevice(i); @@ -4207,7 +4249,8 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 80); tft.print("Active scan (8s)..."); -#ifdef NIMBLE_V2_PLUS +#if defined(NIMBLE_V2_PLUS) + // NimBLE 2.x API: start returns bool, getResults returns NimBLEScanResults bool scanStarted = g_pBLEScan->start(ACTIVE_SCAN_TIME * 1000, false); if (!scanStarted) { displayError("Failed to start BLE scan"); @@ -4215,11 +4258,12 @@ String selectTargetFromScan(const char *title) { } BLEScanResults activeResults = g_pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); #else + // NimBLE 1.x API: start returns NimBLEScanResults BLEScanResults activeResults = g_pBLEScan->start(ACTIVE_SCAN_TIME, false); #endif for (int i = 0; i < activeResults.getCount(); i++) { -#ifdef NIMBLE_V2_PLUS +#if defined(NIMBLE_V2_PLUS) const NimBLEAdvertisedDevice *device = activeResults.getDevice(i); #else NimBLEAdvertisedDevice *device = activeResults.getDevice(i); @@ -4256,7 +4300,7 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 100); tft.print("Passive scan (8s)..."); -#ifdef NIMBLE_V2_PLUS +#if defined(NIMBLE_V2_PLUS) bool passiveScanStarted = g_pBLEScan->start(PASSIVE_SCAN_TIME * 1000, false); if (!passiveScanStarted) { displayError("Failed to start passive BLE scan"); @@ -4268,7 +4312,7 @@ String selectTargetFromScan(const char *title) { #endif for (int i = 0; i < passiveResults.getCount(); i++) { -#ifdef NIMBLE_V2_PLUS +#if defined(NIMBLE_V2_PLUS) const NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); #else NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); From cc924c3211a24f44582c44b6144179b7c4026b3e Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 12:55:39 +0200 Subject: [PATCH 42/74] Enhance BLE common functionality for NimBLE 2.x Refactor BLE handling for NimBLE 2.x compatibility and improve error handling during scanning. --- src/modules/ble/ble_common.cpp | 160 +++++++++++++++++++++------------ 1 file changed, 102 insertions(+), 58 deletions(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index fed38eecb..d145d400b 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -40,10 +40,6 @@ bool bleNotifyRetry(NimBLECharacteristic *chr, uint8_t retries) { return false; } -#if __has_include() -#define NIMBLE_V2_PLUS 1 -#endif - #define ENDIAN_CHANGE_U16(x) ((((x) & 0xFF00) >> 8) + (((x) & 0xFF) << 8)) BLEServer *pServer = NULL; @@ -87,10 +83,20 @@ void ble_info(const String &name, const String &address, const String &signal) { } } -#ifdef NIMBLE_V2_PLUS +//============================================================================= +// NimBLE Callbacks - Version-specific with proper lifetime management +//============================================================================= + +// Static callback instances to prevent premature deletion +static AdvertisedDeviceCallbacks* g_scanCallbacks = nullptr; + +#if NIMBLE_V2_PLUS // NimBLE 2.x uses NimBLEScanCallbacks with const pointers class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { - void onResult(const NimBLEAdvertisedDevice *advertisedDevice) { +public: + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { + if (!advertisedDevice) return; + if (options.size() >= MAX_DISPLAY_DEVICES) { if (pBLEScan) { pBLEScan->stop(); @@ -118,11 +124,18 @@ class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { }); } } + + void onScanEnd(NimBLEScanResults results, int reason) override { + Serial.printf("Scan ended: %d devices found, reason: %d\n", results.getCount(), reason); + } }; #else // NimBLE 1.x uses NimBLEAdvertisedDeviceCallbacks class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { - void onResult(NimBLEAdvertisedDevice *advertisedDevice) { +public: + void onResult(NimBLEAdvertisedDevice *advertisedDevice) override { + if (!advertisedDevice) return; + if (options.size() >= MAX_DISPLAY_DEVICES) { if (pBLEScan) { pBLEScan->stop(); @@ -156,9 +169,17 @@ class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { static bool is_ble_inited = false; void stopBLEStack() { + // Clean up scan callbacks first + if (g_scanCallbacks) { + delete g_scanCallbacks; + g_scanCallbacks = nullptr; + } + if (pBLEScan) { pBLEScan->stop(); pBLEScan->clearResults(); + // Don't delete pBLEScan - it's owned by BLEDevice + pBLEScan = nullptr; } if (is_ble_inited) { @@ -173,7 +194,6 @@ void stopBLEStack() { } } - pBLEScan = nullptr; pServer = nullptr; pService = nullptr; pTxCharacteristic = nullptr; @@ -210,6 +230,7 @@ bool ble_scan_setup() { } if (!is_ble_inited) { + // Use a minimal name to save RAM BLEDevice::init(""); is_ble_inited = true; } @@ -221,10 +242,23 @@ bool ble_scan_setup() { return false; } -#ifdef NIMBLE_V2_PLUS - pBLEScan->setScanCallbacks(new AdvertisedDeviceCallbacks()); + // Clean up old callbacks if they exist + if (g_scanCallbacks) { + delete g_scanCallbacks; + g_scanCallbacks = nullptr; + } + + // Create new callbacks + g_scanCallbacks = new AdvertisedDeviceCallbacks(); + if (!g_scanCallbacks) { + displayError("Failed to create callbacks", true); + return false; + } + +#if NIMBLE_V2_PLUS + pBLEScan->setScanCallbacks(g_scanCallbacks); #else - pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); + pBLEScan->setAdvertisedDeviceCallbacks(g_scanCallbacks); #endif pBLEScan->setActiveScan(true); @@ -267,63 +301,73 @@ void ble_scan() { // Clear previous results before scanning pBLEScan->clearResults(); -#ifdef NIMBLE_V2_PLUS - // NimBLE 2.x: start() returns bool, getResults() gets the data - // Time is in milliseconds for NimBLE 2.x - bool scanStarted = pBLEScan->start(scanTime * 1000, false); - if (!scanStarted) { - displayError("Failed to start BLE scan"); - pBLEScan->clearResults(); - return; - } - // Get results - timeout in milliseconds - BLEScanResults foundDevices = pBLEScan->getResults(scanTime * 1000, false); -#else - // NimBLE 1.x: start() returns results directly, time in seconds - BLEScanResults foundDevices = pBLEScan->start(scanTime, false); -#endif - - int deviceCount = foundDevices.getCount(); - int processedCount = 0; - - for (int i = 0; i < deviceCount && processedCount < MAX_DISPLAY_DEVICES; i++) { -#ifdef NIMBLE_V2_PLUS - const NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); + // Use a try-catch block to handle potential exceptions + try { +#if NIMBLE_V2_PLUS + // NimBLE 2.x: start() returns bool, getResults() gets the data + // Time is in milliseconds for NimBLE 2.x + bool scanStarted = pBLEScan->start(scanTime * 1000, false); + if (!scanStarted) { + displayError("Failed to start BLE scan"); + pBLEScan->clearResults(); + return; + } + // Get results - timeout in milliseconds + BLEScanResults foundDevices = pBLEScan->getResults(scanTime * 1000, false); #else - NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); + // NimBLE 1.x: start() returns results directly, time in seconds + BLEScanResults foundDevices = pBLEScan->start(scanTime, false); #endif - if (!advertisedDevice) continue; - - String bt_title; - String bt_name; - String bt_address; - String bt_signal; - bt_name = advertisedDevice->getName().c_str(); - bt_address = advertisedDevice->getAddress().toString().c_str(); - bt_signal = String(advertisedDevice->getRSSI()); + int deviceCount = foundDevices.getCount(); + int processedCount = 0; - if (bt_name.isEmpty()) bt_name = ""; - bt_title = bt_name; - if (bt_title.isEmpty()) bt_title = bt_address; + // Cap the number of devices to prevent memory issues + int maxToProcess = min(deviceCount, MAX_DISPLAY_DEVICES); - if (options.size() < MAX_DISPLAY_DEVICES) { - options.emplace_back(bt_title.c_str(), [=]() { - ble_info(bt_name, bt_address, bt_signal); - }); - processedCount++; + for (int i = 0; i < maxToProcess && processedCount < MAX_DISPLAY_DEVICES; i++) { +#if NIMBLE_V2_PLUS + const NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); +#else + NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); +#endif + if (!advertisedDevice) continue; + + String bt_title; + String bt_name; + String bt_address; + String bt_signal; + + bt_name = advertisedDevice->getName().c_str(); + bt_address = advertisedDevice->getAddress().toString().c_str(); + bt_signal = String(advertisedDevice->getRSSI()); + + if (bt_name.isEmpty()) bt_name = ""; + bt_title = bt_name; + if (bt_title.isEmpty()) bt_title = bt_address; + + if (options.size() < MAX_DISPLAY_DEVICES) { + options.emplace_back(bt_title.c_str(), [=]() { + ble_info(bt_name, bt_address, bt_signal); + }); + processedCount++; + } } - } - // Show "and more" if we hit the limit - if (options.size() >= MAX_DISPLAY_DEVICES) { - options.emplace_back("... and more devices", nullptr); + // Show "and more" if we hit the limit + if (options.size() >= MAX_DISPLAY_DEVICES) { + options.emplace_back("... and more devices", nullptr); + } + } catch (...) { + displayError("BLE scan error"); + pBLEScan->clearResults(); + return; } - // Stop scan and clean up + // Stop scan if (pBLEScan) { pBLEScan->stop(); - pBLEScan->clearResults(); + // Don't clear results here - we need them for display } // Only stop BLE if it wasn't active before and we're done with it @@ -385,7 +429,7 @@ bool initBLEServer() { } pRxCharacteristic->setCallbacks(new MyCallbacks()); -#ifdef NIMBLE_V2_PLUS +#if NIMBLE_V2_PLUS // NimBLE 2.x: Services start automatically when server starts // No need to call pService->start() #else From 69534f5f95e61b2b54ae4bbab3fd1501cf14ee98 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 12:56:47 +0200 Subject: [PATCH 43/74] Enhance BLE common header with version detection Added version detection for NimBLE and defined constants for BLE scanning. --- src/modules/ble/ble_common.h | 44 ++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index 7a41e8d59..b5e08a180 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -11,16 +11,56 @@ #include "core/display.h" #include +//============================================================================= +// NimBLE Version Detection - Must be consistent across all files +//============================================================================= + +// Detect NimBLE 2.x by checking for features only available in v2+ +#if defined(NIMBLE_VERSION) + #if NIMBLE_VERSION >= 20000 + #define NIMBLE_V2_PLUS 1 + #endif +#elif defined(NIMBLE_CPP_VERSION) && NIMBLE_CPP_VERSION >= 2 + #define NIMBLE_V2_PLUS 1 +#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 + #define NIMBLE_V2_PLUS 1 +#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR == 1 && NIMBLE_VERSION_MINOR >= 5 + #define NIMBLE_V2_PLUS 1 +#elif __has_include() + #define NIMBLE_V2_PLUS 1 +#endif + +// If none of the above matched, check if NimBLEScanResults is a type +#ifndef NIMBLE_V2_PLUS + #ifdef __has_include + #if __has_include() + #if defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + #define NIMBLE_V2_PLUS 1 + #endif + #endif + #endif +#endif + +// If we still don't know, default to v1 behavior (safe fallback) +#ifndef NIMBLE_V2_PLUS + #define NIMBLE_V2_PLUS 0 +#endif + +//============================================================================= +// BLE Constants +//============================================================================= + #define SCANTIME 5 #define SCANTYPE ACTIVE #define SCAN_INT 100 #define SCAN_WINDOW 99 // Maximum number of BLE devices to display to prevent memory issues -// In dense environments (subway, airport, etc.) there can be hundreds of devices -// Limiting to 100 prevents out-of-memory crashes while still showing plenty #define MAX_DISPLAY_DEVICES 100 +// Memory protection: Reduce scan time in low-memory situations +#define SCAN_TIME_REDUCED 3 + extern BLEScan *pBLEScan; extern int scanTime; From f3381b2a86cab59e6ae6c5d94a30b227eb9472e5 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 12:58:23 +0200 Subject: [PATCH 44/74] Enhance NimBLE version detection and scanning Refactor NimBLE version detection and scanning logic to improve compatibility with different NimBLE versions. Adjust scan time based on available memory. --- src/modules/ble/BLE_Suite.cpp | 235 ++++++++++++++++++---------------- 1 file changed, 126 insertions(+), 109 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index f2c461709..b28217ce5 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -26,8 +26,11 @@ #include #include -// NimBLE version detection -// Check for NimBLE version via multiple methods +//============================================================================= +// NimBLE Version Detection - Must match ble_common.h +//============================================================================= + +// Detect NimBLE 2.x by checking for features only available in v2+ #if defined(NIMBLE_VERSION) #if NIMBLE_VERSION >= 20000 #define NIMBLE_V2_PLUS 1 @@ -37,30 +40,27 @@ #elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 #define NIMBLE_V2_PLUS 1 #elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR == 1 && NIMBLE_VERSION_MINOR >= 5 - // v1.5+ has some v2 features #define NIMBLE_V2_PLUS 1 #elif __has_include() #define NIMBLE_V2_PLUS 1 #endif -// If we're compiling with a version that has the new scan API -// Check if NimBLEScan::start returns NimBLEScanResults or bool -// We'll use a simpler approach: detect by checking if NimBLEScanResults is defined -#ifdef __has_include - #if __has_include() - // Check if NimBLEScanResults type exists (v2.x) - // We'll use a compile-time detection approach - #ifndef NIMBLE_V2_PLUS - // Try to detect v2 by checking for NimBLEScanResults type - class __NimBLE_Scanner_Detector { - static void test(NimBLEScanResults*) {} - }; - // If this compiles, we're on v2+ - #define NIMBLE_V2_PLUS 1 +// If none of the above matched, check if NimBLEScanResults is a type +#ifndef NIMBLE_V2_PLUS + #ifdef __has_include + #if __has_include() + #if defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + #define NIMBLE_V2_PLUS 1 + #endif #endif #endif #endif +// If we still don't know, default to v1 behavior (safe fallback) +#ifndef NIMBLE_V2_PLUS + #define NIMBLE_V2_PLUS 0 +#endif + int showSubMenu(const char *title, const char *options[], int optionCount); extern tft_logger tft; @@ -575,7 +575,7 @@ NimBLEClient *attemptConnectionWithStrategies(NimBLEAddress target, String &conn } //============================================================================= -// HID Exploit Engine - Complete Implementation +// HID Exploit Engine //============================================================================= HIDDeviceProfile HIDExploitEngine::analyzeHIDDevice(NimBLEAddress target, const String &name, int rssi) { @@ -651,10 +651,6 @@ HIDDeviceProfile HIDExploitEngine::analyzeHIDDevice(NimBLEAddress target, const return profile; } -//============================================================================= -// [HID Exploit Engine functions - unchanged from previous version] -//============================================================================= - bool HIDExploitEngine::tryAppleMagicSpoof(NimBLEAddress target, HIDDeviceProfile profile) { AutoCleanup cleanup([]() { BLEStateManager::deinitBLE(true); }); @@ -3322,9 +3318,9 @@ std::vector FastPairExploitEngine::scanForFastPairDevices(in pScan->setInterval(97); pScan->setWindow(67); - // This is the key fix - use the correct API based on NimBLE version -#if defined(NIMBLE_V2_PLUS) - // NimBLE 2.x API: start returns bool, getResults returns NimBLEScanResults + // Use the same version detection as ble_common.h +#if NIMBLE_V2_PLUS + // NimBLE 2.x: start returns bool, getResults returns NimBLEScanResults bool scanStarted = pScan->start(duration * 1000, false); if (!scanStarted) { showAttackProgress("Failed to start FastPair scan", TFT_RED); @@ -3332,12 +3328,12 @@ std::vector FastPairExploitEngine::scanForFastPairDevices(in } NimBLEScanResults results = pScan->getResults(duration * 1000, false); #else - // NimBLE 1.x API: start returns NimBLEScanResults + // NimBLE 1.x: start returns NimBLEScanResults directly NimBLEScanResults results = pScan->start(duration, false); #endif for (int i = 0; i < results.getCount(); i++) { -#if defined(NIMBLE_V2_PLUS) +#if NIMBLE_V2_PLUS const NimBLEAdvertisedDevice *device = results.getDevice(i); #else NimBLEAdvertisedDevice *device = results.getDevice(i); @@ -3974,7 +3970,7 @@ void BLE_Sniffer() { padprintln("Status: CAPTURING..."); padprintln("Press [SEL] to stop"); -#if defined(NIMBLE_V2_PLUS) +#if NIMBLE_V2_PLUS pScan->start(10 * 1000, true); NimBLEScanResults results = pScan->getResults(10 * 1000, true); #else @@ -3982,7 +3978,7 @@ void BLE_Sniffer() { #endif for (int i = 0; i < results.getCount(); i++) { -#if defined(NIMBLE_V2_PLUS) +#if NIMBLE_V2_PLUS const NimBLEAdvertisedDevice *device = results.getDevice(i); #else NimBLEAdvertisedDevice *device = results.getDevice(i); @@ -4190,6 +4186,12 @@ void BLE_Sniffer() { //============================================================================= String selectTargetFromScan(const char *title) { + // RAM check - if memory is tight, reduce scan time + if (!radioHasMemForBle()) { + displayError("Low RAM: free WiFi/SD first", true); + return ""; + } + // DO NOT clear scannerData here - it persists between operations g_selectedDevice.address = ""; g_selectedDevice.name = ""; @@ -4241,107 +4243,122 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 60); tft.print("Scanning for devices..."); - const int ACTIVE_SCAN_TIME = 8; - const int PASSIVE_SCAN_TIME = 8; + // Determine scan time based on available memory + int activeScanTime = ACTIVE_SCAN_TIME; + int passiveScanTime = PASSIVE_SCAN_TIME; + + // If memory is tight, use reduced scan times + if (!radioHasMemForBle()) { + activeScanTime = 3; // 3 seconds instead of 8 + passiveScanTime = 3; // 3 seconds instead of 8 + } // === ACTIVE SCAN === g_pBLEScan->setActiveScan(true); tft.setCursor(20, 80); - tft.print("Active scan (8s)..."); + tft.print("Active scan (" + String(activeScanTime) + "s)..."); -#if defined(NIMBLE_V2_PLUS) - // NimBLE 2.x API: start returns bool, getResults returns NimBLEScanResults - bool scanStarted = g_pBLEScan->start(ACTIVE_SCAN_TIME * 1000, false); - if (!scanStarted) { - displayError("Failed to start BLE scan"); - return ""; - } - BLEScanResults activeResults = g_pBLEScan->getResults(ACTIVE_SCAN_TIME * 1000, false); + try { +#if NIMBLE_V2_PLUS + // NimBLE 2.x API: start returns bool, getResults returns NimBLEScanResults + bool scanStarted = g_pBLEScan->start(activeScanTime * 1000, false); + if (!scanStarted) { + displayError("Failed to start BLE scan"); + return ""; + } + BLEScanResults activeResults = g_pBLEScan->getResults(activeScanTime * 1000, false); #else - // NimBLE 1.x API: start returns NimBLEScanResults - BLEScanResults activeResults = g_pBLEScan->start(ACTIVE_SCAN_TIME, false); + // NimBLE 1.x API: start returns NimBLEScanResults + BLEScanResults activeResults = g_pBLEScan->start(activeScanTime, false); #endif - for (int i = 0; i < activeResults.getCount(); i++) { -#if defined(NIMBLE_V2_PLUS) - const NimBLEAdvertisedDevice *device = activeResults.getDevice(i); + for (int i = 0; i < activeResults.getCount(); i++) { +#if NIMBLE_V2_PLUS + const NimBLEAdvertisedDevice *device = activeResults.getDevice(i); #else - NimBLEAdvertisedDevice *device = activeResults.getDevice(i); + NimBLEAdvertisedDevice *device = activeResults.getDevice(i); #endif - if (!device) continue; - - String address = String(device->getAddress().toString().c_str()); - String name = String(device->getName().c_str()); - if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { - name = "Unknown"; - } - int rssi = device->getRSSI(); - if (rssi == 0) rssi = -100; - - bool fastPair = false, hasHFP = false; - uint8_t deviceType = 0; + if (!device) continue; + + String address = String(device->getAddress().toString().c_str()); + String name = String(device->getName().c_str()); + if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { + name = "Unknown"; + } + int rssi = device->getRSSI(); + if (rssi == 0) rssi = -100; + + bool fastPair = false, hasHFP = false; + uint8_t deviceType = 0; + + if (device->haveServiceUUID()) { + NimBLEUUID uuid = device->getServiceUUID(); + std::string uuidStr = uuid.toString(); + if (uuidStr.find("fe2c") != std::string::npos) fastPair = true; + if (uuidStr.find("111e") != std::string::npos || uuidStr.find("111f") != std::string::npos) + hasHFP = true; + if (uuidStr.find("110e") != std::string::npos || uuidStr.find("110f") != std::string::npos) + deviceType |= 0x01; + if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; + } - if (device->haveServiceUUID()) { - NimBLEUUID uuid = device->getServiceUUID(); - std::string uuidStr = uuid.toString(); - if (uuidStr.find("fe2c") != std::string::npos) fastPair = true; - if (uuidStr.find("111e") != std::string::npos || uuidStr.find("111f") != std::string::npos) - hasHFP = true; - if (uuidStr.find("110e") != std::string::npos || uuidStr.find("110f") != std::string::npos) - deviceType |= 0x01; - if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; + scannerData.addDevice(name, address, rssi, fastPair, hasHFP, deviceType); } - scannerData.addDevice(name, address, rssi, fastPair, hasHFP, deviceType); - } - - // === PASSIVE SCAN === - g_pBLEScan->setActiveScan(false); - tft.setCursor(20, 100); - tft.print("Passive scan (8s)..."); + // === PASSIVE SCAN === + g_pBLEScan->setActiveScan(false); + tft.setCursor(20, 100); + tft.print("Passive scan (" + String(passiveScanTime) + "s)..."); -#if defined(NIMBLE_V2_PLUS) - bool passiveScanStarted = g_pBLEScan->start(PASSIVE_SCAN_TIME * 1000, false); - if (!passiveScanStarted) { - displayError("Failed to start passive BLE scan"); - return ""; - } - BLEScanResults passiveResults = g_pBLEScan->getResults(PASSIVE_SCAN_TIME * 1000, false); +#if NIMBLE_V2_PLUS + bool passiveScanStarted = g_pBLEScan->start(passiveScanTime * 1000, false); + if (!passiveScanStarted) { + displayError("Failed to start passive BLE scan"); + return ""; + } + BLEScanResults passiveResults = g_pBLEScan->getResults(passiveScanTime * 1000, false); #else - BLEScanResults passiveResults = g_pBLEScan->start(PASSIVE_SCAN_TIME, false); + BLEScanResults passiveResults = g_pBLEScan->start(passiveScanTime, false); #endif - for (int i = 0; i < passiveResults.getCount(); i++) { -#if defined(NIMBLE_V2_PLUS) - const NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); + for (int i = 0; i < passiveResults.getCount(); i++) { +#if NIMBLE_V2_PLUS + const NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); #else - NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); + NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); #endif - if (!device) continue; - - String address = String(device->getAddress().toString().c_str()); - String name = String(device->getName().c_str()); - if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { - name = "Unknown"; - } - int rssi = device->getRSSI(); - if (rssi == 0) rssi = -100; - - bool fastPair = false, hasHFP = false; - uint8_t deviceType = 0; + if (!device) continue; + + String address = String(device->getAddress().toString().c_str()); + String name = String(device->getName().c_str()); + if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { + name = "Unknown"; + } + int rssi = device->getRSSI(); + if (rssi == 0) rssi = -100; + + bool fastPair = false, hasHFP = false; + uint8_t deviceType = 0; + + if (device->haveServiceUUID()) { + NimBLEUUID uuid = device->getServiceUUID(); + std::string uuidStr = uuid.toString(); + if (uuidStr.find("fe2c") != std::string::npos) fastPair = true; + if (uuidStr.find("111e") != std::string::npos || uuidStr.find("111f") != std::string::npos) + hasHFP = true; + if (uuidStr.find("110e") != std::string::npos || uuidStr.find("110f") != std::string::npos) + deviceType |= 0x01; + if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; + } - if (device->haveServiceUUID()) { - NimBLEUUID uuid = device->getServiceUUID(); - std::string uuidStr = uuid.toString(); - if (uuidStr.find("fe2c") != std::string::npos) fastPair = true; - if (uuidStr.find("111e") != std::string::npos || uuidStr.find("111f") != std::string::npos) - hasHFP = true; - if (uuidStr.find("110e") != std::string::npos || uuidStr.find("110f") != std::string::npos) - deviceType |= 0x01; - if (uuidStr.find("1812") != std::string::npos) deviceType |= 0x02; + scannerData.addDevice(name, address, rssi, fastPair, hasHFP, deviceType); } - - scannerData.addDevice(name, address, rssi, fastPair, hasHFP, deviceType); + } catch (...) { + displayError("BLE scan error"); + if (g_pBLEScan) { + g_pBLEScan->clearResults(); + } + return ""; } // Stop the scan but DON'T clear results yet - we need them for display From 2b51a4d0b01223c7965ae8f348ccc1f509ef818e Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 20:03:08 +0200 Subject: [PATCH 45/74] Refactor NimBLE version detection in BLE_Suite.h Updated NimBLE version detection logic and removed obsolete code. --- src/modules/ble/BLE_Suite.h | 55 +++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index 1bffaacde..bbeb3cfdd 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -14,11 +14,6 @@ #include #include -// NimBLE 2.3.7 doesn't have NimBLEExtAdvertising.h -// #if __has_include() -// #define NIMBLE_V2_PLUS 1 -// #endif - extern volatile int tftWidth; extern volatile int tftHeight; class tft_logger; @@ -28,6 +23,39 @@ extern BruceConfig bruceConfig; bool check(int key); +//============================================================================= +// NimBLE Version Detection - Must match ble_common.h +//============================================================================= + +// Detect NimBLE 2.x by checking for features only available in v2+ +#if defined(NIMBLE_VERSION) + #if NIMBLE_VERSION >= 20000 + #define NIMBLE_V2_PLUS 1 + #endif +#elif defined(NIMBLE_CPP_VERSION) && NIMBLE_CPP_VERSION >= 2 + #define NIMBLE_V2_PLUS 1 +#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 + #define NIMBLE_V2_PLUS 1 +#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR == 1 && NIMBLE_VERSION_MINOR >= 5 + #define NIMBLE_V2_PLUS 1 +#elif __has_include() + #define NIMBLE_V2_PLUS 1 +#endif + +// If none of the above matched, default to v1 behavior (safe fallback) +#ifndef NIMBLE_V2_PLUS + #define NIMBLE_V2_PLUS 0 +#endif + +//============================================================================= +// BLE Scan Constants +//============================================================================= + +#define ACTIVE_SCAN_TIME 8 +#define PASSIVE_SCAN_TIME 8 +#define SCAN_INT 100 +#define SCAN_WINDOW 99 + enum { BLE_ESC_PRESS = 0, BLE_SEL_PRESS = 1, @@ -35,6 +63,23 @@ enum { BLE_NEXT_PRESS = 3 }; +// Forward declaration of AdvertisedDeviceCallbacks for ble_common.cpp +#if NIMBLE_V2_PLUS +class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { +public: + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override; + void onScanEnd(NimBLEScanResults results, int reason) override; +}; +#else +class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { +public: + void onResult(NimBLEAdvertisedDevice *advertisedDevice) override; +}; +#endif + +// External reference to g_scanCallbacks for ble_common.cpp +extern AdvertisedDeviceCallbacks* g_scanCallbacks; + enum FastPairPopupType { FP_POPUP_REGULAR = 0, FP_POPUP_FUN, From 15bff5e841a32a260c72d780c0a5cd2373676345 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 20:05:40 +0200 Subject: [PATCH 46/74] Refactor BLE scanning memory checks and scan times Refactor memory checks for BLE scanning to use heap size instead of RAM checks. Simplify scan time determination based on available memory. --- src/modules/ble/BLE_Suite.cpp | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index b28217ce5..28979b5f0 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -45,18 +45,7 @@ #define NIMBLE_V2_PLUS 1 #endif -// If none of the above matched, check if NimBLEScanResults is a type -#ifndef NIMBLE_V2_PLUS - #ifdef __has_include - #if __has_include() - #if defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) - #define NIMBLE_V2_PLUS 1 - #endif - #endif - #endif -#endif - -// If we still don't know, default to v1 behavior (safe fallback) +// If none of the above matched, default to v1 behavior (safe fallback) #ifndef NIMBLE_V2_PLUS #define NIMBLE_V2_PLUS 0 #endif @@ -4186,10 +4175,10 @@ void BLE_Sniffer() { //============================================================================= String selectTargetFromScan(const char *title) { - // RAM check - if memory is tight, reduce scan time - if (!radioHasMemForBle()) { - displayError("Low RAM: free WiFi/SD first", true); - return ""; + // Simple memory check - if heap is low, warn but continue + if (heap_caps_get_free_size(MALLOC_CAP_DEFAULT) < 10000) { + displayError("Low memory, scan may be unstable", true); + // Don't return - let the user decide } // DO NOT clear scannerData here - it persists between operations @@ -4243,14 +4232,14 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 60); tft.print("Scanning for devices..."); - // Determine scan time based on available memory + // Use fixed scan times int activeScanTime = ACTIVE_SCAN_TIME; int passiveScanTime = PASSIVE_SCAN_TIME; - // If memory is tight, use reduced scan times - if (!radioHasMemForBle()) { - activeScanTime = 3; // 3 seconds instead of 8 - passiveScanTime = 3; // 3 seconds instead of 8 + // If memory is very low, use reduced scan times + if (heap_caps_get_free_size(MALLOC_CAP_DEFAULT) < 15000) { + activeScanTime = 3; + passiveScanTime = 3; } // === ACTIVE SCAN === From 4bc793369c0e6e26a691776c6488e15a365e40b8 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 20:08:29 +0200 Subject: [PATCH 47/74] Refactor BLE initialization by removing radio memory check Removed unnecessary radio memory check for BLE initialization. --- src/modules/ble/ble_common.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index d145d400b..bdef4b34f 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -1,6 +1,5 @@ #include "ble_common.h" #include "core/mykeyboard.h" -#include "core/radio_mem.h" #include "core/ram_profile.h" #include "core/utils.h" #include "core/wifi/wifi_common.h" @@ -88,7 +87,7 @@ void ble_info(const String &name, const String &address, const String &signal) { //============================================================================= // Static callback instances to prevent premature deletion -static AdvertisedDeviceCallbacks* g_scanCallbacks = nullptr; +AdvertisedDeviceCallbacks* g_scanCallbacks = nullptr; #if NIMBLE_V2_PLUS // NimBLE 2.x uses NimBLEScanCallbacks with const pointers @@ -223,11 +222,6 @@ bool ble_scan_setup() { } RAM_LOG("ble-scan pre-init"); - if (!radioHasMemForBle()) { - displayError("Low RAM: free WiFi/SD first", true); - returnToMenu = true; - return false; - } if (!is_ble_inited) { // Use a minimal name to save RAM From dddb716da5afaa77495430f407d8cbdc4ff8af1c Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 21:04:37 +0200 Subject: [PATCH 48/74] Refactor BLE button press enum definition --- src/modules/ble/BLE_Suite.h | 85 ++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index bbeb3cfdd..428b2a3cb 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -56,12 +56,9 @@ bool check(int key); #define SCAN_INT 100 #define SCAN_WINDOW 99 -enum { - BLE_ESC_PRESS = 0, - BLE_SEL_PRESS = 1, - BLE_PREV_PRESS = 2, - BLE_NEXT_PRESS = 3 -}; +//============================================================================= +// Forward Declarations +//============================================================================= // Forward declaration of AdvertisedDeviceCallbacks for ble_common.cpp #if NIMBLE_V2_PLUS @@ -80,6 +77,17 @@ class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { // External reference to g_scanCallbacks for ble_common.cpp extern AdvertisedDeviceCallbacks* g_scanCallbacks; +//============================================================================= +// Enums +//============================================================================= + +enum { + BLE_ESC_PRESS = 0, + BLE_SEL_PRESS = 1, + BLE_PREV_PRESS = 2, + BLE_NEXT_PRESS = 3 +}; + enum FastPairPopupType { FP_POPUP_REGULAR = 0, FP_POPUP_FUN, @@ -216,6 +224,10 @@ struct DuckyCommand { int delay_ms; }; +//============================================================================= +// AutoCleanup Class +//============================================================================= + class AutoCleanup { private: std::function cleanupFunc; @@ -228,6 +240,10 @@ class AutoCleanup { void enable(); }; +//============================================================================= +// BLEStateManager Class +//============================================================================= + class BLEStateManager { private: static bool bleInitialized; @@ -245,6 +261,10 @@ class BLEStateManager { static size_t getActiveClientCount(); }; +//============================================================================= +// BLEAttackManager Class +//============================================================================= + class BLEAttackManager { public: void prepareForConnection(); @@ -253,7 +273,10 @@ class BLEAttackManager { DeviceProfile profileDevice(NimBLEAddress target); }; -// FastPair structs +//============================================================================= +// FastPair Structures and Functions +//============================================================================= + struct FastPairDeviceInfo { NimBLEAddress address; String name; @@ -286,6 +309,10 @@ enum FastPairVersion { FastPairVersion detectFastPairVersion(NimBLEAddress target); +//============================================================================= +// FastPairExploitEngine Class +//============================================================================= + class FastPairExploitEngine { public: std::vector scanForFastPairDevices(int duration); @@ -322,6 +349,10 @@ class FastPairExploitEngine { void generateRandomMac(uint8_t* mac); }; +//============================================================================= +// HIDExploitEngine Class +//============================================================================= + class HIDExploitEngine { public: HIDDeviceProfile analyzeHIDDevice(NimBLEAddress target, const String& name, int rssi); @@ -340,6 +371,10 @@ class HIDExploitEngine { bool testHIDVulnerability(NimBLEAddress target); }; +//============================================================================= +// WhisperPairExploit Class +//============================================================================= + class WhisperPairExploit { public: WhisperPairExploit(); @@ -357,6 +392,10 @@ class WhisperPairExploit { bool executeAdvanced(NimBLEAddress target, int attackType); }; +//============================================================================= +// AudioAttackService Class +//============================================================================= + class AudioAttackService { public: bool findAndAttackAudioServices(NimBLEClient* pClient); @@ -368,6 +407,10 @@ class AudioAttackService { bool crashAudioStack(NimBLEAddress target); }; +//============================================================================= +// DuckyScriptEngine Class +//============================================================================= + class DuckyScriptEngine { public: struct HIDKeycode { @@ -390,6 +433,10 @@ class DuckyScriptEngine { bool scriptLoaded; }; +//============================================================================= +// HIDDuckyService Class +//============================================================================= + class HIDDuckyService { public: HIDDuckyService(); @@ -411,6 +458,10 @@ class HIDDuckyService { bool sendGUIKey(NimBLERemoteCharacteristic* pChar, char key); }; +//============================================================================= +// AuthBypassEngine Class +//============================================================================= + class AuthBypassEngine { private: struct PairedDevice { @@ -430,6 +481,10 @@ class AuthBypassEngine { bool exploitAuthBypass(NimBLEAddress target); }; +//============================================================================= +// MultiConnectionAttack Class +//============================================================================= + class MultiConnectionAttack { public: MultiConnectionAttack(); @@ -448,6 +503,10 @@ class MultiConnectionAttack { std::vector activeConnections; }; +//============================================================================= +// VulnerabilityScanner Class +//============================================================================= + class VulnerabilityScanner { private: struct VulnCheck { @@ -465,6 +524,10 @@ class VulnerabilityScanner { std::vector getVulnerabilities(); }; +//============================================================================= +// Attack Service Classes +//============================================================================= + class HIDAttackServiceClass { public: bool injectKeystrokes(NimBLEAddress target); @@ -482,6 +545,10 @@ class DoSAttackServiceClass { bool advertisingSpam(NimBLEAddress target); }; +//============================================================================= +// Debug Memory Macros +//============================================================================= + #ifdef DEBUG_MEMORY class HeapMonitor { public: @@ -509,6 +576,10 @@ class HeapMonitor { #define MEM_CHECK() #endif +//============================================================================= +// Function Declarations +//============================================================================= + void cleanupBLEStack(); NimBLEClient* attemptConnectionWithStrategies(NimBLEAddress target, String& connectionMethod); From d053b09de774e3201d9b537dd2a45b40ce66a443 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Thu, 16 Jul 2026 21:59:36 +0200 Subject: [PATCH 49/74] Fix header guard in BLE_Suite.h From d0a1cb9daf50159832210e15b91d204668bf48d9 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 07:10:30 +0200 Subject: [PATCH 50/74] Fix header guard in BLE_Suite.h From a15330412757d215a9d86414e47aedd1e3c6bc33 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 07:11:41 +0200 Subject: [PATCH 51/74] Update BLE_Suite.cpp --- src/modules/ble/BLE_Suite.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index 28979b5f0..d77dc8c11 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -2,7 +2,7 @@ * BLE Suite v3.1 - Complete BLE attack and analysis toolkit * Author: Ninja-jr * Version: 3.1 - * Last Updated: 16/07/2026 + * Last Updated: 17/07/2026 * * Contains: Vulnerability scanning, HID attacks, FastPair exploits, * HFP attacks, Audio attacks, DuckyScript injection, From b5f063a0e571641d9e7d8be1eceeb1fd05d64bea Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 07:12:25 +0200 Subject: [PATCH 52/74] Check RAM availability before BLE initialization Added a check for available memory before initializing BLE. --- src/modules/ble/ble_common.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index bdef4b34f..d127941e3 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -1,5 +1,6 @@ #include "ble_common.h" #include "core/mykeyboard.h" +#include "core/radio_mem.h" #include "core/ram_profile.h" #include "core/utils.h" #include "core/wifi/wifi_common.h" @@ -222,6 +223,11 @@ bool ble_scan_setup() { } RAM_LOG("ble-scan pre-init"); + if (!radioHasMemForBle()) { + displayError("Low RAM: free WiFi/SD first", true); + returnToMenu = true; + return false; + } if (!is_ble_inited) { // Use a minimal name to save RAM From b91bbefb6b5cb158be84888a4c2821456e61dcb6 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 07:13:09 +0200 Subject: [PATCH 53/74] Add AdvertisedDeviceCallbacks class for BLE scanning --- src/modules/ble/ble_common.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index b5e08a180..18392fd84 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -61,6 +61,27 @@ // Memory protection: Reduce scan time in low-memory situations #define SCAN_TIME_REDUCED 3 +//============================================================================= +// Forward Declarations +//============================================================================= + +// Forward declaration of AdvertisedDeviceCallbacks for ble_common.cpp +#if NIMBLE_V2_PLUS +class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { +public: + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override; + void onScanEnd(NimBLEScanResults results, int reason) override; +}; +#else +class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { +public: + void onResult(NimBLEAdvertisedDevice *advertisedDevice) override; +}; +#endif + +// External reference to g_scanCallbacks for ble_common.cpp +extern AdvertisedDeviceCallbacks* g_scanCallbacks; + extern BLEScan *pBLEScan; extern int scanTime; From 0dbb7c55bcd05e6d6d86d0afb940ed80145903a7 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 07:13:45 +0200 Subject: [PATCH 54/74] Fix header guard in BLE_Suite.h From 3ec1f3572f7b4a5f26c3487ea1bfa0c9cb47963c Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 07:27:11 +0200 Subject: [PATCH 55/74] Enhance NimBLE v2 detection and improve comments Added compile-time detection for NimBLE v2 features and updated comments for clarity. --- src/modules/ble/ble_common.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index 18392fd84..bc014ae17 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -34,7 +34,17 @@ #ifndef NIMBLE_V2_PLUS #ifdef __has_include #if __has_include() - #if defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + // Try to detect v2 by checking for a v2-only feature + // We'll use a compile-time check + namespace __nimble_detect { + template class has_getResults { + template static auto test(int) -> decltype(std::declval().getResults(0), std::true_type()); + template static auto test(...) -> std::false_type; + public: + static const bool value = decltype(test(0))::value; + }; + } + #if __nimble_detect::has_getResults::value #define NIMBLE_V2_PLUS 1 #endif #endif @@ -62,10 +72,11 @@ #define SCAN_TIME_REDUCED 3 //============================================================================= -// Forward Declarations +// Forward Declaration - SINGLE SOURCE OF TRUTH //============================================================================= -// Forward declaration of AdvertisedDeviceCallbacks for ble_common.cpp +// Forward declaration of AdvertisedDeviceCallbacks +// Only declare onScanEnd for NimBLE 2.x #if NIMBLE_V2_PLUS class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { public: @@ -79,7 +90,7 @@ class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { }; #endif -// External reference to g_scanCallbacks for ble_common.cpp +// External reference to g_scanCallbacks extern AdvertisedDeviceCallbacks* g_scanCallbacks; extern BLEScan *pBLEScan; From f5638e2fbd4019729b9b48cb4dd11381d81026e1 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 07:28:26 +0200 Subject: [PATCH 56/74] Include ble_common.h and refactor AdvertisedDeviceCallbacks Updated BLE_Suite.h to include ble_common.h and removed the forward declaration of AdvertisedDeviceCallbacks. --- src/modules/ble/BLE_Suite.h | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index 428b2a3cb..a94dfb0ed 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -7,6 +7,7 @@ #include "HFP_Exploit.h" #include "fastpair_crypto.h" +#include "ble_common.h" // ← This now provides AdvertisedDeviceCallbacks #include #include #include @@ -57,26 +58,9 @@ bool check(int key); #define SCAN_WINDOW 99 //============================================================================= -// Forward Declarations +// Note: AdvertisedDeviceCallbacks is now defined in ble_common.h //============================================================================= -// Forward declaration of AdvertisedDeviceCallbacks for ble_common.cpp -#if NIMBLE_V2_PLUS -class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { -public: - void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override; - void onScanEnd(NimBLEScanResults results, int reason) override; -}; -#else -class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { -public: - void onResult(NimBLEAdvertisedDevice *advertisedDevice) override; -}; -#endif - -// External reference to g_scanCallbacks for ble_common.cpp -extern AdvertisedDeviceCallbacks* g_scanCallbacks; - //============================================================================= // Enums //============================================================================= From e73c4f8102c5fd9edd71af8c49752037fe21d85c Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 07:37:58 +0200 Subject: [PATCH 57/74] Update BLE_Suite.h --- src/modules/ble/BLE_Suite.h | 40 ++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index a94dfb0ed..1a63e0793 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -7,7 +7,7 @@ #include "HFP_Exploit.h" #include "fastpair_crypto.h" -#include "ble_common.h" // ← This now provides AdvertisedDeviceCallbacks +#include "ble_common.h" // This provides AdvertisedDeviceCallbacks #include #include #include @@ -25,25 +25,37 @@ extern BruceConfig bruceConfig; bool check(int key); //============================================================================= -// NimBLE Version Detection - Must match ble_common.h +// NimBLE Version Detection - Matches ble_common.h //============================================================================= -// Detect NimBLE 2.x by checking for features only available in v2+ -#if defined(NIMBLE_VERSION) - #if NIMBLE_VERSION >= 20000 +// Define NIMBLE_V2_PLUS based on available features +#ifndef NIMBLE_V2_PLUS + #if defined(NIMBLE_VERSION) + #if NIMBLE_VERSION >= 20000 + #define NIMBLE_V2_PLUS 1 + #endif + #endif +#endif + +#ifndef NIMBLE_V2_PLUS + #if defined(NIMBLE_CPP_VERSION) && NIMBLE_CPP_VERSION >= 2 + #define NIMBLE_V2_PLUS 1 + #endif +#endif + +#ifndef NIMBLE_V2_PLUS + #if defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 + #define NIMBLE_V2_PLUS 1 + #endif +#endif + +#ifndef NIMBLE_V2_PLUS + #if __has_include() #define NIMBLE_V2_PLUS 1 #endif -#elif defined(NIMBLE_CPP_VERSION) && NIMBLE_CPP_VERSION >= 2 - #define NIMBLE_V2_PLUS 1 -#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 - #define NIMBLE_V2_PLUS 1 -#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR == 1 && NIMBLE_VERSION_MINOR >= 5 - #define NIMBLE_V2_PLUS 1 -#elif __has_include() - #define NIMBLE_V2_PLUS 1 #endif -// If none of the above matched, default to v1 behavior (safe fallback) +// If we still don't know, default to v1 behavior (safe fallback) #ifndef NIMBLE_V2_PLUS #define NIMBLE_V2_PLUS 0 #endif From 3b3a9508e12dae767031809c0ebb858bab1f3d0d Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 07:40:04 +0200 Subject: [PATCH 58/74] Simplify NimBLE version detection logic Refactor NimBLE version detection for reliability and clarity. --- src/modules/ble/ble_common.h | 76 ++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index bc014ae17..d9ffe89f6 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -12,50 +12,74 @@ #include //============================================================================= -// NimBLE Version Detection - Must be consistent across all files +// NimBLE Version Detection - Simplified for reliability //============================================================================= -// Detect NimBLE 2.x by checking for features only available in v2+ -#if defined(NIMBLE_VERSION) - #if NIMBLE_VERSION >= 20000 - #define NIMBLE_V2_PLUS 1 - #endif -#elif defined(NIMBLE_CPP_VERSION) && NIMBLE_CPP_VERSION >= 2 - #define NIMBLE_V2_PLUS 1 -#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 - #define NIMBLE_V2_PLUS 1 -#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR == 1 && NIMBLE_VERSION_MINOR >= 5 - #define NIMBLE_V2_PLUS 1 -#elif __has_include() - #define NIMBLE_V2_PLUS 1 -#endif - -// If none of the above matched, check if NimBLEScanResults is a type +// Define NIMBLE_V2_PLUS based on available features +// We check for NimBLEScanCallbacks which is v2-specific #ifndef NIMBLE_V2_PLUS + // Check if NimBLEScanCallbacks is defined (v2 feature) #ifdef __has_include #if __has_include() - // Try to detect v2 by checking for a v2-only feature - // We'll use a compile-time check + // Try to detect v2 by checking for a v2-only type + // We'll use a simple approach: check if NimBLEScanCallbacks exists namespace __nimble_detect { - template class has_getResults { - template static auto test(int) -> decltype(std::declval().getResults(0), std::true_type()); - template static auto test(...) -> std::false_type; - public: - static const bool value = decltype(test(0))::value; - }; + template struct void_type { typedef void type; }; + template struct has_scan_callbacks : std::false_type {}; + template struct has_scan_callbacks::type> : std::true_type {}; } - #if __nimble_detect::has_getResults::value + // If the compiler can see NimBLEScanCallbacks, we're on v2+ + #if __nimble_detect::has_scan_callbacks::value #define NIMBLE_V2_PLUS 1 #endif #endif #endif #endif +// Check specific NimBLE version macros +#ifndef NIMBLE_V2_PLUS + #if defined(NIMBLE_VERSION) + #if NIMBLE_VERSION >= 20000 + #define NIMBLE_V2_PLUS 1 + #endif + #endif +#endif + +#ifndef NIMBLE_V2_PLUS + #if defined(NIMBLE_CPP_VERSION) && NIMBLE_CPP_VERSION >= 2 + #define NIMBLE_V2_PLUS 1 + #endif +#endif + +#ifndef NIMBLE_V2_PLUS + #if defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 + #define NIMBLE_V2_PLUS 1 + #endif +#endif + +// If we're on ESP-IDF 5.0+, likely using NimBLE 2.x +#ifndef NIMBLE_V2_PLUS + #if defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + #define NIMBLE_V2_PLUS 1 + #endif +#endif + +// Check for NimBLEExtAdvertising.h which is v2-specific +#ifndef NIMBLE_V2_PLUS + #if __has_include() + #define NIMBLE_V2_PLUS 1 + #endif +#endif + // If we still don't know, default to v1 behavior (safe fallback) #ifndef NIMBLE_V2_PLUS #define NIMBLE_V2_PLUS 0 #endif +// Debug output to help with troubleshooting +#pragma message("NIMBLE_V2_PLUS = " __STRINGIFY(NIMBLE_V2_PLUS)) + //============================================================================= // BLE Constants //============================================================================= From f81baf3fd7b7314ff75d398e710be05974092d14 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 07:59:09 +0200 Subject: [PATCH 59/74] Refactor NimBLE version detection logic Updated NimBLE version detection logic for improved reliability and clarity. Added multiple checks for versioning based on macros and included debugging output. --- src/modules/ble/ble_common.h | 76 +++++++++++++++++------------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index d9ffe89f6..61a4dd9da 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -12,72 +12,63 @@ #include //============================================================================= -// NimBLE Version Detection - Simplified for reliability +// NimBLE Version Detection - SUPER SIMPLE //============================================================================= -// Define NIMBLE_V2_PLUS based on available features -// We check for NimBLEScanCallbacks which is v2-specific -#ifndef NIMBLE_V2_PLUS - // Check if NimBLEScanCallbacks is defined (v2 feature) - #ifdef __has_include - #if __has_include() - // Try to detect v2 by checking for a v2-only type - // We'll use a simple approach: check if NimBLEScanCallbacks exists - namespace __nimble_detect { - template struct void_type { typedef void type; }; - template struct has_scan_callbacks : std::false_type {}; - template struct has_scan_callbacks::type> : std::true_type {}; - } - // If the compiler can see NimBLEScanCallbacks, we're on v2+ - #if __nimble_detect::has_scan_callbacks::value - #define NIMBLE_V2_PLUS 1 - #endif - #endif - #endif -#endif +// Try to detect NimBLE 2.x using simple macro checks +// NIMBLE_V2_PLUS is defined as 1 for v2, 0 for v1 (default) -// Check specific NimBLE version macros -#ifndef NIMBLE_V2_PLUS - #if defined(NIMBLE_VERSION) - #if NIMBLE_VERSION >= 20000 +// First check: NIMBLE_VERSION macro +#ifdef NIMBLE_VERSION + #if NIMBLE_VERSION >= 20000 + #ifndef NIMBLE_V2_PLUS #define NIMBLE_V2_PLUS 1 #endif #endif #endif -#ifndef NIMBLE_V2_PLUS - #if defined(NIMBLE_CPP_VERSION) && NIMBLE_CPP_VERSION >= 2 - #define NIMBLE_V2_PLUS 1 +// Second check: NIMBLE_CPP_VERSION +#ifdef NIMBLE_CPP_VERSION + #if NIMBLE_CPP_VERSION >= 2 + #ifndef NIMBLE_V2_PLUS + #define NIMBLE_V2_PLUS 1 + #endif #endif #endif -#ifndef NIMBLE_V2_PLUS - #if defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 - #define NIMBLE_V2_PLUS 1 +// Third check: NIMBLE_VERSION_MAJOR +#ifdef NIMBLE_VERSION_MAJOR + #if NIMBLE_VERSION_MAJOR >= 2 + #ifndef NIMBLE_V2_PLUS + #define NIMBLE_V2_PLUS 1 + #endif #endif #endif -// If we're on ESP-IDF 5.0+, likely using NimBLE 2.x -#ifndef NIMBLE_V2_PLUS - #if defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) - #define NIMBLE_V2_PLUS 1 +// Fourth check: ESP-IDF version +#ifdef ESP_IDF_VERSION + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + #ifndef NIMBLE_V2_PLUS + #define NIMBLE_V2_PLUS 1 + #endif #endif #endif -// Check for NimBLEExtAdvertising.h which is v2-specific -#ifndef NIMBLE_V2_PLUS +// Fifth check: Check for v2-specific header +#ifdef __has_include #if __has_include() - #define NIMBLE_V2_PLUS 1 + #ifndef NIMBLE_V2_PLUS + #define NIMBLE_V2_PLUS 1 + #endif #endif #endif -// If we still don't know, default to v1 behavior (safe fallback) +// If no detection succeeded, default to v1 (safe fallback) #ifndef NIMBLE_V2_PLUS #define NIMBLE_V2_PLUS 0 #endif -// Debug output to help with troubleshooting +// Print the detected version for debugging #pragma message("NIMBLE_V2_PLUS = " __STRINGIFY(NIMBLE_V2_PLUS)) //============================================================================= @@ -102,15 +93,18 @@ // Forward declaration of AdvertisedDeviceCallbacks // Only declare onScanEnd for NimBLE 2.x #if NIMBLE_V2_PLUS +// NimBLE 2.x version class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { public: void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override; void onScanEnd(NimBLEScanResults results, int reason) override; }; #else +// NimBLE 1.x version - NO onScanEnd! class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { public: void onResult(NimBLEAdvertisedDevice *advertisedDevice) override; + // No onScanEnd - this doesn't exist in NimBLE 1.x }; #endif From a95f54c326909a1932dcb6271429e3ef7850002c Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 07:59:52 +0200 Subject: [PATCH 60/74] Refactor NimBLE version detection macros --- src/modules/ble/BLE_Suite.h | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index 1a63e0793..2f23fe2c8 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -25,12 +25,12 @@ extern BruceConfig bruceConfig; bool check(int key); //============================================================================= -// NimBLE Version Detection - Matches ble_common.h +// NimBLE Version Detection - SUPER SIMPLE //============================================================================= -// Define NIMBLE_V2_PLUS based on available features +// Try to detect NimBLE 2.x using simple macro checks #ifndef NIMBLE_V2_PLUS - #if defined(NIMBLE_VERSION) + #ifdef NIMBLE_VERSION #if NIMBLE_VERSION >= 20000 #define NIMBLE_V2_PLUS 1 #endif @@ -38,28 +38,37 @@ bool check(int key); #endif #ifndef NIMBLE_V2_PLUS - #if defined(NIMBLE_CPP_VERSION) && NIMBLE_CPP_VERSION >= 2 - #define NIMBLE_V2_PLUS 1 + #ifdef NIMBLE_CPP_VERSION + #if NIMBLE_CPP_VERSION >= 2 + #define NIMBLE_V2_PLUS 1 + #endif #endif #endif #ifndef NIMBLE_V2_PLUS - #if defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 - #define NIMBLE_V2_PLUS 1 + #ifdef NIMBLE_VERSION_MAJOR + #if NIMBLE_VERSION_MAJOR >= 2 + #define NIMBLE_V2_PLUS 1 + #endif #endif #endif #ifndef NIMBLE_V2_PLUS - #if __has_include() - #define NIMBLE_V2_PLUS 1 + #ifdef __has_include + #if __has_include() + #define NIMBLE_V2_PLUS 1 + #endif #endif #endif -// If we still don't know, default to v1 behavior (safe fallback) +// If no detection succeeded, default to v1 (safe fallback) #ifndef NIMBLE_V2_PLUS #define NIMBLE_V2_PLUS 0 #endif +// Print the detected version for debugging +#pragma message("BLE_Suite: NIMBLE_V2_PLUS = " __STRINGIFY(NIMBLE_V2_PLUS)) + //============================================================================= // BLE Scan Constants //============================================================================= From ecf7c8e68545e390b64a53e1afbd530d592c326c Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 08:27:35 +0200 Subject: [PATCH 61/74] Simplify NimBLE version detection Removed unnecessary NimBLE version detection macros and simplified the definition of NIMBLE_V2_PLUS. --- src/modules/ble/BLE_Suite.h | 42 +++---------------------------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index 2f23fe2c8..d716f6a4e 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -25,50 +25,14 @@ extern BruceConfig bruceConfig; bool check(int key); //============================================================================= -// NimBLE Version Detection - SUPER SIMPLE +// NimBLE Version - MANUAL FORCE FOR NIMBLE 2.x //============================================================================= -// Try to detect NimBLE 2.x using simple macro checks +// Since you're using NimBLE 2.3.7+, set this to 1 #ifndef NIMBLE_V2_PLUS - #ifdef NIMBLE_VERSION - #if NIMBLE_VERSION >= 20000 - #define NIMBLE_V2_PLUS 1 - #endif - #endif + #define NIMBLE_V2_PLUS 1 #endif -#ifndef NIMBLE_V2_PLUS - #ifdef NIMBLE_CPP_VERSION - #if NIMBLE_CPP_VERSION >= 2 - #define NIMBLE_V2_PLUS 1 - #endif - #endif -#endif - -#ifndef NIMBLE_V2_PLUS - #ifdef NIMBLE_VERSION_MAJOR - #if NIMBLE_VERSION_MAJOR >= 2 - #define NIMBLE_V2_PLUS 1 - #endif - #endif -#endif - -#ifndef NIMBLE_V2_PLUS - #ifdef __has_include - #if __has_include() - #define NIMBLE_V2_PLUS 1 - #endif - #endif -#endif - -// If no detection succeeded, default to v1 (safe fallback) -#ifndef NIMBLE_V2_PLUS - #define NIMBLE_V2_PLUS 0 -#endif - -// Print the detected version for debugging -#pragma message("BLE_Suite: NIMBLE_V2_PLUS = " __STRINGIFY(NIMBLE_V2_PLUS)) - //============================================================================= // BLE Scan Constants //============================================================================= From 6ebf379b967656a56ab89bda95bb8ddaeb775c5e Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 08:28:41 +0200 Subject: [PATCH 62/74] Update NimBLE version detection and callbacks --- src/modules/ble/ble_common.h | 83 +++--------------------------------- 1 file changed, 5 insertions(+), 78 deletions(-) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index 61a4dd9da..824a995db 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -12,65 +12,14 @@ #include //============================================================================= -// NimBLE Version Detection - SUPER SIMPLE +// NimBLE Version - MANUAL FORCE FOR NIMBLE 2.x //============================================================================= -// Try to detect NimBLE 2.x using simple macro checks -// NIMBLE_V2_PLUS is defined as 1 for v2, 0 for v1 (default) - -// First check: NIMBLE_VERSION macro -#ifdef NIMBLE_VERSION - #if NIMBLE_VERSION >= 20000 - #ifndef NIMBLE_V2_PLUS - #define NIMBLE_V2_PLUS 1 - #endif - #endif -#endif - -// Second check: NIMBLE_CPP_VERSION -#ifdef NIMBLE_CPP_VERSION - #if NIMBLE_CPP_VERSION >= 2 - #ifndef NIMBLE_V2_PLUS - #define NIMBLE_V2_PLUS 1 - #endif - #endif -#endif - -// Third check: NIMBLE_VERSION_MAJOR -#ifdef NIMBLE_VERSION_MAJOR - #if NIMBLE_VERSION_MAJOR >= 2 - #ifndef NIMBLE_V2_PLUS - #define NIMBLE_V2_PLUS 1 - #endif - #endif -#endif - -// Fourth check: ESP-IDF version -#ifdef ESP_IDF_VERSION - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) - #ifndef NIMBLE_V2_PLUS - #define NIMBLE_V2_PLUS 1 - #endif - #endif -#endif - -// Fifth check: Check for v2-specific header -#ifdef __has_include - #if __has_include() - #ifndef NIMBLE_V2_PLUS - #define NIMBLE_V2_PLUS 1 - #endif - #endif -#endif - -// If no detection succeeded, default to v1 (safe fallback) +// Since you're using NimBLE 2.3.7+, set this to 1 #ifndef NIMBLE_V2_PLUS - #define NIMBLE_V2_PLUS 0 + #define NIMBLE_V2_PLUS 1 #endif -// Print the detected version for debugging -#pragma message("NIMBLE_V2_PLUS = " __STRINGIFY(NIMBLE_V2_PLUS)) - //============================================================================= // BLE Constants //============================================================================= @@ -83,30 +32,16 @@ // Maximum number of BLE devices to display to prevent memory issues #define MAX_DISPLAY_DEVICES 100 -// Memory protection: Reduce scan time in low-memory situations -#define SCAN_TIME_REDUCED 3 - //============================================================================= -// Forward Declaration - SINGLE SOURCE OF TRUTH +// AdvertisedDeviceCallbacks - For NimBLE 2.x //============================================================================= -// Forward declaration of AdvertisedDeviceCallbacks -// Only declare onScanEnd for NimBLE 2.x -#if NIMBLE_V2_PLUS -// NimBLE 2.x version +// For NimBLE 2.x - uses NimBLEScanCallbacks with onScanEnd class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { public: void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override; void onScanEnd(NimBLEScanResults results, int reason) override; }; -#else -// NimBLE 1.x version - NO onScanEnd! -class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { -public: - void onResult(NimBLEAdvertisedDevice *advertisedDevice) override; - // No onScanEnd - this doesn't exist in NimBLE 1.x -}; -#endif // External reference to g_scanCallbacks extern AdvertisedDeviceCallbacks* g_scanCallbacks; @@ -115,15 +50,7 @@ extern BLEScan *pBLEScan; extern int scanTime; void ble_test(); -#if 0 -#ifdef BOARD_HAS_PSRAM -constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = false; -#else -constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = true; -#endif -#else constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = false; -#endif bool ble_scan_setup(); void ble_scan(); From 36cc72e71f702ef96fea1f2e0607a593aa07ea1a Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 08:42:09 +0200 Subject: [PATCH 63/74] Modify AdvertisedDeviceCallbacks for NimBLE compatibility Updated AdvertisedDeviceCallbacks to reflect that onScanEnd does not exist in NimBLEScanCallbacks. --- src/modules/ble/ble_common.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index 824a995db..232f909f3 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -33,14 +33,14 @@ #define MAX_DISPLAY_DEVICES 100 //============================================================================= -// AdvertisedDeviceCallbacks - For NimBLE 2.x +// AdvertisedDeviceCallbacks - NO onScanEnd (doesn't exist in NimBLE) //============================================================================= -// For NimBLE 2.x - uses NimBLEScanCallbacks with onScanEnd +// For NimBLE 2.x - uses NimBLEScanCallbacks with ONLY onResult class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { public: void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override; - void onScanEnd(NimBLEScanResults results, int reason) override; + // onScanEnd does NOT exist in NimBLEScanCallbacks - DO NOT DECLARE IT! }; // External reference to g_scanCallbacks From 9da4e157d74629935f1fc76e1c1d3fec7b1e53e4 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 08:42:45 +0200 Subject: [PATCH 64/74] Fix include guard and update header includes --- src/modules/ble/BLE_Suite.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index d716f6a4e..ec55ead7d 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -7,7 +7,7 @@ #include "HFP_Exploit.h" #include "fastpair_crypto.h" -#include "ble_common.h" // This provides AdvertisedDeviceCallbacks +#include "ble_common.h" #include #include #include @@ -28,7 +28,6 @@ bool check(int key); // NimBLE Version - MANUAL FORCE FOR NIMBLE 2.x //============================================================================= -// Since you're using NimBLE 2.3.7+, set this to 1 #ifndef NIMBLE_V2_PLUS #define NIMBLE_V2_PLUS 1 #endif From 811b0c5b2ab578274c3b880144f46784f500a2a0 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 10:06:56 +0200 Subject: [PATCH 65/74] Update BLE_Suite.h --- src/modules/ble/BLE_Suite.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index ec55ead7d..eed3ac1db 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -42,7 +42,7 @@ bool check(int key); #define SCAN_WINDOW 99 //============================================================================= -// Note: AdvertisedDeviceCallbacks is now defined in ble_common.h +// Note: AdvertisedDeviceCallbacks is defined in ble_common.h //============================================================================= //============================================================================= From efe75f23bd7ffb4e5b4460a68d129fea828b1d84 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 10:07:43 +0200 Subject: [PATCH 66/74] Refactor BLE scan callbacks and memory management Refactor BLE scanning and callback handling, improve memory management. --- src/modules/ble/ble_common.cpp | 211 +++++++++++++-------------------- 1 file changed, 80 insertions(+), 131 deletions(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index d127941e3..b6ddc3890 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -14,11 +14,56 @@ #define CHARACTERISTIC_RX_UUID "1bc68da0-f3e3-11e9-81b4-2a2ae2dbcce4" #define CHARACTERISTIC_TX_UUID "1bc68efe-f3e3-11e9-81b4-2a2ae2dbcce4" -// Limit the number of devices to prevent memory issues #define MAX_DISPLAY_DEVICES 100 BLEScan *pBLEScan = nullptr; -int scanTime = SCANTIME; // In seconds +int scanTime = SCANTIME; + +//============================================================================= +// AdvertisedDeviceCallbacks - DEFINITION +//============================================================================= + +// Static callback instance +AdvertisedDeviceCallbacks* g_scanCallbacks = nullptr; + +// Class definition - only here, not in header! +class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { +public: + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { + if (!advertisedDevice) return; + + if (options.size() >= MAX_DISPLAY_DEVICES) { + if (pBLEScan) { + pBLEScan->stop(); + Serial.println("Reached max devices, stopping scan"); + } + return; + } + + String bt_title; + String bt_name; + String bt_address; + String bt_signal; + + bt_name = advertisedDevice->getName().c_str(); + bt_address = advertisedDevice->getAddress().toString().c_str(); + bt_signal = String(advertisedDevice->getRSSI()); + + if (bt_name.isEmpty()) bt_name = ""; + bt_title = bt_name; + if (bt_title.isEmpty()) bt_title = bt_address; + + if (options.size() < MAX_DISPLAY_DEVICES) { + options.emplace_back(bt_title.c_str(), [=]() { + ble_info(bt_name, bt_address, bt_signal); + }); + } + } +}; + +//============================================================================= +// Ble Notify +//============================================================================= bool bleNotifyRetry(NimBLECharacteristic *chr, const uint8_t *value, size_t length, uint8_t retries) { if (chr == nullptr) return false; @@ -40,6 +85,10 @@ bool bleNotifyRetry(NimBLECharacteristic *chr, uint8_t retries) { return false; } +//============================================================================= +// BLE Server +//============================================================================= + #define ENDIAN_CHANGE_U16(x) ((((x) & 0xFF00) >> 8) + (((x) & 0xFF) << 8)) BLEServer *pServer = NULL; @@ -53,7 +102,6 @@ bool oldDeviceConnected = false; class MyServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer *pServer) { deviceConnected = true; }; - void onDisconnect(BLEServer *pServer) { deviceConnected = false; } }; @@ -62,6 +110,10 @@ class MyCallbacks : public BLECharacteristicCallbacks { void onWrite(NimBLECharacteristic *pCharacteristic) { data = pCharacteristic->getValue(); } }; +//============================================================================= +// BLE Info Display +//============================================================================= + uint8_t sta_mac[6]; char strID[18]; char strAddl[200]; @@ -83,93 +135,13 @@ void ble_info(const String &name, const String &address, const String &signal) { } } +static bool is_ble_inited = false; + //============================================================================= -// NimBLE Callbacks - Version-specific with proper lifetime management +// Stop BLE Stack //============================================================================= -// Static callback instances to prevent premature deletion -AdvertisedDeviceCallbacks* g_scanCallbacks = nullptr; - -#if NIMBLE_V2_PLUS -// NimBLE 2.x uses NimBLEScanCallbacks with const pointers -class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { -public: - void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { - if (!advertisedDevice) return; - - if (options.size() >= MAX_DISPLAY_DEVICES) { - if (pBLEScan) { - pBLEScan->stop(); - Serial.println("Reached max devices, stopping scan"); - } - return; - } - - String bt_title; - String bt_name; - String bt_address; - String bt_signal; - - bt_name = advertisedDevice->getName().c_str(); - bt_address = advertisedDevice->getAddress().toString().c_str(); - bt_signal = String(advertisedDevice->getRSSI()); - - if (bt_name.isEmpty()) bt_name = ""; - bt_title = bt_name; - if (bt_title.isEmpty()) bt_title = bt_address; - - if (options.size() < MAX_DISPLAY_DEVICES) { - options.emplace_back(bt_title.c_str(), [=]() { - ble_info(bt_name, bt_address, bt_signal); - }); - } - } - - void onScanEnd(NimBLEScanResults results, int reason) override { - Serial.printf("Scan ended: %d devices found, reason: %d\n", results.getCount(), reason); - } -}; -#else -// NimBLE 1.x uses NimBLEAdvertisedDeviceCallbacks -class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { -public: - void onResult(NimBLEAdvertisedDevice *advertisedDevice) override { - if (!advertisedDevice) return; - - if (options.size() >= MAX_DISPLAY_DEVICES) { - if (pBLEScan) { - pBLEScan->stop(); - Serial.println("Reached max devices, stopping scan"); - } - return; - } - - String bt_title; - String bt_name; - String bt_address; - String bt_signal; - - bt_name = advertisedDevice->getName().c_str(); - bt_address = advertisedDevice->getAddress().toString().c_str(); - bt_signal = String(advertisedDevice->getRSSI()); - - if (bt_name.isEmpty()) bt_name = ""; - bt_title = bt_name; - if (bt_title.isEmpty()) bt_title = bt_address; - - if (options.size() < MAX_DISPLAY_DEVICES) { - options.emplace_back(bt_title.c_str(), [=]() { - ble_info(bt_name, bt_address, bt_signal); - }); - } - } -}; -#endif - -static bool is_ble_inited = false; - void stopBLEStack() { - // Clean up scan callbacks first if (g_scanCallbacks) { delete g_scanCallbacks; g_scanCallbacks = nullptr; @@ -178,7 +150,6 @@ void stopBLEStack() { if (pBLEScan) { pBLEScan->stop(); pBLEScan->clearResults(); - // Don't delete pBLEScan - it's owned by BLEDevice pBLEScan = nullptr; } @@ -211,13 +182,16 @@ void stopBLEStack() { #endif } +//============================================================================= +// BLE Scan Setup +//============================================================================= + bool ble_scan_setup() { if (FORCE_RADIO_TEARDOWN_ON_SWITCH) { if (WiFi.getMode() != WIFI_MODE_NULL || wifiConnected) { wifiDisconnect(); delay(200); } - stopBLEStack(); delay(100); } @@ -230,7 +204,6 @@ bool ble_scan_setup() { } if (!is_ble_inited) { - // Use a minimal name to save RAM BLEDevice::init(""); is_ble_inited = true; } @@ -242,46 +215,38 @@ bool ble_scan_setup() { return false; } - // Clean up old callbacks if they exist if (g_scanCallbacks) { delete g_scanCallbacks; g_scanCallbacks = nullptr; } - // Create new callbacks g_scanCallbacks = new AdvertisedDeviceCallbacks(); if (!g_scanCallbacks) { displayError("Failed to create callbacks", true); return false; } -#if NIMBLE_V2_PLUS pBLEScan->setScanCallbacks(g_scanCallbacks); -#else - pBLEScan->setAdvertisedDeviceCallbacks(g_scanCallbacks); -#endif - pBLEScan->setActiveScan(true); pBLEScan->setInterval(SCAN_INT); pBLEScan->setWindow(SCAN_WINDOW); pBLEScan->setDuplicateFilter(false); esp_read_mac(sta_mac, ESP_MAC_BT); - sprintf( strID, "%02X:%02X:%02X:%02X:%02X:%02X", - sta_mac[0], - sta_mac[1], - sta_mac[2], - sta_mac[3], - sta_mac[4], - sta_mac[5] + sta_mac[0], sta_mac[1], sta_mac[2], + sta_mac[3], sta_mac[4], sta_mac[5] ); vTaskDelay(100 / portTICK_PERIOD_MS); return true; } +//============================================================================= +// BLE Scan +//============================================================================= + void ble_scan() { displayTextLine("Scanning.."); @@ -298,39 +263,23 @@ void ble_scan() { return; } - // Clear previous results before scanning pBLEScan->clearResults(); - // Use a try-catch block to handle potential exceptions try { -#if NIMBLE_V2_PLUS - // NimBLE 2.x: start() returns bool, getResults() gets the data - // Time is in milliseconds for NimBLE 2.x bool scanStarted = pBLEScan->start(scanTime * 1000, false); if (!scanStarted) { displayError("Failed to start BLE scan"); pBLEScan->clearResults(); return; } - // Get results - timeout in milliseconds BLEScanResults foundDevices = pBLEScan->getResults(scanTime * 1000, false); -#else - // NimBLE 1.x: start() returns results directly, time in seconds - BLEScanResults foundDevices = pBLEScan->start(scanTime, false); -#endif int deviceCount = foundDevices.getCount(); int processedCount = 0; - - // Cap the number of devices to prevent memory issues int maxToProcess = min(deviceCount, MAX_DISPLAY_DEVICES); for (int i = 0; i < maxToProcess && processedCount < MAX_DISPLAY_DEVICES; i++) { -#if NIMBLE_V2_PLUS const NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); -#else - NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); -#endif if (!advertisedDevice) continue; String bt_title; @@ -354,7 +303,6 @@ void ble_scan() { } } - // Show "and more" if we hit the limit if (options.size() >= MAX_DISPLAY_DEVICES) { options.emplace_back("... and more devices", nullptr); } @@ -364,13 +312,10 @@ void ble_scan() { return; } - // Stop scan if (pBLEScan) { pBLEScan->stop(); - // Don't clear results here - we need them for display } - // Only stop BLE if it wasn't active before and we're done with it if (!bleWasActiveBefore) { #if !defined(LITE_VERSION) if (!BLEStateManager::isBLEActive()) { @@ -391,6 +336,10 @@ void ble_scan() { } } +//============================================================================= +// BLE Server Init +//============================================================================= + bool initBLEServer() { uint64_t chipid = ESP.getEfuseMac(); String blename = "Bruce-" + String((uint8_t)(chipid >> 32), HEX); @@ -429,17 +378,13 @@ bool initBLEServer() { } pRxCharacteristic->setCallbacks(new MyCallbacks()); -#if NIMBLE_V2_PLUS - // NimBLE 2.x: Services start automatically when server starts - // No need to call pService->start() -#else - // NimBLE 1.x: Need to call pService->start() - pService->start(); -#endif - return true; } +//============================================================================= +// BLE Send Display +//============================================================================= + void disPlayBLESend() { uint8_t senddata[2] = {0}; tft.fillScreen(bruceConfig.bgColor); @@ -519,6 +464,10 @@ void disPlayBLESend() { BLEConnected = false; } +//============================================================================= +// BLE Test +//============================================================================= + void ble_test() { printf("ble test\n"); From 854e637043a7ca2e78b6f1a3cccb3bf164019c07 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 10:08:21 +0200 Subject: [PATCH 67/74] Refactor ble_common.h for NimBLE 2.x updates Updated comments and definitions for NimBLE 2.x compatibility. --- src/modules/ble/ble_common.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index 232f909f3..2bb6b433d 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -15,7 +15,6 @@ // NimBLE Version - MANUAL FORCE FOR NIMBLE 2.x //============================================================================= -// Since you're using NimBLE 2.3.7+, set this to 1 #ifndef NIMBLE_V2_PLUS #define NIMBLE_V2_PLUS 1 #endif @@ -29,18 +28,16 @@ #define SCAN_INT 100 #define SCAN_WINDOW 99 -// Maximum number of BLE devices to display to prevent memory issues #define MAX_DISPLAY_DEVICES 100 //============================================================================= -// AdvertisedDeviceCallbacks - NO onScanEnd (doesn't exist in NimBLE) +// AdvertisedDeviceCallbacks - DECLARATION ONLY //============================================================================= -// For NimBLE 2.x - uses NimBLEScanCallbacks with ONLY onResult +// Forward declaration - the class is defined in ble_common.cpp class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { public: void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override; - // onScanEnd does NOT exist in NimBLEScanCallbacks - DO NOT DECLARE IT! }; // External reference to g_scanCallbacks From 59488bf57ec528a274af42621d656ff90250d108 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 15:15:21 +0200 Subject: [PATCH 68/74] Refactor AdvertisedDeviceCallbacks with inline onResult Updated AdvertisedDeviceCallbacks to define onResult inline and handle device scanning logic. --- src/modules/ble/ble_common.h | 41 +++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index 2bb6b433d..c82a5b17b 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -31,13 +31,46 @@ #define MAX_DISPLAY_DEVICES 100 //============================================================================= -// AdvertisedDeviceCallbacks - DECLARATION ONLY +// AdvertisedDeviceCallbacks - SINGLE DEFINITION (Header Only) //============================================================================= -// Forward declaration - the class is defined in ble_common.cpp +// Forward declaration of pBLEScan for the callback +extern BLEScan *pBLEScan; + +// This is the ONLY definition of AdvertisedDeviceCallbacks +// It's defined inline in the header so it's visible everywhere class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { public: - void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override; + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { + if (!advertisedDevice) return; + + if (options.size() >= MAX_DISPLAY_DEVICES) { + if (pBLEScan) { + pBLEScan->stop(); + Serial.println("Reached max devices, stopping scan"); + } + return; + } + + String bt_title; + String bt_name; + String bt_address; + String bt_signal; + + bt_name = advertisedDevice->getName().c_str(); + bt_address = advertisedDevice->getAddress().toString().c_str(); + bt_signal = String(advertisedDevice->getRSSI()); + + if (bt_name.isEmpty()) bt_name = ""; + bt_title = bt_name; + if (bt_title.isEmpty()) bt_title = bt_address; + + if (options.size() < MAX_DISPLAY_DEVICES) { + options.emplace_back(bt_title.c_str(), [=]() { + ble_info(bt_name, bt_address, bt_signal); + }); + } + } }; // External reference to g_scanCallbacks @@ -46,6 +79,8 @@ extern AdvertisedDeviceCallbacks* g_scanCallbacks; extern BLEScan *pBLEScan; extern int scanTime; +// Function declarations +void ble_info(const String &name, const String &address, const String &signal); void ble_test(); constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = false; From 16531860c89520f71c06089a3372cac51fd1b161 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Fri, 17 Jul 2026 15:15:58 +0200 Subject: [PATCH 69/74] Remove AdvertisedDeviceCallbacks class and instance Removed the AdvertisedDeviceCallbacks class definition and its instance. --- src/modules/ble/ble_common.cpp | 38 +--------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index b6ddc3890..366e71157 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -20,47 +20,11 @@ BLEScan *pBLEScan = nullptr; int scanTime = SCANTIME; //============================================================================= -// AdvertisedDeviceCallbacks - DEFINITION +// Global Instance - Definition of the pointer //============================================================================= -// Static callback instance AdvertisedDeviceCallbacks* g_scanCallbacks = nullptr; -// Class definition - only here, not in header! -class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { -public: - void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { - if (!advertisedDevice) return; - - if (options.size() >= MAX_DISPLAY_DEVICES) { - if (pBLEScan) { - pBLEScan->stop(); - Serial.println("Reached max devices, stopping scan"); - } - return; - } - - String bt_title; - String bt_name; - String bt_address; - String bt_signal; - - bt_name = advertisedDevice->getName().c_str(); - bt_address = advertisedDevice->getAddress().toString().c_str(); - bt_signal = String(advertisedDevice->getRSSI()); - - if (bt_name.isEmpty()) bt_name = ""; - bt_title = bt_name; - if (bt_title.isEmpty()) bt_title = bt_address; - - if (options.size() < MAX_DISPLAY_DEVICES) { - options.emplace_back(bt_title.c_str(), [=]() { - ble_info(bt_name, bt_address, bt_signal); - }); - } - } -}; - //============================================================================= // Ble Notify //============================================================================= From 574248f30b211c22523cbbe3d79c50de84e1488a Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Sun, 19 Jul 2026 06:47:19 +0200 Subject: [PATCH 70/74] Refactor BLE handling and optimize memory usage Refactor BLE code by removing unnecessary comments and optimizing BLE scan setup. Adjust BLE server initialization and callbacks for improved memory management. --- src/modules/ble/ble_common.cpp | 142 +++++++++++++++------------------ 1 file changed, 65 insertions(+), 77 deletions(-) diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index 366e71157..d529eb3bc 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -14,20 +14,8 @@ #define CHARACTERISTIC_RX_UUID "1bc68da0-f3e3-11e9-81b4-2a2ae2dbcce4" #define CHARACTERISTIC_TX_UUID "1bc68efe-f3e3-11e9-81b4-2a2ae2dbcce4" -#define MAX_DISPLAY_DEVICES 100 - BLEScan *pBLEScan = nullptr; -int scanTime = SCANTIME; - -//============================================================================= -// Global Instance - Definition of the pointer -//============================================================================= - -AdvertisedDeviceCallbacks* g_scanCallbacks = nullptr; - -//============================================================================= -// Ble Notify -//============================================================================= +int scanTime = SCANTIME; // In seconds bool bleNotifyRetry(NimBLECharacteristic *chr, const uint8_t *value, size_t length, uint8_t retries) { if (chr == nullptr) return false; @@ -49,10 +37,6 @@ bool bleNotifyRetry(NimBLECharacteristic *chr, uint8_t retries) { return false; } -//============================================================================= -// BLE Server -//============================================================================= - #define ENDIAN_CHANGE_U16(x) ((((x) & 0xFF00) >> 8) + (((x) & 0xFF) << 8)) BLEServer *pServer = NULL; @@ -66,6 +50,7 @@ bool oldDeviceConnected = false; class MyServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer *pServer) { deviceConnected = true; }; + void onDisconnect(BLEServer *pServer) { deviceConnected = false; } }; @@ -74,10 +59,6 @@ class MyCallbacks : public BLECharacteristicCallbacks { void onWrite(NimBLECharacteristic *pCharacteristic) { data = pCharacteristic->getValue(); } }; -//============================================================================= -// BLE Info Display -//============================================================================= - uint8_t sta_mac[6]; char strID[18]; char strAddl[200]; @@ -99,21 +80,28 @@ void ble_info(const String &name, const String &address, const String &signal) { } } -static bool is_ble_inited = false; - //============================================================================= -// Stop BLE Stack +// NimBLE Callbacks - Version-specific with proper lifetime management //============================================================================= +#if NIMBLE_V2_PLUS +class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks {}; +#else +class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks { +public: + void onResult(NimBLEAdvertisedDevice *advertisedDevice) override {} +}; +#endif + +static AdvertisedDeviceCallbacks g_scanCallbacks; + +static bool is_ble_inited = false; + void stopBLEStack() { - if (g_scanCallbacks) { - delete g_scanCallbacks; - g_scanCallbacks = nullptr; - } - if (pBLEScan) { pBLEScan->stop(); pBLEScan->clearResults(); + // Don't delete pBLEScan - it's owned by BLEDevice pBLEScan = nullptr; } @@ -146,71 +134,64 @@ void stopBLEStack() { #endif } -//============================================================================= -// BLE Scan Setup -//============================================================================= - bool ble_scan_setup() { if (FORCE_RADIO_TEARDOWN_ON_SWITCH) { if (WiFi.getMode() != WIFI_MODE_NULL || wifiConnected) { wifiDisconnect(); delay(200); } + stopBLEStack(); delay(100); } RAM_LOG("ble-scan pre-init"); - if (!radioHasMemForBle()) { - displayError("Low RAM: free WiFi/SD first", true); - returnToMenu = true; - return false; - } - + if (!is_ble_inited) { + if (!radioHasMemForBle()) { + displayError("Low RAM: free WiFi/SD first", true); + returnToMenu = true; + return false; + } + // Use a minimal name to save RAM BLEDevice::init(""); is_ble_inited = true; } - + RAM_LOG("ble-scan post-init"); pBLEScan = BLEDevice::getScan(); if (!pBLEScan) { displayError("Failed to get scan object", true); return false; } - - if (g_scanCallbacks) { - delete g_scanCallbacks; - g_scanCallbacks = nullptr; - } - - g_scanCallbacks = new AdvertisedDeviceCallbacks(); - if (!g_scanCallbacks) { - displayError("Failed to create callbacks", true); - return false; - } - - pBLEScan->setScanCallbacks(g_scanCallbacks); + +#if NIMBLE_V2_PLUS + pBLEScan->setScanCallbacks(&g_scanCallbacks); +#else + pBLEScan->setAdvertisedDeviceCallbacks(&g_scanCallbacks); +#endif + pBLEScan->setActiveScan(true); pBLEScan->setInterval(SCAN_INT); pBLEScan->setWindow(SCAN_WINDOW); pBLEScan->setDuplicateFilter(false); esp_read_mac(sta_mac, ESP_MAC_BT); + sprintf( strID, "%02X:%02X:%02X:%02X:%02X:%02X", - sta_mac[0], sta_mac[1], sta_mac[2], - sta_mac[3], sta_mac[4], sta_mac[5] + sta_mac[0], + sta_mac[1], + sta_mac[2], + sta_mac[3], + sta_mac[4], + sta_mac[5] ); vTaskDelay(100 / portTICK_PERIOD_MS); return true; } -//============================================================================= -// BLE Scan -//============================================================================= - void ble_scan() { displayTextLine("Scanning.."); @@ -227,23 +208,30 @@ void ble_scan() { return; } + // Clear previous results before scanning pBLEScan->clearResults(); + // Use a try-catch block to handle potential exceptions try { - bool scanStarted = pBLEScan->start(scanTime * 1000, false); - if (!scanStarted) { - displayError("Failed to start BLE scan"); - pBLEScan->clearResults(); - return; - } +#if NIMBLE_V2_PLUS BLEScanResults foundDevices = pBLEScan->getResults(scanTime * 1000, false); +#else + // NimBLE 1.x: start() returns results directly, time in seconds + BLEScanResults foundDevices = pBLEScan->start(scanTime, false); +#endif int deviceCount = foundDevices.getCount(); int processedCount = 0; + + // Cap the number of devices to prevent memory issues int maxToProcess = min(deviceCount, MAX_DISPLAY_DEVICES); for (int i = 0; i < maxToProcess && processedCount < MAX_DISPLAY_DEVICES; i++) { +#if NIMBLE_V2_PLUS const NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); +#else + NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); +#endif if (!advertisedDevice) continue; String bt_title; @@ -267,6 +255,7 @@ void ble_scan() { } } + // Show "and more" if we hit the limit if (options.size() >= MAX_DISPLAY_DEVICES) { options.emplace_back("... and more devices", nullptr); } @@ -276,10 +265,13 @@ void ble_scan() { return; } + // Stop scan if (pBLEScan) { pBLEScan->stop(); + // Don't clear results here - we need them for display } + // Only stop BLE if it wasn't active before and we're done with it if (!bleWasActiveBefore) { #if !defined(LITE_VERSION) if (!BLEStateManager::isBLEActive()) { @@ -300,10 +292,6 @@ void ble_scan() { } } -//============================================================================= -// BLE Server Init -//============================================================================= - bool initBLEServer() { uint64_t chipid = ESP.getEfuseMac(); String blename = "Bruce-" + String((uint8_t)(chipid >> 32), HEX); @@ -342,13 +330,17 @@ bool initBLEServer() { } pRxCharacteristic->setCallbacks(new MyCallbacks()); +#if NIMBLE_V2_PLUS + // NimBLE 2.x: Services start automatically when server starts + // No need to call pService->start() +#else + // NimBLE 1.x: Need to call pService->start() + pService->start(); +#endif + return true; } -//============================================================================= -// BLE Send Display -//============================================================================= - void disPlayBLESend() { uint8_t senddata[2] = {0}; tft.fillScreen(bruceConfig.bgColor); @@ -428,10 +420,6 @@ void disPlayBLESend() { BLEConnected = false; } -//============================================================================= -// BLE Test -//============================================================================= - void ble_test() { printf("ble test\n"); From 65ea3d62923c908773724ba77b0bd2113e203e3e Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Sun, 19 Jul 2026 06:48:06 +0200 Subject: [PATCH 71/74] Refactor NimBLE version detection and clean includes Updated NimBLE version detection logic and removed dependency on ble_common.h. --- src/modules/ble/BLE_Suite.h | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/modules/ble/BLE_Suite.h b/src/modules/ble/BLE_Suite.h index eed3ac1db..a9d5e7002 100644 --- a/src/modules/ble/BLE_Suite.h +++ b/src/modules/ble/BLE_Suite.h @@ -7,7 +7,6 @@ #include "HFP_Exploit.h" #include "fastpair_crypto.h" -#include "ble_common.h" #include #include #include @@ -25,11 +24,27 @@ extern BruceConfig bruceConfig; bool check(int key); //============================================================================= -// NimBLE Version - MANUAL FORCE FOR NIMBLE 2.x +// NimBLE Version Detection - Must match ble_common.h //============================================================================= -#ifndef NIMBLE_V2_PLUS +// Detect NimBLE 2.x by checking for features only available in v2+ +#if defined(NIMBLE_VERSION) + #if NIMBLE_VERSION >= 20000 + #define NIMBLE_V2_PLUS 1 + #endif +#elif defined(NIMBLE_CPP_VERSION) && NIMBLE_CPP_VERSION >= 2 + #define NIMBLE_V2_PLUS 1 +#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 + #define NIMBLE_V2_PLUS 1 +#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR == 1 && NIMBLE_VERSION_MINOR >= 5 #define NIMBLE_V2_PLUS 1 +#elif __has_include() + #define NIMBLE_V2_PLUS 1 +#endif + +// If none of the above matched, default to v1 behavior (safe fallback) +#ifndef NIMBLE_V2_PLUS + #define NIMBLE_V2_PLUS 0 #endif //============================================================================= @@ -41,10 +56,6 @@ bool check(int key); #define SCAN_INT 100 #define SCAN_WINDOW 99 -//============================================================================= -// Note: AdvertisedDeviceCallbacks is defined in ble_common.h -//============================================================================= - //============================================================================= // Enums //============================================================================= From e01e7da7c53ce61f890086a1389700dee9f515d3 Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Sun, 19 Jul 2026 06:49:42 +0200 Subject: [PATCH 72/74] Update BLE_Suite.cpp for memory checks Made various adjustments to BLE scanning logic, including memory checks and error handling. --- src/modules/ble/BLE_Suite.cpp | 46 +++++++++++------------------------ 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index d77dc8c11..7afa2bed6 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -2,7 +2,7 @@ * BLE Suite v3.1 - Complete BLE attack and analysis toolkit * Author: Ninja-jr * Version: 3.1 - * Last Updated: 17/07/2026 + * Last Updated: 19/07/2026 * * Contains: Vulnerability scanning, HID attacks, FastPair exploits, * HFP attacks, Audio attacks, DuckyScript injection, @@ -16,6 +16,7 @@ #include "ble_common.h" #include "core/display.h" #include "core/mykeyboard.h" +#include "core/radio_mem.h" #include "core/utils.h" #include "core/wifi/wifi_common.h" #include "fastpair_crypto.h" @@ -326,6 +327,11 @@ bool BLEStateManager::initBLE(const String &name, int powerLevel) { if (bleInitialized) deinitBLE(true); + if (!radioHasMemForBle()) { + displayError("Low RAM: free WiFi/SD first", true); + return false; + } + std::string nameStr = name.c_str(); NimBLEDevice::init(nameStr); NimBLEDevice::setPower((esp_power_level_t)powerLevel); @@ -341,6 +347,8 @@ void BLEStateManager::deinitBLE(bool immediate) { NimBLEDevice::deinit(true); bleInitialized = false; currentDeviceName = ""; + g_pBLEScan = nullptr; + g_bleScanActive = false; } void BLEStateManager::registerClient(NimBLEClient *client) { @@ -3309,12 +3317,6 @@ std::vector FastPairExploitEngine::scanForFastPairDevices(in // Use the same version detection as ble_common.h #if NIMBLE_V2_PLUS - // NimBLE 2.x: start returns bool, getResults returns NimBLEScanResults - bool scanStarted = pScan->start(duration * 1000, false); - if (!scanStarted) { - showAttackProgress("Failed to start FastPair scan", TFT_RED); - return discoveredDevices; - } NimBLEScanResults results = pScan->getResults(duration * 1000, false); #else // NimBLE 1.x: start returns NimBLEScanResults directly @@ -3959,12 +3961,7 @@ void BLE_Sniffer() { padprintln("Status: CAPTURING..."); padprintln("Press [SEL] to stop"); -#if NIMBLE_V2_PLUS - pScan->start(10 * 1000, true); - NimBLEScanResults results = pScan->getResults(10 * 1000, true); -#else NimBLEScanResults results = pScan->getResults(10 * 1000, true); -#endif for (int i = 0; i < results.getCount(); i++) { #if NIMBLE_V2_PLUS @@ -4249,12 +4246,6 @@ String selectTargetFromScan(const char *title) { try { #if NIMBLE_V2_PLUS - // NimBLE 2.x API: start returns bool, getResults returns NimBLEScanResults - bool scanStarted = g_pBLEScan->start(activeScanTime * 1000, false); - if (!scanStarted) { - displayError("Failed to start BLE scan"); - return ""; - } BLEScanResults activeResults = g_pBLEScan->getResults(activeScanTime * 1000, false); #else // NimBLE 1.x API: start returns NimBLEScanResults @@ -4300,11 +4291,6 @@ String selectTargetFromScan(const char *title) { tft.print("Passive scan (" + String(passiveScanTime) + "s)..."); #if NIMBLE_V2_PLUS - bool passiveScanStarted = g_pBLEScan->start(passiveScanTime * 1000, false); - if (!passiveScanStarted) { - displayError("Failed to start passive BLE scan"); - return ""; - } BLEScanResults passiveResults = g_pBLEScan->getResults(passiveScanTime * 1000, false); #else BLEScanResults passiveResults = g_pBLEScan->start(passiveScanTime, false); @@ -4359,7 +4345,6 @@ String selectTargetFromScan(const char *title) { // Get snapshot of discovered devices DeviceSnapshot* snapshot = scannerData.getSnapshot(); if (!snapshot || snapshot->count == 0) { - if (snapshot) delete snapshot; tft.fillScreen(TFT_YELLOW); tft.drawRect(5, 5, tftWidth - 10, tftHeight - 10, TFT_BLACK); tft.setTextColor(TFT_BLACK, TFT_YELLOW); @@ -4408,7 +4393,9 @@ String selectTargetFromScan(const char *title) { } // UI selection loop - int maxVisibleDevices = 4, deviceItemHeight = 30, menuStartY = 60; + int deviceItemHeight = 30, menuStartY = 60; + int maxVisibleDevices = (tftHeight - 45 - menuStartY) / deviceItemHeight; + if (maxVisibleDevices < 1) maxVisibleDevices = 1; int selectedIdx = 0, scrollOffset = 0; int lastSelected = -1, lastScrollOffset = -1; bool exitLoop = false; @@ -4512,15 +4499,13 @@ String selectTargetFromScan(const char *title) { String returnMac = selectedMAC; returnMac.trim(); - - delete snapshot; + // DO NOT clear scannerData here - keep it for potential reuse return returnMac; } delay(50); } - - delete snapshot; + // DO NOT clear scannerData here - keep it for potential reuse return ""; } @@ -4530,7 +4515,6 @@ String selectMultipleTargetsFromScan(const char *title, std::vectorcount == 0) { - if (snapshot) delete snapshot; showErrorMessage("No devices found. Run scan first."); return ""; } @@ -4600,7 +4584,6 @@ String selectMultipleTargetsFromScan(const char *title, std::vector Date: Sun, 19 Jul 2026 06:51:52 +0200 Subject: [PATCH 73/74] Update NimBLE-Arduino version to 2.5 --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 32dc5cda7..7da672022 100644 --- a/platformio.ini +++ b/platformio.ini @@ -195,7 +195,7 @@ lib_deps = earlephilhower/ESP8266SAM@^1.1.0 mikalhart/TinyGPSPlus tinyu-zhao/FFT@^0.0.1 - h2zero/NimBLE-Arduino@2.3.7 + h2zero/NimBLE-Arduino@2.5 nrf24/RF24 @ 1.4.11 Adafruit Si4713 Library@1.2.3 Bodmer/JPEGDecoder @@ -264,7 +264,7 @@ lib_deps = ;earlephilhower/ESP8266SAM@^1.1.0 mikalhart/TinyGPSPlus@1.1.0 tinyu-zhao/FFT@0.0.1 - h2zero/NimBLE-Arduino@2.3.7 + h2zero/NimBLE-Arduino@2.5 nrf24/RF24 @ 1.4.11 ;Adafruit Si4713 Library@1.2.3 Bodmer/JPEGDecoder From 23e750c76ed22549d718ee247f9476b6798519eb Mon Sep 17 00:00:00 2001 From: Ninja-jr Date: Sun, 19 Jul 2026 07:01:54 +0200 Subject: [PATCH 74/74] Refactor NimBLE version checks and clean up code Refactor NimBLE version detection and remove unused AdvertisedDeviceCallbacks class. --- src/modules/ble/ble_common.h | 89 ++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index c82a5b17b..b5e08a180 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -12,11 +12,38 @@ #include //============================================================================= -// NimBLE Version - MANUAL FORCE FOR NIMBLE 2.x +// NimBLE Version Detection - Must be consistent across all files //============================================================================= -#ifndef NIMBLE_V2_PLUS +// Detect NimBLE 2.x by checking for features only available in v2+ +#if defined(NIMBLE_VERSION) + #if NIMBLE_VERSION >= 20000 + #define NIMBLE_V2_PLUS 1 + #endif +#elif defined(NIMBLE_CPP_VERSION) && NIMBLE_CPP_VERSION >= 2 + #define NIMBLE_V2_PLUS 1 +#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR >= 2 + #define NIMBLE_V2_PLUS 1 +#elif defined(NIMBLE_VERSION_MAJOR) && NIMBLE_VERSION_MAJOR == 1 && NIMBLE_VERSION_MINOR >= 5 #define NIMBLE_V2_PLUS 1 +#elif __has_include() + #define NIMBLE_V2_PLUS 1 +#endif + +// If none of the above matched, check if NimBLEScanResults is a type +#ifndef NIMBLE_V2_PLUS + #ifdef __has_include + #if __has_include() + #if defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + #define NIMBLE_V2_PLUS 1 + #endif + #endif + #endif +#endif + +// If we still don't know, default to v1 behavior (safe fallback) +#ifndef NIMBLE_V2_PLUS + #define NIMBLE_V2_PLUS 0 #endif //============================================================================= @@ -28,61 +55,25 @@ #define SCAN_INT 100 #define SCAN_WINDOW 99 +// Maximum number of BLE devices to display to prevent memory issues #define MAX_DISPLAY_DEVICES 100 -//============================================================================= -// AdvertisedDeviceCallbacks - SINGLE DEFINITION (Header Only) -//============================================================================= - -// Forward declaration of pBLEScan for the callback -extern BLEScan *pBLEScan; - -// This is the ONLY definition of AdvertisedDeviceCallbacks -// It's defined inline in the header so it's visible everywhere -class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks { -public: - void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { - if (!advertisedDevice) return; - - if (options.size() >= MAX_DISPLAY_DEVICES) { - if (pBLEScan) { - pBLEScan->stop(); - Serial.println("Reached max devices, stopping scan"); - } - return; - } - - String bt_title; - String bt_name; - String bt_address; - String bt_signal; - - bt_name = advertisedDevice->getName().c_str(); - bt_address = advertisedDevice->getAddress().toString().c_str(); - bt_signal = String(advertisedDevice->getRSSI()); - - if (bt_name.isEmpty()) bt_name = ""; - bt_title = bt_name; - if (bt_title.isEmpty()) bt_title = bt_address; - - if (options.size() < MAX_DISPLAY_DEVICES) { - options.emplace_back(bt_title.c_str(), [=]() { - ble_info(bt_name, bt_address, bt_signal); - }); - } - } -}; - -// External reference to g_scanCallbacks -extern AdvertisedDeviceCallbacks* g_scanCallbacks; +// Memory protection: Reduce scan time in low-memory situations +#define SCAN_TIME_REDUCED 3 extern BLEScan *pBLEScan; extern int scanTime; -// Function declarations -void ble_info(const String &name, const String &address, const String &signal); void ble_test(); +#if 0 +#ifdef BOARD_HAS_PSRAM +constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = false; +#else +constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = true; +#endif +#else constexpr bool FORCE_RADIO_TEARDOWN_ON_SWITCH = false; +#endif bool ble_scan_setup(); void ble_scan();