#!/usr/bin/env python3 # tisndrcv - Direct USB send/receive code for TI graphing calculators # 01/02/2016 # Brandon Wilson # # I despise software licenses, and I despise thinking about them. # If you really insist that this have one before you use it for your own # purposes, then consider it WTFPL'd before asking me. # I really do not care what you do with this, if anything, nor will I ever. # # TODO: Add error handling for weird response packets # Clean up interface for sending variable requests # Add interface for manipulating/transferring files directly instead of separate .py files # Add 8x*->BIN support (both programs and apps) # Add TI8xProgram and TI8xAppVar classes # Speed up reading of xxU files (it's probably horribly inefficient) import argparse import os import sys import struct from enum import Enum from binascii import b2a_qp, a2b_qp, unhexlify import usb.core TI_VENDOR_ID = 0x0451 TI_PRODUCT_ID_84P = 0xE003 TI_PRODUCT_ID_84PSE = 0xE008 TI_PRODUCT_ID_89T = 0xE004 TI_PRODUCT_NUM_82A = 0x0B USB_ENDPOINT_IN = 0x81 USB_ENDPOINT_OUT = 0x02 USB_ENDPOINT_MAX_PACKET_SIZE = 64 USB_XFER_TIMEOUT = 10000 BUFFER_SIZE_89T = 1023 BUFFER_SIZE_84P = 250 FILE_HEADER = b'**TI83F*' FLASH_HEADER = b'**TIFL**' class TIFlashFile(object): def __init__(self, file_name): if len(file_name) > 0: with open(file_name, "rb") as file: data = file.read() if b2a_qp(data[0:8]) != FLASH_HEADER: raise AssertionError("File header is invalid") self.major_version = data[8] self.minor_version = data[9] self.flags = data[10] name_length = data[0x10] self.name = struct.pack('B' * name_length, *data[0x11:0x11+name_length]).rstrip(bytes([0])) self.device_type = data[0x30] self.data_type = data[0x31] data_length = int.from_bytes(data[0x4A:0x4E], byteorder='little') self.data = data[0x4E:0x4E+data_length] #checksum = int.from_bytes(data[0x4E+data_length:0x50+data_length], byteorder='little') #for i in range(len(self.data)): # checksum = (checksum - self.data[i]) & 0xFFFF #if checksum != 0: # raise AssertionError("Checksum is invalid") return class TIxxUFile(TIFlashFile): def __init__(self, file_name): TIFlashFile.__init__(self, file_name) self.header = [] self.flash_data = {} self.signature = [] section = 0 page = 0x00 for line in self.data.decode('utf-8').split('\n'): line = line.lstrip(':').rstrip('\r').split(' ')[0] line_hex_bytes = [line[i:i+2] for i in range(0, len(line), 2)] line_bytes = [unhexlify(line_hex_bytes[i])[0] for i in range(len(line_hex_bytes))] len_data_bytes = line_bytes[0] address = int.from_bytes(line_bytes[1:3], byteorder='big') block_type = line_bytes[3] if (len_data_bytes > 0): data = line_bytes[4:4+len_data_bytes] else: data = bytes(0) checksum = (0x100 - line_bytes[4+len_data_bytes]) & 0xFF calculated_checksum = 0 for i in range(len(line_bytes)-1): calculated_checksum = (calculated_checksum + line_bytes[i]) & 0xFF if calculated_checksum != checksum: raise AssertionError("Invalid checksum on line: " + line) if block_type == 0: if section == 0: self.header += data elif section == 1: if page not in self.flash_data: self.flash_data[page] = [] self.flash_data[page] += data else: self.signature += data elif block_type == 2: page = int.from_bytes(data, byteorder='big') elif block_type == 1: section += 1 if section >= 3: break return class TIVariableFile(object): def __init__(self, file_name): if len(file_name) > 0: with open(file_name, "rb") as file: data = file.read() if b2a_qp(data[0:8]) != FILE_HEADER: raise AssertionError("File header is invalid") if data[8] != 0x1A or data[9] != 0x0A or data[10] != 0x00: raise AssertionError("File signature is invalid") self.comment = struct.pack('B' * (0x35-0x0B), *data[0x0B:0x35]).rstrip(bytes([0])) length = int.from_bytes(data[0x35:0x37], byteorder='little') self.data = data[0x37:0x37+length] checksum = int.from_bytes(data[0x37+length:0x39+length], byteorder='little') for i in range(len(self.data)): checksum = (checksum - self.data[i]) & 0xFFFF if checksum != 0: raise AssertionError("Checksum is invalid") return def create(file_name, comment, data): with open(file_name, "wb") as file: file.write(a2b_qp(FILE_HEADER)[0:8]) file.write(bytes([0x1A, 0x0A, 0x00])) file.write(a2b_qp(comment.ljust(42, b'\x00'))) file.write(bytes([len(data) >> i & 0xFF for i in (0,8)])) file.write(bytes(data)) checksum = 0 for i in range(len(data)): checksum = (checksum + data[i]) & 0xFFFF file.write(bytes([checksum >> i & 0xFF for i in (0,8)])) return def save(self, file_name): TIVariableFile.create(file_name, self.comment, self.data) return class BackupVarEntry(object): def __init__(self): self.type1 = 0x00 self.type2 = 0x00 self.version = 0x00 self.address = 0x0000 self.page = 0x00 self.has_name_length_byte = False self.data = [] self.leave_address_alone = False @property def name_as_string(self): return b2a_qp(bytes(self.name).rstrip(bytes([0]))) class TIBackupFile(object): def __init__(self, file): self.comment = file.comment self.var_entries = [] self.vars_first_in_mem = [] # Get the user memory address from the header length = int.from_bytes(file.data[0:2], byteorder='little') header = file.data[2:2+length] self.user_mem_address = int.from_bytes(header[7:9], byteorder='little') # Skip past the header offset = 2 + len(header) # Read the individual sections length = int.from_bytes(file.data[offset:offset+2], byteorder='little') self.system_ram = file.data[offset+2:offset+2+length] offset += 2 + length length = int.from_bytes(file.data[offset:offset+2], byteorder='little') self.user_ram = file.data[offset+2:offset+2+length] offset += 2 + length length = int.from_bytes(file.data[offset:offset+2], byteorder='little') self.symbol_table = file.data[offset+2:offset+2+length] # Parse the sections into variable data i = len(self.symbol_table) - 1 while i > 0: entry = BackupVarEntry() entry.type1 = self.symbol_table[i] i -= 1 entry.type2 = self.symbol_table[i] i -= 1 entry.version = self.symbol_table[i] i -= 1 entry.address = self.symbol_table[i] i -= 1 entry.address = entry.address | ((self.symbol_table[i] << 8) & 0xFF00) i -= 1 entry.page = self.symbol_table[i] i -= 1 if (entry.type1 != 0x01 | self.symbol_table[i] <= 8) and entry.type1 != 0x00 and entry.type1 != 0x03: length = self.symbol_table[i] i -= 1 entry.has_name_length_byte = True else: length = 3 entry.name = [] for idx in range(0, length): entry.name.append(self.symbol_table[i]) i -= 1 if entry.page == 0 and entry.address >= self.user_mem_address: index = entry.address - self.user_mem_address size = 0 if entry.type1 == 0x00: size = 9 elif entry.type1 == 0x0C: size = 18 elif entry.type1 == 0x01 or entry.type1 == 0x0D: if index < len(self.user_ram): size = int.from_bytes(self.user_ram[index:index+2], byteorder='little') * 9 + 2 elif entry.type1 == 0x02: if index < len(self.user_ram): size = self.user_ram[index] * self.user_ram[index + 1] * 9 + 2 else: if index < len(self.user_ram): size = int.from_bytes(self.user_ram[index:index+2], byteorder='little') + 2 if size > 0: entry.data = self.user_ram[index:index+size] else: entry.leave_address_alone = True self.var_entries.append(entry) return @classmethod def create(cls, file_name, comment, user_mem_address, system_ram, user_ram, symbol_table): # Generate header section header = [0x09, 0x00] header += [len(system_ram) >> i & 0xFF for i in (0,8)] header += [0x13] header += [len(user_ram) >> i & 0xFF for i in (0,8)] header += [len(symbol_table) >> i & 0xFF for i in (0,8)] header += [user_mem_address >> i & 0xFF for i in (0,8)] # Save file data data = [len(system_ram) >> i & 0xFF for i in (0,8)] data += list(system_ram) data += [len(user_ram) >> i & 0xFF for i in (0,8)] data += list(user_ram) data += [len(symbol_table) >> i & 0xFF for i in (0,8)] data += list(symbol_table) TIVariableFile.create(file_name, comment, header + data) return def save(self, file_name): # Generate user RAM and symbol table images user_ram = [0] * sum(len(i.data) for i in self.var_entries) symbol_table = [0] * sum(6 + len(i.name) + (1 if i.has_name_length_byte else 0) for i in self.var_entries) i = len(symbol_table) - 1 user_offset = 0 for entry in self.vars_first_in_mem: if len(entry.data) > 0: if not entry.leave_address_alone: entry.address = self.user_mem_address + user_offset for idx in range(len(entry.data)): user_ram[user_offset] = entry.data[idx] user_offset += 1 for entry in self.var_entries: address = entry.address symbol_table[i] = entry.type1 i -= 1 symbol_table[i] = entry.type2 i -= 1 symbol_table[i] = entry.version i -= 1 if entry not in self.vars_first_in_mem and len(entry.data) > 0: if not entry.leave_address_alone: address = self.user_mem_address + user_offset for idx in range(len(entry.data)): user_ram[user_offset] = entry.data[idx] user_offset += 1 symbol_table[i] = address & 0xFF i -= 1 symbol_table[i] = (address >> 8) & 0xFF i -= 1 symbol_table[i] = entry.page i -= 1 if (entry.has_name_length_byte): symbol_table[i] = len(entry.name) i -= 1 for idx in range(len(entry.name)): symbol_table[i] = entry.name[idx] i -= 1 TIBackupFile.create(file_name, self.comment, self.user_mem_address, \ self.system_ram, user_ram, symbol_table) return class TIDeviceType(Enum): Unknown = 0 TI84Plus = 1 TI84PlusSE = 2 TI84PlusCSE = 3 TI82Advanced = 4 TI89Titanium = 5 TI84PlusCE = 6 class CommunicationMode(Enum): Startup = 0x01 Basic = 0x02 Normal = 0x03 class VirtualPacketType(Enum): PingSetMode = 0x0001 BeginOSTransfer = 0x0002 AcknowledgeOSTransfer = 0x0003 OSHeaderSignature = 0x0004 OSData = 0x0005 AcknowledgeEOT = 0x0006 ParameterRequest = 0x0007 ParameterData = 0x0008 RequestDirectoryListing = 0x0009 VariableHeader = 0x000A RequestToSend = 0x000B RequestVariable = 0x000C VariableContents = 0x000D ParameterSet = 0x000E DeleteVariable = 0x0010 AcknowledgeModeSetting = 0x0012 AcknowledgeData = 0xAA00 DelayAcknowledgement = 0xBB00 EndOfTransmission = 0xDD00 Error = 0xEE00 class ParameterType(Enum): ProductNumber = 0x0001 ProductName = 0x0002 CalcID1 = 0x0003 HardwareVersion = 0x0004 LanguageID = 0x0006 SubLanguageID = 0x0007 DBUSType = 0x0008 BootVersion = 0x0009 OSLoaded = 0x000A OSVersion = 0x000B PhysicalRAM = 0x000C MaxUserRAM = 0x000D FreeRAM = 0x000E PhysicalFlash = 0x000F MaxUserFlash = 0x0010 FreeFlash = 0x0011 MaxAppPages = 0x0012 AppPagesFree = 0x0013 LCDWidth = 0x001E LCDHeight = 0x001F LCDContents = 0x0022 ClockEnabled = 0x0024 Clock = 0x0025 DateFormat = 0x0027 MilitaryTime = 0x0028 BatteryStatus = 0x002D CertificateList = 0x0031 AtHomescreen = 0x0037 SplitScreen = 0x0039 class RawPacketType(Enum): BufferSizeRequest = 1 BufferSizeAllocation = 2 VirtualPacketDataWithContinuation = 3 VirtualPacketDataFinal = 4 VirtualPacketDataAcknowledgement = 5 class RawPacket(object): def __init__(self, data): self.type = data[4] self.data = data[5:] @classmethod def fromparts(cls, type, data): return cls([(len(data) >> i & 0xFF) for i in (24,16,8,0)] + [type] + data) class VirtualPacket(object): def __init__(self, data): self.type = int.from_bytes(data[4:6], byteorder='big') self.data = data[6:] @classmethod def fromparts(cls, type, data): return cls([(len(data) >> i & 0xFF) for i in (24,16,8,0)] + [(type >> i & 0xFF) for i in (8,0)] + data) class TIxxDevice(object): """Represents a TI-xx device.""" def __init__(self, device): self.buffer_size = None self.product_number = None self.device = device self.device.configuration_set = False return def ensure_configuration_set(self): if not self.device.configuration_set: self.device.set_configuration() self.device.configuration_set = True return def negotiate_buffer_size(self, attempted_size): TIxxDevice.send_raw_packet(self, RawPacket.fromparts(RawPacketType.BufferSizeRequest, \ [(attempted_size >> i & 0xFF) for i in (24,16,8,0)])) self.buffer_size = int.from_bytes(TIxxDevice.receive_raw_packet(self).data, byteorder='big') return def ping_set_mode(self, mode): TIxxDevice.send_virtual_packet(self, VirtualPacketType.PingSetMode, \ [0x00, mode.value, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0xD0]) response = TIxxDevice.receive_virtual_packet(self) return @property def device_type(self): if self.buffer_size is None: if self.device.idProduct == TI_PRODUCT_ID_89T: attempted_size = BUFFER_SIZE_89T else: attempted_size = BUFFER_SIZE_84P TIxxDevice.negotiate_buffer_size(self, attempted_size) TIxxDevice.ping_set_mode(self, CommunicationMode.Basic) if self.product_number is None: TIxxDevice.send_virtual_packet(self, VirtualPacketType.ParameterRequest, [0x00, 0x01, \ (ParameterType.ProductNumber.value >> 8) & 0xFF, ParameterType.ProductNumber.value & 0xFF]) response = TIxxDevice.receive_virtual_packet(self) self.product_number = int.from_bytes(response.data[-4:], byteorder='big') if self.device.idProduct == TI_PRODUCT_ID_89T: return TIDeviceType.TI89Titanium elif self.product_number == TI_PRODUCT_NUM_82A: return TIDeviceType.TI82Advanced elif self.device.idProduct == TI_PRODUCT_ID_84P: return TIDeviceType.TI84Plus elif self.device.idProduct == TI_PRODUCT_ID_84PSE: return TIDeviceType.TI84PlusSE else: return TIDeviceType.Unknown def receive_raw_packet(self): TIxxDevice.ensure_configuration_set(self) first = self.device.read(USB_ENDPOINT_IN, USB_ENDPOINT_MAX_PACKET_SIZE, USB_XFER_TIMEOUT) datalen = int.from_bytes(first[0:4], byteorder='big') type = first[4] ret = first[5:] datalen -= len(ret) while datalen > 0: next = self.device.read(USB_ENDPOINT_IN, USB_ENDPOINT_MAX_PACKET_SIZE, USB_XFER_TIMEOUT) ret += next datalen -= len(next) return RawPacket(first[0:5] + ret) def send_raw_packet(self, packet): TIxxDevice.ensure_configuration_set(self) d = [(len(packet.data) >> i & 0xFF) for i in (24,16,8,0)] + [packet.type.value] + packet.data[:] self.device.write(USB_ENDPOINT_OUT, d, USB_XFER_TIMEOUT) return def send_virtual_packet(self, type, data): total = 4 + 2 + len(data) data = [(len(data) >> i & 0xFF) for i in (24,16,8,0)] + [(type.value >> i & 0xFF) for i in (8,0)] + data while total > 0: if total >= self.buffer_size: length = self.buffer_size else: length = total if total > self.buffer_size: ptype = RawPacketType.VirtualPacketDataWithContinuation else: ptype = RawPacketType.VirtualPacketDataFinal TIxxDevice.send_raw_packet(self, RawPacket.fromparts(ptype, data[0:length])) TIxxDevice.receive_raw_packet(self) data = data[length:] total -= length return def send_virtual_ack(self): TIxxDevice.send_raw_packet(self, RawPacket.fromparts(RawPacketType.VirtualPacketDataAcknowledgement, \ [0xE0, 0x00])) return def receive_virtual_packet(self): while True: # Receive raw packets until we have the start of the virtual packet data (length + type) data = [] while len(data) < 6: data += TIxxDevice.receive_raw_packet(self).data # Now keep receiving raw packets until we have all of the data length = int.from_bytes(data[0:4], byteorder='big') type = int.from_bytes(data[4:6], byteorder='big') data = data[6:] length -= len(data) while length > 0: TIxxDevice.send_virtual_ack(self) response = TIxxDevice.receive_raw_packet(self) data += response.data length -= len(response.data) # Send back an acknowledgement on this TIxxDevice.send_virtual_ack(self) if type != VirtualPacketType.DelayAcknowledgement.value: break # Now return our reconstructed virtual packet return VirtualPacket.fromparts(type, data) def request_memory_backup(self): TIxxDevice.send_virtual_packet(self, VirtualPacketType.RequestVariable, \ [0x00, 0x01, 0x21, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, \ 0x00, 0x01, 0x00, 0x11, 0x00, 0x04, 0xF0, 0x07, 0x00, 0x13, 0x00, 0x00]) return TIxxDevice.receive_virtual_packet(self).data def send_memory_backup(self, system, user, symbol, user_address): system = list(system) user = list(user) symbol = list(symbol) total = len(system) + len(user) + len(symbol) TIxxDevice.send_virtual_packet(self, VirtualPacketType.RequestToSend, \ [0x00, 0x01, 0x21, 0x00, 0x00, 0x00, (total >> 8) & 0xFF, total & 0xFF, \ 0x02, 0x00, 0x03, 0x00, 0x02, 0x00, 0x04, \ 0xF0, 0x07, 0x00, 0x13, 0x00, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFE, 0x00, 0x08, \ (len(system) >> 8) & 0xFF, len(system) & 0xFF, \ (len(user) >> 8) & 0xFF, len(user) & 0xFF, \ (len(symbol) >> 8) & 0xFF, len(symbol) & 0xFF, \ (user_address >> 8) & 0xFF, user_address & 0xFF]) response = TIxxDevice.receive_virtual_packet(self) TIxxDevice.send_virtual_packet(self, VirtualPacketType.VariableContents, system + user + symbol) response = TIxxDevice.receive_virtual_packet(self) TIxxDevice.send_virtual_packet(self, VirtualPacketType.EndOfTransmission, []) return def find_all(): ret = usb.core.find(find_all=True, custom_match = \ lambda e: \ e.idVendor == TI_VENDOR_ID and (e.idProduct == TI_PRODUCT_ID_84P or \ e.idProduct == TI_PRODUCT_ID_84PSE)) r = [] for i, item in enumerate(ret): r.append(TIxxDevice(item)) return r def get_friendly_model_name(type): if type == TIDeviceType.TI82Advanced: return "TI-82 Advanced" elif type == TIDeviceType.TI84Plus: return "TI-84 Plus" elif type == TIDeviceType.TI84PlusCE: return "TI-84 Plus CE" elif type == TIDeviceType.TI84PlusCSE: return "TI-84 Plus C Silver Edition" elif type == TIDeviceType.TI84PlusSE: return "TI-84 Plus Silver Edition" elif type == TIDeviceType.TI89Titanium: return "TI-89 Titanium" else: return "Unknown" def find_calculator(args): # vid and pid, or bus and address devices = TIxxDevice.find_all() device = None ids_specified = args.device_ids is not None and len(args.device_ids) == 2 loc_specified = args.device_loc is not None and len(args.device_loc) == 2 for item in devices: if ids_specified: if item.device.idVendor == args.device_ids[0] and item.device.idProduct == args.device_ids[1]: device = item break elif loc_specified: if item.device.bus == args.device_loc[0] and item.device.address == args.device_loc[1]: device = item break if device is None: # If we were trying to use a specific one, freak out if ids_specified or loc_specified: print("Specified device not detected!") exit() # Fall back on first device found print("Selecting first DirectUSB-connected calculator...") device = next(iter(TIxxDevice.find_all()), None) if device is None: print("No calculator detected!") exit() return device def tisndrcv_list_devices(args): i = 1 devices = TIxxDevice.find_all() print("{} device(s) found.\r\n".format(len(devices))) for item in devices: print("Device {}:".format(i)) print("\tDevice Type: " + get_friendly_model_name(item.device_type)) print("\tVendor ID: {}, Product ID: {}".format(hex(item.device.idVendor), hex(item.device.idProduct))) print("\tBus: {}, Address: {}".format(hex(item.device.bus), hex(item.device.address))) i += 1 return def tisndrcv_send_backup(args): print("File name: " + args.file_name) dev = find_calculator(args) # Find out what type of device this is and set normal mode print("Device found, type: " + get_friendly_model_name(dev.device_type)) print("Setting normal communication mode...") dev.ping_set_mode(CommunicationMode.Normal) # Read memory backup data from file print("Reading backup file...") file = TIBackupFile(TIVariableFile(args.file_name)) # Send memory backup print("Sending memory backup...") print("Please make sure to enter LINK-RECEIVE mode on calculator.") print("Also make sure to select 'Continue' within ten seconds.") dev.send_memory_backup(file.system_ram, file.user_ram, file.symbol_table, file.user_mem_address) print("Backup sending complete.") return def tisndrcv_receive_backup(args): print("File name: " + args.file_name) dev = find_calculator(args) # Find out what type of device this is and set normal mode type = dev.device_type print("Device found, type: " + get_friendly_model_name(type)) print("Setting normal communication mode...") dev.ping_set_mode(CommunicationMode.Normal) # Request memory backup print("Requesting memory backup...") data = dev.request_memory_backup() print("Creating " + args.file_name + "...") ops_offset = 0x9828-0x89F0 sym_start = int.from_bytes(data[ops_offset:ops_offset+2], byteorder='little') sym_size = 0xFE66 - sym_start system_ram = data[0:0x9D95-0x89F0] symbol_table = data[-sym_size:] user_ram = data[len(system_ram):-sym_size] TIBackupFile.create(args.file_name, b'', 0x9D95, system_ram, user_ram, symbol_table) print("Backup receiving complete.") return def tisndrcv_flash_to_bin(args): # Load the **U file print("Reading flash file " + args.flash_file_name + "...") file = TIxxUFile(args.flash_file_name) # Delete the file if it exists dest_file = args.bin_file_name if os.path.exists(dest_file): print("Binary file " + args.bin_file_name + " already exists, deleting first...") os.remove(dest_file) # Write all the pages out print("Writing binary file " + args.bin_file_name + "...") for i in range(max(file.flash_data.keys())+1): if i in file.flash_data.keys(): d = file.flash_data[i] + ([0xFF] * (0x4000 - len(file.flash_data[i]))) else: d = [0xFF] * 0x4000 with open(dest_file, "ab") as output: output.write(bytes(d)) print("File conversion complete.") return def add_device_arguments(subparser): group = subparser.add_mutually_exclusive_group() group.add_argument("--device-loc", nargs=2, type=int, metavar=("bus","address"), help="USB device location (bus and address)") group.add_argument("--device-ids", nargs=2, type=int, metavar=("vendor_id", "product_id"), help="USB device vendor ID and product ID") return if __name__ == "__main__": parser = argparse.ArgumentParser(description="Application/library for sending/receiving & manipulating TI files", formatter_class=argparse.RawDescriptionHelpFormatter) subparsers = parser.add_subparsers(dest="cmd", help="The action to perform.") formatter = argparse.RawDescriptionHelpFormatter device_required = "\r\n\r\nYou should specify a USB device with this command, " device_required += "either by vendor and product ID or by bus and address. " device_required += "If you do not specify one, the first detected device will be used." # List Direct USB devices help = "Lists DirectUSB-connected devices and how to specify them." subparser = subparsers.add_parser("list_devices", help=help, description=help, formatter_class=formatter) subparser.set_defaults(func=tisndrcv_list_devices) # Send memory backup to Direct USB device help = "Sends a memory backup to the specified calculator." description = help + "\r\nThe calculator must be placed in LINK-RECEIVE mode beforehand." description += device_required subparser = subparsers.add_parser("send_backup", help=help, description=description, formatter_class=formatter) subparser.add_argument("file_name", help="File name; can be of type 73B, 82B, or 8XB.") add_device_arguments(subparser) subparser.set_defaults(func=tisndrcv_send_backup) # Receive memory backup from Direct USB device help = "Receives a memory backup from the specified calculator." description = help + device_required subparser = subparsers.add_parser("receive_backup", help=help, description=description, formatter_class=formatter) subparser.add_argument("file_name", help="8*B file name.") add_device_arguments(subparser) subparser.set_defaults(func=tisndrcv_receive_backup) # Convert Intel Hex file to binary format help = "Converts Intel Hex-based flash file to binary format." description = help + "\r\nBinary data will be laid out starting at page 00, " description += "\r\nautomatically inserting gaps of 0x4000 0xFFs between missing pages." subparser = subparsers.add_parser("flash_to_bin", help=help, description=description, formatter_class=formatter) subparser.add_argument("flash_file_name", help="Flash file name; can be of type 73K, 73U, 8XK, 8XU, 8CK, or 8CU.") subparser.add_argument("bin_file_name", help="File name of binary file to write.") subparser.set_defaults(func=tisndrcv_flash_to_bin) # Handle the command args = parser.parse_args() header = parser.prog + " - " + parser.description print(header) print("-" * len(header)) print("Handling command '" + args.cmd + "'...\r\n") args.func(args)