/* 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 . * */ #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 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; EAX.setVal( (int32)EDX.getVal() / (int32)ECX.getVal() ); EDX.setVal( (int32)EDX.getVal() % (int32)ECX.getVal() ); 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 (int 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: return getU32(_stack + offset); 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: return _stack[offset]; 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: setU32(_stack + offset, val); 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: _stack[offset] = val; 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::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 VM::readMemBlocks(uint32 address, uint32 count) { Common::Array 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: { 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< &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()); } }