2025-10-28

逆向工程 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資料 的結構為:

偏移 位元組長度 類型 功能
十進制 十六進制
0 0x00 1 無符號數值 用途不明,必定是 0xF0
1 0x01 1 無符號數值 模式,參考 模式
2 0x02 1 無符號數值 用途不明,必定是 0x00
3 0x03 1 無符號數值 用途不明,必定是 0x00
模式
編號 功能
十進制 十六進制
128 0x80 模式1,參考 更改設定檔編號
144 0x90 模式2,參考 更改設定檔編號
160 0xA0 模式3,參考 更改設定檔編號
測試
0xF0, 0x80, 0x00, 0x00

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

更改設定檔

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

偏移 位元組長度 類型 功能
十進制 十六進制
0 0x00 1 無符號數值 模式,參考 模式
1 0x01 1 無符號數值 LED顏色,參考 LED顏色
2 0x02 1 無符號數值 更新頻率,參考 更新頻率
3 0x03 1 無符號數值 DPI值1,參考 DPI值
4 0x04 1 無符號數值 DPI值2,參考 DPI值
5 0x05 1 無符號數值 DPI值3,參考 DPI值
6 0x06 1 無符號數值 DPI值4,參考 DPI值
7 0x07 1 無符號數值 DPI轉換,參考 DPI值
8 0x08 1 無符號數值 滑鼠左鍵 改變成 滑鼠操作,參考 滑鼠操作
9 0x09 1 無符號數值 滑鼠左鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
10 0x0A 1 無符號數值 滑鼠左鍵 改變成 鍵盤操作,參考 鍵盤操作
11 0x0B 1 無符號數值 滑鼠右鍵 改變成 滑鼠操作,參考 滑鼠操作
12 0x0C 1 無符號數值 滑鼠右鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
13 0x0D 1 無符號數值 滑鼠右鍵 改變成 鍵盤操作,參考 鍵盤操作
14 0x0E 1 無符號數值 滑鼠中鍵 改變成 滑鼠操作,參考 滑鼠操作
15 0x0F 1 無符號數值 滑鼠中鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
16 0x10 1 無符號數值 滑鼠中鍵 改變成 鍵盤操作,參考 鍵盤操作
17 0x11 1 無符號數值 滑鼠左下鍵 改變成 滑鼠操作,參考 滑鼠操作
18 0x12 1 無符號數值 滑鼠左下鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
19 0x13 1 無符號數值 滑鼠左下鍵 改變成 鍵盤操作,參考 鍵盤操作
20 0x14 1 無符號數值 滑鼠左上鍵 改變成 滑鼠操作,參考 滑鼠操作
21 0x15 1 無符號數值 滑鼠左上鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
22 0x16 1 無符號數值 滑鼠左上鍵 改變成 鍵盤操作,參考 鍵盤操作
23 0x17 1 無符號數值 滑鼠右下鍵 改變成 滑鼠操作,參考 滑鼠操作
24 0x18 1 無符號數值 滑鼠右下鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
25 0x19 1 無符號數值 滑鼠右下鍵 改變成 鍵盤操作,參考 鍵盤操作
26 0x1A 1 無符號數值 滑鼠右上鍵 改變成 滑鼠操作,參考 滑鼠操作
27 0x1B 1 無符號數值 滑鼠右上鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
28 0x1C 1 無符號數值 滑鼠右上鍵 改變成 鍵盤操作,參考 鍵盤操作
29 0x1D 1 無符號數值 滑鼠中下鍵 改變成 滑鼠操作,參考 滑鼠操作
30 0x1E 1 無符號數值 滑鼠中下鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
31 0x1F 1 無符號數值 滑鼠中下鍵 改變成 鍵盤操作,參考 鍵盤操作
32 0x20 1 無符號數值 滑鼠中上鍵 改變成 滑鼠操作,參考 滑鼠操作
33 0x21 1 無符號數值 滑鼠中上鍵 改變成 鍵盤修飾鍵,參考 修飾鍵
34 0x22 1 無符號數值 滑鼠中上鍵 改變成 鍵盤操作,參考 鍵盤操作
模式
編號 功能
十進制 十六進制
243 0xF3 模式1
244 0xF4 模式2
245 0xF5 模式3
LED顏色
編號 功能
十進制 十六進制
0 0x00 黑色 (#000000)
1 0x01 紅色 (#FF0000)
2 0x02 綠色 (#00FF00)
3 0x03 黃色 (#FFFF00)
4 0x04 藍色 (#0000FF)
5 0x05 紫色 (#FF00FF)
6 0x06 水色 (#00FFFF)
7 0x07 白色 (#FFFFFF)
更新頻率
編號 功能
十進制 十六進制
0 0x00 每秒1000次
1 0x01 每秒125次
2 0x02 每秒250次
3 0x03 每秒500次
DPI值
編號 功能
十進制 十六進制
x & 128 > 0 x & 0x80 > 0 預設值設定
x & 15 == 1 x & 0x0F == 0x01 設定為 250 DPI
x & 15 == 2 x & 0x0F == 0x02 設定為 500 DPI
x & 15 == 3 x & 0x0F == 0x03 設定為 750 DPI
x & 15 == 4 x & 0x0F == 0x04 設定為 1000 DPI
x & 15 == 5 x & 0x0F == 0x05 設定為 1250 DPI
x & 15 == 6 x & 0x0F == 0x06 設定為 1500 DPI
x & 15 == 7 x & 0x0F == 0x07 設定為 1750 DPI
x & 15 == 8 x & 0x0F == 0x08 設定為 2000 DPI
x & 15 == 9 x & 0x0F == 0x09 設定為 2250 DPI
x & 15 == 10 x & 0x0F == 0x0A 設定為 2500 DPI
滑鼠操作
編號 功能
十進制 十六進制
0 0x00 沒有操作
1 0x01 滑鼠左鍵
2 0x02 滑鼠右鍵
3 0x03 滑鼠中鍵
4 0x04 前進
5 0x05 後退
6 0x06 保留
7 0x07 保留
8 0x08 保留
9 0x09 保留
10 0x0A 增加 DPI值
11 0x0B 減少 DPI值
12 0x0C 按下按鈕時,臨時切換到 預設DPI值;不按按鈕時,切換回 原本DPI值
13 0x0D 循環切換 DPI值
14 0x0E 切換到 預設DPI值
15 0x0F 循環切換 模式
修飾鍵
編號 功能
十進制 十六進制
1 0x01 左Ctrl鍵 / 左Control鍵
2 0x02 左Shift鍵
4 0x04 左Alt鍵 / 左Option鍵
8 0x08 左Meta鍵 / 左Command鍵
16 0x10 右Ctrl鍵 / 右Control鍵
32 0x20 右Shift鍵
64 0x40 右Alt鍵 / 右Option鍵
128 0x80 右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鍵
鍵盤操作
編號 功能
十進制 十六進制
0 0x00 無效
1 0x01 保留
2 0x02 保留
3 0x03 保留
4 0x04 鍵盤小寫 a
5 0x05 鍵盤小寫 b
6 0x06 鍵盤小寫 c
7 0x07 鍵盤小寫 d
8 0x08 鍵盤小寫 e
9 0x09 鍵盤小寫 f
10 0x0A 鍵盤小寫 g
11 0x0B 鍵盤小寫 h
12 0x0C 鍵盤小寫 i
13 0x0D 鍵盤小寫 j
14 0x0E 鍵盤小寫 k
15 0x0F 鍵盤小寫 l
16 0x10 鍵盤小寫 m
17 0x11 鍵盤小寫 n
18 0x12 鍵盤小寫 o
19 0x13 鍵盤小寫 p
20 0x14 鍵盤小寫 q
21 0x15 鍵盤小寫 r
22 0x16 鍵盤小寫 s
23 0x17 鍵盤小寫 t
24 0x18 鍵盤小寫 u
25 0x19 鍵盤小寫 v
26 0x1A 鍵盤小寫 w
27 0x1B 鍵盤小寫 x
28 0x1C 鍵盤小寫 y
29 0x1D 鍵盤小寫 z
30 0x1E 鍵盤數字 1
31 0x1F 鍵盤數字 2
32 0x20 鍵盤數字 3
33 0x21 鍵盤數字 4
34 0x22 鍵盤數字 5
35 0x23 鍵盤數字 6
36 0x24 鍵盤數字 7
37 0x25 鍵盤數字 8
38 0x26 鍵盤數字 9
39 0x27 鍵盤數字 0
40 0x28 鍵盤 Enter
41 0x29 鍵盤 Escape
42 0x2A 鍵盤 Backspace
43 0x2B 鍵盤 Tab
44 0x2C 鍵盤 Space
45 0x2D 鍵盤 -
46 0x2E 鍵盤 =
47 0x2F 鍵盤 [
48 0x30 鍵盤 ]
49 0x31 鍵盤 \
50 0x32 鍵盤 \ (非US)
51 0x33 鍵盤 ;
52 0x34 鍵盤 '
53 0x35 鍵盤 `
54 0x36 鍵盤 ,
55 0x37 鍵盤 .
56 0x38 鍵盤 /
57 0x39 鍵盤 Caps Lock
58 0x3A 鍵盤 F1
59 0x3B 鍵盤 F2
60 0x3C 鍵盤 F3
61 0x3D 鍵盤 F4
62 0x3E 鍵盤 F5
63 0x3F 鍵盤 F6
64 0x40 鍵盤 F7
65 0x41 鍵盤 F8
66 0x42 鍵盤 F9
67 0x43 鍵盤 F10
68 0x44 鍵盤 F11
69 0x45 鍵盤 F12
70 0x46 鍵盤 Print Screen
71 0x47 鍵盤 Scroll Lock
72 0x48 鍵盤 Pause
73 0x49 鍵盤 Insert
74 0x4A 鍵盤 Home
75 0x4B 鍵盤 Page Up
76 0x4C 鍵盤 Delete
77 0x4D 鍵盤 End
78 0x4E 鍵盤 Page Down
79 0x4F 鍵盤 Right Arrow
80 0x50 鍵盤 Left Arrow
81 0x51 鍵盤 Down Arrow
82 0x52 鍵盤 Up Arrow
83 0x53 鍵盤 Num Lock
84 0x54 數字鍵盤 /
85 0x55 數字鍵盤 *
86 0x56 數字鍵盤 -
87 0x57 數字鍵盤 +
88 0x58 數字鍵盤 Enter
89 0x59 數字鍵盤 1
90 0x5A 數字鍵盤 2
91 0x5B 數字鍵盤 3
92 0x5C 數字鍵盤 4
93 0x5D 數字鍵盤 5
94 0x5E 數字鍵盤 6
95 0x5F 數字鍵盤 7
96 0x60 數字鍵盤 8
97 0x61 數字鍵盤 9
98 0x62 數字鍵盤 0
99 0x63 數字鍵盤 .
100 0x64 數字鍵盤 <
101 0x65 鍵盤 Application
102 0x66 系統 關機
103 0x67 數字鍵盤 =
104 0x68 F13
105 0x69 F14
106 0x6A F15
107 0x6B F16
108 0x6C F17
109 0x6D F18
110 0x6E F19
111 0x6F F20
112 0x70 F21
113 0x71 F22
114 0x72 F23
115 0x73 F24
116 0x74 Open
117 0x75 Help
118 0x76 Props
119 0x77 Select
120 0x78 瀏覽器 Stop
121 0x79 Redo
122 0x7A Undo
123 0x7B Cut
124 0x7C Copy
125 0x7D Paste
126 0x7E Find
127 0x7F 系統 靜音
128 0x80 系統 音量增加
129 0x81 系統 音量減少
130 0x82 保留
131 0x83 保留
132 0x84 保留
133 0x85 數字鍵盤 ,
134 0x86 保留
135 0x87 International 1
136 0x88 International 2
137 0x89 International 3
138 0x8A Convert
139 0x8B Non Convert
140 0x8C 保留
141 0x8D 保留
142 0x8E 保留
143 0x8F 保留
144 0x90 Lang 1
145 0x91 Lang 2
146 0x92 保留
147 0x93 保留
148 0x94 保留
149 0x95 保留
150 0x96 保留
151 0x97 保留
152 0x98 保留
153 0x99 保留
154 0x9A 保留
155 0x9B 保留
156 0x9C Delete
157 0x9D 保留
158 0x9E 保留
159 0x9F 保留
160 0xA0 保留
161 0xA1 保留
162 0xA2 保留
163 0xA3 保留
164 0xA4 保留
165 0xA5 保留
166 0xA6 保留
167 0xA7 保留
168 0xA8 保留
169 0xA9 保留
170 0xAA 保留
171 0xAB 保留
172 0xAC 保留
173 0xAD 保留
174 0xAE 保留
175 0xAF 保留
176 0xB0 保留
177 0xB1 保留
178 0xB2 保留
179 0xB3 保留
180 0xB4 保留
181 0xB5 保留
182 0xB6 保留
183 0xB7 保留
184 0xB8 保留
185 0xB9 保留
186 0xBA 保留
187 0xBB 保留
188 0xBC 保留
189 0xBD 保留
190 0xBE 保留
191 0xBF 保留
192 0xC0 保留
193 0xC1 保留
194 0xC2 保留
195 0xC3 保留
196 0xC4 保留
197 0xC5 保留
198 0xC6 保留
199 0xC7 保留
200 0xC8 保留
201 0xC9 保留
202 0xCA 保留
203 0xCB 保留
204 0xCC 保留
205 0xCD 保留
206 0xCE 保留
207 0xCF 保留
208 0xD0 保留
209 0xD1 保留
210 0xD2 保留
211 0xD3 保留
212 0xD4 保留
213 0xD5 保留
214 0xD6 保留
215 0xD7 保留
216 0xD8 保留
217 0xD9 保留
218 0xDA 保留
219 0xDB 保留
220 0xDC 保留
221 0xDD 保留
222 0xDE 保留
223 0xDF 保留
224 0xE0 保留
225 0xE1 保留
226 0xE2 保留
227 0xE3 保留
228 0xE4 保留
229 0xE5 保留
230 0xE6 保留
231 0xE7 保留
232 0xE8 保留
233 0xE9 保留
234 0xEA 保留
235 0xEB 保留
236 0xEC 保留
237 0xED 保留
238 0xEE 保留
239 0xEF 保留
240 0xF0 保留
241 0xF1 保留
242 0xF2 保留
243 0xF3 保留
244 0xF4 保留
245 0xF5 保留
246 0xF6 保留
247 0xF7 保留
248 0xF8 保留
249 0xF9 保留
250 0xFA 保留
251 0xFB 保留
252 0xFC 保留
253 0xFD 保留
254 0xFE 保留
255 0xFF 保留

測試指令

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資料 的結構為:

偏移 位元組長度 類型 功能
十進制 十六進制
0 0x00 1 無符號數值 用途不明,必定是 0xF0
1 0x01 1 無符號數值 模式,參考 讀取設定檔模式
2 0x02 1 無符號數值 用途不明,必定是 0x00
3 0x03 1 無符號數值 用途不明,必定是 0x00

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

切換DPI
編號 功能
十進制 十六進制
64 0x40 DPI值編號1
66 0x42 DPI值編號2
68 0x44 DPI值編號3
70 0x46 DPI值編號4

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

USB通訊

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

除了 資料片段(Data Fragment) 之外,還需要符合 bmRequestwValuewIndexwLength 這些 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

?? 為 當前設定檔的模式

編號 功能
十進制 十六進制
2 0x02 模式1
18 0x12 模式2
34 0x22 模式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()
	if value.startswith("0x"):
		try:
			return int(value, 16)
		except ValueError:
			return 0
	elif value.startswith("0b"):
		try:
			return int(value, 2)
		except ValueError:
			return 0
	else:
		try:
			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 協助訂正

沒有留言 :

張貼留言