이 스크립트는 직접 설치해서 쓰는 게 아닙니다. 다른 스크립트가 메타 명령 // @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;
}
}