Greasy Fork is available in English.

wasm parser

used to modify wasm

Tento skript by nemal byť nainštalovaný priamo. Je to knižnica pre ďalšie skripty, ktorú by mali používať cez meta príkaz // @require https://update.greasyfork.org/scripts/455550/1414617/wasm%20parser.js

// ==UserScript==
// @name         wasm parser
// @version      1.0
// @description  used to modify wasm
// @author       bismuth
// ==/UserScript==
const OP = {
    "unreachable": 0,
    "nop": 1,
    "block": 2,
    "loop": 3,
    "if": 4,
    "else": 5,
    "end": 11,
    "br": 12,
    "br_if": 13,
    "br_table": 14,
    "return": 15,
    "call": 16,
    "call_indirect": 17,
    "drop": 26,
    "select": 27,
    "local": {
        "get": 32,
        "set": 33,
        "tee": 34
    },
    "global": {
        "get": 35,
        "set": 36
    },
    "i32": {
        "load": 40,
        "load8_s": 44,
        "load8_u": 45,
        "load16_s": 46,
        "load16_u": 47,
        "store": 54,
        "store8": 58,
        "store16": 59,
        "const": 65,
        "eqz": 69,
        "eq": 70,
        "ne": 71,
        "lt_s": 72,
        "lt_u": 73,
        "gt_s": 74,
        "gt_u": 75,
        "le_s": 76,
        "le_u": 77,
        "ge_s": 78,
        "ge_u": 79,
        "clz": 103,
        "ctz": 104,
        "popcnt": 105,
        "add": 106,
        "sub": 107,
        "mul": 108,
        "div_s": 109,
        "div_u": 110,
        "rem_s": 111,
        "rem_u": 112,
        "and": 113,
        "or": 114,
        "xor": 115,
        "shl": 116,
        "shr_s": 117,
        "shr_u": 118,
        "rotl": 119,
        "rotr": 120,
        "wrap_i64": 167,
        "wrap_f32_s": 168,
        "wrap_f32_u": 169,
        "wrap_f64_s": 170,
        "wrap_f64_u": 171,
        "reinterpret_f32": 188
    },
    "i64": {
        "load": 41,
        "load8_s": 48,
        "load8_u": 49,
        "load16_s": 50,
        "load16_u": 51,
        "load32_s": 52,
        "load32_u": 53,
        "store": 55,
        "store8": 60,
        "store16": 61,
        "store32": 62,
        "const": 66,
        "eqz": 80,
        "eq": 81,
        "ne": 82,
        "lt_s": 83,
        "lt_u": 84,
        "gt_s": 85,
        "gt_u": 86,
        "le_s": 87,
        "le_u": 88,
        "ge_s": 89,
        "ge_u": 90,
        "clz": 121,
        "ctz": 122,
        "popcnt": 123,
        "add": 124,
        "sub": 125,
        "mul": 126,
        "div_s": 127,
        "div_u": 128,
        "rem_s": 129,
        "rem_u": 130,
        "and": 131,
        "or": 132,
        "xor": 133,
        "shl": 134,
        "shr_s": 135,
        "shr_u": 136,
        "rotl": 137,
        "rotr": 138,
        "extend_i32_s": 172,
        "extend_i32_u": 173,
        "trunc_f32_s": 174,
        "trunc_f32_u": 175,
        "trunc_f64_s": 176,
        "trunc_f64_u": 177,
        "reinterpret_f64": 189
    },
    "f32": {
        "load": 42,
        "store": 56,
        "const": 67,
        "eq": 91,
        "ne": 92,
        "lt": 93,
        "gt": 95,
        "le": 94,
        "ge": 96,
        "abs": 139,
        "neg": 140,
        "ceil": 141,
        "floor": 142,
        "trunc": 143,
        "nearest": 144,
        "sqrt": 145,
        "add": 146,
        "sub": 147,
        "mul": 148,
        "div": 149,
        "min": 150,
        "max": 151,
        "copysign": 152,
        "convert_i32_s": 178,
        "convert_i32_u": 179,
        "convert_i64_s": 180,
        "convert_i64_u": 181,
        "demote_f64": 182,
        "reinterpret_i32": 190
    },
    "f64": {
        "load": 43,
        "store": 57,
        "const": 68,
        "eq": 97,
        "ne": 98,
        "lt": 99,
        "gt": 100,
        "le": 101,
        "ge": 102,
        "abs": 153,
        "neg": 154,
        "ceil": 155,
        "floor": 156,
        "trunc": 157,
        "nearest": 158,
        "sqrt": 159,
        "add": 160,
        "sub": 161,
        "mul": 162,
        "div": 163,
        "min": 164,
        "max": 165,
        "copysign": 166,
        "convert_i32_s": 183,
        "convert_i32_u": 184,
        "convert_i64_s": 185,
        "convert_i64_u": 186,
        "promote_f32": 187,
        "reinterpret_i64": 191
    },
    "memory": {
        "size": 63,
        "grow": 64
    }
};
class WASMSection {
    constructor(desc,length) {
        this.section = desc;
        this.body = new Array(length);
    }
}
class WASMParser {
    constructor(bin) {
        this.lexer = new Reader(new Uint8Array(bin));
        this.sections = new Array(13);
        this.adjustImports = 0;
        this.importFuncCount = 0;
        this.parseWASM();
    }
    read(bin) { this.lexer.packet = new Uint8Array(bin) }
    loadFunc(index) {
        this.lexer.set(this.sections[10].body[index - this.importFuncCount]);
        const localLength = this.lexer.vu();
        for (let n = 0; n < localLength; n++) {
            this.lexer.vu();
            this.lexer.u8();
        }
        return;
    }
    set(index, val = this.lexer.packet) {
        this.sections[10].body[index - this.importFuncCount] = val;
    }
    getAdjusted(index) {
        if (index < this.importFuncCount) return index;
        return index + this.adjustImports;
    }
    addImportEntry(options) {
        const map = ['f64','f32','i64','i32'];
        switch(options.kind) {
            case 'func':
                this.sections[2].body.push({
                    name: options.name,
                    type: "func",
                    index: this.sections[1].body.length,
                    isNew: true
                });
                this.sections[1].body.push({
                    param: options.params,
                    return: options.returns,
                    isNew: true
                });
                this.adjustImports++;
                return this.sections[2].body.length - 1;
            case 'global':
                this.sections[6].body.push({
                    type: options.type,
                    mutable: options.mutable,
                    value: options.mutable,
                    isNew: true
                });
                return this.sections[6].body.length - 1;
            default:
                throw new Error('oops, not supported yet');
        }
    }
    reindex() {
        let section = this.sections[10].body;
        let length = section.length;
        for (let n = 0; n < length; n++) this.sections[10].body[n] = this.parseFunction(section[n]);
        section = this.sections[9].body;
        length = section.length;
        for (let n = 0; n < length; n++) {
            const l = section[n].funcs.length;
            for (let p = 0; p < l; p++) this.sections[9].body[n].funcs[p] = this.getAdjusted(section[n].funcs[p]);
        }
        section = this.sections[7].body;
        length = section.length;
        for (let n = 0; n < length; n++) this.sections[7].body[n].index = this.getAdjusted(section[n].index);
        this.adjustImports = 0;
    }
    compile() {
        const bin = [0, 97, 115, 109, 1, 0, 0, 0];
        for (let n = 0; n < 12; n++) {
            if (!this.sections[n]) continue;
            const section = this[`compileSection0x${n.toString(16)}`]();
            bin.push(n);
            bin.push(...Writer.vu(section.length));
            for (const byte of section) bin.push(byte);
        }
        return new Uint8Array(bin);
    }
    compileSection0x1() {
        const map = ['f64','f32','i64','i32'];
        const section = this.sections[1].body;
        const length = section.length;
        const bin = Writer.vu(length);
        for (let n = 0; n < length; n++) {
            bin.push(0x60);
            bin.push(...Writer.vu(section[n].param.length));
            for (const param of section[n].param) bin.push(map.indexOf(param) + 0x7C);
            bin.push(...Writer.vu(section[n].return.length));
            for (const param of section[n].return) bin.push(map.indexOf(param) + 0x7C);
        }
        return bin;
    }
    compileSection0x2() {
        const map = ['func','table','mem','global'];
        const section = this.sections[2].body;
        const length = section.length;
        const bin = Writer.vu(length);
        for (let n = 0; n < length; n++) {
            const nameSplit = section[n].name.split('.');
            for (const part of nameSplit) bin.push(...Writer.stringLEN(part));
            bin.push(map.indexOf(section[n].type));
            bin.push(...Writer.vu(section[n].index));
            //console.log(bin);
        }
        return bin;
    }
    compileSection0x3() {
        const section = this.sections[3].body;
        const length = section.length;
        const bin = Writer.vu(length);
        for (let n = 0; n < length; n++) bin.push(...Writer.vu(section[n]));
        return bin;
    }
    compileSection0x4() {
        const section = this.sections[4].body;
        const length = section.length;
        const bin = Writer.vu(length);
        for (let n = 0; n < length; n++) for (let p = 0; p < 4; p++) bin.push(...Writer.vu(section[n][p]));
        return bin;
    }
    compileSection0x5() {
        const section = this.sections[5].body;
        const length = section.length;
        const bin = Writer.vu(length);
        for (let n = 0; n < length; n++) {
            bin.push(...Writer.vu(section[n].type));
            bin.push(...Writer.vu(section[n].limit[0]));
            bin.push(...Writer.vu(section[n].limit[1]));
        }
        return bin;
    }
    compileSection0x6() {
        const map = ['f64','f32','i64','i32'];
        const section = this.sections[6].body;
        const length = section.length;
        const bin = Writer.vu(length);
        for (let n = 0; n < length; n++) {
            bin.push(map.indexOf(section[n].type) + 0x7C);
            bin.push(section[n].mutable);
            for (const expr of section[n].expr) bin.push(...Writer.vu(expr));
            bin.push(11);
        }
        return bin;
    }
    compileSection0x7() {
        const map = ['func','table','mem','global'];
        const section = this.sections[7].body;
        const length = section.length;
        const bin = Writer.vu(length);
        for (let n = 0; n < length; n++) {
            bin.push(...Writer.stringLEN(section[n].name));
            bin.push(map.indexOf(section[n].type));
            bin.push(...Writer.vu(section[n].index));
        }
        return bin;
    }
    compileSection0x8() {
        const section = this.sections[8].body;
        const length = 1;
        const bin = [1];
        bin.push(...Writer.vu(section));
        return bin;
    }
    compileSection0x9() {
        const section = this.sections[9].body;
        const length = section.length;
        const bin = Writer.vu(length);
        for (let n = 0; n < length; n++) {
            bin.push(section[n].type, section[n].expr[0]);
            bin.push(...Writer.vi(section[n].expr[1]),11);
            bin.push(...Writer.vu(section[n].funcs.length));
            for (const funcIdx of section[n].funcs) bin.push(...Writer.vu(funcIdx));
        }
        return bin;
    }
    compileSection0xa() {
        const section = this.sections[10].body;
        const length = section.length;
        const bin = Writer.vu(length);
        for (let n = 0; n < length; n++) {
            //section[n] = this.parseFunction(section[n]);
            bin.push(...Writer.vu(section[n].length));
            for (const byte of section[n]) bin.push(byte);
        }
        return bin;
    }
    compileSection0xb() {
        const section = this.sections[11].body;
        const length = section.length;
        const bin = Writer.vu(length);
        for (let n = 0; n < length; n++) {
            bin.push(section[n].type,section[n].expr[0]);
            bin.push(...Writer.vi(section[n].expr[1]),11);
            bin.push(...Writer.vu(section[n].contents.length));
            for (const byte of section[n].contents) bin.push(byte);
        }
        return bin;
    }
    parseWASM() {
        this.lexer.index = 8;
        while (this.lexer.has()) {
            const id = this.lexer.u8();
            if (id > 12) return;
            this[`parseSection0x${id.toString(16)}`]();
        }
        this.importFuncCount = this.sections[2].body.filter(({type}) => type === 'func').length;
    }
    parseSection0x1() {
        const map = ['f64','f32','i64','i32'];
        const rawLength = this.lexer.vu();
        const section = new WASMSection('functypes', this.lexer.vu());
        for (let n = 0; n < section.body.length; n++) {
            const type = { param: [], return: [] }
            if (this.lexer.u8() !== 0x60) break;
            let len = this.lexer.vu();
            for (let n = 0; n < len; n++) type.param.push(map[this.lexer.u8()-0x7C]);
            len = this.lexer.vu();
            for (let n = 0; n < len; n++) type.return.push(map[this.lexer.u8()-0x7C]);
            section.body[n] = type;
        }
        return (this.sections[1] = section);
    }
    parseSection0x2() {
        const map = ['func','table','mem','global'];
        this.lexer.vu();
        const section = new WASMSection('imports', this.lexer.vu());
        for (let n = 0; n < section.body.length; n++) section.body[n] = { name: this.lexer.stringLEN() + '.' + this.lexer.stringLEN(), type: map[this.lexer.u8()], index: this.lexer.vu() };
        return (this.sections[2] = section);
    }
    parseSection0x3() {
        this.lexer.vu();
        const section = new WASMSection('functions', this.lexer.vu());
        for (let n = 0; n < section.body.length; n++) section.body[n] = this.lexer.vu();
        return (this.sections[3] = section);
    }
    parseSection0x4() {
        this.lexer.vu();
        const section = new WASMSection('tables', this.lexer.vu());
        for (let n = 0; n < section.body.length; n++) section.body[n] = [this.lexer.vu(), this.lexer.vu(), this.lexer.vu(), this.lexer.vu()]; //incomplete
        return (this.sections[4] = section);
    }
    parseSection0x5() {
        this.lexer.vu();
        const section = new WASMSection('mem', this.lexer.vu());
        for (let n = 0; n < section.body.length; n++) section.body[n] = { type: this.lexer.vu(), limit: [this.lexer.vu(), this.lexer.vu()] }
        return (this.sections[5] = section);
    }
    parseSection0x6() {
        const map = ['f64','f32','i64','i32'];
        this.lexer.vu();
        const section = new WASMSection('globals', this.lexer.vu());
        for (let n = 0; n < section.body.length; n++) {
            section.body[n] = { type: map[this.lexer.u8()-0x7C], mutable: this.lexer.u8(), expr: [] }
            section.body[n].expr.push(this.lexer.ru8());
            switch(this.lexer.u8()) {
                case OP.i32.const:
                case OP.i64.const:
                    section.body[n].expr.push(this.lexer.vu());
                    break;
                case OP.f32.const:
                    section.body[n].expr.push(this.f32());
                    break;
                case OP.f64.const:
                    section.body[n].expr.push(this.f64());
                    break;
            }
            this.lexer.u8();
        }
        return (this.sections[6] = section);
    }
    parseSection0x7() {
        const map = ['func','table','mem','global'];
        this.lexer.vu();
        const section = new WASMSection('exports', this.lexer.vu());
        for (let n = 0; n < section.body.length; n++) {
            const name = this.lexer.stringLEN();
            const type = map[this.lexer.u8()];
            const index = this.lexer.vu();
            section.body[n] = { name, type, index };
        }
        return (this.sections[7] = section);
    }
    parseSection0x8() {
        this.lexer.vu();
        const section = new WASMSection('start', this.lexer.vu());
        section.body = this.vu();
        return (this.sections[8] = section);
    }
    parseSection0x9() {
        this.lexer.vu();
        const section = new WASMSection('elements', this.lexer.vu());
        for (let n = 0; n < section.body.length; n++) {
            section.body[n] = { type: this.lexer.u8() }; //NEED TO ACCOUNT FOR DIFFERENT TYPES
            section.body[n].expr = [this.lexer.u8(),this.lexer.vu()];
            this.lexer.u8();
            const repeat = this.lexer.vu();
            section.body[n].funcs = [];
            for (let p = 0; p < repeat; p++) section.body[n].funcs.push(this.lexer.vu());
        }
        return (this.sections[9] = section);
    }
    parseSection0xa() {
        this.lexer.vu();
        const section = new WASMSection('code', this.lexer.vu());
        for (let n = 0; n < section.body.length; n++) {
            const len = this.lexer.vu();
            section.body[n] = this.lexer.packet.slice(this.lexer.index, this.lexer.index += len);
        }
        return (this.sections[10] = section);
    }
    parseSection0xb() {
        this.lexer.vu();
        const t = this.lexer.index;

        const section = new WASMSection('data', this.lexer.vu());
        for (let n = 0; n < section.body.length; n++) {
            section.body[n] = { type: this.lexer.u8(), expr: [this.lexer.u8(),this.lexer.vu()] };
            this.lexer.u8();
            const len = this.lexer.vu();
            section.body[n].contents = this.lexer.packet.slice(this.lexer.index, this.lexer.index += len);
        }
        return (this.sections[11] = section);
    }
    parseFunction(func) {
        this.lexer.set(func);
        const localLength = this.lexer.vu();
        for (let n = 0; n < localLength; n++) {
            this.lexer.vu();
            this.lexer.u8();
        }
        while(this.lexer.has()) {
            const before = this.lexer.index;
            const instr = this.parseInstruction();
            if (instr.op === OP.call) {
                this.lexer.index = before + 1;
                this.lexer.replaceVu(this.getAdjusted(instr.immediates[0]));
            }
        }
        return this.lexer.packet;
    }
    parseInstruction() {
        let len;
        const op = this.lexer.u8();
        const immediates = [];
        switch(op) {
            case OP.block: case OP.loop: case OP.if:
            case OP.memory.size: case OP.memory.grow:
                immediates.push(this.lexer.u8());
                break;
            case OP.br: case OP.br_if:
            case OP.local.get: case OP.local.set: case OP.local.tee:
            case OP.i32.const: case OP.i64.const:
                immediates.push(this.lexer.vu());
                break;
            case OP.f32.const:
                immediates.push(this.lexer.f32());
                break;
            case OP.f64.const:
                immediates.push(this.lexer.f64());
                break;
            case OP.global.get: case OP.global.set:
                immediates.push(this.lexer.vu());
                break; //adjust global index later
            case OP.i32.load: case OP.i32.load8_s: case OP.i32.load8_u: case OP.i32.load16_s: case OP.i32.load16_u:
            case OP.i64.load: case OP.i64.load8_s: case OP.i64.load8_u: case OP.i64.load16_s: case OP.i64.load16_u: case OP.i64.load32_s: case OP.i64.load32_u:
            case OP.f32.load:
            case OP.f64.load:
            case OP.i32.store: case OP.i32.store8: case OP.i32.store16:
            case OP.i64.store: case OP.i64.store8: case OP.i64.store16: case OP.i64.store32:
            case OP.f32.store:
            case OP.f64.store:
                immediates.push(this.lexer.vu(),this.lexer.vu());
                break;
            case OP.call_indirect:
                immediates.push(this.lexer.vu(),this.lexer.u8());
                break;
            case OP.br_table:
                len = this.lexer.vu();
                immediates.push(len);
                for (let n = 0; n < len+1; n++) immediates.push(this.lexer.vu());
                break;
            case OP.call:
                immediates.push(this.lexer.vu());
                break;
            default:
                break;
        }
        return {op, immediates}
    }
    regex(func, instrRegex, cb = (sIndex,eIndex,instrs) => {}, all=true) {
        this.lexer.set(this.sections[10].body[func - this.importFuncCount]);
        const todos = [];
        let exprBlob = [];
        const localLength = this.lexer.vu();
        for (let n = 0; n < localLength; n++) {
            this.lexer.vu();
            this.lexer.u8();
        }
        let startIndex = this.lexer.index, regexPos = 0;
        while(startIndex < this.lexer.packet.length) {
            if (regexPos === 0) {
                exprBlob = [];
                startIndex = this.lexer.index;
            }
            const currIndex = this.lexer.index;
            const instr = this.parseInstruction();
            exprBlob.push(instr);
            if (instr.op === instrRegex[regexPos][0]) {
                let good = true;
                for (let n = 1; n < instrRegex[regexPos].length; n++) if (instrRegex[regexPos][n] !== instr.immediates[n-1] && instrRegex[regexPos][n] !== '*') good = false;
                if (good) regexPos++;
                else {
                    regexPos = 0;
                    this.lexer.index = startIndex;
                    this.parseInstruction();
                    continue;
                }
            }
            else {
                regexPos = 0;
                this.lexer.index = startIndex;
                this.parseInstruction();
                continue;
            }
            if (regexPos === instrRegex.length) {
                todos.push([startIndex, this.lexer.index, [...exprBlob]]);
                if (!all) break;
                regexPos = 0;
            }
        }
        for (const [si, ci, eb] of todos) {
            cb(si, ci, eb);
        }
        return this.lexer.packet;
    }
    inject(code, index = this.lexer.index) {
        return this.lexer.inject(code, index);
    }
    remove(code, index = this.lexer.index) {
        return this.lexer.remove(code, index);
    }
}
class Writer {
    static vu(num) {
        const ret = [];
        while (num >= 128) {
            ret.push((num & 127) | 128);
            num >>= 7;
        }
        ret.push(num);
        return ret;
    }
    static vi(num) {
        const ret = [];
        while (num >= 128) {
            ret.push((num & 127) | 128);
            num >>= 7;
        }
        if (num < 0x40) ret.push(num);
        else {
            ret.push(num | 0x80);
            ret.push(num<0?1:0);
        }
        return ret;
    }
    static f32(num) {
        return [...new Uint8Array(new Float32Array([num]).buffer)];
    }
    static stringLEN(str) {
        str = new TextEncoder().encode(str);
        if (str.length > 127) throw new Error('Unsupported string length: don\'t use a string that long (max 127 byte length)');
        return [str.length, ...str];
    }
}
class Reader {
    constructor(packet) {
        this.packet = packet;
        this.index = 0;
        const buffer = new ArrayBuffer(8);
        this._u8 = new Uint8Array(buffer);
        this._f32 = new Float32Array(buffer);
        this._f64 = new Float64Array(buffer);
    }
    inject(code, index = this.index) {
        this.index = index;
        const newBuf = new Uint8Array(code.length + this.packet.length);
        newBuf.set(this.packet.slice(0,this.index),0);
        newBuf.set(code,this.index);
        newBuf.set(this.packet.slice(this.index),(this.index+code.length));
        return (this.packet = newBuf);
    }
    remove(index1, index2 = this.index) {
        this.index = index2;
        const newBuf = new Uint8Array(index1 - index2 + this.packet.length);
        newBuf.set(this.packet.slice(0,index1),0);
        newBuf.set(this.packet.slice(index2,this.packet.length),index1);
        return (this.packet = newBuf);
    }
    replaceVu(replace) {
        const before = this.index, old = this.vu(), now = this.index;
        replace = Writer.vu(replace);
        if (replace.length === now - before) this.packet.set(replace, before);
        else {
            const newBuf = new Uint8Array(this.packet.length-now+before+replace.length);
            newBuf.set(this.packet.slice(0,before),0);
            newBuf.set(replace,before);
            newBuf.set(this.packet.slice(now),(this.index=before+replace.length));
            this.packet = newBuf;
        }
    }
    has() { return this.index < this.packet.length }
    set(packet) {
        this.packet = packet;
        this.index = 0;
    }
    ru8() { return this.packet[this.index] }
    u8() { return this.packet[this.index++] }
    f32() {
        this._u8.set(this.packet.slice(this.index, this.index += 4));
        return this._f32[0];
    }
    f64() {
        this._u8.set(this.packet.slice(this.index, this.index += 8));
        return this._f64[0];
    }
    vu() {
        let out = 0, at = 0;
        while (this.packet[this.index] & 0x80) {
            out |= (this.u8() & 0x7f) << at;
            at += 7;
        }
        out |= this.u8() << at;
        return out;
    }
    stringLEN() {
        const len = this.u8();
        const ret = new TextDecoder().decode(this.packet.slice(this.index, this.index += len));
        return ret;
    }
}