webP7/internal.js

456 lines
19 KiB
JavaScript

"use strict";
var p7 = {};
(function() {
/* ---------------------Utilitaries--------------------- */
/* 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();
}
/* Sum individual bytes
* NOT + 1, as defined in fxReverse documentation.
* Be sure it's under 256! */
Array.prototype.p7Checksum = function () {
return (((~(this.reduce((accumulator, currentValue) => accumulator + currentValue))) + 1) & 0xFF)
}
/* Add the Checksum (CS) field to a packet */
Array.prototype.addP7Checksum = function () {
return this.concat(this.slice(1, this.length).p7Checksum().hexToAscii(2));
}
/* Convert an ascii array into a string */
Array.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 */
Array.prototype.asciiToHex = function () {
return Number('0x' + this.asciiToString());
}
/* Return the decoded Data (D) field */
Array.prototype.decodeDataField = function () {
let buffer = [...this]
buffer.forEach((element, index) => {
if (element === 0x5C)
buffer.splice(index, 2, buffer[index + 1] - (buffer[index + 1] !== 0x5C) * 0x20);
});
return buffer;
}
/* Return the encoded Data (D) field */
Array.prototype.encodeDataField = function () {
return this.flatMap((element) => (element < 0x20) ? [0x5C, (element + 0x20)] : (element === 0x5C) ? [0x5C, 0x5C] : element);
}
/* Return all the informations of the device, and trim string's overage (255) */
Array.prototype.extendedAckPacket = function () {
return {
hardwareIdentifier : this.slice(8, 16).asciiToString(),
processorIdentifier : this.slice(16, 32).asciiToString(),
preprogrammedROMCapacity : this.slice(32, 40).asciiToString(),
flashROMCapacity : this.slice(40, 48).asciiToString(),
ramCapacity : this.slice(48, 56).asciiToString(),
prepogrammedROMVersion : this.slice(56, 72).asciiToString(),
bootCodeVersion : this.slice(72, 88).asciiToString(),
bootCodeOffset : this.slice(88, 96).asciiToString(),
bootCodeSize : this.slice(96, 104).asciiToString(),
osCodeVersion : this.slice(104, 120).filter(number => number !== 0xFF).asciiToString(),
osCodeOffset : this.slice(120, 128).asciiToString(),
osCodeSize : this.slice(128, 136).asciiToString(),
protocolVersion : this.slice(136, 140).asciiToString(),
productID : this.slice(140, 156).filter(number => number !== 0xFF).asciiToString(),
username : this.slice(156, 172).filter(number => number !== 0xFF).asciiToString()
}
};
/* Return the value (as an number) of the Overwrite (OW) subfield of the Data (D) field of an extended command packet */
Array.prototype.commandPacketOverWrite = function () {
return this.slice(8, 10).asciiToHex();
}
/* Return the value (as an number) of the Data type (DT) subfield of the Data (D) field of an extended command packet */
Array.prototype.commandPacketDataType = function () {
return this.slice(10, 12).asciiToHex();
}
/* Return the value (as an number) of the FileSize (FS) subfield of the Data (D) field of an extended command packet */
Array.prototype.commandPacketFileSize = function () {
return this.slice(12, 20).asciiToHex();
}
/* Return the value (as a string) of the requested Data (D) subfield of the Data (D) field of an extended command packet */
Array.prototype.commandPacketDataField = function (field) {
let fieldSize = [];
/* Decode the length of Data {1 - ${length}} (D{1 - ${length}}) subfields */
for (let index = 20, i = 0; i < field ; i++)
fieldSize.push(this.slice(index, (index += 2)).asciiToHex());
/* Get the requested field size */
let fieldLength = fieldSize.pop();
/* Get the index of the requested field */
let fieldIndex = (fieldSize.length) ? fieldSize.reduce((accumulator, currentValue) => accumulator + currentValue) + 32 : 32;
/* Return the ascii array as a string */
return this.slice(fieldIndex, fieldIndex + fieldLength).asciiToString();
};
/* Return an array containing the value (as a string) of all the Data (D) subfield of the Data (D) field of an extended command packet */
Array.prototype.commandPacketAllDataFields = function () {
let dataFieldSize = [];
let index = 20
while(index < 32)
dataFieldSize.push(this.slice(index, (index += 2)).asciiToHex());
return dataFieldSize.map((element) => this.slice(index, (index += element)).asciiToString());
}
/* Return the value (as a number) of the Total number (CN) subfield of the Data (D) field of a data packet */
Array.prototype.dataPacketDataFieldGetTotalNumber = function () {
return this.slice(0, 4).asciiToHex();
}
/* Return the value (as a number) of the Current number (CN) subfield of the Data (D) field of a data packet */
Array.prototype.dataPacketDataFieldGetCurrentNumber = function () {
return this.slice(4, 8).asciiToHex();
}
/* Return the Data (D) subfield of the Data (D) field of a data packet */
Array.prototype.dataPacketDataFieldGetData = function () {
return this.slice(8, this.length);
}
/* ---------------------Packet making--------------------- */
p7.make = {
/* Create a basic (Non-Extended) packet
* Return the packet */
basicPacket: (type, subtype) =>
([type, //Type (T)
...subtype.hexToAscii(2), //Subtype (ST)
0x30].addP7Checksum()), //Extended (EX)
/* Create an Extended packet
* Return the packet */
extendedPacket: (type, subtype, dataField) =>
([type,
...subtype.hexToAscii(2),
0x31,
...(dataField = dataField.encodeDataField()).length.hexToAscii(4),
...dataField].addP7Checksum()),
/* Create a buffer containing the Data (D) field of a command packet
* Return the buffer */
commandPacketDataField: (datatype, fileSize, commandArguments) =>
([0x30, 0x30, //Overwrite (OW)
...datatype.hexToAscii(2), //Data type (DT) ??
...fileSize.hexToAscii(8), //File size (FS)
...commandArguments.flatMap((element) => element.length.hexToAscii(2)), //Size of Data {1..6} (SD{1..6})
...commandArguments.join('').toAscii()]), //Data {1..6} (D{1..6})
/* Create a buffer containing the Data (D) field of a data packet
* Return the buffer */
dataPacketDataField: (totalNumber, currentNumber, data) =>
([...totalNumber.hexToAscii(4), //Total number (TN) subfield
...currentNumber.hexToAscii(4), //Current number (CN) subfield
...data]), //Data (DD) subfield
};
/* ---------------------Packet receiving--------------------- */
p7.receive = {
packet: function () {
console.log("%cReceiving...", "color: orange");
var packetInfo = {};
return p7.device.transferIn(2, 64)
.then(function receive(transferInResult) {
if (!transferInResult.data.byteLength) {
console.log('buffer was empty, retrying');
return p7.device.transferIn(2, 64).then(receive);
}
packetInfo.receiveBuffer = Array.from(new Uint8Array(transferInResult.data.buffer));
packetInfo.length = 6; //Type (T), Subtype (S), Extended (EX) and Checksum (CS) fields
//Set Type (T) field
packetInfo.type = packetInfo.receiveBuffer[0];
//Set Subtype (ST) field
packetInfo.subtype = packetInfo.receiveBuffer.slice(1, 2).asciiToHex();
//Set Extended (EX) field
if ((packetInfo.extended = (packetInfo.receiveBuffer[3] === 0x31))) {
console.log('packet is extended');
//Set Data size (DS) field
packetInfo.dataSize = packetInfo.receiveBuffer.slice(4, 8).asciiToHex();
packetInfo.length += packetInfo.dataSize + 4; //Data size field
if (64 < packetInfo.length)
return p7.device.transferIn(2, packetInfo.length - 64)
.then((transferInResult) => packetInfo.receiveBuffer.push(...(new Uint8Array(transferInResult.data.buffer))));
}
}).then(() => {
if (packetInfo.extended)
packetInfo.data = packetInfo.receiveBuffer.slice(8, packetInfo.receiveBuffer.length - 2).decodeDataField();
//Compute checksum
let checksumError = ((packetInfo.checksum = packetInfo.receiveBuffer.slice(packetInfo.length - 2, packetInfo.length).asciiToHex()) !== packetInfo.receiveBuffer.slice(1, packetInfo.length - 2).p7Checksum());
return packetInfo;
}).catch((err) => {
err.message = "Couldn't receive the packet: " + err.message;
throw err;
});
},
data: function (responsePacketInfo) {
var data = [];
return p7.send.packet(p7.make.basicPacket(p7PacketType.ack, ackSubtype.default))
.then(function receiveData(responsePacketInfo) {
if (responsePacketInfo.type !== p7PacketType.data)
return [data.flat(), responsePacketInfo];
data.push(responsePacketInfo.data.dataPacketDataFieldGetData());
return p7.send.packet(p7.make.basicPacket(p7PacketType.ack, ackSubtype.default)).then(receiveData);
});
}
};
/* ---------------------Packet sending--------------------- */
p7.send = {
packet: function (buffer) {
console.log("%cSending packet...", "color: green");
return p7.device.transferOut(1, Uint8Array.from(buffer))
.catch((err) => {
err.message = "Couldn't send the packet: " + err.message;
throw err;
})
.then((transferOutResult) => p7.receive.packet())
.catch((err) => {
err.message = "Couldn't receive the response packet: " + err.message;
throw err;
});
},
/* Send a single command
* Return the informations about the response ack packet */
singleCommand: (subtype, datatype, fileSize, commandArguments) =>
p7.send.packet(p7.make.extendedPacket(p7PacketType.command, subtype, p7.make.commandPacketDataField(datatype, fileSize, commandArguments)))
.then((responsePacketInfo) => {
if (!(responsePacketInfo.type === p7PacketType.ack && responsePacketInfo.subtype === ackSubtype.default))
throw new Error("The calculator didn't send the expected ack packet!:" +
"\t{Type: " + responsePacketInfo.type +
"\tSubtype: " + responsePacketInfo.subtype + "}");
return responsePacketInfo;
}),
/* Send a single command request
* Return an array containing the filtered (with `callbackFunction`) Data (D) field of each response command packet */
singleCommandRoleswap: function (subtype, datatype, fileSize, commandArguments, callbackFunction) {
var filteredData = [];
return p7.send.singleCommand(subtype, datatype, fileSize, commandArguments)
.then(() => p7.send.packet(p7.make.basicPacket(p7PacketType.roleswap, roleswapSubtype.default)))
.then(function receiveRequestedCommand(responsePacketInfo) {
if (responsePacketInfo.type !== p7PacketType.command)
return filteredData;
filteredData.push(callbackFunction(responsePacketInfo.receiveBuffer));
return p7.send.packet(p7.make.basicPacket(p7PacketType.ack, ackSubtype.default)).then(receiveRequestedCommand);
});
},
dataCommand: function (subtype, datatype, fileSize, commandArguments, data) {
var currentPacketNumber = 0;
var lastPacketSize = data.length & 0xFF; //Equivalent to data.length % 256, but way faster !
var totalPacketNumber = (data.length >> 8) + (lastPacketSize !== 0); //Equivalent to data.length / 256, but way way waaaaaay faster, and return an integer quotient, not a decimal one !!
return p7.send.singleCommand(subtype, datatype, fileSize, commandArguments)
.then(function sendDataPackets(result) {
if ((currentPacketNumber += 1) === totalPacketNumber)
return p7.send.packet(p7.make.extendedPacket(p7PacketType.data, subtype, p7.make.dataPacketDataField(totalPacketNumber, currentPacketNumber, data.slice(data.length - lastPacketSize, data.length))))
return p7.send.packet(p7.make.extendedPacket(p7PacketType.data, subtype, p7.make.dataPacketDataField(totalPacketNumber, currentPacketNumber, data.slice(((currentPacketNumber - 1) << 8), (currentPacketNumber << 8))))).then(sendDataPackets);
});
},
dataCommandRoleswap: async function (subtype, datatype, fileSize, commandArguments, callbackFunction) {
var filteredData = [];
return p7.send.singleCommand(subtype, datatype, fileSize, commandArguments)
.then(() => p7.send.packet(p7.make.basicPacket(p7PacketType.roleswap, roleswapSubtype.default)))
.then(function receiveRequestedData(responsePacketInfo) {
if (responsePacketInfo.type === p7PacketType.roleswap)
return filteredData;
let command = responsePacketInfo.receiveBuffer;
return p7.receive.data()
.then((dataResponse) => {
filteredData.push(callbackFunction(command, dataResponse[0]));
return receiveRequestedData(dataResponse[1]);
});
});
}
};
/* ---------------------Initialization and exiting--------------------- */
/* Connect to the calculator */
p7.connect = () =>
navigator.usb.requestDevice({filters:[
{'vendorId': 0x07cf , 'productId': 0x6101}, //fx-9750gII
{'vendorId': 0x07cf , 'productId': 0x6102}] //fx-CP400
}).then((device) => {
p7.device = device
return p7.device.open();
}).then(() => {
if (p7.device.configuration === null)
return p7.device.selectConfiguration(1); //Only existing one so it should be selected by default, just checking
}).then(() => {
if (p7.device.configuration.interfaces[0].claimed === false)
return p7.device.claimInterface(0); //Same as for the configuration
});
/* 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);
} */
/* Initialize the connection with the calculator */
p7.init = () =>
p7.connect()
.catch(() => {
throw new Error("Couldn't connect to the calculator!");
}).then(() => p7.send.packet(p7.make.basicPacket(p7PacketType.check, checkSubtype.initialization))
.catch(() => {
throw new Error("Couldn't send the initialisation packet!");
}).then(() => p7.send.packet(p7.make.basicPacket(p7PacketType.command, sysCommandSubtype.getDeviceInfo))
.catch(() => {
throw new Error("Couldn't ask for the device infos!");
}).then((responsePacketInfo) => responsePacketInfo.receiveBuffer.extendedAckPacket())));
/* End the connexion with the calculator */
p7.exit = function () {
}
})();