diff options
author | Filippos Karapetis | 2010-06-27 20:38:43 +0000 |
---|---|---|
committer | Filippos Karapetis | 2010-06-27 20:38:43 +0000 |
commit | bb992b0b93f3ee9dfad6524fd0721a350f12085a (patch) | |
tree | 410ea12cc90e24f2f05b6f16d38650024b2cb01a /engines/sci/engine/script.cpp | |
parent | 0436f2823d7c83fa76d68fba3c18a7fcc4a29173 (diff) | |
download | scummvm-rg350-bb992b0b93f3ee9dfad6524fd0721a350f12085a.tar.gz scummvm-rg350-bb992b0b93f3ee9dfad6524fd0721a350f12085a.tar.bz2 scummvm-rg350-bb992b0b93f3ee9dfad6524fd0721a350f12085a.zip |
SCI: Moved all the script-related code inside script.cpp/.h, and all script opcode-related code inside vm.cpp/.h
svn-id: r50396
Diffstat (limited to 'engines/sci/engine/script.cpp')
-rw-r--r-- | engines/sci/engine/script.cpp | 434 |
1 files changed, 330 insertions, 104 deletions
diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index 800460bfc2..748a8f9140 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -35,127 +35,353 @@ namespace Sci { -#define END Script_None - -opcode_format g_opcode_formats[128][4] = { - /*00*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*04*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*08*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*0C*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*10*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*14*/ - {Script_None}, {Script_None}, {Script_None}, {Script_SRelative, END}, - /*18*/ - {Script_SRelative, END}, {Script_SRelative, END}, {Script_SVariable, END}, {Script_None}, - /*1C*/ - {Script_SVariable, END}, {Script_None}, {Script_None}, {Script_Variable, END}, - /*20*/ - {Script_SRelative, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_SVariable, Script_Byte, END}, - /*24 (24=ret)*/ - {Script_End}, {Script_Byte, END}, {Script_Invalid}, {Script_Invalid}, - /*28*/ - {Script_Variable, END}, {Script_Invalid}, {Script_Byte, END}, {Script_Variable, Script_Byte, END}, - /*2C*/ - {Script_SVariable, END}, {Script_SVariable, Script_Variable, END}, {Script_None}, {Script_Invalid}, - /*30*/ - {Script_None}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, - /*34*/ - {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, - /*38*/ - {Script_Property, END}, {Script_SRelative, END}, {Script_SRelative, END}, {Script_None}, - /*3C*/ - {Script_None}, {Script_None}, {Script_None}, {Script_Word}, - /*40-4F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - /*50-5F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - /*60-6F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - /*70-7F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END} -}; -#undef END - -// TODO: script_adjust_opcode_formats should probably be part of the -// constructor (?) of a VirtualMachine or a ScriptManager class. -void script_adjust_opcode_formats() { - if (g_sci->_features->detectLofsType() != SCI_VERSION_0_EARLY) { - g_opcode_formats[op_lofsa][0] = Script_Offset; - g_opcode_formats[op_lofss][0] = Script_Offset; +Script::Script() : SegmentObj(SEG_TYPE_SCRIPT) { + _nr = 0; + _buf = NULL; + _bufSize = 0; + _scriptSize = 0; + _heapSize = 0; + + _synonyms = NULL; + _heapStart = NULL; + _exportTable = NULL; + + _localsOffset = 0; + _localsSegment = 0; + _localsBlock = NULL; + _localsCount = 0; + + _markedAsDeleted = false; +} + +Script::~Script() { + freeScript(); +} + +void Script::freeScript() { + free(_buf); + _buf = NULL; + _bufSize = 0; + + _objects.clear(); +} + +void Script::init(int script_nr, ResourceManager *resMan) { + Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, script_nr), 0); + + _localsOffset = 0; + _localsBlock = NULL; + _localsCount = 0; + + _markedAsDeleted = false; + + _nr = script_nr; + _buf = 0; + _heapStart = 0; + + _scriptSize = script->size; + _bufSize = script->size; + _heapSize = 0; + + _lockers = 1; + + if (getSciVersion() == SCI_VERSION_0_EARLY) { + _bufSize += READ_LE_UINT16(script->data) * 2; + } else if (getSciVersion() >= SCI_VERSION_1_1) { + /** + * In SCI11, the heap was in a separate space from the script. + * We append it to the end of the script, and adjust addressing accordingly. + * However, since we address the heap with a 16-bit pointer, the combined + * size of the stack and the heap must be 64KB. So far this has worked + * for SCI11, SCI2 and SCI21 games. SCI3 games use a different script format, + * and theoretically they can exceed the 64KB boundary using relocation. + */ + Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, script_nr), 0); + _bufSize += heap->size; + _heapSize = heap->size; + + // Ensure that the start of the heap resource can be word-aligned. + if (script->size & 2) { + _bufSize++; + _scriptSize++; + } + + // As mentioned above, the script and the heap together should not exceed 64KB + if (script->size + heap->size > 65535) + error("Script and heap sizes combined exceed 64K. This means a fundamental " + "design bug was made regarding SCI1.1 and newer games.\nPlease " + "report this error to the ScummVM team"); + } +} + +void Script::load(ResourceManager *resMan) { + Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, _nr), 0); + assert(script != 0); + + _buf = (byte *)malloc(_bufSize); + assert(_buf); + + assert(_bufSize >= script->size); + memcpy(_buf, script->data, script->size); + + if (getSciVersion() >= SCI_VERSION_1_1) { + Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, _nr), 0); + assert(heap != 0); + + _heapStart = _buf + _scriptSize; + + assert(_bufSize - _scriptSize <= heap->size); + memcpy(_heapStart, heap->data, heap->size); + } + + _exportTable = 0; + _numExports = 0; + _synonyms = 0; + _numSynonyms = 0; + + if (getSciVersion() >= SCI_VERSION_1_1) { + if (READ_LE_UINT16(_buf + 1 + 5) > 0) { // does the script have an export table? + _exportTable = (const uint16 *)(_buf + 1 + 5 + 2); + _numExports = READ_SCI11ENDIAN_UINT16(_exportTable - 1); + } + _localsOffset = _scriptSize + 4; + _localsCount = READ_SCI11ENDIAN_UINT16(_buf + _localsOffset - 2); + } else { + _exportTable = (const uint16 *)findBlock(SCI_OBJ_EXPORTS); + if (_exportTable) { + _numExports = READ_SCI11ENDIAN_UINT16(_exportTable + 1); + _exportTable += 3; // skip header plus 2 bytes (_exportTable is a uint16 pointer) + } + _synonyms = findBlock(SCI_OBJ_SYNONYMS); + if (_synonyms) { + _numSynonyms = READ_SCI11ENDIAN_UINT16(_synonyms + 2) / 4; + _synonyms += 4; // skip header + } + const byte* localsBlock = findBlock(SCI_OBJ_LOCALVARS); + if (localsBlock) { + _localsOffset = localsBlock - _buf + 4; + _localsCount = (READ_LE_UINT16(_buf + _localsOffset - 2) - 4) >> 1; // half block size + } } -#ifdef ENABLE_SCI32 - // In SCI32, some arguments are now words instead of bytes - if (getSciVersion() >= SCI_VERSION_2) { - g_opcode_formats[op_calle][2] = Script_Word; - g_opcode_formats[op_callk][1] = Script_Word; - g_opcode_formats[op_super][1] = Script_Word; - g_opcode_formats[op_send][0] = Script_Word; - g_opcode_formats[op_self][0] = Script_Word; - g_opcode_formats[op_call][1] = Script_Word; - g_opcode_formats[op_callb][1] = Script_Word; + if (getSciVersion() > SCI_VERSION_0_EARLY) { + // Does the script actually have locals? If not, set the locals offset to 0 + if (!_localsCount) + _localsOffset = 0; + + if (_localsOffset + _localsCount * 2 + 1 >= (int)_bufSize) { + error("Locals extend beyond end of script: offset %04x, count %d vs size %d", _localsOffset, _localsCount, _bufSize); + _localsCount = (_bufSize - _localsOffset) >> 1; + } + } else { + // Old script block. There won't be a localvar block in this case. + // Instead, the script starts with a 16 bit int specifying the + // number of locals we need; these are then allocated and zeroed. + _localsCount = READ_LE_UINT16(_buf); + _localsOffset = -_localsCount * 2; // Make sure it's invalid } -#endif } -void SegManager::createClassTable() { - Resource *vocab996 = _resMan->findResource(ResourceId(kResourceTypeVocab, 996), 1); +Object *Script::allocateObject(uint16 offset) { + return &_objects[offset]; +} + +Object *Script::getObject(uint16 offset) { + if (_objects.contains(offset)) + return &_objects[offset]; + else + return 0; +} + +const Object *Script::getObject(uint16 offset) const { + if (_objects.contains(offset)) + return &_objects[offset]; + else + return 0; +} - if (!vocab996) - error("SegManager: failed to open vocab 996"); +Object *Script::scriptObjInit(reg_t obj_pos, bool fullObjectInit) { + Object *obj; + + if (getSciVersion() < SCI_VERSION_1_1 && fullObjectInit) + obj_pos.offset += 8; // magic offset (SCRIPT_OBJECT_MAGIC_OFFSET) + + VERIFY(obj_pos.offset < _bufSize, "Attempt to initialize object beyond end of script\n"); + + obj = allocateObject(obj_pos.offset); + + VERIFY(obj_pos.offset + kOffsetFunctionArea < (int)_bufSize, "Function area pointer stored beyond end of script\n"); + + obj->init(_buf, obj_pos, fullObjectInit); + + return obj; +} + +void Script::scriptObjRemove(reg_t obj_pos) { + if (getSciVersion() < SCI_VERSION_1_1) + obj_pos.offset += 8; + + _objects.erase(obj_pos.toUint16()); +} - int totalClasses = vocab996->size >> 2; - _classTable.resize(totalClasses); +// This helper function is used by Script::relocateLocal and Object::relocate +// Duplicate in segment.cpp and script.cpp +static bool relocateBlock(Common::Array<reg_t> &block, int block_location, SegmentId segment, int location, size_t scriptSize) { + int rel = location - block_location; - for (uint16 classNr = 0; classNr < totalClasses; classNr++) { - uint16 scriptNr = READ_SCI11ENDIAN_UINT16(vocab996->data + classNr * 4 + 2); + if (rel < 0) + return false; - _classTable[classNr].reg = NULL_REG; - _classTable[classNr].script = scriptNr; + uint idx = rel >> 1; + + if (idx >= block.size()) + return false; + + if (rel & 1) { + error("Attempt to relocate odd variable #%d.5e (relative to %04x)\n", idx, block_location); + return false; } + block[idx].segment = segment; // Perform relocation + if (getSciVersion() >= SCI_VERSION_1_1) + block[idx].offset += scriptSize; - _resMan->unlockResource(vocab996); + return true; } -reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, reg_t caller) { - if (classnr == 0xffff) - return NULL_REG; +bool Script::relocateLocal(SegmentId segment, int location) { + if (_localsBlock) + return relocateBlock(_localsBlock->_locals, _localsOffset, segment, location, _scriptSize); + else + return false; +} - if (classnr < 0 || (int)_classTable.size() <= classnr || _classTable[classnr].script < 0) { - error("[VM] Attempt to dereference class %x, which doesn't exist (max %x)", classnr, _classTable.size()); - return NULL_REG; - } else { - Class *the_class = &_classTable[classnr]; - if (!the_class->reg.segment) { - getScriptSegment(the_class->script, lock); +void Script::relocate(reg_t block) { + byte *heap = _buf; + uint16 heapSize = (uint16)_bufSize; + uint16 heapOffset = 0; - if (!the_class->reg.segment) { - error("[VM] Trying to instantiate class %x by instantiating script 0x%x (%03d) failed;", classnr, the_class->script, the_class->script); - return NULL_REG; - } - } else - if (caller.segment != the_class->reg.segment) - getScript(the_class->reg.segment)->incrementLockers(); + if (getSciVersion() >= SCI_VERSION_1_1) { + heap = _heapStart; + heapSize = (uint16)_heapSize; + heapOffset = _scriptSize; + } + + VERIFY(block.offset < (uint16)heapSize && READ_SCI11ENDIAN_UINT16(heap + block.offset) * 2 + block.offset < (uint16)heapSize, + "Relocation block outside of script\n"); + + int count = READ_SCI11ENDIAN_UINT16(heap + block.offset); + int exportIndex = 0; + int pos = 0; + + for (int i = 0; i < count; i++) { + pos = READ_SCI11ENDIAN_UINT16(heap + block.offset + 2 + (exportIndex * 2)) + heapOffset; + // This occurs in SCI01/SCI1 games where usually one export value + // is zero. It seems that in this situation, we should skip the + // export and move to the next one, though the total count of valid + // exports remains the same + if (!pos) { + exportIndex++; + pos = READ_SCI11ENDIAN_UINT16(heap + block.offset + 2 + (exportIndex * 2)) + heapOffset; + if (!pos) + error("Script::relocate(): Consecutive zero exports found"); + } + + // In SCI0-SCI1, script local variables, objects and code are relocated. We only relocate + // locals and objects here, and ignore relocation of code blocks. In SCI1.1 and newer + // versions, only locals and objects are relocated. + if (!relocateLocal(block.segment, pos)) { + // Not a local? It's probably an object or code block. If it's an object, relocate it. + const ObjMap::iterator end = _objects.end(); + for (ObjMap::iterator it = _objects.begin(); it != end; ++it) + if (it->_value.relocate(block.segment, pos, _scriptSize)) + break; + } + + exportIndex++; + } +} + +void Script::incrementLockers() { + _lockers++; +} + +void Script::decrementLockers() { + if (_lockers > 0) + _lockers--; +} + +int Script::getLockers() const { + return _lockers; +} + +void Script::setLockers(int lockers) { + _lockers = lockers; +} + +uint16 Script::validateExportFunc(int pubfunct) { + bool exportsAreWide = (g_sci->_features->detectLofsType() == SCI_VERSION_1_MIDDLE); - return the_class->reg; + if (_numExports <= pubfunct) { + error("validateExportFunc(): pubfunct is invalid"); + return 0; } + + if (exportsAreWide) + pubfunct *= 2; + uint16 offset = READ_SCI11ENDIAN_UINT16(_exportTable + pubfunct); + VERIFY(offset < _bufSize, "invalid export function pointer"); + + return offset; +} + +byte *Script::findBlock(int type) { + byte *buf = _buf; + bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); + + if (oldScriptHeader) + buf += 2; + + do { + int seekerType = READ_LE_UINT16(buf); + + if (seekerType == 0) + break; + if (seekerType == type) + return buf; + + int seekerSize = READ_LE_UINT16(buf + 2); + assert(seekerSize > 0); + buf += seekerSize; + } while (1); + + return NULL; +} + +// memory operations + +void Script::mcpyInOut(int dst, const void *src, size_t n) { + if (_buf) { + assert(dst + n <= _bufSize); + memcpy(_buf + dst, src, n); + } +} + +bool Script::isValidOffset(uint16 offset) const { + return offset < _bufSize; +} + +SegmentRef Script::dereference(reg_t pointer) { + if (pointer.offset > _bufSize) { + error("Script::dereference(): Attempt to dereference invalid pointer %04x:%04x into script segment (script size=%d)", + PRINT_REG(pointer), (uint)_bufSize); + return SegmentRef(); + } + + SegmentRef ret; + ret.isRaw = true; + ret.maxSize = _bufSize - pointer.offset; + ret.raw = _buf + pointer.offset; + return ret; } void SegManager::scriptInitialiseLocals(SegmentId segmentId) { |