In case you missed it, here is Part 1, where you can read about this DualShock 4 totally messed up that I’m trying to repair.

So, let’s go on with this journey! :grin:

At this point I have a working linux driver and finally I can start testing by myself what is working and what is not.

The result:

  • Bluetooth is totally not working :ballot_box_with_check:
  • Battery controller is not working :ballot_box_with_check:
  • Touchpad not working :ballot_box_with_check:
  • IMU has no calibration :ballot_box_with_check:
  • Broken LED + JDS-055 secondary-board :ballot_box_with_check:

What a nice gift! :grin:

Only last point is not interesting, I just ordered a new JDS-055 one from AliExpress which will fix the broken LED. In the meantime I use another secondary-board from a working DS4 I have at home.

Bluetooth

Probably every PS4 user knows that pressing the combo PS + Share sends the controller into Bluetooth Pairing Mode, the LED starts blinking white and the DS4 can be connected to a smartphone or a computer.

This is a good way to test what kind of issue with Bluetooth we have. Maybe the antenna is broken and we can pair only with a device extremely close. Or maybe the LED blinks but no signal is emitted.

After connecting the JDS-055 of another DS4 to have a fully working LED, double-checking that the LED is working by connecting it to my laptop and loading the hid-sony driver, we can finally try the PS + Share combo.

What do you expect? Nothing happens on this DS4: not only it does not enable pairing with other devices, it does not even make the LED blink white! Totally dead.

Just to be 100% sure the antenna is OK, I tested it with a super-powa oscilloscope borrowed from a friend to see if there are at least attempts by the DS4 to transmit anything.

Well, zero emissions :cry: such a green device. In the JDM-055 board, the chip responsible for Bluetooth is the main microcontroller (MediaTek MT3160N), so we cannot even blame the connection between the main CPU and the Bluetooth transponder.

Battery controller

In the JDM-055 the battery is controlled by the main microcontroller, no external controllers. What is the problem here? It refuses both to charge the battery and to show any kind of statistics about that. :weary:

The linux driver is well written and exposes all battery stats to /sys/class/power_supply/ps-controller-battery-<bt-mac-address>/, this is the result:

$ cat status
Unknown
$ cat capacity
0
$ cat present
1 # even without the battery

Even worse, the controller seems refusing to boot if you try to power it on using a battery. If you press the PS button on a working DS4, the controller turns on and the LED starts fading in and out. If you press the PS button with this controller, well… nothing happens :cry:.

So you may think that the battery port is broken or disconnected. Not even close, connecting a bench power supply to the battery port shows that the DS4 drains 30/40mA for the first 10-30 seconds (don’t remember precisely) and then goes to sleep.

Recap

The hardware seems OK: If I connect the battery or the touchpad into another DualShock4, they work perfectly. If I connect a tested battery or touchpad into this controller, they do not work.

This starts to remind me this joke :thinking::

A man goes to the doctor and says, “Doctor, wherever I touch, it hurts.”

The doctor asks, “What do you mean?”

The man says, “When I touch my shoulder, it really hurts. When I touch my knee - OUCH! When I touch my forehead, it really, really hurts.”

The doctor says, “I know what’s wrong with you. You’ve broken your finger!”

Maybe, just maybe, even if there are 100 different issues, the only real problem is in the microcontroller, maybe a bad firmware update?

Well, let’s try to restore the firmware! :innocent:

Sure. :joy: I dare you to find online any information about firmware updates for a DS4. It seems that updates are really common with the new DualSense PS5 Controller but not with previous models, like the DualShock 4. :disappointed:

Modding community

After googling a bit about how to repair my controller, this interesting page got all my attention.

The repo linked is not available anymore due to DMCA takedown :cry: but there is a mirror on GitHub available, even if some files are missing.

The modding community seems to be focused more on the communication between the DS4 and the PS4, to emulate a fake controller in a way that is accepted by the PS4. That part is working 100% on my controller, so I’m going to focus on other aspects of the reverse engineering.

jedi_tool.py

Anyway, the jedi_tool.py from that repository is really interesting.

It tries to connect to the USB device 054c:05c4 and sends a request to obtain the Bluetooth MAC address and some firmware information.

For the record, 054c is the Vendor ID of Sony, 05c4 is the Device ID of the old DualShock4 V1, I’d say board JDM-001 or something like the JDM-030. It’s an old script, I know.

Here’s the main application code:

bt_addrs = get_bt_mac_addrs()
print('ds4 bt mac: %s host bt mac: %s' %
      (binascii.hexlify(bt_addrs[0]), binascii.hexlify(bt_addrs[1])))
print(get_version_info())
exit()

Let’s just change the Device ID with the one of my DS4 V2: 054c:09cc and run it:

$ python3 jedi_tool.py
ds4 bt mac: b'000000000000' host bt mac: b'947665e36628'
Compiled at: Sep 21 2018 04:50:51
hw_ver:0100.ff08
sw_ver:00000001.a00a sw_series:2010
code size:0002a000

OK, really interesting.

First because this script works with the new version of the DS4, so the protocol is almost the same. Second because the local Bluetooth MAC Address is all zeros. Third because there are some interesting infos about the firmware, like the build date and the version.

This is the output with another DS4 I have:

$ python3 jedi_tool.py
ds4 bt mac: b'28516511aea4' host bt mac: b'9081ee0cdaf0'
Compiled at: Sep 21 2018 04:50:51
hw_ver:0100.b400
sw_ver:00000001.a00a sw_series:2010
code size:0002a000

The Bluetooth MAC Address is there, same build date, same software version but slightly different hardware.

Perfect, we are making progress! At least in understanding of what is broken. :joy:

After this, I spent some time reading both the hid-sony linux driver, the new hid-playstation driver and this jedi_tool.py trying to understand a bit more about which commands I can send to the DS4 and what information I can get about it.

Here is what I figured out.

HID protocol

The DS4 uses a similar protocol between Bluetooth and USB, it consists in three kinds of packets:

  • HID Input Report: DS4 -> Host
  • HID Output Report: Host -> DS4
  • HID Feature Report

Input reports are used by the DS4 to inform the host about the status of the buttons, IMUs & co.

Output reports are sent by the Host to the DS4 to change LEDs, make the motors rumble, etc..

Both of them are well documented online, either in the linux driver or in other projects like DS4Windows.

The Feature Reports are way more interesting, these are the ones used to obtain information or change something about the device.

The DS4 differentiate these reports into two categories. I use the names as they are reported in the jedi_tool.py: HID Get Report (Type ID 0x01) and HID Set Report (Type ID 0x09).

  • The Hid Get Report is a request to read some information.
  • The Hid Set Report is a request to change something.

Obviously, the fact that the GetReport should not change anything depends on the firmware implementation!

After the Type ID byte there is a byte identifying the Request ID. From both the hid-sony and hid-playstation linux kernel drivers I see these feature requests:

Type Req. Id Size Description
GET 0x02 36 Get Calibration Data
GET 0x05 40 Get Calibration Data via Bluetooth (same + 2 bytes of CRC)
GET 0x12 15 Get Pairing Info
GET 0x81 6 Get MAC Address <- hey that’s the one that clones do not implement!
GET 0xA3 48 Get Version Info

Well, interesting. These instead are the feature requests written in the jedi_tool.py:

Type Req. Id Description Payload
SET 0x08 Set Flash Read Pos 3 bytes: 0xff + 2 bytes for the offset
GET 0x11 Read from Flash 2 bytes for the payload
SET 0x13 Set BT Link Info host_mac_addr(6 bytes) + link_key(16 bytes)
SET 0xA0 Test arg0(1 byte) + arg1(1 byte) + arg2(1 byte)
SET 0xA1 Enable Bluetooth 1 byte: 1 or 0
SET 0xA2 Enable DFU Mode 1 byte: 1 or 0
GET 0xA3 Get Version Info 0x30 bytes for the payload

Woah, this is starting to be a really nice playground! :smile:

Playing with HID Requests

Okk now we have a lot of things to play with! :heart_eyes:

Let’s start with something easy: GET 0xA3 which explains how jedi_tool.py is able to obtain the firmware version.

It’s easy to add to jedi_tool a function to dump the output of a GET request as an hex-string:

def dump_req(rid, size):
    try:
        buf = hid_get_report(dev, rid, size)
        strs = ["%02x" % (int(i),) for i in buf]
        print("RID:0x%02x data: " % (rid, ) + ("".join(strs)))
    except:
        pass

And now we can try to read the raw data of the Version Info with GET 0xA3:

$ python3 jedi_tool.py

RID:0xa3 data: 5365702032312032303138000000000030343a35303a35310000000000000000000100b4010000000aa0102000a00200

$ python3 -c 'print(bytes.fromhex("5365702032312032303138000000000030343a35303a35310000000000000000000100b4010000000aa0102000a00200"))'
b'Sep 21 2018\x00\x00\x00\x00\x0004:50:51\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\xb4\x01\x00\x00\x00\n\xa0\x10 \x00\xa0\x02\x00'

Good, our code to dump GET requests seems working.

Get Calibration Data

Now we can try something more interesting, let’s see what happens if we call GET 0x02 (Calibration Data) on two DS4, a working one and the broken one.

Understanding which DS4 is which is left as an exercise to the reader :smile:.

DualShock4 number one:

RID:0x02 data: ffff0000050090226bdd812285dd982277dd1c021c02ab1f55e04320bddffb1f06e00500

DualShock4 number two:

RID:0x02 data: 000000000000000000000000000000000000000000000000000000000000000000000000

Pretty clear I would say :thumbsup:. For some reason, the broken one does not have any calibration data for us, as expected by the crash shown in the previous blog post.

Enable Bluetooth

In jedi_tool.py there is a function called bt_enable() which calls SET 0xa1. That’s it folks, I think we solved one issue! :smile:

I call that function connecting my broken DS4, the call is executed correctly aand..

Nope. Nothing seems to be changed on the broken DS4. :cry:

I’m coward and I don’t want to try it on a working DS4, maybe in future.

Dump the Flash

In jedi_tool.py there is a very interesting function called dump_flash_mirror() that combines SET 0x08 with GET 0x11 to dump the flash.

Here’s a simplified version of the code in jedi_tool:

def flash_mirror_read(offset): # Read a single word
    assert offset < 0x800, 'flash mirror offset out of bounds'
    hid_set_report(dev, 0x08, struct.pack('>BH', 0xff, offset))
    return hid_get_report(dev, 0x11, 2)

def dump_flash_mirror(path): # Read the whole flash
    print('dumping flash mirror to %s...' % (path))
    with open(path, 'wb') as f:
        for i in range(0, 0x800, 2):
            word = flash_mirror_read(i)
            f.write(word)
    print('done')

It seems to dump.. “something :confused:” into a file. Something that is big 0x800 bytes, too few for a ROM. Probably a secondary memory?

Anyway, let’s try to understand how it works. It uses SET 0x08 (with the first payload byte to 0xff) to set the read offset and then GET 0x11 to read these two bytes of flash.

Actually this code works amazingly and dumps 0x800 bytes from the controller! To understand what is this memory, it’s better to have two memory dumps.

One from the broken DS4, one from the working one and diff them using dhex. BTW let me know if you use better tools to do this kind of diffs!

In this picture you can see above the memory of the working DS4 and below the broken one. The highlighted bytes are the ones that differ between them: Diff between two flashes

The broken one is basically 99% of zeros.

For those who already reverse-engineered the DS4 memory, you can see that there is a strange 0x03 at line 1E6, well… the memory dump reported in this picture is not the original broken memory but a close reconstruction. :smile:

Analyze the memory dump

So at this point, we can start to search for known patterns in the memory dump of the good DS4. For example the Bluetooth MAC Address or the IMU calibration data.

And.. Yes! Can you spot them? :grin:

The calibration data is located at line 1E6, starting with ff ff. The MAC Address can be found at line 6F6 (28:51:65:11:ae:a4).

Both of them are all zeros in the broken DS4.

Conclusion

Probably the main issue with this DualShock 4 is that the flash has been wiped out for some reason.

At this point I’m genuinely curious about the story of this controller. What kind of behaviour can wipe out the configuration memory?

So I asked the person who gave me this gift to contact the previous owner and ask him the story of this controller and how it got broken in this way.

Sad to say that the only answer we got is: “A friend gave it to me because it was not working”, without any further explaination. Also, few minutes later he blocked us :joy: :thumbsup:.

This journey will continue in Part 3, where we will see how the DFU mode works and play a bit more with these requests.

Thank you for reading! :heart:

EDIT: Part 3 here

~~~

If you want to get in touch, drop me an email at ds4@the.al or ping me on IRC (the_al@freenode|libera|hackint) or Discord (the_al).