Wie schreibe ich Daten auf ein GIP-Gerät (Xbox Series Controller)?C#

Ein Treffpunkt für C#-Programmierer
Anonymous
 Wie schreibe ich Daten auf ein GIP-Gerät (Xbox Series Controller)?

Post by Anonymous »

Ich arbeite an einem C#-Programm, das ein Ausschaltpaket an meinen Xbox Series-Controller sendet, um ihn auszuschalten.
Im Jahr 2024 veröffentlichte Microsoft GIP-Dokumente und soweit ich weiß, funktionieren Xbox One und Series mit GIP.
Ich betreibe einen Win10-PC und verwende einen Xbox Series-Controller mit einem Microsoft Wireless-Dongle. Aus diesem Beitrag habe ich gelernt, dass ich zunächst ein Handle für die XBOXGIP-Schnittstelle erhalten muss:

Die Nutzung der GIP-Schnittstelle beginnt mit dem Erwerb eines Handles für die
Schnittstelle über einen Gerätepfad von \.\XboxGIP

Code: Select all

HANDLE hFile = CreateFileW(L"\\\\.\\XboxGIP", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
Dann habe ich den Controller mit ReadFile ausgelesen und konnte Daten empfangen, die beim Controller-Start auf den PC kommen. Ich habe drei verschiedene Nachrichten erhalten:
  • Eine Metadatennachricht:

Code: Select all

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
  • Eine Hallo-Nachricht:

Code: Select all

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
  • Und Gerätestatusmeldungen, die etwa alle 500 ms kamen:

Code: Select all

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
Jede Nachricht oben beginnt mit 7E ED 82 C6 8B DA 00 00, was eine eindeutige ID meines Geräts ist.
Ich konnte diese Nachrichten mithilfe der Dokumente und chatGPT entschlüsseln. Sie halten sich an die Dokumentation, aber ich kann nicht herausfinden, wie ich einen Befehl an den Controller zurücksenden kann. Dann habe ich versucht, mit WriteFile auf das Gerät zu schreiben. So sollte ein gesetzter Gerätestatus aussehen. Ich habe zwei Dinge ausprobiert:
  • Nur ein Paket mit dem Befehl senden:

Code: Select all

   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 diesem Fall erhielt ich eine Fehlermeldung, dass das Gerät nicht verbunden ist, obwohl ich Daten von ihm lese.
  • Ein Paket mit der Geräte-ID und demselben Befehlspaket wird gesendet:

Code: Select all

  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 diesem Fall erhalte ich eine Fehlermeldung, die besagt, dass der Parameter falsch ist, was darauf hindeutet, dass das Gerät zwar gefunden wurde, das von mir gesendete Paket ihn jedoch aus irgendeinem Grund nicht erfüllt. Das Ändern eines der ID-Bytes führt zu dem Fehler „Gerät ist nicht verbunden“.
Ich beherrsche C# keineswegs. Es ist nur ein Lieblingsprojekt, deshalb bitte ich um Hilfe beim Senden des Befehls an den Controller. Der folgende Code wurde mit Hilfe von chatGPT und meinen dürftigen Programmierkenntnissen in C# und Javascript geschrieben.
Wichtige Dinge:
  • Möglicherweise stimmt etwas mit der Sequenznummer. Ich habe versucht, verschiedene Bytes wie 0x01, 0x02, 0x03 und einige andere zufällige Bytes zu senden, aber es hat nichts geändert.
  • Wahrscheinlich sind die Informationen zur xboxgip-Schnittstelle veraltet und ich sollte direkt auf den Controller schreiben, aber wenn ich das versuche, erhalte ich die Fehlermeldung „Zugriff verweigert“. Ich habe nachgeschaut, welche Prozesse das Gerät gesperrt haben, konnte aber nicht herausfinden, wie ich dwm.exe so beenden kann, dass meine Desktop-GUI nicht kaputt geht und auch der Controller freigegeben wird. Ich weiß nicht, wie ich verhindern kann, dass der Controller Eingaben an das Betriebssystem sendet, um Menüs in Win10 zu steuern.
  • Außerdem habe ich diese Tabelle gefunden, kann sie aber nicht entschlüsseln. Hier sendet der Host einen Ausschaltbefehl, aber wenn ich das Gleiche tue, wird ein Fehler ausgelöst. Aber 0xD2 Byte sieht interessant aus. Ich verstehe nicht, wofür es ist.
    Abbildung 4-16: Downstream-Set-Gerätestatus-USB-Trace: Aus
    Image
  • Laut den Dokumenten sollten alle Geräte-IDs mit beginnen 0x00, 0x00, 0xFF, 0xFB. Aber hier ist meine Geräte-ID: 7E ED 82 C6 8B DA 00 00. Es gibt kein 0xFF, 0xFB.
Alle GIP-Geräte MÜSSEN eine eindeutige 64-Bit-Primärgeräte-ID haben, von der
die vier höchstwertigen Bytes 0x00, 0x00, 0xFF, 0xFB sind. Die
verbleibenden Bytes MÜSSEN Zufallszahlen sein, die beim Hochfahren des
GIP-Geräts ermittelt werden.

Hier ist der vollständige Code:

Code: Select all

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

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post