diff --git a/BrcmPatchRAM/BlueToolFixup.cpp b/BrcmPatchRAM/BlueToolFixup.cpp index e592e66..9ec46cf 100644 --- a/BrcmPatchRAM/BlueToolFixup.cpp +++ b/BrcmPatchRAM/BlueToolFixup.cpp @@ -161,9 +161,74 @@ static const uint8_t kBadChipsetCheckPatched15_4[] = 0x90, 0x90 }; +// Optional universal workaround for bluetoothd Read Clock failures. +// Many third-party Bluetooth controllers (like Intel) fail Apple's HCI_Read_Clock check. +// When connecting to modern Apple headphones with W1/H1/H2 chips (e.g., AirPods Pro 2), +// this causes bluetoothd to abort A2DP audio bring-up in the Fast-Connect reconnect path. +// This results in the infamous "connected but no sound" issue for these devices. +// See: https://github.com/OpenIntelWireless/IntelBluetoothFirmware/issues/462 +// We patch the Fast-Connect A2DP gate to unconditionally force the success path. +// Activated by boot-arg -btlfxa2dpcheck + +// Apple FastConnect A2DP reconnect gate. +// Replaces "test r14d, r14d; je" with "xor r14d, r14d; jmp" to +// unconditionally take the audio-publish path. + +// macOS 14.x/15.x/16.x (Sonoma/Sequoia/Tahoe) +static const uint8_t kFastConnectA2DPGateOriginal14_0[] = +{ + 0x45, 0x85, 0xF6, + 0x74, 0x25, + 0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x8B, 0x18 +}; + +static const uint8_t kFastConnectA2DPGatePatched14_0[] = +{ + 0x45, 0x31, 0xF6, + 0xEB, 0x25, + 0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x8B, 0x18 +}; + +static const uint8_t kFastConnectA2DPGateMask14_0[] = +{ + 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF +}; + +// macOS 12.x/13.x (Monterey/Ventura) +static const uint8_t kFastConnectA2DPGateOriginal12_0[] = +{ + 0x85, 0xDB, + 0x74, 0x25, + 0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x8B, 0x18 +}; + +static const uint8_t kFastConnectA2DPGatePatched12_0[] = +{ + 0x31, 0xDB, + 0xEB, 0x25, + 0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x8B, 0x18 +}; + +static const uint8_t kFastConnectA2DPGateMask12_0[] = +{ + 0xFF, 0xFF, + 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF +}; + static bool shouldPatchBoardId = false; static bool shouldPatchAddress = false; static bool shouldPatchNvramCheck = false; +static bool shouldPatchA2DPCheck = false; +static bool loggedA2DPCheckMiss = false; static constexpr size_t kBoardIdSize = sizeof("Mac-F60DEB81FF30ACF6"); static constexpr size_t kBoardIdSizeLegacy = sizeof("Mac-F22586C8"); @@ -206,7 +271,23 @@ static inline void searchAndPatchWithMask(const void *haystack, size_t haystackS template static inline void searchAndPatchWithMask(const void *haystack, size_t haystackSize, const char *path, const T (&needle)[findSize], const T (&findMask)[findMaskSize], const T (&patch)[replaceSize], const T (&patchMask)[replaceMaskSize]) { - searchAndPatchWithMask(haystack, haystackSize, path, needle, findSize * sizeof(T), findMask, findMaskSize * sizeof(T), patch, replaceSize * sizeof(T), patchMask, replaceSize * sizeof(T)); + searchAndPatchWithMask(haystack, haystackSize, path, needle, findSize * sizeof(T), findMask, findMaskSize * sizeof(T), patch, replaceSize * sizeof(T), patchMask, replaceMaskSize * sizeof(T)); +} + + + + +static bool searchAndPatchNamedWithMask(const void *haystack, size_t haystackSize, const char *path, const char *name, const void *needle, size_t findSize, const void *findMask, size_t findMaskSize, const void *patch, size_t replaceSize, const void *patchMask, size_t replaceMaskSize) { + if (!KernelPatcher::findAndReplaceWithMask(const_cast(haystack), haystackSize, needle, findSize, findMask, findMaskSize, patch, replaceSize, patchMask, replaceMaskSize)) + return false; + + SYSLOG(MODULE_SHORT, "[a2dpcheck] patched %s in %s", name, path); + return true; +} + +template +static bool searchAndPatchNamedWithMask(const void *haystack, size_t haystackSize, const char *path, const char *name, const T (&needle)[findSize], const T (&findMask)[findMaskSize], const T (&patch)[replaceSize], const T (&patchMask)[replaceMaskSize]) { + return searchAndPatchNamedWithMask(haystack, haystackSize, path, name, needle, findSize * sizeof(T), findMask, findMaskSize * sizeof(T), patch, replaceSize * sizeof(T), patchMask, replaceMaskSize * sizeof(T)); } @@ -236,6 +317,23 @@ static void patched_cs_validate_page(vnode_t vp, memory_object_t pager, memory_o searchAndPatch(data, PAGE_SIZE, path, boardIdsWithUSBBluetooth[0], kBoardIdSize, BaseDeviceInfo::get().boardIdentifier, kBoardIdSize); if (shouldPatchAddress) searchAndPatchWithMask(data, PAGE_SIZE, path, kSkipAddressCheckOriginal, kSkipAddressCheckMask, kSkipAddressCheckPatched, kSkipAddressCheckMask); + if (shouldPatchA2DPCheck) { + bool patched = false; + + // macOS 14.x/15.x/26.x (Sonoma/Sequoia/Tahoe) pattern + if (getKernelVersion() >= KernelVersion::Sonoma) { + patched |= searchAndPatchNamedWithMask(data, PAGE_SIZE, path, "fast-connect A2DP BT-clock gate (macOS 14+)", kFastConnectA2DPGateOriginal14_0, kFastConnectA2DPGateMask14_0, kFastConnectA2DPGatePatched14_0, kFastConnectA2DPGateMask14_0); + } + // macOS 12.x/13.x (Monterey/Ventura) pattern + else if (getKernelVersion() >= KernelVersion::Monterey) { + patched |= searchAndPatchNamedWithMask(data, PAGE_SIZE, path, "fast-connect A2DP BT-clock gate (macOS 12/13)", kFastConnectA2DPGateOriginal12_0, kFastConnectA2DPGateMask12_0, kFastConnectA2DPGatePatched12_0, kFastConnectA2DPGateMask12_0); + } + + if (!patched && !loggedA2DPCheckMiss) { + loggedA2DPCheckMiss = true; + SYSLOG(MODULE_SHORT, "[a2dpcheck] enabled for bluetoothd, but no known FastConnect Gate pattern was seen yet"); + } + } } } } @@ -267,6 +365,9 @@ static void pluginStart() { shouldPatchAddress = checkKernelArgument("-btlfxallowanyaddr"); if (getKernelVersion() <= KernelVersion::Sonoma) shouldPatchNvramCheck = checkKernelArgument("-btlfxnvramcheck"); + shouldPatchA2DPCheck = checkKernelArgument("-btlfxa2dpcheck"); + if (shouldPatchA2DPCheck) + SYSLOG(MODULE_SHORT, "[a2dpcheck] bluetoothd Read Clock workaround enabled (fast-connect A2DP bypass)"); KernelPatcher::RouteRequest csRoute = KernelPatcher::RouteRequest("_cs_validate_page", patched_cs_validate_page, orig_cs_validate); if (!patcher.routeMultipleLong(KernelPatcher::KernelID, &csRoute, 1)) SYSLOG(MODULE_SHORT, "failed to route cs validation pages");