透過 WebHID 與 Stadia 控制器對話

刷機後的 Stadia 控制器會像標準遊戲手把,因此並非所有按鈕都能透過 Gamepad API 存取。現在透過 WebHID,您就能存取缺少的按鈕。

自從 Stadia 關閉後,許多人擔心控制器會變成毫無用處的硬體,最終只能丟進垃圾掩埋場。幸好,Stadia 團隊決定開放使用 Stadia 控制器,並提供自訂韌體,只要前往 Stadia 藍牙模式頁面,即可將韌體刷入控制器。這樣一來,Stadia 控制器就會顯示為標準遊戲手把,你可以透過 USB 傳輸線連線,也可以透過藍牙以無線方式連線。Stadia 藍牙頁面榮登 Project Fugu API 展示頁面,本身使用 WebHIDWebUSB,但這不是本文的主題。在這篇文章中,我將說明如何透過 WebHID 與 Stadia 控制器通訊。

將 Stadia 控制器當做標準遊戲手把使用

韌體更新完成後,作業系統會將控制器視為標準遊戲手把。下圖顯示標準遊戲手把上常見的按鈕和軸配置。如 Gamepad API 規格所定義,標準遊戲手把有 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/,按下「清除」按鈕,插入 Stadia 控制器,然後按下「重新整理」。這兩者提供的資訊相同。

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();

再次記錄 HIDDevice,並將 opened 旗標設為 true

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

開啟裝置後,附加事件監聽器,監聽傳入的 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 中讀取及寫入多種數字類型。如要從這個表示法取得更易消化的內容,請建立 Uint8Array,這樣就能看到個別的 8 位元無正負號整數。ArrayBuffer

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

當您再次記錄輸入報告事件資料時,情況就會開始變得更有意義,「助理按鈕按下」和「助理按鈕放開」事件開始變得可解讀。第一個整數 (兩個事件中的 8) 似乎與按鈕按壓次數有關,第二個整數 (20) 則似乎與是否按下Google 助理按鈕有關。

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

按下「擷取」按鈕 (而非「Google 助理」按鈕),您會發現第二個整數會在按鈕按下時從 1 切換為 0,放開按鈕時則會切換回 1。這樣一來,您就能編寫非常簡單的「驅動程式」,使用缺少的兩個按鈕。

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 控制器。請務必查看原始碼,這些程式碼是以本文的程式碼片段為基礎建構而成。為求簡單,我只顯示 ABXY 按鈕 (由 Gamepad API 控制),以及「Google 助理」和「擷取」按鈕 (由 WebHID API 控制)。在控制器圖片下方,你可以看到原始 WebHID 資料,瞭解控制器上的所有按鈕和軸。

Stadia 控制器示範應用程式,顯示由 Gamepad API 控制的 A、B、X 和 Y 按鈕,以及由 WebHID API 控制的 Google 助理和擷取按鈕。

結論

更新韌體後,Stadia 控制器就能當做標準遊戲手把使用,共有 17 個按鈕,在大多數情況下,這已足夠控制常見的網頁遊戲。如果基於任何原因,您需要控制器上所有 19 個按鈕的資料,WebHID 可讓您存取低階輸入報表,並逐一進行反向工程,解讀這些報表。讀完本文後,如果您剛好撰寫了完整的 WebHID 驅動程式,請務必與我聯絡,我很樂意在此連結您的專案。祝您順利使用 WebHID!

特別銘謝

本文由 François Beaufort 審查。