// ==UserScript==
// @name wasm_patcher_v2
// @version 0.1
// @description WebAssemblyのbufferにパッチを当てる
// @author nekocell
// @namespace https://greasyfork.org/users/762895-nekocell
// ==/UserScript==
const Section = Object.freeze({
Custom: 0x00,
Type: 0x01,
Import: 0x02,
Function: 0x03,
Table: 0x04,
Memory: 0x05,
Global: 0x06,
Export: 0x07,
Start: 0x08,
Element: 0x09,
Code: 0x0A,
Data: 0x0B,
DataCount: 0x0C,
});
const ValueType = Object.freeze({
i32: 0x7F,
i64: 0x7E,
f32: 0x7D,
f64: 0x7C
});
const SignatureType = Object.freeze({
func: 0x60
});
const ExternalKind = Object.freeze({
Function: 0x00,
Table: 0x01,
Memory: 0x02,
Global: 0x03
});
const OP = Object.freeze({
unreachable: 0x00,
nop: 0x01,
block_raw: 0x02,
block: [0x02, 0x40],
loop_raw: 0x03,
loop: [0x03, 0x40],
if_raw: 0x04,
if: [0x04, 0x40],
else: 0x05,
end: 0x0B,
br: 0x0C,
br_if: 0x0D,
br_table: 0x0E,
return: 0x0F,
call: 0x10,
call_indirect: 0x11,
drop: 0x1A,
local: {
get: 0x20,
set: 0x21,
tee: 0x22,
},
global: {
get: 0x23,
set: 0x24,
},
memory: {
size: 0x3F,
grow: 0x40,
},
i32: {
load: 0x28,
load8_s: 0x2C,
load8_u: 0x2D,
load16_s: 0x2E,
load16_u: 0x2F,
store: 0x36,
store8: 0x3A,
store16: 0x3B,
const: 0x41,
eqz: 0x45,
add: 0x6A,
sub: 0x6B,
mul: 0x6C,
xor: 0x73,
extend8_s: 0xC0,
extend16_s: 0xC1,
},
i64: {
load: 0x29,
load8_s: 0x30,
load8_u: 0x31,
load16_s: 0x32,
load16_u: 0x33,
load32_s: 0x34,
load32_u: 0x35,
store: 0x37,
store8: 0x3C,
store16: 0x3D,
const: 0x42,
extend8_s: 0xC2,
extend16_s: 0xC3,
extend32_s: 0xC4,
},
f32: {
load: 0x2A,
store: 0x38,
const: 0x43,
gt: 0x5E,
sub: 0x93,
},
f64: {
load: 0x2B,
store: 0x39,
const: 0x44,
},
table: {
get: 0x25,
set: 0x26,
},
other_instruction: 0xFC,
others: {
memory_init: 0x08,
data_drop: 0x09,
memory_copy: 0x0a,
memory_fill: 0x0b,
table_init: 0x0c,
elem_drop: 0x0d,
table_copy: 0x0e,
}
});
const VAR = Object.freeze({
u32: (val) => {
const result = [];
let value = val;
if (val === 0) {
return [0];
}
while (value > 0) {
let byte = value & 0x7F;
value >>= 7;
if (value !== 0) {
byte |= 0x80;
}
result.push(byte);
}
return result;
},
s32: (value) => {
const result = [];
while (true) {
const byte = value & 0x7f;
value >>= 7;
if ((value === 0 && (byte & 0x40) === 0) ||
(value === -1 && (byte & 0x40) !== 0)
) {
result.push(byte);
return result;
}
result.push(byte | 0x80);
}
},
f32: (value) => {
const f32ary = new Float32Array([value]);
const f32bytes = new Uint8Array(f32ary.buffer);
return [...f32bytes];
},
f64: (value) => {
const f64ary = new Float64Array([value]);
const f64bytes = new Uint8Array(f64ary.buffer);
return [...f64bytes];
}
});
class BufferReader {
constructor(buffer) {
this._buffer = buffer.slice(0);
this._pos = 0;
}
get finished() {
return this._pos >= this._buffer.length;
}
get length() {
return this._buffer.length;
}
reset() {
this._pos = 0;
}
readU8() {
return this._buffer[this._pos++];
}
readU32() {
return this.readU8() |
(this.readU8() << 8) |
(this.readU8() << 16) |
(this.readU8() << 24);
}
readVarU32() {
let result = 0;
let shift = 0;
let currentByte;
do {
currentByte = this.readU8();
result |= (currentByte & 0x7F) << shift;
shift += 7;
} while (currentByte & 0x80);
return result;
}
readVarS32() {
let result = 0;
let shift = 0;
while (true) {
const byte = this.readU8();
result |= (byte & 0x7f) << shift;
shift += 7;
if ((0x80 & byte) === 0) {
if (shift < 32 && (byte & 0x40) !== 0) {
return result | (~0 << shift);
}
return result;
}
}
}
readVarS64() {
let result = BigInt(0);
let shift = BigInt(0);
while (true) {
const byte = BigInt(this.readU8());
result |= (byte & 0x7fn) << shift;
shift += 7n;
if ((0x80n & byte) == 0n) {
if (shift < 64n && (byte & 0x40n) != 0n) {
return result | (-1n << shift);
}
return result;
}
}
/*
let result = BigInt(0);
let shift = 0;
while (true) {
const byte = this.readU8();
result |= BigInt((byte & 0x7f) << shift);
shift += 7;
if ((0x80 & byte) == 0) {
if (shift < 64 && (byte & 0x40) != 0) {
return result | BigInt(~0 << shift);
}
return result;
}
}
*/
}
readBytes(count) {
const bytes = []
for (let i = 0; i < count; i++) {
bytes.push(this.readU8());
}
return bytes;
}
readBytesToEnd() {
const bytes = []
while (!this.finished) {
bytes.push(this.readU8());
}
return bytes;
}
}
class BufferBuilder {
constructor() {
this._buffer = new Uint8Array(100);
this._pos = 0;
}
get length() {
return this._pos;
}
_resizeBuffer() {
if (this._pos >= this._buffer.length - 1) {
let tmp = this._buffer;
this._buffer = new Uint8Array(this._buffer.length * 2);
this._buffer.set(tmp);
}
}
pushU8(val) {
this._resizeBuffer();
this._buffer[this._pos++] = val;
}
pushU32(val) {
this.pushU8(val & 0x000000ff);
this.pushU8((val & 0x0000ff00) >> 8);
this.pushU8((val & 0x00ff0000) >> 16);
this.pushU8((val & 0xff000000) >> 24);
}
pushVarU32(val) {
let byte = 0;
let value = val;
if (val == 0) {
this.pushU8(0);
return;
}
while (value > 0) {
byte = value & 0x7F;
value >>= 7;
if (value !== 0) {
byte |= 0x80;
}
this.pushU8(byte);
}
}
pushVarS32(val) {
let byte = 0;
let value = val;
while (1) {
byte = value & 0x7F;
value >>= 7;
if ((value === -1 && (byte & 0x40)) || (value === 0 && !(byte & 0x40))) {
this.pushU8(byte);
break;
}
this.pushU8(byte | 0x80);
}
}
pushVarS64(val) {
let byte = 0;
let value = val;
while (1) {
byte = value & 0x7Fn;
value >>= 7n;
if ((value === -1n && (byte & 0x40n)) || (value === 0n && !(byte & 0x40n))) {
this.pushU8(Number(byte));
break;
}
this.pushU8(Number(byte | 0x80n));
}
}
pushBytes(bytes) {
for (let i = 0; i < bytes.length; i++) {
this.pushU8(bytes[i]);
}
}
build() {
return this._buffer.slice(0, this._pos);
}
}
class WasmIndex {
constructor(index) {
this._index = index;
}
}
class SectionPatcher {
constructor(oldSection, editDataList, addDataList, options) {
this._oldSection = oldSection;
this._newSection = new BufferBuilder();
this._oldDataCount = 0;
this._newDataCount = 0;
this._editDataList = editDataList instanceof Array ? editDataList : [];
this._addDataList = addDataList instanceof Array ? addDataList : [];
this._options = options;
}
_setOldDataCount() {
this._oldDataCount = this._oldSection.readVarU32();
}
_setNewDataCount() {
this._newDataCount = this._oldDataCount + this._addDataList.length;
this._newSection.pushVarU32(this._newDataCount);
}
_extractIndex(index) {
if (index instanceof WasmIndex) {
return index._index;
}
return index;
}
_readInstruction(reader, builder, importCounts) {
const opcode = reader.readU8();
builder.pushU8(opcode);
switch (opcode) {
case OP.block_raw:
case OP.loop_raw:
case OP.if_raw:
case OP.memory.size:
case OP.memory.grow:
builder.pushU8(reader.readU8());
break;
case OP.br:
case OP.br_if:
case OP.local.get:
case OP.local.set:
case OP.local.tee:
builder.pushVarU32(reader.readVarU32());
break;
case OP.i32.const:
builder.pushVarS32(reader.readVarS32());
break;
case OP.i64.const:
// i64 bitwise nazo akita
/*
const val = reader.readVarS64();
builder.pushVarS64(val);
*/
const p = reader._pos;
reader.readVarS64();
const p2 = reader._pos;
for (let i = p; i < p2; ++i) {
builder.pushU8(reader._buffer[i]);
}
break;
case OP.global.get:
case OP.global.set: {
const oldIndex = reader.readVarU32();
if (oldIndex > importCounts.globalCount - importCounts.newGlobalCount - 1) {
const newIndex = oldIndex + importCounts.newGlobalCount;
builder.pushVarU32(newIndex);
}
else {
builder.pushVarU32(oldIndex);
}
} break;
case OP.f32.const:
builder.pushBytes(reader.readBytes(4));
break;
case OP.f64.const:
builder.pushBytes(reader.readBytes(8));
break;
case OP.i32.load:
case OP.i64.load:
case OP.f32.load:
case OP.f64.load:
case OP.i32.load8_s:
case OP.i32.load8_u:
case OP.i32.load16_s:
case OP.i32.load16_u:
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.i32.store:
case OP.i64.store:
case OP.f32.store:
case OP.f64.store:
case OP.i32.store8:
case OP.i32.store16:
case OP.i64.store8:
case OP.i64.store16:
case OP.i64.store32:
builder.pushVarU32(reader.readVarU32());
builder.pushVarU32(reader.readVarU32());
break;
case OP.br_table:
const tableCount = reader.readVarU32();
builder.pushVarU32(tableCount);
for (let i = 0; i < tableCount; ++i) {
const brIndex = reader.readVarU32();
builder.pushVarU32(brIndex);
}
const defaultIndex = reader.readVarU32();
builder.pushVarU32(defaultIndex);
break;
case OP.call: {
const oldIndex = reader.readVarU32();
if (oldIndex > importCounts.functionCount - importCounts.newFunctionCount - 1) {
const newIndex = oldIndex + importCounts.newFunctionCount;
builder.pushVarU32(newIndex);
}
else {
builder.pushVarU32(oldIndex);
}
} break;
case OP.call_indirect:
builder.pushVarU32(reader.readVarU32());
builder.pushU8(reader.readU8());
break;
case OP.other_instruction: {
const instruction = reader.readU8();
switch (instruction) {
case OP.others.memory_init:
case OP.others.table_init:
builder.pushVarU32(reader.readVarU32());
builder.pushU8(reader.readU8());
break;
case OP.others.data_drop:
case OP.others.elem_drop:
builder.pushVarU32(reader.readVarU32());
break;
case OP.others.memory_copy:
case OP.others.table_copy:
builder.pushU8(reader.readU8());
builder.pushU8(reader.readU8());
break;
case OP.others.memory_fill:
builder.pushU8(reader.readU8());
break;
}
} break;
case 0xFE: { // atomic
const instruction = reader.readU8();
builder.pushU8(instruction);
builder.pushVarU32(reader.readVarU32());
builder.pushVarU32(reader.readVarU32());
} break;
}
}
_readInstantiationTimeInitializer() {
let bytes = new BufferBuilder();
let byte;
while ((byte = this._oldSection.readU8()) !== OP.end) {
bytes.pushU8(byte);
switch (byte) {
case OP.i32.const:
case OP.i64.const: {
const value = this._oldSection.readVarU32();
bytes.pushVarU32(value);
break;
}
case OP.f32.const: {
const valueBytes = this._oldSection.readBytes(4);
bytes.pushVarU32(valueBytes);
break;
}
case OP.f64.const: {
const valueBytes = this._oldSection.readBytes(8);
bytes.pushVarU32(valueBytes);
break;
}
case OP.global.get: {
const index = this._oldSection.readVarU32();
bytes.pushVarU32(index);
break;
}
default:
throw new Error("Invalid Instantiation-Time Initializer");
}
}
bytes.pushU8(OP.end);
return bytes.build();
}
}
class TypeSectionPatcher extends SectionPatcher {
patch() {
this._setOldDataCount();
this._setNewDataCount();
let typeIndex = 0;
for (let i = 0; i < this._oldDataCount; ++i, ++typeIndex) {
let editData;
if (this._editDataList.length !== 0) {
editData = this._editDataList.find(editData => editData.index === typeIndex);
}
let form = this._oldSection.readU8();
let paramCount = this._oldSection.readVarU32();
let params = [];
for (let j = 0; j < paramCount; ++j) {
const param = this._oldSection.readU8();
params.push(param);
}
let returnCount = this._oldSection.readU8();
let returnType;
if (typeof editData !== 'undefined') {
form = editData.form;
paramCount = editData.params.length;
params = editData.params;
returnCount = editData.returnCount;
returnType = editData.returnType;
}
this._newSection.pushU8(form);
this._newSection.pushVarU32(paramCount);
this._newSection.pushBytes(params);
this._newSection.pushU8(returnCount);
if (returnCount === 1) {
returnType = this._oldSection.readU8();
this._newSection.pushU8(returnType);
}
}
for (let i = 0; i < this._addDataList.length; ++i, ++typeIndex) {
const addData = this._addDataList[i];
const form = addData.form;
const paramCount = addData.params.length;
const params = addData.params;
const returnCount = addData.returnCount;
this._newSection.pushU8(form);
this._newSection.pushVarU32(paramCount);
this._newSection.pushBytes(params);
this._newSection.pushU8(returnCount);
if (returnCount === 1) {
const returnType = addData.returnType;
this._newSection.pushU8(returnType);
}
addData.saveTypeIndex._index = typeIndex;
}
return this._newSection;
}
}
class ImportSectionPatcher extends SectionPatcher {
patch() {
this._setOldDataCount();
this._setNewDataCount();
const saveImportCounts = this._options['saveImportCounts'];
for (let i = 0, importIndex = 0; i < this._oldDataCount; ++i, ++importIndex) {
let editData;
if (this._editDataList.length !== 0) {
editData = this._editDataList.find(editData => editData.index === importIndex);
}
let moduleNameLen = this._oldSection.readVarU32();
let moduleName = this._oldSection.readBytes(moduleNameLen);
let exportNameLen = this._oldSection.readVarU32();
let exportName = this._oldSection.readBytes(exportNameLen);
let kind = this._oldSection.readU8();
if (typeof editData !== 'undefined') {
moduleNameLen = editData.moduleName.length;
moduleName = editData.moduleName;
exportNameLen = editData.exportNameLen;
exportName = editData.exportName;
}
this._newSection.pushVarU32(moduleNameLen);
this._newSection.pushBytes(moduleName);
this._newSection.pushVarU32(exportNameLen);
this._newSection.pushBytes(exportName);
this._newSection.pushU8(kind);
switch (kind) {
case ExternalKind.Function:
saveImportCounts.functionCount++;
let typeIndex = this._oldSection.readVarU32();
if (typeof editData !== 'undefined') {
typeIndex = editData.typeIndex;
}
this._newSection.pushVarU32(typeIndex);
break;
case ExternalKind.Table:
let elementType = this._oldSection.readU8();
let resizableFlags = this._oldSection.readU8();
let resizableMinimum = this._oldSection.readVarU32();
if (typeof editData !== 'undefined') {
elementType = editData.elementType;
resizableFlags = editData.resizableFlags;
resizableMinimum = editData.resizableMinimum;
}
this._newSection.pushU8(elementType);
this._newSection.pushU8(resizableFlags);
this._newSection.pushVarU32(resizableMinimum);
if (resizableFlags) {
let resizableMaximum = this._oldSection.readVarU32();
if (typeof editData !== 'undefined') {
resizableMaximum = editData.resizableMaximum;
}
this._newSection.pushVarU32(resizableMaximum);
}
break;
case ExternalKind.Memory:
let limitsFlags = this._oldSection.readU8();
let limitsMinimum = this._oldSection.readVarU32();
this._newSection.pushU8(limitsFlags);
this._newSection.pushVarU32(limitsMinimum);
if (limitsFlags) {
let limitsMaximum = this._oldSection.readVarU32();
if (typeof editData !== 'undefined') {
limitsMaximum = editData.limitsMaximum;
}
this._newSection.pushVarU32(limitsMaximum);
}
break;
case ExternalKind.Global:
saveImportCounts.globalCount++;
let variableType = this._oldSection.readU8();
let variableMutability = this._oldSection.readU8();
this._newSection.pushU8(variableType);
this._newSection.pushU8(variableMutability);
break;
default:
throw new Error("Invalid Import kind");
}
}
this._addDataList.forEach(addData => {
const moduleNameLen = addData.moduleName.length;
const moduleName = addData.moduleName;
const exportNameLen = addData.exportName.length;
const exportName = addData.exportName;
const kind = addData.kind;
this._newSection.pushVarU32(moduleNameLen);
this._newSection.pushBytes(moduleName);
this._newSection.pushVarU32(exportNameLen);
this._newSection.pushBytes(exportName);
this._newSection.pushU8(kind);
switch (kind) {
case ExternalKind.Function:
saveImportCounts.functionCount++;
saveImportCounts.newFunctionCount++;
const typeIndex = this._extractIndex(addData.typeIndex);
this._newSection.pushVarU32(typeIndex);
addData.saveFunctionIndex._index = saveImportCounts.functionCount - 1;
break;
case ExternalKind.Table:
const elementType = addData.elementType;
const resizableFlags = addData.resizableFlags;
const resizableMinimum = addData.resizableMinimum;
this._newSection.pushU8(elementType);
this._newSection.pushU8(resizableFlags);
this._newSection.pushVarU32(resizableMinimum);
if (resizableFlags) {
const resizableMaximum = addData.resizableMaximum;
this._newSection.pushVarU32(resizableMaximum);
}
break;
case ExternalKind.Memory:
const limitsFlags = addData.limitsFlags;
const limitsMinimum = addData.limitsMinimum;
this._newSection.pushU8(limitsFlags);
this._newSection.pushVarU32(limitsMinimum);
if (limitsFlags) {
const limitsMaximum = addData.limitsMaximum;
this._newSection.pushVarU32(limitsMaximum);
}
break;
case ExternalKind.Global:
saveImportCounts.globalCount++;
saveImportCounts.newGlobalCount++;
const variableType = addData.variableType;
const variableMutability = addData.variableMutability;
this._newSection.pushU8(variableType);
this._newSection.pushU8(variableMutability);
addData.saveGlobalIndex._index = saveImportCounts.globalCount - 1;
break;
default:
throw new Error("Invalid Import kind");
}
});
return this._newSection;
}
}
class FunctionSectionPatcher extends SectionPatcher {
patch() {
this._setOldDataCount();
this._setNewDataCount();
let funcIndex = this._options['importFunctionCount'];
for (let i = 0; i < this._oldDataCount; ++i, ++funcIndex) {
let typeIndex = this._oldSection.readVarU32();
if (this._editDataList.length !== 0) {
const editData = this._editDataList.find(editData => editData.index === funcIndex);
if (typeof editData !== 'undefined') {
typeIndex = editData.typeIndex;
}
}
this._newSection.pushVarU32(typeIndex);
}
for (let i = 0; i < this._addDataList.length; ++i, ++funcIndex) {
const addData = this._addDataList[i];
const typeIndex = addData.typeIndex;
this._newSection.pushVarU32(typeIndex);
addData.saveFunctionIndex._index = funcIndex;
}
return this._newSection;
}
}
class GlobalSectionPatcher extends SectionPatcher {
patch() {
this._setOldDataCount();
this._setNewDataCount();
let globalIndex = this._options['importGlobalCount'];
for (let i = 0; i < this._oldDataCount; ++i, ++globalIndex) {
let editData;
if (this._editDataList.length !== 0) {
editData = this._editDataList.find(editData => editData.index === globalIndex);
}
let contentType = this._oldSection.readU8();
let mutability = this._oldSection.readU8();
let initializer = this._readInstantiationTimeInitializer();
if (typeof editData !== 'undefined') {
contentType = editData.contentType;
mutability = editData.mutability;
initializer = editData.initializer;
}
this._newSection.pushU8(contentType);
this._newSection.pushU8(mutability);
this._newSection.pushBytes(initializer);
}
for (let i = 0; i < this._addDataList.length; ++i, ++globalIndex) {
const addData = this._addDataList[i];
const contentType = addData.contentType;
const mutability = addData.mutability;
const initializer = addData.initializer;
this._newSection.pushU8(contentType);
this._newSection.pushU8(mutability);
this._newSection.pushBytes(initializer);
addData.saveGlobalIndex._index = globalIndex;
}
return this._newSection;
}
}
class ExportSectionPatcher extends SectionPatcher {
patch() {
this._setOldDataCount();
this._setNewDataCount();
let exportIndex = 0;
const newImportFunctionCount = this._options['newImportFunctionCount'];
const newImportGlobalCount = this._options['newImportGlobalCount'];
for (let i = 0; i < this._oldDataCount; ++i, ++exportIndex) {
let editData;
if (this._editDataList.length !== 0) {
editData = this._editDataList.find(editData => editData.index === exportIndex);
}
let fieldNameLen = this._oldSection.readVarU32();
let fieldName = this._oldSection.readBytes(fieldNameLen);
let kind = this._oldSection.readU8();
let ownIndex = this._oldSection.readVarU32();
if (typeof editData !== 'undefined') {
fieldNameLen = editData.fieldName.length;
fieldName = editData.fieldName;
kind = editData.kind;
ownIndex = this._extractIndex(editData.ownIndex);
}
else {
switch (kind) {
case ExternalKind.Function:
ownIndex = ownIndex + newImportFunctionCount;
break;
case ExternalKind.Global:
ownIndex = ownIndex + newImportGlobalCount;
}
}
this._newSection.pushVarU32(fieldNameLen);
this._newSection.pushBytes(fieldName);
this._newSection.pushU8(kind);
this._newSection.pushVarU32(ownIndex);
}
for (let i = 0; i < this._addDataList.length; ++i, ++exportIndex) {
const addData = this._addDataList[i];
const fieldNameLen = addData.fieldName.length;
const fieldName = addData.fieldName;
const kind = addData.kind;
const ownIndex = this._extractIndex(addData.ownIndex);
this._newSection.pushVarU32(fieldNameLen);
this._newSection.pushBytes(fieldName);
this._newSection.pushU8(kind);
this._newSection.pushVarU32(ownIndex);
addData.saveExportIndex._index = exportIndex;
}
return this._newSection;
}
}
class StartSectionPatcher extends SectionPatcher {
patch() {
const importFunctionCount = this._options['importFuctionCount'];
const newImportFunctionCount = this._options['newImportFunctionCount'];
const oldDataBodyLen = this._oldSection.readVarU32();
const oldStart = this._oldSection.readVarU32();
let newStart = oldStart;
if (oldStart > importFunctionCount - newImportFunctionCount - 1) {
newStart = oldStart + newImportFunctionCount;
}
const dataBody = VAR.u32(newStart);
this._newSection.pushVarU32(dataBody.length);
this._newSection.pushBytes(dataBody);
return this._newSection;
}
}
class ElementSectionPatcher extends SectionPatcher {
patch() {
const importFunctionCount = this._options['importFunctionCount'];
const newImportFunctionCount = this._options['newImportFunctionCount'];
this._setOldDataCount();
this._setNewDataCount();
for (let i = 0; i < this._oldDataCount; ++i) {
const tableIndex = this._oldSection.readVarU32();
const offset = this._readInstantiationTimeInitializer();
const elementCount = this._oldSection.readVarU32();
this._newSection.pushVarU32(tableIndex);
this._newSection.pushBytes(offset);
this._newSection.pushVarU32(elementCount);
for (let i = 0; i < elementCount; ++i) {
const oldIndex = this._oldSection.readVarU32();
let newIndex = oldIndex;
if (oldIndex > importFunctionCount - newImportFunctionCount - 1) {
newIndex = oldIndex + newImportFunctionCount;
}
this._newSection.pushVarU32(newIndex);
}
}
return this._newSection;
}
}
const ScanMode = Object.freeze({
Insert: 0,
ReplaceStart: 1,
ReplaceEnd: 2,
Skip: 3,
Value: 4
});
class CodeSectionPatcher extends SectionPatcher {
_expandCode(code) {
const newCode = [];
code.forEach(item => {
if (item instanceof WasmIndex) {
newCode.push(...VAR.u32(item._index));
}
else {
newCode.push(item);
}
})
return newCode;
}
_aobScan(data, scan) {
let scanIndex = 0;
for (let i = 0; i < data.length;) {
const val = data[i];
const scanNow = scan[scanIndex];
const scanMode = scanNow.mode;
let scanVal;
switch (scanMode) {
case ScanMode.Insert:
case ScanMode.ReplaceStart:
case ScanMode.ReplaceEnd:
scanIndex++;
break;
case ScanMode.Skip:
scanIndex++;
i++;
break;
case ScanMode.Value:
scanVal = scanNow.value;
if (val === scanVal) {
scanIndex++;
}
else {
scanIndex = 0;
}
i++;
break;
}
if (scanIndex === scan.length) {
return i - 1;
}
}
return -1;
}
_aobPatch(codeBuffer, codeIndex, aobPatchList) {
for (let i = 0; i < aobPatchList.length;) {
const aobPatch = aobPatchList[i];
const scan = aobPatch.scan;
const matchIndex = this._aobScan(codeBuffer, scan);
if (matchIndex === -1) {
++i;
continue;
}
const oldCode = new BufferReader(codeBuffer);
const newCode = new BufferBuilder();
const totalMatches = aobPatch.totalMatches;
const matchEndIndex = matchIndex;
const matchFirstIndex = matchEndIndex - totalMatches + 1;
const beforeMatchBytes = oldCode.readBytes(matchFirstIndex);
const matchBytes = oldCode.readBytes(matchEndIndex - matchFirstIndex + 1);
const afterMatchBytes = oldCode.readBytesToEnd();
const matchBytesReader = new BufferReader(matchBytes);
const codes = aobPatch.codes;
let codesIndex = 0;
let startReplace = false;
newCode.pushBytes(beforeMatchBytes);
scan.forEach(now => {
switch (now.mode) {
case ScanMode.Skip:
case ScanMode.Value: {
const val = matchBytesReader.readU8();
if (!startReplace) {
newCode.pushU8(val);
}
} break;
case ScanMode.Insert:
newCode.pushBytes(this._expandCode(codes[codesIndex++]));
break;
case ScanMode.ReplaceStart:
newCode.pushBytes(this._expandCode(codes[codesIndex++]));
startReplace = true;
break;
case ScanMode.ReplaceEnd:
startReplace = false;
break;
}
});
newCode.pushBytes(afterMatchBytes);
codeBuffer = newCode.build();
aobPatch.onsuccess(codeIndex);
aobPatchList.splice(i, 1);
}
return codeBuffer;
}
_updateCodeInstructions(codeBuffer, importCounts) {
const code = new BufferReader(codeBuffer);
const newCode = new BufferBuilder();
while (!code.finished) {
const opcode = code.readU8();
newCode.pushU8(opcode);
switch (opcode) {
case OP.block_raw:
case OP.loop_raw:
case OP.if_raw:
case OP.memory.size:
case OP.memory.grow:
newCode.pushU8(code.readU8());
break;
case OP.br:
case OP.br_if:
case OP.local.get:
case OP.local.set:
case OP.local.tee:
newCode.pushVarU32(code.readVarU32());
break;
case OP.i32.const:
newCode.pushVarS32(code.readVarS32());
break;
case OP.i64.const:
// i64 bitwise nazo akita
/*
const val = code.readVarS64();
newCode.pushVarS64(val);
*/
const p = code._pos;
code.readVarS64();
const p2 = code._pos;
for (let i = p; i < p2; ++i) {
newCode.pushU8(code._buffer[i]);
}
break;
case OP.global.get:
case OP.global.set: {
const oldIndex = code.readVarU32();
if (oldIndex > importCounts.globalCount - importCounts.newGlobalCount - 1) {
const newIndex = oldIndex + importCounts.newGlobalCount;
newCode.pushVarU32(newIndex);
}
else {
newCode.pushVarU32(oldIndex);
}
} break;
case OP.f32.const:
newCode.pushBytes(code.readBytes(4));
break;
case OP.f64.const:
newCode.pushBytes(code.readBytes(8));
break;
case OP.i32.load:
case OP.i64.load:
case OP.f32.load:
case OP.f64.load:
case OP.i32.load8_s:
case OP.i32.load8_u:
case OP.i32.load16_s:
case OP.i32.load16_u:
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.i32.store:
case OP.i64.store:
case OP.f32.store:
case OP.f64.store:
case OP.i32.store8:
case OP.i32.store16:
case OP.i64.store8:
case OP.i64.store16:
case OP.i64.store32:
newCode.pushVarU32(code.readVarU32());
newCode.pushVarU32(code.readVarU32());
break;
case OP.br_table:
const tableCount = code.readVarU32();
newCode.pushVarU32(tableCount);
for (let i = 0; i < tableCount; ++i) {
const brIndex = code.readVarU32();
newCode.pushVarU32(brIndex);
}
const defaultIndex = code.readVarU32();
newCode.pushVarU32(defaultIndex);
break;
case OP.call: {
const oldIndex = code.readVarU32();
if (oldIndex > importCounts.functionCount - importCounts.newFunctionCount - 1) {
const newIndex = oldIndex + importCounts.newFunctionCount;
newCode.pushVarU32(newIndex);
}
else {
newCode.pushVarU32(oldIndex);
}
} break;
case OP.call_indirect:
newCode.pushVarU32(code.readVarU32());
newCode.pushU8(code.readU8());
break;
case OP.other_instruction: {
const instruction = code.readU8();
switch (instruction) {
case OP.others.memory_init:
case OP.others.table_init:
newCode.pushVarU32(code.readVarU32());
newCode.pushU8(code.readU8());
break;
case OP.others.data_drop:
case OP.others.elem_drop:
newCode.pushVarU32(code.readVarU32());
break;
case OP.others.memory_copy:
case OP.others.table_copy:
newCode.pushU8(code.readU8());
newCode.pushU8(code.readU8());
break;
case OP.others.memory_fill:
newCode.pushU8(code.readU8());
break;
}
} break;
case 0xFE: { // atomic
const instruction = code.readU8();
newCode.pushU8(instruction);
newCode.pushVarU32(code.readVarU32());
newCode.pushVarU32(code.readVarU32());
} break;
}
}
return newCode.build();
}
patch() {
this._setOldDataCount();
this._setNewDataCount();
const importCounts = this._options['importCounts'];
let codeIndex = importCounts.functionCount;
const codePatchList = this._options['codePatchList'];
const prefixEmbedWasmCodeList = this._options['prefixEmbedWasmCodeList'];
for (let i = 0; i < this._oldDataCount; ++i, ++codeIndex) {
let editData;
let prefixEmbed;
if (this._editDataList.length !== 0) {
editData = this._editDataList.find(editData => editData.index === codeIndex - importCounts.newFunctionCount);
}
if (prefixEmbedWasmCodeList.length !== 0) {
prefixEmbed = prefixEmbedWasmCodeList.find(prefixEmbed => prefixEmbed.index === codeIndex - importCounts.newFunctionCount);
}
const funcBytesLen = this._oldSection.readVarU32();
const funcBytes = this._oldSection.readBytes(funcBytesLen);
const func = new BufferReader(funcBytes);
let localCount = func.readVarU32();
const localsBuilder = new BufferBuilder();
for (let i = 0; i < localCount; ++i) {
const count = func.readVarU32();
const varsType = func.readU8();
localsBuilder.pushVarU32(count);
localsBuilder.pushVarU32(varsType);
}
let locals = localsBuilder.build();
let code = func.readBytesToEnd();
if (typeof editData !== 'undefined') {
localCount = editData.locals.length;
locals = editData.locals;
code = editData.code;
}
if (codePatchList.length > 0) {
code = this._aobPatch(code, codeIndex, codePatchList);
}
if (importCounts.newFunctionCount > 0 || importCounts.newGlobalCount > 0) {
code = this._updateCodeInstructions(code, importCounts);
}
const newFunc = new BufferBuilder();
if (localCount === 0) {
newFunc.pushVarU32(0);
}
else {
newFunc.pushVarU32(localCount);
newFunc.pushBytes(locals);
}
if (typeof prefixEmbed !== 'undefined') {
newFunc.pushBytes(this._expandCode(prefixEmbed.code));
}
newFunc.pushBytes(code);
this._newSection.pushVarU32(newFunc.length);
this._newSection.pushBytes(newFunc.build());
}
return this._newSection;
}
}
class WasmPatcher {
constructor(wasmBuffer, importObject) {
this._oldWasm = new BufferReader(new Uint8Array(wasmBuffer));
this._newWasm = new BufferBuilder();
this._importObject = importObject;
this._importFunctionCount = 0;
this._importGlobalCount = 0;
this._newImportFunctionCount = 0;
this._newImportGlobalCount = 0;
this._editSection = {};
this._addSection = {};
this._options = {};
}
_pushEditData(sectionID, data) {
if (!(this._editSection[sectionID] instanceof Array)) {
this._editSection[sectionID] = [];
}
this._editSection[sectionID].push(data);
}
_pushAddData(sectionID, data) {
if (!(this._addSection[sectionID] instanceof Array)) {
this._addSection[sectionID] = [];
}
this._addSection[sectionID].push(data);
}
_pushOptionData(optionName, data) {
if (!(this._options[optionName] instanceof Array)) {
this._options[optionName] = [];
}
this._options[optionName].push(data);
}
_patchSections() {
while (!this._oldWasm.finished) {
const sectionID = this._oldWasm.readU8();
this._newWasm.pushU8(sectionID);
let oldSection = null;
let newSection = null;
if (sectionID >= Section.Custom && sectionID <= Section.DataCount) {
const sectionLen = this._oldWasm.readVarU32();
const sectionBody = this._oldWasm.readBytes(sectionLen);
oldSection = new BufferReader(sectionBody);
}
else {
throw new Error("Invalid section");
}
const editDataList = this._editSection[sectionID];
const addDataList = this._addSection[sectionID];
let options = null;
switch (sectionID) {
case Section.Type:
newSection = (new TypeSectionPatcher(
oldSection,
editDataList,
addDataList,
)).patch();
break;
case Section.Import:
const saveImportCounts = {
functionCount: 0,
globalCount: 0,
newFunctionCount: 0,
newGlobalCount: 0,
};
options = { saveImportCounts };
newSection = (new ImportSectionPatcher(
oldSection,
editDataList,
addDataList,
options
)).patch();
this._importFunctionCount = saveImportCounts.functionCount;
this._importGlobalCount = saveImportCounts.globalCount;
this._newImportFunctionCount = saveImportCounts.newFunctionCount;
this._newImportGlobalCount = saveImportCounts.newGlobalCount;
break;
case Section.Function:
options = {
importFunctionCount: this._importFunctionCount
};
newSection = (new FunctionSectionPatcher(
oldSection,
editDataList,
addDataList,
options
)).patch();
break;
case Section.Global:
options = {
importGlobalCount: this._importGlobalCount,
newImportGlobalCount: this._newImportGlobalCount,
};
newSection = (new GlobalSectionPatcher(
oldSection,
editDataList,
addDataList,
options
)).patch();
break;
case Section.Export:
options = {
newImportFunctionCount: this._newImportFunctionCount,
newImportGlobalCount: this._newImportGlobalCount,
};
newSection = (new ExportSectionPatcher(
oldSection,
editDataList,
addDataList,
options
)).patch();
break;
case Section.Start:
options = {
importFunctionCount: this._importFunctionCount,
newImportFunctionCount: this._newImportFunctionCount,
};
newSection = (new StartSectionPatcher(
oldSection,
editDataList,
addDataList,
options
)).patch();
break;
case Section.Element:
options = {
importFunctionCount: this._importFunctionCount,
newImportFunctionCount: this._newImportFunctionCount,
};
newSection = (new ElementSectionPatcher(
oldSection,
editDataList,
addDataList,
options
)).patch();
break;
case Section.Code:
const importCounts = {
functionCount: this._importFunctionCount,
globalCount: this._importGlobalCount,
newFunctionCount: this._newImportFunctionCount,
newGlobalCount: this._newImportGlobalCount
};
options = {
importCounts,
codePatchList: 'aobPatch' in this._options ? this._options['aobPatch'] : [],
prefixEmbedWasmCodeList: 'prefixEmbedWasmCode' in this._options ? this._options['prefixEmbedWasmCode'] : [],
};
newSection = (new CodeSectionPatcher(
oldSection,
editDataList,
addDataList,
options
)).patch();
break;
default:
newSection = new BufferBuilder();
newSection.pushBytes(oldSection.readBytesToEnd());
break;
}
this._newWasm.pushVarU32(newSection.length);
this._newWasm.pushBytes(newSection.build());
}
}
_string2type(string) {
switch (string) {
case 's32':
case 'u32':
case 'i32': return ValueType.i32;
case 's64':
case 'u64':
case 'i64': return ValueType.i64;
case 'f32': return ValueType.f32;
case 'f64': return ValueType.f64;
default: throw new Error("Invalid string");
}
}
_createInitializer(typeStr, value) {
switch (typeStr) {
case 'i32':
case 's32': return [OP.i32.const, ...VAR.s32(value), OP.end];
case 'u32': return [OP.i32.const, ...VAR.u32(value), OP.end];
case 'i64':
case 's64': return [OP.i64.const, ...VAR.s32(value), OP.end];
case 'u64': return [OP.i64.const, ...VAR.u32(value), OP.end];
case 'f32': return [OP.f32.const, ...VAR.f32(value), OP.end];
case 'f64': return [OP.f64.const, ...VAR.f64(value), OP.end];
default: throw new Error("Invalid type string");
}
}
_convertTypeStrArray(array) {
return array.map(item => {
switch (typeof item) {
case "string": return this._string2type(item);
default: throw new Error("Invalid TypeStr");
}
});
}
_string2bytes(string) {
let bytes = [];
for (let i = 0; i < string.length; i++) {
let code = string.charCodeAt(i);
bytes.push(code);
}
return bytes;
}
_flattenCode(code) {
const newCode = [];
code.forEach(elm => {
if (elm instanceof Array) {
newCode.push(...elm);
}
else {
newCode.push(elm);
}
});
return newCode;
}
_isInvalidCode(code) {
if (!(code instanceof Array)) {
return true;
}
const callback = elm => {
if (elm instanceof Array) {
if (elm.findIndex(e => typeof e !== 'number') !== -1) {
return true;
}
}
else if (typeof elm !== 'number' && !(elm instanceof WasmIndex)) {
return true;
}
return false;
};
if (code.findIndex(callback) !== -1) {
return true;
}
return false;
}
_parseScanStr(scanStr) {
const scanAry = scanStr.split(' ');
let previousBracket = '';
let previousScan = '';
let totalMatches = 0;
let parsedScan = [];
const throwErr = function () {
throw new Error('Invalid options.scan');
};
scanAry.forEach(scan => {
switch (scan) {
case '?':
if (previousBracket === '[') {
//throwErr();
}
parsedScan.push({
mode: ScanMode.Skip
});
totalMatches++;
break;
case '[':
if (previousBracket === '[') {
throwErr();
}
parsedScan.push({
mode: ScanMode.ReplaceStart
});
previousBracket = '[';
break;
case ']':
if (previousBracket === ']' || previousScan === '[') {
throwErr();
}
parsedScan.push({
mode: ScanMode.ReplaceEnd
});
previousBracket = ']';
break;
case '|':
if (previousBracket === '[' || previousScan === '|') {
throwErr();
}
parsedScan.push({
mode: ScanMode.Insert
});
break;
default: {
let parsedVal = parseInt(scan, 16);
if (isNaN(parsedVal)) {
throwErr();
}
parsedScan.push({
mode: ScanMode.Value,
value: parsedVal
});
totalMatches++;
} break;
}
previousScan = scan;
});
return {
scan: parsedScan,
totalMatches: totalMatches
};
}
_parseLineStr(lineStr) {
const lines = lineStr.replaceAll(' ', '').split(',');
const newLines = lines.map(line => {
const result = line.match(/(?<startIndex>\d{1,})-(?<endIndex>\d{1,})/);
const startIndex = result?.groups?.startIndex;
const endIndex = result?.groups?.endIndex;
if (result === null || startIndex >= endIndex) {
throw new Error("Invalid options.line");
}
const newLine = {
startIndex,
endIndex
};
return newLine;
});
return newLines;
}
patch() {
const magic = this._oldWasm.readU32();
const version = this._oldWasm.readU32();
if (magic !== 0x6D736100) {
throw new Error("Invalid magic");
}
this._newWasm.pushU32(magic);
this._newWasm.pushU32(version);
this._patchSections();
return this._newWasm.build();
}
addType(options) {
const form = options?.form; // 現段階でformの値は一つだけだから省略可
let params = options?.params;
const return_ = options?.return;
if (!(params instanceof Array)) {
params = [];
//throw new Error("Invalid options.params");
}
const addData = {};
if (typeof form === 'number') {
addData.form = form;
}
else {
addData.form = SignatureType.func;
}
addData.params = this._convertTypeStrArray(params);
if (typeof return_ === 'string') {
addData.returnCount = 1;
addData.returnType = this._string2type(return_);
}
else {
addData.returnCount = 0;
}
const typeIndex = new WasmIndex();
addData.saveTypeIndex = typeIndex;
this._pushAddData(Section.Type, addData);
return typeIndex;
}
editType(index, options) {
if (typeof index !== 'number') {
throw new Error("Invalid Index");
}
const form = options?.form; // 現段階でformの値は一つだけだから省略可
const params = options?.params;
const return_ = options?.return;
if (!(params instanceof Array)) {
throw new Error("Invalid options.params");
}
const editData = {};
editData.index = index;
if (typeof form !== 'number') {
editData.form = form;
}
else {
editData.form = SignatureType.func;
}
editData.params = this._convertTypeStrArray(params);
if (typeof return_ === 'string') {
editData.returnCount = 1;
editData.returnType = this._string2type(return_);
}
else {
editData.returnCount = 0;
}
this._pushEditData(Section.Type, editData);
}
addImport(options) {
const moduleName = options?.moduleName;
const exportName = options?.exportName;
const kind = options?.kind;
if (typeof moduleName !== 'string') {
throw new Error("Invalid options.moduleName");
}
if (typeof exportName !== 'string') {
throw new Error("Invalid options.exportName");
}
const addData = {};
addData.moduleName = this._string2bytes(moduleName);
addData.exportName = this._string2bytes(exportName);
addData.kind = kind;
switch (kind) {
case ExternalKind.Function:
const typeIndex = options?.typeIndex;
if (typeof typeIndex !== 'number' && !(typeIndex instanceof WasmIndex)) {
throw new Error("Invalid options.typeIndex");
}
addData.typeIndex = typeIndex;
const funcIndex = new WasmIndex();
addData.saveFunctionIndex = funcIndex;
this._pushAddData(Section.Import, addData);
return funcIndex;
/*
case ExternalKind.Table:
const elementType = options?.elementType;
const resizableFlags = options?.resizableFlags;
const resizableMinimum = options?.resizableMinimum;
if()
*/
default:
throw new Error("This kind value is not supported");
}
}
editImport(index, options) {
if (typeof index !== 'number') {
throw new Error("Invalid Index");
}
const moduleName = options?.moduleName;
const exportName = options?.exportName;
const kind = options?.kind;
if (typeof moduleName !== 'string') {
throw new Error("Invalid options.moduleName");
}
if (typeof exportName !== 'string') {
throw new Error("Invalid options.exportName");
}
const editData = {};
editData.index = index;
editData.moduleName = this._string2bytes(moduleName);
editData.exportName = this._string2bytes(exportName);
switch (kind) {
case ExternalKind.Function:
const typeIndex = options?.typeIndex;
if (typeof typeIndex !== 'number' && !(typeIndex instanceof WasmIndex)) {
throw new Error("Invalid options.typeIndex");
}
editData.typeIndex = typeIndex;
this._pushEditData(Section.Import, editData);
return funcIndex;
default:
throw new Error("This kind value is not supported");
}
}
addFunction(options) {
const type = options?.type;
if (!(type instanceof WasmIndex) || typeof type !== 'number') {
throw new Error("Invalid options.type");
}
const addData = {};
addData.typeIndex = type;
const funcIndex = WasmIndex();
addData.saveFunctionIndex = funcIndex;
this._pushAddData(Section.Function, addData);
return funcIndex;
}
editFunction(index, options) {
if (typeof index !== 'number') {
throw new Error("Invalid Index");
}
const type = options?.type;
if (!(type instanceof WasmIndex) || typeof type !== 'number') {
throw new Error("Invalid options.type");
}
const editData = {};
editData.index = index;
editData.typeIndex = type;
this._pushEditData(Section.Function, editData);
}
addGlobal(options) {
const contentType = options?.contentType;
const mutability = options?.mutability;
const initializer = options?.initializer;
if (typeof contentType !== 'string') {
throw new Error("Invalid options.contentType");
}
if (typeof mutability !== 'boolean') {
throw new Error("Invalid options.mutability");
}
if (!(initializer instanceof Array)) {
throw new Error("Invalid options.initializer");
}
const addData = {};
addData.contentType = this._string2type(contentType);
addData.mutability = mutability;
addData.initializer = initializer;
const globalIndex = new WasmIndex();
addData.saveGlobalIndex = globalIndex;
this._pushAddData(Section.Global, addData);
return globalIndex;
}
addExport(options) {
const fieldName = options?.fieldName;
const kind = options?.kind;
const ownIndex = options?.ownIndex;
if (typeof fieldName !== 'string') {
throw new Error("Invalid options.fieldName");
}
if (typeof kind !== 'number' && kind > ExternalKind.Global && kind < ExternalKind.Function) {
throw new Error("Invalid options.kind");
}
if (typeof ownIndex !== 'number' && !(ownIndex instanceof WasmIndex)) {
throw new Error("Invalid options.ownIndex");
}
const addData = {};
addData.fieldName = this._string2bytes(fieldName);
addData.kind = kind;
addData.ownIndex = ownIndex;
const exportIndex = new WasmIndex();
addData.saveExportIndex = exportIndex;
this._pushAddData(Section.Export, addData);
return exportIndex;
}
addGlobalVariable(options) {
const type = options?.type;
const value = options?.value;
const mutability = options?.mutability;
const exportFieldName = options?.exportFieldName;
if (typeof type !== 'string') {
throw new Error("Invalid options.type");
}
if (typeof value !== 'number') {
throw new Error("Invalid options.value");
}
if (typeof mutability !== 'boolean') {
throw new Error("Invalid options.mutability");
}
const addGlobalOptions = {};
addGlobalOptions.contentType = type;
addGlobalOptions.mutability = mutability;
addGlobalOptions.initializer = this._createInitializer(type, value);
const globalIndex = this.addGlobal(addGlobalOptions);
if (typeof exportFieldName === 'string') {
const addExportOptions = {};
addExportOptions.fieldName = exportFieldName;
addExportOptions.kind = ExternalKind.Global;
addExportOptions.ownIndex = globalIndex;
this.addExport(addExportOptions);
}
return globalIndex;
}
addImportFunction(options) {
const moduleName = options.module;
const fieldName = options.field;
const params = options.params;
const func = options.func;
const return_ = options.return;
if (typeof func !== 'function') {
throw new Error("Invalid options.func");
}
const addTypeOptions = {};
addTypeOptions.params = params;
addTypeOptions.return = return_;
const typeIndex = this.addType(addTypeOptions);
const addImportOptions = {};
addImportOptions.moduleName = moduleName;
addImportOptions.exportName = fieldName;
addImportOptions.kind = ExternalKind.Function;
addImportOptions.typeIndex = typeIndex;
const funcIndex = this.addImport(addImportOptions);
this._importObject[moduleName][fieldName] = func;
return funcIndex;
}
/*
prefixJSFunction(index, options) {
if (typeof index !== 'number') {
throw new Error("Invalid Index");
}
const params = options?.params;
const func = options?.func;
const addTypeOptions = {};
addTypeOptions.params = params;
addTypeOptions.return = 'i32';
const typeIndex = this.addType(addTypeOptions);
}
*/
// wasm の関数コードの先頭に新しいコードを埋め込むだけ
prefixEmbedWasmCode(index, options) {
if (typeof index !== 'number') {
throw new Error("Invalid Index");
}
const code = options?.code;
if (this._isInvalidCode(code)) {
throw new Error("Invalid options.code");
}
const optionData = {};
optionData.index = index;
optionData.code = this._flattenCode(code);
this._pushOptionData('prefixEmbedWasmCode', optionData);
}
aobPatch(options) {
const scan = options.scan;
const code = options.code;
const codes = options.codes;
const onsuccess = options.onsuccess;
if (typeof scan !== 'string') {
throw new Error("Invalid options.scan");
}
const parsedScan = this._parseScanStr(scan);
const needCodeCount = parsedScan.scan.filter(scanUnit => {
switch (scanUnit.mode) {
case ScanMode.Insert:
case ScanMode.ReplaceStart:
return true;
default:
return false;
}
}).length;
if (needCodeCount === 0) {
throw new Error("Invalid options.scan");
}
const optionData = {};
optionData.scan = parsedScan.scan;
optionData.totalMatches = parsedScan.totalMatches;
if (needCodeCount === 1) {
if (this._isInvalidCode(code)) {
throw new Error("Invalid options.code");
}
optionData.codes = [this._flattenCode(code)];
}
else {
const throwErr = () => {
throw new Error("Invalid options.codes");
}
if (!(codes instanceof Array)) {
throwErr();
}
if (codes.some(code => this._isInvalidCode(code))) {
throwErr();
}
optionData.codes = codes.map(code => this._flattenCode(code));
}
if (typeof onsuccess !== 'function') {
optionData.onsuccess = () => { };
}
else {
optionData.onsuccess = onsuccess;
}
this._pushOptionData('aobPatch', optionData);
}
/*
codePatch(9887, {
line: '10-13',
code: [
]
});
codePatch(9889, {
line: '10-13, 20-32',
codes: [
[
],
[
]
]
});
*/
codePatch(index, options) {
if (typeof index !== 'number') {
throw new Error("Invalid index");
}
const line = options?.line;
const code = options?.code;
const optionData = {};
if (typeof line !== 'string') {
throw new Error("Invalid options.line");
}
optionData.line = this._parseLineStr(line);
}
}