From 14d18f85d15d2b9fc9cc222fb0dd2a4f6f2d02ab Mon Sep 17 00:00:00 2001 From: commandblockguy Date: Tue, 7 Sep 2021 00:07:41 -0500 Subject: [PATCH 1/9] Support fragmented virtual packets in expect --- src/byte-mangling.js | 18 +++++++++++++++++- src/dusb/device.js | 17 ++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/byte-mangling.js b/src/byte-mangling.js index c3c5e90..1434552 100644 --- a/src/byte-mangling.js +++ b/src/byte-mangling.js @@ -13,7 +13,8 @@ module.exports = { asciiToBytes, bytesToAscii, - chunkArray + chunkArray, + mergeBuffers } function constructRawPacket(packet) { @@ -195,3 +196,18 @@ function chunkArray(array, sizes) { } return result; } + +// Merge multiple byte arrays into a single byte array +function mergeBuffers(buffers) { + let totalLength = 0; + for(const buffer of buffers) { + totalLength += buffer.byteLength; + } + const result = new Uint8Array(totalLength); + let offset = 0; + for(const buffer of buffers) { + result.set(buffer, 0); + offset += buffer.byteLength; + } + return result; +} diff --git a/src/dusb/device.js b/src/dusb/device.js index d792785..681d86c 100644 --- a/src/dusb/device.js +++ b/src/dusb/device.js @@ -54,16 +54,23 @@ module.exports = class Device { } // Expect a virtual packet of a certain type, returns the packet if the type - // matches. Throws an error otherwise. Note that this function does not - // support a fragmented data transfer, and as such can't be used to receive a - // program from the calculator. + // matches. Throws an error otherwise. async expect(virtualType) { - const raw = b.destructRawPacket(await this._receive()); + let raw; + const raw_packets = []; + + // Receive raw packets until we get a VIRT_DATA_LAST packet + do { + raw = b.destructRawPacket(await this._receive()); + raw_packets.push(raw); + } while ( raw.type == v.rawPacketTypes.DUSB_RPKT_VIRT_DATA ); if ( raw.type != v.rawPacketTypes.DUSB_RPKT_VIRT_DATA_LAST ) throw `Expected raw packet type VIRT_DATA_LAST, but got ${raw.type} instead`; - const virtual = b.destructVirtualPacket(raw.data); + const combinedData = b.mergeBuffers(raw_packets.map((x) => x.data)); + + const virtual = b.destructVirtualPacket(combinedData); switch(virtual.type) { From b54c85e397c8a5e1cb3531f081d0f7720973e555 Mon Sep 17 00:00:00 2001 From: commandblockguy Date: Mon, 13 Sep 2021 00:35:10 -0500 Subject: [PATCH 2/9] Add device.expectAny --- src/dusb/device.js | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/dusb/device.js b/src/dusb/device.js index 681d86c..e1ee195 100644 --- a/src/dusb/device.js +++ b/src/dusb/device.js @@ -56,6 +56,14 @@ module.exports = class Device { // Expect a virtual packet of a certain type, returns the packet if the type // matches. Throws an error otherwise. async expect(virtualType) { + const packet = await this.expectAny(); + if ( packet.type != virtualType ) { + throw `Expected virtual packet type ${virtualType}, but got ${packet.type} instead`; + } + return packet; + } + + async expectAny() { let raw; const raw_packets = []; @@ -72,24 +80,14 @@ module.exports = class Device { const virtual = b.destructVirtualPacket(combinedData); - switch(virtual.type) { - - // Did we get what we expected? - case virtualType: - await this._sendAck(); - return virtual; - - // If not, did we get a delay request? - case v.virtualPacketTypes.DUSB_VPKT_DELAY_ACK: - await this._sendAck(); - const delay = b.bytesToInt(virtual.data); - await this.wait(delay / 1000); - return this.expect(virtualType); - - // Otherwise, we have a problem here - default: - throw `Expected virtual packet type ${virtualType}, but got ${virtual.type} instead`; - + if ( virtual.type == v.virtualPacketTypes.DUSB_VPKT_DELAY_ACK ) { + await this._sendAck(); + const delay = b.bytesToInt(virtual.data); + await this.wait(delay / 1000); + return await this.expectAny(); + } else { + await this._sendAck(); + return virtual; } } From 48d72b939399b8f929d9acf20bb1a0e93a91ece5 Mon Sep 17 00:00:00 2001 From: commandblockguy Date: Tue, 7 Sep 2021 23:47:35 -0500 Subject: [PATCH 3/9] Improve device parameter retrieval Add a general purpose function for getting parameters Allow non-integer parameters to be destructed Don't get out of sync if the parameter is not returned Change format of destructed parameters --- src/byte-mangling.js | 26 +++++++++++++------------- src/dusb/ti84series.js | 38 ++++++++++++++++++++++---------------- test/byte-mangling.test.js | 32 +++++++------------------------- 3 files changed, 42 insertions(+), 54 deletions(-) diff --git a/src/byte-mangling.js b/src/byte-mangling.js index 1434552..af68a75 100644 --- a/src/byte-mangling.js +++ b/src/byte-mangling.js @@ -77,10 +77,10 @@ function constructParameters(params) { return new Uint8Array([ intToBytes(params.length, 2), - ...params.map(param => [ + ...params.map(param => [ intToBytes(param.type, 2), - intToBytes(param.size, 2), - intToBytes(param.value, param.size) + intToBytes(param.value.length, 2), + ...param.value ].flat()) ].flat()); } @@ -98,18 +98,18 @@ function destructParameters(params) { * 7-X X parameter value * X- repeat pattern N times */ - const result = []; + const result = {}; const count = bytesToInt(params.slice(0,2)); let j = 2; for ( let i = 0; i < count; i++ ) { - const size = bytesToInt(params.slice(j+3,j+5)); - result.push({ - type: bytesToInt(params.slice(j,j+2)), - ok: params[j+2] == 0, - size, - value: bytesToInt(params.slice(j+5, j+5+size)) - }); - j += 5 + size; + const type = bytesToInt(params.slice(j,j+2)); + if(params[j+2] == 0) { + const size = bytesToInt(params.slice(j+3,j+5)); + result[type] = params.slice(j+5, j+5+size); + j += 5 + size; + } else { + j += 3; + } } return result; } @@ -206,7 +206,7 @@ function mergeBuffers(buffers) { const result = new Uint8Array(totalLength); let offset = 0; for(const buffer of buffers) { - result.set(buffer, 0); + result.set(buffer, offset); offset += buffer.byteLength; } return result; diff --git a/src/dusb/ti84series.js b/src/dusb/ti84series.js index 2cac241..aabbf25 100644 --- a/src/dusb/ti84series.js +++ b/src/dusb/ti84series.js @@ -62,25 +62,34 @@ module.exports = class Ti84series { await this._d.expect(v.virtualPacketTypes.DUSB_VPKT_DATA_ACK); } - // Request the amount of free RAM and Flash memory - async getFreeMem() { + async getParameters(attributes) { await this._d.send({ type: v.virtualPacketTypes.DUSB_VPKT_PARM_REQ, data: [ - 0, 2, - b.intToBytes(v.parameters.DUSB_PID_FREE_RAM, 2), - b.intToBytes(v.parameters.DUSB_PID_FREE_FLASH, 2) + b.intToBytes(attributes.length, 2), + ...attributes.map(x => b.intToBytes(x, 2)) ].flat() }); const paramsResponse = await this._d.expect(v.virtualPacketTypes.DUSB_VPKT_PARM_DATA); - const params = b.destructParameters(paramsResponse.data); - if ( !params.every(p => p.ok) ) - throw 'Could not succesfully get all parameters'; + return b.destructParameters(paramsResponse.data); + } + + // Request the amount of free RAM and Flash memory + async getFreeMem() { + const params = await this.getParameters([ + v.parameters.DUSB_PID_FREE_RAM, + v.parameters.DUSB_PID_FREE_FLASH, + ]); + + if ( !params[v.parameters.DUSB_PID_FREE_RAM] ) + throw 'Could not get amount of free RAM'; + if ( !params[v.parameters.DUSB_PID_FREE_FLASH] ) + throw 'Could not get amount of free FLASH'; return { - ram: params.find(p => p.type == v.parameters.DUSB_PID_FREE_RAM).value, - flash: params.find(p => p.type == v.parameters.DUSB_PID_FREE_FLASH).value, + ram: b.bytesToInt(params[v.parameters.DUSB_PID_FREE_RAM]), + flash: b.bytesToInt(params[v.parameters.DUSB_PID_FREE_FLASH]), }; } @@ -119,18 +128,15 @@ module.exports = class Ti84series { return b.constructParameters([ { type: v.attributes.DUSB_AID_VAR_TYPE, - size: 4, - value: 0xF0070000 + entry.type + value: b.intToBytes(0xF0070000 + entry.type, 4) }, { type: v.attributes.DUSB_AID_ARCHIVED, - size: 1, - value: entry.attributes && entry.attributes.archived ? 1 : 0 + value: b.intToBytes(entry.attributes && entry.attributes.archived ? 1 : 0, 1) }, { type: v.attributes.DUSB_AID_VAR_VERSION, - size: 4, - value: entry.attributes && entry.attributes.version || 0 + value: b.intToBytes(entry.attributes && entry.attributes.version || 0, 1) } ]); } diff --git a/test/byte-mangling.test.js b/test/byte-mangling.test.js index 4b55c03..d1bfa06 100644 --- a/test/byte-mangling.test.js +++ b/test/byte-mangling.test.js @@ -67,13 +67,11 @@ describe('byte-mangling.js', () => { expect(b.constructParameters([ { type: 0x59FA, - size: 3, - value: 0x123456 + value: new Uint8Array([0x12,0x34,0x56]) }, { type: 0x0001, - size: 1, - value: 6 + value: new Uint8Array([6]) } ])).toEqual( new Uint8Array([0,2, 0x59,0xFA, 0,3, 0x12,0x34,0x56, 0x00,0x01, 0,1, 6]) @@ -87,29 +85,13 @@ describe('byte-mangling.js', () => { new Uint8Array([ 0,3, 0xDE,0x35, 0, 0,2, 0xAB,0xCD, - 0x02,0x00, 1, 0,5, 0,0,0x12,0x34,0x56, + 0x02,0x00, 1, 0x00,0x4A, 0, 0,5, 0x12,0x34,0x56,0x78,0x90 ]) - )).toEqual([ - { - type: 0xDE35, - ok: true, - size: 2, - value: 0xABCD - }, - { - type: 0x0200, - ok: false, - size: 5, - value: 0x123456 - }, - { - type: 0x004A, - ok: true, - size: 5, - value: 0x1234567890 - } - ]); + )).toEqual({ + "56885": new Uint8Array([0xAB,0xCD]), + "74": new Uint8Array([0x12,0x34,0x56,0x78,0x90]) + }); }); }); From 0bb14dc30480c5f7484efd2351dfde7eb1883ac5 Mon Sep 17 00:00:00 2001 From: commandblockguy Date: Mon, 13 Sep 2021 13:33:52 -0500 Subject: [PATCH 4/9] Add device.getDirectory --- src/dusb/magic-values.js | 1 + src/dusb/ti84series.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/dusb/magic-values.js b/src/dusb/magic-values.js index 93237bd..3144429 100644 --- a/src/dusb/magic-values.js +++ b/src/dusb/magic-values.js @@ -89,6 +89,7 @@ module.exports = { // https://github.com/debrouxl/tilibs/blob/master/libticalcs/trunk/src/dusb_cmd.h attributes: { + DUSB_AID_VAR_SIZE: 0x01, DUSB_AID_VAR_TYPE: 0x02, DUSB_AID_ARCHIVED: 0x03, DUSB_AID_VAR_VERSION: 0x08, diff --git a/src/dusb/ti84series.js b/src/dusb/ti84series.js index aabbf25..b78ef07 100644 --- a/src/dusb/ti84series.js +++ b/src/dusb/ti84series.js @@ -93,6 +93,37 @@ module.exports = class Ti84series { }; } + async getDirectory() { + await this._d.send({ + type: v.virtualPacketTypes.DUSB_VPKT_DIR_REQ, + data: [ + 0, 0, 0, 3, // attribute count + 0, 1, // size + 0, 2, // type + 0, 3, // archived + 0, 1, 0, 1, 0, 1, 1 // ??? + ] + }); + const result = []; + while ( true ) { + const packet = await this._d.expectAny(); + if ( packet.type == v.virtualPacketTypes.DUSB_VPKT_EOT ) + break; + if ( packet.type != v.virtualPacketTypes.DUSB_VPKT_VAR_HDR ) + throw `Expected virtual packet type ${v.virtualPacketTypes.DUSB_VPKT_VAR_HDR}, but got ${packet.type} instead`; + const nameLength = b.bytesToInt(packet.data.slice(0, 2)); + const name = b.bytesToAscii(packet.data.slice(2, 2 + nameLength)); + const attributes = b.destructParameters(packet.data.slice(2 + nameLength + 1)); + + const size = b.bytesToInt(attributes[v.attributes.DUSB_AID_VAR_SIZE]); + const type = b.bytesToInt(attributes[v.attributes.DUSB_AID_VAR_TYPE].slice(2)); + const archived = attributes[v.attributes.DUSB_AID_ARCHIVED][0] == 1; + + result.push({name, size, type, archived}); + } + return result; + } + // Send a TI file to the calculator async sendFile(file) { for ( let i = 0; i < file.entries.length; i++ ) { From ed0ce02a1a7b0fe8645e204361573641d588053d Mon Sep 17 00:00:00 2001 From: commandblockguy Date: Thu, 16 Sep 2021 22:49:36 -0500 Subject: [PATCH 5/9] Add tests for mergeBuffers --- test/byte-mangling.test.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/byte-mangling.test.js b/test/byte-mangling.test.js index d1bfa06..04551b9 100644 --- a/test/byte-mangling.test.js +++ b/test/byte-mangling.test.js @@ -228,4 +228,28 @@ describe('byte-mangling.js', () => { }); }); + describe('mergeBuffers', () => { + it('combines multiple arrays', () => { + expect(b.mergeBuffers( + [new Uint8Array([1,2,3]), new Uint8Array([4,5,6]), new Uint8Array([7,8,9])] + )).toEqual( + new Uint8Array([1,2,3,4,5,6,7,8,9]) + ); + }); + it('handles empty arrays', () => { + expect(b.mergeBuffers( + [new Uint8Array([1,2,3]), new Uint8Array([]), new Uint8Array([7,8,9])] + )).toEqual( + new Uint8Array([1,2,3,7,8,9]) + ); + }); + it('preserves a single array', () => { + expect(b.mergeBuffers( + [new Uint8Array([1,2,3])] + )).toEqual( + new Uint8Array([1,2,3]) + ); + }); + }); + }); From e724b480c1905857dd77abcac70f47eb55ebd146 Mon Sep 17 00:00:00 2001 From: commandblockguy Date: Thu, 16 Sep 2021 23:17:18 -0500 Subject: [PATCH 6/9] Add replay from TI-83 Premium CE with getDirectory --- test/calculators/ti83pce.replay.json | 1 + test/calculators/ti83pce.test.js | 70 ++++++++++++++++++++++++++++ test/helpers/record.js | 2 + 3 files changed, 73 insertions(+) create mode 100644 test/calculators/ti83pce.replay.json create mode 100644 test/calculators/ti83pce.test.js diff --git a/test/calculators/ti83pce.replay.json b/test/calculators/ti83pce.replay.json new file mode 100644 index 0000000..c8bce90 --- /dev/null +++ b/test/calculators/ti83pce.replay.json @@ -0,0 +1 @@ +[{"action":"asyncFunctionCall","name":"requestDevice","parameters":[{"filters":[{"vendorId":1105,"productId":57352},{"vendorId":1105,"productId":57347}]}],"resolve":{"manufacturerName":"Texas Instruments Incorporated","productName":"TI-83 Premium CE","serialNumber":"Љ","_configurations":[{"configurationValue":1,"configurationName":"Љ","interfaces":[{"interfaceNumber":0,"alternates":[{"alternateSetting":0,"interfaceClass":255,"interfaceSubclass":1,"interfaceProtocol":0,"interfaceName":"Љ","endpoints":[{"endpointNumber":1,"direction":"in","type":"bulk","packetSize":64},{"endpointNumber":2,"direction":"out","type":"bulk","packetSize":64}]}],"_claimed":true,"_currentAlternate":0,"_handle":"1.9"}]},{"configurationValue":2,"configurationName":"Љ","interfaces":[{"interfaceNumber":0,"alternates":[{"alternateSetting":0,"interfaceClass":255,"interfaceSubclass":1,"interfaceProtocol":0,"interfaceName":"Љ","endpoints":[{"endpointNumber":1,"direction":"in","type":"bulk","packetSize":64},{"endpointNumber":2,"direction":"out","type":"bulk","packetSize":64}]}],"_claimed":false,"_currentAlternate":0,"_handle":"1.9"}]},{"configurationValue":3,"configurationName":"Љ","interfaces":[{"interfaceNumber":0,"alternates":[{"alternateSetting":0,"interfaceClass":255,"interfaceSubclass":1,"interfaceProtocol":0,"interfaceName":"Љ","endpoints":[{"endpointNumber":1,"direction":"in","type":"bulk","packetSize":64},{"endpointNumber":2,"direction":"out","type":"bulk","packetSize":64}]}],"_claimed":false,"_currentAlternate":0,"_handle":"1.9"}]}],"_currentConfiguration":1,"url":null,"_maxPacketSize":64,"_handle":"1.9","usbVersionMajor":2,"usbVersionMinor":0,"usbVersionSubminor":0,"deviceClass":0,"deviceSubclass":0,"deviceProtocol":0,"vendorId":1105,"productId":57352,"deviceVersionMajor":2,"deviceVersionMinor":6,"deviceVersionSubminor":0}},{"action":"propertyAccess","name":"then"},{"action":"propertyAccess","name":"vendorId","result":1105},{"action":"propertyAccess","name":"productId","result":57352},{"action":"propertyAccess","name":"vendorId","result":1105},{"action":"propertyAccess","name":"productId","result":57352},{"action":"propertyAccess","name":"productName","result":"TI-83 Premium CE"},{"action":"asyncFunctionCall","name":"open","parameters":[]},{"action":"propertyAccess","name":"configuration","result":{"configurationValue":1,"configurationName":"Љ","interfaces":[{"interfaceNumber":0,"alternates":[{"alternateSetting":0,"interfaceClass":255,"interfaceSubclass":1,"interfaceProtocol":0,"interfaceName":"Љ","endpoints":[{"endpointNumber":1,"direction":"in","type":"bulk","packetSize":64},{"endpointNumber":2,"direction":"out","type":"bulk","packetSize":64}]}],"_claimed":false,"_currentAlternate":0,"_handle":"1.9"}]}},{"action":"asyncFunctionCall","name":"selectConfiguration","parameters":[1]},{"action":"propertyAccess","name":"configuration","result":{"configurationValue":1,"configurationName":"Љ","interfaces":[{"interfaceNumber":0,"alternates":[{"alternateSetting":0,"interfaceClass":255,"interfaceSubclass":1,"interfaceProtocol":0,"interfaceName":"Љ","endpoints":[{"endpointNumber":1,"direction":"in","type":"bulk","packetSize":64},{"endpointNumber":2,"direction":"out","type":"bulk","packetSize":64}]}],"_claimed":false,"_currentAlternate":0,"_handle":"1.9"}]}},{"action":"asyncFunctionCall","name":"claimInterface","parameters":[0]},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":4,"4":1,"5":0,"6":0,"7":4,"8":0}],"resolve":{"bytesWritten":9,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":9,"byteOffset":0,"buffer":[0,0,0,4,2,0,0,3,255]},"status":"ok"}},{"action":"functionCall","name":"toString","parameters":[],"result":"[object Object]"},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":16,"4":4,"5":0,"6":0,"7":0,"8":10,"9":0,"10":1,"11":0,"12":3,"13":0,"14":1,"15":0,"16":0,"17":0,"18":0,"19":7,"20":208}],"resolve":{"bytesWritten":21,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":7,"byteOffset":0,"buffer":[0,0,0,2,5,224,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":15,"byteOffset":0,"buffer":[0,0,0,10,4,0,0,0,4,0,18,0,0,7,208]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":12,"4":4,"5":0,"6":0,"7":0,"8":6,"9":0,"10":7,"11":0,"12":2,"13":0,"14":14,"15":0,"16":17}],"resolve":{"bytesWritten":17,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":7,"byteOffset":0,"buffer":[0,0,0,2,5,224,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":39,"byteOffset":0,"buffer":[0,0,0,34,4,0,0,0,28,0,8,0,2,0,14,0,0,8,0,0,0,0,0,0,0,0,0,17,0,0,8,0,0,0,0,0,43,83,13]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":23,"4":4,"5":0,"6":0,"7":0,"8":17,"9":0,"10":9,"11":0,"12":0,"13":0,"14":3,"15":0,"16":1,"17":0,"18":2,"19":0,"20":3,"21":0,"22":1,"23":0,"24":1,"25":0,"26":1,"27":1}],"resolve":{"bytesWritten":28,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":7,"byteOffset":0,"buffer":[0,0,0,2,5,224,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":15,"byteOffset":0,"buffer":[0,0,0,10,4,0,0,0,4,187,0,0,7,83,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":41,"byteOffset":0,"buffer":[0,0,0,36,4,0,0,0,30,0,10,0,1,89,0,0,3,0,1,0,0,4,0,0,0,9,0,2,0,0,4,240,7,0,0,0,3,0,0,1,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":41,"byteOffset":0,"buffer":[0,0,0,36,4,0,0,0,30,0,10,0,1,88,0,0,3,0,1,0,0,4,0,0,0,9,0,2,0,0,4,240,7,0,0,0,3,0,0,1,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":44,"byteOffset":0,"buffer":[0,0,0,39,4,0,0,0,33,0,10,0,4,76,226,130,129,0,0,3,0,1,0,0,4,0,0,0,2,0,2,0,0,4,240,7,0,1,0,3,0,0,1,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":44,"byteOffset":0,"buffer":[0,0,0,39,4,0,0,0,33,0,10,0,4,76,226,130,130,0,0,3,0,1,0,0,4,0,0,0,2,0,2,0,0,4,240,7,0,1,0,3,0,0,1,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":44,"byteOffset":0,"buffer":[0,0,0,39,4,0,0,0,33,0,10,0,4,76,226,130,131,0,0,3,0,1,0,0,4,0,0,0,2,0,2,0,0,4,240,7,0,1,0,3,0,0,1,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":44,"byteOffset":0,"buffer":[0,0,0,39,4,0,0,0,33,0,10,0,4,76,226,130,132,0,0,3,0,1,0,0,4,0,0,0,2,0,2,0,0,4,240,7,0,1,0,3,0,0,1,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":44,"byteOffset":0,"buffer":[0,0,0,39,4,0,0,0,33,0,10,0,4,76,226,130,133,0,0,3,0,1,0,0,4,0,0,0,2,0,2,0,0,4,240,7,0,1,0,3,0,0,1,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":44,"byteOffset":0,"buffer":[0,0,0,39,4,0,0,0,33,0,10,0,4,76,226,130,134,0,0,3,0,1,0,0,4,0,0,0,2,0,2,0,0,4,240,7,0,1,0,3,0,0,1,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":47,"byteOffset":0,"buffer":[0,0,0,42,4,0,0,0,36,0,10,0,7,76,105,98,76,111,97,100,0,0,3,0,1,0,0,4,0,0,3,213,0,2,0,0,4,240,7,0,21,0,3,0,0,1,1]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":47,"byteOffset":0,"buffer":[0,0,0,42,4,0,0,0,36,0,10,0,7,75,69,89,80,65,68,67,0,0,3,0,1,0,0,4,0,0,0,103,0,2,0,0,4,240,7,0,21,0,3,0,0,1,1]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":48,"byteOffset":0,"buffer":[0,0,0,43,4,0,0,0,37,0,10,0,8,70,79,78,84,76,73,66,67,0,0,3,0,1,0,0,4,0,0,9,203,0,2,0,0,4,240,7,0,21,0,3,0,0,1,1]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":47,"byteOffset":0,"buffer":[0,0,0,42,4,0,0,0,36,0,10,0,7,70,73,76,69,73,79,67,0,0,3,0,1,0,0,4,0,0,10,133,0,2,0,0,4,240,7,0,21,0,3,0,0,1,1]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":46,"byteOffset":0,"buffer":[0,0,0,41,4,0,0,0,35,0,10,0,6,71,82,65,80,72,88,0,0,3,0,1,0,0,4,0,0,44,102,0,2,0,0,4,240,7,0,21,0,3,0,0,1,1]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":46,"byteOffset":0,"buffer":[0,0,0,41,4,0,0,0,35,0,10,0,6,67,69,83,73,85,77,0,0,3,0,1,0,0,4,0,0,101,93,0,2,0,0,4,240,7,0,6,0,3,0,0,1,1]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":44,"byteOffset":0,"buffer":[0,0,0,39,4,0,0,0,33,0,10,0,4,80,82,71,77,0,0,3,0,1,0,0,4,0,0,0,4,0,2,0,0,4,240,7,0,5,0,3,0,0,1,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":47,"byteOffset":0,"buffer":[0,0,0,42,4,0,0,0,36,0,10,0,7,67,97,98,114,105,74,114,0,0,3,0,1,0,0,4,0,1,144,113,0,2,0,0,4,240,15,0,36,0,3,0,0,1,1]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}},{"action":"asyncFunctionCall","name":"transferIn","parameters":[1,64],"resolve":{"data":{"byteLength":11,"byteOffset":0,"buffer":[0,0,0,6,4,0,0,0,0,221,0]},"status":"ok"}},{"action":"asyncFunctionCall","name":"transferOut","parameters":[2,{"0":0,"1":0,"2":0,"3":2,"4":5,"5":224,"6":0}],"resolve":{"bytesWritten":7,"status":"ok"}}] \ No newline at end of file diff --git a/test/calculators/ti83pce.test.js b/test/calculators/ti83pce.test.js new file mode 100644 index 0000000..a42a680 --- /dev/null +++ b/test/calculators/ti83pce.test.js @@ -0,0 +1,70 @@ +const replay = require('../helpers/replay'); +const TI83PCE = require('../../src/calculators/ez80/ti83pce'); +let calculator; + +describe('TI-83 Premium CE support', () => { + + beforeAll(async () => { + calculator = await replay.load({ + calculator: TI83PCE, + replay: './test/calculators/ti83pce.replay.json' + }); + }); + + describe('the device', () => { + it('is recognized as a TI-83 Premium CE', () => { + // This is an insufficient test because it is taken + // from the USB device, we need to fix that: + expect(calculator.name).toEqual('TI-83 Premium CE'); + }); + + it('is handled by the EZ80series class', () => { + expect(calculator.constructor.name).toEqual('EZ80series'); + }); + + it('has a buffer size of 1023 bytes', () => { + expect(calculator._d._bufferSize).toEqual(1023); + }); + }); + + describe('communicating through USB', () => { + it('is ready', async () => { + expect(await calculator.isReady()).toBe(true); + }); + + it('gets the amount of free memory', async () => { + expect(await calculator.getFreeMem()).toEqual({ + ram: undefined, + flash: 2839309 + }); + }); + + it('gets the directory', async () => { + expect(await calculator.getDirectory()).toEqual([ + { name: 'Y', size: 9, type: 0, archived: false }, + { name: 'X', size: 9, type: 0, archived: false }, + { name: 'L₁', size: 2, type: 1, archived: false }, + { name: 'L₂', size: 2, type: 1, archived: false }, + { name: 'L₃', size: 2, type: 1, archived: false }, + { name: 'L₄', size: 2, type: 1, archived: false }, + { name: 'L₅', size: 2, type: 1, archived: false }, + { name: 'L₆', size: 2, type: 1, archived: false }, + { name: 'LibLoad', size: 981, type: 21, archived: true }, + { name: 'KEYPADC', size: 103, type: 21, archived: true }, + { name: 'FONTLIBC', size: 2507, type: 21, archived: true }, + { name: 'FILEIOC', size: 2693, type: 21, archived: true }, + { name: 'GRAPHX', size: 11366, type: 21, archived: true }, + { name: 'CESIUM', size: 25949, type: 6, archived: true }, + { name: 'PRGM', size: 4, type: 5, archived: false }, + { name: 'CabriJr', size: 102513, type: 36, archived: true } + ]); + }); + }); + + describe('the replay', () => { + it('has no unplayed steps left over', () => { + expect(replay.unplayedSteps()).toEqual([]); + }); + }); + +}); diff --git a/test/helpers/record.js b/test/helpers/record.js index 7ba94b9..268f159 100755 --- a/test/helpers/record.js +++ b/test/helpers/record.js @@ -14,6 +14,8 @@ ticalc.addEventListener('connect', async calc => { if ( await calc.isReady() ) { const memFree = await calc.getFreeMem(); console.log(memFree); + const directory = await calc.getDirectory(); + console.log(directory); fs.writeFile(filename, JSON.stringify(recording.getSteps()), err => { if (err) return console.error(err); console.log(`Wrote recording to ${filename}`); From a28a42189c674847fb30ae5496b7b3d1b2a10f04 Mon Sep 17 00:00:00 2001 From: Timendus Date: Sun, 19 Sep 2021 20:17:34 +0200 Subject: [PATCH 7/9] Set support level for tests --- src/ticalc.js | 4 ++++ test/helpers/replay.js | 1 + 2 files changed, 5 insertions(+) diff --git a/src/ticalc.js b/src/ticalc.js index 47be10a..79be1b4 100644 --- a/src/ticalc.js +++ b/src/ticalc.js @@ -62,6 +62,10 @@ module.exports = { } }, + setSupportLevel: level => { + calculators = calculatorsForSupportLevel(level); + }, + choose: async (options = {}) => { const usb = options.usb || findOrCreateWebUSBRecording(); diff --git a/test/helpers/replay.js b/test/helpers/replay.js index c9ed16b..c6e1713 100755 --- a/test/helpers/replay.js +++ b/test/helpers/replay.js @@ -12,6 +12,7 @@ module.exports = { try { replay = JSON.parse(fs.readFileSync(replay, 'utf8')); replayPlayer = player(replay, { requiredMatcher: calculator.matcher }); + ticalc.setSupportLevel('experimental'); ticalc.addEventListener('connect', calc => resolve(calc)); ticalc.choose({ usb: replayPlayer }) .catch(e => reject(e)); From 81cb8fa6a0210022f64133e4197a734d6615f3d6 Mon Sep 17 00:00:00 2001 From: Timendus Date: Sun, 19 Sep 2021 20:43:27 +0200 Subject: [PATCH 8/9] Split off directory entry code --- src/dusb/ti84series.js | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/dusb/ti84series.js b/src/dusb/ti84series.js index b78ef07..7e26631 100644 --- a/src/dusb/ti84series.js +++ b/src/dusb/ti84series.js @@ -104,26 +104,32 @@ module.exports = class Ti84series { 0, 1, 0, 1, 0, 1, 1 // ??? ] }); + const result = []; - while ( true ) { - const packet = await this._d.expectAny(); - if ( packet.type == v.virtualPacketTypes.DUSB_VPKT_EOT ) - break; - if ( packet.type != v.virtualPacketTypes.DUSB_VPKT_VAR_HDR ) - throw `Expected virtual packet type ${v.virtualPacketTypes.DUSB_VPKT_VAR_HDR}, but got ${packet.type} instead`; - const nameLength = b.bytesToInt(packet.data.slice(0, 2)); - const name = b.bytesToAscii(packet.data.slice(2, 2 + nameLength)); - const attributes = b.destructParameters(packet.data.slice(2 + nameLength + 1)); - - const size = b.bytesToInt(attributes[v.attributes.DUSB_AID_VAR_SIZE]); - const type = b.bytesToInt(attributes[v.attributes.DUSB_AID_VAR_TYPE].slice(2)); - const archived = attributes[v.attributes.DUSB_AID_ARCHIVED][0] == 1; - - result.push({name, size, type, archived}); - } + let variable; + while ( variable = await this._getDirectoryEntry() ) + result.push(variable); return result; } + async _getDirectoryEntry() { + const packet = await this._d.expectAny(); + if ( packet.type == v.virtualPacketTypes.DUSB_VPKT_EOT ) + return false; + if ( packet.type != v.virtualPacketTypes.DUSB_VPKT_VAR_HDR ) + throw `Expected virtual packet type ${v.virtualPacketTypes.DUSB_VPKT_VAR_HDR} (DUSB_VPKT_VAR_HDR), but got ${packet.type} instead`; + + const nameLength = b.bytesToInt(packet.data.slice(0, 2)); + const name = b.bytesToAscii(packet.data.slice(2, 2 + nameLength)); + + const attributes = b.destructParameters(packet.data.slice(2 + nameLength + 1)); + const size = b.bytesToInt(attributes[v.attributes.DUSB_AID_VAR_SIZE]); + const type = b.bytesToInt(attributes[v.attributes.DUSB_AID_VAR_TYPE].slice(2)); + const archived = attributes[v.attributes.DUSB_AID_ARCHIVED][0] == 1; + + return {name, size, type, archived}; + } + // Send a TI file to the calculator async sendFile(file) { for ( let i = 0; i < file.entries.length; i++ ) { From 43f925e16bb932a6e63c80b40ccf09e154fe151d Mon Sep 17 00:00:00 2001 From: Timendus Date: Sun, 19 Sep 2021 20:44:31 +0200 Subject: [PATCH 9/9] Fix indenting --- src/byte-mangling.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/byte-mangling.js b/src/byte-mangling.js index af68a75..b4cac1c 100644 --- a/src/byte-mangling.js +++ b/src/byte-mangling.js @@ -77,7 +77,7 @@ function constructParameters(params) { return new Uint8Array([ intToBytes(params.length, 2), - ...params.map(param => [ + ...params.map(param => [ intToBytes(param.type, 2), intToBytes(param.value.length, 2), ...param.value