Praten met de Stadia-controller met WebHID

De geflashte Stadia-controller gedraagt ​​zich als een standaard gamepad, wat betekent dat niet alle knoppen toegankelijk zijn via de Gamepad API. Met WebHID heeft u nu toegang tot de ontbrekende knoppen.

Sinds Stadia werd gesloten, vreesden velen dat de controller als een nutteloos stuk hardware op de vuilstort zou belanden. Gelukkig heeft het Stadia-team besloten om in plaats daarvan de Stadia-controller te openen door aangepaste firmware aan te bieden die je op je controller kunt flashen door naar de Stadia Bluetooth-moduspagina te gaan. Hierdoor verschijnt je Stadia-controller als een standaard gamepad waar je via een USB-kabel of draadloos via Bluetooth verbinding mee kunt maken. De Stadia Bluetooth-pagina, die met trots te zien is in de Project Fugu API Showcase , gebruikt WebHID en WebUSB , maar dit is niet het onderwerp van dit artikel. In dit bericht wil ik uitleggen hoe je via WebHID met de Stadia-controller kunt praten.

De Stadia-controller als standaard gamepad

Na het flashen verschijnt de controller als een standaard gamepad voor het besturingssysteem. Zie de volgende schermafbeelding voor een algemene opstelling van knoppen en assen op een standaard gamepad. Zoals gedefinieerd in de Gamepad API- specificatie, hebben standaard gamepads knoppen van 0 tot 16, dus 17 in totaal (de d-pad telt als vier knoppen). Als je de Stadia-controller op de gamepad-testdemo uitprobeert, zul je merken dat deze als een tierelier werkt.

Een schema van een standaard gamepad met de verschillende assen en knoppen gelabeld.

Als je echter de knoppen op de Stadia-controller meetelt, zijn het er 19. Als je ze systematisch één voor één probeert in de gamepad-tester, zul je merken dat de Assistant- en de Capture- knoppen niet werken. Zelfs als het kenmerk van de buttons zoals gedefinieerd in de Gamepad-specificatie een open einde heeft, worden alleen de knoppen 0–16 toegewezen, aangezien de Stadia-controller als een standaard gamepad verschijnt. Je kunt nog steeds de andere knoppen gebruiken, maar de meeste games verwachten niet dat ze bestaan.

WebHID schiet te hulp

Dankzij de WebHID API kun je praten met de ontbrekende knoppen 17 en 18. En als je echt wilt, kun je zelfs gegevens krijgen over alle andere knoppen en assen die al beschikbaar zijn via de Gamepad API. De eerste stap is uitzoeken hoe de Stadia-controller zichzelf rapporteert aan het besturingssysteem. Eén manier om dit te doen is door de Chrome DevTools Console op een willekeurige pagina te openen en een ongefilterde lijst met apparaten op te vragen via de WebHID API. Vervolgens kies je handmatig de Stadia-controller voor verdere inspectie. Krijg een ongefilterde lijst met apparaten door simpelweg een lege array met filters door te geven.

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

In de kiezer lijkt de voorlaatste vermelding op de Stadia-controller.

De WebHID API-apparaatkiezer toont enkele niet-gerelateerde apparaten en de Stadia-controller op de voorlaatste positie.

Nadat je het apparaat 'Stadia Controller rev. A' hebt geselecteerd, log je het resulterende HIDDevice object in de console. Dit onthult de productId van de Stadia-controller ( 37888 , wat 0x9400 is in hex) en vendorId ( 6353 , wat 0x18d1 is in hex). Als u de vendorID opzoekt in de officiële USB-vendor ID-tabel , ziet u dat 6353 overeenkomt met wat u zou verwachten: Google Inc.

Chrome DevTools Console toont de uitvoer van het loggen van het HIDDevice-object.

Een alternatief voor de hierboven beschreven procedure is navigeren naar chrome://device-log/ in de URL-balk, op de knop Wissen drukken, je Stadia-controller aansluiten en vervolgens op Vernieuwen drukken. Hierdoor krijgt u dezelfde informatie.

De chrome://device-log debug-interface met informatie over de aangesloten Stadia-controller.

Nog een ander alternatief is het gebruik van de HID Explorer- tool waarmee u nog meer details kunt verkennen van de HID-apparaten die op uw computer zijn aangesloten.

Gebruik deze twee ID's, de vendorId en de productId , om te verfijnen wat er in de kiezer wordt weergegeven door nu correct te filteren op het juiste WebHID-apparaat.

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

Nu is het geluid van alle niet-gerelateerde apparaten verdwenen en verschijnt alleen de Stadia-controller.

De WebHID API-apparaatkiezer toont alleen de Stadia-controller.

Open vervolgens het HIDDevice door de methode open() aan te roepen.

await stadiaController.open();

Log het HIDDevice opnieuw in en de opened vlag wordt ingesteld op true .

De Chrome DevTools-console toont de uitvoer van het loggen van het HIDDevice-object nadat het is geopend.

Terwijl het apparaat open is, luistert u naar binnenkomende inputreport gebeurtenissen door een gebeurtenislistener aan te sluiten.

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

Wanneer u de Assistent- knop op de controller indrukt en loslaat, worden er twee gebeurtenissen in de console geregistreerd. Je kunt ze beschouwen als de gebeurtenissen ' Assistent- knop omlaag' en ' Assistent -knop omhoog'. Afgezien van de timeStamp lijken de twee gebeurtenissen op het eerste gezicht niet van elkaar te onderscheiden.

De Chrome DevTools Console toont HIDInputReportEvent-objecten die worden geregistreerd.

De eigenschap reportId van de HIDInputReportEvent interface retourneert het identificatievoorvoegsel van één byte voor dit rapport, of 0 als de HID-interface geen rapport-ID's gebruikt. In dit geval is dat 3 . Het geheim zit in de data eigenschap , die wordt weergegeven als een DataView van grootte 10. Een DataView biedt een interface op laag niveau voor het lezen en schrijven van meerdere getaltypen in een binaire ArrayBuffer . De manier om iets beter verteerbaars uit deze representatie te halen, is door een Uint8Array te maken van de ArrayBuffer , zodat je de individuele 8-bit gehele getallen zonder teken kunt zien.

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

Wanneer u vervolgens de gebeurtenisgegevens van het invoerrapport opnieuw registreert, worden de zaken logischer en beginnen de gebeurtenissen " Assistent- knop omlaag" en " Assistent- knop omhoog" ontcijferbaar te worden. Het eerste gehele getal ( 8 in beide gevallen) lijkt verband te houden met het indrukken van knoppen, en het tweede gehele getal ( 2 en 0 ) lijkt verband te houden met het feit of de Assistent- knop wordt ingedrukt of niet.

De Chrome DevTools Console toont Uint8Array-objecten die worden geregistreerd voor elke HIDInputReportEvent.

Druk op de Capture- knop in plaats van op de Assistent- knop en je zult zien dat het tweede gehele getal verandert van 1 wanneer de knop wordt ingedrukt naar 0 wanneer deze wordt losgelaten. Hiermee kunt u een zeer eenvoudige "driver" schrijven waarmee u gebruik kunt maken van de ontbrekende twee knoppen.

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

Met behulp van een dergelijke reverse-engineering-aanpak kun je knop voor knop en as voor as uitzoeken hoe je met WebHID met de Stadia-controller kunt praten. Als je het eenmaal onder de knie hebt, is de rest bijna mechanisch werk voor het in kaart brengen van gehele getallen.

Het enige dat nu ontbreekt, is de soepele verbindingservaring die de Gamepad API je biedt. Hoewel je om veiligheidsredenen altijd één keer de initiële picker-ervaring moet doorlopen om met een WebHID-apparaat zoals de Stadia-controller te kunnen werken, kun je voor toekomstige verbindingen opnieuw verbinding maken met bekende apparaten. Doe dat door de methode getDevices() aan te roepen.

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

Demo

Je kunt de Stadia-controller zien die gezamenlijk wordt bestuurd door de Gamepad API en de WebHID API in een demo die ik heb gebouwd. Zorg ervoor dat u de broncode bekijkt, die voortbouwt op de fragmenten uit dit artikel. Voor de eenvoud geef ik alleen de knoppen A , B , X en Y weer (bestuurd door de Gamepad API), en de knoppen Assistant en Capture (bestuurd door de WebHID API). Onder de controllerafbeelding ziet u de onbewerkte WebHID-gegevens, zodat u een idee krijgt van alle knoppen en assen op de controller.

De demo-app op https://stadia-controller-webhid-gamepad.glitch.me/ toont de A-, B-, X- en Y-knoppen die worden bestuurd door de Gamepad API, en de Assistent- en de Capture-knoppen die worden bestuurd door de WebHID-API.

Conclusies

Dankzij de nieuwe firmware is de Stadia-controller nu bruikbaar als een standaard gamepad met 17 knoppen, wat in de meeste gevallen ruim voldoende is om gangbare webgames te besturen. Als u om welke reden dan ook gegevens nodig heeft van alle 19 knoppen op de controller, kunt u met WebHID toegang krijgen tot invoerrapporten op laag niveau die u kunt ontcijferen door ze een voor een te reverse-engineeren. Als u na het lezen van dit artikel een compleet WebHID-stuurprogramma schrijft, neem dan zeker contact met mij op, dan zal ik uw project hier graag linken. Veel plezier met WebHIDing!

Dankbetuigingen

Dit artikel werd beoordeeld door François Beaufort .