Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reboot functionality, win device enumeration fix, list hid devices, rk84-iso support #47

Merged
merged 14 commits into from
Mar 9, 2024
Merged
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ sinowealth-kb-tool read \
--isp_usage_page 0xff00 \ # optional
--isp_usage 0x0001 \ # optional
--isp_index 0 \ # optional
--reboot false \ # optional
foobar.hex
```

Expand All @@ -59,6 +60,7 @@ sinowealth-kb-tool write \
--isp_usage_page 0xff00 \ # optional
--isp_usage 0x0001 \ # optional
--isp_index 0 \ # optional
--reboot false \ # optional
foobar.hex
```

Expand All @@ -81,11 +83,36 @@ sinowealth-kb-tool write \
| Royal Kludge RK68 BT Dual | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK901 | ✅ | ✅ |
| Royal Kludge RK68 ISO Return | ❓ | SH68F90? | BYK916 | ✅ | ❓ |
| [Royal Kludge RK71](http://en.rkgaming.com/product/12/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | ❓ | ✅ | ✅ |
| [Royal Kludge RK84](http://en.rkgaming.com/product/16/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ✅ |
| Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ |
| Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ |
| Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ |
| Xinmeng XM-RF68 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90U | ✅ | ✅ |

## Bootloader Support

### Platforms

| ISP MD5 | Windows | macOS | Linux |
| -------------------------------- | -------- | -------- | ----- |
| 3e0ebd0c440af5236d7ff8872343f85d | ok | ok | ok |
| cfc8661da8c9d7e351b36c0a763426aa | ok | fail[^1] | ok |
| 2d169670eae0d36eae8188562c1f66e8 | ok | ? | ok |
| e57490acebcaabfcff84a0ff013955d9 | ok | ? | ? |
| 13df4ce2933f9654ffef80d6a3c27199 | ? | ? | ok |

[^1]: macOS does not recognize the composite device as an HID device

### Functions

| ISP MD5 | Reboot |
| -------------------------------- | ------ |
| 3e0ebd0c440af5236d7ff8872343f85d | no |
| cfc8661da8c9d7e351b36c0a763426aa | yes |
| 2d169670eae0d36eae8188562c1f66e8 | ? |
| e57490acebcaabfcff84a0ff013955d9 | ? |
| 13df4ce2933f9654ffef80d6a3c27199 | ? |

## Prerequisites

### Linux
Expand All @@ -100,6 +127,15 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="0603", ATTRS{idProduct}=="1020", MODE="0660

Make sure your user is part of the `plugdev` group.

### macOS

If you encounter errors like:
```
hid_open_path: failed to open IOHIDDevice from mach entry...
```

Ensure that your terminal application has [access to input monitoring](https://support.apple.com/guide/mac-help/control-access-to-input-monitoring-on-mac-mchl4cedafb6/mac).

## Acknowledgments

Thanks to [@gashtaan](https://github.com/gashtaan) for analyzing and explaining the inner workings of the ISP bootloaders. Without his help, this tool wouldn't be here!
62 changes: 61 additions & 1 deletion src/isp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ const GAMING_KB_PRODUCT_ID: u16 = 0x1020;

const COMMAND_LENGTH: usize = 6;

#[cfg(not(target_os = "linux"))]
const HID_ISP_USAGE_PAGE: u16 = 0xff00;
#[cfg(not(target_os = "linux"))]
const HID_ISP_USAGE: u16 = 0x0001;

const REPORT_ID_CMD: u8 = 0x05;
Expand All @@ -28,6 +30,7 @@ const CMD_ENABLE_FIRMWARE: u8 = 0x55;
const CMD_INIT_READ: u8 = 0x52;
const CMD_INIT_WRITE: u8 = 0x57;
const CMD_ERASE: u8 = 0x45;
const CMD_REBOOT: u8 = 0x5a;

const XFER_READ_PAGE: u8 = 0x72;
const XFER_WRITE_PAGE: u8 = 0x77;
Expand Down Expand Up @@ -75,6 +78,42 @@ impl ISPDevice {
})
}

/// Prints out all connected HID devices and their paths.
pub fn print_connected_devices() -> Result<(), ISPError> {
let api = ISPDevice::hidapi();

info!("Listing all connected HID devices...");
let mut devices: Vec<_> = api.device_list().collect();

devices.sort_by_key(|d| d.path());

for d in &devices {
#[cfg(not(target_os = "linux"))]
info!(
"{:}: ID {:04x}:{:04x} manufacturer=\"{:}\" product=\"{:}\" usage_page={:#06x} usage={:#06x}",
d.path().to_str().unwrap(),
d.vendor_id(),
d.product_id(),
d.manufacturer_string().unwrap_or("None"),
d.product_string().unwrap_or("None"),
d.usage_page(),
d.usage()
);
#[cfg(target_os = "linux")]
info!(
"{:}: ID {:#04x}:{:#04x} manufacturer=\"{:}\" product=\"{:}\"",
d.path().to_str().unwrap(),
d.vendor_id(),
d.product_id(),
d.manufacturer_string().unwrap_or("None"),
d.product_string().unwrap_or("None")
);
}
info!("Found {} devices", devices.len());

Ok(())
}

fn hidapi() -> HidApi {
let api = HidApi::new().unwrap();

Expand All @@ -87,7 +126,7 @@ impl ISPDevice {
fn open_isp_devices() -> Result<HIDDevices, ISPError> {
let api = Self::hidapi();

let devices: Vec<_> = api
let mut devices: Vec<_> = api
.device_list()
.filter(|d| {
#[cfg(not(target_os = "linux"))]
Expand All @@ -103,6 +142,8 @@ impl ISPDevice {
})
.collect();

devices.sort_by_key(|d| d.path());

for d in &devices {
#[cfg(not(target_os = "linux"))]
debug!(
Expand Down Expand Up @@ -255,6 +296,10 @@ impl ISPDevice {
ReadType::Full => self.read(0, self.part.firmware_size + self.part.bootloader_size)?,
};

if self.part.reboot {
self.reboot()?;
}

return Ok(firmware);
}

Expand All @@ -274,6 +319,11 @@ impl ISPDevice {
util::verify(firmware, &read_back).map_err(ISPError::from)?;

self.enable_firmware()?;

if self.part.reboot {
self.reboot()?;
}

Ok(())
}

Expand Down Expand Up @@ -401,4 +451,14 @@ impl ISPDevice {
thread::sleep(time::Duration::from_millis(2000));
Ok(())
}

fn reboot(&self) -> Result<(), ISPError> {
info!("Rebooting...");
let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_REBOOT, 0, 0, 0, 0];
// explicitly ignore the result
let _ = self.request_device.send_feature_report(&cmd);

thread::sleep(time::Duration::from_millis(2000));
Ok(())
}
}
15 changes: 14 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
process::ExitCode,
};

use clap::{arg, ArgMatches, Command};
use clap::{arg, value_parser, ArgMatches, Command};
use clap_num::maybe_hex;
use log::{error, info};
use simple_logger::SimpleLogger;
Expand Down Expand Up @@ -45,6 +45,11 @@ fn cli() -> Command {
.subcommand_required(true)
.arg_required_else_help(true)
.author("Karolis Stasaitis")
.subcommand(
Command::new("list")
.short_flag('l')
.about("List all connected devices and their identifiers. This is useful to find the manufacturer and product id for your keyboard.")
)
.subcommand(
Command::new("read")
.short_flag('r')
Expand Down Expand Up @@ -138,6 +143,9 @@ fn err_main() -> Result<(), CLIError> {
let isp = ISPDevice::new(part).map_err(CLIError::from)?;
isp.write_cycle(&mut firmware).map_err(CLIError::from)?;
}
Some(("list", _)) => {
ISPDevice::print_connected_devices().map_err(CLIError::from)?;
}
_ => unreachable!(),
}
Ok(())
Expand Down Expand Up @@ -175,6 +183,7 @@ impl PartCommand for Command {
.arg(arg!(--isp_usage_page <PAGE>).value_parser(maybe_hex::<u16>))
.arg(arg!(--isp_usage <USAGE>).value_parser(maybe_hex::<u16>))
.arg(arg!(--isp_index <INDEX>).value_parser(maybe_hex::<usize>))
.arg(arg!(--reboot <BOOL>).value_parser(value_parser!(bool)))
}
}

Expand All @@ -195,6 +204,7 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part {
let isp_usage_page = sub_matches.get_one::<u16>("isp_usage_page");
let isp_usage = sub_matches.get_one::<u16>("isp_usage");
let isp_index = sub_matches.get_one::<usize>("isp_index");
let reboot = sub_matches.get_one::<bool>("reboot");

if let Some(firmware_size) = firmware_size {
part.firmware_size = *firmware_size;
Expand Down Expand Up @@ -223,5 +233,8 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part {
if let Some(isp_index) = isp_index {
part.isp_index = *isp_index;
}
if let Some(reboot) = reboot {
part.reboot = *reboot;
}
return part;
}
17 changes: 16 additions & 1 deletion src/part.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use phf::{phf_map, Map};

#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq)]
pub struct Part {
pub firmware_size: usize,
pub bootloader_size: usize,
Expand All @@ -18,6 +18,8 @@ pub struct Part {
pub isp_usage: u16,
/// Index of matching (usage_page && usage) collection at which the ISP report appears in.
pub isp_index: usize,

pub reboot: bool,
}

pub const PART_BASE_DEFAULT: Part = Part {
Expand All @@ -32,6 +34,8 @@ pub const PART_BASE_DEFAULT: Part = Part {
isp_usage_page: 0xff00,
isp_usage: 0x0001,
isp_index: 0,

reboot: false,
};

pub const PART_BASE_SH68F90: Part = Part {
Expand Down Expand Up @@ -118,18 +122,28 @@ pub const PART_ROYALKLUDGE_RK68_ISO_RETURN: Part = Part {
pub const PART_ROYALKLUDGE_RK68_BT_DUAL: Part = Part {
vendor_id: 0x258a,
product_id: 0x008b,
reboot: true,
..PART_BASE_SH68F90
};

pub const PART_ROYALKLUDGE_RK71: Part = Part {
vendor_id: 0x258a,
product_id: 0x00ea,
reboot: true,
..PART_BASE_SH68F90
};

pub const PART_ROYALKLUDGE_RK84_ISO_RETURN: Part = Part {
vendor_id: 0x258a,
product_id: 0x00f4,
reboot: true,
..PART_BASE_SH68F90
};

pub const PART_ROYALKLUDGE_RK100: Part = Part {
vendor_id: 0x258a,
product_id: 0x0056,
reboot: true,
..PART_BASE_SH68F90
};

Expand Down Expand Up @@ -158,6 +172,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! {
"redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614,
"redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617,
"royalkludge-rk100" => PART_ROYALKLUDGE_RK100,
"royalkludge-rk84-iso-return" => PART_ROYALKLUDGE_RK84_ISO_RETURN,
"royalkludge-rk61" => PART_ROYALKLUDGE_RK61,
"royalkludge-rk68-bt-dual" => PART_ROYALKLUDGE_RK68_BT_DUAL,
"royalkludge-rk68-iso-return" => PART_ROYALKLUDGE_RK68_ISO_RETURN,
Expand Down
7 changes: 2 additions & 5 deletions tools/functional-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@ FILE_POST_WRITE="$FILE_PREFIX-post-write.hex"
FILE_POST_WRITE_CUSTOM="$FILE_PREFIX-post-write-custom.hex"

function reboot_device () {
echo "Turning off port..."
uhubctl -a off -p 1 -l 65-1
sleep 1
echo "Turning on port..."
uhubctl -a on -p 1 -l 65-1
echo "Cycling port power..."
uhubctl -a cycle -l "3-3.3.4.4" -p 4 -d 1
echo "Waiting..."
sleep 5
}
Expand Down
Loading