wasm_patcher_v2

WebAssemblyのbufferにパッチを当てる

Questo script non dovrebbe essere installato direttamente. È una libreria per altri script da includere con la chiave // @require https://update.greasyfork.org/scripts/440816/1023576/wasm_patcher_v2.js

// ==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);
    }
}