/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "gamos/gamos.h"

namespace Gamos {

uint8 VM::MemAccess::getU8(uint32 address) {
	if (!_currentBlock || address < _currentBlock->address || address >= (_currentBlock->address + 0x100))
		_currentBlock = _vm.findMemoryBlock(address);

	if (!_currentBlock)
		return 0; // ERROR!

	return _currentBlock->data[address - _currentBlock->address];
}

uint32 VM::MemAccess::getU32(uint32 address) {
	if (!_currentBlock || address < _currentBlock->address || address >= (_currentBlock->address + 0x100))
		_currentBlock = _vm.findMemoryBlock(address);

	if (!_currentBlock)
		return 0; // ERROR!

	uint32 pos = address - _currentBlock->address;
	if ((int32)0x100 - (int32)pos >= 4)
		return VM::getU32(_currentBlock->data + pos); //easy

	MemoryBlock *block = _currentBlock;
	uint32 val = block->data[ pos ];
	for (int i = 1; i < 4; i++) {
		pos++;
		if (pos >= 0x100) {
			block = _vm.findMemoryBlock(address + i);
			if (!block)
				break;
			pos = 0;
		}
		val |= block->data[ pos ] << (i * 8);
	}

	return val;
}

void VM::MemAccess::setU8(uint32 address, uint8 val) {
	if (!_currentBlock || address < _currentBlock->address || address >= (_currentBlock->address + 0x100))
		_currentBlock = _vm.findMemoryBlock(address);

	if (!_currentBlock)
		_currentBlock = _vm.createBlock(address);

	_currentBlock->data[ address - _currentBlock->address ] = val;
}

void VM::MemAccess::setU32(uint32 address, uint32 val) {
	if (!_currentBlock || address < _currentBlock->address || address >= (_currentBlock->address + 0x100))
		_currentBlock = _vm.findMemoryBlock(address);

	if (!_currentBlock)
		_currentBlock = _vm.createBlock(address);

	uint32 pos = address - _currentBlock->address;

	if (address + 4 <= _currentBlock->address + 0x100) {
		VM::setU32(_currentBlock->data + pos, val);
		return;
	}

	MemoryBlock *block = _currentBlock;

	_currentBlock->data[ pos ] = val & 0xff;

	for (int i = 1; i < 4; i++) {
		pos++;
		if (pos >= 0x100) {
			block = _vm.createBlock(address + i);
			if (!block)
				break;
			pos = 0;
		}
		block->data[ pos ] = (val >> (i * 8)) & 0xff;
	}
}





uint32 VM::Context::execute(uint32 scriptAddress, byte *storage) {
	//Common::String disasm = disassembly(scriptAddress);

	ESI = scriptAddress;
	EBX = storage;

	_readAccess.reset();
	_writeAccess.reset();

	SP = STACK_POS;

	//Common::Array<OpLog> cmdlog;

	bool loop = true;
	while (loop) {
		if (_vm._interrupt)
			return 0;

		byte op = _readAccess.getU8(ESI);
		//cmdlog.push_back({ESI, (OP)op, SP});
		ESI++;

		switch (op) {
		default:
		case OP_EXIT:
			loop = false;
			break;

		case OP_CMP_EQ:
			if (EDX.getVal() == EAX.getVal())
				EAX.setVal(1);
			else
				EAX.setVal(0);
			break;

		case OP_CMP_NE:
			if (EDX.getVal() != EAX.getVal())
				EAX.setVal(1);
			else
				EAX.setVal(0);
			break;

		case OP_CMP_LE:
			if ((int32)EDX.getVal() < (int32)EAX.getVal())
				EAX.setVal(1);
			else
				EAX.setVal(0);
			break;

		case OP_CMP_LEQ:
			if ((int32)EDX.getVal() <= (int32)EAX.getVal())
				EAX.setVal(1);
			else
				EAX.setVal(0);
			break;

		case OP_CMP_GR:
			if ((int32)EDX.getVal() > (int32)EAX.getVal())
				EAX.setVal(1);
			else
				EAX.setVal(0);
			break;

		case OP_CMP_GREQ:
			if ((int32)EDX.getVal() >= (int32)EAX.getVal())
				EAX.setVal(1);
			else
				EAX.setVal(0);
			break;

		case OP_CMP_NAE:
			if (EDX.getVal() < EAX.getVal())
				EAX.setVal(1);
			else
				EAX.setVal(0);
			break;

		case OP_CMP_NA:
			if (EDX.getVal() <= EAX.getVal())
				EAX.setVal(1);
			else
				EAX.setVal(0);
			break;

		case OP_CMP_A:
			if (EDX.getVal() > EAX.getVal())
				EAX.setVal(1);
			else
				EAX.setVal(0);
			break;

		case OP_CMP_AE:
			if (EDX.getVal() >= EAX.getVal())
				EAX.setVal(1);
			else
				EAX.setVal(0);
			break;

		case OP_BRANCH:
			if (EAX.getVal() != 0)
				ESI += 4;
			else
				ESI += (int32)_readAccess.getU32(ESI);
			break;

		case OP_JMP:
			ESI += (int32)_readAccess.getU32(ESI);
			break;

		case OP_SP_ADD:
			SP += (int32)_readAccess.getU32(ESI);
			ESI += 4;
			break;

		case OP_MOV_EDI_ECX_AL:
			setMem8(REF_EDI, _readAccess.getU32(ESI), EAX.getVal() & 0xff);
			ESI += 4;
			break;

		case OP_MOV_EBX_ECX_AL:
			setMem8(REF_EBX, _readAccess.getU32(ESI), EAX.getVal() & 0xff);
			ESI += 4;
			break;

		case OP_MOV_EDI_ECX_EAX:
			setMem32(REF_EDI, _readAccess.getU32(ESI), EAX.getVal());
			ESI += 4;
			break;

		case OP_MOV_EBX_ECX_EAX:
			setMem32(REF_EBX, _readAccess.getU32(ESI), EAX.getVal());
			ESI += 4;
			break;

		case OP_RET:
			ESI = pop32();
			ESI += 4;
			break;

		case OP_RETX:
			ECX = popReg();
			SP += _readAccess.getU32(ESI);
			ESI = ECX.getVal();
			ESI += 4;
			break;

		case OP_MOV_EDX_EAX:
			EDX = EAX;
			break;

		case OP_ADD_EAX_EDX:
			EAX.setVal(EAX.getVal() + EDX.getVal());
			break;

		case OP_MUL:
			EAX.setVal(EAX.getVal() * EDX.getVal());
			break;

		case OP_OR:
            EAX.setVal(EAX.getVal() | EDX.getVal());
			break;

		case OP_XOR:
            EAX.setVal(EAX.getVal() ^ EDX.getVal());
			break;

		case OP_AND:
            EAX.setVal(EAX.getVal() & EDX.getVal());
			break;

		case OP_NEG:
            EAX.setVal((uint32)(-(int32)EAX.getVal()));
			break;

		case OP_SAR:
            EAX.setVal((int32)EDX.getVal() >> (EAX.getVal() & 0xff)); /* must be arythmetic shift! */
			break;

		case OP_SHL:
            EAX.setVal(EDX.getVal() << (EAX.getVal() & 0xff));
			break;

		case OP_LOAD:
            EAX.setVal( _readAccess.getU32(ESI) );
			ESI += 4;
			break;

		case OP_INC:
			EAX.setVal( EAX.getVal() + 1 );
			break;

		case OP_DEC:
            EAX.setVal( EAX.getVal() - 1 );
			break;

		case OP_XCHG:
			ECX = EAX;
			EAX = EDX;
			EDX = ECX;
			break;

		case OP_PUSH_EAX:
			pushReg(EAX);
			break;

		case OP_POP_EDX:
			EDX = popReg();
			break;

		case OP_LOAD_OFFSET_EDI:
		case OP_LOAD_OFFSET_EDI2:
            EAX.setAddress(REF_EDI, _readAccess.getU32(ESI));
			ESI += 4;
			break;

		case OP_LOAD_OFFSET_EBX:
            EAX.setAddress(REF_EBX, _readAccess.getU32(ESI));
			ESI += 4;
			break;

		case OP_LOAD_OFFSET_ESP:
            EAX.setAddress(REF_STACK, _readAccess.getU32(ESI) + SP);
			ESI += 4;
			break;

		case OP_MOV_PTR_EDX_AL:
			setMem8(EDX, EAX.getVal() & 0xff);
			break;

		case OP_MOV_PTR_EDX_EAX:
			setMem32(EDX, EAX.getVal());
			break;

		case OP_SHL_2:
			EAX.setVal( EAX.getVal() << 2 );
			break;

		case OP_ADD_4:
            EAX.setVal( EAX.getVal() + 4 );
			break;

		case OP_SUB_4:
            EAX.setVal( EAX.getVal() - 4 );
			break;

		case OP_XCHG_ESP:
			ECX = popReg();
			pushReg(EAX);
			EAX = ECX;
			break;

		case OP_NEG_ADD:
            EAX.setVal( (-(int32)EAX.getVal()) + EDX.getVal() );
			break;

		case OP_DIV:
			ECX = EAX;
			if (ECX.getVal() != 0) {
				EAX.setVal( (int32)EDX.getVal() / (int32)ECX.getVal() );
				EDX.setVal( (int32)EDX.getVal() % (int32)ECX.getVal() );
			} else {
				warning("Division by 0 at %x", ESI);
			}
			break;

		case OP_MOV_EAX_BPTR_EDI:
			EAX.setVal( (int32)((int8)getMem8(REF_EDI, _readAccess.getU32(ESI))) );
			ESI += 4;
			break;

		case OP_MOV_EAX_BPTR_EBX:
            EAX.setVal( (int32)((int8)getMem8(REF_EBX, _readAccess.getU32(ESI))) );
			ESI += 4;
			break;

		case OP_MOV_EAX_DPTR_EDI:
            EAX.setVal( getMem32(REF_EDI, _readAccess.getU32(ESI)) );
			ESI += 4;
			break;

		case OP_MOV_EAX_DPTR_EBX:
            EAX.setVal( getMem32(REF_EBX, _readAccess.getU32(ESI)) );
			ESI += 4;
			break;

		case OP_MOV_EAX_BPTR_EAX:
			EAX.setVal( (int32)( (int8)getMem8(EAX) ) );
			break;

		case OP_MOV_EAX_DPTR_EAX:
            EAX.setVal( getMem32(EAX) );
			break;

		case OP_PUSH_ESI_ADD_EDI:
			push32(ESI);
			ESI = _readAccess.getU32(ESI);
			break;

		case OP_CALL_FUNC:
			EAX.setVal( _readAccess.getU32(ESI) );
			ESI += 4;
			if (_vm._callFuncs)
				_vm._callFuncs(_vm._callingObject, this, EAX.getVal());
			break;

		case OP_PUSH_ESI_SET_EDX_EDI:
			push32(ESI);
			ESI = EDX.getVal();
			break;
		}
	}

	return EAX.getVal();
}


uint32 VM::doScript(uint32 scriptAddress, byte *storage) {
	if (_interrupt)
		return 0;

	for (uint i = 0; i < THREADS_COUNT; i++) {
		if (!_threads[i]._inUse) {
			_threads[i]._inUse = true;
			uint32 res = _threads[i].execute(scriptAddress, storage);
			_threads[i]._inUse = false;
			return res;
		}
	}

	Context *tmpcontext = new Context(*this);
	uint32 res = tmpcontext->execute(scriptAddress, storage);
	delete tmpcontext;
	return res;
}



int32 VM::getS32(const void *mem) {
	const uint8 *mem8 = (const uint8 *)mem;
	return (int32)(mem8[0] | (mem8[1] << 8) | (mem8[2] << 16) | (mem8[3] << 24));
}

uint32 VM::getU32(const void *mem) {
	const uint8 *mem8 = (const uint8 *)mem;
	return (uint32)(mem8[0] | (mem8[1] << 8) | (mem8[2] << 16) | (mem8[3] << 24));
}

void VM::setU32(void *mem, uint32 val) {
	uint8 *mem8 = (uint8 *)mem;
	mem8[0] = val & 0xff;
	mem8[1] = (val >> 8) & 0xff;
	mem8[2] = (val >> 16) & 0xff;
	mem8[3] = (val >> 24) & 0xff;
}

void VM::Context::push32(uint32 val) {
	SP -= 4;
	setU32(_stack + SP, val);
}

uint32 VM::Context::pop32() {
	uint32 val = getU32(_stack + SP);
	SP += 4;
	return val;
}

void VM::Context::pushReg(ValAddr reg) {
	SP -= 4;
	setU32(_stack + SP, reg.getVal());
}

VM::ValAddr VM::Context::popReg() {
	ValAddr tmp;
	tmp.setVal( getU32(_stack + SP) );
	SP += 4;
	return tmp;
}

uint32 VM::Context::getMem32(int memtype, uint32 offset) {
	switch (memtype) {
	default:
	case REF_UNK:
		return 0; // Set here breakpoint for find what is going wrong

	case REF_STACK:	{
		if (offset < (sizeof(_stack) - 4))
			return getU32(_stack + offset);
		else {
			error("getMem32(): Out of bounds read, memType: REF_STACK  offset: 0x%02X", offset);
			return 0;
		}
	}


	case REF_EBX:
		return getU32(EBX + offset);

	case REF_EDI:
		return _readAccess.getU32(offset);
	}
}

uint32 VM::Context::getMem32(const ValAddr &addr) {
    return getMem32(addr.getMemType(), addr.getOffset());
}

uint8 VM::Context::getMem8(int memtype, uint32 offset) {
	switch (memtype) {
	default:
	case REF_UNK:
		return 0; // Set here breakpoint for find what is going wrong

	case REF_STACK: {
		if (offset < sizeof(_stack))
			return _stack[offset];
		else {
			error("getMem8(): Out of bounds read, memType: REF_STACK  offset: 0x%02X", offset);
			return 0;
		}
	}


	case REF_EBX:
		return EBX[offset];

	case REF_EDI:
		return _readAccess.getU8(offset);
	}
}

uint8 VM::Context::getMem8(const ValAddr &addr) {
    return getMem8(addr.getMemType(), addr.getOffset());
}

void VM::Context::setMem32(int memtype, uint32 offset, uint32 val) {
	switch (memtype) {
	default:
	case REF_UNK:
		break; // Set here breakpoint for find what is going wrong
	case REF_STACK: {
		if (offset < (sizeof(_stack) - 4))
			setU32(_stack + offset, val);
		else
			error("setMem32(): Out of bounds write, memType: REF_STACK  offset: 0x%02X", offset);
	} break;

	case REF_EBX:
		setU32(EBX + offset, val);
		break;

	case REF_EDI:
		_writeAccess.setU32(offset, val);
		break;
	}
}

void VM::Context::setMem32(const ValAddr &addr, uint32 val) {
    setMem32(addr.getMemType(), addr.getOffset(), val);
}

void VM::Context::setMem8(int memtype, uint32 offset, uint8 val) {
	switch (memtype) {
	default:
	case REF_UNK:
		break; // Set here breakpoint for find what is going wrong
	case REF_STACK:	{
		if (offset < sizeof(_stack))
			_stack[offset] = val;
		else
			error("setMem8(): Out of bounds write, memType: REF_STACK  offset: 0x%02X", offset);
	} break;

	case REF_EBX:
		EBX[offset] = val;
		break;

	case REF_EDI:
		_writeAccess.setU8(offset, val);
		break;
	}
}

void VM::Context::setMem8(const ValAddr &addr, uint8 val) {
    setMem8(addr.getMemType(), addr.getOffset(), val);
}

void VM::clearMemory() {
	_memMap.clear();
	_memAccess.reset();
}

void VM::writeMemory(uint32 address, const byte* data, uint32 dataSize) {

	uint32 blockAddr = address & (~0xff);
	uint32 pos = 0;
	uint32 remain = dataSize;

	//warning("Write memory at %x sz %x", address, dataSize);

	for (uint32 addr = blockAddr; addr < address + dataSize; addr += 0x100) {
		MemoryBlock &block = _memMap.getOrCreateVal(addr);

		block.address = addr; // update it

		uint32 copyCnt = (addr + 0x100) - (address + pos);
		if (copyCnt > remain)
			copyCnt = remain;

		uint32 blockPos = (address + pos) - addr;
		memcpy(block.data + blockPos, data + pos, copyCnt);

		pos += copyCnt;
		remain -= copyCnt;
	}
}

void VM::zeroMemory(uint32 address, uint32 count) {
	uint32 blockAddr = address & (~0xff);
	uint32 pos = 0;
	uint32 remain = count;

	for (uint32 addr = blockAddr; addr < address + count; addr += 0x100) {
		MemoryBlock *block = findMemoryBlock(addr);

		uint32 zeroCnt = (addr + 0x100) - (address + pos);
		if (zeroCnt > remain)
			zeroCnt = remain;

		uint32 blockPos = (address + pos) - addr;

		if (block)
			memset(block->data + blockPos, 0, zeroCnt);

		pos += zeroCnt;
		remain -= zeroCnt;
	}
}

VM::MemoryBlock *VM::findMemoryBlock(uint32 address) {
	Common::HashMap<uint32, MemoryBlock>::iterator it = _memMap.find(address & (~0xff));
	if (it == _memMap.end())
		return nullptr;

	return &it->_value;
}

VM::MemoryBlock *VM::createBlock(uint32 address) {
	MemoryBlock &blk = _memMap.getOrCreateVal(address & (~0xff));
	blk.address = address & (~0xff);
	return &blk;
}



Common::Array<byte> VM::readMemBlocks(uint32 address, uint32 count) {
	Common::Array<byte> data(count);

	readMemBlocks(data.data(), address, count);

	return data;
}

void VM::readMemBlocks(byte *dst, uint32 address, uint32 count) {
	MemoryBlock *blk = findMemoryBlock(address);

	uint32 pos = 0;
	uint32 blockAddr = address & (~0xff);
	uint32 remain = count;
	while (remain > 0) {
		uint32 dataCpyCount = (blockAddr + 0x100) - (address + pos);
		if (dataCpyCount > remain)
			dataCpyCount = remain;

		if (!blk) {
			memset(dst + pos, 0, dataCpyCount);
		} else {
			memcpy(dst + pos, blk->data + (address + pos - blk->address), dataCpyCount);
		}

		pos += dataCpyCount;
		remain -= dataCpyCount;
		blockAddr += 0x100;
		blk = findMemoryBlock(blockAddr);
	}
}

Common::String VM::readMemString(uint32 address, uint32 maxLen) {
	Common::String s;

	MemoryBlock *blk = findMemoryBlock(address);

	if (!blk)
		return s;

	uint32 pos = address - blk->address;
	char c = blk->data[pos];

	while (c != 0) {
		s += c;

		pos++;
		if (pos >= 0x100) {
			blk = findMemoryBlock(blk->address + 0x100);
			pos = 0;
		}

		if (!blk)
			break;

		c = blk->data[pos];
	}
	return s;
}

Common::String VM::Context::getString(int memtype, uint32 offset, uint32 maxLen) {
	switch (memtype) {
	default:
	case REF_UNK:
		return Common::String();

	case REF_STACK: {
		if (offset >= sizeof(_stack)) {
			error("getString(): Out of bounds read, memType: REF_STACK  offset: 0x%02X", offset);
			return Common::String();
		}

		Common::String s = Common::String((const char *)_stack + offset);
		if (s.size() > maxLen)
			s.erase(maxLen);
		return s;
	}

	case REF_EBX: {
		Common::String s = Common::String((const char *)EBX + offset);
		if (s.size() > maxLen)
			s.erase(maxLen);
		return s;
	}

	case REF_EDI:
		return _vm.readMemString(offset, maxLen);
	}
}

Common::String VM::Context::getString(const ValAddr &addr, uint32 maxLen) {
    return getString(addr.getMemType(), addr.getOffset(), maxLen);
}


Common::String VM::decodeOp(uint32 address, int *size) {
	Common::String tmp;

	MemAccess readmem(*this);

	int sz = 1;
	byte op = readmem.getU8(address);

	address++;

	switch (op) {
	default:
	case OP_EXIT:
		tmp = Common::String("EXIT");
		break;

	case OP_CMP_EQ:
		tmp = Common::String("EAX =  EDX == EAX (CMP_EQ)");
		break;

	case OP_CMP_NE:
		tmp = Common::String("EAX =  EDX != EAX (CMP_NE)");
		break;

	case OP_CMP_LE:
		tmp = Common::String("EAX =  EDX < EAX (CMP_LE) //signed");
		break;

	case OP_CMP_LEQ:
		tmp = Common::String("EAX =  EDX <= EAX (CMP_LEQ) //signed");
		break;

	case OP_CMP_GR:
		tmp = Common::String("EAX =  EDX > EAX (CMP_GR) //signed");
		break;

	case OP_CMP_GREQ:
		tmp = Common::String("EAX =  EDX >= EAX (CMP_GREQ) //signed");
		break;

	case OP_CMP_NAE:
		tmp = Common::String("EAX =  EDX < EAX (CMP_NAE) //unsigned");
		break;

	case OP_CMP_NA:
		tmp = Common::String("EAX =  EDX <= EAX (CMP_NA) //unsigned");
		break;

	case OP_CMP_A:
		tmp = Common::String("EAX =  EDX > EAX (CMP_A) //unsigned");
		break;

	case OP_CMP_AE:
		tmp = Common::String("EAX =  EDX >= EAX (CMP_AE) //unsigned");
		break;

	case OP_BRANCH:
		tmp = Common::String::format("BR %x", address + (int32)readmem.getU32(address));
		sz += 4;
		break;

	case OP_JMP:
		tmp = Common::String::format("JMP %x", address + (int32)readmem.getU32(address));
		sz += 4;
		break;

	case OP_SP_ADD:
		tmp = Common::String::format("ADD SP, %x", (int32)readmem.getU32(address));
		sz += 4;
		break;

	case OP_MOV_EDI_ECX_AL:
		tmp = Common::String::format("MOV byte ptr[EDI + %x], AL", (int32)readmem.getU32(address));
		sz += 4;
		break;

	case OP_MOV_EBX_ECX_AL:
		tmp = Common::String::format("MOV byte ptr[EBX + %x], AL", (int32)readmem.getU32(address));
		sz += 4;
		break;

	case OP_MOV_EDI_ECX_EAX:
		tmp = Common::String::format("MOV dword ptr[EDI + %x], EAX", (int32)readmem.getU32(address));
		sz += 4;
		break;

	case OP_MOV_EBX_ECX_EAX:
		tmp = Common::String::format("MOV dword ptr[EBX + %x], EAX", (int32)readmem.getU32(address));
		sz += 4;
		break;

	case OP_RET:
		tmp = Common::String("RET");
		break;

	case OP_RETX:
		tmp = Common::String::format("RET%x", readmem.getU32(address));
		sz += 4;
		break;

	case OP_MOV_EDX_EAX:
		tmp = Common::String("MOV EDX, EAX");
		break;

	case OP_ADD_EAX_EDX:
		tmp = Common::String("ADD EAX, EDX");
		break;

	case OP_MUL:
		tmp = Common::String("MUL EDX");
		break;

	case OP_OR:
		tmp = Common::String("OR EDX");
		break;

	case OP_XOR:
		tmp = Common::String("XOR EDX");
		break;

	case OP_AND:
		tmp = Common::String("AND EDX");
		break;

	case OP_NEG:
		tmp = Common::String("NEG EAX");
		break;

	case OP_SAR:
		tmp = Common::String("SAR EAX, EDX,EAX // edx>>eax");
		break;

	case OP_SHL:
		tmp = Common::String("SHL EAX, EDX,EAX // edx<<eax");
		break;

	case OP_LOAD:
		tmp = Common::String::format("MOV EAX, %x", readmem.getU32(address));
		sz += 4;
		break;

	case OP_INC:
		tmp = Common::String("INC EAX");
		break;

	case OP_DEC:
		tmp = Common::String("DEC EAX");
		break;

	case OP_XCHG:
		tmp = Common::String("XCHG EAX,EDX");
		break;

	case OP_PUSH_EAX:
		tmp = Common::String("PUSH EAX");
		break;

	case OP_POP_EDX:
		tmp = Common::String("POP EDX");
		break;

	case OP_LOAD_OFFSET_EDI:
	case OP_LOAD_OFFSET_EDI2:
		tmp = Common::String::format("LEA EAX, [EDI + %x]", readmem.getU32(address));
		sz += 4;
		break;

	case OP_LOAD_OFFSET_EBX:
		tmp = Common::String::format("LEA EAX, [EBX + %x]", readmem.getU32(address));
		sz += 4;
		break;

	case OP_LOAD_OFFSET_ESP:
		tmp = Common::String::format("LEA EAX, [SP + %x]", readmem.getU32(address));
		sz += 4;
		break;

	case OP_MOV_PTR_EDX_AL:
		tmp = Common::String("MOV byte ptr [EDX], AL");
		break;

	case OP_MOV_PTR_EDX_EAX:
		tmp = Common::String("MOV dword ptr [EDX], EAX");
		break;

	case OP_SHL_2:
		tmp = Common::String("SHL EAX, 2");
		break;

	case OP_ADD_4:
		tmp = Common::String("ADD EAX, 4");
		break;

	case OP_SUB_4:
		tmp = Common::String("SUB EAX, 4");
		break;

	case OP_XCHG_ESP:
		tmp = Common::String("XCHG EAX, [SP]");
		break;

	case OP_NEG_ADD:
		tmp = Common::String("EAX = EDX - EAX (OP_NEG_ADD)");
		break;

	case OP_DIV:
		tmp = Common::String("EAX = EDX / EAX  |   EDX = EDX %% EAX (DIV)");
		break;

	case OP_MOV_EAX_BPTR_EDI:
		tmp = Common::String::format("MOV EAX, byte ptr [EDI + %x]", readmem.getU32(address));
		sz += 4;
		break;

	case OP_MOV_EAX_BPTR_EBX:
		tmp = Common::String::format("MOV EAX, byte ptr [EBX + %x]", readmem.getU32(address));
		sz += 4;
		break;

	case OP_MOV_EAX_DPTR_EDI:
		tmp = Common::String::format("MOV EAX, dword ptr [EDI + %x]", readmem.getU32(address));
		sz += 4;
		break;

	case OP_MOV_EAX_DPTR_EBX:
		tmp = Common::String::format("MOV EAX, dword ptr [EBX + %x]", readmem.getU32(address));
		sz += 4;
		break;

	case OP_MOV_EAX_BPTR_EAX:
		tmp = Common::String("MOV EAX, byte ptr [EAX]");
		break;

	case OP_MOV_EAX_DPTR_EAX:
		tmp = Common::String("MOV EAX, dword ptr [EAX]");
		break;

	case OP_PUSH_ESI_ADD_EDI:
		tmp = Common::String::format("CALL %x", readmem.getU32(address));
		sz += 4;
		break;

	case OP_CALL_FUNC:
		tmp = Common::String::format("CALL FUNC %d", readmem.getU32(address));
		sz += 4;
		break;

	case OP_PUSH_ESI_SET_EDX_EDI:
		tmp = Common::String("CALL EDX");
		break;
	}

	if (size)
		*size = sz;

	return tmp;
}


Common::String VM::disassembly(uint32 address) {
	Common::String tmp;

	uint32 addr = address;
	MemAccess readmem(*this);

	while (true) {
		tmp += Common::String::format("%08x: ", addr);

		byte op = readmem.getU8(addr);

		int sz = 1;
		tmp += decodeOp(addr, &sz);
		tmp += "\n";

		addr += sz;

		if (op == OP_EXIT)
			break;
	}

	return tmp;
}


Common::String VM::opLog(const Common::Array<OpLog> &log) {
	Common::String tmp;

	for (const OpLog &l : log) {
		tmp += Common::String::format("%08x: SP:%04x OP:[%02d] ", l.addr, l.sp, l.op) + decodeOp(l.addr) + "\n";
	}

	Common::DumpFile f;

	if (f.open("oplog", true)) {
		f.writeString(tmp);
		f.flush();
		f.close();
	}

	return tmp;
}

void VM::printDisassembly(uint32 address) {
	Common::String tmp = disassembly(address);
	warning("%s", tmp.c_str());
}

}
