Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View 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

View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View 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

View 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
}

View 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

View 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
View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

376
engines/sci/engine/script.h Normal file
View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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
View 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

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

431
engines/sci/engine/vm.h Normal file
View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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