我有一支 Logitech MX Vertical 滑鼠,它經常在家用電腦、公司筆記型電腦和其他裝置之間切換使用。有一次,我盯著它看,心想:這東西有內建快閃記憶體。它一定有,否則它怎麼能在拔插之間記住 DPI 設定?那麼,如果我在裡面儲存一些東西呢?
這個想法是將滑鼠視為一個小型 USB 隨身碟。由於它在實體上會在電腦之間移動,所以理論上可以攜帶資料。
Logitech 滑鼠使用一種稱為 HID++ 的協定進行通訊,這是他們在標準 USB HID 之上建構的。Logitech 自己對此有部分文件記錄,開源社群也進行了部分逆向工程。我寫了一個 Rust 工具來枚舉滑鼠暴露的每一個功能。總共有 33 個。
HID++ 2.0 的運作方式如下:每個裝置都有一個功能表。每個項目將一個穩定的功能 ID 映射到一個裝置特定的索引。ID 在所有 Logitech 裝置之間是一致的。索引則因型號而異。所以你首先詢問裝置「功能 0x2201 在哪裡?」,它會告訴你「在這個滑鼠上的索引 0x12」。然後你就可以在所有後續呼叫中使用該索引。每次呼叫都是一個簡短的封包:報告 ID、裝置索引、功能索引、功能 ID,以及最多 3 個位元組的參數。回應以相同的格式返回。
大多數功能都是未記錄的。有些名稱像是 EnableHiddenFeatures 和 TemplateBytesNVS。後者聽起來正是我想要的:非揮發性儲存,索引為 0x1eb0。我在文件中找不到任何相關資訊,但我猜測它與巨集或按鈕範本的設定檔有關。不幸的是,滑鼠對任何操作都沒有回應,所以我無法得知。
我首先花了一些時間研究 0x1c00(PersistentRemappableAction)。它有六個槽位,讀寫旗標等等。結果發現 macOS 的 IOHIDManager 會封鎖你實際寫入所需的較長 HID++ 報告格式。作業系統直接捨棄了封包。沒有錯誤,沒有解釋,什麼都沒有。在寫了一堆探測程式碼並盯著空回應看了比我願意承認的時間還長之後,我才發現這一點。是的,有繞過 IOHIDManager 的方法,例如直接透過 IOKit 與 USB 裝置通訊,但那是另一個故事了。
然後我看了裝置名稱暫存器。它看起來很有希望,寫入呼叫也被接受了。但滑鼠始終顯示「MX Vertical」,無論我發送什麼。它只是在敷衍我。
有效的是 DPI 暫存器 (0x2201)。
設定 DPI 透過功能 0x03 進行,讀取則透過 0x02。
這是重要(且已更新)的部分:
雖然 DPI 暫存器接受任意的 u16 值,但它們不會在斷電後持續存在。
然而,有一個有趣的特性:
只要滑鼠保持供電,該值就能在切換電腦後保留。你可以在一個裝置上寫入一個值,將接收器移到另一個裝置上,然後讀取回來。
2 個位元組的跨電腦、會話範圍的儲存(只要滑鼠保持供電)
所以最初的想法:「將 DPI 作為持久性儲存」實際上並不可行。唯一的解決方案是「快取」並在斷電後重新寫入滑鼠,但這就違背了這個想法。
我也測試了一系列其他功能和寫入路徑(包括 0x1e、0x1f,以及幾個 0x18xx 設定功能)。沒有一個影響了預設 DPI 或暴露了可寫入的持久性儲存。
此時看來,預設 DPI 要么是:
即使最初的目標沒有實現,這個過程仍然是值得的。
我學會了 HID++ 的運作方式。我學會了 macOS 如何在核心層級管理 HID 裝置以及它劃定的界線。我學會了「功能表」和「功能索引」是不同的東西,以及這個裝置上的 IFeatureSet 反向查找顯然是損壞的。我學會了裝置名稱暫存器會禮貌地接受你的寫入,然後完全忽略它們,這是一個非常令人感同身受的回應。
這些知識都不是從閱讀文件中得來的。Logitech hidpp20 文件確實存在,但一半的功能都缺失了,所以我必須親自嘗試。一切都是試驗和錯誤,直到成功為止。但或許不要對你的 BIOS 做這種事。
結果客觀上是無用的。沒有隱藏的儲存,滑鼠裡也沒有秘密的 USB 隨身碟。但這不是重點。重點是看看我能走多遠。
那 2 個位元組仍然在那裡。你只需要讓滑鼠保持電力……
我會繼續深入研究,因為預設 DPI 一定在我的滑鼠裡面的某個地方。
如果你想自己探索,程式碼在這裡:https://github.com/timwehrle/mouse-fs。
有想法、問題或發現錯誤嗎?隨時寄電子郵件給我。