/* 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. * * $URL$ * $Id$ * */ #include "common/endian.h" #include "common/stream.h" #include "common/util.h" #include "common/system.h" #include "kyra/kyra.h" #include "kyra/resource.h" #include "kyra/script.h" namespace Kyra { ScriptHelper::ScriptHelper(KyraEngine *vm) : _vm(vm) { #define COMMAND(x) { &ScriptHelper::x, #x } static CommandEntry commandProcs[] = { // 0x00 COMMAND(cmd_jmpTo), COMMAND(cmd_setRetValue), COMMAND(cmd_pushRetOrPos), COMMAND(cmd_push), // 0x04 COMMAND(cmd_push), COMMAND(cmd_pushReg), COMMAND(cmd_pushBPNeg), COMMAND(cmd_pushBPAdd), // 0x08 COMMAND(cmd_popRetOrPos), COMMAND(cmd_popReg), COMMAND(cmd_popBPNeg), COMMAND(cmd_popBPAdd), // 0x0C COMMAND(cmd_addSP), COMMAND(cmd_subSP), COMMAND(cmd_execOpcode), COMMAND(cmd_ifNotJmp), // 0x10 COMMAND(cmd_negate), COMMAND(cmd_eval), COMMAND(cmd_setRetAndJmp) }; _commands = commandProcs; #undef COMMAND } bool ScriptHelper::loadScript(const char *filename, ScriptData *scriptData, const Common::Array *opcodes) { ScriptFileParser file(filename, _vm->resource()); if (!file) { error("Couldn't open script file '%s'", filename); return false; } uint32 formBlockSize = file.getFORMBlockSize(); if (formBlockSize == (uint32)-1) { error("No FORM chunk found in file: '%s'", filename); return false; } uint32 chunkSize = file.getIFFBlockSize(TEXT_CHUNK); if (chunkSize != (uint32)-1) { scriptData->text = new byte[chunkSize]; if (!file.loadIFFBlock(TEXT_CHUNK, scriptData->text, chunkSize)) { unloadScript(scriptData); error("Couldn't load TEXT chunk from file: '%s'", filename); return false; } } chunkSize = file.getIFFBlockSize(ORDR_CHUNK); if (chunkSize == (uint32)-1) { unloadScript(scriptData); error("No ORDR chunk found in file: '%s'", filename); return false; } chunkSize >>= 1; scriptData->ordr = new uint16[chunkSize]; if (!file.loadIFFBlock(ORDR_CHUNK, scriptData->ordr, chunkSize << 1)) { unloadScript(scriptData); error("Couldn't load ORDR chunk from file: '%s'", filename); return false; } while (chunkSize--) scriptData->ordr[chunkSize] = READ_BE_UINT16(&scriptData->ordr[chunkSize]); chunkSize = file.getIFFBlockSize(DATA_CHUNK); if (chunkSize == (uint32)-1) { unloadScript(scriptData); error("No DATA chunk found in file: '%s'", filename); return false; } chunkSize >>= 1; scriptData->data = new uint16[chunkSize]; if (!file.loadIFFBlock(DATA_CHUNK, scriptData->data, chunkSize << 1)) { unloadScript(scriptData); error("Couldn't load DATA chunk from file: '%s'", filename); return false; } scriptData->dataSize = chunkSize; while (chunkSize--) scriptData->data[chunkSize] = READ_BE_UINT16(&scriptData->data[chunkSize]); scriptData->opcodes = opcodes; return true; } void ScriptHelper::unloadScript(ScriptData *data) { if (!data) return; delete [] data->text; delete [] data->ordr; delete [] data->data; data->text = 0; data->ordr = data->data = 0; } void ScriptHelper::initScript(ScriptState *scriptStat, const ScriptData *data) { scriptStat->dataPtr = data; scriptStat->ip = 0; scriptStat->stack[60] = 0; scriptStat->bp = 62; scriptStat->sp = 60; } bool ScriptHelper::startScript(ScriptState *script, int function) { if (!script->dataPtr) return false; uint16 functionOffset = script->dataPtr->ordr[function]; if (functionOffset == 0xFFFF) return false; if (_vm->game() == GI_KYRA1) { if (_vm->gameFlags().platform == Common::kPlatformFMTowns || _vm->gameFlags().platform == Common::kPlatformPC98) script->ip = &script->dataPtr->data[functionOffset+1]; else script->ip = &script->dataPtr->data[functionOffset]; } else { script->ip = &script->dataPtr->data[functionOffset+1]; } return true; } bool ScriptHelper::validScript(ScriptState *script) { if (!script->ip || !script->dataPtr || _vm->quit()) return false; return true; } bool ScriptHelper::runScript(ScriptState *script) { _parameter = 0; _continue = true; if (!script->ip) return false; // 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("Script unknown command: %d", opcode); } else { debugC(5, kDebugLevelScript, "[0x%.08X] %s([%d/%u])", instOffset, _commands[opcode].desc, _parameter, (uint)_parameter); (this->*(_commands[opcode].proc))(script); } return _continue; } #pragma mark - #pragma mark - ScriptFileParser implementation #pragma mark - void ScriptFileParser::setFile(const char *filename, Resource *res) { destroy(); _stream = res->getFileStream(filename); assert(_stream); _startOffset = 0; _endOffset = _stream->size(); } void ScriptFileParser::destroy() { delete _stream; _stream = 0; _startOffset = _endOffset = 0; } uint32 ScriptFileParser::getFORMBlockSize() { uint32 oldOffset = _stream->pos(); uint32 data = _stream->readUint32LE(); if (data != FORM_CHUNK) { _stream->seek(oldOffset); return (uint32)-1; } data = _stream->readUint32BE(); return data; } uint32 ScriptFileParser::getIFFBlockSize(const uint32 chunkName) { uint32 size = (uint32)-1; _stream->seek(_startOffset + 0x0C); while (_stream->pos() < _endOffset) { uint32 chunk = _stream->readUint32LE(); uint32 size_temp = _stream->readUint32BE(); if (chunk != chunkName) { _stream->seek((size_temp + 1) & (~1), SEEK_CUR); assert(_stream->pos() <= _endOffset); } else { size = size_temp; break; } } return size; } bool ScriptFileParser::loadIFFBlock(const uint32 chunkName, void *loadTo, uint32 ptrSize) { _stream->seek(_startOffset + 0x0C); while (_stream->pos() < _endOffset) { uint32 chunk = _stream->readUint32LE(); uint32 chunkSize = _stream->readUint32BE(); if (chunk != chunkName) { _stream->seek((chunkSize + 1) & (~1), SEEK_CUR); assert(_stream->pos() <= _endOffset); } else { uint32 loadSize = 0; loadSize = MIN(ptrSize, chunkSize); _stream->read(loadTo, loadSize); return true; } } return false; } #pragma mark - #pragma mark - Command implementations #pragma mark - void ScriptHelper::cmd_jmpTo(ScriptState* script) { script->ip = script->dataPtr->data + _parameter; } void ScriptHelper::cmd_setRetValue(ScriptState* script) { script->retValue = _parameter; } void ScriptHelper::cmd_pushRetOrPos(ScriptState* 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: _continue = false; script->ip = 0; break; } } void ScriptHelper::cmd_push(ScriptState* script) { script->stack[--script->sp] = _parameter; } void ScriptHelper::cmd_pushReg(ScriptState* script) { script->stack[--script->sp] = script->regs[_parameter]; } void ScriptHelper::cmd_pushBPNeg(ScriptState* script) { script->stack[--script->sp] = script->stack[(-(int32)(_parameter + 2)) + script->bp]; } void ScriptHelper::cmd_pushBPAdd(ScriptState* script) { script->stack[--script->sp] = script->stack[(_parameter - 1) + script->bp]; } void ScriptHelper::cmd_popRetOrPos(ScriptState* script) { switch (_parameter) { case 0: script->retValue = script->stack[script->sp++]; break; case 1: if (script->sp >= 60) { _continue = false; script->ip = 0; } else { script->bp = script->stack[script->sp++]; script->ip = script->dataPtr->data + script->stack[script->sp++]; } break; default: _continue = false; script->ip = 0; break; } } void ScriptHelper::cmd_popReg(ScriptState* script) { script->regs[_parameter] = script->stack[script->sp++]; } void ScriptHelper::cmd_popBPNeg(ScriptState* script) { script->stack[(-(int32)(_parameter + 2)) + script->bp] = script->stack[script->sp++]; } void ScriptHelper::cmd_popBPAdd(ScriptState* script) { script->stack[(_parameter - 1) + script->bp] = script->stack[script->sp++]; } void ScriptHelper::cmd_addSP(ScriptState* script) { script->sp += _parameter; } void ScriptHelper::cmd_subSP(ScriptState* script) { script->sp -= _parameter; } void ScriptHelper::cmd_execOpcode(ScriptState* script) { uint8 opcode = _parameter; assert(script->dataPtr->opcodes); assert(opcode < script->dataPtr->opcodes->size()); if ((*script->dataPtr->opcodes)[opcode] && ((*script->dataPtr->opcodes)[opcode])->isValid()) { script->retValue = (*(*script->dataPtr->opcodes)[opcode])(script); } else { script->retValue = 0; warning("calling unimplemented opcode(0x%.02X/%d)", opcode, opcode); } } void ScriptHelper::cmd_ifNotJmp(ScriptState* script) { if (!script->stack[script->sp++]) { _parameter &= 0x7FFF; script->ip = script->dataPtr->data + _parameter; } } void ScriptHelper::cmd_negate(ScriptState* 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: _continue = false; break; } } void ScriptHelper::cmd_eval(ScriptState* script) { int16 ret = 0; bool error = false; int16 val1 = script->stack[script->sp++]; int16 val2 = script->stack[script->sp++]; switch (_parameter) { case 0: if (!val2 || !val1) ret = 0; else ret = 1; break; case 1: if (val2 || val1) ret = 1; else ret = 0; break; case 2: if (val1 == val2) ret = 1; else ret = 0; break; case 3: if (val1 != val2) ret = 1; else ret = 0; break; case 4: if (val1 > val2) ret = 1; else ret = 0; break; case 5: if (val1 >= val2) ret = 1; else ret = 0; break; case 6: if (val1 < val2) ret = 1; else ret = 0; break; case 7: if (val1 <= val2) ret = 1; else ret = 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; break; } if (error) { script->ip = 0; _continue = false; } else { script->stack[--script->sp] = ret; } } void ScriptHelper::cmd_setRetAndJmp(ScriptState* script) { if (script->sp >= 60) { _continue = false; script->ip = 0; } else { script->retValue = script->stack[script->sp++]; uint16 temp = script->stack[script->sp++]; script->stack[60] = 0; script->ip = &script->dataPtr->data[temp]; } } } // end of namespace Kyra