逆向工程 G300s巨集滑鼠

這個 G300s巨集滑鼠 在下已經使用了一段時間,但要更改其中的巨集功能,就需要安裝官方提供的軟件
然而,官方只提供 Windows 及 Mac OS 版本的更新軟件,因為在下主要使用 Linux,所以無法更新設定

早期在下還接受在 Windows 或 Mac OS 使用官方軟件來更新設定,畢竟在下有很多備用電腦可以使用
但在下對每次都需要使用 Windows 或 Mac OS 來更新設定感到非常不滿
因此,在下決定逆向工程官方的軟件,以了解更新的方法,並嘗試製作一個能在 Linux 上更新 G300s巨集滑鼠 設定的工具

這個專案參考了在下之前對 逆向工程巨集鍵盤修改軟件 的經驗
不過,在下發現修改方法與 巨集鍵盤 略有不同

巨集鍵盤 可以在 終端機(Terminal) 中使用 標準輸出(Standard Output) 序列資料(Serial Data) 輸出
並將結果 引導(Redirect) 至序列裝置以更新設定

雖然 G300s巨集滑鼠 都是 USB裝置 ,但它不是使用 序列通訊 來更新資料,而是使用 USB通訊
因此,無法直接在 終端機 引導資料來傳送,而必須使用 USB通訊 來傳輸資料
由於在不需要將整個軟件逆向工程,只需了解運作原理及更新設定的資料
因此在下同樣使用 Wireshark 擷取資料後進行分析,這與逆向工程 巨集鍵盤 修改軟件的方法相同

外觀

G300s巨集滑鼠 的外觀
(這類面向一般消費者的產品,由於網上有很多基本介紹資料,因此在下不再詳細說明)

更新軟件

官方軟件會自動偵測 G300s巨集滑鼠 是否連接到電腦
官方軟件提供兩種設定方法:

  • 內建記憶體,使用滑鼠上儲存的設定檔
  • 自動遊戲偵測,使用電腦上儲存的設定檔

由於 自動遊戲偵測 需要依賴官方軟件在背景執行,才能讓 G300s巨集滑鼠 執行電腦中的設定操作,因此需要花更多時間將軟件逆向工程
故此,在下先將 內建記憶體 的運作方式進行逆向工程

相比於 自動遊戲偵測 設定, 內建記憶體 設定沒有太多額外的設定
畢竟是將設定資料保存到 G300s巨集滑鼠 的 內建記憶體,通常不需要保存太多資料
在 內建記憶體 中,可以設定:

  • 讀取設定檔
  • 更改設定檔
  • 更改 LED 燈亮著時的顏色
  • 更改更新頻率
  • 更改 DPI 值
  • 更改 DPI 預設值
  • 更改 DPI 轉換
  • 更改按鈕功能(滑鼠操作)
  • 更改按鈕功能(鍵盤操作)
  • 切換設定檔
  • 切換 DPI 值

在下主要會嘗試將以上功能逆向工程

逆向工程

同樣使用 Wireshark 逆向工程,由於 G300s巨集滑鼠 是 USB裝置,因此可以讓 Wireshark 監聽 USB訊號 來擷取資料

查詢目標 USB裝置 的 ID,可以在 終端機 輸入:

lsusb

啟動 Wireshark 前,輸入:

sudo modprobe usbmon

來啟用 usbmon,以便讓 Wireshark 監聽 USB訊號

由於監聽 USB訊號 需要 root權限,需要輸入:

sudo wireshark

以 root 或 sudo 身份啟用 Wireshark

然後在 Wireshark 的監聽過濾中輸入:

usb.address_address == ? && usb.src == "host" && usb.data_len > 0

讓 Wireshark 只顯示過濾後的資料,減少因監聽所顯示的資料過多而產生混亂 (? 為指定 USB裝置 的 ID)

由於逆向工程主要需要大量時間、耐心及邏輯推理,並不斷重複測試,比逆向工程巨集鍵盤更花時間
因此在下不詳細展示這些測試的步驟

讀取設定檔

G300s巨集滑鼠 使用 4位元組 USB資料 來讀取設定資料
USB資料 的結構為:

偏移位元組長度類型功能
十進制十六進制
00x001無符號數值用途不明,必定是 0xF0
10x011無符號數值模式,參考 模式
20x021無符號數值用途不明,必定是 0x00
30x031無符號數值用途不明,必定是 0x00
模式
編號功能
十進制十六進制
1280x80模式1,參考 更改設定檔編號
1440x90模式2,參考 更改設定檔編號
1600xA0模式3,參考 更改設定檔編號
測試
0xF0, 0x80, 0x00, 0x00

向 G300s巨集滑鼠 發送讀取設定檔的指令會返回 35位元組的資料
而其 USB資料結構 與更新操作的 USB資料結構 相同,參考 更新軟件

更改設定檔

G300s巨集滑鼠 使用 35位元組 USB資料 來更新設定資料
USB資料 的結構為:

偏移位元組長度類型功能
十進制十六進制
00x001無符號數值模式,參考 模式
10x011無符號數值LED顏色,參考 LED顏色
20x021無符號數值更新頻率,參考 更新頻率
30x031無符號數值DPI值1,參考 DPI值
40x041無符號數值DPI值2,參考 DPI值
50x051無符號數值DPI值3,參考 DPI值
60x061無符號數值DPI值4,參考 DPI值
70x071無符號數值DPI轉換,參考 DPI值
80x081無符號數值滑鼠左鍵 改變成 滑鼠操作,參考 滑鼠操作
90x091無符號數值滑鼠左鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
100x0A1無符號數值滑鼠左鍵 改變成 鍵盤操作,參考 鍵盤操作
110x0B1無符號數值滑鼠右鍵 改變成 滑鼠操作,參考 滑鼠操作
120x0C1無符號數值滑鼠右鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
130x0D1無符號數值滑鼠右鍵 改變成 鍵盤操作,參考 鍵盤操作
140x0E1無符號數值滑鼠中鍵 改變成 滑鼠操作,參考 滑鼠操作
150x0F1無符號數值滑鼠中鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
160x101無符號數值滑鼠中鍵 改變成 鍵盤操作,參考 鍵盤操作
170x111無符號數值滑鼠左下鍵 改變成 滑鼠操作,參考 滑鼠操作
180x121無符號數值滑鼠左下鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
190x131無符號數值滑鼠左下鍵 改變成 鍵盤操作,參考 鍵盤操作
200x141無符號數值滑鼠左上鍵 改變成 滑鼠操作,參考 滑鼠操作
210x151無符號數值滑鼠左上鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
220x161無符號數值滑鼠左上鍵 改變成 鍵盤操作,參考 鍵盤操作
230x171無符號數值滑鼠右下鍵 改變成 滑鼠操作,參考 滑鼠操作
240x181無符號數值滑鼠右下鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
250x191無符號數值滑鼠右下鍵 改變成 鍵盤操作,參考 鍵盤操作
260x1A1無符號數值滑鼠右上鍵 改變成 滑鼠操作,參考 滑鼠操作
270x1B1無符號數值滑鼠右上鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
280x1C1無符號數值滑鼠右上鍵 改變成 鍵盤操作,參考 鍵盤操作
290x1D1無符號數值滑鼠中下鍵 改變成 滑鼠操作,參考 滑鼠操作
300x1E1無符號數值滑鼠中下鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
310x1F1無符號數值滑鼠中下鍵 改變成 鍵盤操作,參考 鍵盤操作
320x201無符號數值滑鼠中上鍵 改變成 滑鼠操作,參考 滑鼠操作
330x211無符號數值滑鼠中上鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
340x221無符號數值滑鼠中上鍵 改變成 鍵盤操作,參考 鍵盤操作
模式
編號功能
十進制十六進制
2430xF3模式1
2440xF4模式2
2450xF5模式3
LED顏色
編號功能
十進制十六進制
00x00黑色 (#000000)
10x01紅色 (#FF0000)
20x02綠色 (#00FF00)
30x03黃色 (#FFFF00)
40x04藍色 (#0000FF)
50x05紫色 (#FF00FF)
60x06水色 (#00FFFF)
70x07白色 (#FFFFFF)
更新頻率
編號功能
十進制十六進制
00x00每秒1000次
10x01每秒125次
20x02每秒250次
30x03每秒500次
DPI值
編號功能
十進制十六進制
x & 128 > 0x & 0x80 > 0預設值設定
x & 15 == 1x & 0x0F == 0x01設定為 250 DPI
x & 15 == 2x & 0x0F == 0x02設定為 500 DPI
x & 15 == 3x & 0x0F == 0x03設定為 750 DPI
x & 15 == 4x & 0x0F == 0x04設定為 1000 DPI
x & 15 == 5x & 0x0F == 0x05設定為 1250 DPI
x & 15 == 6x & 0x0F == 0x06設定為 1500 DPI
x & 15 == 7x & 0x0F == 0x07設定為 1750 DPI
x & 15 == 8x & 0x0F == 0x08設定為 2000 DPI
x & 15 == 9x & 0x0F == 0x09設定為 2250 DPI
x & 15 == 10x & 0x0F == 0x0A設定為 2500 DPI
滑鼠操作
編號功能
十進制十六進制
00x00沒有操作
10x01滑鼠左鍵
20x02滑鼠右鍵
30x03滑鼠中鍵
40x04前進
50x05後退
60x06保留
70x07保留
80x08保留
90x09保留
100x0A增加 DPI值
110x0B減少 DPI值
120x0C按下按鈕時,臨時切換到 預設DPI值;不按按鈕時,切換回 原本DPI值
130x0D循環切換 DPI值
140x0E切換到 預設DPI值
150x0F循環切換 模式
修飾鍵
編號功能
十進制十六進制
10x01左Ctrl鍵 / 左Control鍵
20x02左Shift鍵
40x04左Alt鍵 / 左Option鍵
80x08左Meta鍵 / 左Command鍵
160x10右Ctrl鍵 / 右Control鍵
320x20右Shift鍵
640x40右Alt鍵 / 右Option鍵
1280x80右Meta鍵 / 右Command鍵
0b11000011
  ^^^^^^^^
  |||||||+- 左Ctrl鍵 / 左Control鍵
  ||||||+-- 左Shift鍵
  |||||+--- 左Alt鍵 / 左Option鍵
  ||||+---- 左Meta鍵 / 左Command鍵
  |||+----- 右Ctrl鍵 / 右Control鍵
  ||+------ 右Shift鍵
  |+------- 右Alt鍵 / 右Option鍵
  +-------- 右Meta鍵 / 右Command鍵

修飾鍵能夠同時執行,根據例子 0b11000011 = 0xC3 = 195 表示同時按下:

  • 左Ctrl鍵 / 左Control鍵
  • 左Shift鍵
  • 右Alt鍵 / 右Option鍵
  • 右Meta鍵 / 右Command鍵
鍵盤操作
編號功能
十進制十六進制
00x00無效
10x01保留
20x02保留
30x03保留
40x04鍵盤小寫 a
50x05鍵盤小寫 b
60x06鍵盤小寫 c
70x07鍵盤小寫 d
80x08鍵盤小寫 e
90x09鍵盤小寫 f
100x0A鍵盤小寫 g
110x0B鍵盤小寫 h
120x0C鍵盤小寫 i
130x0D鍵盤小寫 j
140x0E鍵盤小寫 k
150x0F鍵盤小寫 l
160x10鍵盤小寫 m
170x11鍵盤小寫 n
180x12鍵盤小寫 o
190x13鍵盤小寫 p
200x14鍵盤小寫 q
210x15鍵盤小寫 r
220x16鍵盤小寫 s
230x17鍵盤小寫 t
240x18鍵盤小寫 u
250x19鍵盤小寫 v
260x1A鍵盤小寫 w
270x1B鍵盤小寫 x
280x1C鍵盤小寫 y
290x1D鍵盤小寫 z
300x1E鍵盤數字 1
310x1F鍵盤數字 2
320x20鍵盤數字 3
330x21鍵盤數字 4
340x22鍵盤數字 5
350x23鍵盤數字 6
360x24鍵盤數字 7
370x25鍵盤數字 8
380x26鍵盤數字 9
390x27鍵盤數字 0
400x28鍵盤 Enter
410x29鍵盤 Escape
420x2A鍵盤 Backspace
430x2B鍵盤 Tab
440x2C鍵盤 Space
450x2D鍵盤 -
460x2E鍵盤 =
470x2F鍵盤 [
480x30鍵盤 ]
490x31鍵盤 \
500x32鍵盤 \ (非US)
510x33鍵盤 ;
520x34鍵盤 '
530x35鍵盤 `
540x36鍵盤 ,
550x37鍵盤 .
560x38鍵盤 /
570x39鍵盤 Caps Lock
580x3A鍵盤 F1
590x3B鍵盤 F2
600x3C鍵盤 F3
610x3D鍵盤 F4
620x3E鍵盤 F5
630x3F鍵盤 F6
640x40鍵盤 F7
650x41鍵盤 F8
660x42鍵盤 F9
670x43鍵盤 F10
680x44鍵盤 F11
690x45鍵盤 F12
700x46鍵盤 Print Screen
710x47鍵盤 Scroll Lock
720x48鍵盤 Pause
730x49鍵盤 Insert
740x4A鍵盤 Home
750x4B鍵盤 Page Up
760x4C鍵盤 Delete
770x4D鍵盤 End
780x4E鍵盤 Page Down
790x4F鍵盤 Right Arrow
800x50鍵盤 Left Arrow
810x51鍵盤 Down Arrow
820x52鍵盤 Up Arrow
830x53鍵盤 Num Lock
840x54數字鍵盤 /
850x55數字鍵盤 *
860x56數字鍵盤 -
870x57數字鍵盤 +
880x58數字鍵盤 Enter
890x59數字鍵盤 1
900x5A數字鍵盤 2
910x5B數字鍵盤 3
920x5C數字鍵盤 4
930x5D數字鍵盤 5
940x5E數字鍵盤 6
950x5F數字鍵盤 7
960x60數字鍵盤 8
970x61數字鍵盤 9
980x62數字鍵盤 0
990x63數字鍵盤 .
1000x64數字鍵盤 <
1010x65鍵盤 Application
1020x66系統 關機
1030x67數字鍵盤 =
1040x68F13
1050x69F14
1060x6AF15
1070x6BF16
1080x6CF17
1090x6DF18
1100x6EF19
1110x6FF20
1120x70F21
1130x71F22
1140x72F23
1150x73F24
1160x74Open
1170x75Help
1180x76Props
1190x77Select
1200x78瀏覽器 Stop
1210x79Redo
1220x7AUndo
1230x7BCut
1240x7CCopy
1250x7DPaste
1260x7EFind
1270x7F系統 靜音
1280x80系統 音量增加
1290x81系統 音量減少
1300x82保留
1310x83保留
1320x84保留
1330x85數字鍵盤 ,
1340x86保留
1350x87International 1
1360x88International 2
1370x89International 3
1380x8AConvert
1390x8BNon Convert
1400x8C保留
1410x8D保留
1420x8E保留
1430x8F保留
1440x90Lang 1
1450x91Lang 2
1460x92保留
1470x93保留
1480x94保留
1490x95保留
1500x96保留
1510x97保留
1520x98保留
1530x99保留
1540x9A保留
1550x9B保留
1560x9CDelete
1570x9D保留
1580x9E保留
1590x9F保留
1600xA0保留
1610xA1保留
1620xA2保留
1630xA3保留
1640xA4保留
1650xA5保留
1660xA6保留
1670xA7保留
1680xA8保留
1690xA9保留
1700xAA保留
1710xAB保留
1720xAC保留
1730xAD保留
1740xAE保留
1750xAF保留
1760xB0保留
1770xB1保留
1780xB2保留
1790xB3保留
1800xB4保留
1810xB5保留
1820xB6保留
1830xB7保留
1840xB8保留
1850xB9保留
1860xBA保留
1870xBB保留
1880xBC保留
1890xBD保留
1900xBE保留
1910xBF保留
1920xC0保留
1930xC1保留
1940xC2保留
1950xC3保留
1960xC4保留
1970xC5保留
1980xC6保留
1990xC7保留
2000xC8保留
2010xC9保留
2020xCA保留
2030xCB保留
2040xCC保留
2050xCD保留
2060xCE保留
2070xCF保留
2080xD0保留
2090xD1保留
2100xD2保留
2110xD3保留
2120xD4保留
2130xD5保留
2140xD6保留
2150xD7保留
2160xD8保留
2170xD9保留
2180xDA保留
2190xDB保留
2200xDC保留
2210xDD保留
2220xDE保留
2230xDF保留
2240xE0保留
2250xE1保留
2260xE2保留
2270xE3保留
2280xE4保留
2290xE5保留
2300xE6保留
2310xE7保留
2320xE8保留
2330xE9保留
2340xEA保留
2350xEB保留
2360xEC保留
2370xED保留
2380xEE保留
2390xEF保留
2400xF0保留
2410xF1保留
2420xF2保留
2430xF3保留
2440xF4保留
2450xF5保留
2460xF6保留
2470xF7保留
2480xF8保留
2490xF9保留
2500xFA保留
2510xFB保留
2520xFC保留
2530xFD保留
2540xFE保留
2550xFF保留

測試指令

0xF3,\
0x01,\
0x00,\
0x04, 0x86, 0x08, 0x0A, 0x06,\
0x01, 0x00, 0x00,\
0x02, 0x00, 0x00,\
0x03, 0x00, 0x00,\
0x04, 0x00, 0x00,\
0x05, 0x00, 0x00,\
0x00, 0x01, 0x06,\
0x00, 0x01, 0x19,\
0x0C, 0x00, 0x00,\
0x00, 0x01, 0x1B

測試指令內容為:

  • 會更新 0xF3 (模式1)
  • LED顏色編號 為 0x01 (紅色)
  • 更新頻率編號 為 0x00 (每秒1000次)
  • DPI值編號1 為 0x04 (1000 DPI)
  • DPI值編號2 為 0x86 (1500 DPI) (預設)
  • DPI值編號3 為 0x08 (2000 DPI)
  • DPI值編號4 為 0x0A (2500 DPI)
  • DPI值編號 為 0x06 (1500 DPI)
  • 滑鼠左鍵(G1) 為 0x01, 0x00, 0x00 (滑鼠左鍵)
  • 滑鼠右鍵(G2) 為 0x02, 0x00, 0x00 (滑鼠右鍵)
  • 滑鼠中鍵(G3) 為 0x03, 0x00, 0x00 (滑鼠中鍵)
  • 滑鼠左下鍵(G4) 為 0x04, 0x00, 0x00 (後退)
  • 滑鼠左上鍵(G5) 為 0x05, 0x00, 0x00 (前進)
  • 滑鼠右下鍵(G6) 為 0x00, 0x01, 0x06 (左Ctrl + C)
  • 滑鼠右上鍵(G7) 為 0x00, 0x01, 0x19 (左Ctrl + V)
  • 滑鼠中下鍵(G8) 為 0x0D, 0x00, 0x00 (循環切換 DPI值)
  • 滑鼠中上鍵(G9) 為 0x00, 0x01, 0x1B (左Ctrl + X)

正確更新 G300s巨集滑鼠 的設定資料,會傳回數值 35 (即是位元組長度的數值)

切換設定檔

G300s巨集滑鼠 使用 4位元組 USB資料 來讀取設定資料
USB資料 的結構為:

偏移位元組長度類型功能
十進制十六進制
00x001無符號數值用途不明,必定是 0xF0
10x011無符號數值模式,參考 讀取設定檔模式
20x021無符號數值用途不明,必定是 0x00
30x031無符號數值用途不明,必定是 0x00

正確切換 G300s巨集滑鼠 的 設定檔,會傳回數值 4 (即是位元組長度的數值)

切換DPI
編號功能
十進制十六進制
640x40DPI值編號1
660x42DPI值編號2
680x44DPI值編號3
700x46DPI值編號4

正確切換 G300s巨集滑鼠 的 DPI值,會傳回數值 4 (即是位元組長度的數值)

USB通訊

USB通訊 並不能像一般 序列裝置 能夠直接將資料發送至 USB裝置,但在下這裡不會詳細說明 USB通訊 的 規格及方法

除了 資料片段(Data Fragment) 之外,還需要符合 bmRequest wValue wIndex wLength 這些 USB通訊設定
才能正確地將指令發送給 G300s巨集滑鼠

Python 及 pyusb

雖然已經完成資料的逆向工程和分析,但還需要編寫程式來執行指令,以更新 G300s巨集滑鼠 的設定
在下使用 Python 配合 pyusb 來處理 USB裝置 相關的操作。安裝 pyusb 可以在 終端機 中輸入:

pip install pyusb

比較新版本的 Linux發行版 會將 Python套件 加入到系統套件中,例如在下正在使用的 Linux Mint 22.2 ,需要輸入:

sudo apt install python3-usb

或點擊安裝 python3-usb

由於 pyusb 已經完成了大部分 USB通訊 的初始操作,因此在下可以專注於編寫關於 USB通訊設定及資料的內容,以存取 G300s巨集滑鼠

import usb.core, usb.util
# Finding the USB device filtered with its Vendor ID and Product ID
device = usb.core.find(idVendor = device_vendor_id, idProduct = device_product_id)
if device == None:
	print("Device not found")
else:
	# Temporarily disconnect the USB device from the kernel driver
	device.detach_kernel_driver(interface_index)
	# Using the target interface
	usb.util.claim_interface(device, interface_index)
	# Transfer data to the device
	result = device.ctrl_transfer(
		# Defining the type, recipient, direction of the request
		bmRequestType = usb.util.CTRL_TYPE_CLASS | usb.util.CTRL_RECIPIENT_INTERFACE | usb.util.CTRL_IN,
		# Defining the request code
		bRequest = 0x01,
		# Defining the request parameter
		wValue = mode | 0x0300,
		# Defining the interface or endpoint
		wIndex = 0x01,
		# Sending array of bytes to device or retrieving number of dates from device
		data_or_wLength = 256,
		# Timeout after the request without response
		timeout = 1000,
	)
	# Release the target interface
	usb.util.release_interface(device, interface_index)
	# Re-disconnect the USB device to the kernel driver
	device.attach_kernel_driver(interface_index)
	# Clearing the device handle
	usb.util.dispose_resources(device)
	# Showing the reesult of the request
	print(result)

在編寫了一個簡單的 Python腳本 來向 USB裝置 發送指令
需要注意的是,向 USB裝置 傳送資料同樣需要 root 權限,因此執行 Python腳本 時需要以 root 或 sudo 身份運行

# g300s-get-mode-data.py
# get data from mode 1
# python g300s-get-mode-data.py "243"
import sys, usb.core, usb.util
device = usb.core.find(idVendor = 0x046D, idProduct = 0xC246)
if device == None:
	print("No G300s")
else:
	mode = int(sys.argv[1])
	interface_index = 1
	device.detach_kernel_driver(interface_index)
	usb.util.claim_interface(device, interface_index)
	result = device.ctrl_transfer(
		bmRequestType = usb.util.CTRL_TYPE_CLASS | usb.util.CTRL_RECIPIENT_INTERFACE | usb.util.CTRL_IN,
		bRequest = 0x01,
		wValue = mode | 0x0300,
		wIndex = 0x01,
		data_or_wLength = 256,
		timeout = 1000,
	)
	usb.util.release_interface(device, interface_index)
	device.attach_kernel_driver(interface_index)
	usb.util.dispose_resources(device)
	# returns 35-byte array
	print(result)
# g300s-change-mode.py
# change to mode 1
# python g300s-change-mode.py "128"
import sys, usb.core, usb.util
device = usb.core.find(idVendor = 0x046D, idProduct = 0xC246)
if device == None:
	print("No G300s")
else:
	mode = int(sys.argv[1])
	interface_index = 1
	usb.util.claim_interface(device, interface_index)
	result = device.ctrl_transfer(
		bmRequestType = usb.util.CTRL_TYPE_CLASS | usb.util.CTRL_RECIPIENT_INTERFACE | usb.util.CTRL_OUT,
		bRequest = 0x09,
		wValue = 0x00F0 | 0x0300,
		wIndex = 0x01,
		data_or_wLength = [0xF0, mode, 0x00, 0x00],
		timeout = 1000,
	)
	usb.util.release_interface(device, interface_index)
	usb.util.dispose_resources(device)
	# returns length of byte array, always 4
	print(result)
# g300-set-mode-data.py
# set mode 1 to
# LED = red
# Rate = 1000
# DPI 1 = 1000
# DPI 2 = 1500 (default)
# DPI 3 = 2000
# DPI 4 = 2500
# DPI Shift = 1500
# Button G1 = Mouse Left
# Button G2 = Mouse Right
# Button G3 = Mouse Middle
# Button G4 = History Backward
# Button G5 = History Forward
# Button G6 = Ctrl C
# Button G7 = Ctrl V
# Button G8 = Mode Switch
# Button G9 = Ctrl X
# python g300-set-mode-data.py "243,1,0,4,134,8,10,6,1,0,0,2,0,0,3,0,0,4,0,0,5,0,0,0,1,6,0,1,25,13,0,0,0,1,27"
import sys, usb.core, usb.util
device = usb.core.find(idVendor = 0x046D, idProduct = 0xC246)
if device == None:
	print("No G300s")
else:
	payload = []
	for arg in sys.argv[1].split(","):
		payload.append(int(arg))
	interface_index = 1
	usb.util.claim_interface(device, interface_index)
	result = device.ctrl_transfer(
		bmRequestType = usb.util.CTRL_TYPE_CLASS | usb.util.CTRL_RECIPIENT_INTERFACE | usb.util.CTRL_OUT,
		bRequest = 0x09,
		wValue = payload[0] | 0x0300,
		wIndex = 0x01,
		data_or_wLength = payload,
		timeout = 1000,
	)
	usb.util.release_interface(device, interface_index)
	usb.util.dispose_resources(device)
	# returns length of byte array, always 35
	print(result)

在下編寫了 3個 Python腳本 來存取 G300s巨集滑鼠 來讀取設定檔、切換設定檔、更新設定檔

測試

測試 切換設定檔

測試更新 LED顏色

雖然使用指令操作很方便,但對於不擅長使用指令的使用者來說並不太方便
因此,在下製作了一個可以經網頁操作的版本,以方便一般使用者使用

補充資料

讀取當前設定檔

發佈這篇文章再次檢視使用網頁更新 G300s巨集滑鼠 的影片,才發現時 G300s巨集滑鼠的LED顏色 與 網頁顯示的LED顏色 不同
在下忘記將將 讀取當前設定檔 的操作逆向工程,因此只好在此補充

# g300s-get-current-mode.py
# python g300s-get-current-mode.py
import sys, usb.core, usb.util
device = usb.core.find(idVendor = 0x046D, idProduct = 0xC246)
if device == None:
	print("No G300s")
else:
	interface_index = 1
	device.detach_kernel_driver(interface_index)
	usb.util.claim_interface(device, interface_index)
	result = device.ctrl_transfer(
		bmRequestType = usb.util.CTRL_TYPE_CLASS | usb.util.CTRL_RECIPIENT_INTERFACE | usb.util.CTRL_IN,
		bRequest = 0x01,
		wValue = 0x00F0 | 0x0300,
		wIndex = 0x01,
		data_or_wLength = 256,
		timeout = 1000,
	)
	usb.util.release_interface(device, interface_index)
	device.attach_kernel_driver(interface_index)
	usb.util.dispose_resources(device)
	# returns 4-byte array
	print(result)

正確讀取 G300s巨集滑鼠 當前設定檔,會傳回數值 4位元組資料

0xF0, 0x??, 0x00, 0x00

?? 為 當前設定檔的模式

編號功能
十進制十六進制
20x02模式1
180x12模式2
340x22模式3
範型程式
# usb-communication.py
# usb-communication.py vendor-id, product-id, interface-index, bm-request-type, b-request, w-value, windex
# usb-communication.py vendor-id, product-id, interface-index, bm-request-type, b-request, w-value, windex, comma-separated-bytes
# python usb-commumication.py "0x046D" "0xC246" "1" "0xA1" "0x01" "0x03F0" "0x01"
import sys, usb.core, usb.util
def value_string_to_value(value_string):
	value = value_string.__str__().strip()
	try:
		if value.startswith("0x"):
			return int(value, 16)
		elif value.startswith("0b"):
			return int(value, 2)
		else:
			return int(value)
	except ValueError:
		return 0
try:
	vendor_id = value_string_to_value(sys.argv[1])
	product_id = value_string_to_value(sys.argv[2])
	interface_index = value_string_to_value(sys.argv[3])
	bmRequestType = value_string_to_value(sys.argv[4])
	bRequest = value_string_to_value(sys.argv[5])
	wValue = value_string_to_value(sys.argv[6])
	wIndex = value_string_to_value(sys.argv[7])
	data_or_wLength = 256
	if 8 in sys.argv:
		data_or_wLength = sys.argv[8].__str__().strip()
		if len(data_or_wLength) > 0:
			payload = []
			for value_string in data_or_wLength.split(","):
				payload.append(value_string_to_value(value_string))
			data_or_wLength = payload
	device = usb.core.find(idVendor = vendor_id, idProduct = product_id)
	device.detach_kernel_driver(interface_index)
	usb.util.claim_interface(device, interface_index)
	data = device.ctrl_transfer(
		bmRequestType = bmRequestType,
		bRequest = bRequest,
		wValue = wValue,
		wIndex = wIndex,
		data_or_wLength = data_or_wLength,
		timeout = 1000,
	)
	usb.util.release_interface(device, interface_index)
	device.attach_kernel_driver(interface_index)
	usb.util.dispose_resources(device)
	print(data)
except Exception as exception:
	print(exception)

另外,由於在下太懶惰,不想編寫多個不同功能的操作
因此編寫了一個範型程式,能夠從參數設定 Vendor ID、Product ID、Interface Index 等資料

總結

由於在下對只有 Windows 和 Mac OS 的更新軟件不滿,因此進行了逆向工程並自行編寫工具
逆向工程後,除了製作指令更新的工具,在下還製作能夠通過網頁更新的版本
在下認為使用網頁更新的方法更符合現代操作方式,像 Python 這類跨平台的編程語言
可以簡單地製作以網頁方式操作的工具,既不受作業系統限制,又能讓其他裝置協助操作
此外,通過 HTTP 請求提交資料,也可以借助 CURL 等指令工具將操作自動化或半自動化,方便管理

尤其這類小型工具不需要佔用太多系統資源,因此根本不應該有這些平台限制的設計
就像許多 Linux 上的 系統管理器 、 路由器(Router) 、 網絡鏡頭(Network Camera) 等工具,早已可以在網頁控制,並不受平台限制

雖然在下有多次逆向工程的經驗,但將原本的工具逆向工程仍需要花費大量時間才能完成
因此,在下更加佩服開源社群的人願意花費這麼多時間,逆向工程更複雜的工具,最後免費讓大家使用

另外,網上也有其他人製作出能在 Linux 上更新 G300s巨集滑鼠 的工具 RatSlap

RatSlap 使用 C 配合 libusb 來編寫,在下最初參考了 RatSlap 同樣使用 C 和 libusb 來編寫比 RatSlap 更簡單的操作方法
不過,在下覺得使用指令操作對一般使用者不方便,因此嘗試使用其他編程語言來實現相同效果

在了解運作原理後,在下發現 Python 配合 pyusb 可以達到相同效果,而且 Python 可以很容易地自建網頁伺服器
因此,在下決定使用 Python 編寫,並通過網頁介面來更新 G300s巨集滑鼠

由於使用網頁更新的原始碼太長,因此在下將原始碼上載到 https://bitbucket.org/hkgoldenmra/linux-g300s-updater
有興趣可以下載、改良、分享

參考資料

文章內容經由 AI 協助訂正