From 0432e568f147dc97a12dbb9f8f0ae96f6c4b283c Mon Sep 17 00:00:00 2001 From: "Swift Geek (Sebastian Grzywna)" <270845+swiftgeek@users.noreply.github.com> Date: Sun, 21 May 2023 13:36:26 +0200 Subject: [PATCH 001/141] part.rs: Add RE-K70-BYK800 board (#1) Board uses BYK801 FW / SH68F881 MCU Tested with read / write operations, dumped contents match one obtained from ICP mode (JTAG pins). USB string descriptor was modified in the dump to provide a simple test case for write feature --- src/part.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/part.rs b/src/part.rs index 101d689..acc6974 100644 --- a/src/part.rs +++ b/src/part.rs @@ -24,9 +24,18 @@ pub const PART_XINMENG_K916: Part = Part { product_id: 0x00a1, }; +pub const PART_RE_K70_BYK800: Part = Part { + flash_size: 28672, // 28672 until bootloader + bootloader_size: 4096, + page_size: 2048, + vendor_id: 0x258a, + product_id: 0x001a, +}; + pub static PARTS: phf::Map<&'static str, Part> = phf_map! { "nuphy-air60" => PART_NUPHY_AIR60, - "xinmeng-k916" => PART_XINMENG_K916 + "xinmeng-k916" => PART_XINMENG_K916, + "re-k70-byk800" => PART_RE_K70_BYK800, }; impl Part { From cfd246ba95b486a65977ea1a5ff18989ca20f283 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 21 May 2023 13:51:41 +0200 Subject: [PATCH 002/141] document RE-K70-BYK800 in readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a46240a..fa873f7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This is an experimental tool, so use it at your own risk. | -------- | ------- | --- | --------- | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅ | | Xinmeng K916 | unknown | SH68F90 | ❓ | +| Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F88 (labeled as BYK801) | ✅ | ## Acknowledgments From 94e414d9c65e38a125d8f8ef639571a519094b86 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 21 May 2023 13:52:35 +0200 Subject: [PATCH 003/141] cargo fmt --- src/hid.rs | 27 ++++++++++++++++----------- src/main.rs | 9 +++------ src/programmer.rs | 12 ++++++++---- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/hid.rs b/src/hid.rs index 02e5888..99b1a53 100644 --- a/src/hid.rs +++ b/src/hid.rs @@ -3,17 +3,20 @@ use std::time; pub struct HidDevice { handle: DeviceHandle, - interface_number: u8 + interface_number: u8, } impl HidDevice { - pub fn open(vendor_id: u16, product_id: u16) -> Option { let Some(mut handle) = open_device_with_vid_pid(vendor_id, product_id) else { return None; }; - let interface_number = handle.device().config_descriptor(0).unwrap().interfaces() + let interface_number = handle + .device() + .config_descriptor(0) + .unwrap() + .interfaces() .map(|i| i.descriptors()) .flatten() .filter(|i| i.class_code() == constants::LIBUSB_CLASS_HID) @@ -28,7 +31,7 @@ impl HidDevice { return Some(HidDevice { handle: handle, - interface_number: interface_number + interface_number: interface_number, }); } @@ -36,12 +39,14 @@ impl HidDevice { let report_number = buf[0] as u16; return self.handle.read_control( - constants::LIBUSB_REQUEST_TYPE_CLASS | constants::LIBUSB_RECIPIENT_INTERFACE | constants::LIBUSB_ENDPOINT_IN, + constants::LIBUSB_REQUEST_TYPE_CLASS + | constants::LIBUSB_RECIPIENT_INTERFACE + | constants::LIBUSB_ENDPOINT_IN, 0x01, (3/*HID feature*/ << 8) | report_number, self.interface_number as u16, buf, - time::Duration::from_millis(1000) + time::Duration::from_millis(1000), ); } @@ -49,14 +54,14 @@ impl HidDevice { let report_number = buf[0] as u16; return self.handle.write_control( - constants::LIBUSB_REQUEST_TYPE_CLASS | constants::LIBUSB_RECIPIENT_INTERFACE | constants::LIBUSB_ENDPOINT_OUT, + constants::LIBUSB_REQUEST_TYPE_CLASS + | constants::LIBUSB_RECIPIENT_INTERFACE + | constants::LIBUSB_ENDPOINT_OUT, 0x09, (3/*HID feature*/ << 8) | report_number, self.interface_number as u16, buf, - time::Duration::from_millis(1000) + time::Duration::from_millis(1000), ); } - - -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index fd13d8b..24367c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,13 +33,10 @@ fn cli() -> Command { .value_parser(PARTS.keys().map(|&s| s).collect::>()) .required(true), ) - .arg( - arg!(-b --bootloader "read only booloader") - .conflicts_with("full") - ) + .arg(arg!(-b --bootloader "read only booloader").conflicts_with("full")) .arg( arg!(-f --full "read complete flash (including the bootloader)") - .conflicts_with("bootloader") + .conflicts_with("bootloader"), ), ) .subcommand( @@ -81,7 +78,7 @@ fn main() { let read_type = match (full, bootloader) { (true, _) => ReadType::Full, (_, true) => ReadType::Bootloader, - _ => ReadType::Normal + _ => ReadType::Normal, }; let result = Programmer::new(part).read_cycle(read_type); diff --git a/src/programmer.rs b/src/programmer.rs index 494fb8e..5b3c834 100644 --- a/src/programmer.rs +++ b/src/programmer.rs @@ -34,7 +34,7 @@ const XFER_WRITE_PAGE: u8 = 0x77; pub enum ReadType { Normal, Bootloader, - Full + Full, } impl Programmer<'static> { @@ -47,7 +47,7 @@ impl Programmer<'static> { } fn find_isp_device(part: &Part) -> HidDevice { - for attempt in 1..MAX_RETRIES+1 { + for attempt in 1..MAX_RETRIES + 1 { if attempt > 1 { info!("Retrying... Attempt {}/{}", attempt, MAX_RETRIES); } @@ -93,7 +93,7 @@ impl Programmer<'static> { return match read_type { ReadType::Normal => self.read(0, self.part.flash_size), ReadType::Bootloader => self.read(self.part.flash_size, self.part.bootloader_size), - ReadType::Full => self.read(0, self.part.flash_size + self.part.bootloader_size) + ReadType::Full => self.read(0, self.part.flash_size + self.part.bootloader_size), }; } @@ -160,7 +160,11 @@ impl Programmer<'static> { let num_page = length / page_size; let mut result: Vec = vec![]; for i in 0..num_page { - debug!("Reading page {} @ offset {:#06x}", i, start_addr + i * page_size); + debug!( + "Reading page {} @ offset {:#06x}", + i, + start_addr + i * page_size + ); self.read_page(&mut result); } return result; From d2a36bd7e1ec36a7115677692d69e9bcce6cbb97 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 21 May 2023 14:09:02 +0200 Subject: [PATCH 004/141] corrected hykker sinowealth part number - SH68F881 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa873f7..ab87ea7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This is an experimental tool, so use it at your own risk. | -------- | ------- | --- | --------- | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅ | | Xinmeng K916 | unknown | SH68F90 | ❓ | -| Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F88 (labeled as BYK801) | ✅ | +| Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 (labeled as BYK801) | ✅ | ## Acknowledgments From ada90ee5f1b66ddc08f3302ade06db5a3e02620e Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 21 May 2023 15:34:22 +0200 Subject: [PATCH 005/141] testing out auto_detach_kernel_driver --- src/hid.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/hid.rs b/src/hid.rs index 99b1a53..5de163e 100644 --- a/src/hid.rs +++ b/src/hid.rs @@ -3,7 +3,7 @@ use std::time; pub struct HidDevice { handle: DeviceHandle, - interface_number: u8, + interface: u8, } impl HidDevice { @@ -12,7 +12,7 @@ impl HidDevice { return None; }; - let interface_number = handle + let interface = handle .device() .config_descriptor(0) .unwrap() @@ -24,14 +24,16 @@ impl HidDevice { .last() .unwrap(); - #[cfg(any(target_os = "linux"))] - if handle.kernel_driver_active(interface_number).unwrap() { - handle.detach_kernel_driver(interface_number).unwrap(); + if supports_detach_kernel_driver() { + handle.set_auto_detach_kernel_driver(true).unwrap(); } + #[cfg(not(any(target_os = "macos")))] + handle.claim_interface(interface).unwrap(); + return Some(HidDevice { handle: handle, - interface_number: interface_number, + interface: interface, }); } @@ -44,7 +46,7 @@ impl HidDevice { | constants::LIBUSB_ENDPOINT_IN, 0x01, (3/*HID feature*/ << 8) | report_number, - self.interface_number as u16, + self.interface as u16, buf, time::Duration::from_millis(1000), ); @@ -59,7 +61,7 @@ impl HidDevice { | constants::LIBUSB_ENDPOINT_OUT, 0x09, (3/*HID feature*/ << 8) | report_number, - self.interface_number as u16, + self.interface as u16, buf, time::Duration::from_millis(1000), ); From a3282313e4f37868b68361c369fbf62b16e6ffe2 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 21 May 2023 15:41:03 +0200 Subject: [PATCH 006/141] remove accidentally duplicated device open calls --- src/programmer.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/programmer.rs b/src/programmer.rs index 5b3c834..ca84145 100644 --- a/src/programmer.rs +++ b/src/programmer.rs @@ -69,12 +69,11 @@ impl Programmer<'static> { let kb_device_info = HidDevice::open(GAMING_KB_VENDOR_ID, GAMING_KB_PRODUCT_ID); - let Some(_) = kb_device_info else { + let Some(device) = kb_device_info else { info!("Device didn't come up..."); continue; }; - let device = HidDevice::open(GAMING_KB_VENDOR_ID, GAMING_KB_PRODUCT_ID).unwrap(); info!("Connected!"); return device; From eb91b208fe9eb370ee02f9b0936b4bb0029faa51 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 21 May 2023 17:23:43 +0200 Subject: [PATCH 007/141] rename programmer to isp --- src/hid.rs | 1 + src/{programmer.rs => isp.rs} | 20 ++++++++------------ src/main.rs | 10 +++++----- 3 files changed, 14 insertions(+), 17 deletions(-) rename src/{programmer.rs => isp.rs} (92%) diff --git a/src/hid.rs b/src/hid.rs index 5de163e..f8c5c9f 100644 --- a/src/hid.rs +++ b/src/hid.rs @@ -28,6 +28,7 @@ impl HidDevice { handle.set_auto_detach_kernel_driver(true).unwrap(); } + // don't try claiming the interface on macos, it should not be necessary and will fail #[cfg(not(any(target_os = "macos")))] handle.claim_interface(interface).unwrap(); diff --git a/src/programmer.rs b/src/isp.rs similarity index 92% rename from src/programmer.rs rename to src/isp.rs index ca84145..67acc51 100644 --- a/src/programmer.rs +++ b/src/isp.rs @@ -6,7 +6,7 @@ use crate::HidDevice; use super::part::*; use super::util; -pub struct Programmer<'a> { +pub struct ISPDevice<'a> { device: HidDevice, part: &'a Part, } @@ -37,7 +37,7 @@ pub enum ReadType { Full, } -impl Programmer<'static> { +impl ISPDevice<'static> { pub fn new(part: &'static Part) -> Self { let device = Self::find_isp_device(part); return Self { @@ -52,31 +52,27 @@ impl Programmer<'static> { info!("Retrying... Attempt {}/{}", attempt, MAX_RETRIES); } - let kb_device_info = HidDevice::open(part.vendor_id, part.product_id); - - let Some(device_info) = kb_device_info else { + let Some(device) = HidDevice::open(part.vendor_id, part.product_id) else { info!("No KB found. Trying bootloader directly..."); - let device = HidDevice::open(GAMING_KB_VENDOR_ID, GAMING_KB_PRODUCT_ID).unwrap(); + let isp_device = HidDevice::open(GAMING_KB_VENDOR_ID, GAMING_KB_PRODUCT_ID).unwrap(); info!("Connected!"); - return device; + return isp_device; }; info!("Found Device. Entering ISP mode..."); - Self::enter_isp_mode(&device_info); + Self::enter_isp_mode(&device); info!("Waiting for bootloader device..."); thread::sleep(time::Duration::from_millis(1000)); - let kb_device_info = HidDevice::open(GAMING_KB_VENDOR_ID, GAMING_KB_PRODUCT_ID); - - let Some(device) = kb_device_info else { + let Some(isp_device) = HidDevice::open(GAMING_KB_VENDOR_ID, GAMING_KB_PRODUCT_ID) else { info!("Device didn't come up..."); continue; }; info!("Connected!"); - return device; + return isp_device; } panic!("Couldn't find ISP device"); } diff --git a/src/main.rs b/src/main.rs index 24367c5..9026f46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,8 @@ use std::fs; mod part; pub use part::*; -mod programmer; -pub use programmer::*; +mod isp; +pub use isp::*; mod util; pub use util::*; @@ -81,7 +81,7 @@ fn main() { _ => ReadType::Normal, }; - let result = Programmer::new(part).read_cycle(read_type); + let result = ISPDevice::new(part).read_cycle(read_type); let ihex = result.to_ihex(); @@ -103,7 +103,7 @@ fn main() { let (mut firmware, _) = load_file_vec(input_file, part.flash_size, 0).unwrap(); - Programmer::new(part).write_cycle(&mut firmware); + ISPDevice::new(part).write_cycle(&mut firmware); } Some(("erase", sub_matches)) => { let part_name = sub_matches @@ -113,7 +113,7 @@ fn main() { let part = PARTS.get(part_name).unwrap(); - Programmer::new(part).erase_cycle(); + ISPDevice::new(part).erase_cycle(); } _ => unreachable!(), } From a3ec2a90ed94beb1aa95658128756340fb505858 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 21 May 2023 17:42:53 +0200 Subject: [PATCH 008/141] add linux prerequisites to readme --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index ab87ea7..85346af 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,20 @@ This is an experimental tool, so use it at your own risk. | Xinmeng K916 | unknown | SH68F90 | ❓ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 (labeled as BYK801) | ✅ | +## Prerequisites + +### Linux + +To enable running this tool without superuser privileges add the following udev rule with `xxxx` and `yyyy` replaced with your device Vendor ID and Product ID respectively. + +```udev +# /etc/udev/rules.d/plugdev.rule +SUBSYSTEMS=="usb", ATTRS{idVendor}=="xxxx", ATTRS{idProduct}=="yyyy", MODE="0660", GROUP="plugdev" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0603", ATTRS{idProduct}=="1020", MODE="0660", GROUP="plugdev" +``` + +Make sure your user is part of the `plugdev` group. + ## Acknowledgments * https://github.com/gashtaan/sinowealth-8051-dumper From 5a0bb6ee429f2aa064092fabed9991dd2203b495 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 16 Jun 2023 16:32:03 +0200 Subject: [PATCH 009/141] MD5 printout & Xinmeng K916 isp bootloader hash (#3) * output md5 checksums as part of the reading operation * updated xinmeng-k916 bootloader md5 --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + README.md | 2 +- src/main.rs | 6 +++++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c73798f..7d73ed7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "num_threads" version = "0.1.6" @@ -387,6 +393,7 @@ dependencies = [ "ihex", "ihex_ext", "log", + "md5", "phf", "rusb", "simple_logger", diff --git a/Cargo.toml b/Cargo.toml index 8cf8f88..dfeae0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ clap = "4.1" rusb = "0.9" ihex = "3.0" ihex_ext = "1.0" +md5 = "0.7" [dependencies.log] version = "0.4" diff --git a/README.md b/README.md index 85346af..746c3aa 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This is an experimental tool, so use it at your own risk. | Keyboard | ISP MD5 | MCU | Supported | | -------- | ------- | --- | --------- | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅ | -| Xinmeng K916 | unknown | SH68F90 | ❓ | +| Xinmeng K916 | ee175d2a74711f32fdc8b56a9dad95d8 | SH68F90 | ❓ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 (labeled as BYK801) | ✅ | ## Prerequisites diff --git a/src/main.rs b/src/main.rs index 9026f46..74d6335 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ fn cli() -> Command { .subcommand( Command::new("write") .short_flag('w') - .about("Write file into flash.") + .about("Write file (ihex) into flash.") .arg(arg!(input_file: "payload to write into flash")) .arg( arg!(-p --part ) @@ -86,6 +86,10 @@ fn main() { let ihex = result.to_ihex(); let obj = create_object_file_representation(&ihex).unwrap(); + + let digest = md5::compute(&obj); + println!("MD5: {:x}", digest); + fs::write(output_file, obj).expect("Unable to write file"); } Some(("write", sub_matches)) => { From 8b932cabefccd31eff5caa7ac1f1917456fcef83 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 16 Jun 2023 17:29:29 +0200 Subject: [PATCH 010/141] md5sum from binary and xinmeng MD5 fix --- README.md | 3 ++- src/main.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 746c3aa..861bbb7 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,9 @@ This is an experimental tool, so use it at your own risk. | Keyboard | ISP MD5 | MCU | Supported | | -------- | ------- | --- | --------- | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅ | -| Xinmeng K916 | ee175d2a74711f32fdc8b56a9dad95d8 | SH68F90 | ❓ | +| [NuPhy Halo65](https://nuphy.com/products/halo65) | | ❓ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 (labeled as BYK801) | ✅ | +| Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ✅ | ## Prerequisites diff --git a/src/main.rs b/src/main.rs index 74d6335..64b8a4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,13 +83,13 @@ fn main() { let result = ISPDevice::new(part).read_cycle(read_type); + let digest = md5::compute(&result); + println!("MD5: {:x}", digest); + let ihex = result.to_ihex(); let obj = create_object_file_representation(&ihex).unwrap(); - let digest = md5::compute(&obj); - println!("MD5: {:x}", digest); - fs::write(output_file, obj).expect("Unable to write file"); } Some(("write", sub_matches)) => { From f440d726fa9cb0242e8226bf44a0d222de3038dd Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 17 Jun 2023 12:04:58 +0200 Subject: [PATCH 011/141] md5sum for nuphy-halo65 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 861bbb7..8622403 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This is an experimental tool, so use it at your own risk. | Keyboard | ISP MD5 | MCU | Supported | | -------- | ------- | --- | --------- | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅ | -| [NuPhy Halo65](https://nuphy.com/products/halo65) | | ❓ | ✅ | +| [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | ❓ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 (labeled as BYK801) | ✅ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ✅ | From 68c156bdd2b20c90d386311e8ac0481f535b69b7 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 19 Jun 2023 05:07:34 +0200 Subject: [PATCH 012/141] GitHub Actions to build and release (#4) * test workflow * install musl tools * remove musl builds * yml adjustment * generate release notes * action version update * allow writting --- .github/workflows/push.yml | 134 +++++++++++++++++++++++++++++++++---- src/part.rs | 6 ++ 2 files changed, 128 insertions(+), 12 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 4f203bc..96ca6ea 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -1,19 +1,129 @@ -on: [push] +name: Test & Build -name: CI +on: [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +defaults: + run: + # necessary for windows + shell: bash jobs: - build_and_test: - name: Rust project + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Cargo cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ./target + key: test-cargo-registry + - name: List + run: find ./ + - name: Run tests + run: cargo test --verbose + + build: + strategy: + fail-fast: false + matrix: + include: + - TARGET: x86_64-unknown-linux-gnu + OS: ubuntu-latest + - TARGET: aarch64-unknown-linux-gnu + OS: ubuntu-latest + - TARGET: armv7-unknown-linux-gnueabihf + OS: ubuntu-latest + - TARGET: arm-unknown-linux-gnueabihf + OS: ubuntu-latest + - TARGET: x86_64-apple-darwin + OS: macos-latest + - TARGET: x86_64-pc-windows-msvc + OS: windows-latest + needs: test + runs-on: ${{ matrix.OS }} + env: + NAME: sinowealth-kb-tool + TARGET: ${{ matrix.TARGET }} + OS: ${{ matrix.OS }} + steps: + - uses: actions/checkout@v3 + - name: Cargo cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ./target + key: build-cargo-registry-${{matrix.TARGET}} + - name: List + run: find ./ + - name: Install and configure dependencies + run: | + # dependencies are only needed on ubuntu as that's the only place where + # we make cross-compilation + if [[ $OS =~ ^ubuntu.*$ ]]; then + sudo apt-get install -qq crossbuild-essential-arm64 crossbuild-essential-armhf + fi + + # some additional configuration for cross-compilation on linux + cat >>~/.cargo/config < = phf_map! { "nuphy-air60" => PART_NUPHY_AIR60, + "nuphy-halo65" => PART_NUPHY_AIR60, // same as nuphy-air60 "xinmeng-k916" => PART_XINMENG_K916, "re-k70-byk800" => PART_RE_K70_BYK800, }; @@ -43,3 +44,8 @@ impl Part { return self.flash_size / self.page_size; } } + +#[test] +fn test_num_pages() { + assert_eq!(PART_NUPHY_AIR60.num_pages(), 30) +} From 81ec8a88d10e96e8083a323a359d9de11411cc60 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 19 Jun 2023 05:35:03 +0200 Subject: [PATCH 013/141] cargo description update --- Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index dfeae0a..d066a2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,9 @@ [package] name = "sinowealth-kb-tool" +description = """ +A utility for reading and writing flash contents on Sinowealth 8051-based devices +""" +repository = "https://github.com/carlossless/sinowealth-kb-tool" version = "0.0.1" edition = "2021" license = "MIT" From f36c41d621dcaeb9dde50dc495b55436201f5ed7 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 19 Jun 2023 22:11:20 +0200 Subject: [PATCH 014/141] readme updates and verfication code rework --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/isp.rs | 27 ++++++++++++++------------- src/main.rs | 15 +++++++++++---- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d73ed7..9ba122d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,7 +387,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.0.1" +version = "0.0.2" dependencies = [ "clap", "ihex", diff --git a/Cargo.toml b/Cargo.toml index d066a2a..fcd540b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based devices """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.0.1" +version = "0.0.2" edition = "2021" license = "MIT" diff --git a/src/isp.rs b/src/isp.rs index 67acc51..d23fa96 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -2,6 +2,7 @@ use log::*; use std::{thread, time}; use crate::HidDevice; +use crate::VerificationError; use super::part::*; use super::util; @@ -30,6 +31,8 @@ const CMD_ERASE: u8 = 0x45; const XFER_READ_PAGE: u8 = 0x72; const XFER_WRITE_PAGE: u8 = 0x77; +const LJMP_OPCODE: u8 = 0x02; + #[derive(Debug, Clone)] pub enum ReadType { Normal, @@ -92,7 +95,7 @@ impl ISPDevice<'static> { }; } - pub fn write_cycle(&self, firmware: &mut Vec) { + pub fn write_cycle(&self, firmware: &mut Vec) -> Result<(), VerificationError> { let length = firmware.len(); assert_eq!( @@ -101,23 +104,22 @@ impl ISPDevice<'static> { self.part.flash_size, length ); - // this is a bit of an arcane part and I'm not certain why this happens - firmware.copy_within(0..3, length - 5); - firmware[(length - 5)..(length - 2)].fill(0); - self.erase(); self.write(&firmware); let written = self.read(0, self.part.flash_size); + // ARCANE: the ISP will copy the LJMP instruction (if existing) from the end to the very start of memory. + // We need to make the modifications to the expected payload to account for this. + if firmware[length - 5] == LJMP_OPCODE { + firmware[0] = LJMP_OPCODE; + } + firmware.copy_within((length - 4)..(length - 2), 1); // Copy LJMP address + firmware[(length - 5)..(length - 2)].fill(0); // Cleanup + info!("Verifying..."); - match util::verify(&firmware, &written) { - Err(e) => { - error!("{}", e.to_message()); - return; - } - Ok(_) => {} - }; + util::verify(&firmware, &written)?; self.finalize(); + return Ok(()); } pub fn erase_cycle(&self) { @@ -217,7 +219,6 @@ impl ISPDevice<'static> { thread::sleep(time::Duration::from_millis(2000)); } - // TODO: verify what this command does and if it's actually needed fn finalize(&self) { info!("Finalizing..."); let cmd: [u8; COMMAND_LENGTH] = [ diff --git a/src/main.rs b/src/main.rs index 64b8a4f..2d23e6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ use clap::*; use ihex::*; use ihex_ext::*; +use log::*; use simple_logger::SimpleLogger; -use std::fs; +use std::{fs, process}; mod part; pub use part::*; @@ -26,7 +27,7 @@ fn cli() -> Command { .subcommand( Command::new("read") .short_flag('r') - .about("Read flash contents. (ihex)") + .about("Read flash contents. (Intel HEX)") .arg(arg!(output_file: "file to write flash contents to")) .arg( arg!(-p --part ) @@ -42,7 +43,7 @@ fn cli() -> Command { .subcommand( Command::new("write") .short_flag('w') - .about("Write file (ihex) into flash.") + .about("Write file (Intel HEX) into flash.") .arg(arg!(input_file: "payload to write into flash")) .arg( arg!(-p --part ) @@ -107,7 +108,13 @@ fn main() { let (mut firmware, _) = load_file_vec(input_file, part.flash_size, 0).unwrap(); - ISPDevice::new(part).write_cycle(&mut firmware); + match ISPDevice::new(part).write_cycle(&mut firmware) { + Err(e) => { + error!("{}", e.to_message()); + process::exit(1); + } + Ok(_) => {} + }; } Some(("erase", sub_matches)) => { let part_name = sub_matches From c2dd2c27f03f94d4ca3dde48d145d8d02b9a9a90 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 8 Jul 2023 21:46:44 +0200 Subject: [PATCH 015/141] grab version from CARGO_PKG_VERSION --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 2d23e6a..a70537d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ pub use hid::*; fn cli() -> Command { return Command::new("sinowealth-kb-tool") .about("A programming tool for Sinowealth Gaming KB devices") - .version("0.0.1") + .version(env!("CARGO_PKG_VERSION")) .subcommand_required(true) .arg_required_else_help(true) .author("Karolis Stasaitis") From 047efe4e600e16a59e175961d696b628c41bcd7d Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 9 Jul 2023 13:18:01 +0200 Subject: [PATCH 016/141] updated simple_logger to remove atty --- Cargo.lock | 59 ++++++++---------------------------------------------- 1 file changed, 8 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ba122d..f5de9d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,17 +42,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -101,13 +90,13 @@ checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" [[package]] name = "colored" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ - "atty", + "is-terminal", "lazy_static", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -146,15 +135,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.1" @@ -184,7 +164,7 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi", "libc", "windows-sys 0.48.0", ] @@ -195,7 +175,7 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi", "io-lifetimes", "rustix", "windows-sys 0.48.0", @@ -374,11 +354,10 @@ checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" [[package]] name = "simple_logger" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78beb34673091ccf96a8816fce8bfd30d1292c7621ca2bcb5f2ba0fae4f558d" +checksum = "2230cd5c29b815c9b699fb610b49a5ed65588f3509d9f0108be3a885da629333" dependencies = [ - "atty", "colored", "log", "time", @@ -500,28 +479,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.42.0" From 94ac0b6973057f43664a9bf1c34bd1a488359938 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Thu, 20 Jul 2023 00:21:48 +0200 Subject: [PATCH 017/141] more limited and defined ihex handling (#5) * more limited and defined ihex handling * error adjustments * bump version --- Cargo.lock | 37 ++++++++++---------------- Cargo.toml | 4 +-- src/ihex.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/isp.rs | 8 +----- src/main.rs | 34 +++++++++++++----------- src/util.rs | 21 --------------- 6 files changed, 110 insertions(+), 69 deletions(-) create mode 100644 src/ihex.rs diff --git a/Cargo.lock b/Cargo.lock index f5de9d1..6daddfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,17 +147,6 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "365a784774bb381e8c19edb91190a90d7f2625e057b55de2bc0f6b57bc779ff2" -[[package]] -name = "ihex_ext" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96571d3f3e7bd5b9b0bb5de815243e8caf38f5bf40b52e023d61037ed2ec717d" -dependencies = [ - "ihex", - "log", - "thiserror", -] - [[package]] name = "io-lifetimes" version = "1.0.10" @@ -291,18 +280,18 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] @@ -366,16 +355,16 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.0.2" +version = "0.0.3" dependencies = [ "clap", "ihex", - "ihex_ext", "log", "md5", "phf", "rusb", "simple_logger", + "thiserror", ] [[package]] @@ -403,9 +392,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" dependencies = [ "proc-macro2", "quote", @@ -414,22 +403,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.25", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index fcd540b..fbf2dfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based devices """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.0.2" +version = "0.0.3" edition = "2021" license = "MIT" @@ -12,8 +12,8 @@ license = "MIT" clap = "4.1" rusb = "0.9" ihex = "3.0" -ihex_ext = "1.0" md5 = "0.7" +thiserror = "1.0" [dependencies.log] version = "0.4" diff --git a/src/ihex.rs b/src/ihex.rs new file mode 100644 index 0000000..69d7c53 --- /dev/null +++ b/src/ihex.rs @@ -0,0 +1,75 @@ +use ihex::*; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum UnpackingError { + #[error("Unsupported record type")] + UnsupportedRecordType(Record), + #[error("Error while parsing IHEX records")] + Parsing(#[from] ReaderError), + #[error("Address ({0}) greater than binary size ({1})")] + AddressTooHigh(usize, usize), +} + +#[derive(Debug, Error)] +pub enum ConversionError { + #[error("Error while unpacking IHEX into array")] + Unpacking(#[from] UnpackingError), + #[error("Errow while writing IHEX to string")] + Serializing(#[from] WriterError) +} + +pub fn to_ihex(byte_array: Vec) -> Result { + let mut result: Vec = vec![]; + for (i, chunk) in byte_array.chunks(16).enumerate() { + result.push(Record::Data { + offset: (i as u16) * 16, + value: chunk.to_vec(), + }); + } + result.push(Record::EndOfFile); + return create_object_file_representation(&result) + .map_err(ConversionError::from); +} + +pub fn from_ihex(ihex_string: &str, max_length: usize) -> Result, ConversionError> { + let mut reader = Reader::new(ihex_string); + return unpack_records(&mut reader, max_length) + .map_err(ConversionError::from); +} + +fn unpack_records( + records: &mut impl Iterator>, + max_length: usize, +) -> Result, UnpackingError> { + let mut result: Vec = vec![]; + for rec in records { + match rec { + Ok(rec) => match rec { + Record::Data { offset, value } => { + let end_addr = offset as usize + value.len(); + if end_addr > max_length { + return Err(UnpackingError::AddressTooHigh(end_addr, max_length)); + } + if end_addr > result.len() { + result.resize(end_addr, 0); + } + + for (n, b) in value.iter().enumerate() { + result[offset as usize + n] = *b; + } + } + Record::ExtendedSegmentAddress(_base) => { + return Err(UnpackingError::UnsupportedRecordType(rec)) + } + Record::ExtendedLinearAddress(_base) => { + return Err(UnpackingError::UnsupportedRecordType(rec)) + } + Record::EndOfFile => break, + Record::StartLinearAddress(_) | Record::StartSegmentAddress { .. } => {} + }, + Err(err) => return Err(UnpackingError::Parsing(err)), + } + } + return Ok(result); +} diff --git a/src/isp.rs b/src/isp.rs index d23fa96..119937d 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -98,18 +98,12 @@ impl ISPDevice<'static> { pub fn write_cycle(&self, firmware: &mut Vec) -> Result<(), VerificationError> { let length = firmware.len(); - assert_eq!( - self.part.flash_size, length, - "Wrong firmware size. Expected {}, but got {}", - self.part.flash_size, length - ); - self.erase(); self.write(&firmware); let written = self.read(0, self.part.flash_size); // ARCANE: the ISP will copy the LJMP instruction (if existing) from the end to the very start of memory. - // We need to make the modifications to the expected payload to account for this. + // We need to make modifications to the expected payload to account for this. if firmware[length - 5] == LJMP_OPCODE { firmware[0] = LJMP_OPCODE; } diff --git a/src/main.rs b/src/main.rs index a70537d..ff5d838 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,20 @@ use clap::*; -use ihex::*; -use ihex_ext::*; use log::*; use simple_logger::SimpleLogger; +use std::io::Read; use std::{fs, process}; mod part; -pub use part::*; - mod isp; -pub use isp::*; - +mod hid; mod util; -pub use util::*; +mod ihex; -mod hid; -pub use hid::*; +pub use crate::part::*; +pub use crate::isp::*; +pub use crate::hid::*; +pub use crate::ihex::*; +pub use crate::util::*; fn cli() -> Command { return Command::new("sinowealth-kb-tool") @@ -87,11 +86,8 @@ fn main() { let digest = md5::compute(&result); println!("MD5: {:x}", digest); - let ihex = result.to_ihex(); - - let obj = create_object_file_representation(&ihex).unwrap(); - - fs::write(output_file, obj).expect("Unable to write file"); + let ihex = to_ihex(result).expect("Failed converting to IHEX"); + fs::write(output_file, ihex).expect("Unable to write file"); } Some(("write", sub_matches)) => { let input_file = sub_matches @@ -106,7 +102,15 @@ fn main() { let part = PARTS.get(part_name).unwrap(); - let (mut firmware, _) = load_file_vec(input_file, part.flash_size, 0).unwrap(); + let mut file = fs::File::open(input_file).unwrap(); + let mut file_buf = Vec::new(); + file.read_to_end(&mut file_buf).unwrap(); + let file_str = String::from_utf8_lossy(&file_buf[..]); + let mut firmware = from_ihex(&file_str, part.flash_size).unwrap(); + + if firmware.len() < part.flash_size { + firmware.resize(part.flash_size, 0); + } match ISPDevice::new(part).write_cycle(&mut firmware) { Err(e) => { diff --git a/src/util.rs b/src/util.rs index f9ceb60..1d3098d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,24 +1,3 @@ -use ihex::*; - -pub trait IHexConversion { - // fn from_ihex() -> Self; - fn to_ihex(&self) -> Vec; -} - -impl IHexConversion for Vec { - fn to_ihex(&self) -> Vec { - let mut result: Vec = vec![]; - for (i, chunk) in self.chunks(16).enumerate() { - result.push(Record::Data { - offset: (i as u16) * 16, - value: chunk.to_vec(), - }); - } - result.push(Record::EndOfFile); - return result; - } -} - #[derive(Debug, Clone)] pub enum VerificationError { ByteMismatch(usize, u8, u8), From eb863311bca50b968baf71c86e61d4628e06b66e Mon Sep 17 00:00:00 2001 From: Mohamed Gaber Date: Tue, 8 Aug 2023 19:31:33 +0300 Subject: [PATCH 018/141] Air75 Read Support Confirmed (#6) * Confirmed following functionality with Air75: * Reading bootloader, hash matches other NuPhy products * Reading full-flash * Added Air75 part (another Air60 reference) * Added minimum Rust version (let-else not stabilized before Rust 1.65 w/ PR https://github.com/rust-lang/rust/pull/93628) --- Cargo.toml | 1 + README.md | 3 +++ src/part.rs | 1 + 3 files changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index fbf2dfd..f1c0137 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ repository = "https://github.com/carlossless/sinowealth-kb-tool" version = "0.0.3" edition = "2021" license = "MIT" +rust-version = "1.65" [dependencies] clap = "4.1" diff --git a/README.md b/README.md index 8622403..4038307 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,13 @@ This is an experimental tool, so use it at your own risk. | Keyboard | ISP MD5 | MCU | Supported | | -------- | ------- | --- | --------- | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅ | +| [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅‡ | | [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | ❓ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 (labeled as BYK801) | ✅ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ✅ | +‡ Reading tested, writing untested. + ## Prerequisites ### Linux diff --git a/src/part.rs b/src/part.rs index 54e0d60..c776304 100644 --- a/src/part.rs +++ b/src/part.rs @@ -35,6 +35,7 @@ pub const PART_RE_K70_BYK800: Part = Part { pub static PARTS: phf::Map<&'static str, Part> = phf_map! { "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-halo65" => PART_NUPHY_AIR60, // same as nuphy-air60 + "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 "xinmeng-k916" => PART_XINMENG_K916, "re-k70-byk800" => PART_RE_K70_BYK800, }; From 31be47020477443a86399248499790a2f9ac24ce Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 20 Aug 2023 23:48:24 +0200 Subject: [PATCH 019/141] NuPhy Air96 (#9) --- README.md | 1 + src/part.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4038307..ef9b7bd 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This is an experimental tool, so use it at your own risk. | -------- | ------- | --- | --------- | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅ | | [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅‡ | +| [NuPhy Air96](https://nuphy.com/products/air96-wireless-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅‡ | | [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | ❓ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 (labeled as BYK801) | ✅ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ✅ | diff --git a/src/part.rs b/src/part.rs index c776304..f6bc4ff 100644 --- a/src/part.rs +++ b/src/part.rs @@ -34,8 +34,9 @@ pub const PART_RE_K70_BYK800: Part = Part { pub static PARTS: phf::Map<&'static str, Part> = phf_map! { "nuphy-air60" => PART_NUPHY_AIR60, - "nuphy-halo65" => PART_NUPHY_AIR60, // same as nuphy-air60 "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 + "nuphy-air96" => PART_NUPHY_AIR60, // same as nuphy-air60 + "nuphy-halo65" => PART_NUPHY_AIR60, // same as nuphy-air60 "xinmeng-k916" => PART_XINMENG_K916, "re-k70-byk800" => PART_RE_K70_BYK800, }; From 0fa7bb5907c5e96f8e74d18a9d2b548b7e856439 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 27 Aug 2023 18:42:02 +0100 Subject: [PATCH 020/141] hidapi instead of libusb (windows support) (#10) * reverted back to using hidapi instead of libusb, macos works * set exclusive only for macos * selecting hid-device path * select only col4 * opening path * conditionals for windows * use a third request device * identified some hid device paths * try using two devices * read and write use the same channel * instantiate 2 devices instead of 3 * account for multiple devices * cargo fmt * nix flake updates and rustfmt * reorganising error mapping * clippy fixes * find instead of filter and next * more error handling * d path fix * using linux-static-libusb * leaving only request device for macos, linux * remove usage page check * remove warning * switch back to nightly * add libusb * install libusb for testing also * remove arm targets while lack of cross-compiled libusb is causing problems * corrected package name * run workflow on push only * return not found for win * warning on return * increase wait to 2secs --- .github/workflows/push.yml | 23 +-- Cargo.lock | 336 ++++++++++++++++--------------------- Cargo.toml | 6 +- flake.lock | 83 ++++++++- flake.nix | 13 +- rustfmt.toml | 2 + shell.nix | 10 +- src/hid.rs | 70 -------- src/ihex.rs | 10 +- src/isp.rs | 323 +++++++++++++++++++++++++++-------- src/main.rs | 76 +++++---- src/part.rs | 2 +- src/util.rs | 46 +++-- 13 files changed, 575 insertions(+), 425 deletions(-) create mode 100644 rustfmt.toml delete mode 100644 src/hid.rs diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 96ca6ea..796f67f 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -1,6 +1,6 @@ name: Test & Build -on: [push, pull_request] +on: [push] env: CARGO_TERM_COLOR: always @@ -24,6 +24,9 @@ jobs: key: test-cargo-registry - name: List run: find ./ + - name: Install and configure dependencies + run: | + sudo apt-get install -qq libusb-1.0.0-dev - name: Run tests run: cargo test --verbose @@ -34,12 +37,6 @@ jobs: include: - TARGET: x86_64-unknown-linux-gnu OS: ubuntu-latest - - TARGET: aarch64-unknown-linux-gnu - OS: ubuntu-latest - - TARGET: armv7-unknown-linux-gnueabihf - OS: ubuntu-latest - - TARGET: arm-unknown-linux-gnueabihf - OS: ubuntu-latest - TARGET: x86_64-apple-darwin OS: macos-latest - TARGET: x86_64-pc-windows-msvc @@ -66,18 +63,8 @@ jobs: # dependencies are only needed on ubuntu as that's the only place where # we make cross-compilation if [[ $OS =~ ^ubuntu.*$ ]]; then - sudo apt-get install -qq crossbuild-essential-arm64 crossbuild-essential-armhf + sudo apt-get install -qq libusb-1.0-0-dev fi - - # some additional configuration for cross-compilation on linux - cat >>~/.cargo/config <, - interface: u8, -} - -impl HidDevice { - pub fn open(vendor_id: u16, product_id: u16) -> Option { - let Some(mut handle) = open_device_with_vid_pid(vendor_id, product_id) else { - return None; - }; - - let interface = handle - .device() - .config_descriptor(0) - .unwrap() - .interfaces() - .map(|i| i.descriptors()) - .flatten() - .filter(|i| i.class_code() == constants::LIBUSB_CLASS_HID) - .map(|i| i.interface_number()) - .last() - .unwrap(); - - if supports_detach_kernel_driver() { - handle.set_auto_detach_kernel_driver(true).unwrap(); - } - - // don't try claiming the interface on macos, it should not be necessary and will fail - #[cfg(not(any(target_os = "macos")))] - handle.claim_interface(interface).unwrap(); - - return Some(HidDevice { - handle: handle, - interface: interface, - }); - } - - pub fn get_feature_report(self: &Self, buf: &mut [u8]) -> Result { - let report_number = buf[0] as u16; - - return self.handle.read_control( - constants::LIBUSB_REQUEST_TYPE_CLASS - | constants::LIBUSB_RECIPIENT_INTERFACE - | constants::LIBUSB_ENDPOINT_IN, - 0x01, - (3/*HID feature*/ << 8) | report_number, - self.interface as u16, - buf, - time::Duration::from_millis(1000), - ); - } - - pub fn send_feature_report(self: &Self, buf: &[u8]) -> Result { - let report_number = buf[0] as u16; - - return self.handle.write_control( - constants::LIBUSB_REQUEST_TYPE_CLASS - | constants::LIBUSB_RECIPIENT_INTERFACE - | constants::LIBUSB_ENDPOINT_OUT, - 0x09, - (3/*HID feature*/ << 8) | report_number, - self.interface as u16, - buf, - time::Duration::from_millis(1000), - ); - } -} diff --git a/src/ihex.rs b/src/ihex.rs index 69d7c53..0e2dc07 100644 --- a/src/ihex.rs +++ b/src/ihex.rs @@ -16,7 +16,7 @@ pub enum ConversionError { #[error("Error while unpacking IHEX into array")] Unpacking(#[from] UnpackingError), #[error("Errow while writing IHEX to string")] - Serializing(#[from] WriterError) + Serializing(#[from] WriterError), } pub fn to_ihex(byte_array: Vec) -> Result { @@ -28,14 +28,12 @@ pub fn to_ihex(byte_array: Vec) -> Result { }); } result.push(Record::EndOfFile); - return create_object_file_representation(&result) - .map_err(ConversionError::from); + create_object_file_representation(&result).map_err(ConversionError::from) } pub fn from_ihex(ihex_string: &str, max_length: usize) -> Result, ConversionError> { let mut reader = Reader::new(ihex_string); - return unpack_records(&mut reader, max_length) - .map_err(ConversionError::from); + unpack_records(&mut reader, max_length).map_err(ConversionError::from) } fn unpack_records( @@ -71,5 +69,5 @@ fn unpack_records( Err(err) => return Err(UnpackingError::Parsing(err)), } } - return Ok(result); + Ok(result) } diff --git a/src/isp.rs b/src/isp.rs index 119937d..600d577 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -1,16 +1,15 @@ -use log::*; use std::{thread, time}; -use crate::HidDevice; +use hidapi::DeviceInfo; +use log::*; +use thiserror::Error; + +use super::{part::*, util}; use crate::VerificationError; -use super::part::*; -use super::util; +extern crate hidapi; -pub struct ISPDevice<'a> { - device: HidDevice, - part: &'a Part, -} +use hidapi::{HidApi, HidDevice, HidError}; const MAX_RETRIES: usize = 10; @@ -23,7 +22,7 @@ const REPORT_ID_CMD: u8 = 0x05; const REPORT_ID_XFER: u8 = 0x06; const CMD_ISP_MODE: u8 = 0x75; -const CMD_MAGIC_SAUCE: u8 = 0x55; // unsure how this command works, hence the name +const CMD_MAGIC_SAUCE: u8 = 0x55; // uncertain how this command works, hence the name const CMD_INIT_READ: u8 = 0x52; const CMD_INIT_WRITE: u8 = 0x57; const CMD_ERASE: u8 = 0x45; @@ -33,6 +32,25 @@ const XFER_WRITE_PAGE: u8 = 0x77; const LJMP_OPCODE: u8 = 0x02; +pub struct ISPDevice<'a> { + request_device: HidDevice, + #[cfg(target_os = "windows")] + data_device: HidDevice, + part: &'a Part, +} + +#[derive(Debug, Error)] +pub enum ISPError { + #[error("Duplicate devices found")] + DuplicateDevices(String, String), + #[error("Device not found")] + NotFound, + #[error(transparent)] + HidError(#[from] HidError), + #[error(transparent)] + VerificationError(#[from] VerificationError), +} + #[derive(Debug, Clone)] pub enum ReadType { Normal, @@ -40,90 +58,237 @@ pub enum ReadType { Full, } +struct HIDDevices { + request: HidDevice, + #[cfg(target_os = "windows")] + data: HidDevice, +} + impl ISPDevice<'static> { - pub fn new(part: &'static Part) -> Self { - let device = Self::find_isp_device(part); - return Self { - device: device, - part: &part, - }; + pub fn new(part: &'static Part) -> Result { + let devices = Self::find_isp_device(part)?; + Ok(Self { + request_device: devices.request, + #[cfg(target_os = "windows")] + data_device: devices.data, + part, + }) } - fn find_isp_device(part: &Part) -> HidDevice { - for attempt in 1..MAX_RETRIES + 1 { - if attempt > 1 { - info!("Retrying... Attempt {}/{}", attempt, MAX_RETRIES); - } + fn hidapi() -> HidApi { + let api = HidApi::new().unwrap(); - let Some(device) = HidDevice::open(part.vendor_id, part.product_id) else { - info!("No KB found. Trying bootloader directly..."); - let isp_device = HidDevice::open(GAMING_KB_VENDOR_ID, GAMING_KB_PRODUCT_ID).unwrap(); - info!("Connected!"); - return isp_device; - }; + #[cfg(target_os = "macos")] + api.set_open_exclusive(false); // macOS will throw a privilege violation error otherwise + + api + } - info!("Found Device. Entering ISP mode..."); - Self::enter_isp_mode(&device); + fn open_isp_devices() -> Result { + let api = Self::hidapi(); - info!("Waiting for bootloader device..."); - thread::sleep(time::Duration::from_millis(1000)); + let mut request_device: Option<&DeviceInfo> = None; + #[cfg(target_os = "windows")] + let mut data_device: Option<&DeviceInfo> = None; - let Some(isp_device) = HidDevice::open(GAMING_KB_VENDOR_ID, GAMING_KB_PRODUCT_ID) else { - info!("Device didn't come up..."); + for device_info in api.device_list() { + if !(device_info.vendor_id() == GAMING_KB_VENDOR_ID + && device_info.product_id() == GAMING_KB_PRODUCT_ID) + { continue; + } + + let path = device_info.path(); + let path_str = path.to_str().unwrap(); + + debug!("Enumerating: {}", path_str); + + #[cfg(target_os = "windows")] + { + // Windows requires that we use specific devices for requests and data + // https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/hidclass-hardware-ids-for-top-level-collections + if path_str.contains("Col02") { + if let Some(request_device) = request_device { + return Err(ISPError::DuplicateDevices( + request_device.path().to_str().unwrap().to_owned(), + path_str.to_owned(), + )); + } + request_device = Some(device_info); + continue; + } + + if path_str.contains("Col03") { + if let Some(data_device) = data_device { + return Err(ISPError::DuplicateDevices( + data_device.path().to_str().unwrap().to_owned(), + path_str.to_owned(), + )); + } + data_device = Some(device_info); + continue; + } }; - info!("Connected!"); + #[cfg(not(target_os = "windows"))] + if let Some(request_device) = request_device { + if request_device.path() != path { + warn!("Duplicate device found. Only the first one will be used"); + } + continue; + } else { + request_device = Some(device_info); + continue; + }; + } + + if let Some(request_device) = request_device { + debug!("Request device: {:?}", request_device.path()); + #[cfg(target_os = "windows")] + if let Some(data_device) = data_device { + debug!("Data device: {:?}", data_device.path()); + return Ok(HIDDevices { + request: api.open_path(request_device.path()).unwrap(), + data: api.open_path(data_device.path()).unwrap(), + }); + } else { + return Err(ISPError::NotFound); + } + + #[cfg(not(target_os = "windows"))] + return Ok(HIDDevices { + request: api.open_path(request_device.path()).unwrap(), + }); + } else { + Err(ISPError::NotFound) + } + } + + fn switch_kb_device(part: &Part) -> Result { + let api = Self::hidapi(); + + info!( + "Looking for vId:{:#06x} pId:{:#06x}", + part.vendor_id, part.product_id + ); + + let request_device_info = api + .device_list() + .filter(|d| { + d.vendor_id() == part.vendor_id + && d.product_id() == part.product_id + && d.interface_number() == 1 + }) + .find(|_d| { + #[cfg(target_os = "windows")] + { + return String::from_utf8_lossy(_d.path().to_bytes()) + .to_string() + .contains("Col05"); + } + #[cfg(not(target_os = "windows"))] + true + }); + + let Some(request_device_info) = request_device_info else { + info!("Regular device didn't come up..."); + return Err(ISPError::NotFound); + }; + + debug!("Opening: {:?}", request_device_info.path()); + + let device = api.open_path(request_device_info.path()).unwrap(); + + info!("Found Regular evice. Entering ISP mode..."); + Self::enter_isp_mode(&device)?; + + info!("Waiting for ISP device..."); + thread::sleep(time::Duration::from_millis(2000)); + + let Ok(isp_device) = Self::open_isp_devices() else { + info!("ISP device didn't come up..."); + return Err(ISPError::NotFound); + }; + + Ok(isp_device) + } + + fn find_isp_device(part: &Part) -> Result { + Self::find_isp_device_retry(part, MAX_RETRIES) + } - return isp_device; + fn find_isp_device_retry(part: &Part, retries: usize) -> Result { + for attempt in 1..retries + 1 { + if attempt > 1 { + info!("Retrying... Attempt {}/{}", attempt, retries); + } + + if let Ok(devices) = Self::switch_kb_device(part) { + info!("Connected!"); + return Ok(devices); + } + info!("Regular device not found. Trying ISP device..."); + if let Ok(devices) = Self::open_isp_devices() { + info!("Connected!"); + return Ok(devices); + } } - panic!("Couldn't find ISP device"); + Err(ISPError::NotFound) } - fn enter_isp_mode(handle: &HidDevice) { + fn enter_isp_mode(handle: &HidDevice) -> Result<(), ISPError> { let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_ISP_MODE, 0x00, 0x00, 0x00, 0x00]; - let _ = handle.send_feature_report(&cmd); // ignore errors, many might be encountered here + handle.send_feature_report(&cmd)?; + Ok(()) } - pub fn read_cycle(&self, read_type: ReadType) -> Vec { - self.magic_sauce(); + pub fn read_cycle(&self, read_type: ReadType) -> Result, ISPError> { + self.magic_sauce()?; - return match read_type { + match read_type { ReadType::Normal => self.read(0, self.part.flash_size), ReadType::Bootloader => self.read(self.part.flash_size, self.part.bootloader_size), ReadType::Full => self.read(0, self.part.flash_size + self.part.bootloader_size), - }; + } } - pub fn write_cycle(&self, firmware: &mut Vec) -> Result<(), VerificationError> { + pub fn write_cycle(&self, firmware: &mut Vec) -> Result<(), ISPError> { let length = firmware.len(); - self.erase(); - self.write(&firmware); - let written = self.read(0, self.part.flash_size); + self.erase()?; + self.write(firmware)?; + let written = self.read(0, self.part.flash_size)?; // ARCANE: the ISP will copy the LJMP instruction (if existing) from the end to the very start of memory. // We need to make modifications to the expected payload to account for this. if firmware[length - 5] == LJMP_OPCODE { firmware[0] = LJMP_OPCODE; + firmware.copy_within((length - 4)..(length - 2), 1); // Copy LJMP address + firmware[(length - 5)..(length - 2)].fill(0); // Cleanup } - firmware.copy_within((length - 4)..(length - 2), 1); // Copy LJMP address - firmware[(length - 5)..(length - 2)].fill(0); // Cleanup info!("Verifying..."); - util::verify(&firmware, &written)?; - self.finalize(); - return Ok(()); + util::verify(firmware, &written).map_err(ISPError::from)?; + self.finalize()?; + Ok(()) } - pub fn erase_cycle(&self) { + pub fn erase_cycle(&self) -> Result<(), ISPError> { info!("Erasing..."); - self.erase(); - self.finalize(); + self.erase()?; + self.finalize()?; + Ok(()) + } + + fn data_device(&self) -> &HidDevice { + #[cfg(target_os = "windows")] + return &self.data_device; + #[cfg(not(target_os = "windows"))] + &self.request_device } /// Allows firmware to be read prior to erasing it - fn magic_sauce(&self) { + fn magic_sauce(&self) -> Result<(), ISPError> { let cmd: [u8; COMMAND_LENGTH] = [ REPORT_ID_CMD, CMD_MAGIC_SAUCE, @@ -133,10 +298,11 @@ impl ISPDevice<'static> { (self.part.flash_size >> 8) as u8, ]; - self.device.send_feature_report(&cmd).unwrap(); + self.request_device.send_feature_report(&cmd)?; + Ok(()) } - fn read(&self, start_addr: usize, length: usize) -> Vec { + fn read(&self, start_addr: usize, length: usize) -> Result, ISPError> { let cmd: [u8; COMMAND_LENGTH] = [ REPORT_ID_CMD, CMD_INIT_READ, @@ -145,7 +311,7 @@ impl ISPDevice<'static> { (length & 0xff) as u8, (length >> 8) as u8, ]; - self.device.send_feature_report(&cmd).unwrap(); + self.request_device.send_feature_report(&cmd)?; let page_size = self.part.page_size; let num_page = length / page_size; @@ -156,21 +322,24 @@ impl ISPDevice<'static> { i, start_addr + i * page_size ); - self.read_page(&mut result); + self.read_page(&mut result)?; } - return result; + Ok(result) } - fn read_page(&self, buf: &mut Vec) { + fn read_page(&self, buf: &mut Vec) -> Result<(), ISPError> { let page_size = self.part.page_size; let mut xfer_buf: Vec = vec![0; page_size + 2]; xfer_buf[0] = REPORT_ID_XFER; xfer_buf[1] = XFER_READ_PAGE; - self.device.get_feature_report(&mut xfer_buf).unwrap(); + self.data_device() + .get_feature_report(&mut xfer_buf) + .map_err(ISPError::from)?; buf.extend_from_slice(&xfer_buf[2..(page_size + 2)]); + Ok(()) } - fn write(&self, buffer: &Vec) { + fn write(&self, buffer: &[u8]) -> Result<(), ISPError> { info!("Writing..."); let cmd: [u8; COMMAND_LENGTH] = [ REPORT_ID_CMD, @@ -181,25 +350,31 @@ impl ISPDevice<'static> { (self.part.flash_size >> 8) as u8, ]; - self.device.send_feature_report(&cmd).unwrap(); + self.request_device + .send_feature_report(&cmd) + .map_err(ISPError::from)?; let page_size = self.part.page_size; for i in 0..self.part.num_pages() { debug!("Writting page {} @ offset {:#06x}", i, i * page_size); - self.write_page(&buffer[(i * page_size)..((i + 1) * page_size)]); + self.write_page(&buffer[(i * page_size)..((i + 1) * page_size)])?; } + Ok(()) } - fn write_page(&self, buf: &[u8]) { + fn write_page(&self, buf: &[u8]) -> Result<(), ISPError> { let page_size = self.part.page_size; let mut xfer_buf: Vec = vec![0; page_size + 2]; xfer_buf[0] = REPORT_ID_XFER; xfer_buf[1] = XFER_WRITE_PAGE; - xfer_buf[2..page_size + 2].clone_from_slice(&buf); - self.device.send_feature_report(&xfer_buf).unwrap(); + xfer_buf[2..page_size + 2].clone_from_slice(buf); + self.data_device() + .send_feature_report(&xfer_buf) + .map_err(ISPError::from)?; + Ok(()) } - fn erase(&self) { + fn erase(&self) -> Result<(), ISPError> { info!("Erasing..."); let cmd: [u8; COMMAND_LENGTH] = [ REPORT_ID_CMD, @@ -209,11 +384,14 @@ impl ISPDevice<'static> { CMD_ERASE, CMD_ERASE, ]; - self.device.send_feature_report(&cmd).unwrap(); + self.request_device + .send_feature_report(&cmd) + .map_err(ISPError::from)?; thread::sleep(time::Duration::from_millis(2000)); + Ok(()) } - fn finalize(&self) { + fn finalize(&self) -> Result<(), ISPError> { info!("Finalizing..."); let cmd: [u8; COMMAND_LENGTH] = [ REPORT_ID_CMD, @@ -223,6 +401,9 @@ impl ISPDevice<'static> { CMD_MAGIC_SAUCE, CMD_MAGIC_SAUCE, ]; - self.device.send_feature_report(&cmd).unwrap(); + self.request_device + .send_feature_report(&cmd) + .map_err(ISPError::from)?; + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index ff5d838..9185aa9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,32 @@ +use std::{ + fs, + io::{self, Read}, + process::ExitCode, +}; + use clap::*; use log::*; use simple_logger::SimpleLogger; -use std::io::Read; -use std::{fs, process}; +use thiserror::Error; -mod part; mod isp; -mod hid; -mod util; +mod part; +// mod hid; mod ihex; +mod util; -pub use crate::part::*; -pub use crate::isp::*; -pub use crate::hid::*; -pub use crate::ihex::*; -pub use crate::util::*; +// pub use crate::hid::*; +pub use crate::{ihex::*, isp::*, part::*, util::*}; + +#[derive(Debug, Error)] +pub enum CLIError { + #[error(transparent)] + IOError(#[from] io::Error), + #[error(transparent)] + ISPError(#[from] ISPError), + #[error(transparent)] + IHEXError(#[from] ConversionError), +} fn cli() -> Command { return Command::new("sinowealth-kb-tool") @@ -30,7 +42,7 @@ fn cli() -> Command { .arg(arg!(output_file: "file to write flash contents to")) .arg( arg!(-p --part ) - .value_parser(PARTS.keys().map(|&s| s).collect::>()) + .value_parser(PARTS.keys().copied().collect::>()) .required(true), ) .arg(arg!(-b --bootloader "read only booloader").conflicts_with("full")) @@ -46,13 +58,13 @@ fn cli() -> Command { .arg(arg!(input_file: "payload to write into flash")) .arg( arg!(-p --part ) - .value_parser(PARTS.keys().map(|&s| s).collect::>()) + .value_parser(PARTS.keys().copied().collect::>()) .required(true), ), ); } -fn main() { +fn err_main() -> Result<(), CLIError> { SimpleLogger::new().init().unwrap(); let matches = cli().get_matches(); @@ -81,13 +93,14 @@ fn main() { _ => ReadType::Normal, }; - let result = ISPDevice::new(part).read_cycle(read_type); + let isp = ISPDevice::new(part).map_err(CLIError::from)?; + let result = isp.read_cycle(read_type).map_err(CLIError::from)?; let digest = md5::compute(&result); - println!("MD5: {:x}", digest); + info!("MD5: {:x}", digest); - let ihex = to_ihex(result).expect("Failed converting to IHEX"); - fs::write(output_file, ihex).expect("Unable to write file"); + let ihex = to_ihex(result).map_err(CLIError::from)?; + fs::write(output_file, ihex).map_err(CLIError::from)?; } Some(("write", sub_matches)) => { let input_file = sub_matches @@ -102,23 +115,18 @@ fn main() { let part = PARTS.get(part_name).unwrap(); - let mut file = fs::File::open(input_file).unwrap(); + let mut file = fs::File::open(input_file).map_err(CLIError::from)?; let mut file_buf = Vec::new(); - file.read_to_end(&mut file_buf).unwrap(); + file.read_to_end(&mut file_buf).map_err(CLIError::from)?; let file_str = String::from_utf8_lossy(&file_buf[..]); - let mut firmware = from_ihex(&file_str, part.flash_size).unwrap(); + let mut firmware = from_ihex(&file_str, part.flash_size).map_err(CLIError::from)?; if firmware.len() < part.flash_size { firmware.resize(part.flash_size, 0); } - match ISPDevice::new(part).write_cycle(&mut firmware) { - Err(e) => { - error!("{}", e.to_message()); - process::exit(1); - } - Ok(_) => {} - }; + let isp = ISPDevice::new(part).map_err(CLIError::from)?; + isp.write_cycle(&mut firmware).map_err(CLIError::from)?; } Some(("erase", sub_matches)) => { let part_name = sub_matches @@ -128,8 +136,20 @@ fn main() { let part = PARTS.get(part_name).unwrap(); - ISPDevice::new(part).erase_cycle(); + let isp = ISPDevice::new(part).map_err(CLIError::from)?; + isp.erase_cycle().map_err(CLIError::from)?; } _ => unreachable!(), } + Ok(()) +} + +fn main() -> ExitCode { + match err_main() { + Ok(_) => ExitCode::SUCCESS, + Err(e) => { + error!("{}", e.to_string()); + ExitCode::FAILURE + } + } } diff --git a/src/part.rs b/src/part.rs index f6bc4ff..c1b02e6 100644 --- a/src/part.rs +++ b/src/part.rs @@ -43,7 +43,7 @@ pub static PARTS: phf::Map<&'static str, Part> = phf_map! { impl Part { pub fn num_pages(&self) -> usize { - return self.flash_size / self.page_size; + self.flash_size / self.page_size } } diff --git a/src/util.rs b/src/util.rs index 1d3098d..9b86c99 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,38 +1,34 @@ -#[derive(Debug, Clone)] -pub enum VerificationError { - ByteMismatch(usize, u8, u8), - LengthMismatch(usize, usize), -} +use thiserror::Error; -impl VerificationError { - pub fn to_message(&self) -> String { - return match self { - Self::LengthMismatch(expected, actual) => { - format!("LENGTH MISMATCH {} {}", expected, actual) - } - Self::ByteMismatch(addr, expected, actual) => { - format!( - "FIRMWARE MISMATCH @ 0x{:04x} --- {:02x} != {:02x}", - addr, expected, actual - ) - } - }; - } +#[derive(Debug, Clone, Error)] +pub enum VerificationError { + #[error("Firmware Mismatch @ {addr:#06x} --- {expected:#04x} != {actual:#04x}")] + ByteMismatch { + addr: usize, + expected: u8, + actual: u8, + }, + #[error("Length Mismatch {expected} {actual}")] + LengthMismatch { expected: usize, actual: usize }, } pub fn verify(expected: &Vec, actual: &Vec) -> Result<(), VerificationError> { if expected.len() != actual.len() { - return Err(VerificationError::LengthMismatch( - expected.len(), - actual.len(), - )); + return Err(VerificationError::LengthMismatch { + expected: expected.len(), + actual: actual.len(), + }); } for i in 0..expected.len() { if expected[i] != actual[i] { - return Err(VerificationError::ByteMismatch(i, expected[i], actual[i])); + return Err(VerificationError::ByteMismatch { + addr: i, + expected: expected[i], + actual: actual[i], + }); } } - return Ok(()); + Ok(()) } From 6dce877f609040eff0dd0c30af914e6ccb9001ef Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Tue, 29 Aug 2023 23:32:18 +0200 Subject: [PATCH 021/141] added status badges --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ef9b7bd..6f1e0b6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # sinowealth-kb-tool +[![crate](https://img.shields.io/crates/v/sinowealth-kb-tool.svg)](https://crates.io/crates/sinowealth-kb-tool) [![ci](https://github.com/carlossless/sinowealth-kb-tool/actions/workflows/push.yml/badge.svg)](https://github.com/carlossless/sinowealth-kb-tool/actions/workflows/push.yml) + A utility for reading and writing flash contents on Sinowealth 8051-based devices (keyboards and mice) since they all seem to have similar ISP bootloaders. ## Disclaimer From 019c8741d01c47809760d946d876e0d38c0da663 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 30 Sep 2023 20:25:50 +0200 Subject: [PATCH 022/141] distribution through nix flake (#11) Changes here enable easy distribution of this tool as a nix flake. Usage example: ```nix { inputs = { flake-utils.url = "github:numtide/flake-utils"; sinowealth-kb-tool.url = "github:carlossless/sinowealth-kb-tool"; }; outputs = { self, nixpkgs, flake-utils, sinowealth-kb-tool }: flake-utils.lib.eachDefaultSystem ( system: let pkgs = nixpkgs.legacyPackages.${system}; in { devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ sinowealth-kb-tool.packages."${system}".default ]; }; } ); } ``` --- .gitignore | 4 +-- flake.lock | 39 +++++++++++++++++++++-- flake.nix | 75 +++++++++++++++++++++++++++++++++++++-------- rust-toolchain.toml | 11 +++++++ shell.nix | 17 ---------- 5 files changed, 112 insertions(+), 34 deletions(-) create mode 100644 rust-toolchain.toml delete mode 100644 shell.nix diff --git a/.gitignore b/.gitignore index 3129e44..b462d9f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .DS_Store -/.direnv/ +/.direnv +/result /private - /target diff --git a/flake.lock b/flake.lock index 7881dcd..297ea2d 100644 --- a/flake.lock +++ b/flake.lock @@ -36,7 +36,39 @@ "type": "github" } }, + "naersk": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1694081375, + "narHash": "sha256-vzJXOUnmkMCm3xw8yfPP5m8kypQ3BhAIRe4RRCWpzy8=", + "owner": "nix-community", + "repo": "naersk", + "rev": "3f976d822b7b37fc6fb8e6f157c2dd05e7e94e89", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, "nixpkgs": { + "locked": { + "lastModified": 1695837737, + "narHash": "sha256-KcqmJ5hNacLuE7fkz5586kp/vt4NLo6+Prq3DMgrxpQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "517501bcf14ae6ec47efd6a17dda0ca8e6d866f9", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { "locked": { "lastModified": 1693003285, "narHash": "sha256-5nm4yrEHKupjn62MibENtfqlP6pWcRTuSKrMiH9bLkc=", @@ -52,7 +84,7 @@ "type": "github" } }, - "nixpkgs_2": { + "nixpkgs_3": { "locked": { "lastModified": 1681358109, "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", @@ -71,14 +103,15 @@ "root": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", + "naersk": "naersk", + "nixpkgs": "nixpkgs_2", "rust-overlay": "rust-overlay" } }, "rust-overlay": { "inputs": { "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_3" }, "locked": { "lastModified": 1693015707, diff --git a/flake.nix b/flake.nix index 267d6f5..932fed1 100644 --- a/flake.nix +++ b/flake.nix @@ -2,18 +2,69 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; + naersk.url = "github:nix-community/naersk"; rust-overlay.url = "github:oxalica/rust-overlay"; }; - outputs = { self, nixpkgs, flake-utils, rust-overlay }: - flake-utils.lib.eachDefaultSystem - (system: - let overlays = [ (import rust-overlay) ]; - pkgs = import nixpkgs { - inherit system overlays; - }; in - { - devShells.default = import ./shell.nix { inherit pkgs; }; - } - ); -} \ No newline at end of file + outputs = { self, nixpkgs, flake-utils, naersk, rust-overlay }: + flake-utils.lib.eachDefaultSystem ( + system: + let + overlays = [ (import rust-overlay) ]; + + pkgs = import nixpkgs { + inherit system overlays; + }; + + toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; + + naersk' = pkgs.callPackage naersk { + cargo = toolchain; + rustc = toolchain; + clippy = toolchain; + }; + + buildInputs = with pkgs; [ + pkg-config + libusb1 + ] ++ + (lib.optionals (stdenv.hostPlatform.isLinux) [ + udev + ]) ++ + (lib.optionals (stdenv.hostPlatform.isDarwin) [ + darwin.apple_sdk.frameworks.IOKit + darwin.apple_sdk.frameworks.AppKit + iconv + ]); + in + { + formatter = pkgs.nixpkgs-fmt; + + packages = { + # For `nix build` `nix run`, & `nix profile install`: + default = naersk'.buildPackage rec { + pname = "sinowealth-kb-tool"; + version = "latest"; + + src = ./.; + doCheck = true; # run `cargo test` on build + + inherit buildInputs; + + meta = with pkgs.lib; { + description = "A utility for reading and writing flash contents on Sinowealth 8051-based devices"; + homepage = "https://github.com/carlossless/sinowealth-kb-tool"; + license = licenses.mit; + mainProgram = "sinowealth-kb-tool"; + maintainers = with maintainers; [ carlossless ]; + }; + }; + }; + + devShells.default = pkgs.mkShell { + inherit buildInputs; + nativeBuildInputs = with pkgs; [ rustup toolchain ]; + }; + } + ); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..629cec8 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,11 @@ +[toolchain] +channel = "1.72" +components = [ + "rustfmt", + "rustc", + "rust-src", + "rust-analyzer", + "cargo", + "clippy", +] +profile = "minimal" diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 1a11e9a..0000000 --- a/shell.nix +++ /dev/null @@ -1,17 +0,0 @@ -{ pkgs ? import { } }: -with pkgs; -mkShell { - buildInputs = [ - rust-bin.nightly.latest.default - pkg-config - libusb1 - ] ++ - (lib.optionals (stdenv.hostPlatform.isLinux) [ - udev - ]) ++ - (lib.optionals (stdenv.hostPlatform.isDarwin) [ - darwin.apple_sdk.frameworks.IOKit - darwin.apple_sdk.frameworks.AppKit - iconv - ]); -} From c5ec616b381e0c0b59e67f49505f81bbf0475897 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 7 Oct 2023 00:28:43 +0200 Subject: [PATCH 023/141] add delays between retries --- src/isp.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/isp.rs b/src/isp.rs index 600d577..94ab53f 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -203,7 +203,7 @@ impl ISPDevice<'static> { Self::enter_isp_mode(&device)?; info!("Waiting for ISP device..."); - thread::sleep(time::Duration::from_millis(2000)); + thread::sleep(time::Duration::from_secs(2)); let Ok(isp_device) = Self::open_isp_devices() else { info!("ISP device didn't come up..."); @@ -220,6 +220,7 @@ impl ISPDevice<'static> { fn find_isp_device_retry(part: &Part, retries: usize) -> Result { for attempt in 1..retries + 1 { if attempt > 1 { + thread::sleep(time::Duration::from_millis(500)); info!("Retrying... Attempt {}/{}", attempt, retries); } From 301a23254923978d542ae1e50a1ed9c492f3c01f Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 7 Oct 2023 18:26:03 +0200 Subject: [PATCH 024/141] bind nix packages to 23.05 --- flake.lock | 62 +++++++++++++++++++++++++++--------------------------- flake.nix | 8 +++---- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/flake.lock b/flake.lock index 297ea2d..47c9b3c 100644 --- a/flake.lock +++ b/flake.lock @@ -4,24 +4,6 @@ "inputs": { "systems": "systems" }, - "locked": { - "lastModified": 1689068808, - "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, "locked": { "lastModified": 1681202837, "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", @@ -56,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1695837737, - "narHash": "sha256-KcqmJ5hNacLuE7fkz5586kp/vt4NLo6+Prq3DMgrxpQ=", + "lastModified": 1696630667, + "narHash": "sha256-kO67pYOeT/6m9BnPO+zNHWnC4eGiW87gIAJ+e8f3gwU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "517501bcf14ae6ec47efd6a17dda0ca8e6d866f9", + "rev": "b604023e0a5549b65da3040a07d2beb29ac9fc63", "type": "github" }, "original": { @@ -70,16 +52,16 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1693003285, - "narHash": "sha256-5nm4yrEHKupjn62MibENtfqlP6pWcRTuSKrMiH9bLkc=", + "lastModified": 1696374741, + "narHash": "sha256-gt8B3G0ryizT9HSB4cCO8QoxdbsHnrQH+/BdKxOwqF0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5690c4271f2998c304a45c91a0aeb8fb69feaea7", + "rev": "8a4c17493e5c39769f79117937c79e1c88de6729", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-unstable", + "ref": "nixos-23.05", "repo": "nixpkgs", "type": "github" } @@ -102,23 +84,23 @@ }, "root": { "inputs": { - "flake-utils": "flake-utils", "naersk": "naersk", "nixpkgs": "nixpkgs_2", - "rust-overlay": "rust-overlay" + "rust-overlay": "rust-overlay", + "utils": "utils" } }, "rust-overlay": { "inputs": { - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils", "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1693015707, - "narHash": "sha256-SFr93DYn502sVT9nB5U8/cKg1INyEk/jCeq8tHioz7Y=", + "lastModified": 1696644659, + "narHash": "sha256-l/DgT519At8HhXDQHz3+H8AjaEbrsb7Xkqgj+JNHV6k=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "e90223633068a44f0fb62374e0fa360ccc987292", + "rev": "126829788e99c188be4eeb805f144d73d8a00f2c", "type": "github" }, "original": { @@ -156,6 +138,24 @@ "repo": "default", "type": "github" } + }, + "utils": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 932fed1..23b685d 100644 --- a/flake.nix +++ b/flake.nix @@ -1,13 +1,13 @@ { inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; + utils.url = "github:numtide/flake-utils"; naersk.url = "github:nix-community/naersk"; rust-overlay.url = "github:oxalica/rust-overlay"; }; - outputs = { self, nixpkgs, flake-utils, naersk, rust-overlay }: - flake-utils.lib.eachDefaultSystem ( + outputs = { self, nixpkgs, utils, naersk, rust-overlay }: + utils.lib.eachDefaultSystem ( system: let overlays = [ (import rust-overlay) ]; From 464cd7c7920d96baad0a22009ed7debe496b6218 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 20 Oct 2023 11:39:23 +0200 Subject: [PATCH 025/141] basic from_ihex unit tests --- src/ihex.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/ihex.rs b/src/ihex.rs index 0e2dc07..ab6d9a4 100644 --- a/src/ihex.rs +++ b/src/ihex.rs @@ -1,7 +1,7 @@ use ihex::*; use thiserror::Error; -#[derive(Debug, Error)] +#[derive(Debug, Error, PartialEq)] pub enum UnpackingError { #[error("Unsupported record type")] UnsupportedRecordType(Record), @@ -11,7 +11,7 @@ pub enum UnpackingError { AddressTooHigh(usize, usize), } -#[derive(Debug, Error)] +#[derive(Debug, Error, PartialEq)] pub enum ConversionError { #[error("Error while unpacking IHEX into array")] Unpacking(#[from] UnpackingError), @@ -71,3 +71,54 @@ fn unpack_records( } Ok(result) } + +#[test] +fn test_from_ihex() { + let result = from_ihex( + ":100000000200660227BD010A32646402CB9053DA13\n:00000001FF", + 16, + ) + .unwrap(); + let expected = vec![ + 2, 0, 102, 2, 39, 189, 1, 10, 50, 100, 100, 2, 203, 144, 83, 218, + ]; + assert_eq!(result, expected); +} + +#[test] +fn test_from_ihex_address_start_at_0x0001() { + let result = from_ihex( + ":100010000200660227BD010A32646402CB9053DA03\n:00000001FF", + 32, + ); + let mut expected: Vec = Vec::new(); + expected.resize(16, 0); + expected.extend_from_slice(&[ + 2, 0, 102, 2, 39, 189, 1, 10, 50, 100, 100, 2, 203, 144, 83, 218, + ]); + assert_eq!(result, Ok(expected)); +} + +#[test] +fn test_from_ihex_err_checksum_mismatch() { + let result = from_ihex( + ":100000000200660227BD010A32646402CB9053DA00\n:00000001FF", + 16, + ); + let expected = Err(ConversionError::Unpacking(UnpackingError::Parsing( + ReaderError::ChecksumMismatch(19, 0), + ))); + assert_eq!(result, expected); +} + +#[test] +fn test_from_ihex_err_address_too_high() { + let result = from_ihex( + ":100010000200660227BD010A32646402CB9053DA03\n:00000001FF", + 16, + ); + let expected = Err(ConversionError::Unpacking(UnpackingError::AddressTooHigh( + 32, 16, + ))); + assert_eq!(result, expected); +} From 82915f090fc922dbb8f1c1e2d76b39aca45dc727 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 11:45:07 +0200 Subject: [PATCH 026/141] Bump rustix from 0.38.8 to 0.38.20 (#12) Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.38.8 to 0.38.20.
Commits
  • 414309a chore: Release rustix version 0.38.20
  • ce11b6c Miscellaneous documentation cleanups. (#887)
  • a59a191 Fix sendmsg_unix's address encoding. (#885)
  • e35481c Fix a documentation link.
  • b8d7e00 Miscellaneous documentation cleanups. (#883)
  • 30a5ae1 Add support for armv7-sony-vita-newlibeabihf. (#882)
  • 702c54a Add documentation for how to do the equivalent of gethostname. (#880)
  • 3a53dfe chore: Release rustix version 0.38.19
  • 55cbe88 Fixes for Dir on macOS, FreeBSD, and WASI.
  • 31fd98c Merge pull request from GHSA-c827-hfw6-qwvm
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rustix&package-manager=cargo&previous-version=0.38.8&new-version=0.38.20)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/carlossless/sinowealth-kb-tool/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a87ebab..855e618 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,15 +186,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "log" @@ -300,9 +300,9 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" dependencies = [ "bitflags", "errno", From b6b0ae95d38afde06f8ff00b610834e02aac9363 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 20 Oct 2023 11:51:55 +0200 Subject: [PATCH 027/141] more descriptive addr_too_high error --- src/ihex.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/ihex.rs b/src/ihex.rs index ab6d9a4..cc2d3c7 100644 --- a/src/ihex.rs +++ b/src/ihex.rs @@ -7,8 +7,8 @@ pub enum UnpackingError { UnsupportedRecordType(Record), #[error("Error while parsing IHEX records")] Parsing(#[from] ReaderError), - #[error("Address ({0}) greater than binary size ({1})")] - AddressTooHigh(usize, usize), + #[error("Address {addr:#06x} greater than binary size {size:#06x}")] + AddressTooHigh { addr: usize, size: usize }, } #[derive(Debug, Error, PartialEq)] @@ -47,7 +47,10 @@ fn unpack_records( Record::Data { offset, value } => { let end_addr = offset as usize + value.len(); if end_addr > max_length { - return Err(UnpackingError::AddressTooHigh(end_addr, max_length)); + return Err(UnpackingError::AddressTooHigh { + addr: end_addr, + size: max_length, + }); } if end_addr > result.len() { result.resize(end_addr, 0); @@ -106,7 +109,7 @@ fn test_from_ihex_err_checksum_mismatch() { 16, ); let expected = Err(ConversionError::Unpacking(UnpackingError::Parsing( - ReaderError::ChecksumMismatch(19, 0), + ReaderError::ChecksumMismatch(0x13, 0x00), ))); assert_eq!(result, expected); } @@ -117,8 +120,9 @@ fn test_from_ihex_err_address_too_high() { ":100010000200660227BD010A32646402CB9053DA03\n:00000001FF", 16, ); - let expected = Err(ConversionError::Unpacking(UnpackingError::AddressTooHigh( - 32, 16, - ))); + let expected = Err(ConversionError::Unpacking(UnpackingError::AddressTooHigh { + addr: 0x20, + size: 0x10, + })); assert_eq!(result, expected); } From 27ee6e7d962985eb844ef15af2a6b8caa42b6b0a Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 4 Nov 2023 17:58:40 +0100 Subject: [PATCH 028/141] device typo fix --- src/isp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/isp.rs b/src/isp.rs index 94ab53f..d35e160 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -199,7 +199,7 @@ impl ISPDevice<'static> { let device = api.open_path(request_device_info.path()).unwrap(); - info!("Found Regular evice. Entering ISP mode..."); + info!("Found Regular device. Entering ISP mode..."); Self::enter_isp_mode(&device)?; info!("Waiting for ISP device..."); From ebc0de0d76575fc4c33434d62facee53c307e714 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 5 Nov 2023 22:23:59 +0100 Subject: [PATCH 029/141] device: terport-tr95 (#14) https://github.com/carlossless/sinowealth-kb-tool/issues/13 --- README.md | 19 +++++++++---------- src/part.rs | 9 +++++++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6f1e0b6..bc30e7b 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,15 @@ This is an experimental tool, so use it at your own risk. ## Supported Hardware -| Keyboard | ISP MD5 | MCU | Supported | -| -------- | ------- | --- | --------- | -| [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅ | -| [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅‡ | -| [NuPhy Air96](https://nuphy.com/products/air96-wireless-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A (labeled as BYK916) | ✅‡ | -| [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | ❓ | ✅ | -| Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 (labeled as BYK801) | ✅ | -| Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ✅ | - -‡ Reading tested, writing untested. +| Keyboard | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | +| -------- | ------- | --- | --------- | ----------- | ------------ | +| [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | +| [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | +| [NuPhy Air96](https://nuphy.com/products/air96-wireless-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | +| [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | +| Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | +| Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | +| Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | ## Prerequisites diff --git a/src/part.rs b/src/part.rs index c1b02e6..5b8ecb1 100644 --- a/src/part.rs +++ b/src/part.rs @@ -32,6 +32,14 @@ pub const PART_RE_K70_BYK800: Part = Part { product_id: 0x001a, }; +pub const PART_TERPORT_TR95: Part = Part { + flash_size: 61440, // 61440 until bootloader + bootloader_size: 4096, + page_size: 2048, + vendor_id: 0x258a, + product_id: 0x0049, +}; + pub static PARTS: phf::Map<&'static str, Part> = phf_map! { "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 @@ -39,6 +47,7 @@ pub static PARTS: phf::Map<&'static str, Part> = phf_map! { "nuphy-halo65" => PART_NUPHY_AIR60, // same as nuphy-air60 "xinmeng-k916" => PART_XINMENG_K916, "re-k70-byk800" => PART_RE_K70_BYK800, + "terport-tr95" => PART_TERPORT_TR95 }; impl Part { From fa81482c551eb53d3a6a0a5a1e8e040b6b93543a Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 3 Dec 2023 11:20:55 +0100 Subject: [PATCH 030/141] Arguments for reading/writing to custom devices (#17) Changes here allow the user to override the default options for the specified part, or provide all of the options necessary to interface with a part that hasn't been added to the parts list in this tool. Example with custom part: ```sh sinowealth-kb-tool read \ --flash_size 61440 \ --bootloader_size 4096 \ --page_size 2048 \ --vendor_id 0x05ac \ --product_id 0x024f \ foobar.hex ``` Example with overriding options: ```sh sinowealth-kb-tool read \ -p nuphy-air60 \ --vendor_id 0x05ac \ --product_id 0x024f \ foobar.hex ``` --- Cargo.lock | 25 ++++++++++ Cargo.toml | 1 + src/ihex.rs | 2 +- src/isp.rs | 25 ++++------ src/main.rs | 134 +++++++++++++++++++++++++++++++++++----------------- src/part.rs | 11 +++-- 6 files changed, 135 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 855e618..2f45d36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "bitflags" version = "2.4.0" @@ -75,6 +81,15 @@ dependencies = [ "clap_builder", ] +[[package]] +name = "clap-num" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488557e97528174edaa2ee268b23a809e0c598213a4bbcb4f34575a46fda147e" +dependencies = [ + "num-traits", +] + [[package]] name = "clap_builder" version = "4.3.23" @@ -208,6 +223,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + [[package]] name = "num_threads" version = "0.1.6" @@ -334,6 +358,7 @@ name = "sinowealth-kb-tool" version = "0.0.3" dependencies = [ "clap", + "clap-num", "hidapi", "ihex", "log", diff --git a/Cargo.toml b/Cargo.toml index 4707293..e934053 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ rust-version = "1.65" [dependencies] clap = "4.1" +clap-num = "1.0" ihex = "3.0" md5 = "0.7" thiserror = "1.0" diff --git a/src/ihex.rs b/src/ihex.rs index cc2d3c7..60e1d7e 100644 --- a/src/ihex.rs +++ b/src/ihex.rs @@ -1,4 +1,4 @@ -use ihex::*; +use ihex::{create_object_file_representation, Reader, ReaderError, Record, WriterError}; use thiserror::Error; #[derive(Debug, Error, PartialEq)] diff --git a/src/isp.rs b/src/isp.rs index d35e160..4993987 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -1,7 +1,7 @@ use std::{thread, time}; use hidapi::DeviceInfo; -use log::*; +use log::{debug, info, warn}; use thiserror::Error; use super::{part::*, util}; @@ -32,11 +32,11 @@ const XFER_WRITE_PAGE: u8 = 0x77; const LJMP_OPCODE: u8 = 0x02; -pub struct ISPDevice<'a> { +pub struct ISPDevice { request_device: HidDevice, #[cfg(target_os = "windows")] data_device: HidDevice, - part: &'a Part, + part: Part, } #[derive(Debug, Error)] @@ -64,8 +64,8 @@ struct HIDDevices { data: HidDevice, } -impl ISPDevice<'static> { - pub fn new(part: &'static Part) -> Result { +impl ISPDevice { + pub fn new(part: Part) -> Result { let devices = Self::find_isp_device(part)?; Ok(Self { request_device: devices.request, @@ -164,7 +164,7 @@ impl ISPDevice<'static> { } } - fn switch_kb_device(part: &Part) -> Result { + fn switch_kb_device(part: Part) -> Result { let api = Self::hidapi(); info!( @@ -199,7 +199,7 @@ impl ISPDevice<'static> { let device = api.open_path(request_device_info.path()).unwrap(); - info!("Found Regular device. Entering ISP mode..."); + info!("Found regular device. Entering ISP mode..."); Self::enter_isp_mode(&device)?; info!("Waiting for ISP device..."); @@ -213,11 +213,11 @@ impl ISPDevice<'static> { Ok(isp_device) } - fn find_isp_device(part: &Part) -> Result { + fn find_isp_device(part: Part) -> Result { Self::find_isp_device_retry(part, MAX_RETRIES) } - fn find_isp_device_retry(part: &Part, retries: usize) -> Result { + fn find_isp_device_retry(part: Part, retries: usize) -> Result { for attempt in 1..retries + 1 { if attempt > 1 { thread::sleep(time::Duration::from_millis(500)); @@ -274,13 +274,6 @@ impl ISPDevice<'static> { Ok(()) } - pub fn erase_cycle(&self) -> Result<(), ISPError> { - info!("Erasing..."); - self.erase()?; - self.finalize()?; - Ok(()) - } - fn data_device(&self) -> &HidDevice { #[cfg(target_os = "windows")] return &self.data_device; diff --git a/src/main.rs b/src/main.rs index 9185aa9..a3603a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,9 @@ use std::{ process::ExitCode, }; -use clap::*; -use log::*; +use clap::{arg, ArgMatches, Command}; +use clap_num::maybe_hex; +use log::{error, info}; use simple_logger::SimpleLogger; use thiserror::Error; @@ -15,7 +16,6 @@ mod part; mod ihex; mod util; -// pub use crate::hid::*; pub use crate::{ihex::*, isp::*, part::*, util::*}; #[derive(Debug, Error)] @@ -28,6 +28,16 @@ pub enum CLIError { IHEXError(#[from] ConversionError), } +fn main() -> ExitCode { + match err_main() { + Ok(_) => ExitCode::SUCCESS, + Err(e) => { + error!("{}", e.to_string()); + ExitCode::FAILURE + } + } +} + fn cli() -> Command { return Command::new("sinowealth-kb-tool") .about("A programming tool for Sinowealth Gaming KB devices") @@ -40,11 +50,7 @@ fn cli() -> Command { .short_flag('r') .about("Read flash contents. (Intel HEX)") .arg(arg!(output_file: "file to write flash contents to")) - .arg( - arg!(-p --part ) - .value_parser(PARTS.keys().copied().collect::>()) - .required(true), - ) + .part_args() .arg(arg!(-b --bootloader "read only booloader").conflicts_with("full")) .arg( arg!(-f --full "read complete flash (including the bootloader)") @@ -56,11 +62,7 @@ fn cli() -> Command { .short_flag('w') .about("Write file (Intel HEX) into flash.") .arg(arg!(input_file: "payload to write into flash")) - .arg( - arg!(-p --part ) - .value_parser(PARTS.keys().copied().collect::>()) - .required(true), - ), + .part_args(), ); } @@ -71,11 +73,6 @@ fn err_main() -> Result<(), CLIError> { match matches.subcommand() { Some(("read", sub_matches)) => { - let part_name = sub_matches - .get_one::("part") - .map(|s| s.as_str()) - .unwrap(); - let output_file = sub_matches .get_one::("output_file") .map(|s| s.as_str()) @@ -85,7 +82,7 @@ fn err_main() -> Result<(), CLIError> { let bootloader = sub_matches.get_flag("bootloader"); - let part = PARTS.get(part_name).unwrap(); + let part = get_part_from_matches(sub_matches); let read_type = match (full, bootloader) { (true, _) => ReadType::Full, @@ -108,12 +105,7 @@ fn err_main() -> Result<(), CLIError> { .map(|s| s.as_str()) .unwrap(); - let part_name = sub_matches - .get_one::("part") - .map(|s| s.as_str()) - .unwrap(); - - let part = PARTS.get(part_name).unwrap(); + let part = get_part_from_matches(sub_matches); let mut file = fs::File::open(input_file).map_err(CLIError::from)?; let mut file_buf = Vec::new(); @@ -128,28 +120,84 @@ fn err_main() -> Result<(), CLIError> { let isp = ISPDevice::new(part).map_err(CLIError::from)?; isp.write_cycle(&mut firmware).map_err(CLIError::from)?; } - Some(("erase", sub_matches)) => { - let part_name = sub_matches - .get_one::("part") - .map(|s| s.as_str()) - .unwrap(); - - let part = PARTS.get(part_name).unwrap(); - - let isp = ISPDevice::new(part).map_err(CLIError::from)?; - isp.erase_cycle().map_err(CLIError::from)?; - } _ => unreachable!(), } Ok(()) } -fn main() -> ExitCode { - match err_main() { - Ok(_) => ExitCode::SUCCESS, - Err(e) => { - error!("{}", e.to_string()); - ExitCode::FAILURE - } +trait PartCommand { + fn part_args(self) -> Command; +} + +impl PartCommand for Command { + fn part_args(self) -> Command { + self.arg( + arg!(-p --part ) + .value_parser(Part::available_parts()) + .required_unless_present_all([ + "flash_size", + "bootloader_size", + "page_size", + "vendor_id", + "product_id", + ]), + ) + .arg( + arg!(--flash_size ) + .required_unless_present("part") + .value_parser(clap::value_parser!(usize)), + ) + .arg( + arg!(--bootloader_size ) + .required_unless_present("part") + .value_parser(clap::value_parser!(usize)), + ) + .arg( + arg!(--page_size ) + .required_unless_present("part") + .value_parser(clap::value_parser!(usize)), + ) + .arg( + arg!(--vendor_id ) + .required_unless_present("part") + .value_parser(maybe_hex::), + ) + .arg( + arg!(--product_id ) + .required_unless_present("part") + .value_parser(maybe_hex::), + ) + } +} + +fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { + let part_name = sub_matches.get_one::("part").map(|s| s.as_str()); + + let mut part = match part_name { + Some(part_name) => *PARTS.get(part_name).unwrap(), + _ => Part::default(), + }; + + let flash_size = sub_matches.get_one::("flash_size"); + let bootloader_size = sub_matches.get_one::("bootloader_size"); + let page_size = sub_matches.get_one::("page_size"); + let vendor_id = sub_matches.get_one::("vendor_id"); + let product_id = sub_matches.get_one::("product_id"); + + if let Some(flash_size) = flash_size { + part.flash_size = *flash_size; + } + if let Some(bootloader_size) = bootloader_size { + part.bootloader_size = *bootloader_size; + } + if let Some(page_size) = page_size { + part.page_size = *page_size; + } + if let Some(vendor_id) = vendor_id { + part.vendor_id = *vendor_id; + } + if let Some(product_id) = product_id { + part.product_id = *product_id; } + return part; } diff --git a/src/part.rs b/src/part.rs index 5b8ecb1..e7a1f8e 100644 --- a/src/part.rs +++ b/src/part.rs @@ -1,5 +1,6 @@ -use phf::*; +use phf::{phf_map, Map}; +#[derive(Default, Clone, Copy)] pub struct Part { pub flash_size: usize, pub bootloader_size: usize, @@ -40,17 +41,21 @@ pub const PART_TERPORT_TR95: Part = Part { product_id: 0x0049, }; -pub static PARTS: phf::Map<&'static str, Part> = phf_map! { +pub static PARTS: Map<&'static str, Part> = phf_map! { "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 "nuphy-air96" => PART_NUPHY_AIR60, // same as nuphy-air60 "nuphy-halo65" => PART_NUPHY_AIR60, // same as nuphy-air60 "xinmeng-k916" => PART_XINMENG_K916, "re-k70-byk800" => PART_RE_K70_BYK800, - "terport-tr95" => PART_TERPORT_TR95 + "terport-tr95" => PART_TERPORT_TR95, }; impl Part { + pub fn available_parts() -> Vec<&'static str> { + PARTS.keys().copied().collect::>() + } + pub fn num_pages(&self) -> usize { self.flash_size / self.page_size } From 9ae986416375bb14ed2f524a0874f834037dffa6 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 3 Dec 2023 12:25:02 +0100 Subject: [PATCH 031/141] updated usage and removed -f flag (--full remains) --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ src/main.rs | 4 ++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bc30e7b..9cd2daf 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,46 @@ A utility for reading and writing flash contents on Sinowealth 8051-based device This is an experimental tool, so use it at your own risk. +## Usage + +### Reading + +```sh +# reads firmware excluding isp bootloader +sinowealth-kb-tool read -p nuphy-air60 foobar.hex + +# reads only isp bootloader section +sinowealth-kb-tool read -p nuphy-air60 -b bootloader.hex + +# full dump including firmware and bootloader +sinowealth-kb-tool read -p nuphy-air60 --full full.hex + +# custom device +sinowealth-kb-tool read \ + --flash_size 61440 \ + --bootloader_size 4096 \ + --page_size 2048 \ + --vendor_id 0x05ac \ + --product_id 0x024f \ + foobar.hex +``` + +### Writing + +```sh +# overwrites firmware (does not touch the bootloader section) +sinowealth-kb-tool write -p nuphy-air60 foobar.hex + +# custom device +sinowealth-kb-tool write \ + --flash_size 61440 \ + --bootloader_size 4096 \ + --page_size 2048 \ + --vendor_id 0x05ac \ + --product_id 0x024f \ + foobar.hex +``` + ## Supported Hardware | Keyboard | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | diff --git a/src/main.rs b/src/main.rs index a3603a1..ae1ed31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,9 +51,9 @@ fn cli() -> Command { .about("Read flash contents. (Intel HEX)") .arg(arg!(output_file: "file to write flash contents to")) .part_args() - .arg(arg!(-b --bootloader "read only booloader").conflicts_with("full")) + .arg(arg!(-b --bootloader --isp "read only booloader").conflicts_with("full")) .arg( - arg!(-f --full "read complete flash (including the bootloader)") + arg!(--full "read complete flash (including the bootloader)") .conflicts_with("bootloader"), ), ) From 66f46f2cea584bee2f3adc0a57b0a318edf456d1 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 3 Dec 2023 23:43:40 +0100 Subject: [PATCH 032/141] check firmware prior to writing, disclaimer updates in readme, functional-test script (#20) Changes here add: * A safeguard to make it less possible to write invalid (bricking) firmware to the device * Disclaimer in readme warning about the potentially unwanted side-effects of reading from the device * A functional test script to run prior to making any pushes or releases (sadly specific to my development environment) --- README.md | 6 ++ flake.nix | 1 + src/isp.rs | 48 ++++++++++++++- tools/functional-test.sh | 128 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 1 deletion(-) create mode 100755 tools/functional-test.sh diff --git a/README.md b/README.md index 9cd2daf..522ce92 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,14 @@ This is an experimental tool, so use it at your own risk. ## Usage +Read [here](https://github.com/carlossless/sinowealth-kb-tool/issues/19) for ISP quirks. + ### Reading +⚠️ Reading is not entirely an idempotent operation. A read operation can change values in the `0xeffb - 0xeffd` region. + +⚠️ The ISP bootloader will blank out bytes in the `0xeffb - 0xeffd` region, therefore the produced dump might not reflect the actual state in ROM. + ```sh # reads firmware excluding isp bootloader sinowealth-kb-tool read -p nuphy-air60 foobar.hex diff --git a/flake.nix b/flake.nix index 23b685d..f9f4b98 100644 --- a/flake.nix +++ b/flake.nix @@ -27,6 +27,7 @@ buildInputs = with pkgs; [ pkg-config libusb1 + binutils ] ++ (lib.optionals (stdenv.hostPlatform.isLinux) [ udev diff --git a/src/isp.rs b/src/isp.rs index 4993987..9118ceb 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -39,6 +39,17 @@ pub struct ISPDevice { part: Part, } +#[derive(Debug, Error)] +pub enum FirmwareError { + #[error("No LJMP found at {location_addr:#06x}")] + NoLJMP { location_addr: u16 }, + #[error("LJMP at {location_addr:#06x} points to invalid address {target_addr:#06x}")] + InvalidLJMPAddr { + location_addr: u16, + target_addr: u16, + }, +} + #[derive(Debug, Error)] pub enum ISPError { #[error("Duplicate devices found")] @@ -49,6 +60,8 @@ pub enum ISPError { HidError(#[from] HidError), #[error(transparent)] VerificationError(#[from] VerificationError), + #[error(transparent)] + FirmwareError(#[from] FirmwareError), } #[derive(Debug, Clone)] @@ -256,9 +269,10 @@ impl ISPDevice { pub fn write_cycle(&self, firmware: &mut Vec) -> Result<(), ISPError> { let length = firmware.len(); + self.check_firmware(firmware)?; + self.erase()?; self.write(firmware)?; - let written = self.read(0, self.part.flash_size)?; // ARCANE: the ISP will copy the LJMP instruction (if existing) from the end to the very start of memory. // We need to make modifications to the expected payload to account for this. @@ -269,6 +283,7 @@ impl ISPDevice { } info!("Verifying..."); + let written = self.read(0, self.part.flash_size)?; util::verify(firmware, &written).map_err(ISPError::from)?; self.finalize()?; Ok(()) @@ -281,6 +296,37 @@ impl ISPDevice { &self.request_device } + fn check_firmware(&self, firmware: &mut Vec) -> Result<(), ISPError> { + let length = firmware.len(); + if firmware[length - 5] != LJMP_OPCODE { + info!("No LJMP detected at {:#06x}", length - 5); + if firmware[0] != LJMP_OPCODE { + return Err(ISPError::FirmwareError(FirmwareError::NoLJMP { + location_addr: 0x0000, + })); + } + let ljmp_addr = (firmware[1] as u16) << 8 | firmware[2] as u16; + if ljmp_addr == 0x0000 { + return Err(ISPError::FirmwareError(FirmwareError::InvalidLJMPAddr { + location_addr: 0x0000, + target_addr: ljmp_addr, + })); + } + info!("Copying LJMP from {:#06x} to {:#06x}", 0x0000, length - 5); + firmware[length - 5] = LJMP_OPCODE; + firmware.copy_within(1..3, length - 4); // Copy LJMP address + } else { + let ljmp_addr = (firmware[length - 4] as u16) << 8 | firmware[length - 3] as u16; + if ljmp_addr == 0x0000 { + return Err(ISPError::FirmwareError(FirmwareError::InvalidLJMPAddr { + location_addr: (length - 5) as u16, + target_addr: ljmp_addr, + })); + } + } + return Ok(()); + } + /// Allows firmware to be read prior to erasing it fn magic_sauce(&self) -> Result<(), ISPError> { let cmd: [u8; COMMAND_LENGTH] = [ diff --git a/tools/functional-test.sh b/tools/functional-test.sh new file mode 100755 index 0000000..8846fb1 --- /dev/null +++ b/tools/functional-test.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# TOOL=sinowealth-kb-tool +TOOL="cargo run --" +FILE_PREFIX="private/test-$(date +'%Y%m%dT%H%M%S')" +PART="nuphy-air60" + +EXPECTED_BOOTLOADER_MD5="3e0ebd0c440af5236d7ff8872343f85d" + +FILE_DEFAULT="$FILE_PREFIX-read.hex" +FILE_BOOTLOADER="$FILE_PREFIX-read-bootloader.hex" +FILE_FULL="$FILE_PREFIX-read-full.hex" +FILE_CUSTOM="$FILE_PREFIX-read-custom.hex" +FILE_OVERRIDE="$FILE_PREFIX-read-override.hex" +FILE_POST_WRITE="$FILE_PREFIX-post-write.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 "Waiting..." + sleep 5 +} + +function get_md5 () { + MD5SUM=($(md5sum "$1")) + echo $MD5SUM +} + +function get_md5_from_hex () { + objcopy --input-target=ihex --output-target=binary "$1" "${1%.hex}.bin" + MD5SUM=$(get_md5 "${1%.hex}.bin") + echo $MD5SUM +} + +echo "Initial reboot..." +reboot_device + +echo "Standard read..." +$TOOL read --part "$PART" "$FILE_DEFAULT" + +reboot_device + +echo "Bootloader read..." +$TOOL read --part "$PART" -b "$FILE_BOOTLOADER" + +reboot_device + +echo "Full read..." +$TOOL read --part "$PART" --full "$FILE_FULL" + +reboot_device + +echo "Custom read..." +$TOOL read \ + --flash_size 61440 \ + --bootloader_size 4096 \ + --page_size 2048 \ + --vendor_id 0x05ac \ + --product_id 0x024f \ + "$FILE_CUSTOM" + +reboot_device + +echo "Override read..." +$TOOL read \ + --part "$PART" \ + --vendor_id 0x05ac \ + --product_id 0x024f \ + "$FILE_OVERRIDE" + +reboot_device + +READ_MD5=$(get_md5_from_hex "$FILE_DEFAULT") +READ_BOOTLOADER_MD5=$(get_md5_from_hex "$FILE_BOOTLOADER") +READ_FULL_MD5=$(get_md5_from_hex "$FILE_FULL") +READ_CUSTOM_MD5=$(get_md5_from_hex "$FILE_CUSTOM") +READ_OVERRIDE_MD5=$(get_md5_from_hex "$FILE_OVERRIDE") + +echo "Checking bootloader checksum" +if [[ "$READ_BOOTLOADER_MD5" != "$EXPECTED_BOOTLOADER_MD5" ]]; then + echo "MD5 mismatch $READ_BOOTLOADER_MD5 != $EXPECTED_BOOTLOADER_MD5" + exit 1 +fi + +echo "Checking custom checksum" +if [[ "$READ_CUSTOM_MD5" != "$READ_MD5" ]]; then + echo "MD5 mismatch $READ_CUSTOM_MD5 != $READ_MD5" + exit 1 +fi + +echo "Checking override checksum" +if [[ "$READ_OVERRIDE_MD5" != "$READ_MD5" ]]; then + echo "MD5 mismatch $READ_OVERRIDE_MD5 != $READ_MD5" + exit 1 +fi + +echo "Checking standard+bootloader == full" +cat "${FILE_DEFAULT%.hex}.bin" "${FILE_BOOTLOADER%.hex}.bin" > "$FILE_PREFIX-concat-full.bin" +EXPECTED_FULL_MD5=$(get_md5 "$FILE_PREFIX-concat-full.bin") +if [[ "$READ_FULL_MD5" != "$EXPECTED_FULL_MD5" ]]; then + echo "MD5 mismatch $READ_FULL_MD5 != $EXPECTED_FULL_MD5" + exit 1 +fi + +echo "Standard write..." +$TOOL write --part "$PART" "$FILE_DEFAULT" + +reboot_device + +echo "Post-write read..." +$TOOL read --part "$PART" "$FILE_POST_WRITE" + +READ_POST_WRITE_MD5=$(get_md5_from_hex "$FILE_POST_WRITE") + +echo "Checking post-write checksum" +if [[ "$READ_POST_WRITE_MD5" != "$READ_MD5" ]]; then + echo "MD5 mismatch $READ_POST_WRITE_MD5 != $READ_MD5" + exit 1 +fi + +reboot_device + +echo "Passed all tests!" From 382aa17e3fae1803bd068be9c98ee5c94f71d736 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 3 Dec 2023 23:47:22 +0100 Subject: [PATCH 033/141] bump to v0.0.4 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f45d36..c05adac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -355,7 +355,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.0.3" +version = "0.0.4" dependencies = [ "clap", "clap-num", diff --git a/Cargo.toml b/Cargo.toml index e934053..915f62a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based devices """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.0.3" +version = "0.0.4" edition = "2021" license = "MIT" rust-version = "1.65" From c393f033310b253d77d5ebab344a973ab4ccd9ec Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 10 Dec 2023 17:19:01 +0100 Subject: [PATCH 034/141] device: redragon-k6170-fizz (#23) https://github.com/carlossless/sinowealth-kb-tool/issues/21 --- README.md | 1 + src/part.rs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 522ce92..3bcea67 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ sinowealth-kb-tool write \ | [NuPhy Air96](https://nuphy.com/products/air96-wireless-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | | [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | +| [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | diff --git a/src/part.rs b/src/part.rs index e7a1f8e..75994b2 100644 --- a/src/part.rs +++ b/src/part.rs @@ -41,6 +41,14 @@ pub const PART_TERPORT_TR95: Part = Part { product_id: 0x0049, }; +pub const PART_REDRAGON_FIZZ_K617: Part = Part { + flash_size: 61440, // 61440 until bootloader + bootloader_size: 4096, + page_size: 2048, + vendor_id: 0x258a, + product_id: 0x0049, +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 @@ -49,6 +57,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "xinmeng-k916" => PART_XINMENG_K916, "re-k70-byk800" => PART_RE_K70_BYK800, "terport-tr95" => PART_TERPORT_TR95, + "redragon-k6170-fizz" => PART_REDRAGON_FIZZ_K617 }; impl Part { From fa240922f9358aa7c658942c4d7889a8127ac5a0 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 11 Dec 2023 16:14:02 +0100 Subject: [PATCH 035/141] corrected ISP behaviour, documented commands (#22) Changes here clean up all of the various ISP commands, remove the unused and not needed command arguments, remove unneeded firmware fixups (pre-read and pre-write), and document ISP behavior. Thanks to @gashtaan for providing details from his analysis of the bootloader code! This also renames the `--flash_size` arg to `--firmware_size`. --- Cargo.lock | 147 +++----------------------- Cargo.toml | 3 +- README.md | 15 ++- src/isp.rs | 223 +++++++++++++++------------------------ src/main.rs | 16 +-- src/part.rs | 14 +-- tools/functional-test.sh | 2 +- 7 files changed, 129 insertions(+), 291 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c05adac..d6f2f4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -48,7 +48,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -122,15 +122,9 @@ checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ "is-terminal", "lazy_static", - "windows-sys 0.48.0", + "windows-sys", ] -[[package]] -name = "deranged" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" - [[package]] name = "errno" version = "0.3.2" @@ -139,7 +133,7 @@ checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -184,15 +178,9 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] -[[package]] -name = "itoa" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" - [[package]] name = "lazy_static" version = "1.4.0" @@ -232,15 +220,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - [[package]] name = "phf" version = "0.11.2" @@ -332,25 +311,18 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] -[[package]] -name = "serde" -version = "1.0.185" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" - [[package]] name = "simple_logger" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2230cd5c29b815c9b699fb610b49a5ed65588f3509d9f0108be3a885da629333" +checksum = "da0ca6504625ee1aa5fda33913d2005eab98c7a42dd85f116ecce3ff54c9d3ef" dependencies = [ "colored", "log", - "time", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -411,36 +383,6 @@ dependencies = [ "syn", ] -[[package]] -name = "time" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb39ee79a6d8de55f48f2293a830e040392f1c5f16e336bdd1788cd0aadce07" -dependencies = [ - "deranged", - "itoa", - "libc", - "num_threads", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" - -[[package]] -name = "time-macros" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733d258752e9303d392b94b75230d07b0b9c489350c69b851fc6c065fde3e8f9" -dependencies = [ - "time-core", -] - [[package]] name = "unicode-ident" version = "1.0.11" @@ -475,21 +417,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -505,93 +432,51 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 915f62a..f9d3d76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,8 @@ version = "0.4" features = ["max_level_debug", "release_max_level_info"] [dependencies.simple_logger] -version = "4.1" +version = "4.3" +default-features = false features = ["stderr", "colors"] [dependencies.phf] diff --git a/README.md b/README.md index 3bcea67..b1b479e 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,11 @@ This is an experimental tool, so use it at your own risk. ## Usage -Read [here](https://github.com/carlossless/sinowealth-kb-tool/issues/19) for ISP quirks. - ### Reading -⚠️ Reading is not entirely an idempotent operation. A read operation can change values in the `0xeffb - 0xeffd` region. +⚠️ A read operation will set an LJMP (0x02) opcode at address `` if it's not already present there. When this opcode is set, the bootloader considers the main firmware enabled and jumps to it when the device is powered on. This opcode should already be set on most devices and therefore the read operation **should** not cause any issues. -⚠️ The ISP bootloader will blank out bytes in the `0xeffb - 0xeffd` region, therefore the produced dump might not reflect the actual state in ROM. +⚠️ During reading the ISP bootloader will redirect values in `0x0001 - 0x0002` to ` - `. Because of this, the produced payload will be different from how memory is actually laid out in the MCU flash. ```sh # reads firmware excluding isp bootloader @@ -30,7 +28,7 @@ sinowealth-kb-tool read -p nuphy-air60 --full full.hex # custom device sinowealth-kb-tool read \ - --flash_size 61440 \ + --firmware_size 61440 \ --bootloader_size 4096 \ --page_size 2048 \ --vendor_id 0x05ac \ @@ -40,13 +38,15 @@ sinowealth-kb-tool read \ ### Writing +⚠️ Same as the [read](#reading) operation, the ISP bootloader will write values meant for addresses `0x0001-0x0002` to ` - `. + ```sh # overwrites firmware (does not touch the bootloader section) sinowealth-kb-tool write -p nuphy-air60 foobar.hex # custom device sinowealth-kb-tool write \ - --flash_size 61440 \ + --firmware_size 61440 \ --bootloader_size 4096 \ --page_size 2048 \ --vendor_id 0x05ac \ @@ -83,5 +83,4 @@ Make sure your user is part of the `plugdev` group. ## Acknowledgments -* https://github.com/gashtaan/sinowealth-8051-dumper -* https://github.com/ayufan-rock64/pinebook-pro-keyboard-updater +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! diff --git a/src/isp.rs b/src/isp.rs index 9118ceb..7f58cc6 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -22,7 +22,7 @@ const REPORT_ID_CMD: u8 = 0x05; const REPORT_ID_XFER: u8 = 0x06; const CMD_ISP_MODE: u8 = 0x75; -const CMD_MAGIC_SAUCE: u8 = 0x55; // uncertain how this command works, hence the name +const CMD_ENABLE_FIRMWARE: u8 = 0x55; const CMD_INIT_READ: u8 = 0x52; const CMD_INIT_WRITE: u8 = 0x57; const CMD_ERASE: u8 = 0x45; @@ -30,8 +30,6 @@ const CMD_ERASE: u8 = 0x45; const XFER_READ_PAGE: u8 = 0x72; const XFER_WRITE_PAGE: u8 = 0x77; -const LJMP_OPCODE: u8 = 0x02; - pub struct ISPDevice { request_device: HidDevice, #[cfg(target_os = "windows")] @@ -39,17 +37,6 @@ pub struct ISPDevice { part: Part, } -#[derive(Debug, Error)] -pub enum FirmwareError { - #[error("No LJMP found at {location_addr:#06x}")] - NoLJMP { location_addr: u16 }, - #[error("LJMP at {location_addr:#06x} points to invalid address {target_addr:#06x}")] - InvalidLJMPAddr { - location_addr: u16, - target_addr: u16, - }, -} - #[derive(Debug, Error)] pub enum ISPError { #[error("Duplicate devices found")] @@ -60,8 +47,6 @@ pub enum ISPError { HidError(#[from] HidError), #[error(transparent)] VerificationError(#[from] VerificationError), - #[error(transparent)] - FirmwareError(#[from] FirmwareError), } #[derive(Debug, Clone)] @@ -257,35 +242,35 @@ impl ISPDevice { } pub fn read_cycle(&self, read_type: ReadType) -> Result, ISPError> { - self.magic_sauce()?; + self.enable_firmware()?; - match read_type { - ReadType::Normal => self.read(0, self.part.flash_size), - ReadType::Bootloader => self.read(self.part.flash_size, self.part.bootloader_size), - ReadType::Full => self.read(0, self.part.flash_size + self.part.bootloader_size), - } + let firmware = match read_type { + ReadType::Normal => self.read(0, self.part.firmware_size)?, + ReadType::Bootloader => { + self.read(self.part.firmware_size, self.part.bootloader_size)? + } + ReadType::Full => self.read(0, self.part.firmware_size + self.part.bootloader_size)?, + }; + + return Ok(firmware); } pub fn write_cycle(&self, firmware: &mut Vec) -> Result<(), ISPError> { - let length = firmware.len(); - - self.check_firmware(firmware)?; + // ensure that the address at is the same as the reset vector + firmware.copy_within(1..3, self.part.firmware_size - 4); self.erase()?; - self.write(firmware)?; - - // ARCANE: the ISP will copy the LJMP instruction (if existing) from the end to the very start of memory. - // We need to make modifications to the expected payload to account for this. - if firmware[length - 5] == LJMP_OPCODE { - firmware[0] = LJMP_OPCODE; - firmware.copy_within((length - 4)..(length - 2), 1); // Copy LJMP address - firmware[(length - 5)..(length - 2)].fill(0); // Cleanup - } + self.write(0, firmware)?; + + // cleanup the address at + firmware[self.part.firmware_size - 4..self.part.firmware_size - 2].fill(0); + + let read_back = self.read(0, self.part.firmware_size)?; info!("Verifying..."); - let written = self.read(0, self.part.flash_size)?; - util::verify(firmware, &written).map_err(ISPError::from)?; - self.finalize()?; + util::verify(firmware, &read_back).map_err(ISPError::from)?; + + self.enable_firmware()?; Ok(()) } @@ -296,62 +281,9 @@ impl ISPDevice { &self.request_device } - fn check_firmware(&self, firmware: &mut Vec) -> Result<(), ISPError> { - let length = firmware.len(); - if firmware[length - 5] != LJMP_OPCODE { - info!("No LJMP detected at {:#06x}", length - 5); - if firmware[0] != LJMP_OPCODE { - return Err(ISPError::FirmwareError(FirmwareError::NoLJMP { - location_addr: 0x0000, - })); - } - let ljmp_addr = (firmware[1] as u16) << 8 | firmware[2] as u16; - if ljmp_addr == 0x0000 { - return Err(ISPError::FirmwareError(FirmwareError::InvalidLJMPAddr { - location_addr: 0x0000, - target_addr: ljmp_addr, - })); - } - info!("Copying LJMP from {:#06x} to {:#06x}", 0x0000, length - 5); - firmware[length - 5] = LJMP_OPCODE; - firmware.copy_within(1..3, length - 4); // Copy LJMP address - } else { - let ljmp_addr = (firmware[length - 4] as u16) << 8 | firmware[length - 3] as u16; - if ljmp_addr == 0x0000 { - return Err(ISPError::FirmwareError(FirmwareError::InvalidLJMPAddr { - location_addr: (length - 5) as u16, - target_addr: ljmp_addr, - })); - } - } - return Ok(()); - } - - /// Allows firmware to be read prior to erasing it - fn magic_sauce(&self) -> Result<(), ISPError> { - let cmd: [u8; COMMAND_LENGTH] = [ - REPORT_ID_CMD, - CMD_MAGIC_SAUCE, - 0x00, - 0x00, - (self.part.flash_size & 0xff) as u8, - (self.part.flash_size >> 8) as u8, - ]; - - self.request_device.send_feature_report(&cmd)?; - Ok(()) - } - fn read(&self, start_addr: usize, length: usize) -> Result, ISPError> { - let cmd: [u8; COMMAND_LENGTH] = [ - REPORT_ID_CMD, - CMD_INIT_READ, - (start_addr & 0xff) as u8, - (start_addr >> 8) as u8, - (length & 0xff) as u8, - (length >> 8) as u8, - ]; - self.request_device.send_feature_report(&cmd)?; + info!("Reading..."); + self.init_read(start_addr)?; let page_size = self.part.page_size; let num_page = length / page_size; @@ -367,83 +299,104 @@ impl ISPDevice { Ok(result) } - fn read_page(&self, buf: &mut Vec) -> Result<(), ISPError> { + fn write(&self, start_addr: usize, buffer: &[u8]) -> Result<(), ISPError> { + info!("Writing..."); + self.init_write(start_addr)?; + let page_size = self.part.page_size; - let mut xfer_buf: Vec = vec![0; page_size + 2]; - xfer_buf[0] = REPORT_ID_XFER; - xfer_buf[1] = XFER_READ_PAGE; - self.data_device() - .get_feature_report(&mut xfer_buf) + for i in 0..self.part.num_pages() { + debug!("Writing page {} @ offset {:#06x}", i, i * page_size); + self.write_page(&buffer[(i * page_size)..((i + 1) * page_size)])?; + } + Ok(()) + } + + /// Initializes the read operation / sets the initial read address + fn init_read(&self, start_addr: usize) -> Result<(), ISPError> { + let cmd: [u8; COMMAND_LENGTH] = [ + REPORT_ID_CMD, + CMD_INIT_READ, + (start_addr & 0xff) as u8, + (start_addr >> 8) as u8, + 0, + 0, + ]; + self.request_device + .send_feature_report(&cmd) .map_err(ISPError::from)?; - buf.extend_from_slice(&xfer_buf[2..(page_size + 2)]); Ok(()) } - fn write(&self, buffer: &[u8]) -> Result<(), ISPError> { - info!("Writing..."); + /// Initializes the write operation / sets the initial write address + fn init_write(&self, start_addr: usize) -> Result<(), ISPError> { let cmd: [u8; COMMAND_LENGTH] = [ REPORT_ID_CMD, CMD_INIT_WRITE, + (start_addr & 0xff) as u8, + (start_addr >> 8) as u8, 0, 0, - (self.part.flash_size & 0xff) as u8, - (self.part.flash_size >> 8) as u8, ]; - self.request_device .send_feature_report(&cmd) .map_err(ISPError::from)?; + Ok(()) + } + /// Reads one page of flash contents + fn read_page(&self, buf: &mut Vec) -> Result<(), ISPError> { let page_size = self.part.page_size; - for i in 0..self.part.num_pages() { - debug!("Writting page {} @ offset {:#06x}", i, i * page_size); - self.write_page(&buffer[(i * page_size)..((i + 1) * page_size)])?; - } + let mut xfer_buf: Vec = vec![0; page_size + 2]; + xfer_buf[0] = REPORT_ID_XFER; + xfer_buf[1] = XFER_READ_PAGE; + self.data_device() + .get_feature_report(&mut xfer_buf) + .map_err(ISPError::from)?; + buf.extend_from_slice(&xfer_buf[2..(page_size + 2)]); Ok(()) } + /// Writes one page to flash + /// + /// Note: The first 3 bytes at address 0x0000 (first-page) are skipped. Instead the second and + /// third bytes (firmware's reset vector LJMP destination address) are written to address + /// and will later be part of the LJMP instruction after the firmware is + /// enabled (`enable_firmware`). This only works once after an erase operation. fn write_page(&self, buf: &[u8]) -> Result<(), ISPError> { - let page_size = self.part.page_size; - let mut xfer_buf: Vec = vec![0; page_size + 2]; + let length = buf.len() + 2; + let mut xfer_buf: Vec = vec![0; length]; xfer_buf[0] = REPORT_ID_XFER; xfer_buf[1] = XFER_WRITE_PAGE; - xfer_buf[2..page_size + 2].clone_from_slice(buf); + xfer_buf[2..length].clone_from_slice(buf); self.data_device() .send_feature_report(&xfer_buf) .map_err(ISPError::from)?; Ok(()) } - fn erase(&self) -> Result<(), ISPError> { - info!("Erasing..."); - let cmd: [u8; COMMAND_LENGTH] = [ - REPORT_ID_CMD, - CMD_ERASE, - CMD_ERASE, - CMD_ERASE, - CMD_ERASE, - CMD_ERASE, - ]; - self.request_device - .send_feature_report(&cmd) - .map_err(ISPError::from)?; - thread::sleep(time::Duration::from_millis(2000)); + /// Sets a LJMP (0x02) opcode at . + /// This enables the main firmware by making the bootloader jump to it on reset. + /// + /// Side-effect: enables reading the firmware without erasing flash first. + /// Credits to @gashtaan for finding this out. + fn enable_firmware(&self) -> Result<(), ISPError> { + info!("Enabling firmware..."); + let cmd: [u8; COMMAND_LENGTH] = + [REPORT_ID_CMD, CMD_ENABLE_FIRMWARE, 0, 0, 0, 0]; + + self.request_device.send_feature_report(&cmd)?; Ok(()) } - fn finalize(&self) -> Result<(), ISPError> { - info!("Finalizing..."); - let cmd: [u8; COMMAND_LENGTH] = [ - REPORT_ID_CMD, - CMD_MAGIC_SAUCE, - CMD_MAGIC_SAUCE, - CMD_MAGIC_SAUCE, - CMD_MAGIC_SAUCE, - CMD_MAGIC_SAUCE, - ]; + /// Erases everything in flash, except the ISP bootloader section itself and initializes the + /// reset vector to jump to ISP. + fn erase(&self) -> Result<(), ISPError> { + info!("Erasing..."); + let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_ERASE, 0, 0, 0, 0]; self.request_device .send_feature_report(&cmd) .map_err(ISPError::from)?; + thread::sleep(time::Duration::from_millis(2000)); Ok(()) } } diff --git a/src/main.rs b/src/main.rs index ae1ed31..dc397e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -111,10 +111,10 @@ fn err_main() -> Result<(), CLIError> { let mut file_buf = Vec::new(); file.read_to_end(&mut file_buf).map_err(CLIError::from)?; let file_str = String::from_utf8_lossy(&file_buf[..]); - let mut firmware = from_ihex(&file_str, part.flash_size).map_err(CLIError::from)?; + let mut firmware = from_ihex(&file_str, part.firmware_size).map_err(CLIError::from)?; - if firmware.len() < part.flash_size { - firmware.resize(part.flash_size, 0); + if firmware.len() < part.firmware_size { + firmware.resize(part.firmware_size, 0); } let isp = ISPDevice::new(part).map_err(CLIError::from)?; @@ -135,7 +135,7 @@ impl PartCommand for Command { arg!(-p --part ) .value_parser(Part::available_parts()) .required_unless_present_all([ - "flash_size", + "firmware_size", "bootloader_size", "page_size", "vendor_id", @@ -143,7 +143,7 @@ impl PartCommand for Command { ]), ) .arg( - arg!(--flash_size ) + arg!(--firmware_size ) .required_unless_present("part") .value_parser(clap::value_parser!(usize)), ) @@ -178,14 +178,14 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { _ => Part::default(), }; - let flash_size = sub_matches.get_one::("flash_size"); + let firmware_size = sub_matches.get_one::("firmware_size"); let bootloader_size = sub_matches.get_one::("bootloader_size"); let page_size = sub_matches.get_one::("page_size"); let vendor_id = sub_matches.get_one::("vendor_id"); let product_id = sub_matches.get_one::("product_id"); - if let Some(flash_size) = flash_size { - part.flash_size = *flash_size; + if let Some(firmware_size) = firmware_size { + part.firmware_size = *firmware_size; } if let Some(bootloader_size) = bootloader_size { part.bootloader_size = *bootloader_size; diff --git a/src/part.rs b/src/part.rs index 75994b2..ef96e79 100644 --- a/src/part.rs +++ b/src/part.rs @@ -2,7 +2,7 @@ use phf::{phf_map, Map}; #[derive(Default, Clone, Copy)] pub struct Part { - pub flash_size: usize, + pub firmware_size: usize, pub bootloader_size: usize, pub page_size: usize, pub vendor_id: u16, @@ -10,7 +10,7 @@ pub struct Part { } pub const PART_NUPHY_AIR60: Part = Part { - flash_size: 61440, // 61440 until bootloader + firmware_size: 61440, // 61440 until bootloader bootloader_size: 4096, page_size: 2048, vendor_id: 0x05ac, @@ -18,7 +18,7 @@ pub const PART_NUPHY_AIR60: Part = Part { }; pub const PART_XINMENG_K916: Part = Part { - flash_size: 61440, // 61440 until bootloader + firmware_size: 61440, // 61440 until bootloader bootloader_size: 4096, page_size: 2048, vendor_id: 0x258a, @@ -26,7 +26,7 @@ pub const PART_XINMENG_K916: Part = Part { }; pub const PART_RE_K70_BYK800: Part = Part { - flash_size: 28672, // 28672 until bootloader + firmware_size: 28672, // 28672 until bootloader bootloader_size: 4096, page_size: 2048, vendor_id: 0x258a, @@ -34,7 +34,7 @@ pub const PART_RE_K70_BYK800: Part = Part { }; pub const PART_TERPORT_TR95: Part = Part { - flash_size: 61440, // 61440 until bootloader + firmware_size: 61440, // 61440 until bootloader bootloader_size: 4096, page_size: 2048, vendor_id: 0x258a, @@ -42,7 +42,7 @@ pub const PART_TERPORT_TR95: Part = Part { }; pub const PART_REDRAGON_FIZZ_K617: Part = Part { - flash_size: 61440, // 61440 until bootloader + firmware_size: 61440, // 61440 until bootloader bootloader_size: 4096, page_size: 2048, vendor_id: 0x258a, @@ -66,7 +66,7 @@ impl Part { } pub fn num_pages(&self) -> usize { - self.flash_size / self.page_size + self.firmware_size / self.page_size } } diff --git a/tools/functional-test.sh b/tools/functional-test.sh index 8846fb1..c6acfbe 100755 --- a/tools/functional-test.sh +++ b/tools/functional-test.sh @@ -57,7 +57,7 @@ reboot_device echo "Custom read..." $TOOL read \ - --flash_size 61440 \ + --firmware_size 61440 \ --bootloader_size 4096 \ --page_size 2048 \ --vendor_id 0x05ac \ From 57fccdce63e2c77cb448bc9832c46143f4642824 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 11 Dec 2023 17:15:08 +0100 Subject: [PATCH 036/141] bump to v0.0.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6f2f4e..ffbf656 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.0.4" +version = "0.0.5" dependencies = [ "clap", "clap-num", diff --git a/Cargo.toml b/Cargo.toml index f9d3d76..d6409ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based devices """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.0.4" +version = "0.0.5" edition = "2021" license = "MIT" rust-version = "1.65" From 92aaa0e5af1f758d66119fc0188d21c749b1499a Mon Sep 17 00:00:00 2001 From: usr44 <140394475+usr44@users.noreply.github.com> Date: Tue, 19 Dec 2023 18:23:32 +0000 Subject: [PATCH 037/141] device: redragon-k614-anivia (#24) Support for [Redragon K614 Anivia](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) keyboard. Writing tested by flashing SMK firmware. --- README.md | 1 + src/part.rs | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b1b479e..a2885a8 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ sinowealth-kb-tool write \ | [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | +| [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | diff --git a/src/part.rs b/src/part.rs index ef96e79..21ae471 100644 --- a/src/part.rs +++ b/src/part.rs @@ -49,6 +49,14 @@ pub const PART_REDRAGON_FIZZ_K617: Part = Part { product_id: 0x0049, }; +pub const PART_REDRAGON_ANIVIA_K614: Part = Part { + firmware_size: 61440, // 61440 until bootloader + bootloader_size: 4096, + page_size: 2048, + vendor_id: 0x258a, + product_id: 0x0049, +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 @@ -57,7 +65,8 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "xinmeng-k916" => PART_XINMENG_K916, "re-k70-byk800" => PART_RE_K70_BYK800, "terport-tr95" => PART_TERPORT_TR95, - "redragon-k6170-fizz" => PART_REDRAGON_FIZZ_K617 + "redragon-k6170-fizz" => PART_REDRAGON_FIZZ_K617, + "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614 }; impl Part { From 1b26ab05efd1f76c155dc89b337cc2fcd5cfea14 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Wed, 20 Dec 2023 21:44:41 +0100 Subject: [PATCH 038/141] device selection by HID usage_page, usage and the index which matching collections appear in (#26) HID device selection in Windows relied on specific "ColXX" values appearing in device paths. This proved to be unreliable because different keyboards have different HID report structures and thus would cause the ISP report-capable device to appear under a different path than expected. Changes here drop the hardcoded approach and attempt to filter out ISP-capable devices by expected `usage_page` & `usage` values. Unfortunately, some devices like the nuphy-air60 have multiple collections in their HID report descriptor with the `usage_page == 0xff00 && usage == 0x0001` and thus it was necessary to introduce an `isp_index` value to tell which device is capable of receiving the ISP report. (Ideally, the whole HID report would be accessible so that one could pinpoint exactly which collection has the ISP report definition) This issue only affected windows, because macos and linux (libusb) allow sending the report to any HID device that is part of the same USB device. The current and latest hidapi-rs library does not expose usage & usage_page values when using the libusb backend, but from what I can tell support in libusb has already been implemented, perhaps I should explore whether support could be enabled and remove some of the `#[cfg(...)]` macros littering the codebase, but for now it's not absolutely necessary since this still works. Side-note: I have attempted to use the hidraw backend, but the ISP device is not recognized as a USBHID device because it has no interrupt endpoint. Addresses https://github.com/carlossless/sinowealth-kb-tool/issues/25 and possibly addresses https://github.com/carlossless/sinowealth-kb-tool/issues/15 https://github.com/carlossless/sinowealth-kb-tool/issues/16 --- README.md | 2 + src/isp.rs | 140 ++++++++++++++++++--------------------- src/main.rs | 10 +++ src/part.rs | 11 ++- tools/functional-test.sh | 1 + 5 files changed, 87 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index a2885a8..ece958c 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ sinowealth-kb-tool read \ --page_size 2048 \ --vendor_id 0x05ac \ --product_id 0x024f \ + --isp_index 0 foobar.hex ``` @@ -51,6 +52,7 @@ sinowealth-kb-tool write \ --page_size 2048 \ --vendor_id 0x05ac \ --product_id 0x024f \ + --isp_index 0 foobar.hex ``` diff --git a/src/isp.rs b/src/isp.rs index 7f58cc6..6ce7370 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -1,7 +1,6 @@ use std::{thread, time}; -use hidapi::DeviceInfo; -use log::{debug, info, warn}; +use log::{debug, info}; use thiserror::Error; use super::{part::*, util}; @@ -18,6 +17,9 @@ const GAMING_KB_PRODUCT_ID: u16 = 0x1020; const COMMAND_LENGTH: usize = 6; +const HID_ISP_USAGE_PAGE: u16 = 0xff00; +const HID_ISP_USAGE: u16 = 0x0001; + const REPORT_ID_CMD: u8 = 0x05; const REPORT_ID_XFER: u8 = 0x06; @@ -39,8 +41,8 @@ pub struct ISPDevice { #[derive(Debug, Error)] pub enum ISPError { - #[error("Duplicate devices found")] - DuplicateDevices(String, String), + #[error("Unusual number of matching HID devices: {0}")] + IrregularDeviceCount(usize), #[error("Device not found")] NotFound, #[error(transparent)] @@ -85,80 +87,56 @@ impl ISPDevice { fn open_isp_devices() -> Result { let api = Self::hidapi(); - let mut request_device: Option<&DeviceInfo> = None; - #[cfg(target_os = "windows")] - let mut data_device: Option<&DeviceInfo> = None; - - for device_info in api.device_list() { - if !(device_info.vendor_id() == GAMING_KB_VENDOR_ID - && device_info.product_id() == GAMING_KB_PRODUCT_ID) - { - continue; - } - - let path = device_info.path(); - let path_str = path.to_str().unwrap(); - - debug!("Enumerating: {}", path_str); - - #[cfg(target_os = "windows")] - { - // Windows requires that we use specific devices for requests and data - // https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/hidclass-hardware-ids-for-top-level-collections - if path_str.contains("Col02") { - if let Some(request_device) = request_device { - return Err(ISPError::DuplicateDevices( - request_device.path().to_str().unwrap().to_owned(), - path_str.to_owned(), - )); - } - request_device = Some(device_info); - continue; - } + let devices: Vec<_> = api.device_list() + .filter(|d| { + #[cfg(not(target_os = "linux"))] + return d.vendor_id() == GAMING_KB_VENDOR_ID + && d.product_id() == GAMING_KB_PRODUCT_ID + && d.usage_page() == HID_ISP_USAGE_PAGE + && d.usage() == HID_ISP_USAGE; + #[cfg(target_os = "linux")] + return d.vendor_id() == GAMING_KB_VENDOR_ID + && d.product_id() == GAMING_KB_PRODUCT_ID; + }) + .collect(); - if path_str.contains("Col03") { - if let Some(data_device) = data_device { - return Err(ISPError::DuplicateDevices( - data_device.path().to_str().unwrap().to_owned(), - path_str.to_owned(), - )); - } - data_device = Some(device_info); - continue; - } - }; + for d in &devices { + #[cfg(not(target_os = "linux"))] + debug!("Found Device: {:?} {:#06x} {:#06x}", d.path(), d.usage_page(), d.usage()); + #[cfg(target_os = "linux")] + debug!("Found Device: {:?}", d.path()); + } - #[cfg(not(target_os = "windows"))] - if let Some(request_device) = request_device { - if request_device.path() != path { - warn!("Duplicate device found. Only the first one will be used"); - } - continue; - } else { - request_device = Some(device_info); - continue; - }; + let device_count = devices.len(); + if device_count == 0 { + return Err(ISPError::NotFound) } - if let Some(request_device) = request_device { + #[cfg(not(target_os = "windows"))] + if device_count == 1 { + let request_device = devices.first().unwrap(); debug!("Request device: {:?}", request_device.path()); - #[cfg(target_os = "windows")] - if let Some(data_device) = data_device { - debug!("Data device: {:?}", data_device.path()); - return Ok(HIDDevices { - request: api.open_path(request_device.path()).unwrap(), - data: api.open_path(data_device.path()).unwrap(), - }); - } else { - return Err(ISPError::NotFound); - } + return Ok(HIDDevices { + request: api.open_path(request_device.path()).unwrap(), + }); + } else { + return Err(ISPError::IrregularDeviceCount(device_count)) + } - #[cfg(not(target_os = "windows"))] + #[cfg(target_os = "windows")] + if device_count == 1 { + return Err(ISPError::IrregularDeviceCount(device_count)) + } else if device_count == 2 { + let request_device = devices[0]; + let data_device = devices[1]; + debug!("Request device: {:?}", request_device.path()); + debug!("Data device: {:?}", data_device.path()); return Ok(HIDDevices { request: api.open_path(request_device.path()).unwrap(), + data: api.open_path(data_device.path()).unwrap(), }); } else { - Err(ISPError::NotFound) + return Err(ISPError::IrregularDeviceCount(device_count)) } } @@ -173,19 +151,29 @@ impl ISPDevice { let request_device_info = api .device_list() .filter(|d| { - d.vendor_id() == part.vendor_id + #[cfg(not(target_os = "linux"))] + return d.vendor_id() == part.vendor_id && d.product_id() == part.product_id - && d.interface_number() == 1 + && d.usage_page() == HID_ISP_USAGE_PAGE + && d.usage() == HID_ISP_USAGE; + #[cfg(target_os = "linux")] + return d.vendor_id() == part.vendor_id + && d.product_id() == part.product_id; }) - .find(|_d| { + .enumerate() + .find_map(|(_i, d)| { + #[cfg(not(target_os = "linux"))] + debug!("Found Device: {:?} {:#06x} {:#06x}", d.path(), d.usage_page(), d.usage()); + #[cfg(target_os = "linux")] + debug!("Found Device: {:?}", d.path()); #[cfg(target_os = "windows")] - { - return String::from_utf8_lossy(_d.path().to_bytes()) - .to_string() - .contains("Col05"); + if _i == part.isp_index { + return Some(d); + } else { + return None; } #[cfg(not(target_os = "windows"))] - true + Some(d) }); let Some(request_device_info) = request_device_info else { diff --git a/src/main.rs b/src/main.rs index dc397e1..f5819ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,6 +140,7 @@ impl PartCommand for Command { "page_size", "vendor_id", "product_id", + "isp_index" ]), ) .arg( @@ -167,6 +168,11 @@ impl PartCommand for Command { .required_unless_present("part") .value_parser(maybe_hex::), ) + .arg( + arg!(--isp_index ) + .required_unless_present("part") + .value_parser(clap::value_parser!(usize)), + ) } } @@ -183,6 +189,7 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { let page_size = sub_matches.get_one::("page_size"); let vendor_id = sub_matches.get_one::("vendor_id"); let product_id = sub_matches.get_one::("product_id"); + let isp_index = sub_matches.get_one::("isp_index"); if let Some(firmware_size) = firmware_size { part.firmware_size = *firmware_size; @@ -199,5 +206,8 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { if let Some(product_id) = product_id { part.product_id = *product_id; } + if let Some(isp_index) = isp_index { + part.isp_index = *isp_index; + } return part; } diff --git a/src/part.rs b/src/part.rs index 21ae471..1b5e9ed 100644 --- a/src/part.rs +++ b/src/part.rs @@ -7,6 +7,9 @@ pub struct Part { pub page_size: usize, pub vendor_id: u16, pub product_id: u16, + /// Index of usage_page == 0xff00 && usage == 0x0001 collections at which the isp report appears in. + /// Important only for windows because its HIDAPI requires us to use a specific device for each collection + pub isp_index: usize } pub const PART_NUPHY_AIR60: Part = Part { @@ -15,6 +18,7 @@ pub const PART_NUPHY_AIR60: Part = Part { page_size: 2048, vendor_id: 0x05ac, product_id: 0x024f, + isp_index: 1, }; pub const PART_XINMENG_K916: Part = Part { @@ -23,6 +27,7 @@ pub const PART_XINMENG_K916: Part = Part { page_size: 2048, vendor_id: 0x258a, product_id: 0x00a1, + isp_index: 1, }; pub const PART_RE_K70_BYK800: Part = Part { @@ -31,6 +36,7 @@ pub const PART_RE_K70_BYK800: Part = Part { page_size: 2048, vendor_id: 0x258a, product_id: 0x001a, + isp_index: 0, }; pub const PART_TERPORT_TR95: Part = Part { @@ -39,6 +45,7 @@ pub const PART_TERPORT_TR95: Part = Part { page_size: 2048, vendor_id: 0x258a, product_id: 0x0049, + isp_index: 1, }; pub const PART_REDRAGON_FIZZ_K617: Part = Part { @@ -47,6 +54,7 @@ pub const PART_REDRAGON_FIZZ_K617: Part = Part { page_size: 2048, vendor_id: 0x258a, product_id: 0x0049, + isp_index: 1, }; pub const PART_REDRAGON_ANIVIA_K614: Part = Part { @@ -55,6 +63,7 @@ pub const PART_REDRAGON_ANIVIA_K614: Part = Part { page_size: 2048, vendor_id: 0x258a, product_id: 0x0049, + isp_index: 1, }; pub static PARTS: Map<&'static str, Part> = phf_map! { @@ -65,7 +74,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "xinmeng-k916" => PART_XINMENG_K916, "re-k70-byk800" => PART_RE_K70_BYK800, "terport-tr95" => PART_TERPORT_TR95, - "redragon-k6170-fizz" => PART_REDRAGON_FIZZ_K617, + "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614 }; diff --git a/tools/functional-test.sh b/tools/functional-test.sh index c6acfbe..45f1605 100755 --- a/tools/functional-test.sh +++ b/tools/functional-test.sh @@ -62,6 +62,7 @@ $TOOL read \ --page_size 2048 \ --vendor_id 0x05ac \ --product_id 0x024f \ + --isp_index 1 \ "$FILE_CUSTOM" reboot_device From c8fe17a2e6447c5bfc8a4eb45d77f3ac76aaea9e Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Wed, 20 Dec 2023 21:46:50 +0100 Subject: [PATCH 039/141] bump to v0.0.6 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffbf656..bf90c5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.0.5" +version = "0.0.6" dependencies = [ "clap", "clap-num", diff --git a/Cargo.toml b/Cargo.toml index d6409ca..f6d103b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based devices """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.0.5" +version = "0.0.6" edition = "2021" license = "MIT" rust-version = "1.65" From d174e51cbdb3923e9b62fb25ce875346e1abecae Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Thu, 21 Dec 2023 13:27:14 +0100 Subject: [PATCH 040/141] fmt --- src/isp.rs | 31 ++++++++++++++++++++----------- src/main.rs | 2 +- src/part.rs | 2 +- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/isp.rs b/src/isp.rs index 6ce7370..254baed 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -87,7 +87,8 @@ impl ISPDevice { fn open_isp_devices() -> Result { let api = Self::hidapi(); - let devices: Vec<_> = api.device_list() + let devices: Vec<_> = api + .device_list() .filter(|d| { #[cfg(not(target_os = "linux"))] return d.vendor_id() == GAMING_KB_VENDOR_ID @@ -102,14 +103,19 @@ impl ISPDevice { for d in &devices { #[cfg(not(target_os = "linux"))] - debug!("Found Device: {:?} {:#06x} {:#06x}", d.path(), d.usage_page(), d.usage()); + debug!( + "Found Device: {:?} {:#06x} {:#06x}", + d.path(), + d.usage_page(), + d.usage() + ); #[cfg(target_os = "linux")] debug!("Found Device: {:?}", d.path()); } let device_count = devices.len(); if device_count == 0 { - return Err(ISPError::NotFound) + return Err(ISPError::NotFound); } #[cfg(not(target_os = "windows"))] @@ -120,12 +126,12 @@ impl ISPDevice { request: api.open_path(request_device.path()).unwrap(), }); } else { - return Err(ISPError::IrregularDeviceCount(device_count)) + return Err(ISPError::IrregularDeviceCount(device_count)); } #[cfg(target_os = "windows")] if device_count == 1 { - return Err(ISPError::IrregularDeviceCount(device_count)) + return Err(ISPError::IrregularDeviceCount(device_count)); } else if device_count == 2 { let request_device = devices[0]; let data_device = devices[1]; @@ -136,7 +142,7 @@ impl ISPDevice { data: api.open_path(data_device.path()).unwrap(), }); } else { - return Err(ISPError::IrregularDeviceCount(device_count)) + return Err(ISPError::IrregularDeviceCount(device_count)); } } @@ -157,13 +163,17 @@ impl ISPDevice { && d.usage_page() == HID_ISP_USAGE_PAGE && d.usage() == HID_ISP_USAGE; #[cfg(target_os = "linux")] - return d.vendor_id() == part.vendor_id - && d.product_id() == part.product_id; + return d.vendor_id() == part.vendor_id && d.product_id() == part.product_id; }) .enumerate() .find_map(|(_i, d)| { #[cfg(not(target_os = "linux"))] - debug!("Found Device: {:?} {:#06x} {:#06x}", d.path(), d.usage_page(), d.usage()); + debug!( + "Found Device: {:?} {:#06x} {:#06x}", + d.path(), + d.usage_page(), + d.usage() + ); #[cfg(target_os = "linux")] debug!("Found Device: {:?}", d.path()); #[cfg(target_os = "windows")] @@ -369,8 +379,7 @@ impl ISPDevice { /// Credits to @gashtaan for finding this out. fn enable_firmware(&self) -> Result<(), ISPError> { info!("Enabling firmware..."); - let cmd: [u8; COMMAND_LENGTH] = - [REPORT_ID_CMD, CMD_ENABLE_FIRMWARE, 0, 0, 0, 0]; + let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_ENABLE_FIRMWARE, 0, 0, 0, 0]; self.request_device.send_feature_report(&cmd)?; Ok(()) diff --git a/src/main.rs b/src/main.rs index f5819ea..a8ccc46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,7 +140,7 @@ impl PartCommand for Command { "page_size", "vendor_id", "product_id", - "isp_index" + "isp_index", ]), ) .arg( diff --git a/src/part.rs b/src/part.rs index 1b5e9ed..9f3962a 100644 --- a/src/part.rs +++ b/src/part.rs @@ -9,7 +9,7 @@ pub struct Part { pub product_id: u16, /// Index of usage_page == 0xff00 && usage == 0x0001 collections at which the isp report appears in. /// Important only for windows because its HIDAPI requires us to use a specific device for each collection - pub isp_index: usize + pub isp_index: usize, } pub const PART_NUPHY_AIR60: Part = Part { From b9c49612155ff28325692ed5522c7a8d91edf85c Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Thu, 21 Dec 2023 21:44:17 +0100 Subject: [PATCH 041/141] device: royalkludge-rk100 (#27) https://github.com/carlossless/sinowealth-kb-tool/issues/25 --- README.md | 1 + src/part.rs | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ece958c..8970f80 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ sinowealth-kb-tool write \ | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | +| [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ❓ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | diff --git a/src/part.rs b/src/part.rs index 9f3962a..58249e6 100644 --- a/src/part.rs +++ b/src/part.rs @@ -66,6 +66,15 @@ pub const PART_REDRAGON_ANIVIA_K614: Part = Part { isp_index: 1, }; +pub const PART_ROYALKLUDGE_RK100: Part = Part { + firmware_size: 61440, // 61440 until bootloader + bootloader_size: 4096, + page_size: 2048, + vendor_id: 0x258a, + product_id: 0x0056, + isp_index: 0, +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 @@ -75,7 +84,8 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "re-k70-byk800" => PART_RE_K70_BYK800, "terport-tr95" => PART_TERPORT_TR95, "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, - "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614 + "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, + "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, }; impl Part { From efd02506ac4e1a2f068c2d1097e0b0af0d966ee9 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Thu, 21 Dec 2023 21:53:49 +0100 Subject: [PATCH 042/141] device: genesis-thor-300 (#30) Added [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue). Thanks to @gashtaan for testing on this keyboard! --- README.md | 1 + src/part.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index 8970f80..2176cb0 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ sinowealth-kb-tool write \ | [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ❓ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | +| [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | ## Prerequisites diff --git a/src/part.rs b/src/part.rs index 58249e6..4cef5fd 100644 --- a/src/part.rs +++ b/src/part.rs @@ -66,6 +66,15 @@ pub const PART_REDRAGON_ANIVIA_K614: Part = Part { isp_index: 1, }; +pub const PART_GENESIS_THOR_300: Part = Part { + firmware_size: 28672, // 28672 until bootloader + bootloader_size: 4096, + page_size: 2048, + vendor_id: 0x258a, + product_id: 0x001f, + isp_index: 0, +}; + pub const PART_ROYALKLUDGE_RK100: Part = Part { firmware_size: 61440, // 61440 until bootloader bootloader_size: 4096, @@ -86,6 +95,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, + "genesis-thor-300" => PART_GENESIS_THOR_300, }; impl Part { From 06874370268ff7bcb0ddae5524cf11f890262448 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Dec 2023 10:42:17 +0100 Subject: [PATCH 043/141] device: genesis-thor-300-rgb (#33) Added [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown). Thanks to @gashtaan for testing on this keyboard! --- README.md | 1 + src/part.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index 2176cb0..962d8da 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ sinowealth-kb-tool write \ | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | | [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | +| [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | ## Prerequisites diff --git a/src/part.rs b/src/part.rs index 4cef5fd..c0738b0 100644 --- a/src/part.rs +++ b/src/part.rs @@ -75,6 +75,15 @@ pub const PART_GENESIS_THOR_300: Part = Part { isp_index: 0, }; +pub const PART_GENESIS_THOR_300_RGB: Part = Part { + firmware_size: 61440, // 61440 until bootloader + bootloader_size: 4096, + page_size: 2048, + vendor_id: 0x258a, + product_id: 0x0090, + isp_index: 0, +}; + pub const PART_ROYALKLUDGE_RK100: Part = Part { firmware_size: 61440, // 61440 until bootloader bootloader_size: 4096, @@ -96,6 +105,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, "genesis-thor-300" => PART_GENESIS_THOR_300, + "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, }; impl Part { From 050d2ac172d3805d69745b2796d12c7e6ef80bd5 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Dec 2023 13:58:54 +0200 Subject: [PATCH 044/141] Added device report issue template --- .github/ISSUE_TEMPLATE/device-report.md | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/device-report.md diff --git a/.github/ISSUE_TEMPLATE/device-report.md b/.github/ISSUE_TEMPLATE/device-report.md new file mode 100644 index 0000000..450b294 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/device-report.md @@ -0,0 +1,72 @@ +--- +name: Device Report +about: For reporting operation with a particular device +title: "[device-report] Manufacturer Model" +labels: device-report +assignees: '' + +--- + +## Device Info + +- Sinowealth Device: _example `SH68F90A`_ +- IC Label: _example `BYK916`_ +- Product Page: _example https://nuphy.com/products/air60_ + +## Part Info + +``` +firmware_size: 61440 +bootloader_size: 4096 +page_size: 2048 +vendor_id: 0x258a +product_id: 0x0049 +isp_index: 0 +``` + +## Operations Tested + +- [ ] Read +- [ ] Write + +## Platforms Tested + +- [ ] linux +- [ ] macos +- [ ] windows + +## Checksums + +- Bootloader MD5: `beefcafebeefcafebeefcafebeefcafe` +- Stock Firmware MD5: `deadbeefdeadbeefdeadbeefdeadbeef` + +## HID Dump + +A dump from [usbhid-dump](https://github.com/DIGImend/usbhid-dump), [win-hid-dump](https://github.com/todbot/win-hid-dump) or [mac-hid-dump](https://github.com/todbot/mac-hid-dump) + +
+HID Tool Output + +``` +# NuPhy Air60 using win-hid-dump +... +05AC:024F: BY Tech - Air60 +PATH:\\?\hid#vid_05ac&pid_024f&mi_01&col05#7&2af01ac7&0&0004#{4d1e55b2-f16f-11cf-88cb-001111000030} +DESCRIPTOR: + 06 00 FF 09 01 A1 01 85 05 15 00 25 01 35 00 45 + 01 65 00 55 00 75 01 95 28 B1 03 C1 00 + (29 bytes) +05AC:024F: BY Tech - Air60 +PATH:\\?\hid#vid_05ac&pid_024f&mi_00#7&132c8e82&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}\kbd +DESCRIPTOR: + 05 01 09 06 A1 01 05 07 19 E0 29 E7 15 00 25 01 + 35 00 45 01 65 00 55 00 75 01 95 08 81 02 95 30 + 81 03 05 FF 09 03 25 FF 45 00 75 08 95 01 81 02 + 05 08 19 01 29 05 25 01 45 01 75 01 95 05 91 02 + 95 03 91 03 05 C0 09 00 25 7F 45 00 75 08 95 40 + B1 02 C1 00 + (84 bytes) +... +``` + +
From 7f7c8f166e6ffd0e368f82d835cf2e8d92e7453a Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Dec 2023 14:29:53 +0200 Subject: [PATCH 045/141] sort possible part values --- src/part.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/part.rs b/src/part.rs index c0738b0..dcd2ecf 100644 --- a/src/part.rs +++ b/src/part.rs @@ -110,7 +110,9 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { impl Part { pub fn available_parts() -> Vec<&'static str> { - PARTS.keys().copied().collect::>() + let mut parts = PARTS.keys().copied().collect::>(); + parts.sort(); + parts } pub fn num_pages(&self) -> usize { From 30de388ab46339de0ea8349fc42b7aaf0d8d9aa9 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Dec 2023 17:45:28 +0100 Subject: [PATCH 046/141] usage_page and usage arguments (#35) Customizable `usage` and `usage_page` selectors, addressing the need found in https://github.com/carlossless/sinowealth-kb-tool/issues/29#issuecomment-1868324411 --- README.md | 20 ++++++----- src/isp.rs | 4 +-- src/main.rs | 47 +++++++++++--------------- src/part.rs | 73 ++++++++++++++++++++++------------------ tools/functional-test.sh | 38 ++++++++++++++++++--- 5 files changed, 107 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 962d8da..be0d7da 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,14 @@ sinowealth-kb-tool read -p nuphy-air60 --full full.hex # custom device sinowealth-kb-tool read \ - --firmware_size 61440 \ - --bootloader_size 4096 \ - --page_size 2048 \ --vendor_id 0x05ac \ --product_id 0x024f \ - --isp_index 0 + --firmware_size 61440 \ + --bootloader_size 4096 \ # optional + --page_size 2048 \ # optional + --isp_usage_page 0xff00 \ # optional + --isp_usage 0x0001 \ # optional + --isp_index 0 \ # optional foobar.hex ``` @@ -47,12 +49,14 @@ sinowealth-kb-tool write -p nuphy-air60 foobar.hex # custom device sinowealth-kb-tool write \ - --firmware_size 61440 \ - --bootloader_size 4096 \ - --page_size 2048 \ --vendor_id 0x05ac \ --product_id 0x024f \ - --isp_index 0 + --firmware_size 61440 \ + --bootloader_size 4096 \ # optional + --page_size 2048 \ # optional + --isp_usage_page 0xff00 \ # optional + --isp_usage 0x0001 \ # optional + --isp_index 0 \ # optional foobar.hex ``` diff --git a/src/isp.rs b/src/isp.rs index 254baed..2a964b3 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -160,8 +160,8 @@ impl ISPDevice { #[cfg(not(target_os = "linux"))] return d.vendor_id() == part.vendor_id && d.product_id() == part.product_id - && d.usage_page() == HID_ISP_USAGE_PAGE - && d.usage() == HID_ISP_USAGE; + && d.usage_page() == part.isp_usage_page + && d.usage() == part.isp_usage; #[cfg(target_os = "linux")] return d.vendor_id() == part.vendor_id && d.product_id() == part.product_id; }) diff --git a/src/main.rs b/src/main.rs index a8ccc46..7a46099 100644 --- a/src/main.rs +++ b/src/main.rs @@ -134,30 +134,13 @@ impl PartCommand for Command { self.arg( arg!(-p --part ) .value_parser(Part::available_parts()) - .required_unless_present_all([ - "firmware_size", - "bootloader_size", - "page_size", - "vendor_id", - "product_id", - "isp_index", - ]), + .required_unless_present_all(["firmware_size", "vendor_id", "product_id"]), ) .arg( arg!(--firmware_size ) .required_unless_present("part") .value_parser(clap::value_parser!(usize)), ) - .arg( - arg!(--bootloader_size ) - .required_unless_present("part") - .value_parser(clap::value_parser!(usize)), - ) - .arg( - arg!(--page_size ) - .required_unless_present("part") - .value_parser(clap::value_parser!(usize)), - ) .arg( arg!(--vendor_id ) .required_unless_present("part") @@ -168,11 +151,11 @@ impl PartCommand for Command { .required_unless_present("part") .value_parser(maybe_hex::), ) - .arg( - arg!(--isp_index ) - .required_unless_present("part") - .value_parser(clap::value_parser!(usize)), - ) + .arg(arg!(--bootloader_size ).value_parser(clap::value_parser!(usize))) + .arg(arg!(--page_size ).value_parser(clap::value_parser!(usize))) + .arg(arg!(--isp_usage_page ).value_parser(maybe_hex::)) + .arg(arg!(--isp_usage ).value_parser(maybe_hex::)) + .arg(arg!(--isp_index ).value_parser(clap::value_parser!(usize))) } } @@ -181,7 +164,7 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { let mut part = match part_name { Some(part_name) => *PARTS.get(part_name).unwrap(), - _ => Part::default(), + _ => PART_BASE_DEFAULT, }; let firmware_size = sub_matches.get_one::("firmware_size"); @@ -189,22 +172,30 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { let page_size = sub_matches.get_one::("page_size"); let vendor_id = sub_matches.get_one::("vendor_id"); let product_id = sub_matches.get_one::("product_id"); + let isp_usage_page = sub_matches.get_one::("isp_usage_page"); + let isp_usage = sub_matches.get_one::("isp_usage"); let isp_index = sub_matches.get_one::("isp_index"); if let Some(firmware_size) = firmware_size { part.firmware_size = *firmware_size; } + if let Some(vendor_id) = vendor_id { + part.vendor_id = *vendor_id; + } + if let Some(product_id) = product_id { + part.product_id = *product_id; + } if let Some(bootloader_size) = bootloader_size { part.bootloader_size = *bootloader_size; } if let Some(page_size) = page_size { part.page_size = *page_size; } - if let Some(vendor_id) = vendor_id { - part.vendor_id = *vendor_id; + if let Some(isp_usage_page) = isp_usage_page { + part.isp_usage_page = *isp_usage_page; } - if let Some(product_id) = product_id { - part.product_id = *product_id; + if let Some(isp_usage) = isp_usage { + part.isp_usage = *isp_usage; } if let Some(isp_index) = isp_index { part.isp_index = *isp_index; diff --git a/src/part.rs b/src/part.rs index dcd2ecf..fb3c5b4 100644 --- a/src/part.rs +++ b/src/part.rs @@ -1,96 +1,103 @@ use phf::{phf_map, Map}; -#[derive(Default, Clone, Copy)] +#[derive(Clone, Copy)] pub struct Part { pub firmware_size: usize, pub bootloader_size: usize, pub page_size: usize, pub vendor_id: u16, pub product_id: u16, - /// Index of usage_page == 0xff00 && usage == 0x0001 collections at which the isp report appears in. - /// Important only for windows because its HIDAPI requires us to use a specific device for each collection + + // The following properties and values are important only for windows support because its + // HIDAPI requires us to use a specific device for each collection + /// HID collection `usage_page` with the ISP report + pub isp_usage_page: u16, + /// HID collection `usage` with the ISP report + pub isp_usage: u16, + /// Index of matching (usage_page && usage) collection at which the ISP report appears in. pub isp_index: usize, } -pub const PART_NUPHY_AIR60: Part = Part { - firmware_size: 61440, // 61440 until bootloader +pub const PART_BASE_DEFAULT: Part = Part { + firmware_size: 0, bootloader_size: 4096, page_size: 2048, + + vendor_id: 0x0000, + product_id: 0x0000, + + isp_usage_page: 0xff00, + isp_usage: 0x0001, + isp_index: 0, +}; + +pub const PART_BASE_SH68F90: Part = Part { + firmware_size: 61440, // 61440 until bootloader + ..PART_BASE_DEFAULT +}; + +pub const PART_BASE_SH68F881: Part = Part { + firmware_size: 28672, // 28672 until bootloader + ..PART_BASE_DEFAULT +}; + +pub const PART_NUPHY_AIR60: Part = Part { vendor_id: 0x05ac, product_id: 0x024f, isp_index: 1, + ..PART_BASE_SH68F90 }; pub const PART_XINMENG_K916: Part = Part { - firmware_size: 61440, // 61440 until bootloader - bootloader_size: 4096, - page_size: 2048, vendor_id: 0x258a, product_id: 0x00a1, isp_index: 1, + ..PART_BASE_SH68F90 }; pub const PART_RE_K70_BYK800: Part = Part { - firmware_size: 28672, // 28672 until bootloader - bootloader_size: 4096, - page_size: 2048, vendor_id: 0x258a, product_id: 0x001a, - isp_index: 0, + ..PART_BASE_SH68F881 }; pub const PART_TERPORT_TR95: Part = Part { - firmware_size: 61440, // 61440 until bootloader - bootloader_size: 4096, - page_size: 2048, vendor_id: 0x258a, product_id: 0x0049, isp_index: 1, + ..PART_BASE_SH68F90 }; pub const PART_REDRAGON_FIZZ_K617: Part = Part { - firmware_size: 61440, // 61440 until bootloader - bootloader_size: 4096, - page_size: 2048, vendor_id: 0x258a, product_id: 0x0049, isp_index: 1, + ..PART_BASE_SH68F90 }; pub const PART_REDRAGON_ANIVIA_K614: Part = Part { - firmware_size: 61440, // 61440 until bootloader - bootloader_size: 4096, - page_size: 2048, vendor_id: 0x258a, product_id: 0x0049, isp_index: 1, + ..PART_BASE_SH68F90 }; pub const PART_GENESIS_THOR_300: Part = Part { - firmware_size: 28672, // 28672 until bootloader - bootloader_size: 4096, - page_size: 2048, vendor_id: 0x258a, product_id: 0x001f, - isp_index: 0, + ..PART_BASE_SH68F881 }; pub const PART_GENESIS_THOR_300_RGB: Part = Part { - firmware_size: 61440, // 61440 until bootloader - bootloader_size: 4096, - page_size: 2048, vendor_id: 0x258a, product_id: 0x0090, - isp_index: 0, + ..PART_BASE_SH68F90 }; pub const PART_ROYALKLUDGE_RK100: Part = Part { - firmware_size: 61440, // 61440 until bootloader - bootloader_size: 4096, - page_size: 2048, vendor_id: 0x258a, product_id: 0x0056, - isp_index: 0, + ..PART_BASE_SH68F90 }; pub static PARTS: Map<&'static str, Part> = phf_map! { diff --git a/tools/functional-test.sh b/tools/functional-test.sh index 45f1605..8821472 100755 --- a/tools/functional-test.sh +++ b/tools/functional-test.sh @@ -15,13 +15,14 @@ FILE_FULL="$FILE_PREFIX-read-full.hex" FILE_CUSTOM="$FILE_PREFIX-read-custom.hex" FILE_OVERRIDE="$FILE_PREFIX-read-override.hex" 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 + #uhubctl -a off -p 1 -l 65-1 sleep 1 echo "Turning on port..." - uhubctl -a on -p 1 -l 65-1 + #uhubctl -a on -p 1 -l 65-1 echo "Waiting..." sleep 5 } @@ -58,10 +59,12 @@ reboot_device echo "Custom read..." $TOOL read \ --firmware_size 61440 \ - --bootloader_size 4096 \ - --page_size 2048 \ --vendor_id 0x05ac \ --product_id 0x024f \ + --bootloader_size 4096 \ + --page_size 2048 \ + --isp_usage_page 0xff00 \ + --isp_usage 0x0001 \ --isp_index 1 \ "$FILE_CUSTOM" @@ -126,4 +129,31 @@ fi reboot_device +echo "Custom write..." +$TOOL write \ + --firmware_size 61440 \ + --vendor_id 0x05ac \ + --product_id 0x024f \ + --bootloader_size 4096 \ + --page_size 2048 \ + --isp_usage_page 0xff00 \ + --isp_usage 0x0001 \ + --isp_index 1 \ + "$FILE_DEFAULT" + +reboot_device + +echo "Post-write read..." +$TOOL read --part "$PART" "$FILE_POST_WRITE_CUSTOM" + +READ_POST_WRITE_CUSTOM_MD5=$(get_md5_from_hex "$FILE_POST_WRITE_CUSTOM") + +echo "Checking post-write checksum" +if [[ "$READ_POST_WRITE_CUSTOM_MD5" != "$READ_MD5" ]]; then + echo "MD5 mismatch $READ_POST_WRITE_CUSTOM_MD5 != $READ_MD5" + exit 1 +fi + +reboot_device + echo "Passed all tests!" From 3a9f81d318f5a6b472294c6e0e32286966cbcbff Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Dec 2023 19:03:24 +0200 Subject: [PATCH 047/141] reeneable uhubctl --- tools/functional-test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/functional-test.sh b/tools/functional-test.sh index 8821472..52c6730 100755 --- a/tools/functional-test.sh +++ b/tools/functional-test.sh @@ -19,10 +19,10 @@ 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 + uhubctl -a off -p 1 -l 65-1 sleep 1 echo "Turning on port..." - #uhubctl -a on -p 1 -l 65-1 + uhubctl -a on -p 1 -l 65-1 echo "Waiting..." sleep 5 } From 817c2867e5eae5cf1efdb48ed34367206993ece7 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Dec 2023 18:07:23 +0100 Subject: [PATCH 048/141] device: weikav-sugar65 (#32) https://github.com/carlossless/sinowealth-kb-tool/issues/29 --- README.md | 1 + src/part.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index be0d7da..cb87c07 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ sinowealth-kb-tool write \ | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | +| Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ | | [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ❓ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | diff --git a/src/part.rs b/src/part.rs index fb3c5b4..eb06278 100644 --- a/src/part.rs +++ b/src/part.rs @@ -100,6 +100,13 @@ pub const PART_ROYALKLUDGE_RK100: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_WEIKAV_SUGAR65: Part = Part { + vendor_id: 0x05ac, + product_id: 0x024f, + isp_usage: 0x0002, + ..PART_BASE_SH68F90 +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 @@ -113,6 +120,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, "genesis-thor-300" => PART_GENESIS_THOR_300, "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, + "weikav-sugar65" => PART_WEIKAV_SUGAR65, }; impl Part { From 37c3db7da8b1c9cc50a9f1e392538d030754f906 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Dec 2023 19:08:11 +0200 Subject: [PATCH 049/141] bump to 0.0.7 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf90c5a..3a44dc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.0.6" +version = "0.0.7" dependencies = [ "clap", "clap-num", diff --git a/Cargo.toml b/Cargo.toml index f6d103b..8bdf269 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based devices """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.0.6" +version = "0.0.7" edition = "2021" license = "MIT" rust-version = "1.65" From d716323627a9aa0fb62af7d2f26445e0ed0a9072 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Dec 2023 22:30:57 +0200 Subject: [PATCH 050/141] update device report issue template --- .github/ISSUE_TEMPLATE/device-report.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/device-report.md b/.github/ISSUE_TEMPLATE/device-report.md index 450b294..f7f4894 100644 --- a/.github/ISSUE_TEMPLATE/device-report.md +++ b/.github/ISSUE_TEMPLATE/device-report.md @@ -17,11 +17,13 @@ assignees: '' ``` firmware_size: 61440 -bootloader_size: 4096 -page_size: 2048 vendor_id: 0x258a product_id: 0x0049 -isp_index: 0 +bootloader_size: 4096 # necessary if not default +page_size: 2048 # necessary if not default +isp_usage_page: 0xff00 # necessary if not default +isp_usage: 0x0001 # necessary if not default +isp_index: 0 # necessary if not default ``` ## Operations Tested From 93f450ea2962b60008a4846ab73729f09ab988a7 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 24 Dec 2023 15:20:50 +0200 Subject: [PATCH 051/141] tests for verify function --- src/util.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/util.rs b/src/util.rs index 9b86c99..ae0cd3a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,6 @@ use thiserror::Error; -#[derive(Debug, Clone, Error)] +#[derive(Debug, Clone, Error, PartialEq)] pub enum VerificationError { #[error("Firmware Mismatch @ {addr:#06x} --- {expected:#04x} != {actual:#04x}")] ByteMismatch { @@ -32,3 +32,31 @@ pub fn verify(expected: &Vec, actual: &Vec) -> Result<(), VerificationEr Ok(()) } + +#[test] +fn test_verify_success() { + assert!(verify(&vec![1, 2, 3, 4], &vec![1, 2, 3, 4]).is_ok()); +} + +#[test] +fn test_verify_error_length_mismatch() { + assert_eq!( + verify(&vec![1, 2, 3, 4], &vec![1, 2, 3]), + Err(VerificationError::LengthMismatch { + expected: 4, + actual: 3 + }) + ); +} + +#[test] +fn test_verify_error_byte_mismatch() { + assert_eq!( + verify(&vec![1, 2, 3, 4], &vec![1, 2, 4, 3]), + Err(VerificationError::ByteMismatch { + addr: 2, + expected: 3, + actual: 4 + }) + ); +} From fadbd582e44b06cafe00ec0f59b5b2b7111567a5 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Wed, 27 Dec 2023 12:01:36 +0200 Subject: [PATCH 052/141] allow hex values for all numeric arguments (#36) --- src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7a46099..ac786f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -139,7 +139,7 @@ impl PartCommand for Command { .arg( arg!(--firmware_size ) .required_unless_present("part") - .value_parser(clap::value_parser!(usize)), + .value_parser(maybe_hex::), ) .arg( arg!(--vendor_id ) @@ -151,11 +151,11 @@ impl PartCommand for Command { .required_unless_present("part") .value_parser(maybe_hex::), ) - .arg(arg!(--bootloader_size ).value_parser(clap::value_parser!(usize))) - .arg(arg!(--page_size ).value_parser(clap::value_parser!(usize))) + .arg(arg!(--bootloader_size ).value_parser(maybe_hex::)) + .arg(arg!(--page_size ).value_parser(maybe_hex::)) .arg(arg!(--isp_usage_page ).value_parser(maybe_hex::)) .arg(arg!(--isp_usage ).value_parser(maybe_hex::)) - .arg(arg!(--isp_index ).value_parser(clap::value_parser!(usize))) + .arg(arg!(--isp_index ).value_parser(maybe_hex::)) } } From a8ab4372c28bab9ffc02d1792ae6a3d8d97c990a Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Thu, 4 Jan 2024 23:24:27 +0100 Subject: [PATCH 053/141] device: royalkludge-rk61 (#39) https://github.com/carlossless/sinowealth-kb-tool/issues/37 --- README.md | 2 ++ src/part.rs | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index cb87c07..e39698d 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,8 @@ sinowealth-kb-tool write \ | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ | +| Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | +| Royal Kludge RK61 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK916 | ✅ | ✅ | | [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ❓ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | diff --git a/src/part.rs b/src/part.rs index eb06278..035f733 100644 --- a/src/part.rs +++ b/src/part.rs @@ -94,6 +94,12 @@ pub const PART_GENESIS_THOR_300_RGB: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_ROYALKLUDGE_RK61: Part = Part { + vendor_id: 0x258a, + product_id: 0x00c7, + ..PART_BASE_SH68F90 +}; + pub const PART_ROYALKLUDGE_RK100: Part = Part { vendor_id: 0x258a, product_id: 0x0056, @@ -117,6 +123,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "terport-tr95" => PART_TERPORT_TR95, "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, + "royalkludge-rk61" => PART_ROYALKLUDGE_RK61, "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, "genesis-thor-300" => PART_GENESIS_THOR_300, "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, From 5a6f9f099499a34142478efddebbdf19df1046a8 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Thu, 4 Jan 2024 23:28:48 +0100 Subject: [PATCH 054/141] add RK61 product page --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e39698d..9e4397f 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ sinowealth-kb-tool write \ | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | -| Royal Kludge RK61 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK916 | ✅ | ✅ | +| [Royal Kludge RK61](http://en.rkgaming.com/product/11/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK916 | ✅ | ✅ | | [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ❓ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | From b9cd7718af48580b8854a61b828ebc7e3497c4af Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 6 Jan 2024 14:00:04 +0100 Subject: [PATCH 055/141] reorder devices/parts --- README.md | 15 +++++++-------- src/part.rs | 12 ++++++------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 9e4397f..0b33bbe 100644 --- a/README.md +++ b/README.md @@ -64,21 +64,20 @@ sinowealth-kb-tool write \ | Keyboard | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | | -------- | ------- | --- | --------- | ----------- | ------------ | +| [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | +| [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | +| Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | | [NuPhy Air96](https://nuphy.com/products/air96-wireless-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | | [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | -| Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | -| [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | -| Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ | -| Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | -| [Royal Kludge RK61](http://en.rkgaming.com/product/11/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK916 | ✅ | ✅ | +| [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ❓ | +| [Royal Kludge RK61](http://en.rkgaming.com/product/11/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK916 | ✅ | ✅ | +| Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | +| Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | -| Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | -| [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | -| [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | ## Prerequisites diff --git a/src/part.rs b/src/part.rs index 035f733..211989f 100644 --- a/src/part.rs +++ b/src/part.rs @@ -114,20 +114,20 @@ pub const PART_WEIKAV_SUGAR65: Part = Part { }; pub static PARTS: Map<&'static str, Part> = phf_map! { + "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, + "genesis-thor-300" => PART_GENESIS_THOR_300, "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 "nuphy-air96" => PART_NUPHY_AIR60, // same as nuphy-air60 "nuphy-halo65" => PART_NUPHY_AIR60, // same as nuphy-air60 - "xinmeng-k916" => PART_XINMENG_K916, "re-k70-byk800" => PART_RE_K70_BYK800, - "terport-tr95" => PART_TERPORT_TR95, - "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, - "royalkludge-rk61" => PART_ROYALKLUDGE_RK61, + "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, - "genesis-thor-300" => PART_GENESIS_THOR_300, - "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, + "royalkludge-rk61" => PART_ROYALKLUDGE_RK61, + "terport-tr95" => PART_TERPORT_TR95, "weikav-sugar65" => PART_WEIKAV_SUGAR65, + "xinmeng-k916" => PART_XINMENG_K916, }; impl Part { From 90687de85d4bbc50656de70d79429cdd47146f34 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 6 Jan 2024 14:32:02 +0100 Subject: [PATCH 056/141] device: digitalalliance-meca-warrior-x (#41) https://github.com/carlossless/sinowealth-kb-tool/issues/16 --- README.md | 1 + src/part.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 0b33bbe..4d3392e 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ sinowealth-kb-tool write \ | Keyboard | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | | -------- | ------- | --- | --------- | ----------- | ------------ | +| Digital Alliance Meca Warrior X | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ | | [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | diff --git a/src/part.rs b/src/part.rs index 211989f..4bd0aab 100644 --- a/src/part.rs +++ b/src/part.rs @@ -106,6 +106,12 @@ pub const PART_ROYALKLUDGE_RK100: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_DIGITALALLIANCE_MECA_WARRIOR_X: Part = Part { + vendor_id: 0x258a, + product_id: 0x0090, + ..PART_BASE_SH68F90 +}; + pub const PART_WEIKAV_SUGAR65: Part = Part { vendor_id: 0x05ac, product_id: 0x024f, @@ -114,6 +120,7 @@ pub const PART_WEIKAV_SUGAR65: Part = Part { }; pub static PARTS: Map<&'static str, Part> = phf_map! { + "digitalalliance-meca-warrior-x" => PART_DIGITALALLIANCE_MECA_WARRIOR_X, "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, "genesis-thor-300" => PART_GENESIS_THOR_300, "nuphy-air60" => PART_NUPHY_AIR60, From 8e890e9aec2baf818462f6214662a9cff0764816 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 12 Jan 2024 22:06:14 +0100 Subject: [PATCH 057/141] setting & using a specific interface number (#43) Addresses the issue encountered in https://github.com/carlossless/sinowealth-kb-tool/issues/28#issuecomment-1880002380 and possibly https://github.com/carlossless/sinowealth-kb-tool/issues/40#issuecomment-1879929586. I initially thought the selected device per interface would not matter for libusb and that any interface would accept a report that was declared for a different interface, but apparently, it does not. Also, libusb might enumerate these interfaces in varying orders, but that seems purely dependent on the USB descriptor. Because of this, it's necessary to specify the interface where the ISP report declaration resides. I'm adding in a default (interface_number = 1) and a new argument to customize it. This might unfortunately break the previously declared parts/devices, but from the device reports I saw myself, the ISP report always lives on interface #1, so perhaps there won't be any issues. Also, just to document this about USBHID somewhere: * macOS - has exactly the same device paths for each interface (does not matter which interface the report is sent to) * linux (libusb) - needs a device path with the correct interface for the report * windows - needs a device path for the specific TLC where thee report was declared in --- README.md | 2 ++ src/isp.rs | 9 +++++++-- src/main.rs | 5 +++++ src/part.rs | 3 +++ tools/functional-test.sh | 2 ++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4d3392e..b883391 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ sinowealth-kb-tool read \ --firmware_size 61440 \ --bootloader_size 4096 \ # optional --page_size 2048 \ # optional + --isp_iface_num 1 \ # optional --isp_usage_page 0xff00 \ # optional --isp_usage 0x0001 \ # optional --isp_index 0 \ # optional @@ -54,6 +55,7 @@ sinowealth-kb-tool write \ --firmware_size 61440 \ --bootloader_size 4096 \ # optional --page_size 2048 \ # optional + --isp_iface_num 1 \ # optional --isp_usage_page 0xff00 \ # optional --isp_usage 0x0001 \ # optional --isp_index 0 \ # optional diff --git a/src/isp.rs b/src/isp.rs index 2a964b3..4f56f8a 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -93,11 +93,13 @@ impl ISPDevice { #[cfg(not(target_os = "linux"))] return d.vendor_id() == GAMING_KB_VENDOR_ID && d.product_id() == GAMING_KB_PRODUCT_ID + && d.interface_number() == 0 && d.usage_page() == HID_ISP_USAGE_PAGE && d.usage() == HID_ISP_USAGE; #[cfg(target_os = "linux")] return d.vendor_id() == GAMING_KB_VENDOR_ID - && d.product_id() == GAMING_KB_PRODUCT_ID; + && d.product_id() == GAMING_KB_PRODUCT_ID + && d.interface_number() == 0; }) .collect(); @@ -160,10 +162,13 @@ impl ISPDevice { #[cfg(not(target_os = "linux"))] return d.vendor_id() == part.vendor_id && d.product_id() == part.product_id + && d.interface_number() == part.isp_iface_num as i32 && d.usage_page() == part.isp_usage_page && d.usage() == part.isp_usage; #[cfg(target_os = "linux")] - return d.vendor_id() == part.vendor_id && d.product_id() == part.product_id; + return d.vendor_id() == part.vendor_id + && d.product_id() == part.product_id + && d.interface_number() == part.isp_iface_num as i32; }) .enumerate() .find_map(|(_i, d)| { diff --git a/src/main.rs b/src/main.rs index ac786f6..fa2c905 100644 --- a/src/main.rs +++ b/src/main.rs @@ -153,6 +153,7 @@ impl PartCommand for Command { ) .arg(arg!(--bootloader_size ).value_parser(maybe_hex::)) .arg(arg!(--page_size ).value_parser(maybe_hex::)) + .arg(arg!(--isp_iface_num ).value_parser(maybe_hex::)) .arg(arg!(--isp_usage_page ).value_parser(maybe_hex::)) .arg(arg!(--isp_usage ).value_parser(maybe_hex::)) .arg(arg!(--isp_index ).value_parser(maybe_hex::)) @@ -172,6 +173,7 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { let page_size = sub_matches.get_one::("page_size"); let vendor_id = sub_matches.get_one::("vendor_id"); let product_id = sub_matches.get_one::("product_id"); + let isp_iface_num = sub_matches.get_one::("isp_iface_num"); let isp_usage_page = sub_matches.get_one::("isp_usage_page"); let isp_usage = sub_matches.get_one::("isp_usage"); let isp_index = sub_matches.get_one::("isp_index"); @@ -191,6 +193,9 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { if let Some(page_size) = page_size { part.page_size = *page_size; } + if let Some(isp_iface_num) = isp_iface_num { + part.isp_iface_num = *isp_iface_num; + } if let Some(isp_usage_page) = isp_usage_page { part.isp_usage_page = *isp_usage_page; } diff --git a/src/part.rs b/src/part.rs index 4bd0aab..d0d1363 100644 --- a/src/part.rs +++ b/src/part.rs @@ -8,6 +8,8 @@ pub struct Part { pub vendor_id: u16, pub product_id: u16, + /// USB interface number with the ISP report + pub isp_iface_num: u8, // The following properties and values are important only for windows support because its // HIDAPI requires us to use a specific device for each collection /// HID collection `usage_page` with the ISP report @@ -26,6 +28,7 @@ pub const PART_BASE_DEFAULT: Part = Part { vendor_id: 0x0000, product_id: 0x0000, + isp_iface_num: 1, isp_usage_page: 0xff00, isp_usage: 0x0001, isp_index: 0, diff --git a/tools/functional-test.sh b/tools/functional-test.sh index 52c6730..3fe925e 100755 --- a/tools/functional-test.sh +++ b/tools/functional-test.sh @@ -63,6 +63,7 @@ $TOOL read \ --product_id 0x024f \ --bootloader_size 4096 \ --page_size 2048 \ + --isp_iface_num 1 \ --isp_usage_page 0xff00 \ --isp_usage 0x0001 \ --isp_index 1 \ @@ -136,6 +137,7 @@ $TOOL write \ --product_id 0x024f \ --bootloader_size 4096 \ --page_size 2048 \ + --isp_iface_num 1 \ --isp_usage_page 0xff00 \ --isp_usage 0x0001 \ --isp_index 1 \ From 051102c9050a3ebf4adf599923daf15f97ecaea7 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 12 Jan 2024 22:08:32 +0100 Subject: [PATCH 058/141] fix argument references --- src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index fa2c905..290f19e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -154,9 +154,9 @@ impl PartCommand for Command { .arg(arg!(--bootloader_size ).value_parser(maybe_hex::)) .arg(arg!(--page_size ).value_parser(maybe_hex::)) .arg(arg!(--isp_iface_num ).value_parser(maybe_hex::)) - .arg(arg!(--isp_usage_page ).value_parser(maybe_hex::)) - .arg(arg!(--isp_usage ).value_parser(maybe_hex::)) - .arg(arg!(--isp_index ).value_parser(maybe_hex::)) + .arg(arg!(--isp_usage_page ).value_parser(maybe_hex::)) + .arg(arg!(--isp_usage ).value_parser(maybe_hex::)) + .arg(arg!(--isp_index ).value_parser(maybe_hex::)) } } From bb43762ea5daeec264a5a69577a2c26001bf4ff0 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 12 Jan 2024 22:10:18 +0100 Subject: [PATCH 059/141] flake update (nixpkgs-23.11) --- flake.lock | 8 ++++---- flake.nix | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/flake.lock b/flake.lock index 47c9b3c..df46b48 100644 --- a/flake.lock +++ b/flake.lock @@ -52,16 +52,16 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1696374741, - "narHash": "sha256-gt8B3G0ryizT9HSB4cCO8QoxdbsHnrQH+/BdKxOwqF0=", + "lastModified": 1704874635, + "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8a4c17493e5c39769f79117937c79e1c88de6729", + "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.05", + "ref": "nixos-23.11", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index f9f4b98..b9cfd12 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; utils.url = "github:numtide/flake-utils"; naersk.url = "github:nix-community/naersk"; rust-overlay.url = "github:oxalica/rust-overlay"; From a48503f570e8524022359db791c5dfa67bafa414 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 12 Jan 2024 22:37:10 +0100 Subject: [PATCH 060/141] simple_logger update and configurable DEBUG flag --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/main.rs | 22 ++++++++++++++++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a44dc6..0cfc0eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,9 +316,9 @@ dependencies = [ [[package]] name = "simple_logger" -version = "4.3.0" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0ca6504625ee1aa5fda33913d2005eab98c7a42dd85f116ecce3ff54c9d3ef" +checksum = "8e7e46c8c90251d47d08b28b8a419ffb4aede0f87c2eea95e17d1d5bacbf3ef1" dependencies = [ "colored", "log", diff --git a/Cargo.toml b/Cargo.toml index 8bdf269..d795780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ features = ["linux-static-libusb"] [dependencies.log] version = "0.4" -features = ["max_level_debug", "release_max_level_info"] +features = ["max_level_debug"] [dependencies.simple_logger] version = "4.3" diff --git a/src/main.rs b/src/main.rs index 290f19e..e5becfe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use std::{ - fs, + env, fs, io::{self, Read}, process::ExitCode, }; @@ -66,8 +66,26 @@ fn cli() -> Command { ); } +fn get_log_level() -> log::LevelFilter { + return if let Ok(debug) = env::var("DEBUG") { + if debug == "1" { + log::LevelFilter::Debug + } else { + log::LevelFilter::Info + } + } else { + #[cfg(debug_assertions)] + return log::LevelFilter::Debug; + #[cfg(not(debug_assertions))] + log::LevelFilter::Info + }; +} + fn err_main() -> Result<(), CLIError> { - SimpleLogger::new().init().unwrap(); + SimpleLogger::new() + .with_level(get_log_level()) + .init() + .unwrap(); let matches = cli().get_matches(); From 5bd32111efdc2d116c18c3c802689084f297777c Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 12 Jan 2024 22:39:11 +0100 Subject: [PATCH 061/141] bump to 0.0.8 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0cfc0eb..de7949a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.0.7" +version = "0.0.8" dependencies = [ "clap", "clap-num", diff --git a/Cargo.toml b/Cargo.toml index d795780..f44451f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based devices """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.0.7" +version = "0.0.8" edition = "2021" license = "MIT" rust-version = "1.65" From 4a8af5b54b15d149c75237ed0c5ab10743640ea5 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 13 Jan 2024 21:58:59 +0100 Subject: [PATCH 062/141] device: xinmeng-xm-rf68 (#42) https://github.com/carlossless/sinowealth-kb-tool/issues/40 --- README.md | 1 + src/part.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index b883391..536a540 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ sinowealth-kb-tool write \ | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | +| Xinmeng XM-RF68 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90U | ✅ | ✅ | ## Prerequisites diff --git a/src/part.rs b/src/part.rs index d0d1363..626df74 100644 --- a/src/part.rs +++ b/src/part.rs @@ -58,6 +58,12 @@ pub const PART_XINMENG_K916: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_XINMENG_XM_RF68: Part = Part { + vendor_id: 0x258a, + product_id: 0x002a, + ..PART_BASE_SH68F90 +}; + pub const PART_RE_K70_BYK800: Part = Part { vendor_id: 0x258a, product_id: 0x001a, @@ -138,6 +144,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "terport-tr95" => PART_TERPORT_TR95, "weikav-sugar65" => PART_WEIKAV_SUGAR65, "xinmeng-k916" => PART_XINMENG_K916, + "xinmeng-xm-rf68" => PART_XINMENG_XM_RF68, }; impl Part { From accb9e4c38364542afed5235551aa52a9e0ae9d5 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 14 Jan 2024 17:03:13 +0100 Subject: [PATCH 063/141] device: royalkludge-rk71 (#34) https://github.com/carlossless/sinowealth-kb-tool/issues/28 --- README.md | 1 + src/part.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 536a540..ceb91df 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ sinowealth-kb-tool write \ | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ❓ | | [Royal Kludge RK61](http://en.rkgaming.com/product/11/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK916 | ✅ | ✅ | +| [Royal Kludge RK71](http://en.rkgaming.com/product/12/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | ❓ | ✅ | ✅ | | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index 626df74..a9bc744 100644 --- a/src/part.rs +++ b/src/part.rs @@ -109,6 +109,12 @@ pub const PART_ROYALKLUDGE_RK61: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_ROYALKLUDGE_RK71: Part = Part { + vendor_id: 0x258a, + product_id: 0x00ea, + ..PART_BASE_SH68F90 +}; + pub const PART_ROYALKLUDGE_RK100: Part = Part { vendor_id: 0x258a, product_id: 0x0056, @@ -141,6 +147,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, "royalkludge-rk61" => PART_ROYALKLUDGE_RK61, + "royalkludge-rk71" => PART_ROYALKLUDGE_RK71, "terport-tr95" => PART_TERPORT_TR95, "weikav-sugar65" => PART_WEIKAV_SUGAR65, "xinmeng-k916" => PART_XINMENG_K916, From 3b30708b0560d55923dad27df8f05a36012f48ff Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 14 Jan 2024 17:52:46 +0100 Subject: [PATCH 064/141] lint on ci (#44) --- .github/workflows/push.yml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 796f67f..a342fcb 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -11,6 +11,20 @@ defaults: shell: bash jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Cargo cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ./target + key: test-cargo-registry + - name: Lint + run: cargo fmt -- --check + test: runs-on: ubuntu-latest steps: @@ -22,8 +36,6 @@ jobs: ~/.cargo/registry ./target key: test-cargo-registry - - name: List - run: find ./ - name: Install and configure dependencies run: | sudo apt-get install -qq libusb-1.0.0-dev @@ -56,8 +68,6 @@ jobs: ~/.cargo/registry ./target key: build-cargo-registry-${{matrix.TARGET}} - - name: List - run: find ./ - name: Install and configure dependencies run: | # dependencies are only needed on ubuntu as that's the only place where @@ -69,8 +79,6 @@ jobs: run: rustup target add $TARGET - name: Run build run: cargo build --release --verbose --target $TARGET - - name: List target - run: find ./target - name: Compress run: | mkdir -p ./artifacts @@ -106,8 +114,6 @@ jobs: with: name: result path: ./artifacts - - name: List - run: find ./artifacts - name: Release uses: softprops/action-gh-release@v1 with: From 93c427cfba43f9ee1a7ffb7864902dccc87f43d8 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Wed, 24 Jan 2024 22:41:27 +0530 Subject: [PATCH 065/141] device: royalkludge-rk68 (ISO Return, BT Dual Mode) (#31) https://github.com/carlossless/sinowealth-kb-tool/issues/15 --- README.md | 2 ++ src/part.rs | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/README.md b/README.md index ceb91df..0f1f35c 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,8 @@ sinowealth-kb-tool write \ | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ❓ | | [Royal Kludge RK61](http://en.rkgaming.com/product/11/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK916 | ✅ | ✅ | +| 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? | ❓ | ✅ | ✅ | | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | | Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ | diff --git a/src/part.rs b/src/part.rs index a9bc744..aa1aeea 100644 --- a/src/part.rs +++ b/src/part.rs @@ -109,6 +109,18 @@ pub const PART_ROYALKLUDGE_RK61: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_ROYALKLUDGE_RK68_ISO_RETURN: Part = Part { + vendor_id: 0x258a, + product_id: 0x00a9, + ..PART_BASE_SH68F90 +}; + +pub const PART_ROYALKLUDGE_RK68_BT_DUAL: Part = Part { + vendor_id: 0x258a, + product_id: 0x008b, + ..PART_BASE_SH68F90 +}; + pub const PART_ROYALKLUDGE_RK71: Part = Part { vendor_id: 0x258a, product_id: 0x00ea, @@ -147,6 +159,8 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, "royalkludge-rk61" => PART_ROYALKLUDGE_RK61, + "royalkludge-rk68-bt-dual" => PART_ROYALKLUDGE_RK68_BT_DUAL, + "royalkludge-rk68-iso-return" => PART_ROYALKLUDGE_RK68_ISO_RETURN, "royalkludge-rk71" => PART_ROYALKLUDGE_RK71, "terport-tr95" => PART_TERPORT_TR95, "weikav-sugar65" => PART_WEIKAV_SUGAR65, From 75f964e5d57ee62f3aaac6bec57fa8614f9b4ae4 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 25 Feb 2024 16:27:38 +0100 Subject: [PATCH 066/141] rust -> 1.76 --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 629cec8..98d869a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.72" +channel = "1.76" components = [ "rustfmt", "rustc", From 0ff0965d0c6aaa932414621002680142a65ae61d Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 25 Feb 2024 16:33:53 +0100 Subject: [PATCH 067/141] flake updates --- flake.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/flake.lock b/flake.lock index df46b48..74e9ffe 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", "owner": "numtide", "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", "type": "github" }, "original": { @@ -23,11 +23,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1694081375, - "narHash": "sha256-vzJXOUnmkMCm3xw8yfPP5m8kypQ3BhAIRe4RRCWpzy8=", + "lastModified": 1698420672, + "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", "owner": "nix-community", "repo": "naersk", - "rev": "3f976d822b7b37fc6fb8e6f157c2dd05e7e94e89", + "rev": "aeb58d5e8faead8980a807c840232697982d47b9", "type": "github" }, "original": { @@ -38,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1696630667, - "narHash": "sha256-kO67pYOeT/6m9BnPO+zNHWnC4eGiW87gIAJ+e8f3gwU=", + "lastModified": 1708751719, + "narHash": "sha256-0uWOKSpXJXmXswOvDM5Vk3blB74apFB6rNGWV5IjoN0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b604023e0a5549b65da3040a07d2beb29ac9fc63", + "rev": "f63ce824cd2f036216eb5f637dfef31e1a03ee89", "type": "github" }, "original": { @@ -52,11 +52,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1704874635, - "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", + "lastModified": 1708702655, + "narHash": "sha256-qxT5jSLhelfLhQ07+AUxSTm1VnVH+hQxDkQSZ/m/Smo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", + "rev": "c5101e457206dd437330d283d6626944e28794b3", "type": "github" }, "original": { @@ -68,11 +68,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1681358109, - "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", + "lastModified": 1706487304, + "narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", + "rev": "90f456026d284c22b3e3497be980b2e47d0b28ac", "type": "github" }, "original": { @@ -96,11 +96,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1696644659, - "narHash": "sha256-l/DgT519At8HhXDQHz3+H8AjaEbrsb7Xkqgj+JNHV6k=", + "lastModified": 1708827164, + "narHash": "sha256-oBNS6pO04Y6gZBLThP3JDDgviex0+WTXz3bVBenyzms=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "126829788e99c188be4eeb805f144d73d8a00f2c", + "rev": "e0626adabd5ea461f80b1b11390da2a6575adb30", "type": "github" }, "original": { @@ -144,11 +144,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", "owner": "numtide", "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", "type": "github" }, "original": { From 68a6f6a92c11899edb6d6d975b6911214f1b53eb Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 9 Mar 2024 21:29:08 +0100 Subject: [PATCH 068/141] reboot functionality, win device enumeration fix, list hid devices, rk84-iso support (#47) Initiated from https://github.com/carlossless/sinowealth-kb-tool/issues/45 and https://github.com/carlossless/sinowealth-kb-tool/pull/46 This PR adds: * Sorting to device enumeration, so that `data` and `request` devices on Windows are selected deterministically. * Reboot functionality for devices that use bootloaders that are currently known to support it - `cfc8661d`. Also exposed as an arg, so that it can be enabled/disabled whenever that's not the default option for the device/bootloader. * A section in the readme tracking bootloader / host platform support and bootloader / function support. * The Royal Kludge RK84 part. * A new debugging function meant to list out all connected HID devices. Thanks to @Luro02 who tracked down the device enumeration issue and proposed a fix for it (along with the reboot feature, list feature, and rk84 device report)! --------- Co-authored-by: Luro02 <24826124+Luro02@users.noreply.github.com> --- README.md | 36 +++++++++++++++++++++++ src/isp.rs | 62 +++++++++++++++++++++++++++++++++++++++- src/main.rs | 15 +++++++++- src/part.rs | 17 ++++++++++- tools/functional-test.sh | 7 ++--- 5 files changed, 129 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0f1f35c..cd0cb71 100644 --- a/README.md +++ b/README.md @@ -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 ``` @@ -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 ``` @@ -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 @@ -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! diff --git a/src/isp.rs b/src/isp.rs index 4f56f8a..3169fda 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -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; @@ -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; @@ -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(); @@ -87,7 +126,7 @@ impl ISPDevice { fn open_isp_devices() -> Result { let api = Self::hidapi(); - let devices: Vec<_> = api + let mut devices: Vec<_> = api .device_list() .filter(|d| { #[cfg(not(target_os = "linux"))] @@ -103,6 +142,8 @@ impl ISPDevice { }) .collect(); + devices.sort_by_key(|d| d.path()); + for d in &devices { #[cfg(not(target_os = "linux"))] debug!( @@ -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); } @@ -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(()) } @@ -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(()) + } } diff --git a/src/main.rs b/src/main.rs index e5becfe..a9c4475 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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; @@ -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') @@ -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(()) @@ -175,6 +183,7 @@ impl PartCommand for Command { .arg(arg!(--isp_usage_page ).value_parser(maybe_hex::)) .arg(arg!(--isp_usage ).value_parser(maybe_hex::)) .arg(arg!(--isp_index ).value_parser(maybe_hex::)) + .arg(arg!(--reboot ).value_parser(value_parser!(bool))) } } @@ -195,6 +204,7 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { let isp_usage_page = sub_matches.get_one::("isp_usage_page"); let isp_usage = sub_matches.get_one::("isp_usage"); let isp_index = sub_matches.get_one::("isp_index"); + let reboot = sub_matches.get_one::("reboot"); if let Some(firmware_size) = firmware_size { part.firmware_size = *firmware_size; @@ -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; } diff --git a/src/part.rs b/src/part.rs index aa1aeea..5bb66bb 100644 --- a/src/part.rs +++ b/src/part.rs @@ -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, @@ -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 { @@ -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 { @@ -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 }; @@ -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, diff --git a/tools/functional-test.sh b/tools/functional-test.sh index 3fe925e..f56ae8e 100755 --- a/tools/functional-test.sh +++ b/tools/functional-test.sh @@ -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 } From 87ed68673d737579f931c65b443db360f2f5a6e1 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 9 Mar 2024 21:31:11 +0100 Subject: [PATCH 069/141] bump to 0.0.9 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de7949a..0859d27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.0.8" +version = "0.0.9" dependencies = [ "clap", "clap-num", diff --git a/Cargo.toml b/Cargo.toml index f44451f..575fac1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based devices """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.0.8" +version = "0.0.9" edition = "2021" license = "MIT" rust-version = "1.65" From ca6cce8fec906c412c01c118d5557a931e791f81 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 9 Mar 2024 21:34:59 +0100 Subject: [PATCH 070/141] change job name --- .github/workflows/push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index a342fcb..6eb8369 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -102,7 +102,7 @@ jobs: path: | ./artifacts - deploy: + release: if: startsWith(github.ref, 'refs/tags/v') needs: build runs-on: ubuntu-latest From f4d1592bb580900b9daac9efd6015c0bcb01fb18 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 10 Mar 2024 18:08:17 +0100 Subject: [PATCH 071/141] rebooting by the default for all known devices/bootloaders (#48) My initial testing `3e0ebd0c440af5236d7ff8872343f85d` seems to have resulted in a false positive and after further testing, it seems that it and all other currently known bootloaders are able to reboot back into main firmware through the same command. --- README.md | 14 ++------------ src/isp.rs | 8 +++++--- src/part.rs | 6 +----- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index cd0cb71..e36d426 100644 --- a/README.md +++ b/README.md @@ -95,24 +95,14 @@ sinowealth-kb-tool write \ | ISP MD5 | Windows | macOS | Linux | | -------------------------------- | -------- | -------- | ----- | +| 13df4ce2933f9654ffef80d6a3c27199 | ? | ? | ok | +| 2d169670eae0d36eae8188562c1f66e8 | ok | ? | ok | | 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 diff --git a/src/isp.rs b/src/isp.rs index 3169fda..5c562e1 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -452,12 +452,14 @@ impl ISPDevice { Ok(()) } + /// Causes the device to start running the main firmware 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); - + if let Err(err) = self.request_device.send_feature_report(&cmd) { + // only log failures + debug!("Reboot error: {:}", err); + } thread::sleep(time::Duration::from_millis(2000)); Ok(()) } diff --git a/src/part.rs b/src/part.rs index 5bb66bb..2074bc3 100644 --- a/src/part.rs +++ b/src/part.rs @@ -35,7 +35,7 @@ pub const PART_BASE_DEFAULT: Part = Part { isp_usage: 0x0001, isp_index: 0, - reboot: false, + reboot: true, }; pub const PART_BASE_SH68F90: Part = Part { @@ -122,28 +122,24 @@ 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 }; From e483311307126322744851144ca785dbd373b11d Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 22 Mar 2024 20:43:55 +0100 Subject: [PATCH 072/141] device: redragon-k641-shaco-pro (#51) https://github.com/carlossless/sinowealth-kb-tool/issues/50 --- README.md | 1 + src/part.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index e36d426..d6ed09f 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ sinowealth-kb-tool write \ | [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | +| [Redragon K641 SHACO PRO](https://www.redragonzone.com/products/redragon-k641-shaco-pro-65-aluminum-rgb-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ❓ | | [Royal Kludge RK61](http://en.rkgaming.com/product/11/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK916 | ✅ | ✅ | | Royal Kludge RK68 BT Dual | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK901 | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index 2074bc3..f1c8e92 100644 --- a/src/part.rs +++ b/src/part.rs @@ -95,6 +95,13 @@ pub const PART_REDRAGON_ANIVIA_K614: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_REDRAGON_K641_SHACO_PRO: Part = Part { + vendor_id: 0x258a, + product_id: 0x0049, + isp_index: 1, + ..PART_BASE_SH68F90 +}; + pub const PART_GENESIS_THOR_300: Part = Part { vendor_id: 0x258a, product_id: 0x001f, @@ -167,6 +174,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "re-k70-byk800" => PART_RE_K70_BYK800, "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, + "redragon-k641-shaco-pro" => PART_REDRAGON_K641_SHACO_PRO, "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, "royalkludge-rk84-iso-return" => PART_ROYALKLUDGE_RK84_ISO_RETURN, "royalkludge-rk61" => PART_ROYALKLUDGE_RK61, From a8c59ddb9528fb79d609c8af45ebbb3d776904c6 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Mar 2024 09:47:35 +0100 Subject: [PATCH 073/141] device: trust-gxt-960 (#54) https://github.com/carlossless/sinowealth-kb-tool/issues/52 --- README.md | 13 +++++++++++-- src/main.rs | 2 +- src/part.rs | 7 +++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d6ed09f..7687f49 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,10 @@ sinowealth-kb-tool write \ ## Supported Hardware -| Keyboard | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | -| -------- | ------- | --- | --------- | ----------- | ------------ | +### Keyboards + +| Model | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | +| ----- | ------- | --- | --------- | ----------- | ------------ | | Digital Alliance Meca Warrior X | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ | | [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | @@ -90,6 +92,12 @@ sinowealth-kb-tool write \ | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | | Xinmeng XM-RF68 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90U | ✅ | ✅ | +### Mice + +| Model | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | +| ----- | ------- | --- | --------- | ----------- | ------------ | +| [Trust GXT 960](https://www.trust.com/en/product/23758-gxt-960-graphin-ultra-lightweight-gaming-mouse) | 620f0b67a91f7f74151bc5be745b7110 | ❓ | BY8801 | ✅ | ❓ | + ## Bootloader Support ### Platforms @@ -99,6 +107,7 @@ sinowealth-kb-tool write \ | 13df4ce2933f9654ffef80d6a3c27199 | ? | ? | ok | | 2d169670eae0d36eae8188562c1f66e8 | ok | ? | ok | | 3e0ebd0c440af5236d7ff8872343f85d | ok | ok | ok | +| 620f0b67a91f7f74151bc5be745b7110 | ? | ? | ok | | cfc8661da8c9d7e351b36c0a763426aa | ok | fail[^1] | ok | | e57490acebcaabfcff84a0ff013955d9 | ok | ? | ? | diff --git a/src/main.rs b/src/main.rs index a9c4475..eed7cf1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,7 +48,7 @@ fn cli() -> Command { .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.") + .about("List all connected devices and their identifiers. This is useful to find the manufacturer and product id for your device.") ) .subcommand( Command::new("read") diff --git a/src/part.rs b/src/part.rs index f1c8e92..424fd9a 100644 --- a/src/part.rs +++ b/src/part.rs @@ -163,6 +163,12 @@ pub const PART_WEIKAV_SUGAR65: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_TRUST_GXT_960: Part = Part { + vendor_id: 0x145f, + product_id: 0x02b6, + ..PART_BASE_SH68F90 +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "digitalalliance-meca-warrior-x" => PART_DIGITALALLIANCE_MECA_WARRIOR_X, "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, @@ -182,6 +188,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "royalkludge-rk68-iso-return" => PART_ROYALKLUDGE_RK68_ISO_RETURN, "royalkludge-rk71" => PART_ROYALKLUDGE_RK71, "terport-tr95" => PART_TERPORT_TR95, + "trust-gxt-960" => PART_TRUST_GXT_960, "weikav-sugar65" => PART_WEIKAV_SUGAR65, "xinmeng-k916" => PART_XINMENG_K916, "xinmeng-xm-rf68" => PART_XINMENG_XM_RF68, From 5eae7ee46f243d0b5539382a04aa782b0a69ee56 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Mar 2024 09:59:36 +0100 Subject: [PATCH 074/141] support for bootloaders with pid 0x1021 (#55) First encountered here: https://github.com/carlossless/sinowealth-kb-tool/issues/53#issue-2200835634 --- src/isp.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/isp.rs b/src/isp.rs index 5c562e1..de2618d 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -14,6 +14,7 @@ const MAX_RETRIES: usize = 10; const GAMING_KB_VENDOR_ID: u16 = 0x0603; const GAMING_KB_PRODUCT_ID: u16 = 0x1020; +const GAMING_KB_V2_PRODUCT_ID: u16 = 0x1021; const COMMAND_LENGTH: usize = 6; @@ -131,13 +132,19 @@ impl ISPDevice { .filter(|d| { #[cfg(not(target_os = "linux"))] return d.vendor_id() == GAMING_KB_VENDOR_ID - && d.product_id() == GAMING_KB_PRODUCT_ID + && matches!( + d.product_id(), + GAMING_KB_PRODUCT_ID | GAMING_KB_V2_PRODUCT_ID + ) && d.interface_number() == 0 && d.usage_page() == HID_ISP_USAGE_PAGE && d.usage() == HID_ISP_USAGE; #[cfg(target_os = "linux")] return d.vendor_id() == GAMING_KB_VENDOR_ID - && d.product_id() == GAMING_KB_PRODUCT_ID + && matches!( + d.product_id(), + GAMING_KB_PRODUCT_ID | GAMING_KB_V2_PRODUCT_ID + ) && d.interface_number() == 0; }) .collect(); @@ -147,13 +154,20 @@ impl ISPDevice { for d in &devices { #[cfg(not(target_os = "linux"))] debug!( - "Found Device: {:?} {:#06x} {:#06x}", + "Found ISP Device: {:#06x} {:#06x} {:?} {:#06x} {:#06x}", + d.vendor_id(), + d.product_id(), d.path(), d.usage_page(), d.usage() ); #[cfg(target_os = "linux")] - debug!("Found Device: {:?}", d.path()); + debug!( + "Found ISP Device: {:#06x} {:#06x} {:?}", + d.vendor_id(), + d.product_id(), + d.path() + ); } let device_count = devices.len(); From 51a3e437575d349f2bf143ecd26a810d5f6e4c59 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Mar 2024 13:05:35 +0100 Subject: [PATCH 075/141] device: glorious-model-o (#56) https://github.com/carlossless/sinowealth-kb-tool/issues/53 --- README.md | 2 ++ src/part.rs | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index 7687f49..1409c5a 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ sinowealth-kb-tool write \ | Model | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | | ----- | ------- | --- | --------- | ----------- | ------------ | +| [Glorious Model O](https://web.archive.org/web/20220609205659mp_/https://www.gloriousgaming.com/products/glorious-model-o-black) | 46459c31e58194fa076b8ce8fb1f3eaa | ❓ | BY8948 | ✅ | ❓ | | [Trust GXT 960](https://www.trust.com/en/product/23758-gxt-960-graphin-ultra-lightweight-gaming-mouse) | 620f0b67a91f7f74151bc5be745b7110 | ❓ | BY8801 | ✅ | ❓ | ## Bootloader Support @@ -107,6 +108,7 @@ sinowealth-kb-tool write \ | 13df4ce2933f9654ffef80d6a3c27199 | ? | ? | ok | | 2d169670eae0d36eae8188562c1f66e8 | ok | ? | ok | | 3e0ebd0c440af5236d7ff8872343f85d | ok | ok | ok | +| 46459c31e58194fa076b8ce8fb1f3eaa | ? | ? | ok | | 620f0b67a91f7f74151bc5be745b7110 | ? | ? | ok | | cfc8661da8c9d7e351b36c0a763426aa | ok | fail[^1] | ok | | e57490acebcaabfcff84a0ff013955d9 | ok | ? | ? | diff --git a/src/part.rs b/src/part.rs index 424fd9a..ba13876 100644 --- a/src/part.rs +++ b/src/part.rs @@ -169,10 +169,17 @@ pub const PART_TRUST_GXT_960: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_GLORIOUS_MODEL_O: Part = Part { + vendor_id: 0x258a, + product_id: 0x0036, + ..PART_BASE_SH68F90 +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "digitalalliance-meca-warrior-x" => PART_DIGITALALLIANCE_MECA_WARRIOR_X, "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, "genesis-thor-300" => PART_GENESIS_THOR_300, + "glorious-model-o" => PART_GLORIOUS_MODEL_O, "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 "nuphy-air96" => PART_NUPHY_AIR60, // same as nuphy-air60 From d2fa17ca0e7afd0cf6085be08c8cf0218e563cb6 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Mar 2024 13:13:59 +0100 Subject: [PATCH 076/141] readme: share write tested state between all bootloaders (#57) Setting "tested write" to true for all devices that share a bootloader that has already been write tested on a different device. --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1409c5a..37acb10 100644 --- a/README.md +++ b/README.md @@ -70,25 +70,25 @@ sinowealth-kb-tool write \ | Model | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | | ----- | ------- | --- | --------- | ----------- | ------------ | -| Digital Alliance Meca Warrior X | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ❓ | +| Digital Alliance Meca Warrior X | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | -| [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | -| [NuPhy Air96](https://nuphy.com/products/air96-wireless-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | -| [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | +| [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | +| [NuPhy Air96](https://nuphy.com/products/air96-wireless-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | +| [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | -| [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ❓ | +| [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K641 SHACO PRO](https://www.redragonzone.com/products/redragon-k641-shaco-pro-65-aluminum-rgb-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | -| [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ❓ | +| [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ✅ | | [Royal Kludge RK61](http://en.rkgaming.com/product/11/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK916 | ✅ | ✅ | | 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 | ✅ | ❓ | +| Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | +| Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | | Xinmeng XM-RF68 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90U | ✅ | ✅ | From 0b589fbaf8d3d15d2ab801c2fe1f0a9ca0ff9ca2 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 23 Mar 2024 13:17:30 +0100 Subject: [PATCH 077/141] issue template update --- .github/ISSUE_TEMPLATE/device-report.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/device-report.md b/.github/ISSUE_TEMPLATE/device-report.md index f7f4894..db86f97 100644 --- a/.github/ISSUE_TEMPLATE/device-report.md +++ b/.github/ISSUE_TEMPLATE/device-report.md @@ -17,13 +17,13 @@ assignees: '' ``` firmware_size: 61440 -vendor_id: 0x258a -product_id: 0x0049 -bootloader_size: 4096 # necessary if not default -page_size: 2048 # necessary if not default -isp_usage_page: 0xff00 # necessary if not default -isp_usage: 0x0001 # necessary if not default -isp_index: 0 # necessary if not default +vendor_id: 0xdead +product_id: 0xcafe +bootloader_size: 4096 # necessary if not default, otherwise remove this line +page_size: 2048 # necessary if not default, otherwise remove this line +isp_usage_page: 0xff00 # necessary if not default, otherwise remove this line +isp_usage: 0x0001 # necessary if not default, otherwise remove this line +isp_index: 0 # necessary if not default, otherwise remove this line ``` ## Operations Tested From 92c9baf5c11d8c62556c09f93aa54ea04bcc2ce8 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 29 Mar 2024 19:22:25 +0100 Subject: [PATCH 078/141] device: machenike-k500-b61 (#59) https://github.com/carlossless/sinowealth-kb-tool/issues/58 --- README.md | 1 + src/part.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 37acb10..4f91e8e 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ sinowealth-kb-tool write \ | [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | +| [Machenike K500-B61](https://global.machenike.com/products/k500-b61) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90? | BYK916 | ✅ | ✅ | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [NuPhy Air96](https://nuphy.com/products/air96-wireless-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index ba13876..7bfac70 100644 --- a/src/part.rs +++ b/src/part.rs @@ -175,11 +175,18 @@ pub const PART_GLORIOUS_MODEL_O: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_MACHENIKE_K500_B61: Part = Part { + vendor_id: 0x258a, + product_id: 0x0049, + ..PART_BASE_SH68F90 +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "digitalalliance-meca-warrior-x" => PART_DIGITALALLIANCE_MECA_WARRIOR_X, "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, "genesis-thor-300" => PART_GENESIS_THOR_300, "glorious-model-o" => PART_GLORIOUS_MODEL_O, + "machenike-k500-b61" => PART_MACHENIKE_K500_B61, "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 "nuphy-air96" => PART_NUPHY_AIR60, // same as nuphy-air60 From 418f8abcced2dbf15dae40a4f1880c9ed58f8f46 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 29 Mar 2024 19:25:21 +0100 Subject: [PATCH 079/141] machenike-k500-b61: set correct isp_index --- src/part.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/part.rs b/src/part.rs index 7bfac70..e702b96 100644 --- a/src/part.rs +++ b/src/part.rs @@ -178,6 +178,7 @@ pub const PART_GLORIOUS_MODEL_O: Part = Part { pub const PART_MACHENIKE_K500_B61: Part = Part { vendor_id: 0x258a, product_id: 0x0049, + isp_index: 1, ..PART_BASE_SH68F90 }; From c4b4ed07b67da5b4c2dcce7e2be07ddba45cee38 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 29 Mar 2024 19:33:00 +0100 Subject: [PATCH 080/141] updated crate description --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 575fac1..8513cf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sinowealth-kb-tool" description = """ -A utility for reading and writing flash contents on Sinowealth 8051-based devices +A utility for reading and writing flash contents on Sinowealth 8051-based HID devices through the commonly found ISP bootloader """ repository = "https://github.com/carlossless/sinowealth-kb-tool" version = "0.0.9" From 14f1295d9395485ad4e44ab0ae1a8c1e2caf4c23 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 29 Mar 2024 19:38:01 +0100 Subject: [PATCH 081/141] bump to 0.0.10 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0859d27..64f1807 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.0.9" +version = "0.0.10" dependencies = [ "clap", "clap-num", diff --git a/Cargo.toml b/Cargo.toml index 8513cf1..4f97e6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based HID devices through the commonly found ISP bootloader """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.0.9" +version = "0.0.10" edition = "2021" license = "MIT" rust-version = "1.65" From 99f838991f32023c58804da063673902f5559d02 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 29 Mar 2024 21:34:03 +0100 Subject: [PATCH 082/141] add a blank bug-report template --- .github/ISSUE_TEMPLATE/bug-report.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..45dbf82 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,8 @@ +--- +name: Bug Report +about: For reporting an encountered issue +title: "[bug] Manufacturer Model" +labels: bug +assignees: '' + +--- From d6907be6f0a5a56871b739d09047309ddc62dc9f Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 6 Apr 2024 21:18:48 +0200 Subject: [PATCH 083/141] document macos failure for bootloader 620f0b --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f91e8e..3bf4d68 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ sinowealth-kb-tool write \ | 2d169670eae0d36eae8188562c1f66e8 | ok | ? | ok | | 3e0ebd0c440af5236d7ff8872343f85d | ok | ok | ok | | 46459c31e58194fa076b8ce8fb1f3eaa | ? | ? | ok | -| 620f0b67a91f7f74151bc5be745b7110 | ? | ? | ok | +| 620f0b67a91f7f74151bc5be745b7110 | ? | fail[^1] | ok | | cfc8661da8c9d7e351b36c0a763426aa | ok | fail[^1] | ok | | e57490acebcaabfcff84a0ff013955d9 | ok | ? | ? | From 7c7646a3b5337da4a7a4e8af129f6eb4cf98e82e Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 7 Apr 2024 18:23:12 +0200 Subject: [PATCH 084/141] device: redragon-k530-draconic-pro (#62) https://github.com/carlossless/sinowealth-kb-tool/issues/60 --- README.md | 1 + src/part.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 3bf4d68..246433f 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ sinowealth-kb-tool write \ | [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [NuPhy Air96](https://nuphy.com/products/air96-wireless-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | +| [Redragon K530 Draconic PRO](https://www.redragonzone.com/products/draconic-k530) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K641 SHACO PRO](https://www.redragonzone.com/products/redragon-k641-shaco-pro-65-aluminum-rgb-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index e702b96..d664cc0 100644 --- a/src/part.rs +++ b/src/part.rs @@ -182,6 +182,12 @@ pub const PART_MACHENIKE_K500_B61: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_REDRAGON_K530_DRACONIC_PRO: Part = Part { + vendor_id: 0x258a, + product_id: 0x0049, + ..PART_BASE_SH68F90 +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "digitalalliance-meca-warrior-x" => PART_DIGITALALLIANCE_MECA_WARRIOR_X, "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, @@ -193,6 +199,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "nuphy-air96" => PART_NUPHY_AIR60, // same as nuphy-air60 "nuphy-halo65" => PART_NUPHY_AIR60, // same as nuphy-air60 "re-k70-byk800" => PART_RE_K70_BYK800, + "redragon-k530-draconic-pro" => PART_REDRAGON_K530_DRACONIC_PRO, "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, "redragon-k641-shaco-pro" => PART_REDRAGON_K641_SHACO_PRO, From 03f3f233a49f3b38658fbace5512481d8d74c115 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 14 Apr 2024 10:42:15 +0200 Subject: [PATCH 085/141] device: eyooso-z11 (#69) https://github.com/carlossless/sinowealth-kb-tool/issues/66 --- README.md | 1 + src/part.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 246433f..bd9ab3b 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ sinowealth-kb-tool write \ | Model | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | | ----- | ------- | --- | --------- | ----------- | ------------ | | Digital Alliance Meca Warrior X | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | +| E-Yooso Z11 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK901 | ✅ | ✅ | | [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | diff --git a/src/part.rs b/src/part.rs index d664cc0..57e2521 100644 --- a/src/part.rs +++ b/src/part.rs @@ -188,8 +188,15 @@ pub const PART_REDRAGON_K530_DRACONIC_PRO: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_EYOOSO_Z11: Part = Part { + vendor_id: 0x258a, + product_id: 0x002a, + ..PART_BASE_SH68F90 +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "digitalalliance-meca-warrior-x" => PART_DIGITALALLIANCE_MECA_WARRIOR_X, + "eyooso-z11" => PART_EYOOSO_Z11, "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, "genesis-thor-300" => PART_GENESIS_THOR_300, "glorious-model-o" => PART_GLORIOUS_MODEL_O, From 8d1a66f65b9474fc054adb52d600efc7e80b73d7 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 14 Apr 2024 10:45:52 +0200 Subject: [PATCH 086/141] device: redragon-k658-pro-se (#63) https://github.com/carlossless/sinowealth-kb-tool/issues/61 --- README.md | 1 + src/part.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index bd9ab3b..054d91c 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ sinowealth-kb-tool write \ | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K641 SHACO PRO](https://www.redragonzone.com/products/redragon-k641-shaco-pro-65-aluminum-rgb-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | +| [Redragon K658 PRO SE](https://www.redragonzone.com/products/k658-pro-se-90-wireless-rgb-gaming-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ✅ | | [Royal Kludge RK61](http://en.rkgaming.com/product/11/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK916 | ✅ | ✅ | | Royal Kludge RK68 BT Dual | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK901 | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index 57e2521..4260344 100644 --- a/src/part.rs +++ b/src/part.rs @@ -182,6 +182,13 @@ pub const PART_MACHENIKE_K500_B61: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_REDRAGON_K658_PRO_SE: Part = Part { + vendor_id: 0x258a, + product_id: 0x0049, + isp_index: 1, + ..PART_BASE_SH68F90 +}; + pub const PART_REDRAGON_K530_DRACONIC_PRO: Part = Part { vendor_id: 0x258a, product_id: 0x0049, @@ -210,6 +217,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, "redragon-k641-shaco-pro" => PART_REDRAGON_K641_SHACO_PRO, + "redragon-k658-pro-se" => PART_REDRAGON_K658_PRO_SE, "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, "royalkludge-rk84-iso-return" => PART_ROYALKLUDGE_RK84_ISO_RETURN, "royalkludge-rk61" => PART_ROYALKLUDGE_RK61, From 2dd7cac8b4e94c673f1e75ad1470f8b50521abf6 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Tue, 16 Apr 2024 22:28:47 +0200 Subject: [PATCH 087/141] normalize list format for linux and other platforms --- src/isp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/isp.rs b/src/isp.rs index de2618d..f1abe2b 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -102,7 +102,7 @@ impl ISPDevice { ); #[cfg(target_os = "linux")] info!( - "{:}: ID {:#04x}:{:#04x} manufacturer=\"{:}\" product=\"{:}\"", + "{:}: ID {:04x}:{:04x} manufacturer=\"{:}\" product=\"{:}\"", d.path().to_str().unwrap(), d.vendor_id(), d.product_id(), From 1439cebdce69f4b5822f039bf7a86a177f176be2 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 27 Apr 2024 17:31:00 +0200 Subject: [PATCH 088/141] device: royalkludge-rkg68 (#75) https://github.com/carlossless/sinowealth-kb-tool/issues/70 --- README.md | 1 + src/part.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 054d91c..c46f57c 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ sinowealth-kb-tool write \ | 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 | ✅ | ✅ | +| Royal Kludge RKG68 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90A | SH68F90AS | ✅ | ✅ | | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index 4260344..b4d3a43 100644 --- a/src/part.rs +++ b/src/part.rs @@ -132,6 +132,12 @@ pub const PART_ROYALKLUDGE_RK68_BT_DUAL: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_ROYALKLUDGE_RKG68: Part = Part { + vendor_id: 0x258a, + product_id: 0x0049, + ..PART_BASE_SH68F90 +}; + pub const PART_ROYALKLUDGE_RK71: Part = Part { vendor_id: 0x258a, product_id: 0x00ea, @@ -219,11 +225,12 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "redragon-k641-shaco-pro" => PART_REDRAGON_K641_SHACO_PRO, "redragon-k658-pro-se" => PART_REDRAGON_K658_PRO_SE, "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, "royalkludge-rk71" => PART_ROYALKLUDGE_RK71, + "royalkludge-rk84-iso-return" => PART_ROYALKLUDGE_RK84_ISO_RETURN, + "royalkludge-rkg68" => PART_ROYALKLUDGE_RKG68, "terport-tr95" => PART_TERPORT_TR95, "trust-gxt-960" => PART_TRUST_GXT_960, "weikav-sugar65" => PART_WEIKAV_SUGAR65, From aa1fcc3cb11625189e64351cf718c821a01790db Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 27 Apr 2024 20:28:50 +0200 Subject: [PATCH 089/141] device: xinmeng-m71 (and yunzii-al71) (#76) https://github.com/carlossless/sinowealth-kb-tool/issues/65 --- README.md | 2 ++ src/part.rs | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index c46f57c..c1c8d0d 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,9 @@ sinowealth-kb-tool write \ | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | +| Xinmeng M71 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | SH68F90AS | ✅ | ✅ | | Xinmeng XM-RF68 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90U | ✅ | ✅ | +| Yunzii AL71 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | SH68F90AS | ✅ | ✅ | ### Mice diff --git a/src/part.rs b/src/part.rs index b4d3a43..53cf983 100644 --- a/src/part.rs +++ b/src/part.rs @@ -68,6 +68,13 @@ pub const PART_XINMENG_XM_RF68: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_XINMENG_M71: Part = Part { + vendor_id: 0x258a, + product_id: 0x010c, + isp_index: 1, + ..PART_BASE_SH68F90 +}; + pub const PART_RE_K70_BYK800: Part = Part { vendor_id: 0x258a, product_id: 0x001a, @@ -235,7 +242,9 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "trust-gxt-960" => PART_TRUST_GXT_960, "weikav-sugar65" => PART_WEIKAV_SUGAR65, "xinmeng-k916" => PART_XINMENG_K916, + "xinmeng-m71" => PART_XINMENG_M71, "xinmeng-xm-rf68" => PART_XINMENG_XM_RF68, + "yunzii-al71" => PART_XINMENG_M71, // same as xinmeng-m71 }; impl Part { From c3033d4712eeae9995c2b2a369b8009391bd42ac Mon Sep 17 00:00:00 2001 From: Daniel Fagadau Date: Sun, 28 Apr 2024 09:39:11 +0200 Subject: [PATCH 090/141] device: add leobog hi75 (#74) https://github.com/carlossless/sinowealth-kb-tool/issues/73 --------- Co-authored-by: Karolis Stasaitis --- README.md | 1 + src/part.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index c1c8d0d..ccedd63 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ sinowealth-kb-tool write \ | [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | +| [Leobog Hi75](https://leobogtech.com/products/leobog-hi75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Machenike K500-B61](https://global.machenike.com/products/k500-b61) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90? | BYK916 | ✅ | ✅ | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index 53cf983..f8e4d00 100644 --- a/src/part.rs +++ b/src/part.rs @@ -55,6 +55,13 @@ pub const PART_NUPHY_AIR60: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_LEOBOG_HI75: Part = Part { + vendor_id: 0x258a, + product_id: 0x010c, + isp_index: 1, + ..PART_BASE_SH68F90 +}; + pub const PART_XINMENG_K916: Part = Part { vendor_id: 0x258a, product_id: 0x00a1, @@ -220,6 +227,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, "genesis-thor-300" => PART_GENESIS_THOR_300, "glorious-model-o" => PART_GLORIOUS_MODEL_O, + "leobog-hi75" => PART_LEOBOG_HI75, "machenike-k500-b61" => PART_MACHENIKE_K500_B61, "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 From 0c7ab783b8fbcd1b99439ddd1b56ef377b72c52c Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 28 Apr 2024 10:26:46 +0200 Subject: [PATCH 091/141] error logging for enter_isp_mode --- src/isp.rs | 7 +++++-- src/main.rs | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/isp.rs b/src/isp.rs index f1abe2b..e47227a 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -256,7 +256,10 @@ impl ISPDevice { let device = api.open_path(request_device_info.path()).unwrap(); info!("Found regular device. Entering ISP mode..."); - Self::enter_isp_mode(&device)?; + if let Err(err) = Self::enter_isp_mode(&device) { + debug!("Error: {:}", err); + return Err(err); + } info!("Waiting for ISP device..."); thread::sleep(time::Duration::from_secs(2)); @@ -472,7 +475,7 @@ impl ISPDevice { let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_REBOOT, 0, 0, 0, 0]; if let Err(err) = self.request_device.send_feature_report(&cmd) { // only log failures - debug!("Reboot error: {:}", err); + debug!("Error: {:}", err); } thread::sleep(time::Duration::from_millis(2000)); Ok(()) diff --git a/src/main.rs b/src/main.rs index eed7cf1..64669ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,8 +31,8 @@ pub enum CLIError { fn main() -> ExitCode { match err_main() { Ok(_) => ExitCode::SUCCESS, - Err(e) => { - error!("{}", e.to_string()); + Err(err) => { + error!("{:}", err); ExitCode::FAILURE } } From af0eaeccc5b3ecb880031f3ce2a4cfbad4c84726 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 28 Apr 2024 10:27:38 +0200 Subject: [PATCH 092/141] bump to 0.0.11 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64f1807..4e2da0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.0.10" +version = "0.0.11" dependencies = [ "clap", "clap-num", diff --git a/Cargo.toml b/Cargo.toml index 4f97e6c..ccf87a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based HID devices through the commonly found ISP bootloader """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.0.10" +version = "0.0.11" edition = "2021" license = "MIT" rust-version = "1.65" From 9e6c26a63ddf8e0af3149fbbcb1289eaba67bcca Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 28 Apr 2024 10:40:02 +0200 Subject: [PATCH 093/141] issue template update --- .github/ISSUE_TEMPLATE/device-report.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/device-report.md b/.github/ISSUE_TEMPLATE/device-report.md index db86f97..3e3f3ff 100644 --- a/.github/ISSUE_TEMPLATE/device-report.md +++ b/.github/ISSUE_TEMPLATE/device-report.md @@ -39,8 +39,8 @@ isp_index: 0 # necessary if not default, otherwise remove this line ## Checksums -- Bootloader MD5: `beefcafebeefcafebeefcafebeefcafe` - Stock Firmware MD5: `deadbeefdeadbeefdeadbeefdeadbeef` +- Bootloader MD5: `beefcafebeefcafebeefcafebeefcafe` _(shown when running `sinowealth-kb-tool read -b ...`)_ ## HID Dump @@ -72,3 +72,7 @@ DESCRIPTOR: ``` + +## PCB Photos + +_If possible, include photos of your device PCB clearly showing MCU and wireless IC labels_ From 0223da96dd31d6681edebfb30e1e9d4fc9cf9539 Mon Sep 17 00:00:00 2001 From: XusMochizuki <45512295+XusMochizuki@users.noreply.github.com> Date: Tue, 14 May 2024 04:11:08 +0800 Subject: [PATCH 094/141] device: kzzi-k68pro (#78) add support for kzzi-k68pro --- README.md | 1 + src/part.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index ccedd63..70e47a4 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ sinowealth-kb-tool write \ | [Royal Kludge RK71](http://en.rkgaming.com/product/12/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | ❓ | ✅ | ✅ | | [Royal Kludge RK84](http://en.rkgaming.com/product/16/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ✅ | | Royal Kludge RKG68 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90A | SH68F90AS | ✅ | ✅ | +| [Kzzi K68Pro](http://en.kzzi.com/product/37/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | ❓ | ✅ | ❓ | | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index f8e4d00..8d7e1de 100644 --- a/src/part.rs +++ b/src/part.rs @@ -176,6 +176,13 @@ pub const PART_DIGITALALLIANCE_MECA_WARRIOR_X: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_KZZI_K68PRO: Part = Part{ + vendor_id: 0x258a, + product_id: 0x0186, + isp_index: 0x0001, + ..PART_BASE_SH68F90 +}; + pub const PART_WEIKAV_SUGAR65: Part = Part { vendor_id: 0x05ac, product_id: 0x024f, @@ -246,6 +253,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "royalkludge-rk71" => PART_ROYALKLUDGE_RK71, "royalkludge-rk84-iso-return" => PART_ROYALKLUDGE_RK84_ISO_RETURN, "royalkludge-rkg68" => PART_ROYALKLUDGE_RKG68, + "kzzi-k68pro" => PART_KZZI_K68PRO, "terport-tr95" => PART_TERPORT_TR95, "trust-gxt-960" => PART_TRUST_GXT_960, "weikav-sugar65" => PART_WEIKAV_SUGAR65, From 03119a3143d5feb05f1f5f52a2671a9019919266 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 13 May 2024 22:25:42 +0200 Subject: [PATCH 095/141] lint fixes, part reordering (also in readme) and marking kzzi-k68pro as write tested (due to bootloader) --- README.md | 2 +- src/part.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 70e47a4..af88832 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ sinowealth-kb-tool write \ | E-Yooso Z11 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK901 | ✅ | ✅ | | [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | +| [Kzzi K68Pro](http://en.kzzi.com/product/37/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | ❓ | ✅ | ✅ | | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | | [Leobog Hi75](https://leobogtech.com/products/leobog-hi75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Machenike K500-B61](https://global.machenike.com/products/k500-b61) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90? | BYK916 | ✅ | ✅ | @@ -93,7 +94,6 @@ sinowealth-kb-tool write \ | [Royal Kludge RK71](http://en.rkgaming.com/product/12/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | ❓ | ✅ | ✅ | | [Royal Kludge RK84](http://en.rkgaming.com/product/16/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ✅ | | Royal Kludge RKG68 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90A | SH68F90AS | ✅ | ✅ | -| [Kzzi K68Pro](http://en.kzzi.com/product/37/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | ❓ | ✅ | ❓ | | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index 8d7e1de..2acb096 100644 --- a/src/part.rs +++ b/src/part.rs @@ -176,7 +176,7 @@ pub const PART_DIGITALALLIANCE_MECA_WARRIOR_X: Part = Part { ..PART_BASE_SH68F90 }; -pub const PART_KZZI_K68PRO: Part = Part{ +pub const PART_KZZI_K68PRO: Part = Part { vendor_id: 0x258a, product_id: 0x0186, isp_index: 0x0001, @@ -234,6 +234,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, "genesis-thor-300" => PART_GENESIS_THOR_300, "glorious-model-o" => PART_GLORIOUS_MODEL_O, + "kzzi-k68pro" => PART_KZZI_K68PRO, "leobog-hi75" => PART_LEOBOG_HI75, "machenike-k500-b61" => PART_MACHENIKE_K500_B61, "nuphy-air60" => PART_NUPHY_AIR60, @@ -253,7 +254,6 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "royalkludge-rk71" => PART_ROYALKLUDGE_RK71, "royalkludge-rk84-iso-return" => PART_ROYALKLUDGE_RK84_ISO_RETURN, "royalkludge-rkg68" => PART_ROYALKLUDGE_RKG68, - "kzzi-k68pro" => PART_KZZI_K68PRO, "terport-tr95" => PART_TERPORT_TR95, "trust-gxt-960" => PART_TRUST_GXT_960, "weikav-sugar65" => PART_WEIKAV_SUGAR65, From dfa949fea9b17792fcd73620c6b6c1c1825d4cbe Mon Sep 17 00:00:00 2001 From: Vladislav Petrov <31391612+Aldnoa19@users.noreply.github.com> Date: Fri, 7 Jun 2024 19:46:39 +0300 Subject: [PATCH 096/141] Added support for Aula F87 (#79) --- README.md | 1 + src/part.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index af88832..0964849 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ sinowealth-kb-tool write \ | Model | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | | ----- | ------- | --- | --------- | ----------- | ------------ | +| [Aula F87](https://www.aulastar.com/index.php/gaming-keyboard/157.html) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | | Digital Alliance Meca Warrior X | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | E-Yooso Z11 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK901 | ✅ | ✅ | | [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index 2acb096..ad5134a 100644 --- a/src/part.rs +++ b/src/part.rs @@ -62,6 +62,13 @@ pub const PART_LEOBOG_HI75: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_AULA_F87: Part = Part { + vendor_id: 0x258a, + product_id: 0x010c, + isp_index: 1, + ..PART_BASE_SH68F90 +}; + pub const PART_XINMENG_K916: Part = Part { vendor_id: 0x258a, product_id: 0x00a1, @@ -229,6 +236,7 @@ pub const PART_EYOOSO_Z11: Part = Part { }; pub static PARTS: Map<&'static str, Part> = phf_map! { + "aula-f87" => PART_AULA_F87, "digitalalliance-meca-warrior-x" => PART_DIGITALALLIANCE_MECA_WARRIOR_X, "eyooso-z11" => PART_EYOOSO_Z11, "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, From 8df94c6d03a409f2f5614879bda138006ebe241b Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 24 Jun 2024 13:19:09 +0200 Subject: [PATCH 097/141] update write status for Aula F87 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0964849..6d843c8 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ sinowealth-kb-tool write \ | Model | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | | ----- | ------- | --- | --------- | ----------- | ------------ | -| [Aula F87](https://www.aulastar.com/index.php/gaming-keyboard/157.html) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ❓ | +| [Aula F87](https://www.aulastar.com/index.php/gaming-keyboard/157.html) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | Digital Alliance Meca Warrior X | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | E-Yooso Z11 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK901 | ✅ | ✅ | | [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | From 6e02ea7c6d669192e53a6fadffa93a5d1ffbbc00 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 24 Jun 2024 13:27:21 +0200 Subject: [PATCH 098/141] device: deltaco-wk95r (#81) https://github.com/carlossless/sinowealth-kb-tool/issues/80 --- README.md | 1 + src/part.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 6d843c8..e0fbe7e 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ sinowealth-kb-tool write \ | Model | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | | ----- | ------- | --- | --------- | ----------- | ------------ | | [Aula F87](https://www.aulastar.com/index.php/gaming-keyboard/157.html) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | +| Deltaco Gaming WK95R | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | Digital Alliance Meca Warrior X | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | E-Yooso Z11 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK901 | ✅ | ✅ | | [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index ad5134a..0592d94 100644 --- a/src/part.rs +++ b/src/part.rs @@ -177,6 +177,12 @@ pub const PART_ROYALKLUDGE_RK100: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_DELTACO_WK95R: Part = Part { + vendor_id: 0x258a, + product_id: 0x0049, + ..PART_BASE_SH68F90 +}; + pub const PART_DIGITALALLIANCE_MECA_WARRIOR_X: Part = Part { vendor_id: 0x258a, product_id: 0x0090, @@ -237,6 +243,7 @@ pub const PART_EYOOSO_Z11: Part = Part { pub static PARTS: Map<&'static str, Part> = phf_map! { "aula-f87" => PART_AULA_F87, + "deltaco-wk95r" => PART_DELTACO_WK95R, "digitalalliance-meca-warrior-x" => PART_DIGITALALLIANCE_MECA_WARRIOR_X, "eyooso-z11" => PART_EYOOSO_Z11, "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, From 942bad080bc2733002076e08c2213f88e438bdbb Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Tue, 9 Jul 2024 21:54:19 +0200 Subject: [PATCH 099/141] feature: payload conversion (to jtag and to isp) + flake, rust updates (#82) A convenience function to convert (full) isp payloads for programming via JTAG and vice versa. --- flake.lock | 74 ++++++++++-------------------------- flake.nix | 2 +- rust-toolchain.toml | 2 +- src/isp.rs | 7 ++-- src/main.rs | 82 ++++++++++++++++++++++++++++++++++++--- src/part.rs | 4 ++ src/util.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 198 insertions(+), 66 deletions(-) diff --git a/flake.lock b/flake.lock index 74e9ffe..019ff72 100644 --- a/flake.lock +++ b/flake.lock @@ -1,33 +1,15 @@ { "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "naersk": { "inputs": { "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1698420672, - "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", + "lastModified": 1718727675, + "narHash": "sha256-uFsCwWYI2pUpt0awahSBorDUrUfBhaAiyz+BPTS2MHk=", "owner": "nix-community", "repo": "naersk", - "rev": "aeb58d5e8faead8980a807c840232697982d47b9", + "rev": "941ce6dc38762a7cfb90b5add223d584feed299b", "type": "github" }, "original": { @@ -38,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708751719, - "narHash": "sha256-0uWOKSpXJXmXswOvDM5Vk3blB74apFB6rNGWV5IjoN0=", + "lastModified": 1719082008, + "narHash": "sha256-jHJSUH619zBQ6WdC21fFAlDxHErKVDJ5fpN0Hgx4sjs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f63ce824cd2f036216eb5f637dfef31e1a03ee89", + "rev": "9693852a2070b398ee123a329e68f0dab5526681", "type": "github" }, "original": { @@ -52,27 +34,27 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1708702655, - "narHash": "sha256-qxT5jSLhelfLhQ07+AUxSTm1VnVH+hQxDkQSZ/m/Smo=", + "lastModified": 1719145550, + "narHash": "sha256-K0i/coxxTEl30tgt4oALaylQfxqbotTSNb1/+g+mKMQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c5101e457206dd437330d283d6626944e28794b3", + "rev": "e4509b3a560c87a8d4cb6f9992b8915abf9e36d8", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.11", + "ref": "nixos-24.05", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_3": { "locked": { - "lastModified": 1706487304, - "narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=", + "lastModified": 1718428119, + "narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "90f456026d284c22b3e3497be980b2e47d0b28ac", + "rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5", "type": "github" }, "original": { @@ -92,15 +74,14 @@ }, "rust-overlay": { "inputs": { - "flake-utils": "flake-utils", "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1708827164, - "narHash": "sha256-oBNS6pO04Y6gZBLThP3JDDgviex0+WTXz3bVBenyzms=", + "lastModified": 1719195554, + "narHash": "sha256-bFXHMjpYlEERexzXa1gLGJO/1l8dxaAtSNE56YALuTg=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "e0626adabd5ea461f80b1b11390da2a6575adb30", + "rev": "577ee84c69ba89894ac622d71a678a14d746b2f7", "type": "github" }, "original": { @@ -124,31 +105,16 @@ "type": "github" } }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "utils": { "inputs": { - "systems": "systems_2" + "systems": "systems" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index b9cfd12..06771e6 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; utils.url = "github:numtide/flake-utils"; naersk.url = "github:nix-community/naersk"; rust-overlay.url = "github:oxalica/rust-overlay"; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 98d869a..08e95c4 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.76" +channel = "1.78" components = [ "rustfmt", "rustc", diff --git a/src/isp.rs b/src/isp.rs index e47227a..e95865f 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -3,8 +3,7 @@ use std::{thread, time}; use log::{debug, info}; use thiserror::Error; -use super::{part::*, util}; -use crate::VerificationError; +use crate::{part::*, util, VerificationError}; extern crate hidapi; @@ -317,10 +316,10 @@ impl ISPDevice { self.reboot()?; } - return Ok(firmware); + Ok(firmware) } - pub fn write_cycle(&self, firmware: &mut Vec) -> Result<(), ISPError> { + pub fn write_cycle(&self, firmware: &mut [u8]) -> Result<(), ISPError> { // ensure that the address at is the same as the reset vector firmware.copy_within(1..3, self.part.firmware_size - 4); diff --git a/src/main.rs b/src/main.rs index 64669ac..7eb7560 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,8 @@ pub enum CLIError { ISPError(#[from] ISPError), #[error(transparent)] IHEXError(#[from] ConversionError), + #[error(transparent)] + PayloadConversionError(#[from] PayloadConversionError), } fn main() -> ExitCode { @@ -39,7 +41,7 @@ fn main() -> ExitCode { } fn cli() -> Command { - return Command::new("sinowealth-kb-tool") + Command::new("sinowealth-kb-tool") .about("A programming tool for Sinowealth Gaming KB devices") .version(env!("CARGO_PKG_VERSION")) .subcommand_required(true) @@ -50,6 +52,15 @@ fn cli() -> Command { .short_flag('l') .about("List all connected devices and their identifiers. This is useful to find the manufacturer and product id for your device.") ) + .subcommand( + Command::new("convert") + .short_flag('c') + .about("Convert payload from bootloader to JTAG and vice versa.") + .arg(arg!(-d --direction "direction of conversion").value_parser(["to_jtag", "to_isp"]).required(true)) + .part_args() + .arg(arg!(input_file: "file to convert")) + .arg(arg!(output_file: "file to write results to")) + ) .subcommand( Command::new("read") .short_flag('r') @@ -68,11 +79,11 @@ fn cli() -> Command { .about("Write file (Intel HEX) into flash.") .arg(arg!(input_file: "payload to write into flash")) .part_args(), - ); + ) } fn get_log_level() -> log::LevelFilter { - return if let Ok(debug) = env::var("DEBUG") { + if let Ok(debug) = env::var("DEBUG") { if debug == "1" { log::LevelFilter::Debug } else { @@ -83,7 +94,7 @@ fn get_log_level() -> log::LevelFilter { return log::LevelFilter::Debug; #[cfg(not(debug_assertions))] log::LevelFilter::Info - }; + } } fn err_main() -> Result<(), CLIError> { @@ -146,6 +157,67 @@ fn err_main() -> Result<(), CLIError> { Some(("list", _)) => { ISPDevice::print_connected_devices().map_err(CLIError::from)?; } + Some(("convert", sub_matches)) => { + let input_file = sub_matches + .get_one::("input_file") + .map(|s| s.as_str()) + .unwrap(); + + let output_file = sub_matches + .get_one::("output_file") + .map(|s| s.as_str()) + .unwrap(); + + let direction = sub_matches + .get_one::("direction") + .map(|s| s.as_str()) + .unwrap(); + + let part = get_part_from_matches(sub_matches); + + let mut file = fs::File::open(input_file).map_err(CLIError::from)?; + let mut file_buf = Vec::new(); + file.read_to_end(&mut file_buf).map_err(CLIError::from)?; + let file_str = String::from_utf8_lossy(&file_buf[..]); + let mut firmware = from_ihex(&file_str, part.firmware_size + part.bootloader_size) + .map_err(CLIError::from)?; + + if firmware.len() < part.firmware_size { + log::warn!( + "Firmware size is more than expected ({}). Increasing to {}", + firmware.len(), + part.firmware_size + ); + firmware.resize(part.firmware_size, 0); + } + + match direction { + "to_jtag" => { + convert_to_jtag_payload(&mut firmware, part).map_err(CLIError::from)?; + if firmware.len() < part.total_flash_size() { + log::warn!( + "Firmware is smaller ({} bytes) than expected ({} bytes). This payload might not be suitable for JTAG flashing.", + firmware.len(), + part.total_flash_size() + ); + } + } + "to_isp" => { + convert_to_isp_payload(&mut firmware, part).map_err(CLIError::from)?; + if firmware.len() > part.firmware_size { + log::warn!( + "Firmware size is larger ({} bytes) than expected ({} bytes). This payload might not be suitable for ISP flashing.", + firmware.len(), + part.firmware_size + ); + } + } + _ => unreachable!(), + } + + let ihex = to_ihex(firmware).map_err(CLIError::from)?; + fs::write(output_file, ihex).map_err(CLIError::from)?; + } _ => unreachable!(), } Ok(()) @@ -236,5 +308,5 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { if let Some(reboot) = reboot { part.reboot = *reboot; } - return part; + part } diff --git a/src/part.rs b/src/part.rs index 0592d94..92cce48 100644 --- a/src/part.rs +++ b/src/part.rs @@ -288,6 +288,10 @@ impl Part { pub fn num_pages(&self) -> usize { self.firmware_size / self.page_size } + + pub fn total_flash_size(&self) -> usize { + self.firmware_size + self.bootloader_size + } } #[test] diff --git a/src/util.rs b/src/util.rs index ae0cd3a..5b2b549 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,9 @@ +use crate::part::Part; use thiserror::Error; +#[cfg(test)] +use crate::part::PART_BASE_SH68F90; + #[derive(Debug, Clone, Error, PartialEq)] pub enum VerificationError { #[error("Firmware Mismatch @ {addr:#06x} --- {expected:#04x} != {actual:#04x}")] @@ -12,7 +16,7 @@ pub enum VerificationError { LengthMismatch { expected: usize, actual: usize }, } -pub fn verify(expected: &Vec, actual: &Vec) -> Result<(), VerificationError> { +pub fn verify(expected: &[u8], actual: &[u8]) -> Result<(), VerificationError> { if expected.len() != actual.len() { return Err(VerificationError::LengthMismatch { expected: expected.len(), @@ -33,6 +37,62 @@ pub fn verify(expected: &Vec, actual: &Vec) -> Result<(), VerificationEr Ok(()) } +#[derive(Debug, Error)] +pub enum PayloadConversionError { + #[error("Expected LJMP not found at {addr:#06x}")] + LJMPNotFoundError { addr: u16 }, + #[error("Unexpected addr at {source_addr:#06x} pointing to {target_addr:#06x}")] + UnexpectedAddressError { source_addr: u16, target_addr: u16 }, +} + +pub fn convert_to_jtag_payload(input: &mut [u8], part: Part) -> Result<(), PayloadConversionError> { + if input[0] != 0x02 { + return Err(PayloadConversionError::LJMPNotFoundError { addr: 0x0000 }); + } + + let main_fw_address = u16::from_be_bytes(input[1..3].try_into().unwrap()); + if main_fw_address > 0xefff { + return Err(PayloadConversionError::UnexpectedAddressError { + source_addr: 0x0001, + target_addr: main_fw_address, + }); + } + + let bootloader_ljmp_addr = (part.firmware_size as u16).to_be_bytes(); + let ljmp_addr = part.firmware_size - 5; + + input[1..3].copy_from_slice(&bootloader_ljmp_addr); + input[ljmp_addr] = 0x02; + input[ljmp_addr + 1..ljmp_addr + 3].copy_from_slice(&main_fw_address.to_be_bytes()); + + Ok(()) +} + +pub fn convert_to_isp_payload(input: &mut [u8], part: Part) -> Result<(), PayloadConversionError> { + if input[0] != 0x02 { + return Err(PayloadConversionError::LJMPNotFoundError { addr: 0 }); + } + + let ljmp_addr = part.firmware_size - 5; + if input[ljmp_addr] != 0x02 { + return Err(PayloadConversionError::LJMPNotFoundError { addr: 0x0000 }); + } + + let main_fw_address = + u16::from_be_bytes(input[ljmp_addr + 1..ljmp_addr + 3].try_into().unwrap()); + if main_fw_address > 0xefff { + return Err(PayloadConversionError::UnexpectedAddressError { + source_addr: (ljmp_addr + 1) as u16, + target_addr: main_fw_address, + }); + } + + input[1..3].copy_from_slice(&main_fw_address.to_be_bytes()); + input[ljmp_addr..ljmp_addr + 3].fill(0x00); + + Ok(()) +} + #[test] fn test_verify_success() { assert!(verify(&vec![1, 2, 3, 4], &vec![1, 2, 3, 4]).is_ok()); @@ -60,3 +120,34 @@ fn test_verify_error_byte_mismatch() { }) ); } + +#[test] +fn test_convert_to_jtag_payload() { + let part = PART_BASE_SH68F90; + let mut firmware: [u8; 65536] = [0; 65536]; + firmware[0] = 0x02; + firmware[1] = 0x00; + firmware[2] = 0x66; + + convert_to_jtag_payload(&mut firmware, part).unwrap(); + + assert_eq!(firmware[0..3], [0x02, 0xf0, 0x00]); + assert_eq!(firmware[0xeffb..0xeffe], [0x02, 0x00, 0x66]); +} + +#[test] +fn test_convert_to_isp_payload() { + let part = PART_BASE_SH68F90; + let mut firmware: [u8; 65536] = [0; 65536]; + firmware[0] = 0x02; + firmware[1] = 0xf0; + firmware[2] = 0x00; + firmware[0xeffb] = 0x02; + firmware[0xeffc] = 0x00; + firmware[0xeffd] = 0x66; + + convert_to_isp_payload(&mut firmware, part).unwrap(); + + assert_eq!(firmware[0..3], [0x02, 0x00, 0x66]); + assert_eq!(firmware[0xeffb..0xeffe], [0x00, 0x00, 0x00]); +} From 8c205caea17249f6d13f17be05ce35619be2fec1 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Tue, 9 Jul 2024 21:56:25 +0200 Subject: [PATCH 100/141] bump to 0.1.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e2da0d..a1acf8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.0.11" +version = "0.1.0" dependencies = [ "clap", "clap-num", diff --git a/Cargo.toml b/Cargo.toml index ccf87a4..47b094a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based HID devices through the commonly found ISP bootloader """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.0.11" +version = "0.1.0" edition = "2021" license = "MIT" rust-version = "1.65" From a882d498bc3fc0d3a2f4cd65957ba4a96fa7d998 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Thu, 11 Jul 2024 12:00:08 +0200 Subject: [PATCH 101/141] update bootloader findings for genesis-thor-300 bootloader --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0fbe7e..49cb391 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ sinowealth-kb-tool write \ | 46459c31e58194fa076b8ce8fb1f3eaa | ? | ? | ok | | 620f0b67a91f7f74151bc5be745b7110 | ? | fail[^1] | ok | | cfc8661da8c9d7e351b36c0a763426aa | ok | fail[^1] | ok | -| e57490acebcaabfcff84a0ff013955d9 | ok | ? | ? | +| e57490acebcaabfcff84a0ff013955d9 | ok | fail[^1] | ? | [^1]: macOS does not recognize the composite device as an HID device From f082d481c2f3fe2ebb08ed2d87179481aa458010 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Thu, 11 Jul 2024 18:46:16 +0200 Subject: [PATCH 102/141] confirmed usability on linux with genesis-thor-300 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49cb391..daf4cf6 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ sinowealth-kb-tool write \ | 46459c31e58194fa076b8ce8fb1f3eaa | ? | ? | ok | | 620f0b67a91f7f74151bc5be745b7110 | ? | fail[^1] | ok | | cfc8661da8c9d7e351b36c0a763426aa | ok | fail[^1] | ok | -| e57490acebcaabfcff84a0ff013955d9 | ok | fail[^1] | ? | +| e57490acebcaabfcff84a0ff013955d9 | ok | fail[^1] | ok | [^1]: macOS does not recognize the composite device as an HID device From f754d069a4abe91133cd5c13cf7ce8652c3eec9c Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Tue, 16 Jul 2024 17:59:21 +0200 Subject: [PATCH 103/141] sorting all hid devices by their paths (#84) Addresses: https://github.com/carlossless/sinowealth-kb-tool/issues/83#issuecomment-2229042784 --- src/isp.rs | 106 ++++++++++++++++++++++++++++------------------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/src/isp.rs b/src/isp.rs index e95865f..6131cb0 100644 --- a/src/isp.rs +++ b/src/isp.rs @@ -7,7 +7,7 @@ use crate::{part::*, util, VerificationError}; extern crate hidapi; -use hidapi::{HidApi, HidDevice, HidError}; +use hidapi::{DeviceInfo, HidApi, HidDevice, HidError}; const MAX_RETRIES: usize = 10; @@ -67,6 +67,18 @@ struct HIDDevices { data: HidDevice, } +pub trait HidApiExtension { + fn sorted_device_list(&self) -> Vec<&DeviceInfo>; +} + +impl HidApiExtension for HidApi { + fn sorted_device_list(self: &HidApi) -> Vec<&DeviceInfo> { + let mut devices: Vec<_> = self.device_list().collect(); + devices.sort_by_key(|d| d.path()); + devices + } +} + impl ISPDevice { pub fn new(part: Part) -> Result { let devices = Self::find_isp_device(part)?; @@ -78,42 +90,6 @@ 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(); @@ -125,9 +101,9 @@ impl ISPDevice { fn open_isp_devices() -> Result { let api = Self::hidapi(); - - let mut devices: Vec<_> = api - .device_list() + let sorted_devices: Vec<_> = api.sorted_device_list(); + let isp_devices: Vec<_> = sorted_devices + .into_iter() .filter(|d| { #[cfg(not(target_os = "linux"))] return d.vendor_id() == GAMING_KB_VENDOR_ID @@ -148,9 +124,7 @@ impl ISPDevice { }) .collect(); - devices.sort_by_key(|d| d.path()); - - for d in &devices { + for d in &isp_devices { #[cfg(not(target_os = "linux"))] debug!( "Found ISP Device: {:#06x} {:#06x} {:?} {:#06x} {:#06x}", @@ -169,14 +143,14 @@ impl ISPDevice { ); } - let device_count = devices.len(); + let device_count = isp_devices.len(); if device_count == 0 { return Err(ISPError::NotFound); } #[cfg(not(target_os = "windows"))] if device_count == 1 { - let request_device = devices.first().unwrap(); + let request_device = isp_devices.first().unwrap(); debug!("Request device: {:?}", request_device.path()); return Ok(HIDDevices { request: api.open_path(request_device.path()).unwrap(), @@ -189,8 +163,8 @@ impl ISPDevice { if device_count == 1 { return Err(ISPError::IrregularDeviceCount(device_count)); } else if device_count == 2 { - let request_device = devices[0]; - let data_device = devices[1]; + let request_device = isp_devices[0]; + let data_device = isp_devices[1]; debug!("Request device: {:?}", request_device.path()); debug!("Data device: {:?}", data_device.path()); return Ok(HIDDevices { @@ -210,8 +184,9 @@ impl ISPDevice { part.vendor_id, part.product_id ); - let request_device_info = api - .device_list() + let sorted_devices: Vec<_> = api.sorted_device_list(); + let request_device_info = sorted_devices + .into_iter() .filter(|d| { #[cfg(not(target_os = "linux"))] return d.vendor_id() == part.vendor_id @@ -301,6 +276,39 @@ impl ISPDevice { Ok(()) } + /// Prints out all connected HID devices and their paths. + pub fn print_connected_devices() -> Result<(), ISPError> { + info!("Listing all connected HID devices..."); + let api = Self::hidapi(); + let devices: Vec<_> = api.sorted_device_list(); + + 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(()) + } + pub fn read_cycle(&self, read_type: ReadType) -> Result, ISPError> { self.enable_firmware()?; From b43377e687204e71e5029d9e910bd383778cb81e Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Wed, 17 Jul 2024 00:15:36 +0200 Subject: [PATCH 104/141] github action updates (#85) --- .github/workflows/push.yml | 23 ++++++++++++----------- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 6eb8369..a6f1964 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -14,9 +14,9 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Cargo cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/registry @@ -28,9 +28,9 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Cargo cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/registry @@ -60,9 +60,9 @@ jobs: TARGET: ${{ matrix.TARGET }} OS: ${{ matrix.OS }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Cargo cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/registry @@ -96,9 +96,9 @@ jobs: mv ./target/$TARGET/release/$EXEC ./$EXEC tar -czf ./artifacts/$NAME-$TARGET-$TAG.tar.gz $EXEC - name: Archive artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: result + name: result-${{ matrix.TARGET }} path: | ./artifacts @@ -110,12 +110,13 @@ jobs: contents: write steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: - name: result + pattern: result-* + merge-multiple: true path: ./artifacts - name: Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: draft: true generate_release_notes: true diff --git a/Cargo.lock b/Cargo.lock index a1acf8e..217d510 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.1.0" +version = "0.1.1" dependencies = [ "clap", "clap-num", diff --git a/Cargo.toml b/Cargo.toml index 47b094a..701041b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based HID devices through the commonly found ISP bootloader """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.1.0" +version = "0.1.1" edition = "2021" license = "MIT" rust-version = "1.65" From 200d4341e15cfded7900d60477998c5c32d5b17b Mon Sep 17 00:00:00 2001 From: Brian Pugh Date: Sat, 20 Jul 2024 06:52:30 -0400 Subject: [PATCH 105/141] device: yunzii-al66 / xinmeng-m66 (#86) I have a yunzii-al66 board, and it is (I think) internally the same as the yunzii-al71, but with 5 fewer keys. The `vendor_id` and `product_id` are the same as the yunzi-al71. It follows that it's probably also the same as the xinmeng-m66 (but i dont own this board). Writing also seemed to work. --- .gitignore | 2 ++ README.md | 2 ++ src/part.rs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index b462d9f..b2f4c9f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ /private /target + +*.hex diff --git a/README.md b/README.md index daf4cf6..8e28f60 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,10 @@ sinowealth-kb-tool write \ | Terport TR95 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | Weikav Sugar65 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | Xinmeng K916 | cfc8661da8c9d7e351b36c0a763426aa | SH68F90 | ❓ | ✅ | ✅ | +| Xinmeng M66 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | SH68F90AS | ✅ | ✅ | | Xinmeng M71 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | SH68F90AS | ✅ | ✅ | | Xinmeng XM-RF68 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90U | ✅ | ✅ | +| Yunzii AL66 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | SH68F90AS | ✅ | ✅ | | Yunzii AL71 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | SH68F90AS | ✅ | ✅ | ### Mice diff --git a/src/part.rs b/src/part.rs index 92cce48..5a60089 100644 --- a/src/part.rs +++ b/src/part.rs @@ -273,8 +273,10 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "trust-gxt-960" => PART_TRUST_GXT_960, "weikav-sugar65" => PART_WEIKAV_SUGAR65, "xinmeng-k916" => PART_XINMENG_K916, + "xinmeng-m66" => PART_XINMENG_M71, "xinmeng-m71" => PART_XINMENG_M71, "xinmeng-xm-rf68" => PART_XINMENG_XM_RF68, + "yunzii-al66" => PART_XINMENG_M71, // same as xinmeng-m71 "yunzii-al71" => PART_XINMENG_M71, // same as xinmeng-m71 }; From deb107dbff23dbb794dd5df043f83b7e906f12e9 Mon Sep 17 00:00:00 2001 From: RoadRoller01 <76426234+RoadRoller01@users.noreply.github.com> Date: Wed, 31 Jul 2024 23:38:56 +0300 Subject: [PATCH 106/141] Update README.md (#87) .rule -> .rules .rule will not work :skull: --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e28f60..00ef5cd 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ sinowealth-kb-tool write \ To enable running this tool without superuser privileges add the following udev rule with `xxxx` and `yyyy` replaced with your device Vendor ID and Product ID respectively. ```udev -# /etc/udev/rules.d/plugdev.rule +# /etc/udev/rules.d/plugdev.rules SUBSYSTEMS=="usb", ATTRS{idVendor}=="xxxx", ATTRS{idProduct}=="yyyy", MODE="0660", GROUP="plugdev" SUBSYSTEMS=="usb", ATTRS{idVendor}=="0603", ATTRS{idProduct}=="1020", MODE="0660", GROUP="plugdev" ``` From bab108bdad2364983fa1d935d95e926b6964c625 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 16 Sep 2024 21:59:55 +0200 Subject: [PATCH 107/141] device: portronics-hydra10 (#89) https://github.com/carlossless/sinowealth-kb-tool/issues/88 --- README.md | 1 + src/part.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 00ef5cd..1f76e70 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ sinowealth-kb-tool write \ | [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [NuPhy Air96](https://nuphy.com/products/air96-wireless-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | +| [Portronics Hydra 10](https://www.portronics.com/products/hydra-10) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K530 Draconic PRO](https://www.redragonzone.com/products/draconic-k530) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index 5a60089..fc3e462 100644 --- a/src/part.rs +++ b/src/part.rs @@ -241,6 +241,12 @@ pub const PART_EYOOSO_Z11: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_PORTRONICS_HYDRA10: Part = Part { + vendor_id: 0x258a, + product_id: 0x0049, + ..PART_BASE_SH68F90 +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "aula-f87" => PART_AULA_F87, "deltaco-wk95r" => PART_DELTACO_WK95R, @@ -256,6 +262,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 "nuphy-air96" => PART_NUPHY_AIR60, // same as nuphy-air60 "nuphy-halo65" => PART_NUPHY_AIR60, // same as nuphy-air60 + "portronics-hydra10" => PART_PORTRONICS_HYDRA10, "re-k70-byk800" => PART_RE_K70_BYK800, "redragon-k530-draconic-pro" => PART_REDRAGON_K530_DRACONIC_PRO, "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, From a15443cb666516b6756eb417ae443517fc97d475 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Tue, 1 Oct 2024 20:29:37 +0200 Subject: [PATCH 108/141] device: redragon-k633-ryze (#91) https://github.com/carlossless/sinowealth-kb-tool/issues/90 --- README.md | 1 + src/part.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 1f76e70..280d243 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ sinowealth-kb-tool write \ | [Redragon K530 Draconic PRO](https://www.redragonzone.com/products/draconic-k530) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | +| [Redragon K633 RYZE](https://www.redragonzone.com/products/redragon-k633-ryze-rgb-led-backlit-mechanical-gaming-keyboard-with-68-professional-keys-linear-red-switches) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K641 SHACO PRO](https://www.redragonzone.com/products/redragon-k641-shaco-pro-65-aluminum-rgb-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K658 PRO SE](https://www.redragonzone.com/products/k658-pro-se-90-wireless-rgb-gaming-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index fc3e462..a586404 100644 --- a/src/part.rs +++ b/src/part.rs @@ -247,6 +247,12 @@ pub const PART_PORTRONICS_HYDRA10: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_REDRAGON_K633_RYZE: Part = Part { + vendor_id: 0x258a, + product_id: 0x0049, + ..PART_BASE_SH68F90 +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "aula-f87" => PART_AULA_F87, "deltaco-wk95r" => PART_DELTACO_WK95R, @@ -267,6 +273,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "redragon-k530-draconic-pro" => PART_REDRAGON_K530_DRACONIC_PRO, "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, + "redragon-k633-ryze" => PART_REDRAGON_K633_RYZE, "redragon-k641-shaco-pro" => PART_REDRAGON_K641_SHACO_PRO, "redragon-k658-pro-se" => PART_REDRAGON_K658_PRO_SE, "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, From a2a85268167e61f655e0ee7961f72cdfb8e8f60a Mon Sep 17 00:00:00 2001 From: Alen Farkas <60406248+Dravelz-21@users.noreply.github.com> Date: Fri, 13 Dec 2024 20:34:17 +0100 Subject: [PATCH 109/141] device: redragon-k630-norgb (#94) Add support for Redragon K630 non-rgb version. It's basically the same as the E-Yooso Z11. Tested Reading and Writing both works. Co-authored-by: Dravelz --- README.md | 1 + src/part.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 280d243..f5cda63 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ sinowealth-kb-tool write \ | [NuPhy Halo65](https://nuphy.com/products/halo65) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Portronics Hydra 10](https://www.portronics.com/products/hydra-10) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K530 Draconic PRO](https://www.redragonzone.com/products/draconic-k530) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90A | BYK916 | ✅ | ✅ | +| [Redragon K630 Single LED version](https://www.redragonzone.com/products/redragon-k630-gaming-mechanical-keyboard) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90A | SH68F90AU | ✅ | ✅ | | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K633 RYZE](https://www.redragonzone.com/products/redragon-k633-ryze-rgb-led-backlit-mechanical-gaming-keyboard-with-68-professional-keys-linear-red-switches) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index a586404..7f89a43 100644 --- a/src/part.rs +++ b/src/part.rs @@ -235,6 +235,12 @@ pub const PART_REDRAGON_K530_DRACONIC_PRO: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_REDRAGON_K630_NO_RGB: Part = Part { + vendor_id: 0x258a, + product_id: 0x002a, + ..PART_BASE_SH68F90 +}; + pub const PART_EYOOSO_Z11: Part = Part { vendor_id: 0x258a, product_id: 0x002a, @@ -271,6 +277,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "portronics-hydra10" => PART_PORTRONICS_HYDRA10, "re-k70-byk800" => PART_RE_K70_BYK800, "redragon-k530-draconic-pro" => PART_REDRAGON_K530_DRACONIC_PRO, + "redragon-k630-norgb" => PART_REDRAGON_K630_NO_RGB, "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, "redragon-k633-ryze" => PART_REDRAGON_K633_RYZE, From 8a5a545f42ee7c138b6ed599c46acf6448073c2f Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Fri, 13 Dec 2024 20:46:56 +0100 Subject: [PATCH 110/141] flake and cargo dep updates --- Cargo.lock | 306 +++++++++++++++++++++++++++-------------------------- Cargo.toml | 4 +- flake.lock | 8 +- flake.nix | 4 +- 4 files changed, 164 insertions(+), 158 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 217d510..cda546e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,97 +4,97 @@ version = 3 [[package]] name = "anstream" -version = "0.3.2" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "1.0.2" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "2.4.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "cc" -version = "1.0.83" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" dependencies = [ - "libc", + "shlex", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" -version = "4.3.23" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", ] [[package]] name = "clap-num" -version = "1.0.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488557e97528174edaa2ee268b23a809e0c598213a4bbcb4f34575a46fda147e" +checksum = "0e063d263364859dc54fb064cedb7c122740cd4733644b14b176c097f51e8ab7" dependencies = [ "num-traits", ] [[package]] name = "clap_builder" -version = "4.3.23" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -104,64 +104,37 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ - "is-terminal", "lazy_static", - "windows-sys", -] - -[[package]] -name = "errno" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", + "windows-sys 0.48.0", ] -[[package]] -name = "hermit-abi" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - [[package]] name = "hidapi" -version = "2.4.1" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723777263b0dcc5730aec947496bd8c3940ba63c15f5633b288cc615f4f6af79" +checksum = "03b876ecf37e86b359573c16c8366bc3eba52b689884a0fc42ba3f67203d2a8b" dependencies = [ "cc", + "cfg-if", "libc", "pkg-config", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -171,39 +144,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "365a784774bb381e8c19edb91190a90d7f2625e057b55de2bc0f6b57bc779ff2" [[package]] -name = "is-terminal" -version = "0.4.9" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" - -[[package]] -name = "linux-raw-sys" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "md5" @@ -213,9 +175,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -264,24 +226,24 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -302,27 +264,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] -name = "rustix" -version = "0.38.20" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simple_logger" -version = "4.3.3" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e7e46c8c90251d47d08b28b8a419ffb4aede0f87c2eea95e17d1d5bacbf3ef1" +checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb" dependencies = [ "colored", "log", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -342,21 +297,21 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.29" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -365,18 +320,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.47" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", @@ -385,45 +340,32 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "winapi" -version = "0.3.9" +name = "windows-sys" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-targets 0.48.5", ] -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -432,13 +374,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -447,38 +405,86 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index 701041b..58b93ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ clap = "4.1" clap-num = "1.0" ihex = "3.0" md5 = "0.7" -thiserror = "1.0" +thiserror = "2.0" [dependencies.hidapi] version = "2.4" @@ -26,7 +26,7 @@ version = "0.4" features = ["max_level_debug"] [dependencies.simple_logger] -version = "4.3" +version = "5.0" default-features = false features = ["stderr", "colors"] diff --git a/flake.lock b/flake.lock index 019ff72..db66b32 100644 --- a/flake.lock +++ b/flake.lock @@ -34,16 +34,16 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1719145550, - "narHash": "sha256-K0i/coxxTEl30tgt4oALaylQfxqbotTSNb1/+g+mKMQ=", + "lastModified": 1733808091, + "narHash": "sha256-KWwINTQelKOoQgrXftxoqxmKFZb9pLVfnRvK270nkVk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e4509b3a560c87a8d4cb6f9992b8915abf9e36d8", + "rev": "a0f3e10d94359665dba45b71b4227b0aeb851f8e", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-24.05", + "ref": "nixos-24.11", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index 06771e6..09603fb 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; utils.url = "github:numtide/flake-utils"; naersk.url = "github:nix-community/naersk"; rust-overlay.url = "github:oxalica/rust-overlay"; @@ -43,7 +43,7 @@ packages = { # For `nix build` `nix run`, & `nix profile install`: - default = naersk'.buildPackage rec { + default = naersk'.buildPackage { pname = "sinowealth-kb-tool"; version = "latest"; From 2c72ad66f3c85e9c49d93b33278c5baefc0b5eda Mon Sep 17 00:00:00 2001 From: JinGen Lim <1116555+jglim@users.noreply.github.com> Date: Thu, 16 Jan 2025 04:24:07 +0800 Subject: [PATCH 111/141] device: yinren-r108 (#99) https://github.com/carlossless/sinowealth-kb-tool/issues/98 --- README.md | 1 + src/part.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index f5cda63..ecaa45b 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ sinowealth-kb-tool write \ | Xinmeng M66 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | SH68F90AS | ✅ | ✅ | | Xinmeng M71 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | SH68F90AS | ✅ | ✅ | | Xinmeng XM-RF68 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90U | ✅ | ✅ | +| Yinren R108 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | Yunzii AL66 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | SH68F90AS | ✅ | ✅ | | Yunzii AL71 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | SH68F90AS | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index 7f89a43..1630c2a 100644 --- a/src/part.rs +++ b/src/part.rs @@ -259,6 +259,13 @@ pub const PART_REDRAGON_K633_RYZE: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_YINREN_R108: Part = Part { + vendor_id: 0x258a, + product_id: 0x0049, + isp_index: 1, + ..PART_BASE_SH68F90 +}; + pub static PARTS: Map<&'static str, Part> = phf_map! { "aula-f87" => PART_AULA_F87, "deltaco-wk95r" => PART_DELTACO_WK95R, @@ -297,6 +304,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "xinmeng-m66" => PART_XINMENG_M71, "xinmeng-m71" => PART_XINMENG_M71, "xinmeng-xm-rf68" => PART_XINMENG_XM_RF68, + "yinren-r108" => PART_YINREN_R108, "yunzii-al66" => PART_XINMENG_M71, // same as xinmeng-m71 "yunzii-al71" => PART_XINMENG_M71, // same as xinmeng-m71 }; From 145b353491f80db71b9df173320d546e62c9f05c Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Wed, 15 Jan 2025 21:29:11 +0100 Subject: [PATCH 112/141] device: aula-f75 (#97) https://github.com/carlossless/sinowealth-kb-tool/issues/96 --- README.md | 1 + src/part.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index ecaa45b..e933a3d 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ sinowealth-kb-tool write \ | Model | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | | ----- | ------- | --- | --------- | ----------- | ------------ | +| [Aula F75](https://www.aulastar.com/gaming-keyboard/176.html) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Aula F87](https://www.aulastar.com/index.php/gaming-keyboard/157.html) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | Deltaco Gaming WK95R | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | Digital Alliance Meca Warrior X | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index 1630c2a..001490a 100644 --- a/src/part.rs +++ b/src/part.rs @@ -62,6 +62,13 @@ pub const PART_LEOBOG_HI75: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_AULA_F75: Part = Part { + vendor_id: 0x258a, + product_id: 0x010c, + isp_index: 1, + ..PART_BASE_SH68F90 +}; + pub const PART_AULA_F87: Part = Part { vendor_id: 0x258a, product_id: 0x010c, @@ -267,6 +274,7 @@ pub const PART_YINREN_R108: Part = Part { }; pub static PARTS: Map<&'static str, Part> = phf_map! { + "aula-f75" => PART_AULA_F75, "aula-f87" => PART_AULA_F87, "deltaco-wk95r" => PART_DELTACO_WK95R, "digitalalliance-meca-warrior-x" => PART_DIGITALALLIANCE_MECA_WARRIOR_X, From 7b291f2cb9a70245ef43f44dac77d0c7341fad14 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 27 Apr 2025 23:21:14 +0200 Subject: [PATCH 113/141] flake update --- flake.lock | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/flake.lock b/flake.lock index db66b32..c7f28f3 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1718727675, - "narHash": "sha256-uFsCwWYI2pUpt0awahSBorDUrUfBhaAiyz+BPTS2MHk=", + "lastModified": 1743800763, + "narHash": "sha256-YFKV+fxEpMgP5VsUcM6Il28lI0NlpM7+oB1XxbBAYCw=", "owner": "nix-community", "repo": "naersk", - "rev": "941ce6dc38762a7cfb90b5add223d584feed299b", + "rev": "ed0232117731a4c19d3ee93aa0c382a8fe754b01", "type": "github" }, "original": { @@ -20,25 +20,27 @@ }, "nixpkgs": { "locked": { - "lastModified": 1719082008, - "narHash": "sha256-jHJSUH619zBQ6WdC21fFAlDxHErKVDJ5fpN0Hgx4sjs=", + "lastModified": 1745377448, + "narHash": "sha256-jhZDfXVKdD7TSEGgzFJQvEEZ2K65UMiqW5YJ2aIqxMA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9693852a2070b398ee123a329e68f0dab5526681", + "rev": "507b63021ada5fee621b6ca371c4fca9ca46f52c", "type": "github" }, "original": { - "id": "nixpkgs", - "type": "indirect" + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1733808091, - "narHash": "sha256-KWwINTQelKOoQgrXftxoqxmKFZb9pLVfnRvK270nkVk=", + "lastModified": 1745487689, + "narHash": "sha256-FQoi3R0NjQeBAsEOo49b5tbDPcJSMWc3QhhaIi9eddw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a0f3e10d94359665dba45b71b4227b0aeb851f8e", + "rev": "5630cf13cceac06cefe9fc607e8dfa8fb342dde3", "type": "github" }, "original": { @@ -50,11 +52,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1718428119, - "narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=", + "lastModified": 1744536153, + "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5", + "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", "type": "github" }, "original": { @@ -77,11 +79,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1719195554, - "narHash": "sha256-bFXHMjpYlEERexzXa1gLGJO/1l8dxaAtSNE56YALuTg=", + "lastModified": 1745721366, + "narHash": "sha256-dm93104HXjKWzkrr7yAPtxpbllOSzrwFFruc+rKQHSg=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "577ee84c69ba89894ac622d71a678a14d746b2f7", + "rev": "621131c9e281d1047bf8937547ed77e97c464aba", "type": "github" }, "original": { @@ -110,11 +112,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { From c6f4b225f1da1132b073ec7737259de2ec83606f Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 27 Apr 2025 23:30:24 +0200 Subject: [PATCH 114/141] device: magegee-mkstar61 (#102) https://github.com/carlossless/sinowealth-kb-tool/issues/101 --- README.md | 1 + src/part.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index e933a3d..473def9 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ sinowealth-kb-tool write \ | Hykker X Range 2017 (RE-K70-BYK800) | 13df4ce2933f9654ffef80d6a3c27199 | SH68F881 | BYK801 | ✅ | ❓ | | [Leobog Hi75](https://leobogtech.com/products/leobog-hi75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Machenike K500-B61](https://global.machenike.com/products/k500-b61) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90? | BYK916 | ✅ | ✅ | +| [MageGee MK-STAR61](https://www.magegee.com/products2.html?pid=1644595&_t=1737191677) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [NuPhy Air60](https://nuphy.com/products/air60) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [NuPhy Air75](https://nuphy.com/products/air75) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [NuPhy Air96](https://nuphy.com/products/air96-wireless-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | diff --git a/src/part.rs b/src/part.rs index 001490a..06a5e6a 100644 --- a/src/part.rs +++ b/src/part.rs @@ -229,6 +229,13 @@ pub const PART_MACHENIKE_K500_B61: Part = Part { ..PART_BASE_SH68F90 }; +pub const PART_MAGEGEE_MKSTAR61: Part = Part { + vendor_id: 0x258a, + product_id: 0x010c, + isp_index: 1, + ..PART_BASE_SH68F90 +}; + pub const PART_REDRAGON_K658_PRO_SE: Part = Part { vendor_id: 0x258a, product_id: 0x0049, @@ -285,6 +292,7 @@ pub static PARTS: Map<&'static str, Part> = phf_map! { "kzzi-k68pro" => PART_KZZI_K68PRO, "leobog-hi75" => PART_LEOBOG_HI75, "machenike-k500-b61" => PART_MACHENIKE_K500_B61, + "magegee-mkstar61" => PART_MAGEGEE_MKSTAR61, "nuphy-air60" => PART_NUPHY_AIR60, "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 "nuphy-air96" => PART_NUPHY_AIR60, // same as nuphy-air60 From 50f1eba6d6fc12bb35ea882c59934112c579d4c7 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 3 May 2025 09:22:37 +0200 Subject: [PATCH 115/141] feature: device selection by report id (#103) Enables correct device selection based on report-id found in the report descriptor. This is a feature I wanted to have in this tool from day one, but due to both hidapi's lack of get_report_descriptor and rust lacking a robust hid descriptor parsing library at the time, I haven't gotten around to doing it... until now! This will greatly simplify selecting the correct HID device on Windows and hopefully make it less error-prone for most users, though this still relies on knowing the correct interface number ahead of time. --- .github/workflows/push.yml | 2 + Cargo.lock | 22 ++ Cargo.toml | 4 +- rustfmt.toml | 2 - src/device_selector.rs | 411 +++++++++++++++++++++++++++++++ src/hid_tree.rs | 173 +++++++++++++ src/isp.rs | 490 ------------------------------------- src/isp_device.rs | 245 +++++++++++++++++++ src/main.rs | 113 ++++++--- src/part.rs | 32 +-- src/util.rs | 19 ++ tools/functional-test.sh | 20 +- 12 files changed, 966 insertions(+), 567 deletions(-) delete mode 100644 rustfmt.toml create mode 100644 src/device_selector.rs create mode 100644 src/hid_tree.rs delete mode 100644 src/isp.rs create mode 100644 src/isp_device.rs diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index a6f1964..41c9818 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -79,6 +79,8 @@ jobs: run: rustup target add $TARGET - name: Run build run: cargo build --release --verbose --target $TARGET + - name: Run integration tests + run: cargo run --release -- list - name: Compress run: | mkdir -p ./artifacts diff --git a/Cargo.lock b/Cargo.lock index cda546e..5a03872 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,6 +124,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "hidapi" version = "2.6.3" @@ -137,6 +143,11 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "hidparser" +version = "1.0.2" +source = "git+https://github.com/microsoft/mu_rust_hid.git?tag=v1.0.3#38e4bd85156e48f341f8d8110c257dfff6aee942" + [[package]] name = "ihex" version = "3.0.0" @@ -149,6 +160,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -287,7 +307,9 @@ dependencies = [ "clap", "clap-num", "hidapi", + "hidparser", "ihex", + "itertools", "log", "md5", "phf", diff --git a/Cargo.toml b/Cargo.toml index 58b93ab..f073319 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,14 @@ rust-version = "1.65" [dependencies] clap = "4.1" clap-num = "1.0" +hidparser = { git = "https://github.com/microsoft/mu_rust_hid.git", tag = "v1.0.3" } # needed until 1.0.3 is published on crates.io ihex = "3.0" +itertools = "0.14.0" md5 = "0.7" thiserror = "2.0" [dependencies.hidapi] -version = "2.4" +version = "2.6" default-features = false features = ["linux-static-libusb"] diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 455c820..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1,2 +0,0 @@ -imports_granularity = "Crate" -group_imports = "StdExternalCrate" diff --git a/src/device_selector.rs b/src/device_selector.rs new file mode 100644 index 0000000..3cbacc9 --- /dev/null +++ b/src/device_selector.rs @@ -0,0 +1,411 @@ +use core::time; +use std::{ffi::CStr, thread}; + +use hidapi::{BusType, DeviceInfo, HidDevice, HidError, MAX_REPORT_DESCRIPTOR_SIZE}; +use hidparser::parse_report_descriptor; +use itertools::Itertools; +use log::{debug, error, info}; +use thiserror::Error; + +use crate::{ + hid_tree::{DeviceNode, InterfaceNode, ItemNode}, + is_expected_error, ISPDevice, Part, +}; + +const REPORT_ID_ISP: u8 = 0x05; +const CMD_ISP_MODE: u8 = 0x75; + +#[cfg(target_os = "windows")] +const REPORT_ID_XFER: u8 = 0x06; + +const GAMING_KB_VENDOR_ID: u16 = 0x0603; +const GAMING_KB_PRODUCT_ID: u16 = 0x1020; +const GAMING_KB_V2_PRODUCT_ID: u16 = 0x1021; +const GAMING_KB_IFACE: i32 = 0; + +const COMMAND_LENGTH: usize = 6; + +#[derive(Debug, Error)] +pub enum DeviceSelectorError { + #[error("Device not found")] + NotFound, + #[error(transparent)] + HidError(#[from] HidError), + #[error("Failed to parse report descriptor {0:?}")] + ReportDescriptorError(hidparser::report_descriptor_parser::ReportDescriptorError), +} + +pub struct DeviceSelector { + api: hidapi::HidApi, +} + +impl DeviceSelector { + pub fn new() -> Result { + let api = hidapi::HidApi::new().map_err(DeviceSelectorError::from)?; + + #[cfg(target_os = "macos")] + api.set_open_exclusive(false); // macOS will throw a privilege violation error otherwise + + Ok(Self { api }) + } + + fn sorted_usb_device_list(&self) -> Vec<&DeviceInfo> { + let mut devices: Vec<_> = self + .api + .device_list() + .filter(|d| d.bus_type() as u32 == BusType::Usb as u32) + .collect(); + // TODO: move out the platform specific sorting + devices.sort_by_key(|d| { + #[cfg(not(target_os = "linux"))] + return ( + d.vendor_id(), + d.product_id(), + d.path(), + d.interface_number(), + d.usage_page(), + d.usage(), + ); + #[cfg(target_os = "linux")] + return ( + d.vendor_id(), + d.product_id(), + d.path(), + d.interface_number(), + ); + }); + devices + } + + fn get_feature_report_ids_from_path( + &self, + path: &CStr, + ) -> Result, DeviceSelectorError> { + let dev = self + .api + .open_path(path) + .map_err(DeviceSelectorError::from)?; + self.get_feature_report_ids_from_device(&dev) + } + + fn get_feature_report_ids_from_device( + &self, + dev: &HidDevice, + ) -> Result, DeviceSelectorError> { + let mut buf: [u8; MAX_REPORT_DESCRIPTOR_SIZE] = [0; MAX_REPORT_DESCRIPTOR_SIZE]; + let size: usize = dev + .get_report_descriptor(&mut buf) + .map_err(DeviceSelectorError::from)?; + let descriptor = buf[..size].to_vec(); + self.get_feature_report_ids_from_descriptor(&descriptor) + } + + fn get_feature_report_ids_from_descriptor( + &self, + descriptor: &[u8], + ) -> Result, DeviceSelectorError> { + let report_descriptor = parse_report_descriptor(descriptor) + .map_err(DeviceSelectorError::ReportDescriptorError)?; + let res = report_descriptor + .features + .iter() + .filter_map(|item| item.report_id) + .map(|report_id| report_id.into()) + .collect(); + Ok(res) + } + + fn get_report_descriptor(&self, dev: &HidDevice) -> Result, DeviceSelectorError> { + let mut buf: [u8; MAX_REPORT_DESCRIPTOR_SIZE] = [0; MAX_REPORT_DESCRIPTOR_SIZE]; + let size: usize = dev + .get_report_descriptor(&mut buf) + .map_err(DeviceSelectorError::from)?; + Ok(buf[..size].to_vec()) + } + + fn get_descriptor_with_features( + &self, + path: &CStr, + ) -> ( + Result, DeviceSelectorError>, + Result, DeviceSelectorError>, + ) { + let descriptor: Result, DeviceSelectorError>; + let feature_report_ids: Result, DeviceSelectorError>; + match self.api.open_path(path) { + Ok(ref dev) => { + descriptor = self.get_report_descriptor(dev); + match descriptor { + Ok(ref report) => { + feature_report_ids = self.get_feature_report_ids_from_descriptor(report); + } + Err(_) => { + feature_report_ids = Err(DeviceSelectorError::NotFound); + } + } + } + Err(err) => { + descriptor = Err(DeviceSelectorError::from(err)); + feature_report_ids = Err(DeviceSelectorError::NotFound); + } + } + (descriptor, feature_report_ids) + } + + fn get_device_for_report_id<'a, I: IntoIterator>( + &self, + devices: I, + report_id: u32, + ) -> Result<&'a DeviceInfo, DeviceSelectorError> { + for d in devices { + let ids = self.get_feature_report_ids_from_path(d.path())?; + for id in ids { + if id == report_id { + return Ok(d); + } + } + } + Err(DeviceSelectorError::NotFound) + } + + fn open_isp_devices(&self, part: Part) -> Result { + let sorted_devices = self.sorted_usb_device_list(); + let isp_devices: Vec<_> = sorted_devices + .clone() + .into_iter() + .filter(|d| { + d.vendor_id() == GAMING_KB_VENDOR_ID + && matches!( + d.product_id(), + GAMING_KB_PRODUCT_ID | GAMING_KB_V2_PRODUCT_ID + ) + && d.interface_number() == GAMING_KB_IFACE + }) + .collect(); + + let device_count = isp_devices.len(); + if device_count == 0 { + return Err(DeviceSelectorError::NotFound); + } + + let s = isp_devices.clone(); + // TODO: check for both feature report IDs in macOS and Linux and get that device + // TODO: check for each feature report IDs in Windows and get each of those devices + let cmd_device = self.get_device_for_report_id(s, REPORT_ID_ISP as u32)?; + debug!("CMD device: {}", cmd_device.info()); + #[cfg(not(target_os = "windows"))] + return Ok(ISPDevice::new( + part, + self.api.open_path(cmd_device.path()).unwrap(), + )); + + #[cfg(target_os = "windows")] + { + let xfer_device = + self.get_device_for_report_id(isp_devices.clone(), REPORT_ID_XFER as u32)?; + debug!("XFER device: {}", xfer_device.info()); + return Ok(ISPDevice::new( + part, + self.api.open_path(cmd_device.path()).unwrap(), + self.api.open_path(xfer_device.path()).unwrap(), + )); + } + } + + fn switch_kb_device(&mut self, part: Part) -> Result { + info!( + "Looking for vId:{:#06x} pId:{:#06x}", + part.vendor_id, part.product_id + ); + + let filtered_devices = self.sorted_usb_device_list().into_iter().filter(|d| { + d.vendor_id() == part.vendor_id + && d.product_id() == part.product_id + && d.interface_number() == part.isp_iface_num + }); + + let mut cmd_device_info: Option<&DeviceInfo> = None; + for d in filtered_devices { + let ids = self + .get_feature_report_ids_from_path(d.path()) + .map_err(|_| DeviceSelectorError::NotFound)?; + for id in ids { + if id == part.isp_report_id { + cmd_device_info = Some(d); + } + } + } + + let Some(cmd_device_info) = cmd_device_info else { + info!("Regular device didn't come up..."); + return Err(DeviceSelectorError::NotFound); + }; + + debug!("Opening: {:?}", cmd_device_info.path()); + let device = self + .api + .open_path(cmd_device_info.path()) + .map_err(DeviceSelectorError::from)?; + + info!("Found regular device. Entering ISP mode..."); + if let Err(err) = self.enter_isp_mode(&device) { + debug!("Error: {:}", err); + match err { + DeviceSelectorError::HidError(err) if is_expected_error(&err) => {} + _ => { + error!("Unexpected: {:}", err); + info!("Waiting..."); + thread::sleep(time::Duration::from_secs(2)); + return Err(err); + } + } + } + + info!("Waiting for ISP device..."); + thread::sleep(time::Duration::from_secs(2)); + + self.api.refresh_devices()?; + + let Ok(isp_device) = self.open_isp_devices(part) else { + info!("ISP device didn't come up..."); + return Err(DeviceSelectorError::NotFound); + }; + Ok(isp_device) + } + + pub fn find_isp_device( + &mut self, + part: Part, + retries: usize, + ) -> Result { + for attempt in 1..retries + 1 { + self.api.refresh_devices()?; + if attempt > 1 { + thread::sleep(time::Duration::from_millis(500)); + info!("Retrying... Attempt {}/{}", attempt, retries); + } + + if let Ok(devices) = self.switch_kb_device(part) { + info!("Connected!"); + return Ok(devices); + } + info!("Regular device not found. Trying ISP device..."); + if let Ok(devices) = self.open_isp_devices(part) { + info!("Connected!"); + return Ok(devices); + } + } + Err(DeviceSelectorError::NotFound) + } + + fn enter_isp_mode(&self, handle: &HidDevice) -> Result<(), DeviceSelectorError> { + let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_ISP, CMD_ISP_MODE, 0x00, 0x00, 0x00, 0x00]; + handle.send_feature_report(&cmd)?; + Ok(()) + } + + pub fn connected_devices_tree(&self) -> Result, DeviceSelectorError> { + let devices: Vec<_> = self.sorted_usb_device_list(); + + let id_chunks = devices.iter().chunk_by(|d| { + return ( + d.vendor_id(), + d.product_id(), + d.manufacturer_string().unwrap_or("None"), + d.product_string().unwrap_or("None"), + ); + }); + + let mut device_tree_devices: Vec = vec![]; + + for ((vid, pid, manufacturer, product), devices) in &id_chunks { + let mut node = DeviceNode { + product_id: pid, + vendor_id: vid, + product_string: product.to_string(), + manufacturer_string: manufacturer.to_string(), + children: vec![], + }; + + let path_chunks = devices.chunk_by(|d| { + #[cfg(any(target_os = "macos", target_os = "linux"))] + return (d.path(), d.interface_number()); + #[cfg(target_os = "windows")] + return (d.path(), d.interface_number(), d.usage_page(), d.usage()); + }); + + for (key, devices) in &path_chunks { + #[cfg(any(target_os = "macos", target_os = "linux"))] + let (path, interface_number) = key; + #[cfg(target_os = "windows")] + let (path, interface_number, usage_page, usage) = key; + + let mut children: Vec = vec![]; + + for d in devices { + #[cfg(target_os = "macos")] + children.push(ItemNode { + usage_page: d.usage_page(), + usage: d.usage(), + }); + #[cfg(target_os = "windows")] + { + let (descriptor, feature_report_ids) = + self.get_descriptor_with_features(path); + children.push(ItemNode { + path: path.to_str().unwrap().to_string(), + usage_page: d.usage_page(), + usage: d.usage(), + descriptor, + feature_report_ids, + }); + } + } + + let (descriptor, feature_report_ids) = self.get_descriptor_with_features(path); + let interface_node = InterfaceNode { + #[cfg(any(target_os = "macos", target_os = "linux"))] + path: path.to_str().unwrap().to_string(), + interface_number, + #[cfg(any(target_os = "macos", target_os = "linux"))] + descriptor, + #[cfg(any(target_os = "macos", target_os = "linux"))] + feature_report_ids, + #[cfg(any(target_os = "macos", target_os = "windows"))] + children, + }; + + node.children.push(interface_node); + } + + device_tree_devices.push(node); + } + Ok(device_tree_devices) + } +} + +trait PlatformSpecificInfo { + fn info(&self) -> String; +} + +impl PlatformSpecificInfo for DeviceInfo { + fn info(&self) -> String { + #[cfg(not(target_os = "linux"))] + return format!( + "Found ISP Device: {:#06x} {:#06x} {:?} {} {:#06x} {:#06x}", + self.vendor_id(), + self.product_id(), + self.path(), + self.interface_number(), + self.usage_page(), + self.usage() + ); + #[cfg(target_os = "linux")] + format!( + "Found ISP Device: {:#06x} {:#06x} {:?}", + self.vendor_id(), + self.product_id(), + self.path() + ) + } +} diff --git a/src/hid_tree.rs b/src/hid_tree.rs new file mode 100644 index 0000000..33ecc83 --- /dev/null +++ b/src/hid_tree.rs @@ -0,0 +1,173 @@ +use crate::{device_selector::DeviceSelectorError, to_hex_string}; + +pub struct DeviceNode { + pub product_id: u16, + pub vendor_id: u16, + pub product_string: String, + pub manufacturer_string: String, + pub children: Vec, +} + +pub struct InterfaceNode { + pub interface_number: i32, + #[cfg(any(target_os = "macos", target_os = "linux"))] + pub path: String, + #[cfg(any(target_os = "macos", target_os = "linux"))] + pub descriptor: Result, DeviceSelectorError>, + #[cfg(any(target_os = "macos", target_os = "linux"))] + pub feature_report_ids: Result, DeviceSelectorError>, + #[cfg(any(target_os = "macos", target_os = "windows"))] + pub children: Vec, +} + +pub struct ItemNode { + #[cfg(target_os = "windows")] + pub path: String, + #[cfg(any(target_os = "macos", target_os = "windows"))] + pub usage_page: u16, + #[cfg(any(target_os = "macos", target_os = "windows"))] + pub usage: u16, + #[cfg(target_os = "windows")] + pub descriptor: Result, DeviceSelectorError>, + #[cfg(target_os = "windows")] + pub feature_report_ids: Result, DeviceSelectorError>, +} + +const INDENT_SIZE: usize = 4; + +pub trait TreeDisplay { + fn to_tree_string(self, level: usize) -> String; +} + +impl TreeDisplay for T +where + T: Iterator, + I: TreeDisplay, +{ + fn to_tree_string(self, level: usize) -> String { + let indent = " ".repeat(INDENT_SIZE).repeat(level); + let mut s: Vec = vec![]; + for item in self { + s.push(format!("{}{}", indent, item.to_tree_string(level))); + } + s.join("\n") + } +} + +impl TreeDisplay for DeviceNode { + fn to_tree_string(self, level: usize) -> String { + let indent = " ".repeat(INDENT_SIZE).repeat(level); + let mut s: Vec = vec![]; + s.push(format!( + "{indent}ID {:04x}:{:04x} manufacturer=\"{:}\" product=\"{:}\"", + self.vendor_id, self.product_id, self.manufacturer_string, self.product_string + )); + for child in self.children { + s.push(child.to_tree_string(level + 1)); + } + s.join("\n") + } +} + +impl TreeDisplay for InterfaceNode { + fn to_tree_string(self, level: usize) -> String { + let indent = " ".repeat(INDENT_SIZE).repeat(level); + let mut s: Vec = vec![]; + #[cfg(any(target_os = "macos", target_os = "linux"))] + s.push(format!( + "{indent}path=\"{}\" interface_number={}", + self.path, self.interface_number + )); + #[cfg(target_os = "windows")] + s.push(format!( + "{indent}interface_number={}", + self.interface_number + )); + #[cfg(any(target_os = "macos", target_os = "linux"))] + { + let descriptor = self.descriptor.as_ref(); + match descriptor { + Ok(descriptor) => { + s.push(format!( + "{indent}report_descriptor=[{}]", + to_hex_string(descriptor) + )); + } + Err(e) => { + s.push(format!("{indent}report_descriptor=error: {}", e)); + } + } + let feature_report_ids = self.feature_report_ids.as_ref(); + match feature_report_ids { + Ok(feature_report_ids) => { + s.push(format!( + "{indent}feature_report_ids=[{}]", + feature_report_ids + .iter() + .map(|rid| format!("{:#04x}", rid)) + .collect::>() + .join(", ") + )); + } + Err(e) => { + s.push(format!("{indent}feature_report_ids=error: {}", e)); + } + } + } + #[cfg(any(target_os = "macos", target_os = "windows"))] + { + for child in self.children { + s.push(child.to_tree_string(level + 1)); + } + } + s.join("\n") + } +} + +impl TreeDisplay for ItemNode { + fn to_tree_string(self, level: usize) -> String { + let indent = " ".repeat(INDENT_SIZE).repeat(level); + let mut s: Vec = vec![]; + #[cfg(target_os = "macos")] + s.push(format!( + "{indent}usage_page={:#06x} usage={:#06x}", + self.usage_page, self.usage + )); + #[cfg(target_os = "windows")] + { + s.push(format!( + "{indent}path=\"{}\" usage_page={:#06x} usage={:#06x}", + self.path, self.usage_page, self.usage + )); + let descriptor = self.descriptor.as_ref(); + match descriptor { + Ok(descriptor) => { + s.push(format!( + "{indent}report_descriptor=[{}]", + to_hex_string(descriptor) + )); + } + Err(e) => { + s.push(format!("{indent}report_descriptor=error: {}", e)); + } + } + let feature_report_ids = self.feature_report_ids.as_ref(); + match feature_report_ids { + Ok(feature_report_ids) => { + s.push(format!( + "{indent}feature_report_ids=[{}]", + feature_report_ids + .iter() + .map(|rid| format!("{:#04x}", rid)) + .collect::>() + .join(", ") + )); + } + Err(e) => { + s.push(format!("{indent}feature_report_ids=error: {}", e)); + } + } + } + s.join("\n") + } +} diff --git a/src/isp.rs b/src/isp.rs deleted file mode 100644 index 6131cb0..0000000 --- a/src/isp.rs +++ /dev/null @@ -1,490 +0,0 @@ -use std::{thread, time}; - -use log::{debug, info}; -use thiserror::Error; - -use crate::{part::*, util, VerificationError}; - -extern crate hidapi; - -use hidapi::{DeviceInfo, HidApi, HidDevice, HidError}; - -const MAX_RETRIES: usize = 10; - -const GAMING_KB_VENDOR_ID: u16 = 0x0603; -const GAMING_KB_PRODUCT_ID: u16 = 0x1020; -const GAMING_KB_V2_PRODUCT_ID: u16 = 0x1021; - -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; -const REPORT_ID_XFER: u8 = 0x06; - -const CMD_ISP_MODE: u8 = 0x75; -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; - -pub struct ISPDevice { - request_device: HidDevice, - #[cfg(target_os = "windows")] - data_device: HidDevice, - part: Part, -} - -#[derive(Debug, Error)] -pub enum ISPError { - #[error("Unusual number of matching HID devices: {0}")] - IrregularDeviceCount(usize), - #[error("Device not found")] - NotFound, - #[error(transparent)] - HidError(#[from] HidError), - #[error(transparent)] - VerificationError(#[from] VerificationError), -} - -#[derive(Debug, Clone)] -pub enum ReadType { - Normal, - Bootloader, - Full, -} - -struct HIDDevices { - request: HidDevice, - #[cfg(target_os = "windows")] - data: HidDevice, -} - -pub trait HidApiExtension { - fn sorted_device_list(&self) -> Vec<&DeviceInfo>; -} - -impl HidApiExtension for HidApi { - fn sorted_device_list(self: &HidApi) -> Vec<&DeviceInfo> { - let mut devices: Vec<_> = self.device_list().collect(); - devices.sort_by_key(|d| d.path()); - devices - } -} - -impl ISPDevice { - pub fn new(part: Part) -> Result { - let devices = Self::find_isp_device(part)?; - Ok(Self { - request_device: devices.request, - #[cfg(target_os = "windows")] - data_device: devices.data, - part, - }) - } - - fn hidapi() -> HidApi { - let api = HidApi::new().unwrap(); - - #[cfg(target_os = "macos")] - api.set_open_exclusive(false); // macOS will throw a privilege violation error otherwise - - api - } - - fn open_isp_devices() -> Result { - let api = Self::hidapi(); - let sorted_devices: Vec<_> = api.sorted_device_list(); - let isp_devices: Vec<_> = sorted_devices - .into_iter() - .filter(|d| { - #[cfg(not(target_os = "linux"))] - return d.vendor_id() == GAMING_KB_VENDOR_ID - && matches!( - d.product_id(), - GAMING_KB_PRODUCT_ID | GAMING_KB_V2_PRODUCT_ID - ) - && d.interface_number() == 0 - && d.usage_page() == HID_ISP_USAGE_PAGE - && d.usage() == HID_ISP_USAGE; - #[cfg(target_os = "linux")] - return d.vendor_id() == GAMING_KB_VENDOR_ID - && matches!( - d.product_id(), - GAMING_KB_PRODUCT_ID | GAMING_KB_V2_PRODUCT_ID - ) - && d.interface_number() == 0; - }) - .collect(); - - for d in &isp_devices { - #[cfg(not(target_os = "linux"))] - debug!( - "Found ISP Device: {:#06x} {:#06x} {:?} {:#06x} {:#06x}", - d.vendor_id(), - d.product_id(), - d.path(), - d.usage_page(), - d.usage() - ); - #[cfg(target_os = "linux")] - debug!( - "Found ISP Device: {:#06x} {:#06x} {:?}", - d.vendor_id(), - d.product_id(), - d.path() - ); - } - - let device_count = isp_devices.len(); - if device_count == 0 { - return Err(ISPError::NotFound); - } - - #[cfg(not(target_os = "windows"))] - if device_count == 1 { - let request_device = isp_devices.first().unwrap(); - debug!("Request device: {:?}", request_device.path()); - return Ok(HIDDevices { - request: api.open_path(request_device.path()).unwrap(), - }); - } else { - return Err(ISPError::IrregularDeviceCount(device_count)); - } - - #[cfg(target_os = "windows")] - if device_count == 1 { - return Err(ISPError::IrregularDeviceCount(device_count)); - } else if device_count == 2 { - let request_device = isp_devices[0]; - let data_device = isp_devices[1]; - debug!("Request device: {:?}", request_device.path()); - debug!("Data device: {:?}", data_device.path()); - return Ok(HIDDevices { - request: api.open_path(request_device.path()).unwrap(), - data: api.open_path(data_device.path()).unwrap(), - }); - } else { - return Err(ISPError::IrregularDeviceCount(device_count)); - } - } - - fn switch_kb_device(part: Part) -> Result { - let api = Self::hidapi(); - - info!( - "Looking for vId:{:#06x} pId:{:#06x}", - part.vendor_id, part.product_id - ); - - let sorted_devices: Vec<_> = api.sorted_device_list(); - let request_device_info = sorted_devices - .into_iter() - .filter(|d| { - #[cfg(not(target_os = "linux"))] - return d.vendor_id() == part.vendor_id - && d.product_id() == part.product_id - && d.interface_number() == part.isp_iface_num as i32 - && d.usage_page() == part.isp_usage_page - && d.usage() == part.isp_usage; - #[cfg(target_os = "linux")] - return d.vendor_id() == part.vendor_id - && d.product_id() == part.product_id - && d.interface_number() == part.isp_iface_num as i32; - }) - .enumerate() - .find_map(|(_i, d)| { - #[cfg(not(target_os = "linux"))] - debug!( - "Found Device: {:?} {:#06x} {:#06x}", - d.path(), - d.usage_page(), - d.usage() - ); - #[cfg(target_os = "linux")] - debug!("Found Device: {:?}", d.path()); - #[cfg(target_os = "windows")] - if _i == part.isp_index { - return Some(d); - } else { - return None; - } - #[cfg(not(target_os = "windows"))] - Some(d) - }); - - let Some(request_device_info) = request_device_info else { - info!("Regular device didn't come up..."); - return Err(ISPError::NotFound); - }; - - debug!("Opening: {:?}", request_device_info.path()); - - let device = api.open_path(request_device_info.path()).unwrap(); - - info!("Found regular device. Entering ISP mode..."); - if let Err(err) = Self::enter_isp_mode(&device) { - debug!("Error: {:}", err); - return Err(err); - } - - info!("Waiting for ISP device..."); - thread::sleep(time::Duration::from_secs(2)); - - let Ok(isp_device) = Self::open_isp_devices() else { - info!("ISP device didn't come up..."); - return Err(ISPError::NotFound); - }; - - Ok(isp_device) - } - - fn find_isp_device(part: Part) -> Result { - Self::find_isp_device_retry(part, MAX_RETRIES) - } - - fn find_isp_device_retry(part: Part, retries: usize) -> Result { - for attempt in 1..retries + 1 { - if attempt > 1 { - thread::sleep(time::Duration::from_millis(500)); - info!("Retrying... Attempt {}/{}", attempt, retries); - } - - if let Ok(devices) = Self::switch_kb_device(part) { - info!("Connected!"); - return Ok(devices); - } - info!("Regular device not found. Trying ISP device..."); - if let Ok(devices) = Self::open_isp_devices() { - info!("Connected!"); - return Ok(devices); - } - } - Err(ISPError::NotFound) - } - - fn enter_isp_mode(handle: &HidDevice) -> Result<(), ISPError> { - let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_ISP_MODE, 0x00, 0x00, 0x00, 0x00]; - handle.send_feature_report(&cmd)?; - Ok(()) - } - - /// Prints out all connected HID devices and their paths. - pub fn print_connected_devices() -> Result<(), ISPError> { - info!("Listing all connected HID devices..."); - let api = Self::hidapi(); - let devices: Vec<_> = api.sorted_device_list(); - - 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(()) - } - - pub fn read_cycle(&self, read_type: ReadType) -> Result, ISPError> { - self.enable_firmware()?; - - let firmware = match read_type { - ReadType::Normal => self.read(0, self.part.firmware_size)?, - ReadType::Bootloader => { - self.read(self.part.firmware_size, self.part.bootloader_size)? - } - ReadType::Full => self.read(0, self.part.firmware_size + self.part.bootloader_size)?, - }; - - if self.part.reboot { - self.reboot()?; - } - - Ok(firmware) - } - - pub fn write_cycle(&self, firmware: &mut [u8]) -> Result<(), ISPError> { - // ensure that the address at is the same as the reset vector - firmware.copy_within(1..3, self.part.firmware_size - 4); - - self.erase()?; - self.write(0, firmware)?; - - // cleanup the address at - firmware[self.part.firmware_size - 4..self.part.firmware_size - 2].fill(0); - - let read_back = self.read(0, self.part.firmware_size)?; - - info!("Verifying..."); - util::verify(firmware, &read_back).map_err(ISPError::from)?; - - self.enable_firmware()?; - - if self.part.reboot { - self.reboot()?; - } - - Ok(()) - } - - fn data_device(&self) -> &HidDevice { - #[cfg(target_os = "windows")] - return &self.data_device; - #[cfg(not(target_os = "windows"))] - &self.request_device - } - - fn read(&self, start_addr: usize, length: usize) -> Result, ISPError> { - info!("Reading..."); - self.init_read(start_addr)?; - - let page_size = self.part.page_size; - let num_page = length / page_size; - let mut result: Vec = vec![]; - for i in 0..num_page { - debug!( - "Reading page {} @ offset {:#06x}", - i, - start_addr + i * page_size - ); - self.read_page(&mut result)?; - } - Ok(result) - } - - fn write(&self, start_addr: usize, buffer: &[u8]) -> Result<(), ISPError> { - info!("Writing..."); - self.init_write(start_addr)?; - - let page_size = self.part.page_size; - for i in 0..self.part.num_pages() { - debug!("Writing page {} @ offset {:#06x}", i, i * page_size); - self.write_page(&buffer[(i * page_size)..((i + 1) * page_size)])?; - } - Ok(()) - } - - /// Initializes the read operation / sets the initial read address - fn init_read(&self, start_addr: usize) -> Result<(), ISPError> { - let cmd: [u8; COMMAND_LENGTH] = [ - REPORT_ID_CMD, - CMD_INIT_READ, - (start_addr & 0xff) as u8, - (start_addr >> 8) as u8, - 0, - 0, - ]; - self.request_device - .send_feature_report(&cmd) - .map_err(ISPError::from)?; - Ok(()) - } - - /// Initializes the write operation / sets the initial write address - fn init_write(&self, start_addr: usize) -> Result<(), ISPError> { - let cmd: [u8; COMMAND_LENGTH] = [ - REPORT_ID_CMD, - CMD_INIT_WRITE, - (start_addr & 0xff) as u8, - (start_addr >> 8) as u8, - 0, - 0, - ]; - self.request_device - .send_feature_report(&cmd) - .map_err(ISPError::from)?; - Ok(()) - } - - /// Reads one page of flash contents - fn read_page(&self, buf: &mut Vec) -> Result<(), ISPError> { - let page_size = self.part.page_size; - let mut xfer_buf: Vec = vec![0; page_size + 2]; - xfer_buf[0] = REPORT_ID_XFER; - xfer_buf[1] = XFER_READ_PAGE; - self.data_device() - .get_feature_report(&mut xfer_buf) - .map_err(ISPError::from)?; - buf.extend_from_slice(&xfer_buf[2..(page_size + 2)]); - Ok(()) - } - - /// Writes one page to flash - /// - /// Note: The first 3 bytes at address 0x0000 (first-page) are skipped. Instead the second and - /// third bytes (firmware's reset vector LJMP destination address) are written to address - /// and will later be part of the LJMP instruction after the firmware is - /// enabled (`enable_firmware`). This only works once after an erase operation. - fn write_page(&self, buf: &[u8]) -> Result<(), ISPError> { - let length = buf.len() + 2; - let mut xfer_buf: Vec = vec![0; length]; - xfer_buf[0] = REPORT_ID_XFER; - xfer_buf[1] = XFER_WRITE_PAGE; - xfer_buf[2..length].clone_from_slice(buf); - self.data_device() - .send_feature_report(&xfer_buf) - .map_err(ISPError::from)?; - Ok(()) - } - - /// Sets a LJMP (0x02) opcode at . - /// This enables the main firmware by making the bootloader jump to it on reset. - /// - /// Side-effect: enables reading the firmware without erasing flash first. - /// Credits to @gashtaan for finding this out. - fn enable_firmware(&self) -> Result<(), ISPError> { - info!("Enabling firmware..."); - let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_ENABLE_FIRMWARE, 0, 0, 0, 0]; - - self.request_device.send_feature_report(&cmd)?; - Ok(()) - } - - /// Erases everything in flash, except the ISP bootloader section itself and initializes the - /// reset vector to jump to ISP. - fn erase(&self) -> Result<(), ISPError> { - info!("Erasing..."); - let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_ERASE, 0, 0, 0, 0]; - self.request_device - .send_feature_report(&cmd) - .map_err(ISPError::from)?; - thread::sleep(time::Duration::from_millis(2000)); - Ok(()) - } - - /// Causes the device to start running the main firmware - fn reboot(&self) -> Result<(), ISPError> { - info!("Rebooting..."); - let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_REBOOT, 0, 0, 0, 0]; - if let Err(err) = self.request_device.send_feature_report(&cmd) { - // only log failures - debug!("Error: {:}", err); - } - thread::sleep(time::Duration::from_millis(2000)); - Ok(()) - } -} diff --git a/src/isp_device.rs b/src/isp_device.rs new file mode 100644 index 0000000..2df6a78 --- /dev/null +++ b/src/isp_device.rs @@ -0,0 +1,245 @@ +use std::{thread, time}; + +use log::{debug, error, info}; +use thiserror::Error; + +use crate::{is_expected_error, part::*, util, VerificationError}; + +extern crate hidapi; + +use hidapi::{HidDevice, HidError}; + +const COMMAND_LENGTH: usize = 6; + +const REPORT_ID_CMD: u8 = 0x05; +const REPORT_ID_XFER: u8 = 0x06; + +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; + +pub struct ISPDevice { + cmd_device: HidDevice, + #[cfg(target_os = "windows")] + xfer_device: HidDevice, + part: Part, +} + +#[derive(Debug, Error)] +pub enum ISPError { + #[error(transparent)] + HidError(#[from] HidError), + #[error(transparent)] + VerificationError(#[from] VerificationError), +} + +#[derive(Debug, Clone)] +pub enum ReadFragment { + Firmware, + Bootloader, + Full, +} + +impl ISPDevice { + #[cfg(not(target_os = "windows"))] + pub fn new(part: Part, device: HidDevice) -> Self { + Self { + cmd_device: device, + part, + } + } + + #[cfg(target_os = "windows")] + pub fn new(part: Part, cmd_device: HidDevice, xfer_device: HidDevice) -> Self { + Self { + cmd_device, + xfer_device, + part, + } + } + + pub fn read_cycle(&self, read_fragment: ReadFragment) -> Result, ISPError> { + self.enable_firmware()?; + + let (start_addr, length) = match read_fragment { + ReadFragment::Firmware => (0, self.part.firmware_size), + ReadFragment::Bootloader => (self.part.firmware_size, self.part.bootloader_size), + ReadFragment::Full => (0, self.part.firmware_size + self.part.bootloader_size), + }; + + let firmware = self.read(start_addr, length)?; + + if self.part.reboot { + self.reboot(); + } + + Ok(firmware) + } + + pub fn write_cycle(&self, firmware: &mut [u8]) -> Result<(), ISPError> { + // ensure that the address at is the same as the reset vector + firmware.copy_within(1..3, self.part.firmware_size - 4); + + self.erase()?; + self.write(0, firmware)?; + + // cleanup the address at + firmware[self.part.firmware_size - 4..self.part.firmware_size - 2].fill(0); + + let read_back = self.read(0, self.part.firmware_size)?; + + info!("Verifying..."); + util::verify(firmware, &read_back).map_err(ISPError::from)?; + + self.enable_firmware()?; + + if self.part.reboot { + self.reboot(); + } + + Ok(()) + } + + fn xfer_device(&self) -> &HidDevice { + #[cfg(target_os = "windows")] + return &self.xfer_device; + #[cfg(not(target_os = "windows"))] + &self.cmd_device + } + + fn read(&self, start_addr: usize, length: usize) -> Result, ISPError> { + info!("Reading..."); + self.init_read(start_addr)?; + + let page_size = self.part.page_size; + let num_page = length / page_size; + let mut result: Vec = vec![]; + for i in 0..num_page { + debug!( + "Reading page {} @ offset {:#06x}", + i, + start_addr + i * page_size + ); + self.read_page(&mut result)?; + } + Ok(result) + } + + fn write(&self, start_addr: usize, buffer: &[u8]) -> Result<(), ISPError> { + info!("Writing..."); + self.init_write(start_addr)?; + + let page_size = self.part.page_size; + for i in 0..self.part.num_pages() { + debug!("Writing page {} @ offset {:#06x}", i, i * page_size); + self.write_page(&buffer[(i * page_size)..((i + 1) * page_size)])?; + } + Ok(()) + } + + /// Initializes the read operation / sets the initial read address + fn init_read(&self, start_addr: usize) -> Result<(), ISPError> { + let cmd: [u8; COMMAND_LENGTH] = [ + REPORT_ID_CMD, + CMD_INIT_READ, + (start_addr & 0xff) as u8, + (start_addr >> 8) as u8, + 0, + 0, + ]; + self.cmd_device + .send_feature_report(&cmd) + .map_err(ISPError::from)?; + Ok(()) + } + + /// Initializes the write operation / sets the initial write address + fn init_write(&self, start_addr: usize) -> Result<(), ISPError> { + let cmd: [u8; COMMAND_LENGTH] = [ + REPORT_ID_CMD, + CMD_INIT_WRITE, + (start_addr & 0xff) as u8, + (start_addr >> 8) as u8, + 0, + 0, + ]; + self.cmd_device + .send_feature_report(&cmd) + .map_err(ISPError::from)?; + Ok(()) + } + + /// Reads one page of flash contents + fn read_page(&self, buf: &mut Vec) -> Result<(), ISPError> { + let page_size = self.part.page_size; + let mut xfer_buf: Vec = vec![0; page_size + 2]; + xfer_buf[0] = REPORT_ID_XFER; + xfer_buf[1] = XFER_READ_PAGE; + self.xfer_device() + .get_feature_report(&mut xfer_buf) + .map_err(ISPError::from)?; + buf.extend_from_slice(&xfer_buf[2..(page_size + 2)]); + Ok(()) + } + + /// Writes one page to flash + /// + /// Note: The first 3 bytes at address 0x0000 (first-page) are skipped. Instead the second and + /// third bytes (firmware's reset vector LJMP destination address) are written to address + /// and will later be part of the LJMP instruction after the firmware is + /// enabled (`enable_firmware`). This only works once after an erase operation. + fn write_page(&self, buf: &[u8]) -> Result<(), ISPError> { + let length = buf.len() + 2; + let mut xfer_buf: Vec = vec![0; length]; + xfer_buf[0] = REPORT_ID_XFER; + xfer_buf[1] = XFER_WRITE_PAGE; + xfer_buf[2..length].clone_from_slice(buf); + self.xfer_device() + .send_feature_report(&xfer_buf) + .map_err(ISPError::from)?; + Ok(()) + } + + /// Sets a LJMP (0x02) opcode at . + /// This enables the main firmware by making the bootloader jump to it on reset. + /// + /// Side-effect: enables reading the firmware without erasing flash first. + /// Credits to @gashtaan for finding this out. + fn enable_firmware(&self) -> Result<(), ISPError> { + info!("Enabling firmware..."); + let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_ENABLE_FIRMWARE, 0, 0, 0, 0]; + + self.cmd_device.send_feature_report(&cmd)?; + Ok(()) + } + + /// Erases everything in flash, except the ISP bootloader section itself and initializes the + /// reset vector to jump to ISP. + fn erase(&self) -> Result<(), ISPError> { + info!("Erasing..."); + let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_ERASE, 0, 0, 0, 0]; + self.cmd_device + .send_feature_report(&cmd) + .map_err(ISPError::from)?; + thread::sleep(time::Duration::from_millis(2000)); + Ok(()) + } + + /// Causes the device to start running the main firmware + fn reboot(&self) { + info!("Rebooting..."); + let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_REBOOT, 0, 0, 0, 0]; + if let Err(err) = self.cmd_device.send_feature_report(&cmd) { + debug!("Error: {:}", err); + if !is_expected_error(&err) { + error!("Unexpected error: {:}", err); + } + } + thread::sleep(time::Duration::from_millis(2000)); + } +} diff --git a/src/main.rs b/src/main.rs index 7eb7560..673e596 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,17 +6,20 @@ use std::{ use clap::{arg, value_parser, ArgMatches, Command}; use clap_num::maybe_hex; +use device_selector::{DeviceSelector, DeviceSelectorError}; +use hid_tree::TreeDisplay; use log::{error, info}; use simple_logger::SimpleLogger; use thiserror::Error; -mod isp; -mod part; -// mod hid; +mod device_selector; +mod hid_tree; mod ihex; +mod isp_device; +mod part; mod util; -pub use crate::{ihex::*, isp::*, part::*, util::*}; +pub use crate::{ihex::*, isp_device::*, part::*, util::*}; #[derive(Debug, Error)] pub enum CLIError { @@ -28,6 +31,8 @@ pub enum CLIError { IHEXError(#[from] ConversionError), #[error(transparent)] PayloadConversionError(#[from] PayloadConversionError), + #[error(transparent)] + DeviceSelectorError(#[from] DeviceSelectorError), } fn main() -> ExitCode { @@ -51,33 +56,33 @@ fn cli() -> Command { 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 device.") + .arg(arg!(--vendor_id ).value_parser(maybe_hex::)) + .arg(arg!(--product_id ).value_parser(maybe_hex::)) ) .subcommand( Command::new("convert") .short_flag('c') .about("Convert payload from bootloader to JTAG and vice versa.") .arg(arg!(-d --direction "direction of conversion").value_parser(["to_jtag", "to_isp"]).required(true)) - .part_args() .arg(arg!(input_file: "file to convert")) .arg(arg!(output_file: "file to write results to")) + .part_args() ) .subcommand( Command::new("read") .short_flag('r') .about("Read flash contents. (Intel HEX)") .arg(arg!(output_file: "file to write flash contents to")) + .arg(arg!(-f --fragment "firmware fragment to read").value_parser(["firmware", "bootloader", "full"]).default_value("firmware")) + .arg(arg!(-r --retry "number of retries trying to find device").value_parser(value_parser!(usize)).default_value("5")) .part_args() - .arg(arg!(-b --bootloader --isp "read only booloader").conflicts_with("full")) - .arg( - arg!(--full "read complete flash (including the bootloader)") - .conflicts_with("bootloader"), - ), ) .subcommand( Command::new("write") .short_flag('w') .about("Write file (Intel HEX) into flash.") .arg(arg!(input_file: "payload to write into flash")) + .arg(arg!(-r --retry "number of retries trying to find device").value_parser(value_parser!(usize)).default_value("5")) .part_args(), ) } @@ -112,20 +117,30 @@ fn err_main() -> Result<(), CLIError> { .map(|s| s.as_str()) .unwrap(); - let full = sub_matches.get_flag("full"); + let retry_count = sub_matches + .get_one::("retry") + .map(|s| s.to_owned()) + .unwrap(); - let bootloader = sub_matches.get_flag("bootloader"); + let fragment = sub_matches + .get_one::("fragment") + .map(|s| s.as_str()) + .unwrap(); let part = get_part_from_matches(sub_matches); - let read_type = match (full, bootloader) { - (true, _) => ReadType::Full, - (_, true) => ReadType::Bootloader, - _ => ReadType::Normal, + let fragment: ReadFragment = match fragment { + "firmware" => ReadFragment::Firmware, + "bootloader" => ReadFragment::Bootloader, + "full" => ReadFragment::Full, + _ => panic!("Invalid read fragment"), }; - let isp = ISPDevice::new(part).map_err(CLIError::from)?; - let result = isp.read_cycle(read_type).map_err(CLIError::from)?; + let mut ds = DeviceSelector::new().map_err(CLIError::DeviceSelectorError)?; + let device = ds + .find_isp_device(part, retry_count) + .map_err(CLIError::from)?; + let result = device.read_cycle(fragment).map_err(CLIError::from)?; let digest = md5::compute(&result); info!("MD5: {:x}", digest); @@ -139,6 +154,11 @@ fn err_main() -> Result<(), CLIError> { .map(|s| s.as_str()) .unwrap(); + let retry_count = sub_matches + .get_one::("retry") + .map(|s| s.to_owned()) + .unwrap(); + let part = get_part_from_matches(sub_matches); let mut file = fs::File::open(input_file).map_err(CLIError::from)?; @@ -151,11 +171,38 @@ fn err_main() -> Result<(), CLIError> { firmware.resize(part.firmware_size, 0); } - let isp = ISPDevice::new(part).map_err(CLIError::from)?; - isp.write_cycle(&mut firmware).map_err(CLIError::from)?; + let mut ds = DeviceSelector::new().map_err(CLIError::DeviceSelectorError)?; + let device = ds + .find_isp_device(part, retry_count) + .map_err(CLIError::from)?; + device.write_cycle(&mut firmware).map_err(CLIError::from)?; } - Some(("list", _)) => { - ISPDevice::print_connected_devices().map_err(CLIError::from)?; + Some(("list", sub_matches)) => { + let vendor_id = sub_matches.get_one::("vendor_id"); + let product_id = sub_matches.get_one::("product_id"); + + let ds = DeviceSelector::new().map_err(CLIError::DeviceSelectorError)?; + let devices = ds + .connected_devices_tree() + .map_err(CLIError::DeviceSelectorError)?; + let tree = devices + .into_iter() + .filter(|device| { + if let Some(vendor_id) = vendor_id { + if device.vendor_id != *vendor_id { + return false; + } + } + if let Some(product_id) = product_id { + if device.product_id != *product_id { + return false; + } + } + true + }) + .to_tree_string(0); + + println!("{}", tree); } Some(("convert", sub_matches)) => { let input_file = sub_matches @@ -251,10 +298,8 @@ impl PartCommand for Command { ) .arg(arg!(--bootloader_size ).value_parser(maybe_hex::)) .arg(arg!(--page_size ).value_parser(maybe_hex::)) - .arg(arg!(--isp_iface_num ).value_parser(maybe_hex::)) - .arg(arg!(--isp_usage_page ).value_parser(maybe_hex::)) - .arg(arg!(--isp_usage ).value_parser(maybe_hex::)) - .arg(arg!(--isp_index ).value_parser(maybe_hex::)) + .arg(arg!(--isp_iface_num ).value_parser(clap::value_parser!(i32))) + .arg(arg!(--isp_report_id ).value_parser(maybe_hex::)) .arg(arg!(--reboot ).value_parser(value_parser!(bool))) } } @@ -272,10 +317,8 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { let page_size = sub_matches.get_one::("page_size"); let vendor_id = sub_matches.get_one::("vendor_id"); let product_id = sub_matches.get_one::("product_id"); - let isp_iface_num = sub_matches.get_one::("isp_iface_num"); - let isp_usage_page = sub_matches.get_one::("isp_usage_page"); - let isp_usage = sub_matches.get_one::("isp_usage"); - let isp_index = sub_matches.get_one::("isp_index"); + let isp_iface_num = sub_matches.get_one::("isp_iface_num"); + let isp_report_id = sub_matches.get_one::("isp_report_id"); let reboot = sub_matches.get_one::("reboot"); if let Some(firmware_size) = firmware_size { @@ -296,14 +339,8 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { if let Some(isp_iface_num) = isp_iface_num { part.isp_iface_num = *isp_iface_num; } - if let Some(isp_usage_page) = isp_usage_page { - part.isp_usage_page = *isp_usage_page; - } - if let Some(isp_usage) = isp_usage { - part.isp_usage = *isp_usage; - } - if let Some(isp_index) = isp_index { - part.isp_index = *isp_index; + if let Some(isp_report_id) = isp_report_id { + part.isp_report_id = *isp_report_id; } if let Some(reboot) = reboot { part.reboot = *reboot; diff --git a/src/part.rs b/src/part.rs index 06a5e6a..c8b0178 100644 --- a/src/part.rs +++ b/src/part.rs @@ -9,15 +9,9 @@ pub struct Part { pub product_id: u16, /// USB interface number with the ISP report - pub isp_iface_num: u8, - // The following properties and values are important only for windows support because its - // HIDAPI requires us to use a specific device for each collection - /// HID collection `usage_page` with the ISP report - pub isp_usage_page: u16, - /// HID collection `usage` with the ISP report - pub isp_usage: u16, - /// Index of matching (usage_page && usage) collection at which the ISP report appears in. - pub isp_index: usize, + pub isp_iface_num: i32, + /// HID report ID + pub isp_report_id: u32, pub reboot: bool, } @@ -31,9 +25,7 @@ pub const PART_BASE_DEFAULT: Part = Part { product_id: 0x0000, isp_iface_num: 1, - isp_usage_page: 0xff00, - isp_usage: 0x0001, - isp_index: 0, + isp_report_id: 5, reboot: true, }; @@ -51,35 +43,30 @@ pub const PART_BASE_SH68F881: Part = Part { pub const PART_NUPHY_AIR60: Part = Part { vendor_id: 0x05ac, product_id: 0x024f, - isp_index: 1, ..PART_BASE_SH68F90 }; pub const PART_LEOBOG_HI75: Part = Part { vendor_id: 0x258a, product_id: 0x010c, - isp_index: 1, ..PART_BASE_SH68F90 }; pub const PART_AULA_F75: Part = Part { vendor_id: 0x258a, product_id: 0x010c, - isp_index: 1, ..PART_BASE_SH68F90 }; pub const PART_AULA_F87: Part = Part { vendor_id: 0x258a, product_id: 0x010c, - isp_index: 1, ..PART_BASE_SH68F90 }; pub const PART_XINMENG_K916: Part = Part { vendor_id: 0x258a, product_id: 0x00a1, - isp_index: 1, ..PART_BASE_SH68F90 }; @@ -92,7 +79,6 @@ pub const PART_XINMENG_XM_RF68: Part = Part { pub const PART_XINMENG_M71: Part = Part { vendor_id: 0x258a, product_id: 0x010c, - isp_index: 1, ..PART_BASE_SH68F90 }; @@ -105,28 +91,24 @@ pub const PART_RE_K70_BYK800: Part = Part { pub const PART_TERPORT_TR95: Part = Part { vendor_id: 0x258a, product_id: 0x0049, - isp_index: 1, ..PART_BASE_SH68F90 }; pub const PART_REDRAGON_FIZZ_K617: Part = Part { vendor_id: 0x258a, product_id: 0x0049, - isp_index: 1, ..PART_BASE_SH68F90 }; pub const PART_REDRAGON_ANIVIA_K614: Part = Part { vendor_id: 0x258a, product_id: 0x0049, - isp_index: 1, ..PART_BASE_SH68F90 }; pub const PART_REDRAGON_K641_SHACO_PRO: Part = Part { vendor_id: 0x258a, product_id: 0x0049, - isp_index: 1, ..PART_BASE_SH68F90 }; @@ -199,14 +181,12 @@ pub const PART_DIGITALALLIANCE_MECA_WARRIOR_X: Part = Part { pub const PART_KZZI_K68PRO: Part = Part { vendor_id: 0x258a, product_id: 0x0186, - isp_index: 0x0001, ..PART_BASE_SH68F90 }; pub const PART_WEIKAV_SUGAR65: Part = Part { vendor_id: 0x05ac, product_id: 0x024f, - isp_usage: 0x0002, ..PART_BASE_SH68F90 }; @@ -225,21 +205,18 @@ pub const PART_GLORIOUS_MODEL_O: Part = Part { pub const PART_MACHENIKE_K500_B61: Part = Part { vendor_id: 0x258a, product_id: 0x0049, - isp_index: 1, ..PART_BASE_SH68F90 }; pub const PART_MAGEGEE_MKSTAR61: Part = Part { vendor_id: 0x258a, product_id: 0x010c, - isp_index: 1, ..PART_BASE_SH68F90 }; pub const PART_REDRAGON_K658_PRO_SE: Part = Part { vendor_id: 0x258a, product_id: 0x0049, - isp_index: 1, ..PART_BASE_SH68F90 }; @@ -276,7 +253,6 @@ pub const PART_REDRAGON_K633_RYZE: Part = Part { pub const PART_YINREN_R108: Part = Part { vendor_id: 0x258a, product_id: 0x0049, - isp_index: 1, ..PART_BASE_SH68F90 }; diff --git a/src/util.rs b/src/util.rs index 5b2b549..b32e609 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,6 @@ use crate::part::Part; +use hidapi::HidError; +use log::error; use thiserror::Error; #[cfg(test)] @@ -93,6 +95,23 @@ pub fn convert_to_isp_payload(input: &mut [u8], part: Part) -> Result<(), Payloa Ok(()) } +pub fn to_hex_string(bytes: &[u8]) -> String { + let strs: Vec = bytes.iter().map(|b| format!("{:02X}", b)).collect(); + strs.join(" ") +} + +pub fn is_expected_error(err: &HidError) -> bool { + match err { + #[cfg(target_os = "macos")] + HidError::HidApiError { ref message } if message == "IOHIDDeviceSetReport failed: (0xE0005000) unknown error code" => true, + #[cfg(target_os = "linux")] + HidError::HidApiError { ref message } if message == "hid_error is not implemented yet" => true, + #[cfg(target_os = "windows")] + HidError::HidApiError { ref message } if message == "HidD_SetFeature: (0x0000001F) A device attached to the system is not functioning." => true, + _ => false, + } +} + #[test] fn test_verify_success() { assert!(verify(&vec![1, 2, 3, 4], &vec![1, 2, 3, 4]).is_ok()); diff --git a/tools/functional-test.sh b/tools/functional-test.sh index f56ae8e..484f389 100755 --- a/tools/functional-test.sh +++ b/tools/functional-test.sh @@ -44,12 +44,12 @@ $TOOL read --part "$PART" "$FILE_DEFAULT" reboot_device echo "Bootloader read..." -$TOOL read --part "$PART" -b "$FILE_BOOTLOADER" +$TOOL read --part "$PART" -f bootloader "$FILE_BOOTLOADER" reboot_device echo "Full read..." -$TOOL read --part "$PART" --full "$FILE_FULL" +$TOOL read --part "$PART" -f full "$FILE_FULL" reboot_device @@ -61,9 +61,7 @@ $TOOL read \ --bootloader_size 4096 \ --page_size 2048 \ --isp_iface_num 1 \ - --isp_usage_page 0xff00 \ - --isp_usage 0x0001 \ - --isp_index 1 \ + --isp_report_id 5 \ "$FILE_CUSTOM" reboot_device @@ -135,9 +133,7 @@ $TOOL write \ --bootloader_size 4096 \ --page_size 2048 \ --isp_iface_num 1 \ - --isp_usage_page 0xff00 \ - --isp_usage 0x0001 \ - --isp_index 1 \ + --isp_report_id 5 \ "$FILE_DEFAULT" reboot_device @@ -155,4 +151,12 @@ fi reboot_device +$TOOL list + +$TOOL list --product_id 0x024f + +$TOOL list --vendor_id 0x05ac + +$TOOL list --vendor_id 0x05ac --product_id 0x024f + echo "Passed all tests!" From 82f9173d03f89652d3f25222b3db587b35561823 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 3 May 2025 09:23:40 +0200 Subject: [PATCH 116/141] bump to 1.0.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a03872..b44b193 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,7 +302,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "0.1.1" +version = "1.0.0" dependencies = [ "clap", "clap-num", diff --git a/Cargo.toml b/Cargo.toml index f073319..c018993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based HID devices through the commonly found ISP bootloader """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "0.1.1" +version = "1.0.0" edition = "2021" license = "MIT" rust-version = "1.65" From 2125249178a63e76de1cd5707da7ffa7bafb47e1 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 3 May 2025 19:36:06 +0200 Subject: [PATCH 117/141] ui improvements and integration tests (#104) * Removed reliance on `log` calls and simple_logger for output meant for users and replaced it with `eprintln!` calls. * Added `indicatif` bars to show activity/progress. * Added rust-based integration tests (which currently rely on the runner having a nuphy-air60, loaded a specific version of SMK). This should make regression testing more convenient and it replaces the need for `tools/functional-test.sh`. --- .github/workflows/push.yml | 4 +- Cargo.lock | 790 ++++- Cargo.toml | 10 +- src/device_selector.rs | 125 +- src/hid_tree.rs | 6 +- src/ihex.rs | 2 +- src/isp_device.rs | 37 +- src/main.rs | 158 +- tests/common/mod.rs | 23 + tests/convert_test.rs | 78 + tests/fixtures/nuphy-air60_smk.bin | Bin 0 -> 61440 bytes tests/fixtures/nuphy-air60_smk.hex | 3841 +++++++++++++++++++++++ tests/fixtures/nuphy-air60_smk_jtag.hex | 3841 +++++++++++++++++++++++ tests/list_test.rs | 98 + tests/read_test.rs | 145 + tests/write_test.rs | 90 + tools/functional-test.sh | 162 - 17 files changed, 9087 insertions(+), 323 deletions(-) create mode 100644 tests/common/mod.rs create mode 100644 tests/convert_test.rs create mode 100644 tests/fixtures/nuphy-air60_smk.bin create mode 100644 tests/fixtures/nuphy-air60_smk.hex create mode 100644 tests/fixtures/nuphy-air60_smk_jtag.hex create mode 100644 tests/list_test.rs create mode 100644 tests/read_test.rs create mode 100644 tests/write_test.rs delete mode 100755 tools/functional-test.sh diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 41c9818..f293854 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -40,7 +40,7 @@ jobs: run: | sudo apt-get install -qq libusb-1.0.0-dev - name: Run tests - run: cargo test --verbose + run: cargo test --bins --verbose build: strategy: @@ -79,8 +79,6 @@ jobs: run: rustup target add $TARGET - name: Run build run: cargo build --release --verbose --target $TARGET - - name: Run integration tests - run: cargo run --release -- list - name: Compress run: | mkdir -p ./artifacts diff --git a/Cargo.lock b/Cargo.lock index b44b193..38886e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,30 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.18" @@ -43,25 +67,65 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] +[[package]] +name = "assert_cmd" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + [[package]] name = "cc" -version = "1.2.4" +version = "1.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" +checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" dependencies = [ "shlex", ] @@ -72,29 +136,43 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "clap" -version = "4.5.23" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" dependencies = [ "clap_builder", ] [[package]] name = "clap-num" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e063d263364859dc54fb064cedb7c122740cd4733644b14b176c097f51e8ab7" +checksum = "822c4000301ac390e65995c62207501e3ef800a1fc441df913a5e8e4dc374816" dependencies = [ "num-traits", ] [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" dependencies = [ "anstream", "anstyle", @@ -116,20 +194,152 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "hidapi" version = "2.6.3" @@ -148,12 +358,49 @@ name = "hidparser" version = "1.0.2" source = "git+https://github.com/microsoft/mu_rust_hid.git?tag=v1.0.3#38e4bd85156e48f341f8d8110c257dfff6aee942" +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ihex" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "365a784774bb381e8c19edb91190a90d7f2625e057b55de2bc0f6b57bc779ff2" +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -169,6 +416,22 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -177,15 +440,25 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "md5" @@ -193,6 +466,24 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -202,11 +493,55 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", @@ -214,9 +549,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand", @@ -224,9 +559,9 @@ dependencies = [ [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", @@ -237,33 +572,87 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -283,6 +672,116 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "scc" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4" +dependencies = [ + "sdd", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "3.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serial_test" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "shlex" version = "1.3.0" @@ -297,6 +796,7 @@ checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb" dependencies = [ "colored", "log", + "time", "windows-sys 0.48.0", ] @@ -304,24 +804,51 @@ dependencies = [ name = "sinowealth-kb-tool" version = "1.0.0" dependencies = [ + "assert_cmd", + "chrono", "clap", "clap-num", "hidapi", "hidparser", "ihex", + "indicatif", "itertools", "log", "md5", "phf", + "predicates", + "serial_test", "simple_logger", + "stdext", "thiserror", ] [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "stdext" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4af28eeb7c18ac2dbdb255d40bee63f203120e1db6b0024b177746ebec7049c1" [[package]] name = "strsim" @@ -331,40 +858,85 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.90" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "thiserror" -version = "2.0.6" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.6" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "utf8parse" @@ -372,6 +944,142 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index c018993..e5506b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ clap = "4.1" clap-num = "1.0" hidparser = { git = "https://github.com/microsoft/mu_rust_hid.git", tag = "v1.0.3" } # needed until 1.0.3 is published on crates.io ihex = "3.0" +indicatif = "0.17.11" itertools = "0.14.0" md5 = "0.7" thiserror = "2.0" @@ -30,8 +31,15 @@ features = ["max_level_debug"] [dependencies.simple_logger] version = "5.0" default-features = false -features = ["stderr", "colors"] +features = ["stderr", "colors", "timestamps"] [dependencies.phf] version = "0.11" features = ["macros"] + +[dev-dependencies] +assert_cmd = "2.0.17" +chrono = "0.4.41" +predicates = "3.1.3" +serial_test = "3.2.0" +stdext = "0.3.3" diff --git a/src/device_selector.rs b/src/device_selector.rs index 3cbacc9..f0af0a6 100644 --- a/src/device_selector.rs +++ b/src/device_selector.rs @@ -1,17 +1,21 @@ use core::time; -use std::{ffi::CStr, thread}; +use std::{ffi::CStr, thread, time::Duration}; use hidapi::{BusType, DeviceInfo, HidDevice, HidError, MAX_REPORT_DESCRIPTOR_SIZE}; use hidparser::parse_report_descriptor; +use indicatif::ProgressBar; use itertools::Itertools; use log::{debug, error, info}; use thiserror::Error; use crate::{ - hid_tree::{DeviceNode, InterfaceNode, ItemNode}, + hid_tree::{DeviceNode, InterfaceNode}, is_expected_error, ISPDevice, Part, }; +#[cfg(any(target_os = "macos", target_os = "windows"))] +use crate::hid_tree::ItemNode; + const REPORT_ID_ISP: u8 = 0x05; const CMD_ISP_MODE: u8 = 0x75; @@ -61,8 +65,8 @@ impl DeviceSelector { return ( d.vendor_id(), d.product_id(), - d.path(), d.interface_number(), + d.path(), d.usage_page(), d.usage(), ); @@ -70,8 +74,8 @@ impl DeviceSelector { return ( d.vendor_id(), d.product_id(), - d.path(), d.interface_number(), + d.path(), ); }); devices @@ -168,7 +172,7 @@ impl DeviceSelector { Err(DeviceSelectorError::NotFound) } - fn open_isp_devices(&self, part: Part) -> Result { + fn find_isp_device(&self, part: Part) -> Result { let sorted_devices = self.sorted_usb_device_list(); let isp_devices: Vec<_> = sorted_devices .clone() @@ -212,12 +216,7 @@ impl DeviceSelector { } } - fn switch_kb_device(&mut self, part: Part) -> Result { - info!( - "Looking for vId:{:#06x} pId:{:#06x}", - part.vendor_id, part.product_id - ); - + fn find_device(&self, part: Part) -> Result { let filtered_devices = self.sorted_usb_device_list().into_iter().filter(|d| { d.vendor_id() == part.vendor_id && d.product_id() == part.product_id @@ -237,7 +236,7 @@ impl DeviceSelector { } let Some(cmd_device_info) = cmd_device_info else { - info!("Regular device didn't come up..."); + info!("Device didn't come up..."); return Err(DeviceSelectorError::NotFound); }; @@ -246,8 +245,14 @@ impl DeviceSelector { .api .open_path(cmd_device_info.path()) .map_err(DeviceSelectorError::from)?; + Ok(device) + } - info!("Found regular device. Entering ISP mode..."); + fn switch_to_isp_device( + &mut self, + device: HidDevice, + part: Part, + ) -> Result { if let Err(err) = self.enter_isp_mode(&device) { debug!("Error: {:}", err); match err { @@ -266,35 +271,52 @@ impl DeviceSelector { self.api.refresh_devices()?; - let Ok(isp_device) = self.open_isp_devices(part) else { + let Ok(isp_device) = self.find_isp_device(part) else { info!("ISP device didn't come up..."); return Err(DeviceSelectorError::NotFound); }; Ok(isp_device) } - pub fn find_isp_device( + pub fn try_fetch_isp_device( &mut self, part: Part, retries: usize, ) -> Result { + eprintln!( + "Looking for {:04x}:{:04x} (interface_num={} report_id={})", + part.vendor_id, part.product_id, part.isp_iface_num, part.isp_report_id + ); + + let bar = ProgressBar::new_spinner() + .with_message(format!("Searching for device... Attempt {}/{}", 1, retries)); + bar.enable_steady_tick(Duration::from_millis(100)); + for attempt in 1..retries + 1 { self.api.refresh_devices()?; if attempt > 1 { - thread::sleep(time::Duration::from_millis(500)); + thread::sleep(time::Duration::from_millis(1000)); + bar.set_message(format!("Retrying... Attempt {}/{}", attempt, retries)); info!("Retrying... Attempt {}/{}", attempt, retries); } - if let Ok(devices) = self.switch_kb_device(part) { - info!("Connected!"); - return Ok(devices); + if let Ok(device) = self.find_device(part) { + bar.set_message("Regular device found. Switching to ISP mode..."); + if let Ok(isp_device) = self.switch_to_isp_device(device, part) { + bar.finish_and_clear(); + eprintln!("Connected!"); + return Ok(isp_device); + } } info!("Regular device not found. Trying ISP device..."); - if let Ok(devices) = self.open_isp_devices(part) { - info!("Connected!"); - return Ok(devices); + if let Ok(isp_device) = self.find_isp_device(part) { + bar.finish_and_clear(); + eprintln!("Connected!"); + return Ok(isp_device); } } + bar.finish(); + eprintln!("Device not found"); Err(DeviceSelectorError::NotFound) } @@ -307,42 +329,36 @@ impl DeviceSelector { pub fn connected_devices_tree(&self) -> Result, DeviceSelectorError> { let devices: Vec<_> = self.sorted_usb_device_list(); - let id_chunks = devices.iter().chunk_by(|d| { - return ( - d.vendor_id(), - d.product_id(), - d.manufacturer_string().unwrap_or("None"), - d.product_string().unwrap_or("None"), - ); - }); + let id_chunks = devices + .into_iter() + .chunk_by(|d| (d.vendor_id(), d.product_id())); let mut device_tree_devices: Vec = vec![]; - for ((vid, pid, manufacturer, product), devices) in &id_chunks { - let mut node = DeviceNode { - product_id: pid, - vendor_id: vid, - product_string: product.to_string(), - manufacturer_string: manufacturer.to_string(), - children: vec![], - }; + for (key, devices) in &id_chunks { + let (vid, pid) = key; - let path_chunks = devices.chunk_by(|d| { - #[cfg(any(target_os = "macos", target_os = "linux"))] - return (d.path(), d.interface_number()); - #[cfg(target_os = "windows")] - return (d.path(), d.interface_number(), d.usage_page(), d.usage()); - }); + let mut interface_nodes: Vec = vec![]; + + // for some reason on linux-libusb the same device might not have the same manufacturer string in some cases + let mut manufacturer_string: Option = None; + let mut product_string: Option = None; + + let path_chunks = devices.chunk_by(|d| (d.path(), d.interface_number())); for (key, devices) in &path_chunks { - #[cfg(any(target_os = "macos", target_os = "linux"))] let (path, interface_number) = key; - #[cfg(target_os = "windows")] - let (path, interface_number, usage_page, usage) = key; + #[cfg(any(target_os = "macos", target_os = "windows"))] let mut children: Vec = vec![]; for d in devices { + if manufacturer_string.is_none() { + manufacturer_string = d.manufacturer_string().map(str::to_string); + } + if product_string.is_none() { + product_string = d.product_string().map(str::to_string); + } #[cfg(target_os = "macos")] children.push(ItemNode { usage_page: d.usage_page(), @@ -362,6 +378,7 @@ impl DeviceSelector { } } + #[cfg(any(target_os = "macos", target_os = "linux"))] let (descriptor, feature_report_ids) = self.get_descriptor_with_features(path); let interface_node = InterfaceNode { #[cfg(any(target_os = "macos", target_os = "linux"))] @@ -375,10 +392,16 @@ impl DeviceSelector { children, }; - node.children.push(interface_node); + interface_nodes.push(interface_node); } - device_tree_devices.push(node); + device_tree_devices.push(DeviceNode { + vendor_id: vid, + product_id: pid, + manufacturer_string: manufacturer_string.clone().unwrap_or("None".to_string()), + product_string: product_string.clone().unwrap_or("None".to_string()), + children: interface_nodes, + }); } Ok(device_tree_devices) } @@ -392,7 +415,7 @@ impl PlatformSpecificInfo for DeviceInfo { fn info(&self) -> String { #[cfg(not(target_os = "linux"))] return format!( - "Found ISP Device: {:#06x} {:#06x} {:?} {} {:#06x} {:#06x}", + "{:#06x} {:#06x} {:?} {} {:#06x} {:#06x}", self.vendor_id(), self.product_id(), self.path(), @@ -402,7 +425,7 @@ impl PlatformSpecificInfo for DeviceInfo { ); #[cfg(target_os = "linux")] format!( - "Found ISP Device: {:#06x} {:#06x} {:?}", + "{:#06x} {:#06x} {:?}", self.vendor_id(), self.product_id(), self.path() diff --git a/src/hid_tree.rs b/src/hid_tree.rs index 33ecc83..097d504 100644 --- a/src/hid_tree.rs +++ b/src/hid_tree.rs @@ -20,6 +20,7 @@ pub struct InterfaceNode { pub children: Vec, } +#[cfg(any(target_os = "macos", target_os = "windows"))] pub struct ItemNode { #[cfg(target_os = "windows")] pub path: String, @@ -104,7 +105,7 @@ impl TreeDisplay for InterfaceNode { "{indent}feature_report_ids=[{}]", feature_report_ids .iter() - .map(|rid| format!("{:#04x}", rid)) + .map(|rid| format!("{}", rid)) .collect::>() .join(", ") )); @@ -124,6 +125,7 @@ impl TreeDisplay for InterfaceNode { } } +#[cfg(any(target_os = "macos", target_os = "windows"))] impl TreeDisplay for ItemNode { fn to_tree_string(self, level: usize) -> String { let indent = " ".repeat(INDENT_SIZE).repeat(level); @@ -158,7 +160,7 @@ impl TreeDisplay for ItemNode { "{indent}feature_report_ids=[{}]", feature_report_ids .iter() - .map(|rid| format!("{:#04x}", rid)) + .map(|rid| format!("{}", rid)) .collect::>() .join(", ") )); diff --git a/src/ihex.rs b/src/ihex.rs index 60e1d7e..216070e 100644 --- a/src/ihex.rs +++ b/src/ihex.rs @@ -19,7 +19,7 @@ pub enum ConversionError { Serializing(#[from] WriterError), } -pub fn to_ihex(byte_array: Vec) -> Result { +pub fn to_ihex(byte_array: &[u8]) -> Result { let mut result: Vec = vec![]; for (i, chunk) in byte_array.chunks(16).enumerate() { result.push(Record::Data { diff --git a/src/isp_device.rs b/src/isp_device.rs index 2df6a78..4e4625d 100644 --- a/src/isp_device.rs +++ b/src/isp_device.rs @@ -1,6 +1,7 @@ use std::{thread, time}; -use log::{debug, error, info}; +use indicatif::ProgressBar; +use log::{debug, error}; use thiserror::Error; use crate::{is_expected_error, part::*, util, VerificationError}; @@ -39,7 +40,7 @@ pub enum ISPError { } #[derive(Debug, Clone)] -pub enum ReadFragment { +pub enum ReadSection { Firmware, Bootloader, Full, @@ -63,13 +64,13 @@ impl ISPDevice { } } - pub fn read_cycle(&self, read_fragment: ReadFragment) -> Result, ISPError> { + pub fn read_cycle(&self, read_fragment: ReadSection) -> Result, ISPError> { self.enable_firmware()?; let (start_addr, length) = match read_fragment { - ReadFragment::Firmware => (0, self.part.firmware_size), - ReadFragment::Bootloader => (self.part.firmware_size, self.part.bootloader_size), - ReadFragment::Full => (0, self.part.firmware_size + self.part.bootloader_size), + ReadSection::Firmware => (0, self.part.firmware_size), + ReadSection::Bootloader => (self.part.firmware_size, self.part.bootloader_size), + ReadSection::Full => (0, self.part.firmware_size + self.part.bootloader_size), }; let firmware = self.read(start_addr, length)?; @@ -93,7 +94,7 @@ impl ISPDevice { let read_back = self.read(0, self.part.firmware_size)?; - info!("Verifying..."); + eprintln!("Verifying..."); util::verify(firmware, &read_back).map_err(ISPError::from)?; self.enable_firmware()?; @@ -113,13 +114,17 @@ impl ISPDevice { } fn read(&self, start_addr: usize, length: usize) -> Result, ISPError> { - info!("Reading..."); - self.init_read(start_addr)?; - let page_size = self.part.page_size; let num_page = length / page_size; let mut result: Vec = vec![]; + + eprintln!("Reading..."); + let bar = ProgressBar::new(num_page as u64); + + self.init_read(start_addr)?; + for i in 0..num_page { + bar.inc(1); debug!( "Reading page {} @ offset {:#06x}", i, @@ -127,18 +132,22 @@ impl ISPDevice { ); self.read_page(&mut result)?; } + bar.finish(); Ok(result) } fn write(&self, start_addr: usize, buffer: &[u8]) -> Result<(), ISPError> { - info!("Writing..."); + eprintln!("Writing..."); + let bar = ProgressBar::new(self.part.num_pages() as u64); self.init_write(start_addr)?; let page_size = self.part.page_size; for i in 0..self.part.num_pages() { + bar.inc(1); debug!("Writing page {} @ offset {:#06x}", i, i * page_size); self.write_page(&buffer[(i * page_size)..((i + 1) * page_size)])?; } + bar.finish(); Ok(()) } @@ -211,7 +220,7 @@ impl ISPDevice { /// Side-effect: enables reading the firmware without erasing flash first. /// Credits to @gashtaan for finding this out. fn enable_firmware(&self) -> Result<(), ISPError> { - info!("Enabling firmware..."); + eprintln!("Enabling firmware..."); let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_ENABLE_FIRMWARE, 0, 0, 0, 0]; self.cmd_device.send_feature_report(&cmd)?; @@ -221,7 +230,7 @@ impl ISPDevice { /// Erases everything in flash, except the ISP bootloader section itself and initializes the /// reset vector to jump to ISP. fn erase(&self) -> Result<(), ISPError> { - info!("Erasing..."); + eprintln!("Erasing..."); let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_ERASE, 0, 0, 0, 0]; self.cmd_device .send_feature_report(&cmd) @@ -232,7 +241,7 @@ impl ISPDevice { /// Causes the device to start running the main firmware fn reboot(&self) { - info!("Rebooting..."); + eprintln!("Rebooting..."); let cmd: [u8; COMMAND_LENGTH] = [REPORT_ID_CMD, CMD_REBOOT, 0, 0, 0, 0]; if let Err(err) = self.cmd_device.send_feature_report(&cmd) { debug!("Error: {:}", err); diff --git a/src/main.rs b/src/main.rs index 673e596..30e2e5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use std::{ env, fs, io::{self, Read}, + path::Path, process::ExitCode, }; @@ -8,7 +9,7 @@ use clap::{arg, value_parser, ArgMatches, Command}; use clap_num::maybe_hex; use device_selector::{DeviceSelector, DeviceSelectorError}; use hid_tree::TreeDisplay; -use log::{error, info}; +use log::error; use simple_logger::SimpleLogger; use thiserror::Error; @@ -35,6 +36,12 @@ pub enum CLIError { DeviceSelectorError(#[from] DeviceSelectorError), } +#[derive(Clone, Copy)] +enum Format { + IntelHex, + Binary, +} + fn main() -> ExitCode { match err_main() { Ok(_) => ExitCode::SUCCESS, @@ -64,16 +71,19 @@ fn cli() -> Command { .short_flag('c') .about("Convert payload from bootloader to JTAG and vice versa.") .arg(arg!(-d --direction "direction of conversion").value_parser(["to_jtag", "to_isp"]).required(true)) + .arg(arg!(--input_format ).value_parser(["ihex", "bin"])) + .arg(arg!(--output_format ).value_parser(["ihex", "bin"])) .arg(arg!(input_file: "file to convert")) .arg(arg!(output_file: "file to write results to")) - .part_args() + .part_args() // TODO: not all of these args are needed and should be removed ) .subcommand( Command::new("read") .short_flag('r') .about("Read flash contents. (Intel HEX)") .arg(arg!(output_file: "file to write flash contents to")) - .arg(arg!(-f --fragment "firmware fragment to read").value_parser(["firmware", "bootloader", "full"]).default_value("firmware")) + .arg(arg!(-f --format ).value_parser(["ihex", "bin"])) + .arg(arg!(-s --section
"firmware section to read").value_parser(["firmware", "bootloader", "full"]).default_value("firmware")) .arg(arg!(-r --retry "number of retries trying to find device").value_parser(value_parser!(usize)).default_value("5")) .part_args() ) @@ -82,29 +92,17 @@ fn cli() -> Command { .short_flag('w') .about("Write file (Intel HEX) into flash.") .arg(arg!(input_file: "payload to write into flash")) + .arg(arg!(-f --format ).value_parser(["ihex", "bin"])) .arg(arg!(-r --retry "number of retries trying to find device").value_parser(value_parser!(usize)).default_value("5")) .part_args(), ) } -fn get_log_level() -> log::LevelFilter { - if let Ok(debug) = env::var("DEBUG") { - if debug == "1" { - log::LevelFilter::Debug - } else { - log::LevelFilter::Info - } - } else { - #[cfg(debug_assertions)] - return log::LevelFilter::Debug; - #[cfg(not(debug_assertions))] - log::LevelFilter::Info - } -} - fn err_main() -> Result<(), CLIError> { SimpleLogger::new() - .with_level(get_log_level()) + .with_utc_timestamps() + .with_level(log::LevelFilter::Off) + .env() .init() .unwrap(); @@ -122,31 +120,32 @@ fn err_main() -> Result<(), CLIError> { .map(|s| s.to_owned()) .unwrap(); - let fragment = sub_matches - .get_one::("fragment") + let section = sub_matches + .get_one::("section") .map(|s| s.as_str()) .unwrap(); + let format = get_format_from_matches(sub_matches, output_file, "format"); + let part = get_part_from_matches(sub_matches); - let fragment: ReadFragment = match fragment { - "firmware" => ReadFragment::Firmware, - "bootloader" => ReadFragment::Bootloader, - "full" => ReadFragment::Full, + let section: ReadSection = match section { + "firmware" => ReadSection::Firmware, + "bootloader" => ReadSection::Bootloader, + "full" => ReadSection::Full, _ => panic!("Invalid read fragment"), }; let mut ds = DeviceSelector::new().map_err(CLIError::DeviceSelectorError)?; let device = ds - .find_isp_device(part, retry_count) + .try_fetch_isp_device(part, retry_count) .map_err(CLIError::from)?; - let result = device.read_cycle(fragment).map_err(CLIError::from)?; + let firmware = device.read_cycle(section).map_err(CLIError::from)?; - let digest = md5::compute(&result); - info!("MD5: {:x}", digest); + let digest = md5::compute(&firmware); + eprintln!("MD5: {:x}", digest); - let ihex = to_ihex(result).map_err(CLIError::from)?; - fs::write(output_file, ihex).map_err(CLIError::from)?; + write_with_format(output_file, &firmware, format).map_err(CLIError::from)?; } Some(("write", sub_matches)) => { let input_file = sub_matches @@ -159,13 +158,11 @@ fn err_main() -> Result<(), CLIError> { .map(|s| s.to_owned()) .unwrap(); + let format = get_format_from_matches(sub_matches, input_file, "format"); + let part = get_part_from_matches(sub_matches); - let mut file = fs::File::open(input_file).map_err(CLIError::from)?; - let mut file_buf = Vec::new(); - file.read_to_end(&mut file_buf).map_err(CLIError::from)?; - let file_str = String::from_utf8_lossy(&file_buf[..]); - let mut firmware = from_ihex(&file_str, part.firmware_size).map_err(CLIError::from)?; + let mut firmware = read_with_format(input_file, format).map_err(CLIError::from)?; if firmware.len() < part.firmware_size { firmware.resize(part.firmware_size, 0); @@ -173,7 +170,7 @@ fn err_main() -> Result<(), CLIError> { let mut ds = DeviceSelector::new().map_err(CLIError::DeviceSelectorError)?; let device = ds - .find_isp_device(part, retry_count) + .try_fetch_isp_device(part, retry_count) .map_err(CLIError::from)?; device.write_cycle(&mut firmware).map_err(CLIError::from)?; } @@ -220,18 +217,17 @@ fn err_main() -> Result<(), CLIError> { .map(|s| s.as_str()) .unwrap(); + let input_format = get_format_from_matches(sub_matches, input_file, "input_format"); + let output_format = get_format_from_matches(sub_matches, output_file, "output_format"); + let part = get_part_from_matches(sub_matches); - let mut file = fs::File::open(input_file).map_err(CLIError::from)?; - let mut file_buf = Vec::new(); - file.read_to_end(&mut file_buf).map_err(CLIError::from)?; - let file_str = String::from_utf8_lossy(&file_buf[..]); - let mut firmware = from_ihex(&file_str, part.firmware_size + part.bootloader_size) - .map_err(CLIError::from)?; + let mut firmware = + read_with_format(input_file, input_format).map_err(CLIError::from)?; if firmware.len() < part.firmware_size { log::warn!( - "Firmware size is more than expected ({}). Increasing to {}", + "Firmware size is less than expected ({}). Increasing to {}", firmware.len(), part.firmware_size ); @@ -242,7 +238,7 @@ fn err_main() -> Result<(), CLIError> { "to_jtag" => { convert_to_jtag_payload(&mut firmware, part).map_err(CLIError::from)?; if firmware.len() < part.total_flash_size() { - log::warn!( + eprintln!( "Firmware is smaller ({} bytes) than expected ({} bytes). This payload might not be suitable for JTAG flashing.", firmware.len(), part.total_flash_size() @@ -252,7 +248,7 @@ fn err_main() -> Result<(), CLIError> { "to_isp" => { convert_to_isp_payload(&mut firmware, part).map_err(CLIError::from)?; if firmware.len() > part.firmware_size { - log::warn!( + eprintln!( "Firmware size is larger ({} bytes) than expected ({} bytes). This payload might not be suitable for ISP flashing.", firmware.len(), part.firmware_size @@ -262,8 +258,7 @@ fn err_main() -> Result<(), CLIError> { _ => unreachable!(), } - let ihex = to_ihex(firmware).map_err(CLIError::from)?; - fs::write(output_file, ihex).map_err(CLIError::from)?; + write_with_format(output_file, &firmware, output_format).map_err(CLIError::from)?; } _ => unreachable!(), } @@ -304,6 +299,49 @@ impl PartCommand for Command { } } +fn get_format_from_matches( + sub_matches: &ArgMatches, + file_path: &str, + format_option: &str, +) -> Format { + let input_ext = Path::new(file_path).extension(); + + let assumed_format = input_ext + .map(|ext| { + if ext == "ihex" || ext == "ihx" || ext == "hex" { + Format::IntelHex + } else { + Format::Binary + } + }) + .unwrap_or(Format::Binary); + + let format = sub_matches + .get_one::(format_option) + .map(|s| s.as_str()) + .map(|format| match format { + "ihex" => Format::IntelHex, + "bin" => Format::Binary, + _ => panic!("Invalid format"), + }) + .unwrap_or(assumed_format); + + match (assumed_format, format) { + (Format::IntelHex, Format::Binary) => { + eprintln!( + "Warning: binary file has {} extension. This might be unintended.", + input_ext.unwrap().to_string_lossy() + ); + } + (Format::Binary, Format::IntelHex) => { + eprintln!("Warning: ihex file does not have .ihex or .ihx or .hex extension. This might be unintended."); + } + _ => {} + } + + format +} + fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { let part_name = sub_matches.get_one::("part").map(|s| s.as_str()); @@ -347,3 +385,27 @@ fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { } part } + +fn read_with_format(file: &str, format: Format) -> Result, CLIError> { + let mut file = fs::File::open(file).map_err(CLIError::from)?; + let mut file_buf = Vec::new(); + file.read_to_end(&mut file_buf).map_err(CLIError::from)?; + + match format { + Format::IntelHex => { + let file_str = String::from_utf8_lossy(&file_buf[..]); + from_ihex(&file_str, 0xFFFF).map_err(CLIError::from) // TODO reasonable length + } + Format::Binary => Ok(file_buf), + } +} + +fn write_with_format(file: &str, data: &[u8], format: Format) -> Result<(), CLIError> { + match format { + Format::IntelHex => { + let ihex = to_ihex(data).map_err(CLIError::from)?; + fs::write(file, ihex).map_err(CLIError::from) + } + Format::Binary => fs::write(file, data).map_err(CLIError::from), + } +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..674dcd7 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,23 @@ +macro_rules! test_filename { + ($format:expr) => {{ + let date = chrono::Utc::now().to_rfc3339().replace(":", "-"); + let mut path = std::env::temp_dir(); + path.push(format!( + "sinowealth-kb-tool-{}-{}.{}", + date, + stdext::function_name!() + .replace(":", "_") + .replace("__{{closure}}", ""), + $format + )); + path.display().to_string() + }}; +} + +pub fn get_fixture_path(filename: &str) -> String { + let mut path = std::env::current_dir().unwrap(); + path.push("tests"); + path.push("fixtures"); + path.push(filename); + path.display().to_string() +} diff --git a/tests/convert_test.rs b/tests/convert_test.rs new file mode 100644 index 0000000..50d0d4a --- /dev/null +++ b/tests/convert_test.rs @@ -0,0 +1,78 @@ +use std::fs; + +use assert_cmd::Command; +use serial_test::serial; + +#[macro_use] +pub mod common; + +use common::get_fixture_path; + +#[test] +#[serial] +fn test_convert_to_jtag() { + let input_file = get_fixture_path("nuphy-air60_smk.hex"); + let output_file = test_filename!("hex"); + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd + .arg("convert") + .args(&["--part", "nuphy-air60"]) + .args(&["--direction", "to_jtag"]) + .arg(&input_file) + .arg(&output_file) + .assert(); + + assert.success(); + + let computed_md5 = md5::compute(fs::read(&output_file).unwrap()); + assert_eq!( + format!("{:x}", computed_md5), + "3bbd99f81678fc11fdf1ba9eaaac2bd1" + ); +} + +#[test] +#[serial] +fn test_convert_to_isp() { + let input_file = get_fixture_path("nuphy-air60_smk_jtag.hex"); + let output_file = test_filename!("hex"); + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd + .arg("convert") + .args(&["--part", "nuphy-air60"]) + .args(&["--direction", "to_isp"]) + .arg(&input_file) + .arg(&output_file) + .assert(); + + assert.success(); + + let computed_md5 = md5::compute(fs::read(&output_file).unwrap()); + assert_eq!( + format!("{:x}", computed_md5), + "6594e5a1ab671deb40f36483a84ad61f" + ); +} + +#[test] +#[serial] +fn test_convert_to_jtag_bin() { + let input_file = get_fixture_path("nuphy-air60_smk.hex"); + let output_file = test_filename!("bin"); + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd + .arg("convert") + .args(&["--part", "nuphy-air60"]) + .args(&["--direction", "to_jtag"]) + .arg(&input_file) + .arg(&output_file) + .assert(); + + assert.success(); + + let computed_md5 = md5::compute(fs::read(&output_file).unwrap()); + assert_eq!( + format!("{:x}", computed_md5), + "df1ff7b247ae12dda37aa69730f090af" + ); +} diff --git a/tests/fixtures/nuphy-air60_smk.bin b/tests/fixtures/nuphy-air60_smk.bin new file mode 100644 index 0000000000000000000000000000000000000000..d56720eed4167b94ab7959becda5e36a0c5b74e4 GIT binary patch literal 61440 zcmeHud0Z4n+JAM=3^Uv?s1Yx40B>{@h>41#BPK>yks-TTP2zY%G(uuGA|Ny~>8Qvt z9;}H+G{M9hHoIXri3$XCH@h)eLSoirci&9RbTo?4$)=-n&HGgM441m=zVGjs-|vqa zK6O1+_0&_}s(PyGsfI?+O+w1=KMm=S(Q-vP@M$^xclGJOFKFr}Tfpn5wh2~?ibm@< zslT0#EQ7Nt`^*_xN7zHObJzFfy0;OnIrRAtUwqFVayheHKVIs{LRX=j&nCnkLe77B zUuw@fa&G!vStwAQ>% zb3Qe1(42S8kpNC-N0h;iXmDz9$N&Y7q9hef{iP)`-W@*SiuAEb)|SXHn`CotSg~ep z`iA^1+ij)0_LLtvQTf~7*Kf=@U9<1tA3vb({j;OX(7?Cgu7Rrp_d9UE1@{)X--EjV z?n8r}vVkiEw-elMa0kJCV6fA}!9{?J1Q!Kv1h|nS9T0s|web9aH{GUGj_#eWz zU)}glqBP$A!TpC{{5*8%?T)C?bdR4(`O&mWh22k$E*f+!CAAR6+(;IN1Z zlc&baFg;@a#ge5f)@&F#c5V8+g&T9!_x}CVh_X7wdLz~c<)M63-@H4hWhJ+pOA2d_ zRKe1VMX>V3=E#t)ctAD!Jdpd$AXsq$CobTStOnlO3Z!!pDGpcUTOH2W>79aaly7@9 z6qybzc6F3HcXbMW7O(7SIny|G_Kciq+o>F`R$J>;tEpAj(zR5r3SwT&SY-`dm)C_* z5P3@~Xizt38oU~`#dL|P{@U*F>${T^W-v4MGRgZACIeINVigm%MDpSM>?n#Akv?D= zbm+KG4g$U^W_GVt1uWa70)yaVi4lBQ@zCMOw_2I$abjD`m{#Z1wx%&4@>DExJBaKm zqDxipQkv6<=F~-+QZL>wwAfl=!&2*`!vrf9EQ2!eenYy*lZ;*DVs(kOzG=6kWj9Kg z&Rp5=JSfW>3i4`~NTNn$D2|SDTol_Rt3F^m_rTpV?6eO8Hfqdf1ala(34?C?7wg^q z^}oRR7KeB`uU3k`j*HJ=j^N@CLGkg$s?%5rp9UrDd-_N}&M%wFL0nKa4Zcsd8hmv& z%0Y$J?KWC98P*Z_(Cks*0~%R-sh+6=UUF3M%K*y9#acycYtJdG0Ec~6L1_5M@o|qm z@${mV&tzD)mh9ep=r_|$Z=B4`eY;wF?_ciad_-&C&;3>jt|>v468ugHeyap;DZ%fR z;DQo-n2)$;a&7q(w=%a7{HJqwg8xMBZt#RebiMb=WAUX{vu$)KegByMag4da{q&iBi%G%JYPoI=$S%bD|xu$uR z1`6MZbU9cs+N5ksXf>5>7~CroZ{i5SDW6y@WUoxY$jqiA~s9MZUVGr{T5>rth`N36fMJ8HmY}l zXbkP7y-<=?O@vt%$9Tc!_Fxy!2Z852Pn7{O&p2GA7tgrf1?G8p#?h)+!zpWptx{y@ z=nm)f&Rj2#4u{U1uzIG5eG_L)6Oxs2ArsH-t`Zig8Eg71X7nIe&T9K#u9F&XdukZG1Qa0G@(YKnW-!7%M zTDRE>F(6feYSu~_d{3j#{kYF?Mc$gp+cxrIR<4VoqcU$h<2JUQ$lA84FnfDW7OKAr zj9yHNxjMSTIi+*EwyS$pEX@r)<9gs~)TRz+Ty|$=PFGe2y;ZH_&cE$tIOC!~u&7KZ zv9Yrfo+cCiiV6xKArTbT^aSvzcIbGdu3x%w47u zY|=1+{dj#4EJj#Jw4aODv&xXreqoAu|Ds+<5~| zjwa()C$+e{EoXQ^hFgS5ExbQIyv_0l?}uc6n0EdKxX&5*04v%d1 zZc(XHW^`C|#o7`LuefEuUxWU$7 zZ4etA4Z|m~mlD`hF1F$uwi2;h5PJf#Cz1JBO&tpQoaRu_CGbqg5&$9~HlotwkT4>- zueS_9!lmdsTx1d*i1qya-GGBWRyH*p_Sw`{r9Jx`Td=K|-%&H&LS@q8OO`FfY zci`1OeHe7_-%q`%+3A%q4d_+iR)bpuE>*0f?DR93oF+;JDME^rqNEYhuh`v7*|K1E zN3H1?B@gyybk>EE1_>`4FZZ{jQSM~{6GOfL_;X)vM(jjg37ofzMg7p#=KT)Sex2!M zWmD|(oV0%`{d9^8*$X(veWAv zou!(yaCaDYmQvCB%o-Z}6E(xZzf{A~TvAN|L|?42g8zQat13DK(IJLvist%Nt7xu& zwT9*fRBLJOfokueTz}w7_e+rd^|G&_`#0lA|3^)Gbf!JDp&HuqsrG^cezh(bu0uFa z!u|t;=|K&iLcU&d#vxC-yONK)(g35U(p66(5K2?63V<^%mSVr`w|X*>OdYVnllI19 z_+T-7;hurZ4pz(lQrR!?lmn(d)ii7vGG}^F2R%4lF7Z2e33u>smU89Jid`+1pSKvl z_A>9hNrzlD?`$#eJlJBq;%(lU4+a<@wHTc`AnSuKa2YMe%Np~}Hf~Le@hgaQa8I`w z9gEF7J54*n6$NA7@g=s~?4dYQvn8R~_>IoItH!jK;$oYPg2ue-OKxnl(dld6)uJEl z#Knd*W1hR(xR7Sdb5{rF(`zqbjQZgN{EAu`P#@e(;nYZ$~lJimmEi@N`qvhp}&T@yVn$pxDx{i`7+-K>=yO-`T zYXZ;Gv)s(FbQy6)xkK|otQMixo z#Zcj5dRfN}Qn;A*>bO9K%RRXXzp_m40l)%_Pl>uzbA}pz77YV+)GRhDpwc3q#~55i z9xlMm*h>lTCM9%K?)SuAVlK$=Q3}h;kMgfUJ<{$z_YU0S4K^pt$RUqfn32-igLOcI z!4B4eq73$S%ChZ~2ina6S9Qo>QO4d@bcOfP0!nzAaCq3KIqvXVD zD=t~F*X#u)Kf)!~f0PpT(fW@(C7azmdrpg9UFEGKdD|!j^S0B}OnZETb5|>vWI77j z;!JiUS7As=R8<>?MgUUrGwgj$p*y&6hr3V$Cmis%f&>sIOFsW}k zkf5S*>Y|%%6D!h(!T56{PQ=z9xhckbj5OLAFO4Zg#ne`ITX(y+w{^D*XJHy{TyTug zC_+W9l*Zy~{J5i|kB#BSM)Tvwvl9?L{3?81g>0$9sQOm;itt}N zl55B_jAj4nsgZf_8tz4SdN^KtgI?^_`ec@U!2|pYFyy881w8j?H3c;fcxoQF|C(-{ zXK)MEEsR^G0~)f$yuNvV%Yg)Skyo)6z;v|9da$De=J0lF5k7|vNzxCo;Qa{pB0OmH zYOD+t6-4!*W5tG*V#%ny>?Pq|gS=rX2 zYLMKP8a6`8uo=u*A!kt#Ze1f&+^m6ZIcR`1xlviXv_67ig(?p|eoC01;jgp{b=HVn z@EI!LEb{FlrFz&Xvjz57(Ea%Ehx^I}R8zh+2zKn$K23mB4p)u3AB0$~BL`fQJe+DY z1G~ts8qENEFa>4V06H*9rn|Qxg$3oKpp3g*LxbW36m`nr!Qpz9L;4lm+#-2f6fch8 zSp%OLVGs<75gGna9YzIs@%#1}Dh!no^^EQnFyv0g8I;Hxb$q0ikFxP2U?@9I8cszt zMc!(^5$ozGG89KJx~NDg(is;eMag5QGgRv$YtZn!R(9gUy}OvwEN?LOv(YREXI7%9fv0}2lG;B61^;=sDfI^K>{aC5F}SL{n={lLD&_gA!S(v0g- z18z0NJ#hQ@K)hGTb|$cnsXZ5A{jlSe*6us7QlMK#JfrBKM_&wT`Df&0GshJB#id~w+4 z<_nK!YtwUVTk^M;?m8$x{?=^R`1{j)_SOF}?B2hh`sS-2^|l>(!E3FZehHsPB1YKJ z`!F_qOgsgh*T*^wW9(H&82N8u3RzxdQ zq*IzIO@l`V{fKjRgp1RL59LFmo!%=SFF^&`>3!}fj_>!xapFK%0;gUPyY(zQX^ZS+ z%Ka$lr929THb?GlZ;7Ql@YV|kYj!e%o%qW@?(f26%Q9#OU-0+BRv8JGy?vlsAcb*| zlmE*+8Z*J%AflY04?zR%*p9Tj{|(us8rn3z^~MavG;D!8(;=tmwj)8@(a{!D=4f|Y zg0QMd>RyeLC zLekW7hdj{qh;s@BZ-nyb<0Mes>eNoZ3K+S~CJ`f){@Cf)9PMVc+d|v~%iI4fq5>4X z1-8_7Tp+~)W^`?vn-~+&RHdQ}T}3*;^r-<0>(aY`W`H?L!vv+XnBy?yo0y^CXTz40 zr;?wP!<5+*(j1wEpOI<&qzv;$2;?raY#5l7p(dUtqkgQk+UY$lBHJ&V?eC`2!yf8o z=^O>qeI=|ZeZ)ae=dv8_XHLUQ95$J=EG;w4?nYtMkDkW_kj+u0c9#SZ9bKK3@vzf5 z-4tw!$31f49^w9g%K^#MJtf2PdOEF=JB{ajj_?rpxzG<*)3*_IWja_b9@z1<(L~g1LjbS!7gqQu9PceJ3?vWHj<4PLH-Y5WT=D=J~kb zV}Sqd$O(WC+{3ys^7IX;pl@<8N?0vy2A2meADrC2Sn+V>6g;t^lvUAD5VN_p6+~a~ zC@zTeo;c?DVpjsEw)E0yTSZ3eIY-**Z6c%VP8EubcXw*L$oO=pibTe@J5?+)e%+}O zk@4?Nm5NM2cgij@f!(PcA`{e|+9@)@-Kkw7qwh}b7MYOlR2hKsD<{mQFewv|1nqjS zb#ljSgqk@m-~q@m6?<2|b0T&R6Q0D`j2bGb`p#eA*JlkV%Gk3CeC4Vd+%g{7TyVWd zw*(Y(!40t#%rDkvv2rR5Yl~Pp4Tco27yrKP@wmN!p6lg~UPj=??!TLN)MvE+a|X(U z)+r25#aB$PoRI@x1az&ZR!cB|a7~o;3!JIG@#DVU?p37yKd`pChfC2sK3E6^^Z#hD zmD+gR{^zy*{-fHwbt7-f;>At52!C%&cH=*kz5ovf*Z+= z;zo00xUt+gF1l{3p>CU@uFz2Tx}ol{p|0Fe_nM*ZRYTn&L)`&G-F`#eK11DJL*2`U zx|a-fWd`9^eTd7f)e2iv1}>b94gF^Om*$sh&4+5uTW=Zkqc7;k3^X014Q!EyeI2ny ziR>HT^XzHx&$GqgBlfEy?B((7*E3k>3|7cuzuCfG>13O;*v|t@w}MQ+=1kln)5k$3 zfirz|$nA5}ycUmTuW72hSpMZN;CcTD9*T6M` zdq*a{ib>ys`xxBYG6_ytcGp#K4dCi!(g{rZC%C_XtCLB`G3k47Ux0g0CLP11@4$Ty zu2v=;#Uv5jC2%z|=?Esl%69z~T(wMk1Czjb==u!YpJdXTm~KHZ0mJ+Q@%On`ZK%lLI#$=4R~X2l)=-A zOtys9P-B@_?o!LZ9J?D8ZxPiAnwmVCi>#h0he6e|%>AVMm4!J}v0SUC|r;@hdkJX@e5&^n`xQE#Nn&;!#HX_}mgJX4TC` zE`ymc>It|lHzRB`@Gyhta$t5Fn6+{Xfs0$JJ4<>>^_HoJ*q+z-Zt;1Om7i_n6Y(L} zWpu!2iMeWcrc-Z1*$`g#V&@?C5j8_anc&lv*`1Y%U7M{d!yBVkGzao)D1U4Z!IhjC z#v0lm;;`G~y3;D0mYK`>6|?dY9Cu1H@i2SrBl@Z5F|-~Ts}X)q zW1r#JZ5;cm8I+8RWBv^B0tU`JE-N@2xEH|j;9g`3!QUe1ZH3r2aG0_kT#=l&1M=7} z3Z=hEf0ucIvDgK-jk~eJSzB<{ot$vi4%h_0pk+q)ZD5)-PEq(!e3N+9O9z?D3nV{3cGEGzq((TIu7NQ9XNUP2Q8j5X)G7z!t%p zVz30W`Z)O94_~4EFX(*`H-*)Y?nM~gT{Of~G(e4u2C^ZZqB6mwJe6B%adt^gX)e!h z<}>s7jrm8jj&0hi+NK`(wu=hAKHn0>Z?^JzHa;J()%J4Ux|z4-Da#QX8RQTAxR3a3 zE1zTIb49~R>nU4BX643eq;9eJg#`*MF7;onU*5t7B}4elVB?P=TWv?J;ga`{M{TSP z8Mw~xIY~4KHsclT3ae{{%~fs?*u+fBoNP*9qZ|?|WX2@urA)zE#tOEw%mkGW_f5T! z83H6WL}Kf4h9t^a8p+0s)*hK(d<7=+Y~Gf`i@8Ep;-(BW-g-`Co!m6|w2K-7U3~Ot z_OYDfxn0InR~HyP`;IrsVep?bl=s%`Z8?Rxpq(~@cFOCl%vbEyT;-|kik!;aE?ouo zloRa9%!-Zp38e(%*OrHF)b9fW$t#l0P&GQQny}Qtb1{Z?!_mekUowE29?&@pVcm?U8s-E zl>F1{wS1hS~ zvJ0wqFn&mj;ok_Bls?(y0@K{oXKSMH``Of0PU&fBsbrCsNz0`b(n@KSv_?vm(%5HQ z>{{2!#iy23Jc(zPjIWelj^|&|;^HMGPcl(e={i2$s*g?M>`X&cI=o9hI8sQ5?&ApK zs(NN#)!Ij4BY^K5`Bauq%d{h(TKRt!^izwDTTj@OFUBg;H>2h;tv9B&U5jjxQm@Cl zq%|+$CVjn|HcvU*|;|FG}95yr2Rp~UhiJ2uQ zTNx7$CwnooarjUfGe-_jGLPaAxXyLcA9JVY&{-gLbe23-LTyodZ_#*f@d^zsN-ADdlI$5=hPP($w&yTJ-c@V8_>5=x=hg~O zXT;YF8D*)$b7g5slZ4dzvJ5FzNGp3zN;{c)BCUwt?uFl<2VYxY31Y3`@&P9{4Sx&B zeO;8gJ?+#p6>IqxLyz;Gq*i)z%+vo31bb}OD1ASgCR9#1;4=wve7 zRVm-Xr0t7}lDTRS128_@B?-(l=uuB$DpF6Sm8goV1>(ChPY@prI1-E27rhM>LZ zsUm6%?Y%|iy+s`wsx)vi`3#2=)Hx=u-x&*zByqlH5^!WLN9Q~N(y2f;);dM@jk2GG zrB$7ti~kQKiRQfcMF8|0&{gocQS5SxUE{-rvukwRU>qC3t_k9T*;N$h=Uy`ElGNff zVU=YGh}Eu)43^0=IH8^e&L+jOi)OOP9G~<&zi0!W%z$QmzSSAGp{;_!@|Ps@&s+Ho zHlD!%cR<(U1{BMy@9-g|HgF%1AkvBDP~caF+VY{F|X1eI!LT z3<;~>q7eN5#cClbaZzG&qB-N;ZR?BIl{||zNrr|^r9i1H&4`AnH2OHq+>`74^5ETL zZ}YyS{&=FmSFLzN;WO4j@4|fMzAS{<3vvgu)@aD+p9%{jmF}O$jThD>K5H2zSa8q&(6CiI$|^}@P+!n6C6rU*+d>Rcm7=Z3>SjP#hN7oh|!xMyL(1>!lr zPF`@&N@<+06KiHpo;kF^cj=Vk`2_Qdf85Id+-AVK8|IotRnFal!@*w3D%keH zmJ9z;!HS2^TWdwIL>+Drqr~$zOo@VjK82%6KjV-6l%t9E8|7$nnY-!SWpEhkF5=UP zf8#}GeVG+LQz(N9LZ6-v`Z3 z{P|o{VtD4aI|p#0-WX!gQiEI_75 zXD)aoad!CpnZHa-o)R9l66Y;QPBbliWWn6UkkO+iWG!2{a_QPto0cw5W2S_!Shjv7 z6AmP(XPoqe4_diuUEf?Fd2%(Bx7VwbMjPrFeWgE&qFAa-S3tA6GO7Sm(P&qHGy=Z8 z)|Gh`AdN1R8m(~`)9j#GRT;H|ss$e0s}xU#7L2yJGgSq2nWg};{Jbg1qC?S`X*6(} zMzcY{8b0kuE;&a79C3!+gyu~eGXsHmuf8J5!W?w7KU6^1>Zu?KA*4I0Sx6&9(JIPY z4WWvnkoWLl3jP^+HqvSeR6%frQ$tb(h20sMkYQCNKnB#LDRihcC;o-_;`rV12jbs| zN9a_%TIH=CIeq%{dDGJ%O`VHZ{W(5rL-=xb_3Bm6hcoHv;p@`ZufaouHmqWnuF5bj zOFbYBs zqRHqXIY&UrXg+eFuhC^WJx=z&Muy}hIkpHbgz*2pe3wB&{PV~c(M!HKRE^G}_fRcT zWvR2ypbB&v9Yx2{G3XoqQD|n~tUOa*LLSV+K7UGnU#gPEHSe9@H%Cc#Q#~<+;?NW{ z6-`6&XgZpK63|RE3z^Vtl=%ONeA5;z44*T9;mnljSZ4Ag2oMAa0t5kq06~BtKoB4Z z5CjMU1Ob8oL4Y7Y5FiK;1PB5I0fGQQfFM8+AP5iy2m%BFf&f8)AV3fx2oMAa0t5kq z06~BtKoB4Z5CjMU1Ob8oL4Y7Y5FiK;1PB5I0fGQQfFM8+AP5iy2m%BFf&f8)AV3fx z2oMAa0t5kq06~BtKoB4Z5CjMU1Ob8oL4Y7Y5FiK;1PB5I0fGQQfFM8+AP5iy2m%BF zf&f8)AV3fx2oMAa0t5kq06~BtKoB4Z5CjMU1Ob8oL4Y7Y5FiK;1PB5I0fGQQfFM8+ zAP5iy2m%BFf&f8)AV3fx2oMAa0t5kq06~BtKoB4Z5CjMU1Ob8oL4Y7Y5FiK;1PB5I z0fGQQfFM8+AP5iy2m%BFf&f8)AV3fx2oMAa0t5kq06~BtKoB4Z5CjMU1c9Fdf&T_a Cze(c& literal 0 HcmV?d00001 diff --git a/tests/fixtures/nuphy-air60_smk.hex b/tests/fixtures/nuphy-air60_smk.hex new file mode 100644 index 0000000..44bf2c2 --- /dev/null +++ b/tests/fixtures/nuphy-air60_smk.hex @@ -0,0 +1,3841 @@ +:100000000200713200000000000000320000000019 +:10001000000000320000000000000032000000007C +:10002000000000320000000000000032000000006C +:100030000000003200000000000000021329000050 +:10004000000000020F5E000000000032000000000F +:10005000000000320000000000000032000000003C +:100060000000003200000000000000020FDA020170 +:10007000F7758185123BFBE582600302006E79040F +:10008000E94400601B7A0190413F78A775A002E423 +:1000900093F2A308B8000205A0D9F4DAF275A0FF24 +:1000A000E478FFF6D8FD7800E84400600A7900752E +:1000B000A000E4F309D8FC78A7E84402600C7903B7 +:1000C000900000E4F0A3D8FCD9FA750D00750E007D +:1000D00075337575343E75357F75363EE4F51AF522 +:1000E0001BE4F540F54102006EAF82BFA502800A15 +:1000F000BFA6028009BFA70E8008900001229000D1 +:1001000002229000042290000022AF82BFA800507B +:10011000030201CFEF243D50030201CFEF2458FF2B +:10012000240A83F582EF241F83F583E47363676BEE +:100130006F737F878B838F9397A3A7ABAFB3B7C33F +:10014000777BBBBF9B9FC7CB01010101010101016F +:10015000010101010101010101010101010101018F +:100160000101019000E2229000E9229000EA229031 +:1001700000B5229000B6229000B3229000B42290E5 +:1001800000B7229000CC229000CD229001832290D3 +:10019000018A22900192229001942290019F229044 +:1001A00001CB22900221229002232290022422904D +:1001B000022522900226229002272290006F229090 +:1001C00000702290022A2290029F229002A0229088 +:1001D000000022120F5A120EDD120F9D1229C212B8 +:1001E0000483120ECE1210D543B54043B94043B537 +:1001F0004085B9B9D2AF221201D374FFC0E0743B7D +:10020000C0E07480C0E012344115811581158174FD +:100210000CC0E0743CC0E07480C0E0123441158131 +:10022000158115819003E8120EF4122C3B121FCF9A +:10023000122DE29003E8120EF475B100122C4C124C +:100240002DB41207F280F2AF82BFA502800ABFA6CA +:10025000028009BFA70E80089000012290000222B0 +:100260009000042290000022AF82BFA80050030239 +:10027000032DEF243D500302032DEF2458FF240AE1 +:1002800083F582EF241F83F583E473C1C5C9CDD103 +:10029000DDE5E9E1EDF1F50105090D111521D5D9EE +:1002A000191DF9FD252902020202020202020202C0 +:1002B0000202030303030303030202030302020314 +:1002C000039000E2229000E9229000EA229000B51B +:1002D000229000B6229000B3229000B4229000B782 +:1002E000229000CC229000CD229001832290018A9E +:1002F00022900192229001942290019F229001CBA2 +:100300002290022122900223229002242290022590 +:1003100022900226229002272290006F22900070E5 +:100320002290022A2290029F229002A02290000096 +:1003300022022D5D022D7AAE82AF839002A7E0FCEF +:10034000A3E0FDEEB50405EFB50501229002A7EE8E +:10035000F0EFA3F09000007401F0900001EEF0EFD8 +:10036000A3F0900000022D97AE82AF839002A9E027 +:10037000FCA3E0FDEEB50405EFB50501229002A94E +:10038000EEF0EFA3F09000007402F0900001EEF0A8 +:10039000EFA3F0900000022D97AF82BFA502800A64 +:1003A000BFA6028009BFA70E80089000012290001E +:1003B00002229000042290000022AF82BFA80050C9 +:1003C0000302047FEF243D500302047FEF2458FF13 +:1003D000240A83F582EF241F83F583E47313171B2C +:1003E0001F232F373B333F434753575B5F6367738D +:1003F000272B6B6F4B4F777B040404040404040425 +:1004000004040404040404040404040404040404AC +:100410000404049000E2229000E9229000EA229075 +:1004200000B5229000B6229000B3229000B4229032 +:1004300000B7229000CC229000CD22900183229020 +:10044000018A22900192229001942290019F229091 +:1004500001CB22900221229002232290022422909A +:10046000022522900226229002272290006F2290DD +:1004700000702290022A2290029F229002A02290D5 +:10048000000022750800750900750A007E007F00D3 +:10049000C3EE9410EF64809480501FEE2403F58225 +:1004A000EF3400F583E4F0EE2413F582EF3400F529 +:1004B00083E4F00EBE00D90F80D622E5822403F536 +:1004C00082E43400F583E0F5822285098222E58208 +:1004D000C42354E0FF24B4FDE4343EFEE50B250BB9 +:1004E000FC2DF582E43EF583E493FDA3E493FEE561 +:1004F0000C60067A3C7B3C80047A3F7B3C8B017B22 +:1005000080C007C006C005C004C002C001C003C04F +:1005100005C006742BC0E0743CC0E0EBC0E01234B0 +:1005200041E58124F8F581D004D005D006D0078DAF +:10053000028E03C3EA9420EB9452403F743F9A74B6 +:10054000529B4037E50C60098D03741F5BF50A80F0 +:100550000C90002575F000120E3C750A00AA0A7B6B +:1005600000C002C0037444C0E0743CC0E07480C0AA +:10057000E0123441E58124FBF58122E50A6033E590 +:100580000A75F0A0A424B4FA743E35F0FBEF2AFA01 +:10059000E43BFBEC2AF582E43BF583E493FCA3E423 +:1005A00093FF8C028F03BA0105BB000280048C0507 +:1005B0008F06850C198D828E83C006C005122D1FF3 +:1005C000E582D005D006700122850C1C8D828E83B9 +:1005D000C006C005123230E582D005D00670012277 +:1005E0008D048E07C3EC94E0EF9400403E74E79CCA +:1005F000E49F4037E50C60198D0374075BF5F00547 +:10060000F07401800225E0D5F0FBF582120E8B809C +:10061000178D0374075BF5F005F07401800225E087 +:10062000D5F0FBF582120E90020A00C3EC9404EFA1 +:100630009400402674A49CE49F401FE50C600D8D3F +:100640001290002575F000120DCC800B8D139000D8 +:100650002575F000120E04020A00C3EC94A5EF9475 +:1006600000403F74A79CE49F4038E50C602E8D034A +:10067000BBA502800ABBA602800BBBA714800C7A24 +:10068000017B0080107A027B00800A7A047B008064 +:10069000047A007B008A828B830203379000000279 +:1006A0000337C3EC94A8EF940050030207D8C37437 +:1006B000C29CE49F50030207D8E50C70030207D1E7 +:1006C0008D07BFA80050030207C4EF243D5003026A +:1006D00007C4EF2458FF240A83F582EF241F83F513 +:1006E00083E4731920272E354A585E51646A70825C +:1006F000888E949AA0B23C43A6AC767CB8BE07071D +:100700000707070707070707070707070707070779 +:100710000707070707070707077CE27F000207C8EC +:100720007CE97F000207C87CEA7F000207C87CB52D +:100730007F000207C87CB67F000207C87CB37F0039 +:100740000207C87CB47F000207C87CB77F0002079D +:10075000C87CCC7F000207C87CCD7F00806A7C8388 +:100760007F0180647C8A7F01805E7C927F0180585B +:100770007C947F0180527C9F7F01804C7CCB7F01E9 +:1007800080467C217F0280407C237F02803A7C244B +:100790007F0280347C257F02802E7C267F02802889 +:1007A0007C277F0280227C6F7F00801C7C707F0012 +:1007B00080167C2A7F0280107C9F7F02800A7CA0AA +:1007C0007F0280047C007F008C828F83120368800C +:1007D0000690000002036822C005C0067458C0E0FD +:1007E000743CC0E07480C0E0123441E58124FBF524 +:1007F00081221204CAE5827003F58222900023E46C +:10080000F0FFBF10005039900023E0FEE07031EFA0 +:100810002413F582E43400F583E0FD8F82C007C025 +:1008200006C0051204BBAC82D005D006D007EC622E +:1008300005EE4205900023ED24FFE433F00F80C263 +:10084000900023E0FFE07005F5098F82227F00BF52 +:100850001000506C8F82C0071204BBAE82D007EF2D +:100860002413F582E43400F583E0FDEE6205ED60CB +:100870004C9000247401F07C00BC050050339000C3 +:1008800024E0FB5D6021EE5203EB24FFE433F50C22 +:100890008F0B8C82C007C006C005C0041204CED0E6 +:1008A00004D005D006D0070C900024E025E0F080AD +:1008B000C8EF2413F582E43400F583EEF00F808F47 +:1008C000750900900023E0F58222122DE5E50970FC +:1008D000238508821229398508821229A3AF82C094 +:1008E000071229B5D007E5082403F582E43400F5A2 +:1008F00083EFF4F085084290006175F000122DF747 +:1009000074F125084007E50804F50880067508001D +:1009100075090102300BAF82BFA502800ABFA60293 +:100920008009BFA70E80089000012290000222904B +:1009300000042290000022AF82BFA80050030209E9 +:10094000FCEF243D50030209FCEF2458FF240A83E6 +:10095000F582EF241F83F583E4739094989CA0ACF8 +:10096000B4B8B0BCC0C4D0D4D8DCE0E4F0A4A8E8EB +:10097000ECC8CCF4F80909090909090909090909A8 +:1009800009090909090909090909090909090909D7 +:100990009000E2229000E9229000EA229000B52225 +:1009A0009000B6229000B3229000B4229000B722AB +:1009B0009000CC229000CD229001832290018A22C7 +:1009C000900192229001942290019F229001CB22CB +:1009D00090022122900223229002242290022522BA +:1009E000900226229002272290006F22900070220F +:1009F00090022A2290029F229002A02290000022C0 +:100A00001211C5AF82BF0109900063E06003020AC2 +:100A100062020A14900025E50DF0E50D450E9000E8 +:100A200025F075752D7576007577007578087579E0 +:100A30000090002575F000123383E5828583F04530 +:100A4000F0601E75752575760075770075780875E8 +:100A5000790090002D75F00012329A900025020363 +:100A600031229000357406F0900036E50DF0E50D6A +:100A7000450E900036F075754B75760075770075EC +:100A8000781675790090003575F000123383E58291 +:100A90008583F045F0601E757535757600757700B5 +:100AA00075781675790090004B75F00012329A90A7 +:100AB000003502033422AD82AE83AFF074022DFD07 +:100AC000E43EFE7C06C007C006C005C0041211C586 +:100AD000AB82D004D005D006D007BB010E900063D6 +:100AE000E060087D377E007F007C147B008C021C58 +:100AF000EA60158D828E838FF0123BA9FAA3AD8236 +:100B0000AE83EA60E80B80E58B8222AD82AE83AFD4 +:100B1000F0C007C006C0051211C5AC82D005D006D2 +:100B2000D007BC0142900063E0603C7C00BC140034 +:100B30005012EC2437FAE43400FB8A828B83E07095 +:100B4000030C80E98C03EBC40354F8FBEC2437F569 +:100B500082E43400F583E0F582C003120EA2AC8279 +:100B6000D003EC4BF5822274022DFDE43EFE8D8213 +:100B70008E838FF0123BA9F58222AD82AE83AFF057 +:100B8000E50F7003F58222C007C006C0051211C52B +:100B9000AC82D005D006D007BC014B900063E0606A +:100BA00045E50FC423541FFCBC14005035EC24371A +:100BB000F582E43400F583E50F5407FC8CF005F072 +:100BC0007C017B008006EC2CFCEB33FBD5F0F7E0DE +:100BD000F97A00E95204EA5203EC4B24FFE433F5BE +:100BE00082227582002274022DFDE43EFE7B007C91 +:100BF00000C3EB9406EC64809480501FEB2DF8EC5E +:100C00003EF98F02888289838AF0123BA9B50F04CE +:100C1000758201220BBB00D90C80D675820022851B +:100C2000827685837785F0787CFF74022576F9E4F7 +:100C30003577FAAB7889798A7A8B7B7800C3E86458 +:100C40008094865033E829FDE43AFE8B078D828E2E +:100C5000838FF0123BA9B57502801DBCFF17E825F4 +:100C600079FDE4357AFEAF7B8D828E838FF0123B67 +:100C7000A9700288040880C5B80622BCFF0122744E +:100C8000022576FDE43577FEAF78EC2DFCE43EFBE3 +:100C90008F028C828B838AF0E57502330E22AD823F +:100CA000AE83AFF074022DFDE43EFE8D778E788F1B +:100CB0007975760074FA25764044C005C006C007F1 +:100CC000E5762577F8E43578F9AF79888289838FDE +:100CD000F0123BA9B575028008D007D006D0058078 +:100CE00019D007D006D005E5762DF8E43EF98F043B +:100CF000888289838CF0E412330E057680B622ADAB +:100D000082AE83AFF0E510C423541FFCBC14005026 +:100D10003674022DFDE43EFEEC2DFCE43EFB8F021A +:100D2000E5105407F5F005F07401800225E0D5F0D8 +:100D3000FBFF8C828B838AF0123BA942078C828B4B +:100D4000838AF0EF02330EAE107F00C006C0077436 +:100D500073C0E0743CC0E07480C0E0123441E581AF +:100D600024FBF58122AD82AE83AFF0E511C423549C +:100D70001FFCBC1400503774022DFDE43EFEEC2D28 +:100D8000FCE43EFB8F02E5115407F5F005F0740119 +:100D9000800225E0D5F0FBF4FF8C828B838AF01271 +:100DA0003BA952078C828B838AF0EF02330EAE117F +:100DB0007F00C006C0077491C0E0743CC0E074803E +:100DC000C0E0123441E58124FBF58122AD82AE837F +:100DD000AFF0C007C006C0051211C5AC82D005D067 +:100DE00006D007BC0112900063E0600C85121090E1 +:100DF000003575F000020CFF8512758D828E838F91 +:100E0000F0020C1FAD82AE83AFF0C007C006C00574 +:100E10001211C5AC82D005D006D007BC01129000DB +:100E200063E0600C85131190003575F000020D65CC +:100E30008513758D828E838FF0020C9EAD82AE83FA +:100E4000AFF0C007C006C0051211C5AC82D005D0F6 +:100E500006D007BC0118900063E06012757500753C +:100E6000761475770090003775F0000232EF740247 +:100E70002DFDE43EFE7575007576067577008D8252 +:100E80008E838FF00232EF850D8222E582420D22A1 +:100E9000E582F4520D2285820D22750D0022850E09 +:100EA0008222AF827E00EFC4540FFD60048D077E66 +:100EB00004EF0303543FFD60088D078E0574022D77 +:100EC000FEEFC31360058E07EF04FE8E82229000B2 +:100ED00061E4F0900062F090006304F02275B208C3 +:100EE00075BC02E5BC20E20575B10080F643BC018B +:100EF00043B20422AE82AF837C007D00C3EC9EED42 +:100F00009F501D9003E8C007C006C005C004120F23 +:100F100021D004D005D006D0070CBC00DF0D80DC4A +:100F200022AE82AF837C007D00C3EC9EED9F501FFC +:100F300075B100000000000000000000000000008B +:100F400000000000000000000CBC00DD0D80DA2273 +:100F5000C2AF75F0A5745A02FF00758F0122C0213F +:100F6000C0E0C0F0C082C083C007C006C005C00496 +:100F7000C003C002C001C000C0D075D0001208CAB2 +:100F8000D0D0D000D001D002D003D004D005D006FC +:100F9000D007D083D082D0F0D0E0D0213275D840B5 +:100FA00075ADFF75AEE675AF0075870075AB007562 +:100FB000AC0053A9BFC20010D902800010D8028033 +:100FC0000043A94022AF8253A9BFD2008FAA43A9F0 +:100FD0004030000575B10080F82253A9BF10D90236 +:100FE0008002C20043A94032020FC5AF82BFA502F2 +:100FF000800ABFA6028009BFA70E800890000122C8 +:10100000900002229000042290000022AF82BFA82C +:101010000050030210D1EF243D50030210D1EF2401 +:1010200058FF240A83F582EF241F83F583E4736558 +:10103000696D717581898D85919599A5A9ADB1B5B8 +:10104000B9C5797DBDC19DA1C9CD1010101010107A +:101050001010101010101010101010101010101090 +:1010600010101010109000E2229000E9229000EA87 +:10107000229000B5229000B6229000B3229000B4D6 +:10108000229000B7229000CC229000CD22900183C4 +:101090002290018A22900192229001942290019F35 +:1010A000229001CB2290022122900223229002243E +:1010B0002290022522900226229002272290006F81 +:1010C000229000702290022A2290029F229002A079 +:1010D0002290000022900264E4F0900265F09002F9 +:1010E00066F0900267F0900268F0C20190026AE434 +:1010F000F0900269F0F522F523F524F525F526F5A3 +:101100009675945F7595117591C043A90122AE82C1 +:10111000AF837D00BDFF00501AE59930E2159000C5 +:1011200028C007C006C005120F21D005D006D00781 +:101130000D80E17D007531088E828F838DF0121D48 +:1011400066539CE0439C0843990422AE82AF837DA2 +:1011500000BDFF00501AE59A30E215900028C00744 +:10116000C006C005120F21D005D006D0070D80E1C2 +:101170007D007532168E828F838DF0121DCF539DA8 +:1011800080439D16439A0422AE82AF837D00BDFF4B +:1011900000501AE59A30E215900028C007C006C03A +:1011A00005120F21D005D006D0070D80E17D007516 +:1011B00032038E828F838DF0121DCF539D80439D0D +:1011C00003439A0422900267E0F58222C2AF9002A4 +:1011D0006B75F000121D48D2AF90026BE0FF9002D9 +:1011E0006CE0FE8F05BD0002802CBD01030212766B +:1011F000BF02030212ABBF21030212D8BF800280DC +:1012000050BF8103021286BF82030212C8BFA1032E +:10121000021300021322BE01028014BE03028015D5 +:10122000BE05028016BE07028017BE091D801590FC +:10123000026B02144090026B02146E90026B021457 +:10124000050215EE90026B0215484397084397027A +:1012500022BE0002800ABE0602800BBE0811800C6E +:1012600090026B0215F590026B02171B021AD04315 +:10127000970843970222BE0B0690026B0215A0430B +:10128000970843970222BE0002800ABE0602800B26 +:10129000BE0A11800C90026B02163090026B02178E +:1012A0001B021AE843970843970222BE01028005F9 +:1012B000BE030E800690026B02149C90026B021417 +:1012C000FA43970843970222BE000690026B02166B +:1012D0007343970843970222BE0902800ABE0A029E +:1012E000800BBE0B14800C90026B021B0490026BEF +:1012F000021B5F90026B021B8143970843970222F7 +:10130000BE0102800ABE02028008BE030E800602F1 +:101310001AFD021B6F90026B021BB643970843979E +:10132000022243970843970222C021C0E0C0F0C0C8 +:1013300082C083C007C006C005C004C003C002C08D +:1013400001C000C0D075D000AF92AE93EF6056EFF1 +:1013500030E3065392F70213E85392EF53929FEF54 +:1013600030E70953927F1210D50213E8EF30E408FA +:101370005392EF1211CC8070EF30E2055392FB8054 +:1013800067EF30E1055392FD805EEF30E05A5392F3 +:10139000FE4391200000000000005391DF1210D5A1 +:1013A0004397018043EE6040EE30E6085393BF431D +:1013B0009A018034EE30E5055393DF802BEE30E464 +:1013C0000B5393EF121BE4439701801CEE30E205B0 +:1013D0005393FB8013EE30E1055393FD800AEE300A +:1013E000E0065393FE121C27D0D0D000D001D002CB +:1013F000D003D004D005D006D007D083D082D0F05F +:10140000D0E0D02132AE82AF8390026AE4F0740261 +:101410002EFEE43FFF8E828F83E0FCA3E04C600849 +:101420009002647401F08005900264E4F08E828F73 +:1014300083E0900265F0539BF043970243970422A8 +:10144000AE82AF8390026AE4F08E828F83E0540F05 +:10145000600280138E828F83A3A3E0FEA3E0FFBE11 +:101460000105BF0002C201539BF043970422AE82E4 +:10147000AF8390026AE4F08E828F83E0540F6002A3 +:1014800080138E828F83A3A3E0FEA3E0FFBE01053D +:10149000BF0002D201539BF043970422AE82AF8378 +:1014A00090026AE4F08E828F83E0FD53050FBD0247 +:1014B000428E828F83A3A3E0FCA3E04C70358E8222 +:1014C0008F83A3A3A3A3E0FEA3E0FF4E700853976E +:1014D000F75397FD801DBE8108BF00055399F78023 +:1014E00012BE8208BF0005539AF78007439708434E +:1014F000970222539BF043970422AE82AF8390025F +:101500006AE4F08E828F83E0FD53050FBD022B8EBF +:10151000828F83A3A3A3A3E0FEA3E0FFBE8108BF45 +:101520000005439908801ABE8208BF0005439A0847 +:10153000800F439708439702800743970843970219 +:1015400022539BF043970422AE82AF8390026AE459 +:10155000F0E596603E8E828F83A3A3E0FEA3E0FFBA +:101560004E600A8E048F05BC0121BD001E900266EC +:10157000EEF0EE60089002647402F080069002645F +:101580007401F0539BF0439704800E4397084397F0 +:10159000028006439708439702539BF04397042227 +:1015A000AE82AF8390026AE4F08E828F83A3A3E0C1 +:1015B000FCA3E04C70318E828F83A3A3A3A3E0FE33 +:1015C000A3E0FF4E700A539BF04397044397022217 +:1015D000BE010DBF000A539BF043970443970222BC +:1015E0004397084397022243970843970222439761 +:1015F0000843970222AE82AF8390026A7402F08E93 +:10160000828F83E0540F6002801F30010890110820 +:101610007402F08005901108E4F0901109E4F05391 +:101620009BF0439B0243970422439708439702226F +:10163000AE82AF83E0FD53050FBD01308E828F83F4 +:10164000A3A3A3A3E0FEA3E0FF4E6006BE0116BF66 +:101650000013901108E4F0901109F0539BF0439BA4 +:101660000243970422439708439702224397084373 +:10167000970222AE82AF83E0FD53050FBD020280C8 +:10168000030217148E828F83A3A3A3A3E0FEA3E01B +:10169000FFBE801BBF0018AC97530402E4A2E713FF +:1016A000CC13CC901108ECF0901109E4F0805BBEF3 +:1016B0008127BF0024AC99530408E423CCC42354ED +:1016C0001F6CCC541FCC6CCC30E40244E090110869 +:1016D000ECF0901109E4F08031BE8227BF0024AE07 +:1016E0009A530608E423CEC423541F6ECE541FCE53 +:1016F0006ECE30E40244E0901108EEF0901109E45F +:10170000F0800743970843970222539BF0439B02C4 +:1017100043970422439708439702228582278583B3 +:1017200028A3A3E0FCA3E0FBBB0141903DD0E493E0 +:10173000FAA3E493FD8A758D767577808A828D830E +:10174000E493FA8A7875790090006475F00012329B +:101750009A9002737464F07400A3F0E4A3F0900014 +:1017600064E0900276F0E4A3F0021A8DBB020280DE +:101770000302188A903DD4E493FDC3EC9D4003021C +:10178000188A903DD5E493FAA3E493FDEC75F0023A +:10179000A42AF582ED35F0F583E493FAA3E493FDF2 +:1017A00074092AF529E43DF52A8A008D0188758996 +:1017B000767577808A828D83E493F98978757900CC +:1017C00090006475F000C005C00212329AD002D0B9 +:1017D000058A828D83E4932464F52BE43400F52C90 +:1017E000A829A92A852B2D852C2E752F0088828962 +:1017F00083E493FAA3E493FF8A758F767577808AE2 +:10180000828F83E493FA8A78757900852D82852EFC +:1018100083852FF0C001C00012329AD000D0018819 +:10182000828983E493FEA3E493FF8E828F83E49303 +:10183000252BF52BE4352CF52C740228F8E439F926 +:1018400088828983E493FEA3E4934E7097900066A8 +:10185000E0FEA3E04E7013E52BC39464FEE52C94E8 +:1018600000FF900066EEF0EFA3F09002737464F056 +:101870007400A3F0E4A3F0900066E0FEA3E0FF9004 +:101880000276EEF0EFA3F0021A8DE4BB030104FF31 +:10189000603BEC70387575C675763D757780903D08 +:1018A000C6E493FE8E7875790090006475F000129E +:1018B000329A9002737464F07400A3F0E4A3F09081 +:1018C0000064E0900276F0E4A3F0021A8DEF70035A +:1018D00002197F7F00EC24FFFDEF34FFFE903DD71F +:1018E000E493F97A00C3ED99EE64808AF063F080A6 +:1018F00095F0400302197F903DD8E493FDA3E49353 +:10190000FE1CBCFF011FEC2CFCEF33FFEC2DF5821D +:10191000EF3EF583E493FEA3E493FF90006474022A +:10192000F090006504F074012465FCE43400FD8E41 +:10193000828F83E493FA602C0EBE00010F8C828D9F +:1019400083EAF074012CF9E43DFA89828A83E4F099 +:101950000429FCE43AFD900064E0FA0A0A9000646D +:10196000EAF080CB9002737464F07400A3F0E4A3F7 +:10197000F0900064E0900276F0E4A3F0021A8DBBD0 +:101980002276852782852883A3A3A3A3E0FE702F58 +:101990009002767444F0E4A3F07575AF75763C75EB +:1019A0007780757844F579900064F5F012329A905A +:1019B00002737464F07400A3F0E4A3F0021A8DBE05 +:1019C000012F9002767476F0E4A3F07575F37576C6 +:1019D0003C757780757876F579900064F5F0123271 +:1019E0009A9002737464F07400A3F0E4A3F0021AF6 +:1019F0008D43970843970222BB21028003021A8677 +:101A0000852782852883A3A3A3A3E0FE703775757D +:101A10008475763D757780903D84E493FF8F78756B +:101A2000790090006475F00012329A900273746429 +:101A3000F07400A3F0E4A3F0900064E0900276F06C +:101A4000E4A3F08048BE013775759D75763D7577C6 +:101A500080903D9DE493FF8F7875790090006475C8 +:101A6000F00012329A9002737464F07400A3F0E4F0 +:101A7000A3F0900064E0900276F0E4A3F0800E43BF +:101A800097084397022243970843970222852782AB +:101A9000852883A3A3A3A3A3A3E0FEA3E0FF900252 +:101AA00076E0FCA3E0FDC3EE9CEF9D40048C068D28 +:101AB00007900273E0FBA3E0FCA3E0FD8E758F7638 +:101AC0008B828C838DF0121C50121C604397042271 +:101AD00090026A7402F0900266E0901108F0539B45 +:101AE000F0439B014397042290026A7402F0901124 +:101AF00008E4F0539BF0439B01439704224397086B +:101B000043970222AE82AF83A3A3E0FCA3E0FABA1C +:101B100002028005BA0341802D8E828F83A3A3A386 +:101B2000A3E0FAA3E04A703674062EF582E43FF58E +:101B300083E0FEA3E0FFBE0125BF002290026A748D +:101B400004F0439701227D00BC0513BD00109002F4 +:101B50006A7405F04397012243970843970222A332 +:101B6000A3E0A3E0900269F0539BF0439704229016 +:101B70000269E0901108F0539BF0439B01439704E6 +:101B800022AE82AF83A3A3A3A3E0FCA3E0FD4C702D +:101B90000D8E828F83A3A3E0900267F08011BC01B9 +:101BA0000EBD000B8E828F83A3A3E0900268F053DA +:101BB0009BF043970422A3A3A3A3E0FEA3E0FF4E60 +:101BC000700A900267E0901108F0800EBE010BBF12 +:101BD0000008900268E0901108F0539BF0439B01CD +:101BE0004397042290026AE0FFBF041490026AE463 +:101BF000F0901100E0900061F0539BF043970422B5 +:101C0000BF051890026AE4F0901100E0FFBF0516CE +:101C1000901101E0FFBF750E020F5090026AE4F0D0 +:101C2000539BF04397042290026AE0FFBF010A121F +:101C30001C6043970443970122BF02074397014367 +:101C4000970822439708439701900265E0F5962292 +:101C500085822485832585F0268575228576232235 +:101C6000E5224523700D90026A7402F0539BF085C3 +:101C70009B9B22AE22AF23C374089EE49F503790F3 +:101C8000026A7401F07530088524828525838526D3 +:101C9000F0121CFDAC22AD23EC24F8FCED34FFFD6A +:101CA0008C228D2374082524F524E43525F525534D +:101CB0009BF0439B0822BE0824BF002190026A7457 +:101CC00001F07530088524828525838526F0121C55 +:101CD000FDE4F522F523539BF0439B082290026A12 +:101CE0007402F08522308524828525838526F012B2 +:101CF0001CFD539BF0AF9BE5224FF59B22AD82AEBE +:101D000083AFF0E53024F7502AAB307C00C003C02D +:101D10000474F1C0E0743DC0E07480C0E074DAC0C7 +:101D2000E0743DC0E07480C0E0123441E58124F8E5 +:101D3000F581228D758E768F778530787579009054 +:101D4000110875F00002329AAD82AE83AFF075755E +:101D5000007576117577007578087579008D828E1B +:101D6000838FF002329AAD82AE83AFF0E53124EF7B +:101D7000502AAB317C00C003C0047403C0E0743E41 +:101D8000C0E07480C0E074DAC0E0743DC0E07480EC +:101D9000C0E0123441E58124F8F581228D758E76FC +:101DA0008F7785317875790090112075F0000232B7 +:101DB0009AAD82AE83AFF0757510757611757700A8 +:101DC0007578107579008D828E838FF002329AAD0E +:101DD00082AE83AFF0E53224BF502AAB327C00C024 +:101DE00003C0047415C0E0743EC0E07480C0E074A9 +:101DF000DAC0E0743DC0E07480C0E0123441E58197 +:101E000024F8F581228D758E768F778532787579F5 +:101E10000090118075F00002329AAD82AE83AFF06F +:101E20007575407576117577007578407579008DF8 +:101E3000828E838FF002329A85821685831785F011 +:101E400018C2AFC285900003120F21C2FC7B007C38 +:101E500000C3EB9514EC64808515F063F08095F079 +:101E6000503FEB2516F8EC3517F9AA1888828983BC +:101E70008AF0123BA9F582C004C003C002C001C0B1 +:101E800000121EAAAF82D000D001D002D003D0042D +:101E9000888289838AF0EF12330E0BBB00B30C806B +:101EA000B0D2FCD287D285D2AF22AF827E007D0035 +:101EB000BD0800502D8E04EC2CFEC2B700000000BF +:101EC00000000000EF30E704D2878002C28730862E +:101ED00003430601D2B7000000008F04EC2CFF0D75 +:101EE00080CE8E8222AF82BFA502800ABFA602806A +:101EF00009BFA70E800890000122900002229000E6 +:101F0000042290000022AF82BFA8005003021FCB22 +:101F1000EF243D5003021FCBEF2458FF240A83F522 +:101F200082EF241F83F583E4735F63676B6F7B83AA +:101F3000877F8B8F939FA3A7ABAFB3BF7377B7BBDD +:101F4000979BC3C71F1F1F1F1F1F1F1F1F1F1F1F61 +:101F50001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F9020 +:101F600000E2229000E9229000EA229000B522903F +:101F700000B6229000B3229000B4229000B72290C5 +:101F800000CC229000CD229001832290018A2290E1 +:101F90000192229001942290019F229001CB2290E5 +:101FA00002212290022322900224229002252290D4 +:101FB0000226229002272290006F22900070229029 +:101FC000022A2290029F229002A022900000221258 +:101FD00028B8900006120EF490029875F0001223B3 +:101FE000269000C8120F2185333D85343E753F8011 +:101FF0007582001227B590000C120EF412268D90F7 +:102000000020120EF485353D85363E753F80758281 +:10201000011227B590000C120EF412268D90001EAE +:10202000120EF47538007582001223C0900014124D +:102030000EF47538007582001223C090000F120E46 +:10204000F4E4F53BF53C9000001226C490001E120B +:102050000EF40222CDAE82AF83E090029AF08E821F +:102060008F83A3A3E090029BF08E828F83A3A3A310 +:10207000E090029CF08E828F83A3A3A3A3E0900242 +:102080009DF08E828F83A3A3A3A3A3E090029EF072 +:1020900074062EF582E43FF583E090029FF07537D9 +:1020A000017E007F00C3EE9406EF64809480501997 +:1020B000EE249AFCEF3402FD8C828D83E060037580 +:1020C00037000EBE00DF0F80DCE537606190029BB9 +:1020D000E4F090029AF5F012240490029B7401F04F +:1020E00090029A75F00012240490029BE4F0900292 +:1020F0009AF5F012240490029B7401F090029A75F4 +:10210000F00012240490029BE4F090029AF5F01281 +:10211000240490029B7401F090029A75F00012243E +:102120000490029BE4F090029AF5F00224049002DD +:102130009A75F000022404AE82AF839002A074016D +:10214000F0FC7D00C3EC9415ED648094805019EC94 +:102150002EFAED3FFB8A828B83E060059002A0E4BB +:10216000F00CBC00DF0D80DC9002A0E0FDFB33959D +:10217000E0FCC007C006C005C003C0047450C0E046 +:10218000743EC0E07480C0E0123441E58124FBF568 +:1021900081D005D006D007ED70030221FE90029B8E +:1021A000E4F090029AF5F012240490029B7401F07E +:1021B00090029A75F00012240490029BE4F09002C1 +:1021C0009AF5F012240490029B7401F090029A7523 +:1021D000F00012240490029BE4F090029AF5F012B1 +:1021E000240490029B7401F090029A75F00012246E +:1021F0000490029BE4F090029AF5F00224048E828F +:102200008F83A3E0FD74022EF539E43FF53A8D8209 +:102210000224E0AE82AF83E0FDBD01028005BD0275 +:102220002A80128E828F83A3E0F53BA3E0F53C90D9 +:1022300000000226C48E828F83A3E0FEA3E0FFE4A9 +:10224000F53BF53C8E828F830226C422AD82AE839D +:10225000AFF09002A175F000C007C006C0051223C0 +:1022600026D005D006D0079002A2E0FC5304078DCB +:10227000828E838FF0EC12330E0DBD00010E8D8225 +:102280008E838FF0123BA9FC9002A2E05460C4033D +:102290005407FB8D828E838FF012330EECB5030151 +:1022A000228D828E838FF0123BA9FD7F00C005C076 +:1022B00007745FC0E0743EC0E07480C0E012344137 +:1022C000E58124FBF581227538000223C090027855 +:1022D00074AAF0900279741DF090027A7402F07E74 +:1022E000037F00C3EE941FEF648094805013EE24AC +:1022F00078F582EF3402F583E4F00EBE00E50F803E +:10230000E275751F75760090027875F0001228F658 +:10231000AF82900297EFF075142075150090027847 +:1023200075F000021E38AD82AE83AFF0C007C00664 +:10233000C00512283C900064120F2112239FD00583 +:10234000D006D007900278E0FCBCBB028004758206 +:10235000002275750275760090027A75F000C0074C +:10236000C006C0051228F6AC82D005D006D0079072 +:102370000279E0B5040280047582002290027AE0BE +:102380008D828E838FF012330E0DBD00010E9002F0 +:102390007BE08D828E838FF012330E758201229046 +:1023A000027874FFF0900279F090027AF090027B4C +:1023B000F075140475150090027875F000021E384F +:1023C000AF8290027874AAF09002797403F09002C0 +:1023D0007A7401F090027BE538F090027CEFF075A2 +:1023E000750575760090027875F0001228F6AF82B8 +:1023F00090027DEFF075140675150090027875F067 +:1024000000021E38AD82AE83AFF090027874AAF05D +:10241000900279741DF090027A7402F08D828E839E +:102420008FF0123BA990027BF074012DFAE43EFB81 +:102430008F048A828B838CF0123BA990027CF0740B +:10244000022DFAE43EFB8F048A828B838CF0123BD0 +:10245000A990027DF074032DFAE43EFB8F048A827A +:102460008B838CF0123BA990027EF074042DFAE469 +:102470003EFB8F048A828B838CF0123BA990027FF3 +:10248000F074052DFDE43EFE8D828E838FF0123BAD +:10249000A9900280F0900281E4F07E0AFFC3EE94DE +:1024A0001FEF648094805013EE2478F582EF34029D +:1024B000F583E4F00EBE00E50F80E275751F7576BA +:1024C0000090027875F0001228F6AF82900297EF24 +:1024D000F075142075150090027875F000021E3812 +:1024E000E5829002A3F090027874AAF090027974C9 +:1024F0001DF090027A7402F09002A3E090027BF04B +:1025000090027CE4F090027DF090027EF090027FD9 +:10251000F0900280F0900281F0AE39AF3A8E828F57 +:1025200083E0900282F08E828F83A3E0900283F09A +:102530008E828F83A3A3E0900284F08E828F83A388 +:10254000A3A3E0900285F08E828F83A3A3A3A3E0D0 +:10255000900286F08E828F83A3A3A3A3A3E09002B0 +:1025600087F074062EF582E43FF583E0900288F050 +:1025700074072EF582E43FF583E0900289F0740839 +:102580002EF582E43FF583E090028AF074092EF57F +:1025900082E43FF583E090028BF0740A2EF582E42A +:1025A0003FF583E090028CF0740B2EF582E43FF54A +:1025B00083E090028DF0740C2EF582E43FF583E009 +:1025C00090028EF0740D2EF582E43FF583E09002C8 +:1025D0008FF0740E2EF582E43FF583E0900290F0C8 +:1025E000740F2EF582E43FF583E0900291F07410B1 +:1025F0002EF582E43FF583E0900292F074112EF5FF +:1026000082E43FF583E0900293F074122EF582E4A9 +:102610003FF583E0900294F074132EF582E43FF5C9 +:1026200083E0900295F0900296E4F075751FF576C0 +:10263000900278F5F01228F6AF82900297EFF075CD +:10264000142075150090027875F000021E38AF82D4 +:1026500090027874AAF09002797403F090027AF0F4 +:1026600090027BEFF090027CE4F0757505F57690B2 +:102670000278F5F01228F6AF8290027DEFF0751423 +:102680000675150090027875F000021E38900278E9 +:1026900074AAF09002797401F090027A7404F075D3 +:1026A000750375760090027875F0001228F6AF82F7 +:1026B00090027BEFF075140475150090027875F0A8 +:1026C00000021E38AE82AF8390027874AAF09002A6 +:1026D00079740BF090027A7405F090027BE4F0902C +:1026E000027CF090027DF090027EF090027FF090EC +:1026F0000280F08E05900281EDF08F06900282EE4E +:10270000F0AF3B900283EFF0AF3C900284EFF075A6 +:10271000750D75760090027875F0001228F6AF827C +:10272000900285EFF075140E75150090027875F023 +:1027300000021E38AF8290027874AAF09002797479 +:1027400003F090027A23F090027BEFF090027CE499 +:10275000F0757505F576900278F5F01228F6AF82DF +:1027600090027DEFF075140675150090027875F0F3 +:1027700000021E38AF8290027874AAF09002797439 +:1027800003F090027A7407F090027BEFF090027CE5 +:10279000E4F0757505F576900278F5F01228F6AF3D +:1027A0008290027DEFF07514067515009002787521 +:1027B000F000021E38AF8290027874AAF09002797D +:1027C000741DF090027A7408F090027BEFF0AD3D3A +:1027D000AE3EAF3F7B047C008D828E838FF0123B38 +:1027E000A9FA601190027CEAF00DBD00010E0BBB4E +:1027F00000E60C80E38B068C07C3EE941FEF648029 +:1028000094805013EE2478F582EF3402F583E4F0DF +:102810000EBE00E50F80E275751F75760090027898 +:1028200075F0001228F6AF82900297EFF075142031 +:1028300075150090027875F000021E3890027874C9 +:10284000AAF09002797403F090027A740AF0900270 +:102850007BE4F090027CF0757505F576900278F5D2 +:10286000F01228F6AF8290027DEFF0751406751510 +:102870000090027875F000021E3890027874AAF079 +:102880009002797403F090027A740BF090027BE46A +:10289000F090027CF0757505F576900278F5F012EF +:1028A00028F6AF8290027DEFF07514067515009042 +:1028B000027875F000021E3890027874AAF0900237 +:1028C000797403F090027A740CF090027BE4F0903B +:1028D000027CF0757505F576900278F5F01228F611 +:1028E000AF8290027DEFF0751406751500900278A6 +:1028F00075F000021E3885827785837885F0797CB3 +:10290000007A007B00C3EA9575EB64808576F063FE +:10291000F08095F0501CEA2577F8EB3578F9AF791F +:10292000888289838FF0123BA92CFC0ABA00D60B4F +:1029300080D37455C39CF58222AF82439020439884 +:102940003F43A03F438807EF24F0500122EF2F9030 +:10295000295373801E801F80208021802280238045 +:102960002480258026802780288029802A802B802B +:102970002C802DC28822C28922C28A22C2A522C2EC +:10298000A422C2A322C2A222C2A122C2A022C29D0C +:1029900022C29C22C29B22C29A22C29922C298229F +:1029A000C29522E5F8C313FF530707E58854184F73 +:1029B00044E0F582225390DF5398C053A0C053885F +:1029C000F8221229CC122A1843A90222758C057507 +:1029D000A500758C4575A600758C8575BB00758C3A +:1029E000C5758D00758C0075E11C75E22E75E33F91 +:1029F00075E43F75E57875E68775E7FE75EE7875E1 +:102A0000D90E43F81043B0804380A043D11043E572 +:102A10008043E1A043E9402290FF9C7404F090FFC2 +:102A200098E4F090FF9D7404F090FF99E4F090FF1B +:102A30009E7404F090FF9AE4F075DE04F5DD75D520 +:102A400004F5D2F5C3F5CE75D604F5D3F5C4F5C1BA +:102A500075D704F5D4F5C5F5C290FFBD7404F090A8 +:102A6000FFA5E4F090FFEDF090FFD5F090FFBC746F +:102A700004F090FFA4E4F090FFECF090FFD4F0900D +:102A8000FFBB7404F090FFA3E4F090FFEBF090FF25 +:102A9000D3F090FFBA7404F090FFA2E4F090FFEA44 +:102AA000F090FFD2F090FFB97404F090FFA1E4F031 +:102AB00090FFE9F090FFD1F090FFB87404F090FF20 +:102AC000A0E4F090FFE8F090FFD0F090FFC3740412 +:102AD000F090FFABE4F090FFF3F090FFDBF090FF9D +:102AE000C27404F090FFAAE4F090FFF2F090FFDAD5 +:102AF000F090FFC17404F090FFA9E4F090FFF1F0B2 +:102B000090FFD9F090FFC07404F090FFA8E4F0901B +:102B1000FFF0F090FFD8F090FFBF7404F090FFA793 +:102B2000E4F090FFEFF090FFD7F090FFBE7404F058 +:102B300090FFA6E4F090FFEEF090FFD6F090FFC972 +:102B40007404F090FFB1E4F090FFF9F090FFE1F031 +:102B500022AF82BFA502800ABFA6028009BFA70ECE +:102B60008008900001229000022290000422900030 +:102B70000022AF82BFA8005003022C37EF243D5043 +:102B800003022C37EF2458FF240A83F582EF241F19 +:102B900083F583E473CBCFD3D7DBE7EFF3EBF7FB1E +:102BA000FF0B0F13171B1F2BDFE3232703072F3305 +:102BB0002B2B2B2B2B2B2B2B2B2B2B2B2C2C2C2C61 +:102BC0002C2C2C2B2B2C2C2C2C2C2C9000E22290FF +:102BD00000E9229000EA229000B5229000B62290EF +:102BE00000B3229000B4229000B7229000CC229033 +:102BF00000CD229001832290018A2290019222909E +:102C000001942290019F229001CB229002212290D8 +:102C10000223229002242290022522900226229052 +:102C200002272290006F229000702290022A2290A8 +:102C3000029F229002A02290000022A28DE43390F5 +:102C400002A4F0A28EE4339002A5F0229002A4E048 +:102C5000FFA28DE433FEEFB50602803EA28DE43381 +:102C60009002A4F09002A4E0FF601ABF012C7489C6 +:102C7000C0E0743EC0E07480C0E01234411581159C +:102C800081158180157494C0E0743EC0E07480C0EA +:102C9000E01234411581158115819002A5E0FFA253 +:102CA0008EE433FEEFB5060122A28EE4339002A536 +:102CB000F09002A5E0FF6019BF012B749EC0E07484 +:102CC0003EC0E07480C0E012344115811581158149 +:102CD0002274A9C0E0743EC0E07480C0E0123441A8 +:102CE00015811581158122AE82AF83BE4005BF7E5E +:102CF000028024BE4105BF7E028010BE4205BF7E19 +:102D000002800CBE4315BF7E128008900001229005 +:102D1000000222900003229000002290FFFF22AECA +:102D200082AF838E048F05BC4005BD7E028016BC39 +:102D30004105BD7E02800EBC4205BD7E028006BC00 +:102D40004317BD7E149002A4E0700A8E828F831216 +:102D50002CE71222C77582002275820122AE82AF53 +:102D6000839002A4E0FD600ABD010E8E828F830273 +:102D7000110E8E828F8302205522AE82AF83900285 +:102D8000A4E0FD600ABD010E8E828F8302114B8E7E +:102D9000828F8302213722AE82AF839002A4E0FDAE +:102DA000600ABD010E8E828F830211888E828F830E +:102DB000022213229002A4E0701FAE1AAF1BC3744C +:102DC000209E744E9F5012C2AF90006175F00012A9 +:102DD000224CE4F51AF51BD2AF051AE4B51A020528 +:102DE0001B220230B15380E35390F153B0875388D4 +:102DF0007F53C0010230FD85824385834485F045C1 +:102E0000E5427016AC4174F42C400A0540E4B5402C +:102E10000905418005E4F540F541E4FBFCF9FAF5CC +:102E200046F54785404885414974FC2549404AA8F4 +:102E30004874042549FF5307078882EF24FCF58373 +:102E4000C002C001123329AE82AF83E4C39EF546AF +:102E500074049FF547AE40AF415307078E82EF24BD +:102E6000FCF583123329AE82AF83D001D002E4C3D4 +:102E70009EFB74049FFC022F1474F825494046AE53 +:102E800048740755498E8224FCF583123329AE829B +:102E9000AF83E4C39EFB74049FFCAE40AF41740457 +:102EA0002FFF5307078E82EF24FCF583C004C00375 +:102EB000123329AE82AF83D003D004E4C39EF974E9 +:102EC000049FFA804FAE4874042549FF5307078ECC +:102ED00082EF24FCF583C004C003123329AE82AF15 +:102EE00083E4C39EF974049FFAAE40AF41530707D1 +:102EF0008E82EF24FCF583C002C001123329AE821A +:102F0000AF83D001D002D003D004E4C39EF5467451 +:102F1000049FF5478543828544838545F0123BA98C +:102F2000FF30E0047B007C04EF30E10479007A0498 +:102F3000EF30E20675460075470474012543FDE451 +:102F40003544FEAF458D828E838FF0123BA9FF7012 +:102F500009FBFCF9FA8C46754704BF010D7B007C28 +:102F60000079007A04E4F546F547BF020D79007A4E +:102F7000007B007C04E4F546F547BF030D79007A39 +:102F8000047B007C04E4F546F547AE427F0075758E +:102F9000038F768E828F83C004C003C002C00112EB +:102FA0003BC5AE82AF83D001D002D003D004BE00B7 +:102FB00005BF00028010BE0105BF0002801ABE02DC +:102FC0003BBF00388024D284D2C7D282D2B5D2B4DB +:102FD000D2918B068C078028D2C1D2C2D2C3D2C470 +:102FE000D2C5D29289068A078016D283D2C6D28FE2 +:102FF000D2B6D2B3D293AE46AF4780047E007F00F4 +:103000008E828F831230167582002290FF80E053EB +:10301000E0DFF00230B1AE82AF83E4C39EFE740401 +:103020009FFD8DC38ECE8DC48EC18DC58EC290FF87 +:10303000EDEDF090FFD5EEF090FFECEDF090FFD4C9 +:10304000EEF090FFEBEDF090FFD3EEF090FFEAEDA5 +:10305000F090FFD2EEF090FFE9EDF090FFD1EEF0AE +:1030600090FFE8EDF090FFD0EEF090FFF3EDF090E0 +:10307000FFDBEEF090FFF2EDF090FFDAEEF090FF64 +:10308000F1EDF090FFD9EEF090FFF0EDF090FFD869 +:10309000EEF090FFEFEDF090FFD7EEF090FFEEED49 +:1030A000F090FFD6EEF090FFF9EDF090FFE1EEF03A +:1030B0002290FF8074CAF090FF817408F090FF8224 +:1030C000F090FF83F090FF84F090FF85F090FF86F2 +:1030D000748AF090FF877408F090FF88F090FF8961 +:1030E000F090FF8AF090FF8BF090FF8C748AF09044 +:1030F000FF917408F075DA8AF5DBF5DC2290FF8029 +:103100007402F090FF81E4F090FF82F090FF83F072 +:1031100090FF84F090FF85F090FF867402F090FF9E +:1031200087E4F090FF88F090FF89F090FF8AF0909C +:10313000FF8BF090FF8C7402F090FF91E4F075DA51 +:1031400002F5DBF5DC22AF82BFA502800ABFA60232 +:103150008009BFA70E8008900001229000022290F3 +:1031600000042290000022AF82BFA8005003023268 +:103170002CEF243D500302322CEF2458FF240A8305 +:10318000F582EF241F83F583E473C0C4C8CCD0DC80 +:10319000E4E8E0ECF0F40004080C101420D4D81893 +:1031A0001CF8FC24283131313131313131313131A8 +:1031B00031323232323232323131323231313232F4 +:1031C0009000E2229000E9229000EA229000B522CD +:1031D0009000B6229000B3229000B4229000B72253 +:1031E0009000CC229000CD229001832290018A226F +:1031F000900192229001942290019F229001CB2273 +:103200009002212290022322900224229002252261 +:10321000900226229002272290006F2290007022B6 +:1032200090022A2290029F229002A0229000002267 +:10323000AE82AF83BE445FBF7E5CE51C602B120E86 +:1032400087E5825422601175123590002575F000D3 +:10325000120DCC120A00803A75122990002575F0E3 +:1032600000120DCC120A008029120E87E58254222A +:10327000601175133590002575F000120E04120AC6 +:1032800000800F75132990002575F000120E0412AE +:103290000A007582002275820122AD82AE83AFF0F2 +:1032A0008D7A8E7B8F7C85757D85767E85777FA8F0 +:1032B00078A9798803890418B8FF0119EB4C6025B7 +:1032C000857D82857E83857FF0123BA9FCA3858264 +:1032D0007D85837E8D828E838FF0EC12330EA3ADBD +:1032E00082AE8380CE857A82857B83857CF022AC1A +:1032F00082AD83AE76AF77BE0004EF600C1F0FE5A2 +:103300007512330EA3DEFADFF88C828D832220F74C +:103310001130F6138883A88220F509F6A8837583F7 +:10332000002280FEF280F5F022E58330E707638219 +:10333000FF6383FFA322E575457660467A01E57554 +:1033400025E0F575E576334012F576E5829575E56D +:1033500083957640030A80E6C3E57613F576E57536 +:1033600013F575C3E5829575F5F0E583957640050F +:10337000F58385F082C3E57613F576E57513F5756B +:10338000DAE12285827A85837B85F07CE578457950 +:10339000700490000022AB78AC791BBBFF011CEBE2 +:1033A0004C6048A87AA97BAA7C888289838AF0121B +:1033B0003BA9FF85757D85767E85777F857D8285B6 +:1033C0007E83857FF0123BA9FEEFB5061E08B8008C +:1033D0000109887A897B8A7C7401257DFDE4357E2C +:1033E000FEAF7F8D758E768F7780AFAD7AAE7BAF77 +:1033F0007C8D828E838FF0123BA9FD7F00AB75AC74 +:1034000076AE778B828C838EF0123BA9FB7E00ED2B +:10341000C39BF582EF9EF58322C01E85811E7E0030 +:103420008E83120FE8D01E2285825A85835B85F039 +:103430005CE4F557F558F559851D5D903419023552 +:1034400002C01EE581F51E24FBFF8F5DE4F557F5F4 +:1034500058F559E51E24FBF8865A08865B08865CF9 +:10346000903419123502D01E22AF82C04DC04EC01A +:103470004F1234768007C04BC04C8F8222158115C5 +:103480008115810555E4B55502055622AF82743089 +:103490002FFF24C6500B74072FFFE54A6003430734 +:1034A000208F82023469E582FFC4540FF582C00781 +:1034B00012348CD007740F5FF58202348C858275CC +:1034C000AB50AC51AD52AE53AA547577208A07EF7A +:1034D0002FF576EE2354014576FAEB2BFBEC33FC0B +:1034E000ED33FDEE33FEC3EA95754008EAC39575EA +:1034F000FA430301D577D68B508C518D528E538A67 +:10350000542285824B85834C85574D85584E85596D +:103510004FE4F555F556AD5AAE5BAF5C8D828E83A8 +:103520008FF0123BA9FC74012DF55AE43EF55B8F38 +:103530005CECFF7003023B6DBF25028003023B651C +:10354000E4F55EF55FF560F561F562F563F564F548 +:1035500065F567F568F569756AFF756BFFAC5AAD7F +:103560005BAE5C8C828D838EF0123BA9F56FA3ACB1 +:1035700082AD838C5A8D5B8E5C7425B56F08856F28 +:1035800082123469809074D0256F4003023627E59B +:103590006F24C6500302362774FFB56A4BB56B48DB +:1035A000C004C005C00685687585697690000AC0AC +:1035B00006C005C004123B8CAA82AB83D004D005A0 +:1035C000D006AE6F7D00EE2AFAED3BFBEA24D0F583 +:1035D00068EB34FFF569E5684569D006D005D0048D +:1035E0007081755F01023563C004C005C006856A3D +:1035F00075856B7690000AC006C005C004123B8C2E +:10360000AA82AB83D004D005D006AD6F7E00ED2A30 +:10361000FAEE3BFBEA24D0F56AEB34FFF56BD006FB +:10362000D005D004023563742EB56F1574FFB56AEA +:1036300005B56B028003023563E4F56AF56B02356C +:1036400063749F256F500EE56F24854008536FDF2C +:10365000754A018003754A007420B56F030236F481 +:10366000742BB56F030236EE742DB56F028079743A +:1036700042B56F030236FA7443B56F03023706741E +:1036800044B56F030238957446B56F030238AC74C5 +:1036900048B56F030235637449B56F0302389574FA +:1036A0004AB56F03023563744CB56F028052744F94 +:1036B000B56F0302389D7450B56F0302382A7453F6 +:1036C000B56F0280627454B56F030235637455B5EB +:1036D0006F030238A27458B56F030238A7745AB545 +:1036E0006F030235630238B1755E010235637560A0 +:1036F000010235637561010235637563010235634B +:10370000756401023563E563600AE55D14F9895D5E +:103710008706800BE55D24FEFD8D5D8D0187068E9D +:10372000821234690238BDE55D24FDFE8E5D8E0196 +:10373000870409870509870619198C508D518E5207 +:103740008C828D838EF0123B7485827085837174B8 +:10375000FFB56A09B56B0685706A85716BE55E70A9 +:103760003DC3E5709568E57195695032E568C3958C +:1037700070F568E5699571F569AC68AB698C028B89 +:10378000061CBCFF011BEA4E6010758220C004C0FD +:1037900003123469D003D00480E38C688B69AD6A6E +:1037A000AE6B8550828551838552F0123BA9FB6038 +:1037B00033C3E49D74808EF063F08095F050251D36 +:1037C000BDFF011E8B82C006C005123469D005D032 +:1037D00006AA50AB51AC520ABA00010B8A508B5169 +:1037E0008C5280BEE55E70030238BDC3E5709568FB +:1037F000E571956940030238BDE568C39570F568C9 +:10380000E5699571F569AE68AD698E038D041EBEDC +:10381000FF011DEB4C70030238B9758220C006C051 +:1038200005123469D005D00680E0E55D24FDFC8CEE +:103830005D8C01870209870309870419198A508B57 +:10384000518C52AC52BC800040047B438014BC605D +:103850000040047B50800BBC400040047B49800248 +:103860007B588B82C00312346975823A12346975B1 +:103870008230123469758278123469D003BB4902F0 +:10388000800BBB500280068551821234A68550827F +:103890001234A6802875620175670A802075670852 +:1038A000801B75670A801675671080117565018029 +:1038B0000C856F8212346980048E688D69E56560BD +:1038C0005BE55D24FCFE8E5D8E018703098704099C +:1038D00087050987061919198B508C518D528E5303 +:1038E000755034755141755280855072855173857C +:1038F000527474012572FAE43573FBAE748A508BEE +:10390000518E528572828573838574F0123BA9FDB6 +:1039100070030235168D8212346980CDE56770031D +:10392000023516E5636029E55D14F9895D87067D3A +:10393000007C007B008E508D518C528B53E5627061 +:1039400063AB50FCFDFE8B508C518D528E538054D6 +:10395000E5646021E55D24FCFE8E5D8E0187030930 +:1039600087040987050987061919198B508C518D11 +:10397000528E53802FE55D24FEFE8E5D8E018705FD +:1039800009870619EE3395E0FCFB8D508E518C5261 +:103990008B53E562700EAB50AC51FDFE8B508C51D9 +:1039A0008D528E53E5626023E55330E71BC3E495E7 +:1039B00050FBE49551FCE49552FDE49553FE8B5089 +:1039C0008C518D528E538003756200756601798526 +:1039D0007D007E00755400856782C006C005C00169 +:1039E0001234BDD001D005D006E5667013E554C48D +:1039F00054F0FCE554C4540F4204E74CF71980021C +:103A0000A7540DBD00010EE566B40100E433F56670 +:103A1000E55045514552455370BA896E8D6C8E6D97 +:103A2000E56845697005756801F569E55F702DE524 +:103A30005E7029AA68AB69AC6C0C7E00C3EC9AEE90 +:103A40009B5015758220C003C002123469D002D089 +:103A5000031ABAFF011B80DF8A688B69E562601177 +:103A600075822D123469156874FFB5680215698076 +:103A70002EE56C456D6028E560601175822B12346F +:103A800069156874FFB5680215698013E561600FF8 +:103A9000758220123469156874FFB568021569E5EE +:103AA0005E702FAE68AD698E038D041EBEFF011DD2 +:103AB000C3E56C9BE56D9C503AE55F60047C30800B +:103AC000027C208C82C006C005123469D005D00665 +:103AD00080D5C3E56C9568E56D9569500FE568C3C1 +:103AE000956CF568E569956DF569800BE4F568F509 +:103AF0006980048E688D69A96EAD6CAE6D8D038E84 +:103B0000041DBDFF011EEB4C6030E566B40100E40E +:103B100033F566700A09E7C4540FFC8C548007879C +:103B200004740F5CF554855482C006C005C00112B0 +:103B3000348CD001D005D00680C3E55E7003023519 +:103B400016AD68AE698D038E041DBDFF011EEB4CE2 +:103B50007003023516758220C006C005123469D084 +:103B600005D00680E08F82123469023516855582B1 +:103B700085568322AA82AB83123BA96003A380F8F7 +:103B8000C3E5829AF582E5839BF58322E5828575FC +:103B9000F0A4C582C0F08576F0A4D0F025F0C583EE +:103BA0008575F0A42583F5832220F71430F6148858 +:103BB00083A88220F507E6A88375830022E280F7B8 +:103BC000E49322E022C2D5E58330E70DD2D5E4C3E9 +:103BD0009582F582E49583F583E57630E70BE4C3BF +:103BE0009575F575E49576F57612333630D50BE498 +:103BF000C39582F582E49583F58322758200225372 +:103C00004D4B2076616C7068610D0A004445564941 +:103C10004345207649643A307830356163207049F5 +:103C2000643A3078303234660A0D004B45593A20F8 +:103C30003078253034782025730D0A005550004423 +:103C40004F574E004348414E474544204C4159454B +:103C5000523A2025640D0A00554E5245434F474EB7 +:103C6000495A4544204B45593A203078253034781C +:103C70000D0A006164645F6B65795F6269743A2064 +:103C800063616E2774206164643A20253032580ADB +:103C90000064656C5F6B65795F6269743A2063618B +:103CA0006E27742064656C3A20253032580A00056E +:103CB000010906A101050719E029E715002501758D +:103CC00001950881027508950181010507190029F0 +:103CD000FF150026FF0075089506810005081901EB +:103CE00029051500250175019505910275039501BA +:103CF0009101C005010980A1018501198129831560 +:103D000000250175019503810295058101C0050C0F +:103D10000901A101850219002A3C021500263C0276 +:103D2000751095018100C00600FF0901A1018505FC +:103D300019012902150026FF0075089505B102C07A +:103D400005010906A1018506050719E029E7150007 +:103D5000250175019508810205071900299F1500A5 +:103D60002501750195A08102C0120110010000001B +:103D700008AC054F02000001020301090400000124 +:103D800003010100092111010001224400070581FE +:103D900003100001090401000103000000092111C2 +:103DA000010001227600070582034000010902009C +:103DB00000020100A0FA7B3D843D8D3D943D9D3D78 +:103DC000A63D0000AD3D04030904273E3E3E4B3EA8 +:103DD000693D000001C43D03CA3D2573206275663C +:103DE00066657220746F6F206C6F6E673A20256471 +:103DF000007365745F6570305F696E5F62756666DB +:103E00006572007365745F6570315F696E5F6275BE +:103E100066666572007365745F6570325F696E5FB8 +:103E200062756666657200636F6E74616374406389 +:103E300061726C6F73736C6573732E696F00534D91 +:103E40004B204B6579626F61726400303030310015 +:103E5000697320626C616E6B3A2025640D0A0072F2 +:103E600066206C696E6B206368616E6765643A20DA +:103E700025640D0A00534D4B204254352E3000531B +:103E80004D4B204254332E30005553425F4D4F442A +:103E9000450D0A0052465F4D4F44450D0A004D4105 +:103EA000435F4D4F44450D0A0057494E5F4D4F4407 +:103EB000450D0A00447E1E001F0020002100220044 +:103EC000230024002500260027002D002E002A00B4 +:103ED000000000002B0014001A0008001500170055 +:103EE0001C0018000C00120013002F0030003100DD +:103EF00000000000390004001600070009000A0055 +:103F00000B000D000E000F003300340000002800ED +:103F100000000000E1001D001B0006001900050064 +:103F20001100100036003700380000000000E500E6 +:103F300052004C00E000E300E200000000002C0012 +:103F400000000000E3002252500000000000510079 +:103F50004F000000447E1E001F00200021002200B0 +:103F6000230024002500260027002D002E002A0013 +:103F7000000000002B0014001A00080015001700B4 +:103F80001C0018000C00120013002F00300031003C +:103F900000000000390004001600070009000A00B4 +:103FA0000B000D000E000F0033003400000028004D +:103FB00000000000E1001D001B00060019000500C4 +:103FC0001100100036003700380000000000E50046 +:103FD00052004C00E000E200E300000000002C0072 +:103FE00000000000E30023525000000000005100D8 +:103FF0004F0000003500BE00BD00C100C0000378C6 +:104000000478BC00AE00BB00A800AA00A9004C00C8 +:10401000000000000100417E427E437E407E0100A0 +:104020000100010001000100010001000100010088 +:10403000000000000100010001000100010001007A +:104040000100010001000100010001000000010069 +:10405000000000000100010001000100010001005A +:10406000010001000100010001000000000001004A +:10407000010001000100010001000000000001003A +:10408000000000000100010001000000000001002C +:104090000100000035003A003B003C003D003E00BE +:1040A0003F004000410042004300440045004C00F6 +:1040B000000000000100417E427E437E407E010000 +:1040C00001000100010001000100010001000100E8 +:1040D00000000000010001000100010001000100DA +:1040E00001000100010001000100010000000100C9 +:1040F00000000000010001000100010001000100BA +:1041000001000100010001000100000000000100A9 +:104110000100010001000100010000000000010099 +:10412000000000000100010001000000000001008B +:10413000010000003C4E4F20464C4F41543E0000D1 +:10414000000000000000000000000000000000006F +:10415000000000000000000000000000000000005F +:10416000000000000000000000000000000000004F +:10417000000000000000000000000000000000003F +:10418000000000000000000000000000000000002F +:10419000000000000000000000000000000000001F +:1041A000000000000000000000000000000000000F +:1041B00000000000000000000000000000000000FF +:1041C00000000000000000000000000000000000EF +:1041D00000000000000000000000000000000000DF +:1041E00000000000000000000000000000000000CF +:1041F00000000000000000000000000000000000BF +:1042000000000000000000000000000000000000AE +:10421000000000000000000000000000000000009E +:10422000000000000000000000000000000000008E +:10423000000000000000000000000000000000007E +:10424000000000000000000000000000000000006E +:10425000000000000000000000000000000000005E +:10426000000000000000000000000000000000004E +:10427000000000000000000000000000000000003E +:10428000000000000000000000000000000000002E +:10429000000000000000000000000000000000001E +:1042A000000000000000000000000000000000000E +:1042B00000000000000000000000000000000000FE +:1042C00000000000000000000000000000000000EE +:1042D00000000000000000000000000000000000DE +:1042E00000000000000000000000000000000000CE +:1042F00000000000000000000000000000000000BE +:1043000000000000000000000000000000000000AD +:10431000000000000000000000000000000000009D +:10432000000000000000000000000000000000008D +:10433000000000000000000000000000000000007D +:10434000000000000000000000000000000000006D +:10435000000000000000000000000000000000005D +:10436000000000000000000000000000000000004D +:10437000000000000000000000000000000000003D +:10438000000000000000000000000000000000002D +:10439000000000000000000000000000000000001D +:1043A000000000000000000000000000000000000D +:1043B00000000000000000000000000000000000FD +:1043C00000000000000000000000000000000000ED +:1043D00000000000000000000000000000000000DD +:1043E00000000000000000000000000000000000CD +:1043F00000000000000000000000000000000000BD +:1044000000000000000000000000000000000000AC +:10441000000000000000000000000000000000009C +:10442000000000000000000000000000000000008C +:10443000000000000000000000000000000000007C +:10444000000000000000000000000000000000006C +:10445000000000000000000000000000000000005C +:10446000000000000000000000000000000000004C +:10447000000000000000000000000000000000003C +:10448000000000000000000000000000000000002C +:10449000000000000000000000000000000000001C +:1044A000000000000000000000000000000000000C +:1044B00000000000000000000000000000000000FC +:1044C00000000000000000000000000000000000EC +:1044D00000000000000000000000000000000000DC +:1044E00000000000000000000000000000000000CC +:1044F00000000000000000000000000000000000BC +:1045000000000000000000000000000000000000AB +:10451000000000000000000000000000000000009B +:10452000000000000000000000000000000000008B +:10453000000000000000000000000000000000007B +:10454000000000000000000000000000000000006B +:10455000000000000000000000000000000000005B +:10456000000000000000000000000000000000004B +:10457000000000000000000000000000000000003B +:10458000000000000000000000000000000000002B +:10459000000000000000000000000000000000001B +:1045A000000000000000000000000000000000000B +:1045B00000000000000000000000000000000000FB +:1045C00000000000000000000000000000000000EB +:1045D00000000000000000000000000000000000DB +:1045E00000000000000000000000000000000000CB +:1045F00000000000000000000000000000000000BB +:1046000000000000000000000000000000000000AA +:10461000000000000000000000000000000000009A +:10462000000000000000000000000000000000008A +:10463000000000000000000000000000000000007A +:10464000000000000000000000000000000000006A +:10465000000000000000000000000000000000005A +:10466000000000000000000000000000000000004A +:10467000000000000000000000000000000000003A +:10468000000000000000000000000000000000002A +:10469000000000000000000000000000000000001A +:1046A000000000000000000000000000000000000A +:1046B00000000000000000000000000000000000FA +:1046C00000000000000000000000000000000000EA +:1046D00000000000000000000000000000000000DA +:1046E00000000000000000000000000000000000CA +:1046F00000000000000000000000000000000000BA +:1047000000000000000000000000000000000000A9 +:104710000000000000000000000000000000000099 +:104720000000000000000000000000000000000089 +:104730000000000000000000000000000000000079 +:104740000000000000000000000000000000000069 +:104750000000000000000000000000000000000059 +:104760000000000000000000000000000000000049 +:104770000000000000000000000000000000000039 +:104780000000000000000000000000000000000029 +:104790000000000000000000000000000000000019 +:1047A0000000000000000000000000000000000009 +:1047B00000000000000000000000000000000000F9 +:1047C00000000000000000000000000000000000E9 +:1047D00000000000000000000000000000000000D9 +:1047E00000000000000000000000000000000000C9 +:1047F00000000000000000000000000000000000B9 +:1048000000000000000000000000000000000000A8 +:104810000000000000000000000000000000000098 +:104820000000000000000000000000000000000088 +:104830000000000000000000000000000000000078 +:104840000000000000000000000000000000000068 +:104850000000000000000000000000000000000058 +:104860000000000000000000000000000000000048 +:104870000000000000000000000000000000000038 +:104880000000000000000000000000000000000028 +:104890000000000000000000000000000000000018 +:1048A0000000000000000000000000000000000008 +:1048B00000000000000000000000000000000000F8 +:1048C00000000000000000000000000000000000E8 +:1048D00000000000000000000000000000000000D8 +:1048E00000000000000000000000000000000000C8 +:1048F00000000000000000000000000000000000B8 +:1049000000000000000000000000000000000000A7 +:104910000000000000000000000000000000000097 +:104920000000000000000000000000000000000087 +:104930000000000000000000000000000000000077 +:104940000000000000000000000000000000000067 +:104950000000000000000000000000000000000057 +:104960000000000000000000000000000000000047 +:104970000000000000000000000000000000000037 +:104980000000000000000000000000000000000027 +:104990000000000000000000000000000000000017 +:1049A0000000000000000000000000000000000007 +:1049B00000000000000000000000000000000000F7 +:1049C00000000000000000000000000000000000E7 +:1049D00000000000000000000000000000000000D7 +:1049E00000000000000000000000000000000000C7 +:1049F00000000000000000000000000000000000B7 +:104A000000000000000000000000000000000000A6 +:104A10000000000000000000000000000000000096 +:104A20000000000000000000000000000000000086 +:104A30000000000000000000000000000000000076 +:104A40000000000000000000000000000000000066 +:104A50000000000000000000000000000000000056 +:104A60000000000000000000000000000000000046 +:104A70000000000000000000000000000000000036 +:104A80000000000000000000000000000000000026 +:104A90000000000000000000000000000000000016 +:104AA0000000000000000000000000000000000006 +:104AB00000000000000000000000000000000000F6 +:104AC00000000000000000000000000000000000E6 +:104AD00000000000000000000000000000000000D6 +:104AE00000000000000000000000000000000000C6 +:104AF00000000000000000000000000000000000B6 +:104B000000000000000000000000000000000000A5 +:104B10000000000000000000000000000000000095 +:104B20000000000000000000000000000000000085 +:104B30000000000000000000000000000000000075 +:104B40000000000000000000000000000000000065 +:104B50000000000000000000000000000000000055 +:104B60000000000000000000000000000000000045 +:104B70000000000000000000000000000000000035 +:104B80000000000000000000000000000000000025 +:104B90000000000000000000000000000000000015 +:104BA0000000000000000000000000000000000005 +:104BB00000000000000000000000000000000000F5 +:104BC00000000000000000000000000000000000E5 +:104BD00000000000000000000000000000000000D5 +:104BE00000000000000000000000000000000000C5 +:104BF00000000000000000000000000000000000B5 +:104C000000000000000000000000000000000000A4 +:104C10000000000000000000000000000000000094 +:104C20000000000000000000000000000000000084 +:104C30000000000000000000000000000000000074 +:104C40000000000000000000000000000000000064 +:104C50000000000000000000000000000000000054 +:104C60000000000000000000000000000000000044 +:104C70000000000000000000000000000000000034 +:104C80000000000000000000000000000000000024 +:104C90000000000000000000000000000000000014 +:104CA0000000000000000000000000000000000004 +:104CB00000000000000000000000000000000000F4 +:104CC00000000000000000000000000000000000E4 +:104CD00000000000000000000000000000000000D4 +:104CE00000000000000000000000000000000000C4 +:104CF00000000000000000000000000000000000B4 +:104D000000000000000000000000000000000000A3 +:104D10000000000000000000000000000000000093 +:104D20000000000000000000000000000000000083 +:104D30000000000000000000000000000000000073 +:104D40000000000000000000000000000000000063 +:104D50000000000000000000000000000000000053 +:104D60000000000000000000000000000000000043 +:104D70000000000000000000000000000000000033 +:104D80000000000000000000000000000000000023 +:104D90000000000000000000000000000000000013 +:104DA0000000000000000000000000000000000003 +:104DB00000000000000000000000000000000000F3 +:104DC00000000000000000000000000000000000E3 +:104DD00000000000000000000000000000000000D3 +:104DE00000000000000000000000000000000000C3 +:104DF00000000000000000000000000000000000B3 +:104E000000000000000000000000000000000000A2 +:104E10000000000000000000000000000000000092 +:104E20000000000000000000000000000000000082 +:104E30000000000000000000000000000000000072 +:104E40000000000000000000000000000000000062 +:104E50000000000000000000000000000000000052 +:104E60000000000000000000000000000000000042 +:104E70000000000000000000000000000000000032 +:104E80000000000000000000000000000000000022 +:104E90000000000000000000000000000000000012 +:104EA0000000000000000000000000000000000002 +:104EB00000000000000000000000000000000000F2 +:104EC00000000000000000000000000000000000E2 +:104ED00000000000000000000000000000000000D2 +:104EE00000000000000000000000000000000000C2 +:104EF00000000000000000000000000000000000B2 +:104F000000000000000000000000000000000000A1 +:104F10000000000000000000000000000000000091 +:104F20000000000000000000000000000000000081 +:104F30000000000000000000000000000000000071 +:104F40000000000000000000000000000000000061 +:104F50000000000000000000000000000000000051 +:104F60000000000000000000000000000000000041 +:104F70000000000000000000000000000000000031 +:104F80000000000000000000000000000000000021 +:104F90000000000000000000000000000000000011 +:104FA0000000000000000000000000000000000001 +:104FB00000000000000000000000000000000000F1 +:104FC00000000000000000000000000000000000E1 +:104FD00000000000000000000000000000000000D1 +:104FE00000000000000000000000000000000000C1 +:104FF00000000000000000000000000000000000B1 +:1050000000000000000000000000000000000000A0 +:105010000000000000000000000000000000000090 +:105020000000000000000000000000000000000080 +:105030000000000000000000000000000000000070 +:105040000000000000000000000000000000000060 +:105050000000000000000000000000000000000050 +:105060000000000000000000000000000000000040 +:105070000000000000000000000000000000000030 +:105080000000000000000000000000000000000020 +:105090000000000000000000000000000000000010 +:1050A0000000000000000000000000000000000000 +:1050B00000000000000000000000000000000000F0 +:1050C00000000000000000000000000000000000E0 +:1050D00000000000000000000000000000000000D0 +:1050E00000000000000000000000000000000000C0 +:1050F00000000000000000000000000000000000B0 +:10510000000000000000000000000000000000009F +:10511000000000000000000000000000000000008F +:10512000000000000000000000000000000000007F +:10513000000000000000000000000000000000006F +:10514000000000000000000000000000000000005F +:10515000000000000000000000000000000000004F +:10516000000000000000000000000000000000003F +:10517000000000000000000000000000000000002F +:10518000000000000000000000000000000000001F +:10519000000000000000000000000000000000000F +:1051A00000000000000000000000000000000000FF +:1051B00000000000000000000000000000000000EF +:1051C00000000000000000000000000000000000DF +:1051D00000000000000000000000000000000000CF +:1051E00000000000000000000000000000000000BF +:1051F00000000000000000000000000000000000AF +:10520000000000000000000000000000000000009E +:10521000000000000000000000000000000000008E +:10522000000000000000000000000000000000007E +:10523000000000000000000000000000000000006E +:10524000000000000000000000000000000000005E +:10525000000000000000000000000000000000004E +:10526000000000000000000000000000000000003E +:10527000000000000000000000000000000000002E +:10528000000000000000000000000000000000001E +:10529000000000000000000000000000000000000E +:1052A00000000000000000000000000000000000FE +:1052B00000000000000000000000000000000000EE +:1052C00000000000000000000000000000000000DE +:1052D00000000000000000000000000000000000CE +:1052E00000000000000000000000000000000000BE +:1052F00000000000000000000000000000000000AE +:10530000000000000000000000000000000000009D +:10531000000000000000000000000000000000008D +:10532000000000000000000000000000000000007D +:10533000000000000000000000000000000000006D +:10534000000000000000000000000000000000005D +:10535000000000000000000000000000000000004D +:10536000000000000000000000000000000000003D +:10537000000000000000000000000000000000002D +:10538000000000000000000000000000000000001D +:10539000000000000000000000000000000000000D +:1053A00000000000000000000000000000000000FD +:1053B00000000000000000000000000000000000ED +:1053C00000000000000000000000000000000000DD +:1053D00000000000000000000000000000000000CD +:1053E00000000000000000000000000000000000BD +:1053F00000000000000000000000000000000000AD +:10540000000000000000000000000000000000009C +:10541000000000000000000000000000000000008C +:10542000000000000000000000000000000000007C +:10543000000000000000000000000000000000006C +:10544000000000000000000000000000000000005C +:10545000000000000000000000000000000000004C +:10546000000000000000000000000000000000003C +:10547000000000000000000000000000000000002C +:10548000000000000000000000000000000000001C +:10549000000000000000000000000000000000000C +:1054A00000000000000000000000000000000000FC +:1054B00000000000000000000000000000000000EC +:1054C00000000000000000000000000000000000DC +:1054D00000000000000000000000000000000000CC +:1054E00000000000000000000000000000000000BC +:1054F00000000000000000000000000000000000AC +:10550000000000000000000000000000000000009B +:10551000000000000000000000000000000000008B +:10552000000000000000000000000000000000007B +:10553000000000000000000000000000000000006B +:10554000000000000000000000000000000000005B +:10555000000000000000000000000000000000004B +:10556000000000000000000000000000000000003B +:10557000000000000000000000000000000000002B +:10558000000000000000000000000000000000001B +:10559000000000000000000000000000000000000B +:1055A00000000000000000000000000000000000FB +:1055B00000000000000000000000000000000000EB +:1055C00000000000000000000000000000000000DB +:1055D00000000000000000000000000000000000CB +:1055E00000000000000000000000000000000000BB +:1055F00000000000000000000000000000000000AB +:10560000000000000000000000000000000000009A +:10561000000000000000000000000000000000008A +:10562000000000000000000000000000000000007A +:10563000000000000000000000000000000000006A +:10564000000000000000000000000000000000005A +:10565000000000000000000000000000000000004A +:10566000000000000000000000000000000000003A +:10567000000000000000000000000000000000002A +:10568000000000000000000000000000000000001A +:10569000000000000000000000000000000000000A +:1056A00000000000000000000000000000000000FA +:1056B00000000000000000000000000000000000EA +:1056C00000000000000000000000000000000000DA +:1056D00000000000000000000000000000000000CA +:1056E00000000000000000000000000000000000BA +:1056F00000000000000000000000000000000000AA +:105700000000000000000000000000000000000099 +:105710000000000000000000000000000000000089 +:105720000000000000000000000000000000000079 +:105730000000000000000000000000000000000069 +:105740000000000000000000000000000000000059 +:105750000000000000000000000000000000000049 +:105760000000000000000000000000000000000039 +:105770000000000000000000000000000000000029 +:105780000000000000000000000000000000000019 +:105790000000000000000000000000000000000009 +:1057A00000000000000000000000000000000000F9 +:1057B00000000000000000000000000000000000E9 +:1057C00000000000000000000000000000000000D9 +:1057D00000000000000000000000000000000000C9 +:1057E00000000000000000000000000000000000B9 +:1057F00000000000000000000000000000000000A9 +:105800000000000000000000000000000000000098 +:105810000000000000000000000000000000000088 +:105820000000000000000000000000000000000078 +:105830000000000000000000000000000000000068 +:105840000000000000000000000000000000000058 +:105850000000000000000000000000000000000048 +:105860000000000000000000000000000000000038 +:105870000000000000000000000000000000000028 +:105880000000000000000000000000000000000018 +:105890000000000000000000000000000000000008 +:1058A00000000000000000000000000000000000F8 +:1058B00000000000000000000000000000000000E8 +:1058C00000000000000000000000000000000000D8 +:1058D00000000000000000000000000000000000C8 +:1058E00000000000000000000000000000000000B8 +:1058F00000000000000000000000000000000000A8 +:105900000000000000000000000000000000000097 +:105910000000000000000000000000000000000087 +:105920000000000000000000000000000000000077 +:105930000000000000000000000000000000000067 +:105940000000000000000000000000000000000057 +:105950000000000000000000000000000000000047 +:105960000000000000000000000000000000000037 +:105970000000000000000000000000000000000027 +:105980000000000000000000000000000000000017 +:105990000000000000000000000000000000000007 +:1059A00000000000000000000000000000000000F7 +:1059B00000000000000000000000000000000000E7 +:1059C00000000000000000000000000000000000D7 +:1059D00000000000000000000000000000000000C7 +:1059E00000000000000000000000000000000000B7 +:1059F00000000000000000000000000000000000A7 +:105A00000000000000000000000000000000000096 +:105A10000000000000000000000000000000000086 +:105A20000000000000000000000000000000000076 +:105A30000000000000000000000000000000000066 +:105A40000000000000000000000000000000000056 +:105A50000000000000000000000000000000000046 +:105A60000000000000000000000000000000000036 +:105A70000000000000000000000000000000000026 +:105A80000000000000000000000000000000000016 +:105A90000000000000000000000000000000000006 +:105AA00000000000000000000000000000000000F6 +:105AB00000000000000000000000000000000000E6 +:105AC00000000000000000000000000000000000D6 +:105AD00000000000000000000000000000000000C6 +:105AE00000000000000000000000000000000000B6 +:105AF00000000000000000000000000000000000A6 +:105B00000000000000000000000000000000000095 +:105B10000000000000000000000000000000000085 +:105B20000000000000000000000000000000000075 +:105B30000000000000000000000000000000000065 +:105B40000000000000000000000000000000000055 +:105B50000000000000000000000000000000000045 +:105B60000000000000000000000000000000000035 +:105B70000000000000000000000000000000000025 +:105B80000000000000000000000000000000000015 +:105B90000000000000000000000000000000000005 +:105BA00000000000000000000000000000000000F5 +:105BB00000000000000000000000000000000000E5 +:105BC00000000000000000000000000000000000D5 +:105BD00000000000000000000000000000000000C5 +:105BE00000000000000000000000000000000000B5 +:105BF00000000000000000000000000000000000A5 +:105C00000000000000000000000000000000000094 +:105C10000000000000000000000000000000000084 +:105C20000000000000000000000000000000000074 +:105C30000000000000000000000000000000000064 +:105C40000000000000000000000000000000000054 +:105C50000000000000000000000000000000000044 +:105C60000000000000000000000000000000000034 +:105C70000000000000000000000000000000000024 +:105C80000000000000000000000000000000000014 +:105C90000000000000000000000000000000000004 +:105CA00000000000000000000000000000000000F4 +:105CB00000000000000000000000000000000000E4 +:105CC00000000000000000000000000000000000D4 +:105CD00000000000000000000000000000000000C4 +:105CE00000000000000000000000000000000000B4 +:105CF00000000000000000000000000000000000A4 +:105D00000000000000000000000000000000000093 +:105D10000000000000000000000000000000000083 +:105D20000000000000000000000000000000000073 +:105D30000000000000000000000000000000000063 +:105D40000000000000000000000000000000000053 +:105D50000000000000000000000000000000000043 +:105D60000000000000000000000000000000000033 +:105D70000000000000000000000000000000000023 +:105D80000000000000000000000000000000000013 +:105D90000000000000000000000000000000000003 +:105DA00000000000000000000000000000000000F3 +:105DB00000000000000000000000000000000000E3 +:105DC00000000000000000000000000000000000D3 +:105DD00000000000000000000000000000000000C3 +:105DE00000000000000000000000000000000000B3 +:105DF00000000000000000000000000000000000A3 +:105E00000000000000000000000000000000000092 +:105E10000000000000000000000000000000000082 +:105E20000000000000000000000000000000000072 +:105E30000000000000000000000000000000000062 +:105E40000000000000000000000000000000000052 +:105E50000000000000000000000000000000000042 +:105E60000000000000000000000000000000000032 +:105E70000000000000000000000000000000000022 +:105E80000000000000000000000000000000000012 +:105E90000000000000000000000000000000000002 +:105EA00000000000000000000000000000000000F2 +:105EB00000000000000000000000000000000000E2 +:105EC00000000000000000000000000000000000D2 +:105ED00000000000000000000000000000000000C2 +:105EE00000000000000000000000000000000000B2 +:105EF00000000000000000000000000000000000A2 +:105F00000000000000000000000000000000000091 +:105F10000000000000000000000000000000000081 +:105F20000000000000000000000000000000000071 +:105F30000000000000000000000000000000000061 +:105F40000000000000000000000000000000000051 +:105F50000000000000000000000000000000000041 +:105F60000000000000000000000000000000000031 +:105F70000000000000000000000000000000000021 +:105F80000000000000000000000000000000000011 +:105F90000000000000000000000000000000000001 +:105FA00000000000000000000000000000000000F1 +:105FB00000000000000000000000000000000000E1 +:105FC00000000000000000000000000000000000D1 +:105FD00000000000000000000000000000000000C1 +:105FE00000000000000000000000000000000000B1 +:105FF00000000000000000000000000000000000A1 +:106000000000000000000000000000000000000090 +:106010000000000000000000000000000000000080 +:106020000000000000000000000000000000000070 +:106030000000000000000000000000000000000060 +:106040000000000000000000000000000000000050 +:106050000000000000000000000000000000000040 +:106060000000000000000000000000000000000030 +:106070000000000000000000000000000000000020 +:106080000000000000000000000000000000000010 +:106090000000000000000000000000000000000000 +:1060A00000000000000000000000000000000000F0 +:1060B00000000000000000000000000000000000E0 +:1060C00000000000000000000000000000000000D0 +:1060D00000000000000000000000000000000000C0 +:1060E00000000000000000000000000000000000B0 +:1060F00000000000000000000000000000000000A0 +:10610000000000000000000000000000000000008F +:10611000000000000000000000000000000000007F +:10612000000000000000000000000000000000006F +:10613000000000000000000000000000000000005F +:10614000000000000000000000000000000000004F +:10615000000000000000000000000000000000003F +:10616000000000000000000000000000000000002F +:10617000000000000000000000000000000000001F +:10618000000000000000000000000000000000000F +:1061900000000000000000000000000000000000FF +:1061A00000000000000000000000000000000000EF +:1061B00000000000000000000000000000000000DF +:1061C00000000000000000000000000000000000CF +:1061D00000000000000000000000000000000000BF +:1061E00000000000000000000000000000000000AF +:1061F000000000000000000000000000000000009F +:10620000000000000000000000000000000000008E +:10621000000000000000000000000000000000007E +:10622000000000000000000000000000000000006E +:10623000000000000000000000000000000000005E +:10624000000000000000000000000000000000004E +:10625000000000000000000000000000000000003E +:10626000000000000000000000000000000000002E +:10627000000000000000000000000000000000001E +:10628000000000000000000000000000000000000E +:1062900000000000000000000000000000000000FE +:1062A00000000000000000000000000000000000EE +:1062B00000000000000000000000000000000000DE +:1062C00000000000000000000000000000000000CE +:1062D00000000000000000000000000000000000BE +:1062E00000000000000000000000000000000000AE +:1062F000000000000000000000000000000000009E +:10630000000000000000000000000000000000008D +:10631000000000000000000000000000000000007D +:10632000000000000000000000000000000000006D +:10633000000000000000000000000000000000005D +:10634000000000000000000000000000000000004D +:10635000000000000000000000000000000000003D +:10636000000000000000000000000000000000002D +:10637000000000000000000000000000000000001D +:10638000000000000000000000000000000000000D +:1063900000000000000000000000000000000000FD +:1063A00000000000000000000000000000000000ED +:1063B00000000000000000000000000000000000DD +:1063C00000000000000000000000000000000000CD +:1063D00000000000000000000000000000000000BD +:1063E00000000000000000000000000000000000AD +:1063F000000000000000000000000000000000009D +:10640000000000000000000000000000000000008C +:10641000000000000000000000000000000000007C +:10642000000000000000000000000000000000006C +:10643000000000000000000000000000000000005C +:10644000000000000000000000000000000000004C +:10645000000000000000000000000000000000003C +:10646000000000000000000000000000000000002C +:10647000000000000000000000000000000000001C +:10648000000000000000000000000000000000000C +:1064900000000000000000000000000000000000FC +:1064A00000000000000000000000000000000000EC +:1064B00000000000000000000000000000000000DC +:1064C00000000000000000000000000000000000CC +:1064D00000000000000000000000000000000000BC +:1064E00000000000000000000000000000000000AC +:1064F000000000000000000000000000000000009C +:10650000000000000000000000000000000000008B +:10651000000000000000000000000000000000007B +:10652000000000000000000000000000000000006B +:10653000000000000000000000000000000000005B +:10654000000000000000000000000000000000004B +:10655000000000000000000000000000000000003B +:10656000000000000000000000000000000000002B +:10657000000000000000000000000000000000001B +:10658000000000000000000000000000000000000B +:1065900000000000000000000000000000000000FB +:1065A00000000000000000000000000000000000EB +:1065B00000000000000000000000000000000000DB +:1065C00000000000000000000000000000000000CB +:1065D00000000000000000000000000000000000BB +:1065E00000000000000000000000000000000000AB +:1065F000000000000000000000000000000000009B +:10660000000000000000000000000000000000008A +:10661000000000000000000000000000000000007A +:10662000000000000000000000000000000000006A +:10663000000000000000000000000000000000005A +:10664000000000000000000000000000000000004A +:10665000000000000000000000000000000000003A +:10666000000000000000000000000000000000002A +:10667000000000000000000000000000000000001A +:10668000000000000000000000000000000000000A +:1066900000000000000000000000000000000000FA +:1066A00000000000000000000000000000000000EA +:1066B00000000000000000000000000000000000DA +:1066C00000000000000000000000000000000000CA +:1066D00000000000000000000000000000000000BA +:1066E00000000000000000000000000000000000AA +:1066F000000000000000000000000000000000009A +:106700000000000000000000000000000000000089 +:106710000000000000000000000000000000000079 +:106720000000000000000000000000000000000069 +:106730000000000000000000000000000000000059 +:106740000000000000000000000000000000000049 +:106750000000000000000000000000000000000039 +:106760000000000000000000000000000000000029 +:106770000000000000000000000000000000000019 +:106780000000000000000000000000000000000009 +:1067900000000000000000000000000000000000F9 +:1067A00000000000000000000000000000000000E9 +:1067B00000000000000000000000000000000000D9 +:1067C00000000000000000000000000000000000C9 +:1067D00000000000000000000000000000000000B9 +:1067E00000000000000000000000000000000000A9 +:1067F0000000000000000000000000000000000099 +:106800000000000000000000000000000000000088 +:106810000000000000000000000000000000000078 +:106820000000000000000000000000000000000068 +:106830000000000000000000000000000000000058 +:106840000000000000000000000000000000000048 +:106850000000000000000000000000000000000038 +:106860000000000000000000000000000000000028 +:106870000000000000000000000000000000000018 +:106880000000000000000000000000000000000008 +:1068900000000000000000000000000000000000F8 +:1068A00000000000000000000000000000000000E8 +:1068B00000000000000000000000000000000000D8 +:1068C00000000000000000000000000000000000C8 +:1068D00000000000000000000000000000000000B8 +:1068E00000000000000000000000000000000000A8 +:1068F0000000000000000000000000000000000098 +:106900000000000000000000000000000000000087 +:106910000000000000000000000000000000000077 +:106920000000000000000000000000000000000067 +:106930000000000000000000000000000000000057 +:106940000000000000000000000000000000000047 +:106950000000000000000000000000000000000037 +:106960000000000000000000000000000000000027 +:106970000000000000000000000000000000000017 +:106980000000000000000000000000000000000007 +:1069900000000000000000000000000000000000F7 +:1069A00000000000000000000000000000000000E7 +:1069B00000000000000000000000000000000000D7 +:1069C00000000000000000000000000000000000C7 +:1069D00000000000000000000000000000000000B7 +:1069E00000000000000000000000000000000000A7 +:1069F0000000000000000000000000000000000097 +:106A00000000000000000000000000000000000086 +:106A10000000000000000000000000000000000076 +:106A20000000000000000000000000000000000066 +:106A30000000000000000000000000000000000056 +:106A40000000000000000000000000000000000046 +:106A50000000000000000000000000000000000036 +:106A60000000000000000000000000000000000026 +:106A70000000000000000000000000000000000016 +:106A80000000000000000000000000000000000006 +:106A900000000000000000000000000000000000F6 +:106AA00000000000000000000000000000000000E6 +:106AB00000000000000000000000000000000000D6 +:106AC00000000000000000000000000000000000C6 +:106AD00000000000000000000000000000000000B6 +:106AE00000000000000000000000000000000000A6 +:106AF0000000000000000000000000000000000096 +:106B00000000000000000000000000000000000085 +:106B10000000000000000000000000000000000075 +:106B20000000000000000000000000000000000065 +:106B30000000000000000000000000000000000055 +:106B40000000000000000000000000000000000045 +:106B50000000000000000000000000000000000035 +:106B60000000000000000000000000000000000025 +:106B70000000000000000000000000000000000015 +:106B80000000000000000000000000000000000005 +:106B900000000000000000000000000000000000F5 +:106BA00000000000000000000000000000000000E5 +:106BB00000000000000000000000000000000000D5 +:106BC00000000000000000000000000000000000C5 +:106BD00000000000000000000000000000000000B5 +:106BE00000000000000000000000000000000000A5 +:106BF0000000000000000000000000000000000095 +:106C00000000000000000000000000000000000084 +:106C10000000000000000000000000000000000074 +:106C20000000000000000000000000000000000064 +:106C30000000000000000000000000000000000054 +:106C40000000000000000000000000000000000044 +:106C50000000000000000000000000000000000034 +:106C60000000000000000000000000000000000024 +:106C70000000000000000000000000000000000014 +:106C80000000000000000000000000000000000004 +:106C900000000000000000000000000000000000F4 +:106CA00000000000000000000000000000000000E4 +:106CB00000000000000000000000000000000000D4 +:106CC00000000000000000000000000000000000C4 +:106CD00000000000000000000000000000000000B4 +:106CE00000000000000000000000000000000000A4 +:106CF0000000000000000000000000000000000094 +:106D00000000000000000000000000000000000083 +:106D10000000000000000000000000000000000073 +:106D20000000000000000000000000000000000063 +:106D30000000000000000000000000000000000053 +:106D40000000000000000000000000000000000043 +:106D50000000000000000000000000000000000033 +:106D60000000000000000000000000000000000023 +:106D70000000000000000000000000000000000013 +:106D80000000000000000000000000000000000003 +:106D900000000000000000000000000000000000F3 +:106DA00000000000000000000000000000000000E3 +:106DB00000000000000000000000000000000000D3 +:106DC00000000000000000000000000000000000C3 +:106DD00000000000000000000000000000000000B3 +:106DE00000000000000000000000000000000000A3 +:106DF0000000000000000000000000000000000093 +:106E00000000000000000000000000000000000082 +:106E10000000000000000000000000000000000072 +:106E20000000000000000000000000000000000062 +:106E30000000000000000000000000000000000052 +:106E40000000000000000000000000000000000042 +:106E50000000000000000000000000000000000032 +:106E60000000000000000000000000000000000022 +:106E70000000000000000000000000000000000012 +:106E80000000000000000000000000000000000002 +:106E900000000000000000000000000000000000F2 +:106EA00000000000000000000000000000000000E2 +:106EB00000000000000000000000000000000000D2 +:106EC00000000000000000000000000000000000C2 +:106ED00000000000000000000000000000000000B2 +:106EE00000000000000000000000000000000000A2 +:106EF0000000000000000000000000000000000092 +:106F00000000000000000000000000000000000081 +:106F10000000000000000000000000000000000071 +:106F20000000000000000000000000000000000061 +:106F30000000000000000000000000000000000051 +:106F40000000000000000000000000000000000041 +:106F50000000000000000000000000000000000031 +:106F60000000000000000000000000000000000021 +:106F70000000000000000000000000000000000011 +:106F80000000000000000000000000000000000001 +:106F900000000000000000000000000000000000F1 +:106FA00000000000000000000000000000000000E1 +:106FB00000000000000000000000000000000000D1 +:106FC00000000000000000000000000000000000C1 +:106FD00000000000000000000000000000000000B1 +:106FE00000000000000000000000000000000000A1 +:106FF0000000000000000000000000000000000091 +:107000000000000000000000000000000000000080 +:107010000000000000000000000000000000000070 +:107020000000000000000000000000000000000060 +:107030000000000000000000000000000000000050 +:107040000000000000000000000000000000000040 +:107050000000000000000000000000000000000030 +:107060000000000000000000000000000000000020 +:107070000000000000000000000000000000000010 +:107080000000000000000000000000000000000000 +:1070900000000000000000000000000000000000F0 +:1070A00000000000000000000000000000000000E0 +:1070B00000000000000000000000000000000000D0 +:1070C00000000000000000000000000000000000C0 +:1070D00000000000000000000000000000000000B0 +:1070E00000000000000000000000000000000000A0 +:1070F0000000000000000000000000000000000090 +:10710000000000000000000000000000000000007F +:10711000000000000000000000000000000000006F +:10712000000000000000000000000000000000005F +:10713000000000000000000000000000000000004F +:10714000000000000000000000000000000000003F +:10715000000000000000000000000000000000002F +:10716000000000000000000000000000000000001F +:10717000000000000000000000000000000000000F +:1071800000000000000000000000000000000000FF +:1071900000000000000000000000000000000000EF +:1071A00000000000000000000000000000000000DF +:1071B00000000000000000000000000000000000CF +:1071C00000000000000000000000000000000000BF +:1071D00000000000000000000000000000000000AF +:1071E000000000000000000000000000000000009F +:1071F000000000000000000000000000000000008F +:10720000000000000000000000000000000000007E +:10721000000000000000000000000000000000006E +:10722000000000000000000000000000000000005E +:10723000000000000000000000000000000000004E +:10724000000000000000000000000000000000003E +:10725000000000000000000000000000000000002E +:10726000000000000000000000000000000000001E +:10727000000000000000000000000000000000000E +:1072800000000000000000000000000000000000FE +:1072900000000000000000000000000000000000EE +:1072A00000000000000000000000000000000000DE +:1072B00000000000000000000000000000000000CE +:1072C00000000000000000000000000000000000BE +:1072D00000000000000000000000000000000000AE +:1072E000000000000000000000000000000000009E +:1072F000000000000000000000000000000000008E +:10730000000000000000000000000000000000007D +:10731000000000000000000000000000000000006D +:10732000000000000000000000000000000000005D +:10733000000000000000000000000000000000004D +:10734000000000000000000000000000000000003D +:10735000000000000000000000000000000000002D +:10736000000000000000000000000000000000001D +:10737000000000000000000000000000000000000D +:1073800000000000000000000000000000000000FD +:1073900000000000000000000000000000000000ED +:1073A00000000000000000000000000000000000DD +:1073B00000000000000000000000000000000000CD +:1073C00000000000000000000000000000000000BD +:1073D00000000000000000000000000000000000AD +:1073E000000000000000000000000000000000009D +:1073F000000000000000000000000000000000008D +:10740000000000000000000000000000000000007C +:10741000000000000000000000000000000000006C +:10742000000000000000000000000000000000005C +:10743000000000000000000000000000000000004C +:10744000000000000000000000000000000000003C +:10745000000000000000000000000000000000002C +:10746000000000000000000000000000000000001C +:10747000000000000000000000000000000000000C +:1074800000000000000000000000000000000000FC +:1074900000000000000000000000000000000000EC +:1074A00000000000000000000000000000000000DC +:1074B00000000000000000000000000000000000CC +:1074C00000000000000000000000000000000000BC +:1074D00000000000000000000000000000000000AC +:1074E000000000000000000000000000000000009C +:1074F000000000000000000000000000000000008C +:10750000000000000000000000000000000000007B +:10751000000000000000000000000000000000006B +:10752000000000000000000000000000000000005B +:10753000000000000000000000000000000000004B +:10754000000000000000000000000000000000003B +:10755000000000000000000000000000000000002B +:10756000000000000000000000000000000000001B +:10757000000000000000000000000000000000000B +:1075800000000000000000000000000000000000FB +:1075900000000000000000000000000000000000EB +:1075A00000000000000000000000000000000000DB +:1075B00000000000000000000000000000000000CB +:1075C00000000000000000000000000000000000BB +:1075D00000000000000000000000000000000000AB +:1075E000000000000000000000000000000000009B +:1075F000000000000000000000000000000000008B +:10760000000000000000000000000000000000007A +:10761000000000000000000000000000000000006A +:10762000000000000000000000000000000000005A +:10763000000000000000000000000000000000004A +:10764000000000000000000000000000000000003A +:10765000000000000000000000000000000000002A +:10766000000000000000000000000000000000001A +:10767000000000000000000000000000000000000A +:1076800000000000000000000000000000000000FA +:1076900000000000000000000000000000000000EA +:1076A00000000000000000000000000000000000DA +:1076B00000000000000000000000000000000000CA +:1076C00000000000000000000000000000000000BA +:1076D00000000000000000000000000000000000AA +:1076E000000000000000000000000000000000009A +:1076F000000000000000000000000000000000008A +:107700000000000000000000000000000000000079 +:107710000000000000000000000000000000000069 +:107720000000000000000000000000000000000059 +:107730000000000000000000000000000000000049 +:107740000000000000000000000000000000000039 +:107750000000000000000000000000000000000029 +:107760000000000000000000000000000000000019 +:107770000000000000000000000000000000000009 +:1077800000000000000000000000000000000000F9 +:1077900000000000000000000000000000000000E9 +:1077A00000000000000000000000000000000000D9 +:1077B00000000000000000000000000000000000C9 +:1077C00000000000000000000000000000000000B9 +:1077D00000000000000000000000000000000000A9 +:1077E0000000000000000000000000000000000099 +:1077F0000000000000000000000000000000000089 +:107800000000000000000000000000000000000078 +:107810000000000000000000000000000000000068 +:107820000000000000000000000000000000000058 +:107830000000000000000000000000000000000048 +:107840000000000000000000000000000000000038 +:107850000000000000000000000000000000000028 +:107860000000000000000000000000000000000018 +:107870000000000000000000000000000000000008 +:1078800000000000000000000000000000000000F8 +:1078900000000000000000000000000000000000E8 +:1078A00000000000000000000000000000000000D8 +:1078B00000000000000000000000000000000000C8 +:1078C00000000000000000000000000000000000B8 +:1078D00000000000000000000000000000000000A8 +:1078E0000000000000000000000000000000000098 +:1078F0000000000000000000000000000000000088 +:107900000000000000000000000000000000000077 +:107910000000000000000000000000000000000067 +:107920000000000000000000000000000000000057 +:107930000000000000000000000000000000000047 +:107940000000000000000000000000000000000037 +:107950000000000000000000000000000000000027 +:107960000000000000000000000000000000000017 +:107970000000000000000000000000000000000007 +:1079800000000000000000000000000000000000F7 +:1079900000000000000000000000000000000000E7 +:1079A00000000000000000000000000000000000D7 +:1079B00000000000000000000000000000000000C7 +:1079C00000000000000000000000000000000000B7 +:1079D00000000000000000000000000000000000A7 +:1079E0000000000000000000000000000000000097 +:1079F0000000000000000000000000000000000087 +:107A00000000000000000000000000000000000076 +:107A10000000000000000000000000000000000066 +:107A20000000000000000000000000000000000056 +:107A30000000000000000000000000000000000046 +:107A40000000000000000000000000000000000036 +:107A50000000000000000000000000000000000026 +:107A60000000000000000000000000000000000016 +:107A70000000000000000000000000000000000006 +:107A800000000000000000000000000000000000F6 +:107A900000000000000000000000000000000000E6 +:107AA00000000000000000000000000000000000D6 +:107AB00000000000000000000000000000000000C6 +:107AC00000000000000000000000000000000000B6 +:107AD00000000000000000000000000000000000A6 +:107AE0000000000000000000000000000000000096 +:107AF0000000000000000000000000000000000086 +:107B00000000000000000000000000000000000075 +:107B10000000000000000000000000000000000065 +:107B20000000000000000000000000000000000055 +:107B30000000000000000000000000000000000045 +:107B40000000000000000000000000000000000035 +:107B50000000000000000000000000000000000025 +:107B60000000000000000000000000000000000015 +:107B70000000000000000000000000000000000005 +:107B800000000000000000000000000000000000F5 +:107B900000000000000000000000000000000000E5 +:107BA00000000000000000000000000000000000D5 +:107BB00000000000000000000000000000000000C5 +:107BC00000000000000000000000000000000000B5 +:107BD00000000000000000000000000000000000A5 +:107BE0000000000000000000000000000000000095 +:107BF0000000000000000000000000000000000085 +:107C00000000000000000000000000000000000074 +:107C10000000000000000000000000000000000064 +:107C20000000000000000000000000000000000054 +:107C30000000000000000000000000000000000044 +:107C40000000000000000000000000000000000034 +:107C50000000000000000000000000000000000024 +:107C60000000000000000000000000000000000014 +:107C70000000000000000000000000000000000004 +:107C800000000000000000000000000000000000F4 +:107C900000000000000000000000000000000000E4 +:107CA00000000000000000000000000000000000D4 +:107CB00000000000000000000000000000000000C4 +:107CC00000000000000000000000000000000000B4 +:107CD00000000000000000000000000000000000A4 +:107CE0000000000000000000000000000000000094 +:107CF0000000000000000000000000000000000084 +:107D00000000000000000000000000000000000073 +:107D10000000000000000000000000000000000063 +:107D20000000000000000000000000000000000053 +:107D30000000000000000000000000000000000043 +:107D40000000000000000000000000000000000033 +:107D50000000000000000000000000000000000023 +:107D60000000000000000000000000000000000013 +:107D70000000000000000000000000000000000003 +:107D800000000000000000000000000000000000F3 +:107D900000000000000000000000000000000000E3 +:107DA00000000000000000000000000000000000D3 +:107DB00000000000000000000000000000000000C3 +:107DC00000000000000000000000000000000000B3 +:107DD00000000000000000000000000000000000A3 +:107DE0000000000000000000000000000000000093 +:107DF0000000000000000000000000000000000083 +:107E00000000000000000000000000000000000072 +:107E10000000000000000000000000000000000062 +:107E20000000000000000000000000000000000052 +:107E30000000000000000000000000000000000042 +:107E40000000000000000000000000000000000032 +:107E50000000000000000000000000000000000022 +:107E60000000000000000000000000000000000012 +:107E70000000000000000000000000000000000002 +:107E800000000000000000000000000000000000F2 +:107E900000000000000000000000000000000000E2 +:107EA00000000000000000000000000000000000D2 +:107EB00000000000000000000000000000000000C2 +:107EC00000000000000000000000000000000000B2 +:107ED00000000000000000000000000000000000A2 +:107EE0000000000000000000000000000000000092 +:107EF0000000000000000000000000000000000082 +:107F00000000000000000000000000000000000071 +:107F10000000000000000000000000000000000061 +:107F20000000000000000000000000000000000051 +:107F30000000000000000000000000000000000041 +:107F40000000000000000000000000000000000031 +:107F50000000000000000000000000000000000021 +:107F60000000000000000000000000000000000011 +:107F70000000000000000000000000000000000001 +:107F800000000000000000000000000000000000F1 +:107F900000000000000000000000000000000000E1 +:107FA00000000000000000000000000000000000D1 +:107FB00000000000000000000000000000000000C1 +:107FC00000000000000000000000000000000000B1 +:107FD00000000000000000000000000000000000A1 +:107FE0000000000000000000000000000000000091 +:107FF0000000000000000000000000000000000081 +:108000000000000000000000000000000000000070 +:108010000000000000000000000000000000000060 +:108020000000000000000000000000000000000050 +:108030000000000000000000000000000000000040 +:108040000000000000000000000000000000000030 +:108050000000000000000000000000000000000020 +:108060000000000000000000000000000000000010 +:108070000000000000000000000000000000000000 +:1080800000000000000000000000000000000000F0 +:1080900000000000000000000000000000000000E0 +:1080A00000000000000000000000000000000000D0 +:1080B00000000000000000000000000000000000C0 +:1080C00000000000000000000000000000000000B0 +:1080D00000000000000000000000000000000000A0 +:1080E0000000000000000000000000000000000090 +:1080F0000000000000000000000000000000000080 +:10810000000000000000000000000000000000006F +:10811000000000000000000000000000000000005F +:10812000000000000000000000000000000000004F +:10813000000000000000000000000000000000003F +:10814000000000000000000000000000000000002F +:10815000000000000000000000000000000000001F +:10816000000000000000000000000000000000000F +:1081700000000000000000000000000000000000FF +:1081800000000000000000000000000000000000EF +:1081900000000000000000000000000000000000DF +:1081A00000000000000000000000000000000000CF +:1081B00000000000000000000000000000000000BF +:1081C00000000000000000000000000000000000AF +:1081D000000000000000000000000000000000009F +:1081E000000000000000000000000000000000008F +:1081F000000000000000000000000000000000007F +:10820000000000000000000000000000000000006E +:10821000000000000000000000000000000000005E +:10822000000000000000000000000000000000004E +:10823000000000000000000000000000000000003E +:10824000000000000000000000000000000000002E +:10825000000000000000000000000000000000001E +:10826000000000000000000000000000000000000E +:1082700000000000000000000000000000000000FE +:1082800000000000000000000000000000000000EE +:1082900000000000000000000000000000000000DE +:1082A00000000000000000000000000000000000CE +:1082B00000000000000000000000000000000000BE +:1082C00000000000000000000000000000000000AE +:1082D000000000000000000000000000000000009E +:1082E000000000000000000000000000000000008E +:1082F000000000000000000000000000000000007E +:10830000000000000000000000000000000000006D +:10831000000000000000000000000000000000005D +:10832000000000000000000000000000000000004D +:10833000000000000000000000000000000000003D +:10834000000000000000000000000000000000002D +:10835000000000000000000000000000000000001D +:10836000000000000000000000000000000000000D +:1083700000000000000000000000000000000000FD +:1083800000000000000000000000000000000000ED +:1083900000000000000000000000000000000000DD +:1083A00000000000000000000000000000000000CD +:1083B00000000000000000000000000000000000BD +:1083C00000000000000000000000000000000000AD +:1083D000000000000000000000000000000000009D +:1083E000000000000000000000000000000000008D +:1083F000000000000000000000000000000000007D +:10840000000000000000000000000000000000006C +:10841000000000000000000000000000000000005C +:10842000000000000000000000000000000000004C +:10843000000000000000000000000000000000003C +:10844000000000000000000000000000000000002C +:10845000000000000000000000000000000000001C +:10846000000000000000000000000000000000000C +:1084700000000000000000000000000000000000FC +:1084800000000000000000000000000000000000EC +:1084900000000000000000000000000000000000DC +:1084A00000000000000000000000000000000000CC +:1084B00000000000000000000000000000000000BC +:1084C00000000000000000000000000000000000AC +:1084D000000000000000000000000000000000009C +:1084E000000000000000000000000000000000008C +:1084F000000000000000000000000000000000007C +:10850000000000000000000000000000000000006B +:10851000000000000000000000000000000000005B +:10852000000000000000000000000000000000004B +:10853000000000000000000000000000000000003B +:10854000000000000000000000000000000000002B +:10855000000000000000000000000000000000001B +:10856000000000000000000000000000000000000B +:1085700000000000000000000000000000000000FB +:1085800000000000000000000000000000000000EB +:1085900000000000000000000000000000000000DB +:1085A00000000000000000000000000000000000CB +:1085B00000000000000000000000000000000000BB +:1085C00000000000000000000000000000000000AB +:1085D000000000000000000000000000000000009B +:1085E000000000000000000000000000000000008B +:1085F000000000000000000000000000000000007B +:10860000000000000000000000000000000000006A +:10861000000000000000000000000000000000005A +:10862000000000000000000000000000000000004A +:10863000000000000000000000000000000000003A +:10864000000000000000000000000000000000002A +:10865000000000000000000000000000000000001A +:10866000000000000000000000000000000000000A +:1086700000000000000000000000000000000000FA +:1086800000000000000000000000000000000000EA +:1086900000000000000000000000000000000000DA +:1086A00000000000000000000000000000000000CA +:1086B00000000000000000000000000000000000BA +:1086C00000000000000000000000000000000000AA +:1086D000000000000000000000000000000000009A +:1086E000000000000000000000000000000000008A +:1086F000000000000000000000000000000000007A +:108700000000000000000000000000000000000069 +:108710000000000000000000000000000000000059 +:108720000000000000000000000000000000000049 +:108730000000000000000000000000000000000039 +:108740000000000000000000000000000000000029 +:108750000000000000000000000000000000000019 +:108760000000000000000000000000000000000009 +:1087700000000000000000000000000000000000F9 +:1087800000000000000000000000000000000000E9 +:1087900000000000000000000000000000000000D9 +:1087A00000000000000000000000000000000000C9 +:1087B00000000000000000000000000000000000B9 +:1087C00000000000000000000000000000000000A9 +:1087D0000000000000000000000000000000000099 +:1087E0000000000000000000000000000000000089 +:1087F0000000000000000000000000000000000079 +:108800000000000000000000000000000000000068 +:108810000000000000000000000000000000000058 +:108820000000000000000000000000000000000048 +:108830000000000000000000000000000000000038 +:108840000000000000000000000000000000000028 +:108850000000000000000000000000000000000018 +:108860000000000000000000000000000000000008 +:1088700000000000000000000000000000000000F8 +:1088800000000000000000000000000000000000E8 +:1088900000000000000000000000000000000000D8 +:1088A00000000000000000000000000000000000C8 +:1088B00000000000000000000000000000000000B8 +:1088C00000000000000000000000000000000000A8 +:1088D0000000000000000000000000000000000098 +:1088E0000000000000000000000000000000000088 +:1088F0000000000000000000000000000000000078 +:108900000000000000000000000000000000000067 +:108910000000000000000000000000000000000057 +:108920000000000000000000000000000000000047 +:108930000000000000000000000000000000000037 +:108940000000000000000000000000000000000027 +:108950000000000000000000000000000000000017 +:108960000000000000000000000000000000000007 +:1089700000000000000000000000000000000000F7 +:1089800000000000000000000000000000000000E7 +:1089900000000000000000000000000000000000D7 +:1089A00000000000000000000000000000000000C7 +:1089B00000000000000000000000000000000000B7 +:1089C00000000000000000000000000000000000A7 +:1089D0000000000000000000000000000000000097 +:1089E0000000000000000000000000000000000087 +:1089F0000000000000000000000000000000000077 +:108A00000000000000000000000000000000000066 +:108A10000000000000000000000000000000000056 +:108A20000000000000000000000000000000000046 +:108A30000000000000000000000000000000000036 +:108A40000000000000000000000000000000000026 +:108A50000000000000000000000000000000000016 +:108A60000000000000000000000000000000000006 +:108A700000000000000000000000000000000000F6 +:108A800000000000000000000000000000000000E6 +:108A900000000000000000000000000000000000D6 +:108AA00000000000000000000000000000000000C6 +:108AB00000000000000000000000000000000000B6 +:108AC00000000000000000000000000000000000A6 +:108AD0000000000000000000000000000000000096 +:108AE0000000000000000000000000000000000086 +:108AF0000000000000000000000000000000000076 +:108B00000000000000000000000000000000000065 +:108B10000000000000000000000000000000000055 +:108B20000000000000000000000000000000000045 +:108B30000000000000000000000000000000000035 +:108B40000000000000000000000000000000000025 +:108B50000000000000000000000000000000000015 +:108B60000000000000000000000000000000000005 +:108B700000000000000000000000000000000000F5 +:108B800000000000000000000000000000000000E5 +:108B900000000000000000000000000000000000D5 +:108BA00000000000000000000000000000000000C5 +:108BB00000000000000000000000000000000000B5 +:108BC00000000000000000000000000000000000A5 +:108BD0000000000000000000000000000000000095 +:108BE0000000000000000000000000000000000085 +:108BF0000000000000000000000000000000000075 +:108C00000000000000000000000000000000000064 +:108C10000000000000000000000000000000000054 +:108C20000000000000000000000000000000000044 +:108C30000000000000000000000000000000000034 +:108C40000000000000000000000000000000000024 +:108C50000000000000000000000000000000000014 +:108C60000000000000000000000000000000000004 +:108C700000000000000000000000000000000000F4 +:108C800000000000000000000000000000000000E4 +:108C900000000000000000000000000000000000D4 +:108CA00000000000000000000000000000000000C4 +:108CB00000000000000000000000000000000000B4 +:108CC00000000000000000000000000000000000A4 +:108CD0000000000000000000000000000000000094 +:108CE0000000000000000000000000000000000084 +:108CF0000000000000000000000000000000000074 +:108D00000000000000000000000000000000000063 +:108D10000000000000000000000000000000000053 +:108D20000000000000000000000000000000000043 +:108D30000000000000000000000000000000000033 +:108D40000000000000000000000000000000000023 +:108D50000000000000000000000000000000000013 +:108D60000000000000000000000000000000000003 +:108D700000000000000000000000000000000000F3 +:108D800000000000000000000000000000000000E3 +:108D900000000000000000000000000000000000D3 +:108DA00000000000000000000000000000000000C3 +:108DB00000000000000000000000000000000000B3 +:108DC00000000000000000000000000000000000A3 +:108DD0000000000000000000000000000000000093 +:108DE0000000000000000000000000000000000083 +:108DF0000000000000000000000000000000000073 +:108E00000000000000000000000000000000000062 +:108E10000000000000000000000000000000000052 +:108E20000000000000000000000000000000000042 +:108E30000000000000000000000000000000000032 +:108E40000000000000000000000000000000000022 +:108E50000000000000000000000000000000000012 +:108E60000000000000000000000000000000000002 +:108E700000000000000000000000000000000000F2 +:108E800000000000000000000000000000000000E2 +:108E900000000000000000000000000000000000D2 +:108EA00000000000000000000000000000000000C2 +:108EB00000000000000000000000000000000000B2 +:108EC00000000000000000000000000000000000A2 +:108ED0000000000000000000000000000000000092 +:108EE0000000000000000000000000000000000082 +:108EF0000000000000000000000000000000000072 +:108F00000000000000000000000000000000000061 +:108F10000000000000000000000000000000000051 +:108F20000000000000000000000000000000000041 +:108F30000000000000000000000000000000000031 +:108F40000000000000000000000000000000000021 +:108F50000000000000000000000000000000000011 +:108F60000000000000000000000000000000000001 +:108F700000000000000000000000000000000000F1 +:108F800000000000000000000000000000000000E1 +:108F900000000000000000000000000000000000D1 +:108FA00000000000000000000000000000000000C1 +:108FB00000000000000000000000000000000000B1 +:108FC00000000000000000000000000000000000A1 +:108FD0000000000000000000000000000000000091 +:108FE0000000000000000000000000000000000081 +:108FF0000000000000000000000000000000000071 +:109000000000000000000000000000000000000060 +:109010000000000000000000000000000000000050 +:109020000000000000000000000000000000000040 +:109030000000000000000000000000000000000030 +:109040000000000000000000000000000000000020 +:109050000000000000000000000000000000000010 +:109060000000000000000000000000000000000000 +:1090700000000000000000000000000000000000F0 +:1090800000000000000000000000000000000000E0 +:1090900000000000000000000000000000000000D0 +:1090A00000000000000000000000000000000000C0 +:1090B00000000000000000000000000000000000B0 +:1090C00000000000000000000000000000000000A0 +:1090D0000000000000000000000000000000000090 +:1090E0000000000000000000000000000000000080 +:1090F0000000000000000000000000000000000070 +:10910000000000000000000000000000000000005F +:10911000000000000000000000000000000000004F +:10912000000000000000000000000000000000003F +:10913000000000000000000000000000000000002F +:10914000000000000000000000000000000000001F +:10915000000000000000000000000000000000000F +:1091600000000000000000000000000000000000FF +:1091700000000000000000000000000000000000EF +:1091800000000000000000000000000000000000DF +:1091900000000000000000000000000000000000CF +:1091A00000000000000000000000000000000000BF +:1091B00000000000000000000000000000000000AF +:1091C000000000000000000000000000000000009F +:1091D000000000000000000000000000000000008F +:1091E000000000000000000000000000000000007F +:1091F000000000000000000000000000000000006F +:10920000000000000000000000000000000000005E +:10921000000000000000000000000000000000004E +:10922000000000000000000000000000000000003E +:10923000000000000000000000000000000000002E +:10924000000000000000000000000000000000001E +:10925000000000000000000000000000000000000E +:1092600000000000000000000000000000000000FE +:1092700000000000000000000000000000000000EE +:1092800000000000000000000000000000000000DE +:1092900000000000000000000000000000000000CE +:1092A00000000000000000000000000000000000BE +:1092B00000000000000000000000000000000000AE +:1092C000000000000000000000000000000000009E +:1092D000000000000000000000000000000000008E +:1092E000000000000000000000000000000000007E +:1092F000000000000000000000000000000000006E +:10930000000000000000000000000000000000005D +:10931000000000000000000000000000000000004D +:10932000000000000000000000000000000000003D +:10933000000000000000000000000000000000002D +:10934000000000000000000000000000000000001D +:10935000000000000000000000000000000000000D +:1093600000000000000000000000000000000000FD +:1093700000000000000000000000000000000000ED +:1093800000000000000000000000000000000000DD +:1093900000000000000000000000000000000000CD +:1093A00000000000000000000000000000000000BD +:1093B00000000000000000000000000000000000AD +:1093C000000000000000000000000000000000009D +:1093D000000000000000000000000000000000008D +:1093E000000000000000000000000000000000007D +:1093F000000000000000000000000000000000006D +:10940000000000000000000000000000000000005C +:10941000000000000000000000000000000000004C +:10942000000000000000000000000000000000003C +:10943000000000000000000000000000000000002C +:10944000000000000000000000000000000000001C +:10945000000000000000000000000000000000000C +:1094600000000000000000000000000000000000FC +:1094700000000000000000000000000000000000EC +:1094800000000000000000000000000000000000DC +:1094900000000000000000000000000000000000CC +:1094A00000000000000000000000000000000000BC +:1094B00000000000000000000000000000000000AC +:1094C000000000000000000000000000000000009C +:1094D000000000000000000000000000000000008C +:1094E000000000000000000000000000000000007C +:1094F000000000000000000000000000000000006C +:10950000000000000000000000000000000000005B +:10951000000000000000000000000000000000004B +:10952000000000000000000000000000000000003B +:10953000000000000000000000000000000000002B +:10954000000000000000000000000000000000001B +:10955000000000000000000000000000000000000B +:1095600000000000000000000000000000000000FB +:1095700000000000000000000000000000000000EB +:1095800000000000000000000000000000000000DB +:1095900000000000000000000000000000000000CB +:1095A00000000000000000000000000000000000BB +:1095B00000000000000000000000000000000000AB +:1095C000000000000000000000000000000000009B +:1095D000000000000000000000000000000000008B +:1095E000000000000000000000000000000000007B +:1095F000000000000000000000000000000000006B +:10960000000000000000000000000000000000005A +:10961000000000000000000000000000000000004A +:10962000000000000000000000000000000000003A +:10963000000000000000000000000000000000002A +:10964000000000000000000000000000000000001A +:10965000000000000000000000000000000000000A +:1096600000000000000000000000000000000000FA +:1096700000000000000000000000000000000000EA +:1096800000000000000000000000000000000000DA +:1096900000000000000000000000000000000000CA +:1096A00000000000000000000000000000000000BA +:1096B00000000000000000000000000000000000AA +:1096C000000000000000000000000000000000009A +:1096D000000000000000000000000000000000008A +:1096E000000000000000000000000000000000007A +:1096F000000000000000000000000000000000006A +:109700000000000000000000000000000000000059 +:109710000000000000000000000000000000000049 +:109720000000000000000000000000000000000039 +:109730000000000000000000000000000000000029 +:109740000000000000000000000000000000000019 +:109750000000000000000000000000000000000009 +:1097600000000000000000000000000000000000F9 +:1097700000000000000000000000000000000000E9 +:1097800000000000000000000000000000000000D9 +:1097900000000000000000000000000000000000C9 +:1097A00000000000000000000000000000000000B9 +:1097B00000000000000000000000000000000000A9 +:1097C0000000000000000000000000000000000099 +:1097D0000000000000000000000000000000000089 +:1097E0000000000000000000000000000000000079 +:1097F0000000000000000000000000000000000069 +:109800000000000000000000000000000000000058 +:109810000000000000000000000000000000000048 +:109820000000000000000000000000000000000038 +:109830000000000000000000000000000000000028 +:109840000000000000000000000000000000000018 +:109850000000000000000000000000000000000008 +:1098600000000000000000000000000000000000F8 +:1098700000000000000000000000000000000000E8 +:1098800000000000000000000000000000000000D8 +:1098900000000000000000000000000000000000C8 +:1098A00000000000000000000000000000000000B8 +:1098B00000000000000000000000000000000000A8 +:1098C0000000000000000000000000000000000098 +:1098D0000000000000000000000000000000000088 +:1098E0000000000000000000000000000000000078 +:1098F0000000000000000000000000000000000068 +:109900000000000000000000000000000000000057 +:109910000000000000000000000000000000000047 +:109920000000000000000000000000000000000037 +:109930000000000000000000000000000000000027 +:109940000000000000000000000000000000000017 +:109950000000000000000000000000000000000007 +:1099600000000000000000000000000000000000F7 +:1099700000000000000000000000000000000000E7 +:1099800000000000000000000000000000000000D7 +:1099900000000000000000000000000000000000C7 +:1099A00000000000000000000000000000000000B7 +:1099B00000000000000000000000000000000000A7 +:1099C0000000000000000000000000000000000097 +:1099D0000000000000000000000000000000000087 +:1099E0000000000000000000000000000000000077 +:1099F0000000000000000000000000000000000067 +:109A00000000000000000000000000000000000056 +:109A10000000000000000000000000000000000046 +:109A20000000000000000000000000000000000036 +:109A30000000000000000000000000000000000026 +:109A40000000000000000000000000000000000016 +:109A50000000000000000000000000000000000006 +:109A600000000000000000000000000000000000F6 +:109A700000000000000000000000000000000000E6 +:109A800000000000000000000000000000000000D6 +:109A900000000000000000000000000000000000C6 +:109AA00000000000000000000000000000000000B6 +:109AB00000000000000000000000000000000000A6 +:109AC0000000000000000000000000000000000096 +:109AD0000000000000000000000000000000000086 +:109AE0000000000000000000000000000000000076 +:109AF0000000000000000000000000000000000066 +:109B00000000000000000000000000000000000055 +:109B10000000000000000000000000000000000045 +:109B20000000000000000000000000000000000035 +:109B30000000000000000000000000000000000025 +:109B40000000000000000000000000000000000015 +:109B50000000000000000000000000000000000005 +:109B600000000000000000000000000000000000F5 +:109B700000000000000000000000000000000000E5 +:109B800000000000000000000000000000000000D5 +:109B900000000000000000000000000000000000C5 +:109BA00000000000000000000000000000000000B5 +:109BB00000000000000000000000000000000000A5 +:109BC0000000000000000000000000000000000095 +:109BD0000000000000000000000000000000000085 +:109BE0000000000000000000000000000000000075 +:109BF0000000000000000000000000000000000065 +:109C00000000000000000000000000000000000054 +:109C10000000000000000000000000000000000044 +:109C20000000000000000000000000000000000034 +:109C30000000000000000000000000000000000024 +:109C40000000000000000000000000000000000014 +:109C50000000000000000000000000000000000004 +:109C600000000000000000000000000000000000F4 +:109C700000000000000000000000000000000000E4 +:109C800000000000000000000000000000000000D4 +:109C900000000000000000000000000000000000C4 +:109CA00000000000000000000000000000000000B4 +:109CB00000000000000000000000000000000000A4 +:109CC0000000000000000000000000000000000094 +:109CD0000000000000000000000000000000000084 +:109CE0000000000000000000000000000000000074 +:109CF0000000000000000000000000000000000064 +:109D00000000000000000000000000000000000053 +:109D10000000000000000000000000000000000043 +:109D20000000000000000000000000000000000033 +:109D30000000000000000000000000000000000023 +:109D40000000000000000000000000000000000013 +:109D50000000000000000000000000000000000003 +:109D600000000000000000000000000000000000F3 +:109D700000000000000000000000000000000000E3 +:109D800000000000000000000000000000000000D3 +:109D900000000000000000000000000000000000C3 +:109DA00000000000000000000000000000000000B3 +:109DB00000000000000000000000000000000000A3 +:109DC0000000000000000000000000000000000093 +:109DD0000000000000000000000000000000000083 +:109DE0000000000000000000000000000000000073 +:109DF0000000000000000000000000000000000063 +:109E00000000000000000000000000000000000052 +:109E10000000000000000000000000000000000042 +:109E20000000000000000000000000000000000032 +:109E30000000000000000000000000000000000022 +:109E40000000000000000000000000000000000012 +:109E50000000000000000000000000000000000002 +:109E600000000000000000000000000000000000F2 +:109E700000000000000000000000000000000000E2 +:109E800000000000000000000000000000000000D2 +:109E900000000000000000000000000000000000C2 +:109EA00000000000000000000000000000000000B2 +:109EB00000000000000000000000000000000000A2 +:109EC0000000000000000000000000000000000092 +:109ED0000000000000000000000000000000000082 +:109EE0000000000000000000000000000000000072 +:109EF0000000000000000000000000000000000062 +:109F00000000000000000000000000000000000051 +:109F10000000000000000000000000000000000041 +:109F20000000000000000000000000000000000031 +:109F30000000000000000000000000000000000021 +:109F40000000000000000000000000000000000011 +:109F50000000000000000000000000000000000001 +:109F600000000000000000000000000000000000F1 +:109F700000000000000000000000000000000000E1 +:109F800000000000000000000000000000000000D1 +:109F900000000000000000000000000000000000C1 +:109FA00000000000000000000000000000000000B1 +:109FB00000000000000000000000000000000000A1 +:109FC0000000000000000000000000000000000091 +:109FD0000000000000000000000000000000000081 +:109FE0000000000000000000000000000000000071 +:109FF0000000000000000000000000000000000061 +:10A000000000000000000000000000000000000050 +:10A010000000000000000000000000000000000040 +:10A020000000000000000000000000000000000030 +:10A030000000000000000000000000000000000020 +:10A040000000000000000000000000000000000010 +:10A050000000000000000000000000000000000000 +:10A0600000000000000000000000000000000000F0 +:10A0700000000000000000000000000000000000E0 +:10A0800000000000000000000000000000000000D0 +:10A0900000000000000000000000000000000000C0 +:10A0A00000000000000000000000000000000000B0 +:10A0B00000000000000000000000000000000000A0 +:10A0C0000000000000000000000000000000000090 +:10A0D0000000000000000000000000000000000080 +:10A0E0000000000000000000000000000000000070 +:10A0F0000000000000000000000000000000000060 +:10A10000000000000000000000000000000000004F +:10A11000000000000000000000000000000000003F +:10A12000000000000000000000000000000000002F +:10A13000000000000000000000000000000000001F +:10A14000000000000000000000000000000000000F +:10A1500000000000000000000000000000000000FF +:10A1600000000000000000000000000000000000EF +:10A1700000000000000000000000000000000000DF +:10A1800000000000000000000000000000000000CF +:10A1900000000000000000000000000000000000BF +:10A1A00000000000000000000000000000000000AF +:10A1B000000000000000000000000000000000009F +:10A1C000000000000000000000000000000000008F +:10A1D000000000000000000000000000000000007F +:10A1E000000000000000000000000000000000006F +:10A1F000000000000000000000000000000000005F +:10A20000000000000000000000000000000000004E +:10A21000000000000000000000000000000000003E +:10A22000000000000000000000000000000000002E +:10A23000000000000000000000000000000000001E +:10A24000000000000000000000000000000000000E +:10A2500000000000000000000000000000000000FE +:10A2600000000000000000000000000000000000EE +:10A2700000000000000000000000000000000000DE +:10A2800000000000000000000000000000000000CE +:10A2900000000000000000000000000000000000BE +:10A2A00000000000000000000000000000000000AE +:10A2B000000000000000000000000000000000009E +:10A2C000000000000000000000000000000000008E +:10A2D000000000000000000000000000000000007E +:10A2E000000000000000000000000000000000006E +:10A2F000000000000000000000000000000000005E +:10A30000000000000000000000000000000000004D +:10A31000000000000000000000000000000000003D +:10A32000000000000000000000000000000000002D +:10A33000000000000000000000000000000000001D +:10A34000000000000000000000000000000000000D +:10A3500000000000000000000000000000000000FD +:10A3600000000000000000000000000000000000ED +:10A3700000000000000000000000000000000000DD +:10A3800000000000000000000000000000000000CD +:10A3900000000000000000000000000000000000BD +:10A3A00000000000000000000000000000000000AD +:10A3B000000000000000000000000000000000009D +:10A3C000000000000000000000000000000000008D +:10A3D000000000000000000000000000000000007D +:10A3E000000000000000000000000000000000006D +:10A3F000000000000000000000000000000000005D +:10A40000000000000000000000000000000000004C +:10A41000000000000000000000000000000000003C +:10A42000000000000000000000000000000000002C +:10A43000000000000000000000000000000000001C +:10A44000000000000000000000000000000000000C +:10A4500000000000000000000000000000000000FC +:10A4600000000000000000000000000000000000EC +:10A4700000000000000000000000000000000000DC +:10A4800000000000000000000000000000000000CC +:10A4900000000000000000000000000000000000BC +:10A4A00000000000000000000000000000000000AC +:10A4B000000000000000000000000000000000009C +:10A4C000000000000000000000000000000000008C +:10A4D000000000000000000000000000000000007C +:10A4E000000000000000000000000000000000006C +:10A4F000000000000000000000000000000000005C +:10A50000000000000000000000000000000000004B +:10A51000000000000000000000000000000000003B +:10A52000000000000000000000000000000000002B +:10A53000000000000000000000000000000000001B +:10A54000000000000000000000000000000000000B +:10A5500000000000000000000000000000000000FB +:10A5600000000000000000000000000000000000EB +:10A5700000000000000000000000000000000000DB +:10A5800000000000000000000000000000000000CB +:10A5900000000000000000000000000000000000BB +:10A5A00000000000000000000000000000000000AB +:10A5B000000000000000000000000000000000009B +:10A5C000000000000000000000000000000000008B +:10A5D000000000000000000000000000000000007B +:10A5E000000000000000000000000000000000006B +:10A5F000000000000000000000000000000000005B +:10A60000000000000000000000000000000000004A +:10A61000000000000000000000000000000000003A +:10A62000000000000000000000000000000000002A +:10A63000000000000000000000000000000000001A +:10A64000000000000000000000000000000000000A +:10A6500000000000000000000000000000000000FA +:10A6600000000000000000000000000000000000EA +:10A6700000000000000000000000000000000000DA +:10A6800000000000000000000000000000000000CA +:10A6900000000000000000000000000000000000BA +:10A6A00000000000000000000000000000000000AA +:10A6B000000000000000000000000000000000009A +:10A6C000000000000000000000000000000000008A +:10A6D000000000000000000000000000000000007A +:10A6E000000000000000000000000000000000006A +:10A6F000000000000000000000000000000000005A +:10A700000000000000000000000000000000000049 +:10A710000000000000000000000000000000000039 +:10A720000000000000000000000000000000000029 +:10A730000000000000000000000000000000000019 +:10A740000000000000000000000000000000000009 +:10A7500000000000000000000000000000000000F9 +:10A7600000000000000000000000000000000000E9 +:10A7700000000000000000000000000000000000D9 +:10A7800000000000000000000000000000000000C9 +:10A7900000000000000000000000000000000000B9 +:10A7A00000000000000000000000000000000000A9 +:10A7B0000000000000000000000000000000000099 +:10A7C0000000000000000000000000000000000089 +:10A7D0000000000000000000000000000000000079 +:10A7E0000000000000000000000000000000000069 +:10A7F0000000000000000000000000000000000059 +:10A800000000000000000000000000000000000048 +:10A810000000000000000000000000000000000038 +:10A820000000000000000000000000000000000028 +:10A830000000000000000000000000000000000018 +:10A840000000000000000000000000000000000008 +:10A8500000000000000000000000000000000000F8 +:10A8600000000000000000000000000000000000E8 +:10A8700000000000000000000000000000000000D8 +:10A8800000000000000000000000000000000000C8 +:10A8900000000000000000000000000000000000B8 +:10A8A00000000000000000000000000000000000A8 +:10A8B0000000000000000000000000000000000098 +:10A8C0000000000000000000000000000000000088 +:10A8D0000000000000000000000000000000000078 +:10A8E0000000000000000000000000000000000068 +:10A8F0000000000000000000000000000000000058 +:10A900000000000000000000000000000000000047 +:10A910000000000000000000000000000000000037 +:10A920000000000000000000000000000000000027 +:10A930000000000000000000000000000000000017 +:10A940000000000000000000000000000000000007 +:10A9500000000000000000000000000000000000F7 +:10A9600000000000000000000000000000000000E7 +:10A9700000000000000000000000000000000000D7 +:10A9800000000000000000000000000000000000C7 +:10A9900000000000000000000000000000000000B7 +:10A9A00000000000000000000000000000000000A7 +:10A9B0000000000000000000000000000000000097 +:10A9C0000000000000000000000000000000000087 +:10A9D0000000000000000000000000000000000077 +:10A9E0000000000000000000000000000000000067 +:10A9F0000000000000000000000000000000000057 +:10AA00000000000000000000000000000000000046 +:10AA10000000000000000000000000000000000036 +:10AA20000000000000000000000000000000000026 +:10AA30000000000000000000000000000000000016 +:10AA40000000000000000000000000000000000006 +:10AA500000000000000000000000000000000000F6 +:10AA600000000000000000000000000000000000E6 +:10AA700000000000000000000000000000000000D6 +:10AA800000000000000000000000000000000000C6 +:10AA900000000000000000000000000000000000B6 +:10AAA00000000000000000000000000000000000A6 +:10AAB0000000000000000000000000000000000096 +:10AAC0000000000000000000000000000000000086 +:10AAD0000000000000000000000000000000000076 +:10AAE0000000000000000000000000000000000066 +:10AAF0000000000000000000000000000000000056 +:10AB00000000000000000000000000000000000045 +:10AB10000000000000000000000000000000000035 +:10AB20000000000000000000000000000000000025 +:10AB30000000000000000000000000000000000015 +:10AB40000000000000000000000000000000000005 +:10AB500000000000000000000000000000000000F5 +:10AB600000000000000000000000000000000000E5 +:10AB700000000000000000000000000000000000D5 +:10AB800000000000000000000000000000000000C5 +:10AB900000000000000000000000000000000000B5 +:10ABA00000000000000000000000000000000000A5 +:10ABB0000000000000000000000000000000000095 +:10ABC0000000000000000000000000000000000085 +:10ABD0000000000000000000000000000000000075 +:10ABE0000000000000000000000000000000000065 +:10ABF0000000000000000000000000000000000055 +:10AC00000000000000000000000000000000000044 +:10AC10000000000000000000000000000000000034 +:10AC20000000000000000000000000000000000024 +:10AC30000000000000000000000000000000000014 +:10AC40000000000000000000000000000000000004 +:10AC500000000000000000000000000000000000F4 +:10AC600000000000000000000000000000000000E4 +:10AC700000000000000000000000000000000000D4 +:10AC800000000000000000000000000000000000C4 +:10AC900000000000000000000000000000000000B4 +:10ACA00000000000000000000000000000000000A4 +:10ACB0000000000000000000000000000000000094 +:10ACC0000000000000000000000000000000000084 +:10ACD0000000000000000000000000000000000074 +:10ACE0000000000000000000000000000000000064 +:10ACF0000000000000000000000000000000000054 +:10AD00000000000000000000000000000000000043 +:10AD10000000000000000000000000000000000033 +:10AD20000000000000000000000000000000000023 +:10AD30000000000000000000000000000000000013 +:10AD40000000000000000000000000000000000003 +:10AD500000000000000000000000000000000000F3 +:10AD600000000000000000000000000000000000E3 +:10AD700000000000000000000000000000000000D3 +:10AD800000000000000000000000000000000000C3 +:10AD900000000000000000000000000000000000B3 +:10ADA00000000000000000000000000000000000A3 +:10ADB0000000000000000000000000000000000093 +:10ADC0000000000000000000000000000000000083 +:10ADD0000000000000000000000000000000000073 +:10ADE0000000000000000000000000000000000063 +:10ADF0000000000000000000000000000000000053 +:10AE00000000000000000000000000000000000042 +:10AE10000000000000000000000000000000000032 +:10AE20000000000000000000000000000000000022 +:10AE30000000000000000000000000000000000012 +:10AE40000000000000000000000000000000000002 +:10AE500000000000000000000000000000000000F2 +:10AE600000000000000000000000000000000000E2 +:10AE700000000000000000000000000000000000D2 +:10AE800000000000000000000000000000000000C2 +:10AE900000000000000000000000000000000000B2 +:10AEA00000000000000000000000000000000000A2 +:10AEB0000000000000000000000000000000000092 +:10AEC0000000000000000000000000000000000082 +:10AED0000000000000000000000000000000000072 +:10AEE0000000000000000000000000000000000062 +:10AEF0000000000000000000000000000000000052 +:10AF00000000000000000000000000000000000041 +:10AF10000000000000000000000000000000000031 +:10AF20000000000000000000000000000000000021 +:10AF30000000000000000000000000000000000011 +:10AF40000000000000000000000000000000000001 +:10AF500000000000000000000000000000000000F1 +:10AF600000000000000000000000000000000000E1 +:10AF700000000000000000000000000000000000D1 +:10AF800000000000000000000000000000000000C1 +:10AF900000000000000000000000000000000000B1 +:10AFA00000000000000000000000000000000000A1 +:10AFB0000000000000000000000000000000000091 +:10AFC0000000000000000000000000000000000081 +:10AFD0000000000000000000000000000000000071 +:10AFE0000000000000000000000000000000000061 +:10AFF0000000000000000000000000000000000051 +:10B000000000000000000000000000000000000040 +:10B010000000000000000000000000000000000030 +:10B020000000000000000000000000000000000020 +:10B030000000000000000000000000000000000010 +:10B040000000000000000000000000000000000000 +:10B0500000000000000000000000000000000000F0 +:10B0600000000000000000000000000000000000E0 +:10B0700000000000000000000000000000000000D0 +:10B0800000000000000000000000000000000000C0 +:10B0900000000000000000000000000000000000B0 +:10B0A00000000000000000000000000000000000A0 +:10B0B0000000000000000000000000000000000090 +:10B0C0000000000000000000000000000000000080 +:10B0D0000000000000000000000000000000000070 +:10B0E0000000000000000000000000000000000060 +:10B0F0000000000000000000000000000000000050 +:10B10000000000000000000000000000000000003F +:10B11000000000000000000000000000000000002F +:10B12000000000000000000000000000000000001F +:10B13000000000000000000000000000000000000F +:10B1400000000000000000000000000000000000FF +:10B1500000000000000000000000000000000000EF +:10B1600000000000000000000000000000000000DF +:10B1700000000000000000000000000000000000CF +:10B1800000000000000000000000000000000000BF +:10B1900000000000000000000000000000000000AF +:10B1A000000000000000000000000000000000009F +:10B1B000000000000000000000000000000000008F +:10B1C000000000000000000000000000000000007F +:10B1D000000000000000000000000000000000006F +:10B1E000000000000000000000000000000000005F +:10B1F000000000000000000000000000000000004F +:10B20000000000000000000000000000000000003E +:10B21000000000000000000000000000000000002E +:10B22000000000000000000000000000000000001E +:10B23000000000000000000000000000000000000E +:10B2400000000000000000000000000000000000FE +:10B2500000000000000000000000000000000000EE +:10B2600000000000000000000000000000000000DE +:10B2700000000000000000000000000000000000CE +:10B2800000000000000000000000000000000000BE +:10B2900000000000000000000000000000000000AE +:10B2A000000000000000000000000000000000009E +:10B2B000000000000000000000000000000000008E +:10B2C000000000000000000000000000000000007E +:10B2D000000000000000000000000000000000006E +:10B2E000000000000000000000000000000000005E +:10B2F000000000000000000000000000000000004E +:10B30000000000000000000000000000000000003D +:10B31000000000000000000000000000000000002D +:10B32000000000000000000000000000000000001D +:10B33000000000000000000000000000000000000D +:10B3400000000000000000000000000000000000FD +:10B3500000000000000000000000000000000000ED +:10B3600000000000000000000000000000000000DD +:10B3700000000000000000000000000000000000CD +:10B3800000000000000000000000000000000000BD +:10B3900000000000000000000000000000000000AD +:10B3A000000000000000000000000000000000009D +:10B3B000000000000000000000000000000000008D +:10B3C000000000000000000000000000000000007D +:10B3D000000000000000000000000000000000006D +:10B3E000000000000000000000000000000000005D +:10B3F000000000000000000000000000000000004D +:10B40000000000000000000000000000000000003C +:10B41000000000000000000000000000000000002C +:10B42000000000000000000000000000000000001C +:10B43000000000000000000000000000000000000C +:10B4400000000000000000000000000000000000FC +:10B4500000000000000000000000000000000000EC +:10B4600000000000000000000000000000000000DC +:10B4700000000000000000000000000000000000CC +:10B4800000000000000000000000000000000000BC +:10B4900000000000000000000000000000000000AC +:10B4A000000000000000000000000000000000009C +:10B4B000000000000000000000000000000000008C +:10B4C000000000000000000000000000000000007C +:10B4D000000000000000000000000000000000006C +:10B4E000000000000000000000000000000000005C +:10B4F000000000000000000000000000000000004C +:10B50000000000000000000000000000000000003B +:10B51000000000000000000000000000000000002B +:10B52000000000000000000000000000000000001B +:10B53000000000000000000000000000000000000B +:10B5400000000000000000000000000000000000FB +:10B5500000000000000000000000000000000000EB +:10B5600000000000000000000000000000000000DB +:10B5700000000000000000000000000000000000CB +:10B5800000000000000000000000000000000000BB +:10B5900000000000000000000000000000000000AB +:10B5A000000000000000000000000000000000009B +:10B5B000000000000000000000000000000000008B +:10B5C000000000000000000000000000000000007B +:10B5D000000000000000000000000000000000006B +:10B5E000000000000000000000000000000000005B +:10B5F000000000000000000000000000000000004B +:10B60000000000000000000000000000000000003A +:10B61000000000000000000000000000000000002A +:10B62000000000000000000000000000000000001A +:10B63000000000000000000000000000000000000A +:10B6400000000000000000000000000000000000FA +:10B6500000000000000000000000000000000000EA +:10B6600000000000000000000000000000000000DA +:10B6700000000000000000000000000000000000CA +:10B6800000000000000000000000000000000000BA +:10B6900000000000000000000000000000000000AA +:10B6A000000000000000000000000000000000009A +:10B6B000000000000000000000000000000000008A +:10B6C000000000000000000000000000000000007A +:10B6D000000000000000000000000000000000006A +:10B6E000000000000000000000000000000000005A +:10B6F000000000000000000000000000000000004A +:10B700000000000000000000000000000000000039 +:10B710000000000000000000000000000000000029 +:10B720000000000000000000000000000000000019 +:10B730000000000000000000000000000000000009 +:10B7400000000000000000000000000000000000F9 +:10B7500000000000000000000000000000000000E9 +:10B7600000000000000000000000000000000000D9 +:10B7700000000000000000000000000000000000C9 +:10B7800000000000000000000000000000000000B9 +:10B7900000000000000000000000000000000000A9 +:10B7A0000000000000000000000000000000000099 +:10B7B0000000000000000000000000000000000089 +:10B7C0000000000000000000000000000000000079 +:10B7D0000000000000000000000000000000000069 +:10B7E0000000000000000000000000000000000059 +:10B7F0000000000000000000000000000000000049 +:10B800000000000000000000000000000000000038 +:10B810000000000000000000000000000000000028 +:10B820000000000000000000000000000000000018 +:10B830000000000000000000000000000000000008 +:10B8400000000000000000000000000000000000F8 +:10B8500000000000000000000000000000000000E8 +:10B8600000000000000000000000000000000000D8 +:10B8700000000000000000000000000000000000C8 +:10B8800000000000000000000000000000000000B8 +:10B8900000000000000000000000000000000000A8 +:10B8A0000000000000000000000000000000000098 +:10B8B0000000000000000000000000000000000088 +:10B8C0000000000000000000000000000000000078 +:10B8D0000000000000000000000000000000000068 +:10B8E0000000000000000000000000000000000058 +:10B8F0000000000000000000000000000000000048 +:10B900000000000000000000000000000000000037 +:10B910000000000000000000000000000000000027 +:10B920000000000000000000000000000000000017 +:10B930000000000000000000000000000000000007 +:10B9400000000000000000000000000000000000F7 +:10B9500000000000000000000000000000000000E7 +:10B9600000000000000000000000000000000000D7 +:10B9700000000000000000000000000000000000C7 +:10B9800000000000000000000000000000000000B7 +:10B9900000000000000000000000000000000000A7 +:10B9A0000000000000000000000000000000000097 +:10B9B0000000000000000000000000000000000087 +:10B9C0000000000000000000000000000000000077 +:10B9D0000000000000000000000000000000000067 +:10B9E0000000000000000000000000000000000057 +:10B9F0000000000000000000000000000000000047 +:10BA00000000000000000000000000000000000036 +:10BA10000000000000000000000000000000000026 +:10BA20000000000000000000000000000000000016 +:10BA30000000000000000000000000000000000006 +:10BA400000000000000000000000000000000000F6 +:10BA500000000000000000000000000000000000E6 +:10BA600000000000000000000000000000000000D6 +:10BA700000000000000000000000000000000000C6 +:10BA800000000000000000000000000000000000B6 +:10BA900000000000000000000000000000000000A6 +:10BAA0000000000000000000000000000000000096 +:10BAB0000000000000000000000000000000000086 +:10BAC0000000000000000000000000000000000076 +:10BAD0000000000000000000000000000000000066 +:10BAE0000000000000000000000000000000000056 +:10BAF0000000000000000000000000000000000046 +:10BB00000000000000000000000000000000000035 +:10BB10000000000000000000000000000000000025 +:10BB20000000000000000000000000000000000015 +:10BB30000000000000000000000000000000000005 +:10BB400000000000000000000000000000000000F5 +:10BB500000000000000000000000000000000000E5 +:10BB600000000000000000000000000000000000D5 +:10BB700000000000000000000000000000000000C5 +:10BB800000000000000000000000000000000000B5 +:10BB900000000000000000000000000000000000A5 +:10BBA0000000000000000000000000000000000095 +:10BBB0000000000000000000000000000000000085 +:10BBC0000000000000000000000000000000000075 +:10BBD0000000000000000000000000000000000065 +:10BBE0000000000000000000000000000000000055 +:10BBF0000000000000000000000000000000000045 +:10BC00000000000000000000000000000000000034 +:10BC10000000000000000000000000000000000024 +:10BC20000000000000000000000000000000000014 +:10BC30000000000000000000000000000000000004 +:10BC400000000000000000000000000000000000F4 +:10BC500000000000000000000000000000000000E4 +:10BC600000000000000000000000000000000000D4 +:10BC700000000000000000000000000000000000C4 +:10BC800000000000000000000000000000000000B4 +:10BC900000000000000000000000000000000000A4 +:10BCA0000000000000000000000000000000000094 +:10BCB0000000000000000000000000000000000084 +:10BCC0000000000000000000000000000000000074 +:10BCD0000000000000000000000000000000000064 +:10BCE0000000000000000000000000000000000054 +:10BCF0000000000000000000000000000000000044 +:10BD00000000000000000000000000000000000033 +:10BD10000000000000000000000000000000000023 +:10BD20000000000000000000000000000000000013 +:10BD30000000000000000000000000000000000003 +:10BD400000000000000000000000000000000000F3 +:10BD500000000000000000000000000000000000E3 +:10BD600000000000000000000000000000000000D3 +:10BD700000000000000000000000000000000000C3 +:10BD800000000000000000000000000000000000B3 +:10BD900000000000000000000000000000000000A3 +:10BDA0000000000000000000000000000000000093 +:10BDB0000000000000000000000000000000000083 +:10BDC0000000000000000000000000000000000073 +:10BDD0000000000000000000000000000000000063 +:10BDE0000000000000000000000000000000000053 +:10BDF0000000000000000000000000000000000043 +:10BE00000000000000000000000000000000000032 +:10BE10000000000000000000000000000000000022 +:10BE20000000000000000000000000000000000012 +:10BE30000000000000000000000000000000000002 +:10BE400000000000000000000000000000000000F2 +:10BE500000000000000000000000000000000000E2 +:10BE600000000000000000000000000000000000D2 +:10BE700000000000000000000000000000000000C2 +:10BE800000000000000000000000000000000000B2 +:10BE900000000000000000000000000000000000A2 +:10BEA0000000000000000000000000000000000092 +:10BEB0000000000000000000000000000000000082 +:10BEC0000000000000000000000000000000000072 +:10BED0000000000000000000000000000000000062 +:10BEE0000000000000000000000000000000000052 +:10BEF0000000000000000000000000000000000042 +:10BF00000000000000000000000000000000000031 +:10BF10000000000000000000000000000000000021 +:10BF20000000000000000000000000000000000011 +:10BF30000000000000000000000000000000000001 +:10BF400000000000000000000000000000000000F1 +:10BF500000000000000000000000000000000000E1 +:10BF600000000000000000000000000000000000D1 +:10BF700000000000000000000000000000000000C1 +:10BF800000000000000000000000000000000000B1 +:10BF900000000000000000000000000000000000A1 +:10BFA0000000000000000000000000000000000091 +:10BFB0000000000000000000000000000000000081 +:10BFC0000000000000000000000000000000000071 +:10BFD0000000000000000000000000000000000061 +:10BFE0000000000000000000000000000000000051 +:10BFF0000000000000000000000000000000000041 +:10C000000000000000000000000000000000000030 +:10C010000000000000000000000000000000000020 +:10C020000000000000000000000000000000000010 +:10C030000000000000000000000000000000000000 +:10C0400000000000000000000000000000000000F0 +:10C0500000000000000000000000000000000000E0 +:10C0600000000000000000000000000000000000D0 +:10C0700000000000000000000000000000000000C0 +:10C0800000000000000000000000000000000000B0 +:10C0900000000000000000000000000000000000A0 +:10C0A0000000000000000000000000000000000090 +:10C0B0000000000000000000000000000000000080 +:10C0C0000000000000000000000000000000000070 +:10C0D0000000000000000000000000000000000060 +:10C0E0000000000000000000000000000000000050 +:10C0F0000000000000000000000000000000000040 +:10C10000000000000000000000000000000000002F +:10C11000000000000000000000000000000000001F +:10C12000000000000000000000000000000000000F +:10C1300000000000000000000000000000000000FF +:10C1400000000000000000000000000000000000EF +:10C1500000000000000000000000000000000000DF +:10C1600000000000000000000000000000000000CF +:10C1700000000000000000000000000000000000BF +:10C1800000000000000000000000000000000000AF +:10C19000000000000000000000000000000000009F +:10C1A000000000000000000000000000000000008F +:10C1B000000000000000000000000000000000007F +:10C1C000000000000000000000000000000000006F +:10C1D000000000000000000000000000000000005F +:10C1E000000000000000000000000000000000004F +:10C1F000000000000000000000000000000000003F +:10C20000000000000000000000000000000000002E +:10C21000000000000000000000000000000000001E +:10C22000000000000000000000000000000000000E +:10C2300000000000000000000000000000000000FE +:10C2400000000000000000000000000000000000EE +:10C2500000000000000000000000000000000000DE +:10C2600000000000000000000000000000000000CE +:10C2700000000000000000000000000000000000BE +:10C2800000000000000000000000000000000000AE +:10C29000000000000000000000000000000000009E +:10C2A000000000000000000000000000000000008E +:10C2B000000000000000000000000000000000007E +:10C2C000000000000000000000000000000000006E +:10C2D000000000000000000000000000000000005E +:10C2E000000000000000000000000000000000004E +:10C2F000000000000000000000000000000000003E +:10C30000000000000000000000000000000000002D +:10C31000000000000000000000000000000000001D +:10C32000000000000000000000000000000000000D +:10C3300000000000000000000000000000000000FD +:10C3400000000000000000000000000000000000ED +:10C3500000000000000000000000000000000000DD +:10C3600000000000000000000000000000000000CD +:10C3700000000000000000000000000000000000BD +:10C3800000000000000000000000000000000000AD +:10C39000000000000000000000000000000000009D +:10C3A000000000000000000000000000000000008D +:10C3B000000000000000000000000000000000007D +:10C3C000000000000000000000000000000000006D +:10C3D000000000000000000000000000000000005D +:10C3E000000000000000000000000000000000004D +:10C3F000000000000000000000000000000000003D +:10C40000000000000000000000000000000000002C +:10C41000000000000000000000000000000000001C +:10C42000000000000000000000000000000000000C +:10C4300000000000000000000000000000000000FC +:10C4400000000000000000000000000000000000EC +:10C4500000000000000000000000000000000000DC +:10C4600000000000000000000000000000000000CC +:10C4700000000000000000000000000000000000BC +:10C4800000000000000000000000000000000000AC +:10C49000000000000000000000000000000000009C +:10C4A000000000000000000000000000000000008C +:10C4B000000000000000000000000000000000007C +:10C4C000000000000000000000000000000000006C +:10C4D000000000000000000000000000000000005C +:10C4E000000000000000000000000000000000004C +:10C4F000000000000000000000000000000000003C +:10C50000000000000000000000000000000000002B +:10C51000000000000000000000000000000000001B +:10C52000000000000000000000000000000000000B +:10C5300000000000000000000000000000000000FB +:10C5400000000000000000000000000000000000EB +:10C5500000000000000000000000000000000000DB +:10C5600000000000000000000000000000000000CB +:10C5700000000000000000000000000000000000BB +:10C5800000000000000000000000000000000000AB +:10C59000000000000000000000000000000000009B +:10C5A000000000000000000000000000000000008B +:10C5B000000000000000000000000000000000007B +:10C5C000000000000000000000000000000000006B +:10C5D000000000000000000000000000000000005B +:10C5E000000000000000000000000000000000004B +:10C5F000000000000000000000000000000000003B +:10C60000000000000000000000000000000000002A +:10C61000000000000000000000000000000000001A +:10C62000000000000000000000000000000000000A +:10C6300000000000000000000000000000000000FA +:10C6400000000000000000000000000000000000EA +:10C6500000000000000000000000000000000000DA +:10C6600000000000000000000000000000000000CA +:10C6700000000000000000000000000000000000BA +:10C6800000000000000000000000000000000000AA +:10C69000000000000000000000000000000000009A +:10C6A000000000000000000000000000000000008A +:10C6B000000000000000000000000000000000007A +:10C6C000000000000000000000000000000000006A +:10C6D000000000000000000000000000000000005A +:10C6E000000000000000000000000000000000004A +:10C6F000000000000000000000000000000000003A +:10C700000000000000000000000000000000000029 +:10C710000000000000000000000000000000000019 +:10C720000000000000000000000000000000000009 +:10C7300000000000000000000000000000000000F9 +:10C7400000000000000000000000000000000000E9 +:10C7500000000000000000000000000000000000D9 +:10C7600000000000000000000000000000000000C9 +:10C7700000000000000000000000000000000000B9 +:10C7800000000000000000000000000000000000A9 +:10C790000000000000000000000000000000000099 +:10C7A0000000000000000000000000000000000089 +:10C7B0000000000000000000000000000000000079 +:10C7C0000000000000000000000000000000000069 +:10C7D0000000000000000000000000000000000059 +:10C7E0000000000000000000000000000000000049 +:10C7F0000000000000000000000000000000000039 +:10C800000000000000000000000000000000000028 +:10C810000000000000000000000000000000000018 +:10C820000000000000000000000000000000000008 +:10C8300000000000000000000000000000000000F8 +:10C8400000000000000000000000000000000000E8 +:10C8500000000000000000000000000000000000D8 +:10C8600000000000000000000000000000000000C8 +:10C8700000000000000000000000000000000000B8 +:10C8800000000000000000000000000000000000A8 +:10C890000000000000000000000000000000000098 +:10C8A0000000000000000000000000000000000088 +:10C8B0000000000000000000000000000000000078 +:10C8C0000000000000000000000000000000000068 +:10C8D0000000000000000000000000000000000058 +:10C8E0000000000000000000000000000000000048 +:10C8F0000000000000000000000000000000000038 +:10C900000000000000000000000000000000000027 +:10C910000000000000000000000000000000000017 +:10C920000000000000000000000000000000000007 +:10C9300000000000000000000000000000000000F7 +:10C9400000000000000000000000000000000000E7 +:10C9500000000000000000000000000000000000D7 +:10C9600000000000000000000000000000000000C7 +:10C9700000000000000000000000000000000000B7 +:10C9800000000000000000000000000000000000A7 +:10C990000000000000000000000000000000000097 +:10C9A0000000000000000000000000000000000087 +:10C9B0000000000000000000000000000000000077 +:10C9C0000000000000000000000000000000000067 +:10C9D0000000000000000000000000000000000057 +:10C9E0000000000000000000000000000000000047 +:10C9F0000000000000000000000000000000000037 +:10CA00000000000000000000000000000000000026 +:10CA10000000000000000000000000000000000016 +:10CA20000000000000000000000000000000000006 +:10CA300000000000000000000000000000000000F6 +:10CA400000000000000000000000000000000000E6 +:10CA500000000000000000000000000000000000D6 +:10CA600000000000000000000000000000000000C6 +:10CA700000000000000000000000000000000000B6 +:10CA800000000000000000000000000000000000A6 +:10CA90000000000000000000000000000000000096 +:10CAA0000000000000000000000000000000000086 +:10CAB0000000000000000000000000000000000076 +:10CAC0000000000000000000000000000000000066 +:10CAD0000000000000000000000000000000000056 +:10CAE0000000000000000000000000000000000046 +:10CAF0000000000000000000000000000000000036 +:10CB00000000000000000000000000000000000025 +:10CB10000000000000000000000000000000000015 +:10CB20000000000000000000000000000000000005 +:10CB300000000000000000000000000000000000F5 +:10CB400000000000000000000000000000000000E5 +:10CB500000000000000000000000000000000000D5 +:10CB600000000000000000000000000000000000C5 +:10CB700000000000000000000000000000000000B5 +:10CB800000000000000000000000000000000000A5 +:10CB90000000000000000000000000000000000095 +:10CBA0000000000000000000000000000000000085 +:10CBB0000000000000000000000000000000000075 +:10CBC0000000000000000000000000000000000065 +:10CBD0000000000000000000000000000000000055 +:10CBE0000000000000000000000000000000000045 +:10CBF0000000000000000000000000000000000035 +:10CC00000000000000000000000000000000000024 +:10CC10000000000000000000000000000000000014 +:10CC20000000000000000000000000000000000004 +:10CC300000000000000000000000000000000000F4 +:10CC400000000000000000000000000000000000E4 +:10CC500000000000000000000000000000000000D4 +:10CC600000000000000000000000000000000000C4 +:10CC700000000000000000000000000000000000B4 +:10CC800000000000000000000000000000000000A4 +:10CC90000000000000000000000000000000000094 +:10CCA0000000000000000000000000000000000084 +:10CCB0000000000000000000000000000000000074 +:10CCC0000000000000000000000000000000000064 +:10CCD0000000000000000000000000000000000054 +:10CCE0000000000000000000000000000000000044 +:10CCF0000000000000000000000000000000000034 +:10CD00000000000000000000000000000000000023 +:10CD10000000000000000000000000000000000013 +:10CD20000000000000000000000000000000000003 +:10CD300000000000000000000000000000000000F3 +:10CD400000000000000000000000000000000000E3 +:10CD500000000000000000000000000000000000D3 +:10CD600000000000000000000000000000000000C3 +:10CD700000000000000000000000000000000000B3 +:10CD800000000000000000000000000000000000A3 +:10CD90000000000000000000000000000000000093 +:10CDA0000000000000000000000000000000000083 +:10CDB0000000000000000000000000000000000073 +:10CDC0000000000000000000000000000000000063 +:10CDD0000000000000000000000000000000000053 +:10CDE0000000000000000000000000000000000043 +:10CDF0000000000000000000000000000000000033 +:10CE00000000000000000000000000000000000022 +:10CE10000000000000000000000000000000000012 +:10CE20000000000000000000000000000000000002 +:10CE300000000000000000000000000000000000F2 +:10CE400000000000000000000000000000000000E2 +:10CE500000000000000000000000000000000000D2 +:10CE600000000000000000000000000000000000C2 +:10CE700000000000000000000000000000000000B2 +:10CE800000000000000000000000000000000000A2 +:10CE90000000000000000000000000000000000092 +:10CEA0000000000000000000000000000000000082 +:10CEB0000000000000000000000000000000000072 +:10CEC0000000000000000000000000000000000062 +:10CED0000000000000000000000000000000000052 +:10CEE0000000000000000000000000000000000042 +:10CEF0000000000000000000000000000000000032 +:10CF00000000000000000000000000000000000021 +:10CF10000000000000000000000000000000000011 +:10CF20000000000000000000000000000000000001 +:10CF300000000000000000000000000000000000F1 +:10CF400000000000000000000000000000000000E1 +:10CF500000000000000000000000000000000000D1 +:10CF600000000000000000000000000000000000C1 +:10CF700000000000000000000000000000000000B1 +:10CF800000000000000000000000000000000000A1 +:10CF90000000000000000000000000000000000091 +:10CFA0000000000000000000000000000000000081 +:10CFB0000000000000000000000000000000000071 +:10CFC0000000000000000000000000000000000061 +:10CFD0000000000000000000000000000000000051 +:10CFE0000000000000000000000000000000000041 +:10CFF0000000000000000000000000000000000031 +:10D000000000000000000000000000000000000020 +:10D010000000000000000000000000000000000010 +:10D020000000000000000000000000000000000000 +:10D0300000000000000000000000000000000000F0 +:10D0400000000000000000000000000000000000E0 +:10D0500000000000000000000000000000000000D0 +:10D0600000000000000000000000000000000000C0 +:10D0700000000000000000000000000000000000B0 +:10D0800000000000000000000000000000000000A0 +:10D090000000000000000000000000000000000090 +:10D0A0000000000000000000000000000000000080 +:10D0B0000000000000000000000000000000000070 +:10D0C0000000000000000000000000000000000060 +:10D0D0000000000000000000000000000000000050 +:10D0E0000000000000000000000000000000000040 +:10D0F0000000000000000000000000000000000030 +:10D10000000000000000000000000000000000001F +:10D11000000000000000000000000000000000000F +:10D1200000000000000000000000000000000000FF +:10D1300000000000000000000000000000000000EF +:10D1400000000000000000000000000000000000DF +:10D1500000000000000000000000000000000000CF +:10D1600000000000000000000000000000000000BF +:10D1700000000000000000000000000000000000AF +:10D18000000000000000000000000000000000009F +:10D19000000000000000000000000000000000008F +:10D1A000000000000000000000000000000000007F +:10D1B000000000000000000000000000000000006F +:10D1C000000000000000000000000000000000005F +:10D1D000000000000000000000000000000000004F +:10D1E000000000000000000000000000000000003F +:10D1F000000000000000000000000000000000002F +:10D20000000000000000000000000000000000001E +:10D21000000000000000000000000000000000000E +:10D2200000000000000000000000000000000000FE +:10D2300000000000000000000000000000000000EE +:10D2400000000000000000000000000000000000DE +:10D2500000000000000000000000000000000000CE +:10D2600000000000000000000000000000000000BE +:10D2700000000000000000000000000000000000AE +:10D28000000000000000000000000000000000009E +:10D29000000000000000000000000000000000008E +:10D2A000000000000000000000000000000000007E +:10D2B000000000000000000000000000000000006E +:10D2C000000000000000000000000000000000005E +:10D2D000000000000000000000000000000000004E +:10D2E000000000000000000000000000000000003E +:10D2F000000000000000000000000000000000002E +:10D30000000000000000000000000000000000001D +:10D31000000000000000000000000000000000000D +:10D3200000000000000000000000000000000000FD +:10D3300000000000000000000000000000000000ED +:10D3400000000000000000000000000000000000DD +:10D3500000000000000000000000000000000000CD +:10D3600000000000000000000000000000000000BD +:10D3700000000000000000000000000000000000AD +:10D38000000000000000000000000000000000009D +:10D39000000000000000000000000000000000008D +:10D3A000000000000000000000000000000000007D +:10D3B000000000000000000000000000000000006D +:10D3C000000000000000000000000000000000005D +:10D3D000000000000000000000000000000000004D +:10D3E000000000000000000000000000000000003D +:10D3F000000000000000000000000000000000002D +:10D40000000000000000000000000000000000001C +:10D41000000000000000000000000000000000000C +:10D4200000000000000000000000000000000000FC +:10D4300000000000000000000000000000000000EC +:10D4400000000000000000000000000000000000DC +:10D4500000000000000000000000000000000000CC +:10D4600000000000000000000000000000000000BC +:10D4700000000000000000000000000000000000AC +:10D48000000000000000000000000000000000009C +:10D49000000000000000000000000000000000008C +:10D4A000000000000000000000000000000000007C +:10D4B000000000000000000000000000000000006C +:10D4C000000000000000000000000000000000005C +:10D4D000000000000000000000000000000000004C +:10D4E000000000000000000000000000000000003C +:10D4F000000000000000000000000000000000002C +:10D50000000000000000000000000000000000001B +:10D51000000000000000000000000000000000000B +:10D5200000000000000000000000000000000000FB +:10D5300000000000000000000000000000000000EB +:10D5400000000000000000000000000000000000DB +:10D5500000000000000000000000000000000000CB +:10D5600000000000000000000000000000000000BB +:10D5700000000000000000000000000000000000AB +:10D58000000000000000000000000000000000009B +:10D59000000000000000000000000000000000008B +:10D5A000000000000000000000000000000000007B +:10D5B000000000000000000000000000000000006B +:10D5C000000000000000000000000000000000005B +:10D5D000000000000000000000000000000000004B +:10D5E000000000000000000000000000000000003B +:10D5F000000000000000000000000000000000002B +:10D60000000000000000000000000000000000001A +:10D61000000000000000000000000000000000000A +:10D6200000000000000000000000000000000000FA +:10D6300000000000000000000000000000000000EA +:10D6400000000000000000000000000000000000DA +:10D6500000000000000000000000000000000000CA +:10D6600000000000000000000000000000000000BA +:10D6700000000000000000000000000000000000AA +:10D68000000000000000000000000000000000009A +:10D69000000000000000000000000000000000008A +:10D6A000000000000000000000000000000000007A +:10D6B000000000000000000000000000000000006A +:10D6C000000000000000000000000000000000005A +:10D6D000000000000000000000000000000000004A +:10D6E000000000000000000000000000000000003A +:10D6F000000000000000000000000000000000002A +:10D700000000000000000000000000000000000019 +:10D710000000000000000000000000000000000009 +:10D7200000000000000000000000000000000000F9 +:10D7300000000000000000000000000000000000E9 +:10D7400000000000000000000000000000000000D9 +:10D7500000000000000000000000000000000000C9 +:10D7600000000000000000000000000000000000B9 +:10D7700000000000000000000000000000000000A9 +:10D780000000000000000000000000000000000099 +:10D790000000000000000000000000000000000089 +:10D7A0000000000000000000000000000000000079 +:10D7B0000000000000000000000000000000000069 +:10D7C0000000000000000000000000000000000059 +:10D7D0000000000000000000000000000000000049 +:10D7E0000000000000000000000000000000000039 +:10D7F0000000000000000000000000000000000029 +:10D800000000000000000000000000000000000018 +:10D810000000000000000000000000000000000008 +:10D8200000000000000000000000000000000000F8 +:10D8300000000000000000000000000000000000E8 +:10D8400000000000000000000000000000000000D8 +:10D8500000000000000000000000000000000000C8 +:10D8600000000000000000000000000000000000B8 +:10D8700000000000000000000000000000000000A8 +:10D880000000000000000000000000000000000098 +:10D890000000000000000000000000000000000088 +:10D8A0000000000000000000000000000000000078 +:10D8B0000000000000000000000000000000000068 +:10D8C0000000000000000000000000000000000058 +:10D8D0000000000000000000000000000000000048 +:10D8E0000000000000000000000000000000000038 +:10D8F0000000000000000000000000000000000028 +:10D900000000000000000000000000000000000017 +:10D910000000000000000000000000000000000007 +:10D9200000000000000000000000000000000000F7 +:10D9300000000000000000000000000000000000E7 +:10D9400000000000000000000000000000000000D7 +:10D9500000000000000000000000000000000000C7 +:10D9600000000000000000000000000000000000B7 +:10D9700000000000000000000000000000000000A7 +:10D980000000000000000000000000000000000097 +:10D990000000000000000000000000000000000087 +:10D9A0000000000000000000000000000000000077 +:10D9B0000000000000000000000000000000000067 +:10D9C0000000000000000000000000000000000057 +:10D9D0000000000000000000000000000000000047 +:10D9E0000000000000000000000000000000000037 +:10D9F0000000000000000000000000000000000027 +:10DA00000000000000000000000000000000000016 +:10DA10000000000000000000000000000000000006 +:10DA200000000000000000000000000000000000F6 +:10DA300000000000000000000000000000000000E6 +:10DA400000000000000000000000000000000000D6 +:10DA500000000000000000000000000000000000C6 +:10DA600000000000000000000000000000000000B6 +:10DA700000000000000000000000000000000000A6 +:10DA80000000000000000000000000000000000096 +:10DA90000000000000000000000000000000000086 +:10DAA0000000000000000000000000000000000076 +:10DAB0000000000000000000000000000000000066 +:10DAC0000000000000000000000000000000000056 +:10DAD0000000000000000000000000000000000046 +:10DAE0000000000000000000000000000000000036 +:10DAF0000000000000000000000000000000000026 +:10DB00000000000000000000000000000000000015 +:10DB10000000000000000000000000000000000005 +:10DB200000000000000000000000000000000000F5 +:10DB300000000000000000000000000000000000E5 +:10DB400000000000000000000000000000000000D5 +:10DB500000000000000000000000000000000000C5 +:10DB600000000000000000000000000000000000B5 +:10DB700000000000000000000000000000000000A5 +:10DB80000000000000000000000000000000000095 +:10DB90000000000000000000000000000000000085 +:10DBA0000000000000000000000000000000000075 +:10DBB0000000000000000000000000000000000065 +:10DBC0000000000000000000000000000000000055 +:10DBD0000000000000000000000000000000000045 +:10DBE0000000000000000000000000000000000035 +:10DBF0000000000000000000000000000000000025 +:10DC00000000000000000000000000000000000014 +:10DC10000000000000000000000000000000000004 +:10DC200000000000000000000000000000000000F4 +:10DC300000000000000000000000000000000000E4 +:10DC400000000000000000000000000000000000D4 +:10DC500000000000000000000000000000000000C4 +:10DC600000000000000000000000000000000000B4 +:10DC700000000000000000000000000000000000A4 +:10DC80000000000000000000000000000000000094 +:10DC90000000000000000000000000000000000084 +:10DCA0000000000000000000000000000000000074 +:10DCB0000000000000000000000000000000000064 +:10DCC0000000000000000000000000000000000054 +:10DCD0000000000000000000000000000000000044 +:10DCE0000000000000000000000000000000000034 +:10DCF0000000000000000000000000000000000024 +:10DD00000000000000000000000000000000000013 +:10DD10000000000000000000000000000000000003 +:10DD200000000000000000000000000000000000F3 +:10DD300000000000000000000000000000000000E3 +:10DD400000000000000000000000000000000000D3 +:10DD500000000000000000000000000000000000C3 +:10DD600000000000000000000000000000000000B3 +:10DD700000000000000000000000000000000000A3 +:10DD80000000000000000000000000000000000093 +:10DD90000000000000000000000000000000000083 +:10DDA0000000000000000000000000000000000073 +:10DDB0000000000000000000000000000000000063 +:10DDC0000000000000000000000000000000000053 +:10DDD0000000000000000000000000000000000043 +:10DDE0000000000000000000000000000000000033 +:10DDF0000000000000000000000000000000000023 +:10DE00000000000000000000000000000000000012 +:10DE10000000000000000000000000000000000002 +:10DE200000000000000000000000000000000000F2 +:10DE300000000000000000000000000000000000E2 +:10DE400000000000000000000000000000000000D2 +:10DE500000000000000000000000000000000000C2 +:10DE600000000000000000000000000000000000B2 +:10DE700000000000000000000000000000000000A2 +:10DE80000000000000000000000000000000000092 +:10DE90000000000000000000000000000000000082 +:10DEA0000000000000000000000000000000000072 +:10DEB0000000000000000000000000000000000062 +:10DEC0000000000000000000000000000000000052 +:10DED0000000000000000000000000000000000042 +:10DEE0000000000000000000000000000000000032 +:10DEF0000000000000000000000000000000000022 +:10DF00000000000000000000000000000000000011 +:10DF10000000000000000000000000000000000001 +:10DF200000000000000000000000000000000000F1 +:10DF300000000000000000000000000000000000E1 +:10DF400000000000000000000000000000000000D1 +:10DF500000000000000000000000000000000000C1 +:10DF600000000000000000000000000000000000B1 +:10DF700000000000000000000000000000000000A1 +:10DF80000000000000000000000000000000000091 +:10DF90000000000000000000000000000000000081 +:10DFA0000000000000000000000000000000000071 +:10DFB0000000000000000000000000000000000061 +:10DFC0000000000000000000000000000000000051 +:10DFD0000000000000000000000000000000000041 +:10DFE0000000000000000000000000000000000031 +:10DFF0000000000000000000000000000000000021 +:10E000000000000000000000000000000000000010 +:10E010000000000000000000000000000000000000 +:10E0200000000000000000000000000000000000F0 +:10E0300000000000000000000000000000000000E0 +:10E0400000000000000000000000000000000000D0 +:10E0500000000000000000000000000000000000C0 +:10E0600000000000000000000000000000000000B0 +:10E0700000000000000000000000000000000000A0 +:10E080000000000000000000000000000000000090 +:10E090000000000000000000000000000000000080 +:10E0A0000000000000000000000000000000000070 +:10E0B0000000000000000000000000000000000060 +:10E0C0000000000000000000000000000000000050 +:10E0D0000000000000000000000000000000000040 +:10E0E0000000000000000000000000000000000030 +:10E0F0000000000000000000000000000000000020 +:10E10000000000000000000000000000000000000F +:10E1100000000000000000000000000000000000FF +:10E1200000000000000000000000000000000000EF +:10E1300000000000000000000000000000000000DF +:10E1400000000000000000000000000000000000CF +:10E1500000000000000000000000000000000000BF +:10E1600000000000000000000000000000000000AF +:10E17000000000000000000000000000000000009F +:10E18000000000000000000000000000000000008F +:10E19000000000000000000000000000000000007F +:10E1A000000000000000000000000000000000006F +:10E1B000000000000000000000000000000000005F +:10E1C000000000000000000000000000000000004F +:10E1D000000000000000000000000000000000003F +:10E1E000000000000000000000000000000000002F +:10E1F000000000000000000000000000000000001F +:10E20000000000000000000000000000000000000E +:10E2100000000000000000000000000000000000FE +:10E2200000000000000000000000000000000000EE +:10E2300000000000000000000000000000000000DE +:10E2400000000000000000000000000000000000CE +:10E2500000000000000000000000000000000000BE +:10E2600000000000000000000000000000000000AE +:10E27000000000000000000000000000000000009E +:10E28000000000000000000000000000000000008E +:10E29000000000000000000000000000000000007E +:10E2A000000000000000000000000000000000006E +:10E2B000000000000000000000000000000000005E +:10E2C000000000000000000000000000000000004E +:10E2D000000000000000000000000000000000003E +:10E2E000000000000000000000000000000000002E +:10E2F000000000000000000000000000000000001E +:10E30000000000000000000000000000000000000D +:10E3100000000000000000000000000000000000FD +:10E3200000000000000000000000000000000000ED +:10E3300000000000000000000000000000000000DD +:10E3400000000000000000000000000000000000CD +:10E3500000000000000000000000000000000000BD +:10E3600000000000000000000000000000000000AD +:10E37000000000000000000000000000000000009D +:10E38000000000000000000000000000000000008D +:10E39000000000000000000000000000000000007D +:10E3A000000000000000000000000000000000006D +:10E3B000000000000000000000000000000000005D +:10E3C000000000000000000000000000000000004D +:10E3D000000000000000000000000000000000003D +:10E3E000000000000000000000000000000000002D +:10E3F000000000000000000000000000000000001D +:10E40000000000000000000000000000000000000C +:10E4100000000000000000000000000000000000FC +:10E4200000000000000000000000000000000000EC +:10E4300000000000000000000000000000000000DC +:10E4400000000000000000000000000000000000CC +:10E4500000000000000000000000000000000000BC +:10E4600000000000000000000000000000000000AC +:10E47000000000000000000000000000000000009C +:10E48000000000000000000000000000000000008C +:10E49000000000000000000000000000000000007C +:10E4A000000000000000000000000000000000006C +:10E4B000000000000000000000000000000000005C +:10E4C000000000000000000000000000000000004C +:10E4D000000000000000000000000000000000003C +:10E4E000000000000000000000000000000000002C +:10E4F000000000000000000000000000000000001C +:10E50000000000000000000000000000000000000B +:10E5100000000000000000000000000000000000FB +:10E5200000000000000000000000000000000000EB +:10E5300000000000000000000000000000000000DB +:10E5400000000000000000000000000000000000CB +:10E5500000000000000000000000000000000000BB +:10E5600000000000000000000000000000000000AB +:10E57000000000000000000000000000000000009B +:10E58000000000000000000000000000000000008B +:10E59000000000000000000000000000000000007B +:10E5A000000000000000000000000000000000006B +:10E5B000000000000000000000000000000000005B +:10E5C000000000000000000000000000000000004B +:10E5D000000000000000000000000000000000003B +:10E5E000000000000000000000000000000000002B +:10E5F000000000000000000000000000000000001B +:10E60000000000000000000000000000000000000A +:10E6100000000000000000000000000000000000FA +:10E6200000000000000000000000000000000000EA +:10E6300000000000000000000000000000000000DA +:10E6400000000000000000000000000000000000CA +:10E6500000000000000000000000000000000000BA +:10E6600000000000000000000000000000000000AA +:10E67000000000000000000000000000000000009A +:10E68000000000000000000000000000000000008A +:10E69000000000000000000000000000000000007A +:10E6A000000000000000000000000000000000006A +:10E6B000000000000000000000000000000000005A +:10E6C000000000000000000000000000000000004A +:10E6D000000000000000000000000000000000003A +:10E6E000000000000000000000000000000000002A +:10E6F000000000000000000000000000000000001A +:10E700000000000000000000000000000000000009 +:10E7100000000000000000000000000000000000F9 +:10E7200000000000000000000000000000000000E9 +:10E7300000000000000000000000000000000000D9 +:10E7400000000000000000000000000000000000C9 +:10E7500000000000000000000000000000000000B9 +:10E7600000000000000000000000000000000000A9 +:10E770000000000000000000000000000000000099 +:10E780000000000000000000000000000000000089 +:10E790000000000000000000000000000000000079 +:10E7A0000000000000000000000000000000000069 +:10E7B0000000000000000000000000000000000059 +:10E7C0000000000000000000000000000000000049 +:10E7D0000000000000000000000000000000000039 +:10E7E0000000000000000000000000000000000029 +:10E7F0000000000000000000000000000000000019 +:10E800000000000000000000000000000000000008 +:10E8100000000000000000000000000000000000F8 +:10E8200000000000000000000000000000000000E8 +:10E8300000000000000000000000000000000000D8 +:10E8400000000000000000000000000000000000C8 +:10E8500000000000000000000000000000000000B8 +:10E8600000000000000000000000000000000000A8 +:10E870000000000000000000000000000000000098 +:10E880000000000000000000000000000000000088 +:10E890000000000000000000000000000000000078 +:10E8A0000000000000000000000000000000000068 +:10E8B0000000000000000000000000000000000058 +:10E8C0000000000000000000000000000000000048 +:10E8D0000000000000000000000000000000000038 +:10E8E0000000000000000000000000000000000028 +:10E8F0000000000000000000000000000000000018 +:10E900000000000000000000000000000000000007 +:10E9100000000000000000000000000000000000F7 +:10E9200000000000000000000000000000000000E7 +:10E9300000000000000000000000000000000000D7 +:10E9400000000000000000000000000000000000C7 +:10E9500000000000000000000000000000000000B7 +:10E9600000000000000000000000000000000000A7 +:10E970000000000000000000000000000000000097 +:10E980000000000000000000000000000000000087 +:10E990000000000000000000000000000000000077 +:10E9A0000000000000000000000000000000000067 +:10E9B0000000000000000000000000000000000057 +:10E9C0000000000000000000000000000000000047 +:10E9D0000000000000000000000000000000000037 +:10E9E0000000000000000000000000000000000027 +:10E9F0000000000000000000000000000000000017 +:10EA00000000000000000000000000000000000006 +:10EA100000000000000000000000000000000000F6 +:10EA200000000000000000000000000000000000E6 +:10EA300000000000000000000000000000000000D6 +:10EA400000000000000000000000000000000000C6 +:10EA500000000000000000000000000000000000B6 +:10EA600000000000000000000000000000000000A6 +:10EA70000000000000000000000000000000000096 +:10EA80000000000000000000000000000000000086 +:10EA90000000000000000000000000000000000076 +:10EAA0000000000000000000000000000000000066 +:10EAB0000000000000000000000000000000000056 +:10EAC0000000000000000000000000000000000046 +:10EAD0000000000000000000000000000000000036 +:10EAE0000000000000000000000000000000000026 +:10EAF0000000000000000000000000000000000016 +:10EB00000000000000000000000000000000000005 +:10EB100000000000000000000000000000000000F5 +:10EB200000000000000000000000000000000000E5 +:10EB300000000000000000000000000000000000D5 +:10EB400000000000000000000000000000000000C5 +:10EB500000000000000000000000000000000000B5 +:10EB600000000000000000000000000000000000A5 +:10EB70000000000000000000000000000000000095 +:10EB80000000000000000000000000000000000085 +:10EB90000000000000000000000000000000000075 +:10EBA0000000000000000000000000000000000065 +:10EBB0000000000000000000000000000000000055 +:10EBC0000000000000000000000000000000000045 +:10EBD0000000000000000000000000000000000035 +:10EBE0000000000000000000000000000000000025 +:10EBF0000000000000000000000000000000000015 +:10EC00000000000000000000000000000000000004 +:10EC100000000000000000000000000000000000F4 +:10EC200000000000000000000000000000000000E4 +:10EC300000000000000000000000000000000000D4 +:10EC400000000000000000000000000000000000C4 +:10EC500000000000000000000000000000000000B4 +:10EC600000000000000000000000000000000000A4 +:10EC70000000000000000000000000000000000094 +:10EC80000000000000000000000000000000000084 +:10EC90000000000000000000000000000000000074 +:10ECA0000000000000000000000000000000000064 +:10ECB0000000000000000000000000000000000054 +:10ECC0000000000000000000000000000000000044 +:10ECD0000000000000000000000000000000000034 +:10ECE0000000000000000000000000000000000024 +:10ECF0000000000000000000000000000000000014 +:10ED00000000000000000000000000000000000003 +:10ED100000000000000000000000000000000000F3 +:10ED200000000000000000000000000000000000E3 +:10ED300000000000000000000000000000000000D3 +:10ED400000000000000000000000000000000000C3 +:10ED500000000000000000000000000000000000B3 +:10ED600000000000000000000000000000000000A3 +:10ED70000000000000000000000000000000000093 +:10ED80000000000000000000000000000000000083 +:10ED90000000000000000000000000000000000073 +:10EDA0000000000000000000000000000000000063 +:10EDB0000000000000000000000000000000000053 +:10EDC0000000000000000000000000000000000043 +:10EDD0000000000000000000000000000000000033 +:10EDE0000000000000000000000000000000000023 +:10EDF0000000000000000000000000000000000013 +:10EE00000000000000000000000000000000000002 +:10EE100000000000000000000000000000000000F2 +:10EE200000000000000000000000000000000000E2 +:10EE300000000000000000000000000000000000D2 +:10EE400000000000000000000000000000000000C2 +:10EE500000000000000000000000000000000000B2 +:10EE600000000000000000000000000000000000A2 +:10EE70000000000000000000000000000000000092 +:10EE80000000000000000000000000000000000082 +:10EE90000000000000000000000000000000000072 +:10EEA0000000000000000000000000000000000062 +:10EEB0000000000000000000000000000000000052 +:10EEC0000000000000000000000000000000000042 +:10EED0000000000000000000000000000000000032 +:10EEE0000000000000000000000000000000000022 +:10EEF0000000000000000000000000000000000012 +:10EF00000000000000000000000000000000000001 +:10EF100000000000000000000000000000000000F1 +:10EF200000000000000000000000000000000000E1 +:10EF300000000000000000000000000000000000D1 +:10EF400000000000000000000000000000000000C1 +:10EF500000000000000000000000000000000000B1 +:10EF600000000000000000000000000000000000A1 +:10EF70000000000000000000000000000000000091 +:10EF80000000000000000000000000000000000081 +:10EF90000000000000000000000000000000000071 +:10EFA0000000000000000000000000000000000061 +:10EFB0000000000000000000000000000000000051 +:10EFC0000000000000000000000000000000000041 +:10EFD0000000000000000000000000000000000031 +:10EFE0000000000000000000000000000000000021 +:10EFF0000000000000000000000000000000000011 +:00000001FF diff --git a/tests/fixtures/nuphy-air60_smk_jtag.hex b/tests/fixtures/nuphy-air60_smk_jtag.hex new file mode 100644 index 0000000..b2c2f7c --- /dev/null +++ b/tests/fixtures/nuphy-air60_smk_jtag.hex @@ -0,0 +1,3841 @@ +:1000000002F000320000000000000032000000009A +:10001000000000320000000000000032000000007C +:10002000000000320000000000000032000000006C +:100030000000003200000000000000021329000050 +:10004000000000020F5E000000000032000000000F +:10005000000000320000000000000032000000003C +:100060000000003200000000000000020FDA020170 +:10007000F7758185123BFBE582600302006E79040F +:10008000E94400601B7A0190413F78A775A002E423 +:1000900093F2A308B8000205A0D9F4DAF275A0FF24 +:1000A000E478FFF6D8FD7800E84400600A7900752E +:1000B000A000E4F309D8FC78A7E84402600C7903B7 +:1000C000900000E4F0A3D8FCD9FA750D00750E007D +:1000D00075337575343E75357F75363EE4F51AF522 +:1000E0001BE4F540F54102006EAF82BFA502800A15 +:1000F000BFA6028009BFA70E8008900001229000D1 +:1001000002229000042290000022AF82BFA800507B +:10011000030201CFEF243D50030201CFEF2458FF2B +:10012000240A83F582EF241F83F583E47363676BEE +:100130006F737F878B838F9397A3A7ABAFB3B7C33F +:10014000777BBBBF9B9FC7CB01010101010101016F +:10015000010101010101010101010101010101018F +:100160000101019000E2229000E9229000EA229031 +:1001700000B5229000B6229000B3229000B42290E5 +:1001800000B7229000CC229000CD229001832290D3 +:10019000018A22900192229001942290019F229044 +:1001A00001CB22900221229002232290022422904D +:1001B000022522900226229002272290006F229090 +:1001C00000702290022A2290029F229002A0229088 +:1001D000000022120F5A120EDD120F9D1229C212B8 +:1001E0000483120ECE1210D543B54043B94043B537 +:1001F0004085B9B9D2AF221201D374FFC0E0743B7D +:10020000C0E07480C0E012344115811581158174FD +:100210000CC0E0743CC0E07480C0E0123441158131 +:10022000158115819003E8120EF4122C3B121FCF9A +:10023000122DE29003E8120EF475B100122C4C124C +:100240002DB41207F280F2AF82BFA502800ABFA6CA +:10025000028009BFA70E80089000012290000222B0 +:100260009000042290000022AF82BFA80050030239 +:10027000032DEF243D500302032DEF2458FF240AE1 +:1002800083F582EF241F83F583E473C1C5C9CDD103 +:10029000DDE5E9E1EDF1F50105090D111521D5D9EE +:1002A000191DF9FD252902020202020202020202C0 +:1002B0000202030303030303030202030302020314 +:1002C000039000E2229000E9229000EA229000B51B +:1002D000229000B6229000B3229000B4229000B782 +:1002E000229000CC229000CD229001832290018A9E +:1002F00022900192229001942290019F229001CBA2 +:100300002290022122900223229002242290022590 +:1003100022900226229002272290006F22900070E5 +:100320002290022A2290029F229002A02290000096 +:1003300022022D5D022D7AAE82AF839002A7E0FCEF +:10034000A3E0FDEEB50405EFB50501229002A7EE8E +:10035000F0EFA3F09000007401F0900001EEF0EFD8 +:10036000A3F0900000022D97AE82AF839002A9E027 +:10037000FCA3E0FDEEB50405EFB50501229002A94E +:10038000EEF0EFA3F09000007402F0900001EEF0A8 +:10039000EFA3F0900000022D97AF82BFA502800A64 +:1003A000BFA6028009BFA70E80089000012290001E +:1003B00002229000042290000022AF82BFA80050C9 +:1003C0000302047FEF243D500302047FEF2458FF13 +:1003D000240A83F582EF241F83F583E47313171B2C +:1003E0001F232F373B333F434753575B5F6367738D +:1003F000272B6B6F4B4F777B040404040404040425 +:1004000004040404040404040404040404040404AC +:100410000404049000E2229000E9229000EA229075 +:1004200000B5229000B6229000B3229000B4229032 +:1004300000B7229000CC229000CD22900183229020 +:10044000018A22900192229001942290019F229091 +:1004500001CB22900221229002232290022422909A +:10046000022522900226229002272290006F2290DD +:1004700000702290022A2290029F229002A02290D5 +:10048000000022750800750900750A007E007F00D3 +:10049000C3EE9410EF64809480501FEE2403F58225 +:1004A000EF3400F583E4F0EE2413F582EF3400F529 +:1004B00083E4F00EBE00D90F80D622E5822403F536 +:1004C00082E43400F583E0F5822285098222E58208 +:1004D000C42354E0FF24B4FDE4343EFEE50B250BB9 +:1004E000FC2DF582E43EF583E493FDA3E493FEE561 +:1004F0000C60067A3C7B3C80047A3F7B3C8B017B22 +:1005000080C007C006C005C004C002C001C003C04F +:1005100005C006742BC0E0743CC0E0EBC0E01234B0 +:1005200041E58124F8F581D004D005D006D0078DAF +:10053000028E03C3EA9420EB9452403F743F9A74B6 +:10054000529B4037E50C60098D03741F5BF50A80F0 +:100550000C90002575F000120E3C750A00AA0A7B6B +:1005600000C002C0037444C0E0743CC0E07480C0AA +:10057000E0123441E58124FBF58122E50A6033E590 +:100580000A75F0A0A424B4FA743E35F0FBEF2AFA01 +:10059000E43BFBEC2AF582E43BF583E493FCA3E423 +:1005A00093FF8C028F03BA0105BB000280048C0507 +:1005B0008F06850C198D828E83C006C005122D1FF3 +:1005C000E582D005D006700122850C1C8D828E83B9 +:1005D000C006C005123230E582D005D00670012277 +:1005E0008D048E07C3EC94E0EF9400403E74E79CCA +:1005F000E49F4037E50C60198D0374075BF5F00547 +:10060000F07401800225E0D5F0FBF582120E8B809C +:10061000178D0374075BF5F005F07401800225E087 +:10062000D5F0FBF582120E90020A00C3EC9404EFA1 +:100630009400402674A49CE49F401FE50C600D8D3F +:100640001290002575F000120DCC800B8D139000D8 +:100650002575F000120E04020A00C3EC94A5EF9475 +:1006600000403F74A79CE49F4038E50C602E8D034A +:10067000BBA502800ABBA602800BBBA714800C7A24 +:10068000017B0080107A027B00800A7A047B008064 +:10069000047A007B008A828B830203379000000279 +:1006A0000337C3EC94A8EF940050030207D8C37437 +:1006B000C29CE49F50030207D8E50C70030207D1E7 +:1006C0008D07BFA80050030207C4EF243D5003026A +:1006D00007C4EF2458FF240A83F582EF241F83F513 +:1006E00083E4731920272E354A585E51646A70825C +:1006F000888E949AA0B23C43A6AC767CB8BE07071D +:100700000707070707070707070707070707070779 +:100710000707070707070707077CE27F000207C8EC +:100720007CE97F000207C87CEA7F000207C87CB52D +:100730007F000207C87CB67F000207C87CB37F0039 +:100740000207C87CB47F000207C87CB77F0002079D +:10075000C87CCC7F000207C87CCD7F00806A7C8388 +:100760007F0180647C8A7F01805E7C927F0180585B +:100770007C947F0180527C9F7F01804C7CCB7F01E9 +:1007800080467C217F0280407C237F02803A7C244B +:100790007F0280347C257F02802E7C267F02802889 +:1007A0007C277F0280227C6F7F00801C7C707F0012 +:1007B00080167C2A7F0280107C9F7F02800A7CA0AA +:1007C0007F0280047C007F008C828F83120368800C +:1007D0000690000002036822C005C0067458C0E0FD +:1007E000743CC0E07480C0E0123441E58124FBF524 +:1007F00081221204CAE5827003F58222900023E46C +:10080000F0FFBF10005039900023E0FEE07031EFA0 +:100810002413F582E43400F583E0FD8F82C007C025 +:1008200006C0051204BBAC82D005D006D007EC622E +:1008300005EE4205900023ED24FFE433F00F80C263 +:10084000900023E0FFE07005F5098F82227F00BF52 +:100850001000506C8F82C0071204BBAE82D007EF2D +:100860002413F582E43400F583E0FDEE6205ED60CB +:100870004C9000247401F07C00BC050050339000C3 +:1008800024E0FB5D6021EE5203EB24FFE433F50C22 +:100890008F0B8C82C007C006C005C0041204CED0E6 +:1008A00004D005D006D0070C900024E025E0F080AD +:1008B000C8EF2413F582E43400F583EEF00F808F47 +:1008C000750900900023E0F58222122DE5E50970FC +:1008D000238508821229398508821229A3AF82C094 +:1008E000071229B5D007E5082403F582E43400F5A2 +:1008F00083EFF4F085084290006175F000122DF747 +:1009000074F125084007E50804F50880067508001D +:1009100075090102300BAF82BFA502800ABFA60293 +:100920008009BFA70E80089000012290000222904B +:1009300000042290000022AF82BFA80050030209E9 +:10094000FCEF243D50030209FCEF2458FF240A83E6 +:10095000F582EF241F83F583E4739094989CA0ACF8 +:10096000B4B8B0BCC0C4D0D4D8DCE0E4F0A4A8E8EB +:10097000ECC8CCF4F80909090909090909090909A8 +:1009800009090909090909090909090909090909D7 +:100990009000E2229000E9229000EA229000B52225 +:1009A0009000B6229000B3229000B4229000B722AB +:1009B0009000CC229000CD229001832290018A22C7 +:1009C000900192229001942290019F229001CB22CB +:1009D00090022122900223229002242290022522BA +:1009E000900226229002272290006F22900070220F +:1009F00090022A2290029F229002A02290000022C0 +:100A00001211C5AF82BF0109900063E06003020AC2 +:100A100062020A14900025E50DF0E50D450E9000E8 +:100A200025F075752D7576007577007578087579E0 +:100A30000090002575F000123383E5828583F04530 +:100A4000F0601E75752575760075770075780875E8 +:100A5000790090002D75F00012329A900025020363 +:100A600031229000357406F0900036E50DF0E50D6A +:100A7000450E900036F075754B75760075770075EC +:100A8000781675790090003575F000123383E58291 +:100A90008583F045F0601E757535757600757700B5 +:100AA00075781675790090004B75F00012329A90A7 +:100AB000003502033422AD82AE83AFF074022DFD07 +:100AC000E43EFE7C06C007C006C005C0041211C586 +:100AD000AB82D004D005D006D007BB010E900063D6 +:100AE000E060087D377E007F007C147B008C021C58 +:100AF000EA60158D828E838FF0123BA9FAA3AD8236 +:100B0000AE83EA60E80B80E58B8222AD82AE83AFD4 +:100B1000F0C007C006C0051211C5AC82D005D006D2 +:100B2000D007BC0142900063E0603C7C00BC140034 +:100B30005012EC2437FAE43400FB8A828B83E07095 +:100B4000030C80E98C03EBC40354F8FBEC2437F569 +:100B500082E43400F583E0F582C003120EA2AC8279 +:100B6000D003EC4BF5822274022DFDE43EFE8D8213 +:100B70008E838FF0123BA9F58222AD82AE83AFF057 +:100B8000E50F7003F58222C007C006C0051211C52B +:100B9000AC82D005D006D007BC014B900063E0606A +:100BA00045E50FC423541FFCBC14005035EC24371A +:100BB000F582E43400F583E50F5407FC8CF005F072 +:100BC0007C017B008006EC2CFCEB33FBD5F0F7E0DE +:100BD000F97A00E95204EA5203EC4B24FFE433F5BE +:100BE00082227582002274022DFDE43EFE7B007C91 +:100BF00000C3EB9406EC64809480501FEB2DF8EC5E +:100C00003EF98F02888289838AF0123BA9B50F04CE +:100C1000758201220BBB00D90C80D675820022851B +:100C2000827685837785F0787CFF74022576F9E4F7 +:100C30003577FAAB7889798A7A8B7B7800C3E86458 +:100C40008094865033E829FDE43AFE8B078D828E2E +:100C5000838FF0123BA9B57502801DBCFF17E825F4 +:100C600079FDE4357AFEAF7B8D828E838FF0123B67 +:100C7000A9700288040880C5B80622BCFF0122744E +:100C8000022576FDE43577FEAF78EC2DFCE43EFBE3 +:100C90008F028C828B838AF0E57502330E22AD823F +:100CA000AE83AFF074022DFDE43EFE8D778E788F1B +:100CB0007975760074FA25764044C005C006C007F1 +:100CC000E5762577F8E43578F9AF79888289838FDE +:100CD000F0123BA9B575028008D007D006D0058078 +:100CE00019D007D006D005E5762DF8E43EF98F043B +:100CF000888289838CF0E412330E057680B622ADAB +:100D000082AE83AFF0E510C423541FFCBC14005026 +:100D10003674022DFDE43EFEEC2DFCE43EFB8F021A +:100D2000E5105407F5F005F07401800225E0D5F0D8 +:100D3000FBFF8C828B838AF0123BA942078C828B4B +:100D4000838AF0EF02330EAE107F00C006C0077436 +:100D500073C0E0743CC0E07480C0E0123441E581AF +:100D600024FBF58122AD82AE83AFF0E511C423549C +:100D70001FFCBC1400503774022DFDE43EFEEC2D28 +:100D8000FCE43EFB8F02E5115407F5F005F0740119 +:100D9000800225E0D5F0FBF4FF8C828B838AF01271 +:100DA0003BA952078C828B838AF0EF02330EAE117F +:100DB0007F00C006C0077491C0E0743CC0E074803E +:100DC000C0E0123441E58124FBF58122AD82AE837F +:100DD000AFF0C007C006C0051211C5AC82D005D067 +:100DE00006D007BC0112900063E0600C85121090E1 +:100DF000003575F000020CFF8512758D828E838F91 +:100E0000F0020C1FAD82AE83AFF0C007C006C00574 +:100E10001211C5AC82D005D006D007BC01129000DB +:100E200063E0600C85131190003575F000020D65CC +:100E30008513758D828E838FF0020C9EAD82AE83FA +:100E4000AFF0C007C006C0051211C5AC82D005D0F6 +:100E500006D007BC0118900063E06012757500753C +:100E6000761475770090003775F0000232EF740247 +:100E70002DFDE43EFE7575007576067577008D8252 +:100E80008E838FF00232EF850D8222E582420D22A1 +:100E9000E582F4520D2285820D22750D0022850E09 +:100EA0008222AF827E00EFC4540FFD60048D077E66 +:100EB00004EF0303543FFD60088D078E0574022D77 +:100EC000FEEFC31360058E07EF04FE8E82229000B2 +:100ED00061E4F0900062F090006304F02275B208C3 +:100EE00075BC02E5BC20E20575B10080F643BC018B +:100EF00043B20422AE82AF837C007D00C3EC9EED42 +:100F00009F501D9003E8C007C006C005C004120F23 +:100F100021D004D005D006D0070CBC00DF0D80DC4A +:100F200022AE82AF837C007D00C3EC9EED9F501FFC +:100F300075B100000000000000000000000000008B +:100F400000000000000000000CBC00DD0D80DA2273 +:100F5000C2AF75F0A5745A02FF00758F0122C0213F +:100F6000C0E0C0F0C082C083C007C006C005C00496 +:100F7000C003C002C001C000C0D075D0001208CAB2 +:100F8000D0D0D000D001D002D003D004D005D006FC +:100F9000D007D083D082D0F0D0E0D0213275D840B5 +:100FA00075ADFF75AEE675AF0075870075AB007562 +:100FB000AC0053A9BFC20010D902800010D8028033 +:100FC0000043A94022AF8253A9BFD2008FAA43A9F0 +:100FD0004030000575B10080F82253A9BF10D90236 +:100FE0008002C20043A94032020FC5AF82BFA502F2 +:100FF000800ABFA6028009BFA70E800890000122C8 +:10100000900002229000042290000022AF82BFA82C +:101010000050030210D1EF243D50030210D1EF2401 +:1010200058FF240A83F582EF241F83F583E4736558 +:10103000696D717581898D85919599A5A9ADB1B5B8 +:10104000B9C5797DBDC19DA1C9CD1010101010107A +:101050001010101010101010101010101010101090 +:1010600010101010109000E2229000E9229000EA87 +:10107000229000B5229000B6229000B3229000B4D6 +:10108000229000B7229000CC229000CD22900183C4 +:101090002290018A22900192229001942290019F35 +:1010A000229001CB2290022122900223229002243E +:1010B0002290022522900226229002272290006F81 +:1010C000229000702290022A2290029F229002A079 +:1010D0002290000022900264E4F0900265F09002F9 +:1010E00066F0900267F0900268F0C20190026AE434 +:1010F000F0900269F0F522F523F524F525F526F5A3 +:101100009675945F7595117591C043A90122AE82C1 +:10111000AF837D00BDFF00501AE59930E2159000C5 +:1011200028C007C006C005120F21D005D006D00781 +:101130000D80E17D007531088E828F838DF0121D48 +:1011400066539CE0439C0843990422AE82AF837DA2 +:1011500000BDFF00501AE59A30E215900028C00744 +:10116000C006C005120F21D005D006D0070D80E1C2 +:101170007D007532168E828F838DF0121DCF539DA8 +:1011800080439D16439A0422AE82AF837D00BDFF4B +:1011900000501AE59A30E215900028C007C006C03A +:1011A00005120F21D005D006D0070D80E17D007516 +:1011B00032038E828F838DF0121DCF539D80439D0D +:1011C00003439A0422900267E0F58222C2AF9002A4 +:1011D0006B75F000121D48D2AF90026BE0FF9002D9 +:1011E0006CE0FE8F05BD0002802CBD01030212766B +:1011F000BF02030212ABBF21030212D8BF800280DC +:1012000050BF8103021286BF82030212C8BFA1032E +:10121000021300021322BE01028014BE03028015D5 +:10122000BE05028016BE07028017BE091D801590FC +:10123000026B02144090026B02146E90026B021457 +:10124000050215EE90026B0215484397084397027A +:1012500022BE0002800ABE0602800BBE0811800C6E +:1012600090026B0215F590026B02171B021AD04315 +:10127000970843970222BE0B0690026B0215A0430B +:10128000970843970222BE0002800ABE0602800B26 +:10129000BE0A11800C90026B02163090026B02178E +:1012A0001B021AE843970843970222BE01028005F9 +:1012B000BE030E800690026B02149C90026B021417 +:1012C000FA43970843970222BE000690026B02166B +:1012D0007343970843970222BE0902800ABE0A029E +:1012E000800BBE0B14800C90026B021B0490026BEF +:1012F000021B5F90026B021B8143970843970222F7 +:10130000BE0102800ABE02028008BE030E800602F1 +:101310001AFD021B6F90026B021BB643970843979E +:10132000022243970843970222C021C0E0C0F0C0C8 +:1013300082C083C007C006C005C004C003C002C08D +:1013400001C000C0D075D000AF92AE93EF6056EFF1 +:1013500030E3065392F70213E85392EF53929FEF54 +:1013600030E70953927F1210D50213E8EF30E408FA +:101370005392EF1211CC8070EF30E2055392FB8054 +:1013800067EF30E1055392FD805EEF30E05A5392F3 +:10139000FE4391200000000000005391DF1210D5A1 +:1013A0004397018043EE6040EE30E6085393BF431D +:1013B0009A018034EE30E5055393DF802BEE30E464 +:1013C0000B5393EF121BE4439701801CEE30E205B0 +:1013D0005393FB8013EE30E1055393FD800AEE300A +:1013E000E0065393FE121C27D0D0D000D001D002CB +:1013F000D003D004D005D006D007D083D082D0F05F +:10140000D0E0D02132AE82AF8390026AE4F0740261 +:101410002EFEE43FFF8E828F83E0FCA3E04C600849 +:101420009002647401F08005900264E4F08E828F73 +:1014300083E0900265F0539BF043970243970422A8 +:10144000AE82AF8390026AE4F08E828F83E0540F05 +:10145000600280138E828F83A3A3E0FEA3E0FFBE11 +:101460000105BF0002C201539BF043970422AE82E4 +:10147000AF8390026AE4F08E828F83E0540F6002A3 +:1014800080138E828F83A3A3E0FEA3E0FFBE01053D +:10149000BF0002D201539BF043970422AE82AF8378 +:1014A00090026AE4F08E828F83E0FD53050FBD0247 +:1014B000428E828F83A3A3E0FCA3E04C70358E8222 +:1014C0008F83A3A3A3A3E0FEA3E0FF4E700853976E +:1014D000F75397FD801DBE8108BF00055399F78023 +:1014E00012BE8208BF0005539AF78007439708434E +:1014F000970222539BF043970422AE82AF8390025F +:101500006AE4F08E828F83E0FD53050FBD022B8EBF +:10151000828F83A3A3A3A3E0FEA3E0FFBE8108BF45 +:101520000005439908801ABE8208BF0005439A0847 +:10153000800F439708439702800743970843970219 +:1015400022539BF043970422AE82AF8390026AE459 +:10155000F0E596603E8E828F83A3A3E0FEA3E0FFBA +:101560004E600A8E048F05BC0121BD001E900266EC +:10157000EEF0EE60089002647402F080069002645F +:101580007401F0539BF0439704800E4397084397F0 +:10159000028006439708439702539BF04397042227 +:1015A000AE82AF8390026AE4F08E828F83A3A3E0C1 +:1015B000FCA3E04C70318E828F83A3A3A3A3E0FE33 +:1015C000A3E0FF4E700A539BF04397044397022217 +:1015D000BE010DBF000A539BF043970443970222BC +:1015E0004397084397022243970843970222439761 +:1015F0000843970222AE82AF8390026A7402F08E93 +:10160000828F83E0540F6002801F30010890110820 +:101610007402F08005901108E4F0901109E4F05391 +:101620009BF0439B0243970422439708439702226F +:10163000AE82AF83E0FD53050FBD01308E828F83F4 +:10164000A3A3A3A3E0FEA3E0FF4E6006BE0116BF66 +:101650000013901108E4F0901109F0539BF0439BA4 +:101660000243970422439708439702224397084373 +:10167000970222AE82AF83E0FD53050FBD020280C8 +:10168000030217148E828F83A3A3A3A3E0FEA3E01B +:10169000FFBE801BBF0018AC97530402E4A2E713FF +:1016A000CC13CC901108ECF0901109E4F0805BBEF3 +:1016B0008127BF0024AC99530408E423CCC42354ED +:1016C0001F6CCC541FCC6CCC30E40244E090110869 +:1016D000ECF0901109E4F08031BE8227BF0024AE07 +:1016E0009A530608E423CEC423541F6ECE541FCE53 +:1016F0006ECE30E40244E0901108EEF0901109E45F +:10170000F0800743970843970222539BF0439B02C4 +:1017100043970422439708439702228582278583B3 +:1017200028A3A3E0FCA3E0FBBB0141903DD0E493E0 +:10173000FAA3E493FD8A758D767577808A828D830E +:10174000E493FA8A7875790090006475F00012329B +:101750009A9002737464F07400A3F0E4A3F0900014 +:1017600064E0900276F0E4A3F0021A8DBB020280DE +:101770000302188A903DD4E493FDC3EC9D4003021C +:10178000188A903DD5E493FAA3E493FDEC75F0023A +:10179000A42AF582ED35F0F583E493FAA3E493FDF2 +:1017A00074092AF529E43DF52A8A008D0188758996 +:1017B000767577808A828D83E493F98978757900CC +:1017C00090006475F000C005C00212329AD002D0B9 +:1017D000058A828D83E4932464F52BE43400F52C90 +:1017E000A829A92A852B2D852C2E752F0088828962 +:1017F00083E493FAA3E493FF8A758F767577808AE2 +:10180000828F83E493FA8A78757900852D82852EFC +:1018100083852FF0C001C00012329AD000D0018819 +:10182000828983E493FEA3E493FF8E828F83E49303 +:10183000252BF52BE4352CF52C740228F8E439F926 +:1018400088828983E493FEA3E4934E7097900066A8 +:10185000E0FEA3E04E7013E52BC39464FEE52C94E8 +:1018600000FF900066EEF0EFA3F09002737464F056 +:101870007400A3F0E4A3F0900066E0FEA3E0FF9004 +:101880000276EEF0EFA3F0021A8DE4BB030104FF31 +:10189000603BEC70387575C675763D757780903D08 +:1018A000C6E493FE8E7875790090006475F000129E +:1018B000329A9002737464F07400A3F0E4A3F09081 +:1018C0000064E0900276F0E4A3F0021A8DEF70035A +:1018D00002197F7F00EC24FFFDEF34FFFE903DD71F +:1018E000E493F97A00C3ED99EE64808AF063F080A6 +:1018F00095F0400302197F903DD8E493FDA3E49353 +:10190000FE1CBCFF011FEC2CFCEF33FFEC2DF5821D +:10191000EF3EF583E493FEA3E493FF90006474022A +:10192000F090006504F074012465FCE43400FD8E41 +:10193000828F83E493FA602C0EBE00010F8C828D9F +:1019400083EAF074012CF9E43DFA89828A83E4F099 +:101950000429FCE43AFD900064E0FA0A0A9000646D +:10196000EAF080CB9002737464F07400A3F0E4A3F7 +:10197000F0900064E0900276F0E4A3F0021A8DBBD0 +:101980002276852782852883A3A3A3A3E0FE702F58 +:101990009002767444F0E4A3F07575AF75763C75EB +:1019A0007780757844F579900064F5F012329A905A +:1019B00002737464F07400A3F0E4A3F0021A8DBE05 +:1019C000012F9002767476F0E4A3F07575F37576C6 +:1019D0003C757780757876F579900064F5F0123271 +:1019E0009A9002737464F07400A3F0E4A3F0021AF6 +:1019F0008D43970843970222BB21028003021A8677 +:101A0000852782852883A3A3A3A3E0FE703775757D +:101A10008475763D757780903D84E493FF8F78756B +:101A2000790090006475F00012329A900273746429 +:101A3000F07400A3F0E4A3F0900064E0900276F06C +:101A4000E4A3F08048BE013775759D75763D7577C6 +:101A500080903D9DE493FF8F7875790090006475C8 +:101A6000F00012329A9002737464F07400A3F0E4F0 +:101A7000A3F0900064E0900276F0E4A3F0800E43BF +:101A800097084397022243970843970222852782AB +:101A9000852883A3A3A3A3A3A3E0FEA3E0FF900252 +:101AA00076E0FCA3E0FDC3EE9CEF9D40048C068D28 +:101AB00007900273E0FBA3E0FCA3E0FD8E758F7638 +:101AC0008B828C838DF0121C50121C604397042271 +:101AD00090026A7402F0900266E0901108F0539B45 +:101AE000F0439B014397042290026A7402F0901124 +:101AF00008E4F0539BF0439B01439704224397086B +:101B000043970222AE82AF83A3A3E0FCA3E0FABA1C +:101B100002028005BA0341802D8E828F83A3A3A386 +:101B2000A3E0FAA3E04A703674062EF582E43FF58E +:101B300083E0FEA3E0FFBE0125BF002290026A748D +:101B400004F0439701227D00BC0513BD00109002F4 +:101B50006A7405F04397012243970843970222A332 +:101B6000A3E0A3E0900269F0539BF0439704229016 +:101B70000269E0901108F0539BF0439B01439704E6 +:101B800022AE82AF83A3A3A3A3E0FCA3E0FD4C702D +:101B90000D8E828F83A3A3E0900267F08011BC01B9 +:101BA0000EBD000B8E828F83A3A3E0900268F053DA +:101BB0009BF043970422A3A3A3A3E0FEA3E0FF4E60 +:101BC000700A900267E0901108F0800EBE010BBF12 +:101BD0000008900268E0901108F0539BF0439B01CD +:101BE0004397042290026AE0FFBF041490026AE463 +:101BF000F0901100E0900061F0539BF043970422B5 +:101C0000BF051890026AE4F0901100E0FFBF0516CE +:101C1000901101E0FFBF750E020F5090026AE4F0D0 +:101C2000539BF04397042290026AE0FFBF010A121F +:101C30001C6043970443970122BF02074397014367 +:101C4000970822439708439701900265E0F5962292 +:101C500085822485832585F0268575228576232235 +:101C6000E5224523700D90026A7402F0539BF085C3 +:101C70009B9B22AE22AF23C374089EE49F503790F3 +:101C8000026A7401F07530088524828525838526D3 +:101C9000F0121CFDAC22AD23EC24F8FCED34FFFD6A +:101CA0008C228D2374082524F524E43525F525534D +:101CB0009BF0439B0822BE0824BF002190026A7457 +:101CC00001F07530088524828525838526F0121C55 +:101CD000FDE4F522F523539BF0439B082290026A12 +:101CE0007402F08522308524828525838526F012B2 +:101CF0001CFD539BF0AF9BE5224FF59B22AD82AEBE +:101D000083AFF0E53024F7502AAB307C00C003C02D +:101D10000474F1C0E0743DC0E07480C0E074DAC0C7 +:101D2000E0743DC0E07480C0E0123441E58124F8E5 +:101D3000F581228D758E768F778530787579009054 +:101D4000110875F00002329AAD82AE83AFF075755E +:101D5000007576117577007578087579008D828E1B +:101D6000838FF002329AAD82AE83AFF0E53124EF7B +:101D7000502AAB317C00C003C0047403C0E0743E41 +:101D8000C0E07480C0E074DAC0E0743DC0E07480EC +:101D9000C0E0123441E58124F8F581228D758E76FC +:101DA0008F7785317875790090112075F0000232B7 +:101DB0009AAD82AE83AFF0757510757611757700A8 +:101DC0007578107579008D828E838FF002329AAD0E +:101DD00082AE83AFF0E53224BF502AAB327C00C024 +:101DE00003C0047415C0E0743EC0E07480C0E074A9 +:101DF000DAC0E0743DC0E07480C0E0123441E58197 +:101E000024F8F581228D758E768F778532787579F5 +:101E10000090118075F00002329AAD82AE83AFF06F +:101E20007575407576117577007578407579008DF8 +:101E3000828E838FF002329A85821685831785F011 +:101E400018C2AFC285900003120F21C2FC7B007C38 +:101E500000C3EB9514EC64808515F063F08095F079 +:101E6000503FEB2516F8EC3517F9AA1888828983BC +:101E70008AF0123BA9F582C004C003C002C001C0B1 +:101E800000121EAAAF82D000D001D002D003D0042D +:101E9000888289838AF0EF12330E0BBB00B30C806B +:101EA000B0D2FCD287D285D2AF22AF827E007D0035 +:101EB000BD0800502D8E04EC2CFEC2B700000000BF +:101EC00000000000EF30E704D2878002C28730862E +:101ED00003430601D2B7000000008F04EC2CFF0D75 +:101EE00080CE8E8222AF82BFA502800ABFA602806A +:101EF00009BFA70E800890000122900002229000E6 +:101F0000042290000022AF82BFA8005003021FCB22 +:101F1000EF243D5003021FCBEF2458FF240A83F522 +:101F200082EF241F83F583E4735F63676B6F7B83AA +:101F3000877F8B8F939FA3A7ABAFB3BF7377B7BBDD +:101F4000979BC3C71F1F1F1F1F1F1F1F1F1F1F1F61 +:101F50001F1F1F1F1F1F1F1F1F1F1F1F1F1F1F9020 +:101F600000E2229000E9229000EA229000B522903F +:101F700000B6229000B3229000B4229000B72290C5 +:101F800000CC229000CD229001832290018A2290E1 +:101F90000192229001942290019F229001CB2290E5 +:101FA00002212290022322900224229002252290D4 +:101FB0000226229002272290006F22900070229029 +:101FC000022A2290029F229002A022900000221258 +:101FD00028B8900006120EF490029875F0001223B3 +:101FE000269000C8120F2185333D85343E753F8011 +:101FF0007582001227B590000C120EF412268D90F7 +:102000000020120EF485353D85363E753F80758281 +:10201000011227B590000C120EF412268D90001EAE +:10202000120EF47538007582001223C0900014124D +:102030000EF47538007582001223C090000F120E46 +:10204000F4E4F53BF53C9000001226C490001E120B +:102050000EF40222CDAE82AF83E090029AF08E821F +:102060008F83A3A3E090029BF08E828F83A3A3A310 +:10207000E090029CF08E828F83A3A3A3A3E0900242 +:102080009DF08E828F83A3A3A3A3A3E090029EF072 +:1020900074062EF582E43FF583E090029FF07537D9 +:1020A000017E007F00C3EE9406EF64809480501997 +:1020B000EE249AFCEF3402FD8C828D83E060037580 +:1020C00037000EBE00DF0F80DCE537606190029BB9 +:1020D000E4F090029AF5F012240490029B7401F04F +:1020E00090029A75F00012240490029BE4F0900292 +:1020F0009AF5F012240490029B7401F090029A75F4 +:10210000F00012240490029BE4F090029AF5F01281 +:10211000240490029B7401F090029A75F00012243E +:102120000490029BE4F090029AF5F00224049002DD +:102130009A75F000022404AE82AF839002A074016D +:10214000F0FC7D00C3EC9415ED648094805019EC94 +:102150002EFAED3FFB8A828B83E060059002A0E4BB +:10216000F00CBC00DF0D80DC9002A0E0FDFB33959D +:10217000E0FCC007C006C005C003C0047450C0E046 +:10218000743EC0E07480C0E0123441E58124FBF568 +:1021900081D005D006D007ED70030221FE90029B8E +:1021A000E4F090029AF5F012240490029B7401F07E +:1021B00090029A75F00012240490029BE4F09002C1 +:1021C0009AF5F012240490029B7401F090029A7523 +:1021D000F00012240490029BE4F090029AF5F012B1 +:1021E000240490029B7401F090029A75F00012246E +:1021F0000490029BE4F090029AF5F00224048E828F +:102200008F83A3E0FD74022EF539E43FF53A8D8209 +:102210000224E0AE82AF83E0FDBD01028005BD0275 +:102220002A80128E828F83A3E0F53BA3E0F53C90D9 +:1022300000000226C48E828F83A3E0FEA3E0FFE4A9 +:10224000F53BF53C8E828F830226C422AD82AE839D +:10225000AFF09002A175F000C007C006C0051223C0 +:1022600026D005D006D0079002A2E0FC5304078DCB +:10227000828E838FF0EC12330E0DBD00010E8D8225 +:102280008E838FF0123BA9FC9002A2E05460C4033D +:102290005407FB8D828E838FF012330EECB5030151 +:1022A000228D828E838FF0123BA9FD7F00C005C076 +:1022B00007745FC0E0743EC0E07480C0E012344137 +:1022C000E58124FBF581227538000223C090027855 +:1022D00074AAF0900279741DF090027A7402F07E74 +:1022E000037F00C3EE941FEF648094805013EE24AC +:1022F00078F582EF3402F583E4F00EBE00E50F803E +:10230000E275751F75760090027875F0001228F658 +:10231000AF82900297EFF075142075150090027847 +:1023200075F000021E38AD82AE83AFF0C007C00664 +:10233000C00512283C900064120F2112239FD00583 +:10234000D006D007900278E0FCBCBB028004758206 +:10235000002275750275760090027A75F000C0074C +:10236000C006C0051228F6AC82D005D006D0079072 +:102370000279E0B5040280047582002290027AE0BE +:102380008D828E838FF012330E0DBD00010E9002F0 +:102390007BE08D828E838FF012330E758201229046 +:1023A000027874FFF0900279F090027AF090027B4C +:1023B000F075140475150090027875F000021E384F +:1023C000AF8290027874AAF09002797403F09002C0 +:1023D0007A7401F090027BE538F090027CEFF075A2 +:1023E000750575760090027875F0001228F6AF82B8 +:1023F00090027DEFF075140675150090027875F067 +:1024000000021E38AD82AE83AFF090027874AAF05D +:10241000900279741DF090027A7402F08D828E839E +:102420008FF0123BA990027BF074012DFAE43EFB81 +:102430008F048A828B838CF0123BA990027CF0740B +:10244000022DFAE43EFB8F048A828B838CF0123BD0 +:10245000A990027DF074032DFAE43EFB8F048A827A +:102460008B838CF0123BA990027EF074042DFAE469 +:102470003EFB8F048A828B838CF0123BA990027FF3 +:10248000F074052DFDE43EFE8D828E838FF0123BAD +:10249000A9900280F0900281E4F07E0AFFC3EE94DE +:1024A0001FEF648094805013EE2478F582EF34029D +:1024B000F583E4F00EBE00E50F80E275751F7576BA +:1024C0000090027875F0001228F6AF82900297EF24 +:1024D000F075142075150090027875F000021E3812 +:1024E000E5829002A3F090027874AAF090027974C9 +:1024F0001DF090027A7402F09002A3E090027BF04B +:1025000090027CE4F090027DF090027EF090027FD9 +:10251000F0900280F0900281F0AE39AF3A8E828F57 +:1025200083E0900282F08E828F83A3E0900283F09A +:102530008E828F83A3A3E0900284F08E828F83A388 +:10254000A3A3E0900285F08E828F83A3A3A3A3E0D0 +:10255000900286F08E828F83A3A3A3A3A3E09002B0 +:1025600087F074062EF582E43FF583E0900288F050 +:1025700074072EF582E43FF583E0900289F0740839 +:102580002EF582E43FF583E090028AF074092EF57F +:1025900082E43FF583E090028BF0740A2EF582E42A +:1025A0003FF583E090028CF0740B2EF582E43FF54A +:1025B00083E090028DF0740C2EF582E43FF583E009 +:1025C00090028EF0740D2EF582E43FF583E09002C8 +:1025D0008FF0740E2EF582E43FF583E0900290F0C8 +:1025E000740F2EF582E43FF583E0900291F07410B1 +:1025F0002EF582E43FF583E0900292F074112EF5FF +:1026000082E43FF583E0900293F074122EF582E4A9 +:102610003FF583E0900294F074132EF582E43FF5C9 +:1026200083E0900295F0900296E4F075751FF576C0 +:10263000900278F5F01228F6AF82900297EFF075CD +:10264000142075150090027875F000021E38AF82D4 +:1026500090027874AAF09002797403F090027AF0F4 +:1026600090027BEFF090027CE4F0757505F57690B2 +:102670000278F5F01228F6AF8290027DEFF0751423 +:102680000675150090027875F000021E38900278E9 +:1026900074AAF09002797401F090027A7404F075D3 +:1026A000750375760090027875F0001228F6AF82F7 +:1026B00090027BEFF075140475150090027875F0A8 +:1026C00000021E38AE82AF8390027874AAF09002A6 +:1026D00079740BF090027A7405F090027BE4F0902C +:1026E000027CF090027DF090027EF090027FF090EC +:1026F0000280F08E05900281EDF08F06900282EE4E +:10270000F0AF3B900283EFF0AF3C900284EFF075A6 +:10271000750D75760090027875F0001228F6AF827C +:10272000900285EFF075140E75150090027875F023 +:1027300000021E38AF8290027874AAF09002797479 +:1027400003F090027A23F090027BEFF090027CE499 +:10275000F0757505F576900278F5F01228F6AF82DF +:1027600090027DEFF075140675150090027875F0F3 +:1027700000021E38AF8290027874AAF09002797439 +:1027800003F090027A7407F090027BEFF090027CE5 +:10279000E4F0757505F576900278F5F01228F6AF3D +:1027A0008290027DEFF07514067515009002787521 +:1027B000F000021E38AF8290027874AAF09002797D +:1027C000741DF090027A7408F090027BEFF0AD3D3A +:1027D000AE3EAF3F7B047C008D828E838FF0123B38 +:1027E000A9FA601190027CEAF00DBD00010E0BBB4E +:1027F00000E60C80E38B068C07C3EE941FEF648029 +:1028000094805013EE2478F582EF3402F583E4F0DF +:102810000EBE00E50F80E275751F75760090027898 +:1028200075F0001228F6AF82900297EFF075142031 +:1028300075150090027875F000021E3890027874C9 +:10284000AAF09002797403F090027A740AF0900270 +:102850007BE4F090027CF0757505F576900278F5D2 +:10286000F01228F6AF8290027DEFF0751406751510 +:102870000090027875F000021E3890027874AAF079 +:102880009002797403F090027A740BF090027BE46A +:10289000F090027CF0757505F576900278F5F012EF +:1028A00028F6AF8290027DEFF07514067515009042 +:1028B000027875F000021E3890027874AAF0900237 +:1028C000797403F090027A740CF090027BE4F0903B +:1028D000027CF0757505F576900278F5F01228F611 +:1028E000AF8290027DEFF0751406751500900278A6 +:1028F00075F000021E3885827785837885F0797CB3 +:10290000007A007B00C3EA9575EB64808576F063FE +:10291000F08095F0501CEA2577F8EB3578F9AF791F +:10292000888289838FF0123BA92CFC0ABA00D60B4F +:1029300080D37455C39CF58222AF82439020439884 +:102940003F43A03F438807EF24F0500122EF2F9030 +:10295000295373801E801F80208021802280238045 +:102960002480258026802780288029802A802B802B +:102970002C802DC28822C28922C28A22C2A522C2EC +:10298000A422C2A322C2A222C2A122C2A022C29D0C +:1029900022C29C22C29B22C29A22C29922C298229F +:1029A000C29522E5F8C313FF530707E58854184F73 +:1029B00044E0F582225390DF5398C053A0C053885F +:1029C000F8221229CC122A1843A90222758C057507 +:1029D000A500758C4575A600758C8575BB00758C3A +:1029E000C5758D00758C0075E11C75E22E75E33F91 +:1029F00075E43F75E57875E68775E7FE75EE7875E1 +:102A0000D90E43F81043B0804380A043D11043E572 +:102A10008043E1A043E9402290FF9C7404F090FFC2 +:102A200098E4F090FF9D7404F090FF99E4F090FF1B +:102A30009E7404F090FF9AE4F075DE04F5DD75D520 +:102A400004F5D2F5C3F5CE75D604F5D3F5C4F5C1BA +:102A500075D704F5D4F5C5F5C290FFBD7404F090A8 +:102A6000FFA5E4F090FFEDF090FFD5F090FFBC746F +:102A700004F090FFA4E4F090FFECF090FFD4F0900D +:102A8000FFBB7404F090FFA3E4F090FFEBF090FF25 +:102A9000D3F090FFBA7404F090FFA2E4F090FFEA44 +:102AA000F090FFD2F090FFB97404F090FFA1E4F031 +:102AB00090FFE9F090FFD1F090FFB87404F090FF20 +:102AC000A0E4F090FFE8F090FFD0F090FFC3740412 +:102AD000F090FFABE4F090FFF3F090FFDBF090FF9D +:102AE000C27404F090FFAAE4F090FFF2F090FFDAD5 +:102AF000F090FFC17404F090FFA9E4F090FFF1F0B2 +:102B000090FFD9F090FFC07404F090FFA8E4F0901B +:102B1000FFF0F090FFD8F090FFBF7404F090FFA793 +:102B2000E4F090FFEFF090FFD7F090FFBE7404F058 +:102B300090FFA6E4F090FFEEF090FFD6F090FFC972 +:102B40007404F090FFB1E4F090FFF9F090FFE1F031 +:102B500022AF82BFA502800ABFA6028009BFA70ECE +:102B60008008900001229000022290000422900030 +:102B70000022AF82BFA8005003022C37EF243D5043 +:102B800003022C37EF2458FF240A83F582EF241F19 +:102B900083F583E473CBCFD3D7DBE7EFF3EBF7FB1E +:102BA000FF0B0F13171B1F2BDFE3232703072F3305 +:102BB0002B2B2B2B2B2B2B2B2B2B2B2B2C2C2C2C61 +:102BC0002C2C2C2B2B2C2C2C2C2C2C9000E22290FF +:102BD00000E9229000EA229000B5229000B62290EF +:102BE00000B3229000B4229000B7229000CC229033 +:102BF00000CD229001832290018A2290019222909E +:102C000001942290019F229001CB229002212290D8 +:102C10000223229002242290022522900226229052 +:102C200002272290006F229000702290022A2290A8 +:102C3000029F229002A02290000022A28DE43390F5 +:102C400002A4F0A28EE4339002A5F0229002A4E048 +:102C5000FFA28DE433FEEFB50602803EA28DE43381 +:102C60009002A4F09002A4E0FF601ABF012C7489C6 +:102C7000C0E0743EC0E07480C0E01234411581159C +:102C800081158180157494C0E0743EC0E07480C0EA +:102C9000E01234411581158115819002A5E0FFA253 +:102CA0008EE433FEEFB5060122A28EE4339002A536 +:102CB000F09002A5E0FF6019BF012B749EC0E07484 +:102CC0003EC0E07480C0E012344115811581158149 +:102CD0002274A9C0E0743EC0E07480C0E0123441A8 +:102CE00015811581158122AE82AF83BE4005BF7E5E +:102CF000028024BE4105BF7E028010BE4205BF7E19 +:102D000002800CBE4315BF7E128008900001229005 +:102D1000000222900003229000002290FFFF22AECA +:102D200082AF838E048F05BC4005BD7E028016BC39 +:102D30004105BD7E02800EBC4205BD7E028006BC00 +:102D40004317BD7E149002A4E0700A8E828F831216 +:102D50002CE71222C77582002275820122AE82AF53 +:102D6000839002A4E0FD600ABD010E8E828F830273 +:102D7000110E8E828F8302205522AE82AF83900285 +:102D8000A4E0FD600ABD010E8E828F8302114B8E7E +:102D9000828F8302213722AE82AF839002A4E0FDAE +:102DA000600ABD010E8E828F830211888E828F830E +:102DB000022213229002A4E0701FAE1AAF1BC3744C +:102DC000209E744E9F5012C2AF90006175F00012A9 +:102DD000224CE4F51AF51BD2AF051AE4B51A020528 +:102DE0001B220230B15380E35390F153B0875388D4 +:102DF0007F53C0010230FD85824385834485F045C1 +:102E0000E5427016AC4174F42C400A0540E4B5402C +:102E10000905418005E4F540F541E4FBFCF9FAF5CC +:102E200046F54785404885414974FC2549404AA8F4 +:102E30004874042549FF5307078882EF24FCF58373 +:102E4000C002C001123329AE82AF83E4C39EF546AF +:102E500074049FF547AE40AF415307078E82EF24BD +:102E6000FCF583123329AE82AF83D001D002E4C3D4 +:102E70009EFB74049FFC022F1474F825494046AE53 +:102E800048740755498E8224FCF583123329AE829B +:102E9000AF83E4C39EFB74049FFCAE40AF41740457 +:102EA0002FFF5307078E82EF24FCF583C004C00375 +:102EB000123329AE82AF83D003D004E4C39EF974E9 +:102EC000049FFA804FAE4874042549FF5307078ECC +:102ED00082EF24FCF583C004C003123329AE82AF15 +:102EE00083E4C39EF974049FFAAE40AF41530707D1 +:102EF0008E82EF24FCF583C002C001123329AE821A +:102F0000AF83D001D002D003D004E4C39EF5467451 +:102F1000049FF5478543828544838545F0123BA98C +:102F2000FF30E0047B007C04EF30E10479007A0498 +:102F3000EF30E20675460075470474012543FDE451 +:102F40003544FEAF458D828E838FF0123BA9FF7012 +:102F500009FBFCF9FA8C46754704BF010D7B007C28 +:102F60000079007A04E4F546F547BF020D79007A4E +:102F7000007B007C04E4F546F547BF030D79007A39 +:102F8000047B007C04E4F546F547AE427F0075758E +:102F9000038F768E828F83C004C003C002C00112EB +:102FA0003BC5AE82AF83D001D002D003D004BE00B7 +:102FB00005BF00028010BE0105BF0002801ABE02DC +:102FC0003BBF00388024D284D2C7D282D2B5D2B4DB +:102FD000D2918B068C078028D2C1D2C2D2C3D2C470 +:102FE000D2C5D29289068A078016D283D2C6D28FE2 +:102FF000D2B6D2B3D293AE46AF4780047E007F00F4 +:103000008E828F831230167582002290FF80E053EB +:10301000E0DFF00230B1AE82AF83E4C39EFE740401 +:103020009FFD8DC38ECE8DC48EC18DC58EC290FF87 +:10303000EDEDF090FFD5EEF090FFECEDF090FFD4C9 +:10304000EEF090FFEBEDF090FFD3EEF090FFEAEDA5 +:10305000F090FFD2EEF090FFE9EDF090FFD1EEF0AE +:1030600090FFE8EDF090FFD0EEF090FFF3EDF090E0 +:10307000FFDBEEF090FFF2EDF090FFDAEEF090FF64 +:10308000F1EDF090FFD9EEF090FFF0EDF090FFD869 +:10309000EEF090FFEFEDF090FFD7EEF090FFEEED49 +:1030A000F090FFD6EEF090FFF9EDF090FFE1EEF03A +:1030B0002290FF8074CAF090FF817408F090FF8224 +:1030C000F090FF83F090FF84F090FF85F090FF86F2 +:1030D000748AF090FF877408F090FF88F090FF8961 +:1030E000F090FF8AF090FF8BF090FF8C748AF09044 +:1030F000FF917408F075DA8AF5DBF5DC2290FF8029 +:103100007402F090FF81E4F090FF82F090FF83F072 +:1031100090FF84F090FF85F090FF867402F090FF9E +:1031200087E4F090FF88F090FF89F090FF8AF0909C +:10313000FF8BF090FF8C7402F090FF91E4F075DA51 +:1031400002F5DBF5DC22AF82BFA502800ABFA60232 +:103150008009BFA70E8008900001229000022290F3 +:1031600000042290000022AF82BFA8005003023268 +:103170002CEF243D500302322CEF2458FF240A8305 +:10318000F582EF241F83F583E473C0C4C8CCD0DC80 +:10319000E4E8E0ECF0F40004080C101420D4D81893 +:1031A0001CF8FC24283131313131313131313131A8 +:1031B00031323232323232323131323231313232F4 +:1031C0009000E2229000E9229000EA229000B522CD +:1031D0009000B6229000B3229000B4229000B72253 +:1031E0009000CC229000CD229001832290018A226F +:1031F000900192229001942290019F229001CB2273 +:103200009002212290022322900224229002252261 +:10321000900226229002272290006F2290007022B6 +:1032200090022A2290029F229002A0229000002267 +:10323000AE82AF83BE445FBF7E5CE51C602B120E86 +:1032400087E5825422601175123590002575F000D3 +:10325000120DCC120A00803A75122990002575F0E3 +:1032600000120DCC120A008029120E87E58254222A +:10327000601175133590002575F000120E04120AC6 +:1032800000800F75132990002575F000120E0412AE +:103290000A007582002275820122AD82AE83AFF0F2 +:1032A0008D7A8E7B8F7C85757D85767E85777FA8F0 +:1032B00078A9798803890418B8FF0119EB4C6025B7 +:1032C000857D82857E83857FF0123BA9FCA3858264 +:1032D0007D85837E8D828E838FF0EC12330EA3ADBD +:1032E00082AE8380CE857A82857B83857CF022AC1A +:1032F00082AD83AE76AF77BE0004EF600C1F0FE5A2 +:103300007512330EA3DEFADFF88C828D832220F74C +:103310001130F6138883A88220F509F6A8837583F7 +:10332000002280FEF280F5F022E58330E707638219 +:10333000FF6383FFA322E575457660467A01E57554 +:1033400025E0F575E576334012F576E5829575E56D +:1033500083957640030A80E6C3E57613F576E57536 +:1033600013F575C3E5829575F5F0E583957640050F +:10337000F58385F082C3E57613F576E57513F5756B +:10338000DAE12285827A85837B85F07CE578457950 +:10339000700490000022AB78AC791BBBFF011CEBE2 +:1033A0004C6048A87AA97BAA7C888289838AF0121B +:1033B0003BA9FF85757D85767E85777F857D8285B6 +:1033C0007E83857FF0123BA9FEEFB5061E08B8008C +:1033D0000109887A897B8A7C7401257DFDE4357E2C +:1033E000FEAF7F8D758E768F7780AFAD7AAE7BAF77 +:1033F0007C8D828E838FF0123BA9FD7F00AB75AC74 +:1034000076AE778B828C838EF0123BA9FB7E00ED2B +:10341000C39BF582EF9EF58322C01E85811E7E0030 +:103420008E83120FE8D01E2285825A85835B85F039 +:103430005CE4F557F558F559851D5D903419023552 +:1034400002C01EE581F51E24FBFF8F5DE4F557F5F4 +:1034500058F559E51E24FBF8865A08865B08865CF9 +:10346000903419123502D01E22AF82C04DC04EC01A +:103470004F1234768007C04BC04C8F8222158115C5 +:103480008115810555E4B55502055622AF82743089 +:103490002FFF24C6500B74072FFFE54A6003430734 +:1034A000208F82023469E582FFC4540FF582C00781 +:1034B00012348CD007740F5FF58202348C858275CC +:1034C000AB50AC51AD52AE53AA547577208A07EF7A +:1034D0002FF576EE2354014576FAEB2BFBEC33FC0B +:1034E000ED33FDEE33FEC3EA95754008EAC39575EA +:1034F000FA430301D577D68B508C518D528E538A67 +:10350000542285824B85834C85574D85584E85596D +:103510004FE4F555F556AD5AAE5BAF5C8D828E83A8 +:103520008FF0123BA9FC74012DF55AE43EF55B8F38 +:103530005CECFF7003023B6DBF25028003023B651C +:10354000E4F55EF55FF560F561F562F563F564F548 +:1035500065F567F568F569756AFF756BFFAC5AAD7F +:103560005BAE5C8C828D838EF0123BA9F56FA3ACB1 +:1035700082AD838C5A8D5B8E5C7425B56F08856F28 +:1035800082123469809074D0256F4003023627E59B +:103590006F24C6500302362774FFB56A4BB56B48DB +:1035A000C004C005C00685687585697690000AC0AC +:1035B00006C005C004123B8CAA82AB83D004D005A0 +:1035C000D006AE6F7D00EE2AFAED3BFBEA24D0F583 +:1035D00068EB34FFF569E5684569D006D005D0048D +:1035E0007081755F01023563C004C005C006856A3D +:1035F00075856B7690000AC006C005C004123B8C2E +:10360000AA82AB83D004D005D006AD6F7E00ED2A30 +:10361000FAEE3BFBEA24D0F56AEB34FFF56BD006FB +:10362000D005D004023563742EB56F1574FFB56AEA +:1036300005B56B028003023563E4F56AF56B02356C +:1036400063749F256F500EE56F24854008536FDF2C +:10365000754A018003754A007420B56F030236F481 +:10366000742BB56F030236EE742DB56F028079743A +:1036700042B56F030236FA7443B56F03023706741E +:1036800044B56F030238957446B56F030238AC74C5 +:1036900048B56F030235637449B56F0302389574FA +:1036A0004AB56F03023563744CB56F028052744F94 +:1036B000B56F0302389D7450B56F0302382A7453F6 +:1036C000B56F0280627454B56F030235637455B5EB +:1036D0006F030238A27458B56F030238A7745AB545 +:1036E0006F030235630238B1755E010235637560A0 +:1036F000010235637561010235637563010235634B +:10370000756401023563E563600AE55D14F9895D5E +:103710008706800BE55D24FEFD8D5D8D0187068E9D +:10372000821234690238BDE55D24FDFE8E5D8E0196 +:10373000870409870509870619198C508D518E5207 +:103740008C828D838EF0123B7485827085837174B8 +:10375000FFB56A09B56B0685706A85716BE55E70A9 +:103760003DC3E5709568E57195695032E568C3958C +:1037700070F568E5699571F569AC68AB698C028B89 +:10378000061CBCFF011BEA4E6010758220C004C0FD +:1037900003123469D003D00480E38C688B69AD6A6E +:1037A000AE6B8550828551838552F0123BA9FB6038 +:1037B00033C3E49D74808EF063F08095F050251D36 +:1037C000BDFF011E8B82C006C005123469D005D032 +:1037D00006AA50AB51AC520ABA00010B8A508B5169 +:1037E0008C5280BEE55E70030238BDC3E5709568FB +:1037F000E571956940030238BDE568C39570F568C9 +:10380000E5699571F569AE68AD698E038D041EBEDC +:10381000FF011DEB4C70030238B9758220C006C051 +:1038200005123469D005D00680E0E55D24FDFC8CEE +:103830005D8C01870209870309870419198A508B57 +:10384000518C52AC52BC800040047B438014BC605D +:103850000040047B50800BBC400040047B49800248 +:103860007B588B82C00312346975823A12346975B1 +:103870008230123469758278123469D003BB4902F0 +:10388000800BBB500280068551821234A68550827F +:103890001234A6802875620175670A802075670852 +:1038A000801B75670A801675671080117565018029 +:1038B0000C856F8212346980048E688D69E56560BD +:1038C0005BE55D24FCFE8E5D8E018703098704099C +:1038D00087050987061919198B508C518D528E5303 +:1038E000755034755141755280855072855173857C +:1038F000527474012572FAE43573FBAE748A508BEE +:10390000518E528572828573838574F0123BA9FDB6 +:1039100070030235168D8212346980CDE56770031D +:10392000023516E5636029E55D14F9895D87067D3A +:10393000007C007B008E508D518C528B53E5627061 +:1039400063AB50FCFDFE8B508C518D528E538054D6 +:10395000E5646021E55D24FCFE8E5D8E0187030930 +:1039600087040987050987061919198B508C518D11 +:10397000528E53802FE55D24FEFE8E5D8E018705FD +:1039800009870619EE3395E0FCFB8D508E518C5261 +:103990008B53E562700EAB50AC51FDFE8B508C51D9 +:1039A0008D528E53E5626023E55330E71BC3E495E7 +:1039B00050FBE49551FCE49552FDE49553FE8B5089 +:1039C0008C518D528E538003756200756601798526 +:1039D0007D007E00755400856782C006C005C00169 +:1039E0001234BDD001D005D006E5667013E554C48D +:1039F00054F0FCE554C4540F4204E74CF71980021C +:103A0000A7540DBD00010EE566B40100E433F56670 +:103A1000E55045514552455370BA896E8D6C8E6D97 +:103A2000E56845697005756801F569E55F702DE524 +:103A30005E7029AA68AB69AC6C0C7E00C3EC9AEE90 +:103A40009B5015758220C003C002123469D002D089 +:103A5000031ABAFF011B80DF8A688B69E562601177 +:103A600075822D123469156874FFB5680215698076 +:103A70002EE56C456D6028E560601175822B12346F +:103A800069156874FFB5680215698013E561600FF8 +:103A9000758220123469156874FFB568021569E5EE +:103AA0005E702FAE68AD698E038D041EBEFF011DD2 +:103AB000C3E56C9BE56D9C503AE55F60047C30800B +:103AC000027C208C82C006C005123469D005D00665 +:103AD00080D5C3E56C9568E56D9569500FE568C3C1 +:103AE000956CF568E569956DF569800BE4F568F509 +:103AF0006980048E688D69A96EAD6CAE6D8D038E84 +:103B0000041DBDFF011EEB4C6030E566B40100E40E +:103B100033F566700A09E7C4540FFC8C548007879C +:103B200004740F5CF554855482C006C005C00112B0 +:103B3000348CD001D005D00680C3E55E7003023519 +:103B400016AD68AE698D038E041DBDFF011EEB4CE2 +:103B50007003023516758220C006C005123469D084 +:103B600005D00680E08F82123469023516855582B1 +:103B700085568322AA82AB83123BA96003A380F8F7 +:103B8000C3E5829AF582E5839BF58322E5828575FC +:103B9000F0A4C582C0F08576F0A4D0F025F0C583EE +:103BA0008575F0A42583F5832220F71430F6148858 +:103BB00083A88220F507E6A88375830022E280F7B8 +:103BC000E49322E022C2D5E58330E70DD2D5E4C3E9 +:103BD0009582F582E49583F583E57630E70BE4C3BF +:103BE0009575F575E49576F57612333630D50BE498 +:103BF000C39582F582E49583F58322758200225372 +:103C00004D4B2076616C7068610D0A004445564941 +:103C10004345207649643A307830356163207049F5 +:103C2000643A3078303234660A0D004B45593A20F8 +:103C30003078253034782025730D0A005550004423 +:103C40004F574E004348414E474544204C4159454B +:103C5000523A2025640D0A00554E5245434F474EB7 +:103C6000495A4544204B45593A203078253034781C +:103C70000D0A006164645F6B65795F6269743A2064 +:103C800063616E2774206164643A20253032580ADB +:103C90000064656C5F6B65795F6269743A2063618B +:103CA0006E27742064656C3A20253032580A00056E +:103CB000010906A101050719E029E715002501758D +:103CC00001950881027508950181010507190029F0 +:103CD000FF150026FF0075089506810005081901EB +:103CE00029051500250175019505910275039501BA +:103CF0009101C005010980A1018501198129831560 +:103D000000250175019503810295058101C0050C0F +:103D10000901A101850219002A3C021500263C0276 +:103D2000751095018100C00600FF0901A1018505FC +:103D300019012902150026FF0075089505B102C07A +:103D400005010906A1018506050719E029E7150007 +:103D5000250175019508810205071900299F1500A5 +:103D60002501750195A08102C0120110010000001B +:103D700008AC054F02000001020301090400000124 +:103D800003010100092111010001224400070581FE +:103D900003100001090401000103000000092111C2 +:103DA000010001227600070582034000010902009C +:103DB00000020100A0FA7B3D843D8D3D943D9D3D78 +:103DC000A63D0000AD3D04030904273E3E3E4B3EA8 +:103DD000693D000001C43D03CA3D2573206275663C +:103DE00066657220746F6F206C6F6E673A20256471 +:103DF000007365745F6570305F696E5F62756666DB +:103E00006572007365745F6570315F696E5F6275BE +:103E100066666572007365745F6570325F696E5FB8 +:103E200062756666657200636F6E74616374406389 +:103E300061726C6F73736C6573732E696F00534D91 +:103E40004B204B6579626F61726400303030310015 +:103E5000697320626C616E6B3A2025640D0A0072F2 +:103E600066206C696E6B206368616E6765643A20DA +:103E700025640D0A00534D4B204254352E3000531B +:103E80004D4B204254332E30005553425F4D4F442A +:103E9000450D0A0052465F4D4F44450D0A004D4105 +:103EA000435F4D4F44450D0A0057494E5F4D4F4407 +:103EB000450D0A00447E1E001F0020002100220044 +:103EC000230024002500260027002D002E002A00B4 +:103ED000000000002B0014001A0008001500170055 +:103EE0001C0018000C00120013002F0030003100DD +:103EF00000000000390004001600070009000A0055 +:103F00000B000D000E000F003300340000002800ED +:103F100000000000E1001D001B0006001900050064 +:103F20001100100036003700380000000000E500E6 +:103F300052004C00E000E300E200000000002C0012 +:103F400000000000E3002252500000000000510079 +:103F50004F000000447E1E001F00200021002200B0 +:103F6000230024002500260027002D002E002A0013 +:103F7000000000002B0014001A00080015001700B4 +:103F80001C0018000C00120013002F00300031003C +:103F900000000000390004001600070009000A00B4 +:103FA0000B000D000E000F0033003400000028004D +:103FB00000000000E1001D001B00060019000500C4 +:103FC0001100100036003700380000000000E50046 +:103FD00052004C00E000E200E300000000002C0072 +:103FE00000000000E30023525000000000005100D8 +:103FF0004F0000003500BE00BD00C100C0000378C6 +:104000000478BC00AE00BB00A800AA00A9004C00C8 +:10401000000000000100417E427E437E407E0100A0 +:104020000100010001000100010001000100010088 +:10403000000000000100010001000100010001007A +:104040000100010001000100010001000000010069 +:10405000000000000100010001000100010001005A +:10406000010001000100010001000000000001004A +:10407000010001000100010001000000000001003A +:10408000000000000100010001000000000001002C +:104090000100000035003A003B003C003D003E00BE +:1040A0003F004000410042004300440045004C00F6 +:1040B000000000000100417E427E437E407E010000 +:1040C00001000100010001000100010001000100E8 +:1040D00000000000010001000100010001000100DA +:1040E00001000100010001000100010000000100C9 +:1040F00000000000010001000100010001000100BA +:1041000001000100010001000100000000000100A9 +:104110000100010001000100010000000000010099 +:10412000000000000100010001000000000001008B +:10413000010000003C4E4F20464C4F41543E0000D1 +:10414000000000000000000000000000000000006F +:10415000000000000000000000000000000000005F +:10416000000000000000000000000000000000004F +:10417000000000000000000000000000000000003F +:10418000000000000000000000000000000000002F +:10419000000000000000000000000000000000001F +:1041A000000000000000000000000000000000000F +:1041B00000000000000000000000000000000000FF +:1041C00000000000000000000000000000000000EF +:1041D00000000000000000000000000000000000DF +:1041E00000000000000000000000000000000000CF +:1041F00000000000000000000000000000000000BF +:1042000000000000000000000000000000000000AE +:10421000000000000000000000000000000000009E +:10422000000000000000000000000000000000008E +:10423000000000000000000000000000000000007E +:10424000000000000000000000000000000000006E +:10425000000000000000000000000000000000005E +:10426000000000000000000000000000000000004E +:10427000000000000000000000000000000000003E +:10428000000000000000000000000000000000002E +:10429000000000000000000000000000000000001E +:1042A000000000000000000000000000000000000E +:1042B00000000000000000000000000000000000FE +:1042C00000000000000000000000000000000000EE +:1042D00000000000000000000000000000000000DE +:1042E00000000000000000000000000000000000CE +:1042F00000000000000000000000000000000000BE +:1043000000000000000000000000000000000000AD +:10431000000000000000000000000000000000009D +:10432000000000000000000000000000000000008D +:10433000000000000000000000000000000000007D +:10434000000000000000000000000000000000006D +:10435000000000000000000000000000000000005D +:10436000000000000000000000000000000000004D +:10437000000000000000000000000000000000003D +:10438000000000000000000000000000000000002D +:10439000000000000000000000000000000000001D +:1043A000000000000000000000000000000000000D +:1043B00000000000000000000000000000000000FD +:1043C00000000000000000000000000000000000ED +:1043D00000000000000000000000000000000000DD +:1043E00000000000000000000000000000000000CD +:1043F00000000000000000000000000000000000BD +:1044000000000000000000000000000000000000AC +:10441000000000000000000000000000000000009C +:10442000000000000000000000000000000000008C +:10443000000000000000000000000000000000007C +:10444000000000000000000000000000000000006C +:10445000000000000000000000000000000000005C +:10446000000000000000000000000000000000004C +:10447000000000000000000000000000000000003C +:10448000000000000000000000000000000000002C +:10449000000000000000000000000000000000001C +:1044A000000000000000000000000000000000000C +:1044B00000000000000000000000000000000000FC +:1044C00000000000000000000000000000000000EC +:1044D00000000000000000000000000000000000DC +:1044E00000000000000000000000000000000000CC +:1044F00000000000000000000000000000000000BC +:1045000000000000000000000000000000000000AB +:10451000000000000000000000000000000000009B +:10452000000000000000000000000000000000008B +:10453000000000000000000000000000000000007B +:10454000000000000000000000000000000000006B +:10455000000000000000000000000000000000005B +:10456000000000000000000000000000000000004B +:10457000000000000000000000000000000000003B +:10458000000000000000000000000000000000002B +:10459000000000000000000000000000000000001B +:1045A000000000000000000000000000000000000B +:1045B00000000000000000000000000000000000FB +:1045C00000000000000000000000000000000000EB +:1045D00000000000000000000000000000000000DB +:1045E00000000000000000000000000000000000CB +:1045F00000000000000000000000000000000000BB +:1046000000000000000000000000000000000000AA +:10461000000000000000000000000000000000009A +:10462000000000000000000000000000000000008A +:10463000000000000000000000000000000000007A +:10464000000000000000000000000000000000006A +:10465000000000000000000000000000000000005A +:10466000000000000000000000000000000000004A +:10467000000000000000000000000000000000003A +:10468000000000000000000000000000000000002A +:10469000000000000000000000000000000000001A +:1046A000000000000000000000000000000000000A +:1046B00000000000000000000000000000000000FA +:1046C00000000000000000000000000000000000EA +:1046D00000000000000000000000000000000000DA +:1046E00000000000000000000000000000000000CA +:1046F00000000000000000000000000000000000BA +:1047000000000000000000000000000000000000A9 +:104710000000000000000000000000000000000099 +:104720000000000000000000000000000000000089 +:104730000000000000000000000000000000000079 +:104740000000000000000000000000000000000069 +:104750000000000000000000000000000000000059 +:104760000000000000000000000000000000000049 +:104770000000000000000000000000000000000039 +:104780000000000000000000000000000000000029 +:104790000000000000000000000000000000000019 +:1047A0000000000000000000000000000000000009 +:1047B00000000000000000000000000000000000F9 +:1047C00000000000000000000000000000000000E9 +:1047D00000000000000000000000000000000000D9 +:1047E00000000000000000000000000000000000C9 +:1047F00000000000000000000000000000000000B9 +:1048000000000000000000000000000000000000A8 +:104810000000000000000000000000000000000098 +:104820000000000000000000000000000000000088 +:104830000000000000000000000000000000000078 +:104840000000000000000000000000000000000068 +:104850000000000000000000000000000000000058 +:104860000000000000000000000000000000000048 +:104870000000000000000000000000000000000038 +:104880000000000000000000000000000000000028 +:104890000000000000000000000000000000000018 +:1048A0000000000000000000000000000000000008 +:1048B00000000000000000000000000000000000F8 +:1048C00000000000000000000000000000000000E8 +:1048D00000000000000000000000000000000000D8 +:1048E00000000000000000000000000000000000C8 +:1048F00000000000000000000000000000000000B8 +:1049000000000000000000000000000000000000A7 +:104910000000000000000000000000000000000097 +:104920000000000000000000000000000000000087 +:104930000000000000000000000000000000000077 +:104940000000000000000000000000000000000067 +:104950000000000000000000000000000000000057 +:104960000000000000000000000000000000000047 +:104970000000000000000000000000000000000037 +:104980000000000000000000000000000000000027 +:104990000000000000000000000000000000000017 +:1049A0000000000000000000000000000000000007 +:1049B00000000000000000000000000000000000F7 +:1049C00000000000000000000000000000000000E7 +:1049D00000000000000000000000000000000000D7 +:1049E00000000000000000000000000000000000C7 +:1049F00000000000000000000000000000000000B7 +:104A000000000000000000000000000000000000A6 +:104A10000000000000000000000000000000000096 +:104A20000000000000000000000000000000000086 +:104A30000000000000000000000000000000000076 +:104A40000000000000000000000000000000000066 +:104A50000000000000000000000000000000000056 +:104A60000000000000000000000000000000000046 +:104A70000000000000000000000000000000000036 +:104A80000000000000000000000000000000000026 +:104A90000000000000000000000000000000000016 +:104AA0000000000000000000000000000000000006 +:104AB00000000000000000000000000000000000F6 +:104AC00000000000000000000000000000000000E6 +:104AD00000000000000000000000000000000000D6 +:104AE00000000000000000000000000000000000C6 +:104AF00000000000000000000000000000000000B6 +:104B000000000000000000000000000000000000A5 +:104B10000000000000000000000000000000000095 +:104B20000000000000000000000000000000000085 +:104B30000000000000000000000000000000000075 +:104B40000000000000000000000000000000000065 +:104B50000000000000000000000000000000000055 +:104B60000000000000000000000000000000000045 +:104B70000000000000000000000000000000000035 +:104B80000000000000000000000000000000000025 +:104B90000000000000000000000000000000000015 +:104BA0000000000000000000000000000000000005 +:104BB00000000000000000000000000000000000F5 +:104BC00000000000000000000000000000000000E5 +:104BD00000000000000000000000000000000000D5 +:104BE00000000000000000000000000000000000C5 +:104BF00000000000000000000000000000000000B5 +:104C000000000000000000000000000000000000A4 +:104C10000000000000000000000000000000000094 +:104C20000000000000000000000000000000000084 +:104C30000000000000000000000000000000000074 +:104C40000000000000000000000000000000000064 +:104C50000000000000000000000000000000000054 +:104C60000000000000000000000000000000000044 +:104C70000000000000000000000000000000000034 +:104C80000000000000000000000000000000000024 +:104C90000000000000000000000000000000000014 +:104CA0000000000000000000000000000000000004 +:104CB00000000000000000000000000000000000F4 +:104CC00000000000000000000000000000000000E4 +:104CD00000000000000000000000000000000000D4 +:104CE00000000000000000000000000000000000C4 +:104CF00000000000000000000000000000000000B4 +:104D000000000000000000000000000000000000A3 +:104D10000000000000000000000000000000000093 +:104D20000000000000000000000000000000000083 +:104D30000000000000000000000000000000000073 +:104D40000000000000000000000000000000000063 +:104D50000000000000000000000000000000000053 +:104D60000000000000000000000000000000000043 +:104D70000000000000000000000000000000000033 +:104D80000000000000000000000000000000000023 +:104D90000000000000000000000000000000000013 +:104DA0000000000000000000000000000000000003 +:104DB00000000000000000000000000000000000F3 +:104DC00000000000000000000000000000000000E3 +:104DD00000000000000000000000000000000000D3 +:104DE00000000000000000000000000000000000C3 +:104DF00000000000000000000000000000000000B3 +:104E000000000000000000000000000000000000A2 +:104E10000000000000000000000000000000000092 +:104E20000000000000000000000000000000000082 +:104E30000000000000000000000000000000000072 +:104E40000000000000000000000000000000000062 +:104E50000000000000000000000000000000000052 +:104E60000000000000000000000000000000000042 +:104E70000000000000000000000000000000000032 +:104E80000000000000000000000000000000000022 +:104E90000000000000000000000000000000000012 +:104EA0000000000000000000000000000000000002 +:104EB00000000000000000000000000000000000F2 +:104EC00000000000000000000000000000000000E2 +:104ED00000000000000000000000000000000000D2 +:104EE00000000000000000000000000000000000C2 +:104EF00000000000000000000000000000000000B2 +:104F000000000000000000000000000000000000A1 +:104F10000000000000000000000000000000000091 +:104F20000000000000000000000000000000000081 +:104F30000000000000000000000000000000000071 +:104F40000000000000000000000000000000000061 +:104F50000000000000000000000000000000000051 +:104F60000000000000000000000000000000000041 +:104F70000000000000000000000000000000000031 +:104F80000000000000000000000000000000000021 +:104F90000000000000000000000000000000000011 +:104FA0000000000000000000000000000000000001 +:104FB00000000000000000000000000000000000F1 +:104FC00000000000000000000000000000000000E1 +:104FD00000000000000000000000000000000000D1 +:104FE00000000000000000000000000000000000C1 +:104FF00000000000000000000000000000000000B1 +:1050000000000000000000000000000000000000A0 +:105010000000000000000000000000000000000090 +:105020000000000000000000000000000000000080 +:105030000000000000000000000000000000000070 +:105040000000000000000000000000000000000060 +:105050000000000000000000000000000000000050 +:105060000000000000000000000000000000000040 +:105070000000000000000000000000000000000030 +:105080000000000000000000000000000000000020 +:105090000000000000000000000000000000000010 +:1050A0000000000000000000000000000000000000 +:1050B00000000000000000000000000000000000F0 +:1050C00000000000000000000000000000000000E0 +:1050D00000000000000000000000000000000000D0 +:1050E00000000000000000000000000000000000C0 +:1050F00000000000000000000000000000000000B0 +:10510000000000000000000000000000000000009F +:10511000000000000000000000000000000000008F +:10512000000000000000000000000000000000007F +:10513000000000000000000000000000000000006F +:10514000000000000000000000000000000000005F +:10515000000000000000000000000000000000004F +:10516000000000000000000000000000000000003F +:10517000000000000000000000000000000000002F +:10518000000000000000000000000000000000001F +:10519000000000000000000000000000000000000F +:1051A00000000000000000000000000000000000FF +:1051B00000000000000000000000000000000000EF +:1051C00000000000000000000000000000000000DF +:1051D00000000000000000000000000000000000CF +:1051E00000000000000000000000000000000000BF +:1051F00000000000000000000000000000000000AF +:10520000000000000000000000000000000000009E +:10521000000000000000000000000000000000008E +:10522000000000000000000000000000000000007E +:10523000000000000000000000000000000000006E +:10524000000000000000000000000000000000005E +:10525000000000000000000000000000000000004E +:10526000000000000000000000000000000000003E +:10527000000000000000000000000000000000002E +:10528000000000000000000000000000000000001E +:10529000000000000000000000000000000000000E +:1052A00000000000000000000000000000000000FE +:1052B00000000000000000000000000000000000EE +:1052C00000000000000000000000000000000000DE +:1052D00000000000000000000000000000000000CE +:1052E00000000000000000000000000000000000BE +:1052F00000000000000000000000000000000000AE +:10530000000000000000000000000000000000009D +:10531000000000000000000000000000000000008D +:10532000000000000000000000000000000000007D +:10533000000000000000000000000000000000006D +:10534000000000000000000000000000000000005D +:10535000000000000000000000000000000000004D +:10536000000000000000000000000000000000003D +:10537000000000000000000000000000000000002D +:10538000000000000000000000000000000000001D +:10539000000000000000000000000000000000000D +:1053A00000000000000000000000000000000000FD +:1053B00000000000000000000000000000000000ED +:1053C00000000000000000000000000000000000DD +:1053D00000000000000000000000000000000000CD +:1053E00000000000000000000000000000000000BD +:1053F00000000000000000000000000000000000AD +:10540000000000000000000000000000000000009C +:10541000000000000000000000000000000000008C +:10542000000000000000000000000000000000007C +:10543000000000000000000000000000000000006C +:10544000000000000000000000000000000000005C +:10545000000000000000000000000000000000004C +:10546000000000000000000000000000000000003C +:10547000000000000000000000000000000000002C +:10548000000000000000000000000000000000001C +:10549000000000000000000000000000000000000C +:1054A00000000000000000000000000000000000FC +:1054B00000000000000000000000000000000000EC +:1054C00000000000000000000000000000000000DC +:1054D00000000000000000000000000000000000CC +:1054E00000000000000000000000000000000000BC +:1054F00000000000000000000000000000000000AC +:10550000000000000000000000000000000000009B +:10551000000000000000000000000000000000008B +:10552000000000000000000000000000000000007B +:10553000000000000000000000000000000000006B +:10554000000000000000000000000000000000005B +:10555000000000000000000000000000000000004B +:10556000000000000000000000000000000000003B +:10557000000000000000000000000000000000002B +:10558000000000000000000000000000000000001B +:10559000000000000000000000000000000000000B +:1055A00000000000000000000000000000000000FB +:1055B00000000000000000000000000000000000EB +:1055C00000000000000000000000000000000000DB +:1055D00000000000000000000000000000000000CB +:1055E00000000000000000000000000000000000BB +:1055F00000000000000000000000000000000000AB +:10560000000000000000000000000000000000009A +:10561000000000000000000000000000000000008A +:10562000000000000000000000000000000000007A +:10563000000000000000000000000000000000006A +:10564000000000000000000000000000000000005A +:10565000000000000000000000000000000000004A +:10566000000000000000000000000000000000003A +:10567000000000000000000000000000000000002A +:10568000000000000000000000000000000000001A +:10569000000000000000000000000000000000000A +:1056A00000000000000000000000000000000000FA +:1056B00000000000000000000000000000000000EA +:1056C00000000000000000000000000000000000DA +:1056D00000000000000000000000000000000000CA +:1056E00000000000000000000000000000000000BA +:1056F00000000000000000000000000000000000AA +:105700000000000000000000000000000000000099 +:105710000000000000000000000000000000000089 +:105720000000000000000000000000000000000079 +:105730000000000000000000000000000000000069 +:105740000000000000000000000000000000000059 +:105750000000000000000000000000000000000049 +:105760000000000000000000000000000000000039 +:105770000000000000000000000000000000000029 +:105780000000000000000000000000000000000019 +:105790000000000000000000000000000000000009 +:1057A00000000000000000000000000000000000F9 +:1057B00000000000000000000000000000000000E9 +:1057C00000000000000000000000000000000000D9 +:1057D00000000000000000000000000000000000C9 +:1057E00000000000000000000000000000000000B9 +:1057F00000000000000000000000000000000000A9 +:105800000000000000000000000000000000000098 +:105810000000000000000000000000000000000088 +:105820000000000000000000000000000000000078 +:105830000000000000000000000000000000000068 +:105840000000000000000000000000000000000058 +:105850000000000000000000000000000000000048 +:105860000000000000000000000000000000000038 +:105870000000000000000000000000000000000028 +:105880000000000000000000000000000000000018 +:105890000000000000000000000000000000000008 +:1058A00000000000000000000000000000000000F8 +:1058B00000000000000000000000000000000000E8 +:1058C00000000000000000000000000000000000D8 +:1058D00000000000000000000000000000000000C8 +:1058E00000000000000000000000000000000000B8 +:1058F00000000000000000000000000000000000A8 +:105900000000000000000000000000000000000097 +:105910000000000000000000000000000000000087 +:105920000000000000000000000000000000000077 +:105930000000000000000000000000000000000067 +:105940000000000000000000000000000000000057 +:105950000000000000000000000000000000000047 +:105960000000000000000000000000000000000037 +:105970000000000000000000000000000000000027 +:105980000000000000000000000000000000000017 +:105990000000000000000000000000000000000007 +:1059A00000000000000000000000000000000000F7 +:1059B00000000000000000000000000000000000E7 +:1059C00000000000000000000000000000000000D7 +:1059D00000000000000000000000000000000000C7 +:1059E00000000000000000000000000000000000B7 +:1059F00000000000000000000000000000000000A7 +:105A00000000000000000000000000000000000096 +:105A10000000000000000000000000000000000086 +:105A20000000000000000000000000000000000076 +:105A30000000000000000000000000000000000066 +:105A40000000000000000000000000000000000056 +:105A50000000000000000000000000000000000046 +:105A60000000000000000000000000000000000036 +:105A70000000000000000000000000000000000026 +:105A80000000000000000000000000000000000016 +:105A90000000000000000000000000000000000006 +:105AA00000000000000000000000000000000000F6 +:105AB00000000000000000000000000000000000E6 +:105AC00000000000000000000000000000000000D6 +:105AD00000000000000000000000000000000000C6 +:105AE00000000000000000000000000000000000B6 +:105AF00000000000000000000000000000000000A6 +:105B00000000000000000000000000000000000095 +:105B10000000000000000000000000000000000085 +:105B20000000000000000000000000000000000075 +:105B30000000000000000000000000000000000065 +:105B40000000000000000000000000000000000055 +:105B50000000000000000000000000000000000045 +:105B60000000000000000000000000000000000035 +:105B70000000000000000000000000000000000025 +:105B80000000000000000000000000000000000015 +:105B90000000000000000000000000000000000005 +:105BA00000000000000000000000000000000000F5 +:105BB00000000000000000000000000000000000E5 +:105BC00000000000000000000000000000000000D5 +:105BD00000000000000000000000000000000000C5 +:105BE00000000000000000000000000000000000B5 +:105BF00000000000000000000000000000000000A5 +:105C00000000000000000000000000000000000094 +:105C10000000000000000000000000000000000084 +:105C20000000000000000000000000000000000074 +:105C30000000000000000000000000000000000064 +:105C40000000000000000000000000000000000054 +:105C50000000000000000000000000000000000044 +:105C60000000000000000000000000000000000034 +:105C70000000000000000000000000000000000024 +:105C80000000000000000000000000000000000014 +:105C90000000000000000000000000000000000004 +:105CA00000000000000000000000000000000000F4 +:105CB00000000000000000000000000000000000E4 +:105CC00000000000000000000000000000000000D4 +:105CD00000000000000000000000000000000000C4 +:105CE00000000000000000000000000000000000B4 +:105CF00000000000000000000000000000000000A4 +:105D00000000000000000000000000000000000093 +:105D10000000000000000000000000000000000083 +:105D20000000000000000000000000000000000073 +:105D30000000000000000000000000000000000063 +:105D40000000000000000000000000000000000053 +:105D50000000000000000000000000000000000043 +:105D60000000000000000000000000000000000033 +:105D70000000000000000000000000000000000023 +:105D80000000000000000000000000000000000013 +:105D90000000000000000000000000000000000003 +:105DA00000000000000000000000000000000000F3 +:105DB00000000000000000000000000000000000E3 +:105DC00000000000000000000000000000000000D3 +:105DD00000000000000000000000000000000000C3 +:105DE00000000000000000000000000000000000B3 +:105DF00000000000000000000000000000000000A3 +:105E00000000000000000000000000000000000092 +:105E10000000000000000000000000000000000082 +:105E20000000000000000000000000000000000072 +:105E30000000000000000000000000000000000062 +:105E40000000000000000000000000000000000052 +:105E50000000000000000000000000000000000042 +:105E60000000000000000000000000000000000032 +:105E70000000000000000000000000000000000022 +:105E80000000000000000000000000000000000012 +:105E90000000000000000000000000000000000002 +:105EA00000000000000000000000000000000000F2 +:105EB00000000000000000000000000000000000E2 +:105EC00000000000000000000000000000000000D2 +:105ED00000000000000000000000000000000000C2 +:105EE00000000000000000000000000000000000B2 +:105EF00000000000000000000000000000000000A2 +:105F00000000000000000000000000000000000091 +:105F10000000000000000000000000000000000081 +:105F20000000000000000000000000000000000071 +:105F30000000000000000000000000000000000061 +:105F40000000000000000000000000000000000051 +:105F50000000000000000000000000000000000041 +:105F60000000000000000000000000000000000031 +:105F70000000000000000000000000000000000021 +:105F80000000000000000000000000000000000011 +:105F90000000000000000000000000000000000001 +:105FA00000000000000000000000000000000000F1 +:105FB00000000000000000000000000000000000E1 +:105FC00000000000000000000000000000000000D1 +:105FD00000000000000000000000000000000000C1 +:105FE00000000000000000000000000000000000B1 +:105FF00000000000000000000000000000000000A1 +:106000000000000000000000000000000000000090 +:106010000000000000000000000000000000000080 +:106020000000000000000000000000000000000070 +:106030000000000000000000000000000000000060 +:106040000000000000000000000000000000000050 +:106050000000000000000000000000000000000040 +:106060000000000000000000000000000000000030 +:106070000000000000000000000000000000000020 +:106080000000000000000000000000000000000010 +:106090000000000000000000000000000000000000 +:1060A00000000000000000000000000000000000F0 +:1060B00000000000000000000000000000000000E0 +:1060C00000000000000000000000000000000000D0 +:1060D00000000000000000000000000000000000C0 +:1060E00000000000000000000000000000000000B0 +:1060F00000000000000000000000000000000000A0 +:10610000000000000000000000000000000000008F +:10611000000000000000000000000000000000007F +:10612000000000000000000000000000000000006F +:10613000000000000000000000000000000000005F +:10614000000000000000000000000000000000004F +:10615000000000000000000000000000000000003F +:10616000000000000000000000000000000000002F +:10617000000000000000000000000000000000001F +:10618000000000000000000000000000000000000F +:1061900000000000000000000000000000000000FF +:1061A00000000000000000000000000000000000EF +:1061B00000000000000000000000000000000000DF +:1061C00000000000000000000000000000000000CF +:1061D00000000000000000000000000000000000BF +:1061E00000000000000000000000000000000000AF +:1061F000000000000000000000000000000000009F +:10620000000000000000000000000000000000008E +:10621000000000000000000000000000000000007E +:10622000000000000000000000000000000000006E +:10623000000000000000000000000000000000005E +:10624000000000000000000000000000000000004E +:10625000000000000000000000000000000000003E +:10626000000000000000000000000000000000002E +:10627000000000000000000000000000000000001E +:10628000000000000000000000000000000000000E +:1062900000000000000000000000000000000000FE +:1062A00000000000000000000000000000000000EE +:1062B00000000000000000000000000000000000DE +:1062C00000000000000000000000000000000000CE +:1062D00000000000000000000000000000000000BE +:1062E00000000000000000000000000000000000AE +:1062F000000000000000000000000000000000009E +:10630000000000000000000000000000000000008D +:10631000000000000000000000000000000000007D +:10632000000000000000000000000000000000006D +:10633000000000000000000000000000000000005D +:10634000000000000000000000000000000000004D +:10635000000000000000000000000000000000003D +:10636000000000000000000000000000000000002D +:10637000000000000000000000000000000000001D +:10638000000000000000000000000000000000000D +:1063900000000000000000000000000000000000FD +:1063A00000000000000000000000000000000000ED +:1063B00000000000000000000000000000000000DD +:1063C00000000000000000000000000000000000CD +:1063D00000000000000000000000000000000000BD +:1063E00000000000000000000000000000000000AD +:1063F000000000000000000000000000000000009D +:10640000000000000000000000000000000000008C +:10641000000000000000000000000000000000007C +:10642000000000000000000000000000000000006C +:10643000000000000000000000000000000000005C +:10644000000000000000000000000000000000004C +:10645000000000000000000000000000000000003C +:10646000000000000000000000000000000000002C +:10647000000000000000000000000000000000001C +:10648000000000000000000000000000000000000C +:1064900000000000000000000000000000000000FC +:1064A00000000000000000000000000000000000EC +:1064B00000000000000000000000000000000000DC +:1064C00000000000000000000000000000000000CC +:1064D00000000000000000000000000000000000BC +:1064E00000000000000000000000000000000000AC +:1064F000000000000000000000000000000000009C +:10650000000000000000000000000000000000008B +:10651000000000000000000000000000000000007B +:10652000000000000000000000000000000000006B +:10653000000000000000000000000000000000005B +:10654000000000000000000000000000000000004B +:10655000000000000000000000000000000000003B +:10656000000000000000000000000000000000002B +:10657000000000000000000000000000000000001B +:10658000000000000000000000000000000000000B +:1065900000000000000000000000000000000000FB +:1065A00000000000000000000000000000000000EB +:1065B00000000000000000000000000000000000DB +:1065C00000000000000000000000000000000000CB +:1065D00000000000000000000000000000000000BB +:1065E00000000000000000000000000000000000AB +:1065F000000000000000000000000000000000009B +:10660000000000000000000000000000000000008A +:10661000000000000000000000000000000000007A +:10662000000000000000000000000000000000006A +:10663000000000000000000000000000000000005A +:10664000000000000000000000000000000000004A +:10665000000000000000000000000000000000003A +:10666000000000000000000000000000000000002A +:10667000000000000000000000000000000000001A +:10668000000000000000000000000000000000000A +:1066900000000000000000000000000000000000FA +:1066A00000000000000000000000000000000000EA +:1066B00000000000000000000000000000000000DA +:1066C00000000000000000000000000000000000CA +:1066D00000000000000000000000000000000000BA +:1066E00000000000000000000000000000000000AA +:1066F000000000000000000000000000000000009A +:106700000000000000000000000000000000000089 +:106710000000000000000000000000000000000079 +:106720000000000000000000000000000000000069 +:106730000000000000000000000000000000000059 +:106740000000000000000000000000000000000049 +:106750000000000000000000000000000000000039 +:106760000000000000000000000000000000000029 +:106770000000000000000000000000000000000019 +:106780000000000000000000000000000000000009 +:1067900000000000000000000000000000000000F9 +:1067A00000000000000000000000000000000000E9 +:1067B00000000000000000000000000000000000D9 +:1067C00000000000000000000000000000000000C9 +:1067D00000000000000000000000000000000000B9 +:1067E00000000000000000000000000000000000A9 +:1067F0000000000000000000000000000000000099 +:106800000000000000000000000000000000000088 +:106810000000000000000000000000000000000078 +:106820000000000000000000000000000000000068 +:106830000000000000000000000000000000000058 +:106840000000000000000000000000000000000048 +:106850000000000000000000000000000000000038 +:106860000000000000000000000000000000000028 +:106870000000000000000000000000000000000018 +:106880000000000000000000000000000000000008 +:1068900000000000000000000000000000000000F8 +:1068A00000000000000000000000000000000000E8 +:1068B00000000000000000000000000000000000D8 +:1068C00000000000000000000000000000000000C8 +:1068D00000000000000000000000000000000000B8 +:1068E00000000000000000000000000000000000A8 +:1068F0000000000000000000000000000000000098 +:106900000000000000000000000000000000000087 +:106910000000000000000000000000000000000077 +:106920000000000000000000000000000000000067 +:106930000000000000000000000000000000000057 +:106940000000000000000000000000000000000047 +:106950000000000000000000000000000000000037 +:106960000000000000000000000000000000000027 +:106970000000000000000000000000000000000017 +:106980000000000000000000000000000000000007 +:1069900000000000000000000000000000000000F7 +:1069A00000000000000000000000000000000000E7 +:1069B00000000000000000000000000000000000D7 +:1069C00000000000000000000000000000000000C7 +:1069D00000000000000000000000000000000000B7 +:1069E00000000000000000000000000000000000A7 +:1069F0000000000000000000000000000000000097 +:106A00000000000000000000000000000000000086 +:106A10000000000000000000000000000000000076 +:106A20000000000000000000000000000000000066 +:106A30000000000000000000000000000000000056 +:106A40000000000000000000000000000000000046 +:106A50000000000000000000000000000000000036 +:106A60000000000000000000000000000000000026 +:106A70000000000000000000000000000000000016 +:106A80000000000000000000000000000000000006 +:106A900000000000000000000000000000000000F6 +:106AA00000000000000000000000000000000000E6 +:106AB00000000000000000000000000000000000D6 +:106AC00000000000000000000000000000000000C6 +:106AD00000000000000000000000000000000000B6 +:106AE00000000000000000000000000000000000A6 +:106AF0000000000000000000000000000000000096 +:106B00000000000000000000000000000000000085 +:106B10000000000000000000000000000000000075 +:106B20000000000000000000000000000000000065 +:106B30000000000000000000000000000000000055 +:106B40000000000000000000000000000000000045 +:106B50000000000000000000000000000000000035 +:106B60000000000000000000000000000000000025 +:106B70000000000000000000000000000000000015 +:106B80000000000000000000000000000000000005 +:106B900000000000000000000000000000000000F5 +:106BA00000000000000000000000000000000000E5 +:106BB00000000000000000000000000000000000D5 +:106BC00000000000000000000000000000000000C5 +:106BD00000000000000000000000000000000000B5 +:106BE00000000000000000000000000000000000A5 +:106BF0000000000000000000000000000000000095 +:106C00000000000000000000000000000000000084 +:106C10000000000000000000000000000000000074 +:106C20000000000000000000000000000000000064 +:106C30000000000000000000000000000000000054 +:106C40000000000000000000000000000000000044 +:106C50000000000000000000000000000000000034 +:106C60000000000000000000000000000000000024 +:106C70000000000000000000000000000000000014 +:106C80000000000000000000000000000000000004 +:106C900000000000000000000000000000000000F4 +:106CA00000000000000000000000000000000000E4 +:106CB00000000000000000000000000000000000D4 +:106CC00000000000000000000000000000000000C4 +:106CD00000000000000000000000000000000000B4 +:106CE00000000000000000000000000000000000A4 +:106CF0000000000000000000000000000000000094 +:106D00000000000000000000000000000000000083 +:106D10000000000000000000000000000000000073 +:106D20000000000000000000000000000000000063 +:106D30000000000000000000000000000000000053 +:106D40000000000000000000000000000000000043 +:106D50000000000000000000000000000000000033 +:106D60000000000000000000000000000000000023 +:106D70000000000000000000000000000000000013 +:106D80000000000000000000000000000000000003 +:106D900000000000000000000000000000000000F3 +:106DA00000000000000000000000000000000000E3 +:106DB00000000000000000000000000000000000D3 +:106DC00000000000000000000000000000000000C3 +:106DD00000000000000000000000000000000000B3 +:106DE00000000000000000000000000000000000A3 +:106DF0000000000000000000000000000000000093 +:106E00000000000000000000000000000000000082 +:106E10000000000000000000000000000000000072 +:106E20000000000000000000000000000000000062 +:106E30000000000000000000000000000000000052 +:106E40000000000000000000000000000000000042 +:106E50000000000000000000000000000000000032 +:106E60000000000000000000000000000000000022 +:106E70000000000000000000000000000000000012 +:106E80000000000000000000000000000000000002 +:106E900000000000000000000000000000000000F2 +:106EA00000000000000000000000000000000000E2 +:106EB00000000000000000000000000000000000D2 +:106EC00000000000000000000000000000000000C2 +:106ED00000000000000000000000000000000000B2 +:106EE00000000000000000000000000000000000A2 +:106EF0000000000000000000000000000000000092 +:106F00000000000000000000000000000000000081 +:106F10000000000000000000000000000000000071 +:106F20000000000000000000000000000000000061 +:106F30000000000000000000000000000000000051 +:106F40000000000000000000000000000000000041 +:106F50000000000000000000000000000000000031 +:106F60000000000000000000000000000000000021 +:106F70000000000000000000000000000000000011 +:106F80000000000000000000000000000000000001 +:106F900000000000000000000000000000000000F1 +:106FA00000000000000000000000000000000000E1 +:106FB00000000000000000000000000000000000D1 +:106FC00000000000000000000000000000000000C1 +:106FD00000000000000000000000000000000000B1 +:106FE00000000000000000000000000000000000A1 +:106FF0000000000000000000000000000000000091 +:107000000000000000000000000000000000000080 +:107010000000000000000000000000000000000070 +:107020000000000000000000000000000000000060 +:107030000000000000000000000000000000000050 +:107040000000000000000000000000000000000040 +:107050000000000000000000000000000000000030 +:107060000000000000000000000000000000000020 +:107070000000000000000000000000000000000010 +:107080000000000000000000000000000000000000 +:1070900000000000000000000000000000000000F0 +:1070A00000000000000000000000000000000000E0 +:1070B00000000000000000000000000000000000D0 +:1070C00000000000000000000000000000000000C0 +:1070D00000000000000000000000000000000000B0 +:1070E00000000000000000000000000000000000A0 +:1070F0000000000000000000000000000000000090 +:10710000000000000000000000000000000000007F +:10711000000000000000000000000000000000006F +:10712000000000000000000000000000000000005F +:10713000000000000000000000000000000000004F +:10714000000000000000000000000000000000003F +:10715000000000000000000000000000000000002F +:10716000000000000000000000000000000000001F +:10717000000000000000000000000000000000000F +:1071800000000000000000000000000000000000FF +:1071900000000000000000000000000000000000EF +:1071A00000000000000000000000000000000000DF +:1071B00000000000000000000000000000000000CF +:1071C00000000000000000000000000000000000BF +:1071D00000000000000000000000000000000000AF +:1071E000000000000000000000000000000000009F +:1071F000000000000000000000000000000000008F +:10720000000000000000000000000000000000007E +:10721000000000000000000000000000000000006E +:10722000000000000000000000000000000000005E +:10723000000000000000000000000000000000004E +:10724000000000000000000000000000000000003E +:10725000000000000000000000000000000000002E +:10726000000000000000000000000000000000001E +:10727000000000000000000000000000000000000E +:1072800000000000000000000000000000000000FE +:1072900000000000000000000000000000000000EE +:1072A00000000000000000000000000000000000DE +:1072B00000000000000000000000000000000000CE +:1072C00000000000000000000000000000000000BE +:1072D00000000000000000000000000000000000AE +:1072E000000000000000000000000000000000009E +:1072F000000000000000000000000000000000008E +:10730000000000000000000000000000000000007D +:10731000000000000000000000000000000000006D +:10732000000000000000000000000000000000005D +:10733000000000000000000000000000000000004D +:10734000000000000000000000000000000000003D +:10735000000000000000000000000000000000002D +:10736000000000000000000000000000000000001D +:10737000000000000000000000000000000000000D +:1073800000000000000000000000000000000000FD +:1073900000000000000000000000000000000000ED +:1073A00000000000000000000000000000000000DD +:1073B00000000000000000000000000000000000CD +:1073C00000000000000000000000000000000000BD +:1073D00000000000000000000000000000000000AD +:1073E000000000000000000000000000000000009D +:1073F000000000000000000000000000000000008D +:10740000000000000000000000000000000000007C +:10741000000000000000000000000000000000006C +:10742000000000000000000000000000000000005C +:10743000000000000000000000000000000000004C +:10744000000000000000000000000000000000003C +:10745000000000000000000000000000000000002C +:10746000000000000000000000000000000000001C +:10747000000000000000000000000000000000000C +:1074800000000000000000000000000000000000FC +:1074900000000000000000000000000000000000EC +:1074A00000000000000000000000000000000000DC +:1074B00000000000000000000000000000000000CC +:1074C00000000000000000000000000000000000BC +:1074D00000000000000000000000000000000000AC +:1074E000000000000000000000000000000000009C +:1074F000000000000000000000000000000000008C +:10750000000000000000000000000000000000007B +:10751000000000000000000000000000000000006B +:10752000000000000000000000000000000000005B +:10753000000000000000000000000000000000004B +:10754000000000000000000000000000000000003B +:10755000000000000000000000000000000000002B +:10756000000000000000000000000000000000001B +:10757000000000000000000000000000000000000B +:1075800000000000000000000000000000000000FB +:1075900000000000000000000000000000000000EB +:1075A00000000000000000000000000000000000DB +:1075B00000000000000000000000000000000000CB +:1075C00000000000000000000000000000000000BB +:1075D00000000000000000000000000000000000AB +:1075E000000000000000000000000000000000009B +:1075F000000000000000000000000000000000008B +:10760000000000000000000000000000000000007A +:10761000000000000000000000000000000000006A +:10762000000000000000000000000000000000005A +:10763000000000000000000000000000000000004A +:10764000000000000000000000000000000000003A +:10765000000000000000000000000000000000002A +:10766000000000000000000000000000000000001A +:10767000000000000000000000000000000000000A +:1076800000000000000000000000000000000000FA +:1076900000000000000000000000000000000000EA +:1076A00000000000000000000000000000000000DA +:1076B00000000000000000000000000000000000CA +:1076C00000000000000000000000000000000000BA +:1076D00000000000000000000000000000000000AA +:1076E000000000000000000000000000000000009A +:1076F000000000000000000000000000000000008A +:107700000000000000000000000000000000000079 +:107710000000000000000000000000000000000069 +:107720000000000000000000000000000000000059 +:107730000000000000000000000000000000000049 +:107740000000000000000000000000000000000039 +:107750000000000000000000000000000000000029 +:107760000000000000000000000000000000000019 +:107770000000000000000000000000000000000009 +:1077800000000000000000000000000000000000F9 +:1077900000000000000000000000000000000000E9 +:1077A00000000000000000000000000000000000D9 +:1077B00000000000000000000000000000000000C9 +:1077C00000000000000000000000000000000000B9 +:1077D00000000000000000000000000000000000A9 +:1077E0000000000000000000000000000000000099 +:1077F0000000000000000000000000000000000089 +:107800000000000000000000000000000000000078 +:107810000000000000000000000000000000000068 +:107820000000000000000000000000000000000058 +:107830000000000000000000000000000000000048 +:107840000000000000000000000000000000000038 +:107850000000000000000000000000000000000028 +:107860000000000000000000000000000000000018 +:107870000000000000000000000000000000000008 +:1078800000000000000000000000000000000000F8 +:1078900000000000000000000000000000000000E8 +:1078A00000000000000000000000000000000000D8 +:1078B00000000000000000000000000000000000C8 +:1078C00000000000000000000000000000000000B8 +:1078D00000000000000000000000000000000000A8 +:1078E0000000000000000000000000000000000098 +:1078F0000000000000000000000000000000000088 +:107900000000000000000000000000000000000077 +:107910000000000000000000000000000000000067 +:107920000000000000000000000000000000000057 +:107930000000000000000000000000000000000047 +:107940000000000000000000000000000000000037 +:107950000000000000000000000000000000000027 +:107960000000000000000000000000000000000017 +:107970000000000000000000000000000000000007 +:1079800000000000000000000000000000000000F7 +:1079900000000000000000000000000000000000E7 +:1079A00000000000000000000000000000000000D7 +:1079B00000000000000000000000000000000000C7 +:1079C00000000000000000000000000000000000B7 +:1079D00000000000000000000000000000000000A7 +:1079E0000000000000000000000000000000000097 +:1079F0000000000000000000000000000000000087 +:107A00000000000000000000000000000000000076 +:107A10000000000000000000000000000000000066 +:107A20000000000000000000000000000000000056 +:107A30000000000000000000000000000000000046 +:107A40000000000000000000000000000000000036 +:107A50000000000000000000000000000000000026 +:107A60000000000000000000000000000000000016 +:107A70000000000000000000000000000000000006 +:107A800000000000000000000000000000000000F6 +:107A900000000000000000000000000000000000E6 +:107AA00000000000000000000000000000000000D6 +:107AB00000000000000000000000000000000000C6 +:107AC00000000000000000000000000000000000B6 +:107AD00000000000000000000000000000000000A6 +:107AE0000000000000000000000000000000000096 +:107AF0000000000000000000000000000000000086 +:107B00000000000000000000000000000000000075 +:107B10000000000000000000000000000000000065 +:107B20000000000000000000000000000000000055 +:107B30000000000000000000000000000000000045 +:107B40000000000000000000000000000000000035 +:107B50000000000000000000000000000000000025 +:107B60000000000000000000000000000000000015 +:107B70000000000000000000000000000000000005 +:107B800000000000000000000000000000000000F5 +:107B900000000000000000000000000000000000E5 +:107BA00000000000000000000000000000000000D5 +:107BB00000000000000000000000000000000000C5 +:107BC00000000000000000000000000000000000B5 +:107BD00000000000000000000000000000000000A5 +:107BE0000000000000000000000000000000000095 +:107BF0000000000000000000000000000000000085 +:107C00000000000000000000000000000000000074 +:107C10000000000000000000000000000000000064 +:107C20000000000000000000000000000000000054 +:107C30000000000000000000000000000000000044 +:107C40000000000000000000000000000000000034 +:107C50000000000000000000000000000000000024 +:107C60000000000000000000000000000000000014 +:107C70000000000000000000000000000000000004 +:107C800000000000000000000000000000000000F4 +:107C900000000000000000000000000000000000E4 +:107CA00000000000000000000000000000000000D4 +:107CB00000000000000000000000000000000000C4 +:107CC00000000000000000000000000000000000B4 +:107CD00000000000000000000000000000000000A4 +:107CE0000000000000000000000000000000000094 +:107CF0000000000000000000000000000000000084 +:107D00000000000000000000000000000000000073 +:107D10000000000000000000000000000000000063 +:107D20000000000000000000000000000000000053 +:107D30000000000000000000000000000000000043 +:107D40000000000000000000000000000000000033 +:107D50000000000000000000000000000000000023 +:107D60000000000000000000000000000000000013 +:107D70000000000000000000000000000000000003 +:107D800000000000000000000000000000000000F3 +:107D900000000000000000000000000000000000E3 +:107DA00000000000000000000000000000000000D3 +:107DB00000000000000000000000000000000000C3 +:107DC00000000000000000000000000000000000B3 +:107DD00000000000000000000000000000000000A3 +:107DE0000000000000000000000000000000000093 +:107DF0000000000000000000000000000000000083 +:107E00000000000000000000000000000000000072 +:107E10000000000000000000000000000000000062 +:107E20000000000000000000000000000000000052 +:107E30000000000000000000000000000000000042 +:107E40000000000000000000000000000000000032 +:107E50000000000000000000000000000000000022 +:107E60000000000000000000000000000000000012 +:107E70000000000000000000000000000000000002 +:107E800000000000000000000000000000000000F2 +:107E900000000000000000000000000000000000E2 +:107EA00000000000000000000000000000000000D2 +:107EB00000000000000000000000000000000000C2 +:107EC00000000000000000000000000000000000B2 +:107ED00000000000000000000000000000000000A2 +:107EE0000000000000000000000000000000000092 +:107EF0000000000000000000000000000000000082 +:107F00000000000000000000000000000000000071 +:107F10000000000000000000000000000000000061 +:107F20000000000000000000000000000000000051 +:107F30000000000000000000000000000000000041 +:107F40000000000000000000000000000000000031 +:107F50000000000000000000000000000000000021 +:107F60000000000000000000000000000000000011 +:107F70000000000000000000000000000000000001 +:107F800000000000000000000000000000000000F1 +:107F900000000000000000000000000000000000E1 +:107FA00000000000000000000000000000000000D1 +:107FB00000000000000000000000000000000000C1 +:107FC00000000000000000000000000000000000B1 +:107FD00000000000000000000000000000000000A1 +:107FE0000000000000000000000000000000000091 +:107FF0000000000000000000000000000000000081 +:108000000000000000000000000000000000000070 +:108010000000000000000000000000000000000060 +:108020000000000000000000000000000000000050 +:108030000000000000000000000000000000000040 +:108040000000000000000000000000000000000030 +:108050000000000000000000000000000000000020 +:108060000000000000000000000000000000000010 +:108070000000000000000000000000000000000000 +:1080800000000000000000000000000000000000F0 +:1080900000000000000000000000000000000000E0 +:1080A00000000000000000000000000000000000D0 +:1080B00000000000000000000000000000000000C0 +:1080C00000000000000000000000000000000000B0 +:1080D00000000000000000000000000000000000A0 +:1080E0000000000000000000000000000000000090 +:1080F0000000000000000000000000000000000080 +:10810000000000000000000000000000000000006F +:10811000000000000000000000000000000000005F +:10812000000000000000000000000000000000004F +:10813000000000000000000000000000000000003F +:10814000000000000000000000000000000000002F +:10815000000000000000000000000000000000001F +:10816000000000000000000000000000000000000F +:1081700000000000000000000000000000000000FF +:1081800000000000000000000000000000000000EF +:1081900000000000000000000000000000000000DF +:1081A00000000000000000000000000000000000CF +:1081B00000000000000000000000000000000000BF +:1081C00000000000000000000000000000000000AF +:1081D000000000000000000000000000000000009F +:1081E000000000000000000000000000000000008F +:1081F000000000000000000000000000000000007F +:10820000000000000000000000000000000000006E +:10821000000000000000000000000000000000005E +:10822000000000000000000000000000000000004E +:10823000000000000000000000000000000000003E +:10824000000000000000000000000000000000002E +:10825000000000000000000000000000000000001E +:10826000000000000000000000000000000000000E +:1082700000000000000000000000000000000000FE +:1082800000000000000000000000000000000000EE +:1082900000000000000000000000000000000000DE +:1082A00000000000000000000000000000000000CE +:1082B00000000000000000000000000000000000BE +:1082C00000000000000000000000000000000000AE +:1082D000000000000000000000000000000000009E +:1082E000000000000000000000000000000000008E +:1082F000000000000000000000000000000000007E +:10830000000000000000000000000000000000006D +:10831000000000000000000000000000000000005D +:10832000000000000000000000000000000000004D +:10833000000000000000000000000000000000003D +:10834000000000000000000000000000000000002D +:10835000000000000000000000000000000000001D +:10836000000000000000000000000000000000000D +:1083700000000000000000000000000000000000FD +:1083800000000000000000000000000000000000ED +:1083900000000000000000000000000000000000DD +:1083A00000000000000000000000000000000000CD +:1083B00000000000000000000000000000000000BD +:1083C00000000000000000000000000000000000AD +:1083D000000000000000000000000000000000009D +:1083E000000000000000000000000000000000008D +:1083F000000000000000000000000000000000007D +:10840000000000000000000000000000000000006C +:10841000000000000000000000000000000000005C +:10842000000000000000000000000000000000004C +:10843000000000000000000000000000000000003C +:10844000000000000000000000000000000000002C +:10845000000000000000000000000000000000001C +:10846000000000000000000000000000000000000C +:1084700000000000000000000000000000000000FC +:1084800000000000000000000000000000000000EC +:1084900000000000000000000000000000000000DC +:1084A00000000000000000000000000000000000CC +:1084B00000000000000000000000000000000000BC +:1084C00000000000000000000000000000000000AC +:1084D000000000000000000000000000000000009C +:1084E000000000000000000000000000000000008C +:1084F000000000000000000000000000000000007C +:10850000000000000000000000000000000000006B +:10851000000000000000000000000000000000005B +:10852000000000000000000000000000000000004B +:10853000000000000000000000000000000000003B +:10854000000000000000000000000000000000002B +:10855000000000000000000000000000000000001B +:10856000000000000000000000000000000000000B +:1085700000000000000000000000000000000000FB +:1085800000000000000000000000000000000000EB +:1085900000000000000000000000000000000000DB +:1085A00000000000000000000000000000000000CB +:1085B00000000000000000000000000000000000BB +:1085C00000000000000000000000000000000000AB +:1085D000000000000000000000000000000000009B +:1085E000000000000000000000000000000000008B +:1085F000000000000000000000000000000000007B +:10860000000000000000000000000000000000006A +:10861000000000000000000000000000000000005A +:10862000000000000000000000000000000000004A +:10863000000000000000000000000000000000003A +:10864000000000000000000000000000000000002A +:10865000000000000000000000000000000000001A +:10866000000000000000000000000000000000000A +:1086700000000000000000000000000000000000FA +:1086800000000000000000000000000000000000EA +:1086900000000000000000000000000000000000DA +:1086A00000000000000000000000000000000000CA +:1086B00000000000000000000000000000000000BA +:1086C00000000000000000000000000000000000AA +:1086D000000000000000000000000000000000009A +:1086E000000000000000000000000000000000008A +:1086F000000000000000000000000000000000007A +:108700000000000000000000000000000000000069 +:108710000000000000000000000000000000000059 +:108720000000000000000000000000000000000049 +:108730000000000000000000000000000000000039 +:108740000000000000000000000000000000000029 +:108750000000000000000000000000000000000019 +:108760000000000000000000000000000000000009 +:1087700000000000000000000000000000000000F9 +:1087800000000000000000000000000000000000E9 +:1087900000000000000000000000000000000000D9 +:1087A00000000000000000000000000000000000C9 +:1087B00000000000000000000000000000000000B9 +:1087C00000000000000000000000000000000000A9 +:1087D0000000000000000000000000000000000099 +:1087E0000000000000000000000000000000000089 +:1087F0000000000000000000000000000000000079 +:108800000000000000000000000000000000000068 +:108810000000000000000000000000000000000058 +:108820000000000000000000000000000000000048 +:108830000000000000000000000000000000000038 +:108840000000000000000000000000000000000028 +:108850000000000000000000000000000000000018 +:108860000000000000000000000000000000000008 +:1088700000000000000000000000000000000000F8 +:1088800000000000000000000000000000000000E8 +:1088900000000000000000000000000000000000D8 +:1088A00000000000000000000000000000000000C8 +:1088B00000000000000000000000000000000000B8 +:1088C00000000000000000000000000000000000A8 +:1088D0000000000000000000000000000000000098 +:1088E0000000000000000000000000000000000088 +:1088F0000000000000000000000000000000000078 +:108900000000000000000000000000000000000067 +:108910000000000000000000000000000000000057 +:108920000000000000000000000000000000000047 +:108930000000000000000000000000000000000037 +:108940000000000000000000000000000000000027 +:108950000000000000000000000000000000000017 +:108960000000000000000000000000000000000007 +:1089700000000000000000000000000000000000F7 +:1089800000000000000000000000000000000000E7 +:1089900000000000000000000000000000000000D7 +:1089A00000000000000000000000000000000000C7 +:1089B00000000000000000000000000000000000B7 +:1089C00000000000000000000000000000000000A7 +:1089D0000000000000000000000000000000000097 +:1089E0000000000000000000000000000000000087 +:1089F0000000000000000000000000000000000077 +:108A00000000000000000000000000000000000066 +:108A10000000000000000000000000000000000056 +:108A20000000000000000000000000000000000046 +:108A30000000000000000000000000000000000036 +:108A40000000000000000000000000000000000026 +:108A50000000000000000000000000000000000016 +:108A60000000000000000000000000000000000006 +:108A700000000000000000000000000000000000F6 +:108A800000000000000000000000000000000000E6 +:108A900000000000000000000000000000000000D6 +:108AA00000000000000000000000000000000000C6 +:108AB00000000000000000000000000000000000B6 +:108AC00000000000000000000000000000000000A6 +:108AD0000000000000000000000000000000000096 +:108AE0000000000000000000000000000000000086 +:108AF0000000000000000000000000000000000076 +:108B00000000000000000000000000000000000065 +:108B10000000000000000000000000000000000055 +:108B20000000000000000000000000000000000045 +:108B30000000000000000000000000000000000035 +:108B40000000000000000000000000000000000025 +:108B50000000000000000000000000000000000015 +:108B60000000000000000000000000000000000005 +:108B700000000000000000000000000000000000F5 +:108B800000000000000000000000000000000000E5 +:108B900000000000000000000000000000000000D5 +:108BA00000000000000000000000000000000000C5 +:108BB00000000000000000000000000000000000B5 +:108BC00000000000000000000000000000000000A5 +:108BD0000000000000000000000000000000000095 +:108BE0000000000000000000000000000000000085 +:108BF0000000000000000000000000000000000075 +:108C00000000000000000000000000000000000064 +:108C10000000000000000000000000000000000054 +:108C20000000000000000000000000000000000044 +:108C30000000000000000000000000000000000034 +:108C40000000000000000000000000000000000024 +:108C50000000000000000000000000000000000014 +:108C60000000000000000000000000000000000004 +:108C700000000000000000000000000000000000F4 +:108C800000000000000000000000000000000000E4 +:108C900000000000000000000000000000000000D4 +:108CA00000000000000000000000000000000000C4 +:108CB00000000000000000000000000000000000B4 +:108CC00000000000000000000000000000000000A4 +:108CD0000000000000000000000000000000000094 +:108CE0000000000000000000000000000000000084 +:108CF0000000000000000000000000000000000074 +:108D00000000000000000000000000000000000063 +:108D10000000000000000000000000000000000053 +:108D20000000000000000000000000000000000043 +:108D30000000000000000000000000000000000033 +:108D40000000000000000000000000000000000023 +:108D50000000000000000000000000000000000013 +:108D60000000000000000000000000000000000003 +:108D700000000000000000000000000000000000F3 +:108D800000000000000000000000000000000000E3 +:108D900000000000000000000000000000000000D3 +:108DA00000000000000000000000000000000000C3 +:108DB00000000000000000000000000000000000B3 +:108DC00000000000000000000000000000000000A3 +:108DD0000000000000000000000000000000000093 +:108DE0000000000000000000000000000000000083 +:108DF0000000000000000000000000000000000073 +:108E00000000000000000000000000000000000062 +:108E10000000000000000000000000000000000052 +:108E20000000000000000000000000000000000042 +:108E30000000000000000000000000000000000032 +:108E40000000000000000000000000000000000022 +:108E50000000000000000000000000000000000012 +:108E60000000000000000000000000000000000002 +:108E700000000000000000000000000000000000F2 +:108E800000000000000000000000000000000000E2 +:108E900000000000000000000000000000000000D2 +:108EA00000000000000000000000000000000000C2 +:108EB00000000000000000000000000000000000B2 +:108EC00000000000000000000000000000000000A2 +:108ED0000000000000000000000000000000000092 +:108EE0000000000000000000000000000000000082 +:108EF0000000000000000000000000000000000072 +:108F00000000000000000000000000000000000061 +:108F10000000000000000000000000000000000051 +:108F20000000000000000000000000000000000041 +:108F30000000000000000000000000000000000031 +:108F40000000000000000000000000000000000021 +:108F50000000000000000000000000000000000011 +:108F60000000000000000000000000000000000001 +:108F700000000000000000000000000000000000F1 +:108F800000000000000000000000000000000000E1 +:108F900000000000000000000000000000000000D1 +:108FA00000000000000000000000000000000000C1 +:108FB00000000000000000000000000000000000B1 +:108FC00000000000000000000000000000000000A1 +:108FD0000000000000000000000000000000000091 +:108FE0000000000000000000000000000000000081 +:108FF0000000000000000000000000000000000071 +:109000000000000000000000000000000000000060 +:109010000000000000000000000000000000000050 +:109020000000000000000000000000000000000040 +:109030000000000000000000000000000000000030 +:109040000000000000000000000000000000000020 +:109050000000000000000000000000000000000010 +:109060000000000000000000000000000000000000 +:1090700000000000000000000000000000000000F0 +:1090800000000000000000000000000000000000E0 +:1090900000000000000000000000000000000000D0 +:1090A00000000000000000000000000000000000C0 +:1090B00000000000000000000000000000000000B0 +:1090C00000000000000000000000000000000000A0 +:1090D0000000000000000000000000000000000090 +:1090E0000000000000000000000000000000000080 +:1090F0000000000000000000000000000000000070 +:10910000000000000000000000000000000000005F +:10911000000000000000000000000000000000004F +:10912000000000000000000000000000000000003F +:10913000000000000000000000000000000000002F +:10914000000000000000000000000000000000001F +:10915000000000000000000000000000000000000F +:1091600000000000000000000000000000000000FF +:1091700000000000000000000000000000000000EF +:1091800000000000000000000000000000000000DF +:1091900000000000000000000000000000000000CF +:1091A00000000000000000000000000000000000BF +:1091B00000000000000000000000000000000000AF +:1091C000000000000000000000000000000000009F +:1091D000000000000000000000000000000000008F +:1091E000000000000000000000000000000000007F +:1091F000000000000000000000000000000000006F +:10920000000000000000000000000000000000005E +:10921000000000000000000000000000000000004E +:10922000000000000000000000000000000000003E +:10923000000000000000000000000000000000002E +:10924000000000000000000000000000000000001E +:10925000000000000000000000000000000000000E +:1092600000000000000000000000000000000000FE +:1092700000000000000000000000000000000000EE +:1092800000000000000000000000000000000000DE +:1092900000000000000000000000000000000000CE +:1092A00000000000000000000000000000000000BE +:1092B00000000000000000000000000000000000AE +:1092C000000000000000000000000000000000009E +:1092D000000000000000000000000000000000008E +:1092E000000000000000000000000000000000007E +:1092F000000000000000000000000000000000006E +:10930000000000000000000000000000000000005D +:10931000000000000000000000000000000000004D +:10932000000000000000000000000000000000003D +:10933000000000000000000000000000000000002D +:10934000000000000000000000000000000000001D +:10935000000000000000000000000000000000000D +:1093600000000000000000000000000000000000FD +:1093700000000000000000000000000000000000ED +:1093800000000000000000000000000000000000DD +:1093900000000000000000000000000000000000CD +:1093A00000000000000000000000000000000000BD +:1093B00000000000000000000000000000000000AD +:1093C000000000000000000000000000000000009D +:1093D000000000000000000000000000000000008D +:1093E000000000000000000000000000000000007D +:1093F000000000000000000000000000000000006D +:10940000000000000000000000000000000000005C +:10941000000000000000000000000000000000004C +:10942000000000000000000000000000000000003C +:10943000000000000000000000000000000000002C +:10944000000000000000000000000000000000001C +:10945000000000000000000000000000000000000C +:1094600000000000000000000000000000000000FC +:1094700000000000000000000000000000000000EC +:1094800000000000000000000000000000000000DC +:1094900000000000000000000000000000000000CC +:1094A00000000000000000000000000000000000BC +:1094B00000000000000000000000000000000000AC +:1094C000000000000000000000000000000000009C +:1094D000000000000000000000000000000000008C +:1094E000000000000000000000000000000000007C +:1094F000000000000000000000000000000000006C +:10950000000000000000000000000000000000005B +:10951000000000000000000000000000000000004B +:10952000000000000000000000000000000000003B +:10953000000000000000000000000000000000002B +:10954000000000000000000000000000000000001B +:10955000000000000000000000000000000000000B +:1095600000000000000000000000000000000000FB +:1095700000000000000000000000000000000000EB +:1095800000000000000000000000000000000000DB +:1095900000000000000000000000000000000000CB +:1095A00000000000000000000000000000000000BB +:1095B00000000000000000000000000000000000AB +:1095C000000000000000000000000000000000009B +:1095D000000000000000000000000000000000008B +:1095E000000000000000000000000000000000007B +:1095F000000000000000000000000000000000006B +:10960000000000000000000000000000000000005A +:10961000000000000000000000000000000000004A +:10962000000000000000000000000000000000003A +:10963000000000000000000000000000000000002A +:10964000000000000000000000000000000000001A +:10965000000000000000000000000000000000000A +:1096600000000000000000000000000000000000FA +:1096700000000000000000000000000000000000EA +:1096800000000000000000000000000000000000DA +:1096900000000000000000000000000000000000CA +:1096A00000000000000000000000000000000000BA +:1096B00000000000000000000000000000000000AA +:1096C000000000000000000000000000000000009A +:1096D000000000000000000000000000000000008A +:1096E000000000000000000000000000000000007A +:1096F000000000000000000000000000000000006A +:109700000000000000000000000000000000000059 +:109710000000000000000000000000000000000049 +:109720000000000000000000000000000000000039 +:109730000000000000000000000000000000000029 +:109740000000000000000000000000000000000019 +:109750000000000000000000000000000000000009 +:1097600000000000000000000000000000000000F9 +:1097700000000000000000000000000000000000E9 +:1097800000000000000000000000000000000000D9 +:1097900000000000000000000000000000000000C9 +:1097A00000000000000000000000000000000000B9 +:1097B00000000000000000000000000000000000A9 +:1097C0000000000000000000000000000000000099 +:1097D0000000000000000000000000000000000089 +:1097E0000000000000000000000000000000000079 +:1097F0000000000000000000000000000000000069 +:109800000000000000000000000000000000000058 +:109810000000000000000000000000000000000048 +:109820000000000000000000000000000000000038 +:109830000000000000000000000000000000000028 +:109840000000000000000000000000000000000018 +:109850000000000000000000000000000000000008 +:1098600000000000000000000000000000000000F8 +:1098700000000000000000000000000000000000E8 +:1098800000000000000000000000000000000000D8 +:1098900000000000000000000000000000000000C8 +:1098A00000000000000000000000000000000000B8 +:1098B00000000000000000000000000000000000A8 +:1098C0000000000000000000000000000000000098 +:1098D0000000000000000000000000000000000088 +:1098E0000000000000000000000000000000000078 +:1098F0000000000000000000000000000000000068 +:109900000000000000000000000000000000000057 +:109910000000000000000000000000000000000047 +:109920000000000000000000000000000000000037 +:109930000000000000000000000000000000000027 +:109940000000000000000000000000000000000017 +:109950000000000000000000000000000000000007 +:1099600000000000000000000000000000000000F7 +:1099700000000000000000000000000000000000E7 +:1099800000000000000000000000000000000000D7 +:1099900000000000000000000000000000000000C7 +:1099A00000000000000000000000000000000000B7 +:1099B00000000000000000000000000000000000A7 +:1099C0000000000000000000000000000000000097 +:1099D0000000000000000000000000000000000087 +:1099E0000000000000000000000000000000000077 +:1099F0000000000000000000000000000000000067 +:109A00000000000000000000000000000000000056 +:109A10000000000000000000000000000000000046 +:109A20000000000000000000000000000000000036 +:109A30000000000000000000000000000000000026 +:109A40000000000000000000000000000000000016 +:109A50000000000000000000000000000000000006 +:109A600000000000000000000000000000000000F6 +:109A700000000000000000000000000000000000E6 +:109A800000000000000000000000000000000000D6 +:109A900000000000000000000000000000000000C6 +:109AA00000000000000000000000000000000000B6 +:109AB00000000000000000000000000000000000A6 +:109AC0000000000000000000000000000000000096 +:109AD0000000000000000000000000000000000086 +:109AE0000000000000000000000000000000000076 +:109AF0000000000000000000000000000000000066 +:109B00000000000000000000000000000000000055 +:109B10000000000000000000000000000000000045 +:109B20000000000000000000000000000000000035 +:109B30000000000000000000000000000000000025 +:109B40000000000000000000000000000000000015 +:109B50000000000000000000000000000000000005 +:109B600000000000000000000000000000000000F5 +:109B700000000000000000000000000000000000E5 +:109B800000000000000000000000000000000000D5 +:109B900000000000000000000000000000000000C5 +:109BA00000000000000000000000000000000000B5 +:109BB00000000000000000000000000000000000A5 +:109BC0000000000000000000000000000000000095 +:109BD0000000000000000000000000000000000085 +:109BE0000000000000000000000000000000000075 +:109BF0000000000000000000000000000000000065 +:109C00000000000000000000000000000000000054 +:109C10000000000000000000000000000000000044 +:109C20000000000000000000000000000000000034 +:109C30000000000000000000000000000000000024 +:109C40000000000000000000000000000000000014 +:109C50000000000000000000000000000000000004 +:109C600000000000000000000000000000000000F4 +:109C700000000000000000000000000000000000E4 +:109C800000000000000000000000000000000000D4 +:109C900000000000000000000000000000000000C4 +:109CA00000000000000000000000000000000000B4 +:109CB00000000000000000000000000000000000A4 +:109CC0000000000000000000000000000000000094 +:109CD0000000000000000000000000000000000084 +:109CE0000000000000000000000000000000000074 +:109CF0000000000000000000000000000000000064 +:109D00000000000000000000000000000000000053 +:109D10000000000000000000000000000000000043 +:109D20000000000000000000000000000000000033 +:109D30000000000000000000000000000000000023 +:109D40000000000000000000000000000000000013 +:109D50000000000000000000000000000000000003 +:109D600000000000000000000000000000000000F3 +:109D700000000000000000000000000000000000E3 +:109D800000000000000000000000000000000000D3 +:109D900000000000000000000000000000000000C3 +:109DA00000000000000000000000000000000000B3 +:109DB00000000000000000000000000000000000A3 +:109DC0000000000000000000000000000000000093 +:109DD0000000000000000000000000000000000083 +:109DE0000000000000000000000000000000000073 +:109DF0000000000000000000000000000000000063 +:109E00000000000000000000000000000000000052 +:109E10000000000000000000000000000000000042 +:109E20000000000000000000000000000000000032 +:109E30000000000000000000000000000000000022 +:109E40000000000000000000000000000000000012 +:109E50000000000000000000000000000000000002 +:109E600000000000000000000000000000000000F2 +:109E700000000000000000000000000000000000E2 +:109E800000000000000000000000000000000000D2 +:109E900000000000000000000000000000000000C2 +:109EA00000000000000000000000000000000000B2 +:109EB00000000000000000000000000000000000A2 +:109EC0000000000000000000000000000000000092 +:109ED0000000000000000000000000000000000082 +:109EE0000000000000000000000000000000000072 +:109EF0000000000000000000000000000000000062 +:109F00000000000000000000000000000000000051 +:109F10000000000000000000000000000000000041 +:109F20000000000000000000000000000000000031 +:109F30000000000000000000000000000000000021 +:109F40000000000000000000000000000000000011 +:109F50000000000000000000000000000000000001 +:109F600000000000000000000000000000000000F1 +:109F700000000000000000000000000000000000E1 +:109F800000000000000000000000000000000000D1 +:109F900000000000000000000000000000000000C1 +:109FA00000000000000000000000000000000000B1 +:109FB00000000000000000000000000000000000A1 +:109FC0000000000000000000000000000000000091 +:109FD0000000000000000000000000000000000081 +:109FE0000000000000000000000000000000000071 +:109FF0000000000000000000000000000000000061 +:10A000000000000000000000000000000000000050 +:10A010000000000000000000000000000000000040 +:10A020000000000000000000000000000000000030 +:10A030000000000000000000000000000000000020 +:10A040000000000000000000000000000000000010 +:10A050000000000000000000000000000000000000 +:10A0600000000000000000000000000000000000F0 +:10A0700000000000000000000000000000000000E0 +:10A0800000000000000000000000000000000000D0 +:10A0900000000000000000000000000000000000C0 +:10A0A00000000000000000000000000000000000B0 +:10A0B00000000000000000000000000000000000A0 +:10A0C0000000000000000000000000000000000090 +:10A0D0000000000000000000000000000000000080 +:10A0E0000000000000000000000000000000000070 +:10A0F0000000000000000000000000000000000060 +:10A10000000000000000000000000000000000004F +:10A11000000000000000000000000000000000003F +:10A12000000000000000000000000000000000002F +:10A13000000000000000000000000000000000001F +:10A14000000000000000000000000000000000000F +:10A1500000000000000000000000000000000000FF +:10A1600000000000000000000000000000000000EF +:10A1700000000000000000000000000000000000DF +:10A1800000000000000000000000000000000000CF +:10A1900000000000000000000000000000000000BF +:10A1A00000000000000000000000000000000000AF +:10A1B000000000000000000000000000000000009F +:10A1C000000000000000000000000000000000008F +:10A1D000000000000000000000000000000000007F +:10A1E000000000000000000000000000000000006F +:10A1F000000000000000000000000000000000005F +:10A20000000000000000000000000000000000004E +:10A21000000000000000000000000000000000003E +:10A22000000000000000000000000000000000002E +:10A23000000000000000000000000000000000001E +:10A24000000000000000000000000000000000000E +:10A2500000000000000000000000000000000000FE +:10A2600000000000000000000000000000000000EE +:10A2700000000000000000000000000000000000DE +:10A2800000000000000000000000000000000000CE +:10A2900000000000000000000000000000000000BE +:10A2A00000000000000000000000000000000000AE +:10A2B000000000000000000000000000000000009E +:10A2C000000000000000000000000000000000008E +:10A2D000000000000000000000000000000000007E +:10A2E000000000000000000000000000000000006E +:10A2F000000000000000000000000000000000005E +:10A30000000000000000000000000000000000004D +:10A31000000000000000000000000000000000003D +:10A32000000000000000000000000000000000002D +:10A33000000000000000000000000000000000001D +:10A34000000000000000000000000000000000000D +:10A3500000000000000000000000000000000000FD +:10A3600000000000000000000000000000000000ED +:10A3700000000000000000000000000000000000DD +:10A3800000000000000000000000000000000000CD +:10A3900000000000000000000000000000000000BD +:10A3A00000000000000000000000000000000000AD +:10A3B000000000000000000000000000000000009D +:10A3C000000000000000000000000000000000008D +:10A3D000000000000000000000000000000000007D +:10A3E000000000000000000000000000000000006D +:10A3F000000000000000000000000000000000005D +:10A40000000000000000000000000000000000004C +:10A41000000000000000000000000000000000003C +:10A42000000000000000000000000000000000002C +:10A43000000000000000000000000000000000001C +:10A44000000000000000000000000000000000000C +:10A4500000000000000000000000000000000000FC +:10A4600000000000000000000000000000000000EC +:10A4700000000000000000000000000000000000DC +:10A4800000000000000000000000000000000000CC +:10A4900000000000000000000000000000000000BC +:10A4A00000000000000000000000000000000000AC +:10A4B000000000000000000000000000000000009C +:10A4C000000000000000000000000000000000008C +:10A4D000000000000000000000000000000000007C +:10A4E000000000000000000000000000000000006C +:10A4F000000000000000000000000000000000005C +:10A50000000000000000000000000000000000004B +:10A51000000000000000000000000000000000003B +:10A52000000000000000000000000000000000002B +:10A53000000000000000000000000000000000001B +:10A54000000000000000000000000000000000000B +:10A5500000000000000000000000000000000000FB +:10A5600000000000000000000000000000000000EB +:10A5700000000000000000000000000000000000DB +:10A5800000000000000000000000000000000000CB +:10A5900000000000000000000000000000000000BB +:10A5A00000000000000000000000000000000000AB +:10A5B000000000000000000000000000000000009B +:10A5C000000000000000000000000000000000008B +:10A5D000000000000000000000000000000000007B +:10A5E000000000000000000000000000000000006B +:10A5F000000000000000000000000000000000005B +:10A60000000000000000000000000000000000004A +:10A61000000000000000000000000000000000003A +:10A62000000000000000000000000000000000002A +:10A63000000000000000000000000000000000001A +:10A64000000000000000000000000000000000000A +:10A6500000000000000000000000000000000000FA +:10A6600000000000000000000000000000000000EA +:10A6700000000000000000000000000000000000DA +:10A6800000000000000000000000000000000000CA +:10A6900000000000000000000000000000000000BA +:10A6A00000000000000000000000000000000000AA +:10A6B000000000000000000000000000000000009A +:10A6C000000000000000000000000000000000008A +:10A6D000000000000000000000000000000000007A +:10A6E000000000000000000000000000000000006A +:10A6F000000000000000000000000000000000005A +:10A700000000000000000000000000000000000049 +:10A710000000000000000000000000000000000039 +:10A720000000000000000000000000000000000029 +:10A730000000000000000000000000000000000019 +:10A740000000000000000000000000000000000009 +:10A7500000000000000000000000000000000000F9 +:10A7600000000000000000000000000000000000E9 +:10A7700000000000000000000000000000000000D9 +:10A7800000000000000000000000000000000000C9 +:10A7900000000000000000000000000000000000B9 +:10A7A00000000000000000000000000000000000A9 +:10A7B0000000000000000000000000000000000099 +:10A7C0000000000000000000000000000000000089 +:10A7D0000000000000000000000000000000000079 +:10A7E0000000000000000000000000000000000069 +:10A7F0000000000000000000000000000000000059 +:10A800000000000000000000000000000000000048 +:10A810000000000000000000000000000000000038 +:10A820000000000000000000000000000000000028 +:10A830000000000000000000000000000000000018 +:10A840000000000000000000000000000000000008 +:10A8500000000000000000000000000000000000F8 +:10A8600000000000000000000000000000000000E8 +:10A8700000000000000000000000000000000000D8 +:10A8800000000000000000000000000000000000C8 +:10A8900000000000000000000000000000000000B8 +:10A8A00000000000000000000000000000000000A8 +:10A8B0000000000000000000000000000000000098 +:10A8C0000000000000000000000000000000000088 +:10A8D0000000000000000000000000000000000078 +:10A8E0000000000000000000000000000000000068 +:10A8F0000000000000000000000000000000000058 +:10A900000000000000000000000000000000000047 +:10A910000000000000000000000000000000000037 +:10A920000000000000000000000000000000000027 +:10A930000000000000000000000000000000000017 +:10A940000000000000000000000000000000000007 +:10A9500000000000000000000000000000000000F7 +:10A9600000000000000000000000000000000000E7 +:10A9700000000000000000000000000000000000D7 +:10A9800000000000000000000000000000000000C7 +:10A9900000000000000000000000000000000000B7 +:10A9A00000000000000000000000000000000000A7 +:10A9B0000000000000000000000000000000000097 +:10A9C0000000000000000000000000000000000087 +:10A9D0000000000000000000000000000000000077 +:10A9E0000000000000000000000000000000000067 +:10A9F0000000000000000000000000000000000057 +:10AA00000000000000000000000000000000000046 +:10AA10000000000000000000000000000000000036 +:10AA20000000000000000000000000000000000026 +:10AA30000000000000000000000000000000000016 +:10AA40000000000000000000000000000000000006 +:10AA500000000000000000000000000000000000F6 +:10AA600000000000000000000000000000000000E6 +:10AA700000000000000000000000000000000000D6 +:10AA800000000000000000000000000000000000C6 +:10AA900000000000000000000000000000000000B6 +:10AAA00000000000000000000000000000000000A6 +:10AAB0000000000000000000000000000000000096 +:10AAC0000000000000000000000000000000000086 +:10AAD0000000000000000000000000000000000076 +:10AAE0000000000000000000000000000000000066 +:10AAF0000000000000000000000000000000000056 +:10AB00000000000000000000000000000000000045 +:10AB10000000000000000000000000000000000035 +:10AB20000000000000000000000000000000000025 +:10AB30000000000000000000000000000000000015 +:10AB40000000000000000000000000000000000005 +:10AB500000000000000000000000000000000000F5 +:10AB600000000000000000000000000000000000E5 +:10AB700000000000000000000000000000000000D5 +:10AB800000000000000000000000000000000000C5 +:10AB900000000000000000000000000000000000B5 +:10ABA00000000000000000000000000000000000A5 +:10ABB0000000000000000000000000000000000095 +:10ABC0000000000000000000000000000000000085 +:10ABD0000000000000000000000000000000000075 +:10ABE0000000000000000000000000000000000065 +:10ABF0000000000000000000000000000000000055 +:10AC00000000000000000000000000000000000044 +:10AC10000000000000000000000000000000000034 +:10AC20000000000000000000000000000000000024 +:10AC30000000000000000000000000000000000014 +:10AC40000000000000000000000000000000000004 +:10AC500000000000000000000000000000000000F4 +:10AC600000000000000000000000000000000000E4 +:10AC700000000000000000000000000000000000D4 +:10AC800000000000000000000000000000000000C4 +:10AC900000000000000000000000000000000000B4 +:10ACA00000000000000000000000000000000000A4 +:10ACB0000000000000000000000000000000000094 +:10ACC0000000000000000000000000000000000084 +:10ACD0000000000000000000000000000000000074 +:10ACE0000000000000000000000000000000000064 +:10ACF0000000000000000000000000000000000054 +:10AD00000000000000000000000000000000000043 +:10AD10000000000000000000000000000000000033 +:10AD20000000000000000000000000000000000023 +:10AD30000000000000000000000000000000000013 +:10AD40000000000000000000000000000000000003 +:10AD500000000000000000000000000000000000F3 +:10AD600000000000000000000000000000000000E3 +:10AD700000000000000000000000000000000000D3 +:10AD800000000000000000000000000000000000C3 +:10AD900000000000000000000000000000000000B3 +:10ADA00000000000000000000000000000000000A3 +:10ADB0000000000000000000000000000000000093 +:10ADC0000000000000000000000000000000000083 +:10ADD0000000000000000000000000000000000073 +:10ADE0000000000000000000000000000000000063 +:10ADF0000000000000000000000000000000000053 +:10AE00000000000000000000000000000000000042 +:10AE10000000000000000000000000000000000032 +:10AE20000000000000000000000000000000000022 +:10AE30000000000000000000000000000000000012 +:10AE40000000000000000000000000000000000002 +:10AE500000000000000000000000000000000000F2 +:10AE600000000000000000000000000000000000E2 +:10AE700000000000000000000000000000000000D2 +:10AE800000000000000000000000000000000000C2 +:10AE900000000000000000000000000000000000B2 +:10AEA00000000000000000000000000000000000A2 +:10AEB0000000000000000000000000000000000092 +:10AEC0000000000000000000000000000000000082 +:10AED0000000000000000000000000000000000072 +:10AEE0000000000000000000000000000000000062 +:10AEF0000000000000000000000000000000000052 +:10AF00000000000000000000000000000000000041 +:10AF10000000000000000000000000000000000031 +:10AF20000000000000000000000000000000000021 +:10AF30000000000000000000000000000000000011 +:10AF40000000000000000000000000000000000001 +:10AF500000000000000000000000000000000000F1 +:10AF600000000000000000000000000000000000E1 +:10AF700000000000000000000000000000000000D1 +:10AF800000000000000000000000000000000000C1 +:10AF900000000000000000000000000000000000B1 +:10AFA00000000000000000000000000000000000A1 +:10AFB0000000000000000000000000000000000091 +:10AFC0000000000000000000000000000000000081 +:10AFD0000000000000000000000000000000000071 +:10AFE0000000000000000000000000000000000061 +:10AFF0000000000000000000000000000000000051 +:10B000000000000000000000000000000000000040 +:10B010000000000000000000000000000000000030 +:10B020000000000000000000000000000000000020 +:10B030000000000000000000000000000000000010 +:10B040000000000000000000000000000000000000 +:10B0500000000000000000000000000000000000F0 +:10B0600000000000000000000000000000000000E0 +:10B0700000000000000000000000000000000000D0 +:10B0800000000000000000000000000000000000C0 +:10B0900000000000000000000000000000000000B0 +:10B0A00000000000000000000000000000000000A0 +:10B0B0000000000000000000000000000000000090 +:10B0C0000000000000000000000000000000000080 +:10B0D0000000000000000000000000000000000070 +:10B0E0000000000000000000000000000000000060 +:10B0F0000000000000000000000000000000000050 +:10B10000000000000000000000000000000000003F +:10B11000000000000000000000000000000000002F +:10B12000000000000000000000000000000000001F +:10B13000000000000000000000000000000000000F +:10B1400000000000000000000000000000000000FF +:10B1500000000000000000000000000000000000EF +:10B1600000000000000000000000000000000000DF +:10B1700000000000000000000000000000000000CF +:10B1800000000000000000000000000000000000BF +:10B1900000000000000000000000000000000000AF +:10B1A000000000000000000000000000000000009F +:10B1B000000000000000000000000000000000008F +:10B1C000000000000000000000000000000000007F +:10B1D000000000000000000000000000000000006F +:10B1E000000000000000000000000000000000005F +:10B1F000000000000000000000000000000000004F +:10B20000000000000000000000000000000000003E +:10B21000000000000000000000000000000000002E +:10B22000000000000000000000000000000000001E +:10B23000000000000000000000000000000000000E +:10B2400000000000000000000000000000000000FE +:10B2500000000000000000000000000000000000EE +:10B2600000000000000000000000000000000000DE +:10B2700000000000000000000000000000000000CE +:10B2800000000000000000000000000000000000BE +:10B2900000000000000000000000000000000000AE +:10B2A000000000000000000000000000000000009E +:10B2B000000000000000000000000000000000008E +:10B2C000000000000000000000000000000000007E +:10B2D000000000000000000000000000000000006E +:10B2E000000000000000000000000000000000005E +:10B2F000000000000000000000000000000000004E +:10B30000000000000000000000000000000000003D +:10B31000000000000000000000000000000000002D +:10B32000000000000000000000000000000000001D +:10B33000000000000000000000000000000000000D +:10B3400000000000000000000000000000000000FD +:10B3500000000000000000000000000000000000ED +:10B3600000000000000000000000000000000000DD +:10B3700000000000000000000000000000000000CD +:10B3800000000000000000000000000000000000BD +:10B3900000000000000000000000000000000000AD +:10B3A000000000000000000000000000000000009D +:10B3B000000000000000000000000000000000008D +:10B3C000000000000000000000000000000000007D +:10B3D000000000000000000000000000000000006D +:10B3E000000000000000000000000000000000005D +:10B3F000000000000000000000000000000000004D +:10B40000000000000000000000000000000000003C +:10B41000000000000000000000000000000000002C +:10B42000000000000000000000000000000000001C +:10B43000000000000000000000000000000000000C +:10B4400000000000000000000000000000000000FC +:10B4500000000000000000000000000000000000EC +:10B4600000000000000000000000000000000000DC +:10B4700000000000000000000000000000000000CC +:10B4800000000000000000000000000000000000BC +:10B4900000000000000000000000000000000000AC +:10B4A000000000000000000000000000000000009C +:10B4B000000000000000000000000000000000008C +:10B4C000000000000000000000000000000000007C +:10B4D000000000000000000000000000000000006C +:10B4E000000000000000000000000000000000005C +:10B4F000000000000000000000000000000000004C +:10B50000000000000000000000000000000000003B +:10B51000000000000000000000000000000000002B +:10B52000000000000000000000000000000000001B +:10B53000000000000000000000000000000000000B +:10B5400000000000000000000000000000000000FB +:10B5500000000000000000000000000000000000EB +:10B5600000000000000000000000000000000000DB +:10B5700000000000000000000000000000000000CB +:10B5800000000000000000000000000000000000BB +:10B5900000000000000000000000000000000000AB +:10B5A000000000000000000000000000000000009B +:10B5B000000000000000000000000000000000008B +:10B5C000000000000000000000000000000000007B +:10B5D000000000000000000000000000000000006B +:10B5E000000000000000000000000000000000005B +:10B5F000000000000000000000000000000000004B +:10B60000000000000000000000000000000000003A +:10B61000000000000000000000000000000000002A +:10B62000000000000000000000000000000000001A +:10B63000000000000000000000000000000000000A +:10B6400000000000000000000000000000000000FA +:10B6500000000000000000000000000000000000EA +:10B6600000000000000000000000000000000000DA +:10B6700000000000000000000000000000000000CA +:10B6800000000000000000000000000000000000BA +:10B6900000000000000000000000000000000000AA +:10B6A000000000000000000000000000000000009A +:10B6B000000000000000000000000000000000008A +:10B6C000000000000000000000000000000000007A +:10B6D000000000000000000000000000000000006A +:10B6E000000000000000000000000000000000005A +:10B6F000000000000000000000000000000000004A +:10B700000000000000000000000000000000000039 +:10B710000000000000000000000000000000000029 +:10B720000000000000000000000000000000000019 +:10B730000000000000000000000000000000000009 +:10B7400000000000000000000000000000000000F9 +:10B7500000000000000000000000000000000000E9 +:10B7600000000000000000000000000000000000D9 +:10B7700000000000000000000000000000000000C9 +:10B7800000000000000000000000000000000000B9 +:10B7900000000000000000000000000000000000A9 +:10B7A0000000000000000000000000000000000099 +:10B7B0000000000000000000000000000000000089 +:10B7C0000000000000000000000000000000000079 +:10B7D0000000000000000000000000000000000069 +:10B7E0000000000000000000000000000000000059 +:10B7F0000000000000000000000000000000000049 +:10B800000000000000000000000000000000000038 +:10B810000000000000000000000000000000000028 +:10B820000000000000000000000000000000000018 +:10B830000000000000000000000000000000000008 +:10B8400000000000000000000000000000000000F8 +:10B8500000000000000000000000000000000000E8 +:10B8600000000000000000000000000000000000D8 +:10B8700000000000000000000000000000000000C8 +:10B8800000000000000000000000000000000000B8 +:10B8900000000000000000000000000000000000A8 +:10B8A0000000000000000000000000000000000098 +:10B8B0000000000000000000000000000000000088 +:10B8C0000000000000000000000000000000000078 +:10B8D0000000000000000000000000000000000068 +:10B8E0000000000000000000000000000000000058 +:10B8F0000000000000000000000000000000000048 +:10B900000000000000000000000000000000000037 +:10B910000000000000000000000000000000000027 +:10B920000000000000000000000000000000000017 +:10B930000000000000000000000000000000000007 +:10B9400000000000000000000000000000000000F7 +:10B9500000000000000000000000000000000000E7 +:10B9600000000000000000000000000000000000D7 +:10B9700000000000000000000000000000000000C7 +:10B9800000000000000000000000000000000000B7 +:10B9900000000000000000000000000000000000A7 +:10B9A0000000000000000000000000000000000097 +:10B9B0000000000000000000000000000000000087 +:10B9C0000000000000000000000000000000000077 +:10B9D0000000000000000000000000000000000067 +:10B9E0000000000000000000000000000000000057 +:10B9F0000000000000000000000000000000000047 +:10BA00000000000000000000000000000000000036 +:10BA10000000000000000000000000000000000026 +:10BA20000000000000000000000000000000000016 +:10BA30000000000000000000000000000000000006 +:10BA400000000000000000000000000000000000F6 +:10BA500000000000000000000000000000000000E6 +:10BA600000000000000000000000000000000000D6 +:10BA700000000000000000000000000000000000C6 +:10BA800000000000000000000000000000000000B6 +:10BA900000000000000000000000000000000000A6 +:10BAA0000000000000000000000000000000000096 +:10BAB0000000000000000000000000000000000086 +:10BAC0000000000000000000000000000000000076 +:10BAD0000000000000000000000000000000000066 +:10BAE0000000000000000000000000000000000056 +:10BAF0000000000000000000000000000000000046 +:10BB00000000000000000000000000000000000035 +:10BB10000000000000000000000000000000000025 +:10BB20000000000000000000000000000000000015 +:10BB30000000000000000000000000000000000005 +:10BB400000000000000000000000000000000000F5 +:10BB500000000000000000000000000000000000E5 +:10BB600000000000000000000000000000000000D5 +:10BB700000000000000000000000000000000000C5 +:10BB800000000000000000000000000000000000B5 +:10BB900000000000000000000000000000000000A5 +:10BBA0000000000000000000000000000000000095 +:10BBB0000000000000000000000000000000000085 +:10BBC0000000000000000000000000000000000075 +:10BBD0000000000000000000000000000000000065 +:10BBE0000000000000000000000000000000000055 +:10BBF0000000000000000000000000000000000045 +:10BC00000000000000000000000000000000000034 +:10BC10000000000000000000000000000000000024 +:10BC20000000000000000000000000000000000014 +:10BC30000000000000000000000000000000000004 +:10BC400000000000000000000000000000000000F4 +:10BC500000000000000000000000000000000000E4 +:10BC600000000000000000000000000000000000D4 +:10BC700000000000000000000000000000000000C4 +:10BC800000000000000000000000000000000000B4 +:10BC900000000000000000000000000000000000A4 +:10BCA0000000000000000000000000000000000094 +:10BCB0000000000000000000000000000000000084 +:10BCC0000000000000000000000000000000000074 +:10BCD0000000000000000000000000000000000064 +:10BCE0000000000000000000000000000000000054 +:10BCF0000000000000000000000000000000000044 +:10BD00000000000000000000000000000000000033 +:10BD10000000000000000000000000000000000023 +:10BD20000000000000000000000000000000000013 +:10BD30000000000000000000000000000000000003 +:10BD400000000000000000000000000000000000F3 +:10BD500000000000000000000000000000000000E3 +:10BD600000000000000000000000000000000000D3 +:10BD700000000000000000000000000000000000C3 +:10BD800000000000000000000000000000000000B3 +:10BD900000000000000000000000000000000000A3 +:10BDA0000000000000000000000000000000000093 +:10BDB0000000000000000000000000000000000083 +:10BDC0000000000000000000000000000000000073 +:10BDD0000000000000000000000000000000000063 +:10BDE0000000000000000000000000000000000053 +:10BDF0000000000000000000000000000000000043 +:10BE00000000000000000000000000000000000032 +:10BE10000000000000000000000000000000000022 +:10BE20000000000000000000000000000000000012 +:10BE30000000000000000000000000000000000002 +:10BE400000000000000000000000000000000000F2 +:10BE500000000000000000000000000000000000E2 +:10BE600000000000000000000000000000000000D2 +:10BE700000000000000000000000000000000000C2 +:10BE800000000000000000000000000000000000B2 +:10BE900000000000000000000000000000000000A2 +:10BEA0000000000000000000000000000000000092 +:10BEB0000000000000000000000000000000000082 +:10BEC0000000000000000000000000000000000072 +:10BED0000000000000000000000000000000000062 +:10BEE0000000000000000000000000000000000052 +:10BEF0000000000000000000000000000000000042 +:10BF00000000000000000000000000000000000031 +:10BF10000000000000000000000000000000000021 +:10BF20000000000000000000000000000000000011 +:10BF30000000000000000000000000000000000001 +:10BF400000000000000000000000000000000000F1 +:10BF500000000000000000000000000000000000E1 +:10BF600000000000000000000000000000000000D1 +:10BF700000000000000000000000000000000000C1 +:10BF800000000000000000000000000000000000B1 +:10BF900000000000000000000000000000000000A1 +:10BFA0000000000000000000000000000000000091 +:10BFB0000000000000000000000000000000000081 +:10BFC0000000000000000000000000000000000071 +:10BFD0000000000000000000000000000000000061 +:10BFE0000000000000000000000000000000000051 +:10BFF0000000000000000000000000000000000041 +:10C000000000000000000000000000000000000030 +:10C010000000000000000000000000000000000020 +:10C020000000000000000000000000000000000010 +:10C030000000000000000000000000000000000000 +:10C0400000000000000000000000000000000000F0 +:10C0500000000000000000000000000000000000E0 +:10C0600000000000000000000000000000000000D0 +:10C0700000000000000000000000000000000000C0 +:10C0800000000000000000000000000000000000B0 +:10C0900000000000000000000000000000000000A0 +:10C0A0000000000000000000000000000000000090 +:10C0B0000000000000000000000000000000000080 +:10C0C0000000000000000000000000000000000070 +:10C0D0000000000000000000000000000000000060 +:10C0E0000000000000000000000000000000000050 +:10C0F0000000000000000000000000000000000040 +:10C10000000000000000000000000000000000002F +:10C11000000000000000000000000000000000001F +:10C12000000000000000000000000000000000000F +:10C1300000000000000000000000000000000000FF +:10C1400000000000000000000000000000000000EF +:10C1500000000000000000000000000000000000DF +:10C1600000000000000000000000000000000000CF +:10C1700000000000000000000000000000000000BF +:10C1800000000000000000000000000000000000AF +:10C19000000000000000000000000000000000009F +:10C1A000000000000000000000000000000000008F +:10C1B000000000000000000000000000000000007F +:10C1C000000000000000000000000000000000006F +:10C1D000000000000000000000000000000000005F +:10C1E000000000000000000000000000000000004F +:10C1F000000000000000000000000000000000003F +:10C20000000000000000000000000000000000002E +:10C21000000000000000000000000000000000001E +:10C22000000000000000000000000000000000000E +:10C2300000000000000000000000000000000000FE +:10C2400000000000000000000000000000000000EE +:10C2500000000000000000000000000000000000DE +:10C2600000000000000000000000000000000000CE +:10C2700000000000000000000000000000000000BE +:10C2800000000000000000000000000000000000AE +:10C29000000000000000000000000000000000009E +:10C2A000000000000000000000000000000000008E +:10C2B000000000000000000000000000000000007E +:10C2C000000000000000000000000000000000006E +:10C2D000000000000000000000000000000000005E +:10C2E000000000000000000000000000000000004E +:10C2F000000000000000000000000000000000003E +:10C30000000000000000000000000000000000002D +:10C31000000000000000000000000000000000001D +:10C32000000000000000000000000000000000000D +:10C3300000000000000000000000000000000000FD +:10C3400000000000000000000000000000000000ED +:10C3500000000000000000000000000000000000DD +:10C3600000000000000000000000000000000000CD +:10C3700000000000000000000000000000000000BD +:10C3800000000000000000000000000000000000AD +:10C39000000000000000000000000000000000009D +:10C3A000000000000000000000000000000000008D +:10C3B000000000000000000000000000000000007D +:10C3C000000000000000000000000000000000006D +:10C3D000000000000000000000000000000000005D +:10C3E000000000000000000000000000000000004D +:10C3F000000000000000000000000000000000003D +:10C40000000000000000000000000000000000002C +:10C41000000000000000000000000000000000001C +:10C42000000000000000000000000000000000000C +:10C4300000000000000000000000000000000000FC +:10C4400000000000000000000000000000000000EC +:10C4500000000000000000000000000000000000DC +:10C4600000000000000000000000000000000000CC +:10C4700000000000000000000000000000000000BC +:10C4800000000000000000000000000000000000AC +:10C49000000000000000000000000000000000009C +:10C4A000000000000000000000000000000000008C +:10C4B000000000000000000000000000000000007C +:10C4C000000000000000000000000000000000006C +:10C4D000000000000000000000000000000000005C +:10C4E000000000000000000000000000000000004C +:10C4F000000000000000000000000000000000003C +:10C50000000000000000000000000000000000002B +:10C51000000000000000000000000000000000001B +:10C52000000000000000000000000000000000000B +:10C5300000000000000000000000000000000000FB +:10C5400000000000000000000000000000000000EB +:10C5500000000000000000000000000000000000DB +:10C5600000000000000000000000000000000000CB +:10C5700000000000000000000000000000000000BB +:10C5800000000000000000000000000000000000AB +:10C59000000000000000000000000000000000009B +:10C5A000000000000000000000000000000000008B +:10C5B000000000000000000000000000000000007B +:10C5C000000000000000000000000000000000006B +:10C5D000000000000000000000000000000000005B +:10C5E000000000000000000000000000000000004B +:10C5F000000000000000000000000000000000003B +:10C60000000000000000000000000000000000002A +:10C61000000000000000000000000000000000001A +:10C62000000000000000000000000000000000000A +:10C6300000000000000000000000000000000000FA +:10C6400000000000000000000000000000000000EA +:10C6500000000000000000000000000000000000DA +:10C6600000000000000000000000000000000000CA +:10C6700000000000000000000000000000000000BA +:10C6800000000000000000000000000000000000AA +:10C69000000000000000000000000000000000009A +:10C6A000000000000000000000000000000000008A +:10C6B000000000000000000000000000000000007A +:10C6C000000000000000000000000000000000006A +:10C6D000000000000000000000000000000000005A +:10C6E000000000000000000000000000000000004A +:10C6F000000000000000000000000000000000003A +:10C700000000000000000000000000000000000029 +:10C710000000000000000000000000000000000019 +:10C720000000000000000000000000000000000009 +:10C7300000000000000000000000000000000000F9 +:10C7400000000000000000000000000000000000E9 +:10C7500000000000000000000000000000000000D9 +:10C7600000000000000000000000000000000000C9 +:10C7700000000000000000000000000000000000B9 +:10C7800000000000000000000000000000000000A9 +:10C790000000000000000000000000000000000099 +:10C7A0000000000000000000000000000000000089 +:10C7B0000000000000000000000000000000000079 +:10C7C0000000000000000000000000000000000069 +:10C7D0000000000000000000000000000000000059 +:10C7E0000000000000000000000000000000000049 +:10C7F0000000000000000000000000000000000039 +:10C800000000000000000000000000000000000028 +:10C810000000000000000000000000000000000018 +:10C820000000000000000000000000000000000008 +:10C8300000000000000000000000000000000000F8 +:10C8400000000000000000000000000000000000E8 +:10C8500000000000000000000000000000000000D8 +:10C8600000000000000000000000000000000000C8 +:10C8700000000000000000000000000000000000B8 +:10C8800000000000000000000000000000000000A8 +:10C890000000000000000000000000000000000098 +:10C8A0000000000000000000000000000000000088 +:10C8B0000000000000000000000000000000000078 +:10C8C0000000000000000000000000000000000068 +:10C8D0000000000000000000000000000000000058 +:10C8E0000000000000000000000000000000000048 +:10C8F0000000000000000000000000000000000038 +:10C900000000000000000000000000000000000027 +:10C910000000000000000000000000000000000017 +:10C920000000000000000000000000000000000007 +:10C9300000000000000000000000000000000000F7 +:10C9400000000000000000000000000000000000E7 +:10C9500000000000000000000000000000000000D7 +:10C9600000000000000000000000000000000000C7 +:10C9700000000000000000000000000000000000B7 +:10C9800000000000000000000000000000000000A7 +:10C990000000000000000000000000000000000097 +:10C9A0000000000000000000000000000000000087 +:10C9B0000000000000000000000000000000000077 +:10C9C0000000000000000000000000000000000067 +:10C9D0000000000000000000000000000000000057 +:10C9E0000000000000000000000000000000000047 +:10C9F0000000000000000000000000000000000037 +:10CA00000000000000000000000000000000000026 +:10CA10000000000000000000000000000000000016 +:10CA20000000000000000000000000000000000006 +:10CA300000000000000000000000000000000000F6 +:10CA400000000000000000000000000000000000E6 +:10CA500000000000000000000000000000000000D6 +:10CA600000000000000000000000000000000000C6 +:10CA700000000000000000000000000000000000B6 +:10CA800000000000000000000000000000000000A6 +:10CA90000000000000000000000000000000000096 +:10CAA0000000000000000000000000000000000086 +:10CAB0000000000000000000000000000000000076 +:10CAC0000000000000000000000000000000000066 +:10CAD0000000000000000000000000000000000056 +:10CAE0000000000000000000000000000000000046 +:10CAF0000000000000000000000000000000000036 +:10CB00000000000000000000000000000000000025 +:10CB10000000000000000000000000000000000015 +:10CB20000000000000000000000000000000000005 +:10CB300000000000000000000000000000000000F5 +:10CB400000000000000000000000000000000000E5 +:10CB500000000000000000000000000000000000D5 +:10CB600000000000000000000000000000000000C5 +:10CB700000000000000000000000000000000000B5 +:10CB800000000000000000000000000000000000A5 +:10CB90000000000000000000000000000000000095 +:10CBA0000000000000000000000000000000000085 +:10CBB0000000000000000000000000000000000075 +:10CBC0000000000000000000000000000000000065 +:10CBD0000000000000000000000000000000000055 +:10CBE0000000000000000000000000000000000045 +:10CBF0000000000000000000000000000000000035 +:10CC00000000000000000000000000000000000024 +:10CC10000000000000000000000000000000000014 +:10CC20000000000000000000000000000000000004 +:10CC300000000000000000000000000000000000F4 +:10CC400000000000000000000000000000000000E4 +:10CC500000000000000000000000000000000000D4 +:10CC600000000000000000000000000000000000C4 +:10CC700000000000000000000000000000000000B4 +:10CC800000000000000000000000000000000000A4 +:10CC90000000000000000000000000000000000094 +:10CCA0000000000000000000000000000000000084 +:10CCB0000000000000000000000000000000000074 +:10CCC0000000000000000000000000000000000064 +:10CCD0000000000000000000000000000000000054 +:10CCE0000000000000000000000000000000000044 +:10CCF0000000000000000000000000000000000034 +:10CD00000000000000000000000000000000000023 +:10CD10000000000000000000000000000000000013 +:10CD20000000000000000000000000000000000003 +:10CD300000000000000000000000000000000000F3 +:10CD400000000000000000000000000000000000E3 +:10CD500000000000000000000000000000000000D3 +:10CD600000000000000000000000000000000000C3 +:10CD700000000000000000000000000000000000B3 +:10CD800000000000000000000000000000000000A3 +:10CD90000000000000000000000000000000000093 +:10CDA0000000000000000000000000000000000083 +:10CDB0000000000000000000000000000000000073 +:10CDC0000000000000000000000000000000000063 +:10CDD0000000000000000000000000000000000053 +:10CDE0000000000000000000000000000000000043 +:10CDF0000000000000000000000000000000000033 +:10CE00000000000000000000000000000000000022 +:10CE10000000000000000000000000000000000012 +:10CE20000000000000000000000000000000000002 +:10CE300000000000000000000000000000000000F2 +:10CE400000000000000000000000000000000000E2 +:10CE500000000000000000000000000000000000D2 +:10CE600000000000000000000000000000000000C2 +:10CE700000000000000000000000000000000000B2 +:10CE800000000000000000000000000000000000A2 +:10CE90000000000000000000000000000000000092 +:10CEA0000000000000000000000000000000000082 +:10CEB0000000000000000000000000000000000072 +:10CEC0000000000000000000000000000000000062 +:10CED0000000000000000000000000000000000052 +:10CEE0000000000000000000000000000000000042 +:10CEF0000000000000000000000000000000000032 +:10CF00000000000000000000000000000000000021 +:10CF10000000000000000000000000000000000011 +:10CF20000000000000000000000000000000000001 +:10CF300000000000000000000000000000000000F1 +:10CF400000000000000000000000000000000000E1 +:10CF500000000000000000000000000000000000D1 +:10CF600000000000000000000000000000000000C1 +:10CF700000000000000000000000000000000000B1 +:10CF800000000000000000000000000000000000A1 +:10CF90000000000000000000000000000000000091 +:10CFA0000000000000000000000000000000000081 +:10CFB0000000000000000000000000000000000071 +:10CFC0000000000000000000000000000000000061 +:10CFD0000000000000000000000000000000000051 +:10CFE0000000000000000000000000000000000041 +:10CFF0000000000000000000000000000000000031 +:10D000000000000000000000000000000000000020 +:10D010000000000000000000000000000000000010 +:10D020000000000000000000000000000000000000 +:10D0300000000000000000000000000000000000F0 +:10D0400000000000000000000000000000000000E0 +:10D0500000000000000000000000000000000000D0 +:10D0600000000000000000000000000000000000C0 +:10D0700000000000000000000000000000000000B0 +:10D0800000000000000000000000000000000000A0 +:10D090000000000000000000000000000000000090 +:10D0A0000000000000000000000000000000000080 +:10D0B0000000000000000000000000000000000070 +:10D0C0000000000000000000000000000000000060 +:10D0D0000000000000000000000000000000000050 +:10D0E0000000000000000000000000000000000040 +:10D0F0000000000000000000000000000000000030 +:10D10000000000000000000000000000000000001F +:10D11000000000000000000000000000000000000F +:10D1200000000000000000000000000000000000FF +:10D1300000000000000000000000000000000000EF +:10D1400000000000000000000000000000000000DF +:10D1500000000000000000000000000000000000CF +:10D1600000000000000000000000000000000000BF +:10D1700000000000000000000000000000000000AF +:10D18000000000000000000000000000000000009F +:10D19000000000000000000000000000000000008F +:10D1A000000000000000000000000000000000007F +:10D1B000000000000000000000000000000000006F +:10D1C000000000000000000000000000000000005F +:10D1D000000000000000000000000000000000004F +:10D1E000000000000000000000000000000000003F +:10D1F000000000000000000000000000000000002F +:10D20000000000000000000000000000000000001E +:10D21000000000000000000000000000000000000E +:10D2200000000000000000000000000000000000FE +:10D2300000000000000000000000000000000000EE +:10D2400000000000000000000000000000000000DE +:10D2500000000000000000000000000000000000CE +:10D2600000000000000000000000000000000000BE +:10D2700000000000000000000000000000000000AE +:10D28000000000000000000000000000000000009E +:10D29000000000000000000000000000000000008E +:10D2A000000000000000000000000000000000007E +:10D2B000000000000000000000000000000000006E +:10D2C000000000000000000000000000000000005E +:10D2D000000000000000000000000000000000004E +:10D2E000000000000000000000000000000000003E +:10D2F000000000000000000000000000000000002E +:10D30000000000000000000000000000000000001D +:10D31000000000000000000000000000000000000D +:10D3200000000000000000000000000000000000FD +:10D3300000000000000000000000000000000000ED +:10D3400000000000000000000000000000000000DD +:10D3500000000000000000000000000000000000CD +:10D3600000000000000000000000000000000000BD +:10D3700000000000000000000000000000000000AD +:10D38000000000000000000000000000000000009D +:10D39000000000000000000000000000000000008D +:10D3A000000000000000000000000000000000007D +:10D3B000000000000000000000000000000000006D +:10D3C000000000000000000000000000000000005D +:10D3D000000000000000000000000000000000004D +:10D3E000000000000000000000000000000000003D +:10D3F000000000000000000000000000000000002D +:10D40000000000000000000000000000000000001C +:10D41000000000000000000000000000000000000C +:10D4200000000000000000000000000000000000FC +:10D4300000000000000000000000000000000000EC +:10D4400000000000000000000000000000000000DC +:10D4500000000000000000000000000000000000CC +:10D4600000000000000000000000000000000000BC +:10D4700000000000000000000000000000000000AC +:10D48000000000000000000000000000000000009C +:10D49000000000000000000000000000000000008C +:10D4A000000000000000000000000000000000007C +:10D4B000000000000000000000000000000000006C +:10D4C000000000000000000000000000000000005C +:10D4D000000000000000000000000000000000004C +:10D4E000000000000000000000000000000000003C +:10D4F000000000000000000000000000000000002C +:10D50000000000000000000000000000000000001B +:10D51000000000000000000000000000000000000B +:10D5200000000000000000000000000000000000FB +:10D5300000000000000000000000000000000000EB +:10D5400000000000000000000000000000000000DB +:10D5500000000000000000000000000000000000CB +:10D5600000000000000000000000000000000000BB +:10D5700000000000000000000000000000000000AB +:10D58000000000000000000000000000000000009B +:10D59000000000000000000000000000000000008B +:10D5A000000000000000000000000000000000007B +:10D5B000000000000000000000000000000000006B +:10D5C000000000000000000000000000000000005B +:10D5D000000000000000000000000000000000004B +:10D5E000000000000000000000000000000000003B +:10D5F000000000000000000000000000000000002B +:10D60000000000000000000000000000000000001A +:10D61000000000000000000000000000000000000A +:10D6200000000000000000000000000000000000FA +:10D6300000000000000000000000000000000000EA +:10D6400000000000000000000000000000000000DA +:10D6500000000000000000000000000000000000CA +:10D6600000000000000000000000000000000000BA +:10D6700000000000000000000000000000000000AA +:10D68000000000000000000000000000000000009A +:10D69000000000000000000000000000000000008A +:10D6A000000000000000000000000000000000007A +:10D6B000000000000000000000000000000000006A +:10D6C000000000000000000000000000000000005A +:10D6D000000000000000000000000000000000004A +:10D6E000000000000000000000000000000000003A +:10D6F000000000000000000000000000000000002A +:10D700000000000000000000000000000000000019 +:10D710000000000000000000000000000000000009 +:10D7200000000000000000000000000000000000F9 +:10D7300000000000000000000000000000000000E9 +:10D7400000000000000000000000000000000000D9 +:10D7500000000000000000000000000000000000C9 +:10D7600000000000000000000000000000000000B9 +:10D7700000000000000000000000000000000000A9 +:10D780000000000000000000000000000000000099 +:10D790000000000000000000000000000000000089 +:10D7A0000000000000000000000000000000000079 +:10D7B0000000000000000000000000000000000069 +:10D7C0000000000000000000000000000000000059 +:10D7D0000000000000000000000000000000000049 +:10D7E0000000000000000000000000000000000039 +:10D7F0000000000000000000000000000000000029 +:10D800000000000000000000000000000000000018 +:10D810000000000000000000000000000000000008 +:10D8200000000000000000000000000000000000F8 +:10D8300000000000000000000000000000000000E8 +:10D8400000000000000000000000000000000000D8 +:10D8500000000000000000000000000000000000C8 +:10D8600000000000000000000000000000000000B8 +:10D8700000000000000000000000000000000000A8 +:10D880000000000000000000000000000000000098 +:10D890000000000000000000000000000000000088 +:10D8A0000000000000000000000000000000000078 +:10D8B0000000000000000000000000000000000068 +:10D8C0000000000000000000000000000000000058 +:10D8D0000000000000000000000000000000000048 +:10D8E0000000000000000000000000000000000038 +:10D8F0000000000000000000000000000000000028 +:10D900000000000000000000000000000000000017 +:10D910000000000000000000000000000000000007 +:10D9200000000000000000000000000000000000F7 +:10D9300000000000000000000000000000000000E7 +:10D9400000000000000000000000000000000000D7 +:10D9500000000000000000000000000000000000C7 +:10D9600000000000000000000000000000000000B7 +:10D9700000000000000000000000000000000000A7 +:10D980000000000000000000000000000000000097 +:10D990000000000000000000000000000000000087 +:10D9A0000000000000000000000000000000000077 +:10D9B0000000000000000000000000000000000067 +:10D9C0000000000000000000000000000000000057 +:10D9D0000000000000000000000000000000000047 +:10D9E0000000000000000000000000000000000037 +:10D9F0000000000000000000000000000000000027 +:10DA00000000000000000000000000000000000016 +:10DA10000000000000000000000000000000000006 +:10DA200000000000000000000000000000000000F6 +:10DA300000000000000000000000000000000000E6 +:10DA400000000000000000000000000000000000D6 +:10DA500000000000000000000000000000000000C6 +:10DA600000000000000000000000000000000000B6 +:10DA700000000000000000000000000000000000A6 +:10DA80000000000000000000000000000000000096 +:10DA90000000000000000000000000000000000086 +:10DAA0000000000000000000000000000000000076 +:10DAB0000000000000000000000000000000000066 +:10DAC0000000000000000000000000000000000056 +:10DAD0000000000000000000000000000000000046 +:10DAE0000000000000000000000000000000000036 +:10DAF0000000000000000000000000000000000026 +:10DB00000000000000000000000000000000000015 +:10DB10000000000000000000000000000000000005 +:10DB200000000000000000000000000000000000F5 +:10DB300000000000000000000000000000000000E5 +:10DB400000000000000000000000000000000000D5 +:10DB500000000000000000000000000000000000C5 +:10DB600000000000000000000000000000000000B5 +:10DB700000000000000000000000000000000000A5 +:10DB80000000000000000000000000000000000095 +:10DB90000000000000000000000000000000000085 +:10DBA0000000000000000000000000000000000075 +:10DBB0000000000000000000000000000000000065 +:10DBC0000000000000000000000000000000000055 +:10DBD0000000000000000000000000000000000045 +:10DBE0000000000000000000000000000000000035 +:10DBF0000000000000000000000000000000000025 +:10DC00000000000000000000000000000000000014 +:10DC10000000000000000000000000000000000004 +:10DC200000000000000000000000000000000000F4 +:10DC300000000000000000000000000000000000E4 +:10DC400000000000000000000000000000000000D4 +:10DC500000000000000000000000000000000000C4 +:10DC600000000000000000000000000000000000B4 +:10DC700000000000000000000000000000000000A4 +:10DC80000000000000000000000000000000000094 +:10DC90000000000000000000000000000000000084 +:10DCA0000000000000000000000000000000000074 +:10DCB0000000000000000000000000000000000064 +:10DCC0000000000000000000000000000000000054 +:10DCD0000000000000000000000000000000000044 +:10DCE0000000000000000000000000000000000034 +:10DCF0000000000000000000000000000000000024 +:10DD00000000000000000000000000000000000013 +:10DD10000000000000000000000000000000000003 +:10DD200000000000000000000000000000000000F3 +:10DD300000000000000000000000000000000000E3 +:10DD400000000000000000000000000000000000D3 +:10DD500000000000000000000000000000000000C3 +:10DD600000000000000000000000000000000000B3 +:10DD700000000000000000000000000000000000A3 +:10DD80000000000000000000000000000000000093 +:10DD90000000000000000000000000000000000083 +:10DDA0000000000000000000000000000000000073 +:10DDB0000000000000000000000000000000000063 +:10DDC0000000000000000000000000000000000053 +:10DDD0000000000000000000000000000000000043 +:10DDE0000000000000000000000000000000000033 +:10DDF0000000000000000000000000000000000023 +:10DE00000000000000000000000000000000000012 +:10DE10000000000000000000000000000000000002 +:10DE200000000000000000000000000000000000F2 +:10DE300000000000000000000000000000000000E2 +:10DE400000000000000000000000000000000000D2 +:10DE500000000000000000000000000000000000C2 +:10DE600000000000000000000000000000000000B2 +:10DE700000000000000000000000000000000000A2 +:10DE80000000000000000000000000000000000092 +:10DE90000000000000000000000000000000000082 +:10DEA0000000000000000000000000000000000072 +:10DEB0000000000000000000000000000000000062 +:10DEC0000000000000000000000000000000000052 +:10DED0000000000000000000000000000000000042 +:10DEE0000000000000000000000000000000000032 +:10DEF0000000000000000000000000000000000022 +:10DF00000000000000000000000000000000000011 +:10DF10000000000000000000000000000000000001 +:10DF200000000000000000000000000000000000F1 +:10DF300000000000000000000000000000000000E1 +:10DF400000000000000000000000000000000000D1 +:10DF500000000000000000000000000000000000C1 +:10DF600000000000000000000000000000000000B1 +:10DF700000000000000000000000000000000000A1 +:10DF80000000000000000000000000000000000091 +:10DF90000000000000000000000000000000000081 +:10DFA0000000000000000000000000000000000071 +:10DFB0000000000000000000000000000000000061 +:10DFC0000000000000000000000000000000000051 +:10DFD0000000000000000000000000000000000041 +:10DFE0000000000000000000000000000000000031 +:10DFF0000000000000000000000000000000000021 +:10E000000000000000000000000000000000000010 +:10E010000000000000000000000000000000000000 +:10E0200000000000000000000000000000000000F0 +:10E0300000000000000000000000000000000000E0 +:10E0400000000000000000000000000000000000D0 +:10E0500000000000000000000000000000000000C0 +:10E0600000000000000000000000000000000000B0 +:10E0700000000000000000000000000000000000A0 +:10E080000000000000000000000000000000000090 +:10E090000000000000000000000000000000000080 +:10E0A0000000000000000000000000000000000070 +:10E0B0000000000000000000000000000000000060 +:10E0C0000000000000000000000000000000000050 +:10E0D0000000000000000000000000000000000040 +:10E0E0000000000000000000000000000000000030 +:10E0F0000000000000000000000000000000000020 +:10E10000000000000000000000000000000000000F +:10E1100000000000000000000000000000000000FF +:10E1200000000000000000000000000000000000EF +:10E1300000000000000000000000000000000000DF +:10E1400000000000000000000000000000000000CF +:10E1500000000000000000000000000000000000BF +:10E1600000000000000000000000000000000000AF +:10E17000000000000000000000000000000000009F +:10E18000000000000000000000000000000000008F +:10E19000000000000000000000000000000000007F +:10E1A000000000000000000000000000000000006F +:10E1B000000000000000000000000000000000005F +:10E1C000000000000000000000000000000000004F +:10E1D000000000000000000000000000000000003F +:10E1E000000000000000000000000000000000002F +:10E1F000000000000000000000000000000000001F +:10E20000000000000000000000000000000000000E +:10E2100000000000000000000000000000000000FE +:10E2200000000000000000000000000000000000EE +:10E2300000000000000000000000000000000000DE +:10E2400000000000000000000000000000000000CE +:10E2500000000000000000000000000000000000BE +:10E2600000000000000000000000000000000000AE +:10E27000000000000000000000000000000000009E +:10E28000000000000000000000000000000000008E +:10E29000000000000000000000000000000000007E +:10E2A000000000000000000000000000000000006E +:10E2B000000000000000000000000000000000005E +:10E2C000000000000000000000000000000000004E +:10E2D000000000000000000000000000000000003E +:10E2E000000000000000000000000000000000002E +:10E2F000000000000000000000000000000000001E +:10E30000000000000000000000000000000000000D +:10E3100000000000000000000000000000000000FD +:10E3200000000000000000000000000000000000ED +:10E3300000000000000000000000000000000000DD +:10E3400000000000000000000000000000000000CD +:10E3500000000000000000000000000000000000BD +:10E3600000000000000000000000000000000000AD +:10E37000000000000000000000000000000000009D +:10E38000000000000000000000000000000000008D +:10E39000000000000000000000000000000000007D +:10E3A000000000000000000000000000000000006D +:10E3B000000000000000000000000000000000005D +:10E3C000000000000000000000000000000000004D +:10E3D000000000000000000000000000000000003D +:10E3E000000000000000000000000000000000002D +:10E3F000000000000000000000000000000000001D +:10E40000000000000000000000000000000000000C +:10E4100000000000000000000000000000000000FC +:10E4200000000000000000000000000000000000EC +:10E4300000000000000000000000000000000000DC +:10E4400000000000000000000000000000000000CC +:10E4500000000000000000000000000000000000BC +:10E4600000000000000000000000000000000000AC +:10E47000000000000000000000000000000000009C +:10E48000000000000000000000000000000000008C +:10E49000000000000000000000000000000000007C +:10E4A000000000000000000000000000000000006C +:10E4B000000000000000000000000000000000005C +:10E4C000000000000000000000000000000000004C +:10E4D000000000000000000000000000000000003C +:10E4E000000000000000000000000000000000002C +:10E4F000000000000000000000000000000000001C +:10E50000000000000000000000000000000000000B +:10E5100000000000000000000000000000000000FB +:10E5200000000000000000000000000000000000EB +:10E5300000000000000000000000000000000000DB +:10E5400000000000000000000000000000000000CB +:10E5500000000000000000000000000000000000BB +:10E5600000000000000000000000000000000000AB +:10E57000000000000000000000000000000000009B +:10E58000000000000000000000000000000000008B +:10E59000000000000000000000000000000000007B +:10E5A000000000000000000000000000000000006B +:10E5B000000000000000000000000000000000005B +:10E5C000000000000000000000000000000000004B +:10E5D000000000000000000000000000000000003B +:10E5E000000000000000000000000000000000002B +:10E5F000000000000000000000000000000000001B +:10E60000000000000000000000000000000000000A +:10E6100000000000000000000000000000000000FA +:10E6200000000000000000000000000000000000EA +:10E6300000000000000000000000000000000000DA +:10E6400000000000000000000000000000000000CA +:10E6500000000000000000000000000000000000BA +:10E6600000000000000000000000000000000000AA +:10E67000000000000000000000000000000000009A +:10E68000000000000000000000000000000000008A +:10E69000000000000000000000000000000000007A +:10E6A000000000000000000000000000000000006A +:10E6B000000000000000000000000000000000005A +:10E6C000000000000000000000000000000000004A +:10E6D000000000000000000000000000000000003A +:10E6E000000000000000000000000000000000002A +:10E6F000000000000000000000000000000000001A +:10E700000000000000000000000000000000000009 +:10E7100000000000000000000000000000000000F9 +:10E7200000000000000000000000000000000000E9 +:10E7300000000000000000000000000000000000D9 +:10E7400000000000000000000000000000000000C9 +:10E7500000000000000000000000000000000000B9 +:10E7600000000000000000000000000000000000A9 +:10E770000000000000000000000000000000000099 +:10E780000000000000000000000000000000000089 +:10E790000000000000000000000000000000000079 +:10E7A0000000000000000000000000000000000069 +:10E7B0000000000000000000000000000000000059 +:10E7C0000000000000000000000000000000000049 +:10E7D0000000000000000000000000000000000039 +:10E7E0000000000000000000000000000000000029 +:10E7F0000000000000000000000000000000000019 +:10E800000000000000000000000000000000000008 +:10E8100000000000000000000000000000000000F8 +:10E8200000000000000000000000000000000000E8 +:10E8300000000000000000000000000000000000D8 +:10E8400000000000000000000000000000000000C8 +:10E8500000000000000000000000000000000000B8 +:10E8600000000000000000000000000000000000A8 +:10E870000000000000000000000000000000000098 +:10E880000000000000000000000000000000000088 +:10E890000000000000000000000000000000000078 +:10E8A0000000000000000000000000000000000068 +:10E8B0000000000000000000000000000000000058 +:10E8C0000000000000000000000000000000000048 +:10E8D0000000000000000000000000000000000038 +:10E8E0000000000000000000000000000000000028 +:10E8F0000000000000000000000000000000000018 +:10E900000000000000000000000000000000000007 +:10E9100000000000000000000000000000000000F7 +:10E9200000000000000000000000000000000000E7 +:10E9300000000000000000000000000000000000D7 +:10E9400000000000000000000000000000000000C7 +:10E9500000000000000000000000000000000000B7 +:10E9600000000000000000000000000000000000A7 +:10E970000000000000000000000000000000000097 +:10E980000000000000000000000000000000000087 +:10E990000000000000000000000000000000000077 +:10E9A0000000000000000000000000000000000067 +:10E9B0000000000000000000000000000000000057 +:10E9C0000000000000000000000000000000000047 +:10E9D0000000000000000000000000000000000037 +:10E9E0000000000000000000000000000000000027 +:10E9F0000000000000000000000000000000000017 +:10EA00000000000000000000000000000000000006 +:10EA100000000000000000000000000000000000F6 +:10EA200000000000000000000000000000000000E6 +:10EA300000000000000000000000000000000000D6 +:10EA400000000000000000000000000000000000C6 +:10EA500000000000000000000000000000000000B6 +:10EA600000000000000000000000000000000000A6 +:10EA70000000000000000000000000000000000096 +:10EA80000000000000000000000000000000000086 +:10EA90000000000000000000000000000000000076 +:10EAA0000000000000000000000000000000000066 +:10EAB0000000000000000000000000000000000056 +:10EAC0000000000000000000000000000000000046 +:10EAD0000000000000000000000000000000000036 +:10EAE0000000000000000000000000000000000026 +:10EAF0000000000000000000000000000000000016 +:10EB00000000000000000000000000000000000005 +:10EB100000000000000000000000000000000000F5 +:10EB200000000000000000000000000000000000E5 +:10EB300000000000000000000000000000000000D5 +:10EB400000000000000000000000000000000000C5 +:10EB500000000000000000000000000000000000B5 +:10EB600000000000000000000000000000000000A5 +:10EB70000000000000000000000000000000000095 +:10EB80000000000000000000000000000000000085 +:10EB90000000000000000000000000000000000075 +:10EBA0000000000000000000000000000000000065 +:10EBB0000000000000000000000000000000000055 +:10EBC0000000000000000000000000000000000045 +:10EBD0000000000000000000000000000000000035 +:10EBE0000000000000000000000000000000000025 +:10EBF0000000000000000000000000000000000015 +:10EC00000000000000000000000000000000000004 +:10EC100000000000000000000000000000000000F4 +:10EC200000000000000000000000000000000000E4 +:10EC300000000000000000000000000000000000D4 +:10EC400000000000000000000000000000000000C4 +:10EC500000000000000000000000000000000000B4 +:10EC600000000000000000000000000000000000A4 +:10EC70000000000000000000000000000000000094 +:10EC80000000000000000000000000000000000084 +:10EC90000000000000000000000000000000000074 +:10ECA0000000000000000000000000000000000064 +:10ECB0000000000000000000000000000000000054 +:10ECC0000000000000000000000000000000000044 +:10ECD0000000000000000000000000000000000034 +:10ECE0000000000000000000000000000000000024 +:10ECF0000000000000000000000000000000000014 +:10ED00000000000000000000000000000000000003 +:10ED100000000000000000000000000000000000F3 +:10ED200000000000000000000000000000000000E3 +:10ED300000000000000000000000000000000000D3 +:10ED400000000000000000000000000000000000C3 +:10ED500000000000000000000000000000000000B3 +:10ED600000000000000000000000000000000000A3 +:10ED70000000000000000000000000000000000093 +:10ED80000000000000000000000000000000000083 +:10ED90000000000000000000000000000000000073 +:10EDA0000000000000000000000000000000000063 +:10EDB0000000000000000000000000000000000053 +:10EDC0000000000000000000000000000000000043 +:10EDD0000000000000000000000000000000000033 +:10EDE0000000000000000000000000000000000023 +:10EDF0000000000000000000000000000000000013 +:10EE00000000000000000000000000000000000002 +:10EE100000000000000000000000000000000000F2 +:10EE200000000000000000000000000000000000E2 +:10EE300000000000000000000000000000000000D2 +:10EE400000000000000000000000000000000000C2 +:10EE500000000000000000000000000000000000B2 +:10EE600000000000000000000000000000000000A2 +:10EE70000000000000000000000000000000000092 +:10EE80000000000000000000000000000000000082 +:10EE90000000000000000000000000000000000072 +:10EEA0000000000000000000000000000000000062 +:10EEB0000000000000000000000000000000000052 +:10EEC0000000000000000000000000000000000042 +:10EED0000000000000000000000000000000000032 +:10EEE0000000000000000000000000000000000022 +:10EEF0000000000000000000000000000000000012 +:10EF00000000000000000000000000000000000001 +:10EF100000000000000000000000000000000000F1 +:10EF200000000000000000000000000000000000E1 +:10EF300000000000000000000000000000000000D1 +:10EF400000000000000000000000000000000000C1 +:10EF500000000000000000000000000000000000B1 +:10EF600000000000000000000000000000000000A1 +:10EF70000000000000000000000000000000000091 +:10EF80000000000000000000000000000000000081 +:10EF90000000000000000000000000000000000071 +:10EFA0000000000000000000000000000000000061 +:10EFB0000000000000000000000000000000000051 +:10EFC0000000000000000000000000000000000041 +:10EFD0000000000000000000000000000000000031 +:10EFE0000000000000000000000000000000000021 +:10EFF000000000000000000000000002007100009E +:00000001FF diff --git a/tests/list_test.rs b/tests/list_test.rs new file mode 100644 index 0000000..fa2bd9d --- /dev/null +++ b/tests/list_test.rs @@ -0,0 +1,98 @@ +use assert_cmd::Command; +use serial_test::serial; + +#[cfg(target_os = "macos")] +const NUPHY_AIR60_SMK_ENTRY: &str = "\ +ID 05ac:024f manufacturer=\"contact@carlossless.io\" product=\"SMK Keyboard\" + path=\".+\" interface_number=0 + report_descriptor=\\[05 01 09 06 A1 01 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 75 08 95 01 81 01 05 07 19 00 29 FF 15 00 26 FF 00 75 08 95 06 81 00 05 08 19 01 29 05 15 00 25 01 75 01 95 05 91 02 75 03 95 01 91 01 C0\\] + feature_report_ids=\\[\\] + usage_page=0x0001 usage=0x0006\ + path=\".+\" interface_number=1 + report_descriptor=\\[05 01 09 80 A1 01 85 01 19 81 29 83 15 00 25 01 75 01 95 03 81 02 95 05 81 01 C0 05 0C 09 01 A1 01 85 02 19 00 2A 3C 02 15 00 26 3C 02 75 10 95 01 81 00 C0 06 00 FF 09 01 A1 01 85 05 19 01 29 02 15 00 26 FF 00 75 08 95 05 B1 02 C0 05 01 09 06 A1 01 85 06 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 05 07 19 00 29 9F 15 00 25 01 75 01 95 A0 81 02 C0\\] + feature_report_ids=\\[5\\] + usage_page=0x0001 usage=0x0006 + usage_page=0x0001 usage=0x0080 + usage_page=0x000c usage=0x0001 + usage_page=0xff00 usage=0x0001 +"; + +#[cfg(target_os = "linux")] +const NUPHY_AIR60_SMK_ENTRY: &str = "\ +ID 05ac:024f manufacturer=\"contact@carlossless.io\" product=\"SMK Keyboard\" + path=\".+\" interface_number=0 + report_descriptor=\\[05 01 09 06 A1 01 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 75 08 95 01 81 01 05 07 19 00 29 FF 15 00 26 FF 00 75 08 95 06 81 00 05 08 19 01 29 05 15 00 25 01 75 01 95 05 91 02 75 03 95 01 91 01 C0\\] + feature_report_ids=\\[\\] + path=\".+\" interface_number=1 + report_descriptor=\\[05 01 09 80 A1 01 85 01 19 81 29 83 15 00 25 01 75 01 95 03 81 02 95 05 81 01 C0 05 0C 09 01 A1 01 85 02 19 00 2A 3C 02 15 00 26 3C 02 75 10 95 01 81 00 C0 06 00 FF 09 01 A1 01 85 05 19 01 29 02 15 00 26 FF 00 75 08 95 05 B1 02 C0 05 01 09 06 A1 01 85 06 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 05 07 19 00 29 9F 15 00 25 01 75 01 95 A0 81 02 C0\\] + feature_report_ids=\\[5\\] +"; + +#[cfg(target_os = "windows")] +const NUPHY_AIR60_SMK_ENTRY: &str = "\ +ID 05ac:024f manufacturer=\"contact@carlossless.io\" product=\"SMK Keyboard\" + interface_number=0 + path=\".+\" usage_page=0x0001 usage=0x0006 + report_descriptor=\\[05 01 09 06 A1 01 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 75 08 95 01 81 03 19 00 29 FF 15 00 26 FF 00 75 08 95 06 81 00 05 08 19 01 29 05 15 00 25 01 75 01 95 05 91 02 75 03 95 01 91 03 C0\\] + feature_report_ids=\\[\\] + interface_number=1 + path=\".+\" usage_page=0x0001 usage=0x0080 + report_descriptor=\\[05 01 09 80 A1 01 85 01 19 81 29 83 15 00 25 01 75 01 95 03 81 02 75 05 95 01 81 03 C0\\] + feature_report_ids=\\[\\] + interface_number=1 + path=\".+\" usage_page=0x000c usage=0x0001 + report_descriptor=\\[05 0C 09 01 A1 01 85 02 19 00 2A 3C 02 15 00 26 3C 02 75 10 95 01 81 00 C0\\] + feature_report_ids=\\[\\] + interface_number=1 + path=\".+\" usage_page=0xff00 usage=0x0001 + report_descriptor=\\[06 00 FF 09 01 A1 01 85 05 19 01 29 02 15 00 26 FF 00 75 08 95 05 B1 02 C0\\] + feature_report_ids=\\[5\\] + interface_number=1 + path=\".+\" usage_page=0x0001 usage=0x0006 + report_descriptor=\\[05 01 09 06 A1 01 85 06 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 19 00 29 9F 15 00 25 01 75 01 95 A0 81 02 C0\\] + feature_report_ids=\\[\\] +"; + +#[test] +#[serial] +fn test_list_devices() { + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd.arg("list").assert(); + assert + .success() + .stdout(predicates::str::is_match(NUPHY_AIR60_SMK_ENTRY).unwrap()); +} + +#[test] +#[serial] +fn test_list_with_vid_filter() { + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd.arg("list").args(&["--vendor_id", "0x05ac"]).assert(); + assert + .success() + .stdout(predicates::str::is_match(NUPHY_AIR60_SMK_ENTRY).unwrap()); +} + +#[test] +#[serial] +fn test_list_with_pid_filter() { + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd.arg("list").args(&["--product_id", "0x024f"]).assert(); + assert + .success() + .stdout(predicates::str::is_match(NUPHY_AIR60_SMK_ENTRY).unwrap()); +} + +#[test] +#[serial] +fn test_list_with_vid_and_pid_filter() { + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd + .arg("list") + .args(&["--vendor_id", "0x05ac"]) + .args(&["--product_id", "0x024f"]) + .assert(); + assert + .success() + .stdout(predicates::str::is_match(NUPHY_AIR60_SMK_ENTRY).unwrap()); +} diff --git a/tests/read_test.rs b/tests/read_test.rs new file mode 100644 index 0000000..5336140 --- /dev/null +++ b/tests/read_test.rs @@ -0,0 +1,145 @@ +use std::fs; + +use assert_cmd::Command; +use serial_test::serial; + +#[macro_use] +pub mod common; + +#[test] +#[serial] +fn test_read() { + let file = test_filename!("hex"); + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd + .arg("read") + .args(&["--part", "nuphy-air60"]) + .arg(&file) + .assert(); + assert.success().stderr(predicates::str::contains( + "MD5: 662c8707c4be0e0712e30336b0e7cfd1", + )); + + let computed_md5 = md5::compute(fs::read(&file).unwrap()); + assert_eq!( + format!("{:x}", computed_md5), + "6594e5a1ab671deb40f36483a84ad61f" + ); +} + +#[test] +#[serial] +fn test_read_bin() { + let file = test_filename!("bin"); + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd + .arg("read") + .args(&["--part", "nuphy-air60"]) + .arg(&file) + .assert(); + assert.success().stderr(predicates::str::contains( + "MD5: 662c8707c4be0e0712e30336b0e7cfd1", + )); + + let computed_md5 = md5::compute(fs::read(&file).unwrap()); + assert_eq!( + format!("{:x}", computed_md5), + "662c8707c4be0e0712e30336b0e7cfd1" + ); +} + +#[test] +#[serial] +fn test_read_bootloader() { + let file = test_filename!("hex"); + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd + .arg("read") + .args(&["--part", "nuphy-air60"]) + .args(&["--section", "bootloader"]) + .arg(&file) + .assert(); + assert.success().stderr(predicates::str::contains( + "MD5: 3e0ebd0c440af5236d7ff8872343f85d", + )); + + let computed_md5 = md5::compute(fs::read(&file).unwrap()); + assert_eq!( + format!("{:x}", computed_md5), + "65956adbe2e77369d3581ebabb1592f7" + ); +} + +#[test] +#[serial] +fn test_read_full() { + let file = test_filename!("hex"); + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd + .arg("read") + .args(&["--part", "nuphy-air60"]) + .args(&["--section", "full"]) + .arg(&file) + .assert(); + assert.success().stderr(predicates::str::contains( + "MD5: 3f87adb2125bcc5beee424a3af5272e9", + )); + + let computed_md5 = md5::compute(fs::read(&file).unwrap()); + assert_eq!( + format!("{:x}", computed_md5), + "e6f497f108dbe82f8d340562eb88fe44" + ); +} + +#[test] +#[serial] +fn test_read_custom_part() { + let file = test_filename!("hex"); + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd + .arg("read") + .args(&["--vendor_id", "0x05ac"]) + .args(&["--product_id", "0x024f"]) + .args(&["--isp_iface_num", "1"]) + .args(&["--isp_report_id", "5"]) + .args(&["--firmware_size", "61440"]) + .arg(&file) + .assert(); + assert.success().stderr(predicates::str::contains( + "MD5: 662c8707c4be0e0712e30336b0e7cfd1", + )); + + let computed_md5 = md5::compute(fs::read(&file).unwrap()); + assert_eq!( + format!("{:x}", computed_md5), + "6594e5a1ab671deb40f36483a84ad61f" + ); +} + +#[test] +#[serial] +fn test_read_forced_format_bin() { + let file = test_filename!("hex"); + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd + .arg("read") + .args(&["--part", "nuphy-air60"]) + .args(&["--format", "bin"]) + .arg(&file) + .assert(); + assert + .success() + .stderr(predicates::str::contains( + "Warning: binary file has hex extension. This might be unintended.", + )) + .stderr(predicates::str::contains( + "MD5: 662c8707c4be0e0712e30336b0e7cfd1", + )); + + let computed_md5 = md5::compute(fs::read(&file).unwrap()); + assert_eq!( + format!("{:x}", computed_md5), + "662c8707c4be0e0712e30336b0e7cfd1" + ); +} diff --git a/tests/write_test.rs b/tests/write_test.rs new file mode 100644 index 0000000..00a8fa9 --- /dev/null +++ b/tests/write_test.rs @@ -0,0 +1,90 @@ +use std::fs; + +use assert_cmd::Command; +use serial_test::serial; + +#[macro_use] +pub mod common; + +use common::get_fixture_path; + +#[test] +#[serial] +fn test_write() { + let file = get_fixture_path("nuphy-air60_smk.hex"); + let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = cmd + .arg("write") + .args(&["--part", "nuphy-air60"]) + .arg(file) + .assert(); + assert.success(); +} + +#[test] +#[serial] +fn test_write_and_readback() { + let fixture_file = get_fixture_path("nuphy-air60_smk.hex"); + let mut write_cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = write_cmd + .arg("write") + .args(&["--part", "nuphy-air60"]) + .arg(&fixture_file) + .assert(); + assert.success(); + + let output_file = test_filename!("hex"); + let mut read_cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = read_cmd + .arg("read") + .args(&["--part", "nuphy-air60"]) + .arg(&output_file) + .assert(); + assert.success().stderr(predicates::str::contains( + "MD5: 662c8707c4be0e0712e30336b0e7cfd1", + )); + + let computed_md5 = md5::compute(fs::read(&output_file).unwrap()); + assert_eq!( + format!("{:x}", computed_md5), + "6594e5a1ab671deb40f36483a84ad61f" + ); +} + +#[test] +#[serial] +fn test_write_custom_and_readback() { + let fixture_file = get_fixture_path("nuphy-air60_smk.hex"); + let mut write_cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = write_cmd + .arg("write") + .args(&["--vendor_id", "0x05ac"]) + .args(&["--product_id", "0x024f"]) + .args(&["--isp_iface_num", "1"]) + .args(&["--isp_report_id", "5"]) + .args(&["--firmware_size", "61440"]) + .arg(&fixture_file) + .assert(); + assert.success(); + + let output_file = test_filename!("hex"); + let mut read_cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); + let assert = read_cmd + .arg("read") + .args(&["--vendor_id", "0x05ac"]) + .args(&["--product_id", "0x024f"]) + .args(&["--isp_iface_num", "1"]) + .args(&["--isp_report_id", "5"]) + .args(&["--firmware_size", "61440"]) + .arg(&output_file) + .assert(); + assert.success().stderr(predicates::str::contains( + "MD5: 662c8707c4be0e0712e30336b0e7cfd1", + )); + + let computed_md5 = md5::compute(fs::read(&output_file).unwrap()); + assert_eq!( + format!("{:x}", computed_md5), + "6594e5a1ab671deb40f36483a84ad61f" + ); +} diff --git a/tools/functional-test.sh b/tools/functional-test.sh deleted file mode 100755 index 484f389..0000000 --- a/tools/functional-test.sh +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# TOOL=sinowealth-kb-tool -TOOL="cargo run --" -FILE_PREFIX="private/test-$(date +'%Y%m%dT%H%M%S')" -PART="nuphy-air60" - -EXPECTED_BOOTLOADER_MD5="3e0ebd0c440af5236d7ff8872343f85d" - -FILE_DEFAULT="$FILE_PREFIX-read.hex" -FILE_BOOTLOADER="$FILE_PREFIX-read-bootloader.hex" -FILE_FULL="$FILE_PREFIX-read-full.hex" -FILE_CUSTOM="$FILE_PREFIX-read-custom.hex" -FILE_OVERRIDE="$FILE_PREFIX-read-override.hex" -FILE_POST_WRITE="$FILE_PREFIX-post-write.hex" -FILE_POST_WRITE_CUSTOM="$FILE_PREFIX-post-write-custom.hex" - -function reboot_device () { - echo "Cycling port power..." - uhubctl -a cycle -l "3-3.3.4.4" -p 4 -d 1 - echo "Waiting..." - sleep 5 -} - -function get_md5 () { - MD5SUM=($(md5sum "$1")) - echo $MD5SUM -} - -function get_md5_from_hex () { - objcopy --input-target=ihex --output-target=binary "$1" "${1%.hex}.bin" - MD5SUM=$(get_md5 "${1%.hex}.bin") - echo $MD5SUM -} - -echo "Initial reboot..." -reboot_device - -echo "Standard read..." -$TOOL read --part "$PART" "$FILE_DEFAULT" - -reboot_device - -echo "Bootloader read..." -$TOOL read --part "$PART" -f bootloader "$FILE_BOOTLOADER" - -reboot_device - -echo "Full read..." -$TOOL read --part "$PART" -f full "$FILE_FULL" - -reboot_device - -echo "Custom read..." -$TOOL read \ - --firmware_size 61440 \ - --vendor_id 0x05ac \ - --product_id 0x024f \ - --bootloader_size 4096 \ - --page_size 2048 \ - --isp_iface_num 1 \ - --isp_report_id 5 \ - "$FILE_CUSTOM" - -reboot_device - -echo "Override read..." -$TOOL read \ - --part "$PART" \ - --vendor_id 0x05ac \ - --product_id 0x024f \ - "$FILE_OVERRIDE" - -reboot_device - -READ_MD5=$(get_md5_from_hex "$FILE_DEFAULT") -READ_BOOTLOADER_MD5=$(get_md5_from_hex "$FILE_BOOTLOADER") -READ_FULL_MD5=$(get_md5_from_hex "$FILE_FULL") -READ_CUSTOM_MD5=$(get_md5_from_hex "$FILE_CUSTOM") -READ_OVERRIDE_MD5=$(get_md5_from_hex "$FILE_OVERRIDE") - -echo "Checking bootloader checksum" -if [[ "$READ_BOOTLOADER_MD5" != "$EXPECTED_BOOTLOADER_MD5" ]]; then - echo "MD5 mismatch $READ_BOOTLOADER_MD5 != $EXPECTED_BOOTLOADER_MD5" - exit 1 -fi - -echo "Checking custom checksum" -if [[ "$READ_CUSTOM_MD5" != "$READ_MD5" ]]; then - echo "MD5 mismatch $READ_CUSTOM_MD5 != $READ_MD5" - exit 1 -fi - -echo "Checking override checksum" -if [[ "$READ_OVERRIDE_MD5" != "$READ_MD5" ]]; then - echo "MD5 mismatch $READ_OVERRIDE_MD5 != $READ_MD5" - exit 1 -fi - -echo "Checking standard+bootloader == full" -cat "${FILE_DEFAULT%.hex}.bin" "${FILE_BOOTLOADER%.hex}.bin" > "$FILE_PREFIX-concat-full.bin" -EXPECTED_FULL_MD5=$(get_md5 "$FILE_PREFIX-concat-full.bin") -if [[ "$READ_FULL_MD5" != "$EXPECTED_FULL_MD5" ]]; then - echo "MD5 mismatch $READ_FULL_MD5 != $EXPECTED_FULL_MD5" - exit 1 -fi - -echo "Standard write..." -$TOOL write --part "$PART" "$FILE_DEFAULT" - -reboot_device - -echo "Post-write read..." -$TOOL read --part "$PART" "$FILE_POST_WRITE" - -READ_POST_WRITE_MD5=$(get_md5_from_hex "$FILE_POST_WRITE") - -echo "Checking post-write checksum" -if [[ "$READ_POST_WRITE_MD5" != "$READ_MD5" ]]; then - echo "MD5 mismatch $READ_POST_WRITE_MD5 != $READ_MD5" - exit 1 -fi - -reboot_device - -echo "Custom write..." -$TOOL write \ - --firmware_size 61440 \ - --vendor_id 0x05ac \ - --product_id 0x024f \ - --bootloader_size 4096 \ - --page_size 2048 \ - --isp_iface_num 1 \ - --isp_report_id 5 \ - "$FILE_DEFAULT" - -reboot_device - -echo "Post-write read..." -$TOOL read --part "$PART" "$FILE_POST_WRITE_CUSTOM" - -READ_POST_WRITE_CUSTOM_MD5=$(get_md5_from_hex "$FILE_POST_WRITE_CUSTOM") - -echo "Checking post-write checksum" -if [[ "$READ_POST_WRITE_CUSTOM_MD5" != "$READ_MD5" ]]; then - echo "MD5 mismatch $READ_POST_WRITE_CUSTOM_MD5 != $READ_MD5" - exit 1 -fi - -reboot_device - -$TOOL list - -$TOOL list --product_id 0x024f - -$TOOL list --vendor_id 0x05ac - -$TOOL list --vendor_id 0x05ac --product_id 0x024f - -echo "Passed all tests!" From 4f5c284cf71002106768503531da32166b0efde2 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 4 May 2025 17:46:26 +0200 Subject: [PATCH 118/141] refactoring, renaming and ui improvements (#105) * added a confirmation prompt before expanding firmware size * renamed Parts to DeviceSpec (cli args changed from `-p --part` to `-d --device`) * separated out platform-specific options into PlatformSpec, introduced `-p --platform` argument to pick defaults for `firmware_size` and other custom options --- Cargo.lock | 134 ++++++++++++++++- Cargo.toml | 1 + src/device_selector.rs | 41 +++--- src/device_spec.rs | 327 +++++++++++++++++++++++++++++++++++++++++ src/isp_device.rs | 79 +++++++--- src/main.rs | 211 +++++++++++++++++--------- src/part.rs | 323 ---------------------------------------- src/platform_spec.rs | 40 +++++ src/util.rs | 28 ++-- tests/convert_test.rs | 6 +- tests/list_test.rs | 2 +- tests/read_test.rs | 11 +- tests/write_test.rs | 12 +- 13 files changed, 753 insertions(+), 462 deletions(-) create mode 100644 src/device_spec.rs delete mode 100644 src/part.rs create mode 100644 src/platform_spec.rs diff --git a/Cargo.lock b/Cargo.lock index 38886e9..d70bf90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,6 +230,19 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "tempfile", + "thiserror 1.0.69", + "zeroize", +] + [[package]] name = "difflib" version = "0.4.0" @@ -254,6 +267,22 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "float-cmp" version = "0.10.0" @@ -340,6 +369,18 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + [[package]] name = "hidapi" version = "2.6.3" @@ -444,6 +485,12 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "lock_api" version = "0.4.12" @@ -657,6 +704,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -710,6 +763,19 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -782,6 +848,12 @@ dependencies = [ "syn", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.3.0" @@ -808,6 +880,7 @@ dependencies = [ "chrono", "clap", "clap-num", + "dialoguer", "hidapi", "hidparser", "ihex", @@ -820,7 +893,7 @@ dependencies = [ "serial_test", "simple_logger", "stdext", - "thiserror", + "thiserror 2.0.12", ] [[package]] @@ -867,19 +940,52 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termtree" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -953,6 +1059,15 @@ dependencies = [ "libc", ] +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1218,3 +1333,18 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index e5506b4..c5f0300 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ rust-version = "1.65" [dependencies] clap = "4.1" clap-num = "1.0" +dialoguer = "0.11.0" hidparser = { git = "https://github.com/microsoft/mu_rust_hid.git", tag = "v1.0.3" } # needed until 1.0.3 is published on crates.io ihex = "3.0" indicatif = "0.17.11" diff --git a/src/device_selector.rs b/src/device_selector.rs index f0af0a6..d6fd767 100644 --- a/src/device_selector.rs +++ b/src/device_selector.rs @@ -10,7 +10,7 @@ use thiserror::Error; use crate::{ hid_tree::{DeviceNode, InterfaceNode}, - is_expected_error, ISPDevice, Part, + is_expected_error, DeviceSpec, ISPDevice, }; #[cfg(any(target_os = "macos", target_os = "windows"))] @@ -172,7 +172,7 @@ impl DeviceSelector { Err(DeviceSelectorError::NotFound) } - fn find_isp_device(&self, part: Part) -> Result { + fn find_isp_device(&self, device_spec: DeviceSpec) -> Result { let sorted_devices = self.sorted_usb_device_list(); let isp_devices: Vec<_> = sorted_devices .clone() @@ -199,7 +199,7 @@ impl DeviceSelector { debug!("CMD device: {}", cmd_device.info()); #[cfg(not(target_os = "windows"))] return Ok(ISPDevice::new( - part, + device_spec, self.api.open_path(cmd_device.path()).unwrap(), )); @@ -209,18 +209,18 @@ impl DeviceSelector { self.get_device_for_report_id(isp_devices.clone(), REPORT_ID_XFER as u32)?; debug!("XFER device: {}", xfer_device.info()); return Ok(ISPDevice::new( - part, + device_spec, self.api.open_path(cmd_device.path()).unwrap(), self.api.open_path(xfer_device.path()).unwrap(), )); } } - fn find_device(&self, part: Part) -> Result { + fn find_device(&self, device_spec: DeviceSpec) -> Result { let filtered_devices = self.sorted_usb_device_list().into_iter().filter(|d| { - d.vendor_id() == part.vendor_id - && d.product_id() == part.product_id - && d.interface_number() == part.isp_iface_num + d.vendor_id() == device_spec.vendor_id + && d.product_id() == device_spec.product_id + && d.interface_number() == device_spec.isp_iface_num }); let mut cmd_device_info: Option<&DeviceInfo> = None; @@ -229,7 +229,7 @@ impl DeviceSelector { .get_feature_report_ids_from_path(d.path()) .map_err(|_| DeviceSelectorError::NotFound)?; for id in ids { - if id == part.isp_report_id { + if id == device_spec.isp_report_id { cmd_device_info = Some(d); } } @@ -251,7 +251,7 @@ impl DeviceSelector { fn switch_to_isp_device( &mut self, device: HidDevice, - part: Part, + device_spec: DeviceSpec, ) -> Result { if let Err(err) = self.enter_isp_mode(&device) { debug!("Error: {:}", err); @@ -271,7 +271,7 @@ impl DeviceSelector { self.api.refresh_devices()?; - let Ok(isp_device) = self.find_isp_device(part) else { + let Ok(isp_device) = self.find_isp_device(device_spec) else { info!("ISP device didn't come up..."); return Err(DeviceSelectorError::NotFound); }; @@ -280,12 +280,15 @@ impl DeviceSelector { pub fn try_fetch_isp_device( &mut self, - part: Part, + device_spec: DeviceSpec, retries: usize, ) -> Result { eprintln!( - "Looking for {:04x}:{:04x} (interface_num={} report_id={})", - part.vendor_id, part.product_id, part.isp_iface_num, part.isp_report_id + "Looking for {:04x}:{:04x} (isp_iface_num={} isp_report_id={})", + device_spec.vendor_id, + device_spec.product_id, + device_spec.isp_iface_num, + device_spec.isp_report_id ); let bar = ProgressBar::new_spinner() @@ -300,23 +303,23 @@ impl DeviceSelector { info!("Retrying... Attempt {}/{}", attempt, retries); } - if let Ok(device) = self.find_device(part) { + if let Ok(device) = self.find_device(device_spec) { bar.set_message("Regular device found. Switching to ISP mode..."); - if let Ok(isp_device) = self.switch_to_isp_device(device, part) { + if let Ok(isp_device) = self.switch_to_isp_device(device, device_spec) { bar.finish_and_clear(); eprintln!("Connected!"); return Ok(isp_device); } } info!("Regular device not found. Trying ISP device..."); - if let Ok(isp_device) = self.find_isp_device(part) { + if let Ok(isp_device) = self.find_isp_device(device_spec) { bar.finish_and_clear(); eprintln!("Connected!"); return Ok(isp_device); } } - bar.finish(); - eprintln!("Device not found"); + bar.finish_and_clear(); + eprintln!("Device could not be found"); Err(DeviceSelectorError::NotFound) } diff --git a/src/device_spec.rs b/src/device_spec.rs new file mode 100644 index 0000000..08815b9 --- /dev/null +++ b/src/device_spec.rs @@ -0,0 +1,327 @@ +use phf::{phf_map, Map}; + +use crate::platform_spec::{PlatformSpec, PLATFORM_SH68F881, PLATFORM_SH68F90}; + +const DEFAULT_ISP_IFACE_NUM: i32 = 1; +const DEFAULT_ISP_REPORT_ID: u32 = 5; +const DEFAULT_REBOOT: bool = true; + +#[derive(Clone, Copy, PartialEq)] +pub struct DeviceSpec { + pub vendor_id: u16, + pub product_id: u16, + + pub platform: PlatformSpec, + + /// USB interface number with the ISP report + pub isp_iface_num: i32, + /// HID report ID + pub isp_report_id: u32, + + pub reboot: bool, +} + +pub const DEVICE_BASE_SH68F90: DeviceSpec = DeviceSpec { + vendor_id: 0x0000, + product_id: 0x0000, + platform: PLATFORM_SH68F90, + isp_iface_num: DEFAULT_ISP_IFACE_NUM, + isp_report_id: DEFAULT_ISP_REPORT_ID, + reboot: DEFAULT_REBOOT, +}; + +pub const DEVICE_BASE_SH68F881: DeviceSpec = DeviceSpec { + vendor_id: 0x0000, + product_id: 0x0000, + platform: PLATFORM_SH68F881, + isp_iface_num: DEFAULT_ISP_IFACE_NUM, + isp_report_id: DEFAULT_ISP_REPORT_ID, + reboot: DEFAULT_REBOOT, +}; + +pub const DEVICE_NUPHY_AIR60: DeviceSpec = DeviceSpec { + vendor_id: 0x05ac, + product_id: 0x024f, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_LEOBOG_HI75: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x010c, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_AULA_F75: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x010c, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_AULA_F87: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x010c, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_XINMENG_K916: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x00a1, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_XINMENG_XM_RF68: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x002a, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_XINMENG_M71: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x010c, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_RE_K70_BYK800: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x001a, + ..DEVICE_BASE_SH68F881 +}; + +pub const DEVICE_TERPORT_TR95: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_REDRAGON_FIZZ_K617: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_REDRAGON_ANIVIA_K614: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_REDRAGON_K641_SHACO_PRO: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_GENESIS_THOR_300: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x001f, + ..DEVICE_BASE_SH68F881 +}; + +pub const DEVICE_GENESIS_THOR_300_RGB: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0090, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_ROYALKLUDGE_RK61: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x00c7, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_ROYALKLUDGE_RK68_ISO_RETURN: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x00a9, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_ROYALKLUDGE_RK68_BT_DUAL: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x008b, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_ROYALKLUDGE_RKG68: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_ROYALKLUDGE_RK71: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x00ea, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_ROYALKLUDGE_RK84_ISO_RETURN: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x00f4, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_ROYALKLUDGE_RK100: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0056, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_DELTACO_WK95R: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_DIGITALALLIANCE_MECA_WARRIOR_X: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0090, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_KZZI_K68PRO: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0186, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_WEIKAV_SUGAR65: DeviceSpec = DeviceSpec { + vendor_id: 0x05ac, + product_id: 0x024f, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_TRUST_GXT_960: DeviceSpec = DeviceSpec { + vendor_id: 0x145f, + product_id: 0x02b6, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_GLORIOUS_MODEL_O: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0036, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_MACHENIKE_K500_B61: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_MAGEGEE_MKSTAR61: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x010c, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_REDRAGON_K658_PRO_SE: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_REDRAGON_K530_DRACONIC_PRO: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_REDRAGON_K630_NO_RGB: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x002a, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_EYOOSO_Z11: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x002a, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_PORTRONICS_HYDRA10: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_REDRAGON_K633_RYZE: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub const DEVICE_YINREN_R108: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + +pub static DEVICES: Map<&'static str, DeviceSpec> = phf_map! { + "aula-f75" => DEVICE_AULA_F75, + "aula-f87" => DEVICE_AULA_F87, + "deltaco-wk95r" => DEVICE_DELTACO_WK95R, + "digitalalliance-meca-warrior-x" => DEVICE_DIGITALALLIANCE_MECA_WARRIOR_X, + "eyooso-z11" => DEVICE_EYOOSO_Z11, + "genesis-thor-300-rgb" => DEVICE_GENESIS_THOR_300_RGB, + "genesis-thor-300" => DEVICE_GENESIS_THOR_300, + "glorious-model-o" => DEVICE_GLORIOUS_MODEL_O, + "kzzi-k68pro" => DEVICE_KZZI_K68PRO, + "leobog-hi75" => DEVICE_LEOBOG_HI75, + "machenike-k500-b61" => DEVICE_MACHENIKE_K500_B61, + "magegee-mkstar61" => DEVICE_MAGEGEE_MKSTAR61, + "nuphy-air60" => DEVICE_NUPHY_AIR60, + "nuphy-air75" => DEVICE_NUPHY_AIR60, // same as nuphy-air60 + "nuphy-air96" => DEVICE_NUPHY_AIR60, // same as nuphy-air60 + "nuphy-halo65" => DEVICE_NUPHY_AIR60, // same as nuphy-air60 + "portronics-hydra10" => DEVICE_PORTRONICS_HYDRA10, + "re-k70-byk800" => DEVICE_RE_K70_BYK800, + "redragon-k530-draconic-pro" => DEVICE_REDRAGON_K530_DRACONIC_PRO, + "redragon-k630-norgb" => DEVICE_REDRAGON_K630_NO_RGB, + "redragon-k614-anivia" => DEVICE_REDRAGON_ANIVIA_K614, + "redragon-k617-fizz" => DEVICE_REDRAGON_FIZZ_K617, + "redragon-k633-ryze" => DEVICE_REDRAGON_K633_RYZE, + "redragon-k641-shaco-pro" => DEVICE_REDRAGON_K641_SHACO_PRO, + "redragon-k658-pro-se" => DEVICE_REDRAGON_K658_PRO_SE, + "royalkludge-rk100" => DEVICE_ROYALKLUDGE_RK100, + "royalkludge-rk61" => DEVICE_ROYALKLUDGE_RK61, + "royalkludge-rk68-bt-dual" => DEVICE_ROYALKLUDGE_RK68_BT_DUAL, + "royalkludge-rk68-iso-return" => DEVICE_ROYALKLUDGE_RK68_ISO_RETURN, + "royalkludge-rk71" => DEVICE_ROYALKLUDGE_RK71, + "royalkludge-rk84-iso-return" => DEVICE_ROYALKLUDGE_RK84_ISO_RETURN, + "royalkludge-rkg68" => DEVICE_ROYALKLUDGE_RKG68, + "terport-tr95" => DEVICE_TERPORT_TR95, + "trust-gxt-960" => DEVICE_TRUST_GXT_960, + "weikav-sugar65" => DEVICE_WEIKAV_SUGAR65, + "xinmeng-k916" => DEVICE_XINMENG_K916, + "xinmeng-m66" => DEVICE_XINMENG_M71, + "xinmeng-m71" => DEVICE_XINMENG_M71, + "xinmeng-xm-rf68" => DEVICE_XINMENG_XM_RF68, + "yinren-r108" => DEVICE_YINREN_R108, + "yunzii-al66" => DEVICE_XINMENG_M71, // same as xinmeng-m71 + "yunzii-al71" => DEVICE_XINMENG_M71, // same as xinmeng-m71 +}; + +impl DeviceSpec { + pub fn available_devices() -> Vec<&'static str> { + let mut device_names = DEVICES.keys().copied().collect::>(); + device_names.sort(); + device_names + } + + pub fn num_pages(&self) -> usize { + self.platform.firmware_size / self.platform.page_size + } + + pub fn total_flash_size(&self) -> usize { + self.platform.firmware_size + self.platform.bootloader_size + } +} + +#[test] +fn test_device_num_pages() { + assert_eq!(DEVICE_NUPHY_AIR60.num_pages(), 30) +} + +#[test] +fn test_device_total_flash_size() { + assert_eq!(DEVICE_NUPHY_AIR60.total_flash_size(), 65536) +} diff --git a/src/isp_device.rs b/src/isp_device.rs index 4e4625d..b983b5a 100644 --- a/src/isp_device.rs +++ b/src/isp_device.rs @@ -1,10 +1,11 @@ -use std::{thread, time}; +use core::panic; +use std::{str::FromStr, thread, time}; use indicatif::ProgressBar; use log::{debug, error}; use thiserror::Error; -use crate::{is_expected_error, part::*, util, VerificationError}; +use crate::{device_spec::*, is_expected_error, util, VerificationError}; extern crate hidapi; @@ -28,7 +29,7 @@ pub struct ISPDevice { cmd_device: HidDevice, #[cfg(target_os = "windows")] xfer_device: HidDevice, - part: Part, + device_spec: DeviceSpec, } #[derive(Debug, Error)] @@ -46,21 +47,51 @@ pub enum ReadSection { Full, } +impl ReadSection { + pub fn to_str(&self) -> &'static str { + match self { + ReadSection::Firmware => "firmware", + ReadSection::Bootloader => "bootloader", + ReadSection::Full => "full", + } + } + + pub fn available_sections() -> Vec<&'static str> { + vec![ + ReadSection::Firmware.to_str(), + ReadSection::Bootloader.to_str(), + ReadSection::Full.to_str(), + ] + } +} + +impl FromStr for ReadSection { + type Err = (); + fn from_str(section: &str) -> Result { + Ok(match section { + "bootloader" => ReadSection::Bootloader, + "full" => ReadSection::Full, + "firmware" => ReadSection::Firmware, + _ => panic!("Invalid read section: {}", section), + }) + } +} + impl ISPDevice { #[cfg(not(target_os = "windows"))] - pub fn new(part: Part, device: HidDevice) -> Self { + pub fn new(device_spec: DeviceSpec, device: HidDevice) -> Self { Self { cmd_device: device, - part, + device_spec, } } #[cfg(target_os = "windows")] - pub fn new(part: Part, cmd_device: HidDevice, xfer_device: HidDevice) -> Self { + pub fn new(device_spec: DeviceSpec, cmd_device: HidDevice, xfer_device: HidDevice) -> Self { Self { cmd_device, xfer_device, - part, + device_spec, } } @@ -68,14 +99,20 @@ impl ISPDevice { self.enable_firmware()?; let (start_addr, length) = match read_fragment { - ReadSection::Firmware => (0, self.part.firmware_size), - ReadSection::Bootloader => (self.part.firmware_size, self.part.bootloader_size), - ReadSection::Full => (0, self.part.firmware_size + self.part.bootloader_size), + ReadSection::Firmware => (0, self.device_spec.platform.firmware_size), + ReadSection::Bootloader => ( + self.device_spec.platform.firmware_size, + self.device_spec.platform.bootloader_size, + ), + ReadSection::Full => ( + 0, + self.device_spec.platform.firmware_size + self.device_spec.platform.bootloader_size, + ), }; let firmware = self.read(start_addr, length)?; - if self.part.reboot { + if self.device_spec.reboot { self.reboot(); } @@ -84,22 +121,24 @@ impl ISPDevice { pub fn write_cycle(&self, firmware: &mut [u8]) -> Result<(), ISPError> { // ensure that the address at is the same as the reset vector - firmware.copy_within(1..3, self.part.firmware_size - 4); + firmware.copy_within(1..3, self.device_spec.platform.firmware_size - 4); self.erase()?; self.write(0, firmware)?; // cleanup the address at - firmware[self.part.firmware_size - 4..self.part.firmware_size - 2].fill(0); + firmware[self.device_spec.platform.firmware_size - 4 + ..self.device_spec.platform.firmware_size - 2] + .fill(0); - let read_back = self.read(0, self.part.firmware_size)?; + let read_back = self.read(0, self.device_spec.platform.firmware_size)?; eprintln!("Verifying..."); util::verify(firmware, &read_back).map_err(ISPError::from)?; self.enable_firmware()?; - if self.part.reboot { + if self.device_spec.reboot { self.reboot(); } @@ -114,7 +153,7 @@ impl ISPDevice { } fn read(&self, start_addr: usize, length: usize) -> Result, ISPError> { - let page_size = self.part.page_size; + let page_size = self.device_spec.platform.page_size; let num_page = length / page_size; let mut result: Vec = vec![]; @@ -138,11 +177,11 @@ impl ISPDevice { fn write(&self, start_addr: usize, buffer: &[u8]) -> Result<(), ISPError> { eprintln!("Writing..."); - let bar = ProgressBar::new(self.part.num_pages() as u64); + let bar = ProgressBar::new(self.device_spec.num_pages() as u64); self.init_write(start_addr)?; - let page_size = self.part.page_size; - for i in 0..self.part.num_pages() { + let page_size = self.device_spec.platform.page_size; + for i in 0..self.device_spec.num_pages() { bar.inc(1); debug!("Writing page {} @ offset {:#06x}", i, i * page_size); self.write_page(&buffer[(i * page_size)..((i + 1) * page_size)])?; @@ -185,7 +224,7 @@ impl ISPDevice { /// Reads one page of flash contents fn read_page(&self, buf: &mut Vec) -> Result<(), ISPError> { - let page_size = self.part.page_size; + let page_size = self.device_spec.platform.page_size; let mut xfer_buf: Vec = vec![0; page_size + 2]; xfer_buf[0] = REPORT_ID_XFER; xfer_buf[1] = XFER_READ_PAGE; diff --git a/src/main.rs b/src/main.rs index 30e2e5b..c4d9c1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,24 +3,30 @@ use std::{ io::{self, Read}, path::Path, process::ExitCode, + str::FromStr, }; use clap::{arg, value_parser, ArgMatches, Command}; use clap_num::maybe_hex; use device_selector::{DeviceSelector, DeviceSelectorError}; +use dialoguer::Confirm; use hid_tree::TreeDisplay; use log::error; +use platform_spec::PlatformSpec; use simple_logger::SimpleLogger; use thiserror::Error; mod device_selector; +mod device_spec; mod hid_tree; mod ihex; mod isp_device; -mod part; +mod platform_spec; mod util; -pub use crate::{ihex::*, isp_device::*, part::*, util::*}; +pub use crate::{device_spec::*, ihex::*, isp_device::*, util::*}; + +const DEFAULT_RETRY_COUNT: &str = "5"; #[derive(Debug, Error)] pub enum CLIError { @@ -42,6 +48,30 @@ enum Format { Binary, } +impl Format { + pub fn to_str(self) -> &'static str { + match self { + Format::IntelHex => "ihex", + Format::Binary => "bin", + } + } + + pub fn available_formats() -> Vec<&'static str> { + vec![Format::IntelHex.to_str(), Format::Binary.to_str()] + } +} + +impl FromStr for Format { + type Err = (); + fn from_str(format: &str) -> Result { + Ok(match format { + "ihex" => Format::IntelHex, + "bin" => Format::Binary, + _ => panic!("Invalid format: {}", format), + }) + } +} + fn main() -> ExitCode { match err_main() { Ok(_) => ExitCode::SUCCESS, @@ -70,31 +100,31 @@ fn cli() -> Command { Command::new("convert") .short_flag('c') .about("Convert payload from bootloader to JTAG and vice versa.") - .arg(arg!(-d --direction "direction of conversion").value_parser(["to_jtag", "to_isp"]).required(true)) - .arg(arg!(--input_format ).value_parser(["ihex", "bin"])) - .arg(arg!(--output_format ).value_parser(["ihex", "bin"])) + .arg(arg!(--direction "direction of conversion").value_parser(["to_jtag", "to_isp"]).required(true)) + .arg(arg!(--input_format ).value_parser(Format::available_formats())) + .arg(arg!(--output_format ).value_parser(Format::available_formats())) .arg(arg!(input_file: "file to convert")) .arg(arg!(output_file: "file to write results to")) - .part_args() // TODO: not all of these args are needed and should be removed + .device_args() // TODO: not all of these args are needed and should be removed ) .subcommand( Command::new("read") .short_flag('r') - .about("Read flash contents. (Intel HEX)") + .about("Read flash contents.") .arg(arg!(output_file: "file to write flash contents to")) - .arg(arg!(-f --format ).value_parser(["ihex", "bin"])) - .arg(arg!(-s --section
"firmware section to read").value_parser(["firmware", "bootloader", "full"]).default_value("firmware")) - .arg(arg!(-r --retry "number of retries trying to find device").value_parser(value_parser!(usize)).default_value("5")) - .part_args() + .arg(arg!(-f --format ).value_parser(Format::available_formats())) + .arg(arg!(-s --section
"firmware section to read").value_parser(ReadSection::available_sections()).default_value(ReadSection::Firmware.to_str())) + .arg(arg!(-r --retry "number of attempts trying to find device").value_parser(value_parser!(usize)).default_value(DEFAULT_RETRY_COUNT)) + .device_args() ) .subcommand( Command::new("write") .short_flag('w') - .about("Write file (Intel HEX) into flash.") + .about("Write file into flash.") .arg(arg!(input_file: "payload to write into flash")) - .arg(arg!(-f --format ).value_parser(["ihex", "bin"])) - .arg(arg!(-r --retry "number of retries trying to find device").value_parser(value_parser!(usize)).default_value("5")) - .part_args(), + .arg(arg!(-f --format ).value_parser(Format::available_formats())) + .arg(arg!(-r --retry "number of attempts trying to find device").value_parser(value_parser!(usize)).default_value(DEFAULT_RETRY_COUNT)) + .device_args(), ) } @@ -123,22 +153,16 @@ fn err_main() -> Result<(), CLIError> { let section = sub_matches .get_one::("section") .map(|s| s.as_str()) + .map(|s| ReadSection::from_str(s).unwrap()) .unwrap(); let format = get_format_from_matches(sub_matches, output_file, "format"); - let part = get_part_from_matches(sub_matches); - - let section: ReadSection = match section { - "firmware" => ReadSection::Firmware, - "bootloader" => ReadSection::Bootloader, - "full" => ReadSection::Full, - _ => panic!("Invalid read fragment"), - }; + let device_spec = get_device_spec_from_matches(sub_matches); let mut ds = DeviceSelector::new().map_err(CLIError::DeviceSelectorError)?; let device = ds - .try_fetch_isp_device(part, retry_count) + .try_fetch_isp_device(device_spec, retry_count) .map_err(CLIError::from)?; let firmware = device.read_cycle(section).map_err(CLIError::from)?; @@ -146,6 +170,12 @@ fn err_main() -> Result<(), CLIError> { eprintln!("MD5: {:x}", digest); write_with_format(output_file, &firmware, format).map_err(CLIError::from)?; + + eprintln!( + "Successfully read {} bytes - {}", + firmware.len(), + output_file + ); } Some(("write", sub_matches)) => { let input_file = sub_matches @@ -160,19 +190,35 @@ fn err_main() -> Result<(), CLIError> { let format = get_format_from_matches(sub_matches, input_file, "format"); - let part = get_part_from_matches(sub_matches); + let device_spec = get_device_spec_from_matches(sub_matches); let mut firmware = read_with_format(input_file, format).map_err(CLIError::from)?; - if firmware.len() < part.firmware_size { - firmware.resize(part.firmware_size, 0); + if firmware.len() < device_spec.platform.firmware_size { + eprintln!( + "Warning: firmware size is less than expected ({}). It will be resized to {} and filled with 0", + firmware.len(), + device_spec.platform.firmware_size + ); + let confirmation = Confirm::new() + .with_prompt("Are you sure you want to continue?") + .default(false) + .interact() + .unwrap(); + + if !confirmation { + return Ok(()); + } + firmware.resize(device_spec.platform.firmware_size, 0); } let mut ds = DeviceSelector::new().map_err(CLIError::DeviceSelectorError)?; let device = ds - .try_fetch_isp_device(part, retry_count) + .try_fetch_isp_device(device_spec, retry_count) .map_err(CLIError::from)?; device.write_cycle(&mut firmware).map_err(CLIError::from)?; + + eprintln!("Successfully wrote {} bytes", firmware.len()); } Some(("list", sub_matches)) => { let vendor_id = sub_matches.get_one::("vendor_id"); @@ -220,38 +266,38 @@ fn err_main() -> Result<(), CLIError> { let input_format = get_format_from_matches(sub_matches, input_file, "input_format"); let output_format = get_format_from_matches(sub_matches, output_file, "output_format"); - let part = get_part_from_matches(sub_matches); + let device_spec = get_device_spec_from_matches(sub_matches); let mut firmware = read_with_format(input_file, input_format).map_err(CLIError::from)?; - if firmware.len() < part.firmware_size { + if firmware.len() < device_spec.platform.firmware_size { log::warn!( "Firmware size is less than expected ({}). Increasing to {}", firmware.len(), - part.firmware_size + device_spec.platform.firmware_size ); - firmware.resize(part.firmware_size, 0); + firmware.resize(device_spec.platform.firmware_size, 0); } match direction { "to_jtag" => { - convert_to_jtag_payload(&mut firmware, part).map_err(CLIError::from)?; - if firmware.len() < part.total_flash_size() { + convert_to_jtag_payload(&mut firmware, device_spec).map_err(CLIError::from)?; + if firmware.len() < device_spec.total_flash_size() { eprintln!( "Firmware is smaller ({} bytes) than expected ({} bytes). This payload might not be suitable for JTAG flashing.", firmware.len(), - part.total_flash_size() + device_spec.total_flash_size() ); } } "to_isp" => { - convert_to_isp_payload(&mut firmware, part).map_err(CLIError::from)?; - if firmware.len() > part.firmware_size { + convert_to_isp_payload(&mut firmware, device_spec).map_err(CLIError::from)?; + if firmware.len() > device_spec.platform.firmware_size { eprintln!( "Firmware size is larger ({} bytes) than expected ({} bytes). This payload might not be suitable for ISP flashing.", firmware.len(), - part.firmware_size + device_spec.platform.firmware_size ); } } @@ -265,32 +311,37 @@ fn err_main() -> Result<(), CLIError> { Ok(()) } -trait PartCommand { - fn part_args(self) -> Command; +trait DeviceCommand { + fn device_args(self) -> Command; } -impl PartCommand for Command { - fn part_args(self) -> Command { +impl DeviceCommand for Command { + fn device_args(self) -> Command { self.arg( - arg!(-p --part ) - .value_parser(Part::available_parts()) - .required_unless_present_all(["firmware_size", "vendor_id", "product_id"]), + arg!(-d --device ) + .required_unless_present_all(["platform", "vendor_id", "product_id"]) + .value_parser(DeviceSpec::available_devices()), ) .arg( - arg!(--firmware_size ) - .required_unless_present("part") - .value_parser(maybe_hex::), + arg!(-p --platform ) + .value_parser(PlatformSpec::available_platforms()) + .required_unless_present("device"), ) .arg( arg!(--vendor_id ) - .required_unless_present("part") + .required_unless_present("device") .value_parser(maybe_hex::), ) .arg( arg!(--product_id ) - .required_unless_present("part") + .required_unless_present("device") .value_parser(maybe_hex::), ) + .arg( + arg!(--firmware_size ) + .required_unless_present_any(["device", "platform"]) + .value_parser(maybe_hex::), + ) .arg(arg!(--bootloader_size ).value_parser(maybe_hex::)) .arg(arg!(--page_size ).value_parser(maybe_hex::)) .arg(arg!(--isp_iface_num ).value_parser(clap::value_parser!(i32))) @@ -319,11 +370,7 @@ fn get_format_from_matches( let format = sub_matches .get_one::(format_option) .map(|s| s.as_str()) - .map(|format| match format { - "ihex" => Format::IntelHex, - "bin" => Format::Binary, - _ => panic!("Invalid format"), - }) + .map(|f| Format::from_str(f).unwrap()) .unwrap_or(assumed_format); match (assumed_format, format) { @@ -342,48 +389,66 @@ fn get_format_from_matches( format } -fn get_part_from_matches(sub_matches: &ArgMatches) -> Part { - let part_name = sub_matches.get_one::("part").map(|s| s.as_str()); +fn get_device_spec_from_matches(sub_matches: &ArgMatches) -> DeviceSpec { + let device_name = sub_matches.get_one::("device").map(|s| s.as_str()); + let platform_name = sub_matches + .get_one::("platform") + .map(|s| s.as_str()); - let mut part = match part_name { - Some(part_name) => *PARTS.get(part_name).unwrap(), - _ => PART_BASE_DEFAULT, - }; + let vendor_id = sub_matches.get_one::("vendor_id"); + let product_id = sub_matches.get_one::("product_id"); let firmware_size = sub_matches.get_one::("firmware_size"); let bootloader_size = sub_matches.get_one::("bootloader_size"); let page_size = sub_matches.get_one::("page_size"); - let vendor_id = sub_matches.get_one::("vendor_id"); - let product_id = sub_matches.get_one::("product_id"); + let isp_iface_num = sub_matches.get_one::("isp_iface_num"); let isp_report_id = sub_matches.get_one::("isp_report_id"); let reboot = sub_matches.get_one::("reboot"); - if let Some(firmware_size) = firmware_size { - part.firmware_size = *firmware_size; + let mut device_spec = None; + if let Some(device_name) = device_name { + device_spec = Some(*DEVICES.get(device_name).unwrap()); + } + + if let Some(platform_name) = platform_name { + device_spec = match platform_name { + "sh68f90" => Some(DEVICE_BASE_SH68F90), + "sh68f91" => Some(DEVICE_BASE_SH68F881), + _ => panic!("Invalid platform"), + } } + + let mut device_spec = device_spec.unwrap(); + if let Some(vendor_id) = vendor_id { - part.vendor_id = *vendor_id; + device_spec.vendor_id = *vendor_id; } if let Some(product_id) = product_id { - part.product_id = *product_id; + device_spec.product_id = *product_id; + } + + if let Some(firmware_size) = firmware_size { + device_spec.platform.firmware_size = *firmware_size; } if let Some(bootloader_size) = bootloader_size { - part.bootloader_size = *bootloader_size; + device_spec.platform.bootloader_size = *bootloader_size; } if let Some(page_size) = page_size { - part.page_size = *page_size; + device_spec.platform.page_size = *page_size; } + if let Some(isp_iface_num) = isp_iface_num { - part.isp_iface_num = *isp_iface_num; + device_spec.isp_iface_num = *isp_iface_num; } if let Some(isp_report_id) = isp_report_id { - part.isp_report_id = *isp_report_id; + device_spec.isp_report_id = *isp_report_id; } if let Some(reboot) = reboot { - part.reboot = *reboot; + device_spec.reboot = *reboot; } - part + + device_spec } fn read_with_format(file: &str, format: Format) -> Result, CLIError> { diff --git a/src/part.rs b/src/part.rs deleted file mode 100644 index c8b0178..0000000 --- a/src/part.rs +++ /dev/null @@ -1,323 +0,0 @@ -use phf::{phf_map, Map}; - -#[derive(Clone, Copy, PartialEq)] -pub struct Part { - pub firmware_size: usize, - pub bootloader_size: usize, - pub page_size: usize, - pub vendor_id: u16, - pub product_id: u16, - - /// USB interface number with the ISP report - pub isp_iface_num: i32, - /// HID report ID - pub isp_report_id: u32, - - pub reboot: bool, -} - -pub const PART_BASE_DEFAULT: Part = Part { - firmware_size: 0, - bootloader_size: 4096, - page_size: 2048, - - vendor_id: 0x0000, - product_id: 0x0000, - - isp_iface_num: 1, - isp_report_id: 5, - - reboot: true, -}; - -pub const PART_BASE_SH68F90: Part = Part { - firmware_size: 61440, // 61440 until bootloader - ..PART_BASE_DEFAULT -}; - -pub const PART_BASE_SH68F881: Part = Part { - firmware_size: 28672, // 28672 until bootloader - ..PART_BASE_DEFAULT -}; - -pub const PART_NUPHY_AIR60: Part = Part { - vendor_id: 0x05ac, - product_id: 0x024f, - ..PART_BASE_SH68F90 -}; - -pub const PART_LEOBOG_HI75: Part = Part { - vendor_id: 0x258a, - product_id: 0x010c, - ..PART_BASE_SH68F90 -}; - -pub const PART_AULA_F75: Part = Part { - vendor_id: 0x258a, - product_id: 0x010c, - ..PART_BASE_SH68F90 -}; - -pub const PART_AULA_F87: Part = Part { - vendor_id: 0x258a, - product_id: 0x010c, - ..PART_BASE_SH68F90 -}; - -pub const PART_XINMENG_K916: Part = Part { - vendor_id: 0x258a, - product_id: 0x00a1, - ..PART_BASE_SH68F90 -}; - -pub const PART_XINMENG_XM_RF68: Part = Part { - vendor_id: 0x258a, - product_id: 0x002a, - ..PART_BASE_SH68F90 -}; - -pub const PART_XINMENG_M71: Part = Part { - vendor_id: 0x258a, - product_id: 0x010c, - ..PART_BASE_SH68F90 -}; - -pub const PART_RE_K70_BYK800: Part = Part { - vendor_id: 0x258a, - product_id: 0x001a, - ..PART_BASE_SH68F881 -}; - -pub const PART_TERPORT_TR95: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub const PART_REDRAGON_FIZZ_K617: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub const PART_REDRAGON_ANIVIA_K614: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub const PART_REDRAGON_K641_SHACO_PRO: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub const PART_GENESIS_THOR_300: Part = Part { - vendor_id: 0x258a, - product_id: 0x001f, - ..PART_BASE_SH68F881 -}; - -pub const PART_GENESIS_THOR_300_RGB: Part = Part { - vendor_id: 0x258a, - product_id: 0x0090, - ..PART_BASE_SH68F90 -}; - -pub const PART_ROYALKLUDGE_RK61: Part = Part { - vendor_id: 0x258a, - product_id: 0x00c7, - ..PART_BASE_SH68F90 -}; - -pub const PART_ROYALKLUDGE_RK68_ISO_RETURN: Part = Part { - vendor_id: 0x258a, - product_id: 0x00a9, - ..PART_BASE_SH68F90 -}; - -pub const PART_ROYALKLUDGE_RK68_BT_DUAL: Part = Part { - vendor_id: 0x258a, - product_id: 0x008b, - ..PART_BASE_SH68F90 -}; - -pub const PART_ROYALKLUDGE_RKG68: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub const PART_ROYALKLUDGE_RK71: Part = Part { - vendor_id: 0x258a, - product_id: 0x00ea, - ..PART_BASE_SH68F90 -}; - -pub const PART_ROYALKLUDGE_RK84_ISO_RETURN: Part = Part { - vendor_id: 0x258a, - product_id: 0x00f4, - ..PART_BASE_SH68F90 -}; - -pub const PART_ROYALKLUDGE_RK100: Part = Part { - vendor_id: 0x258a, - product_id: 0x0056, - ..PART_BASE_SH68F90 -}; - -pub const PART_DELTACO_WK95R: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub const PART_DIGITALALLIANCE_MECA_WARRIOR_X: Part = Part { - vendor_id: 0x258a, - product_id: 0x0090, - ..PART_BASE_SH68F90 -}; - -pub const PART_KZZI_K68PRO: Part = Part { - vendor_id: 0x258a, - product_id: 0x0186, - ..PART_BASE_SH68F90 -}; - -pub const PART_WEIKAV_SUGAR65: Part = Part { - vendor_id: 0x05ac, - product_id: 0x024f, - ..PART_BASE_SH68F90 -}; - -pub const PART_TRUST_GXT_960: Part = Part { - vendor_id: 0x145f, - product_id: 0x02b6, - ..PART_BASE_SH68F90 -}; - -pub const PART_GLORIOUS_MODEL_O: Part = Part { - vendor_id: 0x258a, - product_id: 0x0036, - ..PART_BASE_SH68F90 -}; - -pub const PART_MACHENIKE_K500_B61: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub const PART_MAGEGEE_MKSTAR61: Part = Part { - vendor_id: 0x258a, - product_id: 0x010c, - ..PART_BASE_SH68F90 -}; - -pub const PART_REDRAGON_K658_PRO_SE: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub const PART_REDRAGON_K530_DRACONIC_PRO: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub const PART_REDRAGON_K630_NO_RGB: Part = Part { - vendor_id: 0x258a, - product_id: 0x002a, - ..PART_BASE_SH68F90 -}; - -pub const PART_EYOOSO_Z11: Part = Part { - vendor_id: 0x258a, - product_id: 0x002a, - ..PART_BASE_SH68F90 -}; - -pub const PART_PORTRONICS_HYDRA10: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub const PART_REDRAGON_K633_RYZE: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub const PART_YINREN_R108: Part = Part { - vendor_id: 0x258a, - product_id: 0x0049, - ..PART_BASE_SH68F90 -}; - -pub static PARTS: Map<&'static str, Part> = phf_map! { - "aula-f75" => PART_AULA_F75, - "aula-f87" => PART_AULA_F87, - "deltaco-wk95r" => PART_DELTACO_WK95R, - "digitalalliance-meca-warrior-x" => PART_DIGITALALLIANCE_MECA_WARRIOR_X, - "eyooso-z11" => PART_EYOOSO_Z11, - "genesis-thor-300-rgb" => PART_GENESIS_THOR_300_RGB, - "genesis-thor-300" => PART_GENESIS_THOR_300, - "glorious-model-o" => PART_GLORIOUS_MODEL_O, - "kzzi-k68pro" => PART_KZZI_K68PRO, - "leobog-hi75" => PART_LEOBOG_HI75, - "machenike-k500-b61" => PART_MACHENIKE_K500_B61, - "magegee-mkstar61" => PART_MAGEGEE_MKSTAR61, - "nuphy-air60" => PART_NUPHY_AIR60, - "nuphy-air75" => PART_NUPHY_AIR60, // same as nuphy-air60 - "nuphy-air96" => PART_NUPHY_AIR60, // same as nuphy-air60 - "nuphy-halo65" => PART_NUPHY_AIR60, // same as nuphy-air60 - "portronics-hydra10" => PART_PORTRONICS_HYDRA10, - "re-k70-byk800" => PART_RE_K70_BYK800, - "redragon-k530-draconic-pro" => PART_REDRAGON_K530_DRACONIC_PRO, - "redragon-k630-norgb" => PART_REDRAGON_K630_NO_RGB, - "redragon-k614-anivia" => PART_REDRAGON_ANIVIA_K614, - "redragon-k617-fizz" => PART_REDRAGON_FIZZ_K617, - "redragon-k633-ryze" => PART_REDRAGON_K633_RYZE, - "redragon-k641-shaco-pro" => PART_REDRAGON_K641_SHACO_PRO, - "redragon-k658-pro-se" => PART_REDRAGON_K658_PRO_SE, - "royalkludge-rk100" => PART_ROYALKLUDGE_RK100, - "royalkludge-rk61" => PART_ROYALKLUDGE_RK61, - "royalkludge-rk68-bt-dual" => PART_ROYALKLUDGE_RK68_BT_DUAL, - "royalkludge-rk68-iso-return" => PART_ROYALKLUDGE_RK68_ISO_RETURN, - "royalkludge-rk71" => PART_ROYALKLUDGE_RK71, - "royalkludge-rk84-iso-return" => PART_ROYALKLUDGE_RK84_ISO_RETURN, - "royalkludge-rkg68" => PART_ROYALKLUDGE_RKG68, - "terport-tr95" => PART_TERPORT_TR95, - "trust-gxt-960" => PART_TRUST_GXT_960, - "weikav-sugar65" => PART_WEIKAV_SUGAR65, - "xinmeng-k916" => PART_XINMENG_K916, - "xinmeng-m66" => PART_XINMENG_M71, - "xinmeng-m71" => PART_XINMENG_M71, - "xinmeng-xm-rf68" => PART_XINMENG_XM_RF68, - "yinren-r108" => PART_YINREN_R108, - "yunzii-al66" => PART_XINMENG_M71, // same as xinmeng-m71 - "yunzii-al71" => PART_XINMENG_M71, // same as xinmeng-m71 -}; - -impl Part { - pub fn available_parts() -> Vec<&'static str> { - let mut parts = PARTS.keys().copied().collect::>(); - parts.sort(); - parts - } - - pub fn num_pages(&self) -> usize { - self.firmware_size / self.page_size - } - - pub fn total_flash_size(&self) -> usize { - self.firmware_size + self.bootloader_size - } -} - -#[test] -fn test_num_pages() { - assert_eq!(PART_NUPHY_AIR60.num_pages(), 30) -} diff --git a/src/platform_spec.rs b/src/platform_spec.rs new file mode 100644 index 0000000..4f21adc --- /dev/null +++ b/src/platform_spec.rs @@ -0,0 +1,40 @@ +use phf::{phf_map, Map}; + +const DEFAULT_BOOTLOADER_SIZE: usize = 4096; +const DEFAULT_PAGE_SIZE: usize = 2048; + +#[derive(Clone, Copy, PartialEq)] +pub struct PlatformSpec { + pub firmware_size: usize, + pub bootloader_size: usize, + pub page_size: usize, +} + +const PLATFORM_DEFAULT: PlatformSpec = PlatformSpec { + firmware_size: 0, + bootloader_size: DEFAULT_BOOTLOADER_SIZE, + page_size: DEFAULT_PAGE_SIZE, +}; + +pub const PLATFORM_SH68F90: PlatformSpec = PlatformSpec { + firmware_size: 65536 - PLATFORM_DEFAULT.bootloader_size, // 61440 until bootloader + ..PLATFORM_DEFAULT +}; + +pub const PLATFORM_SH68F881: PlatformSpec = PlatformSpec { + firmware_size: 32768 - PLATFORM_DEFAULT.bootloader_size, // 28672 until bootloader + ..PLATFORM_DEFAULT +}; + +pub static PLATFORMS: Map<&'static str, PlatformSpec> = phf_map! { + "sh68f90" => PLATFORM_SH68F90, + "sh68f881" => PLATFORM_SH68F881, +}; + +impl PlatformSpec { + pub fn available_platforms() -> Vec<&'static str> { + let mut platforms = PLATFORMS.keys().copied().collect::>(); + platforms.sort(); + platforms + } +} diff --git a/src/util.rs b/src/util.rs index b32e609..50694c4 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,10 +1,10 @@ -use crate::part::Part; +use crate::DeviceSpec; use hidapi::HidError; use log::error; use thiserror::Error; #[cfg(test)] -use crate::part::PART_BASE_SH68F90; +use crate::device_spec::DEVICE_BASE_SH68F90; #[derive(Debug, Clone, Error, PartialEq)] pub enum VerificationError { @@ -47,7 +47,10 @@ pub enum PayloadConversionError { UnexpectedAddressError { source_addr: u16, target_addr: u16 }, } -pub fn convert_to_jtag_payload(input: &mut [u8], part: Part) -> Result<(), PayloadConversionError> { +pub fn convert_to_jtag_payload( + input: &mut [u8], + device_spec: DeviceSpec, +) -> Result<(), PayloadConversionError> { if input[0] != 0x02 { return Err(PayloadConversionError::LJMPNotFoundError { addr: 0x0000 }); } @@ -60,8 +63,8 @@ pub fn convert_to_jtag_payload(input: &mut [u8], part: Part) -> Result<(), Paylo }); } - let bootloader_ljmp_addr = (part.firmware_size as u16).to_be_bytes(); - let ljmp_addr = part.firmware_size - 5; + let bootloader_ljmp_addr = (device_spec.platform.firmware_size as u16).to_be_bytes(); + let ljmp_addr = device_spec.platform.firmware_size - 5; input[1..3].copy_from_slice(&bootloader_ljmp_addr); input[ljmp_addr] = 0x02; @@ -70,12 +73,15 @@ pub fn convert_to_jtag_payload(input: &mut [u8], part: Part) -> Result<(), Paylo Ok(()) } -pub fn convert_to_isp_payload(input: &mut [u8], part: Part) -> Result<(), PayloadConversionError> { +pub fn convert_to_isp_payload( + input: &mut [u8], + device_spec: DeviceSpec, +) -> Result<(), PayloadConversionError> { if input[0] != 0x02 { return Err(PayloadConversionError::LJMPNotFoundError { addr: 0 }); } - let ljmp_addr = part.firmware_size - 5; + let ljmp_addr = device_spec.platform.firmware_size - 5; if input[ljmp_addr] != 0x02 { return Err(PayloadConversionError::LJMPNotFoundError { addr: 0x0000 }); } @@ -142,13 +148,13 @@ fn test_verify_error_byte_mismatch() { #[test] fn test_convert_to_jtag_payload() { - let part = PART_BASE_SH68F90; + let device_spec = DEVICE_BASE_SH68F90; let mut firmware: [u8; 65536] = [0; 65536]; firmware[0] = 0x02; firmware[1] = 0x00; firmware[2] = 0x66; - convert_to_jtag_payload(&mut firmware, part).unwrap(); + convert_to_jtag_payload(&mut firmware, device_spec).unwrap(); assert_eq!(firmware[0..3], [0x02, 0xf0, 0x00]); assert_eq!(firmware[0xeffb..0xeffe], [0x02, 0x00, 0x66]); @@ -156,7 +162,7 @@ fn test_convert_to_jtag_payload() { #[test] fn test_convert_to_isp_payload() { - let part = PART_BASE_SH68F90; + let device_spec = DEVICE_BASE_SH68F90; let mut firmware: [u8; 65536] = [0; 65536]; firmware[0] = 0x02; firmware[1] = 0xf0; @@ -165,7 +171,7 @@ fn test_convert_to_isp_payload() { firmware[0xeffc] = 0x00; firmware[0xeffd] = 0x66; - convert_to_isp_payload(&mut firmware, part).unwrap(); + convert_to_isp_payload(&mut firmware, device_spec).unwrap(); assert_eq!(firmware[0..3], [0x02, 0x00, 0x66]); assert_eq!(firmware[0xeffb..0xeffe], [0x00, 0x00, 0x00]); diff --git a/tests/convert_test.rs b/tests/convert_test.rs index 50d0d4a..a9330fc 100644 --- a/tests/convert_test.rs +++ b/tests/convert_test.rs @@ -16,7 +16,7 @@ fn test_convert_to_jtag() { let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = cmd .arg("convert") - .args(&["--part", "nuphy-air60"]) + .args(&["--device", "nuphy-air60"]) .args(&["--direction", "to_jtag"]) .arg(&input_file) .arg(&output_file) @@ -39,7 +39,7 @@ fn test_convert_to_isp() { let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = cmd .arg("convert") - .args(&["--part", "nuphy-air60"]) + .args(&["--device", "nuphy-air60"]) .args(&["--direction", "to_isp"]) .arg(&input_file) .arg(&output_file) @@ -62,7 +62,7 @@ fn test_convert_to_jtag_bin() { let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = cmd .arg("convert") - .args(&["--part", "nuphy-air60"]) + .args(&["--device", "nuphy-air60"]) .args(&["--direction", "to_jtag"]) .arg(&input_file) .arg(&output_file) diff --git a/tests/list_test.rs b/tests/list_test.rs index fa2bd9d..827ca27 100644 --- a/tests/list_test.rs +++ b/tests/list_test.rs @@ -7,7 +7,7 @@ ID 05ac:024f manufacturer=\"contact@carlossless.io\" product=\"SMK Keyboard\" path=\".+\" interface_number=0 report_descriptor=\\[05 01 09 06 A1 01 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 75 08 95 01 81 01 05 07 19 00 29 FF 15 00 26 FF 00 75 08 95 06 81 00 05 08 19 01 29 05 15 00 25 01 75 01 95 05 91 02 75 03 95 01 91 01 C0\\] feature_report_ids=\\[\\] - usage_page=0x0001 usage=0x0006\ + usage_page=0x0001 usage=0x0006 path=\".+\" interface_number=1 report_descriptor=\\[05 01 09 80 A1 01 85 01 19 81 29 83 15 00 25 01 75 01 95 03 81 02 95 05 81 01 C0 05 0C 09 01 A1 01 85 02 19 00 2A 3C 02 15 00 26 3C 02 75 10 95 01 81 00 C0 06 00 FF 09 01 A1 01 85 05 19 01 29 02 15 00 26 FF 00 75 08 95 05 B1 02 C0 05 01 09 06 A1 01 85 06 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 05 07 19 00 29 9F 15 00 25 01 75 01 95 A0 81 02 C0\\] feature_report_ids=\\[5\\] diff --git a/tests/read_test.rs b/tests/read_test.rs index 5336140..ca89cae 100644 --- a/tests/read_test.rs +++ b/tests/read_test.rs @@ -13,7 +13,7 @@ fn test_read() { let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = cmd .arg("read") - .args(&["--part", "nuphy-air60"]) + .args(&["--device", "nuphy-air60"]) .arg(&file) .assert(); assert.success().stderr(predicates::str::contains( @@ -34,7 +34,7 @@ fn test_read_bin() { let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = cmd .arg("read") - .args(&["--part", "nuphy-air60"]) + .args(&["--device", "nuphy-air60"]) .arg(&file) .assert(); assert.success().stderr(predicates::str::contains( @@ -55,7 +55,7 @@ fn test_read_bootloader() { let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = cmd .arg("read") - .args(&["--part", "nuphy-air60"]) + .args(&["--device", "nuphy-air60"]) .args(&["--section", "bootloader"]) .arg(&file) .assert(); @@ -77,7 +77,7 @@ fn test_read_full() { let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = cmd .arg("read") - .args(&["--part", "nuphy-air60"]) + .args(&["--device", "nuphy-air60"]) .args(&["--section", "full"]) .arg(&file) .assert(); @@ -99,6 +99,7 @@ fn test_read_custom_part() { let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = cmd .arg("read") + .args(&["--platform", "sh68f90"]) .args(&["--vendor_id", "0x05ac"]) .args(&["--product_id", "0x024f"]) .args(&["--isp_iface_num", "1"]) @@ -124,7 +125,7 @@ fn test_read_forced_format_bin() { let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = cmd .arg("read") - .args(&["--part", "nuphy-air60"]) + .args(&["--device", "nuphy-air60"]) .args(&["--format", "bin"]) .arg(&file) .assert(); diff --git a/tests/write_test.rs b/tests/write_test.rs index 00a8fa9..84238ed 100644 --- a/tests/write_test.rs +++ b/tests/write_test.rs @@ -15,7 +15,7 @@ fn test_write() { let mut cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = cmd .arg("write") - .args(&["--part", "nuphy-air60"]) + .args(&["--device", "nuphy-air60"]) .arg(file) .assert(); assert.success(); @@ -28,7 +28,7 @@ fn test_write_and_readback() { let mut write_cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = write_cmd .arg("write") - .args(&["--part", "nuphy-air60"]) + .args(&["--device", "nuphy-air60"]) .arg(&fixture_file) .assert(); assert.success(); @@ -37,7 +37,7 @@ fn test_write_and_readback() { let mut read_cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = read_cmd .arg("read") - .args(&["--part", "nuphy-air60"]) + .args(&["--device", "nuphy-air60"]) .arg(&output_file) .assert(); assert.success().stderr(predicates::str::contains( @@ -58,11 +58,12 @@ fn test_write_custom_and_readback() { let mut write_cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = write_cmd .arg("write") + .args(&["--platform", "sh68f90"]) .args(&["--vendor_id", "0x05ac"]) .args(&["--product_id", "0x024f"]) + .args(&["--firmware_size", "61440"]) .args(&["--isp_iface_num", "1"]) .args(&["--isp_report_id", "5"]) - .args(&["--firmware_size", "61440"]) .arg(&fixture_file) .assert(); assert.success(); @@ -71,11 +72,12 @@ fn test_write_custom_and_readback() { let mut read_cmd = Command::cargo_bin("sinowealth-kb-tool").unwrap(); let assert = read_cmd .arg("read") + .args(&["--platform", "sh68f90"]) .args(&["--vendor_id", "0x05ac"]) .args(&["--product_id", "0x024f"]) + .args(&["--firmware_size", "61440"]) .args(&["--isp_iface_num", "1"]) .args(&["--isp_report_id", "5"]) - .args(&["--firmware_size", "61440"]) .arg(&output_file) .assert(); assert.success().stderr(predicates::str::contains( From d415f43790a566f24e6260df8aae4cf6ba2ea05b Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 4 May 2025 21:01:32 +0200 Subject: [PATCH 119/141] device lookup, retry only in specfic cases, readme update (#106) --- README.md | 24 +++--- src/device_selector.rs | 173 +++++++++++++++++++++++++++++++---------- src/main.rs | 86 ++++++++++++-------- 3 files changed, 193 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 473def9..27a9b52 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ [![crate](https://img.shields.io/crates/v/sinowealth-kb-tool.svg)](https://crates.io/crates/sinowealth-kb-tool) [![ci](https://github.com/carlossless/sinowealth-kb-tool/actions/workflows/push.yml/badge.svg)](https://github.com/carlossless/sinowealth-kb-tool/actions/workflows/push.yml) -A utility for reading and writing flash contents on Sinowealth 8051-based devices (keyboards and mice) since they all seem to have similar ISP bootloaders. +A utility for reading and writing flash contents on Sinowealth 8051-based USB HID devices (keyboards and mice) through the commonly found ISP bootloader. ## Disclaimer -This is an experimental tool, so use it at your own risk. +I offer no guarantees that using this tool won't brick your device. Use this tool at your risk. ## Usage @@ -18,25 +18,24 @@ This is an experimental tool, so use it at your own risk. ```sh # reads firmware excluding isp bootloader -sinowealth-kb-tool read -p nuphy-air60 foobar.hex +sinowealth-kb-tool read -d nuphy-air60 foobar.hex # reads only isp bootloader section -sinowealth-kb-tool read -p nuphy-air60 -b bootloader.hex +sinowealth-kb-tool read -d nuphy-air60 -b bootloader.hex # full dump including firmware and bootloader -sinowealth-kb-tool read -p nuphy-air60 --full full.hex +sinowealth-kb-tool read -d nuphy-air60 --full full.hex # custom device sinowealth-kb-tool read \ + --platform sh68f90 \ --vendor_id 0x05ac \ --product_id 0x024f \ - --firmware_size 61440 \ + --firmware_size 61440 \ # optional --bootloader_size 4096 \ # optional --page_size 2048 \ # optional --isp_iface_num 1 \ # optional - --isp_usage_page 0xff00 \ # optional - --isp_usage 0x0001 \ # optional - --isp_index 0 \ # optional + --isp_report_id 5 \ # optional --reboot false \ # optional foobar.hex ``` @@ -51,15 +50,14 @@ sinowealth-kb-tool write -p nuphy-air60 foobar.hex # custom device sinowealth-kb-tool write \ + --platform sh68f90 \ --vendor_id 0x05ac \ --product_id 0x024f \ - --firmware_size 61440 \ + --firmware_size 61440 \ # optional --bootloader_size 4096 \ # optional --page_size 2048 \ # optional --isp_iface_num 1 \ # optional - --isp_usage_page 0xff00 \ # optional - --isp_usage 0x0001 \ # optional - --isp_index 0 \ # optional + --isp_report_id 5 \ # optional --reboot false \ # optional foobar.hex ``` diff --git a/src/device_selector.rs b/src/device_selector.rs index d6fd767..8ea3542 100644 --- a/src/device_selector.rs +++ b/src/device_selector.rs @@ -19,7 +19,6 @@ use crate::hid_tree::ItemNode; const REPORT_ID_ISP: u8 = 0x05; const CMD_ISP_MODE: u8 = 0x75; -#[cfg(target_os = "windows")] const REPORT_ID_XFER: u8 = 0x06; const GAMING_KB_VENDOR_ID: u16 = 0x0603; @@ -37,6 +36,8 @@ pub enum DeviceSelectorError { HidError(#[from] HidError), #[error("Failed to parse report descriptor {0:?}")] ReportDescriptorError(hidparser::report_descriptor_parser::ReportDescriptorError), + #[error("Unexpected device count")] + UnexpectedDeviceCount, } pub struct DeviceSelector { @@ -81,6 +82,19 @@ impl DeviceSelector { devices } + fn unique_usb_device_list(&self) -> Vec<&DeviceInfo> { + let mut devices: Vec<_> = self.sorted_usb_device_list(); + devices.dedup_by_key(|d| { + ( + d.vendor_id(), + d.product_id(), + d.interface_number(), + d.path(), + ) + }); + devices + } + fn get_feature_report_ids_from_path( &self, path: &CStr, @@ -156,24 +170,61 @@ impl DeviceSelector { (descriptor, feature_report_ids) } - fn get_device_for_report_id<'a, I: IntoIterator>( + #[cfg(target_os = "windows")] + fn get_devices_for_report_ids<'a, I: IntoIterator>( &self, devices: I, - report_id: u32, - ) -> Result<&'a DeviceInfo, DeviceSelectorError> { + report_ids: &[u32], + ) -> Result, DeviceSelectorError> { + let mut matched_devices: Vec> = vec![None; report_ids.len()]; + for d in devices { - let ids = self.get_feature_report_ids_from_path(d.path())?; - for id in ids { - if id == report_id { - return Ok(d); + let retrieved_ids = self.get_feature_report_ids_from_path(d.path())?; + for id in retrieved_ids { + for (i, expected_id) in report_ids.iter().enumerate() { + if id == *expected_id { + if matched_devices[i].is_some() { + return Err(DeviceSelectorError::UnexpectedDeviceCount); + } + matched_devices[i] = Some(d); + } } } } - Err(DeviceSelectorError::NotFound) + + if matched_devices.iter().all(|d| d.is_some()) { + let matched_devices: Vec<&DeviceInfo> = + matched_devices.into_iter().map(|d| d.unwrap()).collect(); + Ok(matched_devices) + } else { + Err(DeviceSelectorError::NotFound) + } + } + + #[cfg(any(target_os = "macos", target_os = "linux"))] + fn get_device_for_report_ids<'a, I: IntoIterator>( + &self, + devices: I, + report_ids: &[u32], + ) -> Result<&'a DeviceInfo, DeviceSelectorError> { + let mut matching_devices = vec![]; + + for d in devices { + let retrieved_ids = self.get_feature_report_ids_from_path(d.path())?; + if report_ids.iter().all(|id| retrieved_ids.contains(id)) { + matching_devices.push(d); + } + } + + match matching_devices.len() { + 1 => Ok(matching_devices[0]), + len if len > 1 => Err(DeviceSelectorError::UnexpectedDeviceCount), + _ => Err(DeviceSelectorError::NotFound), + } } fn find_isp_device(&self, device_spec: DeviceSpec) -> Result { - let sorted_devices = self.sorted_usb_device_list(); + let sorted_devices = self.unique_usb_device_list(); let isp_devices: Vec<_> = sorted_devices .clone() .into_iter() @@ -192,32 +243,50 @@ impl DeviceSelector { return Err(DeviceSelectorError::NotFound); } - let s = isp_devices.clone(); - // TODO: check for both feature report IDs in macOS and Linux and get that device - // TODO: check for each feature report IDs in Windows and get each of those devices - let cmd_device = self.get_device_for_report_id(s, REPORT_ID_ISP as u32)?; - debug!("CMD device: {}", cmd_device.info()); - #[cfg(not(target_os = "windows"))] - return Ok(ISPDevice::new( - device_spec, - self.api.open_path(cmd_device.path()).unwrap(), - )); + #[cfg(any(target_os = "macos", target_os = "linux"))] + return { + let device = self.get_device_for_report_ids( + isp_devices.clone(), + &[REPORT_ID_ISP as u32, REPORT_ID_XFER as u32], + )?; + debug!("ISP device: {}", device.info()); + + let handle = self + .api + .open_path(device.path()) + .map_err(DeviceSelectorError::from)?; + + Ok(ISPDevice::new(device_spec, handle)) + }; #[cfg(target_os = "windows")] - { - let xfer_device = - self.get_device_for_report_id(isp_devices.clone(), REPORT_ID_XFER as u32)?; - debug!("XFER device: {}", xfer_device.info()); - return Ok(ISPDevice::new( - device_spec, - self.api.open_path(cmd_device.path()).unwrap(), - self.api.open_path(xfer_device.path()).unwrap(), - )); - } + return { + let devices = self.get_devices_for_report_ids( + isp_devices.clone(), + &[REPORT_ID_ISP as u32, REPORT_ID_XFER as u32], + )?; + + let cmd_device = devices[0]; + debug!("ISP CMD device: {}", cmd_device.info()); + + let xfer_device = devices[1]; + debug!("ISP XFER device: {}", xfer_device.info()); + + let cmd_handle = self + .api + .open_path(cmd_device.path()) + .map_err(DeviceSelectorError::from)?; + let xfer_handle = self + .api + .open_path(xfer_device.path()) + .map_err(DeviceSelectorError::from)?; + + Ok(ISPDevice::new(device_spec, cmd_handle, xfer_handle)) + }; } fn find_device(&self, device_spec: DeviceSpec) -> Result { - let filtered_devices = self.sorted_usb_device_list().into_iter().filter(|d| { + let filtered_devices = self.unique_usb_device_list().into_iter().filter(|d| { d.vendor_id() == device_spec.vendor_id && d.product_id() == device_spec.product_id && d.interface_number() == device_spec.isp_iface_num @@ -296,30 +365,48 @@ impl DeviceSelector { bar.enable_steady_tick(Duration::from_millis(100)); for attempt in 1..retries + 1 { - self.api.refresh_devices()?; if attempt > 1 { - thread::sleep(time::Duration::from_millis(1000)); bar.set_message(format!("Retrying... Attempt {}/{}", attempt, retries)); info!("Retrying... Attempt {}/{}", attempt, retries); + self.api.refresh_devices()?; + thread::sleep(time::Duration::from_millis(1000)); + } + + match self.find_device(device_spec) { + Ok(device) => { + bar.set_message("Device found. Switching to ISP mode..."); + match self.switch_to_isp_device(device, device_spec) { + Ok(isp_device) => { + bar.finish_and_clear(); + eprintln!("Connected!"); + return Ok(isp_device); + } + Err(DeviceSelectorError::NotFound) => {} + Err(err) => { + return Err(err); + } + } + } + Err(DeviceSelectorError::NotFound) => {} + Err(err) => { + return Err(err); + } } - if let Ok(device) = self.find_device(device_spec) { - bar.set_message("Regular device found. Switching to ISP mode..."); - if let Ok(isp_device) = self.switch_to_isp_device(device, device_spec) { + info!("Device not found. Trying ISP device..."); + match self.find_isp_device(device_spec) { + Ok(isp_device) => { bar.finish_and_clear(); eprintln!("Connected!"); return Ok(isp_device); } - } - info!("Regular device not found. Trying ISP device..."); - if let Ok(isp_device) = self.find_isp_device(device_spec) { - bar.finish_and_clear(); - eprintln!("Connected!"); - return Ok(isp_device); + Err(DeviceSelectorError::NotFound) => {} + Err(err) => { + return Err(err); + } } } bar.finish_and_clear(); - eprintln!("Device could not be found"); Err(DeviceSelectorError::NotFound) } diff --git a/src/main.rs b/src/main.rs index c4d9c1c..d78c97c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,7 +76,7 @@ fn main() -> ExitCode { match err_main() { Ok(_) => ExitCode::SUCCESS, Err(err) => { - error!("{:}", err); + eprintln!("{:}", err); ExitCode::FAILURE } } @@ -84,48 +84,61 @@ fn main() -> ExitCode { fn cli() -> Command { Command::new("sinowealth-kb-tool") - .about("A programming tool for Sinowealth Gaming KB devices") + .about("A tool to read and write flash for SinoWealth ISP devices") .version(env!("CARGO_PKG_VERSION")) .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 device.") + .about("List all connected usb hid devices and info about them.") .arg(arg!(--vendor_id ).value_parser(maybe_hex::)) - .arg(arg!(--product_id ).value_parser(maybe_hex::)) - ) - .subcommand( - Command::new("convert") - .short_flag('c') - .about("Convert payload from bootloader to JTAG and vice versa.") - .arg(arg!(--direction "direction of conversion").value_parser(["to_jtag", "to_isp"]).required(true)) - .arg(arg!(--input_format ).value_parser(Format::available_formats())) - .arg(arg!(--output_format ).value_parser(Format::available_formats())) - .arg(arg!(input_file: "file to convert")) - .arg(arg!(output_file: "file to write results to")) - .device_args() // TODO: not all of these args are needed and should be removed + .arg(arg!(--product_id ).value_parser(maybe_hex::)), ) .subcommand( Command::new("read") - .short_flag('r') - .about("Read flash contents.") + .about("Read flash into a file.") .arg(arg!(output_file: "file to write flash contents to")) - .arg(arg!(-f --format ).value_parser(Format::available_formats())) - .arg(arg!(-s --section
"firmware section to read").value_parser(ReadSection::available_sections()).default_value(ReadSection::Firmware.to_str())) - .arg(arg!(-r --retry "number of attempts trying to find device").value_parser(value_parser!(usize)).default_value(DEFAULT_RETRY_COUNT)) - .device_args() + .arg(arg!(--format ).value_parser(Format::available_formats())) + .arg( + arg!(-s --section
"firmware section to read") + .value_parser(ReadSection::available_sections()) + .default_value(ReadSection::Firmware.to_str()), + ) + .arg( + arg!(-r --retry "number of attempts trying to find device") + .value_parser(value_parser!(usize)) + .default_value(DEFAULT_RETRY_COUNT), + ) + .device_args(), ) .subcommand( Command::new("write") - .short_flag('w') - .about("Write file into flash.") + .about("Write a file into flash.") .arg(arg!(input_file: "payload to write into flash")) - .arg(arg!(-f --format ).value_parser(Format::available_formats())) - .arg(arg!(-r --retry "number of attempts trying to find device").value_parser(value_parser!(usize)).default_value(DEFAULT_RETRY_COUNT)) + .arg(arg!(-f --force "ignore firmware size check")) + .arg(arg!(--format ).value_parser(Format::available_formats())) + .arg( + arg!(-r --retry "number of attempts trying to find device") + .value_parser(value_parser!(usize)) + .default_value(DEFAULT_RETRY_COUNT), + ) .device_args(), ) + .subcommand( + Command::new("convert") + .about("Convert payload from ISP to JTAG and vice versa.") + .arg( + arg!(--direction "direction of conversion") + .value_parser(["to_jtag", "to_isp"]) + .required(true), + ) + .arg(arg!(--input_format ).value_parser(Format::available_formats())) + .arg(arg!(--output_format ).value_parser(Format::available_formats())) + .arg(arg!(input_file: "file to convert")) + .arg(arg!(output_file: "file to write results to")) + .device_args(), // TODO: not all of these args are needed and should be removed + ) } fn err_main() -> Result<(), CLIError> { @@ -188,6 +201,8 @@ fn err_main() -> Result<(), CLIError> { .map(|s| s.to_owned()) .unwrap(); + let force = sub_matches.get_flag("force"); + let format = get_format_from_matches(sub_matches, input_file, "format"); let device_spec = get_device_spec_from_matches(sub_matches); @@ -200,14 +215,17 @@ fn err_main() -> Result<(), CLIError> { firmware.len(), device_spec.platform.firmware_size ); - let confirmation = Confirm::new() - .with_prompt("Are you sure you want to continue?") - .default(false) - .interact() - .unwrap(); - - if !confirmation { - return Ok(()); + if !force { + eprintln!("Use --force skip confirmation"); + let confirmation = Confirm::new() + .with_prompt("Are you sure you want to continue?") + .default(false) + .interact() + .unwrap(); + + if !confirmation { + return Ok(()); + } } firmware.resize(device_spec.platform.firmware_size, 0); } From 38fde56ac326bc55f16b11fd66604c4b2f8c14e1 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 4 May 2025 21:31:34 +0200 Subject: [PATCH 120/141] nix check fix --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index 09603fb..4e23703 100644 --- a/flake.nix +++ b/flake.nix @@ -48,6 +48,8 @@ version = "latest"; src = ./.; + + checkFlags = ["--bins"]; # prevent integration tests from running since they require an attached specific device doCheck = true; # run `cargo test` on build inherit buildInputs; From 21372973d95f495452037f3161759bae39c5e55f Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 4 May 2025 21:36:17 +0200 Subject: [PATCH 121/141] disable nix checks --- flake.nix | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 4e23703..a33a2ec 100644 --- a/flake.nix +++ b/flake.nix @@ -49,8 +49,7 @@ src = ./.; - checkFlags = ["--bins"]; # prevent integration tests from running since they require an attached specific device - doCheck = true; # run `cargo test` on build + doCheck = false; # integration tests from running since they require an attached specific device inherit buildInputs; From 6508e9a52247c41dee418b01a6fec0df994b2404 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 7 Jun 2025 21:44:54 +0200 Subject: [PATCH 122/141] flake update, nix build actions (#107) --- .github/workflows/push.yml | 19 +++++++++++++++++++ flake.lock | 26 +++++++++++++------------- flake.nix | 5 ++--- rust-toolchain.toml | 2 +- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index f293854..d1d95dc 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -11,6 +11,25 @@ defaults: shell: bash jobs: + nix-build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: cachix/install-nix-action@v31 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + - uses: cachix/cachix-action@v14 + with: + name: sinowealth-kb-tool + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + - name: Build + run: nix build + lint: runs-on: ubuntu-latest steps: diff --git a/flake.lock b/flake.lock index c7f28f3..c4754a5 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1743800763, - "narHash": "sha256-YFKV+fxEpMgP5VsUcM6Il28lI0NlpM7+oB1XxbBAYCw=", + "lastModified": 1745925850, + "narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=", "owner": "nix-community", "repo": "naersk", - "rev": "ed0232117731a4c19d3ee93aa0c382a8fe754b01", + "rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1745377448, - "narHash": "sha256-jhZDfXVKdD7TSEGgzFJQvEEZ2K65UMiqW5YJ2aIqxMA=", + "lastModified": 1749213349, + "narHash": "sha256-UAaWOyQhdp7nXzsbmLVC67fo+QetzoTm9hsPf9X3yr4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "507b63021ada5fee621b6ca371c4fca9ca46f52c", + "rev": "a4ff0e3c64846abea89662bfbacf037ef4b34207", "type": "github" }, "original": { @@ -36,16 +36,16 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1745487689, - "narHash": "sha256-FQoi3R0NjQeBAsEOo49b5tbDPcJSMWc3QhhaIi9eddw=", + "lastModified": 1749086602, + "narHash": "sha256-DJcgJMekoxVesl9kKjfLPix2Nbr42i7cpEHJiTnBUwU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5630cf13cceac06cefe9fc607e8dfa8fb342dde3", + "rev": "4792576cb003c994bd7cc1edada3129def20b27d", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-24.11", + "ref": "nixos-25.05", "repo": "nixpkgs", "type": "github" } @@ -79,11 +79,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1745721366, - "narHash": "sha256-dm93104HXjKWzkrr7yAPtxpbllOSzrwFFruc+rKQHSg=", + "lastModified": 1749263796, + "narHash": "sha256-m52UsUrcNjAzgc0cwcg94INkiFyVPTn6KbFGr4x4cu8=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "621131c9e281d1047bf8937547ed77e97c464aba", + "rev": "6e1d910306edfe6e4b718878f222c5672500d6b2", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index a33a2ec..d4c8fac 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; utils.url = "github:numtide/flake-utils"; naersk.url = "github:nix-community/naersk"; rust-overlay.url = "github:oxalica/rust-overlay"; @@ -33,8 +33,7 @@ udev ]) ++ (lib.optionals (stdenv.hostPlatform.isDarwin) [ - darwin.apple_sdk.frameworks.IOKit - darwin.apple_sdk.frameworks.AppKit + apple-sdk iconv ]); in diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 08e95c4..fb0f9c3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.78" +channel = "1.87" components = [ "rustfmt", "rustc", From dffac1304d32c9231102abcc45e3c705d738e2e6 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 7 Jun 2025 22:15:33 +0200 Subject: [PATCH 123/141] switch hidparser to v1.0.2 --- Cargo.lock | 3 ++- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d70bf90..3890b81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,7 +397,8 @@ dependencies = [ [[package]] name = "hidparser" version = "1.0.2" -source = "git+https://github.com/microsoft/mu_rust_hid.git?tag=v1.0.3#38e4bd85156e48f341f8d8110c257dfff6aee942" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1198de2f52b8ed4a9d32d1475fbfaba95a7d07b0807efb0f364717563099194c" [[package]] name = "iana-time-zone" diff --git a/Cargo.toml b/Cargo.toml index c5f0300..ea840d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ rust-version = "1.65" clap = "4.1" clap-num = "1.0" dialoguer = "0.11.0" -hidparser = { git = "https://github.com/microsoft/mu_rust_hid.git", tag = "v1.0.3" } # needed until 1.0.3 is published on crates.io +hidparser = "1.0.2" ihex = "3.0" indicatif = "0.17.11" itertools = "0.14.0" From 510f260057b336be190781ca27d0312f699775e9 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 8 Jun 2025 12:59:12 +0200 Subject: [PATCH 124/141] update device report template (#108) * [x] updated tool options/arguments * [x] replaced `hid-dump` with `sinowealth-kb-tool list` --- .github/ISSUE_TEMPLATE/device-report.md | 47 +++++++++++-------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/device-report.md b/.github/ISSUE_TEMPLATE/device-report.md index 3e3f3ff..3bf8d5d 100644 --- a/.github/ISSUE_TEMPLATE/device-report.md +++ b/.github/ISSUE_TEMPLATE/device-report.md @@ -16,14 +16,15 @@ assignees: '' ## Part Info ``` -firmware_size: 61440 +plarform: sh68f90 vendor_id: 0xdead product_id: 0xcafe +firmware_size: 61440 # necessary if not default, otherwise remove this line bootloader_size: 4096 # necessary if not default, otherwise remove this line page_size: 2048 # necessary if not default, otherwise remove this line -isp_usage_page: 0xff00 # necessary if not default, otherwise remove this line -isp_usage: 0x0001 # necessary if not default, otherwise remove this line -isp_index: 0 # necessary if not default, otherwise remove this line +isp_iface_num: 1 # necessary if not default, otherwise remove this line +isp_report_id: 5 # necessary if not default, otherwise remove this line +reboot: false # necessary if not default, otherwise remove this line ``` ## Operations Tested @@ -42,33 +43,27 @@ isp_index: 0 # necessary if not default, otherwise remove this line - Stock Firmware MD5: `deadbeefdeadbeefdeadbeefdeadbeef` - Bootloader MD5: `beefcafebeefcafebeefcafebeefcafe` _(shown when running `sinowealth-kb-tool read -b ...`)_ -## HID Dump +## Device Info (HID Reports) -A dump from [usbhid-dump](https://github.com/DIGImend/usbhid-dump), [win-hid-dump](https://github.com/todbot/win-hid-dump) or [mac-hid-dump](https://github.com/todbot/mac-hid-dump) +Output when running `sinowealth-kb-tool list --vendor_id= --product_id=`
-HID Tool Output +Output ``` -# NuPhy Air60 using win-hid-dump -... -05AC:024F: BY Tech - Air60 -PATH:\\?\hid#vid_05ac&pid_024f&mi_01&col05#7&2af01ac7&0&0004#{4d1e55b2-f16f-11cf-88cb-001111000030} -DESCRIPTOR: - 06 00 FF 09 01 A1 01 85 05 15 00 25 01 35 00 45 - 01 65 00 55 00 75 01 95 28 B1 03 C1 00 - (29 bytes) -05AC:024F: BY Tech - Air60 -PATH:\\?\hid#vid_05ac&pid_024f&mi_00#7&132c8e82&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}\kbd -DESCRIPTOR: - 05 01 09 06 A1 01 05 07 19 E0 29 E7 15 00 25 01 - 35 00 45 01 65 00 55 00 75 01 95 08 81 02 95 30 - 81 03 05 FF 09 03 25 FF 45 00 75 08 95 01 81 02 - 05 08 19 01 29 05 25 01 45 01 75 01 95 05 91 02 - 95 03 91 03 05 C0 09 00 25 7F 45 00 75 08 95 40 - B1 02 C1 00 - (84 bytes) -... +sinowealth-kb-tool list --vendor_id=0x05ac --product_id=0x024f +ID 05ac:024f manufacturer="contact@carlossless.io" product="SMK Keyboard" + path="DevSrvsID:4294974930" interface_number=0 + report_descriptor=[05 01 09 06 A1 01 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 75 08 95 01 81 01 05 07 19 00 29 FF 15 00 26 FF 00 75 08 95 06 81 00 05 08 19 01 29 05 15 00 25 01 75 01 95 05 91 02 75 03 95 01 91 01 C0] + feature_report_ids=[] + usage_page=0x0001 usage=0x0006 + path="DevSrvsID:4294974929" interface_number=1 + report_descriptor=[05 01 09 80 A1 01 85 01 19 81 29 83 15 00 25 01 75 01 95 03 81 02 95 05 81 01 C0 05 0C 09 01 A1 01 85 02 19 00 2A 3C 02 15 00 26 3C 02 75 10 95 01 81 00 C0 06 00 FF 09 01 A1 01 85 05 19 01 29 02 15 00 26 FF 00 75 08 95 05 B1 02 C0 05 01 09 06 A1 01 85 06 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 05 07 19 00 29 9F 15 00 25 01 75 01 95 A0 81 02 C0] + feature_report_ids=[5] + usage_page=0x0001 usage=0x0006 + usage_page=0x0001 usage=0x0080 + usage_page=0x000c usage=0x0001 + usage_page=0xff00 usage=0x0001 ```
From 0b37b70f5d781d246979cee075a4db752faf3043 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 9 Jun 2025 16:43:22 +0200 Subject: [PATCH 125/141] hidparser: 1.0.2 -> 1.0.3 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3890b81..e1b3b6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -396,9 +396,9 @@ dependencies = [ [[package]] name = "hidparser" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1198de2f52b8ed4a9d32d1475fbfaba95a7d07b0807efb0f364717563099194c" +checksum = "efb8ae90b8d0165be79ff98d845bebbbc9dfa0295ff2ce86beba525d93b9ba60" [[package]] name = "iana-time-zone" diff --git a/Cargo.toml b/Cargo.toml index ea840d8..7e5c193 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ rust-version = "1.65" clap = "4.1" clap-num = "1.0" dialoguer = "0.11.0" -hidparser = "1.0.2" +hidparser = "1.0.3" ihex = "3.0" indicatif = "0.17.11" itertools = "0.14.0" From efa55fc4cf12896a44e5113dac0faaa44983c8c3 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 28 Jun 2025 17:20:04 +0200 Subject: [PATCH 126/141] check operation code instead of setting it --- src/isp_device.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/isp_device.rs b/src/isp_device.rs index b983b5a..9b1a03b 100644 --- a/src/isp_device.rs +++ b/src/isp_device.rs @@ -38,6 +38,8 @@ pub enum ISPError { HidError(#[from] HidError), #[error(transparent)] VerificationError(#[from] VerificationError), + #[error("Read/Write operation mistmatch")] + ReadWriteMismatch, } #[derive(Debug, Clone)] @@ -227,11 +229,13 @@ impl ISPDevice { let page_size = self.device_spec.platform.page_size; let mut xfer_buf: Vec = vec![0; page_size + 2]; xfer_buf[0] = REPORT_ID_XFER; - xfer_buf[1] = XFER_READ_PAGE; self.xfer_device() .get_feature_report(&mut xfer_buf) .map_err(ISPError::from)?; buf.extend_from_slice(&xfer_buf[2..(page_size + 2)]); + if xfer_buf[1] != XFER_READ_PAGE { + return Err(ISPError::ReadWriteMismatch); + } Ok(()) } @@ -250,6 +254,9 @@ impl ISPDevice { self.xfer_device() .send_feature_report(&xfer_buf) .map_err(ISPError::from)?; + if xfer_buf[1] != XFER_WRITE_PAGE { + return Err(ISPError::ReadWriteMismatch); + } Ok(()) } From 21d9d2480e488c60352042100597fd98ce5b8ac7 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 28 Jun 2025 17:22:00 +0200 Subject: [PATCH 127/141] cleanup unnecessary map_errs --- src/main.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index d78c97c..7feb593 100644 --- a/src/main.rs +++ b/src/main.rs @@ -182,7 +182,7 @@ fn err_main() -> Result<(), CLIError> { let digest = md5::compute(&firmware); eprintln!("MD5: {:x}", digest); - write_with_format(output_file, &firmware, format).map_err(CLIError::from)?; + write_with_format(output_file, &firmware, format)?; eprintln!( "Successfully read {} bytes - {}", @@ -207,7 +207,7 @@ fn err_main() -> Result<(), CLIError> { let device_spec = get_device_spec_from_matches(sub_matches); - let mut firmware = read_with_format(input_file, format).map_err(CLIError::from)?; + let mut firmware = read_with_format(input_file, format)?; if firmware.len() < device_spec.platform.firmware_size { eprintln!( @@ -286,8 +286,7 @@ fn err_main() -> Result<(), CLIError> { let device_spec = get_device_spec_from_matches(sub_matches); - let mut firmware = - read_with_format(input_file, input_format).map_err(CLIError::from)?; + let mut firmware = read_with_format(input_file, input_format)?; if firmware.len() < device_spec.platform.firmware_size { log::warn!( @@ -322,7 +321,7 @@ fn err_main() -> Result<(), CLIError> { _ => unreachable!(), } - write_with_format(output_file, &firmware, output_format).map_err(CLIError::from)?; + write_with_format(output_file, &firmware, output_format)?; } _ => unreachable!(), } From 8046f9b3ba89eb9c1de7d9153b0f26befd83e205 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 6 Jul 2025 13:52:32 +0200 Subject: [PATCH 128/141] device: redragon-k618 (#110) https://github.com/carlossless/sinowealth-kb-tool/issues/109 --- README.md | 1 + src/device_spec.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 27a9b52..429f6eb 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ sinowealth-kb-tool write \ | [Redragon K630 Single LED version](https://www.redragonzone.com/products/redragon-k630-gaming-mechanical-keyboard) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90A | SH68F90AU | ✅ | ✅ | | [Redragon K614 Anivia 60%](https://www.redragonzone.com/products/redragon-k614-anivia-60-ultra-thin-wired-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K617 FIZZ 60%](https://www.redragonzone.com/collections/keyboard/products/redragon-k617-fizz-60-wired-rgb-gaming-keyboard-61-keys-compact-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | +| [Redragon K618](https://www.redragonzone.com/products/redragon-k618-horus-wireless-rgb-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K633 RYZE](https://www.redragonzone.com/products/redragon-k633-ryze-rgb-led-backlit-mechanical-gaming-keyboard-with-68-professional-keys-linear-red-switches) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K641 SHACO PRO](https://www.redragonzone.com/products/redragon-k641-shaco-pro-65-aluminum-rgb-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K658 PRO SE](https://www.redragonzone.com/products/k658-pro-se-90-wireless-rgb-gaming-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | diff --git a/src/device_spec.rs b/src/device_spec.rs index 08815b9..1195490 100644 --- a/src/device_spec.rs +++ b/src/device_spec.rs @@ -99,6 +99,12 @@ pub const DEVICE_REDRAGON_FIZZ_K617: DeviceSpec = DeviceSpec { ..DEVICE_BASE_SH68F90 }; +pub const DEVICE_REDRAGON_K618: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + pub const DEVICE_REDRAGON_ANIVIA_K614: DeviceSpec = DeviceSpec { vendor_id: 0x258a, product_id: 0x0049, @@ -278,6 +284,7 @@ pub static DEVICES: Map<&'static str, DeviceSpec> = phf_map! { "redragon-k630-norgb" => DEVICE_REDRAGON_K630_NO_RGB, "redragon-k614-anivia" => DEVICE_REDRAGON_ANIVIA_K614, "redragon-k617-fizz" => DEVICE_REDRAGON_FIZZ_K617, + "redragon-k618" => DEVICE_REDRAGON_K618, "redragon-k633-ryze" => DEVICE_REDRAGON_K633_RYZE, "redragon-k641-shaco-pro" => DEVICE_REDRAGON_K641_SHACO_PRO, "redragon-k658-pro-se" => DEVICE_REDRAGON_K658_PRO_SE, From 8589dd096f3a046e428e63db67eb350e3106484e Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 21 Jul 2025 12:58:59 +0300 Subject: [PATCH 129/141] update -b to -s/--section --- .github/ISSUE_TEMPLATE/device-report.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/device-report.md b/.github/ISSUE_TEMPLATE/device-report.md index 3bf8d5d..14a2485 100644 --- a/.github/ISSUE_TEMPLATE/device-report.md +++ b/.github/ISSUE_TEMPLATE/device-report.md @@ -41,7 +41,7 @@ reboot: false # necessary if not default, otherwise remove this line ## Checksums - Stock Firmware MD5: `deadbeefdeadbeefdeadbeefdeadbeef` -- Bootloader MD5: `beefcafebeefcafebeefcafebeefcafe` _(shown when running `sinowealth-kb-tool read -b ...`)_ +- Bootloader MD5: `beefcafebeefcafebeefcafebeefcafe` _(shown when running `sinowealth-kb-tool read -s bootloader ...`)_ ## Device Info (HID Reports) diff --git a/README.md b/README.md index 429f6eb..e8530f4 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ I offer no guarantees that using this tool won't brick your device. Use this too sinowealth-kb-tool read -d nuphy-air60 foobar.hex # reads only isp bootloader section -sinowealth-kb-tool read -d nuphy-air60 -b bootloader.hex +sinowealth-kb-tool read -d nuphy-air60 -s bootloader bootloader.hex # full dump including firmware and bootloader sinowealth-kb-tool read -d nuphy-air60 --full full.hex From c0662894171dba84d118b80fa9aa81f077a94c1b Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Tue, 22 Jul 2025 10:44:46 +0300 Subject: [PATCH 130/141] device: eyooso-z82 (#112) https://github.com/carlossless/sinowealth-kb-tool/issues/111 --- README.md | 1 + src/device_spec.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index e8530f4..10e6b4c 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ sinowealth-kb-tool write \ | Deltaco Gaming WK95R | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | Digital Alliance Meca Warrior X | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | E-Yooso Z11 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK901 | ✅ | ✅ | +| E-Yooso Z82 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Genesis Thor 300 RGB](https://genesis-zone.com/product/thor-300-rgb-brown) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90 | SH68F90S | ✅ | ✅ | | [Genesis Thor 300](https://genesis-zone.com/product/thor-300-outemu-blue) | e57490acebcaabfcff84a0ff013955d9 | SH68F881 | SH68F881W | ✅ | ✅ | | [Kzzi K68Pro](http://en.kzzi.com/product/37/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | ❓ | ✅ | ✅ | diff --git a/src/device_spec.rs b/src/device_spec.rs index 1195490..d5e5a93 100644 --- a/src/device_spec.rs +++ b/src/device_spec.rs @@ -243,6 +243,12 @@ pub const DEVICE_EYOOSO_Z11: DeviceSpec = DeviceSpec { ..DEVICE_BASE_SH68F90 }; +pub const DEVICE_EYOOSO_Z82: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x010c, + ..DEVICE_BASE_SH68F90 +}; + pub const DEVICE_PORTRONICS_HYDRA10: DeviceSpec = DeviceSpec { vendor_id: 0x258a, product_id: 0x0049, @@ -267,6 +273,7 @@ pub static DEVICES: Map<&'static str, DeviceSpec> = phf_map! { "deltaco-wk95r" => DEVICE_DELTACO_WK95R, "digitalalliance-meca-warrior-x" => DEVICE_DIGITALALLIANCE_MECA_WARRIOR_X, "eyooso-z11" => DEVICE_EYOOSO_Z11, + "eyooso-z82" => DEVICE_EYOOSO_Z82, "genesis-thor-300-rgb" => DEVICE_GENESIS_THOR_300_RGB, "genesis-thor-300" => DEVICE_GENESIS_THOR_300, "glorious-model-o" => DEVICE_GLORIOUS_MODEL_O, From 8cf6afec207275ac901b6550ad05c8b496e765aa Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 2 Aug 2025 13:20:06 +0200 Subject: [PATCH 131/141] fix: wrong platform expectation for sh68f881 (#114) fixes #113 --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 7feb593..fb992e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -431,7 +431,7 @@ fn get_device_spec_from_matches(sub_matches: &ArgMatches) -> DeviceSpec { if let Some(platform_name) = platform_name { device_spec = match platform_name { "sh68f90" => Some(DEVICE_BASE_SH68F90), - "sh68f91" => Some(DEVICE_BASE_SH68F881), + "sh68f881" => Some(DEVICE_BASE_SH68F881), _ => panic!("Invalid platform"), } } From f568bd4b7b56c2fc4e5899e3d24ca6106a940b6e Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 2 Aug 2025 13:23:14 +0200 Subject: [PATCH 132/141] bump to 1.0.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1b3b6e..e6ec0aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -875,7 +875,7 @@ dependencies = [ [[package]] name = "sinowealth-kb-tool" -version = "1.0.0" +version = "1.0.1" dependencies = [ "assert_cmd", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 7e5c193..d102942 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ A utility for reading and writing flash contents on Sinowealth 8051-based HID devices through the commonly found ISP bootloader """ repository = "https://github.com/carlossless/sinowealth-kb-tool" -version = "1.0.0" +version = "1.0.1" edition = "2021" license = "MIT" rust-version = "1.65" From 80e485d85b5c3d88727bf58a8740fbd8aba06a36 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 21 Dec 2025 20:04:10 +0200 Subject: [PATCH 133/141] flake update --- flake.lock | 66 +++++++++++++++++++++++++++++++++++++++++++----------- flake.nix | 2 +- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/flake.lock b/flake.lock index c4754a5..72b1f88 100644 --- a/flake.lock +++ b/flake.lock @@ -1,15 +1,38 @@ { "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "naersk", + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1752475459, + "narHash": "sha256-z6QEu4ZFuHiqdOPbYss4/Q8B0BFhacR8ts6jO/F/aOU=", + "owner": "nix-community", + "repo": "fenix", + "rev": "bf0d6f70f4c9a9cf8845f992105652173f4b617f", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, "naersk": { "inputs": { + "fenix": "fenix", "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1745925850, - "narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=", + "lastModified": 1763384566, + "narHash": "sha256-r+wgI+WvNaSdxQmqaM58lVNvJYJ16zoq+tKN20cLst4=", "owner": "nix-community", "repo": "naersk", - "rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f", + "rev": "d4155d6ebb70fbe2314959842f744aa7cabbbf6a", "type": "github" }, "original": { @@ -20,11 +43,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1749213349, - "narHash": "sha256-UAaWOyQhdp7nXzsbmLVC67fo+QetzoTm9hsPf9X3yr4=", + "lastModified": 1752077645, + "narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a4ff0e3c64846abea89662bfbacf037ef4b34207", + "rev": "be9e214982e20b8310878ac2baa063a961c1bdf6", "type": "github" }, "original": { @@ -36,16 +59,16 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1749086602, - "narHash": "sha256-DJcgJMekoxVesl9kKjfLPix2Nbr42i7cpEHJiTnBUwU=", + "lastModified": 1766201043, + "narHash": "sha256-eplAP+rorKKd0gNjV3rA6+0WMzb1X1i16F5m5pASnjA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "4792576cb003c994bd7cc1edada3129def20b27d", + "rev": "b3aad468604d3e488d627c0b43984eb60e75e782", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-25.05", + "ref": "nixos-25.11", "repo": "nixpkgs", "type": "github" } @@ -74,16 +97,33 @@ "utils": "utils" } }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1752428706, + "narHash": "sha256-EJcdxw3aXfP8Ex1Nm3s0awyH9egQvB2Gu+QEnJn2Sfg=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "591e3b7624be97e4443ea7b5542c191311aa141d", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, "rust-overlay": { "inputs": { "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1749263796, - "narHash": "sha256-m52UsUrcNjAzgc0cwcg94INkiFyVPTn6KbFGr4x4cu8=", + "lastModified": 1766285238, + "narHash": "sha256-DqVXFZ4ToiFHgnxebMWVL70W+U+JOxpmfD37eWD/Qc8=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "6e1d910306edfe6e4b718878f222c5672500d6b2", + "rev": "c4249d0c370d573d95e33b472014eae4f2507c2f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index d4c8fac..fd1bd40 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; utils.url = "github:numtide/flake-utils"; naersk.url = "github:nix-community/naersk"; rust-overlay.url = "github:oxalica/rust-overlay"; From dfaf7c8a8ba97e6a19bf60e87e9ac982f3d0cfb7 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 12 Jan 2026 21:23:44 +0100 Subject: [PATCH 134/141] added mcu part number for glorious model o --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 10e6b4c..b239cad 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ sinowealth-kb-tool write \ | Model | ISP MD5 | MCU | MCU Label | Tested Read | Tested Write | | ----- | ------- | --- | --------- | ----------- | ------------ | -| [Glorious Model O](https://web.archive.org/web/20220609205659mp_/https://www.gloriousgaming.com/products/glorious-model-o-black) | 46459c31e58194fa076b8ce8fb1f3eaa | ❓ | BY8948 | ✅ | ❓ | +| [Glorious Model O](https://web.archive.org/web/20220609205659mp_/https://www.gloriousgaming.com/products/glorious-model-o-black) | 46459c31e58194fa076b8ce8fb1f3eaa | SH68F89 | BY8948 | ✅ | ❓ | | [Trust GXT 960](https://www.trust.com/en/product/23758-gxt-960-graphin-ultra-lightweight-gaming-mouse) | 620f0b67a91f7f74151bc5be745b7110 | ❓ | BY8801 | ✅ | ❓ | ## Bootloader Support From 5f6fc9c50e748892ebbce3aa3408d0e97408633d Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 19 Jan 2026 18:37:57 +0100 Subject: [PATCH 135/141] allow convert to work on full dumps --- src/ihex.rs | 4 ++-- src/main.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ihex.rs b/src/ihex.rs index 216070e..accbc66 100644 --- a/src/ihex.rs +++ b/src/ihex.rs @@ -13,9 +13,9 @@ pub enum UnpackingError { #[derive(Debug, Error, PartialEq)] pub enum ConversionError { - #[error("Error while unpacking IHEX into array")] + #[error("Error while unpacking IHEX into array {0:?}")] Unpacking(#[from] UnpackingError), - #[error("Errow while writing IHEX to string")] + #[error("Errow while writing IHEX to string {0:?}")] Serializing(#[from] WriterError), } diff --git a/src/main.rs b/src/main.rs index fb992e1..71db5ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -476,7 +476,7 @@ fn read_with_format(file: &str, format: Format) -> Result, CLIError> { match format { Format::IntelHex => { let file_str = String::from_utf8_lossy(&file_buf[..]); - from_ihex(&file_str, 0xFFFF).map_err(CLIError::from) // TODO reasonable length + from_ihex(&file_str, 0x10000).map_err(CLIError::from) } Format::Binary => Ok(file_buf), } From 307754289d02044c5a3884e355bc7176cc8db324 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 25 Jan 2026 14:49:39 +0100 Subject: [PATCH 136/141] device: eweadn-v20 (#120) https://github.com/carlossless/sinowealth-kb-tool/issues/119 --- src/device_spec.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/device_spec.rs b/src/device_spec.rs index d5e5a93..9c71d9f 100644 --- a/src/device_spec.rs +++ b/src/device_spec.rs @@ -237,6 +237,12 @@ pub const DEVICE_REDRAGON_K630_NO_RGB: DeviceSpec = DeviceSpec { ..DEVICE_BASE_SH68F90 }; +pub const DEVICE_EWEADN_V20: DeviceSpec = DeviceSpec { + vendor_id: 0x05ac, + product_id: 0x024f, + ..DEVICE_BASE_SH68F90 +}; + pub const DEVICE_EYOOSO_Z11: DeviceSpec = DeviceSpec { vendor_id: 0x258a, product_id: 0x002a, @@ -272,6 +278,7 @@ pub static DEVICES: Map<&'static str, DeviceSpec> = phf_map! { "aula-f87" => DEVICE_AULA_F87, "deltaco-wk95r" => DEVICE_DELTACO_WK95R, "digitalalliance-meca-warrior-x" => DEVICE_DIGITALALLIANCE_MECA_WARRIOR_X, + "eweadn-v20" => DEVICE_EWEADN_V20, "eyooso-z11" => DEVICE_EYOOSO_Z11, "eyooso-z82" => DEVICE_EYOOSO_Z82, "genesis-thor-300-rgb" => DEVICE_GENESIS_THOR_300_RGB, From e20f6c4ce8496f94da01a5ee5e93c4bc83e8d300 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Thu, 29 Jan 2026 08:19:49 +0100 Subject: [PATCH 137/141] fix --full reference in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b239cad..8cb1712 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ sinowealth-kb-tool read -d nuphy-air60 foobar.hex sinowealth-kb-tool read -d nuphy-air60 -s bootloader bootloader.hex # full dump including firmware and bootloader -sinowealth-kb-tool read -d nuphy-air60 --full full.hex +sinowealth-kb-tool read -d nuphy-air60 -s full full.hex # custom device sinowealth-kb-tool read \ From 38de0f77be14eca64bfe9f54e523f731b62d2ca7 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Mon, 9 Feb 2026 23:09:10 +0100 Subject: [PATCH 138/141] device: redragon-k652-pro (#121) https://github.com/carlossless/sinowealth-kb-tool/issues/116 --- README.md | 1 + src/device_spec.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 8cb1712..d05b505 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ sinowealth-kb-tool write \ | [Redragon K618](https://www.redragonzone.com/products/redragon-k618-horus-wireless-rgb-mechanical-keyboard) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K633 RYZE](https://www.redragonzone.com/products/redragon-k633-ryze-rgb-led-backlit-mechanical-gaming-keyboard-with-68-professional-keys-linear-red-switches) | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | BYK916 | ✅ | ✅ | | [Redragon K641 SHACO PRO](https://www.redragonzone.com/products/redragon-k641-shaco-pro-65-aluminum-rgb-mechanical-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | +| [Redragon K652 PRO](https://www.redragonzone.com/products/redragon-k652-75-wireless-rgb-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | ❓ | ✅ | ✅ | | [Redragon K658 PRO SE](https://www.redragonzone.com/products/k658-pro-se-90-wireless-rgb-gaming-keyboard) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | [Royal Kludge RK100](http://en.rkgaming.com/product/14/) | cfc8661da8c9d7e351b36c0a763426aa | SH68F90? | BYK916 | ✅ | ✅ | | [Royal Kludge RK61](http://en.rkgaming.com/product/11/) | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90? | BYK916 | ✅ | ✅ | diff --git a/src/device_spec.rs b/src/device_spec.rs index 9c71d9f..179d8bf 100644 --- a/src/device_spec.rs +++ b/src/device_spec.rs @@ -225,6 +225,12 @@ pub const DEVICE_REDRAGON_K658_PRO_SE: DeviceSpec = DeviceSpec { ..DEVICE_BASE_SH68F90 }; +pub const DEVICE_REDRAGON_K652_PRO: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x0049, + ..DEVICE_BASE_SH68F90 +}; + pub const DEVICE_REDRAGON_K530_DRACONIC_PRO: DeviceSpec = DeviceSpec { vendor_id: 0x258a, product_id: 0x0049, @@ -301,6 +307,7 @@ pub static DEVICES: Map<&'static str, DeviceSpec> = phf_map! { "redragon-k618" => DEVICE_REDRAGON_K618, "redragon-k633-ryze" => DEVICE_REDRAGON_K633_RYZE, "redragon-k641-shaco-pro" => DEVICE_REDRAGON_K641_SHACO_PRO, + "redragon-k652-pro" => DEVICE_REDRAGON_K652_PRO, "redragon-k658-pro-se" => DEVICE_REDRAGON_K658_PRO_SE, "royalkludge-rk100" => DEVICE_ROYALKLUDGE_RK100, "royalkludge-rk61" => DEVICE_ROYALKLUDGE_RK61, From 656e855a3c3d186fc5233f1bc92cb60b73abd29e Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sun, 15 Feb 2026 11:39:00 +0100 Subject: [PATCH 139/141] simple_logger bump --- Cargo.lock | 686 ++++++++++++++++++++++++++++---------------- Cargo.toml | 4 +- rust-toolchain.toml | 2 +- 3 files changed, 436 insertions(+), 256 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6ec0aa..ddb41f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,19 +4,13 @@ version = 3 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -28,9 +22,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -43,48 +37,53 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "once_cell", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + [[package]] name = "assert_cmd" -version = "2.0.17" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" +checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514" dependencies = [ "anstyle", "bstr", - "doc-comment", "libc", "predicates", "predicates-core", @@ -94,21 +93,21 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bstr" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", @@ -117,32 +116,32 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "cc" -version = "1.2.21" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", @@ -152,9 +151,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.37" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" dependencies = [ "clap_builder", ] @@ -170,9 +169,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" dependencies = [ "anstream", "anstyle", @@ -182,24 +181,23 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" -version = "2.2.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -223,9 +221,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" dependencies = [ "powerfmt", ] @@ -249,12 +247,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "either" version = "1.15.0" @@ -267,14 +259,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -283,6 +281,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "float-cmp" version = "0.10.0" @@ -293,29 +297,10 @@ dependencies = [ ] [[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "futures-core" @@ -334,18 +319,6 @@ dependencies = [ "futures-util", ] -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - [[package]] name = "futures-task" version = "0.3.31" @@ -358,12 +331,8 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -371,21 +340,43 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasip2", + "wasip3", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hidapi" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b876ecf37e86b359573c16c8366bc3eba52b689884a0fc42ba3f67203d2a8b" +checksum = "565dd4c730b8f8b2c0fb36df6be12e5470ae10895ddcc4e9dcfbfb495de202b0" dependencies = [ "cc", "cfg-if", @@ -402,9 +393,9 @@ checksum = "efb8ae90b8d0165be79ff98d845bebbbc9dfa0295ff2ce86beba525d93b9ba60" [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -424,12 +415,30 @@ dependencies = [ "cc", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ihex" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "365a784774bb381e8c19edb91190a90d7f2625e057b55de2bc0f6b57bc779ff2" +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + [[package]] name = "indicatif" version = "0.17.11" @@ -445,9 +454,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -460,53 +469,52 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "md5" @@ -516,9 +524,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "normalize-line-endings" @@ -528,9 +536,9 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-traits" @@ -562,11 +570,17 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -574,15 +588,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -647,9 +661,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "powerfmt" @@ -659,9 +673,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "predicates" -version = "3.1.3" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" dependencies = [ "anstyle", "difflib", @@ -673,43 +687,53 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" [[package]] name = "predicates-tree" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" dependencies = [ "predicates-core", "termtree", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -728,18 +752,18 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -749,9 +773,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -760,34 +784,34 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "scc" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" dependencies = [ "sdd", ] @@ -800,37 +824,66 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sdd" -version = "3.0.8" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + +[[package]] +name = "semver" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "serial_test" -version = "3.2.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555" dependencies = [ - "futures", + "futures-executor", + "futures-util", "log", "once_cell", "parking_lot", @@ -840,9 +893,9 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "3.2.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83" dependencies = [ "proc-macro2", "quote", @@ -851,9 +904,9 @@ dependencies = [ [[package]] name = "shell-words" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shlex" @@ -863,14 +916,14 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simple_logger" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb" +checksum = "291bee647ce7310b0ea721bfd7e0525517b4468eb7c7e15eb8bd774343179702" dependencies = [ "colored", "log", "time", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -894,29 +947,26 @@ dependencies = [ "serial_test", "simple_logger", "stdext", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "stdext" @@ -932,9 +982,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.101" +version = "2.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" dependencies = [ "proc-macro2", "quote", @@ -943,15 +993,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.1" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", "getrandom", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -971,11 +1021,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.18", ] [[package]] @@ -991,9 +1041,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1002,9 +1052,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -1012,22 +1062,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -1035,15 +1085,21 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utf8parse" @@ -1061,45 +1117,41 @@ dependencies = [ ] [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen" -version = "0.2.100" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" +name = "wasm-bindgen" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1107,26 +1159,60 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "web-time" version = "1.1.0" @@ -1139,9 +1225,9 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", @@ -1152,9 +1238,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -1163,9 +1249,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -1174,24 +1260,24 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] @@ -1214,6 +1300,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -1336,16 +1431,101 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index d102942..cdbad29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ repository = "https://github.com/carlossless/sinowealth-kb-tool" version = "1.0.1" edition = "2021" license = "MIT" -rust-version = "1.65" +rust-version = "1.88" [dependencies] clap = "4.1" @@ -30,7 +30,7 @@ version = "0.4" features = ["max_level_debug"] [dependencies.simple_logger] -version = "5.0" +version = "5.1" default-features = false features = ["stderr", "colors", "timestamps"] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index fb0f9c3..bc2947d 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.87" +channel = "1.88" components = [ "rustfmt", "rustc", From f78bd65a73882a4dcb9faa1fd71c38c823a390ed Mon Sep 17 00:00:00 2001 From: ed Date: Sun, 12 Apr 2026 17:55:34 +0100 Subject: [PATCH 140/141] device: dierya-dk68se --- README.md | 1 + src/device_spec.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index d05b505..1a19c26 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ sinowealth-kb-tool write \ | Yinren R108 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | Yunzii AL66 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | SH68F90AS | ✅ | ✅ | | Yunzii AL71 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | SH68F90AS | ✅ | ✅ | +| Dierya DK68SE | e611dacbc710b758a6fbcc3bcc648b7b | SH68F90A | BYK903 | ✅ | ✅ | ### Mice diff --git a/src/device_spec.rs b/src/device_spec.rs index 179d8bf..f2c9a86 100644 --- a/src/device_spec.rs +++ b/src/device_spec.rs @@ -279,10 +279,17 @@ pub const DEVICE_YINREN_R108: DeviceSpec = DeviceSpec { ..DEVICE_BASE_SH68F90 }; +pub const DEVICE_DIERYA_DK68SE: DeviceSpec = DeviceSpec { + vendor_id: 0x258a, + product_id: 0x013b, + ..DEVICE_BASE_SH68F90 +}; + pub static DEVICES: Map<&'static str, DeviceSpec> = phf_map! { "aula-f75" => DEVICE_AULA_F75, "aula-f87" => DEVICE_AULA_F87, "deltaco-wk95r" => DEVICE_DELTACO_WK95R, + "dierya-dk68se" => DEVICE_DIERYA_DK68SE, "digitalalliance-meca-warrior-x" => DEVICE_DIGITALALLIANCE_MECA_WARRIOR_X, "eweadn-v20" => DEVICE_EWEADN_V20, "eyooso-z11" => DEVICE_EYOOSO_Z11, From 33c00a279b3267f584fb56745f65d4815060884a Mon Sep 17 00:00:00 2001 From: Eduard Nicodei Date: Mon, 13 Apr 2026 16:01:57 +0100 Subject: [PATCH 141/141] Update README.md Co-authored-by: Karolis Stasaitis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a19c26..52317c2 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ sinowealth-kb-tool write \ | Yinren R108 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | BYK916 | ✅ | ✅ | | Yunzii AL66 | 3e0ebd0c440af5236d7ff8872343f85d | SH68F90A | SH68F90AS | ✅ | ✅ | | Yunzii AL71 | 2d169670eae0d36eae8188562c1f66e8 | SH68F90A | SH68F90AS | ✅ | ✅ | -| Dierya DK68SE | e611dacbc710b758a6fbcc3bcc648b7b | SH68F90A | BYK903 | ✅ | ✅ | +| Dierya DK68SE | e611dacbc710b758a6fbcc3bcc648b7b | SH68F90A | BYK903 | ✅ | ✅ | ### Mice