Question How to write data to a GIP device (Xbox Series controller) ?

Aug 29, 2024
46
2
35
Hi

I am working on a C# program that would send a power off packet to my Xbox series controller in order to turn it off.

In 2024 Microsoft released GIP docs and as far as I understand Xbox One and Series work with GIP.

I run a Windows 10 PC and use an Xbox Series controller with a microsoft wireless dongle. From this post I learned that firstly I need to get a handle of XBOXGIP interface:

Usage of the GIP interface starts with acquiring a handle to the
interface via a device path of \\.\XboxGIP

C#:
HANDLE hFile = CreateFileW(L"\\\\.\\XboxGIP", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);

Then I read the controller with ReadFile and was able to receive data that comes to the PC on the controller start. I got 3 different messages which are:
1) A metadata message:
Code:
Received 315 bytes:
7E ED 82 C6 8B DA 00 00 04 20 00 00 27 01 00 00
00 00 00 00 5E 04 12 0B 10 00 01 00 00 00 00 00
00 00 00 00 00 00 23 01 CD 00 16 00 1B 00 1C 00
26 00 2F 00 4C 00 00 00 00 00 00 00 00 00 01 05
00 17 00 00 09 01 02 03 04 06 07 0C 0D 1E 08 01
04 05 06 0A 0C 0D 1E 01 1A 00 57 69 6E 64 6F 77
73 2E 58 62 6F 78 2E 49 6E 70 75 74 2E 47 61 6D
65 70 61 64 08 56 FF 76 97 FD 9B 81 45 AD 45 B6
45 BB A5 26 D6 2C 40 2E 08 DF 07 E1 45 A5 AB A3
12 7A F1 97 B5 E7 1F F3 B8 86 73 E9 40 A9 F8 2F
21 26 3A CF B7 FE D2 DD EC 87 D3 94 42 BD 96 1A
71 2E 3D C7 7D 6B E5 F2 87 BB C3 B1 49 82 65 FF
FF F3 77 99 EE 1E 9B AD 34 AD 36 B5 4F 8A C7 17
23 4C 9F 54 6F 77 CE 34 7A E2 7D C6 45 8C A4 00
42 C0 8B D9 4A C0 C8 96 EA 16 B2 8B 44 BE 80 7E
5D EB 06 98 E2 03 17 00 20 2C 00 01 00 10 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 17 00 09
3C 00 01 00 08 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 17 00 1E 40 00 01 00 22 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00

2) A Hello message:
Code:
Received 53 bytes:
7E ED 82 C6 8B DA 00 00 02 20 00 00 21 00 00 00
00 00 00 00 7E ED 82 C6 8B DA 00 00 5E 04 12 0B
05 00 17 00 06 00 00 00 08 04 01 00 01 00 01 00
00 00 00 00 00

3) And device status messages which came every 500ms or so:
Code:
Received 24 bytes:
7E ED 82 C6 8B DA 00 00 03 20 00 00 04 00 00 00
00 00 00 00 8B 00 00 58

Every message above starts with
Code:
7E ED 82 C6 8B DA 00 00
which is a unique ID of my device.

I was able to decode these messages with the help of the docs and chatGPT. They adhere to the docs but I can't figure out how to send a command back to the controller. Then I tried writing to the device with WriteFile. This is how a set device state should look like. I've tried two things:
1) Sending just a packet with the command:
C#:
   powerOffCommand[0] = 0x05; // Command ID
   powerOffCommand[1] = 0x20; // Flags
   powerOffCommand[2] = 0x01; // Sequence number
   powerOffCommand[3] = 0x01; // payload length
   powerOffCommand[4] = 0x04; // payload. Power Off command.

In this case I got an error saying that the device is not connected even though I am reading data from it.

2) Sending a packet with the device id and the same command packet:

C#:
 powerOffCommand[0] = 0x7E;
  powerOffCommand[1] = 0xED;
  powerOffCommand[2] = 0x82;
  powerOffCommand[3] = 0xC6;
  powerOffCommand[4] = 0x8B;
  powerOffCommand[5] = 0xDA;
  powerOffCommand[6] = 0x00;
  powerOffCommand[7] = 0x00;

  powerOffCommand[8] = 0x05; // Command ID
  powerOffCommand[9] = 0x20; // Flags
  powerOffCommand[10] = 0x01; // Sequence number
  powerOffCommand[11] = 0x01; // payload length
  powerOffCommand[12] = 0x04; // payload. Power Off command.
In this case I get an error saying that the parameter is incorrect which hints at the fact that it found the device but for some reason the packet I send doesn't satisfy it. Changing any of the ID bytes leads to Device is not connected error.

I am by no means proficient with C#. It is just a pet project that's why I am asking for help to send the command to the controller. The code below was written with the help of chatGPT and my meagre knowledge of programming in C# and Javascript.

Things to note:
  • There's might be something with the Sequence number. I tried sending different bytes like 0x01, 0x02, 0x03 and some other random but it didn't change anything.
  • Probably the info about xboxgip interface is outdated and I should write straight to the controller but when I try that I get an Access denied error. I looked up what proccesses locked the device but I couldn't figure out how to end `dwm.exe` in a way that wouldn't break my desktop GUI and also free the controller. I don't know how to stop the controller from sending input to the OS to control menus in Win10.
  • According to the docs all of the devices ids should start with 0x00, 0x00, 0xFF, 0xFB. But here's my device ID: `7E ED 82 C6 8B DA 00 00`. It doesn't have 0xFF, 0xFB.
All GIP devices MUST have a unique 64-bit Primary Device ID of which
the four most significant bytes are 0x00, 0x00, 0xFF, 0xFB. The
remaining bytes MUST be Random numbers, determined on bootup of the
GIP device.
  • Also I found this table but I can't decipher it. Here the host sends a power off command but when I do the same it throws.But `0xD2` byte looks interesting. I don't understand what it's for.
Figure 4-16: Downstream Set Device State USB Trace: Off

Here's the full code:
C#:
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

public class XboxGipController
{
    // Constants
    private const uint GENERIC_READ = 0x80000000;
    private const uint GENERIC_WRITE = 0x40000000;
    private const uint FILE_SHARE_READ = 0x00000001;
    private const uint FILE_SHARE_WRITE = 0x00000002;
    private const uint OPEN_EXISTING = 3;
    private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;

    // Define the specific IOCTL code
    private const uint GIP_ADD_REENUMERATE_CALLER_CONTEXT = 0x40001CD0;

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern SafeFileHandle CreateFileW(
        string lpFileName,
        uint dwDesiredAccess,
        uint dwShareMode,
        IntPtr lpSecurityAttributes,
        uint dwCreationDisposition,
        uint dwFlagsAndAttributes,
        IntPtr hTemplateFile
    );

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool DeviceIoControl(
        SafeFileHandle hDevice,
        uint dwIoControlCode,
        IntPtr lpInBuffer,
        uint nInBufferSize,
        IntPtr lpOutBuffer,
        uint nOutBufferSize,
        out uint lpBytesReturned,
        IntPtr lpOverlapped
    );

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool ReadFile(
        SafeFileHandle hFile,
        byte[] lpBuffer,
        uint nNumberOfBytesToRead,
        out uint lpNumberOfBytesRead,
        IntPtr lpOverlapped
    );

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool WriteFile(
        SafeFileHandle hFile,
        byte[] lpBuffer,
        uint nNumberOfBytesToWrite,
        out uint lpNumberOfBytesWritten,
        IntPtr lpOverlapped
    );

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hObject);

    private static void ProcessGipMessage(byte[] data, int length)
    {
        Console.WriteLine($"Received {length} bytes:");
        for (int i = 0; i < length; i++)
        {
            Console.Write($"{data[i]:X2} ");
            if ((i + 1) % 16 == 0)
                Console.WriteLine();
        }
        Console.WriteLine();
    }

    public static void ReenumerateGipControllers()
    {
        SafeFileHandle? hFile = null;

        try
        {
            // Open the GIP device interface
            hFile = CreateFileW(
                @"\\.\XboxGIP",
                GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                IntPtr.Zero,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                IntPtr.Zero
            );

            if (hFile.IsInvalid)
            {
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            }

            // Send the re-enumeration command
            uint bytesReturned;
            bool success = DeviceIoControl(
                hFile,
                GIP_ADD_REENUMERATE_CALLER_CONTEXT,
                IntPtr.Zero,
                0,
                IntPtr.Zero,
                0,
                out bytesReturned,
                IntPtr.Zero
            );

            if (!success)
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

            Console.WriteLine("GIP controller re-enumeration triggered successfully");

            byte[] powerOffCommand = new byte[64];
            // Xbox controller ID
            powerOffCommand[0] = 0x7E;
            powerOffCommand[1] = 0xED;
            powerOffCommand[2] = 0x82;
            powerOffCommand[3] = 0xC6;
            powerOffCommand[4] = 0x8B;
            powerOffCommand[5] = 0xDA;
            powerOffCommand[6] = 0x00;
            powerOffCommand[7] = 0x00;

            powerOffCommand[8] = 0x05; // Command ID
            powerOffCommand[9] = 0x20; // Flags
            powerOffCommand[10] = 0x01; // Sequence number
            powerOffCommand[11] = 0x01; // payload length
            powerOffCommand[12] = 0x04; // payload

            // Send the power off command
            // Comment the next 15 lines to skip writing and check how ReadFile works.
            // Otherwise the code will throw.
            uint bytesWritten;
            bool successWrite = WriteFile(
                hFile,
                powerOffCommand,
                (uint)powerOffCommand.Length,
                out bytesWritten,
                IntPtr.Zero
            );

            if (!successWrite)
            {
                ProcessGipMessage(powerOffCommand, powerOffCommand.Length);
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }

            byte[] buffer = new byte[1000];
            uint bytesRead;
            while (true)
            {
                bool successRead = ReadFile(
                    hFile, // Handle to the GIP device
                    buffer, // Buffer to receive data
                    (uint)buffer.Length, // Buffer size
                    out bytesRead, // Number of bytes actually read
                    IntPtr.Zero
                );

                if (!successRead)
                {
                    int error = Marshal.GetLastWin32Error();
                    if (error == 259)
                        break;

                    throw new Win32Exception(error);
                }

                if (bytesRead > 0)
                {
                    ProcessGipMessage(buffer, (int)bytesRead);
                }
                else
                {
                    System.Threading.Thread.Sleep(10);
                }
            }
        }
        finally
        {
            hFile?.Close();
        }
    }

    // Usage example
    public static void Main()
    {
        try
        {
            ReenumerateGipControllers();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}
 
Device ID "7E ED 82 C6 8B DA 00 00"

That appears to be a MAC.

However, a manufacturer search using that MAC did not identify a manufacturer.

The problem may be that you are allowed to read the device but prevented from writing to it.

Or at least "writes" that the manufacturer (?) has chosen to prohibit.

And write attempts simply return a generic or some non-specific response such as "not connected".

Do you have any specific references/links that support being able to write commands to the Xbox Series controller?

Consider that you are straying into proprietary or restricted/security related areas.

And as such, those attempts are being prohibited and blocked.

Just my thoughts on the matter.
 
Device ID "7E ED 82 C6 8B DA 00 00"
According to docs first 8 bytes are a device id.
And write attempts simply return a generic or some non-specific response such as "not connected".
I would have thought that too if it was the same error all the time, but it changes.
Do you have any specific references/links that support being able to write commands to the Xbox Series controller?
This is a fair point. GIP docs don't explicitly state that Xbox Controllers are supported. But they released two types of docs at the same time. xusb which is old and for xbox 360 and this new gip protocol which is for newer devices though it doesn't clearly state that I can read\write to\from an xbox device.

Consider that you are straying into proprietary or restricted/security related areas.
I do keep this in mind and I understand that it might be impossible to implement but since there's no concrete info on this I stay positive
Just my thoughts on the matter
Thanks for the input