/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "common/debug.h" #include "common/endian.h" #include "common/stream.h" #include "toon/toon.h" #include "toon/script.h" namespace Toon { EMCInterpreter::EMCInterpreter(ToonEngine *vm) : _vm(vm), _scriptData(0), _filename(0) { #define OPCODE(x) { &EMCInterpreter::x, #x } static const OpcodeEntry opcodes[] = { // 0x00 OPCODE(op_jmp), OPCODE(op_setRetValue), OPCODE(op_pushRetOrPos), OPCODE(op_push), // 0x04 OPCODE(op_push), OPCODE(op_pushReg), OPCODE(op_pushBPNeg), OPCODE(op_pushBPAdd), // 0x08 OPCODE(op_popRetOrPos), OPCODE(op_popReg), OPCODE(op_popBPNeg), OPCODE(op_popBPAdd), // 0x0C OPCODE(op_addSP), OPCODE(op_subSP), OPCODE(op_sysCall), OPCODE(op_ifNotJmp), // 0x10 OPCODE(op_negate), OPCODE(op_eval), OPCODE(op_setRetAndJmp) }; _opcodes = opcodes; #undef OPCODE _parameter = 0; } EMCInterpreter::~EMCInterpreter() { } bool EMCInterpreter::callback(Common::IFFChunk &chunk) { switch (chunk._type) { case MKTAG('T','E','X','T'): delete[] _scriptData->text; _scriptData->text = new byte[chunk._size]; assert(_scriptData->text); if (chunk._stream->read(_scriptData->text, chunk._size) != chunk._size) error("Couldn't read TEXT chunk from file '%s'", _filename); break; case MKTAG('O','R','D','R'): delete[] _scriptData->ordr; _scriptData->ordr = new uint16[chunk._size >> 1]; assert(_scriptData->ordr); if (chunk._stream->read(_scriptData->ordr, chunk._size) != chunk._size) error("Couldn't read ORDR chunk from file '%s'", _filename); for (int i = (chunk._size >> 1) - 1; i >= 0; --i) _scriptData->ordr[i] = READ_BE_UINT16(&_scriptData->ordr[i]); break; case MKTAG('D','A','T','A'): delete[] _scriptData->data; _scriptData->data = new uint16[chunk._size >> 1]; assert(_scriptData->data); if (chunk._stream->read(_scriptData->data, chunk._size) != chunk._size) error("Couldn't read DATA chunk from file '%s'", _filename); for (int i = (chunk._size >> 1) - 1; i >= 0; --i) _scriptData->data[i] = READ_BE_UINT16(&_scriptData->data[i]); break; default: warning("Unexpected chunk '%s' of size %d found in file '%s'", Common::tag2string(chunk._type).c_str(), chunk._size, _filename); } return false; } bool EMCInterpreter::load(const char *filename, EMCData *scriptData, const Common::Array *opcodes) { Common::SeekableReadStream *stream = _vm->resources()->openFile(filename); if (!stream) { error("Couldn't open script file '%s'", filename); return false; // for compilers that don't support NORETURN } memset(scriptData, 0, sizeof(EMCData)); _scriptData = scriptData; _filename = filename; IFFParser iff(*stream); Common::Functor1Mem< Common::IFFChunk &, bool, EMCInterpreter > c(this, &EMCInterpreter::callback); iff.parse(c); if (!_scriptData->ordr) error("No ORDR chunk found in file: '%s'", filename); if (!_scriptData->data) error("No DATA chunk found in file: '%s'", filename); if (stream->err()) error("Read error while parsing file '%s'", filename); delete stream; _scriptData->sysFuncs = opcodes; Common::strlcpy(_scriptData->filename, filename, 13); _scriptData = 0; _filename = 0; return true; } void EMCInterpreter::unload(EMCData *data) { if (!data) return; delete[] data->text; data->text = NULL; delete[] data->ordr; data->ordr = NULL; delete[] data->data; data->data = NULL; } void EMCInterpreter::init(EMCState *scriptStat, const EMCData *data) { scriptStat->dataPtr = data; scriptStat->ip = 0; scriptStat->stack[EMCState::kStackLastEntry] = 0; scriptStat->bp = EMCState::kStackSize + 1; scriptStat->sp = EMCState::kStackLastEntry; scriptStat->running = false; } bool EMCInterpreter::start(EMCState *script, int function) { if (!script->dataPtr) return false; uint16 functionOffset = script->dataPtr->ordr[function]; if (functionOffset == 0xFFFF) return false; script->ip = &script->dataPtr->data[functionOffset + 1]; return true; } bool EMCInterpreter::isValid(EMCState *script) { if (!script->ip || !script->dataPtr || _vm->shouldQuitGame()) return false; return true; } bool EMCInterpreter::run(EMCState *script) { if (script->running) return false; _parameter = 0; if (!script->ip) return false; script->running = true; // Should be no Problem at all to cast to uint32 here, since that's the biggest ptrdiff the original // would allow, of course that's not realistic to happen to be somewhere near the limit of uint32 anyway. const uint32 instOffset = (uint32)((const byte *)script->ip - (const byte *)script->dataPtr->data); int16 code = *script->ip++; int16 opcode = (code >> 8) & 0x1F; if (code & 0x8000) { opcode = 0; _parameter = code & 0x7FFF; } else if (code & 0x4000) { _parameter = (int8)(code); } else if (code & 0x2000) { _parameter = *script->ip++; } else { _parameter = 0; } if (opcode > 18) { error("Unknown script opcode: %d in file '%s' at offset 0x%.08X", opcode, script->dataPtr->filename, instOffset); } else { static bool EMCDebug = false; if (EMCDebug) debugC(5, 0, "[0x%.08X] EMCInterpreter::%s([%d/%u])", instOffset * 2, _opcodes[opcode].desc, _parameter, (uint)_parameter); //printf( "[0x%.08X] EMCInterpreter::%s([%d/%u])\n", instOffset, _opcodes[opcode].desc, _parameter, (uint)_parameter); (this->*(_opcodes[opcode].proc))(script); } script->running = false; return (script->ip != 0); } #pragma mark - #pragma mark - Command implementations #pragma mark - void EMCInterpreter::op_jmp(EMCState *script) { script->ip = script->dataPtr->data + _parameter; } void EMCInterpreter::op_setRetValue(EMCState *script) { script->retValue = _parameter; } void EMCInterpreter::op_pushRetOrPos(EMCState *script) { switch (_parameter) { case 0: script->stack[--script->sp] = script->retValue; break; case 1: script->stack[--script->sp] = script->ip - script->dataPtr->data + 1; script->stack[--script->sp] = script->bp; script->bp = script->sp + 2; break; default: script->ip = 0; } } void EMCInterpreter::op_push(EMCState *script) { script->stack[--script->sp] = _parameter; } void EMCInterpreter::op_pushReg(EMCState *script) { script->stack[--script->sp] = script->regs[_parameter]; } void EMCInterpreter::op_pushBPNeg(EMCState *script) { script->stack[--script->sp] = script->stack[(-(int32)(_parameter + 2)) + script->bp]; } void EMCInterpreter::op_pushBPAdd(EMCState *script) { script->stack[--script->sp] = script->stack[(_parameter - 1) + script->bp]; } void EMCInterpreter::op_popRetOrPos(EMCState *script) { switch (_parameter) { case 0: script->retValue = script->stack[script->sp++]; break; case 1: if (script->sp >= EMCState::kStackLastEntry) { script->ip = 0; } else { script->bp = script->stack[script->sp++]; script->ip = script->dataPtr->data + script->stack[script->sp++]; } break; default: script->ip = 0; } } void EMCInterpreter::op_popReg(EMCState *script) { script->regs[_parameter] = script->stack[script->sp++]; } void EMCInterpreter::op_popBPNeg(EMCState *script) { script->stack[(-(int32)(_parameter + 2)) + script->bp] = script->stack[script->sp++]; } void EMCInterpreter::op_popBPAdd(EMCState *script) { script->stack[(_parameter - 1) + script->bp] = script->stack[script->sp++]; } void EMCInterpreter::op_addSP(EMCState *script) { script->sp += _parameter; } void EMCInterpreter::op_subSP(EMCState *script) { script->sp -= _parameter; } void EMCInterpreter::op_sysCall(EMCState *script) { const uint8 id = _parameter; assert(script->dataPtr->sysFuncs); assert(id < script->dataPtr->sysFuncs->size()); if ((*script->dataPtr->sysFuncs)[id] && ((*script->dataPtr->sysFuncs)[id])->isValid()) { script->retValue = (*(*script->dataPtr->sysFuncs)[id])(script); } else { script->retValue = 0; warning("Unimplemented system call 0x%.02X/%d used in file '%s'", id, id, script->dataPtr->filename); } } void EMCInterpreter::op_ifNotJmp(EMCState *script) { if (!script->stack[script->sp++]) { _parameter &= 0x7FFF; script->ip = script->dataPtr->data + _parameter; } } void EMCInterpreter::op_negate(EMCState *script) { int16 value = script->stack[script->sp]; switch (_parameter) { case 0: if (!value) script->stack[script->sp] = 1; else script->stack[script->sp] = 0; break; case 1: script->stack[script->sp] = -value; break; case 2: script->stack[script->sp] = ~value; break; default: warning("Unknown negation func: %d", _parameter); script->ip = 0; } } void EMCInterpreter::op_eval(EMCState *script) { int16 ret = 0; bool error = false; int16 val1 = script->stack[script->sp++]; int16 val2 = script->stack[script->sp++]; switch (_parameter) { case 0: ret = (val2 && val1) ? 1 : 0; break; case 1: ret = (val2 || val1) ? 1 : 0; break; case 2: ret = (val1 == val2) ? 1 : 0; break; case 3: ret = (val1 != val2) ? 1 : 0; break; case 4: ret = (val1 > val2) ? 1 : 0; break; case 5: ret = (val1 >= val2) ? 1 : 0; break; case 6: ret = (val1 < val2) ? 1 : 0; break; case 7: ret = (val1 <= val2) ? 1 : 0; break; case 8: ret = val1 + val2; break; case 9: ret = val2 - val1; break; case 10: ret = val1 * val2; break; case 11: ret = val2 / val1; break; case 12: ret = val2 >> val1; break; case 13: ret = val2 << val1; break; case 14: ret = val1 & val2; break; case 15: ret = val1 | val2; break; case 16: ret = val2 % val1; break; case 17: ret = val1 ^ val2; break; default: warning("Unknown evaluate func: %d", _parameter); error = true; } if (error) script->ip = 0; else script->stack[--script->sp] = ret; } void EMCInterpreter::op_setRetAndJmp(EMCState *script) { if (script->sp >= EMCState::kStackLastEntry) { script->ip = 0; } else { script->retValue = script->stack[script->sp++]; uint16 temp = script->stack[script->sp++]; script->stack[EMCState::kStackLastEntry] = 0; script->ip = &script->dataPtr->data[temp]; } } void EMCInterpreter::saveState(EMCState *script, Common::WriteStream *stream) { stream->writeSint16LE(script->bp); stream->writeSint16LE(script->sp); if (!script->ip) { stream->writeSint16LE(-1); } else { stream->writeSint16LE(script->ip - script->dataPtr->data); } for (int32 i = 0; i < EMCState::kStackSize; i++) { stream->writeSint16LE(script->stack[i]); } for (int32 i = 0; i < 30; i++) { stream->writeSint16LE(script->regs[i]); } stream->writeSint16LE(script->retValue); stream->writeByte(script->running); } void EMCInterpreter::loadState(EMCState *script, Common::ReadStream *stream) { script->bp = stream->readSint16LE(); script->sp = stream->readSint16LE(); int16 scriptIp = stream->readSint16LE(); if (scriptIp == -1) { script->ip = 0; } else { script->ip = scriptIp + script->dataPtr->data; } for (int32 i = 0; i < EMCState::kStackSize; i++) { script->stack[i] = stream->readSint16LE(); } for (int32 i = 0; i < 30; i++) { script->regs[i] = stream->readSint16LE(); } script->retValue = stream->readSint16LE(); script->running = stream->readByte(); } } // End of namespace Toon