Skip to content

Instantly share code, notes, and snippets.

@craigzour
Created April 21, 2017 13:10
Show Gist options
  • Save craigzour/c9b708a72d831c2a3a65457af3dd7ed8 to your computer and use it in GitHub Desktop.
Save craigzour/c9b708a72d831c2a3a65457af3dd7ed8 to your computer and use it in GitHub Desktop.
MSP firmware update in Rust
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")
}
}
}
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."))
}
};
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