透過 WebHID 與 Stadia 控制器對話

刷過韌體的 Stadia 控制器會像標準遊戲搖桿一樣運作,也就是說,並非所有按鈕都能透過 Gamepad API 存取。有了 WebHID,您現在可以存取缺少的按鈕。

自從 Stadia 關閉後,許多人擔心控制器會變成垃圾場上的無用硬體。幸運的是,Stadia 團隊決定開放 Stadia 控制器,提供自訂韌體,讓你前往 Stadia 藍牙模式頁面,在控制器上刷新韌體。這樣一來,Stadia 控制器就會顯示為標準遊戲搖桿,你可以透過 USB 傳輸線或藍牙無線連線。這個頁面在 Project Fugu API Showcase 中備受推崇,使用 WebHIDWebUSB,但這不是本文的主題。在本篇文章中,我想說明如何透過 WebHID 與 Stadia 控制器互動。

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

刷新後,控制器會在作業系統中顯示為標準遊戲控制器。請參閱下圖,瞭解標準遊戲控制器上常見的按鈕和軸配置。如Gamepad API 規範所定義,標準 Gamepad 的按鈕數量為 0 到 16 之間,因此總共 17 個 (方向鍵算為四個按鈕)。如果你在遊戲控制器測試器示範中試用 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 Controller rev. A」裝置後,請將產生的 HIDDevice 物件記錄到控制台。這會顯示 Stadia 控制器的 productId (37888,十六進制為 0x9400) 和 vendorId (6353,十六進制為 0x18d1)。在官方 USB 供應商 ID 表中查詢 vendorID,您會發現 6353 對應的值與預期的 Google Inc. 相同。

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

除了上述流程,您也可以前往網址列中的 chrome://device-log/,按下「Clear」按鈕,插入 Stadia 控制器,然後按下「Refresh」。這兩種方法會提供相同的資訊。

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

另一個替代方案是使用 HID Explorer 工具,進一步探索電腦上已連線的 HID 裝置。

使用這兩個 ID (vendorIdproductId),藉由正確篩選正確的 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 助理」按鈕按下和按鈕按下的事件。第一個整數 (兩個事件中的 8) 似乎與按下按鈕有關,而第二個整數 (20) 似乎與是否按下 Google 助理按鈕有關。

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

按下「Capture」按鈕,而不是「Google 助理」按鈕,您會發現第二個整數會在按下按鈕時切換為 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;
}

示範

您可以在我建立的示範中,看到 Stadia 控制器同時受到 Gamepad API 和 WebHID API 控制。請務必查看原始碼,這是根據本文的程式碼片段建立而成。為了簡化說明,我只會顯示 ABXY 按鈕 (由 Gamepad API 控制),以及 AssistantCapture 按鈕 (由 WebHID API 控制)。控制器圖片下方會顯示原始 WebHID 資料,讓您瞭解控制器上的所有按鈕和軸。

位於 https://stadia-controller-webhid-gamepad.glitch.me/ 的示範應用程式,顯示 A、B、X 和 Y 按鈕由 Gamepad API 控制,而 Google 助理和擷取按鈕則由 WebHID API 控制。

結論

有了新的韌體,Stadia 控制器現在可做為標準的 17 鍵遊戲搖桿使用,在大多數情況下,這足以控制常見的網路遊戲。無論出於何種原因,如果您需要控制器上所有 19 個按鈕的資料,WebHID 可讓您存取低階輸入報告,然後逐一反向工程解讀。如果您在閱讀本文後撰寫了完整的 WebHID 驅動程式,請務必與我聯絡,我很樂意將您的專案連結至此。祝您使用 WebHIDing 愉快!

特別銘謝

本文由 François Beaufort 審查。