// ==UserScript==
// @name wasm parsere
// @version 1.0
// @description used to modify wasm
// @author bismuth k
// ==/UserScript==
const OP = {"unreachable":0,"nop":1,"block":2,"loop":3,"if":4,"else":5,"end":11,"br":12,"br_if":13,"br_table":13,"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":75,"ne":76,"lt":77,"gt":78,"le":79,"ge":80,"abd":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,"abd":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) }
regex(match, allOccurences) {
let ret = [], rets = [];
search: for (let n = this.lexer.index; n < this.lexer.packet.length - match.length; n++) {
this.lexer.index = n;
ret = [];
for (let p = 0; p < match.length; p++) {
if (match[p] === '*') this.lexer.vu();
else if (match[p] === '+') ret.push(this.lexer.vu());
else if (this.lexer.u8() !== match[p]) continue search;
}
if (allOccurences) rets.push(ret);
else {
this.lexer.index = n;
return ret;
}
}
return rets.length? rets: false;
}
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.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.type) {
case 'func':
this.sections[2].body.push({
name: options.name,
type: "func",
index: this.sections[1].body.length
});
this.sections[1].body.push({
param: options.params,
return: options.returns
});
this.adjustImports++;
return this.sections[2].body.length - 1;
break;
default:
throw new Error('oops, not supported yet');
break;
}
}
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));
bin.push(...section[n].contents);
}
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();
let len, before;
for (let n = 0; n < localLength; n++) {
this.lexer.vu();
this.lexer.u8();
}
while(this.lexer.has()) {
let op;
switch(op = this.lexer.u8()) {
case OP.block: case OP.loop: case OP.if:
case OP.memory.size: case OP.memory.grow:
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:
this.lexer.vu();
break;
case OP.f32.const:
this.lexer.f32();
break;
case OP.f64.const:
this.lexer.f64();
break;
case OP.global.get: case OP.global.set:
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:
this.lexer.vu();
this.lexer.vu();
break;
case OP.call_indirect:
this.lexer.vu();
this.lexer.u8();
break;
case OP.br_table:
len = this.lexer.vu();
for (let n = 0; n < len+1; n++) this.lexer.vu();
break;
case OP.call:
len = this.lexer.index;
before = this.lexer.vu();
this.lexer.index = len;
this.lexer.replaceVu(this.getAdjusted(before));
break;
default:
if (op > 255) throw new Error('oops, you did something wrong');
break;
}
}
return this.lexer.packet;
}
}
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 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) {
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);
}
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;
}
}