diff options
author | Neeraj Kumar | 2010-08-06 20:13:41 +0000 |
---|---|---|
committer | Neeraj Kumar | 2010-08-06 20:13:41 +0000 |
commit | 7e126ed299cb789340cb2f8d409338dbdd6c8235 (patch) | |
tree | 91f6e4be633fd579922ddf270443011582b8f9ec /engines/sci/engine/script.cpp | |
parent | 6c0855f3d3efc52478ba9ce019fbd4c9287f4691 (diff) | |
parent | 4ae7427eed781613e2cda096d0f61c77883bca05 (diff) | |
download | scummvm-rg350-7e126ed299cb789340cb2f8d409338dbdd6c8235.tar.gz scummvm-rg350-7e126ed299cb789340cb2f8d409338dbdd6c8235.tar.bz2 scummvm-rg350-7e126ed299cb789340cb2f8d409338dbdd6c8235.zip |
TESTBED: Merged changes from trunk to my branch
svn-id: r51798
Diffstat (limited to 'engines/sci/engine/script.cpp')
-rw-r--r-- | engines/sci/engine/script.cpp | 794 |
1 files changed, 462 insertions, 332 deletions
diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index 1f32e50b67..645094d9ec 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -35,431 +35,561 @@ 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(EngineState *s) { - // TODO: Check that this is correct - 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; -#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; + _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.\n" + "Please report this error to the ScummVM team"); } -#endif } -void SegManager::createClassTable() { - Resource *vocab996 = _resMan->findResource(ResourceId(kResourceTypeVocab, 996), 1); +void Script::load(ResourceManager *resMan) { + Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, _nr), 0); + assert(script != 0); - if (!vocab996) - error("SegManager: failed to open vocab 996"); + _buf = (byte *)malloc(_bufSize); + assert(_buf); - int totalClasses = vocab996->size >> 2; - _classTable.resize(totalClasses); + 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); - for (uint16 classNr = 0; classNr < totalClasses; classNr++) { - uint16 scriptNr = READ_SCI11ENDIAN_UINT16(vocab996->data + classNr * 4 + 2); + _heapStart = _buf + _scriptSize; - _classTable[classNr].reg = NULL_REG; - _classTable[classNr].script = scriptNr; + assert(_bufSize - _scriptSize <= heap->size); + memcpy(_heapStart, heap->data, heap->size); } - _resMan->unlockResource(vocab996); -} + _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 + } + } -reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, reg_t caller) { - if (classnr == 0xffff) - return NULL_REG; + 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 (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; + 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 { - Class *the_class = &_classTable[classnr]; - if (!the_class->reg.segment) { - getScriptSegment(the_class->script, lock); + // 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 + } +} - 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(); +Object *Script::getObject(uint16 offset) { + if (_objects.contains(offset)) + return &_objects[offset]; + else + return 0; +} - return the_class->reg; - } +const Object *Script::getObject(uint16 offset) const { + if (_objects.contains(offset)) + return &_objects[offset]; + else + return 0; } -void SegManager::scriptInitialiseLocalsZero(SegmentId seg, int count) { - Script *scr = getScript(seg); +Object *Script::scriptObjInit(reg_t obj_pos, bool fullObjectInit) { + if (getSciVersion() < SCI_VERSION_1_1 && fullObjectInit) + obj_pos.offset += 8; // magic offset (SCRIPT_OBJECT_MAGIC_OFFSET) - scr->_localsOffset = -count * 2; // Make sure it's invalid + VERIFY(obj_pos.offset < _bufSize, "Attempt to initialize object beyond end of script\n"); - LocalVariables *locals = allocLocalsSegment(scr, count); - if (locals) { - for (int i = 0; i < count; i++) - locals->_locals[i] = NULL_REG; - } + VERIFY(obj_pos.offset + kOffsetFunctionArea < (int)_bufSize, "Function area pointer stored beyond end of script\n"); + + // Get the object at the specified position and init it. This will + // automatically "allocate" space for it in the _objects map if necessary. + Object *obj = &_objects[obj_pos.offset]; + obj->init(_buf, obj_pos, fullObjectInit); + + return obj; } -void SegManager::scriptInitialiseLocals(reg_t location) { - Script *scr = getScript(location.segment); - unsigned int count; +void Script::scriptObjRemove(reg_t obj_pos) { + if (getSciVersion() < SCI_VERSION_1_1) + obj_pos.offset += 8; - VERIFY(location.offset + 1 < (uint16)scr->getBufSize(), "Locals beyond end of script\n"); + _objects.erase(obj_pos.toUint16()); +} - if (getSciVersion() >= SCI_VERSION_1_1) - count = READ_SCI11ENDIAN_UINT16(scr->_buf + location.offset - 2); - else - count = (READ_LE_UINT16(scr->_buf + location.offset - 2) - 4) >> 1; - // half block size +// 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; - scr->_localsOffset = location.offset; + if (rel < 0) + return false; - if (!(location.offset + count * 2 + 1 < scr->getBufSize())) { - warning("Locals extend beyond end of script: offset %04x, count %x vs size %x", location.offset, count, (uint)scr->getBufSize()); - count = (scr->getBufSize() - location.offset) >> 1; - } + uint idx = rel >> 1; - LocalVariables *locals = allocLocalsSegment(scr, count); - if (locals) { - uint i; - const byte *base = (const byte *)(scr->_buf + location.offset); + if (idx >= block.size()) + return false; - for (i = 0; i < count; i++) - locals->_locals[i] = make_reg(0, READ_SCI11ENDIAN_UINT16(base + i * 2)); + 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; + + return true; } -void SegManager::scriptInitialiseObjectsSci11(SegmentId seg) { - Script *scr = getScript(seg); - const byte *seeker = scr->_heapStart + 4 + READ_SCI11ENDIAN_UINT16(scr->_heapStart + 2) * 2; +bool Script::relocateLocal(SegmentId segment, int location) { + if (_localsBlock) + return relocateBlock(_localsBlock->_locals, _localsOffset, segment, location, _scriptSize); + else + return false; +} - while (READ_SCI11ENDIAN_UINT16(seeker) == SCRIPT_OBJECT_MAGIC_NUMBER) { - if (READ_SCI11ENDIAN_UINT16(seeker + 14) & kInfoFlagClass) { // -info- selector - int classpos = seeker - scr->_buf; - int species = READ_SCI11ENDIAN_UINT16(seeker + 10); - - if (species < 0 || species >= (int)_classTable.size()) { - error("Invalid species %d(0x%x) not in interval [0,%d) while instantiating script %d", - species, species, _classTable.size(), scr->_nr); - return; - } +void Script::relocate(reg_t block) { + const byte *heap = _buf; + uint16 heapSize = (uint16)_bufSize; + uint16 heapOffset = 0; - _classTable[species].reg.segment = seg; - _classTable[species].reg.offset = classpos; - } - seeker += READ_SCI11ENDIAN_UINT16(seeker + 2) * 2; + if (getSciVersion() >= SCI_VERSION_1_1) { + heap = _heapStart; + heapSize = (uint16)_heapSize; + heapOffset = _scriptSize; } - seeker = scr->_heapStart + 4 + READ_SCI11ENDIAN_UINT16(scr->_heapStart + 2) * 2; - while (READ_SCI11ENDIAN_UINT16(seeker) == SCRIPT_OBJECT_MAGIC_NUMBER) { - reg_t reg = make_reg(seg, seeker - scr->_buf); - Object *obj = scr->scriptObjInit(reg); - - // Copy base from species class, as we need its selector IDs - obj->setSuperClassSelector( - getClassAddress(obj->getSuperClassSelector().offset, SCRIPT_GET_LOCK, NULL_REG)); - - // If object is instance, get -propDict- from class and set it for this object - // This is needed for ::isMemberOf() to work. - // Example testcase - room 381 of sq4cd - if isMemberOf() doesn't work, talk-clicks on the robot will act like - // clicking on ego - if (!obj->isClass()) { - reg_t classObject = obj->getSuperClassSelector(); - Object *classObj = getObject(classObject); - obj->setPropDictSelector(classObj->getPropDictSelector()); + 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"); } - // Set the -classScript- selector to the script number. - // FIXME: As this selector is filled in at run-time, it is likely - // that it is supposed to hold a pointer. The Obj::isKindOf method - // uses this selector together with -propDict- to compare classes. - // For the purpose of Obj::isKindOf, using the script number appears - // to be sufficient. - obj->setClassScriptSelector(make_reg(0, scr->_nr)); + // 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; + } - seeker += READ_SCI11ENDIAN_UINT16(seeker + 2) * 2; + exportIndex++; } } -void script_instantiate_sci0(Script *scr, int segmentId, SegManager *segMan) { - int objType; - uint32 objLength = 0; - bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); - uint16 curOffset = oldScriptHeader ? 2 : 0; +void Script::incrementLockers() { + _lockers++; +} - if (oldScriptHeader) { - // 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. - int localsCount = READ_LE_UINT16(scr->_buf); - if (localsCount) - segMan->scriptInitialiseLocalsZero(segmentId, localsCount); - } +void Script::decrementLockers() { + if (_lockers > 0) + _lockers--; +} - // Now do a first pass through the script objects to find the - // local variable blocks +int Script::getLockers() const { + return _lockers; +} - do { - objType = scr->getHeap(curOffset); - if (!objType) - break; +void Script::setLockers(int lockers) { + _lockers = lockers; +} - objLength = scr->getHeap(curOffset + 2); - curOffset += 4; // skip header +uint16 Script::validateExportFunc(int pubfunct) { + bool exportsAreWide = (g_sci->_features->detectLofsType() == SCI_VERSION_1_MIDDLE); - switch (objType) { - case SCI_OBJ_LOCALVARS: - segMan->scriptInitialiseLocals(make_reg(segmentId, curOffset)); - break; - case SCI_OBJ_CLASS: { - int classpos = curOffset - SCRIPT_OBJECT_MAGIC_OFFSET; - int species = scr->getHeap(curOffset - SCRIPT_OBJECT_MAGIC_OFFSET + SCRIPT_SPECIES_OFFSET); - if (species < 0 || species >= (int)segMan->classTableSize()) { - if (species == (int)segMan->classTableSize()) { - // Happens in the LSL2 demo - warning("Applying workaround for an off-by-one invalid species access"); - segMan->resizeClassTable(segMan->classTableSize() + 1); - } else { - error("Invalid species %d(0x%x) not in interval " - "[0,%d) while instantiating script at segment %d\n", - species, species, segMan->classTableSize(), - segmentId); - return; - } - } + if (_numExports <= pubfunct) { + error("validateExportFunc(): pubfunct is invalid"); + return 0; + } - segMan->setClassOffset(species, make_reg(segmentId, classpos)); - // Set technical class position-- into the block allocated for it - } - break; + if (exportsAreWide) + pubfunct *= 2; + uint16 offset = READ_SCI11ENDIAN_UINT16(_exportTable + pubfunct); + VERIFY(offset < _bufSize, "invalid export function pointer"); - default: - break; - } + return offset; +} - curOffset += objLength - 4; - } while (objType != 0 && curOffset < scr->getScriptSize() - 2); +byte *Script::findBlock(int type) { + byte *buf = _buf; + bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); - // And now a second pass to adjust objects and class pointers, and the general pointers - objLength = 0; - curOffset = oldScriptHeader ? 2 : 0; + if (oldScriptHeader) + buf += 2; do { - objType = scr->getHeap(curOffset); - if (!objType) + int seekerType = READ_LE_UINT16(buf); + + if (seekerType == 0) break; + if (seekerType == type) + return buf; - objLength = scr->getHeap(curOffset + 2); - curOffset += 4; // skip header + int seekerSize = READ_LE_UINT16(buf + 2); + assert(seekerSize > 0); + buf += seekerSize; + } while (1); - reg_t addr = make_reg(segmentId, curOffset); + return NULL; +} - switch (objType) { - case SCI_OBJ_CODE: - scr->scriptAddCodeBlock(addr); - break; - case SCI_OBJ_OBJECT: - case SCI_OBJ_CLASS: { // object or class? - Object *obj = scr->scriptObjInit(addr); - obj->initSpecies(segMan, addr); +// memory operations - if (!obj->initBaseObject(segMan, addr)) { - warning("Failed to locate base object for object at %04X:%04X; skipping", PRINT_REG(addr)); - scr->scriptObjRemove(addr); - } - } // if object or class - break; - default: - break; - } +void Script::mcpyInOut(int dst, const void *src, size_t n) { + if (_buf) { + assert(dst + n <= _bufSize); + memcpy(_buf + dst, src, n); + } +} - curOffset += objLength - 4; - } while (objType != 0 && curOffset < scr->getScriptSize() - 2); +bool Script::isValidOffset(uint16 offset) const { + return offset < _bufSize; } -int script_instantiate(ResourceManager *resMan, SegManager *segMan, int scriptNum) { - SegmentId segmentId = segMan->getScriptSegment(scriptNum); - Script *scr = segMan->getScriptIfLoaded(segmentId); - if (scr) { - if (!scr->isMarkedAsDeleted()) { - scr->incrementLockers(); - return segmentId; +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 Script::initialiseLocals(SegManager *segMan) { + LocalVariables *locals = segMan->allocLocalsSegment(this); + if (locals) { + if (getSciVersion() > SCI_VERSION_0_EARLY) { + const byte *base = (const byte *)(_buf + getLocalsOffset()); + + for (uint16 i = 0; i < getLocalsCount(); i++) + locals->_locals[i] = make_reg(0, READ_SCI11ENDIAN_UINT16(base + i * 2)); } else { - scr->freeScript(); + // In SCI0 early, locals are set at run time, thus zero them all here + for (uint16 i = 0; i < getLocalsCount(); i++) + locals->_locals[i] = NULL_REG; } - } else { - scr = segMan->allocateScript(scriptNum, &segmentId); } +} - scr->init(scriptNum, resMan); - scr->load(resMan); - +void Script::initialiseClasses(SegManager *segMan) { + const byte *seeker = 0; + uint16 mult = 0; + if (getSciVersion() >= SCI_VERSION_1_1) { - int heapStart = scr->getScriptSize(); - segMan->scriptInitialiseLocals(make_reg(segmentId, heapStart + 4)); - segMan->scriptInitialiseObjectsSci11(segmentId); - scr->relocate(make_reg(segmentId, READ_SCI11ENDIAN_UINT16(scr->_heapStart))); + seeker = _heapStart + 4 + READ_SCI11ENDIAN_UINT16(_heapStart + 2) * 2; + mult = 2; } else { - script_instantiate_sci0(scr, segmentId, segMan); - byte *relocationBlock = scr->findBlock(SCI_OBJ_POINTERS); - if (relocationBlock) - scr->relocate(make_reg(segmentId, relocationBlock - scr->_buf + 4)); + seeker = findBlock(SCI_OBJ_CLASS); + mult = 1; } - return segmentId; -} - -void script_uninstantiate_sci0(SegManager *segMan, int script_nr, SegmentId seg) { - bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); - reg_t reg = make_reg(seg, oldScriptHeader ? 2 : 0); - int objType, objLength = 0; - Script *scr = segMan->getScript(seg); + if (!seeker) + return; - // Make a pass over the object in order uninstantiate all superclasses + uint16 marker; + bool isClass; + uint16 classpos; + int16 species = 0; - do { - reg.offset += objLength; // Step over the last checked object + while (true) { + // In SCI0-SCI1, this is the segment type. In SCI11, it's a marker (0x1234) + marker = READ_SCI11ENDIAN_UINT16(seeker); + classpos = seeker - _buf; - objType = scr->getHeap(reg.offset); - if (!objType) + if (!marker) break; - objLength = scr->getHeap(reg.offset + 2); // use SEG_UGET_HEAP ?? - reg.offset += 4; // Step over header + if (getSciVersion() >= SCI_VERSION_1_1) { + isClass = (READ_SCI11ENDIAN_UINT16(seeker + 14) & kInfoFlagClass); // -info- selector + species = READ_SCI11ENDIAN_UINT16(seeker + 10); + } else { + isClass = (marker == SCI_OBJ_CLASS); + if (isClass) + species = READ_SCI11ENDIAN_UINT16(seeker + 12); + classpos += 12; + } - if ((objType == SCI_OBJ_OBJECT) || (objType == SCI_OBJ_CLASS)) { // object or class? - int superclass; + if (isClass) { + // WORKAROUNDs for off-by-one script errors + if (g_sci->getGameId() == GID_LSL2 && g_sci->isDemo() && species == (int)segMan->classTableSize()) + segMan->resizeClassTable(segMan->classTableSize() + 1); + if (g_sci->getGameId() == GID_LSL3 && !g_sci->isDemo() && _nr == 500 && species == (int)segMan->classTableSize()) + segMan->resizeClassTable(segMan->classTableSize() + 1); + if (g_sci->getGameId() == GID_SQ3 && !g_sci->isDemo() && _nr == 93 && species == (int)segMan->classTableSize()) + segMan->resizeClassTable(segMan->classTableSize() + 1); + if (g_sci->getGameId() == GID_SQ3 && !g_sci->isDemo() && _nr == 99 && species == (int)segMan->classTableSize()) + segMan->resizeClassTable(segMan->classTableSize() + 1); + + if (species < 0 || species >= (int)segMan->classTableSize()) + error("Invalid species %d(0x%x) unknown max %d(0x%x) while instantiating script %d\n", + species, species, segMan->classTableSize(), segMan->classTableSize(), _nr); + + SegmentId segmentId = segMan->getScriptSegment(_nr); + segMan->setClassOffset(species, make_reg(segmentId, classpos)); + } - reg.offset -= SCRIPT_OBJECT_MAGIC_OFFSET; + seeker += READ_SCI11ENDIAN_UINT16(seeker + 2) * mult; + } +} - superclass = scr->getHeap(reg.offset + SCRIPT_SUPERCLASS_OFFSET); // Get superclass... +void Script::initialiseObjectsSci0(SegManager *segMan, SegmentId segmentId) { + bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); + const byte *seeker = _buf + (oldScriptHeader ? 2 : 0); - if (superclass >= 0) { - int superclass_script = segMan->getClass(superclass).script; + do { + uint16 objType = READ_SCI11ENDIAN_UINT16(seeker); + if (!objType) + break; - if (superclass_script == script_nr) { - if (scr->getLockers()) - scr->decrementLockers(); // Decrease lockers if this is us ourselves - } else - script_uninstantiate(segMan, superclass_script); - // Recurse to assure that the superclass lockers number gets decreased + switch (objType) { + case SCI_OBJ_OBJECT: + case SCI_OBJ_CLASS: + { + reg_t addr = make_reg(segmentId, seeker - _buf + 4); + Object *obj = scriptObjInit(addr); + obj->initSpecies(segMan, addr); + + if (!obj->initBaseObject(segMan, addr)) { + if (_nr == 202 && g_sci->getGameId() == GID_KQ5) { + // WORKAROUND: Script 202 of KQ5 French and German + // (perhaps Spanish too?) has an invalid object. + // This is non-fatal. Refer to bug #3035396. + } else { + error("Failed to locate base object for object at %04X:%04X; skipping", PRINT_REG(addr)); + } + scriptObjRemove(addr); + } } + break; - reg.offset += SCRIPT_OBJECT_MAGIC_OFFSET; - } // if object or class + default: + break; + } - reg.offset -= 4; // Step back on header + seeker += READ_SCI11ENDIAN_UINT16(seeker + 2); + } while ((uint32)(seeker - _buf) < getScriptSize() - 2); - } while (objType != 0); + byte *relocationBlock = findBlock(SCI_OBJ_POINTERS); + if (relocationBlock) + relocate(make_reg(segmentId, relocationBlock - getBuf() + 4)); } -void script_uninstantiate(SegManager *segMan, int script_nr) { - SegmentId segment = segMan->getScriptSegment(script_nr); - Script *scr = segMan->getScriptIfLoaded(segment); +void Script::initialiseObjectsSci11(SegManager *segMan, SegmentId segmentId) { + const byte *seeker = _heapStart + 4 + READ_SCI11ENDIAN_UINT16(_heapStart + 2) * 2; - if (!scr) { // Is it already loaded? - //warning("unloading script 0x%x requested although not loaded", script_nr); - // This is perfectly valid SCI behaviour - return; - } + while (READ_SCI11ENDIAN_UINT16(seeker) == SCRIPT_OBJECT_MAGIC_NUMBER) { + reg_t reg = make_reg(segmentId, seeker - _buf); + Object *obj = scriptObjInit(reg); - scr->decrementLockers(); // One less locker + // Copy base from species class, as we need its selector IDs + obj->setSuperClassSelector( + segMan->getClassAddress(obj->getSuperClassSelector().offset, SCRIPT_GET_LOCK, NULL_REG)); - if (scr->getLockers() > 0) - return; + // If object is instance, get -propDict- from class and set it for this + // object. This is needed for ::isMemberOf() to work. + // Example testcase - room 381 of sq4cd - if isMemberOf() doesn't work, + // talk-clicks on the robot will act like clicking on ego + if (!obj->isClass()) { + reg_t classObject = obj->getSuperClassSelector(); + const Object *classObj = segMan->getObject(classObject); + obj->setPropDictSelector(classObj->getPropDictSelector()); + } - // Free all classtable references to this script - for (uint i = 0; i < segMan->classTableSize(); i++) - if (segMan->getClass(i).reg.segment == segment) - segMan->setClassOffset(i, NULL_REG); + // Set the -classScript- selector to the script number. + // FIXME: As this selector is filled in at run-time, it is likely + // that it is supposed to hold a pointer. The Obj::isKindOf method + // uses this selector together with -propDict- to compare classes. + // For the purpose of Obj::isKindOf, using the script number appears + // to be sufficient. + obj->setClassScriptSelector(make_reg(0, _nr)); - if (getSciVersion() < SCI_VERSION_1_1) - script_uninstantiate_sci0(segMan, script_nr, segment); - // FIXME: Add proper script uninstantiation for SCI 1.1 + seeker += READ_SCI11ENDIAN_UINT16(seeker + 2) * 2; + } - if (scr->getLockers()) - return; // if xxx.lockers > 0 + relocate(make_reg(segmentId, READ_SCI11ENDIAN_UINT16(_heapStart))); +} + +reg_t Script::findCanonicAddress(SegManager *segMan, reg_t addr) const { + addr.offset = 0; + return addr; +} - // Otherwise unload it completely - // Explanation: I'm starting to believe that this work is done by SCI itself. - scr->markDeleted(); +void Script::freeAtAddress(SegManager *segMan, reg_t addr) { + /* + debugC(2, kDebugLevelGC, "[GC] Freeing script %04x:%04x", PRINT_REG(addr)); + if (_localsSegment) + debugC(2, kDebugLevelGC, "[GC] Freeing locals %04x:0000", _localsSegment); + */ + + if (_markedAsDeleted) + segMan->deallocateScript(_nr); +} - debugC(kDebugLevelScripts, "Unloaded script 0x%x.", script_nr); +Common::Array<reg_t> Script::listAllDeallocatable(SegmentId segId) const { + const reg_t r = make_reg(segId, 0); + return Common::Array<reg_t>(&r, 1); +} - return; +Common::Array<reg_t> Script::listAllOutgoingReferences(reg_t addr) const { + Common::Array<reg_t> tmp; + if (addr.offset <= _bufSize && addr.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET && RAW_IS_OBJECT(_buf + addr.offset)) { + const Object *obj = getObject(addr.offset); + if (obj) { + // Note all local variables, if we have a local variable environment + if (_localsSegment) + tmp.push_back(make_reg(_localsSegment, 0)); + + for (uint i = 0; i < obj->getVarCount(); i++) + tmp.push_back(obj->getVariable(i)); + } else { + error("Request for outgoing script-object reference at %04x:%04x failed", PRINT_REG(addr)); + } + } else { + /* warning("Unexpected request for outgoing script-object references at %04x:%04x", PRINT_REG(addr));*/ + /* Happens e.g. when we're looking into strings */ + } + return tmp; } +Common::Array<reg_t> Script::listObjectReferences() const { + Common::Array<reg_t> tmp; + + // Locals, if present + if (_localsSegment) + tmp.push_back(make_reg(_localsSegment, 0)); + + // All objects (may be classes, may be indirectly reachable) + ObjMap::iterator it; + const ObjMap::iterator end = _objects.end(); + for (it = _objects.begin(); it != end; ++it) { + tmp.push_back(it->_value.getPos()); + } + + return tmp; +} } // End of namespace Sci |