透過 WebHID 與 Stadia 控制器對話

刷新的 Stadia 控制器就如同一般遊戲手把,因此並非所有按鈕都能透過 Gamepad API 存取。透過 WebHID,您現在可以存取缺少的按鈕。

隨著 Stadia 關機,許多控制器最終覺得,控制器會成為垃圾掩埋場的無用硬體。幸好,Stadia 團隊已決定改為開啟 Stadia 控制器,方法是提供自訂韌體。你可以在 Stadia 藍牙模式頁面中刷新裝置。讓 Stadia 控制器顯示為標準遊戲搖桿,你可以透過 USB 傳輸線或藍牙以無線方式連接。在 Project Fugu API 展示櫃中主打這項特色,Stadia 藍牙頁面本身使用 WebHIDWebUSB,但本文主題不完全提及。在這篇文章中,我想向你說明如何透過 WebHID 與 Stadia 控制器通訊。

將 Stadia 控制器當成標準遊戲搖桿

刷新後,控制器會以標準遊戲手把顯示在作業系統中。請參閱以下螢幕截圖,瞭解標準遊戲手把上的常用按鈕和軸排列方式。如 Gamepad API 規格定義,標準遊戲搖桿有 0 到 16 個按鈕,因此總共有 17 個按鈕 (D-Pad 計為四個按鈕)。如果您嘗試透過遊戲控制器測試版使用 Stadia 控制器,將會發現它的功能非常靈活。

標準遊戲手把的結構定義,有多個軸和按鈕加上標籤。

不過,如果你計算 Stadia 控制器上的按鈕,則會有 19 個。如果您在遊戲手把測試工具中有系統地逐一試用,就會發現「Google 助理」和「擷取」按鈕沒有作用。即使遊戲手把規格中定義的遊戲手把 buttons 屬性 (定義如下) 是開放式的,由於 Stadia 控制器會顯示為標準遊戲搖桿,只有 0 到 16 個按鈕才會對應。你還是可以使用其他按鈕,但大部分遊戲都不會預期出現這些按鈕。

利用 WebHID 進行救援

有了 WebHID API,您就可以對缺少的按鈕 17 和 18 說話。如果您真的需要,取得可透過 Gamepad API 取得的所有其他按鈕和軸的相關資料。首先,我們來看看 Stadia 控制器回報裝置給作業系統的方式。其中一種方法是隨機開啟 Chrome 開發人員工具控制台,並透過 WebHID API 要求取得未經篩選的裝置清單。接著手動選擇 Stadia 控制器,進一步檢查裝置。只要傳遞空白的 filters 選項陣列,即可取得未經篩選的裝置清單。

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

在挑選器中,最終項目看起來像是 Stadia 控制器。

WebHID API 裝置挑選器顯示了一些不相關的裝置,而 Stadia 控制器位於最終位置。

選取「Stadia 控制器 Rev. A」裝置後,請將產生的 HIDDevice 物件記錄到控制台。這項操作會顯示 Stadia 控制器的 productId (37888,是十六進位的 0x9400) 和 vendorId (6353,以十六進位為單位的 0x18d1)。在官方 USB 供應商 ID 表格中查看 vendorID 時,您會看到 6353 對應至預期內容:Google Inc.

Chrome 開發人員工具控制台顯示記錄 HIDDevice 物件的輸出內容。

除了上述流程之外,你也可以透過網址列前往「chrome://device-log/」、按下「清除」按鈕,將 Stadia 控制器接上電源,然後按下「重新整理」。如此一來,您也會得到相同的資訊。

chrome://device-log 偵錯介面顯示已連接的 Stadia 控制器相關資訊。

但另一種方法是使用 HID Explorer 工具,進一步探索與您電腦連線的 HID 裝置詳細資料。

使用 vendorIdproductId 這兩個 ID,即可正確篩選正確的 WebHID 裝置,修正挑選器中顯示的內容。

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

現在所有不相關裝置的噪音都消失了,只顯示 Stadia 控制器。

WebHID API 裝置挑選器只會顯示 Stadia 控制器。

接下來,呼叫 open() 方法來開啟 HIDDevice

await stadiaController.open();

再次記錄 HIDDeviceopened 旗標則設為 true

Chrome 開發人員工具控制台,顯示開啟 HIDDevice 物件後進行記錄的輸出內容。

開啟裝置後,附加事件監聽器來監聽傳入的 inputreport 事件。

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

當您按下控制器的「Google 助理」按鈕並放開時,控制台會記錄兩個事件。就像是「Google 助理」的向下按鈕和「Google 助理按鈕」事件。除了 timeStamp 以外,這兩個事件一開始看起來很容易區分。

Chrome 開發人員工具控制台顯示系統記錄的 HIDInputReportEvent 物件。

HIDInputReportEvent 介面的 reportId 屬性會傳回這份報表的一位元組識別前置字串;如果 HID 介面未使用報表 ID,則為 0。在本例中為 3。密鑰位於 data 屬性中,以 10 大小的 DataView 表示。DataView 提供低階介面,可讓您在二進位 ArrayBuffer 中讀取及寫入多種數字類型。要用這種表示法加以消化,方法是建立 ArrayBuffer 中的 Uint8Array,方便您查看個別 8 位元無正負號整數。

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

當您再次記錄輸入報表事件資料時,所做到的事會越來越合理,而「Google 助理」的向下按鈕和「Google 助理向上按鈕」事件也會開始變成可解讀的事件。第一個整數 (兩個事件中的 8) 似乎與按下按鈕動作有關,而第二個整數 (20) 似乎與是否按下「Google 助理」按鈕有關。

Chrome 開發人員工具控制台顯示每個 HIDInputReportEvent 記錄的 Uint8Array 物件。

按下「Capture」按鈕,而不是「Assistant」按鈕,當按鈕放開時,按下按鈕就會從 1 切換為 0。這可讓您編寫非常簡單的「驅動程式」,充分利用缺少的兩個按鈕。

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

你可以使用這種反向工程的技巧,使用按鈕和軸軸按鈕,就能瞭解如何使用 WebHID 與 Stadia 控制器通訊。瞭解如何順暢處理其他部分後,其餘部分幾乎是機械的整數對應工作。

目前最缺少的功能是 Gamepad API 為您提供流暢的連線體驗。基於安全考量,即使你一律必須進行一次初始挑選工具體驗,才能與 Stadia 控制器等 WebHID 裝置搭配使用,但之後可以重新連線至已知裝置。方法是呼叫 getDevices() 方法。

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

操作示範

你可以在我製作的示範中,查看由 Gamepad API 和 WebHID API 共同控管的 Stadia 控制器。請務必查看以本文程式碼片段為基礎所構成的原始碼。為了方便起見,我只顯示「A」、「B」、「X」和「Y」按鈕 (由 Gamepad API 控制),以及「Assistant」和「Capture」按鈕 (由 WebHID API 控制)。控制器圖片下方會顯示原始 WebHID 資料,方便您感受控制器的所有按鈕和軸。

位於 https://stadia-controller-webhid-gamepad.glitch.me/ 中的試用版應用程式,顯示了由 Gamepad API 控制的 A、B、X 和 Y 按鈕,以及由 WebHID API 控制的 Google 助理和「擷取」按鈕。

結論

透過新的韌體,Stadia 控制器現在可做為一般遊戲手把使用,而且在大多數情況下 (17 個按鈕) 就已足以控制一般網路遊戲。不論原因為何,如果您需要所有 19 個按鈕的資料,WebHID 都可讓您存取低階輸入報表,然後逐一進行反向工程,藉此加以解讀。如果您閱讀本文後,剛好編寫完整的 WebHID 驅動程式,請務必與我聯絡,我會將您的專案連結在一起。WebHIDing 快樂!

特別銘謝

本文是由 François Beaufort 審查。