Created
April 21, 2017 13:10
-
-
Save craigzour/c9b708a72d831c2a3a65457af3dd7ed8 to your computer and use it in GitHub Desktop.
MSP firmware update in Rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use hidapi::HidApi; | |
use updatable_device::UpdatableDevice; | |
const BSL_VENDOR_ID: u16 = 0x2047; | |
const BSL_PRODUCT_ID: u16 = 0x200; | |
pub struct HIDDeviceManager { | |
hid_api: HidApi | |
} | |
impl HIDDeviceManager { | |
pub fn new() -> Result<Self, &'static str> { | |
match HidApi::new() { | |
Ok(hid_api) => return Ok(HIDDeviceManager { hid_api: hid_api }), | |
Err(_) => return Err("Unable to initialize Human Interface Device API") | |
} | |
} | |
pub fn connect_to_bootstrap_loader_mode_compatible_device(&self) -> Result<UpdatableDevice, &'static str> { | |
match self.hid_api.open(BSL_VENDOR_ID, BSL_PRODUCT_ID) { | |
Ok(hid_device) => return Ok(UpdatableDevice::new(hid_device)), | |
Err(_) => return Err("Unable to connect to Bootstrap Loader mode compatible device") | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let start_firmware_update = |file_name: &str| -> () { | |
match hid_device_manager.connect_to_bootstrap_loader_mode_compatible_device() { | |
Ok(updatable_device) => { | |
match updatable_device.start_first_part() { | |
Ok(_) => { | |
match hid_device_manager.connect_to_bootstrap_loader_mode_compatible_device() { | |
Ok(updatable_device_next) => { | |
match updatable_device_next.start_second_part(Path::new(file_name)) { | |
Ok(_) => { | |
println!("Firmware has been updated !"); | |
}, | |
Err(reason) => println!("{}", Red.paint(format!("Unable to update the firmware. Reason: {}", reason))) | |
} | |
}, | |
Err(_) => println!("{}", Red.paint("Unable to connect to the Bootstrap Loader mode compatible device. Make sure the USB cable is properly plugged in.")) | |
} | |
}, | |
Err(reason) => println!("{}", Red.paint(format!("Unable to update the firmware. Reason: {}", reason))) | |
} | |
}, | |
Err(_) => println!("{}", Red.paint("Unable to connect to the Bootstrap Loader mode compatible device. Make sure the USB cable is properly plugged in.")) | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use hidapi::HidDevice; | |
use byteorder::{ByteOrder, BigEndian}; | |
use std::fs::File; | |
use std::io::prelude::*; | |
use std::i64; | |
use std::{thread, time}; | |
use std::path::Path; | |
pub struct UpdatableDevice<'a> { | |
hid_device: HidDevice<'a> | |
} | |
impl<'a> UpdatableDevice<'a> { | |
pub fn new<'b>(hid_device: HidDevice<'b>) -> UpdatableDevice<'b> { | |
return UpdatableDevice { hid_device: hid_device }; | |
} | |
pub fn start_first_part(&self) -> Result<(), &'static str> { | |
println!("0% (clear incoming messages)"); | |
match self.clear_incoming_messages() { | |
Ok(_) => { | |
println!("10% (erase previous firmware)"); | |
match self.erase_previous_firmware() { | |
Ok(_) => { | |
println!("20% (authenticate)"); | |
match self.authenticate() { | |
Ok(_) => { | |
println!("30% (read firmware code (ram_bsl_00_07_08_38))"); | |
match self::read_firmware_code(self::ram_bsl_00_07_08_38_code()) { | |
Ok(firmware_chunks) => { | |
println!("40% (upload firmware)"); | |
match self.upload_firmware(&firmware_chunks) { | |
Ok(_) => { | |
println!("50% (start bootstrap loader mode execution)"); | |
match self.start_bootstrap_loader_mode_execution() { | |
Ok(_) => { | |
thread::sleep(time::Duration::from_millis(3000)); | |
return Ok(()); | |
}, | |
Err(reason) => return Err(reason) | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
} | |
pub fn start_second_part(&self, firmware_file_path: &Path) -> Result<(), &'static str> { | |
println!("60% (authenticate)"); | |
match self.authenticate() { | |
Ok(_) => { | |
println!("70% (erase reset vector)"); | |
match self.erase_reset_vector() { | |
Ok(_) => { | |
println!("80% (read firmware code from {:?})", firmware_file_path.file_name().unwrap()); | |
match self::read_firmware_code(&self::open_firmware_file(firmware_file_path)) { | |
Ok(firmware_chunks) => { | |
println!("90% (upload firmware)"); | |
match self.upload_firmware(&firmware_chunks) { | |
Ok(_) => { | |
println!("100% (reset box)"); | |
match self.reset_box() { | |
Ok(_) => return Ok(()), | |
Err(reason) => return Err(reason) | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
}, | |
Err(reason) => Err(reason) | |
} | |
}, | |
Err(reason) => Err(reason) | |
} | |
} | |
fn clear_incoming_messages(&self) -> Result<(), &'static str> { | |
loop { | |
match self.hid_device.read_timeout(&mut [0u8; 64], 10) { | |
Ok(response_size) => { | |
if response_size == 0 { | |
return Ok(()); | |
} | |
}, | |
Err(_) => return Err("[firmware-update] Unable to clear incoming messages") | |
} | |
} | |
} | |
fn erase_previous_firmware(&self) -> Result<(), &'static str> { | |
let mut request_buffer: [u8; 35] = [255; 35]; | |
request_buffer[0] = 63; | |
request_buffer[1] = 33; | |
request_buffer[2] = 17; | |
let expected_password_is_wrong_response: [u8; 4] = [63, 2, 59, 5]; | |
let expected_password_is_correct_response: [u8; 4] = [63, 2, 59, 0]; | |
match self.hid_device.write(&request_buffer) { | |
Ok(_) => { | |
let mut response_buffer = [0u8; 4]; | |
match self.hid_device.read_timeout(&mut response_buffer[..], 2000) { | |
Ok(_) => { | |
// If the password is correct it might be related to the fact that we already triggered the mass erase process | |
if response_buffer == expected_password_is_wrong_response || response_buffer == expected_password_is_correct_response { | |
return Ok(()); | |
} else { | |
return Err("[firmware-update] Unable to trigger mass erase process"); | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
} | |
fn authenticate(&self) -> Result<(), &'static str> { | |
let mut request_buffer: [u8; 35] = [255; 35]; | |
request_buffer[0] = 63; | |
request_buffer[1] = 33; | |
request_buffer[2] = 17; | |
let expected_password_is_correct_response: [u8; 4] = [63, 2, 59, 0]; | |
match self.hid_device.write(&request_buffer) { | |
Ok(_) => { | |
let mut response_buffer = [0u8; 4]; | |
match self.hid_device.read_timeout(&mut response_buffer[..], 2000) { | |
Ok(_) => { | |
if response_buffer == expected_password_is_correct_response { | |
return Ok(()); | |
} else { | |
return Err("[firmware-update] Unable to get authorization to access protected commands"); | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
} | |
fn upload_firmware(&self, firmware_chunks: &Vec<FirmwareChunk>) -> Result<(), &'static str> { | |
let build_requests = || -> Vec<[u8; 64]> { | |
let mut requests: Vec<[u8; 64]> = Vec::new(); | |
let mut address_as_uint: u64 = 0; | |
for firmware_chunk in firmware_chunks { | |
let mut i = 0; | |
let mut j = 0; | |
address_as_uint = BigEndian::read_uint(&firmware_chunk.address, 3); | |
while i < firmware_chunk.code.len() { | |
let mut next_address_as_byte_array: [u8; 24] = [0; 24]; | |
BigEndian::write_uint(&mut next_address_as_byte_array, address_as_uint, 3); | |
let mut next_request: [u8; 64] = [0; 64]; | |
next_request[0] = 63; | |
next_request[1] = 62; | |
next_request[2] = 27; | |
next_request[3] = next_address_as_byte_array[2]; | |
next_request[4] = next_address_as_byte_array[1]; | |
next_request[5] = next_address_as_byte_array[0]; | |
let mut request_content: [u8; 58] = [0; 58]; | |
while j < 58 && i + j < firmware_chunk.code.len() { | |
request_content[j] = firmware_chunk.code[i + j]; | |
j += 1; | |
} | |
if i + j == firmware_chunk.code.len() { | |
while j < 58 { | |
request_content[j] = 255; | |
j += 1; | |
} | |
} | |
for k in 0..58 { | |
next_request[k + 6] = request_content[k]; | |
} | |
requests.push(next_request); | |
address_as_uint += 58; // 62 (content without opcode and request length) - 4 (opcode + address) | |
i += 58; | |
j = 0; | |
} | |
} | |
return requests; | |
}; | |
let requests_to_write = build_requests(); | |
for request_buffer in requests_to_write { | |
match self.hid_device.write(&request_buffer) { | |
Ok(_) => (), | |
Err(reason) => return Err(reason) | |
} | |
} | |
return Ok(()); | |
} | |
fn start_bootstrap_loader_mode_execution(&self) -> Result<(), &'static str> { | |
let mut request_buffer: [u8; 6] = [0; 6]; | |
request_buffer[0] = 63; | |
request_buffer[1] = 4; | |
request_buffer[2] = 23; | |
request_buffer[3] = 4; | |
request_buffer[4] = 37; | |
request_buffer[5] = 0; | |
match self.hid_device.write(&request_buffer) { | |
Ok(_) => return Ok(()), | |
Err(reason) => return Err(reason) | |
} | |
} | |
fn erase_reset_vector(&self) -> Result<(), &'static str> { | |
let mut request_buffer: [u8; 6] = [0; 6]; | |
request_buffer[0] = 63; | |
request_buffer[1] = 4; | |
request_buffer[2] = 18; | |
request_buffer[3] = 255; | |
request_buffer[4] = 255; | |
request_buffer[5] = 0; | |
match self.hid_device.write(&request_buffer) { | |
Ok(_) => return Ok(()), | |
Err(reason) => return Err(reason) | |
} | |
} | |
fn reset_box(&self) -> Result<(), &'static str> { | |
// RESETS the MSP430 by forcing a BOR. | |
let mut request_buffer: [u8; 35] = [255; 35]; | |
request_buffer[0] = 63; | |
request_buffer[1] = 6; | |
request_buffer[2] = 27; | |
request_buffer[3] = 32; | |
request_buffer[4] = 1; | |
request_buffer[5] = 0; | |
request_buffer[6] = 4; | |
request_buffer[7] = 165; | |
let expected_response: [u8; 4] = [63, 2, 59, 0]; | |
loop { | |
match self.hid_device.write(&request_buffer) { | |
Ok(_) => { | |
let mut response_buffer = [0u8; 4]; | |
match self.hid_device.read_timeout(&mut response_buffer[..], 2000) { | |
Ok(_) => { | |
if response_buffer == expected_response { | |
match self.hid_device.write(&request_buffer) { | |
Ok(_) => return Ok(()), | |
Err(reason) => return Err(reason) | |
} | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
}, | |
Err(reason) => return Err(reason) | |
} | |
} | |
} | |
} | |
type FirmwareChunkAddress = Vec<u8>; | |
type FirmwareChunkCode = Vec<u8>; | |
struct FirmwareChunk { | |
address: FirmwareChunkAddress, | |
code: FirmwareChunkCode | |
} | |
fn open_firmware_file(file_path: &Path) -> String { | |
let mut file = File::open(file_path).unwrap(); | |
let mut buffer = String::new(); | |
file.read_to_string(&mut buffer); | |
return buffer; | |
} | |
fn read_firmware_code(firmware_code: &str) -> Result<Vec<FirmwareChunk>, &'static str> { | |
let mut firmware_chunks: Vec<FirmwareChunk> = Vec::new(); | |
let mut chunk_address: FirmwareChunkAddress = Vec::new(); | |
let mut chunk_code_lines: Vec<&str> = Vec::new(); | |
for line in firmware_code.lines() { | |
/*match line { | |
Ok(value) => { | |
}, | |
Err(_) => return Err("Unable to read firmware file") | |
}*/ | |
if line.contains("@") { | |
if chunk_address.len() > 0 { | |
match self::read_firmware_code_lines(&chunk_code_lines) { | |
Ok(code) => { | |
firmware_chunks.push(FirmwareChunk { address: chunk_address.to_vec(), code: code }); | |
chunk_address = Vec::new(); | |
chunk_code_lines = Vec::new(); | |
}, | |
Err(reason) => return Err(reason) | |
} | |
} | |
match self::read_firmware_address_line(&line) { | |
Ok(firmware_chunk_address) => chunk_address = firmware_chunk_address, | |
Err(reason) => return Err(reason) | |
} | |
} else if line.contains("q") { | |
if chunk_address.len() > 0 { | |
match self::read_firmware_code_lines(&chunk_code_lines) { | |
Ok(code) => { | |
firmware_chunks.push(FirmwareChunk { address: chunk_address.to_vec(), code: code }); | |
}, | |
Err(reason) => return Err(reason) | |
} | |
} | |
} else { | |
chunk_code_lines.push(line); | |
} | |
} | |
return Ok(firmware_chunks); | |
} | |
fn read_firmware_address_line(line: &str) -> Result<FirmwareChunkAddress, &'static str> { | |
let prepare_string = |raw_string: &str| -> String { | |
let mut final_string = String::new(); | |
let (_, string_without_at_char) = raw_string.split_at(1); // Get rid of the '@' | |
if string_without_at_char.len() < 6 { // Complete string with 0 so that we can | |
let number_of_zeros_to_add = 6 - string_without_at_char.len(); | |
for _ in 0..number_of_zeros_to_add { | |
final_string.push('0'); | |
} | |
final_string.push_str(string_without_at_char); | |
} | |
return final_string; | |
}; | |
let mut remaining_string_to_parse = prepare_string(line); | |
let mut chunk_address: FirmwareChunkAddress = Vec::new(); | |
loop { | |
let string_to_split = remaining_string_to_parse; | |
let (first_two_characters, remaining_part) = string_to_split.split_at(2); | |
match i64::from_str_radix(first_two_characters, 16) { | |
Ok(value) => chunk_address.push(value as u8), | |
Err(_) => return Err("Unable to parse address line. Reason: Can't find hexadecimal value.") | |
} | |
if remaining_part.len() == 0 { | |
if chunk_address.len() == 3 { | |
return Ok(chunk_address); | |
} else { | |
return Err("Unable to parse address line. Reason: Address it too long.") | |
} | |
} else { | |
remaining_string_to_parse = remaining_part.to_string(); | |
} | |
} | |
} | |
fn read_firmware_code_lines(lines: &Vec<&str>) -> Result<FirmwareChunkCode, &'static str> { | |
let mut chunk_code: FirmwareChunkCode = Vec::new(); | |
for line in lines { | |
for hexadecimal_value in line.split_whitespace() { | |
match i64::from_str_radix(hexadecimal_value, 16) { | |
Ok(value) => chunk_code.push(value as u8), | |
Err(_) => return Err("Unable to parse code line. Reason: Can't find hexadecimal value.") | |
} | |
} | |
} | |
return Ok(chunk_code); | |
} | |
fn ram_bsl_00_07_08_38_code() -> &'static str { | |
return "@2500 | |
00 07 08 38 B2 40 80 5A 5C 01 32 C2 31 40 90 33 | |
B0 13 72 2E FF 3F 12 01 00 02 00 00 00 08 47 20 | |
00 02 09 01 00 00 00 01 06 00 FF 09 01 A1 01 85 | |
3F 95 3F 75 08 25 01 15 01 09 01 81 02 85 3F 95 | |
3F 75 08 25 01 15 01 09 01 91 02 C0 09 02 29 00 | |
01 01 00 80 32 09 04 00 00 02 03 00 00 00 09 21 | |
01 01 00 01 22 24 00 07 05 81 03 40 00 01 07 05 | |
01 03 40 00 01 FF F2 B0 0F 00 84 23 09 20 C2 93 | |
84 23 03 34 5E 42 20 09 0B 3C 5E 42 22 09 08 3C | |
C2 93 84 23 03 34 5E 42 C8 23 02 3C 5E 42 88 23 | |
7E F2 C2 4E EA 24 5E 42 EA 24 42 19 4E 10 C2 4E | |
EA 24 B0 13 5E 2F 3C 40 EA 24 80 00 02 2F F2 43 | |
02 24 C2 43 10 24 C2 43 21 09 10 01 C2 93 82 23 | |
11 20 5E 42 84 23 7E F0 0F 00 0A 24 5E 93 0E 20 | |
C2 93 84 23 03 34 F2 D2 C8 23 02 3C F2 D2 88 23 | |
80 00 BE 25 F2 D2 20 09 F2 D2 22 09 10 01 C2 93 | |
80 23 04 34 1F 43 D2 D3 3C 09 03 3C 0F 43 D2 C3 | |
3C 09 5E 42 80 23 7E B0 60 00 8F 20 5D 42 81 23 | |
4D 83 80 24 5D 83 6B 24 6D 83 67 24 6D 83 45 24 | |
5D 83 09 24 6D 83 52 24 5D 83 46 24 5D 83 33 24 | |
5D 83 54 24 7A 3C 1F B3 78 28 5E 42 83 23 5E 83 | |
08 24 5E 83 0F 24 7E 80 1F 00 1C 24 5E 83 13 24 | |
6C 3C C2 43 23 09 F2 40 12 00 02 24 3C 40 16 25 | |
80 00 02 2F C2 43 23 09 F2 40 29 00 02 24 3C 40 | |
4C 25 80 00 02 2F F2 40 24 00 02 24 3C 40 28 25 | |
80 00 02 2F C2 43 23 09 F2 40 09 00 02 24 3C 40 | |
5E 25 80 00 02 2F 1F B3 48 28 B0 13 5E 2F C2 43 | |
EA 24 D2 42 01 24 EB 24 3A 3C F2 D2 22 09 D2 42 | |
82 23 3F 09 80 00 BE 25 F2 D2 22 09 D2 42 82 23 | |
00 24 B0 13 BE 25 D2 43 12 24 10 01 C2 43 23 09 | |
D2 43 02 24 3C 40 00 24 80 00 02 2F F2 D2 22 09 | |
D2 42 84 23 01 24 80 00 BE 25 80 00 CC 25 5E 42 | |
84 23 7E F0 0F 00 0C 24 5E 93 1B 20 C2 93 84 23 | |
04 34 F2 F0 D7 00 C8 23 03 3C F2 F0 D7 00 88 23 | |
80 00 BE 25 7E 90 80 00 03 20 B0 13 5E 2F 43 3F | |
7E 90 82 00 02 20 80 00 76 25 F2 D2 20 09 F2 D2 | |
22 09 10 01 3B 15 4C 43 3D 40 56 24 B0 13 72 2F | |
8F 20 1E 42 D6 24 1F 42 D8 24 7E B0 7F 00 38 20 | |
B2 90 80 00 DE 24 34 20 B0 13 7C 29 B2 40 00 A5 | |
44 01 B2 40 C0 A5 40 01 0D 3C 3A 4D 3B 4D 1F 15 | |
08 16 88 4A 00 00 88 4B 02 00 B2 B2 44 01 FD 2B | |
2E 52 0F 63 1A 42 D6 24 1B 42 D8 24 3A 50 80 00 | |
0B 63 0F 9B EA 2B 02 20 0E 9A E7 2B 92 42 E2 24 | |
40 01 B0 13 7C 29 B0 13 F4 29 82 43 D6 24 82 43 | |
D8 24 82 43 DA 24 82 43 DC 24 82 43 DE 24 51 3C | |
92 42 E2 24 44 01 1B 42 E2 24 3B 50 40 00 82 4B | |
40 01 08 3C 6C 42 2D 53 1E 53 0F 63 4C 93 41 20 | |
1E 53 0F 63 1A 42 DE 24 0B 43 1A 52 D6 24 1B 62 | |
D8 24 0F 9B 03 28 D9 23 0E 9A D7 2F 92 B3 D6 24 | |
06 2C 3A 53 3B 63 0E 9A 18 20 0F 9B 16 20 6A 4D | |
B0 13 72 2F 0F 20 B0 13 7A 29 1F 15 0B 16 CB 4A | |
00 00 B0 13 7C 29 1F 15 0B 16 68 4B 4A 98 03 24 | |
5C 43 01 3C 6C 42 1D 53 D1 3F 2B 4D B0 13 72 2F | |
C9 23 B0 13 7A 29 1F 15 0A 16 8A 4B 00 00 B0 13 | |
7C 29 1F 15 0A 16 28 4A 0B 98 BD 27 5C 43 BB 3F | |
6C 42 38 17 10 01 1B 15 1E 42 E4 24 5D 4E 03 00 | |
5F 4E 01 00 5A 4E 02 00 8A 10 0A DF 0B 4D 6F 4E | |
7F 80 10 00 25 24 5F 83 12 24 5F 83 2B 24 5F 83 | |
2F 24 6F 83 23 24 5F 83 3C 24 5F 83 11 24 5F 83 | |
3D 24 5F 83 3E 24 6F 83 43 20 5F 43 12 3C 1E 53 | |
0C 4E B0 13 84 29 4C 93 13 24 7C 40 05 00 3A 3C | |
B0 13 72 2F 24 20 B0 13 44 27 B0 13 7A 2F 08 3C | |
4F 43 2E 52 0C 4A B0 13 32 2E 2E 3C B0 13 D2 29 | |
4C 43 28 3C 0E 4A 0F 4D 4C 43 B0 13 4C 29 22 3C | |
B0 13 72 2F 0C 20 4C 43 1F 42 44 01 3F F0 10 00 | |
1F 52 E2 24 3F 50 40 00 82 4F 44 01 13 3C 6C 42 | |
11 3C B0 13 42 2F B0 13 A0 2B 0E 3C B0 13 42 2F | |
04 3C 2E 42 3C 40 00 25 0D 43 B0 13 1A 2C 04 3C | |
7C 40 07 00 B0 13 24 2F 1A 17 10 01 B0 13 72 2F | |
12 20 B0 13 7A 29 92 42 E2 24 44 01 1D 42 E2 24 | |
2D 53 82 4D 40 01 1F 15 0D 16 CD 43 00 00 B0 13 | |
7C 29 80 00 F4 29 6C 42 10 01 4C 43 92 B3 44 01 | |
FD 2F 10 01 1B 15 21 83 0D 43 3A 40 E0 FF 0B 4C | |
3B 50 20 00 7E 4A 7F 4C 0E EF 0D DE 0C 9B FA 23 | |
0D 93 10 20 B1 40 FF 7F 00 00 04 3C 2F 41 3F 53 | |
81 4F 00 00 91 93 00 00 F9 37 B2 40 A5 A5 E0 24 | |
4C 43 04 3C B0 13 D2 29 7C 40 05 00 21 53 1A 17 | |
10 01 B0 13 7C 29 92 42 E2 24 44 01 B0 13 7C 29 | |
1F 42 E2 24 3F 50 06 00 82 4F 40 01 C2 43 E0 FF | |
B0 13 7C 29 1F 42 E2 24 3F 50 10 00 82 4F 44 01 | |
10 01 5E 42 3E 09 2E B2 02 28 80 00 F2 2D A2 B3 | |
08 09 0C 28 B0 13 68 2F B0 13 B0 2D B0 13 22 2B | |
B2 F0 F9 FF 08 09 A2 D3 02 09 10 01 A2 B2 08 09 | |
06 28 B0 13 68 2F B2 40 04 A5 20 01 10 01 D2 B3 | |
30 09 10 28 F2 D0 10 00 3C 09 C2 43 23 09 D2 93 | |
10 24 03 20 B0 13 FC 2C 02 3C F2 D2 20 09 D2 C3 | |
30 09 10 01 4E 93 02 34 80 00 22 2B 3E B0 40 00 | |
0B 28 D2 43 11 24 F2 D0 10 00 3C 09 F2 C0 40 00 | |
3E 09 82 43 10 09 10 01 3E B0 20 00 07 28 B0 13 | |
B0 2D F2 F0 9F 00 3E 09 C2 43 11 24 10 01 7B 15 | |
0A 4C 0B 4D 04 4E 06 4F 47 43 25 3C 82 4A D6 24 | |
82 4B D8 24 08 4A 09 4B 08 54 09 63 82 48 DA 24 | |
82 49 DC 24 08 43 09 43 3D 40 56 24 1D 52 DE 24 | |
09 93 24 20 08 94 22 2C FD 46 00 00 92 53 DE 24 | |
1A 53 0B 63 18 53 09 63 1D 53 7A B0 7F 00 F0 23 | |
B0 13 44 27 04 88 B0 13 72 2F 0F 20 1F 42 D6 24 | |
1F D2 D8 24 0F 93 D2 27 82 9A DA 24 03 20 82 9B | |
DC 24 D0 27 B0 13 44 27 EE 3F 67 42 4C 47 74 17 | |
10 01 C2 43 12 24 C2 43 11 24 C2 43 00 24 C2 43 | |
01 24 C2 43 3C 09 F2 43 02 24 F2 43 04 24 C2 43 | |
10 24 7E 40 80 00 C2 4E 21 09 C2 4E 23 09 F2 40 | |
8C 00 20 09 F2 40 8C 00 22 09 F2 40 03 00 2F 09 | |
F2 40 03 00 2E 09 C2 4E C8 23 F2 40 10 00 C9 23 | |
C2 4E CA 23 C2 4E CE 23 F2 40 40 00 CF 23 C2 4E | |
88 23 C2 43 89 23 C2 43 8A 23 F2 40 40 00 8F 23 | |
F2 40 40 00 3C 09 C2 43 3E 09 C2 CE 3E 09 10 01 | |
3B 15 0A 4C 0B 4D 08 4E B0 13 44 27 B2 43 54 01 | |
0C 4A 0D 4B 0C 58 0D 63 0B 9D 03 28 11 20 0A 9C | |
0F 2C B0 13 72 2F 24 20 1B 15 0F 16 6E 4F C2 4E | |
52 01 1A 53 0B 63 0B 9D F4 2B 02 20 0A 9C F1 2B | |
1E 42 54 01 1F 42 E6 24 FF 40 3A 00 00 00 1F 42 | |
E6 24 CF 4E 01 00 8E 10 1F 42 E6 24 CF 4E 02 00 | |
F2 40 03 00 16 24 B0 13 52 2F FD 27 38 17 10 01 | |
6C 42 B0 13 24 2F FA 3F 03 43 5B 15 0A 4C 0B 4D | |
08 4C 09 4D 08 5E 09 63 47 43 04 3C B0 13 24 2F | |
0A 56 0B 63 0B 99 03 28 29 20 0A 98 27 2C 47 93 | |
25 20 0E 48 0F 49 0E 8A 0F 7B 03 20 3E 90 3E 00 | |
03 28 36 40 3D 00 02 3C 06 48 06 8A 1F 42 E6 24 | |
1F 53 0E 46 0C 4A 0D 4B B0 13 5A 2D 47 4C 4C 93 | |
DD 23 1F 42 E6 24 FF 40 3A 00 00 00 4E 46 5E 53 | |
C2 4E 16 24 B0 13 52 2F D3 23 FC 3F 56 17 10 01 | |
32 C2 03 43 B2 40 02 1C E4 24 B2 40 17 24 E6 24 | |
B2 40 28 96 00 09 82 43 02 09 82 43 60 01 B2 40 | |
F3 10 64 01 B2 40 40 00 62 01 B2 40 44 02 68 01 | |
C2 43 0E 24 C2 43 11 24 B2 40 28 96 00 09 82 43 | |
08 09 03 43 B2 40 40 18 08 09 B2 40 80 00 04 09 | |
B0 13 68 2F C2 43 12 24 B2 B2 08 09 06 28 B0 13 | |
B0 2D B0 13 22 2B A2 D3 02 09 10 01 5E 42 02 24 | |
7E 93 28 24 7E 90 09 00 03 28 7F 42 7E 82 09 3C | |
7E 92 02 2C 4F 4E 07 3C 7F 42 D2 93 0E 24 03 20 | |
4E 43 5D 43 02 3C 7E 43 4D 43 C2 4D 10 24 C2 4E | |
02 24 4F 93 0C 24 3E 40 78 23 4D 4F 1C 42 06 24 | |
EE 4C 00 00 92 53 06 24 1E 53 7D 53 F7 23 C2 4F | |
21 09 10 01 C2 43 10 24 10 01 3B 15 0A 4C 0B 4D | |
09 4E 08 4F B0 13 44 27 0E 4A 0F 4B 0E 59 0F 63 | |
3E 53 3F 63 0F 9B 16 28 02 20 0E 9A 13 28 B0 13 | |
72 2F 13 20 1F 15 0D 16 69 4D 0C 4E 0C 8A 0D 48 | |
0D 5C CD 49 00 00 3E 53 3F 63 0F 9B 03 28 EF 23 | |
0E 9A ED 2F 4C 43 38 17 10 01 6C 42 FC 3F 03 43 | |
21 83 81 43 00 00 B2 40 28 96 00 09 92 42 14 24 | |
12 09 B2 40 00 03 10 09 82 43 14 09 3F 40 4C 01 | |
3F 53 FE 2F 2F 41 0E 4F 1E 53 81 4E 00 00 3F 90 | |
E9 03 05 2C 82 93 14 09 EF 23 92 D3 02 09 21 53 | |
10 01 F2 D0 10 00 3C 09 F2 40 80 00 23 09 03 3C | |
F2 F0 FA 00 3E 09 C2 43 10 24 82 43 EA 24 B0 13 | |
FE 25 34 40 80 00 82 C4 20 09 82 C4 22 09 E2 C2 | |
3E 09 82 D4 20 09 82 D4 22 09 D2 B3 3E 09 E8 2F | |
10 01 0A 12 4A 4F 6F 42 B0 13 72 2F 01 20 4F 43 | |
4F 93 0B 20 B2 90 05 00 E8 24 07 28 0F 4E 1E 42 | |
E8 24 2E 82 B0 13 9E 2A 4F 4C 4A 93 03 20 4C 4F | |
B0 13 24 2F 92 42 E2 24 40 01 B0 13 F4 29 3A 41 | |
10 01 B2 40 A5 A5 E0 24 B2 40 00 A5 E2 24 82 43 | |
DA 24 82 43 DC 24 82 43 D6 24 82 43 D8 24 82 43 | |
DE 24 B0 13 90 2C B0 13 A6 2E 5C B3 FC 2B B0 13 | |
76 28 F9 3F 03 43 C2 43 8A 23 B0 13 02 2A D2 93 | |
12 24 FB 23 C2 93 11 24 F8 23 C2 93 8A 23 F5 37 | |
F2 B0 7F 00 8A 23 F1 27 5F 42 01 1C 82 4F E8 24 | |
5C 43 10 01 7E 40 3F 00 C2 93 CA 23 0F 34 C2 4E | |
80 1C 3F 40 81 1C 0D 4C 0D 5E FF 4C 00 00 1F 53 | |
0C 9D FB 23 F2 40 40 00 CA 23 01 3C 4E 43 4C 4E | |
10 01 82 4C 06 24 1E 42 86 23 5F 42 02 24 0F 9E | |
04 28 C2 4E 02 24 4E 43 01 3C 5E 43 C2 4E 0E 24 | |
80 00 FC 2C 1F 42 E6 24 FF 40 3B 00 00 00 1F 42 | |
E6 24 CF 4C 01 00 E2 43 16 24 B0 13 52 2F FD 27 | |
10 01 5F 4E 04 00 5E 4E 05 00 8E 10 0E DF 0C 4A | |
10 01 3C 40 16 24 B0 13 D4 2E 4C 93 10 01 C2 43 | |
23 09 E2 43 02 24 10 01 3F 40 DF 2E 3F 53 FE 2F | |
10 01 B2 90 A5 A5 E0 24 10 01 1B 15 10 01 FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF | |
q"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment