Parler à la manette Stadia avec le WebHID

La manette Stadia flashée fonctionne comme une manette de jeu standard, ce qui signifie que tous ses boutons ne sont pas accessibles via l'API Gamepad. Avec WebHID, vous pouvez désormais accéder aux boutons manquants.

Depuis l'arrêt de Stadia, beaucoup craignaient que la manette ne devienne inutile à la décharge. Heureusement, l'équipe Stadia a décidé d'ouvrir à la place la manette Stadia en fournissant un micrologiciel personnalisé que vous pouvez flasher sur votre manette en accédant à la page du mode Bluetooth pour Stadia. Votre manette Stadia se présente ainsi comme une manette de jeu standard, que vous pouvez connecter à l'aide d'un câble USB ou sans fil via le Bluetooth. Présentée sur la page d'accueil de l'API Project Fugu, la page "Bluetooth" de Stadia utilise WebHID et WebUSB, mais ce n'est pas le sujet de cet article. Dans cet article, je vais vous expliquer comment vous pouvez communiquer avec la manette Stadia via WebHID.

La manette Stadia en tant que manette de jeu standard

Après le flash, la manette apparaît comme une manette de jeu standard pour le système d'exploitation. La capture d'écran suivante présente un bouton commun et la disposition des axes sur une manette de jeu standard. Comme défini dans la spécification de l'API Gamepad, les manettes de jeu standards comportent des boutons de 0 à 16, soit 17 au total (le pavé directionnel compte pour quatre boutons). Si vous testez la manette Stadia dans la démonstration du testeur de manette de jeu, vous remarquerez qu'elle fonctionne comme un bijou.

Schéma d'une manette de jeu standard avec les différents axes et boutons étiquetés.

Cependant, si vous comptez les boutons de la manette Stadia, il y en a 19. Si vous les essayez systématiquement un par un avec le testeur de manette de jeu, vous constaterez que les boutons Assistant et Capture ne fonctionnent pas. Même si l'attribut buttons de la manette de jeu, tel que défini dans les spécifications de la manette de jeu, est ouvert, étant donné que la manette Stadia apparaît comme une manette de jeu standard, seuls les boutons 0 à 16 sont mappés. Vous pouvez toujours utiliser les autres boutons, mais la plupart des jeux ne s'attendront pas à ce qu'ils existent.

WebHID à la rescousse

Grâce à l'API WebHID, vous pouvez communiquer avec les boutons manquants 17 et 18. Si vous le souhaitez, vous pouvez même obtenir des données sur tous les autres boutons et axes déjà disponibles via l'API Gamepad. La première étape consiste à découvrir comment la manette Stadia communique automatiquement au système d'exploitation. Pour ce faire, vous pouvez, par exemple, ouvrir la console des outils pour les développeurs Chrome sur une page aléatoire et demander une liste non filtrée d'appareils à partir de l'API WebHID. Vous choisissez ensuite manuellement la manette Stadia pour un examen plus approfondi. Obtenez une liste non filtrée d'appareils en transmettant simplement un tableau d'options filters vide.

const [device] = await navigator.hid.requestDevice({filters: []});

Dans le sélecteur, l'avant-dernière entrée ressemble à la manette Stadia.

Sélecteur d'appareil de l'API WebHID affichant des appareils sans rapport et la manette Stadia en avant-dernière position.

Après avoir sélectionné l'appareil "Manette Stadia version A", enregistrez l'objet HIDDevice obtenu dans la console. Cela permet d'afficher le productId de la manette Stadia (37888, qui est 0x9400 en hexadécimal) et vendorId (6353, qui est 0x18d1 en hexadécimal). Si vous recherchez vendorID dans le tableau officiel des ID de fournisseur USB, vous constaterez que 6353 correspond à ce que vous attendez: Google Inc..

Console des outils pour les développeurs Chrome affichant le résultat de la journalisation de l'objet HIDDevice.

Au lieu de suivre la procédure décrite ci-dessus, vous pouvez accéder à chrome://device-log/ dans la barre d'adresse, appuyer sur le bouton Clear (Effacer), brancher votre manette Stadia, puis appuyer sur Refresh (Actualiser). Vous obtenez ainsi les mêmes informations.

Interface de débogage chrome://device-log affichant des informations sur la manette Stadia branchée

Vous avez également la possibilité d'utiliser l'outil HID Explorer, qui vous permet d'explorer encore plus de détails sur les périphériques HID connectés à votre ordinateur.

Utilisez ces deux ID, vendorId et productId, pour affiner les informations affichées dans le sélecteur en filtrant correctement l'appareil WebHID approprié.

const [stadiaController] = await navigator.hid.requestDevice({filters: [{
  vendorId: 6353,
  productId: 37888,
}]});

Le bruit provenant de tous les appareils sans aucun lien a disparu, et seule la manette Stadia s'affiche.

Sélecteur d'appareil de l'API WebHID affichant uniquement la manette Stadia.

Ensuite, ouvrez HIDDevice en appelant la méthode open().

await stadiaController.open();

Consignez à nouveau HIDDevice, et l'option opened est définie sur true.

Console des outils pour les développeurs Chrome affichant le résultat de la journalisation de l'objet HIDDevice après l'ouverture

Lorsque l'appareil est ouvert, écoutez les événements inputreport entrants en rattachant un écouteur d'événements.

stadiaController.addEventListener('inputreport', (e) => {
  console.log(e);
});

Lorsque vous appuyez de manière prolongée sur le bouton Assistant de la manette, deux événements sont enregistrés dans la console. Vous pouvez les considérer comme des événements "Bouton Assistant baissé" et "Bouton Assistant relevé". À l'exception de timeStamp, les deux événements semblent impossibles à distinguer à première vue.

Console des outils pour les développeurs Chrome affichant les objets HIDInputReportEvent en cours de journalisation.

La propriété reportId de l'interface HIDInputReportEvent renvoie le préfixe d'identification d'un octet pour ce rapport, ou 0 si l'interface HID n'utilise pas d'ID de rapport. Dans ce cas, il s'agit de 3. Le secret se trouve dans la propriété data, qui est représentée par un DataView de taille 10. Un DataView fournit une interface de bas niveau pour lire et écrire plusieurs types de nombres dans un ArrayBuffer binaire. La façon d'obtenir quelque chose de plus digeste à partir de cette représentation consiste à créer un Uint8Array à partir de ArrayBuffer, afin que vous puissiez voir les entiers individuels non signés de 8 bits.

const data = new Uint8Array(event.data.buffer);

Lorsque vous consignez à nouveau les données d'événement du rapport d'entrée, tout devient plus logique, et les événements "Bouton Assistant vers le bas" et "Bouton Assistant vers le haut" deviennent déchiffrables. Le premier entier (8 dans les deux événements) semble être lié aux pressions sur le bouton, et le second entier (2 et 0) semble être lié à l'appui ou non sur le bouton Assistant.

Console des outils pour les développeurs Chrome affichant les objets Uint8Array enregistrés pour chaque HIDInputReportEvent.

Appuyez sur le bouton Capture au lieu du bouton Assistant. Vous verrez que le second entier passe de 1 lorsque vous appuyez sur le bouton à 0 lorsqu'il est libéré. Cela vous permet d'écrire un « pilote » très simple qui vous permet d'utiliser les deux boutons manquants.

stadia.addEventListener('inputreport', (event) => {
  if (!e.reportId === 3) {
    return;
  }
  const data = new Uint8Array(event.data.buffer);
  if (data[0] === 8) {
    if (data[1] === 1) {
      hidButtons[1].classList.add('highlight');
    } else if (data[1] === 2) {
      hidButtons[0].classList.add('highlight');
    } else if (data[1] === 3) {
      hidButtons[0].classList.add('highlight');
      hidButtons[1].classList.add('highlight');
    } else {
      hidButtons[0].classList.remove('highlight');
      hidButtons[1].classList.remove('highlight');
    }
  }
});

Grâce à cette approche de rétro-ingénierie, vous pouvez, bouton par bouton et axe par axe, déterminer comment communiquer avec la manette Stadia via WebHID. Une fois que vous maîtriserez le processus, le reste est presque un travail de mappage mécanique d'entiers.

La seule chose qui manque à présent, c'est la fluidité de la connexion offerte par l'API Gamepad. Pour des raisons de sécurité, vous devez toujours utiliser l'outil de sélection initial une seule fois afin d'utiliser un appareil WebHID tel que la manette Stadia. Toutefois, pour les connexions ultérieures, vous pouvez vous reconnecter aux appareils connus. Pour ce faire, appelez la méthode getDevices().

let stadiaController;
const [device] = await navigator.hid.getDevices();
if (device && device.vendorId === 6353 && device.productId === 37888) {
  stadiaController = device;
}

Démonstration

Dans une démonstration que j'ai créée, vous pouvez voir la manette Stadia contrôlée conjointement par l'API Gamepad et l'API WebHID. Veillez à vérifier le code source, qui s'appuie sur les extraits de cet article. Par souci de simplicité, nous n'affichons que les boutons A, B, X et Y (contrôlés par l'API Gamepad), ainsi que les boutons Assistant et Capture (contrôlés par l'API WebHID). Sous l'image du contrôleur, vous pouvez voir les données WebHID brutes, ce qui vous permet d'avoir une idée de tous les boutons et axes du contrôleur.

Application de démonstration disponible à l'adresse https://stadia-controller-webhid-gamepad.glitch.me/ présentant les boutons A, B, X et Y contrôlés par l'API Gamepad, ainsi que les boutons Assistant et Capture contrôlés par l'API WebHID

Conclusions

Grâce au nouveau micrologiciel, la manette Stadia peut désormais être utilisée comme une manette de jeu standard avec 17 boutons, ce qui, dans la plupart des cas, est largement suffisant pour contrôler des jeux Web courants. Si, pour une raison quelconque, vous avez besoin des données des 19 boutons du contrôleur, WebHID vous permet d'accéder à des rapports d'entrée de bas niveau que vous pouvez déchiffrer en effectuant une rétro-ingénierie un par un. Si vous écrivez un pilote WebHID complet après avoir lu cet article, n'oubliez pas de me contacter et je me ferai un plaisir d'associer votre projet ici. Bonne utilisation du WebHIDing !

Remerciements

Cet article a été relu par François Beaufort.