From 2fb37063a4ef85aa862b8fdf035d03f6b801679e Mon Sep 17 00:00:00 2001 From: Filippos Karapetis Date: Wed, 3 Feb 2010 11:02:43 +0000 Subject: Placed all the game feature detection code in a separate class svn-id: r47850 --- engines/sci/engine/features.cpp | 520 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 520 insertions(+) create mode 100644 engines/sci/engine/features.cpp (limited to 'engines/sci/engine/features.cpp') diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp new file mode 100644 index 0000000000..e5fcc673cf --- /dev/null +++ b/engines/sci/engine/features.cpp @@ -0,0 +1,520 @@ +/* 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 "sci/engine/features.h" +#include "sci/engine/script.h" +#include "sci/engine/selector.h" +#include "sci/engine/vm.h" + +namespace Sci { + +GameFeatures::GameFeatures(SegManager *segMan, Kernel *kernel) : _segMan(segMan), _kernel(kernel) { + _setCursorType = SCI_VERSION_NONE; + _doSoundType = SCI_VERSION_NONE; + _lofsType = SCI_VERSION_NONE; + _gfxFunctionsType = SCI_VERSION_NONE; + _moveCountType = kMoveCountUninitialized; + +#ifdef ENABLE_SCI32 + _sci21KernelType = SCI_VERSION_NONE; +#endif + _usesCdTrack = Common::File::exists("cdaudio.map"); +} + +void GameFeatures::setGameInfo(reg_t gameObj, Common::String gameId) { + _gameObj = gameObj; + _gameId = gameId; +} + +bool GameFeatures::autoDetectFeature(FeatureDetection featureDetection, int methodNum) { + Common::String objName; + Selector slc = 0; + reg_t objAddr; + bool foundTarget = false; + + // Get address of target script + switch (featureDetection) { + case kDetectGfxFunctions: + objName = "Rm"; + objAddr = _segMan->findObjectByName(objName); + slc = _kernel->_selectorCache.overlay; + break; + case kDetectMoveCountType: + objName = "Motion"; + objAddr = _segMan->findObjectByName(objName); + slc = _kernel->_selectorCache.doit; + break; + case kDetectSoundType: + objName = "Sound"; + objAddr = _segMan->findObjectByName(objName); + slc = _kernel->_selectorCache.play; + break; + case kDetectSetCursorType: + objName = "Game"; + objAddr = _segMan->findObjectByName(objName); + // KQ5CD overrides the default setCursor selector of the Game object, + // so we need to handle this separately + // KQ5 PC floppy is early SCI1, Amiga middle SCI1, and CD late SCI1 + assert(!_gameId.empty()); + if (_gameId == "kq5" && getSciVersion() == SCI_VERSION_1_LATE) + objAddr = _gameObj; + slc = _kernel->_selectorCache.setCursor; + break; + case kDetectLofsType: + objName = "Game"; + objAddr = _segMan->findObjectByName(objName); + break; +#ifdef ENABLE_SCI32 + case kDetectSci21KernelTable: + objName = "Sound"; + objAddr = _segMan->findObjectByName(objName); + slc = _kernel->_selectorCache.play; + break; +#endif + default: + warning("autoDetectFeature: invalid featureDetection value %x", featureDetection); + return false; + } + + reg_t addr; + if (objAddr.isNull()) { + warning("autoDetectFeature: %s object couldn't be found", objName.c_str()); + return false; + } + + if (methodNum == -1) { + if (lookup_selector(_segMan, objAddr, slc, NULL, &addr) != kSelectorMethod) { + warning("autoDetectFeature: target selector is not a method of object %s", objName.c_str()); + return false; + } + } else { + addr = _segMan->getObject(objAddr)->getFunction(methodNum); + } + + uint16 offset = addr.offset; + Script *script = _segMan->getScript(addr.segment); + uint16 intParam = 0xFFFF; + + do { + uint16 kFuncNum; + int opsize = script->_buf[offset++]; + uint opcode = opsize >> 1; + int i = 0; + byte argc; + + if (featureDetection == kDetectLofsType) { + if (opcode == op_lofsa || opcode == op_lofss) { + uint16 lofs; + + // Load lofs operand + if (opsize & 1) { + if (offset >= script->_bufSize) + break; + lofs = script->_buf[offset++]; + } else { + if ((uint32)offset + 1 >= (uint32)script->_bufSize) + break; + lofs = READ_LE_UINT16(script->_buf + offset); + offset += 2; + } + + // Check for going out of bounds when interpreting as abs/rel + if (lofs >= script->_bufSize) + _lofsType = SCI_VERSION_0_EARLY; + + if ((signed)offset + (int16)lofs < 0) + _lofsType = SCI_VERSION_1_MIDDLE; + + if ((signed)offset + (int16)lofs >= (signed)script->_bufSize) + _lofsType = SCI_VERSION_1_MIDDLE; + + if (_lofsType != SCI_VERSION_NONE) + return true; + + // If we reach here, we haven't been able to deduce the lofs parameter + // type, but we have advanced the offset pointer already. So move on + // to the next opcode + continue; + } + } + + if (featureDetection == kDetectSoundType) { + // The play method of the Sound object pushes the DoSound command + // that it'll use just before it calls DoSound. We intercept that here + // in order to check what sound semantics are used, cause the position + // of the sound commands has changed at some point during SCI1 middle + if (opcode == op_pushi) { + // Load the pushi parameter + if (opsize & 1) { + if (offset >= script->_bufSize) + break; + intParam = script->_buf[offset++]; + } else { + if ((uint32)offset + 1 >= (uint32)script->_bufSize) + break; + intParam = READ_LE_UINT16(script->_buf + offset); + offset += 2; + } + + continue; + } + } + + while (g_opcode_formats[opcode][i]) { + switch (g_opcode_formats[opcode][i++]) { + case Script_Invalid: + break; + case Script_SByte: + case Script_Byte: + offset++; + break; + case Script_Word: + case Script_SWord: + offset += 2; + break; + case Script_SVariable: + case Script_Variable: + case Script_Property: + case Script_Global: + case Script_Local: + case Script_Temp: + case Script_Param: + if (opsize & 1) + kFuncNum = script->_buf[offset++]; + else { + kFuncNum = 0xffff & (script->_buf[offset] | (script->_buf[offset + 1] << 8)); + offset += 2; + } + + if (opcode == op_callk) { + argc = script->_buf[offset++]; + + switch (featureDetection) { + case kDetectGfxFunctions: + if (kFuncNum == 8) { // kDrawPic (SCI0 - SCI11) + // If kDrawPic is called with 6 parameters from the + // overlay selector, the game is using old graphics functions. + // Otherwise, if it's called with 8 parameters, it's using new + // graphics functions + _gfxFunctionsType = (argc == 8) ? SCI_VERSION_0_LATE : SCI_VERSION_0_EARLY; + return true; + } + break; + case kDetectMoveCountType: + // Games which ignore move count call kAbs before calling kDoBresen + if (_kernel->getKernelName(kFuncNum) == "Abs") { + foundTarget = true; + } else if (_kernel->getKernelName(kFuncNum) == "DoBresen") { + _moveCountType = foundTarget ? kIgnoreMoveCount : kIncrementMoveCount; + return true; + } + break; + case kDetectSoundType: + // Late SCI1 games call kIsObject before kDoSound + if (kFuncNum == 6) { // kIsObject (SCI0-SCI11) + foundTarget = true; + } else if (kFuncNum == 45) { // kDoSound (SCI1) + // First, check which DoSound function is called by the play method of + // the Sound object + switch (intParam) { + case 1: + _doSoundType = SCI_VERSION_0_EARLY; + break; + case 7: + _doSoundType = SCI_VERSION_1_EARLY; + break; + case 8: + _doSoundType = SCI_VERSION_1_LATE; + break; + default: + // Unknown case... should never happen. We fall back to + // alternative detection here, which works in general, apart from + // some transitive games like Jones CD + _doSoundType = foundTarget ? SCI_VERSION_1_LATE : SCI_VERSION_1_EARLY; + break; + } + + if (_doSoundType != SCI_VERSION_NONE) + return true; + } + break; + case kDetectSetCursorType: + // Games with colored mouse cursors call kIsObject before kSetCursor + if (kFuncNum == 6) { // kIsObject (SCI0-SCI11) + foundTarget = true; + } else if (kFuncNum == 40) { // kSetCursor (SCI0-SCI11) + _setCursorType = foundTarget ? SCI_VERSION_1_1 : SCI_VERSION_0_EARLY; + return true; + } + break; +#ifdef ENABLE_SCI32 + case kDetectSci21KernelTable: + if (kFuncNum == 0x40) { + _sci21KernelType = SCI_VERSION_2; + return true; + } else if (kFuncNum == 0x75) { + _sci21KernelType = SCI_VERSION_2_1; + return true; + } + break; +#endif + default: + break; + } + } + break; + + case Script_Offset: + case Script_SRelative: + offset++; + if (!opsize & 1) + offset++; + break; + case Script_End: + offset = 0; // exit loop + break; + default: + warning("opcode %02x: Invalid", opcode); + + } + } + } while (offset > 0); + + // Some games, like KQ5CD, never actually call SetCursor inside Game::setCursor + // but call isObject. Cover this case here, if we're actually reading the selector + // itself, and not iterating through the Game object (i.e. when the selector + // dictionary is missing) + if (featureDetection == kDetectSetCursorType && methodNum == -1 && foundTarget) { + _setCursorType = SCI_VERSION_1_1; + return true; + } + + return false; // not found +} + +SciVersion GameFeatures::detectDoSoundType() { + if (_doSoundType == SCI_VERSION_NONE) { + if (getSciVersion() == SCI_VERSION_0_EARLY) { + // This game is using early SCI0 sound code (different headers than SCI0 late) + _doSoundType = SCI_VERSION_0_EARLY; + } else if (_kernel->_selectorCache.nodePtr == -1) { + // No nodePtr selector, so this game is definitely using newer + // SCI0 sound code (i.e. SCI_VERSION_0_LATE) + _doSoundType = SCI_VERSION_0_LATE; + } else { + if (getSciVersion() >= SCI_VERSION_1_LATE) { + // All SCI1 late games use the newer doSound semantics + _doSoundType = SCI_VERSION_1_LATE; + } else { + if (!autoDetectFeature(kDetectSoundType)) { + warning("DoSound detection failed, taking an educated guess"); + + if (getSciVersion() >= SCI_VERSION_1_MIDDLE) + _doSoundType = SCI_VERSION_1_LATE; + else if (getSciVersion() > SCI_VERSION_01) + _doSoundType = SCI_VERSION_1_EARLY; + } + } + } + + debugC(1, kDebugLevelSound, "Detected DoSound type: %s", getSciVersionDesc(_doSoundType).c_str()); + } + + return _doSoundType; +} + +SciVersion GameFeatures::detectSetCursorType() { + if (_setCursorType == SCI_VERSION_NONE) { + if (getSciVersion() <= SCI_VERSION_01) { + // SCI0/SCI01 games never use cursor views + _setCursorType = SCI_VERSION_0_EARLY; + } else if (getSciVersion() >= SCI_VERSION_1_EARLY && getSciVersion() <= SCI_VERSION_1_MIDDLE) { + // SCI1 early/SCI1 middle games never use cursor views + _setCursorType = SCI_VERSION_0_EARLY; + } else if (getSciVersion() >= SCI_VERSION_1_1) { + // SCI1.1 games always use cursor views + _setCursorType = SCI_VERSION_1_1; + } else { // SCI1 late game, detect cursor semantics + bool found = false; + + if (_kernel->_selectorCache.setCursor == -1) { + // Find which function of the Game object calls setCursor + + Object *obj = _segMan->getObject(_gameObj); + for (uint m = 0; m < obj->getMethodCount(); m++) { + found = autoDetectFeature(kDetectSetCursorType, m); + if (found) + break; + } + } else { + found = autoDetectFeature(kDetectSetCursorType); + } + + if (!found) { + // Quite normal in several demos which don't have a cursor + warning("SetCursor detection failed, taking an educated guess"); + + if (getSciVersion() >= SCI_VERSION_1_1) + _setCursorType = SCI_VERSION_1_1; + else + _setCursorType = SCI_VERSION_0_EARLY; + } + } + + debugC(1, kDebugLevelGraphics, "Detected SetCursor type: %s", getSciVersionDesc(_setCursorType).c_str()); + } + + return _setCursorType; +} + +SciVersion GameFeatures::detectLofsType() { + if (_lofsType == SCI_VERSION_NONE) { + // This detection only works (and is only needed) for SCI 1 + if (getSciVersion() <= SCI_VERSION_01) { + _lofsType = SCI_VERSION_0_EARLY; + return _lofsType; + } + + if (getSciVersion() >= SCI_VERSION_1_1) { + _lofsType = SCI_VERSION_1_1; + return _lofsType; + } + + // Find a function of the game object which invokes lofsa/lofss + reg_t gameClass = _segMan->findObjectByName("Game"); + Object *obj = _segMan->getObject(gameClass); + bool found = false; + + for (uint m = 0; m < obj->getMethodCount(); m++) { + found = autoDetectFeature(kDetectLofsType, m); + + if (found) + break; + } + + if (!found) { + warning("Lofs detection failed, taking an educated guess"); + + if (getSciVersion() >= SCI_VERSION_1_MIDDLE) + _lofsType = SCI_VERSION_1_MIDDLE; + else + _lofsType = SCI_VERSION_0_EARLY; + } + + debugC(1, kDebugLevelVM, "Detected Lofs type: %s", getSciVersionDesc(_lofsType).c_str()); + } + + return _lofsType; +} + +SciVersion GameFeatures::detectGfxFunctionsType() { + if (_gfxFunctionsType == SCI_VERSION_NONE) { + // This detection only works (and is only needed) for SCI0 games + if (getSciVersion() >= SCI_VERSION_01) { + _gfxFunctionsType = SCI_VERSION_0_LATE; + return _gfxFunctionsType; + } + + if (getSciVersion() > SCI_VERSION_0_EARLY) { + // Check if the game is using an overlay + bool found = false; + + if (_kernel->_selectorCache.overlay == -1) { + // No overlay selector found, check if any method of the Rm object + // is calling kDrawPic, as the overlay selector might be missing in demos + + Object *obj = _segMan->getObject(_segMan->findObjectByName("Rm")); + for (uint m = 0; m < obj->getMethodCount(); m++) { + found = autoDetectFeature(kDetectGfxFunctions, m); + if (found) + break; + } + } + + if (_kernel->_selectorCache.overlay == -1 && !found) { + // No overlay selector found, therefore the game is definitely + // using old graphics functions + _gfxFunctionsType = SCI_VERSION_0_EARLY; + } else if (_kernel->_selectorCache.overlay == -1 && found) { + // Detection already done above + } else { // _kernel->_selectorCache.overlay != -1 + // An in-between case: The game does not have a shiftParser + // selector, but it does have an overlay selector, so it uses an + // overlay. Therefore, check it to see how it calls kDrawPic to + // determine the graphics functions type used + + if (!autoDetectFeature(kDetectGfxFunctions)) { + warning("Graphics functions detection failed, taking an educated guess"); + + // Try detecting the graphics function types from the existence of the motionCue + // selector (which is a bit of a hack) + if (_kernel->findSelector("motionCue") != -1) + _gfxFunctionsType = SCI_VERSION_0_LATE; + else + _gfxFunctionsType = SCI_VERSION_0_EARLY; + } + } + } else { // (getSciVersion() == SCI_VERSION_0_EARLY) + // Old SCI0 games always used old graphics functions + _gfxFunctionsType = SCI_VERSION_0_EARLY; + } + + debugC(1, kDebugLevelVM, "Detected graphics functions type: %s", getSciVersionDesc(_gfxFunctionsType).c_str()); + } + + return _gfxFunctionsType; +} + +#ifdef ENABLE_SCI32 +SciVersion GameFeatures::detectSci21KernelType() { + if (_sci21KernelType == SCI_VERSION_NONE) + if (!autoDetectFeature(kDetectSci21KernelTable)) + error("Could not detect the SCI2.1 kernel table type"); + + debugC(1, kDebugLevelVM, "Detected SCI2.1 kernel type: %s", getSciVersionDesc(_sci21KernelType).c_str()); + + return _sci21KernelType; +} +#endif + +MoveCountType GameFeatures::detectMoveCountType() { + if (_moveCountType == kMoveCountUninitialized) { + // SCI0/SCI01 games always increment move count + if (getSciVersion() <= SCI_VERSION_01) { + _moveCountType = kIncrementMoveCount; + } else { + if (!autoDetectFeature(kDetectMoveCountType)) { + warning("Move count autodetection failed"); + _moveCountType = kIncrementMoveCount; // Most games do this, so best guess + } + } + + debugC(1, kDebugLevelVM, "Detected move count handling: %s", (_moveCountType == kIncrementMoveCount) ? "increment" : "ignore"); + } + + return _moveCountType; +} + +} // End of namespace Sci -- cgit v1.2.3