You are not logged in.

#1 2014-10-07 06:13:18

tacchinotacchi
Member
Registered: 2014-08-18
Posts: 32

Reverse engineering Logitech G300 mouse driver

I sent an email to Logitech asking if they could link (or make ) me some docs about how the Logitech (Windows) driver talks with the mouse to configure the three profiles, wich are held into the mouse internal memory. I know they won't spend a dollar on linux, but better trying than not trying. I'm willing to reverse engineer the driver and replicate it on linux. If someone owns the mouse, windows and optionally knows some basicall c++, please reply to this topic if you want, you'd very helpful.
If I succeed, I'll update the post as I progress.

At the time of writing, I am starting from scratch: I captured the packets windows sends to the mouse in order to switch to the blue profile.

Last edited by tacchinotacchi (2014-10-07 12:50:01)

Offline

#2 2014-10-07 13:55:25

ooo
Member
Registered: 2013-04-10
Posts: 1,637

Re: Reverse engineering Logitech G300 mouse driver

tacchinotacchi wrote:

At the time of writing, I am starting from scratch: I captured the packets windows sends to the mouse in order to switch to the blue profile.

I believe this is how most linux drivers are developed unfortunately. Hopefully the guys at logitech are willing to help.

I don't own the mouse, or know c++, so I'm not much help here, but good luck with the task! :)

Offline

#3 2014-10-07 17:56:52

tolga9009
Member
From: Germany
Registered: 2010-01-08
Posts: 62

Re: Reverse engineering Logitech G300 mouse driver

Hi there,

I've also developed (I am actually still improving) USB drivers for Linux. In my particular example, I've worked on the Sidewinder X4 and X6 driver support. There are actually 2 questions to ask yourself: do your functions fit into kernel space, or will you go for a complete user-space program. If going for a kernel driver (which also involves writing a patch and send it in for a review, which then needs to be approved), you should take a look at Linux source code. You can find HID driver source codes under drivers/hid (http://lxr.free-electrons.com/source/drivers/hid/). I've also written a kernel-space driver, which you can find here (https://github.com/tolga9009/hid-sidewinder).

Together with other Sidewinder X4 users, we have discussed about a driver. You can find that discussion (which might be valuable for you) here: (http://ubuntuforums.org/showthread.php?t=1543370). We've done some major errors, which you can avoid from the beginning. Going for a kernel driver, involves you to setup a virtual machine, in order to debug your code. It saves alot of headache, when you don't have to panic your main machine, but can securely reboot your virtual machine in case of a kernel panic.

Due to the design of my kernel-space driver, which needed it's own ABI (using sysfs) to talk to the device, my commit got denied. So I decided to make a user-space program, which is now called sidewinderd (Sidewinder Daemon). You can find the source code here: (https://github.com/tolga9009/sidewinderd). You have much more freedom in a user-space program, than you have in kernel-space. You can use whichever libraries you want, you can use whatever programming language you prefer (eventhough C or C++ should be used; but theoretically, yes, it's possible). What you basically need, are the following libraries:

1. hidraw - this is a kernel-space library, which will help you to communicate with your device, without detaching it from the kernel. This has only benefits over libusb, as you don't need to detach your device from hid-generic driver and therefore don't need to re-write all the HID stuff. You can concentrate on the extra functionality, like listening to keypresses and setting LEDs and stuff. Just look at sidewinderd, how I solve that part - it's really easy.

2. epoll - this will help you listening to keypresses. I assume, your mouse supports some extra buttons, which don't function at the moment. As long as they send out HID events, you can listen to these events and take specific action in your driver. Epoll will put your main loop into a "sleep" state until something happens on your device node (lets say /dev/hidraw0), which will make your loop very power- and resource-efficient. That's very important, especially for mobile devices!

3. libudev - this will help you find the correct device, using VID and PID of your device. Attaching & detaching the device, or rebooting your computer might result in different device nodes. So, when you hardcode /dev/hidraw0 into your driver, this might be your keyboard on the next reboot, causing your program to crash (worst case). Again, take a look at sidewinderd. There is everything you need finding your device. Imho, the official documentation of libudev is not very beginner-friendly - you should still take a look, but most of the part, just read other programs and try to learn from them.

4. usbmon - eventhough this is not a library (it's actually a Linux module), it will greatly help you reverse engineering the Windows driver. This is how I reverse-engineer USB-related stuff: set up a Windows virtual machine using libvirt + QEMU + KVM, modprobe usbmon and use Wireshark to capture USB packets. When you write your driver and want to check, how "your" driver communicates with your device, you can use usbmon + Wireshark again. So, your workflow stays pretty much the same. I prefer using this workflow over native Windows + proprietary USB analyzing tool.

I think that's for the basics. This will help you setting up your device under Linux and communicating with it. In sidewinderd, I've also added the functionality to read config files (using libconfig++) and recording & playing XML macro files (using tinyxml2 as XML parser). But that's extra stuff, which is not needed recognizing keypresses or setting LEDs.

If you're in the need for a book about Linux programming in general, take a look at (http://www.amazon.de/The-Linux-Programm … 593272200/) by Michael Kerrisk. Eventhough some topics are out-dated by now, it's still an invaluable book for a Linux programmer! If you're new to C, I would recommend learning C, before you buy that book. "C Programming Language (2nd Edition) by Brian W. Kernighan" is pretty much the standard for learning C!

Hope this helped you!

Cheers,
Tolga

Last edited by tolga9009 (2014-10-07 18:04:18)

Offline

#4 2014-10-07 21:11:51

theodore
Member
Registered: 2008-09-09
Posts: 151

Re: Reverse engineering Logitech G300 mouse driver

tacchinotacchi wrote:

I sent an email to Logitech asking if they could link (or make ) me some docs about how the Logitech (Windows) driver talks with the mouse to configure the three profiles, wich are held into the mouse internal memory. I know they won't spend a dollar on linux, but better trying than not trying. I'm willing to reverse engineer the driver and replicate it on linux. If someone owns the mouse, windows and optionally knows some basicall c++, please reply to this topic if you want, you'd very helpful.
If I succeed, I'll update the post as I progress.

At the time of writing, I am starting from scratch: I captured the packets windows sends to the mouse in order to switch to the blue profile.


here is a good blog with another logitech's product reverse engineering trial which i discovered recently. I though that it could help you as well. Good luck with your effort.

Offline

#5 2014-10-07 22:09:03

tacchinotacchi
Member
Registered: 2014-08-18
Posts: 32

Re: Reverse engineering Logitech G300 mouse driver

I don't think I need a kernel space program. The extra mouse buttons already work perfectly as long as the mouse has been configured with windows. In fact, the extra buttons (wich are 4: 2 are supported by standard HID, 2 are extra) work this way: the logitech driver sets them to a keyboard key or combination, and  whenever the extra button is pressed that key combination is simulated. For example, if you set the 6th button as CTRL+C, select some text and click the 6th button, you will copy the text.
As already stated, I can already copy text using the mouse here on linux.
The problem comes when I want to modify my profiles from linux. Everything else works fine, but the only way to edit a profile is through the Logitech driver.
So, for me, detaching the kernel driver is ok as long as I can  re-attach it when I finished talking with the mouse
So far I can program C++ enough to make the driver but I don't know much about the usb protocol. I am learing libusb now.

Last edited by tacchinotacchi (2014-10-07 22:10:18)

Offline

#6 2014-10-08 03:20:29

tolga9009
Member
From: Germany
Registered: 2010-01-08
Posts: 62

Re: Reverse engineering Logitech G300 mouse driver

I see. I have the Logitech G700, which has the same function: once configured with the Windows tool, it does well under Linux. I will try to figure out, how it's working (I think it's pretty much programming the EEPROM via USB / HID) - maybe there are some similarities.

Offline

#7 2014-10-08 06:00:35

tacchinotacchi
Member
Registered: 2014-08-18
Posts: 32

Re: Reverse engineering Logitech G300 mouse driver

You may be unlucky here - from what I have seen, it does not communicate with HID - if that's the case, only the driver knows how to interpret the packets.

Offline

#8 2014-10-08 12:51:26

tacchinotacchi
Member
Registered: 2014-08-18
Posts: 32

Re: Reverse engineering Logitech G300 mouse driver

I could find two endpoints: 129 and 130.
Captured the packets needed to switch to blue.
I exported the packets I wanted to C array.

/* Frame (36 bytes) */
static const unsigned char pkt4[36] = {
0x1c, 0x00, 0x20, 0x28, 0x98, 0x31, 0x00, 0xe0, /* .. (.1.. */
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, /* ........ */
0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0x02, 0x08, /* ........ */
0x00, 0x00, 0x00, 0x00, 0x21, 0x09, 0xf0, 0x03, /* ....!... */
0x01, 0x00, 0x04, 0x00                          /* .... */
};



/* Frame (36 bytes) */
static const unsigned char pkt7[36] = {
0x1c, 0x00, 0x20, 0x38, 0x97, 0x31, 0x00, 0xe0, /* .. 8.1.. */
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, /* ........ */
0x00, 0x02, 0x00, 0x08, 0x00, 0x80, 0x02, 0x08, /* ........ */
0x00, 0x00, 0x00, 0x00, 0xa1, 0x01, 0xf1, 0x03, /* ........ */
0x01, 0x00, 0x02, 0x00                          /* .... */
};


/* Frame (36 bytes) */
static const unsigned char pkt10[36] = {
0x1c, 0x00, 0x20, 0x28, 0x99, 0x31, 0x00, 0xe0, /* .. (.1.. */
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, /* ........ */
0x00, 0x02, 0x00, 0x08, 0x00, 0x80, 0x02, 0x08, /* ........ */
0x00, 0x00, 0x00, 0x00, 0xa1, 0x01, 0xf0, 0x03, /* ........ */
0x01, 0x00, 0x04, 0x00                          /* .... */
};



/* Frame (36 bytes) */
static const unsigned char pkt13[36] = {
0x1c, 0x00, 0x20, 0xc8, 0x9d, 0x31, 0x00, 0xe0, /* .. ..1.. */
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, /* ........ */
0x00, 0x02, 0x00, 0x08, 0x00, 0x80, 0x02, 0x08, /* ........ */
0x00, 0x00, 0x00, 0x00, 0xa1, 0x01, 0xf0, 0x03, /* ........ */
0x01, 0x00, 0x04, 0x00                          /* .... */
};



/* Frame (36 bytes) */
static const unsigned char pkt16[36] = {
0x1c, 0x00, 0x20, 0x28, 0x98, 0x31, 0x00, 0xe0, /* .. (.1.. */
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, /* ........ */
0x00, 0x02, 0x00, 0x08, 0x00, 0x80, 0x02, 0x08, /* ........ */
0x00, 0x00, 0x00, 0x00, 0xa1, 0x01, 0xf0, 0x03, /* ........ */
0x01, 0x00, 0x04, 0x00                          /* .... */
};



/* Frame (36 bytes) */
static const unsigned char pkt19[36] = {
0x1c, 0x00, 0x20, 0x38, 0x97, 0x31, 0x00, 0xe0, /* .. 8.1.. */
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, /* ........ */
0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0x02, 0x08, /* ........ */
0x00, 0x00, 0x00, 0x00, 0x21, 0x09, 0xf0, 0x03, /* ....!... */
0x01, 0x00, 0x04, 0x00                          /* .... */
};

/* Frame (36 bytes) */
static const unsigned char pkt22[36] = {
0x1c, 0x00, 0x20, 0x28, 0x99, 0x31, 0x00, 0xe0, /* .. (.1.. */
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, /* ........ */
0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0x02, 0x08, /* ........ */
0x00, 0x00, 0x00, 0x00, 0x21, 0x09, 0xf0, 0x03, /* ....!... */
0x01, 0x00, 0x04, 0x00                          /* .... */
};


        r = libusb_bulk_transfer(dev_handle, (129 | LIBUSB_ENDPOINT_OUT), pkt4, 36, &actual, 0);
        r = libusb_bulk_transfer(dev_handle, (129 | LIBUSB_ENDPOINT_OUT), pkt7, 36, &actual, 0);
        r = libusb_bulk_transfer(dev_handle, (129 | LIBUSB_ENDPOINT_OUT), pkt10, 36, &actual, 0);
        r = libusb_bulk_transfer(dev_handle, (129 | LIBUSB_ENDPOINT_OUT), pkt13, 36, &actual, 0);
        r = libusb_bulk_transfer(dev_handle, (129 | LIBUSB_ENDPOINT_OUT), pkt16, 36, &actual, 0);
        r = libusb_bulk_transfer(dev_handle, (129 | LIBUSB_ENDPOINT_OUT), pkt19, 36, &actual, 0);
        r = libusb_bulk_transfer(dev_handle, (129 | LIBUSB_ENDPOINT_OUT), pkt22, 36, &actual, 0);

Yep, 7 packets just to switch profile.
LIBUSB_ENDPOINT_OUT does not change the outcome in any way.
Writing to 129, the first transfer never ends.
It doesn't say anything until I press certain buttons.

libusb: error [reap_for_handle] reap failed error -1 errno=14
libusb: error [handle_events] backend handle_events failed with error -1
libusb: error [sync_transfer_wait_for_completion] libusb_handle_events failed: LIBUSB_ERROR_IO, cancelling transfer and retrying

Those "certain buttons" are: right button, roll click, roll up, G5, G7, G8, and moving the cursor.
Writing to 130  I get error:

libusb: error [submit_bulk_transfer] submiturb failed error -1 errno=16
libusb: error [submit_bulk_transfer] submiturb failed error -1 errno=16
libusb: error [submit_bulk_transfer] submiturb failed error -1 errno=16
libusb: error [submit_bulk_transfer] submiturb failed error -1 errno=16
libusb: error [submit_bulk_transfer] submiturb failed error -1 errno=16
libusb: error [submit_bulk_transfer] submiturb failed error -1 errno=16
libusb: error [submit_bulk_transfer] submiturb failed error -1 errno=16
Write Error

"Write Error" is not part of libusb and was sent by me:

    if(r == 0 && actual == 36)
          {
            cout<<"Writing Successful!"<<endl;
            cout<<actual<<endl;
          }
    else
            cout<<"Write Error"<<endl;

Maybe I am writing to the wrong endpoints?
lsusb only shows 0x81 and 0x82 (129, 130)
Here the output of sudo lsusb:

$ lsusb  -v -d 046d:c246
Bus 003 Device 012: ID 046d:c246 Logitech, Inc. Gaming Mouse G300
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x046d Logitech, Inc.
  idProduct          0xc246 Gaming Mouse G300
  bcdDevice           70.02
  iManufacturer           1 Logitech
  iProduct                2 Gaming Mouse G300
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           59
    bNumInterfaces          2
    bConfigurationValue     1
    iConfiguration          4 U70.02_B0012
    bmAttributes         0xa0
      (Bus Powered)
      Remote Wakeup
    MaxPower              200mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      1 Boot Interface Subclass
      bInterfaceProtocol      2 Mouse
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.10
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      58
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0007  1x 7 bytes
        bInterval               1
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      1 Keyboard
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.10
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength     107
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0007  1x 7 bytes
        bInterval               1
Device Status:     0x0000
  (Bus Powered)
$ sudo lsusb -v -d 046d:c246  | grep End
      bNumEndpoints           1
      Endpoint Descriptor:
        bEndpointAddress     0x81  EP 1 IN
      bNumEndpoints           1
      Endpoint Descriptor:
        bEndpointAddress     0x82  EP 2 IN

EDIT: I just noticed the max packet size for both endpoints is 7 bytes, but the packets I captured with wireshark were 36 bytes long.
I still don't know much about the usb protocol, but shouldn't this be impossible?

Last edited by tacchinotacchi (2014-10-08 13:46:00)

Offline

#9 2014-12-06 07:48:43

strang3r
Member
Registered: 2014-12-06
Posts: 8

Re: Reverse engineering Logitech G300 mouse driver

Hello. I own the mouse and a laptop with Arch + Win7 installation.
So far I found some scripts for G500 and G9, played around with some basic c++ and ended up with nothing useful.
I am ready to help, just don't know where to start.

Just in case:

lsusb -d 046d:c246 -v

Bus 001 Device 003: ID 046d:c246 Logitech, Inc. Gaming Mouse G300
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x046d Logitech, Inc.
  idProduct          0xc246 Gaming Mouse G300
  bcdDevice           70.02
  iManufacturer           1 Logitech
  iProduct                2 Gaming Mouse G300
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           59
    bNumInterfaces          2
    bConfigurationValue     1
    iConfiguration          4 U70.02_B0012
    bmAttributes         0xa0
      (Bus Powered)
      Remote Wakeup
    MaxPower              200mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      1 Boot Interface Subclass
      bInterfaceProtocol      2 Mouse
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.10
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      58
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0007  1x 7 bytes
        bInterval               1
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 
      bInterfaceProtocol      1 Keyboard
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.10
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength     107
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0007  1x 7 bytes
        bInterval               1
can't get device qualifier: Resource temporarily unavailable
can't get debug descriptor: Resource temporarily unavailable
Device Status:     0x0000
  (Bus Powered)
xinput list | grep G300
    ↳ Logitech Gaming Mouse G300                id=11   [slave  pointer  (2)]
    ↳ Logitech Gaming Mouse G300                id=12   [slave  keyboard (3)]

Offline

#10 2014-12-07 19:28:32

tacchinotacchi
Member
Registered: 2014-08-18
Posts: 32

Re: Reverse engineering Logitech G300 mouse driver

strang3r wrote:

Hello. I own the mouse and a laptop with Arch + Win7 installation.
So far I found some scripts for G500 and G9, played around with some basic c++ and ended up with nothing useful.
I am ready to help, just don't know where to start.

Nor did I. I abandoned it after, using libusb, the mouse didn't respond to the same packets the software sent to it with the logitech software.
Not only it didn't respond, but the packet never completelly arrived in either ports (it had 2 usb connections internally).

But Today i was thinking of picking it up again. You don't need advanced c++ class stuff  ecc .., you just have to learn libusb.
We could work together, but I hardly see something coming out of this sad

Currently I am trying to install Logitech software on wine, cause this would save me A LOT of reboots
If this doesn't work I have to try virtual machine

Last edited by tacchinotacchi (2014-12-07 19:30:30)

Offline

#11 2014-12-08 14:08:50

strang3r
Member
Registered: 2014-12-06
Posts: 8

Re: Reverse engineering Logitech G300 mouse driver

It seems that wine won't be of much help:
https://appdb.winehq.org/objectManager. … &iId=27214
http://wiki.winehq.org/FAQ#head-8021e00 … 621b9d9366
I tried some versions of LGS and... the result was pretty predictable.
You can try it, though.
If you have trouble finding old logitech software, its on logitech's official ftp:
SetPoint at /pub/techsupport/mouse/
LGS at /pub/techsupport/gaming/
Meanwhile, I'll try digging into libusb.

Offline

#12 2014-12-09 19:27:31

scryan
Member
Registered: 2014-07-01
Posts: 50

Re: Reverse engineering Logitech G300 mouse driver

Not really a fix at all...But perhaps as a work around set it to some fairly obscure key combination in Windows, then create a linux utility to re-define that obscure key combo to what ever you want using xmodmap or similar?

I guess this may require 1 setup/boot to windows still, and if you frequently use windows/the official tool to remap the buttons you may have to remember to re-assign to your linux combo before leaving windows... but if linux is your primary OS you may at least be able to use the functionality of the mouse and change what actually happens when you press the button if you leave it set to a single value and allow linux to re-interpret that value differently, rather then trying to reverse engineer things and figure out some proprietary protocol/software.

Depending on your usage this man not be acceptable, but I guess its SOMETHING?

Offline

#13 2015-04-27 08:20:03

Clément
Member
Registered: 2015-04-27
Posts: 1

Re: Reverse engineering Logitech G300 mouse driver

I have done some reverse engineering on the G500 and I looking for information on other Logitech gaming mice. I find it strange that Logitech is not using its HID++ protocol here. Are you sure you captured the correct device?

About the length, if you are talking about the bLength in the endpoint descriptor, it is the length of the descriptor not the maximum packet size.

Offline

Board footer

Powered by FluxBB