From 1f29e6f241f77e028b31d72d6d4adaf8ce1b29ae Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sat, 20 May 2017 20:59:16 -0500 Subject: SCI: Refactor relocation code This groundwork enables an object to look up its static name separately from the normal process that is used to populate Object::_variables when an object is first constructed. (The static name property needs to be able to be retrieved from objects inside of earlier save games whose name properties may have already been modified at runtime, so the code cannot simply pluck the value out of Object::_variables when they are first initialised and then persisted into the save game, as nice and easy as that would have been.) This commit also helps to clarify the situation with relocation tables in SCI1 games that start with a zero entry. Refs Trac#9780. --- engines/sci/engine/kscripts.cpp | 6 +- engines/sci/engine/object.cpp | 6 +- engines/sci/engine/object.h | 4 +- engines/sci/engine/script.cpp | 144 ++++++++++++++++++++++++++++------------ engines/sci/engine/script.h | 33 +++++++-- engines/sci/engine/vm.cpp | 2 +- 6 files changed, 137 insertions(+), 58 deletions(-) diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp index af4b8ff081..75f905e81a 100644 --- a/engines/sci/engine/kscripts.cpp +++ b/engines/sci/engine/kscripts.cpp @@ -246,11 +246,7 @@ reg_t kScriptID(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } - uint32 address = scr->validateExportFunc(index, true); - - // Point to the heap for SCI1.1 - SCI2.1 games - if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) - address += scr->getScriptSize(); + const uint32 address = scr->validateExportFunc(index, true) + scr->getHeapOffset(); // Bugfix for the intro speed in PQ2 version 1.002.011. // This is taken from the patch by NewRisingSun(NRS) / Belzorash. Global 3 diff --git a/engines/sci/engine/object.cpp b/engines/sci/engine/object.cpp index 386c3e26bf..640feb1418 100644 --- a/engines/sci/engine/object.cpp +++ b/engines/sci/engine/object.cpp @@ -123,12 +123,12 @@ int Object::locateVarSelector(SegManager *segMan, Selector slc) const { return -1; // Failed } -bool Object::relocateSci0Sci21(SegmentId segment, int location, size_t scriptSize) { - return relocateBlock(_variables, getPos().getOffset(), segment, location, scriptSize); +bool Object::relocateSci0Sci21(SegmentId segment, int location, uint32 heapOffset) { + return relocateBlock(_variables, getPos().getOffset(), segment, location, heapOffset); } #ifdef ENABLE_SCI32 -bool Object::relocateSci3(SegmentId segment, uint32 location, int offset, size_t scriptSize) { +bool Object::relocateSci3(SegmentId segment, uint32 location, int offset, uint32 scriptSize) { assert(offset >= 0 && (uint)offset < scriptSize); for (uint i = 0; i < _variables.size(); ++i) { diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h index 71b366dc79..be20e3bef4 100644 --- a/engines/sci/engine/object.h +++ b/engines/sci/engine/object.h @@ -299,9 +299,9 @@ public: #endif } - bool relocateSci0Sci21(SegmentId segment, int location, size_t scriptSize); + bool relocateSci0Sci21(SegmentId segment, int location, uint32 heapOffset); #ifdef ENABLE_SCI32 - bool relocateSci3(SegmentId segment, uint32 location, int offset, size_t scriptSize); + bool relocateSci3(SegmentId segment, uint32 location, int offset, uint32 scriptSize); #endif int propertyOffsetToId(SegManager *segMan, int propertyOffset) const; diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index 2f04534343..ea1b6f2f5f 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -183,7 +183,7 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP _exports = _buf->subspan(kSci11ExportTableOffset, _numExports * sizeof(uint16)); } - _localsOffset = _script.size() + 4; + _localsOffset = getHeapOffset() + 4; _localsCount = _buf->getUint16SEAt(_localsOffset - 2); } else if (getSciVersion() == SCI_VERSION_3) { _localsCount = _buf->getUint16LEAt(12); @@ -676,10 +676,9 @@ bool relocateBlock(Common::Array &block, int block_location, SegmentId se error("Attempt to relocate odd variable #%d.5e (relative to %04x)\n", idx, block_location); return false; } - block[idx].setSegment(segment); // Perform relocation - if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) - block[idx].incOffset(scriptSize); + block[idx].setSegment(segment); + block[idx].incOffset(heapOffset); return true; } @@ -702,60 +701,123 @@ int Script::relocateOffsetSci3(uint32 offset) const { bool Script::relocateLocal(SegmentId segment, int location) { if (_localsBlock) - return relocateBlock(_localsBlock->_locals, _localsOffset, segment, location, _script.size()); + return relocateBlock(_localsBlock->_locals, _localsOffset, segment, location, getHeapOffset()); else return false; } -void Script::relocateSci0Sci21(reg_t block) { - SciSpan heap = *_buf; - uint16 heapOffset = 0; +uint32 Script::getRelocationOffset(const uint32 offset) const { + if (getSciVersion() == SCI_VERSION_3) { + SciSpan relocStart = _buf->subspan(_buf->getUint32SEAt(8)); + const uint relocCount = _buf->getUint16SEAt(18); - if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { - heap = _heap; - heapOffset = _script.size(); + for (uint i = 0; i < relocCount; ++i) { + if (offset == relocStart.getUint32SEAt(0)) { + return relocStart.getUint32SEAt(4); + } + relocStart += 10; + } + } else { + const SciSpan relocTable = getRelocationTableSci0Sci21(); + for (uint i = 0; i < relocTable.size(); ++i) { + if (relocTable.getUint16SEAt(i) == offset) { + return getHeapOffset(); + } + } } - if (block.getOffset() >= (uint16)heap.size() || - heap.getUint16SEAt(block.getOffset()) * 2 + block.getOffset() >= (uint16)heap.size()) - error("Relocation block outside of script"); - - int count = heap.getUint16SEAt(block.getOffset()); - int exportIndex = 0; - int pos = 0; - - for (int i = 0; i < count; i++) { - pos = heap.getUint16SEAt(block.getOffset() + 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 = heap.getUint16SEAt(block.getOffset() + 2 + (exportIndex * 2)) + heapOffset; - if (!pos) - error("Script::relocate(): Consecutive zero exports found"); + return kNoRelocation; +} + +const SciSpan Script::getRelocationTableSci0Sci21() const { + SciSpan relocationBlock; + uint16 numEntries; + uint16 dataOffset; + + if (getSciVersion() < SCI_VERSION_1_1) { + relocationBlock = findBlockSCI0(SCI_OBJ_POINTERS); + + if (!relocationBlock) { + return SciSpan(); + } + + if (relocationBlock != findBlockSCI0(SCI_OBJ_POINTERS, true)) { + warning("script.%u has multiple relocation tables", _nr); } + numEntries = relocationBlock.getUint16SEAt(4); + + if (!numEntries) { + return SciSpan(); + } + + dataOffset = 6; + + // Starting somewhere around SQ1, and continuing through the rest of + // SCI1, the relocation table in scripts started including an extra + // null entry at the beginning of the table, without a corresponding + // increase in the entry count. While this change is consistent in + // most of the SCI1mid+ games (all scripts in LSL1, Jones CD, + // EQ floppy, SQ1, LSL5, and Ms Astro Chicken have the null entry), + // a few games include scripts without the null entry (Castle of Dr + // Brain 947 & 997, PQ3 997, KQ5 CD 975 & 997). Since 0 is never a + // valid relocation offset, we just skip it if we see it + const uint16 firstEntry = relocationBlock.getUint16SEAt(6); + if (firstEntry == 0) { + dataOffset += 2; + } + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { + relocationBlock = _heap.subspan(_heap.getUint16SEAt(0)); + + if (!relocationBlock) { + return SciSpan(); + } + + numEntries = relocationBlock.getUint16SEAt(0); + + if (!numEntries) { + return SciSpan(); + } + + dataOffset = 2; + } else { + error("Invalid engine version called Script::getRelocationTableSci0Sci21"); + } + + // This check should work correctly even with SCI1.1+ because the relocation + // table is always at the very end of the heap in these games + if (dataOffset + numEntries * sizeof(uint16) != relocationBlock.size()) { + warning("script.%u unexpected relocation table size %u", _nr, relocationBlock.size()); + } + + return relocationBlock.subspan(dataOffset, numEntries * sizeof(uint16)); +} + +void Script::relocateSci0Sci21(const SegmentId segmentId) { + const SciSpan relocEntries = getRelocationTableSci0Sci21(); + + const uint32 heapOffset = getHeapOffset(); + + for (uint i = 0; i < relocEntries.size(); ++i) { + const uint pos = relocEntries.getUint16SEAt(i) + heapOffset; + // 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.getSegment(), pos)) { + if (!relocateLocal(segmentId, 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.relocateSci0Sci21(block.getSegment(), pos, _script.size())) + if (it->_value.relocateSci0Sci21(segmentId, pos, getHeapOffset())) break; } - - exportIndex++; } } #ifdef ENABLE_SCI32 -void Script::relocateSci3(reg_t block) { +void Script::relocateSci3(const SegmentId segmentId) { SciSpan relocStart = _buf->subspan(_buf->getUint32SEAt(8)); const uint relocCount = _buf->getUint16SEAt(18); @@ -763,7 +825,7 @@ void Script::relocateSci3(reg_t block) { for (it = _objects.begin(); it != _objects.end(); ++it) { SciSpan seeker = relocStart; for (uint i = 0; i < relocCount; ++i) { - it->_value.relocateSci3(block.getSegment(), + it->_value.relocateSci3(segmentId, seeker.getUint32SEAt(0), seeker.getUint32SEAt(4), _script.size()); @@ -831,7 +893,7 @@ uint32 Script::validateExportFunc(int pubfunct, bool relocSci3) { return offset; } -SciSpan Script::findBlockSCI0(ScriptObjectTypes type, bool findLastBlock) { +SciSpan Script::findBlockSCI0(ScriptObjectTypes type, bool findLastBlock) const { SciSpan foundBlock; bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); @@ -1054,9 +1116,7 @@ void Script::initializeObjectsSci0(SegManager *segMan, SegmentId segmentId) { } while ((uint32)(seeker - *_buf) < getScriptSize() - 2); } - const SciSpan relocationBlock = findBlockSCI0(SCI_OBJ_POINTERS); - if (relocationBlock) - relocateSci0Sci21(make_reg(segmentId, relocationBlock - *_buf + 4)); + relocateSci0Sci21(segmentId); } void Script::initializeObjectsSci11(SegManager *segMan, SegmentId segmentId) { @@ -1108,7 +1168,7 @@ void Script::initializeObjectsSci11(SegManager *segMan, SegmentId segmentId) { seeker += seeker.getUint16SEAt(2) * 2; } - relocateSci0Sci21(make_reg(segmentId, _heap.getUint16SEAt(0))); + relocateSci0Sci21(segmentId); for (uint i = 0; i < mismatchedVarCountObjects.size(); ++i) { const reg_t pos = mismatchedVarCountObjects[i]; @@ -1143,7 +1203,7 @@ void Script::initializeObjectsSci3(SegManager *segMan, SegmentId segmentId) { seeker += seeker.getUint16SEAt(2); } - relocateSci3(make_reg(segmentId, 0)); + relocateSci3(segmentId); } #endif diff --git a/engines/sci/engine/script.h b/engines/sci/engine/script.h index 65f3ffb6ab..b59f87f13a 100644 --- a/engines/sci/engine/script.h +++ b/engines/sci/engine/script.h @@ -56,6 +56,10 @@ enum ScriptOffsetEntryTypes { SCI_SCR_OFFSET_TYPE_SAID }; +enum { + kNoRelocation = 0xFFFFFFFF +}; + struct offsetLookupArrayEntry { uint16 type; // type of entry uint16 id; // id of this type, first item inside script data is 1, second item is 2, etc. @@ -105,6 +109,13 @@ public: uint32 getScriptSize() const { return _script.size(); } uint32 getHeapSize() const { return _heap.size(); } uint32 getBufSize() const { return _buf->size(); } + inline uint32 getHeapOffset() const { + if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) { + return _script.size(); + } + + return 0; + } const byte *getBuf(uint offset = 0) const { return _buf->getUnsafeDataAt(offset); } SciSpan getSpan(uint offset) const { return _buf->subspan(offset); } @@ -247,7 +258,7 @@ public: * Finds the pointer where a block of a specific type starts from, * in SCI0 - SCI1 games */ - SciSpan findBlockSCI0(ScriptObjectTypes type, bool findLastBlock = false); + SciSpan findBlockSCI0(ScriptObjectTypes type, bool findLastBlock = false) const; /** * Syncs the string heap of a script. Used when saving/loading. @@ -276,23 +287,35 @@ public: uint16 getOffsetStringCount() { return _offsetLookupStringCount; }; uint16 getOffsetSaidCount() { return _offsetLookupSaidCount; }; + /** + * @returns kNoRelocation if no relocation exists for the given offset, + * otherwise returns a delta for the offset to its relocated position. + */ + uint32 getRelocationOffset(const uint32 offset) const; + private: + /** + * Returns a Span containing the relocation table for a SCI0-SCI2.1 script. + * (The SCI0-SCI2.1 relocation table is simply a list of all of the + * offsets in the script heap whose values should be treated as pointers to + * objects (vs just being numbers).) + */ + const SciSpan getRelocationTableSci0Sci21() const; + /** * Processes a relocation block within a SCI0-SCI2.1 script * This function is idempotent, but it must only be called after all * objects have been instantiated, or a run-time error will occur. - * @param obj_pos Location (segment, offset) of the block */ - void relocateSci0Sci21(reg_t block); + void relocateSci0Sci21(const SegmentId segmentId); #ifdef ENABLE_SCI32 /** * Processes a relocation block within a SCI3 script * This function is idempotent, but it must only be called after all * objects have been instantiated, or a run-time error will occur. - * @param obj_pos Location (segment, offset) of the block */ - void relocateSci3(reg_t block); + void relocateSci3(const SegmentId segmentId); #endif bool relocateLocal(SegmentId segment, int location); diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 1d86948fdd..f4071a4843 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -570,7 +570,7 @@ uint32 findOffset(const int16 relOffset, const Script *scr, const uint32 pcOffse offset = relOffset; break; case SCI_VERSION_1_1: - offset = relOffset + scr->getScriptSize(); + offset = relOffset + scr->getHeapOffset(); break; #ifdef ENABLE_SCI32 case SCI_VERSION_3: -- cgit v1.2.3