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

magrega 56 Reputation points
2025-07-29T16:28:07.47+00:00

I am working on a 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 win10 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


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:

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

  1. A Hello message:

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

  1. And device status messages which came every 500ms or so:

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 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:

   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.

  1. Sending a packet with the device id and the same command packet:

  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.
  • 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

4a8G8kyL

  • 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.

Here's the full code:


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}");

        }

    }

}

Developer technologies | C#
Developer technologies | C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
0 comments No comments
{count} votes

3 answers

Sort by: Most helpful
  1. Surya Amrutha Vaishnavi Lanka (INFOSYS LIMITED) 1,115 Reputation points Microsoft External Staff
    2025-11-04T09:33:33.4466667+00:00

    Thanks for reaching out!

    Here are some possible solutions

    1. Packet Structure: Ensure that the packet you’re trying to send is structured correctly according to the GIP documentation. The format you mentioned for sending the power off command seems correct, but you might want to verify against the latest documentation to check for any nuances.
    2. Sequence Number: The sequence number should typically be incremented for each command sent. If it’s always 0x01, this could be a potential issue. Try using different, incrementing values.
    3. Device ID: The initial bytes of your packet should typically include the unique device ID. Ensure that it is being correctly formatted and corresponds to the device you’re interacting with. The GIP docs you mentioned indicate that all IDs should start with specific bytes; verify that your device conforms to this structure.
    4. WriteFile Access Rights: If you're getting an "Access denied" error when trying to write directly to the controller, ensure that your program has proper permissions and that no other processes are holding the device open. This may involve checking the device manager for any drivers or services still using the controller.
    5. Consulting Control Messages: If you have access to any example control messages from the documentation, compare them to what you’re sending. Look specifically for the expected formats and lengths.
    6. Debugger/Logging: Consider implementing logging around your packet sending logic to see the exact bytes being sent and the responses (if any) being received back.
    7. Use of Additional APIs: For sending such commands, if GIP doesn't seem to work out, you may want to explore XInput APIs, which are also designed for Xbox controllers and might provide a simpler interface for sending commands.

  2. Deleted

    This answer has been deleted due to a violation of our Code of Conduct. The answer was manually reported or identified through automated detection before action was taken. Please refer to our Code of Conduct for more information.

    1 deleted comment

    Comments have been turned off. Learn more

  3. Deleted

    This answer has been deleted due to a violation of our Code of Conduct. The answer was manually reported or identified through automated detection before action was taken. Please refer to our Code of Conduct for more information.


    Comments have been turned off. Learn more

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.