Skip to content

Instantly share code, notes, and snippets.

@mzyy94
Last active June 21, 2024 16:48
Show Gist options
  • Save mzyy94/60ae253a45e2759451789a117c59acf9 to your computer and use it in GitHub Desktop.
Save mzyy94/60ae253a45e2759451789a117c59acf9 to your computer and use it in GitHub Desktop.
#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p procon
cd procon
echo 0x057e > idVendor
echo 0x2009 > idProduct
echo 0x0200 > bcdDevice
echo 0x0200 > bcdUSB
echo 0x00 > bDeviceClass
echo 0x00 > bDeviceSubClass
echo 0x00 > bDeviceProtocol
mkdir -p strings/0x409
echo "000000000001" > strings/0x409/serialnumber
echo "Nintendo Co., Ltd." > strings/0x409/manufacturer
echo "Pro Controller" > strings/0x409/product
mkdir -p configs/c.1/strings/0x409
echo "Nintendo Switch Pro Controller" > configs/c.1/strings/0x409/configuration
echo 500 > configs/c.1/MaxPower
echo 0xa0 > configs/c.1/bmAttributes
mkdir -p functions/hid.usb0
echo 0 > functions/hid.usb0/protocol
echo 0 > functions/hid.usb0/subclass
echo 64 > functions/hid.usb0/report_length
echo 050115000904A1018530050105091901290A150025017501950A5500650081020509190B290E150025017501950481027501950281030B01000100A1000B300001000B310001000B320001000B35000100150027FFFF0000751095048102C00B39000100150025073500463B0165147504950181020509190F2912150025017501950481027508953481030600FF852109017508953F8103858109027508953F8103850109037508953F9183851009047508953F9183858009057508953F9183858209067508953F9183C0 | xxd -r -ps > functions/hid.usb0/report_desc
ln -s functions/hid.usb0 configs/c.1/
ls /sys/class/udc > UDC
#!/usr/bin/env python3
import os
import threading
import time
# Re-connect USB Gadget device
os.system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
os.system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
time.sleep(0.5)
gadget = os.open('/dev/hidg0', os.O_RDWR | os.O_NONBLOCK)
procon = os.open('/dev/hidraw0', os.O_RDWR | os.O_NONBLOCK)
def procon_input():
while True:
try:
input_data = os.read(gadget, 128)
print('>>>', input_data.hex())
os.write(procon, input_data)
except BlockingIOError:
pass
except:
os._exit(1)
def procon_output():
while True:
try:
output_data = os.read(procon, 128)
print('<<<', output_data.hex())
os.write(gadget, output_data)
except BlockingIOError:
pass
except:
os._exit(1)
threading.Thread(target=procon_input).start()
threading.Thread(target=procon_output).start()
pi@raspberrypi:~ $ sudo python3 bypass_procon.py | grep -v '<<< 30'
>>> 0000
>>> 0000
>>> 8005
>>> 0000
>>> 8001
<<< 810100031f861dd60304000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 8002
<<< 81020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 01000000000000000000033000000000000000000000000000000000000000000000000000000000000000000000000000
<<< 21b191008000f3e77ae1e77e00800300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 8004
>>> 01080000000000000000480000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 01090000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000
<<< 21b691008000f2f77ae2f77e00804800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 010a0000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000
<<< 21e391008000f2e77ae2f77e008202034803020403d61d861f030100000000000000000000000000000000000000000000000000000000000000000000000000
>>> 010b0000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000
<<< 21e791008000f2f77ae2c77e00800800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 010c0000000000000000100060000010000000000000000000000000000000000000000000000000000000000000000000
<<< 21eb91008000f2e77ae3d77e0090100060000010ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
>>> 010d000000000000000010506000000d000000000000000000000000000000000000000000000000000000000000000000
<<< 21ef91008000f4f77ae4e77e009010506000000d323232ffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000
>>> 010e00000000000000000104185395d6030400043c4e696e74656e646f2053776974636800000000006800ac90113f5900
<<< 21f391008000f3f77ae1e77e00810103000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 010f0001404000014040033000000000000000000000000000000000000000000000000000000000000000000000000000
<<< 21f791008000eed77ae2d77e0c800300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 01000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000
<<< 21fc91008000f1e77ae2e77e0a830400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 01010000000000000000108060000018000000000000000000000000000000000000000000000000000000000000000000
<<< 210091008000f2e77ae1e77e099010806000001850fd0000c60f0f30619630f3d41454411554c7799c3336630000000000000000000000000000000000000000
>>> 01020000000000000000109860000012000000000000000000000000000000000000000000000000000000000000000000
<<< 210491008000f2e77ae2e77e0b901098600000120f30619630f3d41454411554c7799c3336630000000000000000000000000000000000000000000000000000
>>> 01030001404000014040101080000018000000000000000000000000000000000000000000000000000000000000000000
<<< 210991008000f4077be3c77e0990101080000018ffffffffffffffffffffffffffffffffffffffffffffb2a10000000000000000000000000000000000000000
>>> 01040000000000000000103d60000019000000000000000000000000000000000000000000000000000000000000000000
<<< 210d91008000f2f77ae3e77e0b90103d60000019ba156211b87f29065bffe77e0e36569e8560ff323232ffffff00000000000000000000000000000000000000
>>> 01050000000000000000102880000018000000000000000000000000000000000000000000000000000000000000000000
<<< 211191008000f3f77ae3d77e0990102880000018beff3e00f001004000400040fefffeff0800e73be73be73b0000000000000000000000000000000000000000
>>> 01060000000000000000400100000000000000000000000000000000000000000000000000000000000000000000000000
>>> 10070001404000014040
<<< 211891008000f4e77ae3c77e0c804000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 10080001404000014040
>>> 10090001404000014040
>>> 100a0001404000014040
>>> 100b0001404000014040
>>> 100c0001404000014040
>>> 010d0000000000000000480100000000000000000000000000000000000000000000000000000000000000000000000000
<<< 218291008000e40bbbe3d77e0c804800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 010ec218037200014040212100000000000000000000000000000000000000000000000000000000000000000000000000
>>> 100f0001404000000000
<<< 218691008000422cc2e0b77e00a0210100ff0003000501000000000000000000000000000000000000000000000000005c000000000000000000000000000000
>>> 01000000000000000000300100000000000000000000000000000000000000000000000000000000000000000000000000
<<< 218c9100800048fcc1e0d77e0a803000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>> 10010000000002786040
>>> 10020001404000014040
>>> 10030001404000014040
>>> 10040001404000014040
>>> 10050001404000014040
>>> 10060001404000014040
>>> 10070001404000014040
>>> 10080001404000014040
>>> 10090001404000014040
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x15, 0x00, // Logical Minimum (0)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
0x85, 0x30, // Report ID (48)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x0A, // Usage Maximum (0x0A)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x0A, // Report Count (10)
0x55, 0x00, // Unit Exponent (0)
0x65, 0x00, // Unit (None)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x09, // Usage Page (Button)
0x19, 0x0B, // Usage Minimum (0x0B)
0x29, 0x0E, // Usage Maximum (0x0E)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x04, // Report Count (4)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x01, // Report Size (1)
0x95, 0x02, // Report Count (2)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x0B, 0x01, 0x00, 0x01, 0x00, // Usage (0x010001)
0xA1, 0x00, // Collection (Physical)
0x0B, 0x30, 0x00, 0x01, 0x00, // Usage (0x010030)
0x0B, 0x31, 0x00, 0x01, 0x00, // Usage (0x010031)
0x0B, 0x32, 0x00, 0x01, 0x00, // Usage (0x010032)
0x0B, 0x35, 0x00, 0x01, 0x00, // Usage (0x010035)
0x15, 0x00, // Logical Minimum (0)
0x27, 0xFF, 0xFF, 0x00, 0x00, // Logical Maximum (65534)
0x75, 0x10, // Report Size (16)
0x95, 0x04, // Report Count (4)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0x0B, 0x39, 0x00, 0x01, 0x00, // Usage (0x010039)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x07, // Logical Maximum (7)
0x35, 0x00, // Physical Minimum (0)
0x46, 0x3B, 0x01, // Physical Maximum (315)
0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x09, // Usage Page (Button)
0x19, 0x0F, // Usage Minimum (0x0F)
0x29, 0x12, // Usage Maximum (0x12)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x04, // Report Count (4)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x08, // Report Size (8)
0x95, 0x34, // Report Count (52)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
0x85, 0x21, // Report ID (33)
0x09, 0x01, // Usage (0x01)
0x75, 0x08, // Report Size (8)
0x95, 0x3F, // Report Count (63)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x85, 0x81, // Report ID (-127)
0x09, 0x02, // Usage (0x02)
0x75, 0x08, // Report Size (8)
0x95, 0x3F, // Report Count (63)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x85, 0x01, // Report ID (1)
0x09, 0x03, // Usage (0x03)
0x75, 0x08, // Report Size (8)
0x95, 0x3F, // Report Count (63)
0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
0x85, 0x10, // Report ID (16)
0x09, 0x04, // Usage (0x04)
0x75, 0x08, // Report Size (8)
0x95, 0x3F, // Report Count (63)
0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
0x85, 0x80, // Report ID (-128)
0x09, 0x05, // Usage (0x05)
0x75, 0x08, // Report Size (8)
0x95, 0x3F, // Report Count (63)
0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
0x85, 0x82, // Report ID (-126)
0x09, 0x06, // Usage (0x06)
0x75, 0x08, // Report Size (8)
0x95, 0x3F, // Report Count (63)
0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
0xC0, // End Collection
// 203 bytes
#!/usr/bin/env python3
import os
import threading
import time
KEY1=21
KEY2=20
KEY3=16
LEFT=5
RIGHT=26
UP=6
DOWN=19
PRESS=13
KEYS = [KEY1,KEY2,KEY3,UP,DOWN,LEFT,RIGHT,PRESS]
export = os.open('/sys/class/gpio/export', os.O_WRONLY | os.O_NONBLOCK)
for gpio in KEYS:
if not os.path.exists('/sys/class/gpio/gpio%d' % gpio):
os.write(export, bytes(str(gpio), 'ascii'))
direction = os.open('/sys/class/gpio/gpio%d/direction' % gpio, os.O_WRONLY | os.O_NONBLOCK)
os.write(direction, bytes('high', 'ascii'))
os.close(direction)
os.close(export)
# Reset USB Gadget
os.system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
os.system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
time.sleep(0.5)
gadget = os.open('/dev/hidg0', os.O_RDWR | os.O_NONBLOCK)
counter = 0
mac_addr = '00005e00535e'
initial_input = '81008000f8d77a22c87b0c'
def countup():
global counter
while True:
counter = (counter + 3) % 256
time.sleep(0.03)
def response(code, cmd, data):
buf = bytearray([code, cmd])
buf.extend(data)
buf.extend(bytearray(64-len(buf)))
try:
os.write(gadget, buf)
except BlockingIOError:
pass
except:
os._exit(1)
def uart_response(code, subcmd, data):
buf = bytearray.fromhex(initial_input)
buf.extend([code, subcmd])
buf.extend(data)
response(0x21, counter, buf)
def spi_response(addr, data):
buf = bytearray(addr)
buf.extend([0x00, 0x00])
buf.append(len(data))
buf.extend(data)
uart_response(0x90, 0x10, buf)
def input_response():
while True:
buf = bytearray.fromhex(initial_input)
inputs = {}
for gpio in KEYS:
with open('/sys/class/gpio/gpio%d/value' % gpio, 'r') as f:
inputs[gpio] = f.read().startswith('0')
buf[1] = inputs[KEY1] << 3 | inputs[KEY2] << 2
buf[2] |= inputs[KEY3] << 4 | inputs[PRESS] << 1
buf[3] = inputs[LEFT] << 3 | inputs[RIGHT] << 2 | inputs[UP] << 1 | inputs[DOWN]
response(0x30, counter, buf)
time.sleep(0.03)
def simulate_procon():
while True:
try:
data = os.read(gadget, 128)
if data[0] == 0x80:
if data[1] == 0x01:
response(0x81, data[1], bytes.fromhex('0003' + mac_addr))
elif data[1] == 0x02:
response(0x81, data[1], [])
elif data[1] == 0x04:
threading.Thread(target=input_response).start()
else:
print('>>>', data.hex())
elif data[0] == 0x01 and len(data) > 16:
if data[10] == 0x01: # Bluetooth manual pairing
uart_response(0x81, data[10], [0x03])
elif data[10] == 0x02: # Request device info
uart_response(0x82, data[10], bytes.fromhex('03480302' + mac_addr[::-1] + '0301'))
elif data[10] == 0x03 or data[10] == 0x08 or data[10] == 0x30 or data[10] == 0x38 or data[10] == 0x40 or data[10] == 0x48:
uart_response(0x80, data[10], [])
elif data[10] == 0x04: # Trigger buttons elapsed time
uart_response(0x83, data[10], [])
elif data[10] == 0x21: # Set NFC/IR MCU configuration
uart_response(0xa0, data[10], bytes.fromhex('0100ff0003000501'))
elif data[10] == 0x10:
if data[11:13] == b'\x00\x60': # Serial number
spi_response(data[11:13], bytes.fromhex('ffffffffffffffffffffffffffffffff'))
elif data[11:13] == b'\x50\x60': # Controller Color
spi_response(data[11:13], bytes.fromhex('bc1142 75a928 ffffff ffffff ff')) # Raspberry Color
elif data[11:13] == b'\x80\x60': # Factory Sensor and Stick device parameters
spi_response(data[11:13], bytes.fromhex('50fd0000c60f0f30619630f3d41454411554c7799c333663'))
elif data[11:13] == b'\x98\x60': # Factory Stick device parameters 2
spi_response(data[11:13], bytes.fromhex('0f30619630f3d41454411554c7799c333663'))
elif data[11:13] == b'\x3d\x60': # Factory configuration & calibration 2
spi_response(data[11:13], bytes.fromhex('ba156211b87f29065bffe77e0e36569e8560ff323232ffffff'))
elif data[11:13] == b'\x10\x80': # User Analog sticks calibration
spi_response(data[11:13], bytes.fromhex('ffffffffffffffffffffffffffffffffffffffffffffb2a1'))
elif data[11:13] == b'\x28\x80': # User 6-Axis Motion Sensor calibration
spi_response(data[11:13], bytes.fromhex('beff3e00f001004000400040fefffeff0800e73be73be73b'))
else:
print("Unknown SPI address:", data[11:13].hex())
else:
print('>>> [UART]', data.hex())
elif data[0] == 0x10 and len(data) == 10:
pass
else:
print('>>>', data.hex())
except BlockingIOError:
pass
except:
os._exit(1)
threading.Thread(target=simulate_procon).start()
threading.Thread(target=countup).start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment