// Copyright (C) 2012 Zeex
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#if defined OPCODE_INC
	#endinput
#endif
#define OPCODE_INC

#if __Pawn >= 0x30A
	// Disable the recursion warning in the Russian compiler.
	#pragma warning push
	#pragma warning disable 207
	#pragma disablerecursion
	#pragma warning pop
	
	// Disable the recursion warning in the fast compiler.
	#pragma warning disable 238
#endif

#define OPCODE_MAX_INSN_NAME 15

enum Opcode {
  OP_NONE,         OP_LOAD_PRI,     OP_LOAD_ALT,
  OP_LOAD_S_PRI,   OP_LOAD_S_ALT,   OP_LREF_PRI,
  OP_LREF_ALT,     OP_LREF_S_PRI,   OP_LREF_S_ALT,
  OP_LOAD_I,       OP_LODB_I,       OP_CONST_PRI,
  OP_CONST_ALT,    OP_ADDR_PRI,     OP_ADDR_ALT,
  OP_STOR_PRI,     OP_STOR_ALT,     OP_STOR_S_PRI,
  OP_STOR_S_ALT,   OP_SREF_PRI,     OP_SREF_ALT,
  OP_SREF_S_PRI,   OP_SREF_S_ALT,   OP_STOR_I,
  OP_STRB_I,       OP_LIDX,         OP_LIDX_B,
  OP_IDXADDR,      OP_IDXADDR_B,    OP_ALIGN_PRI,
  OP_ALIGN_ALT,    OP_LCTRL,        OP_SCTRL,
  OP_MOVE_PRI,     OP_MOVE_ALT,     OP_XCHG,
  OP_PUSH_PRI,     OP_PUSH_ALT,     OP_PUSH_R,
  OP_PUSH_C,       OP_PUSH,         OP_PUSH_S,
  OP_POP_PRI,      OP_POP_ALT,      OP_STACK,
  OP_HEAP,         OP_PROC,         OP_RET,
  OP_RETN,         OP_CALL,         OP_CALL_PRI,
  OP_JUMP,         OP_JREL,         OP_JZER,
  OP_JNZ,          OP_JEQ,          OP_JNEQ,
  OP_JLESS,        OP_JLEQ,         OP_JGRTR,
  OP_JGEQ,         OP_JSLESS,       OP_JSLEQ,
  OP_JSGRTR,       OP_JSGEQ,        OP_SHL,
  OP_SHR,          OP_SSHR,         OP_SHL_C_PRI,
  OP_SHL_C_ALT,    OP_SHR_C_PRI,    OP_SHR_C_ALT,
  OP_SMUL,         OP_SDIV,         OP_SDIV_ALT,
  OP_UMUL,         OP_UDIV,         OP_UDIV_ALT,
  OP_ADD,          OP_SUB,          OP_SUB_ALT,
  OP_AND,          OP_OR,           OP_XOR,
  OP_NOT,          OP_NEG,          OP_INVERT,
  OP_ADD_C,        OP_SMUL_C,       OP_ZERO_PRI,
  OP_ZERO_ALT,     OP_ZERO,         OP_ZERO_S,
  OP_SIGN_PRI,     OP_SIGN_ALT,     OP_EQ,
  OP_NEQ,          OP_LESS,         OP_LEQ,
  OP_GRTR,         OP_GEQ,          OP_SLESS,
  OP_SLEQ,         OP_SGRTR,        OP_SGEQ,
  OP_EQ_C_PRI,     OP_EQ_C_ALT,     OP_INC_PRI,
  OP_INC_ALT,      OP_INC,          OP_INC_S,
  OP_INC_I,        OP_DEC_PRI,      OP_DEC_ALT,
  OP_DEC,          OP_DEC_S,        OP_DEC_I,
  OP_MOVS,         OP_CMPS,         OP_FILL,
  OP_HALT,         OP_BOUNDS,       OP_SYSREQ_PRI,
  OP_SYSREQ_C,     OP_FILE,         OP_LINE,
  OP_SYMBOL,       OP_SRANGE,       OP_JUMP_PRI,
  OP_SWITCH,       OP_CASETBL,      OP_SWAP_PRI,
  OP_SWAP_ALT,     OP_PUSH_ADR,     OP_NOP,
  OP_SYSREQ_D,     OP_SYMTAG,       OP_BREAK,
  OP_LAST_
};

enum OpcodeInsnInfo {
	OpcodeInsnInfo_name[OPCODE_MAX_INSN_NAME],
	OpcodeInsnInfo_num_opers,
	bool:OpcodeInsnInfo_needs_reloc
}

static stock const insn_table[][OpcodeInsnInfo] = {
	{ "none",        0, false },
	{ "load.pri",    1, false },
	{ "load.alt",    1, false },
	{ "load.s.pri",  1, false },
	{ "load.s.alt",  1, false },
	{ "lref.pri",    1, false },
	{ "lref.alt",    1, false },
	{ "lref.s.pri",  1, false },
	{ "lref.s.alt",  1, false },
	{ "load.i",      0, false },
	{ "lodb.i",      1, false },
	{ "const.pri",   1, false },
	{ "const.alt",   1, false },
	{ "addr.pri",    1, false },
	{ "addr.alt",    1, false },
	{ "stor.pri",    1, false },
	{ "stor.alt",    1, false },
	{ "stor.s.pri",  1, false },
	{ "stor.s.alt",  1, false },
	{ "sref.pri",    1, false },
	{ "sref.alt",    1, false },
	{ "sref.s.pri",  1, false },
	{ "sref.s.alt",  1, false },
	{ "stor.i",      0, false },
	{ "strb.i",      1, false },
	{ "lidx",        0, false },
	{ "lidx.b",      1, false },
	{ "idxaddr",     0, false },
	{ "idxaddr.b",   1, false },
	{ "align.pri",   1, false },
	{ "align.alt",   1, false },
	{ "lctrl",       1, false },
	{ "sctrl",       1, false },
	{ "move.pri",    0, false },
	{ "move.alt",    0, false },
	{ "xchg",        0, false },
	{ "push.pri",    0, false },
	{ "push.alt",    0, false },
	{ "push.r",      1, false },
	{ "push.c",      1, false },
	{ "push",        1, false },
	{ "push.s",      1, false },
	{ "pop.pri",     0, false },
	{ "pop.alt",     0, false },
	{ "stack",       1, false },
	{ "heap",        1, false },
	{ "proc",        0, false },
	{ "ret",         0, false },
	{ "retn",        0, false },
	{ "call",        1, true  },
	{ "call.pri",    0, false },
	{ "jump",        1, true  },
	{ "jrel",        1, false },
	{ "jzer",        1, true  },
	{ "jnz",         1, true  },
	{ "jeq",         1, true  },
	{ "jneq",        1, true  },
	{ "jless",       1, true  },
	{ "jleq",        1, true  },
	{ "jgrtr",       1, true  },
	{ "jgeq",        1, true  },
	{ "jsless",      1, true  },
	{ "jsleq",       1, true  },
	{ "jsgrtr",      1, true  },
	{ "jsgeq",       1, true  },
	{ "shl",         0, false },
	{ "shr",         0, false },
	{ "sshr",        0, false },
	{ "shl.c.pri",   1, false },
	{ "shl.c.alt",   1, false },
	{ "shr.c.pri",   1, false },
	{ "shr.c.alt",   1, false },
	{ "smul",        0, false },
	{ "sdiv",        0, false },
	{ "sdiv.alt",    0, false },
	{ "umul",        0, false },
	{ "udiv",        0, false },
	{ "udiv.alt",    0, false },
	{ "add",         0, false },
	{ "sub",         0, false },
	{ "sub.alt",     0, false },
	{ "and",         0, false },
	{ "or",          0, false },
	{ "xort",        0, false },
	{ "not",         0, false },
	{ "neg",         0, false },
	{ "invert",      0, false },
	{ "add.c",       1, false },
	{ "smul.c",      1, false },
	{ "zero.pri",    0, false },
	{ "zero.alt",    0, false },
	{ "zero",        1, false },
	{ "zero.s",      1, false },
	{ "sign.pri",    0, false },
	{ "sign.alt",    0, false },
	{ "eq",          0, false },
	{ "neq",         0, false },
	{ "less",        0, false },
	{ "leq",         0, false },
	{ "grtr",        0, false },
	{ "geq",         0, false },
	{ "sless",       0, false },
	{ "sleq",        0, false },
	{ "sgrtr",       0, false },
	{ "sgeq",        0, false },
	{ "eq.c.pri",    1, false },
	{ "eq.c.alt",    1, false },
	{ "inc.pri",     0, false },
	{ "inc.alt",     0, false },
	{ "inc",         1, false },
	{ "inc.s",       1, false },
	{ "inc.i",       0, false },
	{ "dec.pri",     0, false },
	{ "dec.alt",     0, false },
	{ "dec",         1, false },
	{ "dec.s",       1, false },
	{ "dec.i",       0, false },
	{ "movs",        1, false },
	{ "cmps",        1, false },
	{ "fill",        1, false },
	{ "halt",        1, false },
	{ "bounds",      1, false },
	{ "sysreq.pri",  1, false },
	{ "sysreq.c",    1, false },
	{ "file",       -1, false }, // obsolete
	{ "line",       -1, false }, // obsolete
	{ "symbol",     -1, false }, // obsolete
	{ "srange",     -1, false }, // obsolete
	{ "jump.pri",    0, false },
	{ "switch",      1, true },
	{ "casetbl",    -1, true },
	{ "swap.pri",    0, false },
	{ "swap.alt",    0, false },
	{ "push.adr",    1, false },
	{ "nop",         0, false },
	{ "sysreq.d",    1, false },
	{ "symtag",      1, false }, // obsolete
	{ "break",       0, false }
};

const NUM_OPCODES = _:OP_LAST_;

static stock Opcode:ReadOpcodeNearThis(offset = 0) {
	new ret_addr;

	// Get return address + COD - DAT + offset.
	#emit load.s.alt 4
	#emit lctrl 0
	#emit add
	#emit move.alt
	#emit lctrl 1
	#emit xchg
	#emit sub
	#emit load.s.alt offset
	#emit add

	#emit stor.s.pri ret_addr
	#emit lref.s.pri ret_addr

	#emit stack 4
	#emit retn

	return OP_NONE; // make compiler happy
}

static stock bool:HaveToRelocateOpcodes() {
	return ReadOpcodeNearThis(-8) != OP_CALL;
}

// Based on this idea: http://forum.sa-mp.com/showthread.php?t=358084
stock Opcode:RelocateOpcodeNow(Opcode:opcode) {
	if (!HaveToRelocateOpcodes()) {
		return opcode;
	}
	switch (opcode) {
		case OP_LOAD_PRI: {
			return ReadOpcodeNearThis(4);
			#emit load.pri 0
		}
		case OP_LOAD_ALT: {
			return ReadOpcodeNearThis(4);
			#emit load.alt 0
		}
		case OP_LOAD_S_PRI: {
			return ReadOpcodeNearThis(4);
			#emit load.s.pri 0
		}
		case OP_LOAD_S_ALT: {
			return ReadOpcodeNearThis(4);
			#emit load.s.alt 0
		}
		case OP_LREF_PRI: {
			return ReadOpcodeNearThis(4);
			#emit lref.pri 0
		}
		case OP_LREF_ALT: {
			return ReadOpcodeNearThis(4);
			#emit lref.alt 0
		}
		case OP_LREF_S_PRI: {
			return ReadOpcodeNearThis(4);
			#emit lref.s.pri 0
		}
		case OP_LREF_S_ALT: {
			return ReadOpcodeNearThis(4);
			#emit lref.s.alt 0
		}
		case OP_LOAD_I: {
			return ReadOpcodeNearThis(4);
			#emit load.i
		}
		case OP_LODB_I: {
			return ReadOpcodeNearThis(4);
			#emit lodb.i 1
		}
		case OP_CONST_PRI: {
			return ReadOpcodeNearThis(4);
			#emit const.pri 0
		}
		case OP_CONST_ALT: {
			return ReadOpcodeNearThis(4);
			#emit const.alt 0
		}
		case OP_ADDR_PRI: {
			return ReadOpcodeNearThis(4);
			#emit addr.pri 0
		}
		case OP_ADDR_ALT: {
			return ReadOpcodeNearThis(4);
			#emit addr.alt 0
		}
		case OP_STOR_PRI: {
			return ReadOpcodeNearThis(4);
			#emit stor.pri 0
		}
		case OP_STOR_ALT: {
			return ReadOpcodeNearThis(4);
			#emit stor.alt 0
		}
		case OP_STOR_S_PRI: {
			return ReadOpcodeNearThis(4);
			#emit stor.s.pri 0
		}
		case OP_STOR_S_ALT: {
			return ReadOpcodeNearThis(4);
			#emit stor.s.alt 0
		}
		case OP_SREF_PRI: {
			return ReadOpcodeNearThis(4);
			#emit sref.pri 0
		}
		case OP_SREF_ALT: {
			return ReadOpcodeNearThis(4);
			#emit sref.alt 0
		}
		case OP_SREF_S_PRI: {
			return ReadOpcodeNearThis(4);
			#emit sref.s.pri 0
		}
		case OP_SREF_S_ALT: {
			return ReadOpcodeNearThis(4);
			#emit sref.s.alt 0
		}
		case OP_STOR_I: {
			return ReadOpcodeNearThis(4);
			#emit stor.i
		}
		case OP_STRB_I: {
			return ReadOpcodeNearThis(4);
			#emit strb.i 1
		}
		case OP_LIDX: {
			return ReadOpcodeNearThis(4);
			#emit lidx
		}
		case OP_LIDX_B: {
			return ReadOpcodeNearThis(4);
			#emit lidx.b 0
		}
		case OP_IDXADDR: {
			return ReadOpcodeNearThis(4);
			#emit idxaddr
		}
		case OP_IDXADDR_B: {
			return ReadOpcodeNearThis(4);
			#emit idxaddr.b 0
		}
		case OP_ALIGN_PRI: {
			return ReadOpcodeNearThis(4);
			#emit align.pri 0
		}
		case OP_ALIGN_ALT: {
			return ReadOpcodeNearThis(4);
			#emit align.alt 0
		}
		case OP_LCTRL: {
			return ReadOpcodeNearThis(4);
			#emit lctrl 0
		}
		case OP_SCTRL: {
			return ReadOpcodeNearThis(4);
			#emit sctrl 0
		}
		case OP_MOVE_PRI: {
			return ReadOpcodeNearThis(4);
			#emit move.pri
		}
		case OP_MOVE_ALT: {
			return ReadOpcodeNearThis(4);
			#emit move.alt
		}
		case OP_XCHG: {
			return ReadOpcodeNearThis(4);
			#emit xchg
		}
		case OP_PUSH_PRI: {
			return ReadOpcodeNearThis(4);
			#emit push.pri
		}
		case OP_PUSH_ALT: {
			return ReadOpcodeNearThis(4);
			#emit push.alt
		}
		case OP_PUSH_C: {
			return ReadOpcodeNearThis(4);
			#emit push.c 0
		}
		case OP_PUSH: {
			return ReadOpcodeNearThis(4);
			#emit push 0
		}
		case OP_PUSH_S: {
			return ReadOpcodeNearThis(4);
			#emit push.s 0
		}
		case OP_POP_PRI: {
			return ReadOpcodeNearThis(4);
			#emit pop.pri
		}
		case OP_POP_ALT: {
			return ReadOpcodeNearThis(4);
			#emit pop.alt
		}
		case OP_STACK: {
			return ReadOpcodeNearThis(4);
			#emit stack 0
		}
		case OP_HEAP: {
			return ReadOpcodeNearThis(4);
			#emit heap 0
		}
		case OP_PROC: {
			return ReadOpcodeNearThis(4);
			#emit proc
		}
		case OP_RET: {
			return ReadOpcodeNearThis(4);
			#emit ret
		}
		case OP_RETN: {
			return ReadOpcodeNearThis(4);
			#emit retn
		}
		case OP_CALL: {
			// We can't do just "#emit call 0" - this will crash compiler for some reason (bug?).
			return ReadOpcodeNearThis(-8);
		}
		case OP_JUMP: {
			return ReadOpcodeNearThis(4);
			#emit jump 0
		}
		case OP_JZER: {
			return ReadOpcodeNearThis(4);
			#emit jzer 0
		}
		case OP_JNZ: {
			return ReadOpcodeNearThis(4);
			#emit jnz 0
		}
		case OP_JEQ: {
			return ReadOpcodeNearThis(4);
			#emit jeq 0
		}
		case OP_JNEQ: {
			return ReadOpcodeNearThis(4);
			#emit jneq 0
		}
		case OP_JLESS: {
			return ReadOpcodeNearThis(4);
			#emit jless 0
		}
		case OP_JLEQ: {
			return ReadOpcodeNearThis(4);
			#emit jleq 0
		}
		case OP_JGRTR: {
			return ReadOpcodeNearThis(4);
			#emit jgrtr 0
		}
		case OP_JGEQ: {
			return ReadOpcodeNearThis(4);
			#emit jgeq 0
		}
		case OP_JSLESS: {
			return ReadOpcodeNearThis(4);
			#emit jsless 0
		}
		case OP_JSLEQ: {
			return ReadOpcodeNearThis(4);
			#emit jsleq 0
		}
		case OP_JSGRTR: {
			return ReadOpcodeNearThis(4);
			#emit jsgrtr 0
		}
		case OP_JSGEQ: {
			return ReadOpcodeNearThis(4);
			#emit jsgeq 0
		}
		case OP_SHL: {
			return ReadOpcodeNearThis(4);
			#emit shl
		}
		case OP_SHR: {
			return ReadOpcodeNearThis(4);
			#emit shr
		}
		case OP_SSHR: {
			return ReadOpcodeNearThis(4);
			#emit sshr
		}
		case OP_SHL_C_PRI: {
			return ReadOpcodeNearThis(4);
			#emit shl.c.pri 0
		}
		case OP_SHL_C_ALT: {
			return ReadOpcodeNearThis(4);
			#emit shl.c.alt 0
		}
		case OP_SHR_C_PRI: {
			return ReadOpcodeNearThis(4);
			#emit shr.c.pri 0
		}
		case OP_SHR_C_ALT: {
			return ReadOpcodeNearThis(4);
			#emit shr.c.alt 0
		}
		case OP_SMUL: {
			return ReadOpcodeNearThis(4);
			#emit smul
		}
		case OP_SDIV: {
			return ReadOpcodeNearThis(4);
			#emit sdiv
		}
		case OP_SDIV_ALT: {
			return ReadOpcodeNearThis(4);
			#emit sdiv.alt
		}
		case OP_UMUL: {
			return ReadOpcodeNearThis(4);
			#emit umul
		}
		case OP_UDIV: {
			return ReadOpcodeNearThis(4);
			#emit udiv
		}
		case OP_UDIV_ALT: {
			return ReadOpcodeNearThis(4);
			#emit udiv.alt
		}
		case OP_ADD: {
			return ReadOpcodeNearThis(4);
			#emit add
		}
		case OP_SUB: {
			return ReadOpcodeNearThis(4);
			#emit sub
		}
		case OP_SUB_ALT: {
			return ReadOpcodeNearThis(4);
			#emit sub.alt
		}
		case OP_AND: {
			return ReadOpcodeNearThis(4);
			#emit and
		}
		case OP_OR: {
			return ReadOpcodeNearThis(4);
			#emit or
		}
		case OP_XOR: {
			return ReadOpcodeNearThis(4);
			#emit xor
		}
		case OP_NOT: {
			return ReadOpcodeNearThis(4);
			#emit not
		}
		case OP_NEG: {
			return ReadOpcodeNearThis(4);
			#emit neg
		}
		case OP_INVERT: {
			return ReadOpcodeNearThis(4);
			#emit invert
		}
		case OP_ADD_C: {
			return ReadOpcodeNearThis(4);
			#emit add.c 0
		}
		case OP_SMUL_C: {
			return ReadOpcodeNearThis(4);
			#emit smul.c 0
		}
		case OP_ZERO_PRI: {
			return ReadOpcodeNearThis(4);
			#emit zero.pri
		}
		case OP_ZERO_ALT: {
			return ReadOpcodeNearThis(4);
			#emit zero.alt
		}
		case OP_ZERO: {
			return ReadOpcodeNearThis(4);
			#emit zero 0
		}
		case OP_ZERO_S: {
			return ReadOpcodeNearThis(4);
			#emit zero.s 0
		}
		case OP_SIGN_PRI: {
			return ReadOpcodeNearThis(4);
			#emit sign.pri
		}
		case OP_SIGN_ALT: {
			return ReadOpcodeNearThis(4);
			#emit sign.alt
		}
		case OP_EQ: {
			return ReadOpcodeNearThis(4);
			#emit eq
		}
		case OP_NEQ: {
			return ReadOpcodeNearThis(4);
			#emit neq
		}
		case OP_LESS: {
			return ReadOpcodeNearThis(4);
			#emit less
		}
		case OP_LEQ: {
			return ReadOpcodeNearThis(4);
			#emit leq
		}
		case OP_GRTR: {
			return ReadOpcodeNearThis(4);
			#emit grtr
		}
		case OP_GEQ: {
			return ReadOpcodeNearThis(4);
			#emit geq
		}
		case OP_SLESS: {
			return ReadOpcodeNearThis(4);
			#emit sless
		}
		case OP_SLEQ: {
			return ReadOpcodeNearThis(4);
			#emit sleq
		}
		case OP_SGRTR: {
			return ReadOpcodeNearThis(4);
			#emit sgrtr
		}
		case OP_SGEQ: {
			return ReadOpcodeNearThis(4);
			#emit sgeq
		}
		case OP_EQ_C_PRI: {
			return ReadOpcodeNearThis(4);
			#emit eq.c.pri 0
		}
		case OP_EQ_C_ALT: {
			return ReadOpcodeNearThis(4);
			#emit eq.c.alt 0
		}
		case OP_INC_PRI: {
			return ReadOpcodeNearThis(4);
			#emit inc.pri
		}
		case OP_INC_ALT: {
			return ReadOpcodeNearThis(4);
			#emit inc.alt
		}
		case OP_INC: {
			return ReadOpcodeNearThis(4);
			#emit inc 0
		}
		case OP_INC_S: {
			return ReadOpcodeNearThis(4);
			#emit inc.s 0
		}
		case OP_INC_I: {
			return ReadOpcodeNearThis(4);
			#emit inc.i
		}
		case OP_DEC_PRI: {
			return ReadOpcodeNearThis(4);
			#emit dec.pri
		}
		case OP_DEC_ALT: {
			return ReadOpcodeNearThis(4);
			#emit dec.alt
		}
		case OP_DEC: {
			return ReadOpcodeNearThis(4);
			#emit dec 0
		}
		case OP_DEC_S: {
			return ReadOpcodeNearThis(4);
			#emit dec.s 0
		}
		case OP_DEC_I: {
			return ReadOpcodeNearThis(4);
			#emit dec.i
		}
		case OP_MOVS: {
			return ReadOpcodeNearThis(4);
			#emit movs 0
		}
		case OP_CMPS: {
			return ReadOpcodeNearThis(4);
			#emit cmps 0
		}
		case OP_FILL: {
			return ReadOpcodeNearThis(4);
			#emit fill 0
		}
		case OP_HALT: {
			return ReadOpcodeNearThis(4);
			#emit halt 0
		}
		case OP_BOUNDS: {
			return ReadOpcodeNearThis(4);
			#emit bounds 0
		}
		case OP_SYSREQ_C: {
			return ReadOpcodeNearThis(4);
			#emit sysreq.c 0
		}
		case OP_SWITCH: {
			static T = 1;
			#if debug > 0
				if (T) return ReadOpcodeNearThis(12);
			#else
				if (T) return ReadOpcodeNearThis(8);
			#endif
			switch (0) {
				case 0: {}
			}
		}
		case OP_CASETBL: {
			new x = 0;
			switch (x) { case 0: return ReadOpcodeNearThis(20); }
		}
		case OP_SWAP_PRI: {
			return ReadOpcodeNearThis(4);
			#emit swap.pri
		}
		case OP_SWAP_ALT: {
			return ReadOpcodeNearThis(4);
			#emit swap.alt
		}
		case OP_PUSH_ADR: {
			return ReadOpcodeNearThis(4);
			#emit push.adr 0
		}
		case OP_NOP: {
			return ReadOpcodeNearThis(4);
			#emit nop
		}
		case OP_SYSREQ_D: {
			// Since there's no way to #emit sysreq.d we have to compute its
			// address using a relative offset to another opcode, e.g. nop.
			new bool:is_crashdetect;
			#emit zero.pri
			#emit lctrl 0xFF
			#emit stor.s.pri is_crashdetect
			if (is_crashdetect) {
				// The offset is different when running under crashdetect as
				// it uses its own (modified) version of the VM. This value
				// value works only with crashdetect 4.13.
				return RelocateOpcodeNow(OP_NOP) - Opcode:0x1EA;
			} else {
				return RelocateOpcodeNow(OP_NOP) - Opcode:0x4F;
			}
		}
		case OP_BREAK: {
			return ReadOpcodeNearThis(4);
			#emit break
		}
	}
	return opcode;
}

static stock Opcode:opcode_table[NUM_OPCODES];
static stock bool:opcode_table_is_ready = false;

stock bool:IsOpcodeValid(Opcode:opcode) {
	return (OP_NONE <= opcode < Opcode:NUM_OPCODES);
}

static stock InitOpcodeTable() {
	for (new i = 0; i < NUM_OPCODES; i++) {
		opcode_table[i] = RelocateOpcodeNow(Opcode:i);
	}
	opcode_table_is_ready = true;
}

stock Opcode:RelocateOpcode(Opcode:opcode) {
	if (!opcode_table_is_ready) {
		InitOpcodeTable();
	}
	return opcode_table[_:opcode];
}

stock Opcode:UnrelocateOpcode(Opcode:opcode) {
	if (!opcode_table_is_ready) {
		InitOpcodeTable();
	}
	if (OP_NONE <= opcode < Opcode:NUM_OPCODES) {
		return opcode;
	}
	for (new i = 0; i < NUM_OPCODES; i++) {
		if (opcode_table[i] == opcode) {
			return Opcode:i;
		}
	}
	return opcode;
}

stock GetOpcodeInstructionInformation(Opcode:opcode) {
	if (OP_NONE < opcode < Opcode:NUM_OPCODES) {
		return insn_table[_:opcode];
	}
	return insn_table[0];
}

stock GetOpcodeInstructionName(Opcode:opcode) {
	static const ret[OPCODE_MAX_INSN_NAME] = "none";
	if (OP_NONE < opcode < Opcode:NUM_OPCODES) {
		// This doesn't work.
		//return insn_table[_:opcode][OpcodeInsnInfo_name];
		// Fake it without requiring `strcpy`.
		#emit CONST.alt    insn_table
		#emit LOAD.S.pri   opcode
		#emit IDXADDR
		#emit MOVE.alt
		#emit LOAD.I
		#emit ADD
		#emit LOAD.S.alt   16
		#emit MOVS         60 // 15 * 4
		#emit RETN
		// Check that the code is correct for the size of "OPCODE_MAX_INSN_NAME".
		#assert OPCODE_MAX_INSN_NAME == 15
	}
	return ret;
}

stock GetOpcodeInstructionParameters(Opcode:opcode) {
	if (OP_NONE < opcode < Opcode:NUM_OPCODES) {
		return insn_table[_:opcode][OpcodeInsnInfo_num_opers];
	}
	return 0;
}

stock bool:GetOpcodeInstructionRelocatable(Opcode:opcode) {
	if (OP_NONE < opcode < Opcode:NUM_OPCODES) {
		return insn_table[_:opcode][OpcodeInsnInfo_needs_reloc];
	}
	return false;
}

