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

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