/* 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/stdafx.h" #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" #define FORM_CHUNK 0x4D524F46 #define TEXT_CHUNK 0x54584554 #define DATA_CHUNK 0x41544144 #define ORDR_CHUNK 0x5244524F namespace Kyra { ScriptHelper::ScriptHelper(KyraEngine *vm) : _vm(vm) { #define COMMAND(x) { &ScriptHelper::x, #x } // now we create a list of all Command/Opcode procs and so static CommandEntry commandProcs[] = { // 0x00 COMMAND(c1_jmpTo), COMMAND(c1_setRetValue), COMMAND(c1_pushRetOrPos), COMMAND(c1_push), // 0x04 COMMAND(c1_push), COMMAND(c1_pushVar), COMMAND(c1_pushBPNeg), COMMAND(c1_pushBPAdd), // 0x08 COMMAND(c1_popRetOrPos), COMMAND(c1_popVar), COMMAND(c1_popBPNeg), COMMAND(c1_popBPAdd), // 0x0C COMMAND(c1_addSP), COMMAND(c1_subSP), COMMAND(c1_execOpcode), COMMAND(c1_ifNotJmp), // 0x10 COMMAND(c1_negate), COMMAND(c1_eval), COMMAND(c1_setRetAndJmp) }; _commands = commandProcs; #undef COMMAND } bool ScriptHelper::loadScript(const char *filename, ScriptData *scriptData, const Common::Array *opcodes) { uint32 size = 0; uint8 *data = _vm->resource()->fileData(filename, &size); const byte *curData = data; uint32 formBlockSize = getFORMBlockSize(curData); if (formBlockSize == (uint32)-1) { delete [] data; error("No FORM chunk found in file: '%s'", filename); return false; } uint32 chunkSize = getIFFBlockSize(data, curData, size, TEXT_CHUNK); if (chunkSize != (uint32)-1) { scriptData->text = new byte[chunkSize]; if (!loadIFFBlock(data, curData, size, TEXT_CHUNK, scriptData->text, chunkSize)) { delete [] data; unloadScript(scriptData); error("Couldn't load TEXT chunk from file: '%s'", filename); return false; } } chunkSize = getIFFBlockSize(data, curData, size, ORDR_CHUNK); if (chunkSize == (uint32)-1) { delete [] data; unloadScript(scriptData); error("No ORDR chunk found in file: '%s'", filename); return false; } chunkSize >>= 1; scriptData->ordr = new uint16[chunkSize]; if (!loadIFFBlock(data, curData, size, ORDR_CHUNK, scriptData->ordr, chunkSize << 1)) { delete [] data; 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 = getIFFBlockSize(data, curData, size, DATA_CHUNK); if (chunkSize == (uint32)-1) { delete [] data; unloadScript(scriptData); error("No DATA chunk found in file: '%s'", filename); return false; } chunkSize >>= 1; scriptData->data = new uint16[chunkSize]; if (!loadIFFBlock(data, curData, size, DATA_CHUNK, scriptData->data, chunkSize << 1)) { delete [] data; 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; delete [] data; 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->gameFlags().platform == Common::kPlatformFMTowns) script->ip = &script->dataPtr->data[functionOffset+1]; else script->ip = &script->dataPtr->data[functionOffset]; 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; 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, "%s([%d/%u])", _commands[opcode].desc, _parameter, (uint)_parameter); (this->*(_commands[opcode].proc))(script); } return _continue; } uint32 ScriptHelper::getFORMBlockSize(const byte *&data) const { static const uint32 chunkName = FORM_CHUNK; if (READ_LE_UINT32(data) != chunkName) return (uint32)-1; data += 4; uint32 retValue = READ_BE_UINT32(data); data += 4; return retValue; } uint32 ScriptHelper::getIFFBlockSize(const byte *start, const byte *&data, uint32 maxSize, const uint32 chunkName) const { uint32 size = (uint32)-1; bool special = false; if (data == (start + maxSize)) data = start + 0x0C; while (data < (start + maxSize)) { uint32 chunk = READ_LE_UINT32(data); data += 4; uint32 size_temp = READ_BE_UINT32(data); data += 4; if (chunk != chunkName) { if (special) { data += (size_temp + 1) & 0xFFFFFFFE; } else { data = start + 0x0C; special = true; } } else { // kill our data data = start; size = size_temp; break; } } return size; } bool ScriptHelper::loadIFFBlock(const byte *start, const byte *&data, uint32 maxSize, const uint32 chunkName, void *loadTo, uint32 ptrSize) const { bool special = false; if (data == (start + maxSize)) data = start + 0x0C; while (data < (start + maxSize)) { uint32 chunk = READ_LE_UINT32(data); data += 4; uint32 chunkSize = READ_BE_UINT32(data); data += 4; if (chunk != chunkName) { if (special) { data += (chunkSize + 1) & 0xFFFFFFFE; } else { data = start + 0x0C; special = true; } } else { uint32 loadSize = 0; if (chunkSize < ptrSize) loadSize = chunkSize; else loadSize = ptrSize; memcpy(loadTo, data, loadSize); chunkSize = (chunkSize + 1) & 0xFFFFFFFE; if (chunkSize > loadSize) data += (chunkSize - loadSize); return true; } } return false; } #pragma mark - #pragma mark - Command implementations #pragma mark - void ScriptHelper::c1_jmpTo(ScriptState* script) { script->ip = script->dataPtr->data + _parameter; } void ScriptHelper::c1_setRetValue(ScriptState* script) { script->retValue = _parameter; } void ScriptHelper::c1_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::c1_push(ScriptState* script) { script->stack[--script->sp] = _parameter; } void ScriptHelper::c1_pushVar(ScriptState* script) { script->stack[--script->sp] = script->variables[_parameter]; } void ScriptHelper::c1_pushBPNeg(ScriptState* script) { script->stack[--script->sp] = script->stack[(-(int32)(_parameter + 2)) + script->bp]; } void ScriptHelper::c1_pushBPAdd(ScriptState* script) { script->stack[--script->sp] = script->stack[(_parameter - 1) + script->bp]; } void ScriptHelper::c1_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::c1_popVar(ScriptState* script) { script->variables[_parameter] = script->stack[script->sp++]; } void ScriptHelper::c1_popBPNeg(ScriptState* script) { script->stack[(-(int32)(_parameter + 2)) + script->bp] = script->stack[script->sp++]; } void ScriptHelper::c1_popBPAdd(ScriptState* script) { script->stack[(_parameter - 1) + script->bp] = script->stack[script->sp++]; } void ScriptHelper::c1_addSP(ScriptState* script) { script->sp += _parameter; } void ScriptHelper::c1_subSP(ScriptState* script) { script->sp -= _parameter; } void ScriptHelper::c1_execOpcode(ScriptState* script) { uint8 opcode = _parameter; assert(script->dataPtr->opcodes); assert(opcode < script->dataPtr->opcodes->size()); if ((*script->dataPtr->opcodes)[opcode]) { script->retValue = (*(*script->dataPtr->opcodes)[opcode])(script); } else { script->retValue = 0; warning("calling unimplemented opcode(0x%.02X)", opcode); } } void ScriptHelper::c1_ifNotJmp(ScriptState* script) { if (!script->stack[script->sp++]) { _parameter &= 0x7FFF; script->ip = script->dataPtr->data + _parameter; } } void ScriptHelper::c1_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::c1_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::c1_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