Ejemplo n.º 1
0
async def create_hid_server(protocol_factory,
                            ctl_psm,
                            itr_psm,
                            capture_file=None):
    ctl_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET,
                             socket.BTPROTO_L2CAP)
    itr_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET,
                             socket.BTPROTO_L2CAP)

    # for some reason we need to restart bluetooth here, the Switch does not connect to the sockets if we don't...
    logger.info('Restarting bluetooth service...')
    await utils.run_system_command('systemctl restart bluetooth.service')
    await asyncio.sleep(1)

    ctl_sock.setblocking(False)
    itr_sock.setblocking(False)

    ctl_sock.bind((socket.BDADDR_ANY, ctl_psm))
    itr_sock.bind((socket.BDADDR_ANY, itr_psm))

    ctl_sock.listen(1)
    itr_sock.listen(1)

    protocol = protocol_factory()

    hid = HidDevice()
    # setting bluetooth adapter name and class to the device we wish to emulate
    await hid.set_name(protocol.controller.device_name())
    await hid.set_class()

    logger.info('Advertising the Bluetooth SDP record...')
    hid.register_sdp_record(PROFILE_PATH)
    hid.discoverable()

    loop = asyncio.get_event_loop()
    client_ctl, ctl_address = await loop.sock_accept(ctl_sock)
    logger.info(f'Accepted connection at psm {ctl_psm} from {ctl_address}')
    client_itr, itr_address = await loop.sock_accept(itr_sock)
    logger.info(f'Accepted connection at psm {itr_psm} from {itr_address}')
    assert ctl_address[0] == itr_address[0]

    transport = L2CAP_Transport(asyncio.get_event_loop(),
                                protocol,
                                client_itr,
                                50,
                                capture_file=capture_file)
    protocol.connection_made(transport)

    # send some empty input reports until the switch decides to reply
    future = asyncio.ensure_future(_send_empty_input_reports(transport))
    await protocol.wait_for_output_report()
    future.cancel()
    try:
        await future
    except asyncio.CancelledError:
        pass

    return transport, protocol
Ejemplo n.º 2
0
async def accept_bt():
    loop = asyncio.get_event_loop()

    ctl_srv = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET,
                            socket.BTPROTO_L2CAP)
    itr_srv = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET,
                            socket.BTPROTO_L2CAP)

    print('Waitng for the Switch... Please open the "Change Grip/Order" menu.')

    ctl_srv.setblocking(False)
    itr_srv.setblocking(False)

    ctl_srv.bind((socket.BDADDR_ANY, 17))
    itr_srv.bind((socket.BDADDR_ANY, 19))

    ctl_srv.listen(1)
    itr_srv.listen(1)

    emulated_hid = HidDevice()
    # setting bluetooth adapter name and class to the device we wish to emulate
    await emulated_hid.set_name('Joy-Con (R)')
    logger.info('Advertising the Bluetooth SDP record...')
    emulated_hid.register_sdp_record(PROFILE_PATH)
    #emulated_hid.powered(True)
    emulated_hid.discoverable(True)
    #emulated_hid.pairable(True)
    await emulated_hid.set_class()

    ctl, ctl_address = await loop.sock_accept(ctl_srv)
    print(f'Accepted connection at psm 17 from {ctl_address}')
    itr, itr_address = await loop.sock_accept(itr_srv)
    print(f'Accepted connection at psm 19 from {itr_address}')
    assert ctl_address[0] == itr_address[0]

    # stop advertising
    emulated_hid.discoverable(False)
    ctl_srv.close()
    itr_srv.close()

    return ctl, itr
Ejemplo n.º 3
0
async def create_hid_server(protocol_factory,
                            ctl_psm=17,
                            itr_psm=19,
                            device_id=None,
                            reconnect_bt_addr=None,
                            capture_file=None):
    """
    :param protocol_factory: Factory function returning a ControllerProtocol instance
    :param ctl_psm: hid control channel port
    :param itr_psm: hid interrupt channel port
    :param device_id: ID of the bluetooth adapter.
                      Integer matching the digit in the hci* notation (e.g. hci0, hci1, ...) or
                      Bluetooth mac address in string notation of the adapter (e.g. "FF:FF:FF:FF:FF:FF").
                      If None, choose any device.
                      Note: Selection of adapters may currently not work if the bluez "input" plugin is enabled.
    :param reconnect_bt_addr: The Bluetooth address of the console that was previously connected. Defaults to None.
                      If None, a new hid server will be started for the initial paring.
                      Otherwise, the function assumes an initial pairing with the console was already done
                      and reconnects to the provided Bluetooth address.
    :param capture_file: opened file to log incoming and outgoing messages
    :returns transport for input reports and protocol which handles incoming output reports
    """
    protocol = protocol_factory()

    if reconnect_bt_addr is None:
        ctl_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET,
                                 socket.BTPROTO_L2CAP)
        itr_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET,
                                 socket.BTPROTO_L2CAP)
        ctl_sock.setblocking(False)
        itr_sock.setblocking(False)
        ctl_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        itr_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        try:
            hid = HidDevice(device_id=device_id)

            ctl_sock.bind((hid.address, ctl_psm))
            itr_sock.bind((hid.address, itr_psm))
        except OSError as err:
            #logger.warning(err)
            # If the ports are already taken, this probably means that the bluez "input" plugin is enabled.
            #logger.warning('Fallback: Restarting bluetooth due to incompatibilities with the bluez "input" plugin. '
            #              'Disable the plugin to avoid issues. See https://github.com/mart1nro/joycontrol/issues/8.')
            # HACK: To circumvent incompatibilities with the bluetooth "input" plugin, we need to restart Bluetooth here.
            # The Switch does not connect to the sockets if we don't.
            # For more info see: https://github.com/mart1nro/joycontrol/issues/8
            #logger.info('Restarting bluetooth service...')
            await utils.run_system_command(
                'systemctl restart bluetooth.service')
            await asyncio.sleep(1)

            hid = HidDevice(device_id=device_id)

            ctl_sock.bind((socket.BDADDR_ANY, ctl_psm))
            itr_sock.bind((socket.BDADDR_ANY, itr_psm))

        ctl_sock.listen(1)
        itr_sock.listen(1)

        hid.powered(True)
        hid.pairable(True)

        # setting bluetooth adapter name and class to the device we wish to emulate
        await hid.set_name(protocol.controller.device_name())
        await hid.set_class()

        logger.info('Advertising the Bluetooth SDP record...')
        try:
            HidDevice.register_sdp_record(PROFILE_PATH)
        except dbus.exceptions.DBusException as dbus_err:
            # Already registered (If multiple controllers are being emulated and this method is called consecutive times)
            logger.debug(dbus_err)

        # start advertising
        hid.discoverable()

        logger.info(
            'Waiting for Switch to connect... Please open the "Change Grip/Order" menu.'
        )

        loop = asyncio.get_event_loop()
        client_ctl, ctl_address = await loop.sock_accept(ctl_sock)
        logger.info(f'Accepted connection at psm {ctl_psm} from {ctl_address}')
        client_itr, itr_address = await loop.sock_accept(itr_sock)
        logger.info(f'Accepted connection at psm {itr_psm} from {itr_address}')
        assert ctl_address[0] == itr_address[0]

        # stop advertising
        hid.discoverable(False)
        hid.pairable(False)

        print(" [+] Switch detected : " + str(ctl_address[0]))
        print(" [+] Updating configuration for SwitchX")
        configFile = open("config.ini", "w")
        configFile.write("SwitchBTADDR=" + str(ctl_address[0]) + "\n")
        configFile.close()

    else:
        # Reconnection to reconnect_bt_addr
        client_ctl = ""
        client_itr = ""
        print(" [-] Waiting for console to be available.")
        # Added logic to keep retrying until connected to console - SwitchX
        connected = False
        while not connected:
            try:
                client_ctl = socket.socket(socket.AF_BLUETOOTH,
                                           socket.SOCK_SEQPACKET,
                                           socket.BTPROTO_L2CAP)
                client_itr = socket.socket(socket.AF_BLUETOOTH,
                                           socket.SOCK_SEQPACKET,
                                           socket.BTPROTO_L2CAP)
                client_ctl.connect((reconnect_bt_addr, ctl_psm))
                client_itr.connect((reconnect_bt_addr, itr_psm))
                connected = True
            except Exception as e:
                #print(traceback.format_exc())
                sleep(0.1)

        client_ctl.setblocking(False)
        client_itr.setblocking(False)

    # create transport for the established connection and activate the HID protocol
    transport = L2CAP_Transport(asyncio.get_event_loop(),
                                protocol,
                                client_itr,
                                client_ctl,
                                50,
                                capture_file=capture_file)
    protocol.connection_made(transport)

    # HACK: send some empty input reports until the Switch decides to reply
    future = asyncio.ensure_future(_send_empty_input_reports(transport))
    await protocol.wait_for_output_report()
    """
    future.cancel()
    try:
        await future
    except asyncio.CancelledError:
        pass
    """

    return protocol.transport, protocol
Ejemplo n.º 4
0
import argparse
Ejemplo n.º 5
0
async def create_hid_server(protocol_factory, ctl_psm=17, itr_psm=19, device_id=None, capture_file=None):
    """
    :param protocol_factory: Factory function returning a ControllerProtocol instance
    :param ctl_psm: hid control channel port
    :param itr_psm: hid interrupt channel port
    :param device_id: ID of the bluetooth adapter.
                      Integer matching the digit in the hci* notation (e.g. hci0, hci1, ...) or
                      Bluetooth mac address in string notation of the adapter (e.g. "FF:FF:FF:FF:FF:FF").
                      If None, choose any device.
                      Note: Selection of adapters may currently not work if the bluez "input" plugin is enabled.
    :param capture_file: opened file to log incoming and outgoing messages
    :returns transport for input reports and protocol which handles incoming output reports
    """
    ctl_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
    itr_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
    ctl_sock.setblocking(False)
    itr_sock.setblocking(False)

    try:
        hid = HidDevice(device_id=device_id)

        ctl_sock.bind((hid.address, ctl_psm))
        itr_sock.bind((hid.address, itr_psm))
    except OSError as err:
        logger.warning(err)
        # If the ports are already taken, this probably means that the bluez "input" plugin is enabled.
        logger.warning('Fallback: Restarting bluetooth due to incompatibilities with the bluez "input" plugin. '
                       'Disable the plugin to avoid issues. See https://github.com/mart1nro/joycontrol/issues/8.')
        # HACK: To circumvent incompatibilities with the bluetooth "input" plugin, we need to restart Bluetooth here.
        # The Switch does not connect to the sockets if we don't.
        # For more info see: https://github.com/mart1nro/joycontrol/issues/8
        logger.info('Restarting bluetooth service...')
        await utils.run_system_command('systemctl restart bluetooth.service')
        await asyncio.sleep(1)

        hid = HidDevice(device_id=device_id)

        ctl_sock.bind((socket.BDADDR_ANY, ctl_psm))
        itr_sock.bind((socket.BDADDR_ANY, itr_psm))

    ctl_sock.listen(1)
    itr_sock.listen(1)

    protocol = protocol_factory()

    hid.powered(True)
    # setting bluetooth adapter name and class to the device we wish to emulate
    await hid.set_name(protocol.controller.device_name())
    await hid.set_class()

    logger.info('Advertising the Bluetooth SDP record...')
    try:
        HidDevice.register_sdp_record(PROFILE_PATH)
    except dbus.exceptions.DBusException as dbus_err:
        # Already registered (If multiple controllers are being emulated and this method is called consecutive times)
        logger.debug(dbus_err)

    # start advertising
    hid.discoverable()

    logger.info('Waiting for Switch to connect... Please open the "Change Grip/Order" menu.')

    loop = asyncio.get_event_loop()
    client_ctl, ctl_address = await loop.sock_accept(ctl_sock)
    logger.info(f'Accepted connection at psm {ctl_psm} from {ctl_address}')
    client_itr, itr_address = await loop.sock_accept(itr_sock)
    logger.info(f'Accepted connection at psm {itr_psm} from {itr_address}')
    assert ctl_address[0] == itr_address[0]

    # stop advertising
    hid.discoverable(False)

    transport = L2CAP_Transport(asyncio.get_event_loop(), protocol, client_itr, 50, capture_file=capture_file)
    transport.start_reading()
    protocol.connection_made(transport)

    # send some empty input reports until the Switch decides to reply
    future = asyncio.ensure_future(_send_empty_input_reports(transport))
    await protocol.wait_for_output_report()
    future.cancel()
    try:
        await future
    except asyncio.CancelledError:
        pass

    return transport, protocol
Ejemplo n.º 6
0
async def _main(capture_file=None, reconnect_bt_addr=None):
    loop = asyncio.get_event_loop()

    if reconnect_bt_addr == None:
        # Creating l2cap sockets, we have to do this before restarting bluetooth
        ctl_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
        itr_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)

        # HACK: To circumvent incompatibilities with the bluetooth "input" plugin, we need to restart Bluetooth here.
        # The Switch does not connect to the sockets if we don't.
        # For more info see: https://github.com/mart1nro/joycontrol/issues/8
        logger.info('Restarting bluetooth service...')
        await utils.run_system_command('systemctl restart bluetooth.service')
        await asyncio.sleep(1)

        controller = await get_hid_controller()

        logger.info('Connecting with the Switch... Please open the "Change Grip/Order" menu.')

        ctl_sock.setblocking(False)
        itr_sock.setblocking(False)

        ctl_sock.bind((socket.BDADDR_ANY, 17))
        itr_sock.bind((socket.BDADDR_ANY, 19))

        ctl_sock.listen(1)
        itr_sock.listen(1)

        emulated_hid = HidDevice()
        # setting bluetooth adapter name and class to the device we wish to emulate
        await emulated_hid.set_name(controller['product_string'])
        await emulated_hid.set_class()

        logger.info('Advertising the Bluetooth SDP record...')

        emulated_hid.register_sdp_record(PROFILE_PATH)
        #emulated_hid.powered(True)
        emulated_hid.discoverable(True)
        #emulated_hid.pairable(True)

        client_ctl, ctl_address = await loop.sock_accept(ctl_sock)
        logger.info(f'Accepted connection at psm 17 from {ctl_address}')
        client_itr, itr_address = await loop.sock_accept(itr_sock)
        logger.info(f'Accepted connection at psm 19 from {itr_address}')
        assert ctl_address[0] == itr_address[0]

        # stop advertising
        emulated_hid.discoverable(False)
    else:
        controller = await get_hid_controller()

        client_ctl = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
        client_itr = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)

        client_ctl.connect((reconnect_bt_addr, 17))
        logger.info(f'Reconnected at psm 17 to switch {reconnect_bt_addr}')
        client_itr.connect((reconnect_bt_addr, 19))
        logger.info(f'Reconnected at psm 19 to switch {reconnect_bt_addr}')

        client_ctl.setblocking(False)
        client_itr.setblocking(False)

    relay = Relay(capture_file)

    logger.info('Relaying starting...')

    try:
        with AsyncHID(path=controller['path'], loop=loop) as hid_controller:
            await asyncio.gather(
                asyncio.ensure_future(relay.relay_input(hid_controller, client_itr)),
                asyncio.ensure_future(relay.relay_output(hid_controller, client_itr)),
            )
    finally:
        logger.info('Stopping communication...')
        client_itr.close()
        client_ctl.close()
Ejemplo n.º 7
0
async def create_hid_server(protocol_factory, ctl_psm=17, itr_psm=19, device_id=None, reconnect_bt_addr=None,
                            capture_file=None, interactive=False):
    """
    :param protocol_factory: Factory function returning a ControllerProtocol instance
    :param ctl_psm: hid control channel port
    :param itr_psm: hid interrupt channel port
    :param device_id: ID of the bluetooth adapter.
                      Integer matching the digit in the hci* notation (e.g. hci0, hci1, ...) or
                      Bluetooth mac address in string notation of the adapter (e.g. "FF:FF:FF:FF:FF:FF").
                      If None, choose any device.
                      Note: Selection of adapters may currently not work if the bluez "input" plugin is enabled.
    :param reconnect_bt_addr: The Bluetooth address of the console that was previously connected. Defaults to None.
                      If None, a new hid server will be started for the initial paring.
                      Otherwise, the function assumes an initial pairing with the console was already done
                      and reconnects to the provided Bluetooth address.
    :param capture_file: opened file to log incoming and outgoing messages
    :param interactive: whether or not questions to the user via input and print are allowed
    :returns transport for input reports and protocol which handles incoming output reports
    """
    protocol = protocol_factory()

    hid = HidDevice(device_id=device_id)

    bt_addr = hid.get_address()
    #if bt_addr[:8] != "94:58:CB":
    #    await hid.set_address("94:58:CB" + bt_addr[8:], interactive=interactive)
    #    bt_addr = hid.get_address()

    if reconnect_bt_addr is None:
        if interactive:
            if len(hid.get_UUIDs()) > 3:
                print("too many SPD-records active, Switch might refuse connection.")
                print("try modifieing /lib/systemd/system/bluetooth.service and see")
                print("https://github.com/Poohl/joycontrol/issues/4 if it doesn't work")
            for sw in hid.get_paired_switches():
                print(f"Warning: a switch ({sw}) was found paired, do you want to unpair it?")
                i = input("y/n [y]: ")
                if i == '' or i == 'y' or i == 'Y':
                    hid.unpair_path(sw)
        else:
            if len(hid.get_UUIDs()) > 3:
                logger.warning("detected too many SDP-records. Switch might refuse connection.")
            b = hid.get_paired_switches()
            if b:
                logger.warning(f"Attempting initial pairing, but switches are paired: {b}")

        ctl_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
        itr_sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
        ctl_sock.setblocking(False)
        itr_sock.setblocking(False)
        ctl_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        itr_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        try:
            ctl_sock.bind((bt_addr, ctl_psm))
            itr_sock.bind((bt_addr, itr_psm))
        except OSError as err:
            logger.warning(err)
            # If the ports are already taken, this probably means that the bluez "input" plugin is enabled.
            logger.warning('Fallback: Restarting bluetooth due to incompatibilities with the bluez "input" plugin. '
                           'Disable the plugin to avoid issues. See https://github.com/mart1nro/joycontrol/issues/8.')
            # HACK: To circumvent incompatibilities with the bluetooth "input" plugin, we need to restart Bluetooth here.
            # The Switch does not connect to the sockets if we don't.
            # For more info see: https://github.com/mart1nro/joycontrol/issues/8
            logger.info('Restarting bluetooth service...')
            await utils.run_system_command('systemctl restart bluetooth.service')
            await asyncio.sleep(1)
            hid = HidDevice(device_id=device_id)

            ctl_sock.bind((bt_addr, ctl_psm))
            itr_sock.bind((bt_addr, itr_psm))

        ctl_sock.listen(1)
        itr_sock.listen(1)

        hid.powered(True)
        hid.pairable(True)

        # setting bluetooth adapter name to the device we wish to emulate
        await hid.set_name(protocol.controller.device_name())

        logger.info('Advertising the Bluetooth SDP record...')
        try:
            HidDevice.register_sdp_record(PROFILE_PATH)
        except dbus.exceptions.DBusException as dbus_err:
            # Already registered (If multiple controllers are being emulated and this method is called consecutive times)
            logger.debug(dbus_err)

        # start advertising
        hid.discoverable()

        # set the device class to "Gamepad/joystick"
        await hid.set_class()

        logger.info('Waiting for Switch to connect... Please open the "Change Grip/Order" menu.')

        loop = asyncio.get_event_loop()
        client_ctl, ctl_address = await loop.sock_accept(ctl_sock)
        logger.info(f'Accepted connection at psm {ctl_psm} from {ctl_address}')
        client_itr, itr_address = await loop.sock_accept(itr_sock)
        logger.info(f'Accepted connection at psm {itr_psm} from {itr_address}')
        assert ctl_address[0] == itr_address[0]

        # stop advertising
        hid.discoverable(False)
        hid.pairable(False)

    else:
        if reconnect_bt_addr.lower() == 'auto':
            paths = hid.get_paired_switches()
            path = ""
            if not paths:
                logger.fatal("couldn't find paired switch to reconnect to, terminating...")
                exit(1)
            elif len(paths) > 1:
                if interactive:
                    print("found the following paired switches, please choose one:")
                    for i, p in paths.items():
                        print(f" {i}: {p}")
                    choice = input(f"number 1 - {len(paths)} [1]:")
                    if not choice:
                        path = paths[0]
                    else:
                        path = paths[int(choice)-1]
                else:
                    path = paths[0]
                    logger.warning(f"Automatic reconnect address chose {path} out of {paths}")
            else:
                path = paths[0]
                logger.info(f"auto detected paired switch {path}")
            reconnect_bt_addr = hid.get_address_of_paired_path(path)
        else:
            # Todo: figure out if we're actually paired
            pass
        # Reconnection to reconnect_bt_addr
        client_ctl = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
        client_itr = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
        client_ctl.connect((reconnect_bt_addr, ctl_psm))
        client_itr.connect((reconnect_bt_addr, itr_psm))
        client_ctl.setblocking(False)
        client_itr.setblocking(False)

    # I have spent 8 hours, one stackoverflow question and read pythons socket sourcecode
    # to find tis f*****g option somewhere in a GNUC API description. (here: https://www.gnu.org/software/libc/manual/html_node/Socket_002dLevel-Options.html)
    # F**K LINUX OPEN SOURCE. I'd rather have a DOCUMENTATION than the source of this garbage.
    client_ctl.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 0)
    client_itr.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 0)
    # create transport for the established connection and activate the HID protocol
    transport = L2CAP_Transport(asyncio.get_event_loop(), protocol, client_itr, client_ctl, 50, capture_file=capture_file)
    protocol.connection_made(transport)

    # HACK: send some empty input reports until the Switch decides to reply
    future = asyncio.ensure_future(_send_empty_input_reports(transport))
    await protocol.wait_for_output_report()
    """
    future.cancel()
    try:
        await future
    except asyncio.CancelledError:
        pass
    """

    return protocol.transport, protocol