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:
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:
2) A Hello message:
3) And device status messages which came every 500ms or so:
Every message above starts with
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:
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:
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:
Here's the full code:
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
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.
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.
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}");
}
}
}