aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/engine/features.cpp
diff options
context:
space:
mode:
authorFilippos Karapetis2010-02-03 11:02:43 +0000
committerFilippos Karapetis2010-02-03 11:02:43 +0000
commit2fb37063a4ef85aa862b8fdf035d03f6b801679e (patch)
treeb79cdb9e904ffa954d3ff86fef5eee5b616e10d5 /engines/sci/engine/features.cpp
parentbaf6b53431bbb1b5a630fd153fe3df350e3d16fd (diff)
downloadscummvm-rg350-2fb37063a4ef85aa862b8fdf035d03f6b801679e.tar.gz
scummvm-rg350-2fb37063a4ef85aa862b8fdf035d03f6b801679e.tar.bz2
scummvm-rg350-2fb37063a4ef85aa862b8fdf035d03f6b801679e.zip
Placed all the game feature detection code in a separate class
svn-id: r47850
Diffstat (limited to 'engines/sci/engine/features.cpp')
-rw-r--r--engines/sci/engine/features.cpp520
1 files changed, 520 insertions, 0 deletions
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