Initial commit
This commit is contained in:
972
engines/sci/engine/features.cpp
Normal file
972
engines/sci/engine/features.cpp
Normal file
@@ -0,0 +1,972 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/script.h"
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/vm.h"
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/file.h"
|
||||
#include "common/gui_options.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;
|
||||
_messageFunctionType = SCI_VERSION_NONE;
|
||||
_moveCountType = kMoveCountUninitialized;
|
||||
#ifdef ENABLE_SCI32
|
||||
_sci21KernelType = SCI_VERSION_NONE;
|
||||
#endif
|
||||
_usesCdTrack = Common::File::exists("cdaudio.map");
|
||||
if (!ConfMan.getBool("use_cdaudio"))
|
||||
_usesCdTrack = false;
|
||||
_forceDOSTracks = false;
|
||||
_useWindowsCursors = ConfMan.getBool("windows_cursors");
|
||||
_pseudoMouseAbility = kPseudoMouseAbilityUninitialized;
|
||||
_useAudioPopfix = Common::checkGameGUIOption(GAMEOPTION_GK1_ENABLE_AUDIO_POPFIX, ConfMan.get("guioptions")) &&
|
||||
ConfMan.getBool("audio_popfix_enabled");
|
||||
}
|
||||
|
||||
reg_t GameFeatures::getDetectionAddr(const Common::String &objName, Selector slc, int methodNum) {
|
||||
// Get address of target object
|
||||
reg_t objAddr = _segMan->findObjectByName(objName, 0);
|
||||
reg_t addr;
|
||||
|
||||
if (objAddr.isNull()) {
|
||||
error("getDetectionAddr: %s object couldn't be found", objName.c_str());
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
if (methodNum == -1) {
|
||||
if (lookupSelector(_segMan, objAddr, slc, nullptr, &addr) != kSelectorMethod) {
|
||||
error("getDetectionAddr: target selector is not a method of object %s", objName.c_str());
|
||||
return NULL_REG;
|
||||
}
|
||||
} else {
|
||||
addr = _segMan->getObject(objAddr)->getFunction(methodNum);
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
bool GameFeatures::autoDetectSoundType() {
|
||||
// Look up the script address
|
||||
reg_t addr = getDetectionAddr("Sound", SELECTOR(play));
|
||||
|
||||
if (!addr.getSegment())
|
||||
return false;
|
||||
|
||||
uint32 offset = addr.getOffset();
|
||||
Script *script = _segMan->getScript(addr.getSegment());
|
||||
uint16 intParam = 0xFFFF;
|
||||
bool foundTarget = false;
|
||||
|
||||
while (true) {
|
||||
int16 opparams[4];
|
||||
byte extOpcode;
|
||||
byte opcode;
|
||||
offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
|
||||
opcode = extOpcode >> 1;
|
||||
|
||||
// Check for end of script
|
||||
if (opcode == op_ret || offset >= script->getBufSize())
|
||||
break;
|
||||
|
||||
// The play method of the Sound object pushes the DoSound command that
|
||||
// it will 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
|
||||
intParam = opparams[0];
|
||||
} else if (opcode == op_callk) {
|
||||
uint16 kFuncNum = opparams[0];
|
||||
|
||||
// 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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
SciVersion GameFeatures::detectDoSoundType() {
|
||||
if (_doSoundType == SCI_VERSION_NONE) {
|
||||
if (getSciVersion() == SCI_VERSION_0_EARLY) {
|
||||
// Almost all of the SCI0EARLY games use different sound resources than
|
||||
// SCI0LATE. Although the last SCI0EARLY game (lsl2) uses SCI0LATE resources
|
||||
_doSoundType = g_sci->getResMan()->detectEarlySound() ? SCI_VERSION_0_EARLY : SCI_VERSION_0_LATE;
|
||||
#ifdef ENABLE_SCI32
|
||||
} else if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE &&
|
||||
g_sci->getGameId() != GID_SQ6 &&
|
||||
// Assuming MGDX uses SCI2.1early sound mode since SQ6 does
|
||||
// and it was released earlier, but not verified (Phar Lap
|
||||
// Windows-only release)
|
||||
g_sci->getGameId() != GID_MOTHERGOOSEHIRES) {
|
||||
_doSoundType = SCI_VERSION_2_1_MIDDLE;
|
||||
} else if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
|
||||
_doSoundType = SCI_VERSION_2_1_EARLY;
|
||||
} else if (getSciVersion() >= SCI_VERSION_2) {
|
||||
_doSoundType = SCI_VERSION_2;
|
||||
#endif
|
||||
} else if (SELECTOR(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 (!autoDetectSoundType()) {
|
||||
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));
|
||||
}
|
||||
|
||||
return _doSoundType;
|
||||
}
|
||||
|
||||
SciVersion GameFeatures::detectSetCursorType() {
|
||||
if (_setCursorType == SCI_VERSION_NONE) {
|
||||
if (getSciVersion() <= SCI_VERSION_1_MIDDLE) {
|
||||
// SCI1 middle and older 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
|
||||
// If the Cursor object doesn't exist, we're using the SCI0 early
|
||||
// kSetCursor semantics.
|
||||
if (_segMan->findObjectByName("Cursor") == NULL_REG) {
|
||||
_setCursorType = SCI_VERSION_0_EARLY;
|
||||
debugC(1, kDebugLevelGraphics, "Detected SetCursor type: %s", getSciVersionDesc(_setCursorType));
|
||||
return _setCursorType;
|
||||
}
|
||||
|
||||
// Check for the existence of the handCursor object (first found).
|
||||
// This is based on KQ5.
|
||||
reg_t objAddr = _segMan->findObjectByName("handCursor", 0);
|
||||
|
||||
// If that doesn't exist, we assume it uses SCI1.1 kSetCursor semantics
|
||||
if (objAddr == NULL_REG) {
|
||||
_setCursorType = SCI_VERSION_1_1;
|
||||
debugC(1, kDebugLevelGraphics, "Detected SetCursor type: %s", getSciVersionDesc(_setCursorType));
|
||||
return _setCursorType;
|
||||
}
|
||||
|
||||
// Now we check what the number variable holds in the handCursor
|
||||
// object.
|
||||
uint16 number = readSelectorValue(_segMan, objAddr, SELECTOR(number));
|
||||
|
||||
// If the number is 0, it uses views and therefore the SCI1.1
|
||||
// kSetCursor semantics, otherwise it uses the SCI0 early kSetCursor
|
||||
// semantics.
|
||||
if (number == 0)
|
||||
// KQ5 CD's DOS interpreter contained the new kSetCusor API while
|
||||
// the Windows interpreter contained the old. The scripts tested
|
||||
// the platform to see which version to call.
|
||||
if (g_sci->getGameId() == GID_KQ5 && _useWindowsCursors) {
|
||||
_setCursorType = SCI_VERSION_0_EARLY;
|
||||
} else {
|
||||
_setCursorType = SCI_VERSION_1_1;
|
||||
}
|
||||
else
|
||||
_setCursorType = SCI_VERSION_0_EARLY;
|
||||
}
|
||||
|
||||
debugC(1, kDebugLevelGraphics, "Detected SetCursor type: %s", getSciVersionDesc(_setCursorType));
|
||||
}
|
||||
|
||||
return _setCursorType;
|
||||
}
|
||||
|
||||
bool GameFeatures::autoDetectLofsType(const Common::String &gameSuperClassName, int methodNum) {
|
||||
// Look up the script address
|
||||
reg_t addr = getDetectionAddr(gameSuperClassName.c_str(), -1, methodNum);
|
||||
|
||||
if (!addr.getSegment())
|
||||
return false;
|
||||
|
||||
uint32 offset = addr.getOffset();
|
||||
Script *script = _segMan->getScript(addr.getSegment());
|
||||
|
||||
while (true) {
|
||||
int16 opparams[4];
|
||||
byte extOpcode;
|
||||
byte opcode;
|
||||
offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
|
||||
opcode = extOpcode >> 1;
|
||||
|
||||
// Check for end of script
|
||||
if (opcode == op_ret || offset >= script->getBufSize())
|
||||
break;
|
||||
|
||||
if (opcode == op_lofsa || opcode == op_lofss) {
|
||||
// Load lofs operand
|
||||
uint16 lofs = opparams[0];
|
||||
|
||||
// Check for going out of bounds when interpreting as abs/rel
|
||||
if (lofs >= script->getBufSize())
|
||||
_lofsType = SCI_VERSION_0_EARLY;
|
||||
|
||||
if ((signed)offset + (int16)lofs < 0)
|
||||
_lofsType = SCI_VERSION_1_MIDDLE;
|
||||
|
||||
if ((signed)offset + (int16)lofs >= (signed)script->getBufSize())
|
||||
_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 so far.
|
||||
}
|
||||
}
|
||||
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
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 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
|
||||
// SCI1.1 type, i.e. we compensate for the fact that the heap is attached
|
||||
// to the end of the script
|
||||
_lofsType = SCI_VERSION_1_1;
|
||||
return _lofsType;
|
||||
}
|
||||
|
||||
if (getSciVersion() == SCI_VERSION_3) {
|
||||
// SCI3 type, same as pre-SCI1.1, really, as there is no separate heap
|
||||
// resource
|
||||
_lofsType = SCI_VERSION_3;
|
||||
return _lofsType;
|
||||
}
|
||||
|
||||
// Find a function of the "Game" object (which is the game super class) which invokes lofsa/lofss
|
||||
const Object *gameObject = _segMan->getObject(g_sci->getGameObject());
|
||||
const Object *gameSuperObject = _segMan->getObject(gameObject->getSuperClassSelector());
|
||||
bool found = false;
|
||||
if (gameSuperObject) {
|
||||
Common::String gameSuperClassName = _segMan->getObjectName(gameObject->getSuperClassSelector());
|
||||
|
||||
for (uint m = 0; m < gameSuperObject->getMethodCount(); m++) {
|
||||
found = autoDetectLofsType(gameSuperClassName, m);
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
warning("detectLofsType(): Could not find superclass of game object");
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
warning("detectLofsType(): 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));
|
||||
}
|
||||
|
||||
return _lofsType;
|
||||
}
|
||||
|
||||
bool GameFeatures::autoDetectGfxFunctionsType(int methodNum) {
|
||||
// Look up the script address
|
||||
reg_t addr = getDetectionAddr("Rm", SELECTOR(overlay), methodNum);
|
||||
|
||||
if (!addr.getSegment())
|
||||
return false;
|
||||
|
||||
uint32 offset = addr.getOffset();
|
||||
Script *script = _segMan->getScript(addr.getSegment());
|
||||
|
||||
while (true) {
|
||||
int16 opparams[4];
|
||||
byte extOpcode;
|
||||
byte opcode;
|
||||
offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
|
||||
opcode = extOpcode >> 1;
|
||||
|
||||
// Check for end of script
|
||||
if (opcode == op_ret || offset >= script->getBufSize())
|
||||
break;
|
||||
|
||||
if (opcode == op_callk) {
|
||||
uint16 kFuncNum = opparams[0];
|
||||
uint16 argc = opparams[1] / 2;
|
||||
|
||||
if (kFuncNum == 8) { // kDrawPic (SCI0 - SCI11)
|
||||
// If kDrawPic is called with 3 parameters from the overlay
|
||||
// method then the game is using old graphics functions.
|
||||
// If instead it's called with 4 parameters then it's using
|
||||
// the newer ones. (KQ4 late, SQ3 1.018)
|
||||
// Ignore other arg counts as those are unrelated to overlays
|
||||
// and this detection gets run on all Rm methods when the
|
||||
// overlay selector doesn't exist.
|
||||
if (argc == 3) {
|
||||
_gfxFunctionsType = SCI_VERSION_0_EARLY;
|
||||
return true;
|
||||
} else if (argc == 4) {
|
||||
_gfxFunctionsType = SCI_VERSION_0_LATE;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
SciVersion GameFeatures::detectGfxFunctionsType() {
|
||||
if (_gfxFunctionsType == SCI_VERSION_NONE) {
|
||||
if (getSciVersion() == SCI_VERSION_0_EARLY) {
|
||||
// Old SCI0 games always used old graphics functions
|
||||
_gfxFunctionsType = SCI_VERSION_0_EARLY;
|
||||
} else if (getSciVersion() >= SCI_VERSION_01) {
|
||||
// SCI01 and newer games always used new graphics functions
|
||||
_gfxFunctionsType = SCI_VERSION_0_LATE;
|
||||
} else { // SCI0 late
|
||||
// Check if the game is using an overlay
|
||||
bool searchRoomObj = false;
|
||||
reg_t rmObjAddr = _segMan->findObjectByName("Rm");
|
||||
|
||||
if (SELECTOR(overlay) != -1) {
|
||||
// The game has an overlay selector, check how it calls kDrawPic
|
||||
// to determine the graphics functions type used
|
||||
if (lookupSelector(_segMan, rmObjAddr, SELECTOR(overlay), nullptr, nullptr) == kSelectorMethod) {
|
||||
if (!autoDetectGfxFunctionsType()) {
|
||||
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 {
|
||||
// The game has an overlay selector, but it's not a method
|
||||
// of the Rm object (like in Hoyle 1 and 2), so search for
|
||||
// other methods
|
||||
searchRoomObj = true;
|
||||
}
|
||||
} else {
|
||||
// The game doesn't have an overlay selector, so search for it
|
||||
// manually
|
||||
searchRoomObj = true;
|
||||
}
|
||||
|
||||
if (searchRoomObj) {
|
||||
// If requested, check if any method of the Rm object is calling
|
||||
// kDrawPic, as the overlay selector might be missing in demos
|
||||
bool found = false;
|
||||
|
||||
const Object *obj = _segMan->getObject(rmObjAddr);
|
||||
for (uint m = 0; m < obj->getMethodCount(); m++) {
|
||||
found = autoDetectGfxFunctionsType(m);
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// No method of the Rm object is calling kDrawPic with
|
||||
// 3 or 4 parameters, thus we assume that the game doesn't
|
||||
// have overlays and is using older graphics functions.
|
||||
_gfxFunctionsType = SCI_VERSION_0_EARLY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debugC(1, kDebugLevelVM, "Detected graphics functions type: %s", getSciVersionDesc(_gfxFunctionsType));
|
||||
}
|
||||
|
||||
return _gfxFunctionsType;
|
||||
}
|
||||
|
||||
SciVersion GameFeatures::detectMessageFunctionType() {
|
||||
if (_messageFunctionType != SCI_VERSION_NONE)
|
||||
return _messageFunctionType;
|
||||
|
||||
if (getSciVersion() > SCI_VERSION_1_1) {
|
||||
_messageFunctionType = SCI_VERSION_1_1;
|
||||
return _messageFunctionType;
|
||||
} else if (getSciVersion() < SCI_VERSION_1_1) {
|
||||
_messageFunctionType = SCI_VERSION_1_LATE;
|
||||
return _messageFunctionType;
|
||||
}
|
||||
|
||||
Common::List<ResourceId> resources = g_sci->getResMan()->listResources(kResourceTypeMessage, -1);
|
||||
|
||||
if (resources.empty()) {
|
||||
// No messages found, so this doesn't really matter anyway...
|
||||
_messageFunctionType = SCI_VERSION_1_1;
|
||||
return _messageFunctionType;
|
||||
}
|
||||
|
||||
Resource *res = g_sci->getResMan()->findResource(*resources.begin(), false);
|
||||
assert(res);
|
||||
|
||||
// Only v2 Message resources use the kGetMessage kernel function.
|
||||
// v3-v5 use the kMessage kernel function.
|
||||
|
||||
if (res->getUint32SEAt(0) / 1000 == 2)
|
||||
_messageFunctionType = SCI_VERSION_1_LATE;
|
||||
else
|
||||
_messageFunctionType = SCI_VERSION_1_1;
|
||||
|
||||
debugC(1, kDebugLevelVM, "Detected message function type: %s", getSciVersionDesc(_messageFunctionType));
|
||||
return _messageFunctionType;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
bool GameFeatures::autoDetectSci21KernelType() {
|
||||
// First, check if the Sound object is loaded
|
||||
reg_t soundObjAddr = _segMan->findObjectByName("Sound");
|
||||
if (soundObjAddr.isNull()) {
|
||||
// Usually, this means that the Sound object isn't loaded yet.
|
||||
// This case doesn't occur in early SCI2.1 games, and we've only
|
||||
// seen it happen in the RAMA demo, thus we can assume that the
|
||||
// game is using a SCI2.1 table
|
||||
|
||||
// HACK: The Inside the Chest Demo and King's Questions minigame
|
||||
// don't have sounds at all, but they're using a SCI2 kernel
|
||||
if (g_sci->getGameId() == GID_CHEST || g_sci->getGameId() == GID_SHIELD || g_sci->getGameId() == GID_KQUESTIONS) {
|
||||
_sci21KernelType = SCI_VERSION_2;
|
||||
} else if (g_sci->getGameId() == GID_RAMA && g_sci->isDemo()) {
|
||||
_sci21KernelType = SCI_VERSION_2_1_MIDDLE;
|
||||
} else {
|
||||
warning("autoDetectSci21KernelType(): Sound object not loaded, assuming a SCI2.1 table");
|
||||
_sci21KernelType = SCI_VERSION_2_1_EARLY;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Look up the script address
|
||||
reg_t addr = getDetectionAddr("Sound", SELECTOR(play));
|
||||
|
||||
if (!addr.getSegment())
|
||||
return false;
|
||||
|
||||
uint32 offset = addr.getOffset();
|
||||
Script *script = _segMan->getScript(addr.getSegment());
|
||||
|
||||
while (true) {
|
||||
int16 opparams[4];
|
||||
byte extOpcode;
|
||||
byte opcode;
|
||||
offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
|
||||
opcode = extOpcode >> 1;
|
||||
|
||||
// Check for end of script
|
||||
// We don't check for op_ret here because the Phantasmagoria Mac script
|
||||
// has an op_ret early on in its script (controlled by a branch).
|
||||
if (offset >= script->getBufSize())
|
||||
break;
|
||||
|
||||
if (opcode == op_callk) {
|
||||
uint16 kFuncNum = opparams[0];
|
||||
|
||||
// Here we check for the kDoSound opcode that's used in SCI2.1.
|
||||
// Finding 0x40 as kDoSound in the Sound::play() function means the
|
||||
// game is using the modified SCI2 kernel table found in some older
|
||||
// SCI2.1 games (GK2 demo, KQ7 v1.4).
|
||||
// Finding 0x75 as kDoSound means the game is using the regular
|
||||
// SCI2.1 kernel table.
|
||||
if (kFuncNum == 0x40) {
|
||||
_sci21KernelType = SCI_VERSION_2;
|
||||
return true;
|
||||
} else if (kFuncNum == 0x75) {
|
||||
_sci21KernelType = SCI_VERSION_2_1_EARLY;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
SciVersion GameFeatures::detectSci21KernelType() {
|
||||
if (_sci21KernelType == SCI_VERSION_NONE) {
|
||||
if (!autoDetectSci21KernelType())
|
||||
error("Could not detect the SCI2.1 kernel table type");
|
||||
|
||||
debugC(1, kDebugLevelVM, "Detected SCI2.1 kernel type: %s", getSciVersionDesc(_sci21KernelType));
|
||||
}
|
||||
return _sci21KernelType;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool GameFeatures::supportsSpeechWithSubtitles() const {
|
||||
switch (g_sci->getGameId()) {
|
||||
case GID_SQ4:
|
||||
case GID_FREDDYPHARKAS:
|
||||
case GID_ECOQUEST:
|
||||
case GID_LSL6:
|
||||
case GID_LAURABOW2:
|
||||
case GID_KQ6:
|
||||
#ifdef ENABLE_SCI32
|
||||
case GID_GK1:
|
||||
case GID_KQ7:
|
||||
case GID_LSL6HIRES:
|
||||
case GID_LSL7:
|
||||
case GID_PQ4:
|
||||
case GID_QFG4:
|
||||
case GID_SQ6:
|
||||
case GID_TORIN:
|
||||
#endif
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameFeatures::audioVolumeSyncUsesGlobals() const {
|
||||
switch (g_sci->getGameId()) {
|
||||
case GID_GK1:
|
||||
case GID_GK2:
|
||||
case GID_LSL6:
|
||||
case GID_LSL6HIRES:
|
||||
case GID_LSL7:
|
||||
case GID_PHANTASMAGORIA:
|
||||
case GID_PHANTASMAGORIA2:
|
||||
case GID_RAMA:
|
||||
case GID_TORIN:
|
||||
return true;
|
||||
case GID_HOYLE5:
|
||||
// Hoyle school house math does not use a volume global
|
||||
return !g_sci->getResMan()->testResource(ResourceId(kResourceTypeView, 21));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MessageTypeSyncStrategy GameFeatures::getMessageTypeSyncStrategy() const {
|
||||
if (getSciVersion() < SCI_VERSION_1_1) {
|
||||
return kMessageTypeSyncStrategyNone;
|
||||
}
|
||||
|
||||
if (getSciVersion() == SCI_VERSION_1_1 && g_sci->isCD()) {
|
||||
return kMessageTypeSyncStrategyDefault;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
switch (g_sci->getGameId()) {
|
||||
case GID_GK1:
|
||||
case GID_PQ4:
|
||||
case GID_QFG4:
|
||||
return g_sci->isCD() ? kMessageTypeSyncStrategyDefault : kMessageTypeSyncStrategyNone;
|
||||
|
||||
case GID_KQ7:
|
||||
case GID_LSL7:
|
||||
case GID_MOTHERGOOSEHIRES:
|
||||
case GID_PHANTASMAGORIA:
|
||||
case GID_TORIN:
|
||||
return kMessageTypeSyncStrategyDefault;
|
||||
|
||||
case GID_LSL6HIRES:
|
||||
return kMessageTypeSyncStrategyLSL6Hires;
|
||||
|
||||
case GID_SHIVERS:
|
||||
return kMessageTypeSyncStrategyShivers;
|
||||
|
||||
case GID_SQ6:
|
||||
// don't sync the early demos; they are speechless and
|
||||
// require the message type global to remain unchanged.
|
||||
return (g_sci->isDemo() && getSciVersion() < SCI_VERSION_2_1_MIDDLE) ?
|
||||
kMessageTypeSyncStrategyNone :
|
||||
kMessageTypeSyncStrategyDefault;
|
||||
|
||||
case GID_GK2:
|
||||
case GID_PQSWAT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
return kMessageTypeSyncStrategyNone;
|
||||
}
|
||||
|
||||
int GameFeatures::detectPlaneIdBase() {
|
||||
if (getSciVersion() == SCI_VERSION_2 &&
|
||||
g_sci->getGameId() != GID_PQ4)
|
||||
return 0;
|
||||
else
|
||||
return 20000;
|
||||
}
|
||||
|
||||
bool GameFeatures::autoDetectMoveCountType() {
|
||||
// Look up the script address
|
||||
reg_t addr = getDetectionAddr("Motion", SELECTOR(doit));
|
||||
|
||||
if (!addr.getSegment())
|
||||
return false;
|
||||
|
||||
uint32 offset = addr.getOffset();
|
||||
Script *script = _segMan->getScript(addr.getSegment());
|
||||
bool foundTarget = false;
|
||||
|
||||
while (true) {
|
||||
int16 opparams[4];
|
||||
byte extOpcode;
|
||||
byte opcode;
|
||||
offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
|
||||
opcode = extOpcode >> 1;
|
||||
|
||||
// Check for end of script
|
||||
if (opcode == op_ret || offset >= script->getBufSize())
|
||||
break;
|
||||
|
||||
if (opcode == op_callk) {
|
||||
uint16 kFuncNum = opparams[0];
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
MoveCountType GameFeatures::detectMoveCountType() {
|
||||
if (_moveCountType == kMoveCountUninitialized) {
|
||||
// SCI0/SCI01 games always increment move count
|
||||
if (getSciVersion() <= SCI_VERSION_01) {
|
||||
_moveCountType = kIncrementMoveCount;
|
||||
} else if (getSciVersion() >= SCI_VERSION_1_1) {
|
||||
// SCI1.1 and newer games always ignore move count
|
||||
_moveCountType = kIgnoreMoveCount;
|
||||
} else {
|
||||
if (!autoDetectMoveCountType()) {
|
||||
error("Move count autodetection failed");
|
||||
}
|
||||
}
|
||||
|
||||
debugC(1, kDebugLevelVM, "Detected move count handling: %s", (_moveCountType == kIncrementMoveCount) ? "increment" : "ignore");
|
||||
}
|
||||
|
||||
return _moveCountType;
|
||||
}
|
||||
|
||||
bool GameFeatures::useAltWinGMSound() {
|
||||
if (g_sci && g_sci->getPlatform() == Common::kPlatformWindows && g_sci->isCD() && !_forceDOSTracks) {
|
||||
SciGameId id = g_sci->getGameId();
|
||||
return (id == GID_ECOQUEST ||
|
||||
id == GID_JONES ||
|
||||
id == GID_KQ5 ||
|
||||
//id == GID_FREDDYPHARKAS || // Has alternate tracks, but handles them differently
|
||||
id == GID_SQ4);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameFeatures::generalMidiOnly() {
|
||||
#ifdef ENABLE_SCI32
|
||||
switch (g_sci->getGameId()) {
|
||||
case GID_MOTHERGOOSEHIRES:
|
||||
return (g_sci->getPlatform() != Common::kPlatformMacintosh);
|
||||
|
||||
case GID_KQ7: {
|
||||
if (g_sci->isDemo()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SoundResource sound(13, g_sci->getResMan(), detectDoSoundType());
|
||||
return (sound.exists() && sound.getTrackByType(/* AdLib */ 0) == nullptr);
|
||||
}
|
||||
default:
|
||||
if (g_sci->getPlatform() == Common::kPlatformMacintosh &&
|
||||
getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// PseudoMouse was added during SCI1
|
||||
// PseudoMouseAbility is about a tiny difference in the keyboard driver, which sets the event type to either
|
||||
// 40h (old behaviour) or 44h (the keyboard driver actually added 40h to the existing value).
|
||||
// See engine/kevent.cpp, kMapKeyToDir - also script 933
|
||||
|
||||
// SCI1EGA:
|
||||
// Quest for Glory 2 still used the old way.
|
||||
//
|
||||
// SCI1EARLY:
|
||||
// King's Quest 5 0.000.062 uses the old way.
|
||||
// Leisure Suit Larry 1 demo uses the new way, but no PseudoMouse class.
|
||||
// Fairy Tales uses the new way.
|
||||
// X-Mas 1990 uses the old way, no PseudoMouse class.
|
||||
// Space Quest 4 floppy (1.1) uses the new way.
|
||||
// Mixed Up Mother Goose uses the old way, no PseudoMouse class.
|
||||
//
|
||||
// SCI1MIDDLE:
|
||||
// Leisure Suit Larry 5 demo uses the new way.
|
||||
// Conquests of the Longbow demo uses the new way.
|
||||
// Leisure Suit Larry 1 (2.0) uses the new way.
|
||||
// Astro Chicken II uses the new way.
|
||||
PseudoMouseAbilityType GameFeatures::detectPseudoMouseAbility() {
|
||||
if (_pseudoMouseAbility == kPseudoMouseAbilityUninitialized) {
|
||||
if (getSciVersion() < SCI_VERSION_1_EARLY) {
|
||||
// SCI1 EGA or earlier -> pseudo mouse ability is always disabled
|
||||
_pseudoMouseAbility = kPseudoMouseAbilityFalse;
|
||||
|
||||
} else if (getSciVersion() == SCI_VERSION_1_EARLY) {
|
||||
// For SCI1 early some games had it enabled, some others didn't.
|
||||
// We try to find an object called "PseudoMouse". If it's found, we enable the ability otherwise we don't.
|
||||
reg_t pseudoMouseAddr = _segMan->findObjectByName("PseudoMouse", 0);
|
||||
|
||||
if (pseudoMouseAddr != NULL_REG) {
|
||||
_pseudoMouseAbility = kPseudoMouseAbilityTrue;
|
||||
} else {
|
||||
_pseudoMouseAbility = kPseudoMouseAbilityFalse;
|
||||
}
|
||||
|
||||
} else {
|
||||
// SCI1 middle or later -> pseudo mouse ability is always enabled
|
||||
_pseudoMouseAbility = kPseudoMouseAbilityTrue;
|
||||
}
|
||||
}
|
||||
return _pseudoMouseAbility;
|
||||
}
|
||||
|
||||
// GetLongest(), which calculates the number of characters in a string that can fit
|
||||
// within a width, had two subtle changes which started to appear in interpreters
|
||||
// in late 1990. An off-by-one bug was fixed where the character that exceeds the
|
||||
// width would be applied to the result if a space character hadn't been reached.
|
||||
// The pixel width test was also changed from a greater than or equals to greater
|
||||
// than, but again only if a space character hadn't been reached.
|
||||
//
|
||||
// The notebook in LB1 (bug #10000) is currently the only known script that depended
|
||||
// on the original behavior. This appears to be an isolated fix to an interpreter
|
||||
// edge case, a corresponding script change to allow autodetection hasn't been found.
|
||||
//
|
||||
// The Japanese interpreters have their own versions of GetLongest() to support
|
||||
// double byte characters which seems to be how QFG1 Japanese reintroduced it
|
||||
// even though its interpreter is later than SQ3/LSL3 multilingual versions.
|
||||
bool GameFeatures::useEarlyGetLongestTextCalculations() const {
|
||||
switch (getSciVersion()) {
|
||||
|
||||
// All SCI0, confirmed:
|
||||
// - LSL2 English PC 1.000.011
|
||||
// - LB1 PC 1.000.046
|
||||
// - ICEMAN PC 1.033
|
||||
// - SQ3 English PC 1.018
|
||||
// - PQ2 Japanese 1.000.052
|
||||
case SCI_VERSION_0_EARLY:
|
||||
case SCI_VERSION_0_LATE:
|
||||
return true;
|
||||
|
||||
// SCI01: confirmed KQ1 and QFG1 Japanese,
|
||||
// fixed in SQ3 and LSL3 multilingual PC
|
||||
case SCI_VERSION_01:
|
||||
return (g_sci->getGameId() == GID_KQ1 || g_sci->getGameId() == GID_QFG1);
|
||||
|
||||
// QFG2, confirmed 1.000 and 1.105 (first and last versions)
|
||||
case SCI_VERSION_1_EGA_ONLY:
|
||||
return true;
|
||||
|
||||
// SCI1 Early: just KQ5 English PC versions,
|
||||
// confirmed fixed in:
|
||||
// - LSL1 Demo
|
||||
// - XMAS1990 EGA
|
||||
// - SQ4 1.052
|
||||
case SCI_VERSION_1_EARLY:
|
||||
return (g_sci->getGameId() == GID_KQ5);
|
||||
|
||||
// Fixed in all other versions
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameFeatures::hasScriptObjectNames() const {
|
||||
switch (g_sci->getGameId()) {
|
||||
case GID_HOYLE4:
|
||||
case GID_LSL6:
|
||||
case GID_QFG1VGA:
|
||||
return (g_sci->getPlatform() != Common::kPlatformMacintosh);
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameFeatures::canSaveFromGMM() const {
|
||||
if (!ConfMan.getBool("gmm_save_enabled"))
|
||||
return false;
|
||||
|
||||
switch (g_sci->getGameId()) {
|
||||
// ==== Demos/mini-games with no saving functionality ====
|
||||
case GID_ASTROCHICKEN:
|
||||
case GID_CHEST:
|
||||
case GID_CHRISTMAS1988:
|
||||
case GID_CHRISTMAS1990:
|
||||
case GID_CHRISTMAS1992:
|
||||
case GID_CNICK_KQ:
|
||||
case GID_CNICK_LAURABOW:
|
||||
case GID_CNICK_LONGBOW:
|
||||
case GID_CNICK_LSL:
|
||||
case GID_CNICK_SQ:
|
||||
case GID_FUNSEEKER:
|
||||
case GID_INNDEMO:
|
||||
case GID_KQUESTIONS:
|
||||
case GID_MSASTROCHICKEN:
|
||||
// ==== Games with a different saving scheme =============
|
||||
case GID_HOYLE1:
|
||||
case GID_HOYLE2:
|
||||
case GID_HOYLE3:
|
||||
case GID_HOYLE4:
|
||||
case GID_HOYLE5:
|
||||
case GID_JONES:
|
||||
case GID_MOTHERGOOSE:
|
||||
case GID_MOTHERGOOSE256:
|
||||
case GID_MOTHERGOOSEHIRES:
|
||||
case GID_PHANTASMAGORIA:
|
||||
case GID_RAMA:
|
||||
case GID_SLATER:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
uint16 GameFeatures::getGameFlagsGlobal() const {
|
||||
Common::Platform platform = g_sci->getPlatform();
|
||||
switch (g_sci->getGameId()) {
|
||||
case GID_CAMELOT: return 250;
|
||||
case GID_CASTLEBRAIN: return 250;
|
||||
case GID_ECOQUEST: return (getSciVersion() == SCI_VERSION_1_1) ? 152 : 150;
|
||||
case GID_ECOQUEST2: return 110;
|
||||
case GID_FAIRYTALES: return 250;
|
||||
case GID_FREDDYPHARKAS: return 186;
|
||||
case GID_GK1: return 127;
|
||||
case GID_GK2: return 150;
|
||||
// ICEMAN uses object properties
|
||||
case GID_ISLANDBRAIN: return 250;
|
||||
case GID_KQ1: return 150;
|
||||
// KQ4 has no flags
|
||||
case GID_KQ5: return 129;
|
||||
case GID_KQ6: return 137;
|
||||
case GID_KQ7: return 127;
|
||||
case GID_LAURABOW: return 440;
|
||||
case GID_LAURABOW2: return 186;
|
||||
case GID_LIGHTHOUSE: return 116;
|
||||
case GID_LONGBOW: return 200;
|
||||
case GID_LSL1: return 111;
|
||||
// LSL2 has no flags
|
||||
case GID_LSL3: return 111;
|
||||
case GID_LSL5: return 186;
|
||||
case GID_LSL6: return 137;
|
||||
// LSL6HIRES uses a flags object
|
||||
case GID_PEPPER: return 134;
|
||||
case GID_PHANTASMAGORIA: return 250;
|
||||
case GID_PHANTASMAGORIA2: return 101;
|
||||
case GID_PQ1: return 134;
|
||||
case GID_PQ2: return (platform != Common::kPlatformPC98) ? 250 : 245;
|
||||
case GID_PQ3: return 165;
|
||||
// PQ4 uses object properties
|
||||
case GID_PQSWAT: return 150;
|
||||
case GID_QFG1: return 350;
|
||||
case GID_QFG1VGA: return 290;
|
||||
case GID_QFG2: return 700;
|
||||
case GID_QFG3: return 500;
|
||||
case GID_QFG4: return 500;
|
||||
case GID_RAMA: return 300;
|
||||
case GID_SHIVERS: return 209;
|
||||
case GID_SQ1: return 118;
|
||||
case GID_SQ4: return 114;
|
||||
case GID_SQ5: return 183;
|
||||
case GID_SQ6: return 250;
|
||||
// TORIN uses a flags object
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameFeatures::isGameFlagBitOrderNormal() const {
|
||||
// Most games store flags in reverse bit order
|
||||
switch (g_sci->getGameId()) {
|
||||
case GID_KQ5:
|
||||
case GID_LAURABOW:
|
||||
case GID_PEPPER:
|
||||
case GID_PQ1:
|
||||
case GID_PQ3:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
340
engines/sci/engine/features.h
Normal file
340
engines/sci/engine/features.h
Normal file
@@ -0,0 +1,340 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_FEATURES_H
|
||||
#define SCI_ENGINE_FEATURES_H
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/engine/seg_manager.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
enum MoveCountType {
|
||||
kMoveCountUninitialized,
|
||||
kIgnoreMoveCount,
|
||||
kIncrementMoveCount
|
||||
};
|
||||
|
||||
enum PseudoMouseAbilityType {
|
||||
kPseudoMouseAbilityUninitialized,
|
||||
kPseudoMouseAbilityFalse,
|
||||
kPseudoMouseAbilityTrue
|
||||
};
|
||||
|
||||
enum MessageTypeSyncStrategy {
|
||||
kMessageTypeSyncStrategyNone,
|
||||
kMessageTypeSyncStrategyDefault
|
||||
#ifdef ENABLE_SCI32
|
||||
,
|
||||
kMessageTypeSyncStrategyLSL6Hires,
|
||||
kMessageTypeSyncStrategyShivers
|
||||
#endif
|
||||
};
|
||||
|
||||
enum {
|
||||
kSpeedThrottleDefaultDelay = 30 // kGameIsRestarting default max delay in ms
|
||||
};
|
||||
|
||||
class GameFeatures {
|
||||
public:
|
||||
GameFeatures(SegManager *segMan, Kernel *kernel);
|
||||
~GameFeatures() {}
|
||||
|
||||
/**
|
||||
* Autodetects the DoSound type
|
||||
* @return DoSound type, SCI_VERSION_0_EARLY / SCI_VERSION_0_LATE /
|
||||
* SCI_VERSION_1_EARLY / SCI_VERSION_1_LATE
|
||||
*/
|
||||
SciVersion detectDoSoundType();
|
||||
|
||||
/**
|
||||
* Autodetects the SetCursor type
|
||||
* @return SetCursor type, SCI_VERSION_0_EARLY / SCI_VERSION_1_1
|
||||
*/
|
||||
SciVersion detectSetCursorType();
|
||||
|
||||
/**
|
||||
* Autodetects the Lofs type
|
||||
* @return Lofs type, SCI_VERSION_0_EARLY / SCI_VERSION_1_MIDDLE / SCI_VERSION_1_1 / SCI_VERSION_3
|
||||
*/
|
||||
SciVersion detectLofsType();
|
||||
|
||||
/**
|
||||
* Autodetects the graphics functions used
|
||||
* @return Graphics functions type, SCI_VERSION_0_EARLY / SCI_VERSION_0_LATE
|
||||
*/
|
||||
SciVersion detectGfxFunctionsType();
|
||||
|
||||
/**
|
||||
* Autodetects the message function used
|
||||
* @return Message function type, SCI_VERSION_1_LATE / SCI_VERSION_1_1
|
||||
*/
|
||||
SciVersion detectMessageFunctionType();
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Autodetects the kernel functions used in SCI2.1
|
||||
* @return Graphics functions type, SCI_VERSION_2 / SCI_VERSION_2_1
|
||||
*/
|
||||
SciVersion detectSci21KernelType();
|
||||
|
||||
inline bool usesModifiedAudioAttenuation() const {
|
||||
switch (g_sci->getGameId()) {
|
||||
case GID_PQ4:
|
||||
case GID_QFG4:
|
||||
return g_sci->isCD();
|
||||
case GID_MOTHERGOOSEHIRES:
|
||||
case GID_SQ6:
|
||||
// SQ6 1.0 uses modified attenuation, 1.11 does not.
|
||||
// The interpreters are different even though they both have the
|
||||
// same date and version string. ("May 24 1995", "2.100.002")
|
||||
return true;
|
||||
case GID_KQ7:
|
||||
// KQ7 1.51 (SCI2.1early) uses the non-standard attenuation, but
|
||||
// 2.00b (SCI2.1mid) does not
|
||||
return getSciVersion() == SCI_VERSION_2_1_EARLY;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool gameScriptsControlMasterVolume() const {
|
||||
switch (g_sci->getGameId()) {
|
||||
case GID_LSL7:
|
||||
case GID_PHANTASMAGORIA2:
|
||||
case GID_TORIN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool hasSci3Audio() const {
|
||||
return getSciVersion() == SCI_VERSION_3 || g_sci->getGameId() == GID_GK2;
|
||||
}
|
||||
|
||||
inline bool hasTransparentPicturePlanes() const {
|
||||
const SciGameId &gid = g_sci->getGameId();
|
||||
|
||||
// MGDX is assumed to not have transparent picture planes since it
|
||||
// was released before SQ6, but this has not been verified since it
|
||||
// cannot be disassembled at the moment (Phar Lap Windows-only release)
|
||||
return getSciVersion() >= SCI_VERSION_2_1_MIDDLE &&
|
||||
gid != GID_SQ6 &&
|
||||
gid != GID_MOTHERGOOSEHIRES;
|
||||
}
|
||||
|
||||
inline bool hasMidPaletteCode() const {
|
||||
return getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7;
|
||||
}
|
||||
|
||||
inline bool hasLatePaletteCode() const {
|
||||
return getSciVersion() > SCI_VERSION_2_1_MIDDLE ||
|
||||
g_sci->getGameId() == GID_GK2 ||
|
||||
g_sci->getGameId() == GID_PQSWAT ||
|
||||
// Guessing that Shivers has the late palette code because it has a
|
||||
// brightness slider
|
||||
g_sci->getGameId() == GID_SHIVERS ||
|
||||
g_sci->getGameId() == GID_TORIN;
|
||||
}
|
||||
|
||||
inline bool VMDOpenStopsAudio() const {
|
||||
// Of the games that use VMDs:
|
||||
// Yes: Phant1, Shivers, Torin
|
||||
// No: SQ6
|
||||
// TODO: Optional extra flag to kPlayVMD which defaults to Yes: PQ:SWAT
|
||||
// TODO: SCI3, GK2 (GK2's VMD code is closer to SCI3 than SCI21)
|
||||
return getSciVersion() == SCI_VERSION_2_1_MIDDLE &&
|
||||
g_sci->getGameId() != GID_SQ6 &&
|
||||
g_sci->getGameId() != GID_GK2;
|
||||
}
|
||||
|
||||
inline bool useDoSoundMac32() const {
|
||||
// Several SCI 2.1 Middle Mac games use a modified kDoSound with
|
||||
// different subop numbers.
|
||||
return g_sci->getPlatform() == Common::kPlatformMacintosh &&
|
||||
(g_sci->getGameId() == GID_HOYLE5 ||
|
||||
g_sci->getGameId() == GID_PHANTASMAGORIA ||
|
||||
g_sci->getGameId() == GID_PQSWAT ||
|
||||
g_sci->getGameId() == GID_SHIVERS ||
|
||||
g_sci->getGameId() == GID_SQ6);
|
||||
}
|
||||
|
||||
inline bool useMacGammaLevel() const {
|
||||
// SCI32 Mac interpreters were hard-coded to use gamma level 2 until
|
||||
// Torin's Passage, PQSWAT, and the 2.1 Late games. The colors in
|
||||
// the game resources are significantly darker than their PC versions.
|
||||
// Confirmed in disassembly of all Mac interpreters.
|
||||
return g_sci->getPlatform() == Common::kPlatformMacintosh &&
|
||||
getSciVersion() >= SCI_VERSION_2 &&
|
||||
getSciVersion() < SCI_VERSION_2_1_LATE &&
|
||||
g_sci->getGameId() != GID_PQSWAT &&
|
||||
g_sci->getGameId() != GID_TORIN;
|
||||
}
|
||||
|
||||
inline bool usesAlternateSelectors() const {
|
||||
return g_sci->getGameId() == GID_PHANTASMAGORIA2;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* If true, the current game supports simultaneous speech & subtitles.
|
||||
*/
|
||||
bool supportsSpeechWithSubtitles() const;
|
||||
|
||||
/**
|
||||
* If true, the game supports changing text speed.
|
||||
*/
|
||||
bool supportsTextSpeed() const {
|
||||
switch (g_sci->getGameId()) {
|
||||
case GID_GK1:
|
||||
case GID_SQ6:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, audio volume sync between the game and ScummVM is done by
|
||||
* monitoring and setting game global variables.
|
||||
*/
|
||||
bool audioVolumeSyncUsesGlobals() const;
|
||||
|
||||
/**
|
||||
* The strategy that should be used when synchronising the message type
|
||||
* (text/speech/text+speech) between the game and ScummVM.
|
||||
*/
|
||||
MessageTypeSyncStrategy getMessageTypeSyncStrategy() const;
|
||||
|
||||
/**
|
||||
* Applies to all versions before 0.000.502
|
||||
* Old SCI versions used to interpret the third DrawPic() parameter inversely,
|
||||
* with the opposite default value (obviously).
|
||||
* Also, they used 15 priority zones from 42 to 200 instead of 14 priority
|
||||
* zones from 42 to 190.
|
||||
*/
|
||||
bool usesOldGfxFunctions() { return detectGfxFunctionsType() == SCI_VERSION_0_EARLY; }
|
||||
|
||||
/**
|
||||
* Autodetects the Bresenham routine used in the actor movement functions
|
||||
* @return Move count type, kIncrementMoveCnt / kIgnoreMoveCnt
|
||||
*/
|
||||
MoveCountType detectMoveCountType();
|
||||
|
||||
int detectPlaneIdBase();
|
||||
|
||||
bool handleMoveCount() { return detectMoveCountType() == kIncrementMoveCount; }
|
||||
|
||||
bool usesCdTrack() { return _usesCdTrack; }
|
||||
|
||||
/**
|
||||
* Checks if the alternative Windows GM MIDI soundtrack should be used. Such
|
||||
* soundtracks are available for the Windows CD versions of EcoQuest, Jones,
|
||||
* KQ5 and SQ4.
|
||||
*/
|
||||
bool useAltWinGMSound();
|
||||
|
||||
/**
|
||||
* Checks if the game only supports General MIDI for music playback.
|
||||
*/
|
||||
bool generalMidiOnly();
|
||||
|
||||
/**
|
||||
* Forces DOS soundtracks in Windows CD versions when the user hasn't
|
||||
* selected a MIDI output device
|
||||
*/
|
||||
void forceDOSTracks() { _forceDOSTracks = true; }
|
||||
|
||||
bool useWindowsCursors() { return _useWindowsCursors; }
|
||||
|
||||
/**
|
||||
* Autodetects, if Pseudo Mouse ability is enabled (different behavior in keyboard driver)
|
||||
* @return kPseudoMouseAbilityTrue or kPseudoMouseAbilityFalse
|
||||
*/
|
||||
PseudoMouseAbilityType detectPseudoMouseAbility();
|
||||
|
||||
bool useAudioPopfix() const { return _useAudioPopfix; }
|
||||
|
||||
bool useEarlyGetLongestTextCalculations() const;
|
||||
|
||||
/**
|
||||
* Several SCI1.1 Macintosh games have empty strings for almost all of the
|
||||
* object names in the script resources.
|
||||
*
|
||||
* @return true if the game's object names aren't empty strings.
|
||||
*/
|
||||
bool hasScriptObjectNames() const;
|
||||
|
||||
/**
|
||||
* Returns if the game can be saved via the GMM.
|
||||
* Saving via the GMM doesn't work as expected in
|
||||
* games which don't follow the normal saving scheme.
|
||||
*/
|
||||
bool canSaveFromGMM() const;
|
||||
|
||||
/**
|
||||
* Returns the global variable index to the start of the game's
|
||||
* global flags array. This is used by the console debugger.
|
||||
*
|
||||
* @return Non-zero index if successful, otherwise zero.
|
||||
*/
|
||||
uint16 getGameFlagsGlobal() const;
|
||||
|
||||
/**
|
||||
* Returns the bit order in which game flags are stored.
|
||||
*
|
||||
* @return true if bit order is normal or false if reversed.
|
||||
*/
|
||||
bool isGameFlagBitOrderNormal() const;
|
||||
|
||||
private:
|
||||
reg_t getDetectionAddr(const Common::String &objName, Selector slc, int methodNum = -1);
|
||||
|
||||
bool autoDetectLofsType(const Common::String& gameSuperClassName, int methodNum);
|
||||
bool autoDetectGfxFunctionsType(int methodNum = -1);
|
||||
bool autoDetectSoundType();
|
||||
bool autoDetectMoveCountType();
|
||||
#ifdef ENABLE_SCI32
|
||||
bool autoDetectSci21KernelType();
|
||||
#endif
|
||||
|
||||
SciVersion _doSoundType, _setCursorType, _lofsType, _gfxFunctionsType, _messageFunctionType;
|
||||
#ifdef ENABLE_SCI32
|
||||
SciVersion _sci21KernelType;
|
||||
#endif
|
||||
|
||||
MoveCountType _moveCountType;
|
||||
bool _usesCdTrack;
|
||||
bool _forceDOSTracks;
|
||||
bool _useWindowsCursors;
|
||||
|
||||
PseudoMouseAbilityType _pseudoMouseAbility;
|
||||
|
||||
bool _useAudioPopfix;
|
||||
|
||||
SegManager *_segMan;
|
||||
Kernel *_kernel;
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_FEATURES_H
|
||||
613
engines/sci/engine/file.cpp
Normal file
613
engines/sci/engine/file.cpp
Normal file
@@ -0,0 +1,613 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/savefile.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/unicode-bidi.h"
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/file.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/savegame.h"
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/state.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
uint32 MemoryDynamicRWStream::read(void *dataPtr, uint32 dataSize) {
|
||||
// Read at most as many bytes as are still available...
|
||||
if (dataSize > _size - _pos) {
|
||||
dataSize = _size - _pos;
|
||||
_eos = true;
|
||||
}
|
||||
memcpy(dataPtr, _ptr, dataSize);
|
||||
|
||||
_ptr += dataSize;
|
||||
_pos += dataSize;
|
||||
|
||||
return dataSize;
|
||||
}
|
||||
|
||||
SaveFileRewriteStream::SaveFileRewriteStream(const Common::String &fileName,
|
||||
Common::SeekableReadStream *inFile,
|
||||
kFileOpenMode mode,
|
||||
bool compress) :
|
||||
MemoryDynamicRWStream(DisposeAfterUse::YES),
|
||||
_fileName(fileName),
|
||||
_compress(compress) {
|
||||
const bool truncate = (mode == kFileOpenModeCreate);
|
||||
const bool seekToEnd = (mode == kFileOpenModeOpenOrCreate);
|
||||
|
||||
if (!truncate && inFile) {
|
||||
const uint32 size = inFile->size();
|
||||
ensureCapacity(size);
|
||||
_size = inFile->read(_data, size);
|
||||
if (seekToEnd) {
|
||||
seek(0, SEEK_END);
|
||||
}
|
||||
_changed = false;
|
||||
} else {
|
||||
_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
SaveFileRewriteStream::~SaveFileRewriteStream() {
|
||||
commit();
|
||||
}
|
||||
|
||||
void SaveFileRewriteStream::commit() {
|
||||
if (!_changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::ScopedPtr<Common::WriteStream> outFile(g_sci->getSaveFileManager()->openForSaving(_fileName, _compress));
|
||||
outFile->write(_data, _size);
|
||||
_changed = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint findFreeFileHandle(EngineState *s) {
|
||||
// Find a free file handle
|
||||
uint handle = 1; // Ignore _fileHandles[0]
|
||||
while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen())
|
||||
handle++;
|
||||
|
||||
if (handle == s->_fileHandles.size()) {
|
||||
// Hit size limit => Allocate more space
|
||||
s->_fileHandles.resize(s->_fileHandles.size() + 1);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note on how file I/O is implemented: In ScummVM, one can not create/write
|
||||
* arbitrary data files, simply because many of our target platforms do not
|
||||
* support this. The only files one can create are savestates. But SCI has an
|
||||
* opcode to create and write to seemingly 'arbitrary' files. This is mainly
|
||||
* used in LSL3 for LARRY3.DRV (which is a game data file, not a driver, used
|
||||
* for persisting the results of the "age quiz" across restarts) and in LSL5
|
||||
* for MEMORY.DRV (which is again a game data file and contains the game's
|
||||
* password, XOR encrypted).
|
||||
* To implement that opcode, we combine the SaveFileManager with regular file
|
||||
* code, similarly to how the SCUMM HE engine does it.
|
||||
*
|
||||
* To handle opening a file called "foobar", what we do is this: First, we
|
||||
* create an 'augmented file name', by prepending the game target and a dash,
|
||||
* so if we running game target sq1sci, the name becomes "sq1sci-foobar".
|
||||
* Next, we check if such a file is known to the SaveFileManager. If so, we
|
||||
* we use that for reading/writing, delete it, whatever.
|
||||
*
|
||||
* If no such file is present but we were only asked to *read* the file,
|
||||
* we fallback to looking for a regular file called "foobar", and open that
|
||||
* for reading only.
|
||||
*/
|
||||
|
||||
reg_t file_open(EngineState *s, const Common::String &filename, kFileOpenMode mode, bool unwrapFilename) {
|
||||
Common::String englishName = g_sci->getSciLanguageString(filename, K_LANG_ENGLISH);
|
||||
englishName.toLowercase();
|
||||
|
||||
Common::String wrappedName = unwrapFilename ? g_sci->wrapFilename(englishName) : englishName;
|
||||
Common::SeekableReadStream *inFile = nullptr;
|
||||
Common::WriteStream *outFile = nullptr;
|
||||
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
|
||||
|
||||
bool isCompressed = true;
|
||||
const SciGameId gameId = g_sci->getGameId();
|
||||
|
||||
// QFG Characters are saved via the CharSave object.
|
||||
// We leave them uncompressed so that they can be imported in later QFG
|
||||
// games, even when using the original interpreter.
|
||||
// We check for room numbers in here, because the file suffix can be changed by the user.
|
||||
// Rooms/Scripts: QFG1(EGA/VGA): 601, QFG2: 840, QFG3/4: 52
|
||||
switch (gameId) {
|
||||
case GID_QFG1:
|
||||
case GID_QFG1VGA:
|
||||
if (s->currentRoomNumber() == 601)
|
||||
isCompressed = false;
|
||||
break;
|
||||
case GID_QFG2:
|
||||
if (s->currentRoomNumber() == 840)
|
||||
isCompressed = false;
|
||||
break;
|
||||
case GID_QFG3:
|
||||
case GID_QFG4:
|
||||
if (s->currentRoomNumber() == 52)
|
||||
isCompressed = false;
|
||||
break;
|
||||
#ifdef ENABLE_SCI32
|
||||
// Hoyle5 has no save games, but creates very simple text-based game options
|
||||
// files that do not need to be compressed
|
||||
case GID_HOYLE5:
|
||||
// Phantasmagoria game scripts create their own save files, so they are
|
||||
// interoperable with the original interpreter just by renaming them as long
|
||||
// as they are not compressed. They are also never larger than a couple
|
||||
// hundred bytes, so compression does not do much here anyway
|
||||
case GID_PHANTASMAGORIA:
|
||||
isCompressed = false;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
bool isRewritableFile;
|
||||
switch (g_sci->getGameId()) {
|
||||
case GID_PHANTASMAGORIA:
|
||||
isRewritableFile = (filename == "phantsg.dir" || filename == "chase.dat" || filename == "tmp.dat");
|
||||
break;
|
||||
case GID_PQSWAT:
|
||||
isRewritableFile = (filename == "swat.dat");
|
||||
break;
|
||||
default:
|
||||
isRewritableFile = false;
|
||||
}
|
||||
|
||||
if (isRewritableFile) {
|
||||
debugC(kDebugLevelFile, " -> file_open opening %s for rewriting", wrappedName.c_str());
|
||||
|
||||
inFile = saveFileMan->openForLoading(wrappedName);
|
||||
// If no matching savestate exists: fall back to reading from a regular
|
||||
// file
|
||||
if (!inFile)
|
||||
inFile = SearchMan.createReadStreamForMember(Common::Path(englishName));
|
||||
|
||||
if (mode == kFileOpenModeOpenOrFail && !inFile) {
|
||||
debugC(kDebugLevelFile, " -> file_open(kFileOpenModeOpenOrFail): failed to open file '%s'", englishName.c_str());
|
||||
return SIGNAL_REG;
|
||||
}
|
||||
|
||||
SaveFileRewriteStream *stream;
|
||||
stream = new SaveFileRewriteStream(wrappedName, inFile, mode, isCompressed);
|
||||
|
||||
delete inFile;
|
||||
|
||||
inFile = stream;
|
||||
outFile = stream;
|
||||
} else
|
||||
#endif
|
||||
if (mode == kFileOpenModeOpenOrFail) {
|
||||
// Try to open file, abort if not possible
|
||||
inFile = saveFileMan->openForLoading(wrappedName);
|
||||
// If no matching savestate exists: fall back to reading from a regular
|
||||
// file
|
||||
if (!inFile)
|
||||
inFile = SearchMan.createReadStreamForMember(Common::Path(englishName));
|
||||
|
||||
if (!inFile)
|
||||
debugC(kDebugLevelFile, " -> file_open(kFileOpenModeOpenOrFail): failed to open file '%s'", englishName.c_str());
|
||||
} else if (mode == kFileOpenModeCreate) {
|
||||
// Create the file, destroying any content it might have had
|
||||
outFile = saveFileMan->openForSaving(wrappedName, isCompressed);
|
||||
if (!outFile)
|
||||
debugC(kDebugLevelFile, " -> file_open(kFileOpenModeCreate): failed to create file '%s'", englishName.c_str());
|
||||
} else if (mode == kFileOpenModeOpenOrCreate) {
|
||||
// Try to open file, create it if it doesn't exist
|
||||
outFile = saveFileMan->openForSaving(wrappedName, isCompressed);
|
||||
if (!outFile)
|
||||
debugC(kDebugLevelFile, " -> file_open(kFileOpenModeCreate): failed to create file '%s'", englishName.c_str());
|
||||
|
||||
// QfG1 opens the character export file with kFileOpenModeCreate first,
|
||||
// closes it immediately and opens it again with this here. Perhaps
|
||||
// other games use this for read access as well. I guess changing this
|
||||
// whole code into using virtual files and writing them after close
|
||||
// would be more appropriate.
|
||||
} else {
|
||||
error("file_open: unsupported mode %d (filename '%s')", mode, englishName.c_str());
|
||||
}
|
||||
|
||||
if (!inFile && !outFile) { // Failed
|
||||
debugC(kDebugLevelFile, " -> file_open() failed");
|
||||
return SIGNAL_REG;
|
||||
}
|
||||
|
||||
uint handle = findFreeFileHandle(s);
|
||||
|
||||
s->_fileHandles[handle]._in = inFile;
|
||||
s->_fileHandles[handle]._out = outFile;
|
||||
s->_fileHandles[handle]._name = englishName;
|
||||
|
||||
debugC(kDebugLevelFile, " -> opened file '%s' with handle %d", englishName.c_str(), handle);
|
||||
return make_reg(0, handle);
|
||||
}
|
||||
|
||||
FileHandle *getFileFromHandle(EngineState *s, uint handle) {
|
||||
if ((handle == 0) || ((handle >= kVirtualFileHandleStart) && (handle <= kVirtualFileHandleEnd))) {
|
||||
error("Attempt to use invalid file handle (%d)", handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if ((handle >= s->_fileHandles.size()) || !s->_fileHandles[handle].isOpen()) {
|
||||
warning("Attempt to use invalid/unused file handle %d", handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &s->_fileHandles[handle];
|
||||
}
|
||||
|
||||
int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) {
|
||||
// always initialize because some scripts don't test for errors and
|
||||
// just use the results, even from invalid file handles. bug #12060
|
||||
memset(dest, 0, maxsize);
|
||||
|
||||
FileHandle *f = getFileFromHandle(s, handle);
|
||||
if (!f)
|
||||
return 0;
|
||||
|
||||
if (!f->_in) {
|
||||
error("fgets_wrapper: Trying to read from file '%s' opened for writing", f->_name.c_str());
|
||||
return 0;
|
||||
}
|
||||
int readBytes = 0;
|
||||
if (maxsize > 1) {
|
||||
f->_in->readLine(dest, maxsize);
|
||||
readBytes = Common::strnlen(dest, maxsize); // FIXME: sierra sci returned byte count and didn't react on NUL characters
|
||||
// The returned string must not have an ending LF
|
||||
if (readBytes > 0) {
|
||||
if (dest[readBytes - 1] == 0x0A)
|
||||
dest[readBytes - 1] = 0;
|
||||
}
|
||||
} else {
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
debugC(kDebugLevelFile, " -> FGets'ed \"%s\"", dest);
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
static bool _savegame_sort_byDate(const SavegameDesc &l, const SavegameDesc &r) {
|
||||
if (l.date != r.date)
|
||||
return (l.date > r.date);
|
||||
return (l.time > r.time);
|
||||
}
|
||||
|
||||
bool fillSavegameDesc(const Common::String &filename, SavegameDesc &desc) {
|
||||
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
|
||||
Common::ScopedPtr<Common::SeekableReadStream> in(saveFileMan->openForLoading(filename));
|
||||
if (!in) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SavegameMetadata meta;
|
||||
if (!get_savegame_metadata(in.get(), meta) || meta.name.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int id = strtol(filename.end() - 3, nullptr, 10);
|
||||
desc.id = id;
|
||||
// We need to fix date in here, because we save DDMMYYYY instead of
|
||||
// YYYYMMDD, so sorting wouldn't work
|
||||
desc.date = ((meta.saveDate & 0xFFFF) << 16) | ((meta.saveDate & 0xFF0000) >> 8) | ((meta.saveDate & 0xFF000000) >> 24);
|
||||
desc.time = meta.saveTime;
|
||||
desc.version = meta.version;
|
||||
desc.gameVersion = meta.gameVersion;
|
||||
desc.script0Size = meta.script0Size;
|
||||
desc.gameObjectOffset = meta.gameObjectOffset;
|
||||
#ifdef ENABLE_SCI32
|
||||
if (g_sci->getGameId() == GID_SHIVERS) {
|
||||
desc.lowScore = meta.lowScore;
|
||||
desc.highScore = meta.highScore;
|
||||
} else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
|
||||
desc.avatarId = meta.avatarId;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (meta.name.lastChar() == '\n')
|
||||
meta.name.deleteLastChar();
|
||||
|
||||
Common::String nameString = meta.name;
|
||||
if (g_sci->getLanguage() == Common::HE_ISR) {
|
||||
Common::U32String nameU32String = meta.name.decode(Common::kUtf8);
|
||||
nameString = nameU32String.encode(Common::kWindows1255);
|
||||
} else if (g_sci->getLanguage() == Common::RU_RUS) {
|
||||
Common::U32String nameU32String = meta.name.decode(Common::kUtf8);
|
||||
nameString = nameU32String.encode(Common::kDos866);
|
||||
}
|
||||
|
||||
Common::strlcpy(desc.name, nameString.c_str(), sizeof(desc.name));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create an array containing all found savedgames, sorted by creation date
|
||||
void listSavegames(Common::Array<SavegameDesc> &saves) {
|
||||
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
|
||||
Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern());
|
||||
|
||||
for (Common::StringArray::const_iterator iter = saveNames.begin(); iter != saveNames.end(); ++iter) {
|
||||
const Common::String &filename = *iter;
|
||||
|
||||
// exclude new game and autosave slots, except for QFG3/4,
|
||||
// whose autosave should appear as a normal saved game
|
||||
if (g_sci->getGameId() != GID_QFG3 &&
|
||||
g_sci->getGameId() != GID_QFG4) {
|
||||
const int id = strtol(filename.end() - 3, nullptr, 10);
|
||||
if (id == kNewGameId || id == kAutoSaveId) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
SavegameDesc desc;
|
||||
if (fillSavegameDesc(filename, desc)) {
|
||||
saves.push_back(desc);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the list by creation date of the saves
|
||||
Common::sort(saves.begin(), saves.end(), _savegame_sort_byDate);
|
||||
}
|
||||
|
||||
// Find a savedgame according to virtualId and return the position within our array
|
||||
int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId) {
|
||||
for (uint saveNr = 0; saveNr < saves.size(); saveNr++) {
|
||||
if (saves[saveNr].id == savegameId)
|
||||
return saveNr;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
Common::MemoryReadStream *makeCatalogue(const uint maxNumSaves, const uint gameNameSize, const Common::String &fileNamePattern, const bool ramaFormat) {
|
||||
enum {
|
||||
kGameIdSize = sizeof(int16),
|
||||
kNumSavesSize = sizeof(int16),
|
||||
kFreeSlotSize = sizeof(int16),
|
||||
kTerminatorSize = kGameIdSize,
|
||||
kTerminator = 0xFFFF
|
||||
};
|
||||
|
||||
Common::Array<SavegameDesc> games;
|
||||
listSavegames(games);
|
||||
|
||||
const uint numSaves = MIN(games.size(), maxNumSaves);
|
||||
const uint fileNameSize = fileNamePattern.empty() ? 0 : 12;
|
||||
const uint entrySize = kGameIdSize + fileNameSize + gameNameSize;
|
||||
uint dataSize = numSaves * entrySize + kTerminatorSize;
|
||||
if (ramaFormat) {
|
||||
dataSize += kNumSavesSize + kFreeSlotSize * maxNumSaves;
|
||||
}
|
||||
|
||||
byte *out = (byte *)malloc(dataSize);
|
||||
const byte *const data = out;
|
||||
|
||||
Common::Array<bool> usedSlots;
|
||||
if (ramaFormat) {
|
||||
WRITE_LE_UINT16(out, numSaves);
|
||||
out += kNumSavesSize;
|
||||
usedSlots.resize(maxNumSaves);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < numSaves; ++i) {
|
||||
const SavegameDesc &save = games[i];
|
||||
const uint16 id = save.id - kSaveIdShift;
|
||||
if (!ramaFormat) {
|
||||
WRITE_LE_UINT16(out, id);
|
||||
out += kGameIdSize;
|
||||
}
|
||||
if (fileNameSize) {
|
||||
const Common::String fileName = Common::String::format(fileNamePattern.c_str(), id);
|
||||
strncpy(reinterpret_cast<char *>(out), fileName.c_str(), fileNameSize);
|
||||
out += fileNameSize;
|
||||
}
|
||||
// Game names can be up to exactly gameNameSize
|
||||
strncpy(reinterpret_cast<char *>(out), save.name, gameNameSize);
|
||||
out += gameNameSize;
|
||||
if (ramaFormat) {
|
||||
WRITE_LE_UINT16(out, id);
|
||||
out += kGameIdSize;
|
||||
|
||||
assert(id < maxNumSaves);
|
||||
usedSlots[id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ramaFormat) {
|
||||
// A table indicating which save game slots are occupied
|
||||
for (uint i = 0; i < usedSlots.size(); ++i) {
|
||||
WRITE_LE_UINT16(out, !usedSlots[i]);
|
||||
out += kFreeSlotSize;
|
||||
}
|
||||
}
|
||||
|
||||
WRITE_LE_UINT16(out, kTerminator);
|
||||
|
||||
return new Common::MemoryReadStream(data, dataSize, DisposeAfterUse::YES);
|
||||
}
|
||||
|
||||
int shiftSciToScummVMSaveId(int saveId) {
|
||||
if (saveId == kMaxShiftedSaveId) {
|
||||
return 0;
|
||||
} else if (saveId >= 0) {
|
||||
return saveId + kSaveIdShift;
|
||||
}
|
||||
return saveId;
|
||||
}
|
||||
|
||||
int shiftScummVMToSciSaveId(int saveId) {
|
||||
if (saveId == 0) {
|
||||
return kMaxShiftedSaveId;
|
||||
} else if (saveId > 0) {
|
||||
return saveId - kSaveIdShift;
|
||||
}
|
||||
return saveId;
|
||||
}
|
||||
#endif
|
||||
|
||||
FileHandle::FileHandle() : _in(nullptr), _out(nullptr) {
|
||||
}
|
||||
|
||||
FileHandle::~FileHandle() {
|
||||
close();
|
||||
}
|
||||
|
||||
void FileHandle::close() {
|
||||
// NB: It is possible _in and _out are both non-null, but
|
||||
// then they point to the same object.
|
||||
if (_in)
|
||||
delete _in;
|
||||
else
|
||||
delete _out;
|
||||
_in = nullptr;
|
||||
_out = nullptr;
|
||||
_name.clear();
|
||||
}
|
||||
|
||||
bool FileHandle::isOpen() const {
|
||||
return _in || _out;
|
||||
}
|
||||
|
||||
|
||||
void DirSeeker::addAsVirtualFiles(Common::String title, Common::String fileMask) {
|
||||
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
|
||||
Common::StringArray foundFiles = saveFileMan->listSavefiles(fileMask);
|
||||
if (!foundFiles.empty()) {
|
||||
// Sort all filenames alphabetically
|
||||
Common::sort(foundFiles.begin(), foundFiles.end());
|
||||
|
||||
Common::StringArray::iterator it;
|
||||
Common::StringArray::iterator it_end = foundFiles.end();
|
||||
bool titleAdded = false;
|
||||
|
||||
for (it = foundFiles.begin(); it != it_end; it++) {
|
||||
Common::String regularFilename = *it;
|
||||
Common::String wrappedFilename = Common::String(regularFilename.c_str() + fileMask.size() - 1);
|
||||
|
||||
Common::SeekableReadStream *testfile = saveFileMan->openForLoading(regularFilename);
|
||||
if (testfile == nullptr) {
|
||||
continue;
|
||||
}
|
||||
int32 testfileSize = testfile->size();
|
||||
delete testfile;
|
||||
if (testfileSize > 1024) // check, if larger than 1k. in that case its a saved game.
|
||||
continue; // and we dont want to have those in the list
|
||||
|
||||
if (!titleAdded) {
|
||||
_files.push_back(title);
|
||||
_virtualFiles.push_back("");
|
||||
titleAdded = true;
|
||||
}
|
||||
|
||||
// We need to remove the prefix for display purposes
|
||||
_files.push_back(wrappedFilename);
|
||||
// but remember the actual name as well
|
||||
_virtualFiles.push_back(regularFilename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common::String DirSeeker::getVirtualFilename(uint fileNumber) {
|
||||
if (fileNumber >= _virtualFiles.size())
|
||||
error("invalid virtual filename access");
|
||||
return _virtualFiles[fileNumber];
|
||||
}
|
||||
|
||||
reg_t DirSeeker::firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan) {
|
||||
// Verify that we are given a valid buffer
|
||||
if (!buffer.getSegment()) {
|
||||
error("DirSeeker::firstFile('%s') invoked with invalid buffer", mask.c_str());
|
||||
return NULL_REG;
|
||||
}
|
||||
_outbuffer = buffer;
|
||||
_files.clear();
|
||||
_virtualFiles.clear();
|
||||
|
||||
int QfGImport = g_sci->inQfGImportRoom();
|
||||
if (QfGImport) {
|
||||
_files.clear();
|
||||
addAsVirtualFiles("-QfG1-", "qfg1-*");
|
||||
addAsVirtualFiles("-QfG1VGA-", "qfg1vga-*");
|
||||
if (QfGImport > 2) {
|
||||
addAsVirtualFiles("-QfG2-", "qfg2-*");
|
||||
addAsVirtualFiles("-QfG2 AGDI-", "qfg2agdi-*");
|
||||
}
|
||||
if (QfGImport > 3)
|
||||
addAsVirtualFiles("-QfG3-", "qfg3-*");
|
||||
|
||||
if (QfGImport == 3) {
|
||||
// QfG3 sorts the filelisting itself, we can't let that happen otherwise our
|
||||
// virtual list would go out-of-sync
|
||||
reg_t savedHeros = segMan->findObjectByName("savedHeros");
|
||||
if (!savedHeros.isNull())
|
||||
writeSelectorValue(segMan, savedHeros, SELECTOR(sort), 0);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Prefix the mask
|
||||
const Common::String wrappedMask = g_sci->wrapFilename(mask);
|
||||
|
||||
// Obtain a list of all files matching the given mask
|
||||
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
|
||||
_files = saveFileMan->listSavefiles(wrappedMask);
|
||||
}
|
||||
|
||||
// Reset the list iterator and write the first match to the output buffer,
|
||||
// if any.
|
||||
_iter = _files.begin();
|
||||
return nextFile(segMan);
|
||||
}
|
||||
|
||||
reg_t DirSeeker::nextFile(SegManager *segMan) {
|
||||
if (_iter == _files.end()) {
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
Common::String string;
|
||||
|
||||
if (_virtualFiles.empty()) {
|
||||
// Strip the prefix, if we don't got a virtual filelisting
|
||||
const Common::String wrappedString = *_iter;
|
||||
string = g_sci->unwrapFilename(wrappedString);
|
||||
} else {
|
||||
string = *_iter;
|
||||
}
|
||||
if (string.size() > 12)
|
||||
string = Common::String(string.c_str(), 12);
|
||||
segMan->strcpy_(_outbuffer, string.c_str());
|
||||
|
||||
// Return the result and advance the list iterator :)
|
||||
++_iter;
|
||||
return _outbuffer;
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
187
engines/sci/engine/file.h
Normal file
187
engines/sci/engine/file.h
Normal file
@@ -0,0 +1,187 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_FILE_H
|
||||
#define SCI_ENGINE_FILE_H
|
||||
|
||||
#include "common/memstream.h"
|
||||
#include "common/str-array.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
enum kFileOpenMode {
|
||||
kFileOpenModeOpenOrCreate = 0,
|
||||
kFileOpenModeOpenOrFail = 1,
|
||||
kFileOpenModeCreate = 2
|
||||
};
|
||||
|
||||
enum {
|
||||
kMaxSaveNameLength = 36, ///< Maximum length of a savegame name (excluding terminator character)
|
||||
kMaxNumSaveGames = 20 ///< Maximum number of savegames
|
||||
};
|
||||
|
||||
enum {
|
||||
kAutoSaveId = 0, ///< The save game slot number for autosaves
|
||||
kNewGameId = 999, ///< The save game slot number for a "new game" save
|
||||
|
||||
// SCI engine expects game IDs to start at 0, but slot 0 in ScummVM is
|
||||
// reserved for autosave, so non-autosave games get their IDs shifted up
|
||||
// when saving or restoring, and shifted down when enumerating save games.
|
||||
// ScummVM slot 0 can't be shifted down as -1 is an illegal SCI save ID
|
||||
// so it is instead wrapped around to 99 and then back to 0 when shifting up.
|
||||
kSaveIdShift = 1,
|
||||
kMaxShiftedSaveId = 99
|
||||
};
|
||||
|
||||
enum {
|
||||
kVirtualFileHandleStart = 32000,
|
||||
kVirtualFileHandleSci32Save = 32100,
|
||||
kVirtualFileHandleSciAudio = 32300,
|
||||
kVirtualFileHandleEnd = 32300
|
||||
};
|
||||
|
||||
struct SavegameDesc {
|
||||
int16 id;
|
||||
int date;
|
||||
int time;
|
||||
int version;
|
||||
// name is a null-terminated 36 character array in SCI16,
|
||||
// but in SCI32 the 36th character can be used for text.
|
||||
// At least Phant2 makes use of this. We add an element
|
||||
// so that this string is always terminated internally.
|
||||
char name[kMaxSaveNameLength + 1];
|
||||
Common::String gameVersion;
|
||||
uint32 script0Size;
|
||||
uint32 gameObjectOffset;
|
||||
#ifdef ENABLE_SCI32
|
||||
// Used by Shivers 1
|
||||
uint16 lowScore;
|
||||
uint16 highScore;
|
||||
// Used by MGDX
|
||||
uint8 avatarId;
|
||||
#endif
|
||||
};
|
||||
|
||||
class FileHandle {
|
||||
public:
|
||||
Common::String _name;
|
||||
Common::SeekableReadStream *_in;
|
||||
Common::WriteStream *_out;
|
||||
|
||||
public:
|
||||
FileHandle();
|
||||
~FileHandle();
|
||||
|
||||
void close();
|
||||
bool isOpen() const;
|
||||
};
|
||||
|
||||
|
||||
class DirSeeker {
|
||||
protected:
|
||||
reg_t _outbuffer;
|
||||
Common::StringArray _files;
|
||||
Common::StringArray _virtualFiles;
|
||||
Common::StringArray::const_iterator _iter;
|
||||
|
||||
public:
|
||||
DirSeeker() {
|
||||
_outbuffer = NULL_REG;
|
||||
_iter = _files.begin();
|
||||
}
|
||||
|
||||
reg_t firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan);
|
||||
reg_t nextFile(SegManager *segMan);
|
||||
|
||||
Common::String getVirtualFilename(uint fileNumber);
|
||||
|
||||
private:
|
||||
void addAsVirtualFiles(Common::String title, Common::String fileMask);
|
||||
};
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* A MemoryWriteStreamDynamic with additional read functionality.
|
||||
* The read and write functions share a single stream position.
|
||||
*/
|
||||
class MemoryDynamicRWStream : public Common::MemoryWriteStreamDynamic, public Common::SeekableReadStream {
|
||||
public:
|
||||
MemoryDynamicRWStream(DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) : MemoryWriteStreamDynamic(disposeMemory), _eos(false) { }
|
||||
|
||||
uint32 read(void *dataPtr, uint32 dataSize) override;
|
||||
|
||||
bool eos() const override { return _eos; }
|
||||
int64 pos() const override { return _pos; }
|
||||
int64 size() const override { return _size; }
|
||||
void clearErr() override { _eos = false; Common::MemoryWriteStreamDynamic::clearErr(); }
|
||||
bool seek(int64 offs, int whence = SEEK_SET) override { return Common::MemoryWriteStreamDynamic::seek(offs, whence); }
|
||||
|
||||
protected:
|
||||
bool _eos;
|
||||
};
|
||||
|
||||
/**
|
||||
* A MemoryDynamicRWStream intended to re-write a file.
|
||||
* It reads the contents of `inFile` in the constructor, and writes back
|
||||
* the changes to `fileName` in the destructor (and when calling commit() ).
|
||||
*/
|
||||
class SaveFileRewriteStream : public MemoryDynamicRWStream {
|
||||
public:
|
||||
SaveFileRewriteStream(const Common::String &fileName,
|
||||
Common::SeekableReadStream *inFile,
|
||||
kFileOpenMode mode, bool compress);
|
||||
~SaveFileRewriteStream() override;
|
||||
|
||||
uint32 write(const void *dataPtr, uint32 dataSize) override { _changed = true; return MemoryDynamicRWStream::write(dataPtr, dataSize); }
|
||||
|
||||
void commit(); //< Save back to disk
|
||||
|
||||
protected:
|
||||
Common::String _fileName;
|
||||
bool _compress;
|
||||
bool _changed;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
uint findFreeFileHandle(EngineState *s);
|
||||
reg_t file_open(EngineState *s, const Common::String &filename, kFileOpenMode mode, bool unwrapFilename);
|
||||
FileHandle *getFileFromHandle(EngineState *s, uint handle);
|
||||
int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle);
|
||||
void listSavegames(Common::Array<SavegameDesc> &saves);
|
||||
int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId);
|
||||
bool fillSavegameDesc(const Common::String &filename, SavegameDesc &desc);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Constructs an in-memory stream from the ScummVM save game list that is
|
||||
* compatible with game scripts' game catalogue readers.
|
||||
*/
|
||||
Common::MemoryReadStream *makeCatalogue(const uint maxNumSaves, const uint gameNameSize, const Common::String &fileNamePattern, const bool ramaFormat);
|
||||
|
||||
int shiftSciToScummVMSaveId(int saveId);
|
||||
int shiftScummVMToSciSaveId(int saveId);
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_FILE_H
|
||||
237
engines/sci/engine/gc.cpp
Normal file
237
engines/sci/engine/gc.cpp
Normal file
@@ -0,0 +1,237 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/engine/gc.h"
|
||||
#include "common/array.h"
|
||||
#include "sci/graphics/ports.h"
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
#include "sci/graphics/controls32.h"
|
||||
#endif
|
||||
|
||||
namespace Sci {
|
||||
|
||||
//#define GC_DEBUG_CODE
|
||||
|
||||
#ifdef GC_DEBUG_CODE
|
||||
const char *segmentTypeNames[] = {
|
||||
"invalid", // 0
|
||||
"script", // 1
|
||||
"clones", // 2
|
||||
"locals", // 3
|
||||
"stack", // 4
|
||||
"obsolete", // 5: obsolete system strings
|
||||
"lists", // 6
|
||||
"nodes", // 7
|
||||
"hunk", // 8
|
||||
"dynmem", // 9
|
||||
"obsolete", // 10: obsolete string fragments
|
||||
"array", // 11: SCI32 arrays
|
||||
"obsolete" // 12: obsolete SCI32 strings
|
||||
};
|
||||
#endif
|
||||
|
||||
void WorklistManager::push(reg_t reg) {
|
||||
if (!reg.getSegment()) // No numbers
|
||||
return;
|
||||
|
||||
debugC(kDebugLevelGC, "[GC] Adding %04x:%04x", PRINT_REG(reg));
|
||||
|
||||
if (_map.contains(reg))
|
||||
return; // already dealt with it
|
||||
|
||||
_map.setVal(reg, true);
|
||||
_worklist.push_back(reg);
|
||||
}
|
||||
|
||||
void WorklistManager::pushArray(const Common::Array<reg_t> &tmp) {
|
||||
for (Common::Array<reg_t>::const_iterator it = tmp.begin(); it != tmp.end(); ++it)
|
||||
push(*it);
|
||||
}
|
||||
|
||||
static AddrSet *normalizeAddresses(SegManager *segMan, const AddrSet &nonnormal_map) {
|
||||
AddrSet *normal_map = new AddrSet();
|
||||
|
||||
for (AddrSet::const_iterator i = nonnormal_map.begin(); i != nonnormal_map.end(); ++i) {
|
||||
reg_t reg = i->_key;
|
||||
SegmentObj *mobj = segMan->getSegmentObj(reg.getSegment());
|
||||
|
||||
if (mobj) {
|
||||
reg = mobj->findCanonicAddress(segMan, reg);
|
||||
normal_map->setVal(reg, true);
|
||||
}
|
||||
}
|
||||
|
||||
return normal_map;
|
||||
}
|
||||
|
||||
static void processWorkList(SegManager *segMan, WorklistManager &wm, const Common::Array<SegmentObj *> &heap) {
|
||||
SegmentId stackSegment = segMan->findSegmentByType(SEG_TYPE_STACK);
|
||||
while (!wm._worklist.empty()) {
|
||||
reg_t reg = wm._worklist.back();
|
||||
wm._worklist.pop_back();
|
||||
if (reg.getSegment() != stackSegment) { // No need to repeat this one
|
||||
debugC(kDebugLevelGC, "[GC] Checking %04x:%04x", PRINT_REG(reg));
|
||||
if (reg.getSegment() < heap.size() && heap[reg.getSegment()]) {
|
||||
// Valid heap object? Find its outgoing references!
|
||||
wm.pushArray(heap[reg.getSegment()]->listAllOutgoingReferences(reg));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddrSet *findAllActiveReferences(EngineState *s) {
|
||||
assert(!s->_executionStack.empty());
|
||||
|
||||
WorklistManager wm;
|
||||
|
||||
// Initialize registers
|
||||
wm.push(s->r_acc);
|
||||
wm.push(s->r_prev);
|
||||
|
||||
// Initialize value stack
|
||||
// We do this one by hand since the stack doesn't know the current execution stack
|
||||
Common::List<ExecStack>::const_iterator iter = s->_executionStack.reverse_begin();
|
||||
|
||||
// Skip fake kernel stack frame if it's on top
|
||||
if ((*iter).type == EXEC_STACK_TYPE_KERNEL)
|
||||
--iter;
|
||||
|
||||
assert((iter != s->_executionStack.end()) && ((*iter).type != EXEC_STACK_TYPE_KERNEL));
|
||||
|
||||
const StackPtr sp = iter->sp;
|
||||
|
||||
for (reg_t *pos = s->stack_base; pos < sp; pos++)
|
||||
wm.push(*pos);
|
||||
|
||||
debugC(kDebugLevelGC, "[GC] -- Finished adding value stack");
|
||||
|
||||
// Init: Execution Stack
|
||||
for (iter = s->_executionStack.begin();
|
||||
iter != s->_executionStack.end(); ++iter) {
|
||||
const ExecStack &es = *iter;
|
||||
|
||||
if (es.type != EXEC_STACK_TYPE_KERNEL) {
|
||||
wm.push(es.objp);
|
||||
wm.push(es.sendp);
|
||||
if (es.type == EXEC_STACK_TYPE_VARSELECTOR)
|
||||
wm.push(*(es.getVarPointer(s->_segMan)));
|
||||
}
|
||||
}
|
||||
|
||||
debugC(kDebugLevelGC, "[GC] -- Finished adding execution stack");
|
||||
|
||||
const Common::Array<SegmentObj *> &heap = s->_segMan->getSegments();
|
||||
uint heapSize = heap.size();
|
||||
|
||||
for (uint i = 1; i < heapSize; i++) {
|
||||
if (heap[i]) {
|
||||
// Init: Explicitly loaded scripts
|
||||
if (heap[i]->getType() == SEG_TYPE_SCRIPT) {
|
||||
Script *script = (Script *)heap[i];
|
||||
|
||||
if (script->getLockers()) { // Explicitly loaded?
|
||||
wm.pushArray(script->listObjectReferences());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
// Init: Explicitly opted-out bitmaps
|
||||
else if (heap[i]->getType() == SEG_TYPE_BITMAP) {
|
||||
BitmapTable *bt = static_cast<BitmapTable *>(heap[i]);
|
||||
|
||||
for (uint j = 0; j < bt->_table.size(); j++) {
|
||||
if (bt->_table[j].data && bt->_table[j].data->getShouldGC() == false) {
|
||||
wm.push(make_reg(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
debugC(kDebugLevelGC, "[GC] -- Finished explicitly loaded scripts, done with root set");
|
||||
|
||||
processWorkList(s->_segMan, wm, heap);
|
||||
|
||||
if (g_sci->_gfxPorts)
|
||||
g_sci->_gfxPorts->processEngineHunkList(wm);
|
||||
|
||||
return normalizeAddresses(s->_segMan, wm._map);
|
||||
}
|
||||
|
||||
void run_gc(EngineState *s) {
|
||||
SegManager *segMan = s->_segMan;
|
||||
|
||||
// Some debug stuff
|
||||
debugC(kDebugLevelGC, "[GC] Running...");
|
||||
#ifdef GC_DEBUG_CODE
|
||||
const char *segnames[SEG_TYPE_MAX + 1];
|
||||
int segcount[SEG_TYPE_MAX + 1];
|
||||
memset(segnames, 0, sizeof(segnames));
|
||||
memset(segcount, 0, sizeof(segcount));
|
||||
#endif
|
||||
|
||||
// Compute the set of all segments references currently in use.
|
||||
AddrSet *activeRefs = findAllActiveReferences(s);
|
||||
|
||||
// Iterate over all segments, and check for each whether it
|
||||
// contains stuff that can be collected.
|
||||
const Common::Array<SegmentObj *> &heap = segMan->getSegments();
|
||||
for (uint seg = 1; seg < heap.size(); seg++) {
|
||||
SegmentObj *mobj = heap[seg];
|
||||
|
||||
if (mobj != nullptr) {
|
||||
#ifdef GC_DEBUG_CODE
|
||||
const SegmentType type = mobj->getType();
|
||||
segnames[type] = segmentTypeNames[type];
|
||||
#endif
|
||||
|
||||
// Get a list of all deallocatable objects in this segment,
|
||||
// then free any which are not referenced from somewhere.
|
||||
const Common::Array<reg_t> tmp = mobj->listAllDeallocatable(seg);
|
||||
for (Common::Array<reg_t>::const_iterator it = tmp.begin(); it != tmp.end(); ++it) {
|
||||
const reg_t addr = *it;
|
||||
if (!activeRefs->contains(addr)) {
|
||||
// Not found -> we can free it
|
||||
mobj->freeAtAddress(segMan, addr);
|
||||
debugC(kDebugLevelGC, "[GC] Deallocating %04x:%04x", PRINT_REG(addr));
|
||||
#ifdef GC_DEBUG_CODE
|
||||
segcount[type]++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
delete activeRefs;
|
||||
|
||||
#ifdef GC_DEBUG_CODE
|
||||
// Output debug summary of garbage collection
|
||||
debugC(kDebugLevelGC, "[GC] Summary:");
|
||||
for (int i = 0; i <= SEG_TYPE_MAX; i++)
|
||||
if (segcount[i])
|
||||
debugC(kDebugLevelGC, "\t%d\t* %s", segcount[i], segnames[i]);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
68
engines/sci/engine/gc.h
Normal file
68
engines/sci/engine/gc.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef SCI_ENGINE_GC_H
|
||||
#define SCI_ENGINE_GC_H
|
||||
|
||||
#include "common/hashmap.h"
|
||||
#include "sci/engine/vm_types.h"
|
||||
#include "sci/engine/state.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
struct reg_t_Hash {
|
||||
uint operator()(const reg_t& x) const {
|
||||
return (x.getSegment() << 3) ^ x.getOffset() ^ (x.getOffset() << 16);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* The AddrSet is a "set" of reg_t values.
|
||||
* We don't have a HashSet type, so we abuse a HashMap for this.
|
||||
*/
|
||||
typedef Common::HashMap<reg_t, bool, reg_t_Hash> AddrSet;
|
||||
|
||||
/**
|
||||
* Finds all used references and normalises them to their memory addresses
|
||||
* @param s The state to gather all information from
|
||||
* @return A hash map containing entries for all used references
|
||||
*/
|
||||
AddrSet *findAllActiveReferences(EngineState *s);
|
||||
|
||||
/**
|
||||
* Runs garbage collection on the current system state
|
||||
* @param s The state in which we should gc
|
||||
*/
|
||||
void run_gc(EngineState *s);
|
||||
|
||||
struct WorklistManager {
|
||||
Common::Array<reg_t> _worklist;
|
||||
AddrSet _map; // used for 2 contains() calls, inside push() and run_gc()
|
||||
|
||||
void push(reg_t reg);
|
||||
void pushArray(const Common::Array<reg_t> &tmp);
|
||||
};
|
||||
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_GC_H
|
||||
1748
engines/sci/engine/guest_additions.cpp
Normal file
1748
engines/sci/engine/guest_additions.cpp
Normal file
File diff suppressed because it is too large
Load Diff
467
engines/sci/engine/guest_additions.h
Normal file
467
engines/sci/engine/guest_additions.h
Normal file
@@ -0,0 +1,467 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_GUEST_ADDITIONS_H
|
||||
#define SCI_ENGINE_GUEST_ADDITIONS_H
|
||||
|
||||
#include "sci/engine/vm_types.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
struct EngineState;
|
||||
class GameFeatures;
|
||||
class Kernel;
|
||||
class Script;
|
||||
class SegManager;
|
||||
|
||||
enum {
|
||||
// The in-game volumes for Phant2 use a volume range smaller than the
|
||||
// actual master volume because movie volume needs to be controllable from
|
||||
// the normal ScummVM launcher volume controls, but movie dialogue cannot be
|
||||
// heard if the game audio is at the same level as movies. The game normally
|
||||
// sets defaults so that the in-game volume is 85 and movies are 127, so we
|
||||
// will just use 85 as the maximum volume.
|
||||
kPhant2VolumeMax = 85,
|
||||
|
||||
kRamaVolumeMax = 16,
|
||||
kLSL6UIVolumeMax = 13,
|
||||
kHoyle5VolumeMax = 8,
|
||||
kLSL6HiresSubtitleFlag = 105
|
||||
};
|
||||
|
||||
/**
|
||||
* The GuestAdditions class hooks into the SCI virtual machine to provide
|
||||
* enhanced interactions between the ScummVM GUI and the game engine. Currently,
|
||||
* this enhanced functionality encompasses synchronisation of audio volumes and
|
||||
* other audio-related settings, and integration of the ScummVM GUI when saving
|
||||
* and loading game states.
|
||||
*
|
||||
* NOTE: Some parts of the code used to manage audio sync are applied as script
|
||||
* patches using the normal ScriptPatcher mechanism. These patches prevent the
|
||||
* game from resetting audio volumes to defaults when starting up, and prevent
|
||||
* the game from restoring audio volumes stored inside of a save game.
|
||||
*/
|
||||
class GuestAdditions {
|
||||
public:
|
||||
GuestAdditions(EngineState *state, GameFeatures *features, Kernel *kernel);
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
* Synchronises audio volume settings from ScummVM to the game. Called
|
||||
* whenever the ScummVM global menu is dismissed.
|
||||
*/
|
||||
void syncSoundSettingsFromScummVM() const;
|
||||
|
||||
/**
|
||||
* Synchronises all audio settings from ScummVM to the game. Called when the
|
||||
* game is first started, and when save games are loaded.
|
||||
*/
|
||||
void syncAudioOptionsFromScummVM() const;
|
||||
|
||||
/**
|
||||
* Clears audio settings synchronisation state.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
private:
|
||||
EngineState *_state;
|
||||
GameFeatures *_features;
|
||||
Kernel *_kernel;
|
||||
SegManager *_segMan;
|
||||
|
||||
/**
|
||||
* Convenience function for invoking selectors that reduces boilerplate code
|
||||
* required by Sci::invokeSelector.
|
||||
*/
|
||||
void invokeSelector(const reg_t objId, const Selector selector, const int argc = 0, const StackPtr argv = nullptr) const;
|
||||
|
||||
/**
|
||||
* Determines whether the current stack contains calls from audio controls
|
||||
* that indicate a user-initiated change of audio settings.
|
||||
*/
|
||||
bool shouldSyncAudioToScummVM() const;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Hooks
|
||||
|
||||
public:
|
||||
/**
|
||||
* Guest additions hook for SciEngine::runGame.
|
||||
*/
|
||||
void sciEngineRunGameHook();
|
||||
|
||||
/**
|
||||
* Guest additions hook for write_var.
|
||||
*/
|
||||
void writeVarHook(const int type, const int index, const reg_t value);
|
||||
|
||||
/**
|
||||
* Guest additions hook for kDoSoundMasterVolume.
|
||||
*
|
||||
* @returns true if the default action should be prevented
|
||||
*/
|
||||
bool kDoSoundMasterVolumeHook(const int volume) const;
|
||||
|
||||
/**
|
||||
* Determines whether the user has control.
|
||||
*/
|
||||
bool userHasControl();
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Guest additions hook for SciEngine::initGame.
|
||||
*/
|
||||
void sciEngineInitGameHook();
|
||||
|
||||
/**
|
||||
* Guest additions hook for send_selector.
|
||||
*/
|
||||
void sendSelectorHook(const reg_t sendObj, Selector &selector, reg_t *argp);
|
||||
|
||||
/**
|
||||
* Guest additions hook for Audio32::setVolume.
|
||||
*
|
||||
* @returns true if the default action should be prevented
|
||||
*/
|
||||
bool audio32SetVolumeHook(const int16 channelIndex, const int16 volume) const;
|
||||
|
||||
/**
|
||||
* Guest additions hook for kDoSoundSetVolume.
|
||||
*/
|
||||
void kDoSoundSetVolumeHook(const reg_t soundObj, const int16 volume) const;
|
||||
|
||||
/**
|
||||
* Guest additions hook for SegManager::instantiateScript.
|
||||
*/
|
||||
void instantiateScriptHook(Script &script, const bool ignoreDelayedRestore = false) const;
|
||||
|
||||
/**
|
||||
* Guest additions hook for SegManager::saveLoadWithSerializer.
|
||||
*/
|
||||
void segManSaveLoadScriptHook(Script &script) const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Guest additions hook for kGetEvent.
|
||||
*/
|
||||
bool kGetEventHook() const;
|
||||
|
||||
/**
|
||||
* Guest additions hook for kWait.
|
||||
*/
|
||||
bool kWaitHook() const;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Guest additions hook for kPlayDuck(Play) and kPlayVMD(PlayUntilEvent).
|
||||
*/
|
||||
bool kPlayDuckPlayVMDHook() const;
|
||||
#endif
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Integrated save & restore
|
||||
|
||||
public:
|
||||
/**
|
||||
* Patches game scripts to use the ScummVM save/load dialogue instead of the
|
||||
* game's native save/load dialogue when a user tries to save or restore a
|
||||
* game from inside the game.
|
||||
*/
|
||||
void patchGameSaveRestore() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Patches the ScummVM save/load dialogue into the game for SCI16 games that
|
||||
* use Game::save and Game::restore.
|
||||
*/
|
||||
void patchGameSaveRestoreSCI16() const;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
public:
|
||||
/**
|
||||
* Finds the correct save file number and description to save or load and
|
||||
* returns it to the VM. For user-interactive save file lookup, this method
|
||||
* displays the ScummVM save/load dialogue. For delayed restores, it returns
|
||||
* the save game number sent by the ScummVM launcher without prompting the
|
||||
* user.
|
||||
*/
|
||||
reg_t kScummVMSaveLoad(EngineState *s, int argc, reg_t *argv) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Patches the ScummVM save/load dialogue into SCI32 games that use
|
||||
* SRDialog.
|
||||
*/
|
||||
void patchGameSaveRestoreSCI32(Script &script) const;
|
||||
|
||||
/**
|
||||
* Patches the ScummVM save/load dialogue into Torin/LSL7.
|
||||
*/
|
||||
void patchGameSaveRestoreTorin(Script &script) const;
|
||||
|
||||
/**
|
||||
* Patches the ScummVM save/load dialogue into Phant2.
|
||||
*/
|
||||
void patchGameSaveRestorePhant2(Script &script) const;
|
||||
|
||||
/**
|
||||
* Patches the ScummVM save/load dialogue into RAMA.
|
||||
*/
|
||||
void patchGameSaveRestoreRama(Script &script) const;
|
||||
|
||||
/**
|
||||
* Patches the `doit` method of an SRDialog object with the given name
|
||||
* using the given patch data.
|
||||
*/
|
||||
void patchSRDialogDoit(Script &script, const char *const objectName, const byte *patchData, const int patchSize, const int *uint16Offsets = nullptr, const uint numOffsets = 0) const;
|
||||
|
||||
/**
|
||||
* Prompts for a save game and returns it to game scripts using default
|
||||
* SRDialog game class semantics.
|
||||
*/
|
||||
reg_t promptSaveRestoreDefault(EngineState *s, int argc, reg_t *argv) const;
|
||||
|
||||
/**
|
||||
* Prompts for a save game and returns it to game scripts using Torin/LSL7's
|
||||
* custom NewGame class semantics.
|
||||
*/
|
||||
reg_t promptSaveRestoreTorin(EngineState *s, int argc, reg_t *argv) const;
|
||||
|
||||
/**
|
||||
* Prompts for a save game and returns it to game scripts using Phant2's
|
||||
* custom ControlPanel class semantics.
|
||||
*/
|
||||
reg_t promptSaveRestorePhant2(EngineState *s, int argc, reg_t *argv) const;
|
||||
|
||||
/**
|
||||
* Prompts for a save game and returns it to game scripts using RAMA's
|
||||
* custom SRDialog class semantics.
|
||||
*/
|
||||
reg_t promptSaveRestoreRama(EngineState *s, int argc, reg_t *argv) const;
|
||||
|
||||
/**
|
||||
* Prompts for a save game and returns it to game scripts using Hoyle 5's
|
||||
* custom SRDialog class semantics.
|
||||
*/
|
||||
reg_t promptSaveRestoreHoyle5(EngineState *s, int argc, reg_t *argv) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Prompts the user to save or load a game.
|
||||
*
|
||||
* @param isSave If true, the prompt is for saving.
|
||||
* @param outDescription Will be filled with the save game description.
|
||||
* Optional for loads, required for saves.
|
||||
* @param forcedSaveId During delayed restore, force the returned save game
|
||||
* id to this value.
|
||||
*/
|
||||
int runSaveRestore(const bool isSave, const reg_t outDescription, const int forcedSaveId = -1) const;
|
||||
int runSaveRestore(const bool isSave, Common::String &outDescription, const int forcedSaveId = -1) const;
|
||||
#endif
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Restore from launcher
|
||||
|
||||
private:
|
||||
/**
|
||||
* Invokes the game's save restore mechanism to load a save game that was
|
||||
* selected in the ScummVM launcher when the game was started.
|
||||
*/
|
||||
bool restoreFromLauncher() const;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* If true, GuestAdditions is in the process of handling a delayed game
|
||||
* restore from the ScummVM launcher or global menu.
|
||||
*/
|
||||
mutable bool _restoring;
|
||||
#endif
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Message type sync
|
||||
|
||||
private:
|
||||
/**
|
||||
* True if the message type (text/speech/text+speech) has been synchronised
|
||||
* from ScummVM to the game.
|
||||
*/
|
||||
bool _messageTypeSynced;
|
||||
|
||||
/**
|
||||
* Synchronises the message type (speech/text/speech+text) from ScummVM to
|
||||
* a game.
|
||||
*/
|
||||
void syncMessageTypeFromScummVM() const;
|
||||
|
||||
/**
|
||||
* Synchronises the message type from ScummVM using the default strategy
|
||||
* (global90).
|
||||
*/
|
||||
void syncMessageTypeFromScummVMUsingDefaultStrategy() const;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Synchronises the message type from ScummVM using the strategy used by
|
||||
* Shivers (global211).
|
||||
*/
|
||||
void syncMessageTypeFromScummVMUsingShiversStrategy() const;
|
||||
|
||||
/**
|
||||
* Synchronises the message type from ScummVM using the strategy used by
|
||||
* LSL6hires (gameFlags).
|
||||
*/
|
||||
void syncMessageTypeFromScummVMUsingLSL6HiresStrategy() const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Synchronises the message type (speech/text/speech+text) from a game to
|
||||
* ScummVM.
|
||||
*/
|
||||
void syncMessageTypeToScummVM(const int index, const reg_t value);
|
||||
|
||||
/**
|
||||
* Synchronises the message type to ScummVM using the default strategy
|
||||
* (global90).
|
||||
*/
|
||||
void syncMessageTypeToScummVMUsingDefaultStrategy(const int index, const reg_t value);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Synchronises the message type to ScummVM using the strategy used by
|
||||
* Shivers (global211).
|
||||
*/
|
||||
void syncMessageTypeToScummVMUsingShiversStrategy(const int index, const reg_t value);
|
||||
|
||||
/**
|
||||
* Synchronises the message type to ScummVM using the strategy used by
|
||||
* LSL6hires (gameFlags).
|
||||
*/
|
||||
void syncMessageTypeToScummVMUsingLSL6HiresStrategy(const reg_t sendObj, Selector &selector, reg_t *argp);
|
||||
#endif
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Master volume sync
|
||||
|
||||
private:
|
||||
/**
|
||||
* Synchronises audio volume settings from ScummVM to the game, for games
|
||||
* that do not store volume themselves and just call to the kernel.
|
||||
*/
|
||||
void syncMasterVolumeFromScummVM() const;
|
||||
|
||||
/**
|
||||
* Synchronises audio volume settings from the game to ScummVM, for games
|
||||
* that do not store volume themselves and just call to the kernel.
|
||||
*/
|
||||
void syncMasterVolumeToScummVM(const int16 masterVolume) const;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Globals volume sync
|
||||
|
||||
private:
|
||||
/**
|
||||
* Synchronises audio volume settings from ScummVM to the game, for games
|
||||
* that store volumes in globals.
|
||||
*/
|
||||
void syncAudioVolumeGlobalsFromScummVM() const;
|
||||
|
||||
void syncLSL6VolumeFromScummVM(const int16 musicVolume) const;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Synchronises audio volume settings from ScummVM to GK1 at game startup
|
||||
* time.
|
||||
*/
|
||||
void syncGK1StartupVolumeFromScummVM(const int index, const reg_t value) const;
|
||||
|
||||
/**
|
||||
* Synchronises audio volume settings from ScummVM to GK1 when the game is
|
||||
* running.
|
||||
*/
|
||||
void syncGK1VolumeFromScummVM(const int16 musicVolume, const int16 dacVolume) const;
|
||||
|
||||
void syncGK2VolumeFromScummVM(const int16 musicVolume) const;
|
||||
void syncHoyle5VolumeFromScummVM(const int16 musicVolume) const;
|
||||
void syncPhant2VolumeFromScummVM(const int16 masterVolume) const;
|
||||
void syncRamaVolumeFromScummVM(const int16 musicVolume) const;
|
||||
void syncTorinVolumeFromScummVM(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Synchronises audio volume settings from a game to ScummVM, for games
|
||||
* that store volumes in globals.
|
||||
*/
|
||||
void syncAudioVolumeGlobalsToScummVM(const int index, const reg_t value) const;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Synchronises audio volume settings from GK1 to ScummVM.
|
||||
*/
|
||||
void syncGK1AudioVolumeToScummVM(const reg_t soundObj, const int16 volume) const;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Audio UI sync
|
||||
|
||||
private:
|
||||
/**
|
||||
* Synchronises the in-game control panel UI in response to a change of
|
||||
* volume from the ScummVM GUI. The values of the volume parameters passed
|
||||
* to this function are game-specific.
|
||||
*/
|
||||
void syncInGameUI(const int16 musicVolume, const int16 sfxVolume) const;
|
||||
|
||||
void syncGK1UI() const;
|
||||
void syncGK2UI() const;
|
||||
void syncHoyle5UI(const int16 musicVolume) const;
|
||||
#endif
|
||||
void syncLSL6UI(const int16 musicVolume) const;
|
||||
#ifdef ENABLE_SCI32
|
||||
void syncMGDXUI(const int16 musicVolume) const;
|
||||
void syncPhant1UI(const int16 oldMusicVolume, const int16 musicVolume, reg_t &musicGlobal, const int16 oldDacVolume, const int16 dacVolume, reg_t &dacGlobal) const;
|
||||
void syncPhant2UI(const int16 masterVolume) const;
|
||||
void syncPQ4UI(const int16 musicVolume) const;
|
||||
void syncPQSWATUI() const;
|
||||
void syncQFG4UI(const int16 musicVolume) const;
|
||||
void syncRamaUI(const int16 musicVolume) const;
|
||||
void syncShivers1UI(const int16 dacVolume) const;
|
||||
void syncSQ6UI() const;
|
||||
void syncTorinUI(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Talk speed sync
|
||||
|
||||
private:
|
||||
/**
|
||||
* Synchronises text speed settings from ScummVM to a game.
|
||||
*/
|
||||
void syncTextSpeedFromScummVM() const;
|
||||
|
||||
/**
|
||||
* Synchronises text speed settings from a game to ScummVM.
|
||||
*/
|
||||
void syncTextSpeedToScummVM(const int index, const reg_t value) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_GUEST_ADDITIONS_H
|
||||
515
engines/sci/engine/hoyle5poker.cpp
Normal file
515
engines/sci/engine/hoyle5poker.cpp
Normal file
@@ -0,0 +1,515 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/hoyle5poker.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/script.h"
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/vm.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
|
||||
//#define DEBUG_POKER_LOGIC
|
||||
|
||||
// The logic for the poker game in Hoyle Classic Games (Hoyle 5) is hardcoded
|
||||
// in PENGIN16.DLL, which is then loaded and invoked via the kWinDLL kernel call.
|
||||
// Note that the first player is the left one.
|
||||
|
||||
enum Hoyle5PokerSuits {
|
||||
kSuitSpades = 0,
|
||||
kSuitClubs = 1,
|
||||
kSuitDiamonds = 2,
|
||||
kSuitHearts = 3
|
||||
};
|
||||
|
||||
enum Hoyle5Operations {
|
||||
kCheckPlayerAction = 1, // localproc_0df8
|
||||
kCheckWinner = 2, // localproc_3020
|
||||
kCheckDiscard = 3, // PokerHand::think
|
||||
kCheckHand = 4 // PokerHand::whatAmI
|
||||
};
|
||||
|
||||
enum Hoyle5PlayerActions {
|
||||
kPlayerActionCheck = -2,
|
||||
kPlayerActionFold = -1,
|
||||
kPlayerActionCall = 0,
|
||||
kPlayerActionRaise = 1
|
||||
};
|
||||
|
||||
enum Hoyle5DiscardActions {
|
||||
kDiscardActionKeep = 0,
|
||||
kDiscardActionDiscard = 1
|
||||
};
|
||||
|
||||
enum Hoyle5HandType {
|
||||
kHandTypeFiveOfAKind = 1 << 8, // 256, five of a kind
|
||||
kHandTypeStraightFlush = 1 << 7, // 128, straight flush
|
||||
kHandTypeFourOfAKind = 1 << 6, // 64, four of a kind
|
||||
kHandTypeFullHouse = 1 << 5, // 32, full house
|
||||
kHandTypeFlush = 1 << 4, // 16, flush
|
||||
kHandTypeStraight = 1 << 3, // 8, straight
|
||||
kHandTypeThreeOfAKind = 1 << 2, // 4, three of a kind
|
||||
kHandTypeTwoPairs = 1 << 1, // 2, two pairs
|
||||
kHandTypeOnePair = 1 << 0, // 1, one pair
|
||||
kHandTypeHighCard = 0 // 0, high card
|
||||
};
|
||||
|
||||
enum Hoyle5PokerData {
|
||||
kOperation = 0,
|
||||
kTotalChips = 1,
|
||||
kCurrentPot = 2,
|
||||
kCurrentBet = 3,
|
||||
kTotalChipsPlayer1 = 4,
|
||||
kTotalChipsPlayer2 = 5,
|
||||
kTotalChipsPlayer3 = 6,
|
||||
kTotalChipsPlayer4 = 7,
|
||||
kStatusPlayer1 = 8,
|
||||
kStatusPlayer2 = 9,
|
||||
kStatusPlayer3 = 10,
|
||||
kStatusPlayer4 = 11,
|
||||
kTotalBetPlayer1 = 12,
|
||||
kTotalBetPlayer2 = 13,
|
||||
kTotalBetPlayer3 = 14,
|
||||
kTotalBetPlayer4 = 15,
|
||||
// 16: related to the current bet
|
||||
kCurrentPlayer = 17, // hand number
|
||||
kCurrentStage = 18, // Stage 1: Card changes, 2: Betting
|
||||
kCard0 = 19,
|
||||
kSuit0 = 20,
|
||||
kCard1 = 21,
|
||||
kSuit1 = 22,
|
||||
kCard2 = 23,
|
||||
kSuit2 = 24,
|
||||
kCard3 = 25,
|
||||
kSuit3 = 26,
|
||||
kCard4 = 27,
|
||||
kSuit4 = 28,
|
||||
// 19 - 28: current player's cards (number + suit)
|
||||
// 29 - 38: next clockwise player's cards (number + suit)
|
||||
// 39 - 48: next clockwise player's cards (number + suit)
|
||||
// 49 - 58: next clockwise player's cards (number + suit)
|
||||
kUnkVar = 59, // set by localproc_0df8 to global 906
|
||||
// ---- Return values - start ---------------------------
|
||||
kPlayerAction = 60, // flag, checked by localproc_0df8
|
||||
kWhatAmIResult = 61, // bitmask, 0 - 128, checked by PokerHand::whatAmI. Determines what kind of card each player has
|
||||
kWinningPlayers = 62, // bitmask, winning players (0000 - 1111 binary), checked by localproc_3020
|
||||
kDiscardCard0 = 63, // flag, checked by PokerHand::think
|
||||
kDiscardCard1 = 64, // flag, checked by PokerHand::think
|
||||
kDiscardCard2 = 65, // flag, checked by PokerHand::think
|
||||
kDiscardCard3 = 66, // flag, checked by PokerHand::think
|
||||
kDiscardCard4 = 67, // flag, checked by PokerHand::think
|
||||
// ---- Return values - end -----------------------------
|
||||
// 77 is a random number (0 - 32767)
|
||||
kLastRaise1 = 78,
|
||||
kLastRaise2 = 79,
|
||||
kLastRaise3 = 80,
|
||||
kLastRaise4 = 81,
|
||||
kLastSaw1 = 82,
|
||||
kLastSaw2 = 83,
|
||||
kLastSaw3 = 84,
|
||||
kLastSaw4 = 85,
|
||||
kTookCard1 = 86,
|
||||
kTookCard2 = 87,
|
||||
kTookCard3 = 88,
|
||||
kTookCard4 = 89
|
||||
// 90 is a number
|
||||
};
|
||||
|
||||
#ifdef DEBUG_POKER_LOGIC
|
||||
Common::String getCardDescription(int16 card, int16 suit) {
|
||||
Common::String result;
|
||||
|
||||
if (card >= 2 && card <= 10)
|
||||
result += Common::String::format("%d", card);
|
||||
else if (card == 11)
|
||||
result = "Jack";
|
||||
else if (card == 12)
|
||||
result = "Queen";
|
||||
else if (card == 13)
|
||||
result = "King";
|
||||
else if (card == 14)
|
||||
result = "Ace";
|
||||
else
|
||||
result = "Unknown";
|
||||
|
||||
switch (suit) {
|
||||
case kSuitSpades:
|
||||
return result + " of spades";
|
||||
case kSuitClubs:
|
||||
return result + " of clubs";
|
||||
case kSuitDiamonds:
|
||||
return result + " of diamonds";
|
||||
case kSuitHearts:
|
||||
return result + " of hearts";
|
||||
default:
|
||||
return result + " of unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void debugInputData(SciArray* data) {
|
||||
debug("Player %d's turn", data->getAsInt16(kCurrentPlayer));
|
||||
|
||||
debug("Pot: %d, bet: %d", data->getAsInt16(kCurrentPot), data->getAsInt16(kCurrentBet));
|
||||
|
||||
debug("Chips: %d %d %d %d - %d in total",
|
||||
data->getAsInt16(kTotalChipsPlayer1),
|
||||
data->getAsInt16(kTotalChipsPlayer2),
|
||||
data->getAsInt16(kTotalChipsPlayer3),
|
||||
data->getAsInt16(kTotalChipsPlayer4),
|
||||
data->getAsInt16(kTotalChips)
|
||||
);
|
||||
|
||||
debug("Player status: %d %d %d %d",
|
||||
data->getAsInt16(kStatusPlayer1),
|
||||
data->getAsInt16(kStatusPlayer2),
|
||||
data->getAsInt16(kStatusPlayer3),
|
||||
data->getAsInt16(kStatusPlayer4)
|
||||
);
|
||||
|
||||
for (int player = 0; player < 4; player++) {
|
||||
debug("Player %d cards:", player);
|
||||
for (int i = 19 + player * 10; i < 29 + player * 10; i += 2) {
|
||||
if (data->getAsInt16(i) > 0)
|
||||
debug("- %s", getCardDescription(data->getAsInt16(i), data->getAsInt16(i + 1)).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < data->size(); i++) {
|
||||
if (i >= kTotalChipsPlayer1 && i <= kTotalChipsPlayer4)
|
||||
continue;
|
||||
if (i >= 8 && i <= 11)
|
||||
continue;
|
||||
if (i >= 19 && i <= 58)
|
||||
continue;
|
||||
|
||||
if (data->getAsInt16(i) != 0)
|
||||
debug("%d: %d", i, data->getAsInt16(i));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int getCardValue(int card) {
|
||||
return card == 1 ? 14 : card; // aces are the highest valued cards
|
||||
}
|
||||
|
||||
int getCardTotal(SciArray *data, int player) {
|
||||
int result = 0;
|
||||
|
||||
int cards[5] = {
|
||||
getCardValue(data->getAsInt16(kCard0 + 10 * player)),
|
||||
getCardValue(data->getAsInt16(kCard1 + 10 * player)),
|
||||
getCardValue(data->getAsInt16(kCard2 + 10 * player)),
|
||||
getCardValue(data->getAsInt16(kCard3 + 10 * player)),
|
||||
getCardValue(data->getAsInt16(kCard4 + 10 * player)),
|
||||
};
|
||||
|
||||
Common::sort(cards, cards + 5, Common::Less<int>());
|
||||
|
||||
int sameRank = 0;
|
||||
int sameSuit = 0;
|
||||
int orderedCards = 0;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (cards[i] == cards[i + 1]) {
|
||||
if (sameRank == 0) {
|
||||
result += cards[i] + cards[i + 1];
|
||||
sameRank += 2;
|
||||
} else {
|
||||
result += cards[i + 1];
|
||||
sameRank++;
|
||||
}
|
||||
}
|
||||
if (cards[i] == cards[i + 1] - 1)
|
||||
orderedCards == 0 ? orderedCards += 2 : orderedCards++;
|
||||
}
|
||||
|
||||
bool isFullHouse =
|
||||
(cards[0] == cards[1] && cards[1] == cards[2] && cards[3] == cards[4]) ||
|
||||
(cards[0] == cards[1] && cards[2] == cards[3] && cards[3] == cards[4]);
|
||||
|
||||
if (isFullHouse || sameSuit == 5 || orderedCards == 5) {
|
||||
result = 0;
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
result += cards[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Checks a player's hand, and returns its type using a bitmask
|
||||
int checkHand(SciArray *data, int player = 0) {
|
||||
int cards[5] = {
|
||||
data->getAsInt16(kCard0 + 10 * player),
|
||||
data->getAsInt16(kCard1 + 10 * player),
|
||||
data->getAsInt16(kCard2 + 10 * player),
|
||||
data->getAsInt16(kCard3 + 10 * player),
|
||||
data->getAsInt16(kCard4 + 10 * player),
|
||||
};
|
||||
|
||||
int suits[5] = {
|
||||
data->getAsInt16(kSuit0 + 10 * player),
|
||||
data->getAsInt16(kSuit1 + 10 * player),
|
||||
data->getAsInt16(kSuit2 + 10 * player),
|
||||
data->getAsInt16(kSuit3 + 10 * player),
|
||||
data->getAsInt16(kSuit4 + 10 * player),
|
||||
};
|
||||
|
||||
Common::sort(cards, cards + 5, Common::Less<int>());
|
||||
|
||||
int lastCard = -1;
|
||||
int pairs = 0;
|
||||
int sameRank = 0;
|
||||
int sameSuit = 0;
|
||||
int orderedCards = 0;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (cards[i] == cards[i + 1] && cards[i] != lastCard)
|
||||
pairs++;
|
||||
if (cards[i] == cards[i + 1])
|
||||
sameRank == 0 ? sameRank += 2 : sameRank++;
|
||||
if (suits[i] == suits[i + 1])
|
||||
sameSuit == 0 ? sameSuit += 2 : sameSuit++;
|
||||
if (cards[i] == cards[i + 1] - 1)
|
||||
orderedCards == 0 ? orderedCards += 2 : orderedCards++;
|
||||
|
||||
lastCard = cards[i];
|
||||
}
|
||||
|
||||
bool isFullHouse =
|
||||
(cards[0] == cards[1] && cards[1] == cards[2] && cards[3] == cards[4]) ||
|
||||
(cards[0] == cards[1] && cards[2] == cards[3] && cards[3] == cards[4]);
|
||||
|
||||
if (pairs == 1 && sameRank == 2)
|
||||
return kHandTypeOnePair;
|
||||
else if (pairs == 2 && !isFullHouse)
|
||||
return kHandTypeTwoPairs;
|
||||
else if (sameRank == 3 && !isFullHouse)
|
||||
return kHandTypeThreeOfAKind;
|
||||
else if (orderedCards == 5 && sameSuit < 5)
|
||||
return kHandTypeStraight;
|
||||
else if (orderedCards < 5 && sameSuit == 5)
|
||||
return kHandTypeFlush;
|
||||
else if (isFullHouse)
|
||||
return kHandTypeFullHouse;
|
||||
else if (sameRank == 4)
|
||||
return kHandTypeFourOfAKind;
|
||||
else if (orderedCards == 5 && sameSuit == 5)
|
||||
return kHandTypeStraightFlush;
|
||||
else if (sameRank == 5)
|
||||
return kHandTypeFiveOfAKind;
|
||||
|
||||
return kHandTypeHighCard;
|
||||
}
|
||||
|
||||
struct Hand {
|
||||
int player;
|
||||
int handTotal;
|
||||
|
||||
Hand(int p, int h) : player(p), handTotal(h) {}
|
||||
};
|
||||
|
||||
struct WinningHand : public Common::BinaryFunction<Hand, Hand, bool> {
|
||||
bool operator()(const Hand &x, const Hand &y) const { return x.handTotal > y.handTotal; }
|
||||
};
|
||||
|
||||
int getWinner(SciArray *data) {
|
||||
Hand playerHands[4] = {
|
||||
Hand(0, checkHand(data, 0)),
|
||||
Hand(1, checkHand(data, 1)),
|
||||
Hand(2, checkHand(data, 2)),
|
||||
Hand(3, checkHand(data, 3))
|
||||
};
|
||||
|
||||
Common::sort(playerHands, playerHands + 4, WinningHand());
|
||||
|
||||
if (playerHands[0].handTotal > playerHands[1].handTotal)
|
||||
return playerHands[0].player;
|
||||
else
|
||||
return getCardTotal(data, 0) > getCardTotal(data, 1) ? playerHands[0].player : playerHands[1].player;
|
||||
}
|
||||
|
||||
int16 findMostFrequentCard(int *cards, int16 ignoreCard = -1) {
|
||||
int16 mostFrequentCard = 0;
|
||||
int16 maxCount = 0;
|
||||
|
||||
for (int16 i = 0; i <= 4; ++i) {
|
||||
int16 count = 0;
|
||||
for (int j = 0; j <= 4; ++j) {
|
||||
if (cards[i] == cards[j])
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > maxCount && cards[i] != ignoreCard) {
|
||||
maxCount = count;
|
||||
mostFrequentCard = cards[i];
|
||||
}
|
||||
}
|
||||
|
||||
return mostFrequentCard;
|
||||
}
|
||||
|
||||
void handleDiscard(SciArray *data) {
|
||||
int16 player = data->getAsInt16(kCurrentPlayer);
|
||||
|
||||
int cards[5] = {
|
||||
data->getAsInt16(kCard0 + 10 * player),
|
||||
data->getAsInt16(kCard1 + 10 * player),
|
||||
data->getAsInt16(kCard2 + 10 * player),
|
||||
data->getAsInt16(kCard3 + 10 * player),
|
||||
data->getAsInt16(kCard4 + 10 * player),
|
||||
};
|
||||
|
||||
int hand = checkHand(data, player);
|
||||
int16 cardToKeep = findMostFrequentCard(cards);
|
||||
int16 cardToKeep2 = -1;
|
||||
if (hand != kHandTypeFiveOfAKind) {
|
||||
cardToKeep2 = findMostFrequentCard(cards, cardToKeep);
|
||||
}
|
||||
|
||||
data->setFromInt16(kDiscardCard0, kDiscardActionKeep);
|
||||
data->setFromInt16(kDiscardCard1, kDiscardActionKeep);
|
||||
data->setFromInt16(kDiscardCard2, kDiscardActionKeep);
|
||||
data->setFromInt16(kDiscardCard3, kDiscardActionKeep);
|
||||
data->setFromInt16(kDiscardCard4, kDiscardActionKeep);
|
||||
|
||||
switch (hand) {
|
||||
case kHandTypeFiveOfAKind:
|
||||
case kHandTypeStraightFlush:
|
||||
case kHandTypeFullHouse:
|
||||
case kHandTypeFlush:
|
||||
case kHandTypeStraight:
|
||||
// Nothing is discarded
|
||||
break;
|
||||
case kHandTypeThreeOfAKind:
|
||||
case kHandTypeFourOfAKind:
|
||||
case kHandTypeOnePair:
|
||||
case kHandTypeTwoPairs:
|
||||
// Discard the odd ones out. We don't have a full house case in this branch
|
||||
for (int i = 0; i <= 4; ++i) {
|
||||
if (cards[i] == cardToKeep && hand != kHandTypeTwoPairs)
|
||||
data->setFromInt16(kDiscardCard0 + i, kDiscardActionKeep);
|
||||
else if ((cards[i] == cardToKeep || cards[i] == cardToKeep2) && hand == kHandTypeTwoPairs)
|
||||
data->setFromInt16(kDiscardCard0 + i, kDiscardActionKeep);
|
||||
else
|
||||
data->setFromInt16(kDiscardCard0 + i, kDiscardActionDiscard);
|
||||
}
|
||||
break;
|
||||
case kHandTypeHighCard:
|
||||
// Everything is discarded
|
||||
data->setFromInt16(kDiscardCard0, kDiscardActionDiscard);
|
||||
data->setFromInt16(kDiscardCard1, kDiscardActionDiscard);
|
||||
data->setFromInt16(kDiscardCard2, kDiscardActionDiscard);
|
||||
data->setFromInt16(kDiscardCard3, kDiscardActionDiscard);
|
||||
data->setFromInt16(kDiscardCard4, kDiscardActionDiscard);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void handleRaiseOrCall(SciArray *data) {
|
||||
// Raise if the player has money, call otherwise
|
||||
int16 player = data->getAsInt16(kCurrentPlayer);
|
||||
int16 bet = data->getAsInt16(kCurrentBet);
|
||||
int16 playerBet = data->getAsInt16(kTotalBetPlayer1 + player);
|
||||
int16 chips = data->getAsInt16(kTotalChipsPlayer1 + player);
|
||||
|
||||
if (playerBet < bet && chips > bet)
|
||||
data->setFromInt16(kPlayerAction, kPlayerActionRaise);
|
||||
else
|
||||
data->setFromInt16(kPlayerAction, kPlayerActionCall);
|
||||
}
|
||||
|
||||
void handlePlayerAction(SciArray *data) {
|
||||
// TODO: This implementation is somewhat better than completely
|
||||
// random actions, but it's still severely lacking
|
||||
warning("The Poker player action logic has not been implemented yet");
|
||||
|
||||
int16 player = data->getAsInt16(kCurrentPlayer);
|
||||
int hand = checkHand(data, player);
|
||||
bool shouldBluff = g_sci->getRNG().getRandomBit();
|
||||
|
||||
if (shouldBluff) {
|
||||
handleRaiseOrCall(data);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (hand) {
|
||||
case kHandTypeFiveOfAKind:
|
||||
case kHandTypeStraightFlush:
|
||||
case kHandTypeFullHouse:
|
||||
case kHandTypeFlush:
|
||||
case kHandTypeStraight:
|
||||
case kHandTypeThreeOfAKind:
|
||||
case kHandTypeFourOfAKind:
|
||||
case kHandTypeTwoPairs:
|
||||
handleRaiseOrCall(data);
|
||||
break;
|
||||
case kHandTypeOnePair:
|
||||
data->setFromInt16(kPlayerAction, kPlayerActionCall);
|
||||
break;
|
||||
case kHandTypeHighCard:
|
||||
data->setFromInt16(kPlayerAction, kPlayerActionFold);
|
||||
// TODO: kPlayerActionCheck
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
reg_t hoyle5PokerEngine(SciArray *data) {
|
||||
int16 operation = data->getAsInt16(kOperation);
|
||||
|
||||
#ifdef DEBUG_POKER_LOGIC
|
||||
debug("*** Before running operation %d", operation);
|
||||
debugInputData(data);
|
||||
#endif
|
||||
|
||||
switch (operation) {
|
||||
case kCheckPlayerAction:
|
||||
handlePlayerAction(data);
|
||||
break;
|
||||
case kCheckWinner:
|
||||
data->setFromInt16(kWinningPlayers, 1 << getWinner(data));
|
||||
break;
|
||||
case kCheckDiscard:
|
||||
handleDiscard(data);
|
||||
break;
|
||||
case kCheckHand:
|
||||
data->setFromInt16(kWhatAmIResult, checkHand(data));
|
||||
break;
|
||||
default:
|
||||
error("Unknown Poker logic operation: %d", operation);
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_POKER_LOGIC
|
||||
debug("*** After running operation %d", operation);
|
||||
debugInputData(data);
|
||||
#endif
|
||||
|
||||
return TRUE_REG;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
38
engines/sci/engine/hoyle5poker.h
Normal file
38
engines/sci/engine/hoyle5poker.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_HOYLE5POKER_H
|
||||
#define SCI_ENGINE_HOYLE5POKER_H
|
||||
|
||||
#include "common/serializer.h"
|
||||
#include "common/str.h"
|
||||
#include "sci/engine/object.h"
|
||||
#include "sci/engine/vm.h"
|
||||
#include "sci/engine/vm_types.h" // for reg_t
|
||||
#include "sci/util.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
reg_t hoyle5PokerEngine(SciArray *data);
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_HOYLE5POKER_H
|
||||
974
engines/sci/engine/kernel.cpp
Normal file
974
engines/sci/engine/kernel.cpp
Normal file
@@ -0,0 +1,974 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/event.h"
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/kernel_tables.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/workarounds.h"
|
||||
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
Kernel::Kernel(ResourceManager *resMan, SegManager *segMan) :
|
||||
_resMan(resMan),
|
||||
_segMan(segMan),
|
||||
_invalid("<invalid>") {
|
||||
loadSelectorNames();
|
||||
mapSelectors();
|
||||
}
|
||||
|
||||
Kernel::~Kernel() {
|
||||
for (KernelFunctionArray::iterator it = _kernelFuncs.begin(); it != _kernelFuncs.end(); ++it) {
|
||||
if (it->subFunctionCount) {
|
||||
uint16 subFunctionNr = 0;
|
||||
while (subFunctionNr < it->subFunctionCount) {
|
||||
delete[] it->subFunctions[subFunctionNr].signature;
|
||||
subFunctionNr++;
|
||||
}
|
||||
delete[] it->subFunctions;
|
||||
}
|
||||
delete[] it->signature;
|
||||
}
|
||||
}
|
||||
|
||||
uint Kernel::getSelectorNamesSize() const {
|
||||
return _selectorNames.size();
|
||||
}
|
||||
|
||||
const Common::String &Kernel::getSelectorName(uint selector) {
|
||||
if (selector >= _selectorNames.size()) {
|
||||
// This should only occur in games w/o a selector-table
|
||||
// We need this for proper workaround tables
|
||||
// TODO: maybe check, if there is a fixed selector-table and error() out in that case
|
||||
for (uint loopSelector = _selectorNames.size(); loopSelector <= selector; ++loopSelector)
|
||||
_selectorNames.push_back(Common::String::format("<noname%d>", loopSelector));
|
||||
}
|
||||
|
||||
// Ensure that the selector has a name
|
||||
if (_selectorNames[selector].empty())
|
||||
_selectorNames[selector] = Common::String::format("<noname%d>", selector);
|
||||
|
||||
return _selectorNames[selector];
|
||||
}
|
||||
|
||||
uint Kernel::getKernelNamesSize() const {
|
||||
return _kernelNames.size();
|
||||
}
|
||||
|
||||
const Common::String &Kernel::getKernelName(uint number) const {
|
||||
assert(number < _kernelFuncs.size());
|
||||
return _kernelNames[number];
|
||||
}
|
||||
|
||||
Common::String Kernel::getKernelName(uint number, uint subFunction) const {
|
||||
assert(number < _kernelFuncs.size());
|
||||
const KernelFunction &kernelCall = _kernelFuncs[number];
|
||||
|
||||
assert(subFunction < kernelCall.subFunctionCount);
|
||||
return kernelCall.subFunctions[subFunction].name;
|
||||
}
|
||||
|
||||
|
||||
int Kernel::findKernelFuncPos(Common::String kernelFuncName) {
|
||||
for (uint32 i = 0; i < _kernelNames.size(); i++)
|
||||
if (_kernelNames[i] == kernelFuncName)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Kernel::findSelector(const char *selectorName) const {
|
||||
for (uint pos = 0; pos < _selectorNames.size(); ++pos) {
|
||||
if (_selectorNames[pos] == selectorName)
|
||||
return pos;
|
||||
}
|
||||
|
||||
debugC(kDebugLevelVM, "Could not map '%s' to any selector", selectorName);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Kernel::loadSelectorNames() {
|
||||
Resource *r = _resMan->findResource(ResourceId(kResourceTypeVocab, VOCAB_RESOURCE_SELECTORS), 0);
|
||||
bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
// Starting with KQ7, Mac versions have a BE name table. GK1 Mac and earlier (and all
|
||||
// other platforms) always use LE.
|
||||
const bool isBE = (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1_EARLY
|
||||
&& g_sci->getGameId() != GID_GK1);
|
||||
#else
|
||||
const bool isBE = false;
|
||||
#endif
|
||||
|
||||
if (!r) { // No such resource?
|
||||
// Check if we have a table for this game
|
||||
// Some demos do not have a selector table
|
||||
Common::StringArray staticSelectorTable = checkStaticSelectorNames();
|
||||
|
||||
if (staticSelectorTable.empty())
|
||||
error("Kernel: Could not retrieve selector names");
|
||||
else
|
||||
warning("No selector vocabulary found, using a static one");
|
||||
|
||||
for (uint32 i = 0; i < staticSelectorTable.size(); i++) {
|
||||
_selectorNames.push_back(staticSelectorTable[i]);
|
||||
if (oldScriptHeader)
|
||||
_selectorNames.push_back(staticSelectorTable[i]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int count = (isBE ? r->getUint16BEAt(0) : r->getUint16LEAt(0)) + 1; // Counter is slightly off
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
int offset = isBE ? r->getUint16BEAt(2 + i * 2) : r->getUint16LEAt(2 + i * 2);
|
||||
int len = isBE ? r->getUint16BEAt(offset) : r->getUint16LEAt(offset);
|
||||
|
||||
Common::String tmp = r->getStringAt(offset + 2, len);
|
||||
_selectorNames.push_back(tmp);
|
||||
//debug("%s", tmp.c_str());
|
||||
|
||||
// Early SCI versions used the LSB in the selector ID as a read/write
|
||||
// toggle. To compensate for that, we add every selector name twice.
|
||||
if (oldScriptHeader)
|
||||
_selectorNames.push_back(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
// this parses a written kernel signature into an internal memory format
|
||||
// [io] -> either integer or object
|
||||
// (io) -> optionally integer AND an object
|
||||
// (i) -> optional integer
|
||||
// . -> any type
|
||||
// i* -> at least one integer, more integers may follow after that
|
||||
// (i*) -> optional multiple integers
|
||||
// .* -> at least one parameter of any type and more parameters of any type may follow
|
||||
// (.*) -> any parameters afterwards (or none)
|
||||
// * -> means "more of the last parameter may follow (or none at all)", must be at the end of a signature. Is not valid anywhere else.
|
||||
static uint16 *parseKernelSignature(const char *kernelName, const char *writtenSig) {
|
||||
const char *curPos;
|
||||
char curChar;
|
||||
uint16 *result = nullptr;
|
||||
uint16 *writePos = nullptr;
|
||||
int size = 0;
|
||||
bool validType = false;
|
||||
bool optionalType = false;
|
||||
bool eitherOr = false;
|
||||
bool optional = false;
|
||||
bool hadOptional = false;
|
||||
|
||||
// No signature given? no signature out
|
||||
if (!writtenSig)
|
||||
return nullptr;
|
||||
|
||||
// First, we check how many bytes the result will be
|
||||
// we also check, if the written signature makes any sense
|
||||
curPos = writtenSig;
|
||||
while (*curPos) {
|
||||
curChar = *curPos;
|
||||
switch (curChar) {
|
||||
case '[': // either or
|
||||
if (eitherOr)
|
||||
error("signature for k%s: '[' used within '[]'", kernelName);
|
||||
eitherOr = true;
|
||||
validType = false;
|
||||
break;
|
||||
case ']': // either or end
|
||||
if (!eitherOr)
|
||||
error("signature for k%s: ']' used without leading '['", kernelName);
|
||||
if (!validType)
|
||||
error("signature for k%s: '[]' does not surround valid type(s)", kernelName);
|
||||
eitherOr = false;
|
||||
validType = false;
|
||||
size++;
|
||||
break;
|
||||
case '(': // optional
|
||||
if (optional)
|
||||
error("signature for k%s: '(' used within '()' brackets", kernelName);
|
||||
if (eitherOr)
|
||||
error("signature for k%s: '(' used within '[]' brackets", kernelName);
|
||||
optional = true;
|
||||
validType = false;
|
||||
optionalType = false;
|
||||
break;
|
||||
case ')': // optional end
|
||||
if (!optional)
|
||||
error("signature for k%s: ')' used without leading '('", kernelName);
|
||||
if (!optionalType)
|
||||
error("signature for k%s: '()' does not to surround valid type(s)", kernelName);
|
||||
optional = false;
|
||||
validType = false;
|
||||
hadOptional = true;
|
||||
break;
|
||||
case '0': // allowed types
|
||||
case 'i':
|
||||
case 'o':
|
||||
case 'r':
|
||||
case 'l':
|
||||
case 'n':
|
||||
case '.':
|
||||
case '!':
|
||||
if ((hadOptional) & (!optional))
|
||||
error("signature for k%s: non-optional type may not follow optional type", kernelName);
|
||||
validType = true;
|
||||
if (optional)
|
||||
optionalType = true;
|
||||
if (!eitherOr)
|
||||
size++;
|
||||
break;
|
||||
case '*': // accepts more of the same parameter (must be last char)
|
||||
if (!validType) {
|
||||
if ((writtenSig == curPos) || (*(curPos - 1) != ']'))
|
||||
error("signature for k%s: a valid type must be in front of '*'", kernelName);
|
||||
}
|
||||
if (eitherOr)
|
||||
error("signature for k%s: '*' may not be inside '[]'", kernelName);
|
||||
if (optional) {
|
||||
if ((*(curPos + 1) != ')') || (*(curPos + 2) != 0))
|
||||
error("signature for k%s: '*' may only be used for last type", kernelName);
|
||||
} else {
|
||||
if (*(curPos + 1) != 0)
|
||||
error("signature for k%s: '*' may only be used for last type", kernelName);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
error("signature for k%s: '%c' unknown", kernelName, *curPos);
|
||||
}
|
||||
curPos++;
|
||||
}
|
||||
|
||||
uint16 signature = 0;
|
||||
|
||||
// Now we allocate buffer with required size and fill it
|
||||
result = new uint16[size + 1];
|
||||
writePos = result;
|
||||
curPos = writtenSig;
|
||||
do {
|
||||
curChar = *curPos;
|
||||
if (!eitherOr) {
|
||||
// not within either-or, check if next character forces output
|
||||
switch (curChar) {
|
||||
case 0:
|
||||
case '[':
|
||||
case '(':
|
||||
case ')':
|
||||
case 'i':
|
||||
case 'o':
|
||||
case 'r':
|
||||
case 'l':
|
||||
case 'n':
|
||||
case '.':
|
||||
case '!':
|
||||
// and we also got some signature pending?
|
||||
if (signature) {
|
||||
if (!(signature & SIG_MAYBE_ANY))
|
||||
error("signature for k%s: invalid ('!') may only get used in combination with a real type", kernelName);
|
||||
if ((signature & SIG_IS_INVALID) && ((signature & SIG_MAYBE_ANY) == (SIG_TYPE_NULL | SIG_TYPE_INTEGER)))
|
||||
error("signature for k%s: invalid ('!') should not be used on exclusive null/integer type", kernelName);
|
||||
if (optional) {
|
||||
signature |= SIG_IS_OPTIONAL;
|
||||
if (curChar != ')')
|
||||
signature |= SIG_NEEDS_MORE;
|
||||
}
|
||||
*writePos = signature;
|
||||
writePos++;
|
||||
signature = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (curChar) {
|
||||
case '[': // either or
|
||||
eitherOr = true;
|
||||
break;
|
||||
case ']': // either or end
|
||||
eitherOr = false;
|
||||
break;
|
||||
case '(': // optional
|
||||
optional = true;
|
||||
break;
|
||||
case ')': // optional end
|
||||
optional = false;
|
||||
break;
|
||||
case '0':
|
||||
if (signature & SIG_TYPE_NULL)
|
||||
error("signature for k%s: NULL ('0') specified more than once", kernelName);
|
||||
signature |= SIG_TYPE_NULL;
|
||||
break;
|
||||
case 'i':
|
||||
if (signature & SIG_TYPE_INTEGER)
|
||||
error("signature for k%s: integer ('i') specified more than once", kernelName);
|
||||
signature |= SIG_TYPE_INTEGER | SIG_TYPE_NULL;
|
||||
break;
|
||||
case 'o':
|
||||
if (signature & SIG_TYPE_OBJECT)
|
||||
error("signature for k%s: object ('o') specified more than once", kernelName);
|
||||
signature |= SIG_TYPE_OBJECT;
|
||||
break;
|
||||
case 'r':
|
||||
if (signature & SIG_TYPE_REFERENCE)
|
||||
error("signature for k%s: reference ('r') specified more than once", kernelName);
|
||||
signature |= SIG_TYPE_REFERENCE;
|
||||
break;
|
||||
case 'l':
|
||||
if (signature & SIG_TYPE_LIST)
|
||||
error("signature for k%s: list ('l') specified more than once", kernelName);
|
||||
signature |= SIG_TYPE_LIST;
|
||||
break;
|
||||
case 'n':
|
||||
if (signature & SIG_TYPE_NODE)
|
||||
error("signature for k%s: node ('n') specified more than once", kernelName);
|
||||
signature |= SIG_TYPE_NODE;
|
||||
break;
|
||||
case '.':
|
||||
if (signature & SIG_MAYBE_ANY)
|
||||
error("signature for k%s: maybe-any ('.') shouldn't get specified with other types in front of it", kernelName);
|
||||
signature |= SIG_MAYBE_ANY;
|
||||
break;
|
||||
case '!':
|
||||
if (signature & SIG_IS_INVALID)
|
||||
error("signature for k%s: invalid ('!') specified more than once", kernelName);
|
||||
signature |= SIG_IS_INVALID;
|
||||
break;
|
||||
case '*': // accepts more of the same parameter
|
||||
signature |= SIG_MORE_MAY_FOLLOW;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
curPos++;
|
||||
} while (curChar);
|
||||
|
||||
// Write terminator
|
||||
*writePos = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint16 Kernel::findRegType(reg_t reg) {
|
||||
// No segment? Must be integer
|
||||
if (!reg.getSegment())
|
||||
return SIG_TYPE_INTEGER | (reg.getOffset() ? 0 : SIG_TYPE_NULL);
|
||||
|
||||
if (reg.getSegment() == kUninitializedSegment)
|
||||
return SIG_TYPE_UNINITIALIZED;
|
||||
|
||||
// Otherwise it's an object
|
||||
SegmentObj *mobj = _segMan->getSegmentObj(reg.getSegment());
|
||||
if (!mobj)
|
||||
return SIG_TYPE_ERROR;
|
||||
|
||||
uint16 result = 0;
|
||||
if (!mobj->isValidOffset(reg.getOffset()))
|
||||
result |= SIG_IS_INVALID;
|
||||
|
||||
switch (mobj->getType()) {
|
||||
case SEG_TYPE_SCRIPT:
|
||||
if (reg.getOffset() <= (*(Script *)mobj).getBufSize() &&
|
||||
reg.getOffset() >= (uint)-SCRIPT_OBJECT_MAGIC_OFFSET &&
|
||||
(*(Script *)mobj).offsetIsObject(reg.getOffset())) {
|
||||
result |= ((Script *)mobj)->getObject(reg.getOffset()) ? SIG_TYPE_OBJECT : SIG_TYPE_REFERENCE;
|
||||
} else
|
||||
result |= SIG_TYPE_REFERENCE;
|
||||
break;
|
||||
case SEG_TYPE_CLONES:
|
||||
result |= SIG_TYPE_OBJECT;
|
||||
break;
|
||||
case SEG_TYPE_LOCALS:
|
||||
case SEG_TYPE_STACK:
|
||||
case SEG_TYPE_DYNMEM:
|
||||
case SEG_TYPE_HUNK:
|
||||
#ifdef ENABLE_SCI32
|
||||
case SEG_TYPE_ARRAY:
|
||||
case SEG_TYPE_BITMAP:
|
||||
#endif
|
||||
result |= SIG_TYPE_REFERENCE;
|
||||
break;
|
||||
case SEG_TYPE_LISTS:
|
||||
result |= SIG_TYPE_LIST;
|
||||
break;
|
||||
case SEG_TYPE_NODES:
|
||||
result |= SIG_TYPE_NODE;
|
||||
break;
|
||||
default:
|
||||
result = SIG_TYPE_ERROR;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct SignatureDebugType {
|
||||
uint16 typeCheck;
|
||||
const char *text;
|
||||
};
|
||||
|
||||
static const SignatureDebugType signatureDebugTypeList[] = {
|
||||
{ SIG_TYPE_NULL, "null" },
|
||||
{ SIG_TYPE_INTEGER, "integer" },
|
||||
{ SIG_TYPE_UNINITIALIZED, "uninitialized" },
|
||||
{ SIG_TYPE_OBJECT, "object" },
|
||||
{ SIG_TYPE_REFERENCE, "reference" },
|
||||
{ SIG_TYPE_LIST, "list" },
|
||||
{ SIG_TYPE_NODE, "node" },
|
||||
{ SIG_TYPE_ERROR, "error" },
|
||||
{ SIG_IS_INVALID, "invalid" },
|
||||
{ 0, nullptr }
|
||||
};
|
||||
|
||||
static void kernelSignatureDebugType(Common::String &signatureDetailsStr, const uint16 type) {
|
||||
bool firstPrint = true;
|
||||
|
||||
const SignatureDebugType *list = signatureDebugTypeList;
|
||||
while (list->typeCheck) {
|
||||
if (type & list->typeCheck) {
|
||||
if (!firstPrint)
|
||||
// debugN(", ");
|
||||
signatureDetailsStr += ", ";
|
||||
// debugN("%s", list->text);
|
||||
// signatureDetailsStr += signatureDetailsStr.format("%s", list->text);
|
||||
signatureDetailsStr += list->text;
|
||||
firstPrint = false;
|
||||
}
|
||||
list++;
|
||||
}
|
||||
}
|
||||
|
||||
// Create string, that holds the details of a kernel call signature and current arguments
|
||||
// For debugging purposes
|
||||
void Kernel::signatureDebug(Common::String &signatureDetailsStr, const uint16 *sig, int argc, const reg_t *argv) {
|
||||
int argnr = 0;
|
||||
|
||||
// add ERROR: to debug output
|
||||
debugN("ERROR:");
|
||||
|
||||
while (*sig || argc) {
|
||||
// add leading spaces for additional parameters
|
||||
signatureDetailsStr += signatureDetailsStr.format("parameter %d: ", argnr++);
|
||||
if (argc) {
|
||||
reg_t parameter = *argv;
|
||||
signatureDetailsStr += signatureDetailsStr.format("%04x:%04x (", PRINT_REG(parameter));
|
||||
uint16 regType = findRegType(parameter);
|
||||
kernelSignatureDebugType(signatureDetailsStr, regType);
|
||||
signatureDetailsStr += ")";
|
||||
argv++;
|
||||
argc--;
|
||||
} else {
|
||||
signatureDetailsStr += "not passed";
|
||||
}
|
||||
if (*sig) {
|
||||
const uint16 signature = *sig;
|
||||
if ((signature & SIG_MAYBE_ANY) == SIG_MAYBE_ANY) {
|
||||
signatureDetailsStr += ", may be any";
|
||||
} else {
|
||||
signatureDetailsStr += ", should be ";
|
||||
kernelSignatureDebugType(signatureDetailsStr, signature);
|
||||
}
|
||||
if (signature & SIG_IS_OPTIONAL)
|
||||
signatureDetailsStr += " (optional)";
|
||||
if (signature & SIG_NEEDS_MORE)
|
||||
signatureDetailsStr += " (needs more)";
|
||||
if (signature & SIG_MORE_MAY_FOLLOW)
|
||||
signatureDetailsStr += " (more may follow)";
|
||||
sig++;
|
||||
}
|
||||
signatureDetailsStr += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool Kernel::signatureMatch(const uint16 *sig, int argc, const reg_t *argv) {
|
||||
uint16 nextSig = *sig;
|
||||
uint16 curSig = nextSig;
|
||||
while (nextSig && argc) {
|
||||
curSig = nextSig;
|
||||
int type = findRegType(*argv);
|
||||
|
||||
if ((type & SIG_IS_INVALID) && (!(curSig & SIG_IS_INVALID)))
|
||||
return false; // pointer is invalid and signature doesn't allow that?
|
||||
|
||||
if (!((type & ~SIG_IS_INVALID) & curSig)) {
|
||||
if ((type & ~SIG_IS_INVALID) == SIG_TYPE_ERROR && (curSig & SIG_IS_INVALID)) {
|
||||
// Type is unknown (error - usually because of a deallocated object or
|
||||
// stale pointer) and the signature allows invalid pointers. In this case,
|
||||
// ignore the invalid pointer.
|
||||
} else {
|
||||
return false; // type mismatch
|
||||
}
|
||||
}
|
||||
|
||||
if (!(curSig & SIG_MORE_MAY_FOLLOW)) {
|
||||
sig++;
|
||||
nextSig = *sig;
|
||||
} else {
|
||||
nextSig |= SIG_IS_OPTIONAL; // more may follow -> assumes followers are optional
|
||||
}
|
||||
argv++;
|
||||
argc--;
|
||||
}
|
||||
|
||||
// Too many arguments?
|
||||
if (argc)
|
||||
return false;
|
||||
// Signature end reached?
|
||||
if (nextSig == 0)
|
||||
return true;
|
||||
// current parameter is optional?
|
||||
if (curSig & SIG_IS_OPTIONAL) {
|
||||
// yes, check if nothing more is required
|
||||
if (!(curSig & SIG_NEEDS_MORE))
|
||||
return true;
|
||||
} else {
|
||||
// no, check if next parameter is optional
|
||||
if (nextSig & SIG_IS_OPTIONAL)
|
||||
return true;
|
||||
}
|
||||
// Too few arguments or more optional arguments required
|
||||
return false;
|
||||
}
|
||||
|
||||
void Kernel::mapFunctions(GameFeatures *features) {
|
||||
int mapped = 0;
|
||||
int ignored = 0;
|
||||
uint functionCount = _kernelNames.size();
|
||||
byte platformMask = 0;
|
||||
SciVersion myVersion = getSciVersion();
|
||||
|
||||
switch (g_sci->getPlatform()) {
|
||||
case Common::kPlatformDOS:
|
||||
case Common::kPlatformFMTowns:
|
||||
platformMask = SIGFOR_DOS;
|
||||
break;
|
||||
case Common::kPlatformPC98:
|
||||
platformMask = SIGFOR_PC98;
|
||||
break;
|
||||
case Common::kPlatformWindows:
|
||||
platformMask = SIGFOR_WIN;
|
||||
break;
|
||||
case Common::kPlatformMacintosh:
|
||||
platformMask = SIGFOR_MAC;
|
||||
break;
|
||||
case Common::kPlatformAmiga:
|
||||
platformMask = SIGFOR_AMIGA;
|
||||
break;
|
||||
case Common::kPlatformAtariST:
|
||||
platformMask = SIGFOR_ATARI;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_kernelFuncs.resize(functionCount);
|
||||
|
||||
for (uint id = 0; id < functionCount; id++) {
|
||||
// First, get the name, if known, of the kernel function with number functnr
|
||||
Common::String kernelName = _kernelNames[id];
|
||||
|
||||
// Reset the table entry
|
||||
_kernelFuncs[id].function = nullptr;
|
||||
_kernelFuncs[id].signature = nullptr;
|
||||
_kernelFuncs[id].name = nullptr;
|
||||
_kernelFuncs[id].workarounds = nullptr;
|
||||
_kernelFuncs[id].subFunctions = nullptr;
|
||||
_kernelFuncs[id].subFunctionCount = 0;
|
||||
if (kernelName.empty()) {
|
||||
// No name was given -> must be an unknown opcode
|
||||
warning("Kernel function %x unknown", id);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't map dummy functions - they will never be called
|
||||
if (kernelName == "Dummy") {
|
||||
_kernelFuncs[id].function = kDummy;
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
// Several SCI 2.1 Middle Mac games use a modified kDoSound
|
||||
// with different subop numbers.
|
||||
if (features->useDoSoundMac32() && kernelName == "DoSound") {
|
||||
_kernelFuncs[id].function = kDoSoundMac32;
|
||||
_kernelFuncs[id].signature = parseKernelSignature("DoSoundMac32", "i(.*)");
|
||||
_kernelFuncs[id].name = "DoSoundMac32";
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
// If the name is known, look it up in s_kernelMap. This table
|
||||
// maps kernel func names to actual function (pointers).
|
||||
SciKernelMapEntry *kernelMap = s_kernelMap;
|
||||
bool nameMatch = false;
|
||||
while (kernelMap->name) {
|
||||
if (kernelName == kernelMap->name) {
|
||||
if ((kernelMap->fromVersion == SCI_VERSION_NONE) || (kernelMap->fromVersion <= myVersion))
|
||||
if ((kernelMap->toVersion == SCI_VERSION_NONE) || (kernelMap->toVersion >= myVersion))
|
||||
if (platformMask & kernelMap->forPlatform)
|
||||
break;
|
||||
nameMatch = true;
|
||||
}
|
||||
kernelMap++;
|
||||
}
|
||||
|
||||
if (kernelMap->name) {
|
||||
// A match was found
|
||||
_kernelFuncs[id].function = kernelMap->function;
|
||||
_kernelFuncs[id].name = kernelMap->name;
|
||||
_kernelFuncs[id].signature = parseKernelSignature(kernelMap->name, kernelMap->signature);
|
||||
_kernelFuncs[id].workarounds = kernelMap->workarounds;
|
||||
if (kernelMap->subFunctions) {
|
||||
// Get version for subfunction identification
|
||||
SciVersion mySubVersion = (SciVersion)kernelMap->function(nullptr, 0, nullptr).getOffset();
|
||||
// Now check whats the highest subfunction-id for this version
|
||||
const SciKernelMapSubEntry *kernelSubMap = kernelMap->subFunctions;
|
||||
uint16 subFunctionCount = 0;
|
||||
while (kernelSubMap->function) {
|
||||
if ((kernelSubMap->fromVersion == SCI_VERSION_NONE) || (kernelSubMap->fromVersion <= mySubVersion))
|
||||
if ((kernelSubMap->toVersion == SCI_VERSION_NONE) || (kernelSubMap->toVersion >= mySubVersion))
|
||||
if (subFunctionCount <= kernelSubMap->id)
|
||||
subFunctionCount = kernelSubMap->id + 1;
|
||||
kernelSubMap++;
|
||||
}
|
||||
if (!subFunctionCount)
|
||||
error("k%s[%x]: no subfunctions found for requested version %s", kernelName.c_str(), id, getSciVersionDesc(mySubVersion));
|
||||
// Now allocate required memory and go through it again
|
||||
_kernelFuncs[id].subFunctionCount = subFunctionCount;
|
||||
KernelSubFunction *subFunctions = new KernelSubFunction[subFunctionCount]();
|
||||
_kernelFuncs[id].subFunctions = subFunctions;
|
||||
// And fill this info out
|
||||
kernelSubMap = kernelMap->subFunctions;
|
||||
uint kernelSubNr = 0;
|
||||
while (kernelSubMap->function) {
|
||||
if ((kernelSubMap->fromVersion == SCI_VERSION_NONE) || (kernelSubMap->fromVersion <= mySubVersion))
|
||||
if ((kernelSubMap->toVersion == SCI_VERSION_NONE) || (kernelSubMap->toVersion >= mySubVersion)) {
|
||||
uint subId = kernelSubMap->id;
|
||||
if (!subFunctions[subId].function) {
|
||||
subFunctions[subId].function = kernelSubMap->function;
|
||||
subFunctions[subId].name = kernelSubMap->name;
|
||||
subFunctions[subId].workarounds = kernelSubMap->workarounds;
|
||||
if (kernelSubMap->signature) {
|
||||
subFunctions[subId].signature = parseKernelSignature(kernelSubMap->name, kernelSubMap->signature);
|
||||
} else {
|
||||
// we go back the submap to find the previous signature for that kernel call
|
||||
const SciKernelMapSubEntry *kernelSubMapBack = kernelSubMap;
|
||||
uint kernelSubLeft = kernelSubNr;
|
||||
while (kernelSubLeft) {
|
||||
kernelSubLeft--;
|
||||
kernelSubMapBack--;
|
||||
if (!strcmp(kernelSubMapBack->name, kernelSubMap->name)) {
|
||||
if (kernelSubMapBack->signature) {
|
||||
subFunctions[subId].signature = parseKernelSignature(kernelSubMap->name, kernelSubMapBack->signature);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!subFunctions[subId].signature)
|
||||
error("k%s: no previous signatures", kernelSubMap->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
kernelSubMap++;
|
||||
kernelSubNr++;
|
||||
}
|
||||
}
|
||||
++mapped;
|
||||
} else {
|
||||
if (nameMatch)
|
||||
error("k%s[%x]: not found for this version/platform", kernelName.c_str(), id);
|
||||
// No match but a name was given -> stub
|
||||
warning("k%s[%x]: unmapped", kernelName.c_str(), id);
|
||||
_kernelFuncs[id].function = kStub;
|
||||
}
|
||||
} // for all functions requesting to be mapped
|
||||
|
||||
debugC(kDebugLevelVM, "Handled %d/%d kernel functions, mapping %d and ignoring %d.",
|
||||
mapped + ignored, _kernelNames.size(), mapped, ignored);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
enum {
|
||||
kKernelEntriesSci2 = 0x8b,
|
||||
kKernelEntriesGk2Demo = 0xa0,
|
||||
kKernelEntriesSci21 = 0x9d,
|
||||
kKernelEntriesSci3 = 0xa2
|
||||
};
|
||||
#endif
|
||||
|
||||
void Kernel::loadKernelNames(GameFeatures *features) {
|
||||
_kernelNames.clear();
|
||||
|
||||
if (getSciVersion() <= SCI_VERSION_1_1) {
|
||||
_kernelNames = Common::StringArray(s_defaultKernelNames, ARRAYSIZE(s_defaultKernelNames));
|
||||
|
||||
// Some (later) SCI versions replaced CanBeHere by CantBeHere
|
||||
// If vocab.999 exists, the kernel function is still named CanBeHere
|
||||
if (_selectorCache.cantBeHere != -1)
|
||||
_kernelNames[0x4d] = "CantBeHere";
|
||||
}
|
||||
|
||||
switch (getSciVersion()) {
|
||||
case SCI_VERSION_0_EARLY:
|
||||
case SCI_VERSION_0_LATE:
|
||||
// Insert SCI0 file functions after SetCursor (0x28)
|
||||
_kernelNames.insert_at(0x29, "FOpen");
|
||||
_kernelNames.insert_at(0x2A, "FPuts");
|
||||
_kernelNames.insert_at(0x2B, "FGets");
|
||||
_kernelNames.insert_at(0x2C, "FClose");
|
||||
|
||||
// Function 0x55 is DoAvoider
|
||||
_kernelNames[0x55] = "DoAvoider";
|
||||
|
||||
// Cut off unused functions
|
||||
_kernelNames.resize(0x72);
|
||||
break;
|
||||
|
||||
case SCI_VERSION_01:
|
||||
// Multilingual SCI01 games have StrSplit as function 0x78
|
||||
_kernelNames[0x78] = "StrSplit";
|
||||
|
||||
// Cut off unused functions
|
||||
_kernelNames.resize(0x79);
|
||||
break;
|
||||
|
||||
case SCI_VERSION_1_LATE:
|
||||
_kernelNames[0x71] = "MoveCursor";
|
||||
break;
|
||||
|
||||
case SCI_VERSION_1_1:
|
||||
// In SCI1.1, kSetSynonyms is an empty function
|
||||
_kernelNames[0x26] = "Empty";
|
||||
|
||||
if (g_sci->getGameId() == GID_KQ6) {
|
||||
// In the Windows version of KQ6 CD, the empty kSetSynonyms
|
||||
// function has been replaced with kPortrait. In KQ6 Mac,
|
||||
// kPlayBack has been replaced by kShowMovie.
|
||||
if ((g_sci->getPlatform() == Common::kPlatformWindows) ||
|
||||
(g_sci->getPlatform() == Common::kPlatformDOS && g_sci->useHiresGraphics()))
|
||||
_kernelNames[0x26] = "Portrait";
|
||||
else if (g_sci->getPlatform() == Common::kPlatformMacintosh)
|
||||
_kernelNames[0x84] = "ShowMovie";
|
||||
} else if (g_sci->getGameId() == GID_QFG4DEMO) {
|
||||
_kernelNames[0x7b] = "RemapColors"; // QFG4 Demo has this SCI2 function instead of StrSplit
|
||||
} else if (g_sci->getGameId() == GID_SLATER && g_sci->getPlatform() == Common::kPlatformMacintosh) {
|
||||
// SLATER Macintosh has an empty kDoAudio. Scripts rely on this, as
|
||||
// they contain calls to play non-existent audio from the PC version.
|
||||
_kernelNames[0x75] = "Empty";
|
||||
} else if (_resMan->testResource(ResourceId(kResourceTypeVocab, 184))) {
|
||||
_kernelNames[0x7b] = "RemapColorsKawa";
|
||||
_kernelNames[0x88] = "KawaDbugStr";
|
||||
_kernelNames[0x89] = "KawaHacks";
|
||||
}
|
||||
|
||||
// EcoQuest 1 demo uses kGetMessage and kMoveCursor (SCI_VERSION_1_LATE)
|
||||
// instead of kMessage and kPalVary (SCI_VERSION_1_1).
|
||||
// Detect which functions to use from message resource version.
|
||||
if (features->detectMessageFunctionType() == SCI_VERSION_1_1) {
|
||||
_kernelNames[0x71] = "PalVary";
|
||||
_kernelNames[0x7c] = "Message";
|
||||
} else {
|
||||
_kernelNames[0x71] = "MoveCursor";
|
||||
}
|
||||
break;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
case SCI_VERSION_2:
|
||||
_kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesSci2);
|
||||
break;
|
||||
|
||||
case SCI_VERSION_2_1_EARLY:
|
||||
case SCI_VERSION_2_1_MIDDLE:
|
||||
case SCI_VERSION_2_1_LATE:
|
||||
if (features->detectSci21KernelType() == SCI_VERSION_2) {
|
||||
// Some early SCI2.1 games use a modified SCI2 kernel table instead of
|
||||
// the SCI2.1 kernel table. We detect which version to use based on
|
||||
// how kDoSound is called from Sound::play().
|
||||
// Known games that use this:
|
||||
// GK2 demo
|
||||
// KQ7 1.4/1.51
|
||||
// PQ:SWAT demo
|
||||
// LSL6
|
||||
// PQ4CD
|
||||
// QFG4CD
|
||||
|
||||
// This is interesting because they all have the same interpreter
|
||||
// version (2.100.002), yet they would not be compatible with other
|
||||
// games of the same interpreter.
|
||||
|
||||
_kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesGk2Demo);
|
||||
// OnMe is IsOnMe here, but they should be compatible
|
||||
_kernelNames[0x23] = g_sci->getGameId() == GID_LSL6HIRES ? "Empty" : "Robot"; // Graph in SCI2
|
||||
_kernelNames[0x2e] = "Priority"; // DisposeTextBitmap in SCI2
|
||||
} else {
|
||||
// Normal SCI2.1 kernel table
|
||||
_kernelNames = Common::StringArray(sci21_default_knames, kKernelEntriesSci21);
|
||||
}
|
||||
break;
|
||||
|
||||
case SCI_VERSION_3:
|
||||
_kernelNames = Common::StringArray(sci21_default_knames, kKernelEntriesSci3);
|
||||
|
||||
// In SCI3, some kernel functions have been removed, and others have been added
|
||||
_kernelNames[0x18] = "Dummy"; // AddMagnify in SCI2.1
|
||||
_kernelNames[0x19] = "Dummy"; // DeleteMagnify in SCI2.1
|
||||
_kernelNames[0x30] = "Dummy"; // SetScroll in SCI2.1
|
||||
_kernelNames[0x39] = "Dummy"; // ShowMovie in SCI2.1
|
||||
_kernelNames[0x4c] = "Dummy"; // ScrollWindow in SCI2.1
|
||||
_kernelNames[0x56] = "Dummy"; // VibrateMouse in SCI2.1 (only used in QFG4 floppy)
|
||||
_kernelNames[0x66] = "Dummy"; // MergePoly in SCI2.1
|
||||
_kernelNames[0x8d] = "MessageBox"; // Dummy in SCI2.1
|
||||
_kernelNames[0x9b] = "Minimize"; // Dummy in SCI2.1
|
||||
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
// Use default table for the other versions
|
||||
break;
|
||||
}
|
||||
|
||||
// Reserve a high range of kernel call IDs (0xe0 to 0xef) that can be used
|
||||
// by ScummVM to improve integration and fix bugs in games that require
|
||||
// more help than can be provided by a simple script patch (e.g. spinloops
|
||||
// in Hoyle5).
|
||||
// Using a new high range instead of just replacing dummied kernel calls in
|
||||
// the normal kernel range is intended to avoid any conflicts with fangames
|
||||
// that might try to add their own kernel calls in the same manner. It also
|
||||
// helps to separate ScummVM interpreter's kernel calls from SSCI's standard
|
||||
// kernel calls.
|
||||
uint maxKernelId = kScummVMSleepId;
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() >= SCI_VERSION_2) {
|
||||
maxKernelId = kScummVMSaveLoadId;
|
||||
}
|
||||
#endif
|
||||
const uint kernelListSize = _kernelNames.size();
|
||||
_kernelNames.resize(maxKernelId + 1);
|
||||
for (uint id = kernelListSize; id < kScummVMSleepId; ++id) {
|
||||
_kernelNames[id] = "Dummy";
|
||||
}
|
||||
|
||||
// Used by script patches to remove CPU spinning on kGetTime and add delays
|
||||
_kernelNames[kScummVMSleepId] = "ScummVMSleep";
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() >= SCI_VERSION_2) {
|
||||
// Used by GuestAdditions to support integrated save/load dialog
|
||||
_kernelNames[kScummVMSaveLoadId] = "ScummVMSaveLoad";
|
||||
}
|
||||
#endif
|
||||
|
||||
mapFunctions(features);
|
||||
}
|
||||
|
||||
Common::String Kernel::lookupText(reg_t address, int index) {
|
||||
if (address.getSegment())
|
||||
return _segMan->getString(address);
|
||||
|
||||
ResourceId resourceId = ResourceId(kResourceTypeText, address.getOffset());
|
||||
if (g_sci->getGameId() == GID_HOYLE3 && g_sci->getPlatform() == Common::kPlatformAmiga) {
|
||||
// WORKAROUND: In the Amiga version of Hoyle 3, texts are stored as
|
||||
// either text, font or palette types. Seems like the resource type
|
||||
// bits are used as part of the resource numbers. This is the same
|
||||
// as the workaround used in GfxFontFromResource()
|
||||
resourceId = ResourceId(kResourceTypeText, address.getOffset() & 0x7FF);
|
||||
if (!_resMan->testResource(resourceId))
|
||||
resourceId = ResourceId(kResourceTypeFont, address.getOffset() & 0x7FF);
|
||||
if (!_resMan->testResource(resourceId))
|
||||
resourceId = ResourceId(kResourceTypePalette, address.getOffset() & 0x7FF);
|
||||
}
|
||||
|
||||
Resource *textres = _resMan->findResource(resourceId, false);
|
||||
|
||||
if (!textres) {
|
||||
error("text.%03d not found", address.getOffset());
|
||||
}
|
||||
|
||||
int textlen = textres->size();
|
||||
const char *seeker = (const char *)textres->getUnsafeDataAt(0);
|
||||
|
||||
if (g_sci->getGameId() == GID_LONGBOW && address.getOffset() == 1535 && textlen == 2662) {
|
||||
// WORKAROUND: Longbow 1.0's text resource 1535 is missing 8 texts for
|
||||
// the pub. It appears that only the 5.25 floppy release was affected.
|
||||
// This was fixed by Sierra's 1.0 patch.
|
||||
if (index >= 41) {
|
||||
// texts 41+ exist but with incorrect offsets
|
||||
index -= 8;
|
||||
} else if (index >= 33) {
|
||||
// texts 33 through 40 are missing. they comprise two sequences of
|
||||
// four messages. only one of the two can play, and only once in
|
||||
// the specific circumstance that the player enters the pub as a
|
||||
// merchant, changes beards, and re-enters.
|
||||
return "** MISSING MESSAGE **";
|
||||
}
|
||||
}
|
||||
|
||||
int _index = index;
|
||||
while (index-- && textlen)
|
||||
while (textlen-- && *seeker++)
|
||||
;
|
||||
|
||||
if (textlen)
|
||||
return seeker;
|
||||
|
||||
warning("Index %d out of bounds in text.%03d", _index, address.getOffset());
|
||||
return "";
|
||||
}
|
||||
|
||||
// TODO: script_adjust_opcode_formats should probably be part of the
|
||||
// constructor (?) of a VirtualMachine or a ScriptManager class.
|
||||
void script_adjust_opcode_formats() {
|
||||
|
||||
g_sci->_opcode_formats = new opcode_format[128][4];
|
||||
memcpy(g_sci->_opcode_formats, g_base_opcode_formats, 128*4*sizeof(opcode_format));
|
||||
|
||||
if (g_sci->_features->detectLofsType() != SCI_VERSION_0_EARLY) {
|
||||
g_sci->_opcode_formats[op_lofsa][0] = Script_Offset;
|
||||
g_sci->_opcode_formats[op_lofss][0] = Script_Offset;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
// In SCI32, some arguments are now words instead of bytes
|
||||
if (getSciVersion() >= SCI_VERSION_2) {
|
||||
g_sci->_opcode_formats[op_calle][2] = Script_Word;
|
||||
g_sci->_opcode_formats[op_callk][1] = Script_Word;
|
||||
g_sci->_opcode_formats[op_super][1] = Script_Word;
|
||||
g_sci->_opcode_formats[op_send][0] = Script_Word;
|
||||
g_sci->_opcode_formats[op_self][0] = Script_Word;
|
||||
g_sci->_opcode_formats[op_call][1] = Script_Word;
|
||||
g_sci->_opcode_formats[op_callb][1] = Script_Word;
|
||||
}
|
||||
|
||||
if (getSciVersion() >= SCI_VERSION_3) {
|
||||
g_sci->_opcode_formats[op_info][0] = Script_None;
|
||||
g_sci->_opcode_formats[op_superP][0] = Script_None;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
758
engines/sci/engine/kernel.h
Normal file
758
engines/sci/engine/kernel.h
Normal file
@@ -0,0 +1,758 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_KERNEL_H
|
||||
#define SCI_ENGINE_KERNEL_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/rect.h"
|
||||
#include "common/str-array.h"
|
||||
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/vm_types.h" // for reg_t
|
||||
#include "sci/engine/vm.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
/**
|
||||
* @defgroup engine_sci SCI engine
|
||||
* @ingroup engines_engines
|
||||
* @{
|
||||
*/
|
||||
|
||||
struct Node; // from segment.h
|
||||
struct List; // from segment.h
|
||||
struct SelectorCache; // from selector.h
|
||||
struct SciWorkaroundEntry; // from workarounds.h
|
||||
|
||||
/**
|
||||
* @defgroup vocabulary_resources_sci Vocabulary resources in SCI
|
||||
* @ingroup engine_sci
|
||||
*
|
||||
* vocab.999 / 999.voc (unneeded) contains names of the kernel functions which
|
||||
* are implemented by the interpreter. In Sierra SCI, they are used exclusively
|
||||
* by the debugger, which is why keeping the file up to date was less important.
|
||||
* This resource is notoriously unreliable, and should not be used. Fortunately,
|
||||
* kernel names are the same in major SCI revisions, which is why we have them
|
||||
* hardcoded.
|
||||
*
|
||||
* vocab.998 / 998.voc (unneeded) contains opcode names. Opcodes have remained
|
||||
* the same from SCI0 to SCI2.1, and have changed in SCI3, so this is only used
|
||||
* on demand for debugging purposes, for showing the opcode names
|
||||
*
|
||||
* vocab.997 / 997.voc (usually needed) contains the names of every selector in
|
||||
* the class hierarchy. Each method and property '''name''' consumes one id, but
|
||||
* if a name is shared between classes, one id will do. Some demos do not contain
|
||||
* a selector vocabulary, but the selectors used by the engine have stayed more or
|
||||
* less static, so we add the selectors we need inside static_selectors.cpp
|
||||
* The SCI engine loads vocab.997 on startup, and fills in an internal structure
|
||||
* that allows interpreter code to access these selectors via #defined macros. It
|
||||
* does not use the file after this initial stage.
|
||||
*
|
||||
* vocab.996 / 996.voc (required) contains the classes which are used in each
|
||||
* script, and is required by the segment manager
|
||||
*
|
||||
* vocab.995 / 995.voc (unneeded) contains strings for the embedded SCI debugger
|
||||
*
|
||||
* vocab.994 / 994.voc (unneeded) contains offsets into certain classes of certain
|
||||
* properties. This enables the interpreter to update these properties in O(1) time,
|
||||
* which was important in the era when SCI was initially conceived. In SCI, we
|
||||
* figured out what '''property''' a certain offset refers to (which requires one to
|
||||
* guess what class a pointer points to) and then simply use the property name and
|
||||
* vocab.997. This results in much more readable code. Thus, this vocabulary isn't
|
||||
* used at all.
|
||||
*
|
||||
* 993.voc (unneeded) - Contains the SCI3 equivalent of vocab.994; like its predecessor,
|
||||
* the raw selector numbers can be deduced and used instead. In fact, one version of this
|
||||
* file has turned out to cover all versiona of SCI3.
|
||||
*
|
||||
* SCI0 parser vocabularies:
|
||||
* - vocab.901 / 901.voc - suffix vocabulary
|
||||
* - vocab.900 / 900.voc - parse tree branches
|
||||
* - vocab.0 / 0.voc - main vocabulary, containing words and their attributes
|
||||
* (e.g. "onto" - "position")
|
||||
*
|
||||
* SCI01 parser vocabularies:
|
||||
* - vocab.902 / 902.voc - suffix vocabulary
|
||||
* - vocab.901 / 901.voc - parse tree branches
|
||||
* - vocab.900 / 900.voc - main vocabulary, containing words and their attributes
|
||||
* (e.g. "onto" - "position")
|
||||
*
|
||||
*/
|
||||
//@{
|
||||
|
||||
//#define DEBUG_PARSER // enable for parser debugging
|
||||
|
||||
// ---- Kernel signatures -----------------------------------------------------
|
||||
|
||||
// Kernel functions that have been added for ScummVM script patches to call
|
||||
enum {
|
||||
kScummVMSleepId = 0xe0, // sleeps for a delay while remaining responsive
|
||||
#ifdef ENABLE_SCI32
|
||||
kScummVMSaveLoadId = 0xe1 // launches ScummVM's save/load dialog
|
||||
#endif
|
||||
};
|
||||
|
||||
// internal kernel signature data
|
||||
enum {
|
||||
SIG_TYPE_NULL = 0x01, // may be 0:0 [0]
|
||||
SIG_TYPE_INTEGER = 0x02, // may be 0:* [i], automatically also allows null
|
||||
SIG_TYPE_UNINITIALIZED = 0x04, // may be FFFF:* -> not allowable, only used for comparison
|
||||
SIG_TYPE_OBJECT = 0x08, // may be object [o]
|
||||
SIG_TYPE_REFERENCE = 0x10, // may be reference [r]
|
||||
SIG_TYPE_LIST = 0x20, // may be list [l]
|
||||
SIG_TYPE_NODE = 0x40, // may be node [n]
|
||||
SIG_TYPE_ERROR = 0x80, // happens, when there is a identification error - only used for comparison
|
||||
SIG_IS_INVALID = 0x100, // ptr is invalid [!] -> invalid offset
|
||||
SIG_IS_OPTIONAL = 0x200, // is optional
|
||||
SIG_NEEDS_MORE = 0x400, // needs at least one additional parameter following
|
||||
SIG_MORE_MAY_FOLLOW = 0x800 // may have more parameters of the same type following
|
||||
};
|
||||
|
||||
// this does not include SIG_TYPE_UNINITIALIZED, because we can not allow uninitialized values anywhere
|
||||
#define SIG_MAYBE_ANY (SIG_TYPE_NULL | SIG_TYPE_INTEGER | SIG_TYPE_OBJECT | SIG_TYPE_REFERENCE | SIG_TYPE_LIST | SIG_TYPE_NODE)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/* Generic description: */
|
||||
typedef reg_t KernelFunctionCall(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
struct KernelSubFunction {
|
||||
KernelFunctionCall *function;
|
||||
const char *name;
|
||||
uint16 *signature;
|
||||
const SciWorkaroundEntry *workarounds;
|
||||
bool debugLogging;
|
||||
bool debugBreakpoint;
|
||||
};
|
||||
|
||||
struct KernelFunction {
|
||||
KernelFunctionCall *function;
|
||||
const char *name;
|
||||
uint16 *signature;
|
||||
const SciWorkaroundEntry *workarounds;
|
||||
KernelSubFunction *subFunctions;
|
||||
uint16 subFunctionCount;
|
||||
};
|
||||
|
||||
class Kernel {
|
||||
public:
|
||||
/**
|
||||
* Initializes the SCI kernel.
|
||||
*/
|
||||
Kernel(ResourceManager *resMan, SegManager *segMan);
|
||||
~Kernel();
|
||||
|
||||
uint getSelectorNamesSize() const;
|
||||
const Common::String &getSelectorName(uint selector);
|
||||
int findKernelFuncPos(Common::String kernelFuncName);
|
||||
|
||||
uint getKernelNamesSize() const;
|
||||
const Common::String &getKernelName(uint number) const;
|
||||
Common::String getKernelName(uint number, uint subFunction) const;
|
||||
|
||||
/**
|
||||
* Determines the selector ID of a selector by its name.
|
||||
* @param selectorName Name of the selector to look up
|
||||
* @return The appropriate selector ID, or -1 on error
|
||||
*/
|
||||
int findSelector(const char *selectorName) const;
|
||||
|
||||
// Script dissection/dumping functions
|
||||
void dissectScript(int scriptNumber, Vocabulary *vocab);
|
||||
void dumpScriptObject(const SciSpan<const byte> &script, SciSpan<const byte> object);
|
||||
void dumpScriptClass(const SciSpan<const byte> &script, SciSpan<const byte> clazz);
|
||||
|
||||
SelectorCache _selectorCache; /**< Shortcut list for important selectors. */
|
||||
typedef Common::Array<KernelFunction> KernelFunctionArray;
|
||||
KernelFunctionArray _kernelFuncs; /**< Table of kernel functions. */
|
||||
|
||||
/**
|
||||
* Determines whether a list of registers matches a given signature.
|
||||
* If no signature is given (i.e., if sig is NULL), this is always
|
||||
* treated as a match.
|
||||
*
|
||||
* @param sig signature to test against
|
||||
* @param argc number of arguments to test
|
||||
* @param argv argument list
|
||||
* @return true if the signature was matched, false otherwise
|
||||
*/
|
||||
bool signatureMatch(const uint16 *sig, int argc, const reg_t *argv);
|
||||
|
||||
// Prints out debug information in case a signature check fails
|
||||
void signatureDebug(Common::String &signatureDetails, const uint16 *sig, int argc, const reg_t *argv);
|
||||
|
||||
/**
|
||||
* Determines the type of the object indicated by reg.
|
||||
* @param reg register to check
|
||||
* @return one of KSIG_* below KSIG_NULL.
|
||||
* KSIG_INVALID set if the type of reg can be determined, but is invalid.
|
||||
* 0 on error.
|
||||
*/
|
||||
uint16 findRegType(reg_t reg);
|
||||
|
||||
/******************** Text functionality ********************/
|
||||
/**
|
||||
* Looks up text referenced by scripts.
|
||||
* SCI uses two values to reference to text: An address, and an index. The
|
||||
* address determines whether the text should be read from a resource file,
|
||||
* or from the heap. If the text is read from a resource file, then the
|
||||
* index refers to the index of the string in the text resource.
|
||||
* If the index does not exist in the resource, an empty string is returned.
|
||||
*
|
||||
* @param address The address to look up
|
||||
* @param index The relative index
|
||||
* @return The referenced text, or empty string on error.
|
||||
*/
|
||||
Common::String lookupText(reg_t address, int index);
|
||||
|
||||
/**
|
||||
* Loads the kernel function names.
|
||||
*
|
||||
* This function reads the kernel function name table from resource_map,
|
||||
* and fills the _kernelNames array with them.
|
||||
* The resulting list has the same format regardless of the format of the
|
||||
* name table of the resource (the format changed between version 0 and 1).
|
||||
*/
|
||||
void loadKernelNames(GameFeatures *features);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Loads the kernel selector names.
|
||||
*/
|
||||
void loadSelectorNames();
|
||||
|
||||
/**
|
||||
* Check for any hardcoded selector table we might have that can be used
|
||||
* if a game is missing the selector names.
|
||||
*/
|
||||
Common::StringArray checkStaticSelectorNames();
|
||||
|
||||
/**
|
||||
* Automatically find specific selectors
|
||||
*/
|
||||
void findSpecificSelectors(Common::StringArray &selectorNames);
|
||||
|
||||
/**
|
||||
* Maps special selectors.
|
||||
*/
|
||||
void mapSelectors();
|
||||
|
||||
/**
|
||||
* Maps kernel functions.
|
||||
*/
|
||||
void mapFunctions(GameFeatures *features);
|
||||
|
||||
ResourceManager *_resMan;
|
||||
SegManager *_segMan;
|
||||
|
||||
// Kernel-related lists
|
||||
Common::StringArray _selectorNames;
|
||||
Common::StringArray _kernelNames;
|
||||
|
||||
const Common::String _invalid;
|
||||
};
|
||||
|
||||
/******************** Kernel functions ********************/
|
||||
|
||||
reg_t kStrLen(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetFarText(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kReadNumber(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStrCat(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStrCmp(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetSynonyms(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kLock(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalette(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVary(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAssertPalette(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPortrait(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kNumCels(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kNumLoops(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDrawCel(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCoordPri(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPriCoord(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShakeScreen(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetCursor(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMoveCursor(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPicNotValid(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kOnControl(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDrawPic(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetPort(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetPort(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kNewWindow(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDisposeWindow(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCelWide(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCelHigh(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetJump(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDirLoop(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAvoider(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetAngle(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetDistance(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRandom(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAbs(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSqrt(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kTimesSin(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kTimesCos(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCosMult(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSinMult(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kTimesTan(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kTimesCot(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCosDiv(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSinDiv(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kValidPath(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kWait(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRestartGame16(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDeviceInfo(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetEvent(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFlushResources(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetDebug(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSaveGame(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIO(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetTime(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kHaveMouse(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kJoystick(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGameIsRestarting(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetCWD(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSort(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStrEnd(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMemory(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAvoidPath(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kParse(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSaid(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStrCpy(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStrAt(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kEditControl(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDrawControl(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kHiliteControl(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kClone(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDisposeClone(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCanBeHere(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCantBeHere(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetNowSeen(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kInitBresen(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoBresen(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kBaseSetter(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAddToPic(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAnimate(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDisplay(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGraph(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFormat(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSound(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAddMenu(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetMenu(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetMenu(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDrawStatus(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDrawMenuBar(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMenuSelect(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kLoad(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kUnLoad(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScriptID(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDisposeScript(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kIsObject(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRespondsTo(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kNewList(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDisposeList(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kNewNode(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFirstNode(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kLastNode(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kEmptyList(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kNextNode(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPrevNode(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kNodeValue(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAddAfter(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAddToFront(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAddToEnd(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFindKey(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDeleteKey(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMemoryInfo(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetSaveDir(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kTextSize(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kIsItSkip(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetMessage(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMessage(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudio(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSync(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMemorySegment(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kIntersections(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMergePoly(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kResCheck(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetQuitStr(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShowMovie(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetVideoMode(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStrSplit(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlatform(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kTextColors(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kTextFonts(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShow(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRemapColors(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRemapColorsKawa(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDummy(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kEmpty(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStub(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStubNull(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kKawaHacks(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kKawaDbugStr(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
// SCI2 Kernel Functions
|
||||
reg_t kSetCursor32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetNowSeen32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShakeScreen32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlatform32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioDistort(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioPan(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoAudioPanOff(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kRobot(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRobotGetIsInitialized(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRobotClose(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRobotPause(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDOpen(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDInit(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDIgnorePalettes(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDStartBlob(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDStopBlobs(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDAddBlob(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDDeleteBlob(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayVMDSetPlane(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kSave(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kListAt(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kArray(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kArrayNew(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kArrayGetSize(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kArrayGetElement(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kArraySetElements(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kArrayFree(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kArrayFill(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kArrayCopy(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kArrayCompare(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kArrayDuplicate(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kArrayGetData(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kArrayByteCopy(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kString(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringNew(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringGetChar(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringFree(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringCompare(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringLength(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringFormat(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringFormatAt(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringToInteger(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringTrim(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringToUpperCase(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringToLowerCase(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringReplaceSubstring(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kStringReplaceSubstringEx(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowPageUp(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowPageDown(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowUpArrow(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowDownArrow(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowHome(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowEnd(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowGo(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowModify(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowHide(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kMulDiv(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kBitmap(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kBitmapDrawColor(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kBitmapSetOrigin(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kBitmapGetInfo(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kAddPlane(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMovePlaneItems(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFrameOut(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCelHigh32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCelWide32(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv); // kOnMe for SCI2, kIsOnMe for SCI2.1
|
||||
reg_t kInPolygon(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kListIndexOf(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kListSort(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kEditText(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kInputText(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetScroll(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kPaletteSetFromResource32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPaletteFindColor32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kPalCycle(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalCycleSetCycle(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalCycleDoCycle(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalCyclePause(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalCycleOn(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalCycleOff(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVarySetPercent(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVaryOff(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVaryMergeTarget(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVarySetTime(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVarySetTarget(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVarySetStart(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVaryMergeStart(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
// SCI2.1 Kernel Functions
|
||||
reg_t kMorphOn(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kText(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kTextSize32(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kTextWidth(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kList(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCD(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCheckCD(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAddBefore(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMoveToFront(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMoveToEnd(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kWinHelp(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kMessageBox(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetConfig(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPrintDebug(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kCelInfo(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCelInfoGetOriginX(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCelInfoGetOriginY(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCelInfoGetPixel(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCelLink(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCelLinkGetX(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kCelLinkGetY(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPointSize(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFont(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kAddLine(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kWinDLL(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
// SCI 2.1 Middle Mac modified kDoSound
|
||||
reg_t kDoSoundMac32(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
// SCI3 Kernel functions
|
||||
reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayDuckPlay(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayDuckSetFrameOut(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayDuckOpen(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayDuckClose(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPlayDuckSetVolume(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kWebConnect(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kWinExec(EngineState *s, int argc, reg_t *argv);
|
||||
#endif
|
||||
|
||||
reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundPlay(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundDispose(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundMute(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundStop(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundStopAll(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundPause(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundResumeAfterRestore(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundMasterVolume(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundUpdate(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundFade(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundGetPolyphony(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSetHold(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSuspend(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSetPriority(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSetLoop(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kGraphGetColorCount(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGraphDrawLine(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGraphSaveBox(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGraphRestoreBox(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGraphFillBoxBackground(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGraphFillBoxForeground(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGraphFillBoxAny(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGraphUpdateBox(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGraphRedrawBox(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGraphAdjustPriority(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kGraphSaveUpscaledHiresBox(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kPalVaryInit(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVaryReverse(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVaryGetCurrentStep(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVaryDeinit(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVaryChangeTarget(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVaryChangeTicks(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPalVaryPauseResume(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kPaletteSetFromResource(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPaletteSetFlag(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPaletteUnsetFlag(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPaletteSetIntensity(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPaletteFindColor(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPaletteAnimate(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPaletteSave(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kPaletteRestore(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOReadString(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOFindFirst(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOFindNext(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIORename(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOCopy(EngineState *s, int argc, reg_t *argv);
|
||||
#ifdef ENABLE_SCI32
|
||||
reg_t kFileIOReadByte(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOGetCWD(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kFileIOIsValidDirectory(EngineState *s, int argc, reg_t *argv);
|
||||
#endif
|
||||
|
||||
// Custom ScummVM kernel functions
|
||||
reg_t kScummVMSleep(EngineState *s, int argc, reg_t *argv);
|
||||
#ifdef ENABLE_SCI32
|
||||
reg_t kScummVMSaveLoad(EngineState *s, int argc, reg_t *argv);
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_KERNEL_H
|
||||
1611
engines/sci/engine/kernel_tables.h
Normal file
1611
engines/sci/engine/kernel_tables.h
Normal file
File diff suppressed because it is too large
Load Diff
444
engines/sci/engine/kevent.cpp
Normal file
444
engines/sci/engine/kevent.cpp
Normal file
@@ -0,0 +1,444 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/system.h"
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/guest_additions.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/savegame.h"
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/console.h"
|
||||
#include "sci/debug.h" // for g_debug_simulated_key
|
||||
#include "sci/event.h"
|
||||
#include "sci/graphics/animate.h"
|
||||
#include "sci/graphics/coordadjuster.h"
|
||||
#include "sci/graphics/cursor.h"
|
||||
#include "sci/graphics/maciconbar.h"
|
||||
#ifdef ENABLE_SCI32
|
||||
#include "sci/graphics/frameout.h"
|
||||
#endif
|
||||
|
||||
namespace Sci {
|
||||
|
||||
reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
|
||||
SciEventType mask = (SciEventType)argv[0].toUint16();
|
||||
reg_t obj = argv[1];
|
||||
SciEvent curEvent;
|
||||
uint16 modifiers = 0;
|
||||
SegManager *segMan = s->_segMan;
|
||||
Common::Point mousePos;
|
||||
|
||||
// If there's a simkey pending, and the game wants a keyboard event, use the
|
||||
// simkey instead of a normal event
|
||||
// TODO: This does not really work as expected for keyup events, since the
|
||||
// fake event is disposed halfway through the normal event lifecycle.
|
||||
if (g_debug_simulated_key && (mask & kSciEventKeyDown)) {
|
||||
// In case we use a simulated event we query the current mouse position
|
||||
mousePos = g_sci->_gfxCursor->getPosition();
|
||||
|
||||
// Limit the mouse cursor position, if necessary
|
||||
g_sci->_gfxCursor->refreshPosition();
|
||||
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), kSciEventKeyDown);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), g_debug_simulated_key);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(modifiers), kSciKeyModNumLock);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(x), mousePos.x);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(y), mousePos.y);
|
||||
g_debug_simulated_key = 0;
|
||||
return TRUE_REG;
|
||||
}
|
||||
|
||||
curEvent = g_sci->getEventManager()->getSciEvent(mask);
|
||||
|
||||
// For Mac games with an icon bar, handle possible icon bar events first
|
||||
if (g_sci->hasMacIconBar()) {
|
||||
reg_t iconObj = NULL_REG;
|
||||
if (g_sci->_gfxMacIconBar->handleEvents(curEvent, iconObj)) {
|
||||
if (!iconObj.isNull()) {
|
||||
invokeSelector(s, iconObj, SELECTOR(select), argc, argv, 0, nullptr);
|
||||
}
|
||||
|
||||
// The mouse press event was handled by the mac icon bar so change
|
||||
// its type to none so that generic event processing can continue
|
||||
// without the mouse press being handled twice
|
||||
curEvent.type = kSciEventNone;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_sci->_guestAdditions->kGetEventHook()) {
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
// For a real event we use its associated mouse position
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() >= SCI_VERSION_2) {
|
||||
mousePos = curEvent.mousePosSci;
|
||||
|
||||
// Some games, like LSL6hires (when interacting with the menu bar) and
|
||||
// Phant2 (when on the "click mouse" screen after restoring a game),
|
||||
// have unthrottled loops that call kGetEvent but do not call kFrameOut.
|
||||
// In these cases we still need to call OSystem::updateScreen to update
|
||||
// the mouse cursor (in SSCI this was not necessary because mouse
|
||||
// updates were made directly to hardware from an interrupt handler),
|
||||
// and we need to throttle these calls so the game does not use 100%
|
||||
// CPU.
|
||||
// This situation seems to be detectable by looking at how many times
|
||||
// kGetEvent has been called between calls to kFrameOut. During normal
|
||||
// game operation, there are usually just 0 or 1 kGetEvent calls between
|
||||
// kFrameOut calls; any more than that indicates that we are probably in
|
||||
// one of these ugly loops and should be updating the screen &
|
||||
// throttling the VM.
|
||||
if (++s->_eventCounter > 2) {
|
||||
g_sci->_gfxFrameout->updateScreen();
|
||||
s->speedThrottler(10); // 10ms is an arbitrary value
|
||||
s->_throttleTrigger = true;
|
||||
}
|
||||
} else {
|
||||
#endif
|
||||
mousePos = curEvent.mousePos;
|
||||
// Limit the mouse cursor position, if necessary
|
||||
g_sci->_gfxCursor->refreshPosition();
|
||||
#ifdef ENABLE_SCI32
|
||||
}
|
||||
#endif
|
||||
|
||||
if (g_sci->getVocabulary())
|
||||
g_sci->getVocabulary()->parser_event = NULL_REG; // Invalidate parser event
|
||||
|
||||
if (s->_cursorWorkaroundActive) {
|
||||
// We check if the actual cursor position is inside specific rectangles
|
||||
// where the cursor itself should be moved to. If this is the case, we
|
||||
// set the mouse cursor's position to be within the rectangle in
|
||||
// question. Check GfxCursor::setPosition(), for a more detailed
|
||||
// explanation and a list of cursor position workarounds.
|
||||
if (s->_cursorWorkaroundRect.contains(mousePos.x, mousePos.y)) {
|
||||
// For OpenPandora and possibly other platforms, that support analog-stick control + touch screen
|
||||
// control at the same time: in case the cursor is currently at the coordinate set by the scripts,
|
||||
// we will count down instead of immediately disabling the workaround.
|
||||
// On OpenPandora the cursor position is set, but it's overwritten shortly afterwards by the
|
||||
// touch screen. In this case we would sometimes disable the workaround, simply because the touch
|
||||
// screen hasn't yet overwritten the position and thus the workaround would not work anymore.
|
||||
// On OpenPandora it would sometimes work and sometimes not without this.
|
||||
if (s->_cursorWorkaroundPoint == mousePos) {
|
||||
// Cursor is still at the same spot as set by the scripts
|
||||
if (s->_cursorWorkaroundPosCount > 0) {
|
||||
s->_cursorWorkaroundPosCount--;
|
||||
} else {
|
||||
// Was for quite a bit of time at that spot, so disable workaround now
|
||||
s->_cursorWorkaroundActive = false;
|
||||
}
|
||||
} else {
|
||||
// Cursor has moved, but is within the rect -> disable workaround immediately
|
||||
s->_cursorWorkaroundActive = false;
|
||||
}
|
||||
} else {
|
||||
mousePos.x = s->_cursorWorkaroundPoint.x;
|
||||
mousePos.y = s->_cursorWorkaroundPoint.y;
|
||||
}
|
||||
}
|
||||
|
||||
writeSelectorValue(segMan, obj, SELECTOR(x), mousePos.x);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(y), mousePos.y);
|
||||
|
||||
// Get current keyboard modifiers, only keep relevant bits
|
||||
const int modifierMask = getSciVersion() <= SCI_VERSION_01 ? kSciKeyModAll : kSciKeyModNonSticky;
|
||||
modifiers = curEvent.modifiers & modifierMask;
|
||||
if (g_sci->getPlatform() == Common::kPlatformDOS && getSciVersion() <= SCI_VERSION_01) {
|
||||
// We are supposed to emulate SCI running in DOS
|
||||
|
||||
// We set the higher byte of the modifiers to 02h
|
||||
// Original SCI also did that indirectly, because it asked BIOS for shift status
|
||||
// via AH=0x02 INT16, which then sets the shift flags in AL
|
||||
// AH is supposed to be destroyed in that case and it's not defined that 0x02
|
||||
// is still in it on return. The value of AX was then set into the modifiers selector.
|
||||
// At least one fan-made game (Betrayed Alliance) requires 0x02 to be in the upper byte,
|
||||
// otherwise the darts game (script 111) will not work properly.
|
||||
|
||||
// It seems Sierra fixed this behaviour (effectively bug) in the SCI1 keyboard driver.
|
||||
// SCI32 also resets the upper byte.
|
||||
|
||||
// This was verified in SSCI itself by creating a SCI game and checking behavior.
|
||||
modifiers |= 0x0200;
|
||||
}
|
||||
|
||||
switch (curEvent.type) {
|
||||
case kSciEventQuit:
|
||||
s->abortScriptProcessing = kAbortQuitGame; // Terminate VM
|
||||
g_sci->_debugState.seeking = kDebugSeekNothing;
|
||||
g_sci->_debugState.runningStep = 0;
|
||||
break;
|
||||
|
||||
case kSciEventKeyDown:
|
||||
case kSciEventKeyUp:
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), curEvent.type);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), curEvent.character);
|
||||
// We only care about the translated character
|
||||
writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers);
|
||||
s->r_acc = TRUE_REG;
|
||||
break;
|
||||
|
||||
case kSciEventMouseRelease:
|
||||
case kSciEventMousePress:
|
||||
// track left buttton clicks, if requested
|
||||
if (curEvent.type == kSciEventMousePress && curEvent.modifiers == 0 && g_debug_track_mouse_clicks) {
|
||||
g_sci->getSciDebugger()->debugPrintf("Mouse clicked at %d, %d\n",
|
||||
mousePos.x, mousePos.y);
|
||||
}
|
||||
|
||||
if (mask & curEvent.type) {
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), curEvent.type);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), 0);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers);
|
||||
s->r_acc = TRUE_REG;
|
||||
}
|
||||
break;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
case kSciEventHotRectangle:
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), curEvent.type);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), curEvent.hotRectangleIndex);
|
||||
s->r_acc = TRUE_REG;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
// Return a null event
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), kSciEventNone);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), 0);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers);
|
||||
s->r_acc = NULL_REG;
|
||||
}
|
||||
|
||||
if ((s->r_acc.getOffset()) && (g_sci->_debugState.stopOnEvent)) {
|
||||
g_sci->_debugState.stopOnEvent = false;
|
||||
|
||||
// A SCI event occurred, and we have been asked to stop, so open the debug console
|
||||
Console *con = g_sci->getSciDebugger();
|
||||
con->debugPrintf("SCI event occurred: ");
|
||||
switch (curEvent.type) {
|
||||
case kSciEventQuit:
|
||||
con->debugPrintf("quit event\n");
|
||||
break;
|
||||
case kSciEventKeyDown:
|
||||
case kSciEventKeyUp:
|
||||
con->debugPrintf("keyboard event\n");
|
||||
break;
|
||||
case kSciEventMousePress:
|
||||
case kSciEventMouseRelease:
|
||||
con->debugPrintf("mouse click event\n");
|
||||
break;
|
||||
default:
|
||||
con->debugPrintf("unknown or no event (event type %d)\n", curEvent.type);
|
||||
}
|
||||
|
||||
con->attach();
|
||||
con->onFrame();
|
||||
}
|
||||
|
||||
if (g_sci->_features->detectDoSoundType() <= SCI_VERSION_0_LATE) {
|
||||
// If we're running a sound-SCI0 game, update the sound cues, to
|
||||
// compensate for the fact that sound-SCI0 does not poll to update
|
||||
// the sound cues itself, like sound-SCI1 and later do with
|
||||
// cmdUpdateSoundCues. kGetEvent is called quite often, so emulate
|
||||
// the sound-SCI1 behavior of cmdUpdateSoundCues with this call
|
||||
g_sci->_soundCmd->updateSci0Cues();
|
||||
}
|
||||
|
||||
// If we're in a SCI16 unthrottled inner loop then delay a bit.
|
||||
// This prevents the CPU from maxing out and prevents inner loop animations
|
||||
// from running too fast. "Fast cast" games poll kGetEvent from an inner
|
||||
// loop while they display or say a message. This can be detected by testing
|
||||
// the fast cast global. Other inner loops can be detected by counting the
|
||||
// kGetEvent calls since the last kGameIsRestarting(0) or kWait call.
|
||||
// For example, some versions of Dialog:doit poll without calling kWait(1).
|
||||
// See above for similar SCI32 code that counts calls between kFrameout.
|
||||
// Fixes bugs #5091, #5326, #14020
|
||||
if (getSciVersion() <= SCI_VERSION_1_1) {
|
||||
if (++s->_eventCounter > 2 ||
|
||||
(g_sci->_gfxAnimate->isFastCastEnabled() &&
|
||||
!s->variables[VAR_GLOBAL][kGlobalVarFastCast].isNull())) {
|
||||
g_system->delayMillis(10);
|
||||
}
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
struct KeyDirMapping {
|
||||
SciKeyCode key;
|
||||
uint16 direction;
|
||||
};
|
||||
|
||||
const KeyDirMapping keyToDirMap[] = {
|
||||
{ kSciKeyHome, 8 }, { kSciKeyUp, 1 }, { kSciKeyPageUp, 2 },
|
||||
{ kSciKeyLeft, 7 }, { kSciKeyCenter, 0 }, { kSciKeyRight, 3 },
|
||||
{ kSciKeyEnd, 6 }, { kSciKeyDown, 5 }, { kSciKeyPageDown, 4 },
|
||||
};
|
||||
|
||||
reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t obj = argv[0];
|
||||
SegManager *segMan = s->_segMan;
|
||||
|
||||
if (readSelectorValue(segMan, obj, SELECTOR(type)) == kSciEventKeyDown) {
|
||||
uint16 message = readSelectorValue(segMan, obj, SELECTOR(message));
|
||||
#ifdef ENABLE_SCI32
|
||||
SciEventType eventType = (getSciVersion() < SCI_VERSION_2) ? kSciEventDirection16 : kSciEventDirection32;
|
||||
#else
|
||||
SciEventType eventType = kSciEventDirection16;
|
||||
#endif
|
||||
// It seems with SCI1 Sierra started to add the kSciEventDirection bit instead of setting it directly.
|
||||
// It was done inside the keyboard driver and is required for the PseudoMouse functionality and class
|
||||
// to work (script 933).
|
||||
if (g_sci->_features->detectPseudoMouseAbility() == kPseudoMouseAbilityTrue) {
|
||||
eventType |= kSciEventKeyDown;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(keyToDirMap); i++) {
|
||||
if (keyToDirMap[i].key == message) {
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), eventType);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), keyToDirMap[i].direction);
|
||||
return TRUE_REG; // direction mapped
|
||||
}
|
||||
}
|
||||
|
||||
return NULL_REG; // unknown direction
|
||||
}
|
||||
|
||||
return s->r_acc; // no keyboard event to map, leave accumulator unchanged
|
||||
}
|
||||
|
||||
reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t obj = argv[0];
|
||||
SegManager *segMan = s->_segMan;
|
||||
|
||||
if (obj.getSegment()) {
|
||||
int16 x = readSelectorValue(segMan, obj, SELECTOR(x));
|
||||
int16 y = readSelectorValue(segMan, obj, SELECTOR(y));
|
||||
|
||||
g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y);
|
||||
|
||||
writeSelectorValue(segMan, obj, SELECTOR(x), x);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(y), y);
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
|
||||
}
|
||||
|
||||
reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t obj = argv[0];
|
||||
SegManager *segMan = s->_segMan;
|
||||
|
||||
if (obj.getSegment()) {
|
||||
int16 x = readSelectorValue(segMan, obj, SELECTOR(x));
|
||||
int16 y = readSelectorValue(segMan, obj, SELECTOR(y));
|
||||
|
||||
g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y);
|
||||
|
||||
writeSelectorValue(segMan, obj, SELECTOR(x), x);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(y), y);
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kJoystick(EngineState *s, int argc, reg_t *argv) {
|
||||
// Subfunction 12 sets/gets joystick repeat rate
|
||||
debug(5, "Unimplemented syscall 'Joystick()'");
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv) {
|
||||
const reg_t result = argv[0];
|
||||
const reg_t planeObj = argv[1];
|
||||
|
||||
bool visible = true;
|
||||
Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
|
||||
if (plane == nullptr) {
|
||||
plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
|
||||
visible = false;
|
||||
}
|
||||
if (plane == nullptr) {
|
||||
error("kGlobalToLocal: Plane %04x:%04x not found", PRINT_REG(planeObj));
|
||||
}
|
||||
|
||||
const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) - plane->_gameRect.left;
|
||||
const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) - plane->_gameRect.top;
|
||||
|
||||
writeSelectorValue(s->_segMan, result, SELECTOR(x), x);
|
||||
writeSelectorValue(s->_segMan, result, SELECTOR(y), y);
|
||||
|
||||
return make_reg(0, visible);
|
||||
}
|
||||
|
||||
reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv) {
|
||||
const reg_t result = argv[0];
|
||||
const reg_t planeObj = argv[1];
|
||||
|
||||
bool visible = true;
|
||||
Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
|
||||
if (plane == nullptr) {
|
||||
plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
|
||||
visible = false;
|
||||
}
|
||||
if (plane == nullptr) {
|
||||
error("kLocalToGlobal: Plane %04x:%04x not found", PRINT_REG(planeObj));
|
||||
}
|
||||
|
||||
const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) + plane->_gameRect.left;
|
||||
const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) + plane->_gameRect.top;
|
||||
|
||||
writeSelectorValue(s->_segMan, result, SELECTOR(x), x);
|
||||
writeSelectorValue(s->_segMan, result, SELECTOR(y), y);
|
||||
|
||||
return make_reg(0, visible);
|
||||
}
|
||||
|
||||
reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argc == 1) {
|
||||
g_sci->getEventManager()->setHotRectanglesActive((bool)argv[0].toUint16());
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
const int16 numRects = argv[0].toSint16();
|
||||
SciArray &hotRects = *s->_segMan->lookupArray(argv[1]);
|
||||
|
||||
Common::Array<Common::Rect> rects;
|
||||
rects.resize(numRects);
|
||||
|
||||
for (int16 i = 0; i < numRects; ++i) {
|
||||
rects[i].left = hotRects.getAsInt16(i * 4);
|
||||
rects[i].top = hotRects.getAsInt16(i * 4 + 1);
|
||||
rects[i].right = hotRects.getAsInt16(i * 4 + 2) + 1;
|
||||
rects[i].bottom = hotRects.getAsInt16(i * 4 + 3) + 1;
|
||||
}
|
||||
|
||||
g_sci->getEventManager()->setHotRectanglesActive(true);
|
||||
g_sci->getEventManager()->setHotRectangles(rects);
|
||||
return s->r_acc;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
1593
engines/sci/engine/kfile.cpp
Normal file
1593
engines/sci/engine/kfile.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1392
engines/sci/engine/kgraphics.cpp
Normal file
1392
engines/sci/engine/kgraphics.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1139
engines/sci/engine/kgraphics32.cpp
Normal file
1139
engines/sci/engine/kgraphics32.cpp
Normal file
File diff suppressed because it is too large
Load Diff
926
engines/sci/engine/klists.cpp
Normal file
926
engines/sci/engine/klists.cpp
Normal file
@@ -0,0 +1,926 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
|
||||
namespace Sci {
|
||||
//#define CHECK_LISTS // adds sanity checking for lists and errors out when problems are found
|
||||
|
||||
#ifdef CHECK_LISTS
|
||||
|
||||
static bool isSaneNodePointer(SegManager *segMan, reg_t addr) {
|
||||
bool havePrev = false;
|
||||
reg_t prev = addr;
|
||||
|
||||
do {
|
||||
Node *node = segMan->lookupNode(addr, false);
|
||||
|
||||
if (!node) {
|
||||
if ((g_sci->getGameId() == GID_ICEMAN) && (g_sci->getEngineState()->currentRoomNumber() == 40)) {
|
||||
// ICEMAN: when plotting course, unDrawLast is called by startPlot::changeState
|
||||
// there is no previous entry so we get 0 in here
|
||||
} else if ((g_sci->getGameId() == GID_HOYLE1) && (g_sci->getEngineState()->currentRoomNumber() == 3)) {
|
||||
// HOYLE1: after sorting cards in hearts, in the next round
|
||||
// we get an invalid node - bug #5109
|
||||
} else {
|
||||
error("isSaneNodePointer: Node at %04x:%04x wasn't found", PRINT_REG(addr));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (havePrev && node->pred != prev) {
|
||||
error("isSaneNodePointer: Node at %04x:%04x points to invalid predecessor %04x:%04x (should be %04x:%04x)",
|
||||
PRINT_REG(addr), PRINT_REG(node->pred), PRINT_REG(prev));
|
||||
|
||||
//node->pred = prev; // fix the problem in the node
|
||||
return false;
|
||||
}
|
||||
|
||||
prev = addr;
|
||||
addr = node->succ;
|
||||
havePrev = true;
|
||||
} while (!addr.isNull());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void checkListPointer(SegManager *segMan, reg_t addr) {
|
||||
List *list = segMan->lookupList(addr);
|
||||
|
||||
if (!list) {
|
||||
error("checkListPointer (list %04x:%04x): The requested list wasn't found",
|
||||
PRINT_REG(addr));
|
||||
return;
|
||||
}
|
||||
|
||||
if (list->first.isNull() && list->last.isNull()) {
|
||||
// Empty list is fine
|
||||
} else if (!list->first.isNull() && !list->last.isNull()) {
|
||||
// Normal list
|
||||
Node *node_a = segMan->lookupNode(list->first, false);
|
||||
Node *node_z = segMan->lookupNode(list->last, false);
|
||||
|
||||
if (!node_a) {
|
||||
error("checkListPointer (list %04x:%04x): missing first node", PRINT_REG(addr));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node_z) {
|
||||
error("checkListPointer (list %04x:%04x): missing last node", PRINT_REG(addr));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node_a->pred.isNull()) {
|
||||
error("checkListPointer (list %04x:%04x): First node of the list points to a predecessor node",
|
||||
PRINT_REG(addr));
|
||||
|
||||
//node_a->pred = NULL_REG; // fix the problem in the node
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node_z->succ.isNull()) {
|
||||
error("checkListPointer (list %04x:%04x): Last node of the list points to a successor node",
|
||||
PRINT_REG(addr));
|
||||
|
||||
//node_z->succ = NULL_REG; // fix the problem in the node
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
isSaneNodePointer(segMan, list->first);
|
||||
} else {
|
||||
// Not sane list... it's missing pointers to the first or last element
|
||||
if (list->first.isNull())
|
||||
error("checkListPointer (list %04x:%04x): missing pointer to first element",
|
||||
PRINT_REG(addr));
|
||||
if (list->last.isNull())
|
||||
error("checkListPointer (list %04x:%04x): missing pointer to last element",
|
||||
PRINT_REG(addr));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
reg_t kNewList(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t listRef;
|
||||
List *list = s->_segMan->allocateList(&listRef);
|
||||
list->first = list->last = NULL_REG;
|
||||
debugC(kDebugLevelNodes, "New listRef at %04x:%04x", PRINT_REG(listRef));
|
||||
|
||||
return listRef; // Return list base address
|
||||
}
|
||||
|
||||
reg_t kDisposeList(EngineState *s, int argc, reg_t *argv) {
|
||||
// This function is not needed in ScummVM. The garbage collector
|
||||
// cleans up unused objects automatically
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kNewNode(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t nodeValue = argv[0];
|
||||
// Some SCI32 games call this with 1 parameter (e.g. the demo of Phantasmagoria).
|
||||
// Set the key to be the same as the value in this case
|
||||
reg_t nodeKey = (argc == 2) ? argv[1] : argv[0];
|
||||
s->r_acc = s->_segMan->newNode(nodeValue, nodeKey);
|
||||
|
||||
debugC(kDebugLevelNodes, "New nodeRef at %04x:%04x", PRINT_REG(s->r_acc));
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kFirstNode(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argv[0].isNull())
|
||||
return NULL_REG;
|
||||
|
||||
List *list = s->_segMan->lookupList(argv[0]);
|
||||
|
||||
if (list) {
|
||||
#ifdef CHECK_LISTS
|
||||
checkListPointer(s->_segMan, argv[0]);
|
||||
#endif
|
||||
return list->first;
|
||||
} else {
|
||||
return NULL_REG;
|
||||
}
|
||||
}
|
||||
|
||||
reg_t kLastNode(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argv[0].isNull())
|
||||
return NULL_REG;
|
||||
|
||||
List *list = s->_segMan->lookupList(argv[0]);
|
||||
|
||||
if (list) {
|
||||
#ifdef CHECK_LISTS
|
||||
checkListPointer(s->_segMan, argv[0]);
|
||||
#endif
|
||||
return list->last;
|
||||
} else {
|
||||
return NULL_REG;
|
||||
}
|
||||
}
|
||||
|
||||
reg_t kEmptyList(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argv[0].isNull())
|
||||
return NULL_REG;
|
||||
|
||||
List *list = s->_segMan->lookupList(argv[0]);
|
||||
#ifdef CHECK_LISTS
|
||||
checkListPointer(s->_segMan, argv[0]);
|
||||
#endif
|
||||
return make_reg(0, ((list) ? list->first.isNull() : 0));
|
||||
}
|
||||
|
||||
static void addToFront(EngineState *s, reg_t listRef, reg_t nodeRef) {
|
||||
List *list = s->_segMan->lookupList(listRef);
|
||||
Node *newNode = s->_segMan->lookupNode(nodeRef);
|
||||
|
||||
debugC(kDebugLevelNodes, "Adding node %04x:%04x to end of list %04x:%04x", PRINT_REG(nodeRef), PRINT_REG(listRef));
|
||||
|
||||
if (!newNode)
|
||||
error("Attempt to add non-node (%04x:%04x) to list at %04x:%04x", PRINT_REG(nodeRef), PRINT_REG(listRef));
|
||||
|
||||
#ifdef CHECK_LISTS
|
||||
checkListPointer(s->_segMan, listRef);
|
||||
#endif
|
||||
|
||||
newNode->pred = NULL_REG;
|
||||
newNode->succ = list->first;
|
||||
|
||||
// Set node to be the first and last node if it's the only node of the list
|
||||
if (list->first.isNull())
|
||||
list->last = nodeRef;
|
||||
else {
|
||||
Node *oldNode = s->_segMan->lookupNode(list->first);
|
||||
oldNode->pred = nodeRef;
|
||||
}
|
||||
list->first = nodeRef;
|
||||
}
|
||||
|
||||
static void addToEnd(EngineState *s, reg_t listRef, reg_t nodeRef) {
|
||||
List *list = s->_segMan->lookupList(listRef);
|
||||
Node *newNode = s->_segMan->lookupNode(nodeRef);
|
||||
|
||||
debugC(kDebugLevelNodes, "Adding node %04x:%04x to end of list %04x:%04x", PRINT_REG(nodeRef), PRINT_REG(listRef));
|
||||
|
||||
if (!newNode)
|
||||
error("Attempt to add non-node (%04x:%04x) to list at %04x:%04x", PRINT_REG(nodeRef), PRINT_REG(listRef));
|
||||
|
||||
#ifdef CHECK_LISTS
|
||||
checkListPointer(s->_segMan, listRef);
|
||||
#endif
|
||||
|
||||
newNode->pred = list->last;
|
||||
newNode->succ = NULL_REG;
|
||||
|
||||
// Set node to be the first and last node if it's the only node of the list
|
||||
if (list->last.isNull())
|
||||
list->first = nodeRef;
|
||||
else {
|
||||
Node *old_n = s->_segMan->lookupNode(list->last);
|
||||
old_n->succ = nodeRef;
|
||||
}
|
||||
list->last = nodeRef;
|
||||
}
|
||||
|
||||
reg_t kNextNode(EngineState *s, int argc, reg_t *argv) {
|
||||
Node *n = s->_segMan->lookupNode(argv[0]);
|
||||
|
||||
#ifdef CHECK_LISTS
|
||||
if (!isSaneNodePointer(s->_segMan, argv[0]))
|
||||
return NULL_REG;
|
||||
#endif
|
||||
|
||||
return n->succ;
|
||||
}
|
||||
|
||||
reg_t kPrevNode(EngineState *s, int argc, reg_t *argv) {
|
||||
Node *n = s->_segMan->lookupNode(argv[0]);
|
||||
|
||||
#ifdef CHECK_LISTS
|
||||
if (!isSaneNodePointer(s->_segMan, argv[0]))
|
||||
return NULL_REG;
|
||||
#endif
|
||||
|
||||
return n->pred;
|
||||
}
|
||||
|
||||
reg_t kNodeValue(EngineState *s, int argc, reg_t *argv) {
|
||||
Node *n = s->_segMan->lookupNode(argv[0]);
|
||||
|
||||
#ifdef CHECK_LISTS
|
||||
if (!isSaneNodePointer(s->_segMan, argv[0]))
|
||||
return NULL_REG;
|
||||
#endif
|
||||
|
||||
// ICEMAN: when plotting a course in room 40, unDrawLast is called by
|
||||
// startPlot::changeState, but there is no previous entry, so we get 0 here
|
||||
return n ? n->value : NULL_REG;
|
||||
}
|
||||
|
||||
reg_t kAddToFront(EngineState *s, int argc, reg_t *argv) {
|
||||
addToFront(s, argv[0], argv[1]);
|
||||
|
||||
if (argc == 3)
|
||||
s->_segMan->lookupNode(argv[1])->key = argv[2];
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kAddToEnd(EngineState *s, int argc, reg_t *argv) {
|
||||
addToEnd(s, argv[0], argv[1]);
|
||||
|
||||
if (argc == 3)
|
||||
s->_segMan->lookupNode(argv[1])->key = argv[2];
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kAddAfter(EngineState *s, int argc, reg_t *argv) {
|
||||
List *list = s->_segMan->lookupList(argv[0]);
|
||||
Node *firstNode = s->_segMan->lookupNode(argv[1]);
|
||||
Node *newNode = s->_segMan->lookupNode(argv[2]);
|
||||
|
||||
#ifdef CHECK_LISTS
|
||||
checkListPointer(s->_segMan, argv[0]);
|
||||
#endif
|
||||
|
||||
if (!newNode) {
|
||||
error("New 'node' %04x:%04x is not a node", PRINT_REG(argv[2]));
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
if (argc != 3 && argc != 4) {
|
||||
error("kAddAfter: Haven't got 3 or 4 arguments, aborting");
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
if (argc == 4)
|
||||
newNode->key = argv[3];
|
||||
|
||||
if (firstNode) { // We're really appending after
|
||||
const reg_t oldNext = firstNode->succ;
|
||||
|
||||
newNode->pred = argv[1];
|
||||
firstNode->succ = argv[2];
|
||||
newNode->succ = oldNext;
|
||||
|
||||
if (oldNext.isNull()) // Appended after last node?
|
||||
// Set new node as last list node
|
||||
list->last = argv[2];
|
||||
else
|
||||
s->_segMan->lookupNode(oldNext)->pred = argv[2];
|
||||
|
||||
} else {
|
||||
addToFront(s, argv[0], argv[2]); // Set as initial list node
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kAddBefore(EngineState *s, int argc, reg_t *argv) {
|
||||
List *list = s->_segMan->lookupList(argv[0]);
|
||||
Node *firstNode = s->_segMan->lookupNode(argv[1]);
|
||||
Node *newNode = s->_segMan->lookupNode(argv[2]);
|
||||
|
||||
#ifdef CHECK_LISTS
|
||||
checkListPointer(s->_segMan, argv[0]);
|
||||
#endif
|
||||
|
||||
if (!newNode) {
|
||||
error("New 'node' %04x:%04x is not a node", PRINT_REG(argv[2]));
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
if (argc != 3 && argc != 4) {
|
||||
error("kAddBefore: Haven't got 3 or 4 arguments, aborting");
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
if (argc == 4)
|
||||
newNode->key = argv[3];
|
||||
|
||||
if (firstNode) { // We're really appending before
|
||||
const reg_t oldPred = firstNode->pred;
|
||||
|
||||
newNode->succ = argv[1];
|
||||
firstNode->pred = argv[2];
|
||||
newNode->pred = oldPred;
|
||||
|
||||
if (oldPred.isNull()) // Appended before first node?
|
||||
// Set new node as first list node
|
||||
list->first = argv[2];
|
||||
else
|
||||
s->_segMan->lookupNode(oldPred)->succ = argv[2];
|
||||
|
||||
} else {
|
||||
addToFront(s, argv[0], argv[2]); // Set as initial list node
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kFindKey(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t node_pos;
|
||||
reg_t key = argv[1];
|
||||
reg_t list_pos = argv[0];
|
||||
|
||||
debugC(kDebugLevelNodes, "Looking for key %04x:%04x in list %04x:%04x", PRINT_REG(key), PRINT_REG(list_pos));
|
||||
|
||||
#ifdef CHECK_LISTS
|
||||
checkListPointer(s->_segMan, argv[0]);
|
||||
#endif
|
||||
|
||||
node_pos = s->_segMan->lookupList(list_pos)->first;
|
||||
|
||||
debugC(kDebugLevelNodes, "First node at %04x:%04x", PRINT_REG(node_pos));
|
||||
|
||||
while (!node_pos.isNull()) {
|
||||
Node *n = s->_segMan->lookupNode(node_pos);
|
||||
if (n->key == key) {
|
||||
debugC(kDebugLevelNodes, " Found key at %04x:%04x", PRINT_REG(node_pos));
|
||||
return node_pos;
|
||||
}
|
||||
|
||||
node_pos = n->succ;
|
||||
debugC(kDebugLevelNodes, "NextNode at %04x:%04x", PRINT_REG(node_pos));
|
||||
}
|
||||
|
||||
debugC(kDebugLevelNodes, "Looking for key without success");
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
reg_t kDeleteKey(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t node_pos = kFindKey(s, 2, argv);
|
||||
List *list = s->_segMan->lookupList(argv[0]);
|
||||
|
||||
if (node_pos.isNull())
|
||||
return NULL_REG; // Signal failure
|
||||
|
||||
Node *n = s->_segMan->lookupNode(node_pos);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
for (int i = 1; i <= list->numRecursions; ++i) {
|
||||
if (list->nextNodes[i] == node_pos) {
|
||||
list->nextNodes[i] = n->succ;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (list->first == node_pos)
|
||||
list->first = n->succ;
|
||||
if (list->last == node_pos)
|
||||
list->last = n->pred;
|
||||
|
||||
if (!n->pred.isNull())
|
||||
s->_segMan->lookupNode(n->pred)->succ = n->succ;
|
||||
if (!n->succ.isNull())
|
||||
s->_segMan->lookupNode(n->succ)->pred = n->pred;
|
||||
|
||||
// Erase references to the predecessor and successor nodes, as the game
|
||||
// scripts could reference the node itself again.
|
||||
// Happens in the intro of QFG1 and in Longbow, when exiting the cave.
|
||||
n->pred = NULL_REG;
|
||||
n->succ = NULL_REG;
|
||||
|
||||
return make_reg(0, 1); // Signal success
|
||||
}
|
||||
|
||||
struct sort_temp_t {
|
||||
reg_t key, value;
|
||||
reg_t order;
|
||||
};
|
||||
|
||||
int sort_temp_cmp(const void *p1, const void *p2) {
|
||||
const sort_temp_t *st1 = (const sort_temp_t *)p1;
|
||||
const sort_temp_t *st2 = (const sort_temp_t *)p2;
|
||||
|
||||
if (st1->order.getSegment() < st2->order.getSegment() ||
|
||||
(st1->order.getSegment() == st2->order.getSegment() &&
|
||||
st1->order.getOffset() < st2->order.getOffset()))
|
||||
return -1;
|
||||
|
||||
if (st1->order.getSegment() > st2->order.getSegment() ||
|
||||
(st1->order.getSegment() == st2->order.getSegment() &&
|
||||
st1->order.getOffset() > st2->order.getOffset()))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
reg_t kSort(EngineState *s, int argc, reg_t *argv) {
|
||||
SegManager *segMan = s->_segMan;
|
||||
reg_t source = argv[0];
|
||||
reg_t dest = argv[1];
|
||||
reg_t order_func = argv[2];
|
||||
|
||||
int input_size = (int16)readSelectorValue(segMan, source, SELECTOR(size));
|
||||
reg_t input_data = readSelector(segMan, source, SELECTOR(elements));
|
||||
reg_t output_data = readSelector(segMan, dest, SELECTOR(elements));
|
||||
|
||||
List *list;
|
||||
Node *node;
|
||||
|
||||
if (!input_size)
|
||||
return s->r_acc;
|
||||
|
||||
if (output_data.isNull()) {
|
||||
list = s->_segMan->allocateList(&output_data);
|
||||
list->first = list->last = NULL_REG;
|
||||
writeSelector(segMan, dest, SELECTOR(elements), output_data);
|
||||
}
|
||||
|
||||
writeSelectorValue(segMan, dest, SELECTOR(size), input_size);
|
||||
|
||||
list = s->_segMan->lookupList(input_data);
|
||||
node = s->_segMan->lookupNode(list->first);
|
||||
|
||||
sort_temp_t *temp_array = (sort_temp_t *)malloc(sizeof(sort_temp_t) * input_size);
|
||||
|
||||
int i = 0;
|
||||
while (node) {
|
||||
reg_t params[1] = { node->value };
|
||||
invokeSelector(s, order_func, SELECTOR(doit), argc, argv, 1, params);
|
||||
temp_array[i].key = node->key;
|
||||
temp_array[i].value = node->value;
|
||||
temp_array[i].order = s->r_acc;
|
||||
i++;
|
||||
node = s->_segMan->lookupNode(node->succ);
|
||||
}
|
||||
|
||||
qsort(temp_array, input_size, sizeof(sort_temp_t), sort_temp_cmp);
|
||||
|
||||
for (i = 0;i < input_size;i++) {
|
||||
reg_t lNode = s->_segMan->newNode(temp_array[i].value, temp_array[i].key);
|
||||
addToEnd(s, output_data, lNode);
|
||||
}
|
||||
|
||||
free(temp_array);
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
// SCI32 list functions
|
||||
#ifdef ENABLE_SCI32
|
||||
|
||||
reg_t kListAt(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argc != 2) {
|
||||
error("kListAt called with %d parameters", argc);
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
List *list = s->_segMan->lookupList(argv[0]);
|
||||
reg_t curAddress = list->first;
|
||||
if (list->first.isNull()) {
|
||||
// Happens in Torin when examining Di's locket in chapter 3
|
||||
return NULL_REG;
|
||||
}
|
||||
Node *curNode = s->_segMan->lookupNode(curAddress);
|
||||
reg_t curObject = curNode->value;
|
||||
int16 listIndex = argv[1].toUint16();
|
||||
int curIndex = 0;
|
||||
|
||||
while (curIndex != listIndex) {
|
||||
if (curNode->succ.isNull()) { // end of the list?
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
curAddress = curNode->succ;
|
||||
curNode = s->_segMan->lookupNode(curAddress);
|
||||
curObject = curNode->value;
|
||||
|
||||
curIndex++;
|
||||
}
|
||||
|
||||
// Update the virtual file selected in the character import screen of QFG4.
|
||||
// For the SCI0-SCI1.1 version of this, check kDrawControl().
|
||||
if (g_sci->inQfGImportRoom() && !strcmp(s->_segMan->getObjectName(curObject), "SelectorDText"))
|
||||
s->_chosenQfGImportItem = listIndex;
|
||||
|
||||
return curObject;
|
||||
}
|
||||
|
||||
reg_t kListIndexOf(EngineState *s, int argc, reg_t *argv) {
|
||||
List *list = s->_segMan->lookupList(argv[0]);
|
||||
|
||||
reg_t curAddress = list->first;
|
||||
Node *curNode = s->_segMan->lookupNode(curAddress);
|
||||
reg_t curObject;
|
||||
uint16 curIndex = 0;
|
||||
|
||||
while (curNode) {
|
||||
curObject = curNode->value;
|
||||
if (curObject == argv[1])
|
||||
return make_reg(0, curIndex);
|
||||
|
||||
curAddress = curNode->succ;
|
||||
curNode = s->_segMan->lookupNode(curAddress);
|
||||
curIndex++;
|
||||
}
|
||||
|
||||
return SIGNAL_REG;
|
||||
}
|
||||
|
||||
reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) {
|
||||
const reg_t listReg = argv[0];
|
||||
List *list = s->_segMan->lookupList(listReg);
|
||||
|
||||
Node *curNode = s->_segMan->lookupNode(list->first);
|
||||
Selector slc = argv[1].toUint16();
|
||||
|
||||
ObjVarRef address;
|
||||
|
||||
++list->numRecursions;
|
||||
|
||||
if (list->numRecursions >= ARRAYSIZE(list->nextNodes)) {
|
||||
error("Too much recursion in kListEachElementDo");
|
||||
}
|
||||
|
||||
while (curNode) {
|
||||
// We get the next node here as the current node might be deleted by the
|
||||
// invoke. In the case that the next node is also deleted, kDeleteKey
|
||||
// needs to be able to adjust the location of the next node, which is
|
||||
// why it is stored on the list instead of on the stack
|
||||
list->nextNodes[list->numRecursions] = curNode->succ;
|
||||
reg_t curObject = curNode->value;
|
||||
|
||||
// First, check if the target selector is a variable
|
||||
if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) {
|
||||
// This can only happen with 3 params (list, target selector, variable)
|
||||
if (argc != 3) {
|
||||
error("kListEachElementDo: Attempted to modify a variable selector with %d params", argc);
|
||||
} else {
|
||||
writeSelector(s->_segMan, curObject, slc, argv[2]);
|
||||
}
|
||||
} else {
|
||||
invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2);
|
||||
|
||||
// Check if the call above leads to a game restore, in which case
|
||||
// the segment manager will be reset, and the original list will
|
||||
// be invalidated
|
||||
if (s->abortScriptProcessing == kAbortLoadGame)
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
curNode = s->_segMan->lookupNode(list->nextNodes[list->numRecursions]);
|
||||
}
|
||||
|
||||
if (s->_segMan->isValidAddr(listReg, SEG_TYPE_LISTS)) {
|
||||
--list->numRecursions;
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv) {
|
||||
const reg_t listReg = argv[0];
|
||||
List *list = s->_segMan->lookupList(listReg);
|
||||
|
||||
Node *curNode = s->_segMan->lookupNode(list->first);
|
||||
Selector slc = argv[1].toUint16();
|
||||
|
||||
ObjVarRef address;
|
||||
|
||||
s->r_acc = NULL_REG;
|
||||
|
||||
++list->numRecursions;
|
||||
|
||||
if (list->numRecursions >= ARRAYSIZE(list->nextNodes)) {
|
||||
error("Too much recursion in kListFirstTrue");
|
||||
}
|
||||
|
||||
while (curNode) {
|
||||
// We get the next node here as the current node might be deleted by the
|
||||
// invoke. In the case that the next node is also deleted, kDeleteKey
|
||||
// needs to be able to adjust the location of the next node, which is
|
||||
// why it is stored on the list instead of on the stack
|
||||
list->nextNodes[list->numRecursions] = curNode->succ;
|
||||
reg_t curObject = curNode->value;
|
||||
|
||||
// First, check if the target selector is a variable
|
||||
if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) {
|
||||
// If it's a variable selector, check its value.
|
||||
// Example: script 64893 in Torin, MenuHandler::isHilited checks
|
||||
// all children for variable selector 0x03ba (bHilited).
|
||||
if (!readSelector(s->_segMan, curObject, slc).isNull()) {
|
||||
s->r_acc = curObject;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2);
|
||||
|
||||
// Check if the call above leads to a game restore, in which case
|
||||
// the segment manager will be reset, and the original list will
|
||||
// be invalidated
|
||||
if (s->abortScriptProcessing == kAbortLoadGame)
|
||||
return s->r_acc;
|
||||
|
||||
// Check if the result is true
|
||||
if (!s->r_acc.isNull()) {
|
||||
s->r_acc = curObject;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
curNode = s->_segMan->lookupNode(list->nextNodes[list->numRecursions]);
|
||||
}
|
||||
|
||||
if (s->_segMan->isValidAddr(listReg, SEG_TYPE_LISTS)) {
|
||||
--list->numRecursions;
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv) {
|
||||
const reg_t listReg = argv[0];
|
||||
List *list = s->_segMan->lookupList(listReg);
|
||||
|
||||
Node *curNode = s->_segMan->lookupNode(list->first);
|
||||
reg_t curObject;
|
||||
Selector slc = argv[1].toUint16();
|
||||
|
||||
ObjVarRef address;
|
||||
|
||||
s->r_acc = TRUE_REG;
|
||||
|
||||
++list->numRecursions;
|
||||
|
||||
if (list->numRecursions >= ARRAYSIZE(list->nextNodes)) {
|
||||
error("Too much recursion in kListAllTrue");
|
||||
}
|
||||
|
||||
while (curNode) {
|
||||
// We get the next node here as the current node might be deleted by the
|
||||
// invoke. In the case that the next node is also deleted, kDeleteKey
|
||||
// needs to be able to adjust the location of the next node, which is
|
||||
// why it is stored on the list instead of on the stack
|
||||
list->nextNodes[list->numRecursions] = curNode->succ;
|
||||
curObject = curNode->value;
|
||||
|
||||
// First, check if the target selector is a variable
|
||||
if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) {
|
||||
// If it's a variable selector, check its value
|
||||
s->r_acc = readSelector(s->_segMan, curObject, slc);
|
||||
} else {
|
||||
invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2);
|
||||
|
||||
// Check if the call above leads to a game restore, in which case
|
||||
// the segment manager will be reset, and the original list will
|
||||
// be invalidated
|
||||
if (s->abortScriptProcessing == kAbortLoadGame)
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
// Check if the result isn't true
|
||||
if (s->r_acc.isNull())
|
||||
break;
|
||||
|
||||
curNode = s->_segMan->lookupNode(list->nextNodes[list->numRecursions]);
|
||||
}
|
||||
|
||||
if (s->_segMan->isValidAddr(listReg, SEG_TYPE_LISTS)) {
|
||||
--list->numRecursions;
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kListSort(EngineState *s, int argc, reg_t *argv) {
|
||||
List *list = s->_segMan->lookupList(argv[0]);
|
||||
const int16 selector = argv[1].toSint16();
|
||||
const bool isDescending = argc > 2 ? (bool)argv[2].toUint16() : false;
|
||||
|
||||
reg_t firstNode = list->first;
|
||||
for (reg_t node = firstNode; node != NULL_REG; node = s->_segMan->lookupNode(firstNode)->succ) {
|
||||
|
||||
reg_t a;
|
||||
if (selector == -1) {
|
||||
a = s->_segMan->lookupNode(node)->value;
|
||||
} else {
|
||||
a = readSelector(s->_segMan, s->_segMan->lookupNode(node)->value, selector);
|
||||
}
|
||||
|
||||
firstNode = node;
|
||||
for (reg_t newNode = s->_segMan->lookupNode(node)->succ; newNode != NULL_REG; newNode = s->_segMan->lookupNode(newNode)->succ) {
|
||||
reg_t b;
|
||||
if (selector == -1) {
|
||||
b = s->_segMan->lookupNode(newNode)->value;
|
||||
} else {
|
||||
b = readSelector(s->_segMan, s->_segMan->lookupNode(newNode)->value, selector);
|
||||
}
|
||||
|
||||
if ((!isDescending && b < a) || (isDescending && a < b)) {
|
||||
firstNode = newNode;
|
||||
a = b;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstNode != node) {
|
||||
reg_t buf[4] = { argv[0], s->_segMan->lookupNode(firstNode)->key };
|
||||
kDeleteKey(s, 2, buf);
|
||||
|
||||
buf[1] = node;
|
||||
buf[2] = firstNode;
|
||||
buf[3] = s->_segMan->lookupNode(firstNode)->value;
|
||||
kAddBefore(s, 4, buf);
|
||||
}
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kList(EngineState *s, int argc, reg_t *argv) {
|
||||
if (!s)
|
||||
return make_reg(0, getSciVersion());
|
||||
error("not supposed to call this");
|
||||
}
|
||||
|
||||
reg_t kMoveToFront(EngineState *s, int argc, reg_t *argv) {
|
||||
error("Unimplemented function kMoveToFront called");
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kMoveToEnd(EngineState *s, int argc, reg_t *argv) {
|
||||
error("Unimplemented function kMoveToEnd called");
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kArray(EngineState *s, int argc, reg_t *argv) {
|
||||
if (!s)
|
||||
return make_reg(0, getSciVersion());
|
||||
error("not supposed to call this");
|
||||
}
|
||||
|
||||
reg_t kArrayNew(EngineState *s, int argc, reg_t *argv) {
|
||||
uint16 size = argv[0].toUint16();
|
||||
SciArrayType type = (SciArrayType)argv[1].toUint16();
|
||||
|
||||
if (type == kArrayTypeString) {
|
||||
++size;
|
||||
}
|
||||
|
||||
reg_t arrayHandle;
|
||||
s->_segMan->allocateArray(type, size, &arrayHandle);
|
||||
return arrayHandle;
|
||||
}
|
||||
|
||||
reg_t kArrayGetSize(EngineState *s, int argc, reg_t *argv) {
|
||||
const SciArray &array = *s->_segMan->lookupArray(argv[0]);
|
||||
return make_reg(0, array.size());
|
||||
}
|
||||
|
||||
reg_t kArrayGetElement(EngineState *s, int argc, reg_t *argv) {
|
||||
if (getSciVersion() == SCI_VERSION_2_1_LATE) {
|
||||
return kStringGetChar(s, argc, argv);
|
||||
}
|
||||
|
||||
SciArray &array = *s->_segMan->lookupArray(argv[0]);
|
||||
return array.getAsID(argv[1].toUint16());
|
||||
}
|
||||
|
||||
reg_t kArraySetElements(EngineState *s, int argc, reg_t *argv) {
|
||||
SciArray &array = *s->_segMan->lookupArray(argv[0]);
|
||||
array.setElements(argv[1].toUint16(), argc - 2, argv + 2);
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
reg_t kArrayFree(EngineState *s, int argc, reg_t *argv) {
|
||||
if (getSciVersion() == SCI_VERSION_2_1_LATE && !s->_segMan->isValidAddr(argv[0], SEG_TYPE_ARRAY)) {
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
s->_segMan->freeArray(argv[0]);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kArrayFill(EngineState *s, int argc, reg_t *argv) {
|
||||
SciArray &array = *s->_segMan->lookupArray(argv[0]);
|
||||
array.fill(argv[1].toUint16(), argv[2].toUint16(), argv[3]);
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
reg_t kArrayCopy(EngineState *s, int argc, reg_t *argv) {
|
||||
SciArray &target = *s->_segMan->lookupArray(argv[0]);
|
||||
const uint16 targetIndex = argv[1].toUint16();
|
||||
const uint16 sourceIndex = argv[3].toUint16();
|
||||
const int16 count = argv[4].toSint16();
|
||||
|
||||
if (argv[2].isNull()) {
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
if (!s->_segMan->isArray(argv[2])) {
|
||||
// String copies may be made from static script data
|
||||
SciArray source;
|
||||
source.setType(kArrayTypeString);
|
||||
source.fromString(s->_segMan->getString(argv[2]));
|
||||
target.copy(source, sourceIndex, targetIndex, count);
|
||||
} else {
|
||||
target.copy(*s->_segMan->lookupArray(argv[2]), sourceIndex, targetIndex, count);
|
||||
}
|
||||
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
reg_t kArrayDuplicate(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t targetHandle;
|
||||
|
||||
// String duplicates may be made from static script data
|
||||
if (!s->_segMan->isArray(argv[0])) {
|
||||
const Common::String source = s->_segMan->getString(argv[0]);
|
||||
SciArray &target = *s->_segMan->allocateArray(kArrayTypeString, source.size(), &targetHandle);
|
||||
target.fromString(source);
|
||||
} else {
|
||||
SciArray &source = *s->_segMan->lookupArray(argv[0]);
|
||||
SciArray &target = *s->_segMan->allocateArray(source.getType(), source.size(), &targetHandle);
|
||||
target = source;
|
||||
}
|
||||
|
||||
return targetHandle;
|
||||
}
|
||||
|
||||
reg_t kArrayGetData(EngineState *s, int argc, reg_t *argv) {
|
||||
if (s->_segMan->isObject(argv[0])) {
|
||||
return readSelector(s->_segMan, argv[0], SELECTOR(data));
|
||||
}
|
||||
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
reg_t kArrayByteCopy(EngineState *s, int argc, reg_t *argv) {
|
||||
SciArray &target = *s->_segMan->lookupArray(argv[0]);
|
||||
const uint16 targetOffset = argv[1].toUint16();
|
||||
const SciArray &source = *s->_segMan->lookupArray(argv[2]);
|
||||
const uint16 sourceOffset = argv[3].toUint16();
|
||||
const uint16 count = argv[4].toUint16();
|
||||
|
||||
target.byteCopy(source, sourceOffset, targetOffset, count);
|
||||
return argv[0];
|
||||
}
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
296
engines/sci/engine/kmath.cpp
Normal file
296
engines/sci/engine/kmath.cpp
Normal file
@@ -0,0 +1,296 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// Following variations existed:
|
||||
// up until including 0.530 (Hoyle 1): will always get 2 parameters, even if 2 parameters were not passed
|
||||
// it seems if range is 0, it will return the seed.
|
||||
// 0.566 (Hero's Quest) to SCI1MID: check for 2 parameters, if not 2 parameters get seed.
|
||||
// SCI1LATE+: 2 parameters -> get random number within range
|
||||
// 1 parameter -> set seed
|
||||
// any other amount of parameters -> get seed
|
||||
//
|
||||
// Right now, the weird SCI0 behavior (up until 0.530) for getting parameters and getting the seed is not implemented.
|
||||
// We also do not let through more than 2 parameters to kRandom via signatures. In case there is a game doing this,
|
||||
// a workaround should be added.
|
||||
reg_t kRandom(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::RandomSource &rng = g_sci->getRNG();
|
||||
|
||||
if (argc == 2) {
|
||||
// get random number
|
||||
// numbers are definitely unsigned, for example lsl5 door code in k rap radio is random
|
||||
// and 5-digit - we get called kRandom(10000, 65000)
|
||||
// some codes in sq4 are also random and 5 digit (if i remember correctly)
|
||||
const uint16 fromNumber = argv[0].toUint16();
|
||||
const uint16 toNumber = argv[1].toUint16();
|
||||
// Some scripts may request a range in the reverse order (from largest
|
||||
// to smallest). An example can be found in Longbow, room 710, where a
|
||||
// random number is requested from 119 to 83. In this case, we're
|
||||
// supposed to return toNumber (determined by the KQ5CD disasm).
|
||||
// Fixes bug #5846.
|
||||
if (fromNumber > toNumber)
|
||||
return make_reg(0, toNumber);
|
||||
|
||||
uint16 range = toNumber - fromNumber + 1;
|
||||
// calculating range is exactly how sierra sci did it and is required for hoyle 4
|
||||
// where we get called with kRandom(0, -1) and we are supposed to give back values from 0 to 0
|
||||
// the returned value will be used as displace-offset for a background cel
|
||||
// note: i assume that the hoyle4 code is actually buggy and it was never fixed because of
|
||||
// the way sierra sci handled it - "it just worked". It should have called kRandom(0, 0)
|
||||
if (range)
|
||||
range--; // the range value was never returned, our random generator gets 0->range, so fix it
|
||||
|
||||
const int randomNumber = fromNumber + (int)rng.getRandomNumber(range);
|
||||
return make_reg(0, randomNumber);
|
||||
}
|
||||
|
||||
// for other amounts of arguments
|
||||
if (getSciVersion() >= SCI_VERSION_1_LATE) {
|
||||
if (argc == 1) {
|
||||
// 1 single argument is for setting the seed
|
||||
// right now we do not change the Common RNG seed.
|
||||
// It should never be required unless a game reuses the same seed to get the same combination of numbers multiple times.
|
||||
// And in such a case, we would have to add code for such a feature (ScummVM RNG uses a UINT32 seed).
|
||||
warning("kRandom: caller requested to set the RNG seed");
|
||||
return NULL_REG;
|
||||
}
|
||||
}
|
||||
|
||||
// treat anything else as if caller wants the seed
|
||||
warning("kRandom: caller requested to get the RNG seed");
|
||||
return make_reg(0, rng.getSeed());
|
||||
}
|
||||
|
||||
reg_t kAbs(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, ABS(argv[0].toSint16()));
|
||||
}
|
||||
|
||||
reg_t kSqrt(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, (int16) sqrt((float) ABS(argv[0].toSint16())));
|
||||
}
|
||||
|
||||
uint16 kGetAngle_SCI0(int16 x1, int16 y1, int16 x2, int16 y2) {
|
||||
int16 xRel = x2 - x1;
|
||||
int16 yRel = y1 - y2; // y-axis is mirrored.
|
||||
int16 angle;
|
||||
|
||||
// Move (xrel, yrel) to first quadrant.
|
||||
if (y1 < y2)
|
||||
yRel = -yRel;
|
||||
if (x2 < x1)
|
||||
xRel = -xRel;
|
||||
|
||||
// Compute angle in grads.
|
||||
if (yRel == 0 && xRel == 0)
|
||||
return 0;
|
||||
else
|
||||
angle = 100 * xRel / (xRel + yRel);
|
||||
|
||||
// Fix up angle for actual quadrant of (xRel, yRel).
|
||||
if (y1 < y2)
|
||||
angle = 200 - angle;
|
||||
if (x2 < x1)
|
||||
angle = 400 - angle;
|
||||
|
||||
// Convert from grads to degrees by merging grad 0 with grad 1,
|
||||
// grad 10 with grad 11, grad 20 with grad 21, etc. This leads to
|
||||
// "degrees" that equal either one or two grads.
|
||||
angle -= (angle + 9) / 10;
|
||||
return angle;
|
||||
}
|
||||
|
||||
// atan2 for first octant, x >= y >= 0. Returns [0,45] (inclusive)
|
||||
int kGetAngle_SCI1_atan2_base(int y, int x) {
|
||||
if (x == 0)
|
||||
return 0;
|
||||
|
||||
// fixed point tan(a)
|
||||
int tan_fp = 10000 * y / x;
|
||||
|
||||
if ( tan_fp >= 1000 ) {
|
||||
// For tan(a) >= 0.1, interpolate between multiples of 5 degrees
|
||||
|
||||
// 10000 * tan([5, 10, 15, 20, 25, 30, 35, 40, 45])
|
||||
const int tan_table[] = { 875, 1763, 2679, 3640, 4663, 5774,
|
||||
7002, 8391, 10000 };
|
||||
|
||||
// Look up tan(a) in our table
|
||||
int i = 1;
|
||||
while (tan_fp > tan_table[i]) ++i;
|
||||
|
||||
// The angle a is between 5*i and 5*(i+1). We linearly interpolate.
|
||||
int dist = tan_table[i] - tan_table[i-1];
|
||||
int interp = (5 * (tan_fp - tan_table[i-1]) + dist/2) / dist;
|
||||
return 5*i + interp;
|
||||
} else {
|
||||
// for tan(a) < 0.1, tan(a) is approximately linear in a.
|
||||
// tan'(0) = 1, so in degrees the slope of atan is 180/pi = 57.29...
|
||||
return (57 * y + x/2) / x;
|
||||
}
|
||||
}
|
||||
|
||||
int kGetAngle_SCI1_atan2(int y, int x) {
|
||||
if (y < 0) {
|
||||
int a = kGetAngle_SCI1_atan2(-y, -x);
|
||||
if (a == 180)
|
||||
return 0;
|
||||
else
|
||||
return 180 + a;
|
||||
}
|
||||
if (x < 0)
|
||||
return 90 + kGetAngle_SCI1_atan2(-x, y);
|
||||
if (y > x)
|
||||
return 90 - kGetAngle_SCI1_atan2_base(x, y);
|
||||
else
|
||||
return kGetAngle_SCI1_atan2_base(y, x);
|
||||
|
||||
}
|
||||
|
||||
uint16 kGetAngle_SCI1(int16 x1, int16 y1, int16 x2, int16 y2) {
|
||||
// We flip things around to get into the standard atan2 coordinate system
|
||||
return kGetAngle_SCI1_atan2(x2 - x1, y1 - y2);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the angle (in degrees) between the two points determined by (x1, y1)
|
||||
* and (x2, y2). The angle ranges from 0 to 359 degrees.
|
||||
* What this function does is pretty simple but apparently the original is not
|
||||
* accurate.
|
||||
*/
|
||||
|
||||
uint16 kGetAngleWorker(int16 x1, int16 y1, int16 x2, int16 y2) {
|
||||
if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY)
|
||||
return kGetAngle_SCI1(x1, y1, x2, y2);
|
||||
else
|
||||
return kGetAngle_SCI0(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
reg_t kGetAngle(EngineState *s, int argc, reg_t *argv) {
|
||||
// Based on behavior observed with a test program created with
|
||||
// SCI Studio.
|
||||
int x1 = argv[0].toSint16();
|
||||
int y1 = argv[1].toSint16();
|
||||
int x2 = argv[2].toSint16();
|
||||
int y2 = argv[3].toSint16();
|
||||
|
||||
return make_reg(0, kGetAngleWorker(x1, y1, x2, y2));
|
||||
}
|
||||
|
||||
reg_t kGetDistance(EngineState *s, int argc, reg_t *argv) {
|
||||
int xdiff = (argc > 3) ? argv[3].toSint16() : 0;
|
||||
int ydiff = (argc > 2) ? argv[2].toSint16() : 0;
|
||||
int angle = (argc > 5) ? argv[5].toSint16() : 0;
|
||||
int xrel = (int)(((float) argv[1].toSint16() - xdiff) / cos(angle * M_PI / 180.0)); // This works because cos(0)==1
|
||||
int yrel = argv[0].toSint16() - ydiff;
|
||||
return make_reg(0, (int16)sqrt((float) xrel*xrel + yrel*yrel));
|
||||
}
|
||||
|
||||
reg_t kTimesSin(EngineState *s, int argc, reg_t *argv) {
|
||||
int angle = argv[0].toSint16();
|
||||
int factor = argv[1].toSint16();
|
||||
|
||||
return make_reg(0, (int16)(factor * sin(angle * M_PI / 180.0)));
|
||||
}
|
||||
|
||||
reg_t kTimesCos(EngineState *s, int argc, reg_t *argv) {
|
||||
int angle = argv[0].toSint16();
|
||||
int factor = argv[1].toSint16();
|
||||
|
||||
return make_reg(0, (int16)(factor * cos(angle * M_PI / 180.0)));
|
||||
}
|
||||
|
||||
reg_t kCosDiv(EngineState *s, int argc, reg_t *argv) {
|
||||
int angle = argv[0].toSint16();
|
||||
int value = argv[1].toSint16();
|
||||
double cosval = cos(angle * M_PI / 180.0);
|
||||
|
||||
if ((cosval < 0.0001) && (cosval > -0.0001)) {
|
||||
error("kCosDiv: Attempted division by zero");
|
||||
return SIGNAL_REG;
|
||||
} else
|
||||
return make_reg(0, (int16)(value / cosval));
|
||||
}
|
||||
|
||||
reg_t kSinDiv(EngineState *s, int argc, reg_t *argv) {
|
||||
int angle = argv[0].toSint16();
|
||||
int value = argv[1].toSint16();
|
||||
double sinval = sin(angle * M_PI / 180.0);
|
||||
|
||||
if ((sinval < 0.0001) && (sinval > -0.0001)) {
|
||||
error("kSinDiv: Attempted division by zero");
|
||||
return SIGNAL_REG;
|
||||
} else
|
||||
return make_reg(0, (int16)(value / sinval));
|
||||
}
|
||||
|
||||
reg_t kTimesTan(EngineState *s, int argc, reg_t *argv) {
|
||||
int param = argv[0].toSint16();
|
||||
int scale = (argc > 1) ? argv[1].toSint16() : 1;
|
||||
|
||||
param -= 90;
|
||||
if ((param % 90) == 0) {
|
||||
error("kTimesTan: Attempted tan(pi/2)");
|
||||
return SIGNAL_REG;
|
||||
} else
|
||||
return make_reg(0, (int16) - (tan(param * M_PI / 180.0) * scale));
|
||||
}
|
||||
|
||||
reg_t kTimesCot(EngineState *s, int argc, reg_t *argv) {
|
||||
int param = argv[0].toSint16();
|
||||
int scale = (argc > 1) ? argv[1].toSint16() : 1;
|
||||
|
||||
if ((param % 90) == 0) {
|
||||
error("kTimesCot: Attempted tan(pi/2)");
|
||||
return SIGNAL_REG;
|
||||
} else
|
||||
return make_reg(0, (int16)(tan(param * M_PI / 180.0) * scale));
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
|
||||
reg_t kMulDiv(EngineState *s, int argc, reg_t *argv) {
|
||||
int multiplicant = argv[0].toSint16();
|
||||
int multiplier = argv[1].toSint16();
|
||||
int denominator = argv[2].toSint16();
|
||||
|
||||
// Sanity check...
|
||||
if (!denominator) {
|
||||
error("kMulDiv: attempt to divide by zero (%d * %d / %d", multiplicant, multiplier, denominator);
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
int result = (abs(multiplicant * multiplier) + abs(denominator) / 2) / abs(denominator);
|
||||
if (multiplicant && ((multiplicant / abs(multiplicant)) * multiplier * denominator < 0))
|
||||
result = -result;
|
||||
|
||||
return make_reg(0, (int16)result);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
99
engines/sci/engine/kmenu.cpp
Normal file
99
engines/sci/engine/kmenu.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/graphics/cursor.h"
|
||||
#include "sci/graphics/menu.h"
|
||||
#include "sci/graphics/screen.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
reg_t kAddMenu(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::String title = s->_segMan->getString(argv[0]);
|
||||
Common::String content = s->_segMan->getString(argv[1]);
|
||||
|
||||
g_sci->_gfxMenu->kernelAddEntry(title, content, argv[1]);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
|
||||
reg_t kSetMenu(EngineState *s, int argc, reg_t *argv) {
|
||||
uint16 menuId = argv[0].toUint16() >> 8;
|
||||
uint16 itemId = argv[0].toUint16() & 0xFF;
|
||||
int argPos = 1;
|
||||
|
||||
while (argPos < argc) {
|
||||
uint16 attributeId = argv[argPos].toUint16();
|
||||
// Happens in the fanmade game Cascade Quest when loading - bug #5118
|
||||
reg_t value = (argPos + 1 < argc) ? argv[argPos + 1] : NULL_REG;
|
||||
g_sci->_gfxMenu->kernelSetAttribute(menuId, itemId, attributeId, value);
|
||||
argPos += 2;
|
||||
}
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kGetMenu(EngineState *s, int argc, reg_t *argv) {
|
||||
uint16 menuId = argv[0].toUint16() >> 8;
|
||||
uint16 itemId = argv[0].toUint16() & 0xFF;
|
||||
uint16 attributeId = argv[1].toUint16();
|
||||
|
||||
return g_sci->_gfxMenu->kernelGetAttribute(menuId, itemId, attributeId);
|
||||
}
|
||||
|
||||
|
||||
reg_t kDrawStatus(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t textReference = argv[0];
|
||||
Common::String text;
|
||||
int16 colorPen = (argc > 1) ? argv[1].toSint16() : 0;
|
||||
int16 colorBack = (argc > 2) ? argv[2].toSint16() : g_sci->_gfxScreen->getColorWhite();
|
||||
|
||||
if (!textReference.isNull()) {
|
||||
// Sometimes this is called without giving text, if that's the case don't process it.
|
||||
text = s->_segMan->getString(textReference);
|
||||
|
||||
if (text == "Replaying sound") {
|
||||
// Happens in the fanmade game Cascade Quest when loading - ignore it
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
g_sci->_gfxMenu->kernelDrawStatus(g_sci->strSplit(text.c_str(), nullptr).c_str(), colorPen, colorBack);
|
||||
}
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kDrawMenuBar(EngineState *s, int argc, reg_t *argv) {
|
||||
bool clear = argv[0].isNull() ? true : false;
|
||||
|
||||
g_sci->_gfxMenu->kernelDrawMenuBar(clear);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kMenuSelect(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t eventObject = argv[0];
|
||||
bool pauseSound = argc <= 1 || !argv[1].isNull();
|
||||
|
||||
return g_sci->_gfxMenu->kernelSelect(eventObject, pauseSound);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
1042
engines/sci/engine/kmisc.cpp
Normal file
1042
engines/sci/engine/kmisc.cpp
Normal file
File diff suppressed because it is too large
Load Diff
527
engines/sci/engine/kmovement.cpp
Normal file
527
engines/sci/engine/kmovement.cpp
Normal file
@@ -0,0 +1,527 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/graphics/animate.h"
|
||||
#include "sci/graphics/screen.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
/**
|
||||
* Compute "velocity" vector (xStep,yStep)=(vx,vy) for a jump from (0,0) to
|
||||
* (dx,dy), with gravity constant gy. The gravity is assumed to be non-negative.
|
||||
*
|
||||
* If this was ordinary continuous physics, we would compute the desired
|
||||
* (floating point!) velocity vector (vx,vy) as follows, under the assumption
|
||||
* that vx and vy are linearly correlated by a constant c, i.e., vy = c * vx:
|
||||
* dx = t * vx
|
||||
* dy = t * vy + gy * t^2 / 2
|
||||
* => dy = c * dx + gy * (dx/vx)^2 / 2
|
||||
* => |vx| = sqrt( gy * dx^2 / (2 * (dy - c * dx)) )
|
||||
* Here, the sign of vx must be chosen equal to the sign of dx, obviously.
|
||||
*
|
||||
* This square root only makes sense in our context if the denominator is
|
||||
* positive, or equivalently, (dy - c * dx) must be positive. For simplicity
|
||||
* and by symmetry along the x-axis, we assume dx to be positive for all
|
||||
* computations, and only adjust for its sign in the end. Switching the sign of
|
||||
* c appropriately, we set tmp := (dy + c * dx) and compute c so that this term
|
||||
* becomes positive.
|
||||
*
|
||||
* Remark #1: If the jump is straight up, i.e. dx == 0, then we should not
|
||||
* assume the above linear correlation vy = c * vx of the velocities (as vx
|
||||
* will be 0, but vy shouldn't be, unless we drop down).
|
||||
*
|
||||
* Remark #2: We are actually in a discrete setup. The motion is computed
|
||||
* iteratively: each iteration, we add vx and vy to the position, then add gy
|
||||
* to vy. So the real formula is the following (where t ideally is close to an int):
|
||||
*
|
||||
* dx = t * vx
|
||||
* dy = t * vy + gy * t*(t-1) / 2
|
||||
*
|
||||
* But the solution resulting from that is a lot more complicated, so we use
|
||||
* the above approximation instead.
|
||||
*
|
||||
* Still, what we compute in the end is of course not a real velocity anymore,
|
||||
* but an integer approximation, used in an iterative stepping algorithm.
|
||||
*/
|
||||
reg_t kSetJump(EngineState *s, int argc, reg_t *argv) {
|
||||
SegManager *segMan = s->_segMan;
|
||||
// Input data
|
||||
reg_t object = argv[0];
|
||||
int dx = argv[1].toSint16();
|
||||
int dy = argv[2].toSint16();
|
||||
int gy = argv[3].toSint16();
|
||||
|
||||
// Derived data
|
||||
int c;
|
||||
int tmp;
|
||||
int vx = 0; // x velocity
|
||||
int vy = 0; // y velocity
|
||||
|
||||
int dxWasNegative = (dx < 0);
|
||||
dx = ABS(dx);
|
||||
|
||||
assert(gy >= 0);
|
||||
|
||||
if (dx == 0) {
|
||||
// Upward jump. Value of c doesn't really matter
|
||||
c = 1;
|
||||
} else {
|
||||
// Compute a suitable value for c respectively tmp.
|
||||
// The important thing to consider here is that we want the resulting
|
||||
// *discrete* x/y velocities to be not-too-big integers, for a smooth
|
||||
// curve (i.e. we could just set vx=dx, vy=dy, and be done, but that
|
||||
// is hardly what you would call a parabolic jump, would ya? ;-).
|
||||
//
|
||||
// So, we make sure that 2.0*tmp will be bigger than dx (that way,
|
||||
// we ensure vx will be less than sqrt(gy * dx)).
|
||||
if (dx + dy < 0) {
|
||||
// dy is negative and |dy| > |dx|
|
||||
c = (2 * ABS(dy)) / dx;
|
||||
//tmp = ABS(dy); // ALMOST the resulting value, except for obvious rounding issues
|
||||
} else {
|
||||
// dy is either positive, or |dy| <= |dx|
|
||||
c = (dx * 3 / 2 - dy) / dx;
|
||||
|
||||
// We force c to be strictly positive
|
||||
if (c < 1)
|
||||
c = 1;
|
||||
|
||||
//tmp = dx * 3 / 2; // ALMOST the resulting value, except for obvious rounding issues
|
||||
|
||||
// FIXME: Where is the 3 coming from? Maybe they hard/coded, by "accident", that usually gy=3 ?
|
||||
// Then this choice of scalar will make t equal to roughly sqrt(dx)
|
||||
}
|
||||
}
|
||||
// POST: c >= 1
|
||||
tmp = c * dx + dy;
|
||||
// POST: (dx != 0) ==> ABS(tmp) > ABS(dx)
|
||||
// POST: (dx != 0) ==> ABS(tmp) ~>=~ ABS(dy)
|
||||
|
||||
debugC(kDebugLevelBresen, "c: %d, tmp: %d", c, tmp);
|
||||
|
||||
// Compute x step
|
||||
if (tmp != 0 && dx != 0)
|
||||
vx = (int16)((float)(dx * sqrt(gy / (2.0 * tmp))));
|
||||
else
|
||||
vx = 0;
|
||||
|
||||
// Restore the left/right direction: dx and vx should have the same sign.
|
||||
if (dxWasNegative)
|
||||
vx = -vx;
|
||||
|
||||
if ((dy < 0) && (vx == 0)) {
|
||||
// Special case: If this was a jump (almost) straight upward, i.e. dy < 0 (upward),
|
||||
// and vx == 0 (i.e. no horizontal movement, at least not after rounding), then we
|
||||
// compute vy directly.
|
||||
// For this, we drop the assumption on the linear correlation of vx and vy (obviously).
|
||||
|
||||
// FIXME: This choice of vy makes t roughly (2+sqrt(2))/gy * sqrt(dy);
|
||||
// so if gy==3, then t is roughly sqrt(dy)...
|
||||
vy = (int)sqrt((float)gy * ABS(2 * dy)) + 1;
|
||||
} else {
|
||||
// As stated above, the vertical direction is correlated to the horizontal by the
|
||||
// (non-zero) factor c.
|
||||
// Strictly speaking, we should probably be using the value of vx *before* rounding
|
||||
// it to an integer... Ah well
|
||||
vy = c * vx;
|
||||
}
|
||||
|
||||
// Always force vy to be upwards
|
||||
vy = -ABS(vy);
|
||||
|
||||
debugC(kDebugLevelBresen, "SetJump for object at %04x:%04x", PRINT_REG(object));
|
||||
debugC(kDebugLevelBresen, "xStep: %d, yStep: %d", vx, vy);
|
||||
|
||||
writeSelectorValue(segMan, object, SELECTOR(xStep), vx);
|
||||
writeSelectorValue(segMan, object, SELECTOR(yStep), vy);
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kInitBresen(EngineState *s, int argc, reg_t *argv) {
|
||||
SegManager *segMan = s->_segMan;
|
||||
reg_t mover = argv[0];
|
||||
reg_t client = readSelector(segMan, mover, SELECTOR(client));
|
||||
int16 stepFactor = (argc >= 2) ? argv[1].toUint16() : 1;
|
||||
int16 mover_x = readSelectorValue(segMan, mover, SELECTOR(x));
|
||||
int16 mover_y = readSelectorValue(segMan, mover, SELECTOR(y));
|
||||
int16 client_xStep = readSelectorValue(segMan, client, SELECTOR(xStep)) * stepFactor;
|
||||
int16 client_yStep = readSelectorValue(segMan, client, SELECTOR(yStep)) * stepFactor;
|
||||
|
||||
int16 client_step;
|
||||
if (client_xStep < client_yStep)
|
||||
client_step = client_yStep * 2;
|
||||
else
|
||||
client_step = client_xStep * 2;
|
||||
|
||||
int16 deltaX = mover_x - readSelectorValue(segMan, client, SELECTOR(x));
|
||||
int16 deltaY = mover_y - readSelectorValue(segMan, client, SELECTOR(y));
|
||||
int16 mover_dx = 0;
|
||||
int16 mover_dy = 0;
|
||||
int16 mover_i1 = 0;
|
||||
int16 mover_i2 = 0;
|
||||
int16 mover_di = 0;
|
||||
int16 mover_incr = 0;
|
||||
int16 mover_xAxis = 0;
|
||||
|
||||
while (1) {
|
||||
mover_dx = client_xStep;
|
||||
mover_dy = client_yStep;
|
||||
mover_incr = 1;
|
||||
|
||||
if (ABS(deltaX) >= ABS(deltaY)) {
|
||||
mover_xAxis = 1;
|
||||
if (deltaX < 0)
|
||||
mover_dx = -mover_dx;
|
||||
mover_dy = deltaX ? mover_dx * deltaY / deltaX : 0;
|
||||
mover_i1 = ((mover_dx * deltaY) - (mover_dy * deltaX)) * 2;
|
||||
if (deltaY < 0) {
|
||||
mover_incr = -1;
|
||||
mover_i1 = -mover_i1;
|
||||
}
|
||||
mover_i2 = mover_i1 - (deltaX * 2);
|
||||
mover_di = mover_i1 - deltaX;
|
||||
if (deltaX < 0) {
|
||||
mover_i1 = -mover_i1;
|
||||
mover_i2 = -mover_i2;
|
||||
mover_di = -mover_di;
|
||||
}
|
||||
} else {
|
||||
mover_xAxis = 0;
|
||||
if (deltaY < 0)
|
||||
mover_dy = -mover_dy;
|
||||
mover_dx = deltaY ? mover_dy * deltaX / deltaY : 0;
|
||||
mover_i1 = ((mover_dy * deltaX) - (mover_dx * deltaY)) * 2;
|
||||
if (deltaX < 0) {
|
||||
mover_incr = -1;
|
||||
mover_i1 = -mover_i1;
|
||||
}
|
||||
mover_i2 = mover_i1 - (deltaY * 2);
|
||||
mover_di = mover_i1 - deltaY;
|
||||
if (deltaY < 0) {
|
||||
mover_i1 = -mover_i1;
|
||||
mover_i2 = -mover_i2;
|
||||
mover_di = -mover_di;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (client_xStep <= client_yStep)
|
||||
break;
|
||||
if (!client_xStep)
|
||||
break;
|
||||
if (client_yStep >= ABS(mover_dy + mover_incr))
|
||||
break;
|
||||
|
||||
client_step--;
|
||||
if (!client_step)
|
||||
error("kInitBresen failed");
|
||||
client_xStep--;
|
||||
}
|
||||
|
||||
// set mover
|
||||
writeSelectorValue(segMan, mover, SELECTOR(dx), mover_dx);
|
||||
writeSelectorValue(segMan, mover, SELECTOR(dy), mover_dy);
|
||||
writeSelectorValue(segMan, mover, SELECTOR(b_i1), mover_i1);
|
||||
writeSelectorValue(segMan, mover, SELECTOR(b_i2), mover_i2);
|
||||
writeSelectorValue(segMan, mover, SELECTOR(b_di), mover_di);
|
||||
writeSelectorValue(segMan, mover, SELECTOR(b_incr), mover_incr);
|
||||
writeSelectorValue(segMan, mover, SELECTOR(b_xAxis), mover_xAxis);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) {
|
||||
SegManager *segMan = s->_segMan;
|
||||
reg_t mover = argv[0];
|
||||
reg_t client = readSelector(segMan, mover, SELECTOR(client));
|
||||
bool completed = false;
|
||||
bool handleMoveCount = g_sci->_features->handleMoveCount();
|
||||
|
||||
if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) {
|
||||
uint client_signal = readSelectorValue(segMan, client, SELECTOR(signal));
|
||||
writeSelectorValue(segMan, client, SELECTOR(signal), client_signal & ~kSignalHitObstacle);
|
||||
}
|
||||
|
||||
int16 mover_moveCnt = 1;
|
||||
int16 client_moveSpeed = 0;
|
||||
if (handleMoveCount) {
|
||||
mover_moveCnt = readSelectorValue(segMan, mover, SELECTOR(b_movCnt));
|
||||
client_moveSpeed = readSelectorValue(segMan, client, SELECTOR(moveSpeed));
|
||||
mover_moveCnt++;
|
||||
}
|
||||
|
||||
if (client_moveSpeed < mover_moveCnt) {
|
||||
mover_moveCnt = 0;
|
||||
int16 client_x = readSelectorValue(segMan, client, SELECTOR(x));
|
||||
int16 client_y = readSelectorValue(segMan, client, SELECTOR(y));
|
||||
int16 mover_x = readSelectorValue(segMan, mover, SELECTOR(x));
|
||||
int16 mover_y = readSelectorValue(segMan, mover, SELECTOR(y));
|
||||
int16 mover_xAxis = readSelectorValue(segMan, mover, SELECTOR(b_xAxis));
|
||||
int16 mover_dx = readSelectorValue(segMan, mover, SELECTOR(dx));
|
||||
int16 mover_dy = readSelectorValue(segMan, mover, SELECTOR(dy));
|
||||
int16 mover_incr = readSelectorValue(segMan, mover, SELECTOR(b_incr));
|
||||
int16 mover_i1 = readSelectorValue(segMan, mover, SELECTOR(b_i1));
|
||||
int16 mover_i2 = readSelectorValue(segMan, mover, SELECTOR(b_i2));
|
||||
int16 mover_di = readSelectorValue(segMan, mover, SELECTOR(b_di));
|
||||
int16 mover_org_i1 = mover_i1;
|
||||
int16 mover_org_i2 = mover_i2;
|
||||
int16 mover_org_di = mover_di;
|
||||
|
||||
if ((getSciVersion() >= SCI_VERSION_1_EGA_ONLY)) {
|
||||
// save current position into mover
|
||||
writeSelectorValue(segMan, mover, SELECTOR(xLast), client_x);
|
||||
writeSelectorValue(segMan, mover, SELECTOR(yLast), client_y);
|
||||
}
|
||||
|
||||
// Store backups of all client selector variables. We will restore them
|
||||
// in case of a collision.
|
||||
Object* clientObject = segMan->getObject(client);
|
||||
uint clientVarNum = clientObject->getVarCount();
|
||||
reg_t* clientBackup = new reg_t[clientVarNum];
|
||||
for (uint i = 0; i < clientVarNum; ++i)
|
||||
clientBackup[i] = clientObject->getVariable(i);
|
||||
|
||||
if ((getSciVersion() <= SCI_VERSION_1_EGA_ONLY)) {
|
||||
if (mover_xAxis) {
|
||||
if (ABS(mover_x - client_x) < ABS(mover_dx))
|
||||
completed = true;
|
||||
} else {
|
||||
if (ABS(mover_y - client_y) < ABS(mover_dy))
|
||||
completed = true;
|
||||
}
|
||||
} else {
|
||||
// SCI1EARLY+ code
|
||||
if (mover_xAxis) {
|
||||
if (ABS(mover_x - client_x) <= ABS(mover_dx))
|
||||
completed = true;
|
||||
} else {
|
||||
if (ABS(mover_y - client_y) <= ABS(mover_dy))
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
if (completed) {
|
||||
client_x = mover_x;
|
||||
client_y = mover_y;
|
||||
} else {
|
||||
client_x += mover_dx;
|
||||
client_y += mover_dy;
|
||||
if (mover_di < 0) {
|
||||
mover_di += mover_i1;
|
||||
} else {
|
||||
mover_di += mover_i2;
|
||||
if (mover_xAxis == 0) {
|
||||
client_x += mover_incr;
|
||||
} else {
|
||||
client_y += mover_incr;
|
||||
}
|
||||
}
|
||||
}
|
||||
writeSelectorValue(segMan, client, SELECTOR(x), client_x);
|
||||
writeSelectorValue(segMan, client, SELECTOR(y), client_y);
|
||||
|
||||
// Now call client::canBeHere/client::cantBehere to check for collisions
|
||||
bool collision = false;
|
||||
// adding this here for hoyle 3 to get happy. CantBeHere is a dummy in hoyle 3 and acc is != 0 so we would
|
||||
// get a collision otherwise. Resetting the result was always done in SSCI
|
||||
s->r_acc = NULL_REG;
|
||||
if (SELECTOR(cantBeHere) != -1) {
|
||||
invokeSelector(s, client, SELECTOR(cantBeHere), argc, argv);
|
||||
if (!s->r_acc.isNull())
|
||||
collision = true;
|
||||
} else {
|
||||
invokeSelector(s, client, SELECTOR(canBeHere), argc, argv);
|
||||
if (s->r_acc.isNull())
|
||||
collision = true;
|
||||
}
|
||||
|
||||
if (collision) {
|
||||
// We restore the backup of the client variables
|
||||
for (uint i = 0; i < clientVarNum; ++i)
|
||||
clientObject->getVariableRef(i) = clientBackup[i];
|
||||
|
||||
mover_i1 = mover_org_i1;
|
||||
mover_i2 = mover_org_i2;
|
||||
mover_di = mover_org_di;
|
||||
|
||||
uint16 client_signal = readSelectorValue(segMan, client, SELECTOR(signal));
|
||||
writeSelectorValue(segMan, client, SELECTOR(signal), client_signal | kSignalHitObstacle);
|
||||
}
|
||||
delete[] clientBackup;
|
||||
|
||||
writeSelectorValue(segMan, mover, SELECTOR(b_i1), mover_i1);
|
||||
writeSelectorValue(segMan, mover, SELECTOR(b_i2), mover_i2);
|
||||
writeSelectorValue(segMan, mover, SELECTOR(b_di), mover_di);
|
||||
|
||||
if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) {
|
||||
// In sci1egaonly this block of code was outside of the main if,
|
||||
// but client_x/client_y aren't set there, so it was an
|
||||
// uninitialized read in SSCI. (This issue was fixed in sci1early.)
|
||||
if (handleMoveCount)
|
||||
writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), mover_moveCnt);
|
||||
// We need to compare directly in here, complete may have happened during
|
||||
// the current move
|
||||
if ((client_x == mover_x) && (client_y == mover_y))
|
||||
invokeSelector(s, mover, SELECTOR(moveDone), argc, argv);
|
||||
return s->r_acc;
|
||||
}
|
||||
}
|
||||
|
||||
if (handleMoveCount)
|
||||
writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), mover_moveCnt);
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
extern void kDirLoopWorker(reg_t obj, uint16 angle, EngineState *s, int argc, reg_t *argv);
|
||||
extern uint16 kGetAngleWorker(int16 x1, int16 y1, int16 x2, int16 y2);
|
||||
|
||||
reg_t kDoAvoider(EngineState *s, int argc, reg_t *argv) {
|
||||
SegManager *segMan = s->_segMan;
|
||||
reg_t avoider = argv[0];
|
||||
int16 timesStep = argc > 1 ? argv[1].toUint16() : 1;
|
||||
|
||||
// Note: the avoider must be an object but it may already have been freed.
|
||||
// Avoid:doit calls kDoAvoider multiple times and any of these calls might
|
||||
// result in the avoider being disposed when invoking mover:doit.
|
||||
// This can happen in kq4 early when captured by a witch in room 57.
|
||||
if (!s->_segMan->isObject(avoider)) {
|
||||
error("DoAvoider() where avoider %04x:%04x is not an object", PRINT_REG(avoider));
|
||||
return SIGNAL_REG;
|
||||
}
|
||||
|
||||
reg_t client = readSelector(segMan, avoider, SELECTOR(client));
|
||||
reg_t mover = readSelector(segMan, client, SELECTOR(mover));
|
||||
if (mover.isNull())
|
||||
return SIGNAL_REG;
|
||||
|
||||
// call mover::doit
|
||||
invokeSelector(s, mover, SELECTOR(doit), argc, argv);
|
||||
|
||||
// Read mover again
|
||||
mover = readSelector(segMan, client, SELECTOR(mover));
|
||||
if (mover.isNull())
|
||||
return SIGNAL_REG;
|
||||
|
||||
int16 clientX = readSelectorValue(segMan, client, SELECTOR(x));
|
||||
int16 clientY = readSelectorValue(segMan, client, SELECTOR(y));
|
||||
int16 moverX = readSelectorValue(segMan, mover, SELECTOR(x));
|
||||
int16 moverY = readSelectorValue(segMan, mover, SELECTOR(y));
|
||||
int16 avoiderHeading = readSelectorValue(segMan, avoider, SELECTOR(heading));
|
||||
|
||||
// call client::isBlocked
|
||||
invokeSelector(s, client, SELECTOR(isBlocked), argc, argv);
|
||||
|
||||
if (s->r_acc.isNull()) {
|
||||
// not blocked
|
||||
if (avoiderHeading == -1)
|
||||
return SIGNAL_REG;
|
||||
avoiderHeading = -1;
|
||||
|
||||
uint16 angle = kGetAngleWorker(clientX, clientY, moverX, moverY);
|
||||
|
||||
reg_t clientLooper = readSelector(segMan, client, SELECTOR(looper));
|
||||
if (clientLooper.isNull()) {
|
||||
kDirLoopWorker(client, angle, s, argc, argv);
|
||||
} else {
|
||||
// call looper::doit
|
||||
reg_t params[2] = { make_reg(0, angle), client };
|
||||
invokeSelector(s, clientLooper, SELECTOR(doit), argc, argv, 2, params);
|
||||
}
|
||||
s->r_acc = SIGNAL_REG;
|
||||
|
||||
} else {
|
||||
// is blocked
|
||||
if (avoiderHeading == -1)
|
||||
avoiderHeading = g_sci->getRNG().getRandomBit() ? 45 : -45;
|
||||
int16 clientHeading = readSelectorValue(segMan, client, SELECTOR(heading));
|
||||
clientHeading = (clientHeading / 45) * 45;
|
||||
|
||||
int16 clientXstep = readSelectorValue(segMan, client, SELECTOR(xStep)) * timesStep;
|
||||
int16 clientYstep = readSelectorValue(segMan, client, SELECTOR(yStep)) * timesStep;
|
||||
int16 newHeading = clientHeading;
|
||||
|
||||
while (1) {
|
||||
int16 newX = clientX;
|
||||
int16 newY = clientY;
|
||||
switch (newHeading) {
|
||||
case 45:
|
||||
case 90:
|
||||
case 135:
|
||||
newX += clientXstep;
|
||||
break;
|
||||
case 225:
|
||||
case 270:
|
||||
case 315:
|
||||
newX -= clientXstep;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (newHeading) {
|
||||
case 0:
|
||||
case 45:
|
||||
case 315:
|
||||
newY -= clientYstep;
|
||||
break;
|
||||
case 135:
|
||||
case 180:
|
||||
case 225:
|
||||
newY += clientYstep;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
writeSelectorValue(segMan, client, SELECTOR(x), newX);
|
||||
writeSelectorValue(segMan, client, SELECTOR(y), newY);
|
||||
|
||||
// call client::canBeHere
|
||||
invokeSelector(s, client, SELECTOR(canBeHere), argc, argv);
|
||||
|
||||
if (!s->r_acc.isNull()) {
|
||||
s->r_acc = make_reg(0, newHeading);
|
||||
break; // break out
|
||||
}
|
||||
|
||||
newHeading += avoiderHeading;
|
||||
if (newHeading >= 360)
|
||||
newHeading -= 360;
|
||||
if (newHeading < 0)
|
||||
newHeading += 360;
|
||||
if (newHeading == clientHeading) {
|
||||
// tried everything
|
||||
writeSelectorValue(segMan, client, SELECTOR(x), clientX);
|
||||
writeSelectorValue(segMan, client, SELECTOR(y), clientY);
|
||||
s->r_acc = SIGNAL_REG;
|
||||
break; // break out
|
||||
}
|
||||
}
|
||||
}
|
||||
writeSelectorValue(segMan, avoider, SELECTOR(heading), avoiderHeading);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
217
engines/sci/engine/kparse.cpp
Normal file
217
engines/sci/engine/kparse.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* String and parser handling */
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/message.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
|
||||
//#define DEBUG_PARSER
|
||||
|
||||
namespace Sci {
|
||||
|
||||
/*************************************************************/
|
||||
/* Parser */
|
||||
/**********/
|
||||
|
||||
|
||||
reg_t kSaid(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t heap_said_block = argv[0];
|
||||
byte *said_block;
|
||||
int new_lastmatch;
|
||||
Vocabulary *voc = g_sci->getVocabulary();
|
||||
#ifdef DEBUG_PARSER
|
||||
const int debug_parser = 1;
|
||||
#else
|
||||
const int debug_parser = 0;
|
||||
#endif
|
||||
|
||||
if (!heap_said_block.getSegment())
|
||||
return NULL_REG;
|
||||
|
||||
said_block = (byte *)s->_segMan->derefBulkPtr(heap_said_block, 0);
|
||||
|
||||
if (!said_block) {
|
||||
warning("Said on non-string, pointer %04x:%04x", PRINT_REG(heap_said_block));
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_PARSER
|
||||
debugN("Said block: ");
|
||||
g_sci->getVocabulary()->debugDecipherSaidBlock(said_block);
|
||||
#endif
|
||||
|
||||
if (voc->parser_event.isNull() || (readSelectorValue(s->_segMan, voc->parser_event, SELECTOR(claimed)))) {
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
new_lastmatch = said(said_block, debug_parser);
|
||||
if (new_lastmatch != SAID_NO_MATCH) { /* Build and possibly display a parse tree */
|
||||
|
||||
#ifdef DEBUG_PARSER
|
||||
debugN("kSaid: Match.\n");
|
||||
#endif
|
||||
|
||||
s->r_acc = make_reg(0, 1);
|
||||
|
||||
if (new_lastmatch != SAID_PARTIAL_MATCH)
|
||||
writeSelectorValue(s->_segMan, voc->parser_event, SELECTOR(claimed), 1);
|
||||
|
||||
} else {
|
||||
return NULL_REG;
|
||||
}
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kParse(EngineState *s, int argc, reg_t *argv) {
|
||||
SegManager *segMan = s->_segMan;
|
||||
reg_t stringpos = argv[0];
|
||||
Common::String string = s->_segMan->getString(stringpos);
|
||||
char *error;
|
||||
reg_t event = argv[1];
|
||||
g_sci->checkVocabularySwitch();
|
||||
Vocabulary *voc = g_sci->getVocabulary();
|
||||
voc->parser_event = event;
|
||||
reg_t params[2] = { s->_segMan->getParserPtr(), stringpos };
|
||||
|
||||
ResultWordListList words;
|
||||
bool res = voc->tokenizeString(words, string.c_str(), &error);
|
||||
voc->parserIsValid = false; /* not valid */
|
||||
|
||||
if (res && !words.empty()) {
|
||||
voc->synonymizeTokens(words);
|
||||
|
||||
s->r_acc = make_reg(0, 1);
|
||||
|
||||
#ifdef DEBUG_PARSER
|
||||
debugC(kDebugLevelParser, "Parsed to the following blocks:");
|
||||
|
||||
for (ResultWordListList::const_iterator i = words.begin(); i != words.end(); ++i) {
|
||||
debugCN(2, kDebugLevelParser, " ");
|
||||
for (ResultWordList::const_iterator j = i->begin(); j != i->end(); ++j) {
|
||||
debugCN(2, kDebugLevelParser, "%sType[%04x] Group[%04x]", j == i->begin() ? "" : " / ", j->_class, j->_group);
|
||||
}
|
||||
debugCN(2, kDebugLevelParser, "\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
voc->replacePronouns(words);
|
||||
|
||||
int syntax_fail = voc->parseGNF(words);
|
||||
|
||||
if (syntax_fail) {
|
||||
s->r_acc = make_reg(0, 1);
|
||||
writeSelectorValue(segMan, event, SELECTOR(claimed), 1);
|
||||
|
||||
invokeSelector(s, g_sci->getGameObject(), SELECTOR(syntaxFail), argc, argv, 2, params);
|
||||
/* Issue warning */
|
||||
|
||||
debugC(kDebugLevelParser, "Tree building failed");
|
||||
|
||||
} else {
|
||||
voc->parserIsValid = true;
|
||||
voc->storePronounReference();
|
||||
writeSelectorValue(segMan, event, SELECTOR(claimed), 0);
|
||||
|
||||
#ifdef DEBUG_PARSER
|
||||
voc->dumpParseTree();
|
||||
#endif
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
s->r_acc = make_reg(0, 0);
|
||||
writeSelectorValue(segMan, event, SELECTOR(claimed), 1);
|
||||
|
||||
if (error) {
|
||||
s->_segMan->strcpy_(s->_segMan->getParserPtr(), error);
|
||||
debugC(kDebugLevelParser, "Word unknown: %s", error);
|
||||
/* Issue warning: */
|
||||
|
||||
invokeSelector(s, g_sci->getGameObject(), SELECTOR(wordFail), argc, argv, 2, params);
|
||||
free(error);
|
||||
return make_reg(0, 1); /* Tell them that it didn't work */
|
||||
}
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kSetSynonyms(EngineState *s, int argc, reg_t *argv) {
|
||||
SegManager *segMan = s->_segMan;
|
||||
reg_t object = argv[0];
|
||||
int numSynonyms = 0;
|
||||
Vocabulary *voc = g_sci->getVocabulary();
|
||||
|
||||
// Only SCI0-SCI1 EGA games had a parser. In newer versions, this is a stub
|
||||
if (!g_sci->hasParser())
|
||||
return s->r_acc;
|
||||
|
||||
voc->clearSynonyms();
|
||||
|
||||
List *list = s->_segMan->lookupList(readSelector(segMan, object, SELECTOR(elements)));
|
||||
Node *node = s->_segMan->lookupNode(list->first);
|
||||
|
||||
while (node) {
|
||||
reg_t objpos = node->value;
|
||||
|
||||
int script = readSelectorValue(segMan, objpos, SELECTOR(number));
|
||||
int seg = s->_segMan->getScriptSegment(script);
|
||||
|
||||
if (seg > 0)
|
||||
numSynonyms = s->_segMan->getScript(seg)->getSynonymsNr();
|
||||
|
||||
if (numSynonyms) {
|
||||
const SciSpan<const byte> &synonyms = s->_segMan->getScript(seg)->getSynonyms();
|
||||
|
||||
if (synonyms) {
|
||||
debugC(kDebugLevelParser, "Setting %d synonyms for script.%d",
|
||||
numSynonyms, script);
|
||||
|
||||
if (numSynonyms > 16384) {
|
||||
error("Segtable corruption: script.%03d has %d synonyms",
|
||||
script, numSynonyms);
|
||||
/* We used to reset the corrupted value here. I really don't think it's appropriate.
|
||||
* Lars */
|
||||
} else
|
||||
for (int i = 0; i < numSynonyms; i++) {
|
||||
synonym_t tmp;
|
||||
tmp.replaceant = synonyms.getUint16LEAt(i * 4);
|
||||
tmp.replacement = synonyms.getUint16LEAt(i * 4 + 2);
|
||||
voc->addSynonym(tmp);
|
||||
}
|
||||
} else
|
||||
warning("Synonyms of script.%03d were requested, but script is not available", script);
|
||||
|
||||
}
|
||||
|
||||
node = s->_segMan->lookupNode(node->succ);
|
||||
}
|
||||
|
||||
debugC(kDebugLevelParser, "A total of %d synonyms are active now.", numSynonyms);
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
2511
engines/sci/engine/kpathing.cpp
Normal file
2511
engines/sci/engine/kpathing.cpp
Normal file
File diff suppressed because it is too large
Load Diff
333
engines/sci/engine/kscripts.cpp
Normal file
333
engines/sci/engine/kscripts.cpp
Normal file
@@ -0,0 +1,333 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/engine/seg_manager.h"
|
||||
#include "sci/engine/script.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#ifdef ENABLE_SCI32
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/sound/audio32.h"
|
||||
#endif
|
||||
|
||||
#include "common/file.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// Loads arbitrary resources of type 'restype' with resource numbers 'resnrs'
|
||||
// This implementation ignores all resource numbers except the first one.
|
||||
reg_t kLoad(EngineState *s, int argc, reg_t *argv) {
|
||||
ResourceType restype = g_sci->getResMan()->convertResType(argv[0].toUint16());
|
||||
int resnr = argv[1].toUint16();
|
||||
|
||||
// Request to dynamically allocate hunk memory for later use
|
||||
if (restype == kResourceTypeMemory)
|
||||
return s->_segMan->allocateHunkEntry("kLoad()", resnr);
|
||||
|
||||
return make_reg(0, ((restype << 11) | resnr)); // Return the resource identifier as handle
|
||||
}
|
||||
|
||||
// Unloads an arbitrary resource of type 'restype' with resource number 'resnr'
|
||||
// behavior of this call didn't change between sci0->sci1.1 parameter wise, which means getting called with
|
||||
// 1 or 3+ parameters is not right according to sierra sci
|
||||
reg_t kUnLoad(EngineState *s, int argc, reg_t *argv) {
|
||||
// NOTE: Locked resources in SSCI could be disposed by kUnLoad regardless
|
||||
// of lock state. With this ScummVM implementation of kUnLoad, game scripts
|
||||
// that dispose locked resources via kUnLoad without unlocking them with
|
||||
// kLock will leak the resource until the engine is restarted.
|
||||
|
||||
ResourceType restype = g_sci->getResMan()->convertResType(argv[0].toUint16());
|
||||
reg_t resnr = argv[1];
|
||||
|
||||
if (restype == kResourceTypeMemory)
|
||||
s->_segMan->freeHunkEntry(resnr);
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kLock(EngineState *s, int argc, reg_t *argv) {
|
||||
// NOTE: In SSCI, kLock uses a boolean lock flag, not a lock counter.
|
||||
// ScummVM's current counter-based implementation should be better than SSCI
|
||||
// at dealing with game scripts that unintentionally lock & unlock the same
|
||||
// resource multiple times (e.g. through recursion), but it will introduce
|
||||
// memory bugs (resource leaks lasting until the engine is restarted, or
|
||||
// destruction of kernel locks that lead to a use-after-free) that are
|
||||
// masked by ResourceManager's LRU cache if scripts rely on kLock being
|
||||
// idempotent like it was in SSCI.
|
||||
//
|
||||
// Like SSCI, resource locks are not persisted in save games in ScummVM
|
||||
// until GK2, so it is also possible that kLock bugs will appear only after
|
||||
// restoring a save game.
|
||||
//
|
||||
// See also kUnLoad.
|
||||
|
||||
ResourceType type = g_sci->getResMan()->convertResType(argv[0].toUint16());
|
||||
if (type == kResourceTypeSound && getSciVersion() >= SCI_VERSION_1_1) {
|
||||
type = g_sci->_soundCmd->getSoundResourceType(argv[1].toUint16());
|
||||
}
|
||||
|
||||
const ResourceId id(type, argv[1].toUint16());
|
||||
const bool lock = argc > 2 ? argv[2].toUint16() : true;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
// SSCI GK2+SCI3 also saves lock states for View, Pic, and Sync resources,
|
||||
// but so far it seems like audio resources are the only ones that actually
|
||||
// need to be handled
|
||||
if (g_sci->_features->hasSci3Audio() && type == kResourceTypeAudio) {
|
||||
g_sci->_audio32->lockResource(id, lock);
|
||||
return s->r_acc;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (getSciVersion() == SCI_VERSION_1_1 &&
|
||||
(type == kResourceTypeAudio36 || type == kResourceTypeSync36)) {
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
if (lock) {
|
||||
g_sci->getResMan()->findResource(id, true);
|
||||
} else {
|
||||
if (getSciVersion() < SCI_VERSION_2 && id.getNumber() == 0xFFFF) {
|
||||
// Unlock all resources of the requested type
|
||||
Common::List<ResourceId> resources = g_sci->getResMan()->listResources(type);
|
||||
Common::List<ResourceId>::iterator itr;
|
||||
for (itr = resources.begin(); itr != resources.end(); ++itr) {
|
||||
Resource *res = g_sci->getResMan()->testResource(*itr);
|
||||
if (res->isLocked())
|
||||
g_sci->getResMan()->unlockResource(res);
|
||||
}
|
||||
} else {
|
||||
Resource *which = g_sci->getResMan()->findResource(id, false);
|
||||
|
||||
if (which)
|
||||
g_sci->getResMan()->unlockResource(which);
|
||||
else {
|
||||
if (id.getType() == kResourceTypeInvalid)
|
||||
warning("[resMan] Attempt to unlock resource %i of invalid type %i", id.getNumber(), argv[0].toUint16());
|
||||
else
|
||||
// Happens in CD games (e.g. LSL6CD) with the message
|
||||
// resource. It isn't fatal, and it's usually caused
|
||||
// by leftover scripts.
|
||||
debugC(kDebugLevelResMan, "[resMan] Attempt to unlock non-existent resource %s", id.toString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kResCheck(EngineState *s, int argc, reg_t *argv) {
|
||||
Resource *res = nullptr;
|
||||
ResourceType restype = g_sci->getResMan()->convertResType(argv[0].toUint16());
|
||||
|
||||
if ((restype == kResourceTypeAudio36) || (restype == kResourceTypeSync36)) {
|
||||
if (argc >= 6) {
|
||||
uint noun = argv[2].toUint16() & 0xff;
|
||||
uint verb = argv[3].toUint16() & 0xff;
|
||||
uint cond = argv[4].toUint16() & 0xff;
|
||||
uint seq = argv[5].toUint16() & 0xff;
|
||||
|
||||
res = g_sci->getResMan()->testResource(ResourceId(restype, argv[1].toUint16(), noun, verb, cond, seq));
|
||||
}
|
||||
} else {
|
||||
res = g_sci->getResMan()->testResource(ResourceId(restype, argv[1].toUint16()));
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
// At least LSL6-Hires explicitly treats wave and audio resources the same
|
||||
// in its check routine. This was removed in later interpreters. It may be
|
||||
// in others, but LSL6 is the only game known to have scripts that rely on
|
||||
// this behavior for anything except except kLoad/kUnload calls. Bug #13549
|
||||
if (g_sci->getGameId() == GID_LSL6HIRES) {
|
||||
if (restype == kResourceTypeWave && res == nullptr) {
|
||||
res = g_sci->getResMan()->testResource(ResourceId(kResourceTypeAudio, argv[1].toUint16()));
|
||||
}
|
||||
}
|
||||
|
||||
// GK2 stores some VMDs inside of resource volumes, but usually videos are
|
||||
// streamed from the filesystem.
|
||||
if (res == nullptr) {
|
||||
const char *format;
|
||||
switch (restype) {
|
||||
case kResourceTypeRobot:
|
||||
format = "%u.rbt";
|
||||
break;
|
||||
case kResourceTypeDuck:
|
||||
format = "%u.duk";
|
||||
break;
|
||||
case kResourceTypeVMD:
|
||||
format = "%u.vmd";
|
||||
break;
|
||||
default:
|
||||
format = nullptr;
|
||||
}
|
||||
|
||||
if (format) {
|
||||
const Common::Path fileName(Common::String::format(format, argv[1].toUint16()));
|
||||
return make_reg(0, Common::File::exists(fileName));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return make_reg(0, res != nullptr);
|
||||
}
|
||||
|
||||
reg_t kClone(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t parentAddr = argv[0];
|
||||
const Object *parentObj = s->_segMan->getObject(parentAddr);
|
||||
reg_t cloneAddr;
|
||||
Clone *cloneObj; // same as Object*
|
||||
|
||||
if (!parentObj) {
|
||||
error("Attempt to clone non-object/class at %04x:%04x failed", PRINT_REG(parentAddr));
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
debugC(kDebugLevelMemory, "Attempting to clone from %04x:%04x", PRINT_REG(parentAddr));
|
||||
|
||||
uint16 infoSelector = parentObj->getInfoSelector().toUint16();
|
||||
cloneObj = s->_segMan->allocateClone(&cloneAddr);
|
||||
|
||||
if (!cloneObj) {
|
||||
error("Cloning %04x:%04x failed-- internal error", PRINT_REG(parentAddr));
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
// In case the parent object is a clone itself we need to refresh our
|
||||
// pointer to it here. This is because calling allocateClone might
|
||||
// invalidate all pointers, references and iterators to data in the clones
|
||||
// segment.
|
||||
//
|
||||
// The reason why it might invalidate those is, that the segment code
|
||||
// (Table) uses Common::Array for internal storage. Common::Array now
|
||||
// might invalidate references to its contained data, when it has to
|
||||
// extend the internal storage size.
|
||||
if (infoSelector & kInfoFlagClone)
|
||||
parentObj = s->_segMan->getObject(parentAddr);
|
||||
|
||||
*cloneObj = *parentObj;
|
||||
|
||||
// Mark as clone
|
||||
infoSelector &= ~kInfoFlagClass; // remove class bit
|
||||
cloneObj->setInfoSelector(make_reg(0, infoSelector | kInfoFlagClone));
|
||||
|
||||
cloneObj->setSpeciesSelector(cloneObj->getPos());
|
||||
if (parentObj->isClass())
|
||||
cloneObj->setSuperClassSelector(parentObj->getPos());
|
||||
s->_segMan->getScript(parentObj->getPos().getSegment())->incrementLockers();
|
||||
s->_segMan->getScript(cloneObj->getPos().getSegment())->incrementLockers();
|
||||
|
||||
return cloneAddr;
|
||||
}
|
||||
|
||||
reg_t kDisposeClone(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t obj = argv[0];
|
||||
Clone *object = s->_segMan->getObject(obj);
|
||||
|
||||
if (!object) {
|
||||
error("Attempt to dispose non-class/object at %04x:%04x",
|
||||
PRINT_REG(obj));
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
// SCI uses this technique to find out, if it's a clone and if it's supposed to get freed
|
||||
// At least kq4early relies on this behavior. The scripts clone "Sound", then set bit 1 manually
|
||||
// and call kDisposeClone later. In that case we may not free it, otherwise we will run into issues
|
||||
// later, because kIsObject would then return false and Sound object wouldn't get checked.
|
||||
uint16 infoSelector = object->getInfoSelector().toUint16();
|
||||
if ((infoSelector & 3) == kInfoFlagClone)
|
||||
object->markAsFreed();
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
// Returns script dispatch address index in the supplied script
|
||||
reg_t kScriptID(EngineState *s, int argc, reg_t *argv) {
|
||||
int script = argv[0].toUint16();
|
||||
uint16 index = (argc > 1) ? argv[1].toUint16() : 0;
|
||||
|
||||
if (argv[0].getSegment())
|
||||
return argv[0];
|
||||
|
||||
SegmentId scriptSeg = s->_segMan->getScriptSegment(script, SCRIPT_GET_LOAD);
|
||||
|
||||
if (!scriptSeg)
|
||||
return NULL_REG;
|
||||
|
||||
Script *scr = s->_segMan->getScript(scriptSeg);
|
||||
|
||||
if (!scr->getExportsNr()) {
|
||||
// This is normal. Some scripts don't have a dispatch (exports) table,
|
||||
// and this call is probably used to load them in memory, ignoring
|
||||
// the return value. If only one argument is passed, this call is done
|
||||
// only to load the script in memory. Thus, don't show any warning,
|
||||
// as no return value is expected. If an export is requested, then
|
||||
// it will most certainly fail with OOB access.
|
||||
if (argc == 2)
|
||||
error("Script 0x%x does not have a dispatch table and export %d "
|
||||
"was requested from it", script, index);
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
// WORKAROUND: Avoid referencing invalid export 0 in script 601 (Snakes & Ladders) in Hoyle 3 Amiga
|
||||
if (g_sci->getGameId() == GID_HOYLE3 && g_sci->getPlatform() == Common::kPlatformAmiga && script == 601 && argc == 1)
|
||||
return NULL_REG;
|
||||
|
||||
const uint32 address = scr->validateExportFunc(index, true) + scr->getHeapOffset();
|
||||
return make_reg32(scriptSeg, address);
|
||||
}
|
||||
|
||||
reg_t kDisposeScript(EngineState *s, int argc, reg_t *argv) {
|
||||
int script = argv[0].getOffset();
|
||||
|
||||
SegmentId id = s->_segMan->getScriptSegment(script);
|
||||
Script *scr = s->_segMan->getScriptIfLoaded(id);
|
||||
if (scr && !scr->isMarkedAsDeleted()) {
|
||||
if (s->_executionStack.back().addr.pc.getSegment() != id)
|
||||
scr->setLockers(1);
|
||||
}
|
||||
|
||||
s->_segMan->uninstantiateScript(script);
|
||||
|
||||
if (argc != 2) {
|
||||
return s->r_acc;
|
||||
} else {
|
||||
return argv[1];
|
||||
}
|
||||
}
|
||||
|
||||
reg_t kIsObject(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argv[0].getOffset() == SIGNAL_OFFSET) // Treated specially
|
||||
return NULL_REG;
|
||||
else
|
||||
return make_reg(0, s->_segMan->isHeapObject(argv[0]));
|
||||
}
|
||||
|
||||
reg_t kRespondsTo(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t obj = argv[0];
|
||||
int selector = argv[1].toUint16();
|
||||
|
||||
return make_reg(0, s->_segMan->isHeapObject(obj) && lookupSelector(s->_segMan, obj, selector, nullptr, nullptr) != kSelectorNone);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
556
engines/sci/engine/ksound.cpp
Normal file
556
engines/sci/engine/ksound.cpp
Normal file
@@ -0,0 +1,556 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/vm.h" // for Object
|
||||
#include "sci/sound/audio.h"
|
||||
#ifdef ENABLE_SCI32
|
||||
#include "sci/sound/audio32.h"
|
||||
#endif
|
||||
#include "sci/sound/soundcmd.h"
|
||||
#include "sci/sound/sync.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
/**
|
||||
* Used for synthesized music playback
|
||||
*/
|
||||
reg_t kDoSound(EngineState *s, int argc, reg_t *argv) {
|
||||
if (!s)
|
||||
return make_reg(0, g_sci->_features->detectDoSoundType());
|
||||
error("not supposed to call this");
|
||||
}
|
||||
|
||||
#define CREATE_DOSOUND_FORWARD(_name_) reg_t k##_name_(EngineState *s, int argc, reg_t *argv) { return g_sci->_soundCmd->k##_name_(s, argc, argv); }
|
||||
|
||||
CREATE_DOSOUND_FORWARD(DoSoundInit)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundPlay)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundDispose)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundMute)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundStop)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundStopAll)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundPause)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundResumeAfterRestore)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundMasterVolume)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundUpdate)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundFade)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundGetPolyphony)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundUpdateCues)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundSendMidi)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundGlobalReverb)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundSetHold)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundGetAudioCapability)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundSuspend)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundSetVolume)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundSetPriority)
|
||||
CREATE_DOSOUND_FORWARD(DoSoundSetLoop)
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
reg_t kDoSoundMac32(EngineState *s, int argc, reg_t *argv) {
|
||||
// Several SCI 2.1 Middle Mac games, but not all, contain a modified kDoSound
|
||||
// in which all but eleven subops were removed, changing their subop values to
|
||||
// zero through ten. PQSWAT and HOYLE5 Solitaire restored all of the subops,
|
||||
// but kept the new values that removing caused in the first place.
|
||||
switch (argv[0].toUint16()) {
|
||||
case 0:
|
||||
return g_sci->_soundCmd->kDoSoundMasterVolume(s, argc - 1, argv + 1);
|
||||
case 1:
|
||||
return g_sci->_soundCmd->kDoSoundGetAudioCapability(s, argc - 1, argv + 1);
|
||||
case 2:
|
||||
return g_sci->_soundCmd->kDoSoundInit(s, argc - 1, argv + 1);
|
||||
case 3:
|
||||
return g_sci->_soundCmd->kDoSoundDispose(s, argc - 1, argv + 1);
|
||||
case 4:
|
||||
return g_sci->_soundCmd->kDoSoundPlay(s, argc - 1, argv + 1);
|
||||
case 5:
|
||||
return g_sci->_soundCmd->kDoSoundStop(s, argc - 1, argv + 1);
|
||||
case 6:
|
||||
return g_sci->_soundCmd->kDoSoundPause(s, argc - 1, argv + 1);
|
||||
case 7:
|
||||
return g_sci->_soundCmd->kDoSoundFade(s, argc - 1, argv + 1);
|
||||
case 8:
|
||||
return g_sci->_soundCmd->kDoSoundSetVolume(s, argc - 1, argv + 1);
|
||||
case 9:
|
||||
return g_sci->_soundCmd->kDoSoundSetLoop(s, argc - 1, argv + 1);
|
||||
case 10:
|
||||
return g_sci->_soundCmd->kDoSoundUpdateCues(s, argc - 1, argv + 1);
|
||||
// PQSWAT, HOYLE5 solitaire
|
||||
case 12: // kDoSoundRestore
|
||||
return kEmpty(s, argc - 1, argv + 1);
|
||||
case 13:
|
||||
return g_sci->_soundCmd->kDoSoundGetPolyphony(s, argc - 1, argv + 1);
|
||||
case 14:
|
||||
return g_sci->_soundCmd->kDoSoundSuspend(s, argc - 1, argv + 1);
|
||||
case 15:
|
||||
return g_sci->_soundCmd->kDoSoundSetHold(s, argc - 1, argv + 1);
|
||||
case 17:
|
||||
return g_sci->_soundCmd->kDoSoundSetPriority(s, argc - 1, argv + 1);
|
||||
case 18:
|
||||
return g_sci->_soundCmd->kDoSoundSendMidi(s, argc - 1, argv + 1);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
error("Unknown kDoSoundMac32 subop %d", argv[0].toUint16());
|
||||
return s->r_acc;
|
||||
}
|
||||
#endif
|
||||
|
||||
reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) {
|
||||
switch (argv[0].toUint16()) {
|
||||
case kSciAudioPlay: {
|
||||
if (argc < 2)
|
||||
return NULL_REG;
|
||||
|
||||
uint16 track = argv[1].toUint16();
|
||||
uint32 startFrame = (argc > 2) ? argv[2].toUint16() * 75 : 0;
|
||||
uint32 totalFrames = (argc > 3) ? argv[3].toUint16() * 75 : 0;
|
||||
|
||||
return make_reg(0, g_sci->_audio->audioCdPlay(track, startFrame, totalFrames));
|
||||
}
|
||||
case kSciAudioStop:
|
||||
g_sci->_audio->audioCdStop();
|
||||
|
||||
if (getSciVersion() == SCI_VERSION_1_1)
|
||||
return make_reg(0, 1);
|
||||
|
||||
break;
|
||||
case kSciAudioPause:
|
||||
warning("Can't pause CD Audio");
|
||||
break;
|
||||
case kSciAudioResume:
|
||||
// This seems to be hacked up to update the CD instead of resuming
|
||||
// audio like kDoAudio does.
|
||||
g_sci->_audio->audioCdUpdate();
|
||||
break;
|
||||
case kSciAudioPosition:
|
||||
return make_reg(0, g_sci->_audio->audioCdPosition());
|
||||
case kSciAudioWPlay: // CD Audio can't be preloaded
|
||||
case kSciAudioRate: // No need to set the audio rate
|
||||
case kSciAudioVolume: // The speech setting isn't used by CD Audio
|
||||
case kSciAudioLanguage: // No need to set the language
|
||||
break;
|
||||
case kSciAudioCD:
|
||||
// Init
|
||||
return make_reg(0, 1);
|
||||
default:
|
||||
error("kCdDoAudio: Unhandled case %d", argv[0].toUint16());
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for speech playback and digital soundtracks in CD games.
|
||||
* This is the SCI16 version; SCI32 is handled separately.
|
||||
*/
|
||||
reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
|
||||
// JonesCD and Mothergoose256 CD use different functions
|
||||
// based on the cdaudio.map file to use red book tracks.
|
||||
if (g_sci->_features->usesCdTrack()) {
|
||||
if (g_sci->getGameId() == GID_MOTHERGOOSE256) {
|
||||
// The CD audio version of Mothergoose256 CD is unique with a
|
||||
// custom interpreter. Instead of using AUDIO001 files for English,
|
||||
// the interpreter is hard-coded to use CD audio when the language
|
||||
// is set to English. Otherwise, it uses the normal kDoAudio code
|
||||
// to use AUDIO### files for the other four languages
|
||||
// This is transparent to the scripts; they are the same as in the
|
||||
// version that uses AUDIO001 for English and not CD Audio.
|
||||
int audioLanguage = g_sci->getResMan()->getAudioLanguage();
|
||||
bool english = (audioLanguage == K_LANG_NONE) || (audioLanguage == K_LANG_ENGLISH);
|
||||
if (english && argv[0].toUint16() != kSciAudioLanguage) {
|
||||
return kDoCdAudio(s, argc, argv);
|
||||
}
|
||||
} else {
|
||||
return kDoCdAudio(s, argc, argv);
|
||||
}
|
||||
}
|
||||
|
||||
Audio::Mixer *mixer = g_system->getMixer();
|
||||
|
||||
switch (argv[0].toUint16()) {
|
||||
case kSciAudioWPlay:
|
||||
case kSciAudioPlay: {
|
||||
uint16 module;
|
||||
uint32 number;
|
||||
|
||||
g_sci->_audio->stopAudio();
|
||||
// In SSCI, kDoAudio handles all samples, even if started by kDoSound.
|
||||
// We manage samples started by kDoSound in SoundCommandParser.
|
||||
g_sci->_soundCmd->stopAllSamples();
|
||||
|
||||
if (argc == 2) {
|
||||
module = 65535;
|
||||
number = argv[1].toUint16();
|
||||
} else if (argc == 6 || argc == 8) {
|
||||
module = argv[1].toUint16();
|
||||
number = ((argv[2].toUint16() & 0xff) << 24) |
|
||||
((argv[3].toUint16() & 0xff) << 16) |
|
||||
((argv[4].toUint16() & 0xff) << 8) |
|
||||
(argv[5].toUint16() & 0xff);
|
||||
} else {
|
||||
warning("kDoAudio: Play called with an unknown number of parameters (%d)", argc);
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
if (argv[0].toUint16() == kSciAudioPlay) {
|
||||
g_sci->_audio->incrementPlayCounter();
|
||||
}
|
||||
|
||||
debugC(kDebugLevelSound, "kDoAudio: play sample %d, module %d", number, module);
|
||||
|
||||
// return sample length in ticks
|
||||
if (argv[0].toUint16() == kSciAudioWPlay)
|
||||
return make_reg(0, g_sci->_audio->wPlayAudio(module, number));
|
||||
else
|
||||
return make_reg(0, g_sci->_audio->startAudio(module, number));
|
||||
}
|
||||
case kSciAudioStop:
|
||||
debugC(kDebugLevelSound, "kDoAudio: stop");
|
||||
g_sci->_audio->stopAudio();
|
||||
// In SSCI, kDoAudio handles all samples, even if started by kDoSound.
|
||||
// We manage samples started by kDoSound in SoundCommandParser.
|
||||
g_sci->_soundCmd->stopAllSamples();
|
||||
break;
|
||||
case kSciAudioPause:
|
||||
debugC(kDebugLevelSound, "kDoAudio: pause");
|
||||
g_sci->_audio->pauseAudio();
|
||||
break;
|
||||
case kSciAudioResume:
|
||||
debugC(kDebugLevelSound, "kDoAudio: resume");
|
||||
g_sci->_audio->resumeAudio();
|
||||
break;
|
||||
case kSciAudioPosition: {
|
||||
// SSCI queried the audio driver's AudioLoc function and returned the result.
|
||||
// The driver returned the location in ticks if audio was playing, otherwise -1.
|
||||
// The driver could only play one piece of audio at a time, and it could have
|
||||
// been playing either a digital sample from kDoSound or speech from kDoAudio.
|
||||
// We have two separate components for these interfaces, so we must check both.
|
||||
int audioPosition = g_sci->_audio->getAudioPosition();
|
||||
if (audioPosition == -1) {
|
||||
// kDoAudio is not playing speech, so now we check the kDoSound interface.
|
||||
// SoundCommandParser does not keep track of the audio position in ticks
|
||||
// for digital samples, so we can't return the real position. Scripts only
|
||||
// care about the exact tick value for speech, otherwise it only matters
|
||||
// if the result is -1 or not, so just return 1 if a sample is playing.
|
||||
if (g_sci->_soundCmd->isDigitalSamplePlaying()) {
|
||||
audioPosition = 1;
|
||||
}
|
||||
}
|
||||
return make_reg(0, audioPosition);
|
||||
}
|
||||
case kSciAudioRate:
|
||||
debugC(kDebugLevelSound, "kDoAudio: set audio rate to %d", argv[1].toUint16());
|
||||
g_sci->_audio->setAudioRate(argv[1].toUint16());
|
||||
break;
|
||||
case kSciAudioVolume: {
|
||||
int16 volume = argv[1].toUint16();
|
||||
volume = CLIP<int16>(volume, 0, AUDIO_VOLUME_MAX);
|
||||
debugC(kDebugLevelSound, "kDoAudio: set volume to %d", volume);
|
||||
mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2);
|
||||
break;
|
||||
}
|
||||
case kSciAudioLanguage:
|
||||
if (getSciVersion() == SCI_VERSION_1_1) {
|
||||
// SCI1.1: tests for digital audio support
|
||||
debugC(kDebugLevelSound, "kDoAudio: audio capability test");
|
||||
return make_reg(0, 1);
|
||||
} else {
|
||||
// SCI1: sets audio language, queries current language
|
||||
int newLanguage = argv[1].toSint16();
|
||||
int previousLanguage = g_sci->getResMan()->getAudioLanguage();
|
||||
|
||||
// Handle the CD audio version of Mothergoose256 CD.
|
||||
// See above; when the language is English, kDoCdAudio is used
|
||||
// for all subops except this one.
|
||||
if (g_sci->_features->usesCdTrack()) {
|
||||
// Unload language audio to indicate that the current language is
|
||||
// English, and return success. There are no English audio files.
|
||||
if (newLanguage == K_LANG_ENGLISH) {
|
||||
g_sci->getResMan()->unloadAudioLanguage();
|
||||
debugC(kDebugLevelSound, "kDoAudio: set language to %d", newLanguage);
|
||||
return make_reg(0, newLanguage);
|
||||
}
|
||||
if (previousLanguage == K_LANG_NONE) {
|
||||
previousLanguage = K_LANG_ENGLISH;
|
||||
}
|
||||
}
|
||||
|
||||
if (newLanguage == -1) {
|
||||
if (previousLanguage == K_LANG_NONE) {
|
||||
// Initialize default language, fall back on English.
|
||||
// The original interpreter had a language global with a hard-coded initial value.
|
||||
// For example, KQ5 PC was set to English, FM-Towns was set Japanese.
|
||||
// We initialize from getSciLanguage() to use our own default, or the launcher language
|
||||
int initialLanguage = g_sci->getSciLanguage();
|
||||
if (!g_sci->getResMan()->setAudioLanguage(initialLanguage) && initialLanguage != K_LANG_ENGLISH) {
|
||||
initialLanguage = K_LANG_ENGLISH;
|
||||
g_sci->getResMan()->setAudioLanguage(initialLanguage);
|
||||
}
|
||||
debugC(kDebugLevelSound, "kDoAudio: initialized language to %d", initialLanguage);
|
||||
return make_reg(0, initialLanguage);
|
||||
} else {
|
||||
debugC(kDebugLevelSound, "kDoAudio: current language: %d", previousLanguage);
|
||||
return make_reg(0, previousLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_sci->getResMan()->setAudioLanguage(newLanguage)) {
|
||||
debugC(kDebugLevelSound, "kDoAudio: set language to %d", newLanguage);
|
||||
return make_reg(0, newLanguage);
|
||||
} else {
|
||||
g_sci->getResMan()->setAudioLanguage(previousLanguage);
|
||||
debugC(kDebugLevelSound, "kDoAudio: error setting language: %d, using: %d", newLanguage, previousLanguage);
|
||||
return make_reg(0, previousLanguage);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kSciAudioCD:
|
||||
debugC(kDebugLevelSound, "kDoAudio: CD audio subop");
|
||||
return kDoCdAudio(s, argc - 1, argv + 1);
|
||||
|
||||
// 3 new subops in Pharkas CD (including CD demo). kDoAudio in Pharkas sits at seg026:038C
|
||||
case 11:
|
||||
// Not sure where this is used yet
|
||||
warning("kDoAudio: Unhandled case 11, %d extra arguments passed", argc - 1);
|
||||
break;
|
||||
case 12:
|
||||
// SSCI calls this function with no parameters from
|
||||
// the TalkRandCycle class and branches on the return
|
||||
// value like a boolean. The conjectured purpose of
|
||||
// this function is to ensure that the talker's mouth
|
||||
// does not move if there is read jitter (slow CD
|
||||
// drive, scratched CD). The old behavior here of not
|
||||
// doing anything caused a nonzero value to be left in
|
||||
// the accumulator by chance. This is equivalent, but
|
||||
// more explicit.
|
||||
|
||||
return make_reg(0, 1);
|
||||
case 13:
|
||||
// SSCI returns a counter that increments each time a sample
|
||||
// is played. This is used by the PointsSound and Narrator
|
||||
// classes to detect if their sound was interrupted by
|
||||
// another sample playing, since only one can play at a time.
|
||||
// The counter is incremented on each kDoAudio(Play) and on
|
||||
// each kDoSound(Play) when the sound is a sample, since SSCI
|
||||
// just passes those on to kDoAudio.
|
||||
//
|
||||
// When awarding points, the icon bar is often disabled, and
|
||||
// PointsSound:check polls its signal to detect when the
|
||||
// sound is completed so that it can re-enable the icon bar.
|
||||
// If the sound is interrupted by another sample then the icon
|
||||
// bar could remain permanently disabled, so the play counter
|
||||
// is stored before playing and PointsSound:check polls for
|
||||
// a change. The Narrator class also does this to detect
|
||||
// if speech was interrupted so that the game can continue.
|
||||
return make_reg(0, g_sci->_audio->getPlayCounter());
|
||||
default:
|
||||
warning("kDoAudio: Unhandled case %d, %d extra arguments passed", argv[0].toUint16(), argc - 1);
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kDoSync(EngineState *s, int argc, reg_t *argv) {
|
||||
switch (argv[0].toUint16()) {
|
||||
case kSciAudioSyncStart: {
|
||||
ResourceId id;
|
||||
|
||||
g_sci->_sync->stop();
|
||||
|
||||
if (argc == 3) {
|
||||
id = ResourceId(kResourceTypeSync, argv[2].toUint16());
|
||||
} else if (argc == 7) {
|
||||
if (g_sci->getGameId() == GID_SLATER && g_sci->getPlatform() == Common::kPlatformMacintosh) {
|
||||
// SLATER Macintosh does not support sync36 tuples, but the scripts
|
||||
// still pass 7 parameters because it was modified from the PC
|
||||
// version. Ignore the extra parameters as the Mac interpreter does.
|
||||
id = ResourceId(kResourceTypeSync, argv[2].toUint16());
|
||||
} else {
|
||||
id = ResourceId(kResourceTypeSync36, argv[2].toUint16(), argv[3].toUint16(), argv[4].toUint16(),
|
||||
argv[5].toUint16(), argv[6].toUint16());
|
||||
}
|
||||
} else {
|
||||
warning("kDoSync: Start called with an unknown number of parameters (%d)", argc);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
g_sci->_sync->start(id, argv[1]);
|
||||
break;
|
||||
}
|
||||
case kSciAudioSyncNext:
|
||||
g_sci->_sync->next(argv[1]);
|
||||
break;
|
||||
case kSciAudioSyncStop:
|
||||
g_sci->_sync->stop();
|
||||
break;
|
||||
default:
|
||||
error("DoSync: Unhandled subfunction %d", argv[0].toUint16());
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv) {
|
||||
if (!s)
|
||||
return make_reg(0, getSciVersion());
|
||||
error("not supposed to call this");
|
||||
}
|
||||
|
||||
reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, 0);
|
||||
}
|
||||
|
||||
reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argc == 0) {
|
||||
if (g_sci->_features->hasSci3Audio()) {
|
||||
return make_reg(0, g_sci->_audio32->getNumUnlockedChannels());
|
||||
} else {
|
||||
return make_reg(0, g_sci->_audio32->getNumActiveChannels());
|
||||
}
|
||||
}
|
||||
|
||||
return g_sci->_audio32->kernelPlay(false, s, argc, argv);
|
||||
}
|
||||
|
||||
reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argc == 0) {
|
||||
return make_reg(0, g_sci->_audio32->getNumActiveChannels());
|
||||
}
|
||||
|
||||
return g_sci->_audio32->kernelPlay(true, s, argc, argv);
|
||||
}
|
||||
|
||||
reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv) {
|
||||
return g_sci->_audio32->kernelStop(s, argc, argv);
|
||||
}
|
||||
|
||||
reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv) {
|
||||
return g_sci->_audio32->kernelPause(s, argc, argv);
|
||||
}
|
||||
|
||||
reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv) {
|
||||
return g_sci->_audio32->kernelResume(s, argc, argv);
|
||||
}
|
||||
|
||||
reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv) {
|
||||
return g_sci->_audio32->kernelPosition(s, argc, argv);
|
||||
}
|
||||
|
||||
reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argc > 0) {
|
||||
const uint16 sampleRate = argv[0].toUint16();
|
||||
if (sampleRate != 0) {
|
||||
g_sci->_audio32->setSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return make_reg(0, g_sci->_audio32->getSampleRate());
|
||||
}
|
||||
|
||||
reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv) {
|
||||
return g_sci->_audio32->kernelVolume(s, argc, argv);
|
||||
}
|
||||
|
||||
reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, 1);
|
||||
}
|
||||
|
||||
reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argc > 0) {
|
||||
const uint16 bitDepth = argv[0].toUint16();
|
||||
if (bitDepth != 0) {
|
||||
g_sci->_audio32->setBitDepth(bitDepth);
|
||||
}
|
||||
}
|
||||
|
||||
return make_reg(0, g_sci->_audio32->getBitDepth());
|
||||
}
|
||||
|
||||
reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv) {
|
||||
return g_sci->_audio32->kernelMixing(argc, argv);
|
||||
}
|
||||
|
||||
reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argc > 0) {
|
||||
const int16 numChannels = argv[0].toSint16();
|
||||
if (numChannels != 0) {
|
||||
g_sci->_audio32->setNumOutputChannels(numChannels);
|
||||
}
|
||||
}
|
||||
|
||||
return make_reg(0, g_sci->_audio32->getNumOutputChannels());
|
||||
}
|
||||
|
||||
reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argc > 0) {
|
||||
g_sci->_audio32->setPreload(argv[0].toUint16());
|
||||
}
|
||||
|
||||
return make_reg(0, g_sci->_audio32->getPreload());
|
||||
}
|
||||
|
||||
reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv) {
|
||||
return g_sci->_audio32->kernelFade(s, argc, argv);
|
||||
}
|
||||
|
||||
reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, g_sci->_audio32->hasSignal());
|
||||
}
|
||||
|
||||
reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_audio32->kernelLoop(s, argc, argv);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kDoAudioPan(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_audio32->kernelPan(s, argc, argv);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kDoAudioPanOff(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_audio32->kernelPanOff(s, argc, argv);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv) {
|
||||
// Used by script 90 of MUMG Deluxe from the main menu to toggle between
|
||||
// English and Spanish in some versions and English and Spanish and
|
||||
// French and German in others.
|
||||
const Common::Path audioDirectory(s->_segMan->getString(argv[0]));
|
||||
if (g_sci->getPlatform() == Common::kPlatformMacintosh) {
|
||||
g_sci->getResMan()->changeMacAudioDirectory(audioDirectory);
|
||||
} else {
|
||||
g_sci->getResMan()->changeAudioDirectory(audioDirectory);
|
||||
}
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
848
engines/sci/engine/kstring.cpp
Normal file
848
engines/sci/engine/kstring.cpp
Normal file
@@ -0,0 +1,848 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* String and parser handling */
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/message.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/tts.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
reg_t kStrEnd(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t address = argv[0];
|
||||
address.incOffset(s->_segMan->strlen(address));
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
reg_t kStrCat(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::String s1 = s->_segMan->getString(argv[0]);
|
||||
Common::String s2 = s->_segMan->getString(argv[1]);
|
||||
|
||||
// Japanese PC-9801 interpreter splits strings here
|
||||
// see bug #5834
|
||||
// Verified for Police Quest 2 + Quest For Glory 1
|
||||
// However Space Quest 4 PC-9801 doesn't
|
||||
if ((g_sci->getLanguage() == Common::JA_JPN)
|
||||
&& (getSciVersion() <= SCI_VERSION_01)) {
|
||||
s1 = g_sci->strSplit(s1.c_str(), nullptr);
|
||||
s2 = g_sci->strSplit(s2.c_str(), nullptr);
|
||||
}
|
||||
|
||||
s1 += s2;
|
||||
s->_segMan->strcpy_(argv[0], s1.c_str());
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
reg_t kStrCmp(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::String s1 = s->_segMan->getString(argv[0]);
|
||||
Common::String s2 = s->_segMan->getString(argv[1]);
|
||||
|
||||
int result;
|
||||
if (argc > 2) {
|
||||
result = strncmp(s1.c_str(), s2.c_str(), argv[2].toUint16());
|
||||
} else {
|
||||
result = strcmp(s1.c_str(), s2.c_str());
|
||||
}
|
||||
return make_reg(0, CLIP<int>(result, -32768, 32767));
|
||||
}
|
||||
|
||||
|
||||
reg_t kStrCpy(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argc > 2) {
|
||||
int length = argv[2].toSint16();
|
||||
|
||||
if (length >= 0)
|
||||
s->_segMan->strncpy(argv[0], argv[1], length);
|
||||
else
|
||||
s->_segMan->memcpy(argv[0], argv[1], -length);
|
||||
} else {
|
||||
s->_segMan->strcpy_(argv[0], argv[1]);
|
||||
}
|
||||
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
|
||||
reg_t kStrAt(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argv[0] == SIGNAL_REG) {
|
||||
warning("Attempt to perform kStrAt() on a signal reg");
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
SegmentRef dest_r = s->_segMan->dereference(argv[0]);
|
||||
if (!dest_r.isValid()) {
|
||||
warning("Attempt to StrAt at invalid pointer %04x:%04x", PRINT_REG(argv[0]));
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
byte value;
|
||||
byte newvalue = 0;
|
||||
uint16 offset = argv[1].toUint16();
|
||||
if (argc > 2)
|
||||
newvalue = argv[2].toSint16();
|
||||
|
||||
g_sci->_tts->setMessage(s->_segMan->getString(argv[0]));
|
||||
|
||||
// in kq5 this here gets called with offset 0xFFFF
|
||||
// (in the desert wheng getting the staff)
|
||||
if ((int)offset >= dest_r.maxSize) {
|
||||
warning("kStrAt offset %X exceeds maxSize", offset);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
// FIXME: Move this to segman
|
||||
if (dest_r.isRaw) {
|
||||
value = dest_r.raw[offset];
|
||||
if (argc > 2) /* Request to modify this char */
|
||||
dest_r.raw[offset] = newvalue;
|
||||
} else {
|
||||
if (dest_r.skipByte)
|
||||
offset++;
|
||||
|
||||
reg_t &tmp = dest_r.reg[offset / 2];
|
||||
|
||||
bool oddOffset = offset & 1;
|
||||
if (g_sci->isBE())
|
||||
oddOffset = !oddOffset;
|
||||
|
||||
if (!oddOffset) {
|
||||
value = tmp.getOffset() & 0x00ff;
|
||||
if (argc > 2) { /* Request to modify this char */
|
||||
uint16 tmpOffset = tmp.toUint16();
|
||||
tmpOffset &= 0xff00;
|
||||
tmpOffset |= newvalue;
|
||||
tmp.setOffset(tmpOffset);
|
||||
tmp.setSegment(0);
|
||||
}
|
||||
} else {
|
||||
value = tmp.getOffset() >> 8;
|
||||
if (argc > 2) { /* Request to modify this char */
|
||||
uint16 tmpOffset = tmp.toUint16();
|
||||
tmpOffset &= 0x00ff;
|
||||
tmpOffset |= newvalue << 8;
|
||||
tmp.setOffset(tmpOffset);
|
||||
tmp.setSegment(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return make_reg(0, value);
|
||||
}
|
||||
|
||||
|
||||
reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::String source_str = s->_segMan->getString(argv[0]);
|
||||
const char *source = source_str.c_str();
|
||||
|
||||
while (Common::isSpace(*source))
|
||||
source++; /* Skip whitespace */
|
||||
|
||||
int16 result = 0;
|
||||
int16 sign = 1;
|
||||
|
||||
if (*source == '-') {
|
||||
sign = -1;
|
||||
source++;
|
||||
}
|
||||
if (*source == '$') {
|
||||
// Hexadecimal input
|
||||
source++;
|
||||
char c;
|
||||
while ((c = *source++) != 0) {
|
||||
int16 x = 0;
|
||||
if ((c >= '0') && (c <= '9'))
|
||||
x = c - '0';
|
||||
else if ((c >= 'a') && (c <= 'f'))
|
||||
x = c - 'a' + 10;
|
||||
else if ((c >= 'A') && (c <= 'F'))
|
||||
x = c - 'A' + 10;
|
||||
else
|
||||
// Stop if we encounter anything other than a digit (like atoi)
|
||||
break;
|
||||
result *= 16;
|
||||
result += x;
|
||||
}
|
||||
} else {
|
||||
// Decimal input. We can not use strtol/atoi in here, because while
|
||||
// Sierra used atoi, it was a non standard compliant atoi, that didn't
|
||||
// do clipping. In SQ4 we get the door code in here and that's even
|
||||
// larger than uint32!
|
||||
char c;
|
||||
while ((c = *source++) != 0) {
|
||||
if ((c < '0') || (c > '9'))
|
||||
// Stop if we encounter anything other than a digit (like atoi)
|
||||
break;
|
||||
result *= 10;
|
||||
result += c - '0';
|
||||
}
|
||||
}
|
||||
|
||||
result *= sign;
|
||||
|
||||
return make_reg(0, result);
|
||||
}
|
||||
|
||||
|
||||
/* Format(targ_address, textresnr, index_inside_res, ...)
|
||||
** or
|
||||
** Format(targ_address, heap_text_addr, ...)
|
||||
** Formats the text from text.textresnr (offset index_inside_res) or heap_text_addr according to
|
||||
** the supplied parameters and writes it to the targ_address.
|
||||
*/
|
||||
reg_t kFormat(EngineState *s, int argc, reg_t *argv) {
|
||||
enum {
|
||||
ALIGN_NONE = 0,
|
||||
ALIGN_RIGHT = 1,
|
||||
ALIGN_LEFT = -1,
|
||||
ALIGN_CENTER = 2
|
||||
};
|
||||
|
||||
uint16 *arguments;
|
||||
reg_t dest = argv[0];
|
||||
int maxsize = 4096; /* Arbitrary... */
|
||||
char targetbuf[4096];
|
||||
char *target = targetbuf;
|
||||
reg_t position = argv[1]; /* source */
|
||||
int mode = 0;
|
||||
int paramindex = 0; /* Next parameter to evaluate */
|
||||
char xfer;
|
||||
int i;
|
||||
int startarg;
|
||||
int strLength = 0; /* Used for stuff like "%13s" */
|
||||
bool unsignedVar = false;
|
||||
|
||||
(void)maxsize;
|
||||
if (position.getSegment())
|
||||
startarg = 2;
|
||||
else {
|
||||
// WORKAROUND: QFG1 VGA Mac calls this without the first parameter (dest). It then
|
||||
// treats the source as the dest and overwrites the source string with an empty string.
|
||||
if (argc < 3)
|
||||
return NULL_REG;
|
||||
|
||||
startarg = 3; /* First parameter to use for formatting */
|
||||
}
|
||||
|
||||
int index = (startarg == 3) ? argv[2].toUint16() : 0;
|
||||
Common::String source_str = g_sci->getKernel()->lookupText(position, index);
|
||||
const char* source = source_str.c_str();
|
||||
|
||||
debugC(kDebugLevelStrings, "Formatting \"%s\"", source);
|
||||
|
||||
|
||||
arguments = (uint16 *)malloc(sizeof(uint16) * argc);
|
||||
memset(arguments, 0, sizeof(uint16) * argc);
|
||||
|
||||
for (i = startarg; i < argc; i++)
|
||||
arguments[i-startarg] = argv[i].toUint16(); /* Parameters are copied to prevent overwriting */
|
||||
|
||||
while ((xfer = *source++)) {
|
||||
if (xfer == '%') {
|
||||
if (mode == 1) {
|
||||
assert((target - targetbuf) + 2 <= maxsize);
|
||||
*target++ = '%'; /* Literal % by using "%%" */
|
||||
mode = 0;
|
||||
} else {
|
||||
mode = 1;
|
||||
strLength = 0;
|
||||
}
|
||||
} else if (mode == 1) { /* xfer != '%' */
|
||||
char fillchar = ' ';
|
||||
int align = ALIGN_NONE;
|
||||
|
||||
char *writestart = target; /* Start of the written string, used after the switch */
|
||||
|
||||
/* int writelength; -- unused atm */
|
||||
|
||||
if (Common::isDigit(xfer) || xfer == '-' || xfer == '=') {
|
||||
char *destp;
|
||||
|
||||
if (xfer == '0')
|
||||
fillchar = '0';
|
||||
else if (xfer == '=')
|
||||
align = ALIGN_CENTER;
|
||||
else if (Common::isDigit(xfer) || (xfer == '-'))
|
||||
source--; // Go to start of length argument
|
||||
|
||||
strLength = strtol(source, &destp, 10);
|
||||
|
||||
if (destp > source)
|
||||
source = destp;
|
||||
|
||||
if (strLength < 0) {
|
||||
align = ALIGN_LEFT;
|
||||
strLength = -strLength;
|
||||
} else if (align != ALIGN_CENTER)
|
||||
align = ALIGN_RIGHT;
|
||||
|
||||
xfer = *source++;
|
||||
} else
|
||||
strLength = 0;
|
||||
|
||||
assert((target - targetbuf) + strLength + 1 <= maxsize);
|
||||
|
||||
switch (xfer) {
|
||||
case 's': { /* Copy string */
|
||||
reg_t reg = argv[startarg + paramindex];
|
||||
|
||||
Common::String tempsource = g_sci->getKernel()->lookupText(reg,
|
||||
arguments[paramindex + 1]);
|
||||
int slen = tempsource.size();
|
||||
int extralen = strLength - slen;
|
||||
assert((target - targetbuf) + extralen <= maxsize);
|
||||
if (extralen < 0)
|
||||
extralen = 0;
|
||||
|
||||
if (reg.getSegment()) /* Heap address? */
|
||||
paramindex++;
|
||||
else
|
||||
paramindex += 2; /* No, text resource address */
|
||||
|
||||
switch (align) {
|
||||
|
||||
case ALIGN_NONE:
|
||||
case ALIGN_RIGHT:
|
||||
while (extralen-- > 0)
|
||||
*target++ = ' '; /* Format into the text */
|
||||
break;
|
||||
|
||||
case ALIGN_CENTER: {
|
||||
int half_extralen = extralen >> 1;
|
||||
while (half_extralen-- > 0)
|
||||
*target++ = ' '; /* Format into the text */
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
Common::strcpy_s(target, sizeof(targetbuf) - (target - targetbuf), tempsource.c_str());
|
||||
target += slen;
|
||||
|
||||
switch (align) {
|
||||
|
||||
case ALIGN_CENTER: {
|
||||
int half_extralen;
|
||||
align = 0;
|
||||
half_extralen = extralen - (extralen >> 1);
|
||||
while (half_extralen-- > 0)
|
||||
*target++ = ' '; /* Format into the text */
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
mode = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'c': { /* insert character */
|
||||
assert((target - targetbuf) + 2 <= maxsize);
|
||||
if (align >= 0)
|
||||
while (strLength-- > 1)
|
||||
*target++ = ' '; /* Format into the text */
|
||||
char argchar = arguments[paramindex++];
|
||||
if (argchar)
|
||||
*target++ = argchar;
|
||||
mode = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
case 'u':
|
||||
unsignedVar = true;
|
||||
/* fall through */
|
||||
case 'd': { /* Copy decimal */
|
||||
/* int templen; -- unused atm */
|
||||
const char *format_string = "%d";
|
||||
|
||||
if (xfer == 'x')
|
||||
format_string = "%x";
|
||||
|
||||
int val = arguments[paramindex];
|
||||
if (!unsignedVar)
|
||||
val = (int16)arguments[paramindex];
|
||||
|
||||
target += Common::sprintf_s(target, sizeof(targetbuf) - (target - targetbuf), format_string, val);
|
||||
paramindex++;
|
||||
assert((target - targetbuf) <= maxsize);
|
||||
|
||||
unsignedVar = false;
|
||||
|
||||
mode = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
*target = '%';
|
||||
target++;
|
||||
*target = xfer;
|
||||
target++;
|
||||
mode = 0;
|
||||
}
|
||||
|
||||
if (align) {
|
||||
int written = target - writestart;
|
||||
int padding = strLength - written;
|
||||
|
||||
if (padding > 0) {
|
||||
if (align > 0) {
|
||||
memmove(writestart + padding,
|
||||
writestart, written);
|
||||
memset(writestart, fillchar, padding);
|
||||
} else {
|
||||
memset(target, ' ', padding);
|
||||
}
|
||||
target += padding;
|
||||
}
|
||||
}
|
||||
} else { /* mode != 1 */
|
||||
*target = xfer;
|
||||
target++;
|
||||
}
|
||||
}
|
||||
|
||||
free(arguments);
|
||||
|
||||
*target = 0; /* Terminate string */
|
||||
|
||||
s->_segMan->strcpy_(dest, targetbuf);
|
||||
|
||||
return dest; /* Return target addr */
|
||||
}
|
||||
|
||||
reg_t kStrLen(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, s->_segMan->strlen(argv[0]));
|
||||
}
|
||||
|
||||
|
||||
reg_t kGetFarText(EngineState *s, int argc, reg_t *argv) {
|
||||
const Common::String text = g_sci->getKernel()->lookupText(make_reg(0, argv[0].toUint16()), argv[1].toUint16());
|
||||
|
||||
g_sci->_tts->setMessage(text);
|
||||
|
||||
// If the third argument is NULL, allocate memory for the destination. This
|
||||
// occurs in SCI1 Mac games. The memory will later be freed by the game's
|
||||
// scripts.
|
||||
if (argv[2] == NULL_REG)
|
||||
s->_segMan->allocDynmem(text.size() + 1, "Mac FarText", &argv[2]);
|
||||
|
||||
s->_segMan->strcpy_(argv[2], text.c_str()); // Copy the string and get return value
|
||||
return argv[2];
|
||||
}
|
||||
|
||||
#define DUMMY_MESSAGE "Message not found!"
|
||||
|
||||
enum kMessageFunc {
|
||||
K_MESSAGE_GET,
|
||||
K_MESSAGE_NEXT,
|
||||
K_MESSAGE_SIZE,
|
||||
K_MESSAGE_REFNOUN,
|
||||
K_MESSAGE_REFVERB,
|
||||
K_MESSAGE_REFCOND,
|
||||
K_MESSAGE_PUSH,
|
||||
K_MESSAGE_POP,
|
||||
K_MESSAGE_LASTMESSAGE
|
||||
};
|
||||
|
||||
reg_t kGetMessage(EngineState *s, int argc, reg_t *argv) {
|
||||
MessageTuple tuple = MessageTuple(argv[0].toUint16(), argv[2].toUint16());
|
||||
|
||||
s->_msgState->getMessage(argv[1].toUint16(), tuple, argv[3]);
|
||||
|
||||
return argv[3];
|
||||
}
|
||||
|
||||
reg_t kMessage(EngineState *s, int argc, reg_t *argv) {
|
||||
uint func = argv[0].toUint16();
|
||||
uint16 module = (argc >= 2) ? argv[1].toUint16() : 0;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() >= SCI_VERSION_2) {
|
||||
// In complete weirdness, SCI32 bumps up subops 3-8 to 4-9 and stubs off subop 3.
|
||||
if (func == 3)
|
||||
error("SCI32 kMessage(3)");
|
||||
else if (func > 3)
|
||||
func--;
|
||||
}
|
||||
#endif
|
||||
|
||||
// TODO: Perhaps fix this check, currently doesn't work with PUSH and POP subfunctions
|
||||
// Pepper uses them to handle the glossary
|
||||
// if ((func != K_MESSAGE_NEXT) && (argc < 2)) {
|
||||
// warning("Message: not enough arguments passed to subfunction %d", func);
|
||||
// return NULL_REG;
|
||||
// }
|
||||
|
||||
MessageTuple tuple;
|
||||
|
||||
if (argc >= 6)
|
||||
tuple = MessageTuple(argv[2].toUint16(), argv[3].toUint16(), argv[4].toUint16(), argv[5].toUint16());
|
||||
|
||||
switch (func) {
|
||||
case K_MESSAGE_GET:
|
||||
return make_reg(0, s->_msgState->getMessage(module, tuple, (argc == 7 ? argv[6] : NULL_REG)));
|
||||
case K_MESSAGE_NEXT:
|
||||
return make_reg(0, s->_msgState->nextMessage((argc == 2 ? argv[1] : NULL_REG)));
|
||||
case K_MESSAGE_SIZE:
|
||||
return make_reg(0, s->_msgState->messageSize(module, tuple));
|
||||
case K_MESSAGE_REFCOND:
|
||||
case K_MESSAGE_REFVERB:
|
||||
case K_MESSAGE_REFNOUN: {
|
||||
MessageTuple t;
|
||||
|
||||
if (s->_msgState->messageRef(module, tuple, t)) {
|
||||
switch (func) {
|
||||
case K_MESSAGE_REFCOND:
|
||||
return make_reg(0, t.cond);
|
||||
case K_MESSAGE_REFVERB:
|
||||
return make_reg(0, t.verb);
|
||||
case K_MESSAGE_REFNOUN:
|
||||
return make_reg(0, t.noun);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return SIGNAL_REG;
|
||||
}
|
||||
case K_MESSAGE_LASTMESSAGE: {
|
||||
MessageTuple msg;
|
||||
int lastModule;
|
||||
|
||||
s->_msgState->lastQuery(lastModule, msg);
|
||||
|
||||
bool ok = false;
|
||||
|
||||
if (s->_segMan->dereference(argv[1]).isRaw) {
|
||||
byte *buffer = s->_segMan->derefBulkPtr(argv[1], 10);
|
||||
|
||||
if (buffer) {
|
||||
ok = true;
|
||||
WRITE_LE_UINT16(buffer, lastModule);
|
||||
WRITE_LE_UINT16(buffer + 2, msg.noun);
|
||||
WRITE_LE_UINT16(buffer + 4, msg.verb);
|
||||
WRITE_LE_UINT16(buffer + 6, msg.cond);
|
||||
WRITE_LE_UINT16(buffer + 8, msg.seq);
|
||||
}
|
||||
} else {
|
||||
reg_t *buffer = s->_segMan->derefRegPtr(argv[1], 5);
|
||||
|
||||
if (buffer) {
|
||||
ok = true;
|
||||
buffer[0] = make_reg(0, lastModule);
|
||||
buffer[1] = make_reg(0, msg.noun);
|
||||
buffer[2] = make_reg(0, msg.verb);
|
||||
buffer[3] = make_reg(0, msg.cond);
|
||||
buffer[4] = make_reg(0, msg.seq);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
warning("Message: buffer %04x:%04x invalid or too small to hold the tuple", PRINT_REG(argv[1]));
|
||||
|
||||
return NULL_REG;
|
||||
}
|
||||
case K_MESSAGE_PUSH:
|
||||
s->_msgState->pushCursorStack();
|
||||
break;
|
||||
case K_MESSAGE_POP:
|
||||
s->_msgState->popCursorStack();
|
||||
break;
|
||||
default:
|
||||
warning("Message: subfunction %i invoked (not implemented)", func);
|
||||
}
|
||||
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
reg_t kSetQuitStr(EngineState *s, int argc, reg_t *argv) {
|
||||
//Common::String quitStr = s->_segMan->getString(argv[0]);
|
||||
//debug("Setting quit string to '%s'", quitStr.c_str());
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::String format = s->_segMan->getString(argv[1]);
|
||||
Common::String sep_str;
|
||||
const char *sep = nullptr;
|
||||
if (!argv[2].isNull()) {
|
||||
sep_str = s->_segMan->getString(argv[2]);
|
||||
sep = sep_str.c_str();
|
||||
}
|
||||
Common::String str = g_sci->strSplit(format.c_str(), sep);
|
||||
|
||||
// Make sure target buffer is large enough
|
||||
SegmentRef buf_r = s->_segMan->dereference(argv[0]);
|
||||
if (!buf_r.isValid() || buf_r.maxSize < (int)str.size() + 1) {
|
||||
warning("StrSplit: buffer %04x:%04x invalid or too small to hold the following text of %i bytes: '%s'",
|
||||
PRINT_REG(argv[0]), str.size() + 1, str.c_str());
|
||||
return NULL_REG;
|
||||
}
|
||||
s->_segMan->strcpy_(argv[0], str.c_str());
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
|
||||
reg_t kString(EngineState *s, int argc, reg_t *argv) {
|
||||
if (!s)
|
||||
return make_reg(0, getSciVersion());
|
||||
error("not supposed to call this");
|
||||
}
|
||||
|
||||
reg_t kStringNew(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t stringHandle;
|
||||
const uint16 size = argv[0].toUint16();
|
||||
s->_segMan->allocateArray(kArrayTypeString, size, &stringHandle);
|
||||
return stringHandle;
|
||||
}
|
||||
|
||||
reg_t kStringGetChar(EngineState *s, int argc, reg_t *argv) {
|
||||
const uint16 index = argv[1].toUint16();
|
||||
|
||||
// Game scripts may contain static raw string data
|
||||
if (!s->_segMan->isArray(argv[0])) {
|
||||
const Common::String string = s->_segMan->getString(argv[0]);
|
||||
if (index >= string.size()) {
|
||||
return make_reg(0, 0);
|
||||
}
|
||||
|
||||
return make_reg(0, (byte)string[index]);
|
||||
}
|
||||
|
||||
SciArray &array = *s->_segMan->lookupArray(argv[0]);
|
||||
|
||||
if (index >= array.size()) {
|
||||
return make_reg(0, 0);
|
||||
}
|
||||
|
||||
return array.getAsID(index);
|
||||
}
|
||||
|
||||
reg_t kStringFree(EngineState *s, int argc, reg_t *argv) {
|
||||
s->_segMan->freeArray(argv[0]);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kStringCompare(EngineState *s, int argc, reg_t *argv) {
|
||||
const Common::String string1 = s->_segMan->getString(argv[0]);
|
||||
const Common::String string2 = s->_segMan->getString(argv[1]);
|
||||
|
||||
int result;
|
||||
if (argc == 3) {
|
||||
result = strncmp(string1.c_str(), string2.c_str(), argv[2].toUint16());
|
||||
} else {
|
||||
result = strcmp(string1.c_str(), string2.c_str());
|
||||
}
|
||||
|
||||
return make_reg(0, (result > 0) - (result < 0));
|
||||
}
|
||||
|
||||
reg_t kStringLength(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, s->_segMan->getString(argv[0]).size());
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool isFlag(const char c) {
|
||||
return strchr("-+ 0#", c);
|
||||
}
|
||||
|
||||
bool isPrecision(const char c) {
|
||||
return strchr(".0123456789*", c);
|
||||
}
|
||||
|
||||
bool isWidth(const char c) {
|
||||
return strchr("0123456789*", c);
|
||||
}
|
||||
|
||||
bool isLength(const char c) {
|
||||
return strchr("hjlLtz", c);
|
||||
}
|
||||
|
||||
bool isType(const char c) {
|
||||
return strchr("dsuxXaAceEfFgGinop", c);
|
||||
}
|
||||
|
||||
bool isSignedType(const char type) {
|
||||
// For whatever reason, %d ends up being treated as unsigned in SSCI
|
||||
return type == 'i';
|
||||
}
|
||||
|
||||
bool isUnsignedType(const char type) {
|
||||
return strchr("duxXoc", type);
|
||||
}
|
||||
|
||||
bool isStringType(const char type) {
|
||||
return type == 's';
|
||||
}
|
||||
|
||||
Common::String readPlaceholder(const char *&in, reg_t arg) {
|
||||
const char *const start = in;
|
||||
|
||||
assert(*in == '%');
|
||||
++in;
|
||||
|
||||
while (isFlag(*in)) {
|
||||
++in;
|
||||
}
|
||||
while (isWidth(*in)) {
|
||||
++in;
|
||||
}
|
||||
while (isPrecision(*in)) {
|
||||
++in;
|
||||
}
|
||||
while (isLength(*in)) {
|
||||
++in;
|
||||
}
|
||||
|
||||
char format[64];
|
||||
format[0] = '\0';
|
||||
const char type = *in++;
|
||||
Common::strlcpy(format, start, MIN<size_t>(64, in - start + 1));
|
||||
|
||||
if (isType(type)) {
|
||||
if (isSignedType(type)) {
|
||||
const int value = arg.toSint16();
|
||||
return Common::String::format(format, value);
|
||||
} else if (isUnsignedType(type)) {
|
||||
const uint value = arg.toUint16();
|
||||
return Common::String::format(format, value);
|
||||
} else if (isStringType(type)) {
|
||||
Common::String value;
|
||||
SegManager *segMan = g_sci->getEngineState()->_segMan;
|
||||
if (segMan->isObject(arg)) {
|
||||
value = segMan->getString(readSelector(segMan, arg, SELECTOR(data)));
|
||||
} else {
|
||||
value = segMan->getString(arg);
|
||||
}
|
||||
return Common::String::format(format, value.c_str());
|
||||
} else {
|
||||
error("Unsupported format type %c", type);
|
||||
}
|
||||
} else {
|
||||
return Common::String::format("%s", format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common::String format(const Common::String &source, int argc, const reg_t *argv) {
|
||||
Common::String out;
|
||||
const char *in = source.c_str();
|
||||
int argIndex = 0;
|
||||
while (*in != '\0') {
|
||||
if (*in == '%') {
|
||||
if (in[1] == '%') {
|
||||
in += 2;
|
||||
out += "%";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argIndex < argc) {
|
||||
out += readPlaceholder(in, argv[argIndex++]);
|
||||
} else {
|
||||
out += readPlaceholder(in, NULL_REG);
|
||||
}
|
||||
} else {
|
||||
out += *in++;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
reg_t kStringFormat(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::Array<reg_t> args;
|
||||
args.resize(argc + 1);
|
||||
args[0] = NULL_REG;
|
||||
Common::copy(argv, argv + argc, &args[1]);
|
||||
return kStringFormatAt(s, args.size(), &args[0]);
|
||||
}
|
||||
|
||||
reg_t kStringFormatAt(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t stringHandle;
|
||||
SciArray *target;
|
||||
if (argv[0].isNull()) {
|
||||
target = s->_segMan->allocateArray(kArrayTypeString, 0, &stringHandle);
|
||||
} else {
|
||||
target = s->_segMan->lookupArray(argv[0]);
|
||||
stringHandle = argv[0];
|
||||
}
|
||||
|
||||
reg_t source = argv[1];
|
||||
// Str objects may be passed in place of direct references to string data
|
||||
if (s->_segMan->isObject(argv[1])) {
|
||||
source = readSelector(s->_segMan, argv[1], SELECTOR(data));
|
||||
}
|
||||
target->fromString(format(s->_segMan->getString(source), argc - 2, argv + 2));
|
||||
return stringHandle;
|
||||
}
|
||||
|
||||
reg_t kStringToInteger(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::String string = s->_segMan->getString(argv[0]);
|
||||
int16 result = (int16)atoi(string.c_str());
|
||||
return make_reg(0, result);
|
||||
}
|
||||
|
||||
reg_t kStringTrim(EngineState *s, int argc, reg_t *argv) {
|
||||
SciArray &array = *s->_segMan->lookupArray(argv[0]);
|
||||
const int8 flags = argv[1].toSint16();
|
||||
const char showChar = argc > 2 ? argv[2].toSint16() : '\0';
|
||||
array.trim(flags, showChar);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kStringToUpperCase(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::String string = s->_segMan->getString(argv[0]);
|
||||
string.toUppercase();
|
||||
s->_segMan->strcpy_(argv[0], string.c_str());
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
reg_t kStringToLowerCase(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::String string = s->_segMan->getString(argv[0]);
|
||||
string.toLowercase();
|
||||
s->_segMan->strcpy_(argv[0], string.c_str());
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
reg_t kStringReplaceSubstring(EngineState *s, int argc, reg_t *argv) {
|
||||
error("TODO: kStringReplaceSubstring not implemented");
|
||||
return argv[3];
|
||||
}
|
||||
|
||||
reg_t kStringReplaceSubstringEx(EngineState *s, int argc, reg_t *argv) {
|
||||
error("TODO: kStringReplaceSubstringEx not implemented");
|
||||
return argv[3];
|
||||
}
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
581
engines/sci/engine/kvideo.cpp
Normal file
581
engines/sci/engine/kvideo.cpp
Normal file
@@ -0,0 +1,581 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/graphics/helpers.h"
|
||||
#include "sci/graphics/cursor.h"
|
||||
#include "sci/graphics/drivers/gfxdriver.h"
|
||||
#include "sci/graphics/palette16.h"
|
||||
#include "sci/graphics/screen.h"
|
||||
#include "sci/util.h"
|
||||
#include "common/events.h"
|
||||
#include "common/keyboard.h"
|
||||
#include "common/span.h"
|
||||
#include "common/str.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "graphics/pixelformat.h"
|
||||
#include "graphics/surface.h"
|
||||
#include "video/video_decoder.h"
|
||||
#include "video/avi_decoder.h"
|
||||
#include "video/qt_decoder.h"
|
||||
#include "sci/video/seq_decoder.h"
|
||||
#ifdef ENABLE_SCI32
|
||||
#include "sci/engine/guest_additions.h"
|
||||
#include "sci/graphics/frameout.h"
|
||||
#include "sci/graphics/video32.h"
|
||||
#include "sci/video/robot_decoder.h"
|
||||
#endif
|
||||
|
||||
namespace Sci {
|
||||
|
||||
void playVideo(Video::VideoDecoder &videoDecoder) {
|
||||
videoDecoder.start();
|
||||
|
||||
Common::SpanOwner<SciSpan<byte> > scaleBuffer;
|
||||
byte bytesPerPixel = videoDecoder.getPixelFormat().bytesPerPixel;
|
||||
uint16 width = videoDecoder.getWidth();
|
||||
uint16 height = videoDecoder.getHeight();
|
||||
uint16 pitch = videoDecoder.getWidth() * bytesPerPixel;
|
||||
uint16 screenWidth = g_sci->_gfxScreen->getDisplayWidth();
|
||||
uint16 screenHeight = g_sci->_gfxScreen->getDisplayHeight();
|
||||
|
||||
if (screenWidth == 640 && width <= 320 && height <= 240) {
|
||||
width *= 2;
|
||||
height *= 2;
|
||||
pitch *= 2;
|
||||
uint32 numPixels = width * height * bytesPerPixel;
|
||||
scaleBuffer->allocate(numPixels, "video scale buffer");
|
||||
}
|
||||
|
||||
uint16 x = (screenWidth - width) / 2;
|
||||
uint16 y = (screenHeight - height) / 2;
|
||||
|
||||
bool skipVideo = false;
|
||||
|
||||
if (videoDecoder.hasDirtyPalette()) {
|
||||
const byte *palette = videoDecoder.getPalette();
|
||||
g_sci->_gfxScreen->setPalette(palette, 0, 255);
|
||||
}
|
||||
|
||||
while (!g_engine->shouldQuit() && !videoDecoder.endOfVideo() && !skipVideo) {
|
||||
if (videoDecoder.needsUpdate()) {
|
||||
const Graphics::Surface *frame = videoDecoder.decodeNextFrame();
|
||||
|
||||
if (frame) {
|
||||
Common::Rect rect(x, y, x+width, y+height);
|
||||
if (scaleBuffer) {
|
||||
const SciSpan<const byte> input((const byte *)frame->getPixels(), frame->w * frame->h * bytesPerPixel);
|
||||
// TODO: Probably should do aspect ratio correction in KQ6
|
||||
g_sci->_gfxScreen->scale2x(input, *scaleBuffer, videoDecoder.getWidth(), videoDecoder.getHeight(), bytesPerPixel);
|
||||
g_sci->_gfxScreen->copyVideoFrameToScreen(scaleBuffer->getUnsafeDataAt(0, pitch * height), pitch, rect);
|
||||
} else {
|
||||
g_sci->_gfxScreen->copyVideoFrameToScreen((const byte *)frame->getPixels(), frame->pitch, rect);
|
||||
}
|
||||
|
||||
if (videoDecoder.hasDirtyPalette()) {
|
||||
const byte *palette = videoDecoder.getPalette();
|
||||
g_sci->_gfxScreen->setPalette(palette, 0, 255);
|
||||
}
|
||||
|
||||
g_system->updateScreen();
|
||||
}
|
||||
}
|
||||
|
||||
Common::Event event;
|
||||
while (g_system->getEventManager()->pollEvent(event)) {
|
||||
if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP)
|
||||
skipVideo = true;
|
||||
}
|
||||
if (g_sci->getEngineState()->_delayedRestoreGameId != -1)
|
||||
skipVideo = true;
|
||||
|
||||
g_system->delayMillis(10);
|
||||
}
|
||||
}
|
||||
reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t retval = s->r_acc;
|
||||
|
||||
// Hide the cursor if it's showing and then show it again if it was
|
||||
// previously visible.
|
||||
bool reshowCursor = g_sci->_gfxCursor->isVisible();
|
||||
if (reshowCursor)
|
||||
g_sci->_gfxCursor->kernelHide();
|
||||
|
||||
Common::ScopedPtr<Video::VideoDecoder> videoDecoder;
|
||||
|
||||
bool switchedGraphicsMode = false;
|
||||
bool syncLastFrame = true;
|
||||
|
||||
if (argv[0].isPointer()) {
|
||||
Common::Path filename(s->_segMan->getString(argv[0]));
|
||||
|
||||
if (g_sci->getPlatform() == Common::kPlatformMacintosh) {
|
||||
// Mac QuickTime: the only argument is the string for the video
|
||||
videoDecoder.reset(new Video::QuickTimeDecoder());
|
||||
if (!videoDecoder->loadFile(filename)) {
|
||||
warning("Could not open '%s'", filename.toString().c_str());
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
Graphics::PixelFormat screenFormat = g_system->getScreenFormat();
|
||||
|
||||
if (videoDecoder->getPixelFormat() != screenFormat) {
|
||||
// Attempt to switch to a screen format with higher bpp
|
||||
const Common::List<Graphics::PixelFormat> supportedFormats = g_system->getSupportedFormats();
|
||||
Common::List<Graphics::PixelFormat>::const_iterator it;
|
||||
for (it = supportedFormats.begin(); it != supportedFormats.end(); ++it) {
|
||||
if (it->bytesPerPixel >= videoDecoder->getPixelFormat().bytesPerPixel) {
|
||||
screenFormat = *it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (screenFormat.isCLUT8()) {
|
||||
// We got an indexed screen format, so dither the QuickTime video.
|
||||
uint8 palette[256 * 3];
|
||||
g_sci->_gfxScreen->grabPalette(palette, 0, 256);
|
||||
videoDecoder->setDitheringPalette(palette);
|
||||
} else {
|
||||
// Init the screen again with an RGB source format.
|
||||
// This is needed so that the GFX driver is aware that we'll be
|
||||
// sending RGB instead of paletted graphics.
|
||||
g_sci->_gfxScreen->gfxDriver()->initScreen(&screenFormat);
|
||||
videoDecoder->setOutputPixelFormat(g_system->getScreenFormat());
|
||||
}
|
||||
|
||||
// Switch back to the normal screen format, once the QT video is done playing.
|
||||
// This ensures that the source graphics are in paletted format, but the screen
|
||||
// can be either in paletted or RGB format, if the user has checked the RGB
|
||||
// mode checkbox.
|
||||
switchedGraphicsMode = true;
|
||||
// Never sync the last frame for QT movies
|
||||
syncLastFrame = false;
|
||||
} else {
|
||||
// DOS SEQ
|
||||
// SEQ's are called with no subops, just the string and delay
|
||||
// Time is specified as ticks
|
||||
videoDecoder.reset(new SEQDecoder(argv[1].toUint16()));
|
||||
|
||||
if (!videoDecoder->loadFile(filename)) {
|
||||
warning("Failed to open movie file %s", filename.toString().c_str());
|
||||
videoDecoder.reset();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Windows AVI: Only used by KQ6 CD for the Sierra logo and intro cartoon.
|
||||
// The first parameter is a subop. Some of the subops set the accumulator.
|
||||
// The interpreter implements subops 0-6. KQ6 only calls 0, 1, 2, 6.
|
||||
// Subop 0: Open movie file
|
||||
// Subop 1: Setup movie playback rectangle
|
||||
// Subop 2: Play movie
|
||||
// Subop 6: Close movie file
|
||||
// We just play it on opcode 0, since the config parameters that are passed
|
||||
// to opcodes 1 and 2 aren't properly used anyway (the video will be centered,
|
||||
// regardless of any x, y, width and height settings).
|
||||
// Using any other opcode than 0 would also require unblocking the engine
|
||||
// after the movie playback like this (with <pauseToken> being the second
|
||||
// argument passed to opcode 2):
|
||||
// invokeSelector(s, <pauseToken>, g_sci->getKernel()->findSelector("cue"), argc, argv);
|
||||
switch (argv[0].toUint16()) {
|
||||
case 0: {
|
||||
Common::String filename = s->_segMan->getString(argv[1]);
|
||||
// For KQ6, this changes the vertical 200/440 upscaling to 200/400, since this is the expected behavior. Also,
|
||||
// the calculation of the scaled x/y coordinates works slightly differently compared to the normal gfx rendering.
|
||||
g_sci->_gfxScreen->gfxDriver()->setFlags(GfxDriver::kMovieMode);
|
||||
videoDecoder.reset(new Video::AVIDecoder());
|
||||
videoDecoder->setSoundType(Audio::Mixer::kSFXSoundType);
|
||||
if (!videoDecoder->loadFile(filename.c_str())) {
|
||||
warning("Failed to open movie file %s", filename.c_str());
|
||||
videoDecoder.reset();
|
||||
}
|
||||
syncLastFrame = false;
|
||||
retval = TRUE_REG;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
debug(kDebugLevelVideo, "Unhandled kShowMovie subop %d", argv[0].toUint16());
|
||||
}
|
||||
}
|
||||
|
||||
if (videoDecoder) {
|
||||
if (videoDecoder->getPixelFormat().bytesPerPixel > 1)
|
||||
syncLastFrame = false;
|
||||
|
||||
playVideo(*videoDecoder);
|
||||
|
||||
// Switch back to 8bpp if we played a true color video.
|
||||
// We also won't be copying the screen to the SCI screen.
|
||||
if (switchedGraphicsMode)
|
||||
g_sci->_gfxScreen->gfxDriver()->initScreen();
|
||||
else if (syncLastFrame) {
|
||||
g_sci->_gfxScreen->kernelSyncWithFramebuffer();
|
||||
g_sci->_gfxPalette16->kernelSyncScreenPalette();
|
||||
}
|
||||
|
||||
g_sci->_gfxScreen->gfxDriver()->clearFlags(GfxDriver::kMovieMode);
|
||||
}
|
||||
|
||||
if (reshowCursor)
|
||||
g_sci->_gfxCursor->kernelShow();
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::Path fileName(s->_segMan->getString(argv[0]));
|
||||
const int16 numTicks = argv[1].toSint16();
|
||||
const int16 x = argc > 3 ? argv[2].toSint16() : 0;
|
||||
const int16 y = argc > 3 ? argv[3].toSint16() : 0;
|
||||
|
||||
if (g_sci->getPlatform() == Common::kPlatformMacintosh) {
|
||||
g_sci->_video32->getQuickTimePlayer().play(fileName);
|
||||
} else {
|
||||
g_sci->_video32->getSEQPlayer().play(fileName, numTicks, x, y);
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kRobot(EngineState *s, int argc, reg_t *argv) {
|
||||
if (!s)
|
||||
return make_reg(0, getSciVersion());
|
||||
error("not supposed to call this");
|
||||
}
|
||||
|
||||
reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv) {
|
||||
const GuiResourceId robotId = argv[0].toUint16();
|
||||
const reg_t plane = argv[1];
|
||||
const int16 priority = argv[2].toSint16();
|
||||
const int16 x = argv[3].toSint16();
|
||||
const int16 y = argv[4].toSint16();
|
||||
const int16 scale = argc > 5 ? argv[5].toSint16() : 128;
|
||||
g_sci->_video32->getRobotPlayer().open(robotId, plane, priority, x, y, scale);
|
||||
return make_reg(0, 0);
|
||||
}
|
||||
reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv) {
|
||||
const uint16 frameNo = argv[0].toUint16();
|
||||
const uint16 newX = argc > 1 ? argv[1].toUint16() : (uint16)RobotDecoder::kUnspecified;
|
||||
const uint16 newY = argc > 1 ? argv[2].toUint16() : (uint16)RobotDecoder::kUnspecified;
|
||||
g_sci->_video32->getRobotPlayer().showFrame(frameNo, newX, newY, RobotDecoder::kUnspecified);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv) {
|
||||
Common::Rect frameRect;
|
||||
const uint16 numFramesTotal = g_sci->_video32->getRobotPlayer().getFrameSize(frameRect);
|
||||
|
||||
SciArray *outRect = s->_segMan->lookupArray(argv[0]);
|
||||
reg_t values[4] = {
|
||||
make_reg(0, frameRect.left),
|
||||
make_reg(0, frameRect.top),
|
||||
make_reg(0, frameRect.right - 1),
|
||||
make_reg(0, frameRect.bottom - 1) };
|
||||
outRect->setElements(0, 4, values);
|
||||
|
||||
return make_reg(0, numFramesTotal);
|
||||
}
|
||||
|
||||
reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getRobotPlayer().resume();
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() == RobotDecoder::kRobotStatusEnd);
|
||||
}
|
||||
|
||||
reg_t kRobotGetIsInitialized(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() != RobotDecoder::kRobotStatusUninitialized);
|
||||
}
|
||||
|
||||
reg_t kRobotClose(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getRobotPlayer().close();
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv) {
|
||||
writeSelectorValue(s->_segMan, argv[0], SELECTOR(signal), g_sci->_video32->getRobotPlayer().getCue());
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kRobotPause(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getRobotPlayer().pause();
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, g_sci->_video32->getRobotPlayer().getFrameNo());
|
||||
}
|
||||
|
||||
reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getRobotPlayer().setPriority(argv[0].toSint16());
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv) {
|
||||
if (!s)
|
||||
return make_reg(0, getSciVersion());
|
||||
error("not supposed to call this");
|
||||
}
|
||||
|
||||
reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv) {
|
||||
// SCI2.1 adds a movie ID to the call, but the movie ID is broken,
|
||||
// so just ignore it
|
||||
if (getSciVersion() > SCI_VERSION_2) {
|
||||
++argv;
|
||||
//--argc;
|
||||
}
|
||||
|
||||
const Common::Path fileName(s->_segMan->getString(argv[0]));
|
||||
return make_reg(0, g_sci->_video32->getAVIPlayer().open(fileName));
|
||||
}
|
||||
|
||||
reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv) {
|
||||
// SCI2.1 adds a movie ID to the call, but the movie ID is broken,
|
||||
// so just ignore it
|
||||
if (getSciVersion() > SCI_VERSION_2) {
|
||||
++argv;
|
||||
//--argc;
|
||||
}
|
||||
|
||||
// argv[0] is a broken x-coordinate
|
||||
// argv[1] is a broken y-coordinate
|
||||
// argv[2] is an optional broken width
|
||||
// argv[3] is an optional broken height
|
||||
const bool pixelDouble = argc > 3 && argv[2].toSint16() && argv[3].toSint16();
|
||||
return make_reg(0, g_sci->_video32->getAVIPlayer().init(pixelDouble));
|
||||
}
|
||||
|
||||
reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv) {
|
||||
if (getSciVersion() == SCI_VERSION_2) {
|
||||
AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)argv[0].toUint16();
|
||||
return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags));
|
||||
} else {
|
||||
// argv[0] is a broken movie ID
|
||||
const int16 from = argc > 2 ? argv[1].toSint16() : 0;
|
||||
const int16 to = argc > 2 ? argv[2].toSint16() : 0;
|
||||
const int16 showStyle = argc > 3 ? argv[3].toSint16() : 0;
|
||||
const bool cue = argc > 4 ? (bool)argv[4].toSint16() : false;
|
||||
return make_reg(0, g_sci->_video32->getAVIPlayer().play(from, to, showStyle, cue));
|
||||
}
|
||||
}
|
||||
|
||||
reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, g_sci->_video32->getAVIPlayer().close());
|
||||
}
|
||||
|
||||
reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, g_sci->_video32->getAVIPlayer().getDuration());
|
||||
}
|
||||
|
||||
reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv) {
|
||||
// SCI2.1 adds a movie ID to the call, but the movie ID is broken,
|
||||
// so just ignore it
|
||||
if (getSciVersion() > SCI_VERSION_2) {
|
||||
++argv;
|
||||
//--argc;
|
||||
}
|
||||
|
||||
const uint16 frameNo = argv[0].toUint16();
|
||||
return make_reg(0, g_sci->_video32->getAVIPlayer().cue(frameNo));
|
||||
}
|
||||
|
||||
reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv) {
|
||||
const int defaultFlags =
|
||||
AVIPlayer::kEventFlagEnd |
|
||||
AVIPlayer::kEventFlagEscapeKey;
|
||||
|
||||
// argv[0] is the movie number, which is not used by this method
|
||||
const AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)(argc > 1 ? argv[1].toUint16() : defaultFlags);
|
||||
|
||||
return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags));
|
||||
}
|
||||
|
||||
reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv) {
|
||||
// argv[0] is a broken movie ID
|
||||
// argv[1] is a broken x-coordinate
|
||||
// argv[2] is a broken y-coordinate
|
||||
return make_reg(0, g_sci->_video32->getAVIPlayer().init(true));
|
||||
}
|
||||
|
||||
reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) {
|
||||
if (!s)
|
||||
return make_reg(0, getSciVersion());
|
||||
error("not supposed to call this");
|
||||
}
|
||||
|
||||
reg_t kPlayVMDOpen(EngineState *s, int argc, reg_t *argv) {
|
||||
const Common::Path fileName(s->_segMan->getString(argv[0]));
|
||||
// argv[1] is an optional cache size argument which we do not use
|
||||
// const uint16 cacheSize = argc > 1 ? CLIP<int16>(argv[1].toSint16(), 16, 1024) : 0;
|
||||
const VMDPlayer::OpenFlags flags = argc > 2 ? (VMDPlayer::OpenFlags)argv[2].toUint16() : VMDPlayer::kOpenFlagNone;
|
||||
|
||||
return make_reg(0, g_sci->_video32->getVMDPlayer().open(fileName, flags));
|
||||
}
|
||||
|
||||
reg_t kPlayVMDInit(EngineState *s, int argc, reg_t *argv) {
|
||||
const int16 x = argv[0].toSint16();
|
||||
const int16 y = argv[1].toSint16();
|
||||
const VMDPlayer::PlayFlags flags = argc > 2 ? (VMDPlayer::PlayFlags)argv[2].toUint16() : VMDPlayer::kPlayFlagNone;
|
||||
int16 boostPercent;
|
||||
int16 boostStartColor;
|
||||
int16 boostEndColor;
|
||||
if (argc > 5 && (flags & VMDPlayer::kPlayFlagBoost)) {
|
||||
boostPercent = argv[3].toSint16();
|
||||
boostStartColor = argv[4].toSint16();
|
||||
boostEndColor = argv[5].toSint16();
|
||||
} else {
|
||||
boostPercent = 0;
|
||||
boostStartColor = -1;
|
||||
boostEndColor = -1;
|
||||
}
|
||||
|
||||
g_sci->_video32->getVMDPlayer().init(x, y, flags, boostPercent, boostStartColor, boostEndColor);
|
||||
|
||||
return make_reg(0, 0);
|
||||
}
|
||||
|
||||
reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, g_sci->_video32->getVMDPlayer().close());
|
||||
}
|
||||
|
||||
reg_t kPlayVMDIgnorePalettes(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getVMDPlayer().ignorePalettes();
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv) {
|
||||
return make_reg(0, g_sci->_video32->getVMDPlayer().getStatus());
|
||||
}
|
||||
|
||||
reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv) {
|
||||
if (g_sci->_guestAdditions->kPlayDuckPlayVMDHook()) {
|
||||
return make_reg(0, VMDPlayer::kEventFlagEnd);
|
||||
}
|
||||
|
||||
const VMDPlayer::EventFlags flags = (VMDPlayer::EventFlags)argv[0].toUint16();
|
||||
const int16 lastFrameNo = argc > 1 ? argv[1].toSint16() : -1;
|
||||
const int16 yieldInterval = argc > 2 ? argv[2].toSint16() : -1;
|
||||
return make_reg(0, g_sci->_video32->getVMDPlayer().kernelPlayUntilEvent(flags, lastFrameNo, yieldInterval));
|
||||
}
|
||||
|
||||
reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getVMDPlayer().setShowCursor((bool)argv[0].toUint16());
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kPlayVMDStartBlob(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getVMDPlayer().deleteBlobs();
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
reg_t kPlayVMDStopBlobs(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getVMDPlayer().deleteBlobs();
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
reg_t kPlayVMDAddBlob(EngineState *s, int argc, reg_t *argv) {
|
||||
int16 squareSize = argv[0].toSint16();
|
||||
int16 top = argv[1].toSint16();
|
||||
int16 left = argv[2].toSint16();
|
||||
int16 bottom = argv[3].toSint16();
|
||||
int16 right = argv[4].toSint16();
|
||||
int16 blobNumber = g_sci->_video32->getVMDPlayer().addBlob(squareSize, top, left, bottom, right);
|
||||
return make_reg(0, blobNumber);
|
||||
}
|
||||
|
||||
reg_t kPlayVMDDeleteBlob(EngineState *s, int argc, reg_t *argv) {
|
||||
int16 blobNumber = argv[0].toSint16();
|
||||
g_sci->_video32->getVMDPlayer().deleteBlob(blobNumber);
|
||||
return SIGNAL_REG;
|
||||
}
|
||||
|
||||
reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv) {
|
||||
const int16 scriptWidth = g_sci->_gfxFrameout->getScriptWidth();
|
||||
const int16 scriptHeight = g_sci->_gfxFrameout->getScriptHeight();
|
||||
|
||||
Common::Rect blackoutArea;
|
||||
blackoutArea.left = MAX<int16>(0, argv[0].toSint16());
|
||||
blackoutArea.top = MAX<int16>(0, argv[1].toSint16());
|
||||
blackoutArea.right = MIN<int16>(scriptWidth, argv[2].toSint16() + 1);
|
||||
blackoutArea.bottom = MIN<int16>(scriptHeight, argv[3].toSint16() + 1);
|
||||
g_sci->_video32->getVMDPlayer().setBlackoutArea(blackoutArea);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getVMDPlayer().restrictPalette(argv[0].toUint16(), argv[1].toUint16());
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kPlayVMDSetPlane(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getVMDPlayer().setPlane(argv[0].toSint16(), argc > 1 ? argv[1] : NULL_REG);
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv) {
|
||||
if (!s)
|
||||
return make_reg(0, getSciVersion());
|
||||
error("not supposed to call this");
|
||||
}
|
||||
|
||||
reg_t kPlayDuckPlay(EngineState *s, int argc, reg_t *argv) {
|
||||
if (g_sci->_guestAdditions->kPlayDuckPlayVMDHook()) {
|
||||
return NULL_REG;
|
||||
}
|
||||
kPlayDuckOpen(s, argc, argv);
|
||||
g_sci->_video32->getDuckPlayer().play(-1);
|
||||
g_sci->_video32->getDuckPlayer().close();
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
reg_t kPlayDuckSetFrameOut(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getDuckPlayer().setDoFrameOut((bool)argv[0].toUint16());
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
reg_t kPlayDuckOpen(EngineState *s, int argc, reg_t *argv) {
|
||||
const GuiResourceId resourceId = argv[0].toUint16();
|
||||
const int displayMode = argv[1].toSint16();
|
||||
const int16 x = argv[2].toSint16();
|
||||
const int16 y = argv[3].toSint16();
|
||||
// argv[4] is a cache size argument that we do not use
|
||||
g_sci->_video32->getDuckPlayer().open(resourceId, displayMode, x, y);
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
reg_t kPlayDuckClose(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getDuckPlayer().close();
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
reg_t kPlayDuckSetVolume(EngineState *s, int argc, reg_t *argv) {
|
||||
g_sci->_video32->getDuckPlayer().setVolume(argv[0].toUint16());
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
538
engines/sci/engine/message.cpp
Normal file
538
engines/sci/engine/message.cpp
Normal file
@@ -0,0 +1,538 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "sci/engine/message.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/seg_manager.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/tts.h"
|
||||
#include "sci/engine/workarounds.h"
|
||||
#include "sci/util.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
struct MessageRecord {
|
||||
MessageTuple tuple;
|
||||
MessageTuple refTuple;
|
||||
const char *string;
|
||||
uint32 length;
|
||||
byte talker;
|
||||
};
|
||||
|
||||
class MessageReader {
|
||||
public:
|
||||
bool init() {
|
||||
if (_headerSize > _data.size())
|
||||
return false;
|
||||
|
||||
// Read message count from last word in header
|
||||
_messageCount = _data.getUint16SEAt(_headerSize - 2);
|
||||
|
||||
if (_messageCount * _recordSize + _headerSize > _data.size())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool findRecord(const MessageTuple &tuple, MessageRecord &record) = 0;
|
||||
|
||||
virtual ~MessageReader() { }
|
||||
|
||||
protected:
|
||||
MessageReader(const SciSpan<const byte> &data, uint headerSize, uint recordSize)
|
||||
: _data(data), _headerSize(headerSize), _recordSize(recordSize), _messageCount(0) { }
|
||||
|
||||
const SciSpan<const byte> _data;
|
||||
const uint _headerSize;
|
||||
const uint _recordSize;
|
||||
uint _messageCount;
|
||||
};
|
||||
|
||||
class MessageReaderV2 : public MessageReader {
|
||||
public:
|
||||
MessageReaderV2(const SciSpan<const byte> &data) : MessageReader(data, 6, 4) { }
|
||||
|
||||
bool findRecord(const MessageTuple &tuple, MessageRecord &record) override {
|
||||
SciSpan<const byte> recordPtr = _data.subspan(_headerSize);
|
||||
|
||||
for (uint i = 0; i < _messageCount; i++) {
|
||||
if ((recordPtr[0] == tuple.noun) && (recordPtr[1] == tuple.verb)) {
|
||||
record.tuple = tuple;
|
||||
record.refTuple = MessageTuple();
|
||||
record.talker = 0;
|
||||
const uint16 stringOffset = recordPtr.getUint16LEAt(2);
|
||||
const uint32 maxSize = _data.size() - stringOffset;
|
||||
record.string = (const char *)_data.getUnsafeDataAt(stringOffset, maxSize);
|
||||
record.length = Common::strnlen(record.string, maxSize);
|
||||
if (record.length == maxSize) {
|
||||
warning("Message %s from %s appears truncated at %d", tuple.toString().c_str(), _data.name().c_str(), recordPtr - _data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
recordPtr += _recordSize;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class MessageReaderV3 : public MessageReader {
|
||||
public:
|
||||
MessageReaderV3(const SciSpan<const byte> &data) : MessageReader(data, 8, 10) { }
|
||||
|
||||
bool findRecord(const MessageTuple &tuple, MessageRecord &record) override {
|
||||
SciSpan<const byte> recordPtr = _data.subspan(_headerSize);
|
||||
for (uint i = 0; i < _messageCount; i++) {
|
||||
if ((recordPtr[0] == tuple.noun) && (recordPtr[1] == tuple.verb)
|
||||
&& (recordPtr[2] == tuple.cond) && (recordPtr[3] == tuple.seq)) {
|
||||
record.tuple = tuple;
|
||||
record.refTuple = MessageTuple();
|
||||
record.talker = recordPtr[4];
|
||||
const uint16 stringOffset = recordPtr.getUint16LEAt(5);
|
||||
const uint32 maxSize = _data.size() - stringOffset;
|
||||
record.string = (const char *)_data.getUnsafeDataAt(stringOffset, maxSize);
|
||||
record.length = Common::strnlen(record.string, maxSize);
|
||||
if (record.length == maxSize) {
|
||||
warning("Message %s from %s appears truncated at %d", tuple.toString().c_str(), _data.name().c_str(), recordPtr - _data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
recordPtr += _recordSize;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class MessageReaderV4 : public MessageReader {
|
||||
public:
|
||||
MessageReaderV4(const SciSpan<const byte> &data) : MessageReader(data, 10, 11) { }
|
||||
|
||||
bool findRecord(const MessageTuple &tuple, MessageRecord &record) override {
|
||||
SciSpan<const byte> recordPtr = _data.subspan(_headerSize);
|
||||
for (uint i = 0; i < _messageCount; i++) {
|
||||
if ((recordPtr[0] == tuple.noun) && (recordPtr[1] == tuple.verb)
|
||||
&& (recordPtr[2] == tuple.cond) && (recordPtr[3] == tuple.seq)) {
|
||||
record.tuple = tuple;
|
||||
record.refTuple = MessageTuple(recordPtr[7], recordPtr[8], recordPtr[9]);
|
||||
record.talker = recordPtr[4];
|
||||
const uint16 stringOffset = recordPtr.getUint16SEAt(5);
|
||||
const uint32 maxSize = _data.size() - stringOffset;
|
||||
record.string = (const char *)_data.getUnsafeDataAt(stringOffset, maxSize);
|
||||
record.length = Common::strnlen(record.string, maxSize);
|
||||
if (record.length == maxSize) {
|
||||
warning("Message %s from %s appears truncated at %d", tuple.toString().c_str(), _data.name().c_str(), recordPtr - _data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
recordPtr += _recordSize;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
// SCI32 Mac decided to add an extra byte (currently unknown in meaning) between
|
||||
// the talker and the string...
|
||||
class MessageReaderV4_MacSCI32 : public MessageReader {
|
||||
public:
|
||||
MessageReaderV4_MacSCI32(const SciSpan<const byte> &data) : MessageReader(data, 10, 12) { }
|
||||
|
||||
bool findRecord(const MessageTuple &tuple, MessageRecord &record) override {
|
||||
SciSpan<const byte> recordPtr = _data.subspan(_headerSize);
|
||||
for (uint i = 0; i < _messageCount; i++) {
|
||||
if ((recordPtr[0] == tuple.noun) && (recordPtr[1] == tuple.verb)
|
||||
&& (recordPtr[2] == tuple.cond) && (recordPtr[3] == tuple.seq)) {
|
||||
record.tuple = tuple;
|
||||
record.refTuple = MessageTuple(recordPtr[8], recordPtr[9], recordPtr[10]);
|
||||
record.talker = recordPtr[4];
|
||||
const uint16 stringOffset = recordPtr.getUint16BEAt(6);
|
||||
const uint32 maxSize = _data.size() - stringOffset;
|
||||
record.string = (const char *)_data.getUnsafeDataAt(stringOffset, maxSize);
|
||||
record.length = Common::strnlen(record.string, maxSize);
|
||||
if (record.length == maxSize) {
|
||||
warning("Message %s from %s appears truncated at %d", tuple.toString().c_str(), _data.name().c_str(), recordPtr - _data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
recordPtr += _recordSize;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
bool MessageState::getRecord(CursorStack &stack, bool recurse, MessageRecord &record) {
|
||||
if (stack.empty()) {
|
||||
// SSCI did not check for an empty stack, it would just use the first element
|
||||
// from its zero-initialized array and return false when message lookup failed.
|
||||
// We know that this occurs from crash analytics. kMessage(K_MESSAGE_NEXT)
|
||||
// somehow gets called before an initializing kMessage call. Bug #14613
|
||||
warning("Message: stack is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
// find a workaround for the requested message and use the prescribed module
|
||||
int module = stack.getModule();
|
||||
MessageTuple &tuple = stack.top();
|
||||
SciMessageWorkaroundSolution workaround = findMessageWorkaround(module, tuple.noun, tuple.verb, tuple.cond, tuple.seq);
|
||||
if (workaround.type != MSG_WORKAROUND_NONE) {
|
||||
module = workaround.module;
|
||||
}
|
||||
Resource *res = g_sci->getResMan()->findResource(ResourceId(kResourceTypeMessage, module), false);
|
||||
|
||||
if (!res) {
|
||||
warning("Failed to open message resource %d", module);
|
||||
return false;
|
||||
}
|
||||
|
||||
MessageReader *reader;
|
||||
int version = res->getUint32SEAt(0) / 1000;
|
||||
|
||||
switch (version) {
|
||||
case 2:
|
||||
reader = new MessageReaderV2(*res);
|
||||
break;
|
||||
case 3:
|
||||
reader = new MessageReaderV3(*res);
|
||||
break;
|
||||
case 4:
|
||||
#ifdef ENABLE_SCI32
|
||||
case 5: // v5 seems to be compatible with v4
|
||||
// SCI32 Mac is different than SCI32 DOS/Win here
|
||||
if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1_EARLY)
|
||||
reader = new MessageReaderV4_MacSCI32(*res);
|
||||
else
|
||||
#endif
|
||||
reader = new MessageReaderV4(*res);
|
||||
break;
|
||||
default:
|
||||
error("Message: unsupported resource version %d", version);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!reader->init()) {
|
||||
delete reader;
|
||||
|
||||
warning("Message: failed to read resource header");
|
||||
return false;
|
||||
}
|
||||
|
||||
// apply the message workaround
|
||||
if (workaround.type == MSG_WORKAROUND_REMAP) {
|
||||
// remap the request to a different message record.
|
||||
// this alters the stack, nextMessage() will return the next
|
||||
// record in the sequence following the returned record.
|
||||
stack.setModule(module);
|
||||
tuple.noun = workaround.noun;
|
||||
tuple.verb = workaround.verb;
|
||||
tuple.cond = workaround.cond;
|
||||
tuple.seq = workaround.seq;
|
||||
} else if (workaround.type == MSG_WORKAROUND_FAKE) {
|
||||
// return a fake message record hard-coded in the workaround.
|
||||
// this leaves the stack unchanged.
|
||||
record.tuple = tuple;
|
||||
record.refTuple = MessageTuple();
|
||||
record.string = workaround.text;
|
||||
record.length = strlen(workaround.text);
|
||||
record.talker = workaround.talker;
|
||||
delete reader;
|
||||
return true;
|
||||
} else if (workaround.type == MSG_WORKAROUND_EXTRACT) {
|
||||
// extract and return text from a different message record.
|
||||
// use the talker provided by the workaround since the correct value
|
||||
// could be in either, or neither, of the records.
|
||||
// this leaves the stack unchanged.
|
||||
MessageTuple textTuple(workaround.noun, workaround.verb, workaround.cond, workaround.seq);
|
||||
MessageRecord textRecord;
|
||||
if (reader->findRecord(textTuple, textRecord)) {
|
||||
uint32 textLength = (workaround.substringLength == 0) ? textRecord.length : workaround.substringLength;
|
||||
if (workaround.substringIndex + textLength <= textRecord.length) {
|
||||
record.tuple = tuple;
|
||||
record.refTuple = MessageTuple();
|
||||
record.string = textRecord.string + workaround.substringIndex;
|
||||
record.length = textLength;
|
||||
record.talker = workaround.talker;
|
||||
delete reader;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {
|
||||
MessageTuple &t = stack.top();
|
||||
|
||||
if (!reader->findRecord(t, record)) {
|
||||
// Tuple not found
|
||||
if (recurse && (stack.size() > 1)) {
|
||||
stack.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
delete reader;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recurse) {
|
||||
MessageTuple &ref = record.refTuple;
|
||||
|
||||
if (ref.noun || ref.verb || ref.cond) {
|
||||
t.seq++;
|
||||
stack.push(ref);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
delete reader;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int MessageState::getMessage(int module, const MessageTuple &t, reg_t buf) {
|
||||
_cursorStack.init(module, t);
|
||||
return nextMessage(buf);
|
||||
}
|
||||
|
||||
int MessageState::nextMessage(reg_t buf) {
|
||||
MessageRecord record;
|
||||
|
||||
if (!buf.isNull()) {
|
||||
if (getRecord(_cursorStack, true, record)) {
|
||||
outputString(buf, processString(record.string, record.length));
|
||||
_lastReturned = record.tuple;
|
||||
_lastReturnedModule = _cursorStack.getModule();
|
||||
_cursorStack.top().seq++;
|
||||
g_sci->_tts->setMessage(record.string);
|
||||
return record.talker;
|
||||
} else {
|
||||
MessageTuple t;
|
||||
if (!_cursorStack.empty()) {
|
||||
t = _cursorStack.top();
|
||||
}
|
||||
outputString(buf, Common::String::format("Msg %d: %s not found", _cursorStack.getModule(), t.toString().c_str()));
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
CursorStack stack = _cursorStack;
|
||||
|
||||
if (getRecord(stack, true, record)) {
|
||||
g_sci->_tts->setMessage(record.string);
|
||||
return record.talker;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int MessageState::messageSize(int module, MessageTuple &t) {
|
||||
CursorStack stack;
|
||||
MessageRecord record;
|
||||
|
||||
stack.init(module, t);
|
||||
if (getRecord(stack, true, record))
|
||||
return record.length + 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool MessageState::messageRef(int module, const MessageTuple &t, MessageTuple &ref) {
|
||||
CursorStack stack;
|
||||
MessageRecord record;
|
||||
|
||||
stack.init(module, t);
|
||||
if (getRecord(stack, false, record)) {
|
||||
ref = record.refTuple;
|
||||
g_sci->_tts->setMessage(record.string);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MessageState::pushCursorStack() {
|
||||
_cursorStackStack.push(_cursorStack);
|
||||
}
|
||||
|
||||
void MessageState::popCursorStack() {
|
||||
if (!_cursorStackStack.empty())
|
||||
_cursorStack = _cursorStackStack.pop();
|
||||
else
|
||||
error("Message: attempt to pop from empty stack");
|
||||
}
|
||||
|
||||
int MessageState::hexDigitToWrongInt(char h) {
|
||||
// Hex digits above 9 are incorrectly interpreted by SSCI as 11-16 instead
|
||||
// of 10-15 because of a never-fixed typo
|
||||
if ((h >= 'A') && (h <= 'F'))
|
||||
return h - 'A' + 11;
|
||||
|
||||
if ((h >= 'a') && (h <= 'f'))
|
||||
return h - 'a' + 11;
|
||||
|
||||
if ((h >= '0') && (h <= '9'))
|
||||
return h - '0';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool MessageState::stringHex(Common::String &outStr, const Common::String &inStr, uint &index) {
|
||||
// Hex escape sequences of the form \nn, where n is a hex digit
|
||||
if (inStr[index] != '\\')
|
||||
return false;
|
||||
|
||||
// Check for enough room for a hex escape sequence
|
||||
if (index + 2 >= inStr.size())
|
||||
return false;
|
||||
|
||||
int digit1 = hexDigitToWrongInt(inStr[index + 1]);
|
||||
int digit2 = hexDigitToWrongInt(inStr[index + 2]);
|
||||
|
||||
// Check for hex
|
||||
if ((digit1 == -1) || (digit2 == -1))
|
||||
return false;
|
||||
|
||||
outStr += digit1 * 16 + digit2;
|
||||
index += 3;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MessageState::stringLit(Common::String &outStr, const Common::String &inStr, uint &index) {
|
||||
// Literal escape sequences of the form \n
|
||||
if (inStr[index] != '\\')
|
||||
return false;
|
||||
|
||||
// Check for enough room for a literal escape sequence
|
||||
if (index + 1 >= inStr.size())
|
||||
return false;
|
||||
|
||||
outStr += inStr[index + 1];
|
||||
index += 2;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MessageState::stringStage(Common::String &outstr, const Common::String &inStr, uint &index) {
|
||||
// Stage directions of the form (n *), where n is anything but a digit or a lowercase character
|
||||
if (inStr[index] != '(')
|
||||
return false;
|
||||
|
||||
for (uint i = index + 1; i < inStr.size(); i++) {
|
||||
if (inStr[i] == ')') {
|
||||
// Stage direction found, skip it
|
||||
index = i + 1;
|
||||
|
||||
// Skip trailing white space
|
||||
while ((index < inStr.size()) && ((inStr[index] == '\n') || (inStr[index] == '\r') || (inStr[index] == ' ')))
|
||||
index++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// For Russian we allow all upper characters
|
||||
if (g_sci->getLanguage() == Common::RU_RUS) {
|
||||
if (((byte)inStr[i] >= 'a') || ((inStr[i] >= '0') && (inStr[i] <= '9') && (getSciVersion() < SCI_VERSION_2)))
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we find a lowercase character or a digit, it's not a stage direction
|
||||
// SCI32 seems to support having digits in stage directions
|
||||
if (((inStr[i] >= 'a') && (inStr[i] <= 'z')) || ((inStr[i] >= '0') && (inStr[i] <= '9') && (getSciVersion() < SCI_VERSION_2)))
|
||||
return false;
|
||||
|
||||
// If it contains Hebrew letters, it's not a stage direction
|
||||
if (g_sci->getLanguage() == Common::HE_ISR && (byte)inStr[i] >= 128)
|
||||
return false;
|
||||
}
|
||||
|
||||
// We ran into the end of the string without finding a closing bracket
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::String MessageState::processString(const char *s, uint32 maxLength) {
|
||||
Common::String outStr;
|
||||
Common::String inStr = Common::String(s);
|
||||
|
||||
uint index = 0;
|
||||
|
||||
while (index < inStr.size() && index < maxLength) {
|
||||
// Check for hex escape sequence.
|
||||
// SQ4CD predates this interpreter feature but has a message on the
|
||||
// hintbook screen which appears to contain hex strings and renders
|
||||
// incorrectly if converted, so exclude it. Fixes #11070
|
||||
if (g_sci->getGameId() != GID_SQ4) {
|
||||
if (stringHex(outStr, inStr, index))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for literal escape sequence
|
||||
if (stringLit(outStr, inStr, index))
|
||||
continue;
|
||||
|
||||
// Check for stage direction
|
||||
if (stringStage(outStr, inStr, index))
|
||||
continue;
|
||||
|
||||
// None of the above, copy char
|
||||
outStr += inStr[index++];
|
||||
}
|
||||
|
||||
return outStr;
|
||||
}
|
||||
|
||||
void MessageState::outputString(reg_t buf, const Common::String &str) {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() >= SCI_VERSION_2) {
|
||||
SciArray *sciString = _segMan->lookupArray(buf);
|
||||
sciString->fromString(str);
|
||||
} else {
|
||||
#endif
|
||||
SegmentRef buffer_r = _segMan->dereference(buf);
|
||||
|
||||
if ((unsigned)buffer_r.maxSize >= str.size() + 1) {
|
||||
_segMan->strcpy_(buf, str.c_str());
|
||||
} else {
|
||||
// LSL6 sets an exit text here, but the buffer size allocated
|
||||
// is too small. Don't display a warning in this case, as we
|
||||
// don't use the exit text anyway - bug report #5000
|
||||
if (g_sci->getGameId() == GID_LSL6 && str.hasPrefix("\r\n(c) 1993 Sierra On-Line, Inc")) {
|
||||
// LSL6 buggy exit text, don't show warning
|
||||
} else {
|
||||
warning("Message: buffer %04x:%04x invalid or too small to hold the following text of %i bytes: '%s'", PRINT_REG(buf), str.size() + 1, str.c_str());
|
||||
}
|
||||
|
||||
// Set buffer to empty string if possible
|
||||
if (buffer_r.maxSize > 0)
|
||||
_segMan->strcpy_(buf, "");
|
||||
}
|
||||
#ifdef ENABLE_SCI32
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MessageState::lastQuery(int &module, MessageTuple &tuple) {
|
||||
module = _lastReturnedModule;
|
||||
tuple = _lastReturned;
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
98
engines/sci/engine/message.h
Normal file
98
engines/sci/engine/message.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_MESSAGE_H
|
||||
#define SCI_ENGINE_MESSAGE_H
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/engine/vm_types.h"
|
||||
#include "common/stack.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class SegManager;
|
||||
struct MessageRecord;
|
||||
|
||||
struct MessageTuple {
|
||||
byte noun;
|
||||
byte verb;
|
||||
byte cond;
|
||||
byte seq;
|
||||
|
||||
MessageTuple(byte noun_ = 0, byte verb_ = 0, byte cond_ = 0, byte seq_ = 1)
|
||||
: noun(noun_), verb(verb_), cond(cond_), seq(seq_) { }
|
||||
|
||||
Common::String toString() const {
|
||||
return Common::String::format("noun %d, verb %d, cond %d, seq %d",
|
||||
noun, verb, cond, seq);
|
||||
}
|
||||
};
|
||||
|
||||
class CursorStack : public Common::Stack<MessageTuple> {
|
||||
public:
|
||||
CursorStack() : Common::Stack<MessageTuple>(), _module(0) {}
|
||||
|
||||
void init(int module, MessageTuple t) {
|
||||
clear();
|
||||
push(t);
|
||||
_module = module;
|
||||
}
|
||||
|
||||
int getModule() const { return _module; }
|
||||
void setModule(int module) { _module = module; }
|
||||
|
||||
private:
|
||||
int _module;
|
||||
};
|
||||
|
||||
typedef Common::Stack<CursorStack> CursorStackStack;
|
||||
|
||||
class MessageState {
|
||||
public:
|
||||
MessageState(SegManager *segMan) : _segMan(segMan), _lastReturnedModule(0) { }
|
||||
int getMessage(int module, const MessageTuple &t, reg_t buf);
|
||||
int nextMessage(reg_t buf);
|
||||
int messageSize(int module, MessageTuple &t);
|
||||
bool messageRef(int module, const MessageTuple &t, MessageTuple &ref);
|
||||
void lastQuery(int &module, MessageTuple &tuple);
|
||||
void pushCursorStack();
|
||||
void popCursorStack();
|
||||
|
||||
private:
|
||||
bool getRecord(CursorStack &stack, bool recurse, MessageRecord &record);
|
||||
void outputString(reg_t buf, const Common::String &str);
|
||||
Common::String processString(const char *s, uint32 maxLength);
|
||||
int hexDigitToWrongInt(char h);
|
||||
bool stringHex(Common::String &outStr, const Common::String &inStr, uint &index);
|
||||
bool stringLit(Common::String &outStr, const Common::String &inStr, uint &index);
|
||||
bool stringStage(Common::String &outStr, const Common::String &inStr, uint &index);
|
||||
|
||||
CursorStack _cursorStack;
|
||||
CursorStackStack _cursorStackStack;
|
||||
MessageTuple _lastReturned;
|
||||
int _lastReturnedModule;
|
||||
|
||||
SegManager *_segMan;
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_MESSAGE_H
|
||||
485
engines/sci/engine/object.cpp
Normal file
485
engines/sci/engine/object.cpp
Normal file
@@ -0,0 +1,485 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/object.h"
|
||||
#include "sci/engine/script.h"
|
||||
#include "sci/engine/seg_manager.h"
|
||||
#ifdef ENABLE_SCI32
|
||||
#include "sci/engine/features.h"
|
||||
#endif
|
||||
|
||||
namespace Sci {
|
||||
|
||||
extern bool relocateBlock(Common::Array<reg_t> &block, int block_location, SegmentId segment, int location, uint32 heapOffset);
|
||||
|
||||
void Object::init(const Script &owner, reg_t obj_pos, bool initVariables) {
|
||||
const SciSpan<const byte> buf = owner.getSpan(0);
|
||||
const SciSpan<const byte> data = owner.getSpan(obj_pos.getOffset());
|
||||
_baseObj = data;
|
||||
_pos = obj_pos;
|
||||
|
||||
// Calling Object::init more than once will screw up _baseVars/_baseMethod
|
||||
// by duplicating data. This could be turned into a soft error by warning
|
||||
// instead and clearing arrays, but there does not currently seem to be any
|
||||
// reason for an object to be initialized multiple times
|
||||
if (_baseVars.size() || _baseMethod.size()) {
|
||||
error("Attempt to reinitialize already-initialized object %04x:%04x in script %u", PRINT_REG(obj_pos), owner.getScriptNumber());
|
||||
}
|
||||
|
||||
if (getSciVersion() <= SCI_VERSION_1_LATE) {
|
||||
const SciSpan<const byte> header = buf.subspan(obj_pos.getOffset() - kOffsetHeaderSize);
|
||||
_variables.resize(header.getUint16LEAt(kOffsetHeaderSelectorCounter));
|
||||
|
||||
// Non-class objects do not have a baseVars section
|
||||
const uint16 infoSelector = data.getUint16SEAt((_offset + 2) * sizeof(uint16));
|
||||
if (infoSelector & kInfoFlagClass) {
|
||||
_baseVars.reserve(_variables.size());
|
||||
uint baseVarsOffset = _variables.size() * sizeof(uint16);
|
||||
for (uint i = 0; i < _variables.size(); ++i) {
|
||||
_baseVars.push_back(data.getUint16SEAt(baseVarsOffset));
|
||||
baseVarsOffset += sizeof(uint16);
|
||||
}
|
||||
}
|
||||
|
||||
// method block structure:
|
||||
// uint16 count;
|
||||
// uint16 selectorNos[count];
|
||||
// uint16 zero;
|
||||
// uint16 codeOffsets[count];
|
||||
|
||||
const uint16 methodBlockOffset = header.getUint16LEAt(kOffsetHeaderFunctionArea) - 2;
|
||||
_methodCount = data.getUint16LEAt(methodBlockOffset);
|
||||
const uint32 methodBlockSize = _methodCount * 2 * sizeof(uint16) + /* zero-terminator after selector list */ sizeof(uint16);
|
||||
|
||||
SciSpan<const uint16> methodEntries = data.subspan<const uint16>(methodBlockOffset + /* count */ sizeof(uint16), methodBlockSize);
|
||||
|
||||
// If this happens, then there is either a corrupt script or this code
|
||||
// misunderstands the structure of the SCI0/1 method block
|
||||
if (methodEntries.getUint16SEAt(_methodCount) != 0) {
|
||||
warning("Object %04x:%04x in script %u has a value (0x%04x) in its zero-terminator field", PRINT_REG(obj_pos), owner.getScriptNumber(), methodEntries.getUint16SEAt(_methodCount));
|
||||
}
|
||||
|
||||
_baseMethod.reserve(_methodCount * 2);
|
||||
for (uint i = 0; i < _methodCount; ++i) {
|
||||
_baseMethod.push_back(methodEntries.getUint16SEAt(0));
|
||||
_baseMethod.push_back(methodEntries.getUint16SEAt(_methodCount + /* zero-terminator */ 1));
|
||||
++methodEntries;
|
||||
}
|
||||
} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
|
||||
_variables.resize(data.getUint16SEAt(2));
|
||||
|
||||
// Non-class objects do not have a baseVars section
|
||||
const uint16 infoSelector = data.getUint16SEAt((_offset + 2) * sizeof(uint16));
|
||||
if (infoSelector & kInfoFlagClass) {
|
||||
_baseVars.reserve(_variables.size());
|
||||
uint baseVarsOffset = data.getUint16SEAt(4);
|
||||
for (uint i = 0; i < _variables.size(); ++i) {
|
||||
_baseVars.push_back(buf.getUint16SEAt(baseVarsOffset));
|
||||
baseVarsOffset += sizeof(uint16);
|
||||
}
|
||||
}
|
||||
|
||||
// method block structure:
|
||||
// uint16 count;
|
||||
// struct {
|
||||
// uint16 selectorNo;
|
||||
// uint16 codeOffset;
|
||||
// } entries[count];
|
||||
|
||||
const uint16 methodBlockOffset = data.getUint16SEAt(6);
|
||||
_methodCount = buf.getUint16SEAt(methodBlockOffset);
|
||||
|
||||
// Each entry in _baseMethod is actually two values; the first field is
|
||||
// a selector number, and the second field is an offset to the method's
|
||||
// code in the script
|
||||
const uint32 methodBlockSize = _methodCount * 2 * sizeof(uint16);
|
||||
_baseMethod.reserve(_methodCount * 2);
|
||||
|
||||
SciSpan<const uint16> methodEntries = buf.subspan<const uint16>(methodBlockOffset + /* count */ sizeof(uint16), methodBlockSize);
|
||||
for (uint i = 0; i < _methodCount; ++i) {
|
||||
_baseMethod.push_back(methodEntries.getUint16SEAt(0));
|
||||
_baseMethod.push_back(methodEntries.getUint16SEAt(1));
|
||||
methodEntries += 2;
|
||||
}
|
||||
#ifdef ENABLE_SCI32
|
||||
} else if (getSciVersion() == SCI_VERSION_3) {
|
||||
initSelectorsSci3(buf, initVariables);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Some objects, like the unnamed LarryTalker instance in LSL6hires script
|
||||
// 610, and the File class in Torin script 64993, have a `name` property
|
||||
// that is assigned dynamically by game scripts, overriding the static name
|
||||
// value that is normally created by the SC compiler. When this happens, the
|
||||
// value can be set to anything: in LSL6hires it becomes a Str object; in
|
||||
// Torin, it becomes a dynamically allocated string that is disposed before
|
||||
// the corresponding File instance is disposed.
|
||||
// To ensure `SegManager::getObjectName` works consistently and correctly,
|
||||
// without hacks to bypass unexpected/invalid types of dynamic `name` data,
|
||||
// the reg_t pointer to the original static name value for the object is
|
||||
// stored here, ensuring that it is constant and guaranteed to be either a
|
||||
// valid dereferenceable string or NULL_REG.
|
||||
if (getSciVersion() != SCI_VERSION_3) {
|
||||
const uint32 heapOffset = owner.getHeapOffset();
|
||||
const uint32 nameOffset = (obj_pos.getOffset() - heapOffset) + (_offset + 3) * sizeof(uint16);
|
||||
const uint32 relocOffset = owner.getRelocationOffset(nameOffset);
|
||||
if (relocOffset != kNoRelocation) {
|
||||
_name = make_reg(obj_pos.getSegment(), relocOffset + _baseObj.getUint16SEAt((_offset + 3) * sizeof(uint16)));
|
||||
}
|
||||
#ifdef ENABLE_SCI32
|
||||
} else if (_propertyOffsetsSci3.size()) {
|
||||
const uint32 nameOffset = _propertyOffsetsSci3[0];
|
||||
const uint32 relocOffset = owner.getRelocationOffset(nameOffset);
|
||||
if (relocOffset != kNoRelocation) {
|
||||
_name = make_reg32(obj_pos.getSegment(), relocOffset + buf.getUint16SEAt(nameOffset));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (initVariables) {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3) {
|
||||
_infoSelectorSci3 = make_reg(0, data.getUint16SEAt(10));
|
||||
} else {
|
||||
#else
|
||||
{
|
||||
#endif
|
||||
for (uint i = 0; i < _variables.size(); i++)
|
||||
_variables[i] = make_reg(0, data.getUint16SEAt(i * sizeof(uint16)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Object *Object::getClass(SegManager *segMan) const {
|
||||
return isClass() ? this : segMan->getObject(getSuperClassSelector());
|
||||
}
|
||||
|
||||
int Object::locateVarSelector(SegManager *segMan, Selector slc) const {
|
||||
const Common::Array<uint16> *buf;
|
||||
uint varCount;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3) {
|
||||
buf = &_baseVars;
|
||||
varCount = getVarCount();
|
||||
} else {
|
||||
#else
|
||||
{
|
||||
#endif
|
||||
const Object *obj = getClass(segMan);
|
||||
buf = &obj->_baseVars;
|
||||
varCount = obj->getVarCount();
|
||||
}
|
||||
|
||||
for (uint i = 0; i < varCount; i++)
|
||||
if ((*buf)[i] == slc) // Found it?
|
||||
return i; // report success
|
||||
|
||||
return -1; // Failed
|
||||
}
|
||||
|
||||
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, uint32 scriptSize) {
|
||||
assert(offset >= 0 && (uint)offset < scriptSize);
|
||||
|
||||
for (uint i = 0; i < _variables.size(); ++i) {
|
||||
if (location == _propertyOffsetsSci3[i]) {
|
||||
_variables[i].setSegment(segment);
|
||||
_variables[i].incOffset(offset);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
int Object::propertyOffsetToId(SegManager *segMan, int propertyOffset) const {
|
||||
int selectors = getVarCount();
|
||||
|
||||
if (propertyOffset < 0 || (propertyOffset >> 1) >= selectors) {
|
||||
// Scripts contain instructions with invalid properties
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (getSciVersion() < SCI_VERSION_1_1) {
|
||||
const SciSpan<const byte> selectoroffset = _baseObj.subspan(kOffsetSelectorSegment + selectors * 2);
|
||||
return selectoroffset.getUint16SEAt(propertyOffset);
|
||||
} else {
|
||||
const Object *obj = this;
|
||||
if (!isClass())
|
||||
obj = segMan->getObject(getSuperClassSelector());
|
||||
|
||||
return obj->_baseVars[propertyOffset >> 1];
|
||||
}
|
||||
}
|
||||
|
||||
void Object::initSpecies(SegManager *segMan, reg_t addr, bool applyScriptPatches) {
|
||||
uint16 speciesOffset = getSpeciesSelector().getOffset();
|
||||
|
||||
if (speciesOffset == 0xffff) // -1
|
||||
setSpeciesSelector(NULL_REG); // no species
|
||||
else {
|
||||
reg_t species = segMan->getClassAddress(speciesOffset, SCRIPT_GET_LOCK, addr.getSegment(), applyScriptPatches);
|
||||
setSpeciesSelector(species);
|
||||
}
|
||||
}
|
||||
|
||||
void Object::initSuperClass(SegManager *segMan, reg_t addr, bool applyScriptPatches) {
|
||||
uint16 superClassOffset = getSuperClassSelector().getOffset();
|
||||
|
||||
if (superClassOffset == 0xffff) // -1
|
||||
setSuperClassSelector(NULL_REG); // no superclass
|
||||
else {
|
||||
reg_t classAddress = segMan->getClassAddress(superClassOffset, SCRIPT_GET_LOCK, addr.getSegment(), applyScriptPatches);
|
||||
setSuperClassSelector(classAddress);
|
||||
}
|
||||
}
|
||||
|
||||
bool Object::initBaseObject(SegManager *segMan, reg_t addr, bool doInitSuperClass, bool applyScriptPatches) {
|
||||
const Object *baseObj = segMan->getObject(getSpeciesSelector());
|
||||
|
||||
if (baseObj) {
|
||||
uint originalVarCount = _variables.size();
|
||||
|
||||
if (_variables.size() != baseObj->getVarCount())
|
||||
_variables.resize(baseObj->getVarCount());
|
||||
// Copy base from species class, as we need its selector IDs
|
||||
_baseObj = baseObj->_baseObj;
|
||||
assert(_baseObj);
|
||||
if (doInitSuperClass)
|
||||
initSuperClass(segMan, addr, applyScriptPatches);
|
||||
|
||||
if (_variables.size() != originalVarCount) {
|
||||
// These objects are probably broken.
|
||||
// An example is 'witchCage' in script 200 in KQ5 (#4964),
|
||||
// but also 'girl' in script 216 and 'door' in script 22.
|
||||
// In LSL3 a number of sound objects trigger this right away.
|
||||
// SQ4-floppy's bug #5093 also seems related.
|
||||
|
||||
// The effect is that a number of its method selectors may be
|
||||
// treated as variable selectors, causing unpredictable effects.
|
||||
int objScript = segMan->getScript(_pos.getSegment())->getScriptNumber();
|
||||
|
||||
// We have to do a little bit of work to get the name of the object
|
||||
// before any relocations are done.
|
||||
reg_t nameReg = getNameSelector();
|
||||
const char *name;
|
||||
if (nameReg.isNull()) {
|
||||
name = "<no name>";
|
||||
} else {
|
||||
nameReg.setSegment(_pos.getSegment());
|
||||
name = segMan->derefString(nameReg);
|
||||
if (!name)
|
||||
name = "<invalid name>";
|
||||
}
|
||||
|
||||
debugC(kDebugLevelVM, "Object %04x:%04x (name %s, script %d) "
|
||||
"varnum doesn't match baseObj's: obj %d, base %d",
|
||||
PRINT_REG(_pos), name, objScript,
|
||||
originalVarCount, baseObj->getVarCount());
|
||||
|
||||
#if 0
|
||||
// We enumerate the methods selectors which could be hidden here
|
||||
if (getSciVersion() <= SCI_VERSION_2_1) {
|
||||
const SegmentRef objRef = segMan->dereference(baseObj->_pos);
|
||||
assert(objRef.isRaw);
|
||||
uint segBound = objRef.maxSize/2 - baseObj->getVarCount();
|
||||
const byte* buf = (const byte *)baseObj->_baseVars;
|
||||
if (!buf) {
|
||||
// While loading this may happen due to objects being loaded
|
||||
// out of order, and we can't proceed then, unfortunately.
|
||||
segBound = 0;
|
||||
}
|
||||
for (uint i = baseObj->getVarCount();
|
||||
i < originalVarCount && i < segBound; ++i) {
|
||||
uint16 slc = READ_SCI11ENDIAN_UINT16(buf + 2*i);
|
||||
// Skip any numbers which happen to be varselectors too
|
||||
bool found = false;
|
||||
for (uint j = 0; j < baseObj->getVarCount() && !found; ++j)
|
||||
found = READ_SCI11ENDIAN_UINT16(buf + 2*j) == slc;
|
||||
if (found) continue;
|
||||
// Skip any selectors which aren't method selectors,
|
||||
// so couldn't be mistaken for varselectors
|
||||
if (lookupSelector(segMan, _pos, slc, 0, 0) != kSelectorMethod) continue;
|
||||
warning(" Possibly affected selector: %02x (%s)", slc,
|
||||
g_sci->getKernel()->getSelectorName(slc).c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
bool Object::mustSetViewVisible(int index, const bool fromPropertyOp) const {
|
||||
if (getSciVersion() == SCI_VERSION_3) {
|
||||
// In SCI3, visible flag lookups are based on selectors
|
||||
|
||||
if (!fromPropertyOp) {
|
||||
// varindexes must be converted to selectors
|
||||
index = getVarSelector(index);
|
||||
}
|
||||
|
||||
if (index == -1) {
|
||||
error("Selector %d is invalid for object %04x:%04x", index, PRINT_REG(_pos));
|
||||
}
|
||||
|
||||
return _mustSetViewVisible[index >> 5];
|
||||
} else {
|
||||
// In SCI2, visible flag lookups are based on varindexes
|
||||
|
||||
if (fromPropertyOp) {
|
||||
// property offsets must be converted to varindexes
|
||||
assert((index % 2) == 0);
|
||||
index >>= 1;
|
||||
}
|
||||
|
||||
int minIndex, maxIndex;
|
||||
if (g_sci->_features->usesAlternateSelectors()) {
|
||||
minIndex = 24;
|
||||
maxIndex = 43;
|
||||
} else {
|
||||
minIndex = 26;
|
||||
maxIndex = 44;
|
||||
}
|
||||
|
||||
return index >= minIndex && index <= maxIndex;
|
||||
}
|
||||
}
|
||||
|
||||
void Object::initSelectorsSci3(const SciSpan<const byte> &buf, const bool initVariables) {
|
||||
enum {
|
||||
kObjectHeaderSize = 16,
|
||||
kSelectorBankSize = 256,
|
||||
kGroupSize = 64,
|
||||
kSelectorsInGroup = 32
|
||||
};
|
||||
|
||||
// The selector bank is a 256 byte array. Each byte represents a range of
|
||||
// 32 selectors. If an object has any selectors in the range then this
|
||||
// byte is a one-based index to the list of selector-group structures that
|
||||
// follows, otherwise it is zero.
|
||||
const SciSpan<const byte> selectorBank = _baseObj.subspan(kObjectHeaderSize, kSelectorBankSize);
|
||||
const SciSpan<const byte> groups = _baseObj.subspan(kObjectHeaderSize + kSelectorBankSize);
|
||||
|
||||
// Instead of testing all 256 selector bank entries, we calculate how many
|
||||
// groups there are according to the size of the selector table.
|
||||
int numGroups = g_sci->getKernel()->getSelectorNamesSize() / kSelectorsInGroup;
|
||||
if (g_sci->getKernel()->getSelectorNamesSize() % kSelectorsInGroup)
|
||||
++numGroups;
|
||||
|
||||
_mustSetViewVisible.resize(numGroups);
|
||||
|
||||
int numMethods = 0;
|
||||
int numProperties = 0;
|
||||
|
||||
// Selectors are divided into groups of 32, of which the first two selectors
|
||||
// are unused because their bytes are used for a 32-bit typeMask that indicates
|
||||
// which of the remaining 30 selectors are properties. This means that in SCI3
|
||||
// there are no selectors with the values 0, 1, 32, 33, etc.
|
||||
// We don't know beforehand how many methods and properties
|
||||
// there are, so we count them first.
|
||||
for (int bankIndex = 0; bankIndex < numGroups; ++bankIndex) {
|
||||
byte groupIndex = selectorBank[bankIndex]; // one-based index
|
||||
|
||||
if (groupIndex != 0) {
|
||||
// This object actually has selectors belonging to this group
|
||||
const SciSpan<const byte> group = groups.subspan((groupIndex - 1) * kGroupSize, kGroupSize);
|
||||
uint32 typeMask = group.getUint32SEAt(0);
|
||||
|
||||
_mustSetViewVisible[bankIndex] = (typeMask & 1);
|
||||
|
||||
for (int bit = 2; bit < kSelectorsInGroup; ++bit) {
|
||||
uint16 value = group.getUint16SEAt(bit * sizeof(uint16));
|
||||
if (typeMask & (1 << bit)) { // Property
|
||||
++numProperties;
|
||||
} else if (value != 0xffff) { // Method
|
||||
++numMethods;
|
||||
} else {
|
||||
// Undefined selector
|
||||
}
|
||||
}
|
||||
} else
|
||||
_mustSetViewVisible[bankIndex] = false;
|
||||
}
|
||||
|
||||
_methodCount = numMethods;
|
||||
_baseMethod.resize(numMethods * 2);
|
||||
_variables.resize(numProperties);
|
||||
_baseVars.resize(numProperties);
|
||||
_propertyOffsetsSci3.resize(numProperties);
|
||||
|
||||
// Go through the whole thing again to get the property values
|
||||
// and method pointers
|
||||
int propertyCounter = 0;
|
||||
int methodCounter = 0;
|
||||
uint32 codeOffset = buf.getUint32SEAt(0);
|
||||
for (int bankIndex = 0; bankIndex < numGroups; ++bankIndex) {
|
||||
byte groupIndex = selectorBank[bankIndex]; // one-based index
|
||||
|
||||
if (groupIndex != 0) {
|
||||
// This object actually has selectors belonging to this group
|
||||
const SciSpan<const byte> group = groups.subspan((groupIndex - 1) * kGroupSize, kGroupSize);
|
||||
uint32 typeMask = group.getUint32SEAt(0);
|
||||
int groupBaseId = bankIndex * kSelectorsInGroup;
|
||||
|
||||
for (int bit = 2; bit < kSelectorsInGroup; ++bit) {
|
||||
uint16 value = group.getUint16SEAt(bit * sizeof(uint16));
|
||||
if (typeMask & (1 << bit)) { // Property
|
||||
_baseVars[propertyCounter] = groupBaseId + bit;
|
||||
if (initVariables) {
|
||||
_variables[propertyCounter] = make_reg(0, value);
|
||||
}
|
||||
uint32 propertyOffset = (group + bit * sizeof(uint16)) - buf;
|
||||
_propertyOffsetsSci3[propertyCounter] = propertyOffset;
|
||||
++propertyCounter;
|
||||
} else if (value != 0xffff) { // Method
|
||||
_baseMethod[methodCounter * 2] = groupBaseId + bit;
|
||||
uint32 methodOffset = value + codeOffset;
|
||||
assert(methodOffset <= kOffsetMask);
|
||||
_baseMethod[methodCounter * 2 + 1] = methodOffset;
|
||||
++methodCounter;
|
||||
} else {
|
||||
// Undefined selector
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (initVariables) {
|
||||
_speciesSelectorSci3 = make_reg(0, _baseObj.getUint16SEAt(4));
|
||||
_superClassPosSci3 = make_reg(0, _baseObj.getUint16SEAt(8));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
382
engines/sci/engine/object.h
Normal file
382
engines/sci/engine/object.h
Normal file
@@ -0,0 +1,382 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_OBJECT_H
|
||||
#define SCI_ENGINE_OBJECT_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/serializer.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "sci/engine/vm_types.h" // for reg_t
|
||||
#include "sci/util.h"
|
||||
#include "sci/version.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class SegManager;
|
||||
class Script;
|
||||
|
||||
enum infoSelectorFlags {
|
||||
kInfoFlagClone = 0x0001,
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* When set, indicates to game scripts that a screen
|
||||
* item can be updated.
|
||||
*/
|
||||
kInfoFlagViewVisible = 0x0008,
|
||||
|
||||
/**
|
||||
* When set, the VM object has an associated ScreenItem in
|
||||
* the rendering tree.
|
||||
*/
|
||||
kInfoFlagViewInserted = 0x0010,
|
||||
#endif
|
||||
kInfoFlagClass = 0x8000
|
||||
};
|
||||
|
||||
enum ObjectOffsets {
|
||||
kOffsetHeaderSize = 6,
|
||||
kOffsetHeaderLocalVariables = 0,
|
||||
kOffsetHeaderFunctionArea = 2,
|
||||
kOffsetHeaderSelectorCounter = 4,
|
||||
|
||||
kOffsetSelectorSegment = 0,
|
||||
kOffsetInfoSelectorSci0 = 4,
|
||||
kOffsetNamePointerSci0 = 6,
|
||||
kOffsetInfoSelectorSci11 = 14,
|
||||
kOffsetNamePointerSci11 = 16
|
||||
};
|
||||
|
||||
class Object : public Common::Serializable {
|
||||
public:
|
||||
Object() :
|
||||
_name(NULL_REG),
|
||||
_offset(getSciVersion() < SCI_VERSION_1_1 ? 0 : 5),
|
||||
_isFreed(false),
|
||||
_methodCount(0),
|
||||
_pos(NULL_REG)
|
||||
#ifdef ENABLE_SCI32
|
||||
,
|
||||
_infoSelectorSci3(NULL_REG),
|
||||
_speciesSelectorSci3(NULL_REG),
|
||||
_superClassPosSci3(NULL_REG)
|
||||
#endif
|
||||
{}
|
||||
|
||||
Object &operator=(const Object &other) {
|
||||
_name = other._name;
|
||||
_baseObj = other._baseObj;
|
||||
_baseMethod = other._baseMethod;
|
||||
_variables = other._variables;
|
||||
_methodCount = other._methodCount;
|
||||
_isFreed = other._isFreed;
|
||||
_offset = other._offset;
|
||||
_pos = other._pos;
|
||||
_baseVars = other._baseVars;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3) {
|
||||
_propertyOffsetsSci3 = other._propertyOffsetsSci3;
|
||||
_superClassPosSci3 = other._superClassPosSci3;
|
||||
_speciesSelectorSci3 = other._speciesSelectorSci3;
|
||||
_infoSelectorSci3 = other._infoSelectorSci3;
|
||||
_mustSetViewVisible = other._mustSetViewVisible;
|
||||
}
|
||||
#endif
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
reg_t getSpeciesSelector() const {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3)
|
||||
return _speciesSelectorSci3;
|
||||
else
|
||||
#endif
|
||||
return _variables[_offset];
|
||||
}
|
||||
|
||||
void setSpeciesSelector(reg_t value) {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3)
|
||||
_speciesSelectorSci3 = value;
|
||||
else
|
||||
#endif
|
||||
_variables[_offset] = value;
|
||||
}
|
||||
|
||||
reg_t getSuperClassSelector() const {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3)
|
||||
return _superClassPosSci3;
|
||||
else
|
||||
#endif
|
||||
return _variables[_offset + 1];
|
||||
}
|
||||
|
||||
void setSuperClassSelector(reg_t value) {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3)
|
||||
_superClassPosSci3 = value;
|
||||
else
|
||||
#endif
|
||||
_variables[_offset + 1] = value;
|
||||
}
|
||||
|
||||
reg_t getInfoSelector() const {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3)
|
||||
return _infoSelectorSci3;
|
||||
else
|
||||
#endif
|
||||
return _variables[_offset + 2];
|
||||
}
|
||||
|
||||
void setInfoSelector(reg_t info) {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3)
|
||||
_infoSelectorSci3 = info;
|
||||
else
|
||||
#endif
|
||||
_variables[_offset + 2] = info;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
void setInfoSelectorFlag(infoSelectorFlags flag) {
|
||||
if (getSciVersion() == SCI_VERSION_3) {
|
||||
_infoSelectorSci3 |= flag;
|
||||
} else {
|
||||
_variables[_offset + 2] |= flag;
|
||||
}
|
||||
}
|
||||
|
||||
void clearInfoSelectorFlag(infoSelectorFlags flag) {
|
||||
if (getSciVersion() == SCI_VERSION_3) {
|
||||
_infoSelectorSci3 &= ~flag;
|
||||
} else {
|
||||
_variables[_offset + 2] &= ~flag;
|
||||
}
|
||||
}
|
||||
|
||||
bool isInserted() const {
|
||||
return getInfoSelector().toUint16() & kInfoFlagViewInserted;
|
||||
}
|
||||
#endif
|
||||
|
||||
reg_t getNameSelector() const {
|
||||
return _name;
|
||||
}
|
||||
|
||||
// No setter for the name selector
|
||||
|
||||
reg_t getPropDictSelector() const {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3)
|
||||
// This should never occur, this is called from a SCI1.1 - SCI2.1 only function
|
||||
error("getPropDictSelector called for SCI3");
|
||||
else
|
||||
#endif
|
||||
return _variables[2];
|
||||
}
|
||||
|
||||
void setPropDictSelector(reg_t value) {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3)
|
||||
// This should never occur, this is called from a SCI1.1 - SCI2.1 only function
|
||||
error("setPropDictSelector called for SCI3");
|
||||
else
|
||||
#endif
|
||||
_variables[2] = value;
|
||||
}
|
||||
|
||||
reg_t getClassScriptSelector() const {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3)
|
||||
return make_reg(0, _baseObj.getUint16SEAt(6));
|
||||
else
|
||||
#endif
|
||||
return _variables[4];
|
||||
}
|
||||
|
||||
void setClassScriptSelector(reg_t value) {
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3)
|
||||
// This should never occur, this is called from a SCI1.1 - SCI2.1 only function
|
||||
error("setClassScriptSelector called for SCI3");
|
||||
else
|
||||
#endif
|
||||
_variables[4] = value;
|
||||
}
|
||||
|
||||
Selector getVarSelector(uint16 i) const { return _baseVars[i]; }
|
||||
|
||||
/**
|
||||
* @returns A pointer to the code for the method at the given index.
|
||||
*/
|
||||
reg_t getFunction(const uint16 index) const {
|
||||
return make_reg32(_pos.getSegment(), _baseMethod[index * 2 + 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The selector for the method at the given index.
|
||||
*/
|
||||
Selector getFuncSelector(const uint16 index) const {
|
||||
return _baseMethod[index * 2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this object is a class and explicitly defines the
|
||||
* selector as a funcselector. Does NOT say anything about the object's
|
||||
* superclasses, i.e. failure may be returned even if one of the
|
||||
* superclasses defines the funcselector
|
||||
*/
|
||||
int funcSelectorPosition(Selector sel) const {
|
||||
for (uint i = 0; i < _methodCount; i++)
|
||||
if (getFuncSelector(i) == sel)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the object explicitly defines slc as a varselector.
|
||||
* Returns -1 if not found.
|
||||
*/
|
||||
int locateVarSelector(SegManager *segMan, Selector slc) const;
|
||||
|
||||
bool isClass() const { return (getInfoSelector().getOffset() & kInfoFlagClass); }
|
||||
const Object *getClass(SegManager *segMan) const;
|
||||
|
||||
void markAsFreed() { _isFreed = true; }
|
||||
bool isFreed() const { return _isFreed; }
|
||||
|
||||
uint getVarCount() const { return _variables.size(); }
|
||||
|
||||
void init(const Script &owner, reg_t obj_pos, bool initVariables = true);
|
||||
|
||||
reg_t getVariable(uint var) const { return _variables[var]; }
|
||||
reg_t &getVariableRef(uint var) { return _variables[var]; }
|
||||
|
||||
uint16 getMethodCount() const { return _methodCount; }
|
||||
reg_t getPos() const { return _pos; }
|
||||
|
||||
void saveLoadWithSerializer(Common::Serializer &ser) override;
|
||||
|
||||
void cloneFromObject(const Object *obj) {
|
||||
_name = obj ? obj->_name : NULL_REG;
|
||||
_baseObj = obj ? obj->_baseObj : SciSpan<const byte>();
|
||||
_baseMethod = obj ? obj->_baseMethod : Common::Array<uint32>();
|
||||
_baseVars = obj ? obj->_baseVars : Common::Array<uint16>();
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() == SCI_VERSION_3) {
|
||||
_mustSetViewVisible = obj ? obj->_mustSetViewVisible : Common::Array<bool>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool relocateSci0Sci21(SegmentId segment, int location, uint32 heapOffset);
|
||||
#ifdef ENABLE_SCI32
|
||||
bool relocateSci3(SegmentId segment, uint32 location, int offset, uint32 scriptSize);
|
||||
#endif
|
||||
|
||||
int propertyOffsetToId(SegManager *segMan, int propertyOffset) const;
|
||||
|
||||
void initSpecies(SegManager *segMan, reg_t addr, bool applyScriptPatches);
|
||||
void initSuperClass(SegManager *segMan, reg_t addr, bool applyScriptPatches);
|
||||
bool initBaseObject(SegManager *segMan, reg_t addr, bool doInitSuperClass = true, bool applyScriptPatches = true);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
bool mustSetViewVisible(const int index, const bool fromPropertyOp) const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
#ifdef ENABLE_SCI32
|
||||
void initSelectorsSci3(const SciSpan<const byte> &buf, const bool initVariables);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The name of the object.
|
||||
*/
|
||||
reg_t _name;
|
||||
|
||||
/**
|
||||
* A pointer to the raw object data within the object's owner script.
|
||||
*/
|
||||
SciSpan<const byte> _baseObj;
|
||||
|
||||
/**
|
||||
* A lookup table from a property index to its corresponding selector
|
||||
* number.
|
||||
*/
|
||||
Common::Array<uint16> _baseVars;
|
||||
|
||||
/**
|
||||
* A lookup table from a method index to its corresponding selector number
|
||||
* or offset to code. The table contains selector + offset in pairs.
|
||||
*/
|
||||
Common::Array<uint32> _baseMethod;
|
||||
|
||||
/**
|
||||
* A lookup table from a property index to the property's current value.
|
||||
*/
|
||||
Common::Array<reg_t> _variables;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* A lookup table from a property index to the property's original absolute
|
||||
* offset within the raw script data. This absolute offset is coded into the
|
||||
* script's relocation table, and is used to look up 32-bit values for
|
||||
* properties in the relocation table (which is used to break the 16-bit
|
||||
* barrier, since the script format still only holds 16-bit values inline).
|
||||
*/
|
||||
Common::Array<uint32> _propertyOffsetsSci3;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The number of methods on the object.
|
||||
*/
|
||||
uint16 _methodCount;
|
||||
|
||||
/**
|
||||
* Whether or not a clone object has been marked as 'freed'.
|
||||
*/
|
||||
bool _isFreed;
|
||||
|
||||
/**
|
||||
* For SCI0 through SCI2.1, an extra index offset used when looking up
|
||||
* special object properties -species-, -super-, -info-, and name.
|
||||
*/
|
||||
uint16 _offset;
|
||||
|
||||
reg_t _pos; /**< Object offset within its script; for clones, this is their base */
|
||||
#ifdef ENABLE_SCI32
|
||||
reg_t _superClassPosSci3; /**< reg_t pointing to superclass for SCI3 */
|
||||
reg_t _speciesSelectorSci3; /**< reg_t containing species "selector" for SCI3 */
|
||||
reg_t _infoSelectorSci3; /**< reg_t containing info "selector" for SCI3 */
|
||||
Common::Array<bool> _mustSetViewVisible; /** cached bit of info to make lookup fast, SCI3 only */
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_OBJECT_H
|
||||
1586
engines/sci/engine/savegame.cpp
Normal file
1586
engines/sci/engine/savegame.cpp
Normal file
File diff suppressed because it is too large
Load Diff
151
engines/sci/engine/savegame.h
Normal file
151
engines/sci/engine/savegame.h
Normal file
@@ -0,0 +1,151 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_SAVEGAME_H
|
||||
#define SCI_ENGINE_SAVEGAME_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/str.h"
|
||||
|
||||
#include "sci/sci.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
struct EngineState;
|
||||
|
||||
/*
|
||||
* Savegame format history:
|
||||
*
|
||||
* Version - new/changed feature
|
||||
* =============================
|
||||
* 46 - Sync MusicEntry::fadeSetVolume and MusicEntry::fadeCompleted
|
||||
* 45 - Sync MusicEntry::stopAfterFading
|
||||
* 44 - GK2+SCI3 audio resource locks
|
||||
* 43 - stop saving SCI3 mustSetViewVisible array
|
||||
* 42 - SCI3 robots and VM objects
|
||||
* 41 - palette support for newer SCI2.1 games; stable SCI2/2.1 save games
|
||||
* 40 - always store palvary variables
|
||||
* 39 - Accurate SCI32 arrays/strings, score metadata, avatar metadata
|
||||
* 38 - SCI32 cursor
|
||||
* 37 - Segment entry data changed to pointers
|
||||
* 36 - SCI32 bitmap segment
|
||||
* 35 - SCI32 remap
|
||||
* 34 - SCI32 palettes, and store play time in ticks
|
||||
* 33 - new overridePriority flag in MusicEntry
|
||||
* 32 - new playBed flag in MusicEntry
|
||||
* 31 - priority for sound effects/music is now a signed int16, instead of a byte
|
||||
* 30 - synonyms
|
||||
* 29 - system strings
|
||||
* 28 - heap
|
||||
* 27 - script created windows
|
||||
* 26 - play time
|
||||
* 25 - palette intensity
|
||||
* 24 - palvary
|
||||
* 23 - script buffer and heap size
|
||||
* 22 - game signature
|
||||
* 21 - script local variables
|
||||
* 20 - exports/synonyms
|
||||
* 19 - exportsAreWide
|
||||
* 18 - SCI32 arrays/strings
|
||||
* 17 - sound
|
||||
*
|
||||
*/
|
||||
|
||||
enum {
|
||||
CURRENT_SAVEGAME_VERSION = 46,
|
||||
MINIMUM_SAVEGAME_VERSION = 14
|
||||
#ifdef ENABLE_SCI32
|
||||
,
|
||||
MINIMUM_SCI32_SAVEGAME_VERSION = 41
|
||||
#endif
|
||||
};
|
||||
|
||||
// Savegame metadata
|
||||
struct SavegameMetadata {
|
||||
Common::String name;
|
||||
int version;
|
||||
Common::String gameVersion;
|
||||
int saveDate;
|
||||
int saveTime;
|
||||
uint32 playTime;
|
||||
uint16 gameObjectOffset;
|
||||
uint16 script0Size;
|
||||
|
||||
// Used by Shivers 1
|
||||
uint16 lowScore;
|
||||
uint16 highScore;
|
||||
|
||||
// Used by MGDX
|
||||
uint8 avatarId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves a game state to the hard disk in a portable way.
|
||||
* @param s The state to save
|
||||
* @param saveId The id of the savegame
|
||||
* @param savename The description of the savegame
|
||||
* @param version The version string of the game
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
bool gamestate_save(EngineState *s, int saveId, const Common::String &savename, const Common::String &version);
|
||||
|
||||
/**
|
||||
* Saves a game state to the hard disk in a portable way.
|
||||
* @param s The state to save
|
||||
* @param save The stream to save to
|
||||
* @param savename The description of the savegame
|
||||
* @param version The version string of the game
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
bool gamestate_save(EngineState *s, Common::WriteStream *save, const Common::String &savename, const Common::String &version);
|
||||
|
||||
// does a few fixups right after restoring a saved game
|
||||
void gamestate_afterRestoreFixUp(EngineState *s, int savegameId);
|
||||
|
||||
/**
|
||||
* Restores a game state from a directory.
|
||||
* @param s An older state from the same game
|
||||
* @param saveId The id of the savegame to restore from
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
bool gamestate_restore(EngineState *s, int saveId);
|
||||
|
||||
/**
|
||||
* Restores a game state from a directory.
|
||||
* @param s An older state from the same game
|
||||
* @param save The stream to restore from
|
||||
*/
|
||||
void gamestate_restore(EngineState *s, Common::SeekableReadStream *save);
|
||||
|
||||
/**
|
||||
* Read the header from a savegame.
|
||||
*/
|
||||
bool get_savegame_metadata(Common::SeekableReadStream *stream, SavegameMetadata &meta);
|
||||
|
||||
/**
|
||||
* Write the header to a savegame.
|
||||
*/
|
||||
void set_savegame_metadata(Common::Serializer &ser, Common::WriteStream *fh, const Common::String &savename, const Common::String &version);
|
||||
void set_savegame_metadata(Common::WriteStream *fh, const Common::String &savename, const Common::String &version);
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_SAVEGAME_H
|
||||
1290
engines/sci/engine/script.cpp
Normal file
1290
engines/sci/engine/script.cpp
Normal file
File diff suppressed because it is too large
Load Diff
376
engines/sci/engine/script.h
Normal file
376
engines/sci/engine/script.h
Normal file
@@ -0,0 +1,376 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_SCRIPT_H
|
||||
#define SCI_ENGINE_SCRIPT_H
|
||||
|
||||
#include "common/str.h"
|
||||
#include "sci/util.h"
|
||||
#include "sci/engine/segment.h"
|
||||
#include "sci/engine/script_patches.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
struct EngineState;
|
||||
class ResourceManager;
|
||||
struct SciScriptPatcherEntry;
|
||||
|
||||
enum ScriptObjectTypes {
|
||||
SCI_OBJ_TERMINATOR,
|
||||
SCI_OBJ_OBJECT,
|
||||
SCI_OBJ_CODE,
|
||||
SCI_OBJ_SYNONYMS,
|
||||
SCI_OBJ_SAID,
|
||||
SCI_OBJ_STRINGS,
|
||||
SCI_OBJ_CLASS,
|
||||
SCI_OBJ_EXPORTS,
|
||||
SCI_OBJ_POINTERS,
|
||||
SCI_OBJ_PRELOAD_TEXT, /* This is really just a flag. */
|
||||
SCI_OBJ_LOCALVARS
|
||||
};
|
||||
|
||||
typedef Common::HashMap<uint32, Object> ObjMap;
|
||||
|
||||
enum ScriptOffsetEntryTypes {
|
||||
SCI_SCR_OFFSET_TYPE_OBJECT = 0, // classes are handled by this type as well
|
||||
SCI_SCR_OFFSET_TYPE_STRING,
|
||||
SCI_SCR_OFFSET_TYPE_SAID
|
||||
};
|
||||
|
||||
enum : uint {
|
||||
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.
|
||||
uint32 offset; // offset of entry within script resource data
|
||||
uint16 stringSize; // size of string, including terminating [NUL]
|
||||
};
|
||||
|
||||
typedef Common::Array<offsetLookupArrayEntry> offsetLookupArrayType;
|
||||
|
||||
class Script : public SegmentObj {
|
||||
private:
|
||||
int _nr; /**< Script number */
|
||||
Common::SpanOwner<SciSpan<byte> > _buf; /**< Static data buffer, or NULL if not used */
|
||||
SciSpan<byte> _script; /**< Script size includes alignment byte */
|
||||
SciSpan<byte> _heap; /**< Start of heap if SCI1.1, NULL otherwise */
|
||||
|
||||
uint _lockers; /**< Number of classes and objects that require this script */
|
||||
|
||||
SciSpan<const uint16> _exports; /**< Exports block or 0 if not present */
|
||||
uint16 _numExports; /**< Number of export entries */
|
||||
SciSpan<const byte> _synonyms; /**< Synonyms block or 0 if not present */
|
||||
uint16 _numSynonyms; /**< Number of synonym entries */
|
||||
|
||||
int _codeOffset; /**< The absolute offset of the VM code block */
|
||||
|
||||
int _localsOffset;
|
||||
uint16 _localsCount;
|
||||
|
||||
bool _markedAsDeleted;
|
||||
SegmentId _localsSegment; /**< The local variable segment */
|
||||
LocalVariables *_localsBlock;
|
||||
|
||||
ObjMap _objects; /**< Table for objects, contains property variables */
|
||||
|
||||
protected:
|
||||
offsetLookupArrayType _offsetLookupArray; // Table of all elements of currently loaded script, that may get pointed to
|
||||
|
||||
private:
|
||||
uint16 _offsetLookupObjectCount;
|
||||
uint16 _offsetLookupStringCount;
|
||||
uint16 _offsetLookupSaidCount;
|
||||
|
||||
public:
|
||||
int getLocalsOffset() const { return _localsOffset; }
|
||||
uint16 getLocalsCount() const { return _localsCount; }
|
||||
|
||||
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<const byte> getSpan(uint offset) const { return _buf->subspan(offset); }
|
||||
|
||||
int getScriptNumber() const { return _nr; }
|
||||
SegmentId getLocalsSegment() const { return _localsSegment; }
|
||||
reg_t *getLocalsBegin() { return _localsBlock ? _localsBlock->_locals.begin() : NULL; }
|
||||
void syncLocalsBlock(SegManager *segMan);
|
||||
ObjMap &getObjectMap() { return _objects; }
|
||||
const ObjMap &getObjectMap() const { return _objects; }
|
||||
|
||||
// speed optimization: inline due to frequent calling
|
||||
bool offsetIsObject(uint32 offset) const {
|
||||
return _buf->getUint16SEAt(offset + SCRIPT_OBJECT_MAGIC_OFFSET) == SCRIPT_OBJECT_MAGIC_NUMBER;
|
||||
}
|
||||
|
||||
public:
|
||||
Script();
|
||||
~Script() override;
|
||||
|
||||
void freeScript(const bool keepLocalsSegment = false);
|
||||
void load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptPatcher, bool applyScriptPatches = true);
|
||||
|
||||
bool isValidOffset(uint32 offset) const override;
|
||||
SegmentRef dereference(reg_t pointer) override;
|
||||
reg_t findCanonicAddress(SegManager *segMan, reg_t sub_addr) const override;
|
||||
void freeAtAddress(SegManager *segMan, reg_t sub_addr) override;
|
||||
Common::Array<reg_t> listAllDeallocatable(SegmentId segId) const override;
|
||||
Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const override;
|
||||
|
||||
/**
|
||||
* Return a list of all references to objects in this script
|
||||
* (and also to the locals segment, if any).
|
||||
* Used by the garbage collector.
|
||||
* @return a list of outgoing references within the object
|
||||
*/
|
||||
Common::Array<reg_t> listObjectReferences() const;
|
||||
|
||||
void saveLoadWithSerializer(Common::Serializer &ser) override;
|
||||
|
||||
Object *getObject(uint32 offset);
|
||||
const Object *getObject(uint32 offset) const;
|
||||
|
||||
/**
|
||||
* Initializes an object within the segment manager
|
||||
* @param obj_pos Location (segment, offset) of the object. It must
|
||||
* point to the beginning of the script/class block
|
||||
* (as opposed to what the VM considers to be the
|
||||
* object location)
|
||||
* @returns A newly created Object describing the object,
|
||||
* stored within the relevant script
|
||||
*/
|
||||
Object *scriptObjInit(reg_t obj_pos, bool fullObjectInit = true);
|
||||
|
||||
/**
|
||||
* Initializes the script's local variables
|
||||
* @param segMan A reference to the segment manager
|
||||
*/
|
||||
void initializeLocals(SegManager *segMan);
|
||||
|
||||
/**
|
||||
* Adds a script's class to the segment manager's class table
|
||||
* @param segMan A reference to the segment manager
|
||||
* @param species The class number (index)
|
||||
* @param position The position of the class (object) in the script
|
||||
*/
|
||||
void initializeClass(SegManager *segMan, uint16 species, uint32 position);
|
||||
|
||||
/**
|
||||
* Initializes the script's objects
|
||||
* @param segMan A reference to the segment manager
|
||||
* @param segmentId The script's segment id
|
||||
* @param applyScriptPatches Apply patches for the script, if available
|
||||
*/
|
||||
void initializeObjects(SegManager *segMan, SegmentId segmentId, bool applyScriptPatches);
|
||||
|
||||
// script lock operations
|
||||
|
||||
/** Increments the number of lockers of this script by one. */
|
||||
void incrementLockers();
|
||||
|
||||
/** Decrements the number of lockers of this script by one. */
|
||||
void decrementLockers();
|
||||
|
||||
/**
|
||||
* Retrieves the number of locks held on this script.
|
||||
* @return the number of locks held on the previously identified script
|
||||
*/
|
||||
uint getLockers() const;
|
||||
|
||||
/** Sets the number of locks held on this script. */
|
||||
void setLockers(uint lockers);
|
||||
|
||||
/**
|
||||
* Retrieves the offset of the export table in the script
|
||||
* @return the exports offset.
|
||||
*/
|
||||
uint getExportsOffset() const { return _exports.sourceByteOffset(); }
|
||||
|
||||
/**
|
||||
* Retrieves the number of exports of script.
|
||||
* @return the number of exports of this script
|
||||
*/
|
||||
uint16 getExportsNr() const { return _numExports; }
|
||||
|
||||
/**
|
||||
* Retrieves a pointer to the synonyms associated with this script
|
||||
* @return pointer to the synonyms, in non-parsed format.
|
||||
*/
|
||||
const SciSpan<const byte> &getSynonyms() const { return _synonyms; }
|
||||
|
||||
/**
|
||||
* Retrieves the number of synonyms associated with this script.
|
||||
* @return the number of synonyms associated with this script
|
||||
*/
|
||||
uint16 getSynonymsNr() const { return _numSynonyms; }
|
||||
|
||||
/**
|
||||
* Validate whether the specified public function is exported by
|
||||
* the script in the specified segment.
|
||||
* @param pubfunct Index of the function to validate
|
||||
* @param relocSci3 Decide whether to relocate this SCI3 public function or not
|
||||
* @return NULL if the public function is invalid, its
|
||||
* offset into the script's segment otherwise
|
||||
*/
|
||||
uint32 validateExportFunc(int pubfunct, bool relocSci3);
|
||||
|
||||
/**
|
||||
* Marks the script as deleted.
|
||||
* This will not actually delete the script. If references remain present on the
|
||||
* heap or the stack, the script will stay in memory in a quasi-deleted state until
|
||||
* either unreachable (resulting in its eventual deletion) or reloaded (resulting
|
||||
* in its data being updated).
|
||||
*/
|
||||
void markDeleted() {
|
||||
_markedAsDeleted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the script is marked as being deleted.
|
||||
*/
|
||||
bool isMarkedAsDeleted() const {
|
||||
return _markedAsDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the pointer where a block of a specific type starts from,
|
||||
* in SCI0 - SCI1 games
|
||||
*/
|
||||
SciSpan<const byte> findBlockSCI0(ScriptObjectTypes type, bool findLastBlock = false) const;
|
||||
|
||||
/**
|
||||
* Syncs the string heap of a script. Used when saving/loading.
|
||||
*/
|
||||
void syncStringHeap(Common::Serializer &ser);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Resolve a relocation in an SCI3 script
|
||||
* @param offset The offset to relocate from
|
||||
*/
|
||||
int relocateOffsetSci3(uint32 offset) const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Gets an offset to the beginning of the code block in a SCI1.1 or later
|
||||
* script
|
||||
*/
|
||||
int getCodeBlockOffset() { return _codeOffset; }
|
||||
|
||||
/**
|
||||
* Get the offset array
|
||||
*/
|
||||
const offsetLookupArrayType *getOffsetArray() { return &_offsetLookupArray; };
|
||||
uint16 getOffsetObjectCount() { return _offsetLookupObjectCount; };
|
||||
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<const uint16> 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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
void relocateSci3(const SegmentId segmentId);
|
||||
#endif
|
||||
|
||||
bool relocateLocal(SegmentId segment, int location, uint32 offset);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Gets a pointer to the beginning of the objects in a SCI3 script
|
||||
*/
|
||||
SciSpan<const byte> getSci3ObjectsPointer();
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initializes the script's objects (SCI0)
|
||||
* @param segMan A reference to the segment manager
|
||||
* @param segmentId The script's segment id
|
||||
* @applyScriptPatches Apply patches for the script, if available
|
||||
*/
|
||||
void initializeObjectsSci0(SegManager *segMan, SegmentId segmentId, bool applyScriptPatches);
|
||||
|
||||
/**
|
||||
* Initializes the script's objects (SCI1.1 - SCI2.1)
|
||||
* @param segMan A reference to the segment manager
|
||||
* @param segmentId The script's segment id
|
||||
* @applyScriptPatches Apply patches for the script, if available
|
||||
*/
|
||||
void initializeObjectsSci11(SegManager *segMan, SegmentId segmentId, bool applyScriptPatches);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* Initializes the script's objects (SCI3)
|
||||
* @param segMan A reference to the segment manager
|
||||
* @param segmentId The script's segment id
|
||||
*/
|
||||
void initializeObjectsSci3(SegManager *segMan, SegmentId segmentId, bool applyScriptPatches);
|
||||
#endif
|
||||
|
||||
LocalVariables *allocLocalsSegment(SegManager *segMan);
|
||||
|
||||
/**
|
||||
* Identifies certain offsets within script data and set up lookup-table
|
||||
*/
|
||||
void identifyOffsets();
|
||||
|
||||
/**
|
||||
* Apply workarounds to known broken Said strings
|
||||
*/
|
||||
void applySaidWorkarounds();
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_SCRIPT_H
|
||||
27201
engines/sci/engine/script_patches.cpp
Normal file
27201
engines/sci/engine/script_patches.cpp
Normal file
File diff suppressed because it is too large
Load Diff
131
engines/sci/engine/script_patches.h
Normal file
131
engines/sci/engine/script_patches.h
Normal file
@@ -0,0 +1,131 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_SCRIPT_PATCHES_H
|
||||
#define SCI_ENGINE_SCRIPT_PATCHES_H
|
||||
|
||||
#include "sci/sci.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// Please do not use the #defines, that are called SIG_CODE_* / PATCH_CODE_* inside signature/patch-tables
|
||||
#define SIG_END 0xFFFF
|
||||
#define SIG_MISMATCH 0xFFFE
|
||||
#define SIG_COMMANDMASK 0xF000
|
||||
#define SIG_VALUEMASK 0x0FFF
|
||||
#define SIG_BYTEMASK 0x00FF
|
||||
#define SIG_MAGICDWORD 0xF000
|
||||
#define SIG_CODE_ADDTOOFFSET 0xE000
|
||||
#define SIG_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | (_offset_)
|
||||
#define SIG_CODE_SELECTOR16 0x9000
|
||||
#define SIG_SELECTOR16(_selectorID_) SIG_CODE_SELECTOR16 | SELECTOR_##_selectorID_
|
||||
#define SIG_CODE_SELECTOR8 0x8000
|
||||
#define SIG_SELECTOR8(_selectorID_) SIG_CODE_SELECTOR8 | SELECTOR_##_selectorID_
|
||||
#define SIG_CODE_UINT16 0x1000
|
||||
#define SIG_UINT16(_value_) SIG_CODE_UINT16 | ((_value_) & 0xFF), ((_value_) >> 8)
|
||||
#define SIG_CODE_BYTE 0x0000
|
||||
|
||||
#define PATCH_END SIG_END
|
||||
#define PATCH_COMMANDMASK SIG_COMMANDMASK
|
||||
#define PATCH_VALUEMASK SIG_VALUEMASK
|
||||
#define PATCH_BYTEMASK SIG_BYTEMASK
|
||||
#define PATCH_CODE_ADDTOOFFSET SIG_CODE_ADDTOOFFSET
|
||||
#define PATCH_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | (_offset_)
|
||||
#define PATCH_CODE_GETORIGINALBYTES 0xB000
|
||||
#define PATCH_GETORIGINALBYTES(_offset_, _length_) PATCH_CODE_GETORIGINALBYTES | (_offset_), (uint16)(_length_)
|
||||
#define PATCH_CODE_GETORIGINALBYTE 0xC000
|
||||
#define PATCH_GETORIGINALBYTE(_offset_) PATCH_CODE_GETORIGINALBYTE | (_offset_), 0
|
||||
#define PATCH_GETORIGINALBYTEADJUST(_offset_, _adjustValue_) PATCH_CODE_GETORIGINALBYTE | (_offset_), (uint16)(_adjustValue_)
|
||||
#define PATCH_CODE_GETORIGINALUINT16 0xD000
|
||||
#define PATCH_GETORIGINALUINT16(_offset_) PATCH_CODE_GETORIGINALUINT16 | (_offset_), 0
|
||||
#define PATCH_GETORIGINALUINT16ADJUST(_offset_, _adjustValue_) PATCH_CODE_GETORIGINALUINT16 | (_offset_), (uint16)(_adjustValue_)
|
||||
#define PATCH_CODE_SELECTOR16 SIG_CODE_SELECTOR16
|
||||
#define PATCH_SELECTOR16(_selectorID_) SIG_CODE_SELECTOR16 | SELECTOR_##_selectorID_
|
||||
#define PATCH_CODE_SELECTOR8 SIG_CODE_SELECTOR8
|
||||
#define PATCH_SELECTOR8(_selectorID_) SIG_CODE_SELECTOR8 | SELECTOR_##_selectorID_
|
||||
#define PATCH_CODE_UINT16 SIG_CODE_UINT16
|
||||
#define PATCH_UINT16(_value_) SIG_CODE_UINT16 | ((_value_) & 0xFF), ((_value_) >> 8)
|
||||
#define PATCH_CODE_BYTE SIG_CODE_BYTE
|
||||
|
||||
// defines maximum scratch area for getting original bytes from unpatched script data
|
||||
#define PATCH_VALUELIMIT 4096
|
||||
|
||||
struct SciScriptPatcherEntry {
|
||||
bool defaultActive;
|
||||
uint16 scriptNr;
|
||||
const char *description;
|
||||
int16 applyCount;
|
||||
const uint16 *signatureData;
|
||||
const uint16 *patchData;
|
||||
};
|
||||
|
||||
#define SCI_SIGNATUREENTRY_TERMINATOR { false, 0, NULL, 0, NULL, NULL }
|
||||
|
||||
struct SciScriptPatcherRuntimeEntry {
|
||||
bool active;
|
||||
uint32 magicDWord;
|
||||
int magicOffset;
|
||||
};
|
||||
|
||||
/**
|
||||
* ScriptPatcher class, handles on-the-fly patching of script data
|
||||
*/
|
||||
class ScriptPatcher {
|
||||
public:
|
||||
ScriptPatcher();
|
||||
~ScriptPatcher();
|
||||
|
||||
// Calculates the magic DWord for fast search and verifies signature/patch data
|
||||
// Returns the magic DWord in platform-specific byte-order. This is done on purpose for performance.
|
||||
void calculateMagicDWordAndVerify(const char *signatureDescription, const uint16 *signatureData, bool magicDWordIncluded, uint32 &calculatedMagicDWord, int &calculatedMagicDWordOffset);
|
||||
|
||||
// Called when a script is loaded to check for signature matches and apply patches in such cases
|
||||
void processScript(uint16 scriptNr, SciSpan<byte> scriptData);
|
||||
|
||||
// Verifies, if a given signature matches the given script data (pointed to by additional byte offset)
|
||||
bool verifySignature(uint32 byteOffset, const uint16 *signatureData, const char *signatureDescription, const SciSpan<const byte> &scriptData);
|
||||
|
||||
// searches for a given signature inside script data
|
||||
// returns -1 in case it was not found or an offset to the matching data
|
||||
int32 findSignature(uint32 magicDWord, int magicOffset, const uint16 *signatureData, const char *patchDescription, const SciSpan<const byte> &scriptData);
|
||||
|
||||
private:
|
||||
// Initializes a patch table and creates run time information for it (for enabling/disabling), also calculates magic DWORD)
|
||||
void initSignature(const SciScriptPatcherEntry *patchTable);
|
||||
|
||||
// Enables a patch inside the patch table (used for optional patches like CD+Text support for KQ6 & LB2)
|
||||
void enablePatch(const SciScriptPatcherEntry *patchTable, const char *searchDescription);
|
||||
|
||||
// Searches for a given signature entry inside script data
|
||||
// returns -1 in case it was not found or an offset to the matching data
|
||||
int32 findSignature(const SciScriptPatcherEntry *patchEntry, const SciScriptPatcherRuntimeEntry *runtimeEntry, const SciSpan<const byte> &scriptData);
|
||||
|
||||
// Applies a patch to a given script + offset (overwrites parts)
|
||||
void applyPatch(const SciScriptPatcherEntry *patchEntry, SciSpan<byte> scriptData, int32 signatureOffset);
|
||||
|
||||
Selector *_selectorIdTable;
|
||||
SciScriptPatcherRuntimeEntry *_runtimeTable;
|
||||
bool _isMacSci11;
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_WORKAROUNDS_H
|
||||
1264
engines/sci/engine/scriptdebug.cpp
Normal file
1264
engines/sci/engine/scriptdebug.cpp
Normal file
File diff suppressed because it is too large
Load Diff
47
engines/sci/engine/scriptdebug.h
Normal file
47
engines/sci/engine/scriptdebug.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_SCRIPTDEBUG_H
|
||||
#define SCI_ENGINE_SCRIPTDEBUG_H
|
||||
|
||||
namespace Sci {
|
||||
|
||||
#ifndef REDUCE_MEMORY_USAGE
|
||||
extern const char *opcodeNames[];
|
||||
#endif
|
||||
|
||||
void debugSelectorCall(reg_t send_obj, Selector selector, int argc, StackPtr argp, ObjVarRef &varp, reg_t funcp, SegManager *segMan, SelectorType selectorType);
|
||||
|
||||
void debugPropertyAccess(Object *obj, reg_t objp, unsigned int index, Selector selector, reg_t curValue, reg_t newValue, SegManager *segMan, BreakpointType breakpointType);
|
||||
|
||||
void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunction *kernelSubCall, EngineState *s, int argc, reg_t *argv, reg_t result);
|
||||
|
||||
void logExportCall(uint16 script, uint16 pubfunct, EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
void logBacktrace();
|
||||
|
||||
bool printObject(reg_t obj);
|
||||
|
||||
bool matchKernelBreakpointPattern(const Common::String &pattern, const Common::String &name);
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_SCRIPTDEBUG_H
|
||||
1185
engines/sci/engine/seg_manager.cpp
Normal file
1185
engines/sci/engine/seg_manager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
510
engines/sci/engine/seg_manager.h
Normal file
510
engines/sci/engine/seg_manager.h
Normal file
@@ -0,0 +1,510 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_SEG_MANAGER_H
|
||||
#define SCI_ENGINE_SEG_MANAGER_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/serializer.h"
|
||||
#include "sci/engine/script.h"
|
||||
#include "sci/engine/vm.h"
|
||||
#include "sci/engine/vm_types.h"
|
||||
#include "sci/engine/segment.h"
|
||||
#ifdef ENABLE_SCI32
|
||||
#include "sci/graphics/celobj32.h" // kLowResX, kLowResY
|
||||
#endif
|
||||
|
||||
namespace Sci {
|
||||
|
||||
/**
|
||||
* Parameters for getScriptSegment().
|
||||
*/
|
||||
enum ScriptLoadType {
|
||||
SCRIPT_GET_DONT_LOAD = 0, /**< Fail if not loaded */
|
||||
SCRIPT_GET_LOAD = 1, /**< Load, if necessary */
|
||||
SCRIPT_GET_LOCK = 3 /**< Load, if necessary, and lock */
|
||||
};
|
||||
|
||||
class Script;
|
||||
|
||||
class SegManager : public Common::Serializable {
|
||||
friend class Console;
|
||||
public:
|
||||
/**
|
||||
* Initialize the segment manager.
|
||||
*/
|
||||
SegManager(ResourceManager *resMan, ScriptPatcher *scriptPatcher);
|
||||
|
||||
/**
|
||||
* Deallocate all memory associated with the segment manager.
|
||||
*/
|
||||
~SegManager() override;
|
||||
|
||||
void resetSegMan();
|
||||
|
||||
void saveLoadWithSerializer(Common::Serializer &ser) override;
|
||||
|
||||
// 1. Scripts
|
||||
|
||||
/**
|
||||
* Allocate a script into the segment manager.
|
||||
* @param script_nr The number of the script to load
|
||||
* @param seg_id The segment ID of the newly allocated segment.
|
||||
* @return The script
|
||||
*/
|
||||
Script *allocateScript(int script_nr, SegmentId &seg_id);
|
||||
|
||||
// The script must then be initialized; see section (1b.), below.
|
||||
|
||||
/**
|
||||
* Forcefully deallocate a previously allocated script.
|
||||
* @param script_nr number of the script to deallocate
|
||||
*/
|
||||
void deallocateScript(int script_nr);
|
||||
|
||||
/**
|
||||
* Reconstructs the stack. Used when restoring saved games
|
||||
*/
|
||||
void reconstructStack(EngineState *s);
|
||||
|
||||
/**
|
||||
* Determines the segment occupied by a certain script, if any.
|
||||
* @param script_nr Number of the script to look up
|
||||
* @return The script's segment ID, or 0 on failure
|
||||
*/
|
||||
SegmentId getScriptSegment(int script_nr) const;
|
||||
|
||||
/**
|
||||
* Determines the segment occupied by a certain script. Optionally
|
||||
* load it, or load & lock it.
|
||||
* @param[in] script_nr Number of the script to look up
|
||||
* @param[in] load Flag determining whether to load/lock the script
|
||||
* @param[in] applyScriptPatches Apply patches for the script, if available
|
||||
* @return The script's segment ID, or 0 on failure
|
||||
*/
|
||||
SegmentId getScriptSegment(int script_nr, ScriptLoadType load, bool applyScriptPatches = true);
|
||||
|
||||
/**
|
||||
* Makes sure that a script and its superclasses get loaded to the heap.
|
||||
* If the script already has been loaded, only the number of lockers is
|
||||
* increased. All scripts containing superclasses of this script are loaded
|
||||
* recursively as well, unless 'recursive' is set to zero. The
|
||||
* complementary function is "uninstantiateScript()" below.
|
||||
* @param[in] script_nr The script number to load
|
||||
* @param[in] applyScriptPatches Apply patches for the script, if available
|
||||
* @return The script's segment ID or 0 if out of heap
|
||||
*/
|
||||
int instantiateScript(int script_nr, bool applyScriptPatches = true);
|
||||
|
||||
/**
|
||||
* Decreases the numer of lockers of a script and unloads it if that number
|
||||
* reaches zero.
|
||||
* This function will recursively unload scripts containing its
|
||||
* superclasses, if those aren't locked by other scripts as well.
|
||||
* @param[in] script_nr The script number that is requestet to be unloaded
|
||||
*/
|
||||
void uninstantiateScript(int script_nr);
|
||||
|
||||
private:
|
||||
void uninstantiateScriptSci0(int script_nr);
|
||||
|
||||
public:
|
||||
// TODO: document this
|
||||
reg_t getClassAddress(int classnr, ScriptLoadType lock, uint16 callerSegment, bool applyScriptPatches = true);
|
||||
|
||||
/**
|
||||
* Return a pointer to the specified script.
|
||||
* If the id is invalid, does not refer to a script or the script is
|
||||
* not loaded, this will invoke error().
|
||||
* @param seg ID of the script segment to check for
|
||||
* @return A pointer to the Script object
|
||||
*/
|
||||
Script *getScript(SegmentId seg);
|
||||
|
||||
|
||||
/**
|
||||
* Return a pointer to the specified script.
|
||||
* If the id is invalid, does not refer to a script, or
|
||||
* the script is not loaded, this will return NULL
|
||||
* @param seg ID of the script segment to check for
|
||||
* @return A pointer to the Script object, or NULL
|
||||
*/
|
||||
Script *getScriptIfLoaded(SegmentId seg) const;
|
||||
|
||||
// 2. Clones
|
||||
|
||||
/**
|
||||
* Allocate a fresh clone
|
||||
* @param addr The offset of the freshly allocated clone
|
||||
* @return Reference to the memory allocated for the clone
|
||||
*/
|
||||
Clone *allocateClone(reg_t *addr);
|
||||
|
||||
/**
|
||||
* Reconstructs clones. Used when restoring saved games
|
||||
*/
|
||||
void reconstructClones();
|
||||
|
||||
// 4. Stack
|
||||
|
||||
/**
|
||||
* Allocates a data stack
|
||||
* @param size Number of stack entries to reserve
|
||||
* @return The data stack
|
||||
*/
|
||||
DataStack *allocateStack(int size);
|
||||
|
||||
|
||||
// 5. System Strings
|
||||
|
||||
/**
|
||||
* Initializes the system string table.
|
||||
*/
|
||||
void initSysStrings();
|
||||
|
||||
|
||||
// 6, 7. Lists and Nodes
|
||||
|
||||
/**
|
||||
* Allocate a fresh list
|
||||
* @param[in] addr The offset of the freshly allocated list
|
||||
* @return Reference to the memory allocated for the list
|
||||
*/
|
||||
List *allocateList(reg_t *addr);
|
||||
|
||||
/**
|
||||
* Allocate a fresh node
|
||||
* @param[in] addr The offset of the freshly allocated node
|
||||
* @return Reference to the memory allocated for the node
|
||||
*/
|
||||
Node *allocateNode(reg_t *addr);
|
||||
|
||||
/**
|
||||
* Allocate and initialize a new list node.
|
||||
* @param[in] value The value to set the node to
|
||||
* @param[in] key The key to set
|
||||
* @return Pointer to the newly initialized list node
|
||||
*/
|
||||
reg_t newNode(reg_t value, reg_t key);
|
||||
|
||||
/**
|
||||
* Resolves a list pointer to a list.
|
||||
* @param addr The address to resolve
|
||||
* @return The list referenced, or NULL on error
|
||||
*/
|
||||
List *lookupList(reg_t addr);
|
||||
|
||||
/**
|
||||
* Resolves an address into a list node.
|
||||
* @param addr The address to resolve
|
||||
* @return The list node referenced, or NULL on error
|
||||
*/
|
||||
Node *lookupNode(reg_t addr, bool stopOnDiscarded = true);
|
||||
|
||||
|
||||
// 8. Hunk Memory
|
||||
|
||||
/**
|
||||
* Allocate a fresh chunk of the hunk
|
||||
* @param[in] size Number of bytes to allocate for the hunk entry
|
||||
* @param[in] hunk_type A descriptive string for the hunk entry, for
|
||||
* debugging purposes
|
||||
* @return The offset of the freshly allocated hunk entry
|
||||
*/
|
||||
reg_t allocateHunkEntry(const char *hunk_type, int size);
|
||||
|
||||
/**
|
||||
* Deallocates a hunk entry
|
||||
* @param[in] addr Offset of the hunk entry to delete
|
||||
*/
|
||||
void freeHunkEntry(reg_t addr);
|
||||
|
||||
/**
|
||||
* Gets a pointer to the hunk memory referenced by a specified handle
|
||||
* @param[in] addr Offset of the hunk entry
|
||||
*/
|
||||
byte *getHunkPointer(reg_t addr);
|
||||
|
||||
// 9. Dynamic Memory
|
||||
|
||||
/**
|
||||
* Allocate some dynamic memory
|
||||
* @param[in] size Number of bytes to allocate
|
||||
* @param[in] description A descriptive string for debugging purposes
|
||||
* @param[out] addr The offset of the freshly allocated X
|
||||
* @return Raw pointer into the allocated dynamic
|
||||
* memory
|
||||
*/
|
||||
byte *allocDynmem(int size, const char *description, reg_t *addr);
|
||||
|
||||
/**
|
||||
* Deallocates a piece of dynamic memory
|
||||
* @param[in] addr Offset of the dynmem chunk to free
|
||||
*/
|
||||
bool freeDynmem(reg_t addr);
|
||||
|
||||
|
||||
// Generic Operations on Segments and Addresses
|
||||
|
||||
/**
|
||||
* Dereferences a raw memory pointer
|
||||
* @param[in] reg The reference to dereference
|
||||
* @return The data block referenced
|
||||
*/
|
||||
SegmentRef dereference(reg_t pointer);
|
||||
|
||||
/**
|
||||
* Dereferences a heap pointer pointing to raw memory.
|
||||
* @param pointer The pointer to dereference
|
||||
* @parm entries The number of values expected (for checkingO
|
||||
* @return A physical reference to the address pointed to, or NULL on error or
|
||||
* if not enough entries were available.
|
||||
*/
|
||||
byte *derefBulkPtr(reg_t pointer, int entries);
|
||||
|
||||
/**
|
||||
* Dereferences a heap pointer pointing to a (list of) register(s).
|
||||
* Ensures alignedness of data.
|
||||
* @param pointer The pointer to dereference
|
||||
* @parm entries The number of values expected (for checking)
|
||||
* @return A physical reference to the address pointed to, or NULL on error or
|
||||
* if not enough entries were available.
|
||||
*/
|
||||
reg_t *derefRegPtr(reg_t pointer, int entries);
|
||||
|
||||
/**
|
||||
* Dereferences a heap pointer pointing to raw memory.
|
||||
* @param pointer The pointer to dereference
|
||||
* @parm entries The number of values expected (for checking)
|
||||
* @return A physical reference to the address pointed to, or NULL on error or
|
||||
* if not enough entries were available.
|
||||
*/
|
||||
char *derefString(reg_t pointer, int entries = 0);
|
||||
|
||||
/**
|
||||
* Return the string referenced by pointer.
|
||||
* pointer can point to either a raw or non-raw segment.
|
||||
* @param pointer The pointer to dereference
|
||||
* @return The string referenced, or an empty string if not enough
|
||||
* entries were available.
|
||||
*/
|
||||
Common::String getString(reg_t pointer);
|
||||
|
||||
|
||||
/**
|
||||
* Copies a string from src to dest.
|
||||
* src and dest can point to raw and non-raw segments.
|
||||
* Conversion is performed as required.
|
||||
*/
|
||||
void strcpy_(reg_t dest, reg_t src);
|
||||
|
||||
/**
|
||||
* Copies a string from src to dest.
|
||||
* dest can point to a raw or non-raw segment.
|
||||
* Conversion is performed as required.
|
||||
*/
|
||||
void strcpy_(reg_t dest, const char *src);
|
||||
|
||||
/**
|
||||
* Copies a string from src to dest.
|
||||
* src and dest can point to raw and non-raw segments.
|
||||
* Conversion is performed as required. At most n characters are copied.
|
||||
* TODO: determine if dest should always be null-terminated.
|
||||
*/
|
||||
void strncpy(reg_t dest, reg_t src, size_t n);
|
||||
|
||||
/**
|
||||
* Copies a string from src to dest.
|
||||
* dest can point to a raw or non-raw segment.
|
||||
* Conversion is performed as required. At most n characters are copied.
|
||||
* TODO: determine if dest should always be null-terminated.
|
||||
*/
|
||||
void strncpy(reg_t dest, const char *src, size_t n);
|
||||
|
||||
/**
|
||||
* Copies n bytes of data from src to dest.
|
||||
* src and dest can point to raw and non-raw segments.
|
||||
* Conversion is performed as required.
|
||||
*/
|
||||
void memcpy(reg_t dest, reg_t src, size_t n);
|
||||
|
||||
/**
|
||||
* Copies n bytes of data from src to dest.
|
||||
* dest can point to a raw or non-raw segment.
|
||||
* Conversion is performed as required.
|
||||
*/
|
||||
void memcpy(reg_t dest, const byte* src, size_t n);
|
||||
|
||||
/**
|
||||
* Copies n bytes of data from src to dest.
|
||||
* src can point to raw and non-raw segments.
|
||||
* Conversion is performed as required.
|
||||
*/
|
||||
void memcpy(byte *dest, reg_t src, size_t n);
|
||||
|
||||
/**
|
||||
* Determine length of string at str.
|
||||
* str can point to a raw or non-raw segment.
|
||||
*/
|
||||
size_t strlen(reg_t str);
|
||||
|
||||
/**
|
||||
* Finds a unique segment by type
|
||||
* @param type The type of the segment to find
|
||||
* @return The segment number, or -1 if the segment wasn't found
|
||||
*/
|
||||
SegmentId findSegmentByType(int type) const;
|
||||
|
||||
// TODO: document this
|
||||
SegmentObj *getSegmentObj(SegmentId seg) const;
|
||||
|
||||
// TODO: document this
|
||||
SegmentType getSegmentType(SegmentId seg) const;
|
||||
|
||||
// TODO: document this
|
||||
SegmentObj *getSegment(SegmentId seg, SegmentType type) const;
|
||||
|
||||
/**
|
||||
* Retrieves an object from the specified location
|
||||
* @param[in] offset Location (segment, offset) of the object
|
||||
* @return The object in question, or NULL if there is none
|
||||
*/
|
||||
Object *getObject(reg_t pos) const;
|
||||
|
||||
/**
|
||||
* Checks whether a heap address contains an object
|
||||
* @parm obj The address to check
|
||||
* @return True if it is an object, false otherwise
|
||||
*/
|
||||
bool isObject(reg_t obj) const { return getObject(obj) != NULL; }
|
||||
|
||||
// TODO: document this
|
||||
bool isHeapObject(reg_t pos) const;
|
||||
|
||||
/**
|
||||
* Determines the name of an object
|
||||
* @param[in] pos Location (segment, offset) of the object
|
||||
* @return A name for that object, or a string describing an error
|
||||
* that occurred while looking it up. The string is stored
|
||||
* in a static buffer and need not be freed (neither may
|
||||
* it be modified).
|
||||
*/
|
||||
const char *getObjectName(reg_t pos);
|
||||
|
||||
/**
|
||||
* Finds the addresses of all objects with the given name.
|
||||
*/
|
||||
Common::Array<reg_t> findObjectsByName(const Common::String &name);
|
||||
|
||||
/**
|
||||
* Find the address of an object by its name. In case multiple objects
|
||||
* with the same name occur, the optional index parameter can be used
|
||||
* to distinguish between them. If index is -1, then if there is a
|
||||
* unique object with the specified name, its address is returned;
|
||||
* if there are multiple matches, or none, then NULL_REG is returned.
|
||||
*
|
||||
* @param name the name of the object we are looking for
|
||||
* @param index the index of the object in case there are multiple
|
||||
* @return the address of the object, or NULL_REG
|
||||
*/
|
||||
reg_t findObjectByName(const Common::String &name, int index = -1);
|
||||
|
||||
/**
|
||||
* Finds the addresses of all objects with the superclass of the given name.
|
||||
*/
|
||||
Common::Array<reg_t> findObjectsBySuperClass(const Common::String &superClassName);
|
||||
|
||||
uint32 classTableSize() const { return _classTable.size(); }
|
||||
Class getClass(int index) const { return _classTable[index]; }
|
||||
void setClassOffset(int index, reg_t offset) { _classTable[index].reg = offset; }
|
||||
void resizeClassTable(uint32 size) { _classTable.resize(size); }
|
||||
|
||||
reg_t getSaveDirPtr() const { return _saveDirPtr; }
|
||||
reg_t getParserPtr() const { return _parserPtr; }
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
bool isValidAddr(reg_t reg, SegmentType expected) const {
|
||||
SegmentObj *mobj = getSegmentObj(reg.getSegment());
|
||||
return (mobj &&
|
||||
mobj->getType() == expected &&
|
||||
mobj->isValidOffset(reg.getOffset()));
|
||||
}
|
||||
|
||||
SciArray *allocateArray(SciArrayType type, uint16 size, reg_t *addr);
|
||||
SciArray *lookupArray(reg_t addr);
|
||||
void freeArray(reg_t addr);
|
||||
bool isArray(reg_t addr) const;
|
||||
|
||||
SciBitmap *allocateBitmap(reg_t *addr, const int16 width, const int16 height, const uint8 skipColor = kDefaultSkipColor, const int16 originX = 0, const int16 originY = 0, const int16 xResolution = kLowResX, const int16 yResolution = kLowResY, const uint32 paletteSize = 0, const bool remap = false, const bool gc = true);
|
||||
SciBitmap *lookupBitmap(reg_t addr);
|
||||
void freeBitmap(reg_t addr);
|
||||
#endif
|
||||
|
||||
const Common::Array<SegmentObj *> &getSegments() const { return _heap; }
|
||||
|
||||
private:
|
||||
Common::Array<SegmentObj *> _heap;
|
||||
Common::Array<Class> _classTable; /**< Table of all classes */
|
||||
/** Map script ids to segment ids. */
|
||||
Common::HashMap<int, SegmentId> _scriptSegMap;
|
||||
|
||||
ResourceManager *_resMan;
|
||||
ScriptPatcher *_scriptPatcher;
|
||||
|
||||
SegmentId _clonesSegId; ///< ID of the (a) clones segment
|
||||
SegmentId _listsSegId; ///< ID of the (a) list segment
|
||||
SegmentId _nodesSegId; ///< ID of the (a) node segment
|
||||
SegmentId _hunksSegId; ///< ID of the (a) hunk segment
|
||||
|
||||
// Statically allocated memory for system strings
|
||||
reg_t _saveDirPtr;
|
||||
reg_t _parserPtr;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
SegmentId _arraysSegId;
|
||||
SegmentId _bitmapSegId;
|
||||
#endif
|
||||
|
||||
public:
|
||||
SegmentId allocSegment(SegmentObj *mobj);
|
||||
|
||||
private:
|
||||
void deallocate(SegmentId seg);
|
||||
void createClassTable();
|
||||
|
||||
SegmentId findFreeSegment() const;
|
||||
|
||||
/**
|
||||
* This implements our handling of scripts greater than 64K in size.
|
||||
* They occur sporadically in SCI3 games (and in The Realm (SCI2.1),
|
||||
* if we ever decide to support it). It works by "stealing" the upper
|
||||
* two bits of the segment value to use as extra offset bits, making
|
||||
* the maximum offset 0x3FFFF (262143). This is enough.
|
||||
*
|
||||
* The "actual" segment, then, is the segment value with the upper two
|
||||
* bits masked out.
|
||||
*/
|
||||
SegmentId getActualSegment(SegmentId seg) const;
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_SEG_MANAGER_H
|
||||
290
engines/sci/engine/segment.cpp
Normal file
290
engines/sci/engine/segment.cpp
Normal file
@@ -0,0 +1,290 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/endian.h"
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/object.h"
|
||||
#include "sci/engine/script.h" // for SCI_OBJ_EXPORTS and SCI_OBJ_SYNONYMS
|
||||
#include "sci/engine/segment.h"
|
||||
#include "sci/engine/seg_manager.h"
|
||||
#include "sci/engine/state.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
//#define GC_DEBUG // Debug garbage collection
|
||||
//#define GC_DEBUG_VERBOSE // Debug garbage verbosely
|
||||
|
||||
SegmentObj *SegmentObj::createSegmentObj(SegmentType type) {
|
||||
SegmentObj *mem = nullptr;
|
||||
switch (type) {
|
||||
case SEG_TYPE_SCRIPT:
|
||||
mem = new Script();
|
||||
break;
|
||||
case SEG_TYPE_CLONES:
|
||||
mem = new CloneTable();
|
||||
break;
|
||||
case SEG_TYPE_LOCALS:
|
||||
mem = new LocalVariables();
|
||||
break;
|
||||
case SEG_TYPE_STACK:
|
||||
mem = new DataStack();
|
||||
break;
|
||||
case SEG_TYPE_HUNK:
|
||||
mem = new HunkTable();
|
||||
break;
|
||||
case SEG_TYPE_LISTS:
|
||||
mem = new ListTable();
|
||||
break;
|
||||
case SEG_TYPE_NODES:
|
||||
mem = new NodeTable();
|
||||
break;
|
||||
case SEG_TYPE_DYNMEM:
|
||||
mem = new DynMem();
|
||||
break;
|
||||
#ifdef ENABLE_SCI32
|
||||
case SEG_TYPE_ARRAY:
|
||||
mem = new ArrayTable();
|
||||
break;
|
||||
case SEG_TYPE_BITMAP:
|
||||
mem = new BitmapTable();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
error("Unknown SegmentObj type %d", type);
|
||||
break;
|
||||
}
|
||||
|
||||
assert(mem);
|
||||
assert(mem->_type == type);
|
||||
return mem;
|
||||
}
|
||||
|
||||
SegmentRef SegmentObj::dereference(reg_t pointer) {
|
||||
error("Error: Trying to dereference pointer %04x:%04x to inappropriate segment",
|
||||
PRINT_REG(pointer));
|
||||
return SegmentRef();
|
||||
}
|
||||
|
||||
//-------------------- clones --------------------
|
||||
|
||||
Common::Array<reg_t> CloneTable::listAllOutgoingReferences(reg_t addr) const {
|
||||
Common::Array<reg_t> tmp;
|
||||
// assert(addr.segment == _segId);
|
||||
|
||||
if (!isValidEntry(addr.getOffset())) {
|
||||
error("Unexpected request for outgoing references from clone at %04x:%04x", PRINT_REG(addr));
|
||||
}
|
||||
|
||||
const Clone *clone = &at(addr.getOffset());
|
||||
|
||||
// Emit all member variables (including references to the 'super' delegate)
|
||||
for (uint i = 0; i < clone->getVarCount(); i++)
|
||||
tmp.push_back(clone->getVariable(i));
|
||||
|
||||
// Note that this also includes the 'base' object, which is part of the script and therefore also emits the locals.
|
||||
tmp.push_back(clone->getPos());
|
||||
//debugC(kDebugLevelGC, "[GC] Reporting clone-pos %04x:%04x", PRINT_REG(clone->pos));
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void CloneTable::freeAtAddress(SegManager *segMan, reg_t addr) {
|
||||
#ifdef GC_DEBUG
|
||||
Object *victim_obj = &at(addr.getOffset());
|
||||
|
||||
if (!(victim_obj->_flags & OBJECT_FLAG_FREED))
|
||||
warning("[GC] Clone %04x:%04x not reachable and not freed (freeing now)", PRINT_REG(addr));
|
||||
#ifdef GC_DEBUG_VERBOSE
|
||||
else
|
||||
warning("[GC-DEBUG] Clone %04x:%04x: Freeing", PRINT_REG(addr));
|
||||
|
||||
warning("[GC] Clone had pos %04x:%04x", PRINT_REG(victim_obj->pos));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
freeEntry(addr.getOffset());
|
||||
}
|
||||
|
||||
|
||||
//-------------------- locals --------------------
|
||||
|
||||
SegmentRef LocalVariables::dereference(reg_t pointer) {
|
||||
SegmentRef ret;
|
||||
ret.isRaw = false; // reg_t based data!
|
||||
ret.maxSize = (_locals.size() - pointer.getOffset() / 2) * 2;
|
||||
|
||||
if (pointer.getOffset() & 1) {
|
||||
ret.maxSize -= 1;
|
||||
ret.skipByte = true;
|
||||
}
|
||||
|
||||
if (ret.maxSize > 0) {
|
||||
ret.reg = &_locals[pointer.getOffset() / 2];
|
||||
} else {
|
||||
if ((g_sci->getEngineState()->currentRoomNumber() == 160 ||
|
||||
g_sci->getEngineState()->currentRoomNumber() == 220)
|
||||
&& g_sci->getGameId() == GID_LAURABOW2) {
|
||||
// WORKAROUND: Happens in two places during the intro of LB2CD, both
|
||||
// from kMemory(peek):
|
||||
// - room 160: Heap 160 has 83 local variables (0-82), and the game
|
||||
// asks for variables at indices 83 - 90 too.
|
||||
// - room 220: Heap 220 has 114 local variables (0-113), and the
|
||||
// game asks for variables at indices 114-120 too.
|
||||
} else {
|
||||
error("LocalVariables::dereference: Offset at end or out of bounds %04x:%04x", PRINT_REG(pointer));
|
||||
}
|
||||
ret.reg = nullptr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
reg_t LocalVariables::findCanonicAddress(SegManager *segMan, reg_t addr) const {
|
||||
// Reference the owning script
|
||||
SegmentId owner_seg = segMan->getScriptSegment(script_id);
|
||||
assert(owner_seg > 0);
|
||||
return make_reg(owner_seg, 0);
|
||||
}
|
||||
|
||||
Common::Array<reg_t> LocalVariables::listAllOutgoingReferences(reg_t addr) const {
|
||||
Common::Array<reg_t> tmp;
|
||||
for (uint i = 0; i < _locals.size(); i++)
|
||||
tmp.push_back(_locals[i]);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
//-------------------- stack --------------------
|
||||
|
||||
SegmentRef DataStack::dereference(reg_t pointer) {
|
||||
SegmentRef ret;
|
||||
ret.isRaw = false; // reg_t based data!
|
||||
ret.maxSize = (_capacity - pointer.getOffset() / 2) * 2;
|
||||
|
||||
if (pointer.getOffset() & 1) {
|
||||
ret.maxSize -= 1;
|
||||
ret.skipByte = true;
|
||||
}
|
||||
|
||||
ret.reg = &_entries[pointer.getOffset() / 2];
|
||||
return ret;
|
||||
}
|
||||
|
||||
Common::Array<reg_t> DataStack::listAllOutgoingReferences(reg_t object) const {
|
||||
Common::Array<reg_t> tmp;
|
||||
for (uint i = 0; i < _capacity; i++)
|
||||
tmp.push_back(_entries[i]);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
//-------------------- lists --------------------
|
||||
|
||||
Common::Array<reg_t> ListTable::listAllOutgoingReferences(reg_t addr) const {
|
||||
Common::Array<reg_t> tmp;
|
||||
if (!isValidEntry(addr.getOffset())) {
|
||||
error("Invalid list referenced for outgoing references: %04x:%04x", PRINT_REG(addr));
|
||||
}
|
||||
|
||||
const List *list = &at(addr.getOffset());
|
||||
|
||||
tmp.push_back(list->first);
|
||||
tmp.push_back(list->last);
|
||||
// We could probably get away with just one of them, but
|
||||
// let's be conservative here.
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
//-------------------- nodes --------------------
|
||||
|
||||
Common::Array<reg_t> NodeTable::listAllOutgoingReferences(reg_t addr) const {
|
||||
Common::Array<reg_t> tmp;
|
||||
if (!isValidEntry(addr.getOffset())) {
|
||||
error("Invalid node referenced for outgoing references: %04x:%04x", PRINT_REG(addr));
|
||||
}
|
||||
const Node *node = &at(addr.getOffset());
|
||||
|
||||
// We need all four here. Can't just stick with 'pred' OR 'succ' because node operations allow us
|
||||
// to walk around from any given node
|
||||
tmp.push_back(node->pred);
|
||||
tmp.push_back(node->succ);
|
||||
tmp.push_back(node->key);
|
||||
tmp.push_back(node->value);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
//-------------------- dynamic memory --------------------
|
||||
|
||||
SegmentRef DynMem::dereference(reg_t pointer) {
|
||||
SegmentRef ret;
|
||||
ret.isRaw = true;
|
||||
ret.maxSize = _size - pointer.getOffset();
|
||||
ret.raw = _buf + pointer.getOffset();
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
|
||||
SegmentRef ArrayTable::dereference(reg_t pointer) {
|
||||
SegmentRef ret;
|
||||
|
||||
SciArray &array = at(pointer.getOffset());
|
||||
const bool isRaw = array.getType() == kArrayTypeByte || array.getType() == kArrayTypeString;
|
||||
|
||||
ret.isRaw = isRaw;
|
||||
ret.maxSize = array.byteSize();
|
||||
if (isRaw) {
|
||||
ret.raw = (byte *)array.getRawData();
|
||||
} else {
|
||||
ret.reg = (reg_t *)array.getRawData();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Common::Array<reg_t> ArrayTable::listAllOutgoingReferences(reg_t addr) const {
|
||||
Common::Array<reg_t> refs;
|
||||
if (!isValidEntry(addr.getOffset())) {
|
||||
// Scripts may still hold references to array memory that has been
|
||||
// explicitly freed; ignore these references
|
||||
return refs;
|
||||
}
|
||||
|
||||
SciArray &array = const_cast<SciArray &>(at(addr.getOffset()));
|
||||
if (array.getType() == kArrayTypeID || array.getType() == kArrayTypeInt16) {
|
||||
for (uint16 i = 0; i < array.size(); ++i) {
|
||||
const reg_t value = array.getAsID(i);
|
||||
if (value.isPointer()) {
|
||||
refs.push_back(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
1207
engines/sci/engine/segment.h
Normal file
1207
engines/sci/engine/segment.h
Normal file
File diff suppressed because it is too large
Load Diff
366
engines/sci/engine/selector.cpp
Normal file
366
engines/sci/engine/selector.cpp
Normal file
@@ -0,0 +1,366 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/scriptdebug.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/selector.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
#if 1
|
||||
|
||||
#define FIND_SELECTOR(_slc_) _selectorCache._slc_ = findSelector(#_slc_)
|
||||
#define FIND_SELECTOR2(_slc_, _slcstr_) _selectorCache._slc_ = findSelector(_slcstr_)
|
||||
|
||||
#else
|
||||
|
||||
// The defines below can be used to construct static selector tables for games which don't have
|
||||
// a vocab.997 resource, by dumping the selector table from other similar versions or games
|
||||
#define FIND_SELECTOR(_slc_) _selectorCache._slc_ = findSelector(#_slc_); \
|
||||
debugN("\t{ \"%s\", %d },\n", #_slc_, _selectorCache._slc_)
|
||||
|
||||
#define FIND_SELECTOR2(_slc_, _slcstr_) _selectorCache._slc_ = findSelector(_slcstr_); \
|
||||
debugN("\t{ \"%s\", %d },\n", _slcstr_, _selectorCache._slc_)
|
||||
|
||||
#endif
|
||||
|
||||
void Kernel::mapSelectors() {
|
||||
// species
|
||||
// superClass
|
||||
FIND_SELECTOR2(_info_, "-info-");
|
||||
FIND_SELECTOR(y);
|
||||
FIND_SELECTOR(x);
|
||||
FIND_SELECTOR(view);
|
||||
FIND_SELECTOR(loop);
|
||||
FIND_SELECTOR(cel);
|
||||
FIND_SELECTOR(underBits);
|
||||
FIND_SELECTOR(nsTop);
|
||||
FIND_SELECTOR(nsLeft);
|
||||
FIND_SELECTOR(nsBottom);
|
||||
FIND_SELECTOR(nsRight);
|
||||
FIND_SELECTOR(lsTop);
|
||||
FIND_SELECTOR(lsLeft);
|
||||
FIND_SELECTOR(lsBottom);
|
||||
FIND_SELECTOR(lsRight);
|
||||
FIND_SELECTOR(signal);
|
||||
FIND_SELECTOR(illegalBits);
|
||||
FIND_SELECTOR(brTop);
|
||||
FIND_SELECTOR(brLeft);
|
||||
FIND_SELECTOR(brBottom);
|
||||
FIND_SELECTOR(brRight);
|
||||
// name
|
||||
// key
|
||||
// time
|
||||
FIND_SELECTOR(text);
|
||||
FIND_SELECTOR(elements);
|
||||
// color
|
||||
// back
|
||||
FIND_SELECTOR(mode);
|
||||
// style
|
||||
FIND_SELECTOR(state);
|
||||
FIND_SELECTOR(font);
|
||||
FIND_SELECTOR(type);
|
||||
// window
|
||||
FIND_SELECTOR(cursor);
|
||||
FIND_SELECTOR(max);
|
||||
FIND_SELECTOR(mark);
|
||||
FIND_SELECTOR(sort);
|
||||
// who
|
||||
FIND_SELECTOR(message);
|
||||
// edit
|
||||
FIND_SELECTOR(play);
|
||||
FIND_SELECTOR(restore);
|
||||
FIND_SELECTOR(number);
|
||||
FIND_SELECTOR(handle); // nodePtr
|
||||
FIND_SELECTOR(client);
|
||||
FIND_SELECTOR(dx);
|
||||
FIND_SELECTOR(dy);
|
||||
FIND_SELECTOR2(b_movCnt, "b-moveCnt");
|
||||
FIND_SELECTOR2(b_i1, "b-i1");
|
||||
FIND_SELECTOR2(b_i2, "b-i2");
|
||||
FIND_SELECTOR2(b_di, "b-di");
|
||||
FIND_SELECTOR2(b_xAxis, "b-xAxis");
|
||||
FIND_SELECTOR2(b_incr, "b-incr");
|
||||
FIND_SELECTOR(xStep);
|
||||
FIND_SELECTOR(yStep);
|
||||
FIND_SELECTOR(xLast);
|
||||
FIND_SELECTOR(yLast);
|
||||
FIND_SELECTOR(moveSpeed);
|
||||
FIND_SELECTOR(canBeHere); // cantBeHere
|
||||
FIND_SELECTOR(heading);
|
||||
FIND_SELECTOR(mover);
|
||||
FIND_SELECTOR(doit);
|
||||
FIND_SELECTOR(isBlocked);
|
||||
FIND_SELECTOR(looper);
|
||||
FIND_SELECTOR(priority);
|
||||
FIND_SELECTOR(modifiers);
|
||||
FIND_SELECTOR(replay);
|
||||
// setPri
|
||||
// at
|
||||
// next
|
||||
// done
|
||||
// width
|
||||
FIND_SELECTOR(wordFail);
|
||||
FIND_SELECTOR(syntaxFail);
|
||||
// semanticFail
|
||||
// pragmaFail
|
||||
// said
|
||||
FIND_SELECTOR(claimed);
|
||||
// value
|
||||
// save
|
||||
// restore
|
||||
// title
|
||||
// button
|
||||
// icon
|
||||
// draw
|
||||
FIND_SELECTOR2(delete_, "delete");
|
||||
FIND_SELECTOR(z);
|
||||
// -----------------------------
|
||||
FIND_SELECTOR(size);
|
||||
FIND_SELECTOR(moveDone);
|
||||
FIND_SELECTOR(vol);
|
||||
FIND_SELECTOR(pri);
|
||||
FIND_SELECTOR(min);
|
||||
FIND_SELECTOR(sec);
|
||||
FIND_SELECTOR(frame);
|
||||
FIND_SELECTOR(dataInc);
|
||||
FIND_SELECTOR(palette);
|
||||
FIND_SELECTOR(cantBeHere);
|
||||
FIND_SELECTOR(nodePtr);
|
||||
FIND_SELECTOR(flags);
|
||||
FIND_SELECTOR(points);
|
||||
FIND_SELECTOR(syncCue);
|
||||
FIND_SELECTOR(syncTime);
|
||||
FIND_SELECTOR(printLang);
|
||||
FIND_SELECTOR(subtitleLang);
|
||||
FIND_SELECTOR(parseLang);
|
||||
FIND_SELECTOR(overlay);
|
||||
FIND_SELECTOR(topString);
|
||||
FIND_SELECTOR(scaleSignal);
|
||||
FIND_SELECTOR(scaleX);
|
||||
FIND_SELECTOR(scaleY);
|
||||
FIND_SELECTOR(maxScale);
|
||||
FIND_SELECTOR(vanishingX);
|
||||
FIND_SELECTOR(vanishingY);
|
||||
FIND_SELECTOR(iconIndex);
|
||||
FIND_SELECTOR(select);
|
||||
FIND_SELECTOR(handsOff);
|
||||
FIND_SELECTOR(setStep);
|
||||
FIND_SELECTOR(setMotion);
|
||||
FIND_SELECTOR(cycleSpeed);
|
||||
FIND_SELECTOR(owner);
|
||||
FIND_SELECTOR(curPos);
|
||||
FIND_SELECTOR(update);
|
||||
FIND_SELECTOR(canInput);
|
||||
FIND_SELECTOR(input);
|
||||
FIND_SELECTOR(controls);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
FIND_SELECTOR(data);
|
||||
FIND_SELECTOR(picture);
|
||||
FIND_SELECTOR(bitmap);
|
||||
FIND_SELECTOR(plane);
|
||||
FIND_SELECTOR(top);
|
||||
FIND_SELECTOR(left);
|
||||
FIND_SELECTOR(bottom);
|
||||
FIND_SELECTOR(right);
|
||||
FIND_SELECTOR(seenRect);
|
||||
FIND_SELECTOR(resY);
|
||||
FIND_SELECTOR(resX);
|
||||
FIND_SELECTOR(dimmed);
|
||||
FIND_SELECTOR(fore);
|
||||
FIND_SELECTOR(back);
|
||||
FIND_SELECTOR(skip);
|
||||
FIND_SELECTOR(borderColor);
|
||||
FIND_SELECTOR(width);
|
||||
FIND_SELECTOR(fixPriority);
|
||||
FIND_SELECTOR(mirrored);
|
||||
FIND_SELECTOR(visible);
|
||||
FIND_SELECTOR(useInsetRect);
|
||||
FIND_SELECTOR(inTop);
|
||||
FIND_SELECTOR(inLeft);
|
||||
FIND_SELECTOR(inBottom);
|
||||
FIND_SELECTOR(inRight);
|
||||
FIND_SELECTOR(textTop);
|
||||
FIND_SELECTOR(textLeft);
|
||||
FIND_SELECTOR(textBottom);
|
||||
FIND_SELECTOR(textRight);
|
||||
FIND_SELECTOR(title);
|
||||
FIND_SELECTOR(titleFont);
|
||||
FIND_SELECTOR(titleFore);
|
||||
FIND_SELECTOR(titleBack);
|
||||
FIND_SELECTOR(magnifier);
|
||||
FIND_SELECTOR(frameOut);
|
||||
FIND_SELECTOR(casts);
|
||||
FIND_SELECTOR(setVol);
|
||||
FIND_SELECTOR(reSyncVol);
|
||||
FIND_SELECTOR(set);
|
||||
FIND_SELECTOR(clear);
|
||||
FIND_SELECTOR(show);
|
||||
FIND_SELECTOR(position);
|
||||
FIND_SELECTOR(musicVolume);
|
||||
FIND_SELECTOR(soundVolume);
|
||||
FIND_SELECTOR(initialOff);
|
||||
FIND_SELECTOR(setPos);
|
||||
FIND_SELECTOR(setSize);
|
||||
FIND_SELECTOR(displayValue);
|
||||
FIND_SELECTOR2(new_, "new");
|
||||
FIND_SELECTOR(mainCel);
|
||||
FIND_SELECTOR(move);
|
||||
FIND_SELECTOR(eachElementDo);
|
||||
FIND_SELECTOR(physicalBar);
|
||||
FIND_SELECTOR(init);
|
||||
FIND_SELECTOR(scratch);
|
||||
FIND_SELECTOR(num);
|
||||
FIND_SELECTOR(reallyRestore);
|
||||
FIND_SELECTOR(bookMark);
|
||||
FIND_SELECTOR(fileNumber);
|
||||
FIND_SELECTOR(description);
|
||||
FIND_SELECTOR(dispose);
|
||||
FIND_SELECTOR(masterVolume);
|
||||
FIND_SELECTOR(setCel);
|
||||
FIND_SELECTOR(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
reg_t readSelector(SegManager *segMan, reg_t object, Selector selectorId) {
|
||||
ObjVarRef address;
|
||||
|
||||
if (lookupSelector(segMan, object, selectorId, &address, nullptr) != kSelectorVariable)
|
||||
return NULL_REG;
|
||||
|
||||
if (g_sci->_debugState._activeBreakpointTypes & BREAK_SELECTORREAD) {
|
||||
reg_t curValue = *address.getPointer(segMan);
|
||||
debugPropertyAccess(segMan->getObject(object), object, 0, selectorId,
|
||||
curValue, NULL_REG, segMan, BREAK_SELECTORREAD);
|
||||
}
|
||||
|
||||
return *address.getPointer(segMan);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
void updateInfoFlagViewVisible(Object *obj, int index, bool fromPropertyOp) {
|
||||
if (getSciVersion() >= SCI_VERSION_2 && obj->mustSetViewVisible(index, fromPropertyOp)) {
|
||||
obj->setInfoSelectorFlag(kInfoFlagViewVisible);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t value) {
|
||||
ObjVarRef address;
|
||||
|
||||
if ((selectorId < 0) || (selectorId > (int)g_sci->getKernel()->getSelectorNamesSize())) {
|
||||
error("Attempt to write to invalid selector %d. Address %04x:%04x", selectorId, PRINT_REG(object));
|
||||
}
|
||||
|
||||
if (lookupSelector(segMan, object, selectorId, &address, nullptr) != kSelectorVariable) {
|
||||
error("Selector '%s' of object could not be written to. Address %04x:%04x", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
|
||||
}
|
||||
|
||||
if (g_sci->_debugState._activeBreakpointTypes & BREAK_SELECTORWRITE) {
|
||||
reg_t curValue = *address.getPointer(segMan);
|
||||
debugPropertyAccess(segMan->getObject(object), object, 0, selectorId,
|
||||
curValue, value, segMan, BREAK_SELECTORWRITE);
|
||||
}
|
||||
|
||||
*address.getPointer(segMan) = value;
|
||||
#ifdef ENABLE_SCI32
|
||||
updateInfoFlagViewVisible(segMan->getObject(object), address.varindex);
|
||||
#endif
|
||||
}
|
||||
|
||||
void invokeSelector(EngineState *s, reg_t object, int selectorId,
|
||||
int k_argc, StackPtr k_argp, int argc, const reg_t *argv) {
|
||||
int i;
|
||||
int framesize = 2 + 1 * argc;
|
||||
int slc_type;
|
||||
StackPtr stackframe = k_argp + k_argc;
|
||||
|
||||
stackframe[0] = make_reg(0, selectorId); // The selector we want to call
|
||||
stackframe[1] = make_reg(0, argc); // Argument count
|
||||
|
||||
slc_type = lookupSelector(s->_segMan, object, selectorId, nullptr, nullptr);
|
||||
|
||||
if (slc_type == kSelectorNone) {
|
||||
error("invokeSelector: Selector '%s' could not be invoked. Address %04x:%04x", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
|
||||
}
|
||||
if (slc_type == kSelectorVariable) {
|
||||
error("invokeSelector: Attempting to invoke variable selector %s. Address %04x:%04x", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
|
||||
}
|
||||
|
||||
for (i = 0; i < argc; i++)
|
||||
stackframe[2 + i] = argv[i]; // Write each argument
|
||||
|
||||
ExecStack *xstack;
|
||||
|
||||
// Now commit the actual function:
|
||||
xstack = send_selector(s, object, object, stackframe, framesize, stackframe);
|
||||
|
||||
xstack->sp += argc + 2;
|
||||
xstack->fp += argc + 2;
|
||||
|
||||
run_vm(s); // Start a new vm
|
||||
}
|
||||
|
||||
SelectorType lookupSelector(SegManager *segMan, reg_t obj_location, Selector selectorId, ObjVarRef *varp, reg_t *fptr) {
|
||||
const Object *obj = segMan->getObject(obj_location);
|
||||
bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);
|
||||
|
||||
// Early SCI versions used the LSB in the selector ID as a read/write
|
||||
// toggle, meaning that we must remove it for selector lookup.
|
||||
if (oldScriptHeader)
|
||||
selectorId &= ~1;
|
||||
|
||||
if (!obj) {
|
||||
error("lookupSelector: Attempt to send to non-object or invalid script. Address %04x:%04x", PRINT_REG(obj_location));
|
||||
}
|
||||
|
||||
int index = obj->locateVarSelector(segMan, selectorId);
|
||||
|
||||
if (index >= 0) {
|
||||
// Found it as a variable
|
||||
if (varp) {
|
||||
varp->obj = obj_location;
|
||||
varp->varindex = index;
|
||||
}
|
||||
return kSelectorVariable;
|
||||
} else {
|
||||
// Check if it's a method, with recursive lookup in superclasses
|
||||
while (obj) {
|
||||
index = obj->funcSelectorPosition(selectorId);
|
||||
if (index >= 0) {
|
||||
if (fptr)
|
||||
*fptr = obj->getFunction(index);
|
||||
|
||||
return kSelectorMethod;
|
||||
} else {
|
||||
obj = segMan->getObject(obj->getSuperClassSelector());
|
||||
}
|
||||
}
|
||||
|
||||
return kSelectorNone;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
253
engines/sci/engine/selector.h
Normal file
253
engines/sci/engine/selector.h
Normal file
@@ -0,0 +1,253 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_SELECTOR_H
|
||||
#define SCI_ENGINE_SELECTOR_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
#include "sci/engine/vm_types.h" // for reg_t
|
||||
#include "sci/engine/vm.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
/** Contains selector IDs for a few selected selectors */
|
||||
struct SelectorCache {
|
||||
SelectorCache() {
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
// Statically defined selectors, (almost the) same in all SCI versions
|
||||
Selector _info_; ///< Removed in SCI3
|
||||
Selector y;
|
||||
Selector x;
|
||||
Selector view, loop, cel; ///< Description of a specific image
|
||||
Selector underBits; ///< Used by the graphics subroutines to store backupped BG pic data
|
||||
Selector nsTop, nsLeft, nsBottom, nsRight; ///< View boundaries ('now seen')
|
||||
Selector lsTop, lsLeft, lsBottom, lsRight; ///< Used by Animate() subfunctions and scroll list controls
|
||||
Selector signal; ///< Used by Animate() to control a view's behavior
|
||||
Selector illegalBits; ///< Used by CanBeHere
|
||||
Selector brTop, brLeft, brBottom, brRight; ///< Bounding Rectangle
|
||||
// name, key, time
|
||||
Selector text; ///< Used by controls
|
||||
Selector elements; ///< Used by SetSynonyms()
|
||||
// color, back
|
||||
Selector mode; ///< Used by text controls (-> DrawControl())
|
||||
// style
|
||||
Selector state, font, type;///< Used by controls
|
||||
// window
|
||||
Selector cursor; ///< Used by EditControl
|
||||
Selector max; ///< Used by EditControl, removed in SCI3
|
||||
Selector mark; //< Used by list controls (script internal, is needed by us for the QfG import rooms)
|
||||
Selector sort; //< Used by list controls (script internal, is needed by us for QfG3 import room)
|
||||
// who
|
||||
Selector message; ///< Used by GetEvent
|
||||
// edit
|
||||
Selector play; ///< Play function (first function to be called)
|
||||
Selector restore;
|
||||
Selector number;
|
||||
Selector handle; ///< Replaced by nodePtr in SCI1+
|
||||
Selector nodePtr; ///< Replaces handle in SCI1+
|
||||
Selector client; ///< The object that wants to be moved
|
||||
Selector dx, dy; ///< Deltas
|
||||
Selector b_movCnt, b_i1, b_i2, b_di, b_xAxis, b_incr; ///< Various Bresenham vars
|
||||
Selector xStep, yStep; ///< BR adjustments
|
||||
Selector xLast, yLast; ///< BR last position of client
|
||||
Selector moveSpeed; ///< Used for DoBresen
|
||||
Selector canBeHere; ///< Funcselector: Checks for movement validity in SCI0
|
||||
Selector heading, mover; ///< Used in DoAvoider
|
||||
Selector doit; ///< Called (!) by the Animate() system call
|
||||
Selector isBlocked, looper; ///< Used in DoAvoider
|
||||
Selector priority;
|
||||
Selector modifiers; ///< Used by GetEvent
|
||||
Selector replay; ///< Replay function
|
||||
// setPri, at, next, done, width
|
||||
Selector wordFail, syntaxFail; ///< Used by Parse()
|
||||
// semanticFail, pragmaFail
|
||||
// said
|
||||
Selector claimed; ///< Used generally by the event mechanism
|
||||
// value, save, restore, title, button, icon, draw
|
||||
Selector delete_; ///< Called by Animate() to dispose a view object
|
||||
Selector z;
|
||||
Selector setPri;
|
||||
|
||||
// SCI1+ static selectors
|
||||
Selector parseLang;
|
||||
Selector printLang; ///< Used for i18n
|
||||
Selector subtitleLang;
|
||||
Selector size;
|
||||
Selector points; ///< Used by AvoidPath()
|
||||
Selector palette; ///< Used by the SCI0-SCI1.1 animate code, unused in SCI2-SCI2.1, removed in SCI3
|
||||
Selector dataInc; ///< Used to sync music with animations, removed in SCI3
|
||||
// handle (in SCI1)
|
||||
Selector min; ///< SMPTE time format
|
||||
Selector sec;
|
||||
Selector frame;
|
||||
Selector vol;
|
||||
Selector pri;
|
||||
// perform
|
||||
Selector moveDone; ///< used for DoBresen
|
||||
|
||||
// SCI1 selectors which have been moved a bit in SCI1.1, but otherwise static
|
||||
Selector cantBeHere; ///< Checks for movement avoidance in SCI1+. Replaces canBeHere
|
||||
Selector topString; ///< SCI1 scroll lists use this instead of lsTop. Removed in SCI3
|
||||
Selector flags;
|
||||
|
||||
// SCI1+ audio sync related selectors, not static. They're used for lip syncing in
|
||||
// CD talkie games
|
||||
Selector syncCue; ///< Used by DoSync()
|
||||
Selector syncTime;
|
||||
|
||||
// SCI1.1 specific selectors
|
||||
Selector scaleSignal; //< Used by kAnimate() for cel scaling (SCI1.1+)
|
||||
Selector scaleX, scaleY; ///< SCI1.1 view scaling
|
||||
Selector maxScale; ///< SCI1.1 view scaling, limit for cel, when using global scaling
|
||||
Selector vanishingX; ///< SCI1.1 view scaling, used by global scaling
|
||||
Selector vanishingY; ///< SCI1.1 view scaling, used by global scaling
|
||||
|
||||
// Used for auto detection purposes
|
||||
Selector overlay; ///< Used to determine if a game is using old gfx functions or not
|
||||
|
||||
// SCI1.1 Mac icon bar selectors
|
||||
Selector iconIndex; ///< Used to index icon bar objects
|
||||
Selector select;
|
||||
|
||||
Selector handsOff;
|
||||
Selector setStep;
|
||||
Selector setMotion;
|
||||
Selector cycleSpeed;
|
||||
Selector owner;
|
||||
|
||||
Selector curPos; // for LSL6 volume sync
|
||||
Selector update; // for LSL6 volume sync
|
||||
|
||||
Selector canInput; // for Phant2 restore from launcher and checking if user has input
|
||||
Selector input; // for checking if user has input
|
||||
Selector controls; // for checking if user has input
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
Selector data; // Used by Array()/String()
|
||||
Selector picture; // Used to hold the picture ID for SCI32 pictures
|
||||
Selector bitmap; // Used to hold the text bitmap for SCI32 texts
|
||||
|
||||
Selector plane;
|
||||
Selector top, left, bottom, right;
|
||||
Selector resX, resY;
|
||||
|
||||
Selector fore;
|
||||
Selector back;
|
||||
Selector skip;
|
||||
Selector dimmed;
|
||||
Selector borderColor;
|
||||
Selector width;
|
||||
|
||||
Selector fixPriority;
|
||||
Selector mirrored;
|
||||
Selector visible;
|
||||
|
||||
Selector seenRect;
|
||||
Selector useInsetRect;
|
||||
Selector inTop, inLeft, inBottom, inRight;
|
||||
Selector textTop, textLeft, textBottom, textRight;
|
||||
Selector title, titleFont, titleFore, titleBack;
|
||||
|
||||
Selector magnifier;
|
||||
Selector frameOut;
|
||||
Selector casts; // needed for sync'ing screen items/planes with scripts, when our save/restore code is patched in (see GfxFrameout::syncWithScripts)
|
||||
Selector setVol; // for GK2 volume sync on restore
|
||||
Selector reSyncVol; // for Torin volume sync on restore
|
||||
Selector set; // for LSL6hires subtitle sync
|
||||
Selector clear; // for LSL6hires subtitle sync
|
||||
Selector show; // for GK1 volume sync
|
||||
Selector position; // for GK1 volume sync
|
||||
Selector musicVolume; // for GK1 volume sync
|
||||
Selector soundVolume; // for GK1 volume sync
|
||||
Selector initialOff; // for GK2 volume sync
|
||||
Selector setPos; // for Torin volume sync
|
||||
Selector setSize; // for PQ4 volume sync
|
||||
Selector displayValue; // for PQ:SWAT volume sync
|
||||
Selector new_; // for Torin/LSL7 save/load patching
|
||||
Selector mainCel; // for MGDX volume sync
|
||||
Selector move; // for Phant2 volume sync
|
||||
Selector eachElementDo; // for Phant2 volume sync
|
||||
Selector physicalBar; // for Phant2 volume sync
|
||||
Selector init; // for Phant2 save/load patching
|
||||
Selector scratch; // for Phant2 save/load patching
|
||||
Selector num; // for Phant2 restore from launcher
|
||||
Selector reallyRestore; // for Phant2 restore from launcher
|
||||
Selector bookMark; // for Phant2 auto-save
|
||||
Selector fileNumber; // for RAMA save/load
|
||||
Selector description; // for RAMA save/load
|
||||
Selector dispose; // for RAMA save/load save from launcher
|
||||
Selector masterVolume; // for RAMA volume sync
|
||||
Selector setCel; // for RAMA volume sync
|
||||
Selector value; // for QFG4 import dialog
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Map a selector name to a selector id. Shortcut for accessing the selector cache.
|
||||
*/
|
||||
#define SELECTOR(_slc_) (g_sci->getKernel()->_selectorCache._slc_)
|
||||
|
||||
/**
|
||||
* Retrieves a selector from an object.
|
||||
* @param segMan the segment mananger
|
||||
* @param _obj_ the address of the object which the selector should be read from
|
||||
* @param _slc_ the selector to refad
|
||||
* @return the selector value as a reg_t
|
||||
* This macro halts on error. 'selector' must be a selector name registered in vm.h's
|
||||
* SelectorCache and mapped in script.cpp.
|
||||
*/
|
||||
reg_t readSelector(SegManager *segMan, reg_t object, Selector selectorId);
|
||||
#define readSelectorValue(segMan, _obj_, _slc_) (readSelector(segMan, _obj_, _slc_).getOffset())
|
||||
|
||||
/**
|
||||
* Writes a selector value to an object.
|
||||
* @param segMan the segment mananger
|
||||
* @param _obj_ the address of the object which the selector should be written to
|
||||
* @param _slc_ the selector to read
|
||||
* @param _val_ the value to write
|
||||
* This macro halts on error. 'selector' must be a selector name registered in vm.h's
|
||||
* SelectorCache and mapped in script.cpp.
|
||||
*/
|
||||
void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t value);
|
||||
#define writeSelectorValue(segMan, _obj_, _slc_, _val_) writeSelector(segMan, _obj_, _slc_, make_reg(0, _val_))
|
||||
|
||||
/**
|
||||
* Invokes a selector from an object.
|
||||
*/
|
||||
void invokeSelector(EngineState *s, reg_t object, int selectorId,
|
||||
int k_argc, StackPtr k_argp, int argc = 0, const reg_t *argv = 0);
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
/**
|
||||
* SCI32 set kInfoFlagViewVisible in the -info- selector if a certain
|
||||
* range of properties was written to.
|
||||
* This function checks if index is in the right range, and sets the flag
|
||||
* on obj.-info- if it is.
|
||||
*/
|
||||
void updateInfoFlagViewVisible(Object *obj, int index, bool fromPropertyOp = false);
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_SELECTOR_H
|
||||
477
engines/sci/engine/state.cpp
Normal file
477
engines/sci/engine/state.cpp
Normal file
@@ -0,0 +1,477 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#include "common/system.h"
|
||||
|
||||
#include "sci/sci.h" // for INCLUDE_OLDGFX
|
||||
#include "sci/debug.h" // for g_debug_sleeptime_factor
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/file.h"
|
||||
#include "sci/engine/guest_additions.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/vm.h"
|
||||
#include "sci/engine/script.h"
|
||||
#include "sci/engine/message.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// Maps half-width single-byte SJIS to full-width double-byte SJIS
|
||||
// Note: SSCI maps 0x5C (the Yen symbol) to 0x005C, which terminates
|
||||
// the string with the leading 0x00 byte. We map Yen to 0x818F.
|
||||
static const uint16 s_halfWidthSJISMap[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x81A8, 0x81A9, 0x81AA, 0x81AB,
|
||||
0x8140, 0x8149, 0x818D, 0x8194, 0x8190, 0x8193, 0x8195, 0x818C,
|
||||
0x8169, 0x816A, 0x8196, 0x817B, 0x8143, 0x817C, 0x8144, 0x815E,
|
||||
0x824F, 0x8250, 0x8251, 0x8252, 0x8253, 0x8254, 0x8255, 0x8256,
|
||||
0x8257, 0x8258, 0x8146, 0x8147, 0x8183, 0x8181, 0x8184, 0x8148,
|
||||
0x8197, 0x8260, 0x8261, 0x8262, 0x8263, 0x8264, 0x8265, 0x8266,
|
||||
0x8267, 0x8268, 0x8269, 0x826A, 0x826B, 0x826C, 0x826D, 0x826E,
|
||||
0x826F, 0x8270, 0x8271, 0x8272, 0x8273, 0x8274, 0x8275, 0x8276,
|
||||
0x8277, 0x8278, 0x8279, 0x816D, 0x818F /* 0x005C */, 0x816E, 0x814F, 0x8151,
|
||||
0x8280, 0x8281, 0x8282, 0x8283, 0x8284, 0x8285, 0x8286, 0x8287,
|
||||
0x8288, 0x8289, 0x828A, 0x828B, 0x828C, 0x828D, 0x828E, 0x828F,
|
||||
0x8290, 0x8291, 0x8292, 0x8293, 0x8294, 0x8295, 0x8296, 0x8297,
|
||||
0x8298, 0x8299, 0x829A, 0x816F, 0x8162, 0x8170, 0x8160, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0x8140, 0x8142, 0x8175, 0x8176, 0x8141, 0x8145, 0x8392, 0x8340,
|
||||
0x8342, 0x8344, 0x8346, 0x8348, 0x8383, 0x8385, 0x8387, 0x8362,
|
||||
0x815C, 0x8341, 0x8343, 0x8345, 0x8347, 0x8349, 0x834A, 0x834C,
|
||||
0x834E, 0x8350, 0x8352, 0x8354, 0x8356, 0x8358, 0x835A, 0x835C,
|
||||
0x835E, 0x8360, 0x8363, 0x8365, 0x8367, 0x8369, 0x836A, 0x836B,
|
||||
0x836C, 0x836D, 0x836E, 0x8371, 0x8374, 0x8377, 0x837A, 0x837D,
|
||||
0x837E, 0x8380, 0x8381, 0x8382, 0x8384, 0x8386, 0x8388, 0x8389,
|
||||
0x838A, 0x838B, 0x838C, 0x838D, 0x838F, 0x8393, 0x814A, 0x814B,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
EngineState::EngineState(SegManager *segMan) :
|
||||
_segMan(segMan),
|
||||
_msgState(nullptr),
|
||||
_dirseeker() {
|
||||
|
||||
reset(false);
|
||||
}
|
||||
|
||||
EngineState::~EngineState() {
|
||||
delete _msgState;
|
||||
}
|
||||
|
||||
void EngineState::reset(bool isRestoring) {
|
||||
if (!isRestoring) {
|
||||
_memorySegmentSize = 0;
|
||||
_fileHandles.resize(5);
|
||||
abortScriptProcessing = kAbortNone;
|
||||
} else {
|
||||
g_sci->_guestAdditions->reset();
|
||||
}
|
||||
|
||||
_delayedRestoreGameId = -1;
|
||||
|
||||
_kq7MacSaveGameId = -1;
|
||||
_kq7MacSaveGameDescription.clear();
|
||||
|
||||
executionStackBase = 0;
|
||||
_executionStackPosChanged = false;
|
||||
stack_base = nullptr;
|
||||
stack_top = nullptr;
|
||||
|
||||
r_acc = NULL_REG;
|
||||
r_prev = NULL_REG;
|
||||
r_rest = 0;
|
||||
|
||||
lastWaitTime = 0;
|
||||
|
||||
gcCountDown = 0;
|
||||
|
||||
_eventCounter = 0;
|
||||
_paletteSetIntensityCounter = 0;
|
||||
_throttleLastTime = 0;
|
||||
_throttleTrigger = false;
|
||||
|
||||
_lastSaveVirtualId = SAVEGAMEID_OFFICIALRANGE_START;
|
||||
_lastSaveNewId = 0;
|
||||
|
||||
_chosenQfGImportItem = 0;
|
||||
|
||||
_cursorWorkaroundActive = false;
|
||||
|
||||
scriptStepCounter = 0;
|
||||
scriptGCInterval = GC_INTERVAL;
|
||||
}
|
||||
|
||||
void EngineState::speedThrottler(uint32 neededSleep) {
|
||||
if (_throttleTrigger) {
|
||||
uint32 curTime = g_system->getMillis();
|
||||
uint32 duration = curTime - _throttleLastTime;
|
||||
|
||||
if (duration < neededSleep) {
|
||||
g_sci->sleep(neededSleep - duration);
|
||||
_throttleLastTime = g_system->getMillis();
|
||||
} else {
|
||||
_throttleLastTime = curTime;
|
||||
}
|
||||
_throttleTrigger = false;
|
||||
}
|
||||
}
|
||||
|
||||
uint16 EngineState::wait(uint16 ticks) {
|
||||
uint32 time = g_system->getMillis();
|
||||
|
||||
uint32 ms = ticks * 1000 / 60;
|
||||
uint32 duration = time - lastWaitTime;
|
||||
if (ms > duration) {
|
||||
uint32 sleepTime = ms - duration;
|
||||
sleepTime *= g_debug_sleeptime_factor;
|
||||
g_sci->sleep(sleepTime);
|
||||
time += sleepTime;
|
||||
}
|
||||
|
||||
uint16 tickDelta = (uint16)(((long)time - lastWaitTime) * 60 / 1000);
|
||||
lastWaitTime = time;
|
||||
return tickDelta;
|
||||
}
|
||||
|
||||
void EngineState::sleep(uint16 ticks) {
|
||||
ticks *= g_debug_sleeptime_factor;
|
||||
g_sci->sleep(ticks * 1000 / 60);
|
||||
}
|
||||
|
||||
void EngineState::initGlobals() {
|
||||
Script *script_000 = _segMan->getScript(1);
|
||||
|
||||
if (script_000->getLocalsCount() == 0)
|
||||
error("Script 0 has no locals block");
|
||||
|
||||
variablesSegment[VAR_GLOBAL] = script_000->getLocalsSegment();
|
||||
variablesBase[VAR_GLOBAL] = variables[VAR_GLOBAL] = script_000->getLocalsBegin();
|
||||
variablesMax[VAR_GLOBAL] = script_000->getLocalsCount();
|
||||
|
||||
// The KQ5 CD Windows interpreter set global 400 to tell the scripts that the
|
||||
// platform was Windows. The global determines which cursors the scripts use,
|
||||
// so we only set this if the user has chosen to use Windows cursors.
|
||||
if (g_sci->getGameId() == GID_KQ5 && g_sci->isCD()) {
|
||||
variables[VAR_GLOBAL][400].setOffset(g_sci->_features->useWindowsCursors());
|
||||
}
|
||||
}
|
||||
|
||||
void EngineState::initMessageState() {
|
||||
delete _msgState;
|
||||
_msgState = new MessageState(_segMan);
|
||||
}
|
||||
|
||||
uint16 EngineState::currentRoomNumber() const {
|
||||
return variables[VAR_GLOBAL][kGlobalVarNewRoomNo].toUint16();
|
||||
}
|
||||
|
||||
void EngineState::setRoomNumber(uint16 roomNumber) {
|
||||
variables[VAR_GLOBAL][kGlobalVarNewRoomNo] = make_reg(0, roomNumber);
|
||||
}
|
||||
|
||||
void EngineState::shrinkStackToBase() {
|
||||
if (_executionStack.size() > 0) {
|
||||
uint size = executionStackBase + 1;
|
||||
assert(_executionStack.size() >= size);
|
||||
Common::List<ExecStack>::iterator iter = _executionStack.begin();
|
||||
for (uint i = 0; i < size; ++i)
|
||||
++iter;
|
||||
_executionStack.erase(iter, _executionStack.end());
|
||||
}
|
||||
}
|
||||
|
||||
static kLanguage charToLanguage(const char c) {
|
||||
switch (c) {
|
||||
case 'F':
|
||||
return K_LANG_FRENCH;
|
||||
case 'S':
|
||||
return K_LANG_SPANISH;
|
||||
case 'I':
|
||||
return K_LANG_ITALIAN;
|
||||
case 'G':
|
||||
return K_LANG_GERMAN;
|
||||
case 'J':
|
||||
case 'j':
|
||||
return K_LANG_JAPANESE;
|
||||
case 'P':
|
||||
return K_LANG_PORTUGUESE;
|
||||
default:
|
||||
return K_LANG_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
Common::String SciEngine::getSciLanguageString(const Common::String &str, kLanguage requestedLanguage, kLanguage *secondaryLanguage, uint16 *languageSplitter) const {
|
||||
kLanguage foundLanguage = K_LANG_NONE;
|
||||
const byte *textPtr = (const byte *)str.c_str();
|
||||
if (secondaryLanguage) {
|
||||
*secondaryLanguage = K_LANG_NONE;
|
||||
}
|
||||
byte curChar = 0;
|
||||
byte curChar2 = 0;
|
||||
|
||||
while (1) {
|
||||
curChar = *textPtr;
|
||||
if (!curChar)
|
||||
break;
|
||||
|
||||
if ((curChar == '%') || (curChar == '#')) {
|
||||
curChar2 = *(textPtr + 1);
|
||||
foundLanguage = charToLanguage(curChar2);
|
||||
|
||||
if (foundLanguage != K_LANG_NONE) {
|
||||
// Return language splitter
|
||||
if (languageSplitter)
|
||||
*languageSplitter = curChar | ( curChar2 << 8 );
|
||||
// Return the secondary language found in the string
|
||||
if (secondaryLanguage)
|
||||
*secondaryLanguage = foundLanguage;
|
||||
break;
|
||||
}
|
||||
}
|
||||
textPtr++;
|
||||
}
|
||||
|
||||
if (foundLanguage == requestedLanguage) {
|
||||
if (curChar2 == 'J' && g_sci->getGameId() != GID_PQ2) {
|
||||
// Japanese including Kanji, displayed with system font
|
||||
// Convert half-width characters to full-width equivalents.
|
||||
// PQ2 does not do this.
|
||||
Common::String fullWidth;
|
||||
|
||||
textPtr += 2; // skip over language splitter
|
||||
|
||||
while (1) {
|
||||
curChar = *textPtr;
|
||||
|
||||
switch (curChar) {
|
||||
case 0: // Terminator NUL
|
||||
return fullWidth;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
textPtr++;
|
||||
|
||||
uint16 mappedChar = s_halfWidthSJISMap[curChar];
|
||||
if (mappedChar) {
|
||||
fullWidth += mappedChar >> 8;
|
||||
fullWidth += mappedChar & 0xFF;
|
||||
} else {
|
||||
// Copy double-byte character
|
||||
curChar2 = *(textPtr++);
|
||||
if (!curChar2) {
|
||||
error("SJIS character %02X is missing second byte", curChar);
|
||||
break;
|
||||
}
|
||||
fullWidth += curChar;
|
||||
fullWidth += curChar2;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
return Common::String((const char *)(textPtr + 2));
|
||||
}
|
||||
}
|
||||
|
||||
if (curChar)
|
||||
return Common::String(str.c_str(), (const char *)textPtr - str.c_str());
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
kLanguage SciEngine::getSciLanguage() {
|
||||
kLanguage lang = K_LANG_ENGLISH;
|
||||
|
||||
if (SELECTOR(printLang) != -1) {
|
||||
lang = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(printLang));
|
||||
|
||||
if ((getSciVersion() >= SCI_VERSION_1_1) || (lang == K_LANG_NONE)) {
|
||||
// If language is set to none, we use the language from the game detector.
|
||||
// SSCI reads this from resource.cfg (early games do not have a language
|
||||
// setting in resource.cfg, but instead have the secondary language number
|
||||
// hardcoded in the game script).
|
||||
// SCI1.1 games always use the language setting from the config file
|
||||
// (essentially disabling runtime language switching).
|
||||
// Note: only a limited number of multilanguage games have been tested
|
||||
// so far, so this information may not be 100% accurate.
|
||||
switch (getLanguage()) {
|
||||
case Common::FR_FRA:
|
||||
lang = K_LANG_FRENCH;
|
||||
// WORKAROUND: The French version of LSL1VGA is a fan patch that's based
|
||||
// on the official Spanish version, with the Spanish content replaced
|
||||
// with French content. The game scripts require printLang to be Spanish
|
||||
// in order to use the French text and load the correct views.
|
||||
if (g_sci->getGameId() == GID_LSL1) {
|
||||
lang = K_LANG_SPANISH;
|
||||
}
|
||||
break;
|
||||
case Common::ES_ESP:
|
||||
lang = K_LANG_SPANISH;
|
||||
break;
|
||||
case Common::IT_ITA:
|
||||
lang = K_LANG_ITALIAN;
|
||||
break;
|
||||
case Common::DE_DEU:
|
||||
lang = K_LANG_GERMAN;
|
||||
break;
|
||||
case Common::JA_JPN:
|
||||
lang = K_LANG_JAPANESE;
|
||||
break;
|
||||
case Common::PT_BRA:
|
||||
lang = K_LANG_PORTUGUESE;
|
||||
break;
|
||||
default:
|
||||
lang = K_LANG_ENGLISH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lang;
|
||||
}
|
||||
|
||||
void SciEngine::setSciLanguage(kLanguage lang) {
|
||||
if (SELECTOR(printLang) != -1)
|
||||
writeSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(printLang), lang);
|
||||
}
|
||||
|
||||
void SciEngine::setSciLanguage() {
|
||||
setSciLanguage(getSciLanguage());
|
||||
}
|
||||
|
||||
Common::String SciEngine::strSplitLanguage(const char *str, uint16 *languageSplitter, const char *sep) {
|
||||
kLanguage activeLanguage = getSciLanguage();
|
||||
kLanguage subtitleLanguage = K_LANG_NONE;
|
||||
|
||||
if (SELECTOR(subtitleLang) != -1)
|
||||
subtitleLanguage = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(subtitleLang));
|
||||
|
||||
kLanguage foundLanguage;
|
||||
Common::String retval = getSciLanguageString(str, activeLanguage, &foundLanguage, languageSplitter);
|
||||
|
||||
// Don't add subtitle when separator is not set, subtitle language is not set, or
|
||||
// string contains only one language
|
||||
if ((sep == nullptr) || (subtitleLanguage == K_LANG_NONE) || (foundLanguage == K_LANG_NONE))
|
||||
return retval;
|
||||
|
||||
// Add subtitle, unless the subtitle language doesn't match the languages in the string
|
||||
if ((subtitleLanguage == K_LANG_ENGLISH) || (subtitleLanguage == foundLanguage)) {
|
||||
retval += sep;
|
||||
retval += getSciLanguageString(str, subtitleLanguage);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void SciEngine::checkVocabularySwitch() {
|
||||
uint16 parserLanguage = 1;
|
||||
if (SELECTOR(parseLang) != -1)
|
||||
parserLanguage = readSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(parseLang));
|
||||
|
||||
if (parserLanguage != _vocabularyLanguage) {
|
||||
delete _vocabulary;
|
||||
_vocabulary = new Vocabulary(_resMan, parserLanguage > 1 ? true : false);
|
||||
_vocabulary->reset();
|
||||
_vocabularyLanguage = parserLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
SciCallOrigin EngineState::getCurrentCallOrigin() const {
|
||||
// IMPORTANT: This method must always return values that match *exactly* the
|
||||
// values in the workaround tables in workarounds.cpp, or workarounds will
|
||||
// be broken
|
||||
|
||||
Common::String curObjectName = _segMan->getObjectName(xs->sendp);
|
||||
Common::String curMethodName;
|
||||
const Script *localScript = _segMan->getScriptIfLoaded(xs->local_segment);
|
||||
if (localScript == nullptr) {
|
||||
error("current script not found at: %04x", xs->local_segment);
|
||||
}
|
||||
int curScriptNr = localScript->getScriptNumber();
|
||||
|
||||
Selector debugSelector = xs->debugSelector;
|
||||
int debugExportId = xs->debugExportId;
|
||||
|
||||
if (xs->debugLocalCallOffset != -1) {
|
||||
// if lastcall was actually a local call search back for a real call
|
||||
Common::List<ExecStack>::const_iterator callIterator = _executionStack.end();
|
||||
while (callIterator != _executionStack.begin()) {
|
||||
callIterator--;
|
||||
const ExecStack &loopCall = *callIterator;
|
||||
if (loopCall.debugSelector != -1 || loopCall.debugExportId != -1) {
|
||||
debugSelector = loopCall.debugSelector;
|
||||
debugExportId = loopCall.debugExportId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xs->type == EXEC_STACK_TYPE_CALL) {
|
||||
if (debugSelector != -1) {
|
||||
curMethodName = g_sci->getKernel()->getSelectorName(debugSelector);
|
||||
} else if (debugExportId != -1) {
|
||||
curObjectName = "";
|
||||
curMethodName = Common::String::format("export %d", debugExportId);
|
||||
}
|
||||
}
|
||||
|
||||
SciCallOrigin reply;
|
||||
reply.objectName = curObjectName;
|
||||
reply.methodName = curMethodName;
|
||||
reply.scriptNr = curScriptNr;
|
||||
reply.localCallOffset = xs->debugLocalCallOffset;
|
||||
reply.roomNr = currentRoomNumber();
|
||||
return reply;
|
||||
}
|
||||
|
||||
bool EngineState::callInStack(const reg_t object, const Selector selector) const {
|
||||
Common::List<ExecStack>::const_iterator it;
|
||||
for (it = _executionStack.begin(); it != _executionStack.end(); ++it) {
|
||||
const ExecStack &call = *it;
|
||||
if (call.sendp == object && call.debugSelector == selector) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::String EngineState::getGameVersionFromGlobal() const {
|
||||
// The version global was originally 28 but then became 27.
|
||||
// When it was 28, 27 was a volume level, so differentiate by type.
|
||||
reg_t versionRef = variables[VAR_GLOBAL][kGlobalVarVersionNew];
|
||||
if (versionRef.isNumber()) {
|
||||
versionRef = variables[VAR_GLOBAL][kGlobalVarVersionOld];
|
||||
}
|
||||
#ifdef ENABLE_SCI32
|
||||
// LSL7 and Phant2 store the version string as an object instead of a reference
|
||||
if (_segMan->isObject(versionRef)) {
|
||||
versionRef = readSelector(_segMan, versionRef, SELECTOR(data));
|
||||
}
|
||||
#endif
|
||||
if (versionRef.isPointer()) {
|
||||
return _segMan->getString(versionRef);
|
||||
}
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
228
engines/sci/engine/state.h
Normal file
228
engines/sci/engine/state.h
Normal file
@@ -0,0 +1,228 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_STATE_H
|
||||
#define SCI_ENGINE_STATE_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/array.h"
|
||||
#include "common/serializer.h"
|
||||
#include "common/str-array.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
class WriteStream;
|
||||
}
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/file.h"
|
||||
#include "sci/engine/seg_manager.h"
|
||||
|
||||
#include "sci/parser/vocabulary.h"
|
||||
|
||||
#include "sci/sound/soundcmd.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class FileHandle;
|
||||
class DirSeeker;
|
||||
class EventManager;
|
||||
class MessageState;
|
||||
class SoundCommandParser;
|
||||
class VirtualIndexFile;
|
||||
|
||||
enum AbortGameState {
|
||||
kAbortNone = 0,
|
||||
kAbortLoadGame = 1,
|
||||
kAbortRestartGame = 2,
|
||||
kAbortQuitGame = 3
|
||||
};
|
||||
|
||||
// We assume that scripts give us savegameId 0->99 for creating a new save slot
|
||||
// and savegameId 100->199 for existing save slots. Refer to kfile.cpp
|
||||
enum {
|
||||
SAVEGAMEID_OFFICIALRANGE_START = 100,
|
||||
SAVEGAMEID_OFFICIALRANGE_END = 199
|
||||
};
|
||||
|
||||
enum {
|
||||
GAMEISRESTARTING_NONE = 0,
|
||||
GAMEISRESTARTING_RESTART = 1,
|
||||
GAMEISRESTARTING_RESTORE = 2
|
||||
};
|
||||
|
||||
enum VideoFlags {
|
||||
kNone = 0,
|
||||
kDoubled = 1 << 0,
|
||||
kDropFrames = 1 << 1,
|
||||
kBlackLines = 1 << 2,
|
||||
kUnkBit3 = 1 << 3,
|
||||
kGammaBoost = 1 << 4,
|
||||
kHoldBlackFrame = 1 << 5,
|
||||
kHoldLastFrame = 1 << 6,
|
||||
kUnkBit7 = 1 << 7,
|
||||
kStretch = 1 << 8
|
||||
};
|
||||
|
||||
/**
|
||||
* Trace information about a VM function call.
|
||||
*/
|
||||
struct SciCallOrigin {
|
||||
int scriptNr; //< The source script of the function
|
||||
Common::String objectName; //< The name of the object being called
|
||||
Common::String methodName; //< The name of the method being called
|
||||
int localCallOffset; //< The byte offset of a local script subroutine called by the origin method. -1 if not in a local subroutine.
|
||||
int roomNr; //< The room that was loaded at the time of the call
|
||||
|
||||
Common::String toString() const {
|
||||
return Common::String::format("method %s::%s (room %d, script %d, localCall %x)", objectName.c_str(), methodName.c_str(), roomNr, scriptNr, localCallOffset);
|
||||
}
|
||||
};
|
||||
|
||||
struct EngineState : public Common::Serializable {
|
||||
EngineState(SegManager *segMan);
|
||||
~EngineState() override;
|
||||
|
||||
void saveLoadWithSerializer(Common::Serializer &ser) override;
|
||||
|
||||
SegManager *_segMan; /**< The segment manager */
|
||||
|
||||
/* Non-VM information */
|
||||
|
||||
uint32 lastWaitTime; /**< The last time the game invoked Wait() */
|
||||
uint32 _screenUpdateTime; /**< The last time the game updated the screen */
|
||||
|
||||
void speedThrottler(uint32 neededSleep);
|
||||
uint16 wait(uint16 ticks);
|
||||
void sleep(uint16 ticks);
|
||||
|
||||
uint32 _eventCounter; /**< total times kGetEvent was invoked since the last call to kGameIsRestarting(0) or kWait or kFrameOut */
|
||||
uint32 _paletteSetIntensityCounter; /**< total times kPaletteSetIntensity was invoked since the last call to kGameIsRestarting(0) or kWait */
|
||||
uint32 _throttleLastTime; /**< last time kAnimate was invoked */
|
||||
bool _throttleTrigger;
|
||||
|
||||
/* Kernel File IO stuff */
|
||||
|
||||
Common::Array<FileHandle> _fileHandles; /**< Array of file handles. Dynamically increased if required. */
|
||||
|
||||
DirSeeker _dirseeker;
|
||||
|
||||
int16 _lastSaveVirtualId; // last virtual id fed to kSaveGame, if no kGetSaveFiles was called inbetween
|
||||
int16 _lastSaveNewId; // last newly created filename-id by kSaveGame
|
||||
|
||||
// see detection.cpp / SciEngine::loadGameState()
|
||||
int _delayedRestoreGameId; // the saved game id, that it supposed to get restored (triggered by ScummVM menu)
|
||||
|
||||
// see kmisc.cpp / kMacPlatform32
|
||||
int _kq7MacSaveGameId; // the saved game id to use when saving (might not exist yet)
|
||||
Common::String _kq7MacSaveGameDescription; // description to use when saving game
|
||||
|
||||
uint _chosenQfGImportItem; // Remembers the item selected in QfG import rooms
|
||||
|
||||
bool _cursorWorkaroundActive; // Refer to GfxCursor::setPosition()
|
||||
int16 _cursorWorkaroundPosCount; // When the cursor is reported to be at the previously set coordinate, we won't disable the workaround unless it happened for this many times
|
||||
Common::Point _cursorWorkaroundPoint;
|
||||
Common::Rect _cursorWorkaroundRect;
|
||||
|
||||
/* VM Information */
|
||||
|
||||
Common::List<ExecStack> _executionStack; /**< The execution stack */
|
||||
/**
|
||||
* When called from kernel functions, the vm is re-started recursively on
|
||||
* the same stack. This variable contains the stack base for the current vm.
|
||||
*/
|
||||
int executionStackBase;
|
||||
bool _executionStackPosChanged; /**< Set to true if the execution stack position should be re-evaluated by the vm */
|
||||
|
||||
// Registers
|
||||
reg_t r_acc; /**< Accumulator */
|
||||
reg_t r_prev; /**< previous comparison result */
|
||||
int16 r_rest; /**< current &rest register */
|
||||
|
||||
StackPtr stack_base; /**< Pointer to the least stack element */
|
||||
StackPtr stack_top; /**< First invalid stack element */
|
||||
|
||||
// Script state
|
||||
ExecStack *xs;
|
||||
reg_t *variables[4]; ///< global, local, temp, param, as immediate pointers
|
||||
reg_t *variablesBase[4]; ///< Used for referencing VM ops
|
||||
SegmentId variablesSegment[4]; ///< Same as above, contains segment IDs
|
||||
int variablesMax[4]; ///< Max. values for all variables
|
||||
|
||||
AbortGameState abortScriptProcessing;
|
||||
int16 gameIsRestarting; // is set when restarting (=1) or restoring the game (=2)
|
||||
|
||||
int scriptStepCounter; // Counts the number of steps executed
|
||||
int scriptGCInterval; // Number of steps in between gcs
|
||||
|
||||
uint16 currentRoomNumber() const;
|
||||
void setRoomNumber(uint16 roomNumber);
|
||||
|
||||
/**
|
||||
* Sets global variables from script 0
|
||||
*/
|
||||
void initGlobals();
|
||||
|
||||
/**
|
||||
* Shrink execution stack to size.
|
||||
* Contains an assert if it is not already smaller.
|
||||
*/
|
||||
void shrinkStackToBase();
|
||||
|
||||
int gcCountDown; /**< Number of kernel calls until next gc */
|
||||
|
||||
MessageState *_msgState;
|
||||
void initMessageState();
|
||||
|
||||
// MemorySegment provides access to a 256-byte block of memory that remains
|
||||
// intact across restarts and restores
|
||||
enum {
|
||||
kMemorySegmentMax = 256
|
||||
};
|
||||
uint16 _memorySegmentSize;
|
||||
byte _memorySegment[kMemorySegmentMax];
|
||||
|
||||
/**
|
||||
* Resets the engine state.
|
||||
*/
|
||||
void reset(bool isRestoring);
|
||||
|
||||
/**
|
||||
* Finds and returns the origin of the current call.
|
||||
*/
|
||||
SciCallOrigin getCurrentCallOrigin() const;
|
||||
|
||||
/**
|
||||
* Determines whether the given object method is in the current stack.
|
||||
*/
|
||||
bool callInStack(const reg_t object, const Selector selector) const;
|
||||
|
||||
/**
|
||||
* Returns the game's version string from its global variable.
|
||||
* Most games initialize this to a string embedded in a script resource,
|
||||
* or the contents of the VERSION file in the game directory.
|
||||
*/
|
||||
Common::String getGameVersionFromGlobal() const;
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_STATE_H
|
||||
332
engines/sci/engine/static_selectors.cpp
Normal file
332
engines/sci/engine/static_selectors.cpp
Normal file
@@ -0,0 +1,332 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// We place selector vocab name tables here for any game that doesn't have
|
||||
// them. This includes the King's Quest IV Demo and LSL3 Demo.
|
||||
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/seg_manager.h"
|
||||
#include "sci/engine/vm.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
struct SelectorRemap {
|
||||
SciVersion minVersion;
|
||||
SciVersion maxVersion;
|
||||
const char *name;
|
||||
uint32 slot;
|
||||
};
|
||||
|
||||
static const char * const sci0Selectors[] = {
|
||||
"y", "x", "view", "loop", "cel", // 0 - 4
|
||||
"underBits", "nsTop", "nsLeft", "nsBottom", "nsRight", // 5 - 9
|
||||
"lsTop", "lsLeft", "lsBottom", "lsRight", "signal", // 10 - 14
|
||||
"illegalBits", "brTop", "brLeft", "brBottom", "brRight", // 15 - 19
|
||||
"name", "key", "time", "text", "elements", // 20 - 25
|
||||
"color", "back", "mode", "style", "state", // 25 - 29
|
||||
"font", "type", "window", "cursor", "max", // 30 - 34
|
||||
"mark", "who", "message", "edit", "play", // 35 - 39
|
||||
"number", "handle", "client", "dx", "dy", // 40 - 44
|
||||
"b-moveCnt", "b-i1", "b-i2", "b-di", "b-xAxis", // 45 - 49
|
||||
"b-incr", "xStep", "yStep", "moveSpeed", "canBeHere", // 50 - 54
|
||||
"heading", "mover", "doit", "isBlocked", "looper", // 55 - 59
|
||||
"priority", "modifiers", "replay", "setPri", "at", // 60 - 64
|
||||
"next", "done", "width", "wordFail", "syntaxFail", // 65 - 69
|
||||
"semanticFail", "pragmaFail", "said", "claimed", "value", // 70 - 74
|
||||
"save", "restore", "title", "button", "icon", // 75 - 79
|
||||
"draw", "delete", "z" // 80 - 82
|
||||
};
|
||||
|
||||
static const char * const sci1Selectors[] = {
|
||||
"parseLang", "printLang", "subtitleLang", "size", "points", // 83 - 87
|
||||
"palette", "dataInc", "handle", "min", "sec", // 88 - 92
|
||||
"frame", "vol", "pri", "perform", "moveDone" // 93 - 97
|
||||
};
|
||||
|
||||
static const char * const sci11Selectors[] = {
|
||||
"topString", "flags", "quitGame", "restart", "hide", // 98 - 102
|
||||
"scaleSignal", "scaleX", "scaleY", "maxScale","vanishingX", // 103 - 107
|
||||
"vanishingY" // 108
|
||||
};
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
static const char * const sci2Selectors[] = {
|
||||
"plane", "x", "y", "z", "scaleX", // 0 - 4
|
||||
"scaleY", "maxScale", "priority", "fixPriority", "inLeft", // 5 - 9
|
||||
"inTop", "inRight", "inBottom", "useInsetRect", "view", // 10 - 14
|
||||
"loop", "cel", "bitmap", "nsLeft", "nsTop", // 15 - 19
|
||||
"nsRight", "nsBottom", "lsLeft", "lsTop", "lsRight", // 20 - 25
|
||||
"lsBottom", "signal", "illegalBits", "brLeft", "brTop", // 25 - 29
|
||||
"brRight", "brBottom", "name", "key", "time", // 30 - 34
|
||||
"text", "elements", "fore", "back", "mode", // 35 - 39
|
||||
"style", "state", "font", "type", "window", // 40 - 44
|
||||
"cursor", "max", "mark", "who", "message", // 45 - 49
|
||||
"edit", "play", "number", "nodePtr", "client", // 50 - 54
|
||||
"dx", "dy", "b-moveCnt", "b-i1", "b-i2", // 55 - 59
|
||||
"b-di", "b-xAxis", "b-incr", "xStep", "yStep", // 60 - 64
|
||||
"moveSpeed", "cantBeHere", "heading", "mover", "doit", // 65 - 69
|
||||
"isBlocked", "looper", "modifiers", "replay", "setPri", // 70 - 74
|
||||
"at", "next", "done", "width", "pragmaFail", // 75 - 79
|
||||
"claimed", "value", "save", "restore", "title", // 80 - 84
|
||||
"button", "icon", "draw", "delete", "printLang", // 85 - 89
|
||||
"size", "points", "palette", "dataInc", "handle", // 90 - 94
|
||||
"min", "sec", "frame", "vol", "perform", // 95 - 99
|
||||
"moveDone", "topString", "flags", "quitGame", "restart", // 100 - 104
|
||||
"hide", "scaleSignal", "vanishingX", "vanishingY", "picture", // 105 - 109
|
||||
"resX", "resY", "coordType", "data", "skip", // 110 - 104
|
||||
"center", "all", "show", "textLeft", "textTop", // 115 - 119
|
||||
"textRight", "textBottom", "borderColor", "titleFore", "titleBack", // 120 - 124
|
||||
"titleFont", "dimmed", "frameOut", "lastKey", "magnifier", // 125 - 129
|
||||
"magPower", "mirrored", "pitch", "roll", "yaw", // 130 - 134
|
||||
"left", "right", "top", "bottom", "numLines" // 135 - 139
|
||||
};
|
||||
#endif
|
||||
|
||||
static const SelectorRemap sciSelectorRemap[] = {
|
||||
{ SCI_VERSION_0_EARLY, SCI_VERSION_0_LATE, "moveDone", 170 },
|
||||
{ SCI_VERSION_0_EARLY, SCI_VERSION_0_LATE, "points", 316 },
|
||||
{ SCI_VERSION_0_EARLY, SCI_VERSION_0_LATE, "flags", 368 },
|
||||
{ SCI_VERSION_1_EARLY, SCI_VERSION_1_LATE, "nodePtr", 44 },
|
||||
{ SCI_VERSION_1_LATE, SCI_VERSION_1_LATE, "cantBeHere", 57 },
|
||||
{ SCI_VERSION_1_EARLY, SCI_VERSION_1_LATE, "topString", 101 },
|
||||
{ SCI_VERSION_1_EARLY, SCI_VERSION_1_LATE, "flags", 102 },
|
||||
// SCI1.1
|
||||
{ SCI_VERSION_1_1, SCI_VERSION_1_1, "nodePtr", 41 },
|
||||
{ SCI_VERSION_1_1, SCI_VERSION_1_1, "cantBeHere", 54 },
|
||||
// The following are not really needed. They've only been defined to
|
||||
// ease game debugging.
|
||||
{ SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-objID-", 4096 },
|
||||
{ SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-size-", 4097 },
|
||||
{ SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-propDict-", 4098 },
|
||||
{ SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-methDict-", 4099 },
|
||||
{ SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-classScript-", 4100 },
|
||||
{ SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-script-", 4101 },
|
||||
{ SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-super-", 4102 },
|
||||
//
|
||||
{ SCI_VERSION_1_1, SCI_VERSION_2_1_LATE, "-info-", 4103 },
|
||||
{ SCI_VERSION_NONE, SCI_VERSION_NONE, nullptr, 0 }
|
||||
};
|
||||
|
||||
struct ClassReference {
|
||||
int script;
|
||||
const char *className;
|
||||
const char *selectorName;
|
||||
SelectorType selectorType;
|
||||
uint selectorOffset;
|
||||
};
|
||||
|
||||
// For variable selectors, we ignore the global selectors and start off from
|
||||
// the object's selectors (i.e. from the name selector onwards). Thus, the
|
||||
// following are not taken into consideration when calculating the indices of
|
||||
// variable selectors in this array:
|
||||
// SCI0 - SCI1: species, superClass, -info-
|
||||
// SCI1.1: -objID-, -size-, -propDict-, -methDict-, -classScript-, -script-,
|
||||
// -super-, -info-
|
||||
static const ClassReference classReferences[] = {
|
||||
{ 0, "Character", "say", kSelectorMethod, 5 }, // Crazy Nick's Soft Picks
|
||||
{ 928, "Narrator", "say", kSelectorMethod, 4 },
|
||||
{ 928, "Narrator", "startText", kSelectorMethod, 5 },
|
||||
{ 929, "Sync", "syncTime", kSelectorVariable, 1 },
|
||||
{ 929, "Sync", "syncCue", kSelectorVariable, 2 },
|
||||
{ 981, "SysWindow", "open", kSelectorMethod, 1 },
|
||||
{ 999, "Script", "init", kSelectorMethod, 0 },
|
||||
{ 999, "Script", "dispose", kSelectorMethod, 2 },
|
||||
{ 999, "Script", "changeState", kSelectorMethod, 3 }
|
||||
#ifdef ENABLE_SCI32
|
||||
,
|
||||
{ 64929, "Sync", "syncTime", kSelectorVariable, 2 },
|
||||
{ 64929, "Sync", "syncCue", kSelectorVariable, 3 },
|
||||
{ 64999, "Obj", "init", kSelectorMethod, 1 },
|
||||
{ 64999, "Obj", "doit", kSelectorMethod, 2 }
|
||||
#endif
|
||||
};
|
||||
|
||||
Common::StringArray Kernel::checkStaticSelectorNames() {
|
||||
Common::StringArray names;
|
||||
const int offset = (getSciVersion() < SCI_VERSION_1_1) ? 3 : 0;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
const int count = (getSciVersion() <= SCI_VERSION_1_1) ? ARRAYSIZE(sci0Selectors) + offset : ARRAYSIZE(sci2Selectors);
|
||||
#else
|
||||
const int count = ARRAYSIZE(sci0Selectors) + offset;
|
||||
#endif
|
||||
int countSci1 = ARRAYSIZE(sci1Selectors);
|
||||
int countSci11 = ARRAYSIZE(sci11Selectors);
|
||||
|
||||
// Resize the list of selector names and fill in the SCI 0 names.
|
||||
names.resize(count);
|
||||
if (getSciVersion() <= SCI_VERSION_1_LATE) {
|
||||
// Fill selectors 0 - 2 for SCI0 - SCI1 late
|
||||
names[0] = "species";
|
||||
names[1] = "superClass";
|
||||
names[2] = "-info-";
|
||||
}
|
||||
|
||||
if (getSciVersion() <= SCI_VERSION_1_1) {
|
||||
// SCI0 - SCI11
|
||||
for (int i = offset; i < count; i++)
|
||||
names[i] = sci0Selectors[i - offset];
|
||||
|
||||
if (getSciVersion() > SCI_VERSION_01) {
|
||||
// Several new selectors were added in SCI 1 and later.
|
||||
names.resize(count + countSci1);
|
||||
for (int i = count; i < count + countSci1; i++)
|
||||
names[i] = sci1Selectors[i - count];
|
||||
}
|
||||
|
||||
if (getSciVersion() >= SCI_VERSION_1_1) {
|
||||
// Several new selectors were added in SCI 1.1
|
||||
names.resize(count + countSci1 + countSci11);
|
||||
for (int i = count + countSci1; i < count + countSci1 + countSci11; i++)
|
||||
names[i] = sci11Selectors[i - count - countSci1];
|
||||
}
|
||||
#ifdef ENABLE_SCI32
|
||||
} else {
|
||||
// SCI2+
|
||||
for (int i = 0; i < count; i++)
|
||||
names[i] = sci2Selectors[i];
|
||||
#endif
|
||||
}
|
||||
|
||||
findSpecificSelectors(names);
|
||||
|
||||
// HACK for LB2 floppy, for saving via GMM
|
||||
if (g_sci->getGameId() == GID_LAURABOW2) {
|
||||
names[342] = "input";
|
||||
names[343] = "controls";
|
||||
}
|
||||
|
||||
for (const SelectorRemap *selectorRemap = sciSelectorRemap; selectorRemap->slot; ++selectorRemap) {
|
||||
if (getSciVersion() >= selectorRemap->minVersion && getSciVersion() <= selectorRemap->maxVersion) {
|
||||
const uint32 slot = selectorRemap->slot;
|
||||
if (slot >= names.size())
|
||||
names.resize(slot + 1);
|
||||
names[slot] = selectorRemap->name;
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
void Kernel::findSpecificSelectors(Common::StringArray &selectorNames) {
|
||||
// Now, we need to find out selectors which keep changing place...
|
||||
// We do that by dissecting game objects, and looking for selectors at
|
||||
// specified locations. We need to load some game scripts here to
|
||||
// find these selectors, but all of the loaded scripts will be
|
||||
// purged at the end of this function, as the segment manager will be
|
||||
// reset.
|
||||
|
||||
// We need to initialize script 0 here, to make sure that it's always
|
||||
// located at segment 1.
|
||||
_segMan->instantiateScript(0, false);
|
||||
|
||||
// The Actor class contains the init, xLast and yLast selectors, which
|
||||
// we reference directly. It's always in script 998, so we need to
|
||||
// explicitly load it here.
|
||||
if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) {
|
||||
uint16 actorScript = 998;
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() >= SCI_VERSION_2) {
|
||||
actorScript += 64000;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_resMan->testResource(ResourceId(kResourceTypeScript, actorScript))) {
|
||||
_segMan->instantiateScript(actorScript, false);
|
||||
|
||||
const Object *actorClass = _segMan->getObject(_segMan->findObjectByName("Actor"));
|
||||
|
||||
if (actorClass) {
|
||||
// Find the xLast and yLast selectors, used in kDoBresen
|
||||
|
||||
int offset = (getSciVersion() < SCI_VERSION_1_1) ? 3 : 0;
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() >= SCI_VERSION_2) {
|
||||
offset += 12;
|
||||
}
|
||||
#endif
|
||||
// xLast and yLast always come between illegalBits and xStep
|
||||
int illegalBitsSelectorPos = actorClass->locateVarSelector(_segMan, 15 + offset); // illegalBits
|
||||
int xStepSelectorPos = actorClass->locateVarSelector(_segMan, 51 + offset); // xStep
|
||||
if (xStepSelectorPos - illegalBitsSelectorPos != 3) {
|
||||
error("illegalBits and xStep selectors aren't found in "
|
||||
"known locations. illegalBits = %d, xStep = %d",
|
||||
illegalBitsSelectorPos, xStepSelectorPos);
|
||||
}
|
||||
|
||||
int xLastSelectorPos = actorClass->getVarSelector(illegalBitsSelectorPos + 1);
|
||||
int yLastSelectorPos = actorClass->getVarSelector(illegalBitsSelectorPos + 2);
|
||||
|
||||
if (selectorNames.size() < (uint32)yLastSelectorPos + 1)
|
||||
selectorNames.resize((uint32)yLastSelectorPos + 1);
|
||||
|
||||
selectorNames[xLastSelectorPos] = "xLast";
|
||||
selectorNames[yLastSelectorPos] = "yLast";
|
||||
}
|
||||
|
||||
_segMan->uninstantiateScript(actorScript);
|
||||
}
|
||||
}
|
||||
|
||||
// Find selectors from specific classes
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(classReferences); i++) {
|
||||
if (!_resMan->testResource(ResourceId(kResourceTypeScript, classReferences[i].script)))
|
||||
continue;
|
||||
|
||||
_segMan->instantiateScript(classReferences[i].script, false);
|
||||
|
||||
const Object *targetClass = _segMan->getObject(_segMan->findObjectByName(classReferences[i].className));
|
||||
uint selectorOffset = classReferences[i].selectorOffset;
|
||||
|
||||
if (targetClass) {
|
||||
int targetSelectorPos;
|
||||
if (classReferences[i].selectorType == kSelectorMethod) {
|
||||
if (targetClass->getMethodCount() < selectorOffset + 1)
|
||||
error("The %s class has less than %d methods (%d)",
|
||||
classReferences[i].className, selectorOffset + 1,
|
||||
targetClass->getMethodCount());
|
||||
|
||||
targetSelectorPos = targetClass->getFuncSelector(selectorOffset);
|
||||
} else {
|
||||
// Add the global selectors to the selector ID
|
||||
selectorOffset += (getSciVersion() <= SCI_VERSION_1_LATE) ? 3 : 8;
|
||||
|
||||
if (targetClass->getVarCount() < selectorOffset + 1)
|
||||
error("The %s class has less than %d variables (%d)",
|
||||
classReferences[i].className, selectorOffset + 1,
|
||||
targetClass->getVarCount());
|
||||
|
||||
targetSelectorPos = targetClass->getVarSelector(selectorOffset);
|
||||
}
|
||||
|
||||
if (selectorNames.size() < (uint32)targetSelectorPos + 1)
|
||||
selectorNames.resize((uint32)targetSelectorPos + 1);
|
||||
|
||||
|
||||
selectorNames[targetSelectorPos] = classReferences[i].selectorName;
|
||||
}
|
||||
}
|
||||
|
||||
_segMan->resetSegMan();
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
110
engines/sci/engine/tts.cpp
Normal file
110
engines/sci/engine/tts.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/tts.h"
|
||||
#include "common/system.h"
|
||||
#include "common/text-to-speech.h"
|
||||
#include "common/config-manager.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
SciTTS::SciTTS() : _curMessage("") {
|
||||
_ttsMan = ConfMan.getBool("tts_enabled") ? g_system->getTextToSpeechManager() : nullptr;
|
||||
if (_ttsMan != nullptr) {
|
||||
_ttsMan->setLanguage(ConfMan.get("language"));
|
||||
_ttsMan->enable(true);
|
||||
}
|
||||
}
|
||||
|
||||
void SciTTS::button(const Common::String &text) {
|
||||
if (_ttsMan != nullptr && shouldPerformTTS(text))
|
||||
_ttsMan->say(getMessage(text), Common::TextToSpeechManager::QUEUE_NO_REPEAT);
|
||||
}
|
||||
|
||||
void SciTTS::text(const Common::String &text) {
|
||||
if (_ttsMan != nullptr && shouldPerformTTS(text))
|
||||
_ttsMan->say(getMessage(text), Common::TextToSpeechManager::INTERRUPT);
|
||||
}
|
||||
|
||||
void SciTTS::stop() {
|
||||
if (_ttsMan != nullptr)
|
||||
_ttsMan->stop();
|
||||
}
|
||||
|
||||
void SciTTS::setMessage(const Common::String &text) {
|
||||
if (text.size() > 0)
|
||||
_curMessage = text;
|
||||
}
|
||||
|
||||
Common::String SciTTS::getMessage(const Common::String &text) {
|
||||
Common::String message = text;
|
||||
|
||||
// If the current message contains a substring of the text to be displayed,
|
||||
// minus the first letter, prefer the message instead. The first letter is
|
||||
// chopped off in messages in games such as KQ6 and is replaced with tabs in
|
||||
// KQ6 or spaces in KQ5, so that a calligraphic first letter is drawn instead.
|
||||
if (_curMessage.size() > 0 && text.size() > 0 && text.hasSuffix(_curMessage.substr(1)))
|
||||
message = _curMessage;
|
||||
|
||||
// Strip color code characters in SCI1.1
|
||||
if (getSciVersion() == SCI_VERSION_1_1) {
|
||||
int32 index = message.find('|');
|
||||
|
||||
while (index >= 0) {
|
||||
do {
|
||||
message.deleteChar(index);
|
||||
} while (message.size() > 0 && message[index] != '|');
|
||||
|
||||
if (message.size() > 0)
|
||||
message.deleteChar(index);
|
||||
|
||||
index = message.find('|');
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
bool SciTTS::shouldPerformTTS(const Common::String &message) const {
|
||||
SciGameId gameId = g_sci->getGameId();
|
||||
uint16 roomNumber = g_sci->getEngineState()->currentRoomNumber();
|
||||
|
||||
// Check if it's an actual message, by checking for the
|
||||
// existence of any vowel.
|
||||
// For example, when talking to the alien in SQ5 room 500, a
|
||||
// series of symbols is shown, as part of a joke.
|
||||
if (!message.contains('a') &&
|
||||
!message.contains('e') &&
|
||||
!message.contains('i') &&
|
||||
!message.contains('o') &&
|
||||
!message.contains('u'))
|
||||
return false;
|
||||
|
||||
// Skip TTS for QFG4 room 140 (character creation screen).
|
||||
if (gameId == GID_QFG4 && roomNumber == 140)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
49
engines/sci/engine/tts.h
Normal file
49
engines/sci/engine/tts.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_TTS_H
|
||||
#define SCI_ENGINE_TTS_H
|
||||
|
||||
#include "common/text-to-speech.h"
|
||||
#include "sci/sci.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class SciTTS {
|
||||
public:
|
||||
SciTTS();
|
||||
|
||||
void button(const Common::String &text);
|
||||
void text(const Common::String &text);
|
||||
void stop();
|
||||
void setMessage(const Common::String &text);
|
||||
|
||||
private:
|
||||
Common::TextToSpeechManager *_ttsMan;
|
||||
Common::String _curMessage;
|
||||
|
||||
Common::String getMessage(const Common::String &text);
|
||||
bool shouldPerformTTS(const Common::String &message) const;
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_TTS_H
|
||||
1412
engines/sci/engine/vm.cpp
Normal file
1412
engines/sci/engine/vm.cpp
Normal file
File diff suppressed because it is too large
Load Diff
431
engines/sci/engine/vm.h
Normal file
431
engines/sci/engine/vm.h
Normal file
@@ -0,0 +1,431 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_VM_H
|
||||
#define SCI_ENGINE_VM_H
|
||||
|
||||
/* VM and kernel declarations */
|
||||
|
||||
#include "sci/engine/vm_types.h" // for reg_t
|
||||
#include "sci/resource/resource.h" // for SciVersion
|
||||
|
||||
#include "common/util.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class SegManager;
|
||||
struct EngineState;
|
||||
class Object;
|
||||
class ResourceManager;
|
||||
class Script;
|
||||
|
||||
/** Number of bytes to be allocated for the stack */
|
||||
#define VM_STACK_SIZE 0x1000
|
||||
|
||||
/** Magical object identifier */
|
||||
#define SCRIPT_OBJECT_MAGIC_NUMBER 0x1234
|
||||
|
||||
/** Offset of this identifier */
|
||||
#define SCRIPT_OBJECT_MAGIC_OFFSET (getSciVersion() < SCI_VERSION_1_1 ? -8 : 0)
|
||||
|
||||
/** Stack pointer value: Use predecessor's value */
|
||||
#define CALL_SP_CARRY NULL
|
||||
|
||||
/** Types of selectors as returned by lookupSelector() below. */
|
||||
enum SelectorType {
|
||||
kSelectorNone = 0,
|
||||
kSelectorVariable,
|
||||
kSelectorMethod
|
||||
};
|
||||
|
||||
struct Class {
|
||||
int script; ///< number of the script the class is in, -1 for non-existing
|
||||
reg_t reg; ///< offset; script-relative offset, segment: 0 if not instantiated
|
||||
};
|
||||
|
||||
// A reference to an object's variable.
|
||||
// The object is stored as a reg_t, the variable as an index into _variables
|
||||
struct ObjVarRef {
|
||||
reg_t obj;
|
||||
int varindex;
|
||||
|
||||
reg_t* getPointer(SegManager *segMan) const;
|
||||
};
|
||||
|
||||
enum ExecStackType {
|
||||
EXEC_STACK_TYPE_CALL = 0,
|
||||
EXEC_STACK_TYPE_KERNEL = 1,
|
||||
EXEC_STACK_TYPE_VARSELECTOR = 2
|
||||
};
|
||||
|
||||
struct ExecStack {
|
||||
reg_t objp; ///< Pointer to the beginning of the current object
|
||||
reg_t sendp; ///< Pointer to the object containing the invoked method
|
||||
|
||||
union {
|
||||
ObjVarRef varp; // Variable pointer for r/w access
|
||||
reg_t pc; // Pointer to the initial program counter. Not accurate for the TOS element
|
||||
} addr;
|
||||
|
||||
StackPtr fp; // Frame pointer
|
||||
StackPtr sp; // Stack pointer
|
||||
|
||||
int argc;
|
||||
StackPtr variables_argp; // Argument pointer
|
||||
|
||||
int tempCount; // Number of temp variables allocated by link opcode
|
||||
|
||||
SegmentId local_segment; // local variables etc
|
||||
|
||||
Selector debugSelector; // The selector which was used to call or -1 if not applicable
|
||||
int debugExportId; // The exportId which was called or -1 if not applicable
|
||||
int debugLocalCallOffset; // Local call offset or -1 if not applicable
|
||||
int debugOrigin; // The stack frame position the call was made from, or -1 if it was the initial call
|
||||
int debugKernelFunction; // The kernel function called, or -1 if not applicable
|
||||
int debugKernelSubFunction; // The kernel subfunction called, or -1 if not applicable
|
||||
ExecStackType type;
|
||||
|
||||
reg_t* getVarPointer(SegManager *segMan) const;
|
||||
|
||||
ExecStack(reg_t objp_, reg_t sendp_, StackPtr sp_, int argc_, StackPtr argp_,
|
||||
SegmentId localsSegment_, reg_t pc_, Selector debugSelector_,
|
||||
int debugKernelFunction_, int debugKernelSubFunction_,
|
||||
int debugExportId_, int debugLocalCallOffset_, int debugOrigin_,
|
||||
ExecStackType type_) {
|
||||
objp = objp_;
|
||||
sendp = sendp_;
|
||||
// varp is set separately for varselector calls
|
||||
addr.pc = pc_;
|
||||
fp = sp = sp_;
|
||||
argc = argc_;
|
||||
variables_argp = argp_;
|
||||
tempCount = 0;
|
||||
if (localsSegment_ != kUninitializedSegment)
|
||||
local_segment = localsSegment_;
|
||||
else
|
||||
local_segment = pc_.getSegment();
|
||||
debugSelector = debugSelector_;
|
||||
debugKernelFunction = debugKernelFunction_;
|
||||
debugKernelSubFunction = debugKernelSubFunction_;
|
||||
debugExportId = debugExportId_;
|
||||
debugLocalCallOffset = debugLocalCallOffset_;
|
||||
debugOrigin = debugOrigin_;
|
||||
type = type_;
|
||||
}
|
||||
};
|
||||
|
||||
enum {
|
||||
VAR_GLOBAL = 0,
|
||||
VAR_LOCAL = 1,
|
||||
VAR_TEMP = 2,
|
||||
VAR_PARAM = 3
|
||||
};
|
||||
|
||||
enum GlobalVar {
|
||||
kGlobalVarEgo = 0,
|
||||
kGlobalVarGame = 1,
|
||||
kGlobalVarCurrentRoom = 2,
|
||||
kGlobalVarSpeed = 3, // SCI16
|
||||
kGlobalVarQuit = 4,
|
||||
kGlobalVarCast = 5,
|
||||
kGlobalVarSounds = 8,
|
||||
kGlobalVarPlanes = 10, // SCI32
|
||||
kGlobalVarCurrentRoomNo = 11,
|
||||
kGlobalVarPreviousRoomNo = 12,
|
||||
kGlobalVarNewRoomNo = 13,
|
||||
kGlobalVarScore = 15,
|
||||
kGlobalVarVersionNew = 27, // version string or object in later games
|
||||
kGlobalVarVersionOld = 28, // version string in earlier games
|
||||
kGlobalVarGK2MusicVolume = 76, // 0 to 127
|
||||
kGlobalVarPhant2SecondaryVolume = 76, // 0 to 127
|
||||
kGlobalVarUser = 80,
|
||||
kGlobalVarFastCast = 84, // SCI16
|
||||
kGlobalVarMessageType = 90,
|
||||
kGlobalVarTextSpeed = 94, // SCI32; 0 is fastest, 8 is slowest
|
||||
kGlobalVarGK1Music1 = 102, // 0 to 127
|
||||
kGlobalVarGK1Music2 = 103, // 0 to 127
|
||||
kGlobalVarRamaCatalogFile = 130,
|
||||
kGlobalVarLSL6HiresGameFlags = 137,
|
||||
kGlobalVarKQ7UpscaleVideos = 160,
|
||||
kGlobalVarGK1NarratorMode = 166, // 0 for text, 1 for speech
|
||||
kGlobalVarRamaMusicVolume = 176, // 0 to 16
|
||||
kGlobalVarPhant1MusicVolume = 187, // 0 to 15
|
||||
kGlobalVarPhant1DACVolume = 188, // 0 to 127
|
||||
kGlobalVarLSL6MusicVolume = 194, // 0 to 13
|
||||
kGlobalVarGK1DAC1 = 207, // 0 to 127
|
||||
kGlobalVarPhant2CensorshipFlag = 207,
|
||||
kGlobalVarGK1DAC2 = 208, // 0 to 127
|
||||
kGlobalVarLSL6HiresRestoreTextWindow = 210,
|
||||
kGlobalVarGK1DAC3 = 211, // 0 to 127
|
||||
kGlobalVarShiversFlags = 211,
|
||||
kGlobalVarTorinMusicVolume = 227, // 0 to 100
|
||||
kGlobalVarTorinSFXVolume = 228, // 0 to 100
|
||||
kGlobalVarTorinSpeechVolume = 229, // 0 to 100
|
||||
// Phant2 labels its volume slider as "music volume" but it is actually
|
||||
// a master volume that affects both music *and* sound effects
|
||||
kGlobalVarPhant2MasterVolume = 236, // 0 to 127
|
||||
kGlobalVarPhant2ControlPanel = 250,
|
||||
kGlobalVarShivers1Score = 349,
|
||||
kGlobalVarQFG4Flags = 500,
|
||||
kGlobalVarHoyle5MusicVolume = 897
|
||||
};
|
||||
|
||||
/** Number of kernel calls in between gcs; should be < 50000 */
|
||||
enum {
|
||||
GC_INTERVAL = 0x8000
|
||||
};
|
||||
|
||||
enum SciOpcodes {
|
||||
op_bnot = 0x00, // 000
|
||||
op_add = 0x01, // 001
|
||||
op_sub = 0x02, // 002
|
||||
op_mul = 0x03, // 003
|
||||
op_div = 0x04, // 004
|
||||
op_mod = 0x05, // 005
|
||||
op_shr = 0x06, // 006
|
||||
op_shl = 0x07, // 007
|
||||
op_xor = 0x08, // 008
|
||||
op_and = 0x09, // 009
|
||||
op_or = 0x0a, // 010
|
||||
op_neg = 0x0b, // 011
|
||||
op_not = 0x0c, // 012
|
||||
op_eq_ = 0x0d, // 013
|
||||
op_ne_ = 0x0e, // 014
|
||||
op_gt_ = 0x0f, // 015
|
||||
op_ge_ = 0x10, // 016
|
||||
op_lt_ = 0x11, // 017
|
||||
op_le_ = 0x12, // 018
|
||||
op_ugt_ = 0x13, // 019
|
||||
op_uge_ = 0x14, // 020
|
||||
op_ult_ = 0x15, // 021
|
||||
op_ule_ = 0x16, // 022
|
||||
op_bt = 0x17, // 023
|
||||
op_bnt = 0x18, // 024
|
||||
op_jmp = 0x19, // 025
|
||||
op_ldi = 0x1a, // 026
|
||||
op_push = 0x1b, // 027
|
||||
op_pushi = 0x1c, // 028
|
||||
op_toss = 0x1d, // 029
|
||||
op_dup = 0x1e, // 030
|
||||
op_link = 0x1f, // 031
|
||||
op_call = 0x20, // 032
|
||||
op_callk = 0x21, // 033
|
||||
op_callb = 0x22, // 034
|
||||
op_calle = 0x23, // 035
|
||||
op_ret = 0x24, // 036
|
||||
op_send = 0x25, // 037
|
||||
op_info = 0x26, // 038
|
||||
op_superP = 0x27, // 039
|
||||
op_class = 0x28, // 040
|
||||
// dummy 0x29, // 041
|
||||
op_self = 0x2a, // 042
|
||||
op_super = 0x2b, // 043
|
||||
op_rest = 0x2c, // 044
|
||||
op_lea = 0x2d, // 045
|
||||
op_selfID = 0x2e, // 046
|
||||
// dummy 0x2f // 047
|
||||
op_pprev = 0x30, // 048
|
||||
op_pToa = 0x31, // 049
|
||||
op_aTop = 0x32, // 050
|
||||
op_pTos = 0x33, // 051
|
||||
op_sTop = 0x34, // 052
|
||||
op_ipToa = 0x35, // 053
|
||||
op_dpToa = 0x36, // 054
|
||||
op_ipTos = 0x37, // 055
|
||||
op_dpTos = 0x38, // 056
|
||||
op_lofsa = 0x39, // 057
|
||||
op_lofss = 0x3a, // 058
|
||||
op_push0 = 0x3b, // 059
|
||||
op_push1 = 0x3c, // 060
|
||||
op_push2 = 0x3d, // 061
|
||||
op_pushSelf = 0x3e, // 062
|
||||
op_line = 0x3f, // 063
|
||||
//
|
||||
op_lag = 0x40, // 064
|
||||
op_lal = 0x41, // 065
|
||||
op_lat = 0x42, // 066
|
||||
op_lap = 0x43, // 067
|
||||
op_lsg = 0x44, // 068
|
||||
op_lsl = 0x45, // 069
|
||||
op_lst = 0x46, // 070
|
||||
op_lsp = 0x47, // 071
|
||||
op_lagi = 0x48, // 072
|
||||
op_lali = 0x49, // 073
|
||||
op_lati = 0x4a, // 074
|
||||
op_lapi = 0x4b, // 075
|
||||
op_lsgi = 0x4c, // 076
|
||||
op_lsli = 0x4d, // 077
|
||||
op_lsti = 0x4e, // 078
|
||||
op_lspi = 0x4f, // 079
|
||||
//
|
||||
op_sag = 0x50, // 080
|
||||
op_sal = 0x51, // 081
|
||||
op_sat = 0x52, // 082
|
||||
op_sap = 0x53, // 083
|
||||
op_ssg = 0x54, // 084
|
||||
op_ssl = 0x55, // 085
|
||||
op_sst = 0x56, // 086
|
||||
op_ssp = 0x57, // 087
|
||||
op_sagi = 0x58, // 088
|
||||
op_sali = 0x59, // 089
|
||||
op_sati = 0x5a, // 090
|
||||
op_sapi = 0x5b, // 091
|
||||
op_ssgi = 0x5c, // 092
|
||||
op_ssli = 0x5d, // 093
|
||||
op_ssti = 0x5e, // 094
|
||||
op_sspi = 0x5f, // 095
|
||||
//
|
||||
op_plusag = 0x60, // 096
|
||||
op_plusal = 0x61, // 097
|
||||
op_plusat = 0x62, // 098
|
||||
op_plusap = 0x63, // 099
|
||||
op_plussg = 0x64, // 100
|
||||
op_plussl = 0x65, // 101
|
||||
op_plusst = 0x66, // 102
|
||||
op_plussp = 0x67, // 103
|
||||
op_plusagi = 0x68, // 104
|
||||
op_plusali = 0x69, // 105
|
||||
op_plusati = 0x6a, // 106
|
||||
op_plusapi = 0x6b, // 107
|
||||
op_plussgi = 0x6c, // 108
|
||||
op_plussli = 0x6d, // 109
|
||||
op_plussti = 0x6e, // 110
|
||||
op_plusspi = 0x6f, // 111
|
||||
//
|
||||
op_minusag = 0x70, // 112
|
||||
op_minusal = 0x71, // 113
|
||||
op_minusat = 0x72, // 114
|
||||
op_minusap = 0x73, // 115
|
||||
op_minussg = 0x74, // 116
|
||||
op_minussl = 0x75, // 117
|
||||
op_minusst = 0x76, // 118
|
||||
op_minussp = 0x77, // 119
|
||||
op_minusagi = 0x78, // 120
|
||||
op_minusali = 0x79, // 121
|
||||
op_minusati = 0x7a, // 122
|
||||
op_minusapi = 0x7b, // 123
|
||||
op_minussgi = 0x7c, // 124
|
||||
op_minussli = 0x7d, // 125
|
||||
op_minussti = 0x7e, // 126
|
||||
op_minusspi = 0x7f // 127
|
||||
};
|
||||
|
||||
void script_adjust_opcode_formats();
|
||||
|
||||
/**
|
||||
* Executes function pubfunct of the specified script.
|
||||
* @param[in] s The state which is to be executed with
|
||||
* @param[in] script The script which is called
|
||||
* @param[in] pubfunct The exported script function which is to
|
||||
* be called
|
||||
* @param[in] sp Stack pointer position
|
||||
* @param[in] calling_obj The heap address of the object that
|
||||
* executed the call
|
||||
* @param[in] argc Number of arguments supplied
|
||||
* @param[in] argp Pointer to the first supplied argument
|
||||
* @return A pointer to the new exec stack TOS entry
|
||||
*/
|
||||
ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct,
|
||||
StackPtr sp, reg_t calling_obj, uint16 argc, StackPtr argp);
|
||||
|
||||
|
||||
/**
|
||||
* Executes a "send" or related operation to a selector.
|
||||
* @param[in] s The EngineState to operate on
|
||||
* @param[in] send_obj Heap address of the object to send to
|
||||
* @param[in] work_obj Heap address of the object initiating the send
|
||||
* @param[in] sp Stack pointer position
|
||||
* @param[in] framesize Size of the send as determined by the "send"
|
||||
* operation
|
||||
* @param[in] argp Pointer to the beginning of the heap block
|
||||
* containing the data to be sent. This area is a
|
||||
* succession of one or more sequences of
|
||||
* [selector_number][argument_counter] and then
|
||||
* "argument_counter" word entries with the
|
||||
* parameter values.
|
||||
* @return A pointer to the new execution stack TOS entry
|
||||
*/
|
||||
ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj,
|
||||
StackPtr sp, int framesize, StackPtr argp);
|
||||
|
||||
|
||||
/**
|
||||
* This function executes SCI bytecode
|
||||
* It executes the code on s->heap[pc] until it hits a 'ret' operation
|
||||
* while (stack_base == stack_pos). Requires s to be set up correctly.
|
||||
* @param[in] s The state to use
|
||||
*/
|
||||
void run_vm(EngineState *s);
|
||||
|
||||
/**
|
||||
* Looks up a selector and returns its type and value
|
||||
* varindex is written to iff it is non-NULL and the selector indicates a property of the object.
|
||||
* @param[in] segMan The Segment Manager
|
||||
* @param[in] obj Address of the object to look the selector up in
|
||||
* @param[in] selectorid The selector to look up
|
||||
* @param[out] varp A reference to the selector, if it is a
|
||||
* variable.
|
||||
* @param[out] fptr A reference to the function described by that
|
||||
* selector, if it is a valid function selector.
|
||||
* fptr is written to iff it is non-NULL and the
|
||||
* selector indicates a member function of that
|
||||
* object.
|
||||
* @return kSelectorNone if the selector was not found in
|
||||
* the object or its superclasses.
|
||||
* kSelectorVariable if the selector represents an
|
||||
* object-relative variable.
|
||||
* kSelectorMethod if the selector represents a
|
||||
* method
|
||||
*/
|
||||
SelectorType lookupSelector(SegManager *segMan, reg_t obj, Selector selectorid,
|
||||
ObjVarRef *varp, reg_t *fptr);
|
||||
|
||||
/**
|
||||
* Read a PMachine instruction from a memory buffer and return its length.
|
||||
*
|
||||
* @param[in] src address from which to start parsing
|
||||
* @param[out] extOpcode "extended" opcode of the parsed instruction
|
||||
* @param[out] opparams parameter for the parsed instruction
|
||||
* @return the length in bytes of the instruction
|
||||
*
|
||||
* @todo How about changing opparams from int16 to int / int32 to preserve
|
||||
* unsigned 16bit words as read for Script_Word? In the past, this
|
||||
* was irrelevant as only a debug opcode used Script_Word. But with
|
||||
* SCI32 we are now using Script_Word for more opcodes. Maybe this is
|
||||
* just a mistake and those opcodes should used Script_SWord -- but if
|
||||
* not then we definitely should change this to int, else we might run
|
||||
* into trouble if we encounter high value words. *If* those exist at all.
|
||||
*/
|
||||
int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4]);
|
||||
|
||||
/**
|
||||
* Finds the script-absolute offset of a relative object offset.
|
||||
*
|
||||
* @param[in] relOffset the relative object offset
|
||||
* @param[in] scr the owner script object, used by SCI1.1+
|
||||
* @param[in] pcOffset the offset of the program counter, used by SCI0early and
|
||||
* SCI3
|
||||
*/
|
||||
uint32 findOffset(const int16 relOffset, const Script *scr, const uint32 pcOffset);
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_VM_H
|
||||
294
engines/sci/engine/vm_types.cpp
Normal file
294
engines/sci/engine/vm_types.cpp
Normal file
@@ -0,0 +1,294 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/engine/seg_manager.h"
|
||||
#include "sci/engine/vm_types.h"
|
||||
#include "sci/engine/workarounds.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
void reg_t::init(SegmentId segment, uint32 offset) {
|
||||
if (getSciVersion() < SCI_VERSION_3) {
|
||||
_segment = segment;
|
||||
_offset = offset;
|
||||
} else {
|
||||
// Set the lower 14 bits of the segment, and preserve the upper 2 ones for the offset
|
||||
_segment = ((offset & 0x30000) >> 2) | (segment & 0x3FFF);
|
||||
_offset = offset & 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
SegmentId reg_t::getSegment() const {
|
||||
if (getSciVersion() < SCI_VERSION_3) {
|
||||
return _segment;
|
||||
} else {
|
||||
// Return the lower 14 bits of the segment
|
||||
return (_segment & 0x3FFF);
|
||||
}
|
||||
}
|
||||
|
||||
void reg_t::setSegment(SegmentId segment) {
|
||||
if (getSciVersion() < SCI_VERSION_3) {
|
||||
_segment = segment;
|
||||
} else {
|
||||
// Set the lower 14 bits of the segment, and preserve the upper 2 ones for the offset
|
||||
_segment = (_segment & 0xC000) | (segment & 0x3FFF);
|
||||
}
|
||||
}
|
||||
|
||||
reg_t reg_t::lookForWorkaround(const reg_t right, const char *operation) const {
|
||||
SciCallOrigin originReply;
|
||||
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, arithmeticWorkarounds, &originReply);
|
||||
if (solution.type == WORKAROUND_NONE)
|
||||
error("Invalid arithmetic operation (%s - params: %04x:%04x and %04x:%04x)", operation, PRINT_REG(*this), PRINT_REG(right));
|
||||
assert(solution.type == WORKAROUND_FAKE);
|
||||
return make_reg(0, solution.value);
|
||||
}
|
||||
|
||||
reg_t reg_t::operator+(const reg_t right) const {
|
||||
if (isPointer() && right.isNumber()) {
|
||||
// Pointer arithmetics. Only some pointer types make sense here
|
||||
SegmentObj *mobj = g_sci->getEngineState()->_segMan->getSegmentObj(getSegment());
|
||||
|
||||
if (!mobj)
|
||||
error("[VM]: Attempt to add %d to invalid pointer %04x:%04x", right.getOffset(), PRINT_REG(*this));
|
||||
|
||||
switch (mobj->getType()) {
|
||||
case SEG_TYPE_LOCALS:
|
||||
case SEG_TYPE_SCRIPT:
|
||||
case SEG_TYPE_STACK:
|
||||
case SEG_TYPE_DYNMEM:
|
||||
return make_reg(getSegment(), getOffset() + right.toSint16());
|
||||
default:
|
||||
return lookForWorkaround(right, "addition");
|
||||
}
|
||||
} else if (isNumber() && right.isPointer()) {
|
||||
// Adding a pointer to a number, flip the order
|
||||
return right + *this;
|
||||
} else if (isNumber() && right.isNumber()) {
|
||||
// Normal arithmetics
|
||||
return make_reg(0, toSint16() + right.toSint16());
|
||||
} else {
|
||||
return lookForWorkaround(right, "addition");
|
||||
}
|
||||
}
|
||||
|
||||
reg_t reg_t::operator-(const reg_t right) const {
|
||||
if (getSegment() == right.getSegment()) {
|
||||
// We can subtract numbers, or pointers with the same segment,
|
||||
// an operation which will yield a number like in C
|
||||
return make_reg(0, toSint16() - right.toSint16());
|
||||
} else {
|
||||
return *this + make_reg(right.getSegment(), -right.toSint16());
|
||||
}
|
||||
}
|
||||
|
||||
reg_t reg_t::operator*(const reg_t right) const {
|
||||
if (isNumber() && right.isNumber())
|
||||
return make_reg(0, toSint16() * right.toSint16());
|
||||
else
|
||||
return lookForWorkaround(right, "multiplication");
|
||||
}
|
||||
|
||||
reg_t reg_t::operator/(const reg_t right) const {
|
||||
if (isNumber() && right.isNumber() && !right.isNull())
|
||||
return make_reg(0, toSint16() / right.toSint16());
|
||||
else
|
||||
return lookForWorkaround(right, "division");
|
||||
}
|
||||
|
||||
reg_t reg_t::operator%(const reg_t right) const {
|
||||
if (isNumber() && right.isNumber() && !right.isNull()) {
|
||||
// Support for negative numbers was added in Iceman, and perhaps in
|
||||
// SCI0 0.000.685 and later. Theoretically, this wasn't really used
|
||||
// in SCI0, so the result is probably unpredictable. Such a case
|
||||
// would indicate either a script bug, or a modulo on an unsigned
|
||||
// integer larger than 32767. In any case, such a case should be
|
||||
// investigated, instead of being silently accepted.
|
||||
if (getSciVersion() <= SCI_VERSION_0_LATE && (toSint16() < 0 || right.toSint16() < 0))
|
||||
warning("Modulo of a negative number has been requested for SCI0. This *could* lead to issues");
|
||||
int16 value = toSint16();
|
||||
int16 modulo = ABS(right.toSint16());
|
||||
int16 result;
|
||||
if (getSciVersion() <= SCI_VERSION_2_1_MIDDLE) {
|
||||
result = value % modulo;
|
||||
if (result < 0)
|
||||
result += modulo;
|
||||
} else {
|
||||
// SCI2.1 Late and SCI3 treat the dividend as unsigned.
|
||||
// LSL7 ocean motion depends on this. Bug #10270
|
||||
result = (uint16)value % modulo;
|
||||
}
|
||||
return make_reg(0, result);
|
||||
} else
|
||||
return lookForWorkaround(right, "modulo");
|
||||
}
|
||||
|
||||
reg_t reg_t::operator>>(const reg_t right) const {
|
||||
if (isNumber() && right.isNumber())
|
||||
return make_reg(0, toUint16() >> right.toUint16());
|
||||
else
|
||||
return lookForWorkaround(right, "shift right");
|
||||
}
|
||||
|
||||
reg_t reg_t::operator<<(const reg_t right) const {
|
||||
if (isNumber() && right.isNumber())
|
||||
return make_reg(0, toUint16() << right.toUint16());
|
||||
else
|
||||
return lookForWorkaround(right, "shift left");
|
||||
}
|
||||
|
||||
reg_t reg_t::operator+(int16 right) const {
|
||||
return *this + make_reg(0, right);
|
||||
}
|
||||
|
||||
reg_t reg_t::operator-(int16 right) const {
|
||||
return *this - make_reg(0, right);
|
||||
}
|
||||
|
||||
uint16 reg_t::requireUint16() const {
|
||||
if (isNumber())
|
||||
return toUint16();
|
||||
else
|
||||
// The right parameter is NULL_REG because
|
||||
// we're not comparing *this with anything here.
|
||||
return lookForWorkaround(NULL_REG, "require unsigned number").toUint16();
|
||||
}
|
||||
|
||||
int16 reg_t::requireSint16() const {
|
||||
if (isNumber())
|
||||
return toSint16();
|
||||
else
|
||||
// The right parameter is NULL_REG because
|
||||
// we're not comparing *this with anything here.
|
||||
return lookForWorkaround(NULL_REG, "require signed number").toSint16();
|
||||
}
|
||||
|
||||
reg_t reg_t::operator&(const reg_t right) const {
|
||||
if (isNumber() && right.isNumber())
|
||||
return make_reg(0, toUint16() & right.toUint16());
|
||||
else
|
||||
return lookForWorkaround(right, "bitwise AND");
|
||||
}
|
||||
|
||||
reg_t reg_t::operator|(const reg_t right) const {
|
||||
if (isNumber() && right.isNumber())
|
||||
return make_reg(0, toUint16() | right.toUint16());
|
||||
else
|
||||
return lookForWorkaround(right, "bitwise OR");
|
||||
}
|
||||
|
||||
reg_t reg_t::operator^(const reg_t right) const {
|
||||
if (isNumber() && right.isNumber())
|
||||
return make_reg(0, toUint16() ^ right.toUint16());
|
||||
else
|
||||
return lookForWorkaround(right, "bitwise XOR");
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
reg_t reg_t::operator&(int16 right) const {
|
||||
return *this & make_reg(0, right);
|
||||
}
|
||||
|
||||
reg_t reg_t::operator|(int16 right) const {
|
||||
return *this | make_reg(0, right);
|
||||
}
|
||||
|
||||
reg_t reg_t::operator^(int16 right) const {
|
||||
return *this ^ make_reg(0, right);
|
||||
}
|
||||
#endif
|
||||
|
||||
int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const {
|
||||
if (getSegment() == right.getSegment()) { // can compare things in the same segment
|
||||
if (treatAsUnsigned || !isNumber())
|
||||
return toUint16() - right.toUint16();
|
||||
else
|
||||
return toSint16() - right.toSint16();
|
||||
#ifdef ENABLE_SCI32
|
||||
} else if (getSciVersion() >= SCI_VERSION_2) {
|
||||
return sci32Comparison(right);
|
||||
#endif
|
||||
} else if (pointerComparisonWithInteger(right)) {
|
||||
return 1;
|
||||
} else if (right.pointerComparisonWithInteger(*this)) {
|
||||
return -1;
|
||||
} else
|
||||
return lookForWorkaround(right, "comparison").toSint16();
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
int reg_t::sci32Comparison(const reg_t right) const {
|
||||
// In SCI32, MemIDs are normally indexes into the memory manager's handle
|
||||
// list, but the engine reserves indexes at and above 20000 for objects
|
||||
// that were created inside the engine (as opposed to inside the VM). The
|
||||
// engine compares these as a tiebreaker for graphics objects that are at
|
||||
// the same priority, and it is necessary to at least minimally handle
|
||||
// this situation.
|
||||
// This is obviously a bogus comparison, but then, this entire thing is
|
||||
// bogus. For the moment, it just needs to be deterministic.
|
||||
if (isNumber() && !right.isNumber()) {
|
||||
return 1;
|
||||
} else if (right.isNumber() && !isNumber()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return getOffset() - right.getOffset();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool reg_t::pointerComparisonWithInteger(const reg_t right) const {
|
||||
// This function handles the case where a script tries to compare a pointer
|
||||
// to a number. Normally, we would not want to allow that. However, SCI0 -
|
||||
// SCI1.1 scripts do this in order to distinguish references to
|
||||
// external resources (which are numbers) from pointers. In
|
||||
// our SCI implementation, such a check may seem pointless, as
|
||||
// one can simply use the segment value to achieve this goal.
|
||||
// But Sierra's SCI did not have the notion of segment IDs, so
|
||||
// both pointer and numbers were simple integers.
|
||||
//
|
||||
// But for some things, scripts had (and have) to distinguish between
|
||||
// numbers and pointers. Lacking the segment information, Sierra's
|
||||
// developers resorted to a hack: If an integer is smaller than a certain
|
||||
// bound, it can be assumed to be a number, otherwise it is assumed to be a
|
||||
// pointer. This allowed them to implement polymorphic functions, such as
|
||||
// the Print function, which can be called in two different ways, with a
|
||||
// pointer or a far text reference:
|
||||
//
|
||||
// (Print "foo") // Pointer to a string
|
||||
// (Print 420 5) // Reference to the fifth message in text resource 420
|
||||
// It works because in those games, the maximum resource number is 999,
|
||||
// so any parameter value above that threshold must be a pointer.
|
||||
// PQ2 japanese compares pointers to 2000 to find out if its a pointer
|
||||
// or a resource ID. Thus, we check for all integers <= 2000.
|
||||
//
|
||||
// Some examples where game scripts check for arbitrary numbers against
|
||||
// pointers:
|
||||
// Hoyle 3, Pachisi, when any opponent is about to talk
|
||||
// SQ1, room 28, when throwing water at the Orat
|
||||
// SQ1, room 58, when giving the ID card to the robot
|
||||
// SQ4 CD, at the first game screen, when the narrator is about to speak
|
||||
return (isPointer() && right.isNumber() && right.getOffset() <= 2000 && getSciVersion() <= SCI_VERSION_1_1);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
257
engines/sci/engine/vm_types.h
Normal file
257
engines/sci/engine/vm_types.h
Normal file
@@ -0,0 +1,257 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_VM_TYPES_H
|
||||
#define SCI_ENGINE_VM_TYPES_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "sci/version.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// Segment ID type
|
||||
typedef uint16 SegmentId;
|
||||
|
||||
enum {
|
||||
kUninitializedSegment = 0x1FFF,
|
||||
kSegmentMask = 0x1FFF,
|
||||
kOffsetMask = 0x7FFFF
|
||||
};
|
||||
|
||||
struct reg_t {
|
||||
// Segment and offset. These should never be accessed directly
|
||||
SegmentId _segment;
|
||||
uint16 _offset;
|
||||
|
||||
void init(SegmentId segment, uint32 offset);
|
||||
|
||||
SegmentId getSegment() const;
|
||||
void setSegment(SegmentId segment);
|
||||
|
||||
// speed optimization: inline due to frequent calling
|
||||
uint32 getOffset() const {
|
||||
if (getSciVersion() < SCI_VERSION_3) {
|
||||
return _offset;
|
||||
} else {
|
||||
// Return the lower 16 bits from the offset, and the 17th and 18th bits from the segment
|
||||
return ((_segment & 0xC000) << 2) | _offset;
|
||||
}
|
||||
}
|
||||
|
||||
// speed optimization: inline due to frequent calling
|
||||
void setOffset(uint32 offset) {
|
||||
if (getSciVersion() < SCI_VERSION_3) {
|
||||
_offset = offset;
|
||||
} else {
|
||||
// Store the lower 16 bits in the offset, and the 17th and 18th bits in the segment
|
||||
_offset = offset & 0xFFFF;
|
||||
_segment = ((offset & 0x30000) >> 2) | (_segment & 0x3FFF);
|
||||
}
|
||||
}
|
||||
|
||||
inline void incOffset(int32 offset) {
|
||||
setOffset(getOffset() + offset);
|
||||
}
|
||||
|
||||
inline bool isNull() const {
|
||||
return (getOffset() | getSegment()) == 0;
|
||||
}
|
||||
|
||||
inline uint16 toUint16() const {
|
||||
return (uint16)getOffset();
|
||||
}
|
||||
|
||||
inline int16 toSint16() const {
|
||||
return (int16)getOffset();
|
||||
}
|
||||
|
||||
bool isNumber() const {
|
||||
return getSegment() == 0;
|
||||
}
|
||||
|
||||
bool isPointer() const {
|
||||
return getSegment() != 0 && getSegment() != kUninitializedSegment;
|
||||
}
|
||||
|
||||
uint16 requireUint16() const;
|
||||
int16 requireSint16() const;
|
||||
|
||||
inline bool isInitialized() const {
|
||||
return getSegment() != kUninitializedSegment;
|
||||
}
|
||||
|
||||
// Comparison operators
|
||||
bool operator==(const reg_t &x) const {
|
||||
return (getOffset() == x.getOffset()) && (getSegment() == x.getSegment());
|
||||
}
|
||||
|
||||
bool operator!=(const reg_t &x) const {
|
||||
return (getOffset() != x.getOffset()) || (getSegment() != x.getSegment());
|
||||
}
|
||||
|
||||
bool operator>(const reg_t right) const {
|
||||
return cmp(right, false) > 0;
|
||||
}
|
||||
|
||||
bool operator>=(const reg_t right) const {
|
||||
return cmp(right, false) >= 0;
|
||||
}
|
||||
|
||||
bool operator<(const reg_t right) const {
|
||||
return cmp(right, false) < 0;
|
||||
}
|
||||
|
||||
bool operator<=(const reg_t right) const {
|
||||
return cmp(right, false) <= 0;
|
||||
}
|
||||
|
||||
// Same as the normal operators, but perform unsigned
|
||||
// integer checking
|
||||
bool gtU(const reg_t right) const {
|
||||
return cmp(right, true) > 0;
|
||||
}
|
||||
|
||||
bool geU(const reg_t right) const {
|
||||
return cmp(right, true) >= 0;
|
||||
}
|
||||
|
||||
bool ltU(const reg_t right) const {
|
||||
return cmp(right, true) < 0;
|
||||
}
|
||||
|
||||
bool leU(const reg_t right) const {
|
||||
return cmp(right, true) <= 0;
|
||||
}
|
||||
|
||||
// Arithmetic operators
|
||||
reg_t operator+(const reg_t right) const;
|
||||
reg_t operator-(const reg_t right) const;
|
||||
reg_t operator*(const reg_t right) const;
|
||||
reg_t operator/(const reg_t right) const;
|
||||
reg_t operator%(const reg_t right) const;
|
||||
reg_t operator>>(const reg_t right) const;
|
||||
reg_t operator<<(const reg_t right) const;
|
||||
|
||||
reg_t operator+(int16 right) const;
|
||||
reg_t operator-(int16 right) const;
|
||||
|
||||
void operator+=(const reg_t &right) { *this = *this + right; }
|
||||
void operator-=(const reg_t &right) { *this = *this - right; }
|
||||
void operator+=(int16 right) { *this = *this + right; }
|
||||
void operator-=(int16 right) { *this = *this - right; }
|
||||
|
||||
// Boolean operators
|
||||
reg_t operator&(const reg_t right) const;
|
||||
reg_t operator|(const reg_t right) const;
|
||||
reg_t operator^(const reg_t right) const;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
reg_t operator&(int16 right) const;
|
||||
reg_t operator|(int16 right) const;
|
||||
reg_t operator^(int16 right) const;
|
||||
|
||||
void operator&=(const reg_t &right) { *this = *this & right; }
|
||||
void operator|=(const reg_t &right) { *this = *this | right; }
|
||||
void operator^=(const reg_t &right) { *this = *this ^ right; }
|
||||
void operator&=(int16 right) { *this = *this & right; }
|
||||
void operator|=(int16 right) { *this = *this | right; }
|
||||
void operator^=(int16 right) { *this = *this ^ right; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
/**
|
||||
* Compares two reg_t's.
|
||||
* Returns:
|
||||
* - a positive number if *this > right
|
||||
* - 0 if *this == right
|
||||
* - a negative number if *this < right
|
||||
*/
|
||||
int cmp(const reg_t right, bool treatAsUnsigned) const;
|
||||
reg_t lookForWorkaround(const reg_t right, const char *operation) const;
|
||||
bool pointerComparisonWithInteger(const reg_t right) const;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
int sci32Comparison(const reg_t right) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline reg_t make_reg(SegmentId segment, uint16 offset) {
|
||||
reg_t r;
|
||||
r.init(segment, offset);
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline reg_t make_reg32(SegmentId segment, uint32 offset) {
|
||||
reg_t r;
|
||||
r.init(segment, offset);
|
||||
return r;
|
||||
}
|
||||
|
||||
#define PRINT_REG(r) (kSegmentMask) & (unsigned) (r).getSegment(), (unsigned) (r).getOffset()
|
||||
|
||||
// Stack pointer type
|
||||
typedef reg_t *StackPtr;
|
||||
|
||||
enum {
|
||||
/**
|
||||
* Special reg_t 'offset' used to indicate an error, or that an operation has
|
||||
* finished (depending on the case).
|
||||
* @see SIGNAL_REG
|
||||
*/
|
||||
SIGNAL_OFFSET = 0xffff
|
||||
};
|
||||
|
||||
extern const reg_t NULL_REG;
|
||||
extern const reg_t SIGNAL_REG;
|
||||
extern const reg_t TRUE_REG;
|
||||
|
||||
// Selector ID
|
||||
typedef int Selector;
|
||||
|
||||
enum {
|
||||
/** Special 'selector' value, used when calling add_exec_stack_entry. */
|
||||
NULL_SELECTOR = -1
|
||||
};
|
||||
|
||||
// Opcode formats
|
||||
enum opcode_format {
|
||||
Script_Invalid = -1,
|
||||
Script_None = 0,
|
||||
Script_Byte,
|
||||
Script_SByte,
|
||||
Script_Word,
|
||||
Script_SWord,
|
||||
Script_Variable,
|
||||
Script_SVariable,
|
||||
Script_SRelative,
|
||||
Script_Property,
|
||||
Script_Global,
|
||||
Script_Local,
|
||||
Script_Temp,
|
||||
Script_Param,
|
||||
Script_Offset,
|
||||
Script_End
|
||||
};
|
||||
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_VM_TYPES_H
|
||||
1429
engines/sci/engine/workarounds.cpp
Normal file
1429
engines/sci/engine/workarounds.cpp
Normal file
File diff suppressed because it is too large
Load Diff
166
engines/sci/engine/workarounds.h
Normal file
166
engines/sci/engine/workarounds.h
Normal file
@@ -0,0 +1,166 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCI_ENGINE_WORKAROUNDS_H
|
||||
#define SCI_ENGINE_WORKAROUNDS_H
|
||||
|
||||
#include "sci/engine/vm_types.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/sci.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
enum SciWorkaroundType {
|
||||
WORKAROUND_NONE, // only used by terminator or when no workaround was found
|
||||
WORKAROUND_IGNORE, // ignore kernel call
|
||||
WORKAROUND_STILLCALL, // still do kernel call
|
||||
WORKAROUND_FAKE // fake kernel call / replace temp value / fake opcode
|
||||
};
|
||||
|
||||
struct SciWorkaroundSolution {
|
||||
SciWorkaroundType type;
|
||||
uint16 value;
|
||||
};
|
||||
|
||||
/**
|
||||
* A structure describing a 'workaround' for a SCI script bug.
|
||||
*
|
||||
* Arrays of SciWorkaroundEntry instances are terminated by
|
||||
* a fake entry in which "objectName" is NULL.
|
||||
*/
|
||||
struct SciWorkaroundEntry {
|
||||
SciGameId gameId;
|
||||
int roomNr;
|
||||
int scriptNr;
|
||||
int16 inheritanceLevel;
|
||||
const char *objectName;
|
||||
const char *methodName;
|
||||
const uint16 *localCallSignature;
|
||||
int fromIndex;
|
||||
int toIndex;
|
||||
SciWorkaroundSolution newValue;
|
||||
};
|
||||
|
||||
extern const SciWorkaroundEntry arithmeticWorkarounds[];
|
||||
extern const SciWorkaroundEntry uninitializedReadWorkarounds[];
|
||||
extern const SciWorkaroundEntry uninitializedReadForParamWorkarounds[];
|
||||
extern const SciWorkaroundEntry kAbs_workarounds[];
|
||||
extern const SciWorkaroundEntry kAnimate_workarounds[];
|
||||
extern const SciWorkaroundEntry kCelHigh_workarounds[];
|
||||
extern const SciWorkaroundEntry kCelWide_workarounds[];
|
||||
extern const SciWorkaroundEntry kDeviceInfo_workarounds[];
|
||||
extern const SciWorkaroundEntry kDisplay_workarounds[];
|
||||
extern const SciWorkaroundEntry kDirLoop_workarounds[];
|
||||
extern const SciWorkaroundEntry kDisposeScript_workarounds[];
|
||||
extern const SciWorkaroundEntry kDoAudioResume_workarounds[];
|
||||
extern const SciWorkaroundEntry kDoSoundPlay_workarounds[];
|
||||
extern const SciWorkaroundEntry kDoSoundFade_workarounds[];
|
||||
extern const SciWorkaroundEntry kFileIOOpen_workarounds[];
|
||||
extern const SciWorkaroundEntry kFileIOCheckFreeSpace_workarounds[];
|
||||
extern const SciWorkaroundEntry kFileIOReadString_workarounds[];
|
||||
extern const SciWorkaroundEntry kFindKey_workarounds[];
|
||||
extern const SciWorkaroundEntry kFrameOut_workarounds[];
|
||||
extern const SciWorkaroundEntry kDeleteKey_workarounds[];
|
||||
extern const SciWorkaroundEntry kGetAngle_workarounds[];
|
||||
extern const SciWorkaroundEntry kGetCWD_workarounds[];
|
||||
extern const SciWorkaroundEntry kGetSaveFiles_workarounds[];
|
||||
extern const SciWorkaroundEntry kGraphDrawLine_workarounds[];
|
||||
extern const SciWorkaroundEntry kGraphSaveBox_workarounds[];
|
||||
extern const SciWorkaroundEntry kGraphRestoreBox_workarounds[];
|
||||
extern const SciWorkaroundEntry kGraphUpdateBox_workarounds[];
|
||||
extern const SciWorkaroundEntry kGraphFillBoxForeground_workarounds[];
|
||||
extern const SciWorkaroundEntry kGraphFillBoxAny_workarounds[];
|
||||
extern const SciWorkaroundEntry kGraphRedrawBox_workarounds[];
|
||||
extern const SciWorkaroundEntry kIsObject_workarounds[];
|
||||
extern const SciWorkaroundEntry kListAt_workarounds[];
|
||||
extern const SciWorkaroundEntry kLock_workarounds[];
|
||||
extern const SciWorkaroundEntry kMemory_workarounds[];
|
||||
extern const SciWorkaroundEntry kMoveCursor_workarounds[];
|
||||
extern const SciWorkaroundEntry kNewWindow_workarounds[];
|
||||
extern const SciWorkaroundEntry kPalVarySetVary_workarounds[];
|
||||
extern const SciWorkaroundEntry kPalVarySetPercent_workarounds[];
|
||||
extern const SciWorkaroundEntry kPalVarySetStart_workarounds[];
|
||||
extern const SciWorkaroundEntry kPalVaryMergeStart_workarounds[];
|
||||
extern const SciWorkaroundEntry kPlatform32_workarounds[];
|
||||
extern const SciWorkaroundEntry kRandom_workarounds[];
|
||||
extern const SciWorkaroundEntry kReadNumber_workarounds[];
|
||||
extern const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[];
|
||||
extern const SciWorkaroundEntry kSetCursor_workarounds[];
|
||||
extern const SciWorkaroundEntry kArraySetElements_workarounds[];
|
||||
extern const SciWorkaroundEntry kArrayFill_workarounds[];
|
||||
extern const SciWorkaroundEntry kSetPort_workarounds[];
|
||||
extern const SciWorkaroundEntry kStrAt_workarounds[];
|
||||
extern const SciWorkaroundEntry kStrCpy_workarounds[];
|
||||
extern const SciWorkaroundEntry kStrLen_workarounds[];
|
||||
extern const SciWorkaroundEntry kUnLoad_workarounds[];
|
||||
extern const SciWorkaroundEntry kWait_workarounds[];
|
||||
extern const SciWorkaroundEntry kStringNew_workarounds[];
|
||||
extern const SciWorkaroundEntry kScrollWindowAdd_workarounds[];
|
||||
|
||||
extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciCallOrigin *trackOrigin);
|
||||
|
||||
enum SciMessageWorkaroundType {
|
||||
MSG_WORKAROUND_NONE, // only used by terminator or when no workaround was found
|
||||
MSG_WORKAROUND_REMAP, // use a different tuple instead
|
||||
MSG_WORKAROUND_FAKE, // use a hard-coded response
|
||||
MSG_WORKAROUND_EXTRACT // use text from a different record, optionally a substring
|
||||
};
|
||||
|
||||
enum SciMedia : uint {
|
||||
SCI_MEDIA_ALL,
|
||||
SCI_MEDIA_FLOPPY,
|
||||
SCI_MEDIA_CD,
|
||||
SCI_MEDIA_MAC, // mac floppy
|
||||
};
|
||||
|
||||
struct SciMessageWorkaroundSolution {
|
||||
SciMessageWorkaroundType type;
|
||||
int module;
|
||||
byte noun;
|
||||
byte verb;
|
||||
byte cond;
|
||||
byte seq;
|
||||
byte talker;
|
||||
uint32 substringIndex;
|
||||
uint32 substringLength;
|
||||
const char *text;
|
||||
};
|
||||
|
||||
struct SciMessageWorkaroundEntry {
|
||||
SciGameId gameId;
|
||||
SciMedia media;
|
||||
kLanguage language;
|
||||
int roomNumber;
|
||||
int module;
|
||||
byte noun;
|
||||
byte verb;
|
||||
byte cond;
|
||||
byte seq;
|
||||
SciMessageWorkaroundSolution solution;
|
||||
};
|
||||
|
||||
extern SciMessageWorkaroundSolution findMessageWorkaround(int module, byte noun, byte verb, byte cond, byte seq);
|
||||
extern ResourceId remapAudio36ResourceId(const ResourceId &resourceId);
|
||||
extern ResourceId remapSync36ResourceId(const ResourceId &resourceId);
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_ENGINE_WORKAROUNDS_H
|
||||
Reference in New Issue
Block a user