Greasy Fork

Greasy Fork is available in English.

APM

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

当前为 2023-04-11 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         APM
// @version      1.0.2
// @author       ABC & Ray Adams
// @namespace    github.com/ABCxFF
// @description  Standard for hooking into the client -> server connection in arras.io
// @match        *://arras.io/
// @match        *://arras.netlify.app/
// @grant        none
// @run-at       document-start
// @license      GPL-3.0
// ==/UserScript==
 
/*
 * Copyright (C) 2021  ABC & Ray Adams
 * Licensed under GNU General Public License v3.0
 */
 
const arras = (() => {
    const u32 = new Uint32Array(1);
    const u16 = new Uint16Array(1);
    const c16 = new Uint8Array(u16.buffer);
    const c32 = new Uint8Array(u32.buffer);
    const f32 = new Float32Array(u32.buffer);
 
    Array.prototype.remove = function (index) {
        if (index === this.length - 1) return this.pop();
        this[index] = this.pop();
    };
 
    function encode(message) {
        let headers = []
        let headerCodes = []
        let contentSize = 0
        let lastTypeCode = 0b1111
        let repeatTypeCount = 0
        for (let block of message) {
            let typeCode = 0
            if (block === 0 || block === false) {
                typeCode = 0b0000
            } else if (block === 1 || block === true) {
                typeCode = 0b0001
            } else if (typeof block === 'number') {
                if (!Number.isInteger(block) || block < -0x100000000 || block >= 0x100000000) {
                    typeCode = 0b1000
                    contentSize += 4
                } else if (block >= 0) {
                    if (block < 0x100) {
                        typeCode = 0b0010
                        contentSize++
                    } else if (block < 0x10000) {
                        typeCode = 0b0100
                        contentSize += 2
                    } else if (block < 0x100000000) {
                        typeCode = 0b0110
                        contentSize += 4
                    }
                } else {
                    if (block >= -0x100) {
                        typeCode = 0b0011
                        contentSize++
                    } else if (block >= -0x10000) {
                        typeCode = 0b0101
                        contentSize += 2
                    } else if (block >= -0x100000000) {
                        typeCode = 0b0111
                        contentSize += 4
                    }
                }
            } else if (typeof block === 'string') {
                let hasUnicode = false
                for (let i = 0; i < block.length; i++) {
                    if (block.charAt(i) > '\xff') {
                        hasUnicode = true
                    } else if (block.charAt(i) === '\x00') {
                        console.error('Null containing string', block)
                        throw new Error('Null containing string')
                    }
                }
                if (!hasUnicode && block.length <= 1) {
                    typeCode = 0b1001
                    contentSize++
                } else if (hasUnicode) {
                    typeCode = 0b1011
                    contentSize += block.length * 2 + 2
                } else {
                    typeCode = 0b1010
                    contentSize += block.length + 1
                }
            } else {
                console.error('Unencodable data type', block)
                throw new Error('Unencodable data type')
            }
            headers.push(typeCode)
            if (typeCode === lastTypeCode) {
                repeatTypeCount++
            } else {
                headerCodes.push(lastTypeCode)
                if (repeatTypeCount >= 1) {
                    while (repeatTypeCount > 19) {
                        headerCodes.push(0b1110)
                        headerCodes.push(15)
                        repeatTypeCount -= 19
                    }
                    if (repeatTypeCount === 1)
                        headerCodes.push(lastTypeCode)
                    else if (repeatTypeCount === 2)
                        headerCodes.push(0b1100)
                    else if (repeatTypeCount === 3)
                        headerCodes.push(0b1101)
                    else if (repeatTypeCount < 20) {
                        headerCodes.push(0b1110)
                        headerCodes.push(repeatTypeCount - 4)
                    }
                }
                repeatTypeCount = 0
                lastTypeCode = typeCode
            }
        }
        headerCodes.push(lastTypeCode)
        if (repeatTypeCount >= 1) {
            while (repeatTypeCount > 19) {
                headerCodes.push(0b1110)
                headerCodes.push(15)
                repeatTypeCount -= 19
            }
            if (repeatTypeCount === 1)
                headerCodes.push(lastTypeCode)
            else if (repeatTypeCount === 2)
                headerCodes.push(0b1100)
            else if (repeatTypeCount === 3)
                headerCodes.push(0b1101)
            else if (repeatTypeCount < 20) {
                headerCodes.push(0b1110)
                headerCodes.push(repeatTypeCount - 4)
            }
        }
        headerCodes.push(0b1111)
        if (headerCodes.length % 2 === 1)
            headerCodes.push(0b1111)
    
        let output = new Uint8Array((headerCodes.length >> 1) + contentSize)
        for (let i = 0; i < headerCodes.length; i += 2) {
            let upper = headerCodes[i]
            let lower = headerCodes[i + 1]
            output[i >> 1] = (upper << 4) | lower
        }
        let index = headerCodes.length >> 1
        for (let i = 0; i < headers.length; i++) {
            let block = message[i]
            switch (headers[i]) {
                case 0b0000:
                case 0b0001:
                    break
                case 0b0010:
                case 0b0011:
                    output[index++] = block
                    break
                case 0b0100:
                case 0b0101:
                    u16[0] = block
                    output.set(c16, index)
                    index += 2
                    break
                case 0b0110:
                case 0b0111:
                    u32[0] = block
                    output.set(c32, index)
                    index += 4
                    break
                case 0b1000:
                    f32[0] = block
                    output.set(c32, index)
                    index += 4
                    break
                case 0b1001:
                    {
                        let byte = block.length === 0 ? 0 : block.charCodeAt(0)
                        output[index++] = byte
                    }
                    break
                case 0b1010:
                    for (let i = 0; i < block.length; i++) {
                        output[index++] = block.charCodeAt(i)
                    }
                    output[index++] = 0
                    break
                case 0b1011:
                    for (let i = 0; i < block.length; i++) {
                        let charCode = block.charCodeAt(i)
                        output[index++] = charCode & 0xff
                        output[index++] = charCode >> 8
                    }
                    output[index++] = 0
                    output[index++] = 0
                    break
            }
        }
    
        return output
    };
 
    function decode(packet) {
        let data = new Uint8Array(packet)
        if (data[0] >> 4 !== 0b1111)
            return null
    
        let headers = []
        let lastTypeCode = 0b1111
        let index = 0
        let consumedHalf = true
        while (true) {
            if (index >= data.length)
                return null
            let typeCode = data[index]
    
            if (consumedHalf) {
                typeCode &= 0b1111
                index++
            } else {
                typeCode >>= 4
            }
            consumedHalf = !consumedHalf
    
            if ((typeCode & 0b1100) === 0b1100) {
                if (typeCode === 0b1111) {
                    if (consumedHalf)
                        index++
                    break
                }
    
                let repeat = typeCode - 10 // 0b1100 - 2
                if (typeCode === 0b1110) {
                    if (index >= data.length)
                        return null
                    let repeatCode = data[index]
    
                    if (consumedHalf) {
                        repeatCode &= 0b1111
                        index++
                    } else {
                        repeatCode >>= 4
                    }
                    consumedHalf = !consumedHalf
    
                    repeat += repeatCode
                }
    
                for (let i = 0; i < repeat; i++)
                    headers.push(lastTypeCode)
            } else {
                headers.push(typeCode)
                lastTypeCode = typeCode
            }
        }
    
        let output = []
        for (let header of headers) {
            switch (header) {
                case 0b0000:
                    output.push(0)
                    break
                case 0b0001:
                    output.push(1)
                    break
                case 0b0010:
                    output.push(data[index++])
                    break
                case 0b0011:
                    output.push(data[index++] - 0x100)
                    break
                case 0b0100:
                    c16[0] = data[index++]
                    c16[1] = data[index++]
                    output.push(u16[0])
                    break
                case 0b0101:
                    c16[0] = data[index++]
                    c16[1] = data[index++]
                    output.push(u16[0] - 0x10000)
                    break
                case 0b0110:
                    c32[0] = data[index++]
                    c32[1] = data[index++]
                    c32[2] = data[index++]
                    c32[3] = data[index++]
                    output.push(u32[0])
                    break
                case 0b0111:
                    c32[0] = data[index++]
                    c32[1] = data[index++]
                    c32[2] = data[index++]
                    c32[3] = data[index++]
                    output.push(u32[0] - 0x100000000)
                    break
                case 0b1000:
                    c32[0] = data[index++]
                    c32[1] = data[index++]
                    c32[2] = data[index++]
                    c32[3] = data[index++]
                    output.push(f32[0])
                    break
                case 0b1001:
                    {
                        let byte = data[index++]
                        output.push(byte === 0 ? '' : String.fromCharCode(byte))
                    }
                    break
                case 0b1010:
                    {
                        let string = ''
                        let byte = 0
                        while (byte = data[index++]) {
                            string += String.fromCharCode(byte)
                        }
                        output.push(string)
                    }
                    break
                case 0b1011:
                    {
                        let string = ''
                        let byte = 0
                        while (byte = data[index++] | (data[index++] << 8)) {
                            string += String.fromCharCode(byte)
                        }
                        output.push(string)
                    }
                    break
            }
        }
    
        return output
    };
    
    function rotator(packet) {
        return {
            i: 0,
            arr: packet,
            get(index) {
                return packet[index];
            },
            set(index, value) {
                return (packet[index] = value);
            },
            nex() {
                if (this.i === this.arr.length) {
                    console.error(new Error('End reached'), this.arr)
                    return -1;
                }
                return packet[this.i++];
            }
        }
    };
 
    class BroadcastParser {
        constructor() {
            this.leaderboard = [];
            this.teamMinimap = [];
            this.globalMinimap = [];
        }
    
        parse(packet) {
            const rot = rotator(packet);
    
            if (rot.nex() !== 'b') throw new TypeError('Invalid packet header; expected packet `b`');
    
            this._array(rot, () => {
                const del = rot.nex();
    
                this.globalMinimap.remove(this.globalMinimap.findIndex(({ id }) => id === del));
            });
    
            this._array(rot, () => {
                const dot = {
                    id: rot.nex(),
                    type: rot.nex(),
                    x: rot.nex(),
                    y: rot.nex(),
                    color: rot.nex(),
                    size: rot.nex()
                };
    
                let index = this.globalMinimap.findIndex(({ id }) => id === dot.id);
                if (index === -1) index = this.globalMinimap.length;
    
                this.globalMinimap[index] = dot;
            });
    
            this._array(rot, () => {
                const del = rot.nex();
    
                this.teamMinimap.remove(this.teamMinimap.findIndex(({ id }) => id === del));
            });
    
            this._array(rot, () => {
                const dot = {
                    id: rot.nex(),
                    x: rot.nex(),
                    y: rot.nex(),
                    size: rot.nex()
                };
    
                let index = this.teamMinimap.findIndex(({ id }) => id === dot.id);
                if (index === -1) index = this.teamMinimap.length;
    
                this.teamMinimap[index] = dot;
            });
    
            this._array(rot, () => {
                const del = rot.nex();
    
                this.leaderboard.remove(this.leaderboard.findIndex(({ id }) => id === del));
            });
    
            this._array(rot, () => {
                const champ = {
                    id: rot.nex(),
                    score: rot.nex(),
                    index: rot.nex(),
                    name: rot.nex(),
                    color: rot.nex(),
                    barColor: rot.nex()
                };
    
                let index = this.leaderboard.findIndex(({ id }) => id === champ.id);
                if (index === -1) index = this.leaderboard.length;
    
                this.leaderboard[index] = champ;
            });
    
            this.leaderboard.sort((c1, c2) => c2.score - c1.score);
    
            return this;
        }
    
        _array(rot, read, length = rot.nex()) {
            const out = Array(Math.max(0, length));
    
            for (let i = 0; i < length; ++i) out[i] = read.call(this, i, rot);
    
            return out;
        }
    };
 
    class RecordParser {
        constructor() {
            this.score = null;
            this.seconds = null;
            this.killCount = {
                players: null,
                assists: null,
                bosses: null
            };
            this.killersLength = null;
            this.killers = [];
        }
    
        parse(packet) {
            if (packet.shift() !== 'F') throw new TypeError('Invalid packet header; expected packet `F`');
    
            this.score = packet.shift();
            this.seconds = packet.shift();
    
            this.killCount.players = packet.shift();
            this.killCount.assists = packet.shift();
            this.killCount.bosses = packet.shift();
    
            this.killersLength = packet.shift();
            this.killers = packet.slice(0, this.killersLength);
    
            return this;
        }
    };
 
    class UpdateParser {
        constructor(doEntities = true) {
            this.camera = { x: null, y: null, vx: null, vy: null, fov: null };
            this.now = 0;
            this.player = {
                fps: 1,
                body: {
                    type: null,
                    color: null,
                    id: null,
                },
                score: null,
                points: null,
                upgrades: [],
                stats: [],
                skills: null,
                accel: null,
                top: null,
                party: null
            }
            this.entities = doEntities ? [] : false;
        }
        parse(packet) {
            const rot = rotator(packet);
    
            if (rot.nex() !== 'u') throw new TypeError('Invalid packet header; expected packet `u`');
    
            this.now = rot.nex();
    
            const version = this.now === 0 ? 2 : 1;
    
            this.camera.x = rot.nex();
            this.camera.y = rot.nex();
            this.camera.fov = rot.nex();
            this.camera.vx = rot.nex();
            this.camera.vy = rot.nex();
    
            const flags = rot.nex();
            if (flags & 0x0001) this.player.fps = rot.nex();
            if (flags & 0x0002) {
                this.player.body.type = rot.nex();
                this.player.body.color = rot.nex();
                this.player.body.id = rot.nex();
            }
            if (flags & 0x0004) this.player.score = rot.nex();
            if (flags & 0x0008) this.player.points = rot.nex();
            if (flags & 0x0010) this.player.upgrades = Array(Math.max(0, rot.nex())).fill(-1).map(() => rot.nex());
            if (flags & 0x0020) this.player.stats = Array(30).fill(0).map(() => rot.nex());
            if (flags & 0x0040) {
                const result = rot.nex();
    
                this.player.skills = [
                    (result / 0x1000000000 & 15),
                    (result / 0x0100000000 & 15),
                    (result / 0x0010000000 & 15),
                    (result / 0x0001000000 & 15),
                    (result / 0x0000100000 & 15),
                    (result / 0x0000010000 & 15),
                    (result / 0x0000001000 & 15),
                    (result / 0x0000000100 & 15),
                    (result / 0x0000000010 & 15),
                    (result / 0x0000000001 & 15)
                ]
            }
            if (flags & 0x0080) this.player.accel = rot.nex();
            if (flags & 0x0100) this.player.top = rot.nex();
            if (flags & 0x0200) this.player.party = rot.nex();
            if (flags & 0x0400) this.player.speed = rot.nex();
    
            if (version === 2 && this.entities !== false) {
                this._parseEnts(rot)
            } else if (version !== 2 && this.entities !== false) {
                this.entities = false;
                console.error('Invalid version, expected version 2. Disabling entities');
            }
            return this;
        }
        _table(rot, read) {
            const out = [];
            for (let id = rot.nex(); id !== -1; id = rot.nex()) {
                out[out.length] = read.call(this, id, rot)
            }
            return out
        }
        _parseEnts(rot) {
            if (rot.nex() !== -1) return console.warn('uhhhh-cancelling', rot.arr);
    
            this._table(rot, (id) => {
                const index = this.entities.findIndex(ent => ent.id === id);
                if (index === -1) {
                    return console.warn('Possible desync, deletion of non existant entity ' + id, this.entities.findIndex(ent => ent.id2 === id), JSON.stringify(this.entities));
                }
                this.entities[index] = this.entities[this.entities.length - 1]
                --this.entities.length;
            });
 
            this._table(rot, (id) => {
                let index = this.entities.findIndex(ent => ent.id === id)
                if (index === -1) this.entities[index = this.entities.length] = { id };
    
                const ent = this.entities[index];
                this._parseEnt(ent, rot)
            });
        }
 
        _parseEnt(ent, rot) {
            const flags = rot.nex();
            if (!ent) console.log(this.entities.length, rot.get(rot.i - 1));
            if (flags & 0x0001) {
                let { x: lastX, y: lastY } = ent;
                ent.x = rot.nex() * 0.0625;
                ent.y = rot.nex() * 0.0625;
                if (typeof lastX !== 'undefined') {
                    ent.vx = (ent.x - lastX);
                    ent.vy = (ent.y - lastY);
                } else ent.vx = ent.vy = 0;
            }
            if (flags & 0x0002) ent.facing = rot.nex() * (360 / 256);
            if (flags & 0x0004) ent.flags = rot.nex();
            if (flags & 0x0008) ent.health = rot.nex() / 255;
            if (flags & 0x0010) ent.shield = Math.max(0, rot.nex() / 255);
            if (flags & 0x0020) ent.alpha = rot.nex() / 255;
            if (flags & 0x0040) ent.size = rot.nex() * 0.0625;
            if (flags & 0x0080) ent.score = rot.nex();
            if (flags & 0x0100) ent.name = rot.nex();
            if (flags & 0x0200) ent.id2 = rot.nex();
            if (flags & 0x0400) ent.color = rot.nex();
            if (flags & 0x0800) ent.layer = rot.nex();
            if (flags & 0x1000) {
                if (!ent.guns) ent.guns = []
    
                this._table(rot, (index) => {
                    const flag = rot.nex();
                    if (!ent.guns[index]) ent.guns[index] = {};
                    if (flag & 1) ent.guns[index].time = rot.nex();
                    if (flag & 2) ent.guns[index].power = Math.sqrt(rot.nex()) / 20;
                });
            }
            if (flags & 0x2000) {
                if (!ent.turrets) ent.turrets = [];
    
                ent.turrets = this._table(rot, (index) => {
                    let i = ent.turrets.findIndex(ent => ent.index === index)
                    if (i === -1) ent.turrets[i = ent.turrets.length] = { index };
                    const turret = ent.turrets[i];
    
                    return this._parseEnt(turret, rot);
                });
            }
    
            return ent;
        }
    };
 
    const coder = { encode, decode };
 
    const hijack = () => {
        if (window['%arras']) return window['%arras'];
        
        window['%arras'] = new Promise(r => {
            const _send = WebSocket.prototype.send;
            window.WebSocket = class ArrasSocket extends WebSocket {
                constructor(...args) {
                    super(...args);
                    this.isntArras = true;
                    if (Array.isArray(args[1])) {
                        this.isntArras = false;
                        this._hook();
                        
                        this.onopen = () => r(this);
                        this.sendHooks = [];
                        this.msgHooks = [];
                    }
                }
                
                _hook() {
                    if (this.isntArras) throw 'sus';
                    
                    let send = this.send;
                    this.send = function(buf) {
                        return send.call(this, coder.encode(this.sendHooks.reduce((data, hook) => hook(data) || data, coder.decode(buf))));
                    }
                    
                    let adv = this.addEventListener;
                    this.addEventListener = function(type, cb, pro=false) {
                        if (pro) return adv.call(this, type, cb, pro);
                        
                        if (type === 'message') {
                            adv.call(this, 'message', (event) => {
                                cb(new MessageEvent('message', {
                                    data: coder.encode(this.msgHooks.reduce((data, hook) => hook(data) || data, coder.decode(new Uint8Array(event.data)))).buffer
                                }));
                            });
                        } else return adv.call(this, type, cb, pro);
                    }
                }
                
                hookSend(...funcs) {
                    this.sendHooks.push.apply(this.sendHooks, funcs)
                }
                hookMsg(...funcs) {
                    this.msgHooks.push.apply(this.msgHooks, funcs)
                }
                
                directTalk(...data) {
                    _send.call(this, coder.encode(data));
                }
                
                talk(...data) {
                    this.send(coder.encode(data));
                }
            }
        });
        
        return window['%arras']
    };
    
    return { encode, decode, BroadcastParser, RecordParser, UpdateParser, hijack };
})()