232 lines
7 KiB
Python
Executable file
232 lines
7 KiB
Python
Executable file
#!/usr/bin/env python
|
|
# Compatible with python 3.11, NOT TESTED IN OLD VERSIONS OF PYTHON
|
|
|
|
from __future__ import print_function
|
|
import socket, struct, time
|
|
# EVDEV: https://python-evdev.readthedocs.io/en/latest/tutorial.html#specifying-uinput-device-options
|
|
import evdev
|
|
from evdev import UInput, AbsInfo, ecodes as e
|
|
|
|
##########################################################
|
|
# CONFIGURABLE REGION START - Don't touch anything above #
|
|
##########################################################
|
|
port = 42020
|
|
# Set the debug variable to 1 to print Debug information
|
|
debug = 1
|
|
|
|
# Valid values can be found in any of these locations on your Linux system (some may not exist):
|
|
# /usr/include/linux/input-event-codes.h
|
|
# The virtual device is defined on the device variable and the mapping of the keys on btn_map
|
|
# RECAP keynames (DO NOT TOUCH) are the keys that we expect commming from the 3ds, device is
|
|
# the virtual device that we define and btn_map maps what we recieve with our virtual device
|
|
btn_map = {
|
|
"A": e.BTN_A,
|
|
"B": e.BTN_B,
|
|
"X": e.BTN_X,
|
|
"Y": e.BTN_Y,
|
|
"L": e.BTN_TL,
|
|
"R": e.BTN_TR,
|
|
"ZL": e.BTN_THUMBL,
|
|
"ZR": e.BTN_THUMBR,
|
|
"Left": e.BTN_DPAD_LEFT,
|
|
"Right": e.BTN_DPAD_RIGHT,
|
|
"Up": e.BTN_DPAD_UP,
|
|
"Down": e.BTN_DPAD_DOWN,
|
|
"Start": e.BTN_START,
|
|
"Select": e.BTN_SELECT,
|
|
"Tap": e.BTN_MODE
|
|
}
|
|
|
|
device = {
|
|
# EV_ABS is for Absolute movement with static values,
|
|
# like the sticks and the volume
|
|
e.EV_ABS: [
|
|
(e.ABS_X, AbsInfo(0, -159, 159, 0, 10, 0)),
|
|
(e.ABS_Y, AbsInfo(0, -159, 159, 0, 10, 0)),
|
|
(e.ABS_RX, AbsInfo(0, -146, 146, 0, 16, 0)),
|
|
(e.ABS_RY, AbsInfo(0, -146, 146, 0, 16, 0)),
|
|
(e.ABS_VOLUME, AbsInfo(0, 0, 63, 0, 0, 0)),
|
|
],
|
|
# The keys lol
|
|
e.EV_KEY: [
|
|
e.BTN_A,
|
|
e.BTN_B,
|
|
e.BTN_X,
|
|
e.BTN_Y,
|
|
e.BTN_TL,
|
|
e.BTN_TR,
|
|
e.BTN_THUMBL,
|
|
e.BTN_THUMBR,
|
|
e.BTN_DPAD_LEFT,
|
|
e.BTN_DPAD_RIGHT,
|
|
e.BTN_DPAD_UP,
|
|
e.BTN_DPAD_DOWN,
|
|
e.BTN_START,
|
|
e.BTN_SELECT,
|
|
e.BTN_MODE,
|
|
],
|
|
}
|
|
|
|
ui = UInput(device, name="3DS", phys="3ds", vendor=0x1, version=0x1, product=0x1)
|
|
|
|
gyroAxis = {
|
|
e.EV_ABS: [
|
|
e.ABS_X,
|
|
e.ABS_Y,
|
|
e.ABS_Z,
|
|
e.ABS_RX,
|
|
e.ABS_RY,
|
|
e.ABS_RZ
|
|
]
|
|
}
|
|
|
|
uiGyro = UInput(gyroAxis, name="3DS Gyroscope", phys="3ds", vendor=0x1, version=0x1, product=0x2, input_props=[6])
|
|
|
|
if (debug):
|
|
print (ui)
|
|
|
|
########################################################
|
|
# CONFIGURABLE REGION END - Don't touch anything below #
|
|
########################################################
|
|
|
|
class x: pass
|
|
|
|
command = x()
|
|
command.CONNECT = 0
|
|
command.KEYS = 1
|
|
command.SCREENSHOT = 2
|
|
|
|
keynames = [
|
|
"A", "B", "Select", "Start", "Right", "Left", "Up", "Down",
|
|
"R", "L", "X", "Y", None, None, "ZL", "ZR",
|
|
None, None, None, None, "Tap", None, None, None,
|
|
"CSRight", "CSLeft", "CSUp", "CSDown", "CRight", "CLeft", "CUp", "CDown",
|
|
]
|
|
|
|
keys = x()
|
|
keys.A = 1<<0
|
|
keys.B = 1<<1
|
|
keys.Select = 1<<2
|
|
keys.Start = 1<<3
|
|
keys.Right = 1<<4
|
|
keys.Left = 1<<5
|
|
keys.Up = 1<<6
|
|
keys.Down = 1<<7
|
|
keys.R = 1<<8
|
|
keys.L = 1<<9
|
|
keys.X = 1<<10
|
|
keys.Y = 1<<11
|
|
keys.ZL = 1<<14 # (new 3DS only)
|
|
keys.ZR = 1<<15 # (new 3DS only)
|
|
keys.Tap = 1<<20 # Not actually provided by HID
|
|
keys.CSRight = 1<<24 # c-stick (new 3DS only)
|
|
keys.CSLeft = 1<<25 # c-stick (new 3DS only)
|
|
keys.CSUp = 1<<26 # c-stick (new 3DS only)
|
|
keys.CSDown = 1<<27 # c-stick (new 3DS only)
|
|
keys.CRight = 1<<28 # circle pad
|
|
keys.CLeft = 1<<29 # circle pad
|
|
keys.CUp = 1<<30 # circle pad
|
|
keys.CDown = 1<<31 # circle pad
|
|
|
|
def press_key(key):
|
|
ui.write(e.EV_KEY, key, 1)
|
|
ui.syn()
|
|
|
|
def release_key(key):
|
|
ui.write(e.EV_KEY,key, 0)
|
|
ui.syn()
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
sock.bind(("", port))
|
|
|
|
prevkeys = 0
|
|
|
|
touch_start = 0
|
|
touch_last_x = 0
|
|
touch_last_y = 0
|
|
|
|
while True:
|
|
rawdata, addr = sock.recvfrom(4092)
|
|
rawdata = bytearray(rawdata)
|
|
# print("Received message", rawdata)
|
|
|
|
if rawdata[0]==command.CONNECT:
|
|
print("Connected with 3DS at address", addr)
|
|
pass # CONNECT packets are empty
|
|
|
|
if rawdata[0]==command.KEYS:
|
|
# Just to explain this fuckery of struct.unpack:
|
|
# <bbxxIhhHHhhBxxxhhhxx:
|
|
# <: This represents that the data received is on Little-Endian
|
|
# bb: 2 signed chars which they are 1 byte each one, so 2 bytes in total, the `command` and `keyboardActive` data is here
|
|
# xx: Just 2 padding bytes, they are bytes without any data so with `xx` we skip them
|
|
# I: A signed integer of 4 bytes, the `keys` pressed data is here
|
|
# hh: 2 shorts which they are 2 bytes each one, 4 bytes on total, the X and Y data of the Circle Pad is here
|
|
# HH: 2 shorts which they are 2 bytes each one, 4 bytes on total, the X and Y data of the Touch Pad is here
|
|
# (where are you pressing in the screen, it could be useful somehow I guess.)
|
|
# hh: 2 shorts which they are 2 bytes each one, 4 bytes on total, the X and Y data of the C Stick Pad is here
|
|
# B: A unsigned char, just 1 byte and represents the value of the Volume Slider of the 3DS
|
|
# xxx: Other 3 extra bytes of padding
|
|
# hhh: 3 shorts which they are 2 bytes each one, 6 bytes on total, the X, Y, Z data of the GYROSCOPE is here
|
|
# xx: And another 2 extra bytes of padding
|
|
# hhh: 3 shorts which they are 2 bytes each one, 6 bytes on total, the X, Y, Z data of the ACCELEROMETER is here
|
|
# xxx: And another 2 extra bytes of padding from the fucking nowhere
|
|
fields = struct.unpack("<bbxxIhhHHhhBxxxhhhxxhhhxx", rawdata)
|
|
|
|
data = {
|
|
"command": fields[0],
|
|
"keyboardActive": fields[1],
|
|
"keys": fields[2],
|
|
"circleX": fields[3],
|
|
"circleY": fields[4],
|
|
"touchX": fields[5],
|
|
"touchY": fields[6],
|
|
"cstickX": fields[7],
|
|
"cstickY": fields[8],
|
|
"vol": fields[9],
|
|
"gyroX": fields[10],
|
|
"gyroY": fields[11],
|
|
"gyroZ": fields[12],
|
|
"accelX": fields[13],
|
|
"accelY": fields[14],
|
|
"accelZ": fields[15]
|
|
}
|
|
|
|
if (debug):
|
|
def pprint(obj):
|
|
import pprint
|
|
pprint.PrettyPrinter().pprint(obj)
|
|
pprint(data)
|
|
|
|
newkeys = data["keys"] & ~prevkeys
|
|
oldkeys = ~data["keys"] & prevkeys
|
|
prevkeys = data["keys"]
|
|
|
|
for btnid in range(16):
|
|
if newkeys & (1<<btnid):
|
|
press_key(btn_map[keynames[btnid]])
|
|
if oldkeys & (1<<btnid):
|
|
release_key(btn_map[keynames[btnid]])
|
|
if newkeys & keys.Tap:
|
|
press_key(btn_map["Tap"])
|
|
if oldkeys & keys.Tap:
|
|
release_key(btn_map["Tap"])
|
|
|
|
|
|
ui.write(e.EV_ABS, e.ABS_X, data["circleX"])
|
|
ui.write(e.EV_ABS, e.ABS_Y, 0-data["circleY"])
|
|
ui.write(e.EV_ABS, e.ABS_RX, data["cstickX"])
|
|
ui.write(e.EV_ABS, e.ABS_RY, data["cstickY"])
|
|
ui.write(e.EV_ABS, e.ABS_VOLUME, data["vol"])
|
|
ui.syn()
|
|
uiGyro.write(e.EV_ABS, e.ABS_RX, data["gyroX"])
|
|
uiGyro.write(e.EV_ABS, e.ABS_RY, data["gyroY"])
|
|
uiGyro.write(e.EV_ABS, e.ABS_RZ, data["gyroZ"])
|
|
uiGyro.write(e.EV_ABS, e.ABS_X, data["accelX"])
|
|
uiGyro.write(e.EV_ABS, e.ABS_Y, data["accelY"])
|
|
uiGyro.write(e.EV_ABS, e.ABS_Z, data["accelZ"])
|
|
uiGyro.syn()
|
|
|
|
|
|
if rawdata[0]==command.SCREENSHOT:
|
|
pass # unused by both 3DS and PC applications
|