"use strict"; var p7 = { device: {}, make: {}, decode: {}, receive: {}, send: {} }; (function() { /* ---------------------Global variables--------------------- */ var checksumTries = 0; /* ---------------------Utilitaries--------------------- */ /* Sum individual bytes * NOT + 1, as defined in fxReverse documentation. * Be sure it's under 256! */ Uint8Array.prototype.p7Checksum = function () { return (((~(this.reduce((accumulator, currentValue) => accumulator + currentValue))) + 1) & 0xFF) } /* Convert a string into an array of ascii numbers */ String.prototype.toAscii = function () { return this.split('').map((char) => char.charCodeAt(0)) } /* Convert an hex number into an array of ascii values * Use the uppercase representation of the number */ Number.prototype.hexToAscii = function (padding) { return this.toString(16).toUpperCase().padStart(padding, 0).toAscii(); } /* Convert an ascii array into a string */ Uint8Array.prototype.asciiToString = function () { let string = []; this.forEach((element) => string.push(String.fromCharCode(element))); return string.join(''); } /* Convert an ascii array representing an hex number into a number */ Uint8Array.prototype.asciiToHex = function () { return Number('0x' + this.asciiToString()); } p7.sendBuffer = new Uint8Array(1032); p7.receiveBuffer = new Uint8Array(1032 + 1); /* ---------------------Packet making--------------------- */ /* Create a basic (Non-Extended) packet and push it to p7.sendBuffer */ /* return it's length (6) */ p7.make.basicPacket = (type, subtype) => { //Set the Type (T), Subtype (ST) and Extended (EX) fields p7.sendBuffer.set([type] .concat(subtype.hexToAscii(2)) .concat([0x30]) , 0); //Set the Checksum (CS) field p7.sendBuffer.set(p7.sendBuffer.slice(1, 4).p7Checksum().hexToAscii(2), 4); //Return the packet length return 6; } /* Fill the Data (D) field of a command packet in p7.sendBuffer */ /* return the Data (D) field length */ p7.make.commandPacketDataField = function (dataType, fileSize, commandArguments) { //Set Overwrite (OW), Data type (DT) and File size (FS) fields p7.sendBuffer.set([0x30, 0x30, /* Overwrite */ 0x30, 0x30] /* Data type field ?? */ .concat(fileSize.hexToAscii(8)) /* File size field */ , 8); let index = 18; //Set Size of Data {1-6} (SD{1-6}) fields commandArguments.forEach(element => p7.sendBuffer.set(element.length.hexToAscii(2), (index += 2))); //Set Data {1-6} (D{1-6}) fields let data = commandArguments.join(''); p7.sendBuffer.set(data.toAscii(), 32); return data.length + 24; /* Overwrite (OW), Data type (DT), File size (FS) and Data size (DS) subfields of the Data (D) field of the packet */; } /* Create an extended command packet and push it to p7.sendBuffer */ /* return it's length */ p7.make.extendedCommandPacket = function (subtype, dataType, fileSize, commandArguments) { let dataSize = p7.make.commandPacketDataField(dataType, fileSize, commandArguments); /* Data field */ //Set Type (T), Subtype (ST), Extended (EX) and Data size (DT) fields p7.sendBuffer.set([0x01] /* Type field */ .concat(subtype.hexToAscii(2)) /* Subtype field */ .concat([0x31]) /* Extended field */ .concat(dataSize.hexToAscii(4)) /* Data size field */ , 0); //Set Checksum (CS) field p7.sendBuffer.set(p7.sendBuffer.slice(1, dataSize + 8).p7Checksum().hexToAscii(2), dataSize + 8); //Return it's length return dataSize + 10 /* Type (T), Subtype (ST), Extended (EX), Data size (DT) and Checksum (CS) fields */; } /* Create a data packet and push it to p7.sendBuffer */ /* return it's length */ p7.make.dataPacket = function (subType, totalNumber, currentNumber, data) { /* Set Type (T), Subtype (ST), Extended (EX), Data size (DS), * Data (D) fields and Data (D) subfields : * Total Number (TN), Current Number (CN), and Data (DD) */ p7.sendBuffer.set([0x02] /* Type field */ .concat(subType.hexToAscii(2)) /* Subtype (ST) field */ .concat([0x31]) /* Extended (EX) field */ .concat( /* Data size (DS) field */ (data.length + 8 /* Total number (TN) and Current number (CN) subfields */ ).hexToAscii(4)) .concat(totalNumber.hexToAscii(4)) /* Total number (TN) subfield */ .concat(currentNumber.hexToAscii(4)) /* Current number (CN) subfield */ .concat(data) /* Data (DD) subfield */ , 0); //Set Checksum (CS) field p7.sendBuffer.set(p7.sendBuffer.slice(1, data.length + 16).p7Checksum().hexToAscii(2), data.length + 16); //Return it's length return data.length + 18 /* All other fields and subfields */; } /* ---------------------Packet decoding--------------------- */ /* Return all the informations of the device, and trim string's overage (255) */ p7.decode.extendedAckPacket = () => { return { hardwareIdentifier : p7.receiveBuffer.slice(8, 16).asciiToString(), processorIdentifier : p7.receiveBuffer.slice(16, 32).asciiToString(), preprogrammedROMCapacity : p7.receiveBuffer.slice(32, 40).asciiToString(), flashROMCapacity : p7.receiveBuffer.slice(40, 48).asciiToString(), ramCapacity : p7.receiveBuffer.slice(48, 56).asciiToString(), prepogrammedROMVersion : p7.receiveBuffer.slice(56, 72).filter(number => number !== 255).asciiToString(), bootCodeVersion : p7.receiveBuffer.slice(72, 88).filter(number => number !== 255).asciiToString(), bootCodeOffset : p7.receiveBuffer.slice(88, 96).filter(number => number !== 255).asciiToString(), bootCodeSize : p7.receiveBuffer.slice(96, 104).filter(number => number !== 255).asciiToString(), osCodeVersion : p7.receiveBuffer.slice(104, 120).filter(number => number !== 255).asciiToString(), osCodeOffset : p7.receiveBuffer.slice(120, 128).asciiToString(), osCodeSize : p7.receiveBuffer.slice(128, 136).asciiToString(), protocolVersion : p7.receiveBuffer.slice(136, 140).asciiToString(), productID : p7.receiveBuffer.slice(140, 156).filter(number => number !== 255).asciiToString(), username : p7.receiveBuffer.slice(156, 172).filter(number => number !== 255).asciiToString() } }; /* Rturn the value (as an number) of the Filesize (FS) subfield of the Data (D) field of an extended command packet */ p7.decode.commandPacketFilesize = () => p7.receiveBuffer.slice(12, 20).asciiToHex(); /* ---------------------Packet receiving--------------------- */ p7.receive.packet = async function () { console.log("%cReceiving...", "color: orange"); let transferInResult = undefined; try { while (! (transferInResult = await p7.device.transferIn(2, 64)).data.byteLength) console.log('The buffer was empty, trying again!'); p7.receiveBuffer.set(new Uint8Array(transferInResult.data.buffer), 0); } catch (err) { throw new Error("Couldn't receive the first 64 bytes of the packet: " + err.message); } let packetInfo = {} packetInfo.length = 6; /* Type (T), Subtype (S), Extended (EX) and Checksum (CS) fields */ //Set Type (T) field packetInfo.type = p7.receiveBuffer[0]; //Set Subtype (ST) field packetInfo.subtype = p7.receiveBuffer.slice(1, 2).asciiToHex(); //Set Extended (EX) field packetInfo.extended = (p7.receiveBuffer[3] === 0x31); if (packetInfo.extended) { //Set Data size (DS) field packetInfo.dataSize = p7.receiveBuffer.slice(4, 8).asciiToHex(); packetInfo.length += packetInfo.dataSize + 4 /* Data size field */; for (let transfered = 64 ; transfered < packetInfo.length ; transfered += 64) { try { transferInResult = await p7.device.transferIn(2, 64); } catch (err) { throw new Error("Couldn't receive the " + transfered + "th and following 64 bytes of the packet: " + err.message) } p7.receiveBuffer.set(new Uint8Array(transferInResult.data.buffer), transfered); } } /* Compute checksum */ let checksumError = ((packetInfo.checksum = p7.receiveBuffer.slice(packetInfo.length - 2, packetInfo.length).asciiToHex()) !== p7.receiveBuffer.slice(1, packetInfo.length - 2).p7Checksum()); if (checksumError && (checksumTries++) < 3) { console.log('there was a checksum error'); try { packetInfo = await p7.send.packet(p7.make.basicPacket(p7PacketType.error, errorSubtype.resendRequest)); } catch (err) { throw new Error("Couldn't send the resend request: " + err.message); } } if (checksumTries === 4) throw new Error("There were three checksum in a row, the calculator or the cable may be broken!"); return packetInfo; } /* ---------------------Packet sending--------------------- */ p7.send.packet = async function (length) { let responsePacketInfo = undefined; let tries = 0; console.log("%cSending packet...", "color: green"); for ( ; tries === 0 || (responsePacketInfo.type === p7PacketType.error && responsePacketInfo.subtype === errorSubtype.resendRequest && tries < 3) ; tries++) { try { await p7.device.transferOut(1, p7.sendBuffer.slice(0, length)); } catch (err) { throw new Error("Couldn't send the packet: " + err.message); } try { responsePacketInfo = await p7.receive.packet(); } catch (err) { throw new Error("Couldn't receive the response packet: " + err.message); } } if (tries === 3) throw new Error("Received three resend request in a row, abandon"); return responsePacketInfo; }; /* ---------------------Initialization and exiting--------------------- */ /* Initiate the connexion with the calculator */ p7.init = async function () { try { p7.device = await navigator.usb.requestDevice({filters:[ {'vendorId': 0x07cf , 'productId': 0x6101}, /* fx-9750gII */ {'vendorId': 0x07cf , 'productId': 0x6102}] /* fx-CP400 */ }); } catch (err) { throw new Error("Couldn't find the calculator: " + err.message); } await p7.device.open(); if (p7.device.configuration === null) await p7.device.selectConfiguration(1); /* Only existing one so it should be selected by default, just checking */ if (p7.device.configuration.interfaces[0].claimed === false) await p7.device.claimInterface(0); /* Same as for the configuration */ console.log(p7.device); /* Receive and send some usb control message, as defined in fxReverse documentation * * Adapted version (some parameters changed since fxReverse's writing) of usb_control_msg() function's protoype * from linux-manual : https://manpages.debian.org/jessie-backports/linux-manual-4.8/usb_control_msg.9.en.html * * int usb_control_msg(struct usb_device * dev, * uint8_t requesttype, * uint8_t request, * uint16_t value, * uint16_t index, * void * data, * uint16_t size, * int timeout); * * fxReverse2:13: * * int init_connection() { * char *buffer = calloc(0x29, sizeof(char)); * * //Receive 0x12 bytes of data * usb_control_message(usb_handle, * 0x80, = 0b10000000 * - D7 = 1 -> Data transfer direction: Device-to-host * - D6...D5 = 0 -> Type: Standard * - D4...D0 = 0 -> Recipient: Device * 0x6, * 0x0100, * 0x0, * buffer, * 0x12, * 200); * * //Same arguments (except timeout), but value is 0x200 and it receives 0x29 bytes of data * usb_control_message(usb_handle, 0x80, 0x6, 0x200, 0, buffer, 0x29, 250); * * * usb_control_message(usb_handle, * 0x41, = 0b01000001 * - D7 = 0 -> Data transfer direction: Host-to-device * - D6...D5 = 2 -> Type: Vendor * - D4...D0 = 1 -> Interface: Interface * 0x1, * 0x0, * 0, * buffer, * 0x0, * 250); * * free(buffer); * return 0; * } **************************************************************** let transferInResult = undefined; let controlData = undefined; try { transferInResult = await p7.device.controlTransferIn({ requestType: 'standard', recipient: 'device', request: 0x06, // GET_DESCRIPTOR value: 0x0100, // Descriptor Type and Descriptor Index index: 0x0000 }, 0x12); // Length } catch (err) { console.log(err); } controlData = new Uint8Array(transferInResult.data.buffer); console.log('vendor id : 0x' + controlData[9].toString(16).padStart(2, 0) + controlData[8].toString(16).padStart(2,0)); console.log('product id : 0x' + controlData[11].toString(16).padStart(2, 0) + controlData[10].toString(16).padStart(2,0)); try { transferInResult = await p7.device.controlTransferIn({ requestType: 'standard', recipient: 'device', request: 0x06, // GET_DESCRIPTOR value: 0x0200, // Descriptor Type and Descriptor Index index: 0x0000 }, 0x29); // Length } catch (err) { console.log(err); } controlData = new Uint8Array(transferInResult.data.buffer); console.log(controlData); try { await p7.device.controlTransferOut({ requestType: 'vendor', recipient: 'interface', request: 0x01, value: 0x0000, index: 0x0000 }, controlData); } catch (err) { console.log(err); } */ /* Everything went right */ return 0; } /* End the connexion with the calculator */ p7.exit = async function () { } document.getElementById('connect').addEventListener('click', async function () { try { await p7.init(); } catch (err) { console.error(err); return err; } try { await p7.send.packet(p7.make.basicPacket(p7PacketType.check, checkSubtype.initialization)); } catch (err) { console.error(err); return err; } try { await p7.send.packet(p7.make.basicPacket(p7PacketType.command, sysCommandSubtype.getDeviceInfo)); } catch (err) { console.error(err); return err; } console.log(p7.decode.extendedAckPacket()); }); })();