index.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. const usb = require('usb');
  2. const readline = require('readline');
  3. const readlineInterface = readline.createInterface({
  4. input: process.stdin,
  5. output: process.stdout,
  6. terminal: false
  7. });
  8. const track0ISOAlphabet = Object.fromEntries(Object.entries({
  9. '0000001': ' ',
  10. '1000000': '!',
  11. '0100000': '"',
  12. '1100001': '#',
  13. '0010000': '$',
  14. '1010001': '%',
  15. '0110001': '&',
  16. '1110000': '\'',
  17. '0001000': '(',
  18. '1001001': ')',
  19. '0101001': '*',
  20. '1101000': '+',
  21. '0011001': ',',
  22. '1011000': '-',
  23. '0111000': '.',
  24. '1001001': '/',
  25. '0000100': '0',
  26. '1000101': '1',
  27. '0100101': '2',
  28. '1100100': '3',
  29. '0010101': '4',
  30. '1010100': '5',
  31. '0110100': '6',
  32. '1110101': '7',
  33. '0001101': '8',
  34. '1001100': '9',
  35. '0101100': ':',
  36. '1101101': ';',
  37. '0011100': '<',
  38. '1011101': '=',
  39. '0111101': '>',
  40. '1111100': '?',
  41. '0000010': '@',
  42. '1000011': 'A',
  43. '0100011': 'B',
  44. '1100010': 'C',
  45. '0010011': 'D',
  46. '1010010': 'E',
  47. '0110010': 'F',
  48. '1110011': 'G',
  49. '0001011': 'H',
  50. '1001010': 'I',
  51. '0101010': 'J',
  52. '1101011': 'K',
  53. '0011010': 'L',
  54. '1011011': 'M',
  55. '0111011': 'N',
  56. '1111010': 'O',
  57. '0000111': 'P',
  58. '1000110': 'Q',
  59. '0100110': 'R',
  60. '1100111': 'S',
  61. '0010110': 'T',
  62. '1010111': 'U',
  63. '0110111': 'V',
  64. '1110110': 'W',
  65. '0001110': 'X',
  66. '1001111': 'Y',
  67. '0101111': 'Z',
  68. '1101110': '[',
  69. '0011111': '\\',
  70. '1011110': ']',
  71. '0111110': '^',
  72. '1111111': '_',
  73. }).map(([key, value]) => [parseInt(key, 2).toString(), value]));
  74. const track0ISOAlphabetInverted = Object.fromEntries(Object.entries(track0ISOAlphabet).map(([key, value]) => [value, parseInt(key)]));
  75. const track1ISOAlphabet = Object.fromEntries(Object.entries({
  76. '00001': '0',
  77. '10000': '1',
  78. '01000': '2',
  79. '11001': '3',
  80. '00100': '4',
  81. '10101': '5',
  82. '01101': '6',
  83. '11100': '7',
  84. '00010': '8',
  85. '10011': '9',
  86. '01011': ':',
  87. '11010': ';',
  88. '00111': '<',
  89. '10110': '=',
  90. '01110': '>',
  91. '11111': '?',
  92. }).map(([key, value]) => [parseInt(key, 2).toString(), value]));
  93. const track1ISOAlphabetInverted = Object.fromEntries(Object.entries(track1ISOAlphabet).map(([key, value]) => [value, parseInt(key)]));
  94. const lrc = (len, alphabet, data) => {
  95. let bits = [];
  96. for (let i = 0; i < len; ++i) {
  97. bits[i] = 0;
  98. }
  99. for (let octet of data) {
  100. let bin = alphabet[octet.toString()];
  101. for (let i = 0; i < len; ++i) {
  102. if (bin & (1 << i)) {
  103. bits[i] += 1;
  104. }
  105. }
  106. }
  107. bits = bits.map(x => x % 2 == 1);
  108. bits[0] = bits.slice(1).filter(x => x).length % 2 == 0;
  109. let final = 0;
  110. for (let i = 0; i < bits.length; ++i) {
  111. if (bits[i]) {
  112. final |= 1 << i;
  113. }
  114. }
  115. return final;
  116. };
  117. const parsePacket = hex => {
  118. let acc = [];
  119. hex.split('').forEach((item, i) => {
  120. i % 2 == 0 ? acc.push([item]) : acc[acc.length - 1].push(item);
  121. }, []);
  122. return acc.map(octSet => parseInt(octSet.join(''), 16));
  123. }
  124. const promisify = func => (...args) => new Promise((resolve, reject) => {
  125. try {
  126. func(...args, (...subArgs) => resolve(subArgs));
  127. } catch (e) {
  128. reject(e);
  129. }
  130. });
  131. const sendControlChunk = packet => new Promise((resolve, reject) => {
  132. device.controlTransfer(0x21, 9, 0x0300, 0, packet, (error, data) => {
  133. if (error != null) {
  134. reject(error);
  135. }
  136. resolve(data);
  137. });
  138. });
  139. const sendControl = async packet => {
  140. let written = 0;
  141. while (written < packet.length) {
  142. let header = 0x80;
  143. let len = 0x3F;
  144. if (packet.length - written < 0x3F) {
  145. header |= 0x40;
  146. len = packet.length - written;
  147. }
  148. header |= len;
  149. let chunk = [header, ...packet.slice(written, written + len)];
  150. written += len;
  151. await sendControlChunk(Buffer.from(chunk));
  152. }
  153. }
  154. const outbound = {
  155. 'reset': '1b61',
  156. 'getFirmwareVersion': '1b76',
  157. 'setBPC': '1b6f',
  158. 'setHiCo': '1b78',
  159. 'setLoCo': '1b79',
  160. 'setBPI': '1b62',
  161. 'setLeadingZeros': '1b7a',
  162. 'enableRead': '1b6d',
  163. 'disableRead': '1b61',
  164. 'greenLEDOn': '1b83',
  165. 'redLEDOn': '1b85',
  166. 'powerOff': '1bac',
  167. 'getVoltage': '1ba3',
  168. 'getParameter': '1ba2',
  169. 'getDeviceModel': '1b74',
  170. 'setParameter': '1ba1',
  171. 'enableWrite': '1b6e1b73',
  172. };
  173. const assemblePacket = (opcode, data = []) => {
  174. const opcodeEncoded = parsePacket(outbound[opcode] || opcode);
  175. return [...opcodeEncoded, ...data];
  176. };
  177. const connectToDevice = () => {
  178. const device = usb.findByIds(0x0801, 0x0003);
  179. device.open();
  180. console.log(`${device.interfaces.length} interfaces found.`);
  181. const [interface] = device.interfaces;
  182. if (interface.isKernelDriverActive()) {
  183. console.log('detaching device from kernel...');
  184. interface.detachKernelDriver();
  185. console.log('detached');
  186. }
  187. interface.claim();
  188. return { device, interface };
  189. }
  190. const { device, interface } = connectToDevice();
  191. let connectedToDevice = true;
  192. function* receivePacket(endpoint) {
  193. endpoint.transferType = 2;
  194. endpoint.startPoll(1, 64);
  195. let currentWaiters = [];
  196. let incoming = [];
  197. let currentData = [];
  198. endpoint.on('data', function (data) {
  199. let head = data[0];
  200. if ((head & 0x80) != 0x80 && currentData.length == 0) {
  201. throw new Error('invalid header byte received');
  202. }
  203. let length = head & 0x3F;
  204. currentData.push(...(data.slice(1, length + 1)));
  205. if ((head & 0x40) != 0x40) { // continuation
  206. return;
  207. }
  208. if (currentWaiters.length > 0) {
  209. currentWaiters.shift()(currentData);
  210. } else {
  211. incoming.push(currentData);
  212. }
  213. currentData = [];
  214. });
  215. endpoint.on('error', function (error) {
  216. throw new Error(error);
  217. });
  218. endpoint.on('end', function (error) {
  219. connectedToDevice = false;
  220. });
  221. while (connectedToDevice) {
  222. yield new Promise((resolve) => {
  223. if (incoming.length > 0) {
  224. resolve(incoming.shift());
  225. } else {
  226. currentWaiters.push(resolve);
  227. }
  228. });
  229. }
  230. }
  231. const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
  232. const resetDevice = async () => void 0; // promisify(device.reset.bind(device));
  233. // valid BPIs are 75 & 210
  234. const deviceConfig = {
  235. tracks: [
  236. {
  237. bpc: 8,
  238. bpi: 210,
  239. },
  240. {
  241. bpc: 8,
  242. bpi: 75,
  243. },
  244. {
  245. bpc: 8,
  246. bpi: 210,
  247. },
  248. ],
  249. leadingZero210: 61,
  250. leadingZero75: 22,
  251. isHiCo: true,
  252. };
  253. const toHexString = arr => {
  254. let out = [];
  255. for (let i of arr) {
  256. let c = i.toString(16);
  257. while (c.length < 2) {
  258. c = `0${c}`;
  259. }
  260. out.push(c);
  261. }
  262. return out.join('');
  263. }
  264. const bitStream = data => {
  265. return {
  266. raw: data,
  267. _bitIndex: 0,
  268. read: function (ct) {
  269. if (this._bitIndex + ct >= this.raw.length * 8) {
  270. return null;
  271. }
  272. let baseIndex = (this._bitIndex / 8) | 0;
  273. let bytes = this.raw.slice(baseIndex, Math.ceil((this._bitIndex + ct) / 8));
  274. let value = 0;
  275. let bitOffset = this._bitIndex % 8;
  276. let bitMask = 0;
  277. for (let i = bitOffset; i < Math.min(bitOffset + ct, 8); ++i) {
  278. bitMask |= 1 << (7 - i);
  279. }
  280. value = (bytes[0] & bitMask) >>> Math.max(0, (8 - ct) - bitOffset);
  281. for (let i = 1; i < bytes.length - 1; ++i) {
  282. value <<= 8;
  283. value |= bytes[i];
  284. }
  285. if (bytes.length > 1) {
  286. let remainingBits = ct - (8 - (this._bitIndex % 8)) - Math.max(0, bytes.length - 2) * 8;
  287. let bitTrailOffset = remainingBits;
  288. value <<= bitTrailOffset;
  289. bitMask = 0;
  290. for (let i = 0; i < bitTrailOffset; ++i) {
  291. bitMask |= 1 << (7 - i);
  292. }
  293. value |= (bytes[bytes.length - 1] & bitMask) >>> (8 - bitTrailOffset);
  294. }
  295. this._bitIndex += ct;
  296. return value;
  297. },
  298. write: function (ct, value) {
  299. let bitMask = 0;
  300. for (let i = 0; i < ct; ++i) {
  301. bitMask |= 0x01 << i;
  302. }
  303. let toWrite = value & bitMask;
  304. let bitOffset = this._bitIndex % 8;
  305. let byteOffset = (this._bitIndex / 8) | 0;
  306. const rsh = (a1, a2) => a2 >= 0 ? a1 >>> a2 : a1 << -a2;
  307. // clear
  308. this.raw[byteOffset] &= ~rsh(bitMask, (ct - (8 - bitOffset))) & 0xFF;
  309. this.raw[byteOffset] |= rsh(toWrite, (ct - (8 - bitOffset))) & 0xFF;
  310. let octetCount = Math.floor((ct - (8 - bitOffset)) / 8);
  311. const modifiedBitOffset = (ct - (8 - bitOffset)) % 8;
  312. for (let i = byteOffset + octetCount; i >= byteOffset + 1; --i) {
  313. this.raw[i] = rsh(toWrite, modifiedBitOffset + (8 * (i - byteOffset - octetCount))) & 0xFF;
  314. }
  315. if (modifiedBitOffset > 0) {
  316. this.raw[byteOffset + octetCount + 1] &= ~(bitMask << (8 - modifiedBitOffset)) & 0xFF;
  317. this.raw[byteOffset + octetCount + 1] |= (toWrite << (8 - modifiedBitOffset)) & 0xFF;
  318. }
  319. this._bitIndex += ct;
  320. },
  321. seek: function (ct) {
  322. this._bitIndex += ct;
  323. if (this._bitIndex < 0) {
  324. this._bitIndex = 0;
  325. } else if (this._bitIndex > this.raw.length * 8) {
  326. this._bitIndex = this.raw.length * 8;
  327. }
  328. }
  329. }
  330. };
  331. (async () => {
  332. await resetDevice();
  333. console.log(`${interface.endpoints.length} endpoints found.`);
  334. const [inEndpoint] = interface.endpoints;
  335. const reader = receivePacket(inEndpoint);
  336. try {
  337. await sendControl(assemblePacket('reset'));
  338. } catch (e) {
  339. }
  340. console.log('Resetting device');
  341. const readSuccess = async () => {
  342. for (let i = 0; i < 5; ++i) {
  343. const received = await reader.next().value;
  344. if (received[0] == 0x1B) {
  345. if (received[1] == 0x30) {
  346. return true;
  347. } else {
  348. await sendControl(assemblePacket('disableRead'));
  349. return false;
  350. }
  351. }
  352. }
  353. return false;
  354. }
  355. const readReturn = async () => {
  356. for (let i = 0; i < 5; ++i) {
  357. const received = await reader.next().value;
  358. if (received != null && received[0] == 0x1B && received[1] != 0x30) {
  359. return received.slice(1);
  360. }
  361. }
  362. return null;
  363. }
  364. while (true) {
  365. await sleep(250);
  366. try {
  367. await sendControl(assemblePacket('getFirmwareVersion'));
  368. } catch (e) {
  369. continue;
  370. }
  371. break;
  372. }
  373. console.log('Requested firmware version');
  374. const firmwareReceived = await readReturn();
  375. console.log('Received firmware version');
  376. const firmwareVersion = Buffer.from(firmwareReceived.slice(1)).toString();
  377. console.log(`Firmware Version: ${firmwareVersion}`);
  378. let bpc = deviceConfig.tracks.map(track => track.bpc);
  379. await sendControl(assemblePacket('setBPC', bpc));
  380. await readSuccess();
  381. await sendControl(assemblePacket(deviceConfig.isHiCo ? 'setHiCo' : 'setLoCo'));
  382. await readSuccess();
  383. await sendControl(assemblePacket('setBPI', [deviceConfig.tracks[0].bpi == 210 ? 0xa1 : 0xa0]));
  384. await readSuccess();
  385. await sendControl(assemblePacket('setBPI', [deviceConfig.tracks[1].bpi == 210 ? 0xc1 : 0xc0]));
  386. await readSuccess();
  387. await sendControl(assemblePacket('setBPI', [deviceConfig.tracks[2].bpi == 210 ? 0xd2 : 0x4b]));
  388. await readSuccess();
  389. await sendControl(assemblePacket('setLeadingZeros', [deviceConfig.leadingZero210, deviceConfig.leadingZero75]));
  390. await readSuccess();
  391. await sendControl(assemblePacket('getVoltage'));
  392. const rawVoltage = await readReturn();
  393. const voltage = Math.round((rawVoltage[0] + (rawVoltage[1] / 255.0)) * 9.9 / 128.0 * 100.0) / 100.0;
  394. console.log(`Voltage: ${voltage}`);
  395. await sendControl(assemblePacket('getDeviceModel'));
  396. const rawModel = await readReturn();
  397. console.log(`Model version: 0x${toHexString(rawModel)}`);
  398. console.log('Ready to read.\n');
  399. let isReading = false;
  400. let isWriting = false;
  401. const readData = async () => {
  402. const received = await reader.next().value;
  403. if (received == null || received[0] != 0x1B || received[1] != 0x73) {
  404. throw new Error('malformed response from device');
  405. }
  406. let trackData = [];
  407. let rIndex = 2;
  408. for (let i = 1; i <= 3; ++i) {
  409. if (received[rIndex] != 0x1B || received[rIndex + 1] != i) {
  410. throw new Error('malformed response from device');
  411. }
  412. rIndex += 2;
  413. const trackLength = received[rIndex];
  414. ++rIndex;
  415. trackData.push(received.slice(rIndex, rIndex + trackLength));
  416. rIndex += trackLength;
  417. }
  418. console.log('Incoming read: [RAW]');
  419. console.log(trackData.map((track, i) => `Track ${i + 1}: ${toHexString(track)}`).join('\n'));
  420. const isoDecoded = [[], [], []];
  421. let track0Stream = bitStream(trackData[0]);
  422. let temp = null;
  423. while ((temp = track0Stream.read(7)) != null) {
  424. isoDecoded[0].push(track0ISOAlphabet[temp.toString()] || '~');
  425. }
  426. let track1Stream = bitStream(trackData[1]);
  427. while ((temp = track1Stream.read(5)) != null) {
  428. isoDecoded[1].push(track1ISOAlphabet[temp.toString()] || '~');
  429. }
  430. let track2Stream = bitStream(trackData[2]);
  431. while ((temp = track2Stream.read(5)) != null) {
  432. isoDecoded[2].push(track1ISOAlphabet[temp.toString()] || '~');
  433. }
  434. let isoTracks = isoDecoded.map(track => track.join(''));
  435. for (let i = 0; i < isoTracks.length; ++i) {
  436. let endIndex = isoTracks[i].indexOf('?');
  437. let startIndex = isoTracks[i].indexOf(';');
  438. if (startIndex < 0 || endIndex < 0) {
  439. isoTracks[i] = isoTracks[i].length > 0 ? 'Corrupt Data' : 'No Data';
  440. } else {
  441. isoTracks[i] = isoTracks[i].slice(startIndex, endIndex + 1);
  442. if (isoTracks[i].includes('~')) {
  443. isoTracks[i] = 'Corrupt Data';
  444. }
  445. }
  446. }
  447. let track0Checksum = lrc(7, track0ISOAlphabetInverted, isoTracks[0].split(''));
  448. let track1Checksum = lrc(5, track1ISOAlphabetInverted, isoTracks[1].split(''));
  449. let track2Checksum = lrc(5, track1ISOAlphabetInverted, isoTracks[2].split(''));
  450. //TODO: validate checksums
  451. console.log('Incoming read: [ISO]');
  452. console.log(isoTracks.map((track, i) => `Track ${i + 1}: ${track}`).join('\n'));
  453. return { isoTracks, trackData };
  454. }
  455. process.on('SIGINT', function () {
  456. if (isReading || isWriting) {
  457. sendControl(assemblePacket('disableRead')).then(() => {
  458. isReading = false;
  459. isWriting = false;
  460. process.exit();
  461. });
  462. } else {
  463. process.exit();
  464. }
  465. });
  466. const writeRawData = async data => {
  467. const tracks = data.map(track => track.map(octet => {
  468. let bits = [octet & 0x80, octet & 0x40, octet & 0x20, octet & 0x10, octet & 0x08, octet & 0x04, octet & 0x02, octet & 0x01].map(bit => bit != 0);
  469. let value = 0;
  470. let currentBit = 0x80;
  471. for (let i = bits.length - 1; i >= 0; --i) {
  472. if (bits[i]) {
  473. value |= currentBit;
  474. }
  475. currentBit /= 2;
  476. }
  477. return value;
  478. }));
  479. const outData = [0x1b, 0x01, tracks[0].length, ...tracks[0], 0x1b, 0x02, tracks[1].length, ...tracks[1], 0x1b, 0x03, tracks[2].length, ...tracks[2], 0x3F, 0x1C];
  480. isWriting = true;
  481. await sendControl(assemblePacket('enableWrite', outData));
  482. const success = await readSuccess();
  483. isWriting = false;
  484. console.log(success ? 'Written successfully' : 'Failed to write');
  485. return success;
  486. };
  487. const encodeISO = async (map, length, track) => {
  488. const output = [];
  489. const outStream = bitStream(output);
  490. track.split('').map(c => map[c] || c).forEach(c => {
  491. if (typeof c == 'string') {
  492. throw new Error('invalid character in track: ' + c);
  493. }
  494. outStream.write(length, c);
  495. });
  496. if (track.length > 0) {
  497. outStream.write(length, lrc(length, map, track.split('')));
  498. }
  499. return output;
  500. };
  501. readlineInterface.on("line", async line => {
  502. let endOfCommand = line.indexOf(" ");
  503. if (endOfCommand < 0) {
  504. endOfCommand = line.length;
  505. }
  506. const command = line.substring(0, endOfCommand).trim();
  507. const arg = endOfCommand >= line.length ? '' : line.substring(endOfCommand + 1).trim();
  508. if (command == 'read') {
  509. isReading = true;
  510. await sendControl(assemblePacket('enableRead'));
  511. await readData();
  512. isReading = false;
  513. // read is disabled
  514. } else if (command == 'read_cycle') {
  515. while (true) {
  516. isReading = true;
  517. await sendControl(assemblePacket('enableRead'));
  518. await readData();
  519. }
  520. } else if (command == 'write_raw') {
  521. const data = arg.split(' ').map(datum => datum == 'none' || datum == 'null' ? '' : datum);
  522. if (data.length != 3) {
  523. console.log('invalid data, need 3 tracks, space delimited');
  524. return;
  525. }
  526. await writeRawData(data.map(packet => parsePacket(packet)));
  527. } else if (command == 'clone') {
  528. isReading = true;
  529. await sendControl(assemblePacket('enableRead'));
  530. const { trackData } = await readData();
  531. isReading = false;
  532. console.log(trackData)
  533. for (let i = 0; i < 5; ++i) {
  534. if (await writeRawData(trackData)) {
  535. break;
  536. }
  537. console.log(`Attempt #${i + 1}/5 failed, try again.`);
  538. }
  539. } else if (command == 'write_iso') {
  540. const data = arg.split('~').map(datum => datum == 'none' || datum == 'null' ? '' : (datum == 'erase' ? '\0' : datum));
  541. if (data.length != 3) {
  542. console.log('invalid data, need 3 tracks, ~ delimited');
  543. return;
  544. }
  545. const isoEncoded = [
  546. data[0] == '\0' ? [0] : await encodeISO(track0ISOAlphabetInverted, 7, data[0]),
  547. data[1] == '\0' ? [0] : await encodeISO(track1ISOAlphabetInverted, 5, data[1]),
  548. data[2] == '\0' ? [0] : await encodeISO(track1ISOAlphabetInverted, 5, data[2]),
  549. ];
  550. for (let i = 0; i < 5; ++i) {
  551. if (await writeRawData(isoEncoded)) {
  552. break;
  553. }
  554. console.log(`Attempt #${i + 1}/5 failed, try again.`);
  555. }
  556. } else if (command == '5dump') {
  557. let data = arg
  558. if (data.length <= 10 | !(data.indexOf("=") >= 0)) {
  559. console.log('invalid data, need 3 tracks, ~ delimited');
  560. return;
  561. }
  562. if (!data.startsWith(";")) {
  563. data = ";" + data;
  564. }
  565. if (!data.endsWith("?")) {
  566. data = data + "?";
  567. }
  568. const isoEncoded = [
  569. [],
  570. await encodeISO(track1ISOAlphabetInverted, 5, data),
  571. []
  572. ];
  573. for (let i = 0; i < 5; ++i) {
  574. if (await writeRawData(isoEncoded)) {
  575. break;
  576. }
  577. console.log(`Attempt #${i + 1}/5 failed, try again.`);
  578. }
  579. } else if (command == 'write_script') {
  580. script = require(arg);
  581. for (let write of script.getISOWrites()) {
  582. if (write.trim().length == 0) {
  583. return;
  584. }
  585. console.log('Script writing: ' + write.trim());
  586. const data = write.trim().split('~').map(datum => datum == 'none' || datum == 'null' ? '' : (datum == 'erase' ? '\0' : datum));
  587. if (data.length != 3) {
  588. console.log('invalid data, need 3 tracks, ~ delimited');
  589. return;
  590. }
  591. const isoEncoded = [
  592. data[0] == '\0' ? [0] : await encodeISO(track0ISOAlphabetInverted, 7, data[0]),
  593. data[1] == '\0' ? [0] : await encodeISO(track1ISOAlphabetInverted, 5, data[1]),
  594. data[2] == '\0' ? [0] : await encodeISO(track1ISOAlphabetInverted, 5, data[2]),
  595. ];
  596. for (let i = 0; i < 5; ++i) {
  597. if (await writeRawData(isoEncoded)) {
  598. break;
  599. }
  600. console.log(`Attempt #${i + 1}/5 failed, try again.`);
  601. }
  602. }
  603. } else if (command == "exit") {
  604. process.exit()
  605. }
  606. else {
  607. console.log(`Invalid command: ${command}`);
  608. console.log('Valid commands:');
  609. console.log('* read -- prepare the card reader to read a card, outputs in Raw and ISO');
  610. console.log('* read_cycle -- read repeatedly until execution halts');
  611. console.log('* write_raw -- prepare to write a raw hex stream to the card. usage: write_raw track1/none track2/none track3/none');
  612. console.log('* clone -- prepare to read a card, and upon read success, prepare to write another card with raw equivalent data');
  613. console.log('* write_iso -- prepare to write ISO data to a card, usage: write_iso track1/none~track2/none~track3/none');
  614. console.log('* 5dump -- Пятёрочка')
  615. console.log('* write_script -- enables write_iso macro to be loaded');
  616. console.log('* exit -- "exit"')
  617. }
  618. });
  619. console.log('Initialized Device');
  620. })();