APMU

Standard for hooking into the client -> server connection in arras.io

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/483575/1304377/APMU.js

  1. /* ==UserScript==
  2. // @name APMU
  3. // @version 1.2.1
  4. // @author ABC & Ray Adams
  5. // @namespace https://github.com/ABCxFF
  6. // @description Standard for hooking into the client -> server connection in arras.io
  7. // @match *://arras.io/*
  8. // @match *://arras.netlify.app/*
  9. // @homepageURL https://github.com/Ray-Adams/Arras-Archive
  10. // @grant none
  11. // @run-at document-start
  12. // @license GPL-3.0
  13. */
  14.  
  15. /****************************************************
  16. *
  17. * Copyright (C) 2021 ABC & Ray Adams
  18. * Licensed under GNU General Public License v3.0
  19. *
  20. ***************************************************/
  21.  
  22. const arras = (() => {
  23.  
  24. // API
  25.  
  26. const gamemodeTable = [
  27. [{
  28. id: 'x',
  29. u: 'Private'
  30. }],
  31. [{
  32. id: 'e',
  33. Hb: 'word'
  34. }],
  35. [{
  36. id: 'w',
  37. Hb: 'words'
  38. }],
  39. [{
  40. id: 'p',
  41. u: 'Portal'
  42. }],
  43. [{
  44. id: 'o',
  45. u: 'Open'
  46. }],
  47. [{
  48. id: 'm',
  49. u: 'Maze',
  50. delay: !0,
  51. remove: 'f'
  52. }],
  53. [{
  54. id: 'f',
  55. u: 'FFA'
  56. },
  57. {
  58. id: 'd',
  59. u: 'Duos'
  60. },
  61. {
  62. id: 's',
  63. u: 'Squads'
  64. },
  65. {
  66. id: '1',
  67. u: '1 Team',
  68. advance: !0
  69. },
  70. {
  71. id: '2',
  72. u: '2 Team',
  73. advance: !0,
  74. end: '2TDM'
  75. },
  76. {
  77. id: '3',
  78. u: '3 Team',
  79. advance: !0,
  80. end: '3TDM'
  81. },
  82. {
  83. id: '4',
  84. u: '4 Team',
  85. advance: !0,
  86. end: '4TDM'
  87. }
  88. ],
  89. [{
  90. id: 'd',
  91. u: 'Domination'
  92. },
  93. {
  94. id: 'm',
  95. u: 'Mothership',
  96. remove: '2'
  97. },
  98. {
  99. id: 'a',
  100. u: 'Assault',
  101. remove: ['2', 'm']
  102. },
  103. {
  104. id: 's',
  105. u: 'Siege',
  106. remove: '1'
  107. },
  108. {
  109. id: 't',
  110. u: 'Tag',
  111. remove: ['o', '4']
  112. },
  113. {
  114. id: 'p',
  115. u: 'Pandemic',
  116. remove: ['o', '2']
  117. },
  118. {
  119. id: 'z',
  120. u: 'Sandbox'
  121. }
  122. ]
  123. ];
  124.  
  125. const regionTable = {
  126. xyz: ['Local', 'Localhost', null],
  127. unk: ['Unknown', 'Unknown', null],
  128. svx: ['US West', 'Silicon Valley, CA, US', -7],
  129. lax: ['US West', 'Los Angeles, CA, US', -7],
  130. dal: ['USA', 'Dallas, TX, US', -5],
  131. kci: ['USA', 'Kansas City, MO, US', -5],
  132. vin: ['US East', 'Vint Hill, VA, US', -4],
  133. mtl: ['US East', 'Montreal, CA', -4],
  134. lon: ['Europe', 'London, UK', 1],
  135. fra: ['Europe', 'Frankfurt, DE', 2],
  136. sgp: ['Asia', 'Singapore', 8]
  137. };
  138.  
  139. const hostTable = {
  140. z: ['Private', null],
  141. x: ['Local', null],
  142. glitch: ['Glitch', 10],
  143. vultr: ['Vultr', 30],
  144. buyvm: ['BuyVM', 15],
  145. extravm: ['ExtraVM', 40],
  146. ovh: ['OVH', 45],
  147. wsi: ['WSI', 50]
  148. };
  149.  
  150. class Server {
  151. static parseGamemode(code) {
  152. if ('%' === code) return 'Unknown';
  153. let tags = [];
  154. let filter = [];
  155. let at = 0;
  156.  
  157. for (const games of gamemodeTable) {
  158. for (const game of games) {
  159. if (game.id === code.charAt(at)) {
  160. if (Array.isArray(game.remove)) {
  161. filter.push.apply(filter, game.remove);
  162. } else if (game.remove) {
  163. filter.push(game.remove);
  164. }
  165. tags.push(Object.assign({}, game));
  166. at++;
  167. break;
  168. }
  169. }
  170. }
  171. if (tags.length == 0) return 'Unknown';
  172.  
  173. return tags.map((n, i, l) => l[Math.min(i + Math.pow(-1, i), l.length - 1)]).filter(({ id }) => !filter.includes(id)).map(data => data.u).join(' ');
  174. }
  175. static parseRegion(code) {
  176. return regionTable[code][0];
  177. }
  178. static parseHost(code) {
  179. return hostTable[code][0];
  180. }
  181. static parseCode(code) {
  182. const [host, region, gamemode] = code.split('-');
  183.  
  184. return [Server.parseHost(host), Server.parseRegion(region), Server.parseGamemode(gamemode)].join(' - ');
  185. }
  186. }
  187.  
  188. // PROTOCOL
  189.  
  190. const u32 = new Uint32Array(1);
  191. const u16 = new Uint16Array(1);
  192. const c16 = new Uint8Array(u16.buffer);
  193. const c32 = new Uint8Array(u32.buffer);
  194. const f32 = new Float32Array(u32.buffer);
  195.  
  196. Array.prototype.remove = function (index) {
  197. if (index === this.length - 1) return this.pop();
  198. this[index] = this.pop();
  199. };
  200.  
  201. function encode(message) {
  202. let headers = [];
  203. let headerCodes = [];
  204. let contentSize = 0;
  205. let lastTypeCode = 0b1111;
  206. let repeatTypeCount = 0;
  207. for (let block of message) {
  208. let typeCode = 0;
  209. if (block === 0 || block === false) {
  210. typeCode = 0b0000;
  211. } else if (block === 1 || block === true) {
  212. typeCode = 0b0001;
  213. } else if (typeof block === 'number') {
  214. if (!Number.isInteger(block) || block < -0x100000000 || block >= 0x100000000) {
  215. typeCode = 0b1000;
  216. contentSize += 4;
  217. } else if (block >= 0) {
  218. if (block < 0x100) {
  219. typeCode = 0b0010;
  220. contentSize++;
  221. } else if (block < 0x10000) {
  222. typeCode = 0b0100;
  223. contentSize += 2;
  224. } else if (block < 0x100000000) {
  225. typeCode = 0b0110;
  226. contentSize += 4;
  227. }
  228. } else {
  229. if (block >= -0x100) {
  230. typeCode = 0b0011;
  231. contentSize++;
  232. } else if (block >= -0x10000) {
  233. typeCode = 0b0101;
  234. contentSize += 2;
  235. } else if (block >= -0x100000000) {
  236. typeCode = 0b0111;
  237. contentSize += 4;
  238. }
  239. }
  240. } else if (typeof block === 'string') {
  241. let hasUnicode = false;
  242. for (let i = 0; i < block.length; i++) {
  243. if (block.charAt(i) > '\xff') {
  244. hasUnicode = true;
  245. } else if (block.charAt(i) === '\x00') {
  246. console.error('Null containing string', block);
  247. throw new Error('Null containing string');
  248. }
  249. }
  250. if (!hasUnicode && block.length <= 1) {
  251. typeCode = 0b1001;
  252. contentSize++;
  253. } else if (hasUnicode) {
  254. typeCode = 0b1011;
  255. contentSize += block.length * 2 + 2;
  256. } else {
  257. typeCode = 0b1010;
  258. contentSize += block.length + 1;
  259. }
  260. } else {
  261. console.error('Unencodable data type', block);
  262. throw new Error('Unencodable data type');
  263. }
  264. headers.push(typeCode);
  265. if (typeCode === lastTypeCode) {
  266. repeatTypeCount++;
  267. } else {
  268. headerCodes.push(lastTypeCode);
  269. if (repeatTypeCount >= 1) {
  270. while (repeatTypeCount > 19) {
  271. headerCodes.push(0b1110);
  272. headerCodes.push(15);
  273. repeatTypeCount -= 19;
  274. }
  275. if (repeatTypeCount === 1)
  276. headerCodes.push(lastTypeCode);
  277. else if (repeatTypeCount === 2)
  278. headerCodes.push(0b1100);
  279. else if (repeatTypeCount === 3)
  280. headerCodes.push(0b1101);
  281. else if (repeatTypeCount < 20) {
  282. headerCodes.push(0b1110);
  283. headerCodes.push(repeatTypeCount - 4);
  284. }
  285. }
  286. repeatTypeCount = 0;
  287. lastTypeCode = typeCode;
  288. }
  289. }
  290. headerCodes.push(lastTypeCode);
  291. if (repeatTypeCount >= 1) {
  292. while (repeatTypeCount > 19) {
  293. headerCodes.push(0b1110);
  294. headerCodes.push(15);
  295. repeatTypeCount -= 19;
  296. }
  297. if (repeatTypeCount === 1)
  298. headerCodes.push(lastTypeCode);
  299. else if (repeatTypeCount === 2)
  300. headerCodes.push(0b1100);
  301. else if (repeatTypeCount === 3)
  302. headerCodes.push(0b1101);
  303. else if (repeatTypeCount < 20) {
  304. headerCodes.push(0b1110);
  305. headerCodes.push(repeatTypeCount - 4);
  306. }
  307. }
  308. headerCodes.push(0b1111);
  309. if (headerCodes.length % 2 === 1)
  310. headerCodes.push(0b1111);
  311.  
  312. let output = new Uint8Array((headerCodes.length >> 1) + contentSize);
  313. for (let i = 0; i < headerCodes.length; i += 2) {
  314. let upper = headerCodes[i];
  315. let lower = headerCodes[i + 1];
  316. output[i >> 1] = (upper << 4) | lower;
  317. }
  318. let index = headerCodes.length >> 1;
  319. for (let i = 0; i < headers.length; i++) {
  320. let block = message[i];
  321. switch (headers[i]) {
  322. case 0b0000:
  323. case 0b0001:
  324. break;
  325. case 0b0010:
  326. case 0b0011:
  327. output[index++] = block;
  328. break;
  329. case 0b0100:
  330. case 0b0101:
  331. u16[0] = block;
  332. output.set(c16, index);
  333. index += 2;
  334. break;
  335. case 0b0110:
  336. case 0b0111:
  337. u32[0] = block;
  338. output.set(c32, index);
  339. index += 4;
  340. break;
  341. case 0b1000:
  342. f32[0] = block;
  343. output.set(c32, index);
  344. index += 4;
  345. break;
  346. case 0b1001:
  347. {
  348. let byte = block.length === 0 ? 0 : block.charCodeAt(0);
  349. output[index++] = byte;
  350. }
  351. break;
  352. case 0b1010:
  353. for (let i = 0; i < block.length; i++) {
  354. output[index++] = block.charCodeAt(i);
  355. }
  356. output[index++] = 0;
  357. break;
  358. case 0b1011:
  359. for (let i = 0; i < block.length; i++) {
  360. let charCode = block.charCodeAt(i);
  361. output[index++] = charCode & 0xff;
  362. output[index++] = charCode >> 8;
  363. }
  364. output[index++] = 0;
  365. output[index++] = 0;
  366. break;
  367. }
  368. }
  369.  
  370. return output;
  371. }
  372.  
  373. function decode(packet) {
  374. let data = new Uint8Array(packet);
  375. if (data[0] >> 4 !== 0b1111)
  376. return null;
  377.  
  378. let headers = [];
  379. let lastTypeCode = 0b1111;
  380. let index = 0;
  381. let consumedHalf = true;
  382. while (true) {
  383. if (index >= data.length)
  384. return null;
  385. let typeCode = data[index];
  386.  
  387. if (consumedHalf) {
  388. typeCode &= 0b1111;
  389. index++;
  390. } else {
  391. typeCode >>= 4;
  392. }
  393. consumedHalf = !consumedHalf;
  394.  
  395. if ((typeCode & 0b1100) === 0b1100) {
  396. if (typeCode === 0b1111) {
  397. if (consumedHalf)
  398. index++;
  399. break;
  400. }
  401.  
  402. let repeat = typeCode - 10; // 0b1100 - 2
  403. if (typeCode === 0b1110) {
  404. if (index >= data.length)
  405. return null;
  406. let repeatCode = data[index];
  407.  
  408. if (consumedHalf) {
  409. repeatCode &= 0b1111;
  410. index++;
  411. } else {
  412. repeatCode >>= 4;
  413. }
  414. consumedHalf = !consumedHalf;
  415.  
  416. repeat += repeatCode;
  417. }
  418.  
  419. for (let i = 0; i < repeat; i++)
  420. headers.push(lastTypeCode);
  421. } else {
  422. headers.push(typeCode);
  423. lastTypeCode = typeCode;
  424. }
  425. }
  426.  
  427. let output = [];
  428. for (let header of headers) {
  429. switch (header) {
  430. case 0b0000:
  431. output.push(0);
  432. break;
  433. case 0b0001:
  434. output.push(1);
  435. break;
  436. case 0b0010:
  437. output.push(data[index++]);
  438. break;
  439. case 0b0011:
  440. output.push(data[index++] - 0x100);
  441. break;
  442. case 0b0100:
  443. c16[0] = data[index++];
  444. c16[1] = data[index++];
  445. output.push(u16[0]);
  446. break;
  447. case 0b0101:
  448. c16[0] = data[index++];
  449. c16[1] = data[index++];
  450. output.push(u16[0] - 0x10000);
  451. break;
  452. case 0b0110:
  453. c32[0] = data[index++];
  454. c32[1] = data[index++];
  455. c32[2] = data[index++];
  456. c32[3] = data[index++];
  457. output.push(u32[0]);
  458. break;
  459. case 0b0111:
  460. c32[0] = data[index++];
  461. c32[1] = data[index++];
  462. c32[2] = data[index++];
  463. c32[3] = data[index++];
  464. output.push(u32[0] - 0x100000000);
  465. break;
  466. case 0b1000:
  467. c32[0] = data[index++];
  468. c32[1] = data[index++];
  469. c32[2] = data[index++];
  470. c32[3] = data[index++];
  471. output.push(f32[0]);
  472. break;
  473. case 0b1001:
  474. {
  475. let byte = data[index++];
  476. output.push(byte === 0 ? '' : String.fromCharCode(byte));
  477. }
  478. break;
  479. case 0b1010:
  480. {
  481. let string = '';
  482. let byte = 0;
  483. while (byte = data[index++]) {
  484. string += String.fromCharCode(byte);
  485. }
  486. output.push(string);
  487. }
  488. break;
  489. case 0b1011:
  490. {
  491. let string = '';
  492. let byte = 0;
  493. while (byte = data[index++] | (data[index++] << 8)) {
  494. string += String.fromCharCode(byte);
  495. }
  496. output.push(string);
  497. }
  498. break;
  499. }
  500. }
  501.  
  502. return output;
  503. }
  504.  
  505. function rotator(packet) {
  506. return {
  507. i: 0,
  508. arr: packet,
  509. get(index) {
  510. return packet[index];
  511. },
  512. set(index, value) {
  513. return (packet[index] = value);
  514. },
  515. nex() {
  516. if (this.i === this.arr.length) {
  517. console.error(new Error('End reached'), this.arr);
  518. return -1;
  519. }
  520. return packet[this.i++];
  521. }
  522. };
  523. }
  524.  
  525. class BroadcastParser {
  526. constructor() {
  527. this.leaderboard = [];
  528. this.teamMinimap = [];
  529. this.globalMinimap = [];
  530. }
  531.  
  532. parse(packet) {
  533. const rot = rotator(packet);
  534.  
  535. if (rot.nex() !== 'b') throw new TypeError('Invalid packet header; expected packet `b`');
  536.  
  537. this._array(rot, () => {
  538. const del = rot.nex();
  539.  
  540. this.globalMinimap.remove(this.globalMinimap.findIndex(({ id }) => id === del));
  541. });
  542.  
  543. this._array(rot, () => {
  544. const dot = {
  545. id: rot.nex(),
  546. type: rot.nex(),
  547. x: rot.nex(),
  548. y: rot.nex(),
  549. color: rot.nex(),
  550. size: rot.nex()
  551. };
  552.  
  553. let index = this.globalMinimap.findIndex(({ id }) => id === dot.id);
  554. if (index === -1) index = this.globalMinimap.length;
  555.  
  556. this.globalMinimap[index] = dot;
  557. });
  558.  
  559. this._array(rot, () => {
  560. const del = rot.nex();
  561.  
  562. this.teamMinimap.remove(this.teamMinimap.findIndex(({ id }) => id === del));
  563. });
  564.  
  565. this._array(rot, () => {
  566. const dot = {
  567. id: rot.nex(),
  568. x: rot.nex(),
  569. y: rot.nex(),
  570. color: rot.nex()
  571. };
  572.  
  573. let index = this.teamMinimap.findIndex(({ id }) => id === dot.id);
  574. if (index === -1) index = this.teamMinimap.length;
  575.  
  576. this.teamMinimap[index] = dot;
  577. });
  578.  
  579. this._array(rot, () => {
  580. const del = rot.nex();
  581.  
  582. this.leaderboard.remove(this.leaderboard.findIndex(({ id }) => id === del));
  583. });
  584.  
  585. this._array(rot, () => {
  586. const champ = {
  587. id: rot.nex(),
  588. score: rot.nex(),
  589. index: rot.nex(),
  590. name: rot.nex(),
  591. color: rot.nex(),
  592. barColor: rot.nex()
  593. };
  594.  
  595. let index = this.leaderboard.findIndex(({ id }) => id === champ.id);
  596. if (index === -1) index = this.leaderboard.length;
  597.  
  598. this.leaderboard[index] = champ;
  599. });
  600.  
  601. this.leaderboard.sort((c1, c2) => c2.score - c1.score);
  602.  
  603. return this;
  604. }
  605.  
  606. _array(rot, read, length = rot.nex()) {
  607. const out = Array(Math.max(0, length));
  608.  
  609. for (let i = 0; i < length; ++i) out[i] = read.call(this, i, rot);
  610.  
  611. return out;
  612. }
  613. }
  614.  
  615. class RecordParser {
  616. constructor() {
  617. this.score = null;
  618. this.seconds = null;
  619. this.killCount = {
  620. players: null,
  621. assists: null,
  622. bosses: null
  623. };
  624. this.killersLength = null;
  625. this.killers = [];
  626. this.baseCooldown = null;
  627. }
  628. parse(packet) {
  629. const rot = rotator(packet);
  630. if (rot.nex() !== 'F') throw new TypeError('Invalid packet header; expected packet `F`');
  631. this.score = rot.nex();
  632. this.seconds = rot.nex();
  633. this.killCount.players = rot.nex();
  634. this.killCount.assists = rot.nex();
  635. this.killCount.bosses = rot.nex();
  636. this.killersLength = rot.nex();
  637. for (let i = 0; i < this.killersLength; i++) {
  638. this.killers.push(rot.nex());
  639. }
  640. this.baseCooldown = rot.nex();
  641. return this;
  642. }
  643. }
  644.  
  645. class UpdateParser {
  646. constructor(doEntities = true) {
  647. this.camera = { x: null, y: null, vx: null, vy: null, fov: null };
  648. this.now = 0;
  649. this.player = {
  650. fps: 1,
  651. body: {
  652. type: null,
  653. color: null,
  654. id: null,
  655. },
  656. score: null,
  657. points: null,
  658. upgrades: [],
  659. stats: [],
  660. skills: null,
  661. accel: null,
  662. top: null,
  663. party: null
  664. };
  665. this.entities = doEntities ? [] : false;
  666. }
  667. parse(packet) {
  668. const rot = rotator(packet);
  669.  
  670. if (rot.nex() !== 'u') throw new TypeError('Invalid packet header; expected packet `u`');
  671.  
  672. this.now = rot.nex();
  673.  
  674. const version = this.now === 0 ? 2 : 1;
  675.  
  676. this.camera.x = rot.nex();
  677. this.camera.y = rot.nex();
  678. this.camera.fov = rot.nex();
  679. this.camera.vx = rot.nex();
  680. this.camera.vy = rot.nex();
  681.  
  682. const flags = rot.nex();
  683. if (flags & 0x0001) this.player.fps = rot.nex();
  684. if (flags & 0x0002) {
  685. this.player.body.type = rot.nex();
  686. this.player.body.color = rot.nex();
  687. this.player.body.id = rot.nex();
  688. }
  689. if (flags & 0x0004) this.player.score = rot.nex();
  690. if (flags & 0x0008) this.player.points = rot.nex();
  691. if (flags & 0x0010) this.player.upgrades = Array(Math.max(0, rot.nex())).fill(-1).map(() => rot.nex());
  692. if (flags & 0x0020) this.player.stats = Array(30).fill(0).map(() => rot.nex());
  693. if (flags & 0x0040) {
  694. const result = parseInt(rot.nex(), 36);
  695.  
  696. this.player.skills = [
  697. (result / 0x1000000000 & 15),
  698. (result / 0x0100000000 & 15),
  699. (result / 0x0010000000 & 15),
  700. (result / 0x0001000000 & 15),
  701. (result / 0x0000100000 & 15),
  702. (result / 0x0000010000 & 15),
  703. (result / 0x0000001000 & 15),
  704. (result / 0x0000000100 & 15),
  705. (result / 0x0000000010 & 15),
  706. (result / 0x0000000001 & 15)
  707. ];
  708. }
  709. if (flags & 0x0080) this.player.accel = rot.nex();
  710. if (flags & 0x0100) this.player.top = rot.nex();
  711. if (flags & 0x0200) this.player.party = rot.nex();
  712. if (flags & 0x0400) this.player.speed = rot.nex();
  713.  
  714. if (version === 2 && this.entities !== false) {
  715. this._parseEnts(rot);
  716. } else if (version !== 2 && this.entities !== false) {
  717. this.entities = false;
  718. console.error('Invalid version, expected version 2. Disabling entities');
  719. }
  720. return this;
  721. }
  722. _table(rot, read) {
  723. const out = [];
  724. for (let id = rot.nex(); id !== -1; id = rot.nex()) {
  725. out[out.length] = read.call(this, id, rot);
  726. }
  727. return out;
  728. }
  729. _parseEnts(rot) {
  730. if (rot.nex() !== -1) return console.warn('uhhhh-cancelling', rot.arr);
  731.  
  732. this._table(rot, (id) => {
  733. const index = this.entities.findIndex(ent => ent.id === id);
  734. if (index === -1) {
  735. return console.warn('Possible desync, deletion of non existent entity ' + id);
  736. }
  737. this.entities[index] = this.entities[this.entities.length - 1];
  738. --this.entities.length;
  739. });
  740.  
  741. this._table(rot, (id) => {
  742. let index = this.entities.findIndex(ent => ent.id === id);
  743. if (index === -1) this.entities[index = this.entities.length] = { id };
  744.  
  745. const ent = this.entities[index];
  746. this._parseEnt(ent, rot);
  747. });
  748. }
  749.  
  750. _parseEnt(ent, rot) {
  751. const flags = rot.nex();
  752. if (!ent) console.log(this.entities.length, rot.get(rot.i - 1));
  753. if (flags & 0x0001) {
  754. let { x: lastX, y: lastY } = ent;
  755. ent.x = rot.nex() * 0.0625;
  756. ent.y = rot.nex() * 0.0625;
  757. if (typeof lastX !== 'undefined') {
  758. ent.vx = (ent.x - lastX);
  759. ent.vy = (ent.y - lastY);
  760. } else ent.vx = ent.vy = 0;
  761. }
  762. if (flags & 0x0002) ent.facing = rot.nex() * (360 / 256);
  763. if (flags & 0x0004) ent.flags = rot.nex();
  764. if (flags & 0x0008) ent.health = rot.nex() / 255;
  765. if (flags & 0x0010) ent.shield = Math.max(0, rot.nex() / 255);
  766. if (flags & 0x0020) ent.alpha = rot.nex() / 255;
  767. if (flags & 0x0040) ent.size = rot.nex() * 0.0625;
  768. if (flags & 0x0080) ent.score = rot.nex();
  769. if (flags & 0x0100) ent.name = rot.nex();
  770. if (flags & 0x0200) ent.mockupIndex = rot.nex();
  771. if (flags & 0x0400) ent.color = rot.nex();
  772. if (flags & 0x0800) ent.layer = rot.nex();
  773. if (flags & 0x1000) {
  774. if (!ent.guns) ent.guns = [];
  775.  
  776. this._table(rot, (index) => {
  777. const flag = rot.nex();
  778. if (!ent.guns[index]) ent.guns[index] = {};
  779. if (flag & 1) ent.guns[index].time = rot.nex();
  780. if (flag & 2) ent.guns[index].power = Math.sqrt(rot.nex()) / 20;
  781. });
  782. }
  783. if (flags & 0x2000) {
  784. if (!ent.turrets) ent.turrets = [];
  785.  
  786. ent.turrets = this._table(rot, (index) => {
  787. let i = ent.turrets.findIndex(ent => ent.index === index);
  788. if (i === -1) ent.turrets[i = ent.turrets.length] = { index };
  789. const turret = ent.turrets[i];
  790.  
  791. return this._parseEnt(turret, rot);
  792. });
  793. }
  794.  
  795. return ent;
  796. }
  797. }
  798.  
  799. class MockupsParser {
  800. constructor() {
  801. this.entries = [];
  802. }
  803. parse(packet) {
  804. if (packet[0] !== 'J') throw new TypeError('Invalid packet header; expected packet `J`');
  805. this.entries.push(...packet.slice(1));
  806. return this;
  807. }
  808. get(index) {
  809. const idx = this.entries.indexOf(index) + 1;
  810. if (idx === 0) return console.error(`Index ${index} not present in mockups`, this.entries);
  811. return JSON.parse(this.entries[idx]);
  812. }
  813. }
  814.  
  815. const coder = { encode, decode };
  816.  
  817. // HOOKING
  818.  
  819. const hijack = () => {
  820. if (window['%arras']) return window['%arras'];
  821.  
  822. window['%arras'] = new Promise(r => {
  823. const _send = WebSocket.prototype.send;
  824. window.WebSocket = class ArrasSocket extends WebSocket {
  825. constructor(...args) {
  826. super(...args);
  827. this.isntArras = true;
  828. if (Array.isArray(args[1]) && !(args[2] && args[2] === 'apm-ignore')) {
  829. this.isntArras = false;
  830. this._hook();
  831.  
  832. this.onopen = () => r(this);
  833. this.sendHooks = [];
  834. this.msgHooks = [];
  835. }
  836. }
  837.  
  838. _hook() {
  839. if (this.isntArras) throw 'sus';
  840.  
  841. let send = this.send;
  842. this.send = function (buf) {
  843. return send.call(this, coder.encode(this.sendHooks.reduce((data, hook) => hook(data) || data, coder.decode(buf))));
  844. };
  845.  
  846. let adv = this.addEventListener;
  847. this.addEventListener = function (type, cb, pro = false) {
  848. if (pro) return adv.call(this, type, cb, pro);
  849.  
  850. if (type === 'message') {
  851. adv.call(this, 'message', (event) => {
  852. this.msgCallback = cb;
  853. cb(new MessageEvent('message', {
  854. data: coder.encode(this.msgHooks.reduce((data, hook) => hook(data) || data, coder.decode(new Uint8Array(event.data)))).buffer
  855. }));
  856. });
  857. } else return adv.call(this, type, cb, pro);
  858. };
  859. }
  860.  
  861. hookSend(...funcs) {
  862. this.sendHooks.push.apply(this.sendHooks, funcs);
  863. return this.sendHooks.length - 1;
  864. }
  865. hookMsg(...funcs) {
  866. this.msgHooks.push.apply(this.msgHooks, funcs);
  867. return this.msgHooks.length - 1;
  868. }
  869.  
  870. directTalk(...data) {
  871. _send.call(this, coder.encode(data));
  872. }
  873.  
  874. talk(...data) {
  875. this.send(coder.encode(data));
  876. }
  877.  
  878. receive(...data) {
  879. this.msgCallback(new MessageEvent('message', { data: coder.encode(data) }));
  880. }
  881. };
  882. });
  883.  
  884. return window['%arras'];
  885. };
  886.  
  887. return { encode, decode, BroadcastParser, RecordParser, UpdateParser, MockupsParser, hijack, Server };
  888. })();