MCHOSE G75 Pro 强行改键

前言

手里有把 MCHOSE G75 Pro 键盘,功能、声音什么的都挺好,就一个非常脑瘫的问题,MacOS 下要按出来 F1-F12 必须得 Fn + F? , 还不支持反转,这不是糖吗?凭什么 MacOS 用户按 F1-F12 就得按两下,Windows 就只用按一下,问客服也说只能这样。

image.png

我就这样用了大概有一年,直到昨天晚上,我突发觉得我必须解决这个问题,我要改掉它,于是就有了下面的内容。

提取固件

为了提取固件,我费了很大力气把键盘壳撬开里面主控应该是 BYK916 ,搜索了一下总结下来这个芯片用的是 8051 指令集,而且大概率是 Sinowealth SH68F90A 这款芯片的换皮。

DD5266AB-4A51-4BAD-A439-95BBEE4B6D3D_1_201_a.jpeg

然后,我继续搜怎么提取固件,就搜到了 sinowealth-kb-tool 这个工具,可以直接通过 USB 对固件进行读取、刷写。

根本不用拆开键盘,更不用把自己的三模切换开关撬碎 😭😭

https://github.com/carlossless/sinowealth-kb-tool

看一下 vendor_id 和 product_id

image.png

sinowealth-kb-tool read --platform sh68f90 --vendor_id 0x258a --product_id 0x010C firmware.hex

固件分析、Patch

参考当年分析 iMK 键盘思路,尝试直接硬搜按键映射矩阵,

最终搜索 00 00 00 29 00 00 00 35 (0x29 代表 esc)、(0x35 代表 ~/`)找到了 4 组

image.png

然后发现 0xb400 和 0xcc00 内容完全一样,对照键位都是 windows 的键位,而 0xb600 和 0xce00 内容完全一样,对照键位都是 macos 的键位。

def read_keyboard_map(base):
keyboard_map = []
for i in range(16): # 后面发现 20 列,但是多的没用到,因为我这是一把 75 配列的
col = []
for j in range(6): # 6 行
col.append(idaapi.get_dword(base + (i * 6 + j) * 4))
keyboard_map.append(col)
return keyboard_map

后面分析了很久的代码,以为会像 iMK 那个一样会有针对 fn 层的专门的处理,决定 fn + ? 输出什么,但我在 int0 的按键扫描处理逻辑中并没有发现这样的东西(虽然我也没完全看明白,但是我就看到它一直在查表)我就想是不是还会有别的表呢。

我就又用 read_keyboard_map 往后读了 0xb800 的,发现还真是 windows 的 fn 层的映射矩阵。于是我得到了

地址 长度 含义
0xb400/0xcc00 0x200 windows layer 0
0xb600/0xce00 0x200 macos layer 0
0xb800/0xd000 0x200 windows layer fn
0xba00/0xd200 0x200 macos layer fn

那还扯啥呢?

"""
MacOS 模式键位映射地址
layer0 0xb600、0xce00
layer1 0xba00、0xd200

f1 - f12 layer0 和 layer1 互换

"""

from intelhex import IntelHex
import struct

def get_dword(ih: IntelHex, addr: int) -> int:
b0 = ih[addr]
b1 = ih[addr + 1]
b2 = ih[addr + 2]
b3 = ih[addr + 3]
return struct.unpack(">I", bytes([b0, b1, b2, b3]))[0]

def patch_dword(ih: IntelHex, addr: int, value: int) -> None:
b = struct.pack(">I", value)
ih[addr] = b[0]
ih[addr + 1] = b[1]
ih[addr + 2] = b[2]
ih[addr + 3] = b[3]

def impl(ih: IntelHex, layer0: int, layer1: int) -> None:
"""交换 layer0 和 layer1 中 F1-F12 键的映射"""
for i in range(12):
addr0 = layer0 + (i + 1) * 6 * 4
addr1 = layer1 + (i + 1) * 6 * 4
v0 = get_dword(ih, addr0)
v1 = get_dword(ih, addr1)
print(f"f{i + 1}: {v0:08x} <-> {v1:08x}")
patch_dword(ih, addr0, v1)
patch_dword(ih, addr1, v0)

def main():
input_file = "firmware.hex"
output_file = "firmware_patched.hex"

ih = IntelHex(input_file)

impl(ih, 0xb600, 0xba00)
impl(ih, 0xce00, 0xd200)

ih.write_hex_file(output_file)

if __name__ == "__main__":
main()

然后写回去即可

sinowealth-kb-tool write --platform sh68f90 --vendor_id 0x258a --product_id 0x010C firmware_patched.hex