GhosttoothAPI - Custom payload + framework for bluetooth audio and bluetooth gamepads

TheStonedModder

Developer
Developer
Joined
Dec 25, 2022
Messages
3,017
Reaction score
5,222
Trophies
2
Age
29
XP
9,834
Country
United States
Only audio is supported for now. Meaning you can use any bluetooth audio device. Such as Airpods or Beats!

ghost-toothAPI



The PS5 has no Bluetooth audio. Sony's own wireless headsets need a USB dongle. The Bluetooth chip in the console is for DualSense, not headphones.

ghost-toothAPI is a payload that plays the console's sound — game and system UI on a normal Bluetooth headset, Turn the headset off to stop.

This is v1. Same Bluetooth path is meant to take third-party controllers later, plus a PKG with an on-console pairing screen. That is not in this build.




What you need



  • Jailbroken PS5 with a payload loader (the usual ELF send on 9021)
  • A Bluetooth headset that does A2DP (basically all of them)
  • DualSense can stay on. That is the point.

Only tested on 5.50.





How to use it



First time

  1. Put the headset in pairing mode.
  2. Send ghost-toothAPI.elf.
  3. Wait for the notification searching - put the headset in pairing mode.
  4. It scans ~10 s, ignores TVs/soundbars, and picks headphones (name + class). Notification shows the name it is connecting to.
  5. When you see audio on the headset - switch it off to stop, you are in.

Pairing is saved. Next send reconnects that headset. TVs are never saved.

Optional: /data/ghost-toothAPI/headset.ini — leave it blank, or name=beats / address=AA:BB:CC:DD:EE:FF (address as printed in the log).

Stop

Turn the headset off. That is the exit.

Stuck on "already linked"

Power-cycle the headset, send the ELF again. A killed payload can leave the radio thinking the headset is still connected. Do not send two copies at once.

Notifications are prefixed ghost-toothAPI:. If something fails, it tells you to read the log.





What you get / what you don't



WorksDoes not
Game audio + system sounds (same mix Remote Play uses)Mic / party chat from the headset
PS menu stays audible (not muted like the capture UI mix)Headset volume / pause buttons (no AVRCP)
DualSense stays wirelessaptX / AAC / LDAC — SBC only
No kernel patch, no install3D audio beds — you get the stereo mix Remote Play already has

SBC at 48 kHz if the headset allows it, otherwise 44.1. Bitpool 40, locked (not a 2–53 range — that made Beats Solo 3 sound like static).

If you close a game, the stream keeps going as silence and picks the mix back up when the next one starts. If audio drifts more than ~160 ms behind the picture, one packet is dropped.




If it breaks



Log is here:

Code:
/data/ghost-toothAPI/ghost-toothAPI.log

Bonded headset key:

Code:
/data/ghost-toothAPI/bond.key

Delete bond.key if you want to pair a different headset (or if auth keeps failing — a bad key is deleted automatically, but you can do it by hand).

NotificationMeaning
already runningELF is already up. Wait, or the last run crashed less than 15 seconds ago.
audio capture unavailableCould not open the Remote Play capture path. Log has the error.
the Bluetooth controller did not answerUSB HCI setup failed. Log has the error.
no headphones found - pairing mode?Scan only saw TVs/speakers. Headset needs to stay in pairing until it connects.
could not connect to the headsetPair / auth / A2DP failed. Headset off, out of range, or already on a phone.
turn the headset off and on, then try againRadio still has an old link. Power-cycle the headset.




How it actually works



Short version: the PS5 wireless chip is two Bluetooth controllers on one USB device.

  • Controller 1 is DualSense. We never touch it.
  • Controller 0 is sitting there claimed by the same kernel driver. We open /dev/ugen0.2 and talk HCI on those pipes without detaching the driver. Detach any interface on that chip and DualSense dies.

Because the kernel is still reading the same endpoints, some HCI events get stolen (ours, and some of the system's). The stack retries, ignores TVs, and keeps at most 3 ACL packets in flight on a controller that reports 7 buffers. Two copies of the payload must not run — they would split the same controller.

Audio is not a tap of the HDMI encoder. It is libSceAvcap2 opened the way Remote Play opens it (source 2, raw 48 kHz stereo float). That mix includes system sounds and is not muted when the PS menu is up. libSceIpmi has to be loaded first or the process dies with no signal.

A2DP is Source, SBC, no AVRCP, no HFP. Headset sees the name ghost-toothAPI.

USB node: /dev/ugen0.2

Code:
Function 0 (this payload)
  events 0x82, ACL in 0x81, ACL out 0x01
  commands: control pipe, class-device, bRequest 0

Function 1 (system DualSense)
  events 0x85, ACL in 0x84, ACL out 0x04

Do not HCI Reset. Do not unbind interfaces.

Capture NIDs (resolved with kernel_dynlib_resolve, not dlsym by name):

Code:
svzPXluOz8U  sceAvcap2Initialize
gojqghDU+1Y  sceAvcap2Terminate
3nHpE7Dp5SE  sceAvcap2OpenAudio
tR7gPe1i8hw  sceAvcap2Close
8CGNCwBsItI  sceAvcap2Start
Z5dKK0xnQ8g  sceAvcap2Stop
WhsHggqdwcg  sceAvcap2ReadAudio

OpenAudio block is 0x38 bytes, source=2, mode=0. ReadAudio 0x81950002 empty, 0x81950004 overrun, 0x81950008 session ended.

State:

Code:
/data/ghost-toothAPI/ghost-toothAPI.log
/data/ghost-toothAPI/ghost-toothAPI.lock
/data/ghost-toothAPI/bond.key          (6-byte addr + 16-byte key + 1-byte type)
/data/ghost-toothAPI/headset.ini       (optional name= / address=)

Build: ps5-payload-dev SDK, make → ghost-toothAPI.elf.




What's next



  • HID — third-party controllers on the same controller-0 path
  • Companion PKG — pairing UI on the console, device list, not just notifications




Bug reports: paste the last page of /data/ghost-toothAPI/ghost-toothAPI.log, headset model, and whether DualSense dropped. If DualSense dropped, say so first — that means something unbound USB, which this payload is not supposed to do.
 
Awesome work, keep it up dude.

Sadly I get the dualsense dropout and a fail to connect after successful pairing. Dualsense won't connect again until a reboot.
I'm trying to use a generic bluetooth receiver connected to my Logitech Z200's to convert them into Z207's

13:53:15 ========================================
13:53:15 ghost-toothAPI 1.0
13:53:15 capture: running, 48000 Hz stereo
13:53:15 hci: controller open alongside the system, 48 event and 16 ACL reads
13:53:17 controller: 7 ACL buffers of 1021 bytes
13:53:17 radio: leftover links cleared
13:53:17 radio ready
13:53:17 wrote /data/ghost-toothAPI/headset.ini (optional)
13:53:17 searching for a headset in pairing mode
13:53:20 found 48:69:FF:EB:6E:9A 'EB01' (class 240404 rssi -47)
13:53:27 candidate 48:69:FF:EB:6E:9A 'EB01' class 240404 rssi -53 score 152
13:53:27 trying 48:69:FF:EB:6E:9A 'EB01' (score 152)
13:53:28 connecting EB01
13:53:28 48:69:FF:EB:6E:9A
13:53:29 connection complete: status 0000 handle 0x001
13:53:29 no stored key: pairing
13:53:30 paired with 48:69:FF:EB:6E:9A, key saved
13:53:30 authentication complete: status 0000
13:53:32 encryption change: status 0000 enabled 1
13:53:32 link encrypted
13:53:32 sdp: search+attributes -> 0 record(s), 2 bytes
13:53:42 l2cap: signaling connection timed out
13:53:43 disconnected: status 0000 reason 0x16
13:53:43 disconnected: status 0x02 reason 0x1f
13:53:43 radio: leftover links cleared
13:53:44 ghost-toothAPI done
 

Site & Scene News