diff options
Diffstat (limited to 'engines/sci/engine')
34 files changed, 3530 insertions, 2675 deletions
diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp index fee6e69da7..315c86c56c 100644 --- a/engines/sci/engine/features.cpp +++ b/engines/sci/engine/features.cpp @@ -38,6 +38,7 @@ GameFeatures::GameFeatures(SegManager *segMan, Kernel *kernel) : _segMan(segMan) _doSoundType = SCI_VERSION_NONE; _lofsType = SCI_VERSION_NONE; _gfxFunctionsType = SCI_VERSION_NONE; + _messageFunctionType = SCI_VERSION_NONE; _moveCountType = kMoveCountUninitialized; #ifdef ENABLE_SCI32 @@ -140,6 +141,10 @@ SciVersion GameFeatures::detectDoSoundType() { // This game is using early SCI0 sound code (different headers than // SCI0 late) _doSoundType = SCI_VERSION_0_EARLY; +#ifdef ENABLE_SCI32 + } else if (getSciVersion() >= SCI_VERSION_2_1) { + _doSoundType = SCI_VERSION_2_1; +#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) @@ -403,6 +408,41 @@ SciVersion GameFeatures::detectGfxFunctionsType() { 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 (READ_SCI11ENDIAN_UINT32(res->data) / 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 diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h index 5b383746d8..167c207437 100644 --- a/engines/sci/engine/features.h +++ b/engines/sci/engine/features.h @@ -66,6 +66,12 @@ public: * @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 /** @@ -105,7 +111,7 @@ private: bool autoDetectSci21KernelType(); #endif - SciVersion _doSoundType, _setCursorType, _lofsType, _gfxFunctionsType; + SciVersion _doSoundType, _setCursorType, _lofsType, _gfxFunctionsType, _messageFunctionType; #ifdef ENABLE_SCI32 SciVersion _sci21KernelType; #endif diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp index beb1d3ce35..d76199c794 100644 --- a/engines/sci/engine/kernel.cpp +++ b/engines/sci/engine/kernel.cpp @@ -27,619 +27,15 @@ #include "sci/engine/kernel.h" #include "sci/event.h" #include "sci/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 { -// Uncompiled kernel signatures are formed from a string of letters. -// each corresponding to a type of a parameter (see below). -// Use small letters to indicate end of sum type. -// Use capital letters for sum types, e.g. -// "LNoLr" for a function which takes two arguments: -// (1) list, node or object -// (2) list or ref -#define KSIG_SPEC_LIST 'l' -#define KSIG_SPEC_NODE 'n' -#define KSIG_SPEC_OBJECT 'o' -#define KSIG_SPEC_REF 'r' // Said Specs and strings -#define KSIG_SPEC_ARITHMETIC 'i' -#define KSIG_SPEC_NULL 'z' -#define KSIG_SPEC_ANY '.' -#define KSIG_SPEC_ELLIPSIS '*' // Arbitrarily more TYPED arguments - -#define KSIG_SPEC_SUM_DONE ('a' - 'A') - - - -/** Default kernel name table. */ -static const char *s_defaultKernelNames[] = { - /*0x00*/ "Load", - /*0x01*/ "UnLoad", - /*0x02*/ "ScriptID", - /*0x03*/ "DisposeScript", - /*0x04*/ "Clone", - /*0x05*/ "DisposeClone", - /*0x06*/ "IsObject", - /*0x07*/ "RespondsTo", - /*0x08*/ "DrawPic", - /*0x09*/ "Dummy", // Show - /*0x0a*/ "PicNotValid", - /*0x0b*/ "Animate", - /*0x0c*/ "SetNowSeen", - /*0x0d*/ "NumLoops", - /*0x0e*/ "NumCels", - /*0x0f*/ "CelWide", - /*0x10*/ "CelHigh", - /*0x11*/ "DrawCel", - /*0x12*/ "AddToPic", - /*0x13*/ "NewWindow", - /*0x14*/ "GetPort", - /*0x15*/ "SetPort", - /*0x16*/ "DisposeWindow", - /*0x17*/ "DrawControl", - /*0x18*/ "HiliteControl", - /*0x19*/ "EditControl", - /*0x1a*/ "TextSize", - /*0x1b*/ "Display", - /*0x1c*/ "GetEvent", - /*0x1d*/ "GlobalToLocal", - /*0x1e*/ "LocalToGlobal", - /*0x1f*/ "MapKeyToDir", - /*0x20*/ "DrawMenuBar", - /*0x21*/ "MenuSelect", - /*0x22*/ "AddMenu", - /*0x23*/ "DrawStatus", - /*0x24*/ "Parse", - /*0x25*/ "Said", - /*0x26*/ "SetSynonyms", // Portrait (KQ6 hires) - /*0x27*/ "HaveMouse", - /*0x28*/ "SetCursor", - // FOpen (SCI0) - // FPuts (SCI0) - // FGets (SCI0) - // FClose (SCI0) - /*0x29*/ "SaveGame", - /*0x2a*/ "RestoreGame", - /*0x2b*/ "RestartGame", - /*0x2c*/ "GameIsRestarting", - /*0x2d*/ "DoSound", - /*0x2e*/ "NewList", - /*0x2f*/ "DisposeList", - /*0x30*/ "NewNode", - /*0x31*/ "FirstNode", - /*0x32*/ "LastNode", - /*0x33*/ "EmptyList", - /*0x34*/ "NextNode", - /*0x35*/ "PrevNode", - /*0x36*/ "NodeValue", - /*0x37*/ "AddAfter", - /*0x38*/ "AddToFront", - /*0x39*/ "AddToEnd", - /*0x3a*/ "FindKey", - /*0x3b*/ "DeleteKey", - /*0x3c*/ "Random", - /*0x3d*/ "Abs", - /*0x3e*/ "Sqrt", - /*0x3f*/ "GetAngle", - /*0x40*/ "GetDistance", - /*0x41*/ "Wait", - /*0x42*/ "GetTime", - /*0x43*/ "StrEnd", - /*0x44*/ "StrCat", - /*0x45*/ "StrCmp", - /*0x46*/ "StrLen", - /*0x47*/ "StrCpy", - /*0x48*/ "Format", - /*0x49*/ "GetFarText", - /*0x4a*/ "ReadNumber", - /*0x4b*/ "BaseSetter", - /*0x4c*/ "DirLoop", - /*0x4d*/ "CanBeHere", // CantBeHere in newer SCI versions - /*0x4e*/ "OnControl", - /*0x4f*/ "InitBresen", - /*0x50*/ "DoBresen", - /*0x51*/ "Platform", // DoAvoider (SCI0) - /*0x52*/ "SetJump", - /*0x53*/ "SetDebug", - /*0x54*/ "Dummy", // InspectObj - /*0x55*/ "Dummy", // ShowSends - /*0x56*/ "Dummy", // ShowObjs - /*0x57*/ "Dummy", // ShowFree - /*0x58*/ "MemoryInfo", - /*0x59*/ "Dummy", // StackUsage - /*0x5a*/ "Dummy", // Profiler - /*0x5b*/ "GetMenu", - /*0x5c*/ "SetMenu", - /*0x5d*/ "GetSaveFiles", - /*0x5e*/ "GetCWD", - /*0x5f*/ "CheckFreeSpace", - /*0x60*/ "ValidPath", - /*0x61*/ "CoordPri", - /*0x62*/ "StrAt", - /*0x63*/ "DeviceInfo", - /*0x64*/ "GetSaveDir", - /*0x65*/ "CheckSaveGame", - /*0x66*/ "ShakeScreen", - /*0x67*/ "FlushResources", - /*0x68*/ "SinMult", - /*0x69*/ "CosMult", - /*0x6a*/ "SinDiv", - /*0x6b*/ "CosDiv", - /*0x6c*/ "Graph", - /*0x6d*/ "Joystick", - // End of kernel function table for SCI0 - /*0x6e*/ "Dummy", // ShiftScreen - /*0x6f*/ "Palette", - /*0x70*/ "MemorySegment", - /*0x71*/ "Intersections", // MoveCursor (SCI1 late), PalVary (SCI1.1) - /*0x72*/ "Memory", - /*0x73*/ "Dummy", // ListOps - /*0x74*/ "FileIO", - /*0x75*/ "DoAudio", - /*0x76*/ "DoSync", - /*0x77*/ "AvoidPath", - /*0x78*/ "Sort", // StrSplit (SCI01) - /*0x79*/ "Dummy", // ATan - /*0x7a*/ "Lock", - /*0x7b*/ "StrSplit", - /*0x7c*/ "GetMessage", // Message (SCI1.1) - /*0x7d*/ "IsItSkip", - /*0x7e*/ "MergePoly", - /*0x7f*/ "ResCheck", - /*0x80*/ "AssertPalette", - /*0x81*/ "TextColors", - /*0x82*/ "TextFonts", - /*0x83*/ "Dummy", // Record - /*0x84*/ "Dummy", // PlayBack - /*0x85*/ "ShowMovie", - /*0x86*/ "SetVideoMode", - /*0x87*/ "SetQuitStr", - /*0x88*/ "Dummy" // DbugStr -}; - -reg_t kStub(EngineState *s, int argc, reg_t *argv) { - Kernel *kernel = g_sci->getKernel(); - int kernelCallNr = -1; - - Common::List<ExecStack>::iterator callIterator = s->_executionStack.end(); - if (callIterator != s->_executionStack.begin()) { - callIterator--; - ExecStack lastCall = *callIterator; - kernelCallNr = lastCall.debugSelector; - } - - Common::String warningMsg = "Dummy function k" + kernel->getKernelName(kernelCallNr) + - Common::String::printf("[%x]", kernelCallNr) + - " invoked. Params: " + - Common::String::printf("%d", argc) + " ("; - - for (int i = 0; i < argc; i++) { - warningMsg += Common::String::printf("%04x:%04x", PRINT_REG(argv[i])); - warningMsg += (i == argc - 1 ? ")" : ", "); - } - - warning("%s", warningMsg.c_str()); - return s->r_acc; -} - -reg_t kStubNull(EngineState *s, int argc, reg_t *argv) { - kStub(s, argc, argv); - return NULL_REG; -} - -reg_t kDummy(EngineState *s, int argc, reg_t *argv) { - kStub(s, argc, argv); - error("Kernel function was called, which was considered to be unused - see log for details"); -} - -// [io] -> either integer or object -// (io) -> optionally integer AND an object -// (i) -> optional integer -// . -> any type -// i* -> optional multiple integers -// .* -> any parameters afterwards (or none) - -// gameID, scriptNr,lvl, object-name, method-name, call, index, replace -static const SciWorkaroundEntry kAbs_workarounds[] = { - { GID_HOYLE1, 1, 0, "room1", "doit", -1, 0, { 2, 0x3e9 } }, // crazy eights - called with objects instead of integers - { GID_HOYLE1, 2, 0, "room2", "doit", -1, 0, { 2, 0x3e9 } }, // old maid - called with objects instead of integers - { GID_HOYLE1, 3, 0, "room3", "doit", -1, 0, { 2, 0x3e9 } }, // hearts - called with objects instead of integers - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, scriptNr,lvl, object-name, method-name, call, index, replace -static const SciWorkaroundEntry kDisposeScript_workarounds[] = { - { GID_QFG1, 64, 0, "rm64", "dispose", -1, 0, { 1, 0 } }, // when leaving graveyard, parameter 0 is an object - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, scriptNr,lvl, object-name, method-name, call, index, replace -static const SciWorkaroundEntry kDoSoundFade_workarounds[] = { - { GID_KQ6, 989, 0, "globalSound", "fade", -1, 0, { 0, 0 } }, // during intro, parameter 4 is an object - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, scriptNr,lvl, object-name, method-name, call, index, replace -static const SciWorkaroundEntry kGraphRestoreBox_workarounds[] = { - { GID_LSL6, 85, 0, "rScroller", "hide", -1, 0, { 0, 0 } }, // happens when restoring (sometimes), same as the one below - { GID_LSL6, 85, 0, "lScroller", "hide", -1, 0, { 0, 0 } }, // happens when restoring (sometimes), same as the one below - { GID_LSL6, 86, 0, "LL6Inv", "show", -1, 0, { 0, 0 } }, // happens when restoring, is called with hunk segment, but hunk is not allocated at that time - // ^^ TODO: check, if this is really a script error or an issue with our restore code - { GID_LSL6, 86, 0, "LL6Inv", "hide", -1, 0, { 0, 0 } }, // happens during the game, gets called with 1 extra parameter - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, scriptNr,lvl, object-name, method-name, call, index, replace -static const SciWorkaroundEntry kGraphFillBoxForeground_workarounds[] = { - { GID_LSL6, 0, 0, "LSL6", "hideControls", -1, 0, { 0, 0 } }, // happens when giving the bungee key to merrily - gets called with additional 5th parameter - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, scriptNr,lvl, object-name, method-name, call, index, replace -static const SciWorkaroundEntry kGraphFillBoxAny_workarounds[] = { - { GID_SQ4, 818, 0, "iconTextSwitch", "show", -1, 0, { 0, 0 } }, // game menu "text/speech" display - parameter 5 is missing, but the right color number is on the stack - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, scriptNr,lvl, object-name, method-name, call, index, replace -static const SciWorkaroundEntry kUnLoad_workarounds[] = { - { GID_LSL6, 130, 0, "recruitLarryScr", "changeState", -1, 0, { 1, 0 } }, // during intro, a 3rd parameter is passed by accident - { GID_LSL6, 740, 0, "showCartoon", "changeState", -1, 0, { 1, 0 } }, // during ending, 4 additional parameters are passed by accident - { GID_SQ1, 303, 0, "slotGuy", "dispose", -1, 0, { 1, 0 } }, // when leaving ulence flats bar, parameter 1 is not passed - script error - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -struct SciKernelMapSubEntry { - SciVersion fromVersion; - SciVersion toVersion; - - uint16 id; - - const char *name; - KernelFunctionCall *function; - - const char *signature; - const SciWorkaroundEntry *workarounds; -}; - -#define SCI_SUBOPENTRY_TERMINATOR { SCI_VERSION_NONE, SCI_VERSION_NONE, 0, NULL, NULL, NULL, NULL } - - -#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE -#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01 -#define SIG_SCI1 SCI_VERSION_1_EGA, SCI_VERSION_1_LATE -#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1 -#define SIG_SCI16 SCI_VERSION_NONE, SCI_VERSION_1_1 -#define SIG_SCI32 SCI_VERSION_2, SCI_VERSION_NONE - -// SCI-Sound-Version -#define SIG_SOUNDSCI0 SCI_VERSION_0_EARLY, SCI_VERSION_0_LATE -#define SIG_SOUNDSCI1EARLY SCI_VERSION_1_EARLY, SCI_VERSION_1_EARLY -#define SIG_SOUNDSCI1LATE SCI_VERSION_1_LATE, SCI_VERSION_1_LATE - -#define SIGFOR_ALL 0x3f -#define SIGFOR_DOS 1 << 0 -#define SIGFOR_PC98 1 << 1 -#define SIGFOR_WIN 1 << 2 -#define SIGFOR_MAC 1 << 3 -#define SIGFOR_AMIGA 1 << 4 -#define SIGFOR_ATARI 1 << 5 -#define SIGFOR_PC SIGFOR_DOS|SIGFOR_WIN - -#define SIG_EVERYWHERE SIG_SCIALL, SIGFOR_ALL - -#define MAP_CALL(_name_) #_name_, k##_name_ - -// version, subId, function-mapping, signature, workarounds -static const SciKernelMapSubEntry kDoSound_subops[] = { - { SIG_SOUNDSCI0, 0, MAP_CALL(DoSoundInit), "o", NULL }, - { SIG_SOUNDSCI0, 1, MAP_CALL(DoSoundPlay), "o", NULL }, - { SIG_SOUNDSCI0, 2, MAP_CALL(DoSoundDummy), "o", NULL }, - { SIG_SOUNDSCI0, 3, MAP_CALL(DoSoundDispose), "o", NULL }, - { SIG_SOUNDSCI0, 4, MAP_CALL(DoSoundMute), "(i)", NULL }, - { SIG_SOUNDSCI0, 5, MAP_CALL(DoSoundStop), "o", NULL }, - { SIG_SOUNDSCI0, 6, MAP_CALL(DoSoundPause), "i", NULL }, - { SIG_SOUNDSCI0, 7, MAP_CALL(DoSoundResume), "", NULL }, - { SIG_SOUNDSCI0, 8, MAP_CALL(DoSoundMasterVolume), "(i)", NULL }, - { SIG_SOUNDSCI0, 9, MAP_CALL(DoSoundUpdate), "o", NULL }, - { SIG_SOUNDSCI0, 10, MAP_CALL(DoSoundFade), "o", NULL }, - { SIG_SOUNDSCI0, 11, MAP_CALL(DoSoundGetPolyphony), "", NULL }, - { SIG_SOUNDSCI0, 12, MAP_CALL(DoSoundStopAll), "", NULL }, - { SIG_SOUNDSCI1EARLY, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL }, - { SIG_SOUNDSCI1EARLY, 1, MAP_CALL(DoSoundMute), NULL, NULL }, - { SIG_SOUNDSCI1EARLY, 2, MAP_CALL(DoSoundDummy), NULL, NULL }, - { SIG_SOUNDSCI1EARLY, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL }, - { SIG_SOUNDSCI1EARLY, 4, MAP_CALL(DoSoundUpdate), NULL, NULL }, - { SIG_SOUNDSCI1EARLY, 5, MAP_CALL(DoSoundInit), NULL, NULL }, - { SIG_SOUNDSCI1EARLY, 6, MAP_CALL(DoSoundDispose), NULL, NULL }, - { SIG_SOUNDSCI1EARLY, 7, MAP_CALL(DoSoundPlay), "oi", NULL }, - { SIG_SOUNDSCI1EARLY, 8, MAP_CALL(DoSoundStop), NULL, NULL }, - { SIG_SOUNDSCI1EARLY, 9, MAP_CALL(DoSoundPause), "[o0]i", NULL }, - { SIG_SOUNDSCI1EARLY, 10, MAP_CALL(DoSoundFade), "oiiii", NULL }, - { SIG_SOUNDSCI1EARLY, 11, MAP_CALL(DoSoundUpdateCues), "o", NULL }, - { SIG_SOUNDSCI1EARLY, 12, MAP_CALL(DoSoundSendMidi), "oiiii", NULL }, - { SIG_SOUNDSCI1EARLY, 13, MAP_CALL(DoSoundReverb), "oi", NULL }, - { SIG_SOUNDSCI1EARLY, 14, MAP_CALL(DoSoundSetHold), "oi", NULL }, - { SIG_SOUNDSCI1EARLY, 15, MAP_CALL(DoSoundDummy), "", NULL }, - // ^^ Longbow demo - { SIG_SOUNDSCI1LATE, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 1, MAP_CALL(DoSoundMute), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 2, MAP_CALL(DoSoundDummy), "", NULL }, - { SIG_SOUNDSCI1LATE, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 4, MAP_CALL(DoSoundGetAudioCapability), "", NULL }, - { SIG_SOUNDSCI1LATE, 5, MAP_CALL(DoSoundSuspend), "i", NULL }, - { SIG_SOUNDSCI1LATE, 6, MAP_CALL(DoSoundInit), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 7, MAP_CALL(DoSoundDispose), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 8, MAP_CALL(DoSoundPlay), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 9, MAP_CALL(DoSoundStop), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 10, MAP_CALL(DoSoundPause), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 11, MAP_CALL(DoSoundFade), "oiiii(i)", kDoSoundFade_workarounds }, - { SIG_SOUNDSCI1LATE, 12, MAP_CALL(DoSoundSetHold), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 13, MAP_CALL(DoSoundDummy), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 14, MAP_CALL(DoSoundSetVolume), "oi", NULL }, - { SIG_SOUNDSCI1LATE, 15, MAP_CALL(DoSoundSetPriority), "oi", NULL }, - { SIG_SOUNDSCI1LATE, 16, MAP_CALL(DoSoundSetLoop), "oi", NULL }, - { SIG_SOUNDSCI1LATE, 17, MAP_CALL(DoSoundUpdateCues), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 18, MAP_CALL(DoSoundSendMidi), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 19, MAP_CALL(DoSoundReverb), NULL, NULL }, - { SIG_SOUNDSCI1LATE, 20, MAP_CALL(DoSoundUpdate), NULL, NULL }, - SCI_SUBOPENTRY_TERMINATOR -}; - -// version, subId, function-mapping, signature, workarounds -static const SciKernelMapSubEntry kGraph_subops[] = { - { SIG_SCI32, 1, MAP_CALL(StubNull), "", NULL }, // called by gk1 sci32 right at the start - { SIG_SCIALL, 2, MAP_CALL(GraphGetColorCount), "", NULL }, - // 3 - set palette via resource - { SIG_SCIALL, 4, MAP_CALL(GraphDrawLine), "iiiii(i)(i)", NULL }, - // 5 - nop - // 6 - draw pattern - { SIG_SCIALL, 7, MAP_CALL(GraphSaveBox), "iiiii", NULL }, - { SIG_SCIALL, 8, MAP_CALL(GraphRestoreBox), "[r0!]", kGraphRestoreBox_workarounds }, - // ^ this may get called with invalid references, we check them within restoreBits() and sierra sci behaves the same - { SIG_SCIALL, 9, MAP_CALL(GraphFillBoxBackground), "iiii", NULL }, - { SIG_SCIALL, 10, MAP_CALL(GraphFillBoxForeground), "iiii", kGraphFillBoxForeground_workarounds }, - { SIG_SCIALL, 11, MAP_CALL(GraphFillBoxAny), "iiiiii(i)(i)", kGraphFillBoxAny_workarounds }, - { SIG_SCI11, 12, MAP_CALL(GraphUpdateBox), "iiii(i)(r0)", NULL }, // kq6 hires - { SIG_SCIALL, 12, MAP_CALL(GraphUpdateBox), "iiii(i)", NULL }, - { SIG_SCIALL, 13, MAP_CALL(GraphRedrawBox), "iiii", NULL }, - { SIG_SCIALL, 14, MAP_CALL(GraphAdjustPriority), "ii", NULL }, - { SIG_SCI11, 15, MAP_CALL(GraphSaveUpscaledHiresBox), "iiii", NULL }, // kq6 hires - SCI_SUBOPENTRY_TERMINATOR -}; - -// version, subId, function-mapping, signature, workarounds -static const SciKernelMapSubEntry kPalVary_subops[] = { - { SIG_SCIALL, 0, MAP_CALL(PalVaryInit), "ii(i)(i)", NULL }, - { SIG_SCIALL, 1, MAP_CALL(PalVaryReverse), "(i)(i)(i)", NULL }, - { SIG_SCIALL, 2, MAP_CALL(PalVaryGetCurrentStep), "", NULL }, - { SIG_SCIALL, 3, MAP_CALL(PalVaryDeinit), "", NULL }, - { SIG_SCIALL, 4, MAP_CALL(PalVaryChangeTarget), "i", NULL }, - { SIG_SCIALL, 5, MAP_CALL(PalVaryChangeTicks), "i", NULL }, - { SIG_SCIALL, 6, MAP_CALL(PalVaryPauseResume), "i", NULL }, - { SIG_SCI32, 8, MAP_CALL(PalVaryUnknown), "", NULL }, - SCI_SUBOPENTRY_TERMINATOR -}; - -// version, subId, function-mapping, signature, workarounds -static const SciKernelMapSubEntry kPalette_subops[] = { - { SIG_SCIALL, 1, MAP_CALL(PaletteSetFromResource), "i(i)", NULL }, - { SIG_SCIALL, 2, MAP_CALL(PaletteSetFlag), "iii", NULL }, - { SIG_SCIALL, 3, MAP_CALL(PaletteUnsetFlag), "iii", NULL }, - { SIG_SCIALL, 4, MAP_CALL(PaletteSetIntensity), "iii(i)", NULL }, - { SIG_SCIALL, 5, MAP_CALL(PaletteFindColor), "iii", NULL }, - { SIG_SCIALL, 6, MAP_CALL(PaletteAnimate), "i*", NULL }, - { SIG_SCIALL, 7, MAP_CALL(PaletteSave), "", NULL }, - { SIG_SCIALL, 8, MAP_CALL(PaletteRestore), "i", NULL }, - SCI_SUBOPENTRY_TERMINATOR -}; - -struct SciKernelMapEntry { - const char *name; - KernelFunctionCall *function; - - SciVersion fromVersion; - SciVersion toVersion; - byte forPlatform; - - const char *signature; - const SciKernelMapSubEntry *subFunctions; - const SciWorkaroundEntry *workarounds; -}; - -// name, version/platform, signature, sub-signatures, workarounds -static SciKernelMapEntry s_kernelMap[] = { - { MAP_CALL(Abs), SIG_EVERYWHERE, "i", NULL, kAbs_workarounds }, - { MAP_CALL(AddAfter), SIG_EVERYWHERE, "lnn", NULL, NULL }, - { MAP_CALL(AddMenu), SIG_EVERYWHERE, "rr", NULL, NULL }, - { MAP_CALL(AddToEnd), SIG_EVERYWHERE, "ln", NULL, NULL }, - { MAP_CALL(AddToFront), SIG_EVERYWHERE, "ln", NULL, NULL }, - { MAP_CALL(AddToPic), SIG_EVERYWHERE, "[il](iiiiii)", NULL, NULL }, - { MAP_CALL(Animate), SIG_EVERYWHERE, "(l0)(i)", NULL, NULL }, - { MAP_CALL(AssertPalette), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(AvoidPath), SIG_EVERYWHERE, "ii(.*)", NULL, NULL }, - { MAP_CALL(BaseSetter), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(CanBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, - { MAP_CALL(CantBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, - { MAP_CALL(CelHigh), SIG_EVERYWHERE, "ii(i)", NULL, NULL }, - { MAP_CALL(CelWide), SIG_EVERYWHERE, "ii(i)", NULL, NULL }, - { MAP_CALL(CheckFreeSpace), SIG_SCI32, SIGFOR_ALL, "r.*", NULL, NULL }, - { MAP_CALL(CheckFreeSpace), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(CheckSaveGame), SIG_EVERYWHERE, ".*", NULL, NULL }, - { MAP_CALL(Clone), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(CoordPri), SIG_EVERYWHERE, "i(i)", NULL, NULL }, - { MAP_CALL(CosDiv), SIG_EVERYWHERE, "ii", NULL, NULL }, - { MAP_CALL(DeleteKey), SIG_EVERYWHERE, "l.", NULL, NULL }, - { MAP_CALL(DeviceInfo), SIG_EVERYWHERE, "i(r)(r)(i)", NULL, NULL }, // subop - { MAP_CALL(Display), SIG_EVERYWHERE, "[ir]([ir!]*)", NULL, NULL }, - // ^ we allow invalid references here, because kDisplay gets called with those in e.g. pq3 during intro - // restoreBits() checks and skips invalid handles, so that's fine. Sierra SCI behaved the same - { MAP_CALL(DirLoop), SIG_EVERYWHERE, "oi", NULL, NULL }, - { MAP_CALL(DisposeClone), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(DisposeList), SIG_EVERYWHERE, "l", NULL, NULL }, - { MAP_CALL(DisposeScript), SIG_EVERYWHERE, "i(i*)", NULL, kDisposeScript_workarounds }, - { MAP_CALL(DisposeWindow), SIG_EVERYWHERE, "i(i)", NULL, NULL }, - { MAP_CALL(DoAudio), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop - { MAP_CALL(DoAvoider), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(DoBresen), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(DoSound), SIG_EVERYWHERE, "i([io])(i)(ii[io])(i)", kDoSound_subops, NULL }, - { MAP_CALL(DoSync), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop - { MAP_CALL(DrawCel), SIG_SCI11, SIGFOR_PC, "iiiii(i)(i)(r0)", NULL, NULL }, // for kq6 hires - { MAP_CALL(DrawCel), SIG_EVERYWHERE, "iiiii(i)(i)", NULL, NULL }, - { MAP_CALL(DrawControl), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(DrawMenuBar), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(DrawPic), SIG_EVERYWHERE, "i(i)(i)(i)", NULL, NULL }, - { MAP_CALL(DrawStatus), SIG_EVERYWHERE, "[r0](i)(i)", NULL, NULL }, - { MAP_CALL(EditControl), SIG_EVERYWHERE, "[o0][o0]", NULL, NULL }, - { MAP_CALL(Empty), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(EmptyList), SIG_EVERYWHERE, "l", NULL, NULL }, - { MAP_CALL(FClose), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(FGets), SIG_EVERYWHERE, "rii", NULL, NULL }, - { MAP_CALL(FOpen), SIG_EVERYWHERE, "ri", NULL, NULL }, - { MAP_CALL(FPuts), SIG_EVERYWHERE, "ir", NULL, NULL }, - { MAP_CALL(FileIO), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop - { MAP_CALL(FindKey), SIG_EVERYWHERE, "l.", NULL, NULL }, - { MAP_CALL(FirstNode), SIG_EVERYWHERE, "[l0]", NULL, NULL }, - { MAP_CALL(FlushResources), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(Format), SIG_EVERYWHERE, "r(.*)", NULL, NULL }, - { MAP_CALL(GameIsRestarting), SIG_EVERYWHERE, "(i)", NULL, NULL }, - { MAP_CALL(GetAngle), SIG_EVERYWHERE, "iiii", NULL, NULL }, - // ^^ FIXME - occasionally KQ6 passes a 5th argument by mistake - { MAP_CALL(GetCWD), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(GetDistance), SIG_EVERYWHERE, "ii(i)(i)(i)(i)", NULL, NULL }, - { MAP_CALL(GetEvent), SIG_SCIALL, SIGFOR_MAC, "io(i*)", NULL, NULL }, - { MAP_CALL(GetEvent), SIG_EVERYWHERE, "io", NULL, NULL }, - { MAP_CALL(GetFarText), SIG_EVERYWHERE, "ii[r0]", NULL, NULL }, - { MAP_CALL(GetMenu), SIG_EVERYWHERE, "i.", NULL, NULL }, - { MAP_CALL(GetMessage), SIG_EVERYWHERE, "iiir", NULL, NULL }, - { MAP_CALL(GetPort), SIG_EVERYWHERE, "", NULL, NULL }, - { MAP_CALL(GetSaveDir), SIG_SCI32, SIGFOR_ALL, "(r*)", NULL, NULL }, - { MAP_CALL(GetSaveDir), SIG_EVERYWHERE, "", NULL, NULL }, - { MAP_CALL(GetSaveFiles), SIG_EVERYWHERE, "rrr", NULL, NULL }, - { MAP_CALL(GetTime), SIG_EVERYWHERE, "(i)", NULL, NULL }, - { MAP_CALL(GlobalToLocal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, - { MAP_CALL(GlobalToLocal), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(Graph), SIG_EVERYWHERE, NULL, kGraph_subops, NULL }, - { MAP_CALL(HaveMouse), SIG_EVERYWHERE, "", NULL, NULL }, - { MAP_CALL(HiliteControl), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(InitBresen), SIG_EVERYWHERE, "o(i)", NULL, NULL }, - { MAP_CALL(Intersections), SIG_EVERYWHERE, "iiiiriiiri", NULL, NULL }, - { MAP_CALL(IsItSkip), SIG_EVERYWHERE, "iiiii", NULL, NULL }, - { MAP_CALL(IsObject), SIG_EVERYWHERE, ".", NULL, NULL }, - { MAP_CALL(Joystick), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop - { MAP_CALL(LastNode), SIG_EVERYWHERE, "l", NULL, NULL }, - { MAP_CALL(Load), SIG_EVERYWHERE, "ii(i*)", NULL, NULL }, - { MAP_CALL(LocalToGlobal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, - { MAP_CALL(LocalToGlobal), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(Lock), SIG_EVERYWHERE, "ii(i)", NULL, NULL }, - { MAP_CALL(MapKeyToDir), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(Memory), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop - { MAP_CALL(MemoryInfo), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(MemorySegment), SIG_EVERYWHERE, "ir(i)", NULL, NULL }, // subop - { MAP_CALL(MenuSelect), SIG_EVERYWHERE, "o(i)", NULL, NULL }, - { MAP_CALL(MergePoly), SIG_EVERYWHERE, "rli", NULL, NULL }, - { MAP_CALL(Message), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop - { MAP_CALL(MoveCursor), SIG_EVERYWHERE, "ii", NULL, NULL }, - { MAP_CALL(NewList), SIG_EVERYWHERE, "", NULL, NULL }, - { MAP_CALL(NewNode), SIG_EVERYWHERE, "..", NULL, NULL }, - { MAP_CALL(NewWindow), SIG_SCIALL, SIGFOR_MAC, ".*", NULL, NULL }, - { MAP_CALL(NewWindow), SIG_SCI0, SIGFOR_ALL, "iiii[r0]i(i)(i)(i)", NULL, NULL }, - { MAP_CALL(NewWindow), SIG_SCI1, SIGFOR_ALL, "iiii[ir]i(i)(i)([ir])(i)(i)(i)(i)", NULL, NULL }, - { MAP_CALL(NewWindow), SIG_SCI11, SIGFOR_ALL, "iiiiiiii[r0]i(i)(i)(i)", NULL, NULL }, - { MAP_CALL(NextNode), SIG_EVERYWHERE, "n", NULL, NULL }, - { MAP_CALL(NodeValue), SIG_EVERYWHERE, "[n0]", NULL, NULL }, - { MAP_CALL(NumCels), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(NumLoops), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(OnControl), SIG_EVERYWHERE, "ii(i)(i)(i)", NULL, NULL }, - { MAP_CALL(PalVary), SIG_EVERYWHERE, "i(i*)", kPalVary_subops, NULL }, - { MAP_CALL(Palette), SIG_EVERYWHERE, "i(.*)", kPalette_subops, NULL }, - { MAP_CALL(Parse), SIG_EVERYWHERE, "ro", NULL, NULL }, - { MAP_CALL(PicNotValid), SIG_EVERYWHERE, "(i)", NULL, NULL }, - { MAP_CALL(Platform), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(Portrait), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop - { MAP_CALL(PrevNode), SIG_EVERYWHERE, "n", NULL, NULL }, - { MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(Random), SIG_EVERYWHERE, "ii", NULL, NULL }, - { MAP_CALL(ReadNumber), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL }, - { MAP_CALL(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL }, - { MAP_CALL(RestartGame), SIG_EVERYWHERE, "", NULL, NULL }, - { MAP_CALL(RestoreGame), SIG_EVERYWHERE, "rir", NULL, NULL }, - { MAP_CALL(Said), SIG_EVERYWHERE, "[r0]", NULL, NULL }, - { MAP_CALL(SaveGame), SIG_EVERYWHERE, "rir(r)", NULL, NULL }, - { MAP_CALL(ScriptID), SIG_EVERYWHERE, "[io](i)", NULL, NULL }, - { MAP_CALL(SetCursor), SIG_EVERYWHERE, "i(i*)", NULL, NULL }, - { MAP_CALL(SetDebug), SIG_EVERYWHERE, "(i*)", NULL, NULL }, - { MAP_CALL(SetJump), SIG_EVERYWHERE, "oiii", NULL, NULL }, - { MAP_CALL(SetMenu), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, - { MAP_CALL(SetNowSeen), SIG_EVERYWHERE, "o(i)", NULL, NULL }, - { MAP_CALL(SetPort), SIG_EVERYWHERE, "i(iii)(i)(i)(i)", NULL, NULL }, - { MAP_CALL(SetQuitStr), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(SetVideoMode), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(ShakeScreen), SIG_EVERYWHERE, "(i)(i)", NULL, NULL }, - { MAP_CALL(ShowMovie), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(SinDiv), SIG_EVERYWHERE, "ii", NULL, NULL }, - { MAP_CALL(Sort), SIG_EVERYWHERE, "ooo", NULL, NULL }, - { MAP_CALL(Sqrt), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(StrAt), SIG_EVERYWHERE, "ri(i)", NULL, NULL }, - { MAP_CALL(StrCat), SIG_EVERYWHERE, "rr", NULL, NULL }, - { MAP_CALL(StrCmp), SIG_EVERYWHERE, "rr(i)", NULL, NULL }, - { MAP_CALL(StrCpy), SIG_EVERYWHERE, "[r0]r(i)", NULL, NULL }, - { MAP_CALL(StrEnd), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(StrLen), SIG_EVERYWHERE, "[r0]", NULL, NULL }, - { MAP_CALL(StrSplit), SIG_EVERYWHERE, "rr[r0]", NULL, NULL }, - { MAP_CALL(TextColors), SIG_EVERYWHERE, "(i*)", NULL, NULL }, - { MAP_CALL(TextFonts), SIG_EVERYWHERE, "(i*)", NULL, NULL }, - { MAP_CALL(TextSize), SIG_EVERYWHERE, "r[r0]i(i)(r0)", NULL, NULL }, - { MAP_CALL(TimesCos), SIG_EVERYWHERE, "ii", NULL, NULL }, - { "CosMult", kTimesCos, SIG_EVERYWHERE, "ii", NULL, NULL }, - { MAP_CALL(TimesCot), SIG_EVERYWHERE, "ii", NULL, NULL }, - { MAP_CALL(TimesSin), SIG_EVERYWHERE, "ii", NULL, NULL }, - { "SinMult", kTimesSin, SIG_EVERYWHERE, "ii", NULL, NULL }, - { MAP_CALL(TimesTan), SIG_EVERYWHERE, "ii", NULL, NULL }, - { MAP_CALL(UnLoad), SIG_EVERYWHERE, "i[ri]", NULL, kUnLoad_workarounds }, - { MAP_CALL(ValidPath), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(Wait), SIG_EVERYWHERE, "i", NULL, NULL }, - -#ifdef ENABLE_SCI32 - // SCI2 Kernel Functions - { MAP_CALL(AddPlane), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(AddScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(Array), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(CreateTextBitmap), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, - { MAP_CALL(DeletePlane), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(DeleteScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(FrameOut), SIG_EVERYWHERE, "", NULL, NULL }, - { MAP_CALL(GetHighPlanePri), SIG_EVERYWHERE, "", NULL, NULL }, - { MAP_CALL(InPolygon), SIG_EVERYWHERE, "iio", NULL, NULL }, - { MAP_CALL(IsHiRes), SIG_EVERYWHERE, "", NULL, NULL }, - { MAP_CALL(ListAllTrue), SIG_EVERYWHERE, "li(.*)", NULL, NULL }, - { MAP_CALL(ListAt), SIG_EVERYWHERE, "li", NULL, NULL }, - { MAP_CALL(ListEachElementDo), SIG_EVERYWHERE, "li(.*)", NULL, NULL }, - { MAP_CALL(ListFirstTrue), SIG_EVERYWHERE, "li(.*)", NULL, NULL }, - { MAP_CALL(ListIndexOf), SIG_EVERYWHERE, "l[o0]", NULL, NULL }, - { MAP_CALL(OnMe), SIG_EVERYWHERE, "iio(.*)", NULL, NULL }, - { MAP_CALL(RepaintPlane), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(String), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(UpdatePlane), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(UpdateScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, - - // SCI2.1 Kernel Functions - { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iio(.*)", NULL, NULL }, - { MAP_CALL(List), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL }, - { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(Save), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(Text), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { NULL, NULL, SIG_EVERYWHERE, NULL, NULL, NULL } -#endif -}; - Kernel::Kernel(ResourceManager *resMan, SegManager *segMan) : _resMan(resMan), _segMan(segMan), _invalid("<invalid>") { loadSelectorNames(); @@ -648,16 +44,24 @@ Kernel::Kernel(ResourceManager *resMan, SegManager *segMan) Kernel::~Kernel() { for (KernelFunctionArray::iterator i = _kernelFuncs.begin(); i != _kernelFuncs.end(); ++i) - free(i->signature); + delete[] i->signature; } uint Kernel::getSelectorNamesSize() const { return _selectorNames.size(); } -const Common::String &Kernel::getSelectorName(uint selector) const { - if (selector >= _selectorNames.size()) - return _invalid; +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++) { + Common::String newSelectorName; + newSelectorName = newSelectorName.printf("<noname %d>", loopSelector); + _selectorNames.push_back(newSelectorName); + } + } return _selectorNames[selector]; } @@ -1140,7 +544,7 @@ void Kernel::mapFunctions() { _kernelFuncs[id].workarounds = NULL; _kernelFuncs[id].subFunctions = NULL; _kernelFuncs[id].subFunctionCount = 0; - _kernelFuncs[id].debugCalls = false; + _kernelFuncs[id].debugLogging = false; if (kernelName.empty()) { // No name was given -> must be an unknown opcode warning("Kernel function %x unknown", id); @@ -1246,12 +650,77 @@ void Kernel::mapFunctions() { return; } -void Kernel::setDefaultKernelNames() { +bool Kernel::debugSetFunctionLogging(const char *kernelName, bool logging) { + if (strcmp(kernelName, "*")) { + for (uint id = 0; id < _kernelFuncs.size(); id++) { + if (_kernelFuncs[id].name) { + if (strcmp(kernelName, _kernelFuncs[id].name) == 0) { + if (_kernelFuncs[id].subFunctions) { + // sub-functions available and main name matched, in that case set logging of all sub-functions + KernelSubFunction *kernelSubCall = _kernelFuncs[id].subFunctions; + uint kernelSubCallCount = _kernelFuncs[id].subFunctionCount; + for (uint subId = 0; subId < kernelSubCallCount; subId++) { + if (kernelSubCall->function) + kernelSubCall->debugLogging = logging; + kernelSubCall++; + } + return true; + } + // function name matched, set for this one and exit + _kernelFuncs[id].debugLogging = logging; + return true; + } else { + // main name was not matched + if (_kernelFuncs[id].subFunctions) { + // Sub-Functions available + KernelSubFunction *kernelSubCall = _kernelFuncs[id].subFunctions; + uint kernelSubCallCount = _kernelFuncs[id].subFunctionCount; + for (uint subId = 0; subId < kernelSubCallCount; subId++) { + if (kernelSubCall->function) { + if (strcmp(kernelName, kernelSubCall->name) == 0) { + // sub-function name matched, set for this one and exit + kernelSubCall->debugLogging = logging; + return true; + } + } + kernelSubCall++; + } + } + } + } + } + return false; + } + // Set debugLogging for all calls + for (uint id = 0; id < _kernelFuncs.size(); id++) { + if (_kernelFuncs[id].name) { + if (!_kernelFuncs[id].subFunctions) { + // No sub-functions, enable actual kernel function + _kernelFuncs[id].debugLogging = logging; + } else { + // Sub-Functions available, enable those too + KernelSubFunction *kernelSubCall = _kernelFuncs[id].subFunctions; + uint kernelSubCallCount = _kernelFuncs[id].subFunctionCount; + for (uint subId = 0; subId < kernelSubCallCount; subId++) { + if (kernelSubCall->function) + kernelSubCall->debugLogging = logging; + kernelSubCall++; + } + } + } + } + return true; +} + +void Kernel::setDefaultKernelNames(GameFeatures *features) { _kernelNames = Common::StringArray(s_defaultKernelNames, ARRAYSIZE(s_defaultKernelNames)); // Some (later) SCI versions replaced CanBeHere by CantBeHere - if (_selectorCache.cantBeHere != -1) - _kernelNames[0x4d] = "CantBeHere"; + if (_selectorCache.cantBeHere != -1) { + // hoyle 3 has cantBeHere selector but is assuming to call kCanBeHere + if (g_sci->getGameId() != GID_HOYLE3) + _kernelNames[0x4d] = "CantBeHere"; + } switch (getSciVersion()) { case SCI_VERSION_0_EARLY: @@ -1298,7 +767,11 @@ void Kernel::setDefaultKernelNames() { } _kernelNames[0x71] = "PalVary"; - _kernelNames[0x7c] = "Message"; + + // At least EcoQuest 1 demo uses kGetMessage instead of kMessage. + // Detect which function to use. + if (features->detectMessageFunctionType() == SCI_VERSION_1_1) + _kernelNames[0x7c] = "Message"; break; default: @@ -1307,6 +780,39 @@ void Kernel::setDefaultKernelNames() { } } +#ifdef ENABLE_SCI32 + +enum { + kKernelEntriesSci2 = 0x8b, + kKernelEntriesGk2Demo = 0xa0, + kKernelEntriesSci21 = 0x9d +}; + +void Kernel::setKernelNamesSci2() { + _kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesSci2); +} + +void Kernel::setKernelNamesSci21(GameFeatures *features) { + // Some SCI games use a modified SCI2 kernel table instead of the + // SCI2.1 kernel table. The GK2 demo does this as well as at least + // one version of KQ7 (1.4). We detect which version to use based on + // how kDoSound is called from Sound::play(). + + // 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. + + if (features->detectSci21KernelType() == SCI_VERSION_2) { + _kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesGk2Demo); + // OnMe is IsOnMe here, but they should be compatible + _kernelNames[0x23] = "Robot"; // Graph in SCI2 + _kernelNames[0x2e] = "Priority"; // DisposeTextBitmap in SCI2 + } else + _kernelNames = Common::StringArray(sci21_default_knames, kKernelEntriesSci21); +} + +#endif + void Kernel::loadKernelNames(GameFeatures *features) { _kernelNames.clear(); @@ -1317,7 +823,7 @@ void Kernel::loadKernelNames(GameFeatures *features) { setKernelNamesSci2(); else #endif - setDefaultKernelNames(); + setDefaultKernelNames(features); mapFunctions(); } @@ -1352,4 +858,26 @@ Common::String Kernel::lookupText(reg_t address, int index) { return NULL; } +// TODO: script_adjust_opcode_formats should probably be part of the +// constructor (?) of a VirtualMachine or a ScriptManager class. +void script_adjust_opcode_formats() { + if (g_sci->_features->detectLofsType() != SCI_VERSION_0_EARLY) { + g_opcode_formats[op_lofsa][0] = Script_Offset; + g_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_opcode_formats[op_calle][2] = Script_Word; + g_opcode_formats[op_callk][1] = Script_Word; + g_opcode_formats[op_super][1] = Script_Word; + g_opcode_formats[op_send][0] = Script_Word; + g_opcode_formats[op_self][0] = Script_Word; + g_opcode_formats[op_call][1] = Script_Word; + g_opcode_formats[op_callb][1] = Script_Word; + } +#endif +} + } // End of namespace Sci diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index fa206e8053..285e746349 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -40,6 +40,7 @@ namespace Sci { struct Node; // from segment.h struct List; // from segment.h struct SelectorCache; // from selector.h +struct SciWorkaroundEntry; // from workarounds.h /** * @defgroup VocabularyResources Vocabulary resources in SCI @@ -100,12 +101,12 @@ struct SelectorCache; // from selector.h 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 comparsion + 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 comparsion + 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 @@ -120,24 +121,12 @@ enum { /* Generic description: */ typedef reg_t KernelFunctionCall(EngineState *s, int argc, reg_t *argv); -struct SciWorkaroundEntry { - SciGameId gameId; - int scriptNr; - int16 inheritanceLevel; - const char *objectName; - const char *methodName; - int localCallOffset; - int index; - reg_t newValue; -}; - -#define SCI_WORKAROUNDENTRY_TERMINATOR { (SciGameId)0, -1, 0, NULL, NULL, -1, 0, { 0, 0 } } - struct KernelSubFunction { KernelFunctionCall *function; const char *name; uint16 *signature; const SciWorkaroundEntry *workarounds; + bool debugLogging; }; struct KernelFunction { @@ -145,9 +134,9 @@ struct KernelFunction { const char *name; uint16 *signature; const SciWorkaroundEntry *workarounds; - const KernelSubFunction *subFunctions; + KernelSubFunction *subFunctions; uint16 subFunctionCount; - bool debugCalls; + bool debugLogging; }; class Kernel { @@ -159,7 +148,7 @@ public: ~Kernel(); uint getSelectorNamesSize() const; - const Common::String &getSelectorName(uint selector) const; + const Common::String &getSelectorName(uint selector); uint getKernelNamesSize() const; const Common::String &getKernelName(uint number) const; @@ -228,11 +217,16 @@ public: */ void loadKernelNames(GameFeatures *features); + /** + * Sets debugCalls flag for a kernel function + */ + bool debugSetFunctionLogging(const char *kernelName, bool debugCalls); + private: /** * Sets the default kernel function names, based on the SCI version used. */ - void setDefaultKernelNames(); + void setDefaultKernelNames(GameFeatures *features); #ifdef ENABLE_SCI32 /** @@ -423,7 +417,10 @@ 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 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); #ifdef ENABLE_SCI32 // SCI2 Kernel Functions @@ -432,6 +429,7 @@ reg_t kArray(EngineState *s, int argc, reg_t *argv); reg_t kListAt(EngineState *s, int argc, reg_t *argv); reg_t kString(EngineState *s, int argc, reg_t *argv); reg_t kMulDiv(EngineState *s, int argc, reg_t *argv); +reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv); // "Screen items" in SCI32 are views reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv); reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv); @@ -460,7 +458,11 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv); reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv); reg_t kCD(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); #endif reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv); @@ -471,7 +473,7 @@ 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 kDoSoundResume(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); @@ -516,6 +518,25 @@ 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); +#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); +#endif + //@} } // End of namespace Sci diff --git a/engines/sci/engine/kernel32.cpp b/engines/sci/engine/kernel32.cpp deleted file mode 100644 index eab1b90139..0000000000 --- a/engines/sci/engine/kernel32.cpp +++ /dev/null @@ -1,932 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -#ifdef ENABLE_SCI32 - -#include "sci/engine/features.h" -#include "sci/engine/kernel.h" -#include "sci/engine/segment.h" -#include "sci/engine/state.h" -#include "sci/engine/selector.h" -#include "sci/graphics/frameout.h" -#include "sci/graphics/screen.h" - -#include "common/system.h" - -namespace Sci { - -// NOTE: 0x72-0x79, 0x85-0x86, 0x88 are from the GK2 demo (which has debug support) and are -// just Dummy in other SCI2 games. -static const char *sci2_default_knames[] = { - /*0x00*/ "Load", - /*0x01*/ "UnLoad", - /*0x02*/ "ScriptID", - /*0x03*/ "DisposeScript", - /*0x04*/ "Lock", - /*0x05*/ "ResCheck", - /*0x06*/ "Purge", - /*0x07*/ "Clone", - /*0x08*/ "DisposeClone", - /*0x09*/ "RespondsTo", - /*0x0a*/ "SetNowSeen", - /*0x0b*/ "NumLoops", - /*0x0c*/ "NumCels", - /*0x0d*/ "CelWide", - /*0x0e*/ "CelHigh", - /*0x0f*/ "GetHighPlanePri", - /*0x10*/ "GetHighItemPri", - /*0x11*/ "ShakeScreen", - /*0x12*/ "OnMe", - /*0x13*/ "ShowMovie", - /*0x14*/ "SetVideoMode", - /*0x15*/ "AddScreenItem", - /*0x16*/ "DeleteScreenItem", - /*0x17*/ "UpdateScreenItem", - /*0x18*/ "FrameOut", - /*0x19*/ "AddPlane", - /*0x1a*/ "DeletePlane", - /*0x1b*/ "UpdatePlane", - /*0x1c*/ "RepaintPlane", - /*0x1d*/ "SetShowStyle", - /*0x1e*/ "ShowStylePercent", - /*0x1f*/ "SetScroll", - /*0x20*/ "AddMagnify", - /*0x21*/ "DeleteMagnify", - /*0x22*/ "IsHiRes", - /*0x23*/ "Graph", - /*0x24*/ "InvertRect", - /*0x25*/ "TextSize", - /*0x26*/ "Message", - /*0x27*/ "TextColors", - /*0x28*/ "TextFonts", - /*0x29*/ "Dummy", - /*0x2a*/ "SetQuitStr", - /*0x2b*/ "EditText", - /*0x2c*/ "InputText", - /*0x2d*/ "CreateTextBitmap", - /*0x2e*/ "DisposeTextBitmap", - /*0x2f*/ "GetEvent", - /*0x30*/ "GlobalToLocal", - /*0x31*/ "LocalToGlobal", - /*0x32*/ "MapKeyToDir", - /*0x33*/ "HaveMouse", - /*0x34*/ "SetCursor", - /*0x35*/ "VibrateMouse", - /*0x36*/ "SaveGame", - /*0x37*/ "RestoreGame", - /*0x38*/ "RestartGame", - /*0x39*/ "GameIsRestarting", - /*0x3a*/ "MakeSaveCatName", - /*0x3b*/ "MakeSaveFileName", - /*0x3c*/ "GetSaveFiles", - /*0x3d*/ "GetSaveDir", - /*0x3e*/ "CheckSaveGame", - /*0x3f*/ "CheckFreeSpace", - /*0x40*/ "DoSound", - /*0x41*/ "DoAudio", - /*0x42*/ "DoSync", - /*0x43*/ "NewList", - /*0x44*/ "DisposeList", - /*0x45*/ "NewNode", - /*0x46*/ "FirstNode", - /*0x47*/ "LastNode", - /*0x48*/ "EmptyList", - /*0x49*/ "NextNode", - /*0x4a*/ "PrevNode", - /*0x4b*/ "NodeValue", - /*0x4c*/ "AddAfter", - /*0x4d*/ "AddToFront", - /*0x4e*/ "AddToEnd", - /*0x4f*/ "Dummy", - /*0x50*/ "Dummy", - /*0x51*/ "FindKey", - /*0x52*/ "Dummy", - /*0x53*/ "Dummy", - /*0x54*/ "Dummy", - /*0x55*/ "DeleteKey", - /*0x56*/ "Dummy", - /*0x57*/ "Dummy", - /*0x58*/ "ListAt", - /*0x59*/ "ListIndexOf", - /*0x5a*/ "ListEachElementDo", - /*0x5b*/ "ListFirstTrue", - /*0x5c*/ "ListAllTrue", - /*0x5d*/ "Random", - /*0x5e*/ "Abs", - /*0x5f*/ "Sqrt", - /*0x60*/ "GetAngle", - /*0x61*/ "GetDistance", - /*0x62*/ "ATan", - /*0x63*/ "SinMult", - /*0x64*/ "CosMult", - /*0x65*/ "SinDiv", - /*0x66*/ "CosDiv", - /*0x67*/ "GetTime", - /*0x68*/ "Platform", - /*0x69*/ "BaseSetter", - /*0x6a*/ "DirLoop", - /*0x6b*/ "CantBeHere", - /*0x6c*/ "InitBresen", - /*0x6d*/ "DoBresen", - /*0x6e*/ "SetJump", - /*0x6f*/ "AvoidPath", - /*0x70*/ "InPolygon", - /*0x71*/ "MergePoly", - /*0x72*/ "SetDebug", - /*0x73*/ "InspectObject", - /*0x74*/ "MemoryInfo", - /*0x75*/ "Profiler", - /*0x76*/ "Record", - /*0x77*/ "PlayBack", - /*0x78*/ "MonoOut", - /*0x79*/ "SetFatalStr", - /*0x7a*/ "GetCWD", - /*0x7b*/ "ValidPath", - /*0x7c*/ "FileIO", - /*0x7d*/ "Dummy", - /*0x7e*/ "DeviceInfo", - /*0x7f*/ "Palette", - /*0x80*/ "PalVary", - /*0x81*/ "PalCycle", - /*0x82*/ "Array", - /*0x83*/ "String", - /*0x84*/ "RemapColors", - /*0x85*/ "IntegrityChecking", - /*0x86*/ "CheckIntegrity", - /*0x87*/ "ObjectIntersect", - /*0x88*/ "MarkMemory", - /*0x89*/ "TextWidth", - /*0x8a*/ "PointSize", - - // GK2 Demo only kernel functions - /*0x8b*/ "AddLine", - /*0x8c*/ "DeleteLine", - /*0x8d*/ "UpdateLine", - /*0x8e*/ "AddPolygon", - /*0x8f*/ "DeletePolygon", - /*0x90*/ "UpdatePolygon", - /*0x91*/ "Bitmap", - /*0x92*/ "ScrollWindow", - /*0x93*/ "SetFontRes", - /*0x94*/ "MovePlaneItems", - /*0x95*/ "PreloadResource", - /*0x96*/ "Dummy", - /*0x97*/ "ResourceTrack", - /*0x98*/ "CheckCDisc", - /*0x99*/ "GetSaveCDisc", - /*0x9a*/ "TestPoly", - /*0x9b*/ "WinHelp", - /*0x9c*/ "LoadChunk", - /*0x9d*/ "SetPalStyleRange", - /*0x9e*/ "AddPicAt", - /*0x9f*/ "MessageBox" -}; - -static const char *sci21_default_knames[] = { - /*0x00*/ "Load", - /*0x01*/ "UnLoad", - /*0x02*/ "ScriptID", - /*0x03*/ "DisposeScript", - /*0x04*/ "Lock", - /*0x05*/ "ResCheck", - /*0x06*/ "Purge", - /*0x07*/ "SetLanguage", - /*0x08*/ "Dummy", - /*0x09*/ "Dummy", - /*0x0a*/ "Clone", - /*0x0b*/ "DisposeClone", - /*0x0c*/ "RespondsTo", - /*0x0d*/ "FindSelector", - /*0x0e*/ "FindClass", - /*0x0f*/ "Dummy", - /*0x10*/ "Dummy", - /*0x11*/ "Dummy", - /*0x12*/ "Dummy", - /*0x13*/ "Dummy", - /*0x14*/ "SetNowSeen", - /*0x15*/ "NumLoops", - /*0x16*/ "NumCels", - /*0x17*/ "IsOnMe", - /*0x18*/ "AddMagnify", - /*0x19*/ "DeleteMagnify", - /*0x1a*/ "CelRect", - /*0x1b*/ "BaseLineSpan", - /*0x1c*/ "CelWide", - /*0x1d*/ "CelHigh", - /*0x1e*/ "AddScreenItem", - /*0x1f*/ "DeleteScreenItem", - /*0x20*/ "UpdateScreenItem", - /*0x21*/ "FrameOut", - /*0x22*/ "CelInfo", - /*0x23*/ "Bitmap", - /*0x24*/ "CelLink", - /*0x25*/ "Dummy", - /*0x26*/ "Dummy", - /*0x27*/ "Dummy", - /*0x28*/ "AddPlane", - /*0x29*/ "DeletePlane", - /*0x2a*/ "UpdatePlane", - /*0x2b*/ "RepaintPlane", - /*0x2c*/ "GetHighPlanePri", - /*0x2d*/ "GetHighItemPri", - /*0x2e*/ "SetShowStyle", - /*0x2f*/ "ShowStylePercent", - /*0x30*/ "SetScroll", - /*0x31*/ "MovePlaneItems", - /*0x32*/ "ShakeScreen", - /*0x33*/ "Dummy", - /*0x34*/ "Dummy", - /*0x35*/ "Dummy", - /*0x36*/ "Dummy", - /*0x37*/ "IsHiRes", - /*0x38*/ "SetVideoMode", - /*0x39*/ "ShowMovie", - /*0x3a*/ "Robot", - /*0x3b*/ "CreateTextBitmap", - /*0x3c*/ "Random", - /*0x3d*/ "Abs", - /*0x3e*/ "Sqrt", - /*0x3f*/ "GetAngle", - /*0x40*/ "GetDistance", - /*0x41*/ "ATan", - /*0x42*/ "SinMult", - /*0x43*/ "CosMult", - /*0x44*/ "SinDiv", - /*0x45*/ "CosDiv", - /*0x46*/ "Text", - /*0x47*/ "Dummy", - /*0x48*/ "Message", - /*0x49*/ "Font", - /*0x4a*/ "EditText", - /*0x4b*/ "InputText", - /*0x4c*/ "ScrollWindow", - /*0x4d*/ "Dummy", - /*0x4e*/ "Dummy", - /*0x4f*/ "Dummy", - /*0x50*/ "GetEvent", - /*0x51*/ "GlobalToLocal", - /*0x52*/ "LocalToGlobal", - /*0x53*/ "MapKeyToDir", - /*0x54*/ "HaveMouse", - /*0x55*/ "SetCursor", - /*0x56*/ "VibrateMouse", // NOTE: Not in SCI3, instead replaced by Dummy. - /*0x57*/ "Dummy", - /*0x58*/ "Dummy", - /*0x59*/ "Dummy", - /*0x5a*/ "List", - /*0x5b*/ "Array", - /*0x5c*/ "String", - /*0x5d*/ "FileIO", - /*0x5e*/ "BaseSetter", - /*0x5f*/ "DirLoop", - /*0x60*/ "CantBeHere", - /*0x61*/ "InitBresen", - /*0x62*/ "DoBresen", - /*0x63*/ "SetJump", - /*0x64*/ "AvoidPath", - /*0x65*/ "InPolygon", - /*0x66*/ "MergePoly", - /*0x67*/ "ObjectIntersect", - /*0x68*/ "Dummy", - /*0x69*/ "MemoryInfo", - /*0x6a*/ "DeviceInfo", - /*0x6b*/ "Palette", - /*0x6c*/ "PalVary", - /*0x6d*/ "PalCycle", - /*0x6e*/ "RemapColors", - /*0x6f*/ "AddLine", - /*0x70*/ "DeleteLine", - /*0x71*/ "UpdateLine", - /*0x72*/ "AddPolygon", - /*0x73*/ "DeletePolygon", - /*0x74*/ "UpdatePolygon", - /*0x75*/ "DoSound", - /*0x76*/ "DoAudio", - /*0x77*/ "DoSync", - /*0x78*/ "Save", - /*0x79*/ "GetTime", - /*0x7a*/ "Platform", - /*0x7b*/ "CD", - /*0x7c*/ "SetQuitStr", - /*0x7d*/ "GetConfig", - /*0x7e*/ "Table", - /*0x7f*/ "WinHelp", // Windows only - /*0x80*/ "Dummy", - /*0x81*/ "Dummy", - /*0x82*/ "Dummy", - /*0x83*/ "PrintDebug", // used by the Shivers 2 demo - /*0x84*/ "Dummy", - /*0x85*/ "Dummy", - /*0x86*/ "Dummy", - /*0x87*/ "Dummy", - /*0x88*/ "Dummy", - /*0x89*/ "Dummy", - /*0x8a*/ "LoadChunk", - /*0x8b*/ "SetPalStyleRange", - /*0x8c*/ "AddPicAt", - /*0x8d*/ "Dummy", - /*0x8e*/ "NewRoom", - /*0x8f*/ "Dummy", - /*0x90*/ "Priority", - /*0x91*/ "MorphOn", - /*0x92*/ "PlayVMD", - /*0x93*/ "SetHotRectangles", - /*0x94*/ "MulDiv", - /*0x95*/ "GetSierraProfileInt", // Windows only - /*0x96*/ "GetSierraProfileString", // Windows only - /*0x97*/ "SetWindowsOption", // Windows only - /*0x98*/ "GetWindowsOption", // Windows only - /*0x99*/ "WinDLL", // Windows only - - // SCI3 - /*0x9a*/ "Dummy", - /*0x9b*/ "Dummy", - /*0x9c*/ "DeletePic" -}; - -enum { - kKernelEntriesSci2 = 0x8b, - kKernelEntriesGk2Demo = 0xa0, - kKernelEntriesSci21 = 0x9a, - kKernelEntriesSci3 = 0x9d -}; - -void Kernel::setKernelNamesSci2() { - _kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesSci2); -} - -void Kernel::setKernelNamesSci21(GameFeatures *features) { - // Some SCI games use a modified SCI2 kernel table instead of the - // SCI2.1/SCI3 kernel table. The GK2 demo does this as well as at - // least one version of KQ7. We detect which version to use based on - // where kDoSound is called from Sound::play(). - - // 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. - - if (features->detectSci21KernelType() == SCI_VERSION_2) { - _kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesGk2Demo); - // OnMe is IsOnMe here, but they should be compatible - _kernelNames[0x23] = "Robot"; // Graph in SCI2 - _kernelNames[0x2e] = "Priority"; // DisposeTextBitmap in SCI2 - } else { - // TODO: Differentiate between SCI2.1/3 - _kernelNames = Common::StringArray(sci21_default_knames, kKernelEntriesSci3); - } -} - -// SCI2 Kernel Functions - -reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) { - // Returns 0 if the screen width or height is less than 640 or 400, - // respectively. - if (g_system->getWidth() < 640 || g_system->getHeight() < 400) - return make_reg(0, 0); - - return make_reg(0, 1); -} - -reg_t kArray(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 0: { // New - reg_t arrayHandle; - SciArray<reg_t> *array = s->_segMan->allocateArray(&arrayHandle); - array->setType(argv[2].toUint16()); - array->setSize(argv[1].toUint16()); - return arrayHandle; - } - case 1: { // Size - SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); - return make_reg(0, array->getSize()); - } - case 2: { // At (return value at an index) - SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); - return array->getValue(argv[2].toUint16()); - } - case 3: { // Atput (put value at an index) - SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); - - uint32 index = argv[2].toUint16(); - uint32 count = argc - 3; - - if (index + count > 65535) - break; - - if (array->getSize() < index + count) - array->setSize(index + count); - - for (uint16 i = 0; i < count; i++) - array->setValue(i + index, argv[i + 3]); - - return argv[1]; // We also have to return the handle - } - case 4: // Free - // Freeing of arrays is handled by the garbage collector - return s->r_acc; - case 5: { // Fill - SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); - uint16 index = argv[2].toUint16(); - - // A count of -1 means fill the rest of the array - uint16 count = argv[3].toSint16() == -1 ? array->getSize() - index : argv[3].toUint16(); - uint16 arraySize = array->getSize(); - - if (arraySize < index + count) - array->setSize(index + count); - - for (uint16 i = 0; i < count; i++) - array->setValue(i + index, argv[4]); - - return argv[1]; - } - case 6: { // Cpy - if (s->_segMan->getSegmentObj(argv[1].segment)->getType() != SEG_TYPE_ARRAY || - s->_segMan->getSegmentObj(argv[3].segment)->getType() != SEG_TYPE_ARRAY) { - // Happens in the RAMA demo - warning("kArray(Cpy): Request to copy a segment which isn't an array, ignoring"); - return NULL_REG; - } - - SciArray<reg_t> *array1 = s->_segMan->lookupArray(argv[1]); - SciArray<reg_t> *array2 = s->_segMan->lookupArray(argv[3]); - uint32 index1 = argv[2].toUint16(); - uint32 index2 = argv[4].toUint16(); - - // The original engine ignores bad copies too - if (index2 > array2->getSize()) - break; - - // A count of -1 means fill the rest of the array - uint32 count = argv[5].toSint16() == -1 ? array2->getSize() - index2 : argv[5].toUint16(); - - if (array1->getSize() < index1 + count) - array1->setSize(index1 + count); - - for (uint16 i = 0; i < count; i++) - array1->setValue(i + index1, array2->getValue(i + index2)); - - return argv[1]; - } - case 7: // Cmp - // Not implemented in SSCI - return s->r_acc; - case 8: { // Dup - SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); - reg_t arrayHandle; - SciArray<reg_t> *dupArray = s->_segMan->allocateArray(&arrayHandle); - - dupArray->setType(array->getType()); - dupArray->setSize(array->getSize()); - - for (uint32 i = 0; i < array->getSize(); i++) - dupArray->setValue(i, array->getValue(i)); - - return arrayHandle; - } - case 9: // Getdata - if (!s->_segMan->isHeapObject(argv[1])) - return argv[1]; - - return readSelector(s->_segMan, argv[1], SELECTOR(data)); - default: - error("Unknown kArray subop %d", argv[0].toUint16()); - } - - return NULL_REG; -} - -reg_t kText(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 0: - return kTextSize(s, argc - 1, argv + 1); - default: - // TODO: Other subops here too, perhaps kTextColors and kTextFonts - warning("kText(%d)", argv[0].toUint16()); - break; - } - - return s->r_acc; -} - -reg_t kString(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 0: { // New - reg_t stringHandle; - SciString *string = s->_segMan->allocateString(&stringHandle); - string->setSize(argv[1].toUint16()); - - // Make sure the first character is a null character - if (string->getSize() > 0) - string->setValue(0, 0); - - return stringHandle; - } - case 1: // Size - return make_reg(0, s->_segMan->getString(argv[1]).size()); - case 2: { // At (return value at an index) - if (argv[1].segment == s->_segMan->getStringSegmentId()) - return make_reg(0, s->_segMan->lookupString(argv[1])->getRawData()[argv[2].toUint16()]); - - return make_reg(0, s->_segMan->getString(argv[1])[argv[2].toUint16()]); - } - case 3: { // Atput (put value at an index) - SciString *string = s->_segMan->lookupString(argv[1]); - - uint32 index = argv[2].toUint16(); - uint32 count = argc - 3; - - if (index + count > 65535) - break; - - if (string->getSize() < index + count) - string->setSize(index + count); - - for (uint16 i = 0; i < count; i++) - string->setValue(i + index, argv[i + 3].toUint16()); - - return argv[1]; // We also have to return the handle - } - case 4: // Free - // Freeing of strings is handled by the garbage collector - return s->r_acc; - case 5: { // Fill - SciString *string = s->_segMan->lookupString(argv[1]); - uint16 index = argv[2].toUint16(); - - // A count of -1 means fill the rest of the array - uint16 count = argv[3].toSint16() == -1 ? string->getSize() - index : argv[3].toUint16(); - uint16 stringSize = string->getSize(); - - if (stringSize < index + count) - string->setSize(index + count); - - for (uint16 i = 0; i < count; i++) - string->setValue(i + index, argv[4].toUint16()); - - return argv[1]; - } - case 6: { // Cpy - const char *string2 = 0; - uint32 string2Size = 0; - - if (argv[3].segment == s->_segMan->getStringSegmentId()) { - SciString *string = s->_segMan->lookupString(argv[3]); - string2 = string->getRawData(); - string2Size = string->getSize(); - } else { - Common::String string = s->_segMan->getString(argv[3]); - string2 = string.c_str(); - string2Size = string.size() + 1; - } - - uint32 index1 = argv[2].toUint16(); - uint32 index2 = argv[4].toUint16(); - - // The original engine ignores bad copies too - if (index2 > string2Size) - break; - - // A count of -1 means fill the rest of the array - uint32 count = argv[5].toSint16() == -1 ? string2Size - index2 + 1 : argv[5].toUint16(); - - // We have a special case here for argv[1] being a system string - if (argv[1].segment == s->_segMan->getSysStringsSegment()) { - // Resize if necessary - const uint16 sysStringId = argv[1].toUint16(); - SystemString *sysString = s->_segMan->getSystemString(sysStringId); - assert(sysString); - if ((uint32)sysString->_maxSize < index1 + count) { - free(sysString->_value); - sysString->_maxSize = index1 + count; - sysString->_value = (char *)calloc(index1 + count, sizeof(char)); - } - - strncpy(sysString->_value + index1, string2 + index2, count); - } else { - SciString *string1 = s->_segMan->lookupString(argv[1]); - - if (string1->getSize() < index1 + count) - string1->setSize(index1 + count); - - // Note: We're accessing from c_str() here because the - // string's size ignores the trailing 0 and therefore - // triggers an assert when doing string2[i + index2]. - for (uint16 i = 0; i < count; i++) - string1->setValue(i + index1, string2[i + index2]); - } - - } return argv[1]; - case 7: { // Cmp - Common::String string1 = argv[1].isNull() ? "" : s->_segMan->getString(argv[1]); - Common::String string2 = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]); - - if (argc == 4) // Strncmp - return make_reg(0, strncmp(string1.c_str(), string2.c_str(), argv[3].toUint16())); - else // Strcmp - return make_reg(0, strcmp(string1.c_str(), string2.c_str())); - } - case 8: { // Dup - const char *rawString = 0; - uint32 size = 0; - - if (argv[1].segment == s->_segMan->getStringSegmentId()) { - SciString *string = s->_segMan->lookupString(argv[1]); - rawString = string->getRawData(); - size = string->getSize(); - } else { - Common::String string = s->_segMan->getString(argv[1]); - rawString = string.c_str(); - size = string.size() + 1; - } - - reg_t stringHandle; - SciString *dupString = s->_segMan->allocateString(&stringHandle); - dupString->setSize(size); - - for (uint32 i = 0; i < size; i++) - dupString->setValue(i, rawString[i]); - - return stringHandle; - } - case 9: // Getdata - if (!s->_segMan->isHeapObject(argv[1])) - return argv[1]; - - return readSelector(s->_segMan, argv[1], SELECTOR(data)); - case 10: // Stringlen - return make_reg(0, s->_segMan->strlen(argv[1])); - case 11: { // Printf - reg_t stringHandle; - s->_segMan->allocateString(&stringHandle); - - reg_t *adjustedArgs = new reg_t[argc]; - adjustedArgs[0] = stringHandle; - memcpy(&adjustedArgs[1], argv + 1, (argc - 1) * sizeof(reg_t)); - - kFormat(s, argc, adjustedArgs); - delete[] adjustedArgs; - return stringHandle; - } - case 12: // Printf Buf - return kFormat(s, argc - 1, argv + 1); - case 13: { // atoi - Common::String string = s->_segMan->getString(argv[1]); - return make_reg(0, (uint16)atoi(string.c_str())); - } - default: - error("Unknown kString subop %d", argv[0].toUint16()); - } - - return NULL_REG; -} - -reg_t kSave(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 2: // GetSaveDir - // Yay! Reusing the old kernel function! - return kGetSaveDir(s, argc - 1, argv + 1); - case 8: - // TODO - // This function has to return something other than 0 to proceed - return s->r_acc; - default: - warning("Unknown/unhandled kSave subop %d", argv[0].toUint16()); - } - - return NULL_REG; -} - -reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) { - reg_t viewObj = argv[0]; - - g_sci->_gfxFrameout->kernelAddScreenItem(viewObj); - return NULL_REG; -} - -reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) { - //reg_t viewObj = argv[0]; - - //warning("kUpdateScreenItem, object %04x:%04x, view %d, loop %d, cel %d, pri %d", PRINT_REG(viewObj), viewId, loopNo, celNo, priority); - return NULL_REG; -} - -reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) { - reg_t viewObj = argv[0]; - - g_sci->_gfxFrameout->kernelDeleteScreenItem(viewObj); - - /* - reg_t viewObj = argv[0]; - uint16 viewId = readSelectorValue(s->_segMan, viewObj, SELECTOR(view)); - int16 loopNo = readSelectorValue(s->_segMan, viewObj, SELECTOR(loop)); - int16 celNo = readSelectorValue(s->_segMan, viewObj, SELECTOR(cel)); - //int16 leftPos = 0; - //int16 topPos = 0; - int16 priority = readSelectorValue(s->_segMan, viewObj, SELECTOR(priority)); - //int16 control = 0; - */ - - // TODO - - //warning("kDeleteScreenItem, view %d, loop %d, cel %d, pri %d", viewId, loopNo, celNo, priority); - return NULL_REG; -} - -reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) { - reg_t planeObj = argv[0]; - - g_sci->_gfxFrameout->kernelAddPlane(planeObj); - warning("kAddPlane object %04x:%04x", PRINT_REG(planeObj)); - return NULL_REG; -} - -reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv) { - reg_t planeObj = argv[0]; - - g_sci->_gfxFrameout->kernelDeletePlane(planeObj); - warning("kDeletePlane object %04x:%04x", PRINT_REG(planeObj)); - return NULL_REG; -} - -reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) { - reg_t planeObj = argv[0]; - - g_sci->_gfxFrameout->kernelUpdatePlane(planeObj); - return s->r_acc; -} - -reg_t kRepaintPlane(EngineState *s, int argc, reg_t *argv) { - reg_t picObj = argv[0]; - - // TODO - - warning("kRepaintPlane object %04x:%04x", PRINT_REG(picObj)); - return NULL_REG; -} - -reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) { - warning("kGetHighPlanePri: %d", g_sci->_gfxFrameout->kernelGetHighPlanePri()); - return make_reg(0, g_sci->_gfxFrameout->kernelGetHighPlanePri()); -} - -reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) { - // This kernel call likely seems to be doing the screen updates, - // as its called right after a view is updated - - // TODO - g_sci->_gfxFrameout->kernelFrameout(); - - return NULL_REG; -} - -reg_t kOnMe(EngineState *s, int argc, reg_t *argv) { - // Tests if the cursor is on the passed object - - uint16 x = argv[0].toUint16(); - uint16 y = argv[1].toUint16(); - reg_t targetObject = argv[2]; - // TODO: argv[3] - it's usually 0 - Common::Rect nsRect; - - // Get the bounding rectangle of the object - nsRect.left = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsLeft)); - nsRect.top = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsTop)); - nsRect.right = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsRight)); - nsRect.bottom = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsBottom)); - uint16 itemX = readSelectorValue(s->_segMan, targetObject, SELECTOR(x)); - uint16 itemY = readSelectorValue(s->_segMan, targetObject, SELECTOR(y)); - - // If top and left are negative, we need to adjust coordinates by - // the item's x and y (e.g. happens in GK1, day 1, with detective - // Mosely's hotspot in his office) - - if (nsRect.left < 0) - nsRect.translate(itemX, 0); - - if (nsRect.top < 0) - nsRect.translate(0, itemY); - - // HACK: nsLeft and nsTop can be invalid, so try and fix them here - // using x and y (e.g. with the inventory screen in GK1) - if (nsRect.left == itemY && nsRect.top == itemX) { - // Swap the values, as they're inversed (eh???) - nsRect.left = itemX; - nsRect.top = itemY; - } - - /* - warning("kOnMe: (%d, %d) on object %04x:%04x (%s), rect (%d, %d, %d, %d), parameter %d", - argv[0].toUint16(), argv[1].toUint16(), PRINT_REG(argv[2]), s->_segMan->getObjectName(argv[2]), - nsRect.left, nsRect.top, nsRect.right, nsRect.bottom, - argv[3].toUint16()); - */ - - return make_reg(0, nsRect.contains(x, y)); -} - -reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv) { - // Tests if the cursor is on the passed object, after adjusting the - // coordinates of the object according to the object's plane - - uint16 x = argv[0].toUint16(); - uint16 y = argv[1].toUint16(); - reg_t targetObject = argv[2]; - // TODO: argv[3] - it's usually 0 - Common::Rect nsRect; - - // Get the bounding rectangle of the object - nsRect.left = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsLeft)); - nsRect.top = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsTop)); - nsRect.right = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsRight)); - nsRect.bottom = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsBottom)); - - // Get the object's plane - reg_t planeObject = readSelector(s->_segMan, targetObject, SELECTOR(plane)); - if (!planeObject.isNull()) { - uint16 itemX = readSelectorValue(s->_segMan, targetObject, SELECTOR(x)); - uint16 itemY = readSelectorValue(s->_segMan, targetObject, SELECTOR(y)); - uint16 planeResY = readSelectorValue(s->_segMan, planeObject, SELECTOR(resY)); - uint16 planeResX = readSelectorValue(s->_segMan, planeObject, SELECTOR(resX)); - uint16 planeTop = readSelectorValue(s->_segMan, planeObject, SELECTOR(top)); - uint16 planeLeft = readSelectorValue(s->_segMan, planeObject, SELECTOR(left)); - planeTop = (planeTop * g_sci->_gfxScreen->getHeight()) / planeResY; - planeLeft = (planeLeft * g_sci->_gfxScreen->getWidth()) / planeResX; - - // Adjust the bounding rectangle of the object by the object's - // actual X, Y coordinates - itemY = ((itemY * g_sci->_gfxScreen->getHeight()) / planeResY); - itemX = ((itemX * g_sci->_gfxScreen->getWidth()) / planeResX); - itemY += planeTop; - itemX += planeLeft; - - nsRect.translate(itemX, itemY); - } - - //warning("kIsOnMe: (%d, %d) on object %04x:%04x, parameter %d", argv[0].toUint16(), argv[1].toUint16(), PRINT_REG(argv[2]), argv[3].toUint16()); - - return make_reg(0, nsRect.contains(x, y)); -} - -reg_t kInPolygon(EngineState *s, int argc, reg_t *argv) { - // kAvoidPath already implements this - return kAvoidPath(s, argc, argv); -} - -reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) { - // TODO: argument 0 is usually 0, and arguments 1 and 2 are usually 1 - switch (argv[0].toUint16()) { - case 0: { - if (argc != 4) { - warning("kCreateTextBitmap(0): expected 4 arguments, got %i", argc); - return NULL_REG; - } - reg_t object = argv[3]; - Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text))); - debug("kCreateTextBitmap: %s", text.c_str()); - } - default: - warning("CreateTextBitmap(%d)", argv[0].toUint16()); - } - - return NULL_REG; -} - -reg_t kCD(EngineState *s, int argc, reg_t *argv) { - // TODO: Stub - switch (argv[0].toUint16()) { - case 0: - // Return whether the contents of disc argv[1] is available. - return TRUE_REG; - default: - warning("CD(%d)", argv[0].toUint16()); - } - - return NULL_REG; -} - -} // End of namespace Sci - -#endif // ENABLE_SCI32 diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h new file mode 100644 index 0000000000..886e918fd8 --- /dev/null +++ b/engines/sci/engine/kernel_tables.h @@ -0,0 +1,1030 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SCI_ENGINE_KERNEL_TABLES_H +#define SCI_ENGINE_KERNEL_TABLES_H + +#include "sci/engine/workarounds.h" + +namespace Sci { + +// [io] -> either integer or object +// (io) -> optionally integer AND an object +// (i) -> optional integer +// . -> any type +// i* -> optional multiple integers +// .* -> any parameters afterwards (or none) + +struct SciKernelMapSubEntry { + SciVersion fromVersion; + SciVersion toVersion; + + uint16 id; + + const char *name; + KernelFunctionCall *function; + + const char *signature; + const SciWorkaroundEntry *workarounds; +}; + +#define SCI_SUBOPENTRY_TERMINATOR { SCI_VERSION_NONE, SCI_VERSION_NONE, 0, NULL, NULL, NULL, NULL } + + +#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE +#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01 +#define SIG_SCI1 SCI_VERSION_1_EGA, SCI_VERSION_1_LATE +#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1 +#define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE +#define SIG_SCI21 SCI_VERSION_2_1, SCI_VERSION_2_1 + +#define SIG_SCI16 SCI_VERSION_NONE, SCI_VERSION_1_1 +#define SIG_SCI32 SCI_VERSION_2, SCI_VERSION_NONE + +// SCI-Sound-Version +#define SIG_SOUNDSCI0 SCI_VERSION_0_EARLY, SCI_VERSION_0_LATE +#define SIG_SOUNDSCI1EARLY SCI_VERSION_1_EARLY, SCI_VERSION_1_EARLY +#define SIG_SOUNDSCI1LATE SCI_VERSION_1_LATE, SCI_VERSION_1_LATE +#define SIG_SOUNDSCI21 SCI_VERSION_2_1, SCI_VERSION_2_1 + +#define SIGFOR_ALL 0x3f +#define SIGFOR_DOS 1 << 0 +#define SIGFOR_PC98 1 << 1 +#define SIGFOR_WIN 1 << 2 +#define SIGFOR_MAC 1 << 3 +#define SIGFOR_AMIGA 1 << 4 +#define SIGFOR_ATARI 1 << 5 +#define SIGFOR_PC SIGFOR_DOS|SIGFOR_WIN + +#define SIG_EVERYWHERE SIG_SCIALL, SIGFOR_ALL + +#define MAP_CALL(_name_) #_name_, k##_name_ + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kDoSound_subops[] = { + { SIG_SOUNDSCI0, 0, MAP_CALL(DoSoundInit), "o", NULL }, + { SIG_SOUNDSCI0, 1, MAP_CALL(DoSoundPlay), "o", NULL }, + { SIG_SOUNDSCI0, 2, MAP_CALL(DoSoundDummy), "(o)", NULL }, + { SIG_SOUNDSCI0, 3, MAP_CALL(DoSoundDispose), "o", NULL }, + { SIG_SOUNDSCI0, 4, MAP_CALL(DoSoundMute), "(i)", NULL }, + { SIG_SOUNDSCI0, 5, MAP_CALL(DoSoundStop), "o", NULL }, + { SIG_SOUNDSCI0, 6, MAP_CALL(DoSoundPause), "i", NULL }, + { SIG_SOUNDSCI0, 7, MAP_CALL(DoSoundResumeAfterRestore), "", NULL }, + { SIG_SOUNDSCI0, 8, MAP_CALL(DoSoundMasterVolume), "(i)", NULL }, + { SIG_SOUNDSCI0, 9, MAP_CALL(DoSoundUpdate), "o", NULL }, + { SIG_SOUNDSCI0, 10, MAP_CALL(DoSoundFade), "o", kDoSoundFade_workarounds }, + { SIG_SOUNDSCI0, 11, MAP_CALL(DoSoundGetPolyphony), "", NULL }, + { SIG_SOUNDSCI0, 12, MAP_CALL(DoSoundStopAll), "", NULL }, + { SIG_SOUNDSCI1EARLY, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 1, MAP_CALL(DoSoundMute), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 2, MAP_CALL(DoSoundDummy), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 4, MAP_CALL(DoSoundUpdate), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 5, MAP_CALL(DoSoundInit), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 6, MAP_CALL(DoSoundDispose), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 7, MAP_CALL(DoSoundPlay), "oi", NULL }, + // ^^ TODO: In SCI1-SCI1.1 DoSound (play) is called by 2 methods of the Sound object: play and + // playBed. The methods are the same, apart from the second integer parameter: it's 0 in + // play and 1 in playBed, to distinguish the caller. It's passed on, we should find out what + // it actually does internally + { SIG_SOUNDSCI1EARLY, 8, MAP_CALL(DoSoundStop), NULL, NULL }, + { SIG_SOUNDSCI1EARLY, 9, MAP_CALL(DoSoundPause), "[o0]i", NULL }, + { SIG_SOUNDSCI1EARLY, 10, MAP_CALL(DoSoundFade), "oiiii", NULL }, + { SIG_SOUNDSCI1EARLY, 11, MAP_CALL(DoSoundUpdateCues), "o", NULL }, + { SIG_SOUNDSCI1EARLY, 12, MAP_CALL(DoSoundSendMidi), "oiii", NULL }, + { SIG_SOUNDSCI1EARLY, 13, MAP_CALL(DoSoundReverb), "i", NULL }, + { SIG_SOUNDSCI1EARLY, 14, MAP_CALL(DoSoundSetHold), "oi", NULL }, + { SIG_SOUNDSCI1EARLY, 15, MAP_CALL(DoSoundDummy), "", NULL }, + // ^^ Longbow demo + { SIG_SOUNDSCI1LATE, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 1, MAP_CALL(DoSoundMute), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 2, MAP_CALL(DoSoundDummy), "", NULL }, + { SIG_SOUNDSCI1LATE, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 4, MAP_CALL(DoSoundGetAudioCapability), "", NULL }, + { SIG_SOUNDSCI1LATE, 5, MAP_CALL(DoSoundSuspend), "i", NULL }, + { SIG_SOUNDSCI1LATE, 6, MAP_CALL(DoSoundInit), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 7, MAP_CALL(DoSoundDispose), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 8, MAP_CALL(DoSoundPlay), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 9, MAP_CALL(DoSoundStop), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 10, MAP_CALL(DoSoundPause), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 11, MAP_CALL(DoSoundFade), "oiiii(i)", kDoSoundFade_workarounds }, + { SIG_SOUNDSCI1LATE, 12, MAP_CALL(DoSoundSetHold), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 13, MAP_CALL(DoSoundDummy), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 14, MAP_CALL(DoSoundSetVolume), "oi", NULL }, + { SIG_SOUNDSCI1LATE, 15, MAP_CALL(DoSoundSetPriority), "oi", NULL }, + { SIG_SOUNDSCI1LATE, 16, MAP_CALL(DoSoundSetLoop), "oi", NULL }, + { SIG_SOUNDSCI1LATE, 17, MAP_CALL(DoSoundUpdateCues), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 18, MAP_CALL(DoSoundSendMidi), "oiii(i)", NULL }, + { SIG_SOUNDSCI1LATE, 19, MAP_CALL(DoSoundReverb), NULL, NULL }, + { SIG_SOUNDSCI1LATE, 20, MAP_CALL(DoSoundUpdate), NULL, NULL }, +#ifdef ENABLE_SCI32 + { SIG_SOUNDSCI21, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL }, + { SIG_SOUNDSCI21, 1, MAP_CALL(DoSoundMute), NULL, NULL }, + { SIG_SOUNDSCI21, 2, MAP_CALL(DoSoundDummy), NULL, NULL }, + { SIG_SOUNDSCI21, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL }, + { SIG_SOUNDSCI21, 4, MAP_CALL(DoSoundGetAudioCapability), NULL, NULL }, + { SIG_SOUNDSCI21, 5, MAP_CALL(DoSoundSuspend), NULL, NULL }, + { SIG_SOUNDSCI21, 6, MAP_CALL(DoSoundInit), NULL, NULL }, + { SIG_SOUNDSCI21, 7, MAP_CALL(DoSoundDispose), NULL, NULL }, + { SIG_SOUNDSCI21, 8, MAP_CALL(DoSoundPlay), "o(i)", NULL }, + // ^^ TODO: if this is really the only change between SCI1LATE AND SCI21, we could rename the + // SIG_SOUNDSCI1LATE #define to SIG_SINCE_SOUNDSCI1LATE and make it being SCI1LATE+. Although + // I guess there are many more changes somewhere + // TODO: Quest for Glory 4 (SCI2.1) uses the old scheme, we need to detect it accordingly + // signature for SCI21 should be "o" + { SIG_SOUNDSCI21, 9, MAP_CALL(DoSoundStop), NULL, NULL }, + { SIG_SOUNDSCI21, 10, MAP_CALL(DoSoundPause), NULL, NULL }, + { SIG_SOUNDSCI21, 11, MAP_CALL(DoSoundFade), NULL, NULL }, + { SIG_SOUNDSCI21, 12, MAP_CALL(DoSoundSetHold), NULL, NULL }, + { SIG_SOUNDSCI21, 13, MAP_CALL(DoSoundDummy), NULL, NULL }, + { SIG_SOUNDSCI21, 14, MAP_CALL(DoSoundSetVolume), NULL, NULL }, + { SIG_SOUNDSCI21, 15, MAP_CALL(DoSoundSetPriority), NULL, NULL }, + { SIG_SOUNDSCI21, 16, MAP_CALL(DoSoundSetLoop), NULL, NULL }, + { SIG_SOUNDSCI21, 17, MAP_CALL(DoSoundUpdateCues), NULL, NULL }, + { SIG_SOUNDSCI21, 18, MAP_CALL(DoSoundSendMidi), NULL, NULL }, + { SIG_SOUNDSCI21, 19, MAP_CALL(DoSoundReverb), NULL, NULL }, + { SIG_SOUNDSCI21, 20, MAP_CALL(DoSoundUpdate), NULL, NULL }, +#endif + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kGraph_subops[] = { + { SIG_SCI32, 1, MAP_CALL(StubNull), "", NULL }, // called by gk1 sci32 right at the start + { SIG_SCIALL, 2, MAP_CALL(GraphGetColorCount), "", NULL }, + // 3 - set palette via resource + { SIG_SCIALL, 4, MAP_CALL(GraphDrawLine), "iiiii(i)(i)", kGraphDrawLine_workarounds }, + // 5 - nop + // 6 - draw pattern + { SIG_SCIALL, 7, MAP_CALL(GraphSaveBox), "iiiii", kGraphSaveBox_workarounds }, + { SIG_SCIALL, 8, MAP_CALL(GraphRestoreBox), "[r0!]", kGraphRestoreBox_workarounds }, + // ^ this may get called with invalid references, we check them within restoreBits() and sierra sci behaves the same + { SIG_SCIALL, 9, MAP_CALL(GraphFillBoxBackground), "iiii", NULL }, + { SIG_SCIALL, 10, MAP_CALL(GraphFillBoxForeground), "iiii", kGraphFillBoxForeground_workarounds }, + { SIG_SCIALL, 11, MAP_CALL(GraphFillBoxAny), "iiiiii(i)(i)", kGraphFillBoxAny_workarounds }, + { SIG_SCI11, 12, MAP_CALL(GraphUpdateBox), "iiii(i)(r0)", NULL }, // kq6 hires + { SIG_SCIALL, 12, MAP_CALL(GraphUpdateBox), "iiii(i)", NULL }, + { SIG_SCIALL, 13, MAP_CALL(GraphRedrawBox), "iiii", kGraphRedrawBox_workarounds }, + { SIG_SCIALL, 14, MAP_CALL(GraphAdjustPriority), "ii", NULL }, + { SIG_SCI11, 15, MAP_CALL(GraphSaveUpscaledHiresBox), "iiii", NULL }, // kq6 hires + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kPalVary_subops[] = { + { SIG_SCI21, 0, MAP_CALL(PalVaryInit), "ii(i)(i)(i)", NULL }, + { SIG_SCIALL, 0, MAP_CALL(PalVaryInit), "ii(i)(i)", NULL }, + { SIG_SCIALL, 1, MAP_CALL(PalVaryReverse), "(i)(i)(i)", NULL }, + { SIG_SCIALL, 2, MAP_CALL(PalVaryGetCurrentStep), "", NULL }, + { SIG_SCIALL, 3, MAP_CALL(PalVaryDeinit), "", NULL }, + { SIG_SCIALL, 4, MAP_CALL(PalVaryChangeTarget), "i", NULL }, + { SIG_SCIALL, 5, MAP_CALL(PalVaryChangeTicks), "i", NULL }, + { SIG_SCIALL, 6, MAP_CALL(PalVaryPauseResume), "i", NULL }, + { SIG_SCI32, 8, MAP_CALL(PalVaryUnknown), "i", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kPalette_subops[] = { + { SIG_SCIALL, 1, MAP_CALL(PaletteSetFromResource), "i(i)", NULL }, + { SIG_SCIALL, 2, MAP_CALL(PaletteSetFlag), "iii", NULL }, + { SIG_SCIALL, 3, MAP_CALL(PaletteUnsetFlag), "iii", kPaletteUnsetFlag_workarounds }, + { SIG_SCIALL, 4, MAP_CALL(PaletteSetIntensity), "iii(i)", NULL }, + { SIG_SCIALL, 5, MAP_CALL(PaletteFindColor), "iii", NULL }, + { SIG_SCIALL, 6, MAP_CALL(PaletteAnimate), "i*", NULL }, + { SIG_SCIALL, 7, MAP_CALL(PaletteSave), "", NULL }, + { SIG_SCIALL, 8, MAP_CALL(PaletteRestore), "[r0]", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +static const SciKernelMapSubEntry kFileIO_subops[] = { + { SIG_SCI32, 0, MAP_CALL(FileIOOpen), "r(i)", NULL }, + { SIG_SCIALL, 0, MAP_CALL(FileIOOpen), "ri", NULL }, + { SIG_SCIALL, 1, MAP_CALL(FileIOClose), "i", NULL }, + { SIG_SCIALL, 2, MAP_CALL(FileIOReadRaw), "iri", NULL }, + { SIG_SCIALL, 3, MAP_CALL(FileIOWriteRaw), "iri", NULL }, + { SIG_SCIALL, 4, MAP_CALL(FileIOUnlink), "r", NULL }, + { SIG_SCIALL, 5, MAP_CALL(FileIOReadString), "rii", NULL }, + { SIG_SCIALL, 6, MAP_CALL(FileIOWriteString), "ir", NULL }, + { SIG_SCIALL, 7, MAP_CALL(FileIOSeek), "iii", NULL }, + { SIG_SCIALL, 8, MAP_CALL(FileIOFindFirst), "rri", NULL }, + { SIG_SCIALL, 9, MAP_CALL(FileIOFindNext), "r", NULL }, + { SIG_SCIALL, 10, MAP_CALL(FileIOExists), "r", NULL }, + { SIG_SINCE_SCI11, 11, MAP_CALL(FileIORename), "rr", NULL }, +#ifdef ENABLE_SCI32 + { SIG_SCI32, 13, MAP_CALL(FileIOReadByte), "i", NULL }, + { SIG_SCI32, 14, MAP_CALL(FileIOWriteByte), "ii", NULL }, + { SIG_SCI32, 15, MAP_CALL(FileIOReadWord), "i", NULL }, + { SIG_SCI32, 16, MAP_CALL(FileIOWriteWord), "ii", NULL }, + { SIG_SCI32, 19, MAP_CALL(Stub), "r", NULL }, // for Torin / Torin demo +#endif + SCI_SUBOPENTRY_TERMINATOR +}; + +#ifdef ENABLE_SCI32 +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kList_subops[] = { + { SIG_SCI21, 0, MAP_CALL(NewList), "", NULL }, + { SIG_SCI21, 1, MAP_CALL(DisposeList), "l", NULL }, + { SIG_SCI21, 2, MAP_CALL(NewNode), ".", NULL }, + { SIG_SCI21, 3, MAP_CALL(FirstNode), "[l0]", NULL }, + { SIG_SCI21, 4, MAP_CALL(LastNode), "l", NULL }, + { SIG_SCI21, 5, MAP_CALL(EmptyList), "l", NULL }, + { SIG_SCI21, 6, MAP_CALL(NextNode), "n", NULL }, + { SIG_SCI21, 7, MAP_CALL(PrevNode), "n", NULL }, + { SIG_SCI21, 8, MAP_CALL(NodeValue), "[n0]", NULL }, + { SIG_SCI21, 9, MAP_CALL(AddAfter), "lnn.", NULL }, + { SIG_SCI21, 10, MAP_CALL(AddToFront), "ln.", NULL }, + { SIG_SCI21, 11, MAP_CALL(AddToEnd), "ln.", NULL }, + { SIG_SCI21, 12, MAP_CALL(AddBefore), "ln.", NULL }, + { SIG_SCI21, 13, MAP_CALL(MoveToFront), "ln", NULL }, + { SIG_SCI21, 14, MAP_CALL(MoveToEnd), "ln", NULL }, + { SIG_SCI21, 15, MAP_CALL(FindKey), "l.", NULL }, + { SIG_SCI21, 16, MAP_CALL(DeleteKey), "l.", NULL }, + { SIG_SCI21, 17, MAP_CALL(ListAt), "li", NULL }, + // FIXME: This doesn't seem to be ListIndexOf. In Torin demo, an index is + // passed as a second parameter instead of an object. Thus, it seems to + // be something like ListAt instead... If we swap the two subops though, + // Torin demo crashes complaining that it tried to send to a non-object, + // therefore the semantics might be different here (signature was l[o0]) + // In SQ6 object is passed right when skipping the intro + { SIG_SCI21, 18, MAP_CALL(StubNull), "l[io]", NULL }, + { SIG_SCI21, 19, MAP_CALL(ListEachElementDo), "li(.*)", NULL }, + { SIG_SCI21, 20, MAP_CALL(ListFirstTrue), "li(.*)", NULL }, + { SIG_SCI21, 21, MAP_CALL(ListAllTrue), "li(.*)", NULL }, + { SIG_SCI21, 22, MAP_CALL(Sort), "ooo", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; +#endif + +struct SciKernelMapEntry { + const char *name; + KernelFunctionCall *function; + + SciVersion fromVersion; + SciVersion toVersion; + byte forPlatform; + + const char *signature; + const SciKernelMapSubEntry *subFunctions; + const SciWorkaroundEntry *workarounds; +}; + +// name, version/platform, signature, sub-signatures, workarounds +static SciKernelMapEntry s_kernelMap[] = { + { MAP_CALL(Abs), SIG_EVERYWHERE, "i", NULL, kAbs_workarounds }, + { MAP_CALL(AddAfter), SIG_EVERYWHERE, "lnn", NULL, NULL }, + { MAP_CALL(AddMenu), SIG_EVERYWHERE, "rr", NULL, NULL }, + { MAP_CALL(AddToEnd), SIG_EVERYWHERE, "ln", NULL, NULL }, + { MAP_CALL(AddToFront), SIG_EVERYWHERE, "ln", NULL, NULL }, + { MAP_CALL(AddToPic), SIG_EVERYWHERE, "[il](iiiiii)", NULL, NULL }, + { MAP_CALL(Animate), SIG_EVERYWHERE, "(l0)(i)", NULL, NULL }, + { MAP_CALL(AssertPalette), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(AvoidPath), SIG_EVERYWHERE, "ii(.*)", NULL, NULL }, + { MAP_CALL(BaseSetter), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(CanBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "CantBeHere", kCantBeHere32, SIG_SCI32, SIGFOR_ALL, "ol", NULL, NULL }, +#endif + { MAP_CALL(CantBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, + { MAP_CALL(CelHigh), SIG_EVERYWHERE, "ii(i)", NULL, kCelHigh_workarounds }, + { MAP_CALL(CelWide), SIG_EVERYWHERE, "ii(i)", NULL, kCelWide_workarounds }, + { MAP_CALL(CheckFreeSpace), SIG_SCI32, SIGFOR_ALL, "r.*", NULL, NULL }, + { MAP_CALL(CheckFreeSpace), SIG_SCI11, SIGFOR_ALL, "r(i)", NULL, NULL }, + { MAP_CALL(CheckFreeSpace), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(CheckSaveGame), SIG_EVERYWHERE, ".*", NULL, NULL }, + { MAP_CALL(Clone), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(CoordPri), SIG_EVERYWHERE, "i(i)", NULL, NULL }, + { MAP_CALL(CosDiv), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(DeleteKey), SIG_EVERYWHERE, "l.", NULL, NULL }, + { MAP_CALL(DeviceInfo), SIG_EVERYWHERE, "i(r)(r)(i)", NULL, NULL }, // subop + { MAP_CALL(Display), SIG_EVERYWHERE, "[ir]([ir!]*)", NULL, NULL }, + // ^ we allow invalid references here, because kDisplay gets called with those in e.g. pq3 during intro + // restoreBits() checks and skips invalid handles, so that's fine. Sierra SCI behaved the same + { MAP_CALL(DirLoop), SIG_EVERYWHERE, "oi", NULL, NULL }, + { MAP_CALL(DisposeClone), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(DisposeList), SIG_EVERYWHERE, "l", NULL, NULL }, + { MAP_CALL(DisposeScript), SIG_EVERYWHERE, "i(i*)", NULL, kDisposeScript_workarounds }, + { MAP_CALL(DisposeWindow), SIG_EVERYWHERE, "i(i)", NULL, NULL }, + { MAP_CALL(DoAudio), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(DoAvoider), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(DoBresen), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(DoSound), SIG_EVERYWHERE, "i(.*)", kDoSound_subops, NULL }, + { MAP_CALL(DoSync), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(DrawCel), SIG_SCI11, SIGFOR_PC, "iiiii(i)(i)([ri])", NULL, NULL }, // reference for kq6 hires + { MAP_CALL(DrawCel), SIG_EVERYWHERE, "iiiii(i)(i)", NULL, NULL }, + { MAP_CALL(DrawControl), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(DrawMenuBar), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(DrawPic), SIG_EVERYWHERE, "i(i)(i)(i)", NULL, NULL }, + { MAP_CALL(DrawStatus), SIG_EVERYWHERE, "[r0](i)(i)", NULL, NULL }, + { MAP_CALL(EditControl), SIG_EVERYWHERE, "[o0][o0]", NULL, NULL }, + { MAP_CALL(Empty), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(EmptyList), SIG_EVERYWHERE, "l", NULL, NULL }, + { MAP_CALL(FClose), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(FGets), SIG_EVERYWHERE, "rii", NULL, NULL }, + { MAP_CALL(FOpen), SIG_EVERYWHERE, "ri", NULL, NULL }, + { MAP_CALL(FPuts), SIG_EVERYWHERE, "ir", NULL, NULL }, + { MAP_CALL(FileIO), SIG_EVERYWHERE, "i(.*)", kFileIO_subops, NULL }, + { MAP_CALL(FindKey), SIG_EVERYWHERE, "l.", NULL, kFindKey_workarounds }, + { MAP_CALL(FirstNode), SIG_EVERYWHERE, "[l0]", NULL, NULL }, + { MAP_CALL(FlushResources), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(Format), SIG_EVERYWHERE, "r(.*)", NULL, NULL }, + { MAP_CALL(GameIsRestarting), SIG_EVERYWHERE, "(i)", NULL, NULL }, + { MAP_CALL(GetAngle), SIG_EVERYWHERE, "iiii", NULL, kGetAngle_workarounds }, + { MAP_CALL(GetCWD), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(GetDistance), SIG_EVERYWHERE, "ii(i)(i)(i)(i)", NULL, NULL }, + { MAP_CALL(GetEvent), SIG_SCIALL, SIGFOR_MAC, "io(i*)", NULL, NULL }, + { MAP_CALL(GetEvent), SIG_EVERYWHERE, "io", NULL, NULL }, + { MAP_CALL(GetFarText), SIG_EVERYWHERE, "ii[r0]", NULL, NULL }, + { MAP_CALL(GetMenu), SIG_EVERYWHERE, "i.", NULL, NULL }, + { MAP_CALL(GetMessage), SIG_EVERYWHERE, "iiir", NULL, NULL }, + { MAP_CALL(GetPort), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(GetSaveDir), SIG_SCI32, SIGFOR_ALL, "(r*)", NULL, NULL }, + { MAP_CALL(GetSaveDir), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(GetSaveFiles), SIG_EVERYWHERE, "rrr", NULL, NULL }, + { MAP_CALL(GetTime), SIG_EVERYWHERE, "(i)", NULL, NULL }, + { MAP_CALL(GlobalToLocal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, + { MAP_CALL(GlobalToLocal), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(Graph), SIG_EVERYWHERE, NULL, kGraph_subops, NULL }, + { MAP_CALL(HaveMouse), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(HiliteControl), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(InitBresen), SIG_EVERYWHERE, "o(i)", NULL, NULL }, + { MAP_CALL(Intersections), SIG_EVERYWHERE, "iiiiriiiri", NULL, NULL }, + { MAP_CALL(IsItSkip), SIG_EVERYWHERE, "iiiii", NULL, NULL }, + { MAP_CALL(IsObject), SIG_EVERYWHERE, ".", NULL, kIsObject_workarounds }, + { MAP_CALL(Joystick), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(LastNode), SIG_EVERYWHERE, "l", NULL, NULL }, + { MAP_CALL(Load), SIG_EVERYWHERE, "ii(i*)", NULL, NULL }, + { MAP_CALL(LocalToGlobal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, + { MAP_CALL(LocalToGlobal), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(Lock), SIG_EVERYWHERE, "ii(i)", NULL, NULL }, + { MAP_CALL(MapKeyToDir), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(Memory), SIG_EVERYWHERE, "i(.*)", NULL, kMemory_workarounds }, // subop + { MAP_CALL(MemoryInfo), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(MemorySegment), SIG_EVERYWHERE, "ir(i)", NULL, NULL }, // subop + { MAP_CALL(MenuSelect), SIG_EVERYWHERE, "o(i)", NULL, NULL }, + { MAP_CALL(MergePoly), SIG_EVERYWHERE, "rli", NULL, NULL }, + { MAP_CALL(Message), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(MoveCursor), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(NewList), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(NewNode), SIG_EVERYWHERE, "..", NULL, NULL }, + { MAP_CALL(NewWindow), SIG_SCIALL, SIGFOR_MAC, ".*", NULL, NULL }, + { MAP_CALL(NewWindow), SIG_SCI0, SIGFOR_ALL, "iiii[r0]i(i)(i)(i)", NULL, NULL }, + { MAP_CALL(NewWindow), SIG_SCI1, SIGFOR_ALL, "iiii[ir]i(i)(i)([ir])(i)(i)(i)(i)", NULL, NULL }, + { MAP_CALL(NewWindow), SIG_SCI11, SIGFOR_ALL, "iiiiiiii[r0]i(i)(i)(i)", NULL, kNewWindow_workarounds }, + { MAP_CALL(NextNode), SIG_EVERYWHERE, "n", NULL, NULL }, + { MAP_CALL(NodeValue), SIG_EVERYWHERE, "[n0]", NULL, NULL }, + { MAP_CALL(NumCels), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(NumLoops), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(OnControl), SIG_EVERYWHERE, "ii(i)(i)(i)", NULL, NULL }, + { MAP_CALL(PalVary), SIG_EVERYWHERE, "i(i*)", kPalVary_subops, NULL }, + { MAP_CALL(Palette), SIG_EVERYWHERE, "i(.*)", kPalette_subops, NULL }, + { MAP_CALL(Parse), SIG_EVERYWHERE, "ro", NULL, NULL }, + { MAP_CALL(PicNotValid), SIG_EVERYWHERE, "(i)", NULL, NULL }, + { MAP_CALL(Platform), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Portrait), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop + { MAP_CALL(PrevNode), SIG_EVERYWHERE, "n", NULL, NULL }, + { MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, NULL }, + { MAP_CALL(ReadNumber), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL }, + { MAP_CALL(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL }, + { MAP_CALL(RestartGame), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(RestoreGame), SIG_EVERYWHERE, "rir", NULL, NULL }, + { MAP_CALL(Said), SIG_EVERYWHERE, "[r0]", NULL, NULL }, + { MAP_CALL(SaveGame), SIG_EVERYWHERE, "rir(r)", NULL, NULL }, + { MAP_CALL(ScriptID), SIG_EVERYWHERE, "[io](i)", NULL, NULL }, + { MAP_CALL(SetCursor), SIG_SCI21, SIGFOR_ALL, "i(i)([io])(i*)", NULL, NULL }, + // TODO: SCI2.1 may supply an object optionally (mother goose sci21 right on startup) - find out why + { MAP_CALL(SetCursor), SIG_EVERYWHERE, "i(i*)", NULL, NULL }, + { MAP_CALL(SetDebug), SIG_EVERYWHERE, "(i*)", NULL, NULL }, + { MAP_CALL(SetJump), SIG_EVERYWHERE, "oiii", NULL, NULL }, + { MAP_CALL(SetMenu), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, + { MAP_CALL(SetNowSeen), SIG_EVERYWHERE, "o(i)", NULL, NULL }, + { MAP_CALL(SetPort), SIG_EVERYWHERE, "i(iiiii)(i)", NULL, kSetPort_workarounds }, + { MAP_CALL(SetQuitStr), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(SetVideoMode), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(ShakeScreen), SIG_EVERYWHERE, "(i)(i)", NULL, NULL }, + { MAP_CALL(ShowMovie), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(SinDiv), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(Sort), SIG_EVERYWHERE, "ooo", NULL, NULL }, + { MAP_CALL(Sqrt), SIG_EVERYWHERE, "i", NULL, NULL }, + { MAP_CALL(StrAt), SIG_EVERYWHERE, "ri(i)", NULL, NULL }, + { MAP_CALL(StrCat), SIG_EVERYWHERE, "rr", NULL, NULL }, + { MAP_CALL(StrCmp), SIG_EVERYWHERE, "rr(i)", NULL, NULL }, + { MAP_CALL(StrCpy), SIG_EVERYWHERE, "r[r0](i)", NULL, NULL }, + { MAP_CALL(StrEnd), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(StrLen), SIG_EVERYWHERE, "[r0]", NULL, NULL }, + { MAP_CALL(StrSplit), SIG_EVERYWHERE, "rr[r0]", NULL, NULL }, + { MAP_CALL(TextColors), SIG_EVERYWHERE, "(i*)", NULL, NULL }, + { MAP_CALL(TextFonts), SIG_EVERYWHERE, "(i*)", NULL, NULL }, + { MAP_CALL(TextSize), SIG_SCIALL, SIGFOR_MAC, "r[r0]i(i)(r0)(i)", NULL, NULL }, + { MAP_CALL(TextSize), SIG_EVERYWHERE, "r[r0]i(i)(r0)", NULL, NULL }, + { MAP_CALL(TimesCos), SIG_EVERYWHERE, "ii", NULL, NULL }, + { "CosMult", kTimesCos, SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(TimesCot), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(TimesSin), SIG_EVERYWHERE, "ii", NULL, NULL }, + { "SinMult", kTimesSin, SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(TimesTan), SIG_EVERYWHERE, "ii", NULL, NULL }, + { MAP_CALL(UnLoad), SIG_EVERYWHERE, "i[ri]", NULL, kUnLoad_workarounds }, + { MAP_CALL(ValidPath), SIG_EVERYWHERE, "r", NULL, NULL }, + { MAP_CALL(Wait), SIG_EVERYWHERE, "i", NULL, NULL }, + +#ifdef ENABLE_SCI32 + // SCI2 Kernel Functions + { MAP_CALL(AddPlane), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(AddScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(Array), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(CreateTextBitmap), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, + { MAP_CALL(DeletePlane), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(DeleteScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(FrameOut), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(GetHighPlanePri), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(InPolygon), SIG_EVERYWHERE, "iio", NULL, NULL }, + { MAP_CALL(IsHiRes), SIG_EVERYWHERE, "", NULL, NULL }, + { MAP_CALL(ListAllTrue), SIG_EVERYWHERE, "li(.*)", NULL, NULL }, + { MAP_CALL(ListAt), SIG_EVERYWHERE, "li", NULL, NULL }, + { MAP_CALL(ListEachElementDo), SIG_EVERYWHERE, "li(.*)", NULL, NULL }, + { MAP_CALL(ListFirstTrue), SIG_EVERYWHERE, "li(.*)", NULL, NULL }, + { MAP_CALL(ListIndexOf), SIG_EVERYWHERE, "l[o0]", NULL, NULL }, + { MAP_CALL(OnMe), SIG_EVERYWHERE, "iio(.*)", NULL, NULL }, + { MAP_CALL(RepaintPlane), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(String), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(UpdatePlane), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(UpdateScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, + + // SCI2.1 Kernel Functions + { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iio(.*)", NULL, NULL }, + { MAP_CALL(List), SIG_SCI21, SIGFOR_ALL, "(.*)", kList_subops, NULL }, + { MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL }, + { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Save), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Text), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii", NULL, NULL }, + { NULL, NULL, SIG_EVERYWHERE, NULL, NULL, NULL } +#endif +}; + +/** Default kernel name table. */ +static const char *s_defaultKernelNames[] = { + /*0x00*/ "Load", + /*0x01*/ "UnLoad", + /*0x02*/ "ScriptID", + /*0x03*/ "DisposeScript", + /*0x04*/ "Clone", + /*0x05*/ "DisposeClone", + /*0x06*/ "IsObject", + /*0x07*/ "RespondsTo", + /*0x08*/ "DrawPic", + /*0x09*/ "Dummy", // Show + /*0x0a*/ "PicNotValid", + /*0x0b*/ "Animate", + /*0x0c*/ "SetNowSeen", + /*0x0d*/ "NumLoops", + /*0x0e*/ "NumCels", + /*0x0f*/ "CelWide", + /*0x10*/ "CelHigh", + /*0x11*/ "DrawCel", + /*0x12*/ "AddToPic", + /*0x13*/ "NewWindow", + /*0x14*/ "GetPort", + /*0x15*/ "SetPort", + /*0x16*/ "DisposeWindow", + /*0x17*/ "DrawControl", + /*0x18*/ "HiliteControl", + /*0x19*/ "EditControl", + /*0x1a*/ "TextSize", + /*0x1b*/ "Display", + /*0x1c*/ "GetEvent", + /*0x1d*/ "GlobalToLocal", + /*0x1e*/ "LocalToGlobal", + /*0x1f*/ "MapKeyToDir", + /*0x20*/ "DrawMenuBar", + /*0x21*/ "MenuSelect", + /*0x22*/ "AddMenu", + /*0x23*/ "DrawStatus", + /*0x24*/ "Parse", + /*0x25*/ "Said", + /*0x26*/ "SetSynonyms", // Portrait (KQ6 hires) + /*0x27*/ "HaveMouse", + /*0x28*/ "SetCursor", + // FOpen (SCI0) + // FPuts (SCI0) + // FGets (SCI0) + // FClose (SCI0) + /*0x29*/ "SaveGame", + /*0x2a*/ "RestoreGame", + /*0x2b*/ "RestartGame", + /*0x2c*/ "GameIsRestarting", + /*0x2d*/ "DoSound", + /*0x2e*/ "NewList", + /*0x2f*/ "DisposeList", + /*0x30*/ "NewNode", + /*0x31*/ "FirstNode", + /*0x32*/ "LastNode", + /*0x33*/ "EmptyList", + /*0x34*/ "NextNode", + /*0x35*/ "PrevNode", + /*0x36*/ "NodeValue", + /*0x37*/ "AddAfter", + /*0x38*/ "AddToFront", + /*0x39*/ "AddToEnd", + /*0x3a*/ "FindKey", + /*0x3b*/ "DeleteKey", + /*0x3c*/ "Random", + /*0x3d*/ "Abs", + /*0x3e*/ "Sqrt", + /*0x3f*/ "GetAngle", + /*0x40*/ "GetDistance", + /*0x41*/ "Wait", + /*0x42*/ "GetTime", + /*0x43*/ "StrEnd", + /*0x44*/ "StrCat", + /*0x45*/ "StrCmp", + /*0x46*/ "StrLen", + /*0x47*/ "StrCpy", + /*0x48*/ "Format", + /*0x49*/ "GetFarText", + /*0x4a*/ "ReadNumber", + /*0x4b*/ "BaseSetter", + /*0x4c*/ "DirLoop", + /*0x4d*/ "CanBeHere", // CantBeHere in newer SCI versions + /*0x4e*/ "OnControl", + /*0x4f*/ "InitBresen", + /*0x50*/ "DoBresen", + /*0x51*/ "Platform", // DoAvoider (SCI0) + /*0x52*/ "SetJump", + /*0x53*/ "SetDebug", + /*0x54*/ "Dummy", // InspectObj + /*0x55*/ "Dummy", // ShowSends + /*0x56*/ "Dummy", // ShowObjs + /*0x57*/ "Dummy", // ShowFree + /*0x58*/ "MemoryInfo", + /*0x59*/ "Dummy", // StackUsage + /*0x5a*/ "Dummy", // Profiler + /*0x5b*/ "GetMenu", + /*0x5c*/ "SetMenu", + /*0x5d*/ "GetSaveFiles", + /*0x5e*/ "GetCWD", + /*0x5f*/ "CheckFreeSpace", + /*0x60*/ "ValidPath", + /*0x61*/ "CoordPri", + /*0x62*/ "StrAt", + /*0x63*/ "DeviceInfo", + /*0x64*/ "GetSaveDir", + /*0x65*/ "CheckSaveGame", + /*0x66*/ "ShakeScreen", + /*0x67*/ "FlushResources", + /*0x68*/ "SinMult", + /*0x69*/ "CosMult", + /*0x6a*/ "SinDiv", + /*0x6b*/ "CosDiv", + /*0x6c*/ "Graph", + /*0x6d*/ "Joystick", + // End of kernel function table for SCI0 + /*0x6e*/ "Dummy", // ShiftScreen + /*0x6f*/ "Palette", + /*0x70*/ "MemorySegment", + /*0x71*/ "Intersections", // MoveCursor (SCI1 late), PalVary (SCI1.1) + /*0x72*/ "Memory", + /*0x73*/ "Dummy", // ListOps + /*0x74*/ "FileIO", + /*0x75*/ "DoAudio", + /*0x76*/ "DoSync", + /*0x77*/ "AvoidPath", + /*0x78*/ "Sort", // StrSplit (SCI01) + /*0x79*/ "Dummy", // ATan + /*0x7a*/ "Lock", + /*0x7b*/ "StrSplit", + /*0x7c*/ "GetMessage", // Message (SCI1.1) + /*0x7d*/ "IsItSkip", + /*0x7e*/ "MergePoly", + /*0x7f*/ "ResCheck", + /*0x80*/ "AssertPalette", + /*0x81*/ "TextColors", + /*0x82*/ "TextFonts", + /*0x83*/ "Dummy", // Record + /*0x84*/ "Dummy", // PlayBack + /*0x85*/ "ShowMovie", + /*0x86*/ "SetVideoMode", + /*0x87*/ "SetQuitStr", + /*0x88*/ "Dummy" // DbugStr +}; + +#ifdef ENABLE_SCI32 + +// NOTE: 0x72-0x79, 0x85-0x86, 0x88 are from the GK2 demo (which has debug support) and are +// just Dummy in other SCI2 games. +static const char *sci2_default_knames[] = { + /*0x00*/ "Load", + /*0x01*/ "UnLoad", + /*0x02*/ "ScriptID", + /*0x03*/ "DisposeScript", + /*0x04*/ "Lock", + /*0x05*/ "ResCheck", + /*0x06*/ "Purge", + /*0x07*/ "Clone", + /*0x08*/ "DisposeClone", + /*0x09*/ "RespondsTo", + /*0x0a*/ "SetNowSeen", + /*0x0b*/ "NumLoops", + /*0x0c*/ "NumCels", + /*0x0d*/ "CelWide", + /*0x0e*/ "CelHigh", + /*0x0f*/ "GetHighPlanePri", + /*0x10*/ "GetHighItemPri", + /*0x11*/ "ShakeScreen", + /*0x12*/ "OnMe", + /*0x13*/ "ShowMovie", + /*0x14*/ "SetVideoMode", + /*0x15*/ "AddScreenItem", + /*0x16*/ "DeleteScreenItem", + /*0x17*/ "UpdateScreenItem", + /*0x18*/ "FrameOut", + /*0x19*/ "AddPlane", + /*0x1a*/ "DeletePlane", + /*0x1b*/ "UpdatePlane", + /*0x1c*/ "RepaintPlane", + /*0x1d*/ "SetShowStyle", + /*0x1e*/ "ShowStylePercent", + /*0x1f*/ "SetScroll", + /*0x20*/ "AddMagnify", + /*0x21*/ "DeleteMagnify", + /*0x22*/ "IsHiRes", + /*0x23*/ "Graph", + /*0x24*/ "InvertRect", + /*0x25*/ "TextSize", + /*0x26*/ "Message", + /*0x27*/ "TextColors", + /*0x28*/ "TextFonts", + /*0x29*/ "Dummy", + /*0x2a*/ "SetQuitStr", + /*0x2b*/ "EditText", + /*0x2c*/ "InputText", + /*0x2d*/ "CreateTextBitmap", + /*0x2e*/ "DisposeTextBitmap", + /*0x2f*/ "GetEvent", + /*0x30*/ "GlobalToLocal", + /*0x31*/ "LocalToGlobal", + /*0x32*/ "MapKeyToDir", + /*0x33*/ "HaveMouse", + /*0x34*/ "SetCursor", + /*0x35*/ "VibrateMouse", + /*0x36*/ "SaveGame", + /*0x37*/ "RestoreGame", + /*0x38*/ "RestartGame", + /*0x39*/ "GameIsRestarting", + /*0x3a*/ "MakeSaveCatName", + /*0x3b*/ "MakeSaveFileName", + /*0x3c*/ "GetSaveFiles", + /*0x3d*/ "GetSaveDir", + /*0x3e*/ "CheckSaveGame", + /*0x3f*/ "CheckFreeSpace", + /*0x40*/ "DoSound", + /*0x41*/ "DoAudio", + /*0x42*/ "DoSync", + /*0x43*/ "NewList", + /*0x44*/ "DisposeList", + /*0x45*/ "NewNode", + /*0x46*/ "FirstNode", + /*0x47*/ "LastNode", + /*0x48*/ "EmptyList", + /*0x49*/ "NextNode", + /*0x4a*/ "PrevNode", + /*0x4b*/ "NodeValue", + /*0x4c*/ "AddAfter", + /*0x4d*/ "AddToFront", + /*0x4e*/ "AddToEnd", + /*0x4f*/ "Dummy", + /*0x50*/ "Dummy", + /*0x51*/ "FindKey", + /*0x52*/ "Dummy", + /*0x53*/ "Dummy", + /*0x54*/ "Dummy", + /*0x55*/ "DeleteKey", + /*0x56*/ "Dummy", + /*0x57*/ "Dummy", + /*0x58*/ "ListAt", + /*0x59*/ "ListIndexOf", + /*0x5a*/ "ListEachElementDo", + /*0x5b*/ "ListFirstTrue", + /*0x5c*/ "ListAllTrue", + /*0x5d*/ "Random", + /*0x5e*/ "Abs", + /*0x5f*/ "Sqrt", + /*0x60*/ "GetAngle", + /*0x61*/ "GetDistance", + /*0x62*/ "ATan", + /*0x63*/ "SinMult", + /*0x64*/ "CosMult", + /*0x65*/ "SinDiv", + /*0x66*/ "CosDiv", + /*0x67*/ "GetTime", + /*0x68*/ "Platform", + /*0x69*/ "BaseSetter", + /*0x6a*/ "DirLoop", + /*0x6b*/ "CantBeHere", + /*0x6c*/ "InitBresen", + /*0x6d*/ "DoBresen", + /*0x6e*/ "SetJump", + /*0x6f*/ "AvoidPath", + /*0x70*/ "InPolygon", + /*0x71*/ "MergePoly", + /*0x72*/ "SetDebug", + /*0x73*/ "InspectObject", + /*0x74*/ "MemoryInfo", + /*0x75*/ "Profiler", + /*0x76*/ "Record", + /*0x77*/ "PlayBack", + /*0x78*/ "MonoOut", + /*0x79*/ "SetFatalStr", + /*0x7a*/ "GetCWD", + /*0x7b*/ "ValidPath", + /*0x7c*/ "FileIO", + /*0x7d*/ "Dummy", + /*0x7e*/ "DeviceInfo", + /*0x7f*/ "Palette", + /*0x80*/ "PalVary", + /*0x81*/ "PalCycle", + /*0x82*/ "Array", + /*0x83*/ "String", + /*0x84*/ "RemapColors", + /*0x85*/ "IntegrityChecking", + /*0x86*/ "CheckIntegrity", + /*0x87*/ "ObjectIntersect", + /*0x88*/ "MarkMemory", + /*0x89*/ "TextWidth", + /*0x8a*/ "PointSize", + + // GK2 Demo (and similar) only kernel functions + /*0x8b*/ "AddLine", + /*0x8c*/ "DeleteLine", + /*0x8d*/ "UpdateLine", + /*0x8e*/ "AddPolygon", + /*0x8f*/ "DeletePolygon", + /*0x90*/ "UpdatePolygon", + /*0x91*/ "Bitmap", + /*0x92*/ "ScrollWindow", + /*0x93*/ "SetFontRes", + /*0x94*/ "MovePlaneItems", + /*0x95*/ "PreloadResource", + /*0x96*/ "Dummy", + /*0x97*/ "ResourceTrack", + /*0x98*/ "CheckCDisc", + /*0x99*/ "GetSaveCDisc", + /*0x9a*/ "TestPoly", + /*0x9b*/ "WinHelp", + /*0x9c*/ "LoadChunk", + /*0x9d*/ "SetPalStyleRange", + /*0x9e*/ "AddPicAt", + /*0x9f*/ "MessageBox" +}; + +static const char *sci21_default_knames[] = { + /*0x00*/ "Load", + /*0x01*/ "UnLoad", + /*0x02*/ "ScriptID", + /*0x03*/ "DisposeScript", + /*0x04*/ "Lock", + /*0x05*/ "ResCheck", + /*0x06*/ "Purge", + /*0x07*/ "SetLanguage", + /*0x08*/ "Dummy", + /*0x09*/ "Dummy", + /*0x0a*/ "Clone", + /*0x0b*/ "DisposeClone", + /*0x0c*/ "RespondsTo", + /*0x0d*/ "FindSelector", + /*0x0e*/ "FindClass", + /*0x0f*/ "Dummy", + /*0x10*/ "Dummy", + /*0x11*/ "Dummy", + /*0x12*/ "Dummy", + /*0x13*/ "Dummy", + /*0x14*/ "SetNowSeen", + /*0x15*/ "NumLoops", + /*0x16*/ "NumCels", + /*0x17*/ "IsOnMe", + /*0x18*/ "AddMagnify", + /*0x19*/ "DeleteMagnify", + /*0x1a*/ "CelRect", + /*0x1b*/ "BaseLineSpan", + /*0x1c*/ "CelWide", + /*0x1d*/ "CelHigh", + /*0x1e*/ "AddScreenItem", + /*0x1f*/ "DeleteScreenItem", + /*0x20*/ "UpdateScreenItem", + /*0x21*/ "FrameOut", + /*0x22*/ "CelInfo", + /*0x23*/ "Bitmap", + /*0x24*/ "CelLink", + /*0x25*/ "Dummy", + /*0x26*/ "Dummy", + /*0x27*/ "Dummy", + /*0x28*/ "AddPlane", + /*0x29*/ "DeletePlane", + /*0x2a*/ "UpdatePlane", + /*0x2b*/ "RepaintPlane", + /*0x2c*/ "GetHighPlanePri", + /*0x2d*/ "GetHighItemPri", + /*0x2e*/ "SetShowStyle", + /*0x2f*/ "ShowStylePercent", + /*0x30*/ "SetScroll", + /*0x31*/ "MovePlaneItems", + /*0x32*/ "ShakeScreen", + /*0x33*/ "Dummy", + /*0x34*/ "Dummy", + /*0x35*/ "Dummy", + /*0x36*/ "Dummy", + /*0x37*/ "IsHiRes", + /*0x38*/ "SetVideoMode", + /*0x39*/ "ShowMovie", + /*0x3a*/ "Robot", + /*0x3b*/ "CreateTextBitmap", + /*0x3c*/ "Random", + /*0x3d*/ "Abs", + /*0x3e*/ "Sqrt", + /*0x3f*/ "GetAngle", + /*0x40*/ "GetDistance", + /*0x41*/ "ATan", + /*0x42*/ "SinMult", + /*0x43*/ "CosMult", + /*0x44*/ "SinDiv", + /*0x45*/ "CosDiv", + /*0x46*/ "Text", + /*0x47*/ "Dummy", + /*0x48*/ "Message", + /*0x49*/ "Font", + /*0x4a*/ "EditText", + /*0x4b*/ "InputText", + /*0x4c*/ "ScrollWindow", + /*0x4d*/ "Dummy", + /*0x4e*/ "Dummy", + /*0x4f*/ "Dummy", + /*0x50*/ "GetEvent", + /*0x51*/ "GlobalToLocal", + /*0x52*/ "LocalToGlobal", + /*0x53*/ "MapKeyToDir", + /*0x54*/ "HaveMouse", + /*0x55*/ "SetCursor", + /*0x56*/ "VibrateMouse", + /*0x57*/ "Dummy", + /*0x58*/ "Dummy", + /*0x59*/ "Dummy", + /*0x5a*/ "List", + /*0x5b*/ "Array", + /*0x5c*/ "String", + /*0x5d*/ "FileIO", + /*0x5e*/ "BaseSetter", + /*0x5f*/ "DirLoop", + /*0x60*/ "CantBeHere", + /*0x61*/ "InitBresen", + /*0x62*/ "DoBresen", + /*0x63*/ "SetJump", + /*0x64*/ "AvoidPath", + /*0x65*/ "InPolygon", + /*0x66*/ "MergePoly", + /*0x67*/ "ObjectIntersect", + /*0x68*/ "Dummy", + /*0x69*/ "MemoryInfo", + /*0x6a*/ "DeviceInfo", + /*0x6b*/ "Palette", + /*0x6c*/ "PalVary", + /*0x6d*/ "PalCycle", + /*0x6e*/ "RemapColors", + /*0x6f*/ "AddLine", + /*0x70*/ "DeleteLine", + /*0x71*/ "UpdateLine", + /*0x72*/ "AddPolygon", + /*0x73*/ "DeletePolygon", + /*0x74*/ "UpdatePolygon", + /*0x75*/ "DoSound", + /*0x76*/ "DoAudio", + /*0x77*/ "DoSync", + /*0x78*/ "Save", + /*0x79*/ "GetTime", + /*0x7a*/ "Platform", + /*0x7b*/ "CD", + /*0x7c*/ "SetQuitStr", + /*0x7d*/ "GetConfig", + /*0x7e*/ "Table", + /*0x7f*/ "WinHelp", // Windows only + /*0x80*/ "Dummy", + /*0x81*/ "Dummy", + /*0x82*/ "Dummy", + /*0x83*/ "PrintDebug", // used by Shivers 2 (demo and full) + /*0x84*/ "Dummy", + /*0x85*/ "Dummy", + /*0x86*/ "Dummy", + /*0x87*/ "Dummy", + /*0x88*/ "Dummy", + /*0x89*/ "Dummy", + /*0x8a*/ "LoadChunk", + /*0x8b*/ "SetPalStyleRange", + /*0x8c*/ "AddPicAt", + /*0x8d*/ "Dummy", + /*0x8e*/ "NewRoom", + /*0x8f*/ "Dummy", + /*0x90*/ "Priority", + /*0x91*/ "MorphOn", + /*0x92*/ "PlayVMD", + /*0x93*/ "SetHotRectangles", + /*0x94*/ "MulDiv", + /*0x95*/ "GetSierraProfileInt", // Windows only + /*0x96*/ "GetSierraProfileString", // Windows only + /*0x97*/ "SetWindowsOption", // Windows only + /*0x98*/ "GetWindowsOption", // Windows only + /*0x99*/ "WinDLL", // Windows only + /*0x9a*/ "Dummy", + /*0x9b*/ "Dummy", + /*0x9c*/ "DeletePic" +}; + +#endif + +#define END Script_None + +opcode_format g_opcode_formats[128][4] = { + /*00*/ + {Script_None}, {Script_None}, {Script_None}, {Script_None}, + /*04*/ + {Script_None}, {Script_None}, {Script_None}, {Script_None}, + /*08*/ + {Script_None}, {Script_None}, {Script_None}, {Script_None}, + /*0C*/ + {Script_None}, {Script_None}, {Script_None}, {Script_None}, + /*10*/ + {Script_None}, {Script_None}, {Script_None}, {Script_None}, + /*14*/ + {Script_None}, {Script_None}, {Script_None}, {Script_SRelative, END}, + /*18*/ + {Script_SRelative, END}, {Script_SRelative, END}, {Script_SVariable, END}, {Script_None}, + /*1C*/ + {Script_SVariable, END}, {Script_None}, {Script_None}, {Script_Variable, END}, + /*20*/ + {Script_SRelative, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_SVariable, Script_Byte, END}, + /*24 (24=ret)*/ + {Script_End}, {Script_Byte, END}, {Script_Invalid}, {Script_Invalid}, + /*28*/ + {Script_Variable, END}, {Script_Invalid}, {Script_Byte, END}, {Script_Variable, Script_Byte, END}, + /*2C*/ + {Script_SVariable, END}, {Script_SVariable, Script_Variable, END}, {Script_None}, {Script_Invalid}, + /*30*/ + {Script_None}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, + /*34*/ + {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, + /*38*/ + {Script_Property, END}, {Script_SRelative, END}, {Script_SRelative, END}, {Script_None}, + /*3C*/ + {Script_None}, {Script_None}, {Script_None}, {Script_Word}, + /*40-4F*/ + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + /*50-5F*/ + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + /*60-6F*/ + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + /*70-7F*/ + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, + {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END} +}; +#undef END + +} // End of namespace Sci + +#endif // SCI_ENGINE_KERNEL_TABLES_H diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp index 7547ad5ab6..3395811700 100644 --- a/engines/sci/engine/kevent.cpp +++ b/engines/sci/engine/kevent.cpp @@ -50,6 +50,10 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { // Limit the mouse cursor position, if necessary g_sci->_gfxCursor->refreshPosition(); mousePos = g_sci->_gfxCursor->getPosition(); +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2_1) + g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x); +#endif // If there's a simkey pending, and the game wants a keyboard event, use the // simkey instead of a normal event @@ -150,11 +154,11 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { } if (g_sci->_features->detectDoSoundType() <= SCI_VERSION_0_LATE) { - // If we're running a SCI0 game, update the sound cues, to compensate - // for the fact that SCI0 does not poll to update the sound cues itself, - // like SCI01 and later do with cmdUpdateSoundCues. kGetEvent is called - // quite often, so emulate the SCI01 behavior of cmdUpdateSoundCues with - // this call + // 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(); } @@ -215,7 +219,7 @@ reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) { } reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) { - reg_t obj = argc ? argv[0] : NULL_REG; // Can this really happen? Lars + reg_t obj = argv[0]; reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32 SegManager *segMan = s->_segMan; @@ -234,7 +238,7 @@ reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) { } reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv) { - reg_t obj = argc ? argv[0] : NULL_REG; // Can this really happen? Lars + reg_t obj = argv[0]; reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32 SegManager *segMan = s->_segMan; diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 807edc63a5..d4ba467b25 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -98,7 +98,7 @@ enum { -void file_open(EngineState *s, const char *filename, int mode) { +reg_t file_open(EngineState *s, const char *filename, int mode) { // QfG3 character import prepends /\ to the filenames. if (filename[0] == '/' && filename[1] == '\\') filename += 2; @@ -132,17 +132,17 @@ void file_open(EngineState *s, const char *filename, int mode) { } if (!inFile) - warning(" -> file_open(_K_FILE_MODE_OPEN_OR_FAIL): failed to open file '%s'", englishName.c_str()); + debugC(2, kDebugLevelFile, " -> file_open(_K_FILE_MODE_OPEN_OR_FAIL): failed to open file '%s'", englishName.c_str()); } else if (mode == _K_FILE_MODE_CREATE) { // Create the file, destroying any content it might have had outFile = saveFileMan->openForSaving(wrappedName); if (!outFile) - warning(" -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str()); + debugC(2, kDebugLevelFile, " -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str()); } else if (mode == _K_FILE_MODE_OPEN_OR_CREATE) { // Try to open file, create it if it doesn't exist outFile = saveFileMan->openForSaving(wrappedName); if (!outFile) - warning(" -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str()); + debugC(2, kDebugLevelFile, " -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str()); // QfG1 opens the character export file with _K_FILE_MODE_CREATE 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 @@ -154,8 +154,7 @@ void file_open(EngineState *s, const char *filename, int mode) { if (!inFile && !outFile) { // Failed debugC(2, kDebugLevelFile, " -> file_open() failed"); - s->r_acc = SIGNAL_REG; - return; + return SIGNAL_REG; } // Find a free file handle @@ -172,9 +171,8 @@ void file_open(EngineState *s, const char *filename, int mode) { s->_fileHandles[handle]._out = outFile; s->_fileHandles[handle]._name = englishName; - s->r_acc = make_reg(0, handle); - debugC(2, kDebugLevelFile, " -> opened file '%s' with handle %d", englishName.c_str(), handle); + return make_reg(0, handle); } reg_t kFOpen(EngineState *s, int argc, reg_t *argv) { @@ -182,8 +180,7 @@ reg_t kFOpen(EngineState *s, int argc, reg_t *argv) { int mode = argv[1].toUint16(); debugC(2, kDebugLevelFile, "kFOpen(%s,0x%x)", name.c_str(), mode); - file_open(s, name.c_str(), mode); - return s->r_acc; + return file_open(s, name.c_str(), mode); } static FileHandle *getFileFromHandle(EngineState *s, uint handle) { @@ -368,11 +365,25 @@ reg_t kGetSaveDir(EngineState *s, int argc, reg_t *argv) { } reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) { -#ifdef ENABLE_SCI32 - // TODO: SCI32 uses a parameter here. - if (argc > 1) - warning("kCheckFreeSpace called with %d parameter(s): %04x:%04x", argc, PRINT_REG(argv[1])); -#endif + if (argc > 1) { + // SCI1.1/SCI32 + // TODO: don't know if those are right for SCI32 as well + // Please note that sierra sci supported both calls either w/ or w/o opcode in SCI1.1 + switch (argv[1].toUint16()) { + case 0: // return saved game size + return make_reg(0, 0); // we return 0 + + case 1: // return free harddisc space (shifted right somehow) + return make_reg(0, 0x7fff); // we return maximum + + case 2: // same as call w/o opcode + break; + return make_reg(0, 1); + + default: + error("kCheckFreeSpace: called with unknown sub-op %d", argv[1].toUint16()); + } + } Common::String path = s->_segMan->getString(argv[0]); @@ -534,6 +545,12 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { debug(3, "kSaveGame(%s,%d,%s,%s)", game_id.c_str(), virtualId, game_description.c_str(), version.c_str()); + // We check here, we don't want to delete a users save in case we are within a kernel function + if (s->executionStackBase) { + warning("kSaveGame - won't save from within kernel function"); + return NULL_REG; + } + Common::Array<SavegameDesc> saves; listSavegames(saves); @@ -541,7 +558,7 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { if ((virtualId >= SAVEGAMEID_OFFICIALRANGE_START) && (virtualId <= SAVEGAMEID_OFFICIALRANGE_END)) { // savegameId is an actual Id, so search for it just to make sure savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START; - if (findSavegame(saves, savegameId) != -1) + if (findSavegame(saves, savegameId) == -1) return NULL_REG; } else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) { // virtualId is low, we assume that scripts expect us to create new slot @@ -603,11 +620,15 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId); - if ((savegameId < 1000) || (savegameId > 1999)) { - warning("Savegame ID %d is not allowed", savegameId); - return TRUE_REG; + if (argv[0].isNull()) { + // Loading from the launcher, don't adjust the ID of the saved game + } else { + if ((savegameId < 1000) || (savegameId > 1999)) { + warning("Savegame ID %d is not allowed", savegameId); + return TRUE_REG; + } + savegameId -= 1000; } - savegameId -= 1000; Common::Array<SavegameDesc> saves; listSavegames(saves); @@ -643,28 +664,6 @@ reg_t kValidPath(EngineState *s, int argc, reg_t *argv) { return make_reg(0, 1); } -enum { - K_FILEIO_OPEN = 0, - K_FILEIO_CLOSE = 1, - K_FILEIO_READ_RAW = 2, - K_FILEIO_WRITE_RAW = 3, - K_FILEIO_UNLINK = 4, - K_FILEIO_READ_STRING = 5, - K_FILEIO_WRITE_STRING = 6, - K_FILEIO_SEEK = 7, - K_FILEIO_FIND_FIRST = 8, - K_FILEIO_FIND_NEXT = 9, - K_FILEIO_FILE_EXISTS = 10, - // SCI1.1 - K_FILEIO_RENAME = 11, - // SCI32 - // 12? - K_FILEIO_READ_BYTE = 13, - K_FILEIO_WRITE_BYTE = 14, - K_FILEIO_READ_WORD = 15, - K_FILEIO_WRITE_WORD = 16 -}; - reg_t DirSeeker::firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan) { // Verify that we are given a valid buffer if (!buffer.segment) { @@ -705,263 +704,298 @@ reg_t DirSeeker::nextFile(SegManager *segMan) { } reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { - int func_nr = argv[0].toUint16(); + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - switch (func_nr) { - case K_FILEIO_OPEN : { - Common::String name = s->_segMan->getString(argv[1]); +reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { + Common::String name = s->_segMan->getString(argv[0]); - // SCI32 can call K_FILEIO_OPEN with only two arguments. It seems to - // just be checking if it exists. - int mode = (argc < 3) ? (int)_K_FILE_MODE_OPEN_OR_FAIL : argv[2].toUint16(); + // SCI32 can call K_FILEIO_OPEN with only one argument. It seems to + // just be checking if it exists. + int mode = (argc < 2) ? (int)_K_FILE_MODE_OPEN_OR_FAIL : argv[1].toUint16(); - // SQ4 floppy prepends /\ to the filenames - if (name.hasPrefix("/\\")) { - name.deleteChar(0); - name.deleteChar(0); - } + // SQ4 floppy prepends /\ to the filenames + if (name.hasPrefix("/\\")) { + name.deleteChar(0); + name.deleteChar(0); + } - // SQ4 floppy attempts to update the savegame index file sq4sg.dir when - // deleting saved games. We don't use an index file for saving or - // loading, so just stop the game from modifying the file here in order - // to avoid having it saved in the ScummVM save directory. - if (name == "sq4sg.dir") { - debugC(2, kDebugLevelFile, "Not opening unused file sq4sg.dir"); - return SIGNAL_REG; - } + // SQ4 floppy attempts to update the savegame index file sq4sg.dir when + // deleting saved games. We don't use an index file for saving or + // loading, so just stop the game from modifying the file here in order + // to avoid having it saved in the ScummVM save directory. + if (name == "sq4sg.dir") { + debugC(2, kDebugLevelFile, "Not opening unused file sq4sg.dir"); + return SIGNAL_REG; + } - if (name.empty()) { - warning("Attempted to open a file with an empty filename"); - return SIGNAL_REG; - } - debugC(2, kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode); - file_open(s, name.c_str(), mode); - break; + if (name.empty()) { + warning("Attempted to open a file with an empty filename"); + return SIGNAL_REG; } - case K_FILEIO_CLOSE : { - debugC(2, kDebugLevelFile, "kFileIO(close): %d", argv[1].toUint16()); + debugC(2, kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode); + return file_open(s, name.c_str(), mode); +} - FileHandle *f = getFileFromHandle(s, argv[1].toUint16()); - if (f) - f->close(); - break; +reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) { + debugC(2, kDebugLevelFile, "kFileIO(close): %d", argv[0].toUint16()); + + FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); + if (f) { + f->close(); + return SIGNAL_REG; } - case K_FILEIO_READ_RAW : { - int handle = argv[1].toUint16(); - int size = argv[3].toUint16(); - char *buf = new char[size]; - debugC(2, kDebugLevelFile, "kFileIO(readRaw): %d, %d", handle, size); + return NULL_REG; +} + +reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) { + int handle = argv[0].toUint16(); + int size = argv[2].toUint16(); + int bytesRead = 0; + char *buf = new char[size]; + debugC(2, kDebugLevelFile, "kFileIO(readRaw): %d, %d", handle, size); - FileHandle *f = getFileFromHandle(s, handle); - if (f) { - s->r_acc = make_reg(0, f->_in->read(buf, size)); - s->_segMan->memcpy(argv[2], (const byte*)buf, size); - } + FileHandle *f = getFileFromHandle(s, handle); + if (f) { + bytesRead = f->_in->read(buf, size); + s->_segMan->memcpy(argv[1], (const byte*)buf, size); + } - delete[] buf; - break; + delete[] buf; + return make_reg(0, bytesRead); +} + +reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) { + int handle = argv[0].toUint16(); + int size = argv[2].toUint16(); + char *buf = new char[size]; + bool success = false; + s->_segMan->memcpy((byte*)buf, argv[1], size); + debugC(2, kDebugLevelFile, "kFileIO(writeRaw): %d, %d", handle, size); + + FileHandle *f = getFileFromHandle(s, handle); + if (f) { + f->_out->write(buf, size); + success = true; } - case K_FILEIO_WRITE_RAW : { - int handle = argv[1].toUint16(); - int size = argv[3].toUint16(); - char *buf = new char[size]; - s->_segMan->memcpy((byte*)buf, argv[2], size); - debugC(2, kDebugLevelFile, "kFileIO(writeRaw): %d, %d", handle, size); - FileHandle *f = getFileFromHandle(s, handle); - if (f) - f->_out->write(buf, size); + delete[] buf; + if (success) + return NULL_REG; + return make_reg(0, 6); // DOS - invalid handle +} - delete[] buf; - break; +reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) { + Common::String name = s->_segMan->getString(argv[0]); + Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager(); + bool result; + + // SQ4 floppy prepends /\ to the filenames + if (name.hasPrefix("/\\")) { + name.deleteChar(0); + name.deleteChar(0); + } + + // Special case for SQ4 floppy: This game has hardcoded names for all of + // its savegames, and they are all named "sq4sg.xxx", where xxx is the + // slot. We just take the slot number here, and delete the appropriate + // save game. + if (name.hasPrefix("sq4sg.")) { + // Special handling for SQ4... get the slot number and construct the + // save game name. + int slotNum = atoi(name.c_str() + name.size() - 3); + Common::Array<SavegameDesc> saves; + listSavegames(saves); + int savedir_nr = saves[slotNum].id; + name = g_sci->getSavegameName(savedir_nr); + result = saveFileMan->removeSavefile(name); + } else { + const Common::String wrappedName = g_sci->wrapFilename(name); + result = saveFileMan->removeSavefile(wrappedName); } - case K_FILEIO_UNLINK : { - Common::String name = s->_segMan->getString(argv[1]); - Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager(); - // SQ4 floppy prepends /\ to the filenames - if (name.hasPrefix("/\\")) { - name.deleteChar(0); - name.deleteChar(0); - } - // Special case for SQ4 floppy: This game has hardcoded names for all of - // its savegames, and they are all named "sq4sg.xxx", where xxx is the - // slot. We just take the slot number here, and delete the appropriate - // save game. - if (name.hasPrefix("sq4sg.")) { - // Special handling for SQ4... get the slot number and construct the - // save game name. - int slotNum = atoi(name.c_str() + name.size() - 3); - Common::Array<SavegameDesc> saves; - listSavegames(saves); - int savedir_nr = saves[slotNum].id; - name = g_sci->getSavegameName(savedir_nr); - saveFileMan->removeSavefile(name); - } else { - const Common::String wrappedName = g_sci->wrapFilename(name); - saveFileMan->removeSavefile(wrappedName); - } + debugC(2, kDebugLevelFile, "kFileIO(unlink): %s", name.c_str()); + if (result) + return NULL_REG; + return make_reg(0, 2); // DOS - file not found error code +} - debugC(2, kDebugLevelFile, "kFileIO(unlink): %s", name.c_str()); +reg_t kFileIOReadString(EngineState *s, int argc, reg_t *argv) { + int size = argv[1].toUint16(); + char *buf = new char[size]; + int handle = argv[2].toUint16(); + debugC(2, kDebugLevelFile, "kFileIO(readString): %d, %d", handle, size); - // TODO/FIXME: Should we return something (like, a bool indicating - // whether deleting the save succeeded or failed)? - break; - } - case K_FILEIO_READ_STRING : { - int size = argv[2].toUint16(); - char *buf = new char[size]; - int handle = argv[3].toUint16(); - debugC(2, kDebugLevelFile, "kFileIO(readString): %d, %d", handle, size); + fgets_wrapper(s, buf, size, handle); + s->_segMan->memcpy(argv[0], (const byte*)buf, size); + delete[] buf; + return argv[0]; +} - fgets_wrapper(s, buf, size, handle); - s->_segMan->memcpy(argv[1], (const byte*)buf, size); - delete[] buf; - return argv[1]; - } - case K_FILEIO_WRITE_STRING : { - int handle = argv[1].toUint16(); - int size = argv[3].toUint16(); - Common::String str = s->_segMan->getString(argv[2]); - debugC(2, kDebugLevelFile, "kFileIO(writeString): %d, %d", handle, size); - - // CHECKME: Is the size parameter used at all? - // In the LSL5 password protection it is zero, and we should - // then write a full string. (Not sure if it should write the - // terminating zero.) - - FileHandle *f = getFileFromHandle(s, handle); - if (f) - f->_out->write(str.c_str(), str.size()); - break; - } - case K_FILEIO_SEEK : { - int handle = argv[1].toUint16(); - int offset = argv[2].toUint16(); - int whence = argv[3].toUint16(); - debugC(2, kDebugLevelFile, "kFileIO(seek): %d, %d, %d", handle, offset, whence); +reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) { + int handle = argv[0].toUint16(); + Common::String str = s->_segMan->getString(argv[1]); + debugC(2, kDebugLevelFile, "kFileIO(writeString): %d", handle); + + FileHandle *f = getFileFromHandle(s, handle); + if (f) + f->_out->write(str.c_str(), str.size()); + return NULL_REG; + return make_reg(0, 6); // DOS - invalid handle +} + +reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv) { + int handle = argv[0].toUint16(); + int offset = argv[1].toUint16(); + int whence = argv[2].toUint16(); + debugC(2, kDebugLevelFile, "kFileIO(seek): %d, %d, %d", handle, offset, whence); - FileHandle *f = getFileFromHandle(s, handle); - if (f) - s->r_acc = make_reg(0, f->_in->seek(offset, whence)); - break; - } - case K_FILEIO_FIND_FIRST : { - Common::String mask = s->_segMan->getString(argv[1]); - reg_t buf = argv[2]; - int attr = argv[3].toUint16(); // We won't use this, Win32 might, though... - debugC(2, kDebugLevelFile, "kFileIO(findFirst): %s, 0x%x", mask.c_str(), attr); - - // We remove ".*". mask will get prefixed, so we will return all additional files for that gameid - if (mask == "*.*") - mask = "*"; - - // QfG3 uses this mask for the character import - if (mask == "/\\*.*") - mask = "*"; -//#ifndef WIN32 -// if (mask == "*.*") -// mask = "*"; // For UNIX -//#endif - s->r_acc = s->_dirseeker.firstFile(mask, buf, s->_segMan); + FileHandle *f = getFileFromHandle(s, handle); + if (f) + s->r_acc = make_reg(0, f->_in->seek(offset, whence)); + return SIGNAL_REG; +} - break; - } - case K_FILEIO_FIND_NEXT : { - debugC(2, kDebugLevelFile, "kFileIO(findNext)"); - s->r_acc = s->_dirseeker.nextFile(s->_segMan); - break; +reg_t kFileIOFindFirst(EngineState *s, int argc, reg_t *argv) { + Common::String mask = s->_segMan->getString(argv[0]); + reg_t buf = argv[1]; + int attr = argv[2].toUint16(); // We won't use this, Win32 might, though... + debugC(2, kDebugLevelFile, "kFileIO(findFirst): %s, 0x%x", mask.c_str(), attr); + + // QfG3 uses "/\*.*" for the character import, QfG4 uses "/\*" + if (mask.hasPrefix("/\\")) { + mask.deleteChar(0); + mask.deleteChar(0); } - case K_FILEIO_FILE_EXISTS : { - Common::String name = s->_segMan->getString(argv[1]); - // Check for regular file - bool exists = Common::File::exists(name); - Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager(); - const Common::String wrappedName = g_sci->wrapFilename(name); + // We remove ".*". mask will get prefixed, so we will return all additional files for that gameid + if (mask == "*.*") + mask = "*"; + return s->_dirseeker.firstFile(mask, buf, s->_segMan); +} - if (!exists) - exists = !saveFileMan->listSavefiles(name).empty(); +reg_t kFileIOFindNext(EngineState *s, int argc, reg_t *argv) { + debugC(2, kDebugLevelFile, "kFileIO(findNext)"); + return s->_dirseeker.nextFile(s->_segMan); +} - if (!exists) { - // Try searching for the file prepending target- - Common::SeekableReadStream *inFile = saveFileMan->openForLoading(wrappedName); - exists = (inFile != 0); - delete inFile; - } +reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { + Common::String name = s->_segMan->getString(argv[0]); - // Special case for non-English versions of LSL5: The English version of - // LSL5 calls kFileIO(), case K_FILEIO_OPEN for reading to check if - // memory.drv exists (which is where the game's password is stored). If - // it's not found, it calls kFileIO() again, case K_FILEIO_OPEN for - // writing and creates a new file. Non-English versions call kFileIO(), - // case K_FILEIO_FILE_EXISTS instead, and fail if memory.drv can't be - // found. We create a default memory.drv file with no password, so that - // the game can continue. - if (!exists && name == "memory.drv") { - // Create a new file, and write the bytes for the empty password - // string inside - byte defaultContent[] = { 0xE9, 0xE9, 0xEB, 0xE1, 0x0D, 0x0A, 0x31, 0x30, 0x30, 0x30 }; - Common::WriteStream *outFile = saveFileMan->openForSaving(wrappedName); - for (int i = 0; i < 10; i++) - outFile->writeByte(defaultContent[i]); - outFile->finalize(); - delete outFile; - exists = true; - } + // Check for regular file + bool exists = Common::File::exists(name); + Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager(); + const Common::String wrappedName = g_sci->wrapFilename(name); + + if (!exists) + exists = !saveFileMan->listSavefiles(name).empty(); + + if (!exists) { + // Try searching for the file prepending target- + Common::SeekableReadStream *inFile = saveFileMan->openForLoading(wrappedName); + exists = (inFile != 0); + delete inFile; + } + + // Special case for non-English versions of LSL5: The English version of + // LSL5 calls kFileIO(), case K_FILEIO_OPEN for reading to check if + // memory.drv exists (which is where the game's password is stored). If + // it's not found, it calls kFileIO() again, case K_FILEIO_OPEN for + // writing and creates a new file. Non-English versions call kFileIO(), + // case K_FILEIO_FILE_EXISTS instead, and fail if memory.drv can't be + // found. We create a default memory.drv file with no password, so that + // the game can continue. + if (!exists && name == "memory.drv") { + // Create a new file, and write the bytes for the empty password + // string inside + byte defaultContent[] = { 0xE9, 0xE9, 0xEB, 0xE1, 0x0D, 0x0A, 0x31, 0x30, 0x30, 0x30 }; + Common::WriteStream *outFile = saveFileMan->openForSaving(wrappedName); + for (int i = 0; i < 10; i++) + outFile->writeByte(defaultContent[i]); + outFile->finalize(); + delete outFile; + exists = true; + } + + debugC(2, kDebugLevelFile, "kFileIO(fileExists) %s -> %d", name.c_str(), exists); + return make_reg(0, exists); +} - debugC(2, kDebugLevelFile, "kFileIO(fileExists) %s -> %d", name.c_str(), exists); - return make_reg(0, exists); - } - case K_FILEIO_RENAME: { - Common::String oldName = s->_segMan->getString(argv[1]); - Common::String newName = s->_segMan->getString(argv[2]); +reg_t kFileIORename(EngineState *s, int argc, reg_t *argv) { + Common::String oldName = s->_segMan->getString(argv[0]); + Common::String newName = s->_segMan->getString(argv[1]); + + // SCI1.1 returns 0 on success and a DOS error code on fail. SCI32 + // returns -1 on fail. We just return -1 for all versions. + if (g_engine->getSaveFileManager()->renameSavefile(oldName, newName)) + return NULL_REG; + else + return SIGNAL_REG; +} - // SCI1.1 returns 0 on success and a DOS error code on fail. SCI32 - // returns -1 on fail. We just return -1 for all versions. - if (g_engine->getSaveFileManager()->renameSavefile(oldName, newName)) - return NULL_REG; - else - return SIGNAL_REG; - } #ifdef ENABLE_SCI32 - case K_FILEIO_READ_BYTE: { - // Read the byte into the low byte of the accumulator - FileHandle *f = getFileFromHandle(s, argv[1].toUint16()); - if (!f) - return NULL_REG; - - return make_reg(0, (s->r_acc.toUint16() & 0xff00) | f->_in->readByte()); - } - case K_FILEIO_WRITE_BYTE: { - FileHandle *f = getFileFromHandle(s, argv[1].toUint16()); - if (f) - f->_out->writeByte(argv[2].toUint16() & 0xff); - break; - } - case K_FILEIO_READ_WORD: { - FileHandle *f = getFileFromHandle(s, argv[1].toUint16()); - if (!f) - return NULL_REG; - - return make_reg(0, f->_in->readUint16LE()); - } - case K_FILEIO_WRITE_WORD: { - FileHandle *f = getFileFromHandle(s, argv[1].toUint16()); - if (f) - f->_out->writeUint16LE(argv[2].toUint16()); - break; +reg_t kFileIOReadByte(EngineState *s, int argc, reg_t *argv) { + // Read the byte into the low byte of the accumulator + FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); + if (!f) + return NULL_REG; + return make_reg(0, (s->r_acc.toUint16() & 0xff00) | f->_in->readByte()); +} + +reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv) { + FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); + if (f) + f->_out->writeByte(argv[1].toUint16() & 0xff); + return s->r_acc; // FIXME: does this really doesn't return anything? +} + +reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv) { + FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); + if (!f) + return NULL_REG; + return make_reg(0, f->_in->readUint16LE()); +} + +reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv) { + FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); + if (f) + f->_out->writeUint16LE(argv[1].toUint16()); + return s->r_acc; // FIXME: does this really doesn't return anything? +} + +reg_t kCD(EngineState *s, int argc, reg_t *argv) { + // TODO: Stub + switch (argv[0].toUint16()) { + case 0: + // Return whether the contents of disc argv[1] is available. + return TRUE_REG; + default: + warning("CD(%d)", argv[0].toUint16()); } - case 19: - // TODO: Torin's Passage uses this early on in the Sierra logo - warning("kFileIO(19)"); - break; -#endif + + return NULL_REG; +} + +reg_t kSave(EngineState *s, int argc, reg_t *argv) { + switch (argv[0].toUint16()) { + case 0: // Called by kq7 when starting chapters + return SIGNAL_REG; + case 2: // GetSaveDir + // Yay! Reusing the old kernel function! + return kGetSaveDir(s, argc - 1, argv + 1); + case 8: + // TODO + // This function has to return something other than 0 to proceed + return s->r_acc; default: - error("kFileIO(): unknown sub-command: %d", func_nr); + warning("Unknown/unhandled kSave subop %d", argv[0].toUint16()); } - return s->r_acc; + return NULL_REG; } +#endif + } // End of namespace Sci diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index 29f7565ef9..56518f10bf 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -23,16 +23,15 @@ * */ +#include "common/system.h" + #include "engines/util.h" #include "graphics/cursorman.h" -#include "graphics/video/avi_decoder.h" -#include "graphics/video/qt_decoder.h" #include "graphics/surface.h" #include "sci/sci.h" #include "sci/debug.h" // for g_debug_sleeptime_factor #include "sci/resource.h" -#include "sci/video/seq_decoder.h" #include "sci/engine/features.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" @@ -44,12 +43,14 @@ #include "sci/graphics/cursor.h" #include "sci/graphics/palette.h" #include "sci/graphics/paint16.h" +#include "sci/graphics/picture.h" #include "sci/graphics/ports.h" +#include "sci/graphics/robot.h" #include "sci/graphics/screen.h" #include "sci/graphics/text16.h" #include "sci/graphics/view.h" #ifdef ENABLE_SCI32 -#include "sci/video/vmd_decoder.h" +#include "sci/graphics/frameout.h" #endif namespace Sci { @@ -129,9 +130,10 @@ static reg_t kSetCursorSci11(EngineState *s, int argc, reg_t *argv) { break; case -1: // TODO: Special case at least in kq6, check disassembly + // Does something with magCursor, which is set on argc = 10, which we don't support break; case -2: - // TODO: Special case at least in kq6, check disassembly + g_sci->_gfxCursor->kernelResetMoveZone(); break; default: g_sci->_gfxCursor->kernelShow(); @@ -145,14 +147,22 @@ static reg_t kSetCursorSci11(EngineState *s, int argc, reg_t *argv) { g_sci->_gfxCursor->kernelSetPos(pos); break; case 4: { - int16 top = argv[0].toSint16(); - int16 left = argv[1].toSint16(); - int16 bottom = argv[2].toSint16(); - int16 right = argv[3].toSint16(); + int16 top, left, bottom, right; - // In SCI32, the right parameter seems to be divided by 2 - if (getSciVersion() >= SCI_VERSION_2) - right *= 2; + if (getSciVersion() >= SCI_VERSION_2) { + top = argv[1].toSint16(); + left = argv[0].toSint16(); + bottom = argv[3].toSint16(); + right = argv[2].toSint16(); + } else { + top = argv[0].toSint16(); + left = argv[1].toSint16(); + bottom = argv[2].toSint16(); + right = argv[3].toSint16(); + } + // bottom/right needs to be included into our movezone, because we compare it like any regular Common::Rect + bottom++; + right++; if ((right >= left) && (bottom >= top)) { Common::Rect rect = Common::Rect(left, top, right, bottom); @@ -250,7 +260,7 @@ reg_t kGraphDrawLine(EngineState *s, int argc, reg_t *argv) { reg_t kGraphSaveBox(EngineState *s, int argc, reg_t *argv) { Common::Rect rect = getGraphRect(argv); - uint16 screenMask = (argc > 4) ? argv[4].toUint16() : 0; + uint16 screenMask = argv[4].toUint16() & GFX_SCREEN_MASK_ALL; return g_sci->_gfxPaint16->kernelGraphSaveBox(rect, screenMask); } @@ -382,17 +392,16 @@ reg_t kCanBeHere(EngineState *s, int argc, reg_t *argv) { reg_t curObject = argv[0]; reg_t listReference = (argc > 1) ? argv[1] : NULL_REG; - bool canBeHere = g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference); - return make_reg(0, canBeHere); + reg_t canBeHere = g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference); + return make_reg(0, canBeHere.isNull() ? 1 : 0); } -// kCantBeHere does the same thing as kCanBeHere, except that it returns the opposite result. reg_t kCantBeHere(EngineState *s, int argc, reg_t *argv) { reg_t curObject = argv[0]; reg_t listReference = (argc > 1) ? argv[1] : NULL_REG; - bool canBeHere = g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference); - return make_reg(0, !canBeHere); + reg_t canBeHere = g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference); + return canBeHere; } reg_t kIsItSkip(EngineState *s, int argc, reg_t *argv) { @@ -518,14 +527,6 @@ reg_t kBaseSetter(EngineState *s, int argc, reg_t *argv) { reg_t object = argv[0]; g_sci->_gfxCompare->kernelBaseSetter(object); - - // WORKAROUND for a problem in LSL1VGA. This allows the casino door to be opened, - // till the actual problem is found - if (s->currentRoomNumber() == 300 && g_sci->getGameId() == GID_LSL1) { - int top = readSelectorValue(s->_segMan, object, SELECTOR(brTop)); - writeSelectorValue(s->_segMan, object, SELECTOR(brTop), top + 2); - } - return s->r_acc; } @@ -615,16 +616,16 @@ reg_t kPaletteAnimate(EngineState *s, int argc, reg_t *argv) { reg_t kPaletteSave(EngineState *s, int argc, reg_t *argv) { if (g_sci->getResMan()->isVGA()) { - warning("kPalette(7), save palette to heap STUB"); + return g_sci->_gfxPalette->kernelSave(); } return NULL_REG; } reg_t kPaletteRestore(EngineState *s, int argc, reg_t *argv) { if (g_sci->getResMan()->isVGA()) { - warning("kPalette(8), restore palette from heap STUB"); + g_sci->_gfxPalette->kernelRestore(argv[0]); } - return s->r_acc; + return argv[0]; } reg_t kPalVary(EngineState *s, int argc, reg_t *argv) { @@ -889,8 +890,8 @@ reg_t kDrawControl(EngineState *s, int argc, reg_t *argv) { reg_t textReference = readSelector(s->_segMan, controlObject, SELECTOR(text)); if (!textReference.isNull()) { Common::String text = s->_segMan->getString(textReference); - if (text == "a:hq1_hero.sav") { - // Remove "a:" from hero quest export default filename + if ((text == "a:hq1_hero.sav") || (text == "a:glory1.sav") || (text == "a:glory2.sav") || (text == "a:glory3.sav")) { + // Remove "a:" from hero quest / quest for glory export default filenames text.deleteChar(0); text.deleteChar(0); s->_segMan->strcpy(textReference, text.c_str()); @@ -974,14 +975,13 @@ reg_t kSetPort(EngineState *s, int argc, reg_t *argv) { case 7: initPriorityBandsFlag = true; - case 4: case 6: picRect.top = argv[0].toSint16(); picRect.left = argv[1].toSint16(); picRect.bottom = argv[2].toSint16(); picRect.right = argv[3].toSint16(); - picTop = (argc >= 6) ? argv[4].toSint16() : 0; - picLeft = (argc >= 6) ? argv[5].toSint16() : 0; + picTop = argv[4].toSint16(); + picLeft = argv[5].toSint16(); g_sci->_gfxPorts->kernelSetPicWindow(picRect, picTop, picLeft, initPriorityBandsFlag); break; @@ -1000,10 +1000,26 @@ reg_t kDrawCel(EngineState *s, int argc, reg_t *argv) { uint16 y = argv[4].toUint16(); int16 priority = (argc > 5) ? argv[5].toSint16() : -1; uint16 paletteNo = (argc > 6) ? argv[6].toUint16() : 0; - bool hiresMode = (argc > 7) ? true : false; - reg_t upscaledHiresHandle = (argc > 7) ? argv[7] : NULL_REG; + bool hiresMode = false; + reg_t upscaledHiresHandle = NULL_REG; + uint16 scaleX = 128; + uint16 scaleY = 128; + + if (argc > 7) { + // this is either kq6 hires or scaling + if (paletteNo > 0) { + // it's scaling + scaleX = argv[6].toUint16(); + scaleY = argv[7].toUint16(); + paletteNo = 0; + } else { + // KQ6 hires + hiresMode = true; + upscaledHiresHandle = argv[7]; + } + } - g_sci->_gfxPaint16->kernelDrawCel(viewId, loopNo, celNo, x, y, priority, paletteNo, hiresMode, upscaledHiresHandle); + g_sci->_gfxPaint16->kernelDrawCel(viewId, loopNo, celNo, x, y, priority, paletteNo, scaleX, scaleY, hiresMode, upscaledHiresHandle); return s->r_acc; } @@ -1075,153 +1091,264 @@ reg_t kDisplay(EngineState *s, int argc, reg_t *argv) { return g_sci->_gfxPaint16->kernelDisplay(g_sci->strSplit(text.c_str()).c_str(), argc, argv); } -void playVideo(Graphics::VideoDecoder *videoDecoder) { - if (!videoDecoder) - return; +reg_t kSetVideoMode(EngineState *s, int argc, reg_t *argv) { + // This call is used for KQ6's intro. It has one parameter, which is 1 when + // the intro begins, and 0 when it ends. It is suspected that this is + // actually a flag to enable video planar memory access, as the video + // decoder in KQ6 is specifically written for the planar memory model. + // Planar memory mode access was used for VGA "Mode X" (320x240 resolution, + // although the intro in KQ6 is 320x200). + // Refer to http://en.wikipedia.org/wiki/Mode_X - byte *scaleBuffer = 0; - uint16 width = videoDecoder->getWidth(); - uint16 height = videoDecoder->getHeight(); - uint16 screenWidth = g_system->getWidth(); - uint16 screenHeight = g_system->getHeight(); - - if (screenWidth == 640 && width <= 320 && height <= 240) { - assert(videoDecoder->getPixelFormat().bytesPerPixel == 1); - width *= 2; - height *= 2; - scaleBuffer = new byte[width * height]; - } + //warning("STUB: SetVideoMode %d", argv[0].toUint16()); + return s->r_acc; +} - uint16 x = (screenWidth - width) / 2; - uint16 y = (screenHeight - height) / 2; - bool skipVideo = false; - - while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { - if (videoDecoder->needsUpdate()) { - Graphics::Surface *frame = videoDecoder->decodeNextFrame(); - if (frame) { - if (scaleBuffer) { - // TODO: Probably should do aspect ratio correction in e.g. GK1 Windows - g_sci->_gfxScreen->scale2x((byte *)frame->pixels, scaleBuffer, videoDecoder->getWidth(), videoDecoder->getHeight()); - g_system->copyRectToScreen(scaleBuffer, width, x, y, width, height); - } else - g_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, width, height); - - if (videoDecoder->hasDirtyPalette()) - videoDecoder->setSystemPalette(); - - g_system->updateScreen(); - } - } +// New calls for SCI11. Using those is only needed when using text-codes so that +// one is able to change font and/or color multiple times during kDisplay and +// kDrawControl +reg_t kTextFonts(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxText16->kernelTextFonts(argc, argv); + return s->r_acc; +} - 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; - } +reg_t kTextColors(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxText16->kernelTextColors(argc, argv); + return s->r_acc; +} - g_system->delayMillis(10); - } +#ifdef ENABLE_SCI32 - delete[] scaleBuffer; - delete videoDecoder; +reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) { + // Returns 0 if the screen width or height is less than 640 or 400, + // respectively. + if (g_system->getWidth() < 640 || g_system->getHeight() < 400) + return make_reg(0, 0); + + return make_reg(0, 1); } -reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { - // 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(); +// SCI32 variant, can't work like sci16 variants +reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv) { +// reg_t curObject = argv[0]; +// reg_t listReference = (argc > 1) ? argv[1] : NULL_REG; + + return NULL_REG; +} - uint16 screenWidth = g_system->getWidth(); - uint16 screenHeight = g_system->getHeight(); - - Graphics::VideoDecoder *videoDecoder = 0; +reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) { + reg_t viewObj = argv[0]; - if (argv[0].segment != 0) { - Common::String filename = s->_segMan->getString(argv[0]); + g_sci->_gfxFrameout->kernelAddScreenItem(viewObj); + return NULL_REG; +} - if (g_sci->getPlatform() == Common::kPlatformMacintosh) { - // Mac QuickTime - // The only argument is the string for the video +reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) { + //reg_t viewObj = argv[0]; - // HACK: Switch to 16bpp graphics for Cinepak. - initGraphics(screenWidth, screenHeight, screenWidth > 320, NULL); + //warning("kUpdateScreenItem, object %04x:%04x, view %d, loop %d, cel %d, pri %d", PRINT_REG(viewObj), viewId, loopNo, celNo, priority); + return NULL_REG; +} - if (g_system->getScreenFormat().bytesPerPixel == 1) { - error("This video requires >8bpp color to be displayed, but could not switch to RGB color mode."); - return NULL_REG; - } +reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) { + reg_t viewObj = argv[0]; - videoDecoder = new Graphics::QuickTimeDecoder(); - if (!videoDecoder->loadFile(filename)) - error("Could not open '%s'", filename.c_str()); - } else { - // DOS SEQ - // SEQ's are called with no subops, just the string and delay - SeqDecoder *seqDecoder = new SeqDecoder(); - seqDecoder->setFrameDelay(argv[1].toUint16()); // Time between frames in ticks - videoDecoder = seqDecoder; - - if (!videoDecoder->loadFile(filename)) { - warning("Failed to open movie file %s", filename.c_str()); - delete videoDecoder; - videoDecoder = 0; - } - } - } else { - // Windows AVI - // TODO: This appears to be some sort of subop. case 0 contains the string - // for the video, so we'll just play it from there for now. + g_sci->_gfxFrameout->kernelDeleteScreenItem(viewObj); -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1) { - // SCI2.1 always has argv[0] as 1, the rest of the arguments seem to - // follow SCI1.1/2. - if (argv[0].toUint16() != 1) - error("SCI2.1 kShowMovie argv[0] not 1"); - argv++; - argc--; - } -#endif - switch (argv[0].toUint16()) { - case 0: { - Common::String filename = s->_segMan->getString(argv[1]); - videoDecoder = new Graphics::AviDecoder(g_system->getMixer()); - - if (!videoDecoder->loadFile(filename.c_str())) { - warning("Failed to open movie file %s", filename.c_str()); - delete videoDecoder; - videoDecoder = 0; - } - break; - } - default: - warning("Unhandled SCI kShowMovie subop %d", argv[1].toUint16()); - } + /* + reg_t viewObj = argv[0]; + uint16 viewId = readSelectorValue(s->_segMan, viewObj, SELECTOR(view)); + int16 loopNo = readSelectorValue(s->_segMan, viewObj, SELECTOR(loop)); + int16 celNo = readSelectorValue(s->_segMan, viewObj, SELECTOR(cel)); + //int16 leftPos = 0; + //int16 topPos = 0; + int16 priority = readSelectorValue(s->_segMan, viewObj, SELECTOR(priority)); + //int16 control = 0; + */ + + // TODO + + //warning("kDeleteScreenItem, view %d, loop %d, cel %d, pri %d", viewId, loopNo, celNo, priority); + return NULL_REG; +} + +reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) { + reg_t planeObj = argv[0]; + + g_sci->_gfxFrameout->kernelAddPlane(planeObj); + return NULL_REG; +} + +reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv) { + reg_t planeObj = argv[0]; + + g_sci->_gfxFrameout->kernelDeletePlane(planeObj); + return NULL_REG; +} + +reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) { + reg_t planeObj = argv[0]; + + g_sci->_gfxFrameout->kernelUpdatePlane(planeObj); + return s->r_acc; +} + +reg_t kRepaintPlane(EngineState *s, int argc, reg_t *argv) { + reg_t picObj = argv[0]; + + // TODO + + warning("kRepaintPlane object %04x:%04x", PRINT_REG(picObj)); + return NULL_REG; +} + +reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv) { + reg_t planeObj = argv[0]; + GuiResourceId pictureId = argv[1].toUint16(); + int16 forWidth = argv[2].toSint16(); + // argv[3] seems to be 0 most of the time + + g_sci->_gfxFrameout->kernelAddPicAt(planeObj, forWidth, pictureId); + return s->r_acc; +} + +reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_gfxFrameout->kernelGetHighPlanePri()); +} + +reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) { + // This kernel call likely seems to be doing the screen updates, + // as its called right after a view is updated + + // TODO + g_sci->_gfxFrameout->kernelFrameout(); + + return NULL_REG; +} + +reg_t kOnMe(EngineState *s, int argc, reg_t *argv) { + // Tests if the cursor is on the passed object + + uint16 x = argv[0].toUint16(); + uint16 y = argv[1].toUint16(); + reg_t targetObject = argv[2]; + uint16 illegalBits = argv[3].offset; + Common::Rect nsRect; + + // we assume that x, y are local coordinates + + // Get the bounding rectangle of the object + nsRect.left = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsLeft)); + nsRect.top = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsTop)); + nsRect.right = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsRight)); + nsRect.bottom = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsBottom)); + + // nsRect top/left may be negative, adjust accordingly + Common::Rect checkRect = nsRect; + if (checkRect.top < 0) + checkRect.top = 0; + if (checkRect.left < 0) + checkRect.left = 0; + + bool contained = checkRect.contains(x, y); + if (contained && illegalBits) { + // If illegalbits are set, we check the color of the pixel that got clicked on + // for now, we return false if the pixel is transparent + // although illegalBits may get differently set, don't know yet how this really works out + uint16 viewId = readSelectorValue(s->_segMan, targetObject, SELECTOR(view)); + int16 loopNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(loop)); + int16 celNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(cel)); + if (g_sci->_gfxCompare->kernelIsItSkip(viewId, loopNo, celNo, Common::Point(x - nsRect.left, y - nsRect.top))) + contained = false; + } +// these hacks shouldn't be needed anymore +// uint16 itemX = readSelectorValue(s->_segMan, targetObject, SELECTOR(x)); +// uint16 itemY = readSelectorValue(s->_segMan, targetObject, SELECTOR(y)); + + // If top and left are negative, we need to adjust coordinates by + // the item's x and y (e.g. happens in GK1, day 1, with detective + // Mosely's hotspot in his office) + +// if (nsRect.left < 0) +// nsRect.translate(itemX, 0); +// +// if (nsRect.top < 0) +// nsRect.translate(0, itemY); + +// // HACK: nsLeft and nsTop can be invalid, so try and fix them here +// // using x and y (e.g. with the inventory screen in GK1) +// if (nsRect.left == itemY && nsRect.top == itemX) { +// // Swap the values, as they're inversed (eh???) +// nsRect.left = itemX; +// nsRect.top = itemY; +// } + + return make_reg(0, contained); +} + +reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv) { + // Tests if the cursor is on the passed object, after adjusting the + // coordinates of the object according to the object's plane + + uint16 x = argv[0].toUint16(); + uint16 y = argv[1].toUint16(); + reg_t targetObject = argv[2]; + // TODO: argv[3] - it's usually 0 + Common::Rect nsRect; + + // Get the bounding rectangle of the object + nsRect.left = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsLeft)); + nsRect.top = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsTop)); + nsRect.right = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsRight)); + nsRect.bottom = readSelectorValue(s->_segMan, targetObject, SELECTOR(nsBottom)); + + // Get the object's plane +#if 0 + reg_t planeObject = readSelector(s->_segMan, targetObject, SELECTOR(plane)); + if (!planeObject.isNull()) { + //uint16 itemX = readSelectorValue(s->_segMan, targetObject, SELECTOR(x)); + //uint16 itemY = readSelectorValue(s->_segMan, targetObject, SELECTOR(y)); + uint16 planeResY = readSelectorValue(s->_segMan, planeObject, SELECTOR(resY)); + uint16 planeResX = readSelectorValue(s->_segMan, planeObject, SELECTOR(resX)); + uint16 planeTop = readSelectorValue(s->_segMan, planeObject, SELECTOR(top)); + uint16 planeLeft = readSelectorValue(s->_segMan, planeObject, SELECTOR(left)); + planeTop = (planeTop * g_sci->_gfxScreen->getHeight()) / planeResY; + planeLeft = (planeLeft * g_sci->_gfxScreen->getWidth()) / planeResX; + + // Adjust the bounding rectangle of the object by the object's + // actual X, Y coordinates + nsRect.top = ((nsRect.top * g_sci->_gfxScreen->getHeight()) / planeResY); + nsRect.left = ((nsRect.left * g_sci->_gfxScreen->getWidth()) / planeResX); + nsRect.bottom = ((nsRect.bottom * g_sci->_gfxScreen->getHeight()) / planeResY); + nsRect.right = ((nsRect.right * g_sci->_gfxScreen->getWidth()) / planeResX); + + nsRect.translate(planeLeft, planeTop); } +#endif + //warning("kIsOnMe: (%d, %d) on object %04x:%04x, parameter %d", argv[0].toUint16(), argv[1].toUint16(), PRINT_REG(argv[2]), argv[3].toUint16()); - if (videoDecoder) { - playVideo(videoDecoder); + return make_reg(0, nsRect.contains(x, y)); +} - // HACK: Switch back to 8bpp if we played a QuickTime video. - // We also won't be copying the screen to the SCI screen... - if (g_system->getScreenFormat().bytesPerPixel != 1) - initGraphics(screenWidth, screenHeight, screenWidth > 320); - else { - g_sci->_gfxScreen->kernelSyncWithFramebuffer(); - g_sci->_gfxPalette->kernelSyncScreenPalette(); +reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) { + // TODO: argument 0 is usually 0, and arguments 1 and 2 are usually 1 + switch (argv[0].toUint16()) { + case 0: { + if (argc != 4) { + warning("kCreateTextBitmap(0): expected 4 arguments, got %i", argc); + return NULL_REG; } + reg_t object = argv[3]; + Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text))); + break; + } + default: + warning("CreateTextBitmap(%d)", argv[0].toUint16()); } - if (reshowCursor) - g_sci->_gfxCursor->kernelShow(); - - return s->r_acc; + return NULL_REG; } -#ifdef ENABLE_SCI32 reg_t kRobot(EngineState *s, int argc, reg_t *argv) { int16 subop = argv[0].toUint16(); @@ -1234,6 +1361,10 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) { int16 x = argv[4].toUint16(); int16 y = argv[5].toUint16(); warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y); + GfxRobot *test = new GfxRobot(g_sci->getResMan(), g_sci->_gfxScreen, id); + test->draw(); + delete test; + } break; case 1: // LSL6 hires (startup) @@ -1255,140 +1386,6 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } -reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) { - uint16 operation = argv[0].toUint16(); - Graphics::VideoDecoder *videoDecoder = 0; - bool reshowCursor = g_sci->_gfxCursor->isVisible(); - Common::String fileName, warningMsg; - - switch (operation) { - case 0: // init - // This is actually meant to init the video file, but we play it instead - fileName = s->_segMan->derefString(argv[1]); - // TODO: argv[2] (usually null). When it exists, it points to an "Event" object, - // that holds no data initially (e.g. in the intro of Phantasmagoria 1 demo). - // Perhaps it's meant for syncing - if (argv[2] != NULL_REG) - warning("kPlayVMD: third parameter isn't 0 (it's %04x:%04x - %s)", PRINT_REG(argv[2]), s->_segMan->getObjectName(argv[2])); - - videoDecoder = new VMDDecoder(g_system->getMixer()); - - if (reshowCursor) - g_sci->_gfxCursor->kernelHide(); - - if (videoDecoder && videoDecoder->loadFile(fileName)) - playVideo(videoDecoder); - - if (reshowCursor) - g_sci->_gfxCursor->kernelShow(); - break; - case 1: - { - // Set VMD parameters. Called with a maximum of 6 parameters: - // - // x, y, flags, gammaBoost, gammaFirst, gammaLast - // - // Flags are as follows: - // bit 0 doubled - // bit 1 "drop frames"? - // bit 2 insert black lines - // bit 3 unknown - // bit 4 gamma correction - // bit 5 hold black frame - // bit 6 hold last frame - // bit 7 unknown - // bit 8 stretch - - // gammaBoost boosts palette colors in the range gammaFirst to - // gammaLast, but only if bit 4 in flags is set. Percent value such that - // 0% = no amplification These three parameters are optional if bit 4 is - // clear. Also note that the x, y parameters play subtle games if used - // with subfx 21. The subtleness has to do with creation of temporary - // planes and positioning relative to such planes. - - int flags = argv[3].offset; - Common::String flagspec; - - if (argc > 3) { - if (flags & 1) - flagspec += "doubled "; - if (flags & 2) - flagspec += "dropframes "; - if (flags & 4) - flagspec += "blacklines "; - if (flags & 8) - flagspec += "bit3 "; - if (flags & 16) - flagspec += "gammaboost "; - if (flags & 32) - flagspec += "holdblack "; - if (flags & 64) - flagspec += "holdlast "; - if (flags & 128) - flagspec += "bit7 "; - if (flags & 256) - flagspec += "stretch"; - - warning("VMDFlags: %s", flagspec.c_str()); - } - - warning("x, y: %d, %d", argv[1].offset, argv[2].offset); - - if (argc > 4 && flags & 16) - warning("gammaBoost: %d%% between palette entries %d and %d", argv[4].offset, argv[5].offset, argv[6].offset); - break; - } - case 6: - // Play, perhaps? Or stop? This is the last call made, and takes no extra parameters - case 14: - // Takes an additional integer parameter (e.g. 3) - case 16: - // Takes an additional parameter, usually 0 - case 21: - // Looks to be setting the video size and position. Called with 4 extra integer - // parameters (e.g. 86, 41, 235, 106) - default: - warningMsg = "PlayVMD - unsupported subop. Params: " + - Common::String::printf("%d", argc) + " ("; - - for (int i = 0; i < argc; i++) { - warningMsg += Common::String::printf("%04x:%04x", PRINT_REG(argv[i])); - warningMsg += (i == argc - 1 ? ")" : ", "); - } - - warning("%s", warningMsg.c_str()); - break; - } - - return s->r_acc; -} - #endif -reg_t kSetVideoMode(EngineState *s, int argc, reg_t *argv) { - // This call is used for KQ6's intro. It has one parameter, which is 1 when - // the intro begins, and 0 when it ends. It is suspected that this is - // actually a flag to enable video planar memory access, as the video - // decoder in KQ6 is specifically written for the planar memory model. - // Planar memory mode access was used for VGA "Mode X" (320x240 resolution, - // although the intro in KQ6 is 320x200). - // Refer to http://en.wikipedia.org/wiki/Mode_X - - //warning("STUB: SetVideoMode %d", argv[0].toUint16()); - return s->r_acc; -} - -// New calls for SCI11. Using those is only needed when using text-codes so that -// one is able to change font and/or color multiple times during kDisplay and -// kDrawControl -reg_t kTextFonts(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxText16->kernelTextFonts(argc, argv); - return s->r_acc; -} - -reg_t kTextColors(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxText16->kernelTextColors(argc, argv); - return s->r_acc; -} - } // End of namespace Sci diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp index 0701883a9b..93e95099f5 100644 --- a/engines/sci/engine/klists.cpp +++ b/engines/sci/engine/klists.cpp @@ -34,10 +34,15 @@ static bool isSaneNodePointer(SegManager *segMan, reg_t addr) { reg_t prev = addr; do { - Node *node = segMan->lookupNode(addr); + Node *node = segMan->lookupNode(addr, false); if (!node) { - error("isSaneNodePointer: Node at %04x:%04x wasn't found", PRINT_REG(addr)); + 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 { + error("isSaneNodePointer: Node at %04x:%04x wasn't found", PRINT_REG(addr)); + } return false; } @@ -70,8 +75,8 @@ static void checkListPointer(SegManager *segMan, reg_t addr) { // Empty list is fine } else if (!list->first.isNull() && !list->last.isNull()) { // Normal list - Node *node_a = segMan->lookupNode(list->first); - Node *node_z = segMan->lookupNode(list->last); + 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)); @@ -251,6 +256,19 @@ reg_t kNodeValue(EngineState *s, int argc, reg_t *argv) { 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; } @@ -294,11 +312,6 @@ reg_t kAddAfter(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } -reg_t kAddToEnd(EngineState *s, int argc, reg_t *argv) { - addToEnd(s, argv[0], argv[1]); - return s->r_acc; -} - reg_t kFindKey(EngineState *s, int argc, reg_t *argv) { reg_t node_pos; reg_t key = argv[1]; @@ -580,65 +593,134 @@ reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } -// In SCI2.1, all the list functions were merged in one 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 kAddBefore(EngineState *s, int argc, reg_t *argv) { + error("Unimplemented function kAddBefore called"); + return s->r_acc; +} + +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) { switch (argv[0].toUint16()) { - case 0: - return kNewList(s, argc - 1, argv + 1); - case 1: - return kDisposeList(s, argc - 1, argv + 1); - case 2: - return kNewNode(s, argc - 1, argv + 1); - case 3: - return kFirstNode(s, argc - 1, argv + 1); - case 4: - return kLastNode(s, argc - 1, argv + 1); - case 5: - return kEmptyList(s, argc - 1, argv + 1); - case 6: - return kNextNode(s, argc - 1, argv + 1); - case 7: - return kPrevNode(s, argc - 1, argv + 1); - case 8: - return kNodeValue(s, argc - 1, argv + 1); - case 9: - return kAddAfter(s, argc - 1, argv + 1); - case 10: - return kAddToFront(s, argc - 1, argv + 1); - case 11: - return kAddToEnd(s, argc - 1, argv + 1); - case 12: - error("kList: unimplemented subfunction kAddBefore"); - //return kAddBefore(s, argc - 1, argv + 1); - return NULL_REG; - case 13: - error("kList: unimplemented subfunction kMoveToFront"); - //return kMoveToFront(s, argc - 1, argv + 1); - return NULL_REG; - case 14: - error("kList: unimplemented subfunction kMoveToEnd"); - //return kMoveToEnd(s, argc - 1, argv + 1); - return NULL_REG; - case 15: - return kFindKey(s, argc - 1, argv + 1); - case 16: - return kDeleteKey(s, argc - 1, argv + 1); - case 17: - return kListAt(s, argc - 1, argv + 1); - case 18: - return kListIndexOf(s, argc - 1, argv + 1); - case 19: - return kListEachElementDo(s, argc - 1, argv + 1); - case 20: - return kListFirstTrue(s, argc - 1, argv + 1); - case 21: - return kListAllTrue(s, argc - 1, argv + 1); - case 22: - return kSort(s, argc - 1, argv + 1); + case 0: { // New + reg_t arrayHandle; + SciArray<reg_t> *array = s->_segMan->allocateArray(&arrayHandle); + array->setType(argv[2].toUint16()); + array->setSize(argv[1].toUint16()); + return arrayHandle; + } + case 1: { // Size + SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); + return make_reg(0, array->getSize()); + } + case 2: { // At (return value at an index) + SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); + return array->getValue(argv[2].toUint16()); + } + case 3: { // Atput (put value at an index) + SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); + + uint32 index = argv[2].toUint16(); + uint32 count = argc - 3; + + if (index + count > 65535) + break; + + if (array->getSize() < index + count) + array->setSize(index + count); + + for (uint16 i = 0; i < count; i++) + array->setValue(i + index, argv[i + 3]); + + return argv[1]; // We also have to return the handle + } + case 4: // Free + // Freeing of arrays is handled by the garbage collector + return s->r_acc; + case 5: { // Fill + SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); + uint16 index = argv[2].toUint16(); + + // A count of -1 means fill the rest of the array + uint16 count = argv[3].toSint16() == -1 ? array->getSize() - index : argv[3].toUint16(); + uint16 arraySize = array->getSize(); + + if (arraySize < index + count) + array->setSize(index + count); + + for (uint16 i = 0; i < count; i++) + array->setValue(i + index, argv[4]); + + return argv[1]; + } + case 6: { // Cpy + if (s->_segMan->getSegmentObj(argv[1].segment)->getType() != SEG_TYPE_ARRAY || + s->_segMan->getSegmentObj(argv[3].segment)->getType() != SEG_TYPE_ARRAY) { + // Happens in the RAMA demo + warning("kArray(Cpy): Request to copy a segment which isn't an array, ignoring"); + return NULL_REG; + } + + SciArray<reg_t> *array1 = s->_segMan->lookupArray(argv[1]); + SciArray<reg_t> *array2 = s->_segMan->lookupArray(argv[3]); + uint32 index1 = argv[2].toUint16(); + uint32 index2 = argv[4].toUint16(); + + // The original engine ignores bad copies too + if (index2 > array2->getSize()) + break; + + // A count of -1 means fill the rest of the array + uint32 count = argv[5].toSint16() == -1 ? array2->getSize() - index2 : argv[5].toUint16(); + + if (array1->getSize() < index1 + count) + array1->setSize(index1 + count); + + for (uint16 i = 0; i < count; i++) + array1->setValue(i + index1, array2->getValue(i + index2)); + + return argv[1]; + } + case 7: // Cmp + // Not implemented in SSCI + return s->r_acc; + case 8: { // Dup + SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]); + reg_t arrayHandle; + SciArray<reg_t> *dupArray = s->_segMan->allocateArray(&arrayHandle); + + dupArray->setType(array->getType()); + dupArray->setSize(array->getSize()); + + for (uint32 i = 0; i < array->getSize(); i++) + dupArray->setValue(i, array->getValue(i)); + + return arrayHandle; + } + case 9: // Getdata + if (!s->_segMan->isHeapObject(argv[1])) + return argv[1]; + + return readSelector(s->_segMan, argv[1], SELECTOR(data)); default: - error("kList: Unhandled case %d", argv[0].toUint16()); - return NULL_REG; + error("Unknown kArray subop %d", argv[0].toUint16()); } + + return NULL_REG; } #endif diff --git a/engines/sci/engine/kmath.cpp b/engines/sci/engine/kmath.cpp index eab964d624..bdc705cae3 100644 --- a/engines/sci/engine/kmath.cpp +++ b/engines/sci/engine/kmath.cpp @@ -29,10 +29,27 @@ namespace Sci { reg_t kRandom(EngineState *s, int argc, reg_t *argv) { - int fromNumber = argv[0].toUint16(); - int toNumber = argv[1].toUint16(); - double randomNumber = fromNumber + ((toNumber + 1.0 - fromNumber) * (rand() / (RAND_MAX + 1.0))); - return make_reg(0, (int)randomNumber); + switch (argc) { + case 1: // set seed to argv[0] + // SCI0/SCI01 just reset the seed to 0 instead of using argv[0] at all + return NULL_REG; + + case 2: { // get random number + int fromNumber = argv[0].toUint16(); + int toNumber = argv[1].toUint16(); + double randomNumber = fromNumber + ((toNumber + 1.0 - fromNumber) * (rand() / (RAND_MAX + 1.0))); + return make_reg(0, (int)randomNumber); + } + + case 3: // get seed + // SCI0/01 did not support this at all + // Actually we would have to return the previous seed + error("kRandom: scripts asked for previous seed"); + break; + + default: + error("kRandom: unsupported argc"); + } } reg_t kAbs(EngineState *s, int argc, reg_t *argv) { diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp index b8c62210f9..305e202ae9 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -368,4 +368,39 @@ reg_t kEmpty(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } +reg_t kStub(EngineState *s, int argc, reg_t *argv) { + Kernel *kernel = g_sci->getKernel(); + int kernelCallNr = -1; + + Common::List<ExecStack>::iterator callIterator = s->_executionStack.end(); + if (callIterator != s->_executionStack.begin()) { + callIterator--; + ExecStack lastCall = *callIterator; + kernelCallNr = lastCall.debugSelector; + } + + Common::String warningMsg = "Dummy function k" + kernel->getKernelName(kernelCallNr) + + Common::String::printf("[%x]", kernelCallNr) + + " invoked. Params: " + + Common::String::printf("%d", argc) + " ("; + + for (int i = 0; i < argc; i++) { + warningMsg += Common::String::printf("%04x:%04x", PRINT_REG(argv[i])); + warningMsg += (i == argc - 1 ? ")" : ", "); + } + + warning("%s", warningMsg.c_str()); + return s->r_acc; +} + +reg_t kStubNull(EngineState *s, int argc, reg_t *argv) { + kStub(s, argc, argv); + return NULL_REG; +} + +reg_t kDummy(EngineState *s, int argc, reg_t *argv) { + kStub(s, argc, argv); + error("Kernel function was called, which was considered to be unused - see log for details"); +} + } // End of namespace Sci diff --git a/engines/sci/engine/kmovement.cpp b/engines/sci/engine/kmovement.cpp index ccef3d862a..114b6eb755 100644 --- a/engines/sci/engine/kmovement.cpp +++ b/engines/sci/engine/kmovement.cpp @@ -267,6 +267,15 @@ reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) { bdelta = (int16)readSelectorValue(segMan, mover, SELECTOR(b_incr)); axis = (int16)readSelectorValue(segMan, mover, SELECTOR(b_xAxis)); + if ((getSciVersion() >= SCI_VERSION_1_LATE)) { + // Mixed-Up Fairy Tales has no xLast/yLast selectors + if (SELECTOR(xLast) != -1) { + // save last position into mover + writeSelectorValue(segMan, mover, SELECTOR(xLast), x); + writeSelectorValue(segMan, mover, SELECTOR(yLast), y); + } + } + //printf("movecnt %d, move speed %d\n", movcnt, max_movcnt); if (g_sci->_features->handleMoveCount()) { @@ -316,14 +325,24 @@ reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) { debugC(2, kDebugLevelBresen, "New data: (x,y)=(%d,%d), di=%d", x, y, bdi); + bool collision = false; + reg_t cantBeHere = NULL_REG; + if (SELECTOR(cantBeHere) != -1) { + // 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 + s->r_acc = NULL_REG; invokeSelector(s, client, SELECTOR(cantBeHere), argc, argv); - s->r_acc = make_reg(0, !s->r_acc.offset); + if (!s->r_acc.isNull()) + collision = true; + cantBeHere = s->r_acc; } else { invokeSelector(s, client, SELECTOR(canBeHere), argc, argv); + if (s->r_acc.isNull()) + collision = true; } - if (!s->r_acc.offset) { // Contains the return value + if (collision) { signal = readSelectorValue(segMan, client, SELECTOR(signal)); writeSelectorValue(segMan, client, SELECTOR(x), oldx); @@ -331,13 +350,16 @@ reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) { writeSelectorValue(segMan, client, SELECTOR(signal), (signal | kSignalHitObstacle)); debugC(2, kDebugLevelBresen, "Finished mover %04x:%04x by collision", PRINT_REG(mover)); - completed = 1; + // We shall not set completed in this case, sierra sci also doesn't do it + // if we set call .moveDone in those cases qfg1 vga gate at the castle and lsl1 casino door will not work } if ((getSciVersion() >= SCI_VERSION_1_EGA)) if (completed) invokeSelector(s, mover, SELECTOR(moveDone), argc, argv); + if (SELECTOR(cantBeHere) != -1) + return cantBeHere; return make_reg(0, completed); } diff --git a/engines/sci/engine/kparse.cpp b/engines/sci/engine/kparse.cpp index 45493a95d2..552e425906 100644 --- a/engines/sci/engine/kparse.cpp +++ b/engines/sci/engine/kparse.cpp @@ -31,6 +31,8 @@ #include "sci/engine/message.h" #include "sci/engine/kernel.h" +//#define DEBUG_PARSER + namespace Sci { /*************************************************************/ @@ -60,8 +62,8 @@ reg_t kSaid(EngineState *s, int argc, reg_t *argv) { } #ifdef DEBUG_PARSER - debugC(2, kDebugLevelParser, "Said block:", 0); - s->_voc->decipherSaidBlock(said_block); + printf("Said block: "); + g_sci->getVocabulary()->debugDecipherSaidBlock(said_block); #endif if (voc->parser_event.isNull() || (readSelectorValue(s->_segMan, voc->parser_event, SELECTOR(claimed)))) { @@ -93,6 +95,7 @@ reg_t kParse(EngineState *s, int argc, reg_t *argv) { char *error; ResultWordList words; reg_t event = argv[1]; + g_sci->checkVocabularySwitch(); Vocabulary *voc = g_sci->getVocabulary(); voc->parser_event = event; reg_t params[2] = { voc->parser_base, stringpos }; @@ -106,7 +109,7 @@ reg_t kParse(EngineState *s, int argc, reg_t *argv) { s->r_acc = make_reg(0, 1); #ifdef DEBUG_PARSER - debugC(2, kDebugLevelParser, "Parsed to the following blocks:", 0); + debugC(2, kDebugLevelParser, "Parsed to the following blocks:"); for (ResultWordList::const_iterator i = words.begin(); i != words.end(); ++i) debugC(2, kDebugLevelParser, " Type[%04x] Group[%04x]", i->_class, i->_group); diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp index fdaae3e121..fdebc0599c 100644 --- a/engines/sci/engine/kpathing.cpp +++ b/engines/sci/engine/kpathing.cpp @@ -933,9 +933,11 @@ static Common::Point *fixup_start_point(PathfindingState *s, const Common::Point case POLY_NEAREST_ACCESS: if (cont == CONT_INSIDE) { if (s->_prependPoint != NULL) { - // We shouldn't get here twice + // We shouldn't get here twice. + // We need to break in this case, otherwise we'll end in an infinite + // loop. warning("AvoidPath: start point is contained in multiple polygons"); - continue; + break; } if (s->findNearPoint(start, (*it), new_start) != PF_OK) { @@ -944,7 +946,7 @@ static Common::Point *fixup_start_point(PathfindingState *s, const Common::Point } if ((type == POLY_BARRED_ACCESS) || (type == POLY_CONTAINED_ACCESS)) - warning("AvoidPath: start position at unreachable location"); + debugC(2, kDebugLevelAvoidPath, "AvoidPath: start position at unreachable location"); // The original start position is in an invalid location, so we // use the moved point and add the original one to the final path @@ -987,9 +989,14 @@ static Common::Point *fixup_end_point(PathfindingState *s, const Common::Point & case POLY_NEAREST_ACCESS: if (cont != CONT_OUTSIDE) { if (s->_appendPoint != NULL) { - // We shouldn't get here twice + // We shouldn't get here twice. + // Happens in LB2CD, inside the speakeasy when walking from the + // speakeasy (room 310) into the bathroom (room 320), after having + // consulted the notebook (bug #3036299). + // We need to break in this case, otherwise we'll end in an infinite + // loop. warning("AvoidPath: end point is contained in multiple polygons"); - continue; + break; } // The original end position is in an invalid location, so we move the point @@ -1315,7 +1322,7 @@ static void AStar(PathfindingState *s) { } if (openSet.empty()) - warning("[avoidpath] End point (%i, %i) is unreachable", s->vertex_end->v.x, s->vertex_end->v.y); + debugC(2, kDebugLevelAvoidPath, "AvoidPath: End point (%i, %i) is unreachable", s->vertex_end->v.x, s->vertex_end->v.y); } static reg_t allocateOutputArray(SegManager *segMan, int size) { @@ -1770,4 +1777,13 @@ reg_t kMergePoly(EngineState *s, int argc, reg_t *argv) { return output; } +#ifdef ENABLE_SCI32 + +reg_t kInPolygon(EngineState *s, int argc, reg_t *argv) { + // kAvoidPath already implements this + return kAvoidPath(s, argc, argv); +} + +#endif + } // End of namespace Sci diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp index 029943b070..e211867ef9 100644 --- a/engines/sci/engine/kscripts.cpp +++ b/engines/sci/engine/kscripts.cpp @@ -38,7 +38,7 @@ 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 = (ResourceType)(argv[0].toUint16() & 0x7f); + ResourceType restype = g_sci->getResMan()->convertResType(argv[0].toUint16()); int resnr = argv[1].toUint16(); // Request to dynamically allocate hunk memory for later use @@ -53,7 +53,7 @@ reg_t kLoad(EngineState *s, int argc, reg_t *argv) { // 1 or 3+ parameters is not right according to sierra sci reg_t kUnLoad(EngineState *s, int argc, reg_t *argv) { if (argc >= 2) { - ResourceType restype = (ResourceType)(argv[0].toUint16() & 0x7f); + ResourceType restype = g_sci->getResMan()->convertResType(argv[0].toUint16()); reg_t resnr = argv[1]; // WORKAROUND for a broken script in room 320 in Castle of Dr. Brain. @@ -73,7 +73,7 @@ reg_t kUnLoad(EngineState *s, int argc, reg_t *argv) { reg_t kLock(EngineState *s, int argc, reg_t *argv) { int state = argc > 2 ? argv[2].toUint16() : 1; - ResourceType type = (ResourceType)(argv[0].toUint16() & 0x7f); + ResourceType type = g_sci->getResMan()->convertResType(argv[0].toUint16()); ResourceId id = ResourceId(type, argv[1].toUint16()); Resource *which; @@ -104,8 +104,10 @@ reg_t kLock(EngineState *s, int argc, reg_t *argv) { if (id.getType() == kResourceTypeInvalid) warning("[resMan] Attempt to unlock resource %i of invalid type %i", id.getNumber(), type); else - // Happens in CD games (e.g. LSL6CD) with the message resource - warning("[resMan] Attempt to unlock non-existant resource %s", id.toString().c_str()); + // Happens in CD games (e.g. LSL6CD) with the message + // resource. It isn't fatal, and it's usually caused + // by leftover scripts. + debugC(2, kDebugLevelResMan, "[resMan] Attempt to unlock non-existant resource %s", id.toString().c_str()); } } break; @@ -115,7 +117,7 @@ reg_t kLock(EngineState *s, int argc, reg_t *argv) { reg_t kResCheck(EngineState *s, int argc, reg_t *argv) { Resource *res = NULL; - ResourceType restype = (ResourceType)(argv[0].toUint16() & 0x7f); + ResourceType restype = g_sci->getResMan()->convertResType(argv[0].toUint16()); if (restype == kResourceTypeVMD) { char fileName[10]; diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp index 69ae68674b..4e5ddc5e96 100644 --- a/engines/sci/engine/ksound.cpp +++ b/engines/sci/engine/ksound.cpp @@ -54,7 +54,7 @@ CREATE_DOSOUND_FORWARD(DoSoundMute) CREATE_DOSOUND_FORWARD(DoSoundStop) CREATE_DOSOUND_FORWARD(DoSoundStopAll) CREATE_DOSOUND_FORWARD(DoSoundPause) -CREATE_DOSOUND_FORWARD(DoSoundResume) +CREATE_DOSOUND_FORWARD(DoSoundResumeAfterRestore) CREATE_DOSOUND_FORWARD(DoSoundMasterVolume) CREATE_DOSOUND_FORWARD(DoSoundUpdate) CREATE_DOSOUND_FORWARD(DoSoundFade) diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index f2f9543ad2..9254bce9c1 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -566,7 +566,7 @@ reg_t kMessage(EngineState *s, int argc, reg_t *argv) { } reg_t kSetQuitStr(EngineState *s, int argc, reg_t *argv) { - Common::String quitStr = s->_segMan->getString(argv[0]); + //Common::String quitStr = s->_segMan->getString(argv[0]); //debug("Setting quit string to '%s'", quitStr.c_str()); return s->r_acc; } @@ -592,4 +592,193 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) { return argv[0]; } +#ifdef ENABLE_SCI32 + +reg_t kText(EngineState *s, int argc, reg_t *argv) { + switch (argv[0].toUint16()) { + case 0: + return kTextSize(s, argc - 1, argv + 1); + default: + // TODO: Other subops here too, perhaps kTextColors and kTextFonts + warning("kText(%d)", argv[0].toUint16()); + break; + } + + return s->r_acc; +} + +reg_t kString(EngineState *s, int argc, reg_t *argv) { + switch (argv[0].toUint16()) { + case 0: { // New + reg_t stringHandle; + SciString *string = s->_segMan->allocateString(&stringHandle); + string->setSize(argv[1].toUint16()); + + // Make sure the first character is a null character + if (string->getSize() > 0) + string->setValue(0, 0); + + return stringHandle; + } + case 1: // Size + return make_reg(0, s->_segMan->getString(argv[1]).size()); + case 2: { // At (return value at an index) + if (argv[1].segment == s->_segMan->getStringSegmentId()) + return make_reg(0, s->_segMan->lookupString(argv[1])->getRawData()[argv[2].toUint16()]); + + return make_reg(0, s->_segMan->getString(argv[1])[argv[2].toUint16()]); + } + case 3: { // Atput (put value at an index) + SciString *string = s->_segMan->lookupString(argv[1]); + + uint32 index = argv[2].toUint16(); + uint32 count = argc - 3; + + if (index + count > 65535) + break; + + if (string->getSize() < index + count) + string->setSize(index + count); + + for (uint16 i = 0; i < count; i++) + string->setValue(i + index, argv[i + 3].toUint16()); + + return argv[1]; // We also have to return the handle + } + case 4: // Free + // Freeing of strings is handled by the garbage collector + return s->r_acc; + case 5: { // Fill + SciString *string = s->_segMan->lookupString(argv[1]); + uint16 index = argv[2].toUint16(); + + // A count of -1 means fill the rest of the array + uint16 count = argv[3].toSint16() == -1 ? string->getSize() - index : argv[3].toUint16(); + uint16 stringSize = string->getSize(); + + if (stringSize < index + count) + string->setSize(index + count); + + for (uint16 i = 0; i < count; i++) + string->setValue(i + index, argv[4].toUint16()); + + return argv[1]; + } + case 6: { // Cpy + const char *string2 = 0; + uint32 string2Size = 0; + + if (argv[3].segment == s->_segMan->getStringSegmentId()) { + SciString *string = s->_segMan->lookupString(argv[3]); + string2 = string->getRawData(); + string2Size = string->getSize(); + } else { + Common::String string = s->_segMan->getString(argv[3]); + string2 = string.c_str(); + string2Size = string.size() + 1; + } + + uint32 index1 = argv[2].toUint16(); + uint32 index2 = argv[4].toUint16(); + + // The original engine ignores bad copies too + if (index2 > string2Size) + break; + + // A count of -1 means fill the rest of the array + uint32 count = argv[5].toSint16() == -1 ? string2Size - index2 + 1 : argv[5].toUint16(); + + // We have a special case here for argv[1] being a system string + if (argv[1].segment == s->_segMan->getSysStringsSegment()) { + // Resize if necessary + const uint16 sysStringId = argv[1].toUint16(); + SystemString *sysString = s->_segMan->getSystemString(sysStringId); + assert(sysString); + if ((uint32)sysString->_maxSize < index1 + count) { + free(sysString->_value); + sysString->_maxSize = index1 + count; + sysString->_value = (char *)calloc(index1 + count, sizeof(char)); + } + + strncpy(sysString->_value + index1, string2 + index2, count); + } else { + SciString *string1 = s->_segMan->lookupString(argv[1]); + + if (string1->getSize() < index1 + count) + string1->setSize(index1 + count); + + // Note: We're accessing from c_str() here because the + // string's size ignores the trailing 0 and therefore + // triggers an assert when doing string2[i + index2]. + for (uint16 i = 0; i < count; i++) + string1->setValue(i + index1, string2[i + index2]); + } + + } return argv[1]; + case 7: { // Cmp + Common::String string1 = argv[1].isNull() ? "" : s->_segMan->getString(argv[1]); + Common::String string2 = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]); + + if (argc == 4) // Strncmp + return make_reg(0, strncmp(string1.c_str(), string2.c_str(), argv[3].toUint16())); + else // Strcmp + return make_reg(0, strcmp(string1.c_str(), string2.c_str())); + } + case 8: { // Dup + const char *rawString = 0; + uint32 size = 0; + + if (argv[1].segment == s->_segMan->getStringSegmentId()) { + SciString *string = s->_segMan->lookupString(argv[1]); + rawString = string->getRawData(); + size = string->getSize(); + } else { + Common::String string = s->_segMan->getString(argv[1]); + rawString = string.c_str(); + size = string.size() + 1; + } + + reg_t stringHandle; + SciString *dupString = s->_segMan->allocateString(&stringHandle); + dupString->setSize(size); + + for (uint32 i = 0; i < size; i++) + dupString->setValue(i, rawString[i]); + + return stringHandle; + } + case 9: // Getdata + if (!s->_segMan->isHeapObject(argv[1])) + return argv[1]; + + return readSelector(s->_segMan, argv[1], SELECTOR(data)); + case 10: // Stringlen + return make_reg(0, s->_segMan->strlen(argv[1])); + case 11: { // Printf + reg_t stringHandle; + s->_segMan->allocateString(&stringHandle); + + reg_t *adjustedArgs = new reg_t[argc]; + adjustedArgs[0] = stringHandle; + memcpy(&adjustedArgs[1], argv + 1, (argc - 1) * sizeof(reg_t)); + + kFormat(s, argc, adjustedArgs); + delete[] adjustedArgs; + return stringHandle; + } + case 12: // Printf Buf + return kFormat(s, argc - 1, argv + 1); + case 13: { // atoi + Common::String string = s->_segMan->getString(argv[1]); + return make_reg(0, (uint16)atoi(string.c_str())); + } + default: + error("Unknown kString subop %d", argv[0].toUint16()); + } + + return NULL_REG; +} + +#endif + } // End of namespace Sci diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp new file mode 100644 index 0000000000..cd103dade7 --- /dev/null +++ b/engines/sci/engine/kvideo.cpp @@ -0,0 +1,300 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "engines/util.h" +#include "sci/engine/state.h" +#include "sci/graphics/helpers.h" +#include "sci/graphics/cursor.h" +#include "sci/graphics/palette.h" +#include "sci/graphics/screen.h" +#include "graphics/cursorman.h" +#include "graphics/video/avi_decoder.h" +#include "graphics/video/qt_decoder.h" +#include "sci/video/seq_decoder.h" +#ifdef ENABLE_SCI32 +#include "sci/video/vmd_decoder.h" +#endif + +namespace Sci { + +void playVideo(Graphics::VideoDecoder *videoDecoder) { + if (!videoDecoder) + return; + + byte *scaleBuffer = 0; + uint16 width = videoDecoder->getWidth(); + uint16 height = videoDecoder->getHeight(); + uint16 screenWidth = g_system->getWidth(); + uint16 screenHeight = g_system->getHeight(); + + if (screenWidth == 640 && width <= 320 && height <= 240) { + assert(videoDecoder->getPixelFormat().bytesPerPixel == 1); + width *= 2; + height *= 2; + scaleBuffer = new byte[width * height]; + } + + uint16 x = (screenWidth - width) / 2; + uint16 y = (screenHeight - height) / 2; + bool skipVideo = false; + + while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { + if (videoDecoder->needsUpdate()) { + Graphics::Surface *frame = videoDecoder->decodeNextFrame(); + if (frame) { + if (scaleBuffer) { + // TODO: Probably should do aspect ratio correction in e.g. GK1 Windows + g_sci->_gfxScreen->scale2x((byte *)frame->pixels, scaleBuffer, videoDecoder->getWidth(), videoDecoder->getHeight()); + g_system->copyRectToScreen(scaleBuffer, width, x, y, width, height); + } else + g_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, width, height); + + if (videoDecoder->hasDirtyPalette()) + videoDecoder->setSystemPalette(); + + 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; + } + + g_system->delayMillis(10); + } + + delete[] scaleBuffer; + delete videoDecoder; +} + +reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { + // 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(); + + uint16 screenWidth = g_system->getWidth(); + uint16 screenHeight = g_system->getHeight(); + + Graphics::VideoDecoder *videoDecoder = 0; + + if (argv[0].segment != 0) { + Common::String filename = s->_segMan->getString(argv[0]); + + if (g_sci->getPlatform() == Common::kPlatformMacintosh) { + // Mac QuickTime + // The only argument is the string for the video + + // HACK: Switch to 16bpp graphics for Cinepak. + initGraphics(screenWidth, screenHeight, screenWidth > 320, NULL); + + if (g_system->getScreenFormat().bytesPerPixel == 1) { + error("This video requires >8bpp color to be displayed, but could not switch to RGB color mode."); + return NULL_REG; + } + + videoDecoder = new Graphics::QuickTimeDecoder(); + if (!videoDecoder->loadFile(filename)) + error("Could not open '%s'", filename.c_str()); + } else { + // DOS SEQ + // SEQ's are called with no subops, just the string and delay + SeqDecoder *seqDecoder = new SeqDecoder(); + seqDecoder->setFrameDelay(argv[1].toUint16()); // Time between frames in ticks + videoDecoder = seqDecoder; + + if (!videoDecoder->loadFile(filename)) { + warning("Failed to open movie file %s", filename.c_str()); + delete videoDecoder; + videoDecoder = 0; + } + } + } else { + // Windows AVI + // TODO: This appears to be some sort of subop. case 0 contains the string + // for the video, so we'll just play it from there for now. + +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2_1) { + // SCI2.1 always has argv[0] as 1, the rest of the arguments seem to + // follow SCI1.1/2. + if (argv[0].toUint16() != 1) + error("SCI2.1 kShowMovie argv[0] not 1"); + argv++; + argc--; + } +#endif + switch (argv[0].toUint16()) { + case 0: { + Common::String filename = s->_segMan->getString(argv[1]); + videoDecoder = new Graphics::AviDecoder(g_system->getMixer()); + + if (!videoDecoder->loadFile(filename.c_str())) { + warning("Failed to open movie file %s", filename.c_str()); + delete videoDecoder; + videoDecoder = 0; + } + break; + } + default: + warning("Unhandled SCI kShowMovie subop %d", argv[1].toUint16()); + } + } + + if (videoDecoder) { + playVideo(videoDecoder); + + // HACK: Switch back to 8bpp if we played a QuickTime video. + // We also won't be copying the screen to the SCI screen... + if (g_system->getScreenFormat().bytesPerPixel != 1) + initGraphics(screenWidth, screenHeight, screenWidth > 320); + else { + g_sci->_gfxScreen->kernelSyncWithFramebuffer(); + g_sci->_gfxPalette->kernelSyncScreenPalette(); + } + } + + if (reshowCursor) + g_sci->_gfxCursor->kernelShow(); + + return s->r_acc; +} + +#ifdef ENABLE_SCI32 + +reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) { + uint16 operation = argv[0].toUint16(); + Graphics::VideoDecoder *videoDecoder = 0; + bool reshowCursor = g_sci->_gfxCursor->isVisible(); + Common::String fileName, warningMsg; + + switch (operation) { + case 0: // init + // This is actually meant to init the video file, but we play it instead + fileName = s->_segMan->derefString(argv[1]); + // TODO: argv[2] (usually null). When it exists, it points to an "Event" object, + // that holds no data initially (e.g. in the intro of Phantasmagoria 1 demo). + // Perhaps it's meant for syncing + if (argv[2] != NULL_REG) + warning("kPlayVMD: third parameter isn't 0 (it's %04x:%04x - %s)", PRINT_REG(argv[2]), s->_segMan->getObjectName(argv[2])); + + videoDecoder = new VMDDecoder(g_system->getMixer()); + + if (reshowCursor) + g_sci->_gfxCursor->kernelHide(); + + if (videoDecoder && videoDecoder->loadFile(fileName)) + playVideo(videoDecoder); + + if (reshowCursor) + g_sci->_gfxCursor->kernelShow(); + break; + case 1: + { + // Set VMD parameters. Called with a maximum of 6 parameters: + // + // x, y, flags, gammaBoost, gammaFirst, gammaLast + // + // Flags are as follows: + // bit 0 doubled + // bit 1 "drop frames"? + // bit 2 insert black lines + // bit 3 unknown + // bit 4 gamma correction + // bit 5 hold black frame + // bit 6 hold last frame + // bit 7 unknown + // bit 8 stretch + + // gammaBoost boosts palette colors in the range gammaFirst to + // gammaLast, but only if bit 4 in flags is set. Percent value such that + // 0% = no amplification These three parameters are optional if bit 4 is + // clear. Also note that the x, y parameters play subtle games if used + // with subfx 21. The subtleness has to do with creation of temporary + // planes and positioning relative to such planes. + + int flags = argv[3].offset; + Common::String flagspec; + + if (argc > 3) { + if (flags & 1) + flagspec += "doubled "; + if (flags & 2) + flagspec += "dropframes "; + if (flags & 4) + flagspec += "blacklines "; + if (flags & 8) + flagspec += "bit3 "; + if (flags & 16) + flagspec += "gammaboost "; + if (flags & 32) + flagspec += "holdblack "; + if (flags & 64) + flagspec += "holdlast "; + if (flags & 128) + flagspec += "bit7 "; + if (flags & 256) + flagspec += "stretch"; + + warning("VMDFlags: %s", flagspec.c_str()); + } + + warning("x, y: %d, %d", argv[1].offset, argv[2].offset); + + if (argc > 4 && flags & 16) + warning("gammaBoost: %d%% between palette entries %d and %d", argv[4].offset, argv[5].offset, argv[6].offset); + break; + } + case 6: + // Play, perhaps? Or stop? This is the last call made, and takes no extra parameters + case 14: + // Takes an additional integer parameter (e.g. 3) + case 16: + // Takes an additional parameter, usually 0 + case 21: + // Looks to be setting the video size and position. Called with 4 extra integer + // parameters (e.g. 86, 41, 235, 106) + default: + warningMsg = "PlayVMD - unsupported subop. Params: " + + Common::String::printf("%d", argc) + " ("; + + for (int i = 0; i < argc; i++) { + warningMsg += Common::String::printf("%04x:%04x", PRINT_REG(argv[i])); + warningMsg += (i == argc - 1 ? ")" : ", "); + } + + warning("%s", warningMsg.c_str()); + break; + } + + return s->r_acc; +} + +#endif + +} // End of namespace Sci diff --git a/engines/sci/engine/message.cpp b/engines/sci/engine/message.cpp index 18e60eb521..cdecc556e8 100644 --- a/engines/sci/engine/message.cpp +++ b/engines/sci/engine/message.cpp @@ -380,7 +380,14 @@ void MessageState::outputString(reg_t buf, const Common::String &str) { if ((unsigned)buffer_r.maxSize >= str.size() + 1) { _segMan->strcpy(buf, str.c_str()); } 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()); + // 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 #3035533 + 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) diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index 0fe5f2088a..806c8893b4 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -40,6 +40,7 @@ #include "sci/engine/selector.h" #include "sci/engine/vm_types.h" #include "sci/engine/script.h" // for SCI_OBJ_EXPORTS and SCI_OBJ_SYNONYMS +#include "sci/graphics/palette.h" #include "sci/graphics/ports.h" #include "sci/sound/audio.h" #include "sci/sound/music.h" @@ -64,47 +65,20 @@ const uint32 INTMAPPER_MAGIC_KEY = 0xDEADBEEF; #define DEFROBNICATE_HANDLE(handle) (make_reg((handle >> 16) & 0xffff, handle & 0xffff)) void MusicEntry::saveLoadWithSerializer(Common::Serializer &s) { - if (s.getVersion() < 14) { - // Old sound system data. This data is only loaded, never saved (as we're never - // saving in the older version format) - uint32 handle = 0; - s.syncAsSint32LE(handle); - soundObj = DEFROBNICATE_HANDLE(handle); - s.syncAsSint32LE(resourceId); - s.syncAsSint32LE(priority); - s.syncAsSint32LE(status); - s.skip(4); // restoreBehavior - uint32 restoreTime = 0; - s.syncAsSint32LE(restoreTime); - ticker = restoreTime * 60 / 1000; - s.syncAsSint32LE(loop); - s.syncAsSint32LE(hold); - // volume and dataInc will be synced from the sound objects - // when the sound list is reconstructed in gamestate_restore() - volume = MUSIC_VOLUME_MAX; - dataInc = 0; - // No fading info - fadeTo = 0; - fadeStep = 0; - fadeTicker = 0; - fadeTickerStep = 0; - } else { - // A bit more optimized saving - soundObj.saveLoadWithSerializer(s); - s.syncAsSint16LE(resourceId); - s.syncAsSint16LE(dataInc); - s.syncAsSint16LE(ticker); - s.syncAsSint16LE(signal, VER(17)); - s.syncAsByte(priority); - s.syncAsSint16LE(loop, VER(17)); - s.syncAsByte(volume); - s.syncAsByte(hold, VER(17)); - s.syncAsByte(fadeTo); - s.syncAsSint16LE(fadeStep); - s.syncAsSint32LE(fadeTicker); - s.syncAsSint32LE(fadeTickerStep); - s.syncAsByte(status); - } + soundObj.saveLoadWithSerializer(s); + s.syncAsSint16LE(resourceId); + s.syncAsSint16LE(dataInc); + s.syncAsSint16LE(ticker); + s.syncAsSint16LE(signal, VER(17)); + s.syncAsByte(priority); + s.syncAsSint16LE(loop, VER(17)); + s.syncAsByte(volume); + s.syncAsByte(hold, VER(17)); + s.syncAsByte(fadeTo); + s.syncAsSint16LE(fadeStep); + s.syncAsSint32LE(fadeTicker); + s.syncAsSint32LE(fadeTickerStep); + s.syncAsByte(status); // pMidiParser and pStreamAud will be initialized when the // sound list is reconstructed in gamestate_restore() @@ -181,7 +155,7 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) { if (s.isLoading()) resetSegMan(); - s.skip(4, VER(12), VER(18)); // OBSOLETE: Used to be _exportsAreWide + s.skip(4, VER(14), VER(18)); // OBSOLETE: Used to be _exportsAreWide if (s.isLoading()) { // Reset _scriptSegMap, to be restored below @@ -256,40 +230,9 @@ static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj) void EngineState::saveLoadWithSerializer(Common::Serializer &s) { Common::String tmp; - s.syncString(tmp, VER(12), VER(23)); // OBSOLETE: Used to be game_version - - // OBSOLETE: Saved menus. Skip all of the saved data - if (s.getVersion() < 14) { - int totalMenus = 0; - s.syncAsUint32LE(totalMenus); - - // Now iterate through the obsolete saved menu data - for (int i = 0; i < totalMenus; i++) { - s.syncString(tmp); // OBSOLETE: Used to be _title - s.skip(4, VER(12), VER(12)); // OBSOLETE: Used to be _titleWidth - s.skip(4, VER(12), VER(12)); // OBSOLETE: Used to be _width - - int menuLength = 0; - s.syncAsUint32LE(menuLength); - - for (int j = 0; j < menuLength; j++) { - s.skip(4, VER(12), VER(12)); // OBSOLETE: Used to be _type - s.syncString(tmp); // OBSOLETE: Used to be _keytext - - s.skip(4, VER(12), VER(12)); // OBSOLETE: Used to be _flags - s.skip(64, VER(12), VER(12)); // OBSOLETE: Used to be MENU_SAID_SPEC_SIZE - s.skip(4, VER(12), VER(12)); // OBSOLETE: Used to be _saidPos - s.syncString(tmp); // OBSOLETE: Used to be _text - s.skip(4, VER(12), VER(12)); // OBSOLETE: Used to be _textPos - s.skip(4 * 4, VER(12), VER(12)); // OBSOLETE: Used to be _modifiers, _key, _enabled and _tag - } - } - } + s.syncString(tmp, VER(14), VER(23)); // OBSOLETE: Used to be game_version - s.skip(4, VER(12), VER(12)); // obsolete: used to be status_bar_foreground - s.skip(4, VER(12), VER(12)); // obsolete: used to be status_bar_background - - if (s.getVersion() >= 13 && getSciVersion() <= SCI_VERSION_1_1) { + if (getSciVersion() <= SCI_VERSION_1_1) { // Save/Load picPort as well for SCI0-SCI1.1. Necessary for Castle of Dr. Brain, // as the picPort has been changed when loading during the intro int16 picPortTop, picPortLeft; @@ -312,6 +255,7 @@ void EngineState::saveLoadWithSerializer(Common::Serializer &s) { _segMan->saveLoadWithSerializer(s); g_sci->_soundCmd->syncPlayList(s); + g_sci->_gfxPalette->saveLoadWithSerializer(s); } void LocalVariables::saveLoadWithSerializer(Common::Serializer &s) { @@ -323,7 +267,6 @@ void LocalVariables::saveLoadWithSerializer(Common::Serializer &s) { void Object::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsSint32LE(_flags); _pos.saveLoadWithSerializer(s); - s.skip(4, VER(12), VER(12)); // OBSOLETE: Used to be variable_names_nr s.syncAsSint32LE(_methodCount); // that's actually a uint16 syncArray<reg_t>(s, _variables); @@ -449,12 +392,12 @@ void Script::saveLoadWithSerializer(Common::Serializer &s) { if (s.isLoading()) init(_nr, g_sci->getResMan()); - s.skip(4, VER(12), VER(22)); // OBSOLETE: Used to be _bufSize - s.skip(4, VER(12), VER(22)); // OBSOLETE: Used to be _scriptSize - s.skip(4, VER(12), VER(22)); // OBSOLETE: Used to be _heapSize + s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _bufSize + s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _scriptSize + s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _heapSize - s.skip(4, VER(12), VER(19)); // OBSOLETE: Used to be _numExports - s.skip(4, VER(12), VER(19)); // OBSOLETE: Used to be _numSynonyms + s.skip(4, VER(14), VER(19)); // OBSOLETE: Used to be _numExports + s.skip(4, VER(14), VER(19)); // OBSOLETE: Used to be _numSynonyms s.syncAsSint32LE(_lockers); // Sync _objects. This is a hashmap, and we use the following on disk format: @@ -482,7 +425,7 @@ void Script::saveLoadWithSerializer(Common::Serializer &s) { } } - s.skip(4, VER(12), VER(20)); // OBSOLETE: Used to be _localsOffset + s.skip(4, VER(14), VER(20)); // OBSOLETE: Used to be _localsOffset s.syncAsSint32LE(_localsSegment); s.syncAsSint32LE(_markedAsDeleted); @@ -599,14 +542,6 @@ void SoundCommandParser::reconstructPlayList(int savegame_version) { (*i)->soundRes = 0; } if ((*i)->status == kSoundPlaying) { - if (savegame_version < 14) { - (*i)->dataInc = readSelectorValue(_segMan, (*i)->soundObj, SELECTOR(dataInc)); - (*i)->signal = readSelectorValue(_segMan, (*i)->soundObj, SELECTOR(signal)); - - if (_soundVersion >= SCI_VERSION_1_LATE) - (*i)->volume = readSelectorValue(_segMan, (*i)->soundObj, SELECTOR(vol)); - } - processPlaySound((*i)->soundObj); } } @@ -628,6 +563,42 @@ void StringTable::saveLoadWithSerializer(Common::Serializer &ser) { } #endif +void GfxPalette::palVarySaveLoadPalette(Common::Serializer &s, Palette *palette) { + s.syncBytes(palette->mapping, 256); + s.syncAsUint32LE(palette->timestamp); + for (int i = 0; i < 256; i++) { + s.syncAsByte(palette->colors[i].used); + s.syncAsByte(palette->colors[i].r); + s.syncAsByte(palette->colors[i].g); + s.syncAsByte(palette->colors[i].b); + } + s.syncBytes(palette->intensity, 256); +} + +void GfxPalette::saveLoadWithSerializer(Common::Serializer &s) { + if (s.getVersion() < 24) + return; + + if (s.isLoading() && _palVaryResourceId != -1) + palVaryRemoveTimer(); + + s.syncAsSint32LE(_palVaryResourceId); + if (_palVaryResourceId != -1) { + palVarySaveLoadPalette(s, &_palVaryOriginPalette); + palVarySaveLoadPalette(s, &_palVaryTargetPalette); + s.syncAsSint16LE(_palVaryStep); + s.syncAsSint16LE(_palVaryStepStop); + s.syncAsSint16LE(_palVaryDirection); + s.syncAsUint16LE(_palVaryTicks); + s.syncAsSint32LE(_palVaryPaused); + } + + if (s.isLoading() && _palVaryResourceId != -1) { + _palVarySignal = 0; + palVaryInstallTimer(); + } +} + void SegManager::reconstructStack(EngineState *s) { DataStack *stack = (DataStack *)(_heap[findSegmentByType(SEG_TYPE_STACK)]); s->stack_base = stack->_entries; @@ -722,6 +693,7 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const char* savenam meta.script0_size = script0->size; meta.game_object_offset = g_sci->getGameObject().offset; + // Checking here again if (s->executionStackBase) { warning("Cannot save from below kernel function"); return false; @@ -804,7 +776,6 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { s->_msgState = new MessageState(s->_segMan); s->abortScriptProcessing = kAbortLoadGame; - s->shrinkStackToBase(); } bool get_savegame_metadata(Common::SeekableReadStream *stream, SavegameMetadata *meta) { diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h index 9a882f2bf7..fc254ba33d 100644 --- a/engines/sci/engine/savegame.h +++ b/engines/sci/engine/savegame.h @@ -36,8 +36,8 @@ namespace Sci { struct EngineState; enum { - CURRENT_SAVEGAME_VERSION = 23, - MINIMUM_SAVEGAME_VERSION = 12 + CURRENT_SAVEGAME_VERSION = 24, + MINIMUM_SAVEGAME_VERSION = 14 }; // Savegame metadata diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index a293f81d2f..645094d9ec 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -434,13 +434,19 @@ void Script::initialiseClasses(SegManager *segMan) { } if (isClass) { - // WORKAROUND for an invalid species access in the demo of LSL2 + // WORKAROUNDs for off-by-one script errors if (g_sci->getGameId() == GID_LSL2 && g_sci->isDemo() && species == (int)segMan->classTableSize()) segMan->resizeClassTable(segMan->classTableSize() + 1); + if (g_sci->getGameId() == GID_LSL3 && !g_sci->isDemo() && _nr == 500 && species == (int)segMan->classTableSize()) + segMan->resizeClassTable(segMan->classTableSize() + 1); + if (g_sci->getGameId() == GID_SQ3 && !g_sci->isDemo() && _nr == 93 && species == (int)segMan->classTableSize()) + segMan->resizeClassTable(segMan->classTableSize() + 1); + if (g_sci->getGameId() == GID_SQ3 && !g_sci->isDemo() && _nr == 99 && species == (int)segMan->classTableSize()) + segMan->resizeClassTable(segMan->classTableSize() + 1); if (species < 0 || species >= (int)segMan->classTableSize()) - error("Invalid species %d(0x%x) not in interval [0,%d) while instantiating script %d\n", - species, species, segMan->classTableSize(), _nr); + error("Invalid species %d(0x%x) unknown max %d(0x%x) while instantiating script %d\n", + species, species, segMan->classTableSize(), segMan->classTableSize(), _nr); SegmentId segmentId = segMan->getScriptSegment(_nr); segMan->setClassOffset(species, make_reg(segmentId, classpos)); @@ -468,8 +474,10 @@ void Script::initialiseObjectsSci0(SegManager *segMan, SegmentId segmentId) { obj->initSpecies(segMan, addr); if (!obj->initBaseObject(segMan, addr)) { - if (_nr == 202 && g_sci->getGameId() == GID_KQ5 && g_sci->getSciLanguage() == K_LANG_FRENCH) { - // Script 202 of KQ5 French has an invalid object. This is non-fatal. + if (_nr == 202 && g_sci->getGameId() == GID_KQ5) { + // WORKAROUND: Script 202 of KQ5 French and German + // (perhaps Spanish too?) has an invalid object. + // This is non-fatal. Refer to bug #3035396. } else { error("Failed to locate base object for object at %04X:%04X; skipping", PRINT_REG(addr)); } diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp index 915a6fa994..9c08526fbb 100644 --- a/engines/sci/engine/scriptdebug.cpp +++ b/engines/sci/engine/scriptdebug.cpp @@ -488,48 +488,7 @@ void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) { Common::hexdump(script->data + seeker, objsize - 4, 16, seeker); printf("%04x: ", seeker); - while (seeker < _seeker) { - unsigned char nextitem = script->data [seeker++]; - if (nextitem == 0xFF) - printf("\n%04x: ", seeker); - else if (nextitem >= 0xF0) { - switch (nextitem) { - case 0xf0: - printf(", "); - break; - case 0xf1: - printf("& "); - break; - case 0xf2: - printf("/ "); - break; - case 0xf3: - printf("( "); - break; - case 0xf4: - printf(") "); - break; - case 0xf5: - printf("[ "); - break; - case 0xf6: - printf("] "); - break; - case 0xf7: - printf("# "); - break; - case 0xf8: - printf("< "); - break; - case 0xf9: - printf("> "); - break; - } - } else { - nextitem = nextitem << 8 | script->data [seeker++]; - printf("%s[%03x] ", vocab->getAnyWordFromGroup(nextitem), nextitem); - } - } + vocab->debugDecipherSaidBlock(script->data + seeker); printf("\n"); break; diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp index ef2279e492..25cf1d069f 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -508,7 +508,7 @@ List *SegManager::lookupList(reg_t addr) { return &(lt->_table[addr.offset]); } -Node *SegManager::lookupNode(reg_t addr) { +Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) { if (addr.isNull()) return NULL; // Non-error null @@ -522,6 +522,9 @@ Node *SegManager::lookupNode(reg_t addr) { NodeTable *nt = (NodeTable *)_heap[addr.segment]; if (!nt->isValidEntry(addr.offset)) { + if (!stopOnDiscarded) + return NULL; + error("Attempt to use invalid or discarded reference %04x:%04x as list node", PRINT_REG(addr)); return NULL; } diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h index a7f5f8517f..e0808dbb1b 100644 --- a/engines/sci/engine/seg_manager.h +++ b/engines/sci/engine/seg_manager.h @@ -232,7 +232,7 @@ public: * @param addr The address to resolve * @return The list node referenced, or NULL on error */ - Node *lookupNode(reg_t addr); + Node *lookupNode(reg_t addr, bool stopOnDiscarded = true); // 8. Hunk Memory diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index 00480743cc..f5eb9eb73a 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -104,6 +104,8 @@ void Kernel::mapSelectors() { 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); @@ -176,6 +178,13 @@ void Kernel::mapSelectors() { FIND_SELECTOR(dimmed); FIND_SELECTOR(fore); FIND_SELECTOR(back); + FIND_SELECTOR(fixPriority); + FIND_SELECTOR(mirrored); + FIND_SELECTOR(useInsetRect); + FIND_SELECTOR(inTop); + FIND_SELECTOR(inLeft); + FIND_SELECTOR(inBottom); + FIND_SELECTOR(inRight); #endif } @@ -236,7 +245,7 @@ void invokeSelector(EngineState *s, reg_t object, int selectorId, xstack->sp += argc + 2; xstack->fp += argc + 2; - run_vm(s, false); // Start a new vm + run_vm(s); // Start a new vm } SelectorType lookupSelector(SegManager *segMan, reg_t obj_location, Selector selectorId, ObjVarRef *varp, reg_t *fptr) { diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h index acb7912d8d..661290f58c 100644 --- a/engines/sci/engine/selector.h +++ b/engines/sci/engine/selector.h @@ -69,6 +69,7 @@ struct SelectorCache { 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 @@ -141,6 +142,12 @@ struct SelectorCache { Selector fore; Selector back; Selector dimmed; + + Selector fixPriority; + Selector mirrored; + + Selector useInsetRect; + Selector inTop, inLeft, inBottom, inRight; #endif }; diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index 36b03c0ad9..a069344d61 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -324,4 +324,17 @@ Common::String SciEngine::strSplit(const char *str, const char *sep) { return retval; } +void SciEngine::checkVocabularySwitch() { + uint16 parserLanguage = 1; + if (SELECTOR(parseLang) != -1) + parserLanguage = readSelectorValue(_gamestate->_segMan, _gameObj, SELECTOR(parseLang)); + + if (parserLanguage != _vocabularyLanguage) { + delete _vocabulary; + _vocabulary = new Vocabulary(_resMan, parserLanguage > 1 ? true : false); + _vocabulary->reset(); + _vocabularyLanguage = parserLanguage; + } +} + } // End of namespace Sci diff --git a/engines/sci/engine/static_selectors.cpp b/engines/sci/engine/static_selectors.cpp index 85089e74c8..55e18613e0 100644 --- a/engines/sci/engine/static_selectors.cpp +++ b/engines/sci/engine/static_selectors.cpp @@ -168,6 +168,20 @@ Common::StringArray Kernel::checkStaticSelectorNames() { names[274] = "syncTime"; names[275] = "syncCue"; + } else if (g_sci->getGameId() == GID_ISLANDBRAIN) { + // The demo of Island of Dr. Brain needs the init selector set to match up with the full + // game's workaround - bug #3035033 + if (names.size() < 111) + names.resize(111); + + names[110] = "init"; + } else if (g_sci->getGameId() == GID_LAURABOW2) { + // The floppy of version needs the open selector set to match up with the CD version's + // workaround - bug #3035694 + if (names.size() < 190) + names.resize(190); + + names[189] = "open"; } #ifdef ENABLE_SCI32 diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 8108440102..b7f6896a48 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -38,6 +38,7 @@ #include "sci/engine/seg_manager.h" #include "sci/engine/selector.h" // for SELECTOR #include "sci/engine/gc.h" +#include "sci/engine/workarounds.h" namespace Sci { @@ -48,86 +49,6 @@ const reg_t TRUE_REG = {0, 1}; #define SCI_XS_CALLEE_LOCALS ((SegmentId)-1) -#define END Script_None - -opcode_format g_opcode_formats[128][4] = { - /*00*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*04*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*08*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*0C*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*10*/ - {Script_None}, {Script_None}, {Script_None}, {Script_None}, - /*14*/ - {Script_None}, {Script_None}, {Script_None}, {Script_SRelative, END}, - /*18*/ - {Script_SRelative, END}, {Script_SRelative, END}, {Script_SVariable, END}, {Script_None}, - /*1C*/ - {Script_SVariable, END}, {Script_None}, {Script_None}, {Script_Variable, END}, - /*20*/ - {Script_SRelative, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_SVariable, Script_Byte, END}, - /*24 (24=ret)*/ - {Script_End}, {Script_Byte, END}, {Script_Invalid}, {Script_Invalid}, - /*28*/ - {Script_Variable, END}, {Script_Invalid}, {Script_Byte, END}, {Script_Variable, Script_Byte, END}, - /*2C*/ - {Script_SVariable, END}, {Script_SVariable, Script_Variable, END}, {Script_None}, {Script_Invalid}, - /*30*/ - {Script_None}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, - /*34*/ - {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END}, - /*38*/ - {Script_Property, END}, {Script_SRelative, END}, {Script_SRelative, END}, {Script_None}, - /*3C*/ - {Script_None}, {Script_None}, {Script_None}, {Script_Word}, - /*40-4F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - /*50-5F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - /*60-6F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - /*70-7F*/ - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}, - {Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END} -}; -#undef END - -// TODO: script_adjust_opcode_formats should probably be part of the -// constructor (?) of a VirtualMachine or a ScriptManager class. -void script_adjust_opcode_formats() { - if (g_sci->_features->detectLofsType() != SCI_VERSION_0_EARLY) { - g_opcode_formats[op_lofsa][0] = Script_Offset; - g_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_opcode_formats[op_calle][2] = Script_Word; - g_opcode_formats[op_callk][1] = Script_Word; - g_opcode_formats[op_super][1] = Script_Word; - g_opcode_formats[op_send][0] = Script_Word; - g_opcode_formats[op_self][0] = Script_Word; - g_opcode_formats[op_call][1] = Script_Word; - g_opcode_formats[op_callb][1] = Script_Word; - } -#endif -} - /** * Adds an entry to the top of the execution stack. * @@ -248,7 +169,7 @@ static bool validate_variable(reg_t *r, reg_t *stack_base, int type, int max, in } else { // WORKAROUND: Mixed-Up Mother Goose tries to use an invalid parameter in Event::new(). // Just skip around it here so we don't error out in validate_arithmetic. - if (g_sci->getGameId() == GID_MOTHERGOOSE && getSciVersion() <= SCI_VERSION_1_1 && type == VAR_PARAM && index == 1) + if (g_sci->getGameId() == GID_MOTHERGOOSE && type == VAR_PARAM && index == 1) return false; debugC(2, kDebugLevelVM, "%s", txt.c_str()); @@ -262,109 +183,33 @@ static bool validate_variable(reg_t *r, reg_t *stack_base, int type, int max, in return true; } -struct SciTrackOriginReply { - int scriptNr; - Common::String objectName; - Common::String methodName; - int localCallOffset; -}; - -static reg_t trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin) { - EngineState *state = g_sci->getEngineState(); - ExecStack *lastCall = state->xs; - Script *local_script = state->_segMan->getScriptIfLoaded(lastCall->local_segment); - int curScriptNr = local_script->getScriptNumber(); - - if (lastCall->debugLocalCallOffset != -1) { - // if lastcall was actually a local call search back for a real call - Common::List<ExecStack>::iterator callIterator = state->_executionStack.end(); - while (callIterator != state->_executionStack.begin()) { - callIterator--; - ExecStack loopCall = *callIterator; - if ((loopCall.debugSelector != -1) || (loopCall.debugExportId != -1)) { - lastCall->debugSelector = loopCall.debugSelector; - lastCall->debugExportId = loopCall.debugExportId; - break; - } - } - } - - Common::String curObjectName = state->_segMan->getObjectName(lastCall->sendp); - Common::String curMethodName; - const SciGameId gameId = g_sci->getGameId(); - - if (lastCall->type == EXEC_STACK_TYPE_CALL) { - if (lastCall->debugSelector != -1) { - curMethodName = g_sci->getKernel()->getSelectorName(lastCall->debugSelector); - } else if (lastCall->debugExportId != -1) { - curObjectName = ""; - curMethodName = curMethodName.printf("export %d", lastCall->debugExportId); - } - } - - if (workaroundList) { - // Search if there is a workaround for this one - const SciWorkaroundEntry *workaround; - int16 inheritanceLevel = 0; - Common::String searchObjectName = curObjectName; - reg_t searchObject = lastCall->sendp; - do { - workaround = workaroundList; - while (workaround->objectName) { - if (workaround->gameId == gameId && workaround->scriptNr == curScriptNr - && ((workaround->inheritanceLevel == -1) || (workaround->inheritanceLevel == inheritanceLevel)) - && (workaround->objectName == searchObjectName) - && workaround->methodName == curMethodName && workaround->localCallOffset == lastCall->debugLocalCallOffset && workaround->index == index) { - // Workaround found - return workaround->newValue; - } - workaround++; - } - - // Go back to the parent - inheritanceLevel++; - searchObject = state->_segMan->getObject(searchObject)->getSuperClassSelector(); - if (!searchObject.isNull()) - searchObjectName = state->_segMan->getObjectName(searchObject); - } while (!searchObject.isNull()); // no parent left? - } +static bool validate_unsignedInteger(reg_t reg, uint16 &integer) { + if (reg.segment) + return false; + integer = reg.offset; + return true; +} - // give caller origin data - trackOrigin->objectName = curObjectName; - trackOrigin->methodName = curMethodName; - trackOrigin->scriptNr = curScriptNr; - trackOrigin->localCallOffset = lastCall->debugLocalCallOffset; - return make_reg(0xFFFF, 0xFFFF); +static bool validate_signedInteger(reg_t reg, int16 &integer) { + if (reg.segment) + return false; + integer = (int16)reg.offset; + return true; } -// gameID, scriptNr,lvl, object-name, method-name, call, index, replace -static const SciWorkaroundEntry uninitializedReadWorkarounds[] = { - { GID_LAURABOW2, 24, 0, "gcWin", "open", -1, 5, { 0, 0xf } }, // is used as priority for game menu - { GID_FREDDYPHARKAS, 24, 0, "gcWin", "open", -1, 5, { 0, 0xf } }, // is used as priority for game menu - { GID_FREDDYPHARKAS, 31, 0, "quitWin", "open", -1, 5, { 0, 0xf } }, // is used as priority for game menu - { GID_GK2, 11, 0, "", "export 10", -1, 3, { 0, 0 } }, // called when the game starts - { GID_JONES, 232, 0, "weekendText", "draw", 0x3d3, 0, { 0, 0 } }, // jones/cd only - gets called during the game - { GID_JONES, 255, 0, "", "export 0", -1, 13, { 0, 0 } }, // jones/ega&vga only - called when the game starts - { GID_JONES, 255, 0, "", "export 0", -1, 14, { 0, 0 } }, // jones/ega&vga only - called when the game starts - { GID_LSL1, 720, 0, "rm720", "init", -1, 0, { 0, 0 } }, // age check room - { GID_LSL3, 997, 0, "TheMenuBar", "handleEvent", -1, 1, { 0, 0xf } }, // when setting volume the first time, this temp is used to set volume on entry (normally it would have been initialized to 's') - { GID_LSL6, 928, -1, "Narrator", "startText", -1, 0, { 0, 0 } }, // used by various objects that are even translated in foreign versions, that's why we use the base-class - { GID_ISLANDBRAIN, 140, 0, "piece", "init", -1, 3, { 0, 1 } }, // first puzzle right at the start, some initialization variable. bnt is done on it, and it should be non-0 - { GID_ISLANDBRAIN, 268, 0, "anElement", "select", -1, 0, { 0, 0 } }, // elements puzzle, gets used before super TextIcon - { GID_KQ5, 0, 0, "", "export 29", -1, 3, { 0, 0 } }, // called when playing harp for the harpies, is used for kDoAudio - { GID_KQ5, 25, 0, "rm025", "doit", -1, 0, { 0, 0 } }, // inside witch forest, where the walking rock is - { GID_KQ6, 903, 0, "controlWin", "open", -1, 4, { 0, 0 } }, // when opening the controls window (save, load etc) - { GID_KQ6, 500, 0, "rm500", "init", -1, 0, { 0, 0 } }, // going to island of the beast - { GID_KQ6, 520, 0, "rm520", "init", -1, 0, { 0, 0 } }, // going to boiling water trap on beast isle - { GID_KQ6, 30, 0, "rats", "changeState", -1, 0, { 0, 0 } }, // rats in the catacombs - { GID_LSL6, 85, 0, "washcloth", "doVerb", -1, 0, { 0, 0 } }, // washcloth in inventory - { GID_SQ1, 703, 0, "", "export 1", -1, 0, { 0, 0 } }, // sub that's called from several objects while on sarien battle cruiser - { GID_SQ1, 703, 0, "firePulsar", "changeState", 0x18a, 0, { 0, 0 } }, // export 1, but called locally (when shooting at aliens) - { GID_SQ4, 928, 0, "Narrator", "startText", -1, 1000, { 0, 1 } }, // sq4cd: method returns this to the caller - { GID_SQ6, 0, 0, "SQ6", "init", -1, 2, { 0, 0 } }, // called when the game starts - { GID_SQ6, 64950, 0, "View", "handleEvent", -1, 0, { 0, 0 } }, // called when pressing "Start game" in the main menu - SCI_WORKAROUNDENTRY_TERMINATOR -}; +extern const char *opcodeNames[]; // from scriptdebug.cpp + +static reg_t arithmetic_lookForWorkaround(const byte opcode, const SciWorkaroundEntry *workaroundList, reg_t value1, reg_t value2) { + SciTrackOriginReply originReply; + SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, workaroundList, &originReply); + if (solution.type == WORKAROUND_NONE) + error("%s on non-integer (%04x:%04x, %04x:%04x) from method %s::%s (script %d, room %d, localCall %x)", + opcodeNames[opcode], PRINT_REG(value1), PRINT_REG(value2), originReply.objectName.c_str(), + originReply.methodName.c_str(), originReply.scriptNr, g_sci->getEngineState()->currentRoomNumber(), + originReply.localCallOffset); + assert(solution.type == WORKAROUND_FAKE); + return make_reg(0, solution.value); +} static reg_t validate_read_var(reg_t *r, reg_t *stack_base, int type, int max, int index, reg_t default_value) { if (validate_variable(r, stack_base, type, max, index)) { @@ -374,9 +219,13 @@ static reg_t validate_read_var(reg_t *r, reg_t *stack_base, int type, int max, i // Uninitialized read on a temp // We need to find correct replacements for each situation manually SciTrackOriginReply originReply; - r[index] = trackOriginAndFindWorkaround(index, uninitializedReadWorkarounds, &originReply); - if ((r[index].segment == 0xFFFF) && (r[index].offset == 0xFFFF)) - error("Uninitialized read for temp %d from method %s::%s (script %d, localCall %x)", index, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, originReply.localCallOffset); + SciWorkaroundSolution solution = trackOriginAndFindWorkaround(index, uninitializedReadWorkarounds, &originReply); + if (solution.type == WORKAROUND_NONE) + error("Uninitialized read for temp %d from method %s::%s (script %d, room %d, localCall %x)", + index, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, + g_sci->getEngineState()->currentRoomNumber(), originReply.localCallOffset); + assert(solution.type == WORKAROUND_FAKE); + r[index] = make_reg(0, solution.value); break; } case VAR_PARAM: @@ -440,12 +289,9 @@ static void validate_write_var(reg_t *r, reg_t *stack_base, int type, int max, i #define WRITE_VAR(type, index, value) validate_write_var(s->variables[type], s->stack_base, type, s->variablesMax[type], index, value, s->_segMan, g_sci->getKernel()) #define WRITE_VAR16(type, index, value) WRITE_VAR(type, index, make_reg(0, value)); -#define ACC_ARITHMETIC_L(op) make_reg(0, (op validate_arithmetic(s->r_acc))) - // Operating on the stack // 16 bit: #define PUSH(v) PUSH32(make_reg(0, v)) -#define POP() (validate_arithmetic(POP32())) // 32 bit: #define PUSH32(a) (*(validate_stack_addr(s, (s->xs->sp)++)) = (a)) #define POP32() (*(validate_stack_addr(s, --(s->xs->sp)))) @@ -618,29 +464,17 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt if (argc > 1) { // argc can indeed be bigger than 1 in some cases, and it's usually the - // result of a script bug + // result of a script bug. Usually these aren't fatal. const char *objectName = s->_segMan->getObjectName(send_obj); - if (!strcmp(objectName, "Sq4GlobalNarrator") && selector == 606) { - // SQ4 has a script bug in the Sq4GlobalNarrator object when invoking the - // returnVal selector, which doesn't affect gameplay, thus don't diplay it - } else if (!strcmp(objectName, "longSong") && selector == 3 && g_sci->getGameId() == GID_QFG1) { - // QFG1VGA has a script bug in the longSong object when invoking the - // loop selector, which doesn't affect gameplay, thus don't diplay it - } else if (!strcmp(objectName, "PuzPiece") && selector == 77 && g_sci->getGameId() == GID_CASTLEBRAIN) { - // Castle of Dr. Brain has a script bug in the PuzPiece object when invoking - // the value selector, which doesn't affect gameplay, thus don't display it - } else { - // Unknown script bug, show it. Usually these aren't fatal. - reg_t oldReg = *varp.getPointer(s->_segMan); - reg_t newReg = argp[1]; - const char *selectorName = g_sci->getKernel()->getSelectorName(selector).c_str(); - warning("send_selector(): argc = %d while modifying variable selector " - "%x (%s) of object %04x:%04x (%s) from %04x:%04x to %04x:%04x", - argc, selector, selectorName, PRINT_REG(send_obj), - objectName, PRINT_REG(oldReg), PRINT_REG(newReg)); - } + reg_t oldReg = *varp.getPointer(s->_segMan); + reg_t newReg = argp[1]; + const char *selectorName = g_sci->getKernel()->getSelectorName(selector).c_str(); + debug(2, "send_selector(): argc = %d while modifying variable selector " + "%x (%s) of object %04x:%04x (%s) from %04x:%04x to %04x:%04x", + argc, selector, selectorName, PRINT_REG(send_obj), + objectName, PRINT_REG(oldReg), PRINT_REG(newReg)); } { @@ -813,6 +647,61 @@ static void addKernelCallToExecStack(EngineState *s, int kernelCallNr, int argc, xstack->type = EXEC_STACK_TYPE_KERNEL; } +static void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunction *kernelSubCall, EngineState *s, int argc, reg_t *argv, reg_t result) { + Kernel *kernel = g_sci->getKernel(); + if (!kernelSubCall) { + printf("k%s: ", kernelCall->name); + } else { + int callNameLen = strlen(kernelCall->name); + if (strncmp(kernelCall->name, kernelSubCall->name, callNameLen) == 0) { + const char *subCallName = kernelSubCall->name + callNameLen; + printf("k%s(%s): ", kernelCall->name, subCallName); + } else { + printf("k%s(%s): ", kernelCall->name, kernelSubCall->name); + } + } + for (int parmNr = 0; parmNr < argc; parmNr++) { + if (parmNr) + printf(", "); + uint16 regType = kernel->findRegType(argv[parmNr]); + if (regType & SIG_TYPE_NULL) + printf("0"); + else if (regType & SIG_TYPE_UNINITIALIZED) + printf("UNINIT"); + else if (regType & SIG_IS_INVALID) + printf("INVALID"); + else if (regType & SIG_TYPE_INTEGER) + printf("%d", argv[parmNr].offset); + else { + printf("%04x:%04x", PRINT_REG(argv[parmNr])); + switch (regType) { + case SIG_TYPE_OBJECT: + printf(" (%s)", s->_segMan->getObjectName(argv[parmNr])); + break; + case SIG_TYPE_REFERENCE: + if (kernelCall->function == kSaid) { + SegmentRef saidSpec = s->_segMan->dereference(argv[parmNr]); + if (saidSpec.isRaw) { + printf(" ('"); + g_sci->getVocabulary()->debugDecipherSaidBlock(saidSpec.raw); + printf("')"); + } else { + printf(" (non-raw said-spec)"); + } + } else { + printf(" ('%s')", s->_segMan->getString(argv[parmNr]).c_str()); + } + default: + break; + } + } + } + if (result.segment) + printf(" = %04x:%04x\n", PRINT_REG(result)); + else + printf(" = %d\n", result.offset); +} + static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { Kernel *kernel = g_sci->getKernel(); @@ -826,17 +715,24 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { && !kernel->signatureMatch(kernelCall.signature, argc, argv)) { // signature mismatch, check if a workaround is available SciTrackOriginReply originReply; - reg_t workaround; - workaround = trackOriginAndFindWorkaround(0, kernelCall.workarounds, &originReply); - if ((workaround.segment == 0xFFFF) && (workaround.offset == 0xFFFF)) { + SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelCall.workarounds, &originReply); + switch (solution.type) { + case WORKAROUND_NONE: kernel->signatureDebug(kernelCall.signature, argc, argv); - error("[VM] k%s[%x]: signature mismatch via method %s::%s (script %d, localCall %x)", kernelCall.name, kernelCallNr, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, originReply.localCallOffset); - } - // FIXME: implement some real workaround type logic - ignore call, still do call etc. - if (workaround.segment == 2) - s->r_acc = make_reg(0, workaround.offset); - if (workaround.segment) + error("[VM] k%s[%x]: signature mismatch via method %s::%s (script %d, room %d, localCall 0x%x)", + kernelCall.name, kernelCallNr, originReply.objectName.c_str(), originReply.methodName.c_str(), + originReply.scriptNr, s->currentRoomNumber(), originReply.localCallOffset); + break; + case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone + return; + case WORKAROUND_STILLCALL: // call kernel anyway + break; + case WORKAROUND_FAKE: // don't do kernel call, fake acc + s->r_acc = make_reg(0, solution.value); return; + default: + error("unknown workaround type"); + } } @@ -844,6 +740,9 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { if (!kernelCall.subFunctionCount) { addKernelCallToExecStack(s, kernelCallNr, argc, argv); s->r_acc = kernelCall.function(s, argc, argv); + + if (kernelCall.debugLogging) + logKernelCall(&kernelCall, NULL, s, argc, argv, s->r_acc); } else { // Sub-functions available, check signature and call that one directly if (argc < 1) @@ -860,43 +759,41 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { if (kernelSubCall.signature && !kernel->signatureMatch(kernelSubCall.signature, argc, argv)) { // Signature mismatch SciTrackOriginReply originReply; - reg_t workaround; - workaround = trackOriginAndFindWorkaround(0, kernelSubCall.workarounds, &originReply); - if ((workaround.segment == 0xFFFF) && (workaround.offset == 0xFFFF)) { + SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelSubCall.workarounds, &originReply); + switch (solution.type) { + case WORKAROUND_NONE: { kernel->signatureDebug(kernelSubCall.signature, argc, argv); int callNameLen = strlen(kernelCall.name); if (strncmp(kernelCall.name, kernelSubCall.name, callNameLen) == 0) { const char *subCallName = kernelSubCall.name + callNameLen; - error("[VM] k%s(%s): signature mismatch via method %s::%s (script %d, localCall %x)", kernelCall.name, subCallName, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, originReply.localCallOffset); + error("[VM] k%s(%s): signature mismatch via method %s::%s (script %d, room %d, localCall %x)", + kernelCall.name, subCallName, originReply.objectName.c_str(), originReply.methodName.c_str(), + originReply.scriptNr, s->currentRoomNumber(), originReply.localCallOffset); } - error("[VM] k%s: signature mismatch via method %s::%s (script %d, localCall %x)", kernelSubCall.name, originReply.objectName.c_str(), originReply.methodName.c_str(), originReply.scriptNr, originReply.localCallOffset); + error("[VM] k%s: signature mismatch via method %s::%s (script %d, room %d, localCall %x)", + kernelSubCall.name, originReply.objectName.c_str(), originReply.methodName.c_str(), + originReply.scriptNr, s->currentRoomNumber(), originReply.localCallOffset); + break; } - // FIXME: implement some real workaround type logic - ignore call, still do call etc. - if (workaround.segment == 2) - s->r_acc = make_reg(0, workaround.offset); - if (workaround.segment) + case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone + return; + case WORKAROUND_STILLCALL: // call kernel anyway + break; + case WORKAROUND_FAKE: // don't do kernel call, fake acc + s->r_acc = make_reg(0, solution.value); return; + default: + error("unknown workaround type"); + } } if (!kernelSubCall.function) error("[VM] k%s: subfunction-id %d requested, but not available", kernelCall.name, subId); addKernelCallToExecStack(s, kernelCallNr, argc, argv); s->r_acc = kernelSubCall.function(s, argc, argv); - } - -#if 0 - // Used for debugging - Common::String debugMsg = Common::String::printf("%s [0x%x]", kernelCall.name, kernelCallNr) + - Common::String::printf(", %d params: ", argc) + - " ("; - for (int i = 0; i < argc; i++) { - debugMsg += Common::String::printf("%04x:%04x", PRINT_REG(argv[i])); - debugMsg += (i == argc - 1 ? ")" : ", "); - } - - debugMsg += ", result: " + Common::String::printf("%04x:%04x", PRINT_REG(s->r_acc)); - debug("%s", debugMsg.c_str()); -#endif + if (kernelSubCall.debugLogging) + logKernelCall(&kernelCall, &kernelSubCall, s, argc, argv, s->r_acc); + } // Remove callk stack frame again, if there's still an execution stack if (s->_executionStack.begin() != s->_executionStack.end()) @@ -978,7 +875,7 @@ int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4]) return offset; } -void run_vm(EngineState *s, bool restoring) { +void run_vm(EngineState *s) { assert(s); int temp; @@ -999,8 +896,7 @@ void run_vm(EngineState *s, bool restoring) { if (!local_script) error("run_vm(): program counter gone astray (local_script pointer is null)"); - if (!restoring) - s->executionStackBase = s->_executionStack.size() - 1; + s->executionStackBase = s->_executionStack.size() - 1; s->variablesSegment[VAR_TEMP] = s->variablesSegment[VAR_PARAM] = s->_segMan->findSegmentByType(SEG_TYPE_STACK); s->variablesBase[VAR_TEMP] = s->variablesBase[VAR_PARAM] = s->stack_base; @@ -1059,9 +955,7 @@ void run_vm(EngineState *s, bool restoring) { g_sci->_debugState.breakpointWasHit = false; } Console *con = g_sci->getSciDebugger(); - if (con->isAttached()) { - con->onFrame(); - } + con->onFrame(); if (s->xs->sp < s->xs->fp) error("run_vm(): stack underflow, sp: %04x:%04x, fp: %04x:%04x", @@ -1080,10 +974,15 @@ void run_vm(EngineState *s, bool restoring) { switch (opcode) { - case op_bnot: // 0x00 (00) + case op_bnot: { // 0x00 (00) // Binary not - s->r_acc = ACC_ARITHMETIC_L(0xffff ^ /*acc*/); + int16 value; + if (validate_signedInteger(s->r_acc, value)) + s->r_acc = make_reg(0, 0xffff ^ value); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, NULL_REG); break; + } case op_add: // 0x01 (01) r_temp = POP32(); @@ -1148,45 +1047,96 @@ void run_vm(EngineState *s, bool restoring) { } break; - case op_mul: // 0x03 (03) - s->r_acc = ACC_ARITHMETIC_L(((int16)POP()) * (int16)/*acc*/); + case op_mul: { // 0x03 (03) + r_temp = POP32(); + int16 value1, value2; + if (validate_signedInteger(s->r_acc, value1) && validate_signedInteger(r_temp, value2)) + s->r_acc = make_reg(0, value1 * value2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, r_temp); break; + } case op_div: { // 0x04 (04) - int16 divisor = signed_validate_arithmetic(s->r_acc); - s->r_acc = make_reg(0, (divisor != 0 ? ((int16)POP()) / divisor : 0)); + r_temp = POP32(); + int16 divisor, dividend; + if (validate_signedInteger(s->r_acc, divisor) && validate_signedInteger(r_temp, dividend)) + s->r_acc = make_reg(0, (divisor != 0 ? dividend / divisor : 0)); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeDivWorkarounds, s->r_acc, r_temp); break; } + case op_mod: { // 0x05 (05) - int16 modulo = signed_validate_arithmetic(s->r_acc); - s->r_acc = make_reg(0, (modulo != 0 ? ((int16)POP()) % modulo : 0)); + r_temp = POP32(); + int16 modulo, value; + if (validate_signedInteger(s->r_acc, modulo) && validate_signedInteger(r_temp, value)) + s->r_acc = make_reg(0, (modulo != 0 ? value % modulo : 0)); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, r_temp); break; } - case op_shr: // 0x06 (06) + + case op_shr: { // 0x06 (06) // Shift right logical - s->r_acc = ACC_ARITHMETIC_L(((uint16)POP()) >> /*acc*/); + r_temp = POP32(); + uint16 value, shiftCount; + if (validate_unsignedInteger(r_temp, value) && validate_unsignedInteger(s->r_acc, shiftCount)) + s->r_acc = make_reg(0, value >> shiftCount); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); break; + } - case op_shl: // 0x07 (07) + case op_shl: { // 0x07 (07) // Shift left logical - s->r_acc = ACC_ARITHMETIC_L(((uint16)POP()) << /*acc*/); + r_temp = POP32(); + uint16 value, shiftCount; + if (validate_unsignedInteger(r_temp, value) && validate_unsignedInteger(s->r_acc, shiftCount)) + s->r_acc = make_reg(0, value << shiftCount); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); break; + } - case op_xor: // 0x08 (08) - s->r_acc = ACC_ARITHMETIC_L(POP() ^ /*acc*/); + case op_xor: { // 0x08 (08) + r_temp = POP32(); + uint16 value1, value2; + if (validate_unsignedInteger(r_temp, value1) && validate_unsignedInteger(s->r_acc, value2)) + s->r_acc = make_reg(0, value1 ^ value2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); break; + } - case op_and: // 0x09 (09) - s->r_acc = ACC_ARITHMETIC_L(POP() & /*acc*/); + case op_and: { // 0x09 (09) + r_temp = POP32(); + uint16 value1, value2; + if (validate_unsignedInteger(r_temp, value1) && validate_unsignedInteger(s->r_acc, value2)) + s->r_acc = make_reg(0, value1 & value2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); break; + } - case op_or: // 0x0a (10) - s->r_acc = ACC_ARITHMETIC_L(POP() | /*acc*/); + case op_or: { // 0x0a (10) + r_temp = POP32(); + uint16 value1, value2; + if (validate_unsignedInteger(r_temp, value1) && validate_unsignedInteger(s->r_acc, value2)) + s->r_acc = make_reg(0, value1 | value2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeOrWorkarounds, r_temp, s->r_acc); break; + } - case op_neg: // 0x0b (11) - s->r_acc = ACC_ARITHMETIC_L(-/*acc*/); + case op_neg: { // 0x0b (11) + int16 value; + if (validate_signedInteger(s->r_acc, value)) + s->r_acc = make_reg(0, -value); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, NULL_REG); break; + } case op_not: // 0x0c (12) s->r_acc = make_reg(0, !(s->r_acc.offset || s->r_acc.segment)); @@ -1220,12 +1170,17 @@ void run_vm(EngineState *s, bool restoring) { s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset > s->r_acc.offset); } else if (r_temp.segment && !s->r_acc.segment) { if (s->r_acc.offset >= 1000) - error("[VM] op_gt: comparsion between a pointer and number"); - // Pseudo-WORKAROUND: sierra allows any pointer <-> value comparsion + error("[VM] op_gt: comparison between a pointer and number"); + // Pseudo-WORKAROUND: Sierra allows any pointer <-> value comparison // Happens in SQ1, room 28, when throwing the water at Orat s->r_acc = make_reg(0, 1); - } else - s->r_acc = ACC_ARITHMETIC_L(signed_validate_arithmetic(r_temp) > (int16)/*acc*/); + } else { + int16 compare1, compare2; + if (validate_signedInteger(r_temp, compare1) && validate_signedInteger(s->r_acc, compare2)) + s->r_acc = make_reg(0, compare1 > compare2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); + } break; case op_ge_: // 0x10 (16) @@ -1236,8 +1191,13 @@ void run_vm(EngineState *s, bool restoring) { if (r_temp.segment != s->r_acc.segment) warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset >= s->r_acc.offset); - } else - s->r_acc = ACC_ARITHMETIC_L(signed_validate_arithmetic(r_temp) >= (int16)/*acc*/); + } else { + int16 compare1, compare2; + if (validate_signedInteger(r_temp, compare1) && validate_signedInteger(s->r_acc, compare2)) + s->r_acc = make_reg(0, compare1 >= compare2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); + } break; case op_lt_: // 0x11 (17) @@ -1250,12 +1210,17 @@ void run_vm(EngineState *s, bool restoring) { s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset < s->r_acc.offset); } else if (r_temp.segment && !s->r_acc.segment) { if (s->r_acc.offset >= 1000) - error("[VM] op_lt: comparsion between a pointer and number"); - // Pseudo-WORKAROUND: sierra allows any pointer <-> value comparsion + error("[VM] op_lt: comparison between a pointer and number"); + // Pseudo-WORKAROUND: Sierra allows any pointer <-> value comparison // Happens in SQ1, room 58, when giving id-card to robot s->r_acc = make_reg(0, 1); - } else - s->r_acc = ACC_ARITHMETIC_L(signed_validate_arithmetic(r_temp) < (int16)/*acc*/); + } else { + int16 compare1, compare2; + if (validate_signedInteger(r_temp, compare1) && validate_signedInteger(s->r_acc, compare2)) + s->r_acc = make_reg(0, compare1 < compare2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); + } break; case op_le_: // 0x12 (18) @@ -1266,8 +1231,13 @@ void run_vm(EngineState *s, bool restoring) { if (r_temp.segment != s->r_acc.segment) warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset <= s->r_acc.offset); - } else - s->r_acc = ACC_ARITHMETIC_L(signed_validate_arithmetic(r_temp) <= (int16)/*acc*/); + } else { + int16 compare1, compare2; + if (validate_signedInteger(r_temp, compare1) && validate_signedInteger(s->r_acc, compare2)) + s->r_acc = make_reg(0, compare1 <= compare2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); + } break; case op_ugt_: // 0x13 (19) @@ -1289,8 +1259,13 @@ void run_vm(EngineState *s, bool restoring) { s->r_acc = make_reg(0, 1); else if (r_temp.segment && s->r_acc.segment) s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset > s->r_acc.offset); - else - s->r_acc = ACC_ARITHMETIC_L(validate_arithmetic(r_temp) > /*acc*/); + else { + uint16 compare1, compare2; + if (validate_unsignedInteger(r_temp, compare1) && validate_unsignedInteger(s->r_acc, compare2)) + s->r_acc = make_reg(0, compare1 > compare2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); + } break; case op_uge_: // 0x14 (20) @@ -1303,8 +1278,13 @@ void run_vm(EngineState *s, bool restoring) { s->r_acc = make_reg(0, 1); else if (r_temp.segment && s->r_acc.segment) s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset >= s->r_acc.offset); - else - s->r_acc = ACC_ARITHMETIC_L(validate_arithmetic(r_temp) >= /*acc*/); + else { + uint16 compare1, compare2; + if (validate_unsignedInteger(r_temp, compare1) && validate_unsignedInteger(s->r_acc, compare2)) + s->r_acc = make_reg(0, compare1 >= compare2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); + } break; case op_ult_: // 0x15 (21) @@ -1313,12 +1293,18 @@ void run_vm(EngineState *s, bool restoring) { r_temp = POP32(); // See above - if (r_temp.segment && (s->r_acc == make_reg(0, 1000))) + // PQ2 japanese compares pointers to 2000 to find out if its a pointer or a resourceid + if (r_temp.segment && (s->r_acc == make_reg(0, 1000) || (s->r_acc == make_reg(0, 2000)))) s->r_acc = NULL_REG; else if (r_temp.segment && s->r_acc.segment) s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset < s->r_acc.offset); - else - s->r_acc = ACC_ARITHMETIC_L(validate_arithmetic(r_temp) < /*acc*/); + else { + uint16 compare1, compare2; + if (validate_unsignedInteger(r_temp, compare1) && validate_unsignedInteger(s->r_acc, compare2)) + s->r_acc = make_reg(0, compare1 < compare2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); + } break; case op_ule_: // 0x16 (22) @@ -1331,8 +1317,13 @@ void run_vm(EngineState *s, bool restoring) { s->r_acc = NULL_REG; else if (r_temp.segment && s->r_acc.segment) s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset <= s->r_acc.offset); - else - s->r_acc = ACC_ARITHMETIC_L(validate_arithmetic(r_temp) <= /*acc*/); + else { + uint16 compare1, compare2; + if (validate_unsignedInteger(r_temp, compare1) && validate_unsignedInteger(s->r_acc, compare2)) + s->r_acc = make_reg(0, compare1 <= compare2); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); + } break; case op_bt: // 0x17 (23) @@ -1647,32 +1638,55 @@ void run_vm(EngineState *s, bool restoring) { validate_property(obj, (opparams[0] >> 1)) = POP32(); break; - case op_ipToa: // 0x35 (53) - // Incement Property and copy To Accumulator - s->r_acc = validate_property(obj, (opparams[0] >> 1)); - s->r_acc = validate_property(obj, (opparams[0] >> 1)) = ACC_ARITHMETIC_L(1 + /*acc*/); + case op_ipToa: { // 0x35 (53) + // Increment Property and copy To Accumulator + reg_t &opProperty = validate_property(obj, opparams[0] >> 1); + uint16 valueProperty; + if (validate_unsignedInteger(opProperty, valueProperty)) + s->r_acc = make_reg(0, valueProperty + 1); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, opProperty, NULL_REG); + opProperty = s->r_acc; break; + } case op_dpToa: { // 0x36 (54) // Decrement Property and copy To Accumulator - s->r_acc = validate_property(obj, (opparams[0] >> 1)); - s->r_acc = validate_property(obj, (opparams[0] >> 1)) = ACC_ARITHMETIC_L(-1 + /*acc*/); + reg_t &opProperty = validate_property(obj, opparams[0] >> 1); + uint16 valueProperty; + if (validate_unsignedInteger(opProperty, valueProperty)) + s->r_acc = make_reg(0, valueProperty - 1); + else + s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeDptoaWorkarounds, opProperty, NULL_REG); + opProperty = s->r_acc; break; } - case op_ipTos: // 0x37 (55) + case op_ipTos: { // 0x37 (55) // Increment Property and push to Stack - validate_arithmetic(validate_property(obj, (opparams[0] >> 1))); - temp = ++validate_property(obj, (opparams[0] >> 1)).offset; - PUSH(temp); + reg_t &opProperty = validate_property(obj, opparams[0] >> 1); + uint16 valueProperty; + if (validate_unsignedInteger(opProperty, valueProperty)) + valueProperty++; + else + valueProperty = arithmetic_lookForWorkaround(opcode, NULL, opProperty, NULL_REG).offset; + opProperty = make_reg(0, valueProperty); + PUSH(valueProperty); break; + } - case op_dpTos: // 0x38 (56) + case op_dpTos: { // 0x38 (56) // Decrement Property and push to Stack - validate_arithmetic(validate_property(obj, (opparams[0] >> 1))); - temp = --validate_property(obj, (opparams[0] >> 1)).offset; - PUSH(temp); + reg_t &opProperty = validate_property(obj, opparams[0] >> 1); + uint16 valueProperty; + if (validate_unsignedInteger(opProperty, valueProperty)) + valueProperty--; + else + valueProperty = arithmetic_lookForWorkaround(opcode, NULL, opProperty, NULL_REG).offset; + opProperty = make_reg(0, valueProperty); + PUSH(valueProperty); break; + } case op_lofsa: // 0x39 (57) // Load Offset to Accumulator diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h index 81ec4f1c61..ee22e03310 100644 --- a/engines/sci/engine/vm.h +++ b/engines/sci/engine/vm.h @@ -315,9 +315,8 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, * 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 - * @param[in] restoring true if s has just been restored, false otherwise */ -void run_vm(EngineState *s, bool restoring); +void run_vm(EngineState *s); /** * Debugger functionality diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp new file mode 100644 index 0000000000..0db73e34d5 --- /dev/null +++ b/engines/sci/engine/workarounds.cpp @@ -0,0 +1,357 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "sci/engine/kernel.h" +#include "sci/engine/state.h" +#include "sci/engine/vm.h" +#include "sci/engine/workarounds.h" + +#define SCI_WORKAROUNDENTRY_TERMINATOR { (SciGameId)0, -1, -1, 0, NULL, NULL, -1, 0, { WORKAROUND_NONE, 0 } } + +namespace Sci { + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry opcodeDivWorkarounds[] = { + { GID_QFG1VGA, 301, 928, 0, "Blink", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // when entering inn, gets called with 1 parameter, but 2nd parameter is used for div which happens to be an object + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry opcodeOrWorkarounds[] = { + { GID_ECOQUEST2, 100, 0, 0, "Rain", "points", 0xcc6, 0, { WORKAROUND_FAKE, 0 } }, // when giving the papers to the customs officer, gets called against a pointer instead of a number - bug #3034464 + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call, index, workaround +const SciWorkaroundEntry opcodeDptoaWorkarounds[] = { + { GID_LSL6, 360, 938, 0, "ROsc", "cycleDone", -1, 0, { WORKAROUND_FAKE, 1 } }, // when looking through tile in the shower room initial cycles get set to an object instead of 2, we fix this by setting 1 after decrease + { GID_LSL6HIRES, 360,64938, 0, "ROsc", "cycleDone", -1, 0, { WORKAROUND_FAKE, 1 } }, // when looking through tile in the shower room initial cycles get set to an object instead of 2, we fix this by setting 1 after decrease + { GID_SQ5, 200, 939, 0, "Osc", "cycleDone", -1, 0, { WORKAROUND_FAKE, 1 } }, // when going back to bridge the crew is goofing off, we get an object as cycle count + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry uninitializedReadWorkarounds[] = { + { GID_CNICK_KQ, 200, 0, 1, "Character", "<noname 446>", -1, 504, { WORKAROUND_FAKE, 0 } }, // checkers, like in hoyle 3 + { GID_CNICK_KQ, 200, 0, 1, "Character", "<noname 446>", -1, 505, { WORKAROUND_FAKE, 0 } }, // checkers, like in hoyle 3 + { GID_CNICK_KQ, -1, 700, 0, "gcWindow", "<noname 183>", -1, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu, like in hoyle 3 + { GID_CNICK_LONGBOW, 0, 0, 0, "RH Budget", "<noname 110>", -1, 1, { WORKAROUND_FAKE, 0 } }, // when starting the game + { GID_ECOQUEST, -1, -1, 0, NULL, "doVerb", -1, 0, { WORKAROUND_FAKE, 0 } }, // almost clicking anywhere triggers this in almost all rooms + { GID_FREDDYPHARKAS, -1, 24, 0, "gcWin", "open", -1, 5, { WORKAROUND_FAKE, 0xf } }, // is used as priority for game menu + { GID_FREDDYPHARKAS, -1, 31, 0, "quitWin", "open", -1, 5, { WORKAROUND_FAKE, 0xf } }, // is used as priority for game menu + { GID_GK1, -1, 64950, 1, "Feature", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // sometimes when walk-clicking + { GID_GK2, -1, 11, 0, "", "export 10", -1, 3, { WORKAROUND_FAKE, 0 } }, // called when the game starts + { GID_GK2, -1, 11, 0, "", "export 10", -1, 4, { WORKAROUND_FAKE, 0 } }, // called during the game + { GID_HOYLE1, 4, 104, 0, "GinRummyCardList", "calcRuns", -1, 4, { WORKAROUND_FAKE, 0 } }, // Gin Rummy / right when the game starts + { GID_HOYLE1, 5, 204, 0, "tableau", "checkRuns", -1, 2, { WORKAROUND_FAKE, 0 } }, // Cribbage / during the game + { GID_HOYLE3, -1, 0, 1, "Character", "say", -1, 504, { WORKAROUND_FAKE, 0 } }, // when starting checkers or dominoes, first time a character says something + { GID_HOYLE3, -1, 0, 1, "Character", "say", -1, 505, { WORKAROUND_FAKE, 0 } }, // when starting checkers or dominoes, first time a character says something + { GID_HOYLE3, -1, 700, 0, "gcWindow", "open", -1, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu + { GID_ISLANDBRAIN, 140, 140, 0, "piece", "init", -1, 3, { WORKAROUND_FAKE, 1 } }, // first puzzle right at the start, some initialization variable. bnt is done on it, and it should be non-0 + { GID_ISLANDBRAIN, 200, 268, 0, "anElement", "select", -1, 0, { WORKAROUND_FAKE, 0 } }, // elements puzzle, gets used before super TextIcon + { GID_JONES, 1, 232, 0, "weekendText", "draw", 0x3d3, 0, { WORKAROUND_FAKE, 0 } }, // jones/cd only - gets called during the game + { GID_JONES, 1, 255, 0, "", "export 0", -1, 13, { WORKAROUND_FAKE, 0 } }, // jones/cd only - called when a game ends + { GID_JONES, 1, 255, 0, "", "export 0", -1, 14, { WORKAROUND_FAKE, 0 } }, // jones/cd only - called when a game ends + { GID_JONES, 764, 255, 0, "", "export 0", -1, 13, { WORKAROUND_FAKE, 0 } }, // jones/ega&vga only - called when the game starts + { GID_JONES, 764, 255, 0, "", "export 0", -1, 14, { WORKAROUND_FAKE, 0 } }, // jones/ega&vga only - called when the game starts + { GID_KQ5, -1, 0, 0, "", "export 29", -1, 3, { WORKAROUND_FAKE, 0 } }, // called when playing harp for the harpies or when aborting dialog in toy shop, is used for kDoAudio - bug #3034700 + { GID_KQ5, 25, 25, 0, "rm025", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // inside witch forest, when going to the room where the walking rock is + { GID_KQ6, -1, 30, 0, "rats", "changeState", -1, -1, { WORKAROUND_FAKE, 0 } }, // rats in the catacombs (temps 1 - 5) - bugs #3034597, #3035495, #3035824 + { GID_KQ6, 210, 210, 0, "rm210", "scriptCheck", -1, 0, { WORKAROUND_FAKE, 1 } }, // using inventory in that room - bug #3034565 + { GID_KQ6, 500, 500, 0, "rm500", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // going to island of the beast + { GID_KQ6, 520, 520, 0, "rm520", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // going to boiling water trap on beast isle + { GID_KQ6, -1, 903, 0, "controlWin", "open", -1, 4, { WORKAROUND_FAKE, 0 } }, // when opening the controls window (save, load etc) + { GID_KQ7, 30, 64996, 0, "User", "handleEvent", -1, 1, { WORKAROUND_FAKE, 0 } }, // called when pushing a keyboard key + { GID_LAURABOW, 44, 967, 0, "myIcon", "cycle", -1, 1, { WORKAROUND_FAKE, 0 } }, // second dialog box after the intro, when talking with Lillian - bug #3034985 + { GID_LAURABOW2, -1, 24, 0, "gcWin", "open", -1, 5, { WORKAROUND_FAKE, 0xf } }, // is used as priority for game menu + { GID_LAURABOW2, -1, 21, 0, "dropCluesCode", "doit", -1, 1, { WORKAROUND_FAKE, 0 } }, // when asking some questions (e.g. the reporter about the burglary, or the policeman about Ziggy) - bugs #3035068, #3036274 + { GID_LAURABOW2, 240, 240, 0, "sSteveAnimates", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // Steve Dorian's idle animation at the docks - bug #3036291 + { GID_LONGBOW, -1, 213, 0, "clear", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // When giving an aswer using the druid hand sign code in any room + { GID_LONGBOW, -1, 213, 0, "letter", "handleEvent", 0xa8, 1, { WORKAROUND_FAKE, 0 } }, // When using the druid hand sign code in any room - bug #3036601 + { GID_LSL1, 250, 250, 0, "increase", "handleEvent", -1, 2, { WORKAROUND_FAKE, 0 } }, // casino, playing game, increasing bet + { GID_LSL1, 720, 720, 0, "rm720", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // age check room + { GID_LSL2, 38, 38, 0, "cloudScript", "changeState", -1, 1, { WORKAROUND_FAKE, 0 } }, // entering the room in the middle deck of the ship - bug #3036483 + { GID_LSL3, 340, 340, 0, "ComicScript", "changeState", -1, -1, { WORKAROUND_FAKE, 0 } }, // right after entering the 3 ethnic groups inside comedy club (temps 200, 201, 202, 203) + { GID_LSL3, -1, 997, 0, "TheMenuBar", "handleEvent", -1, 1, { WORKAROUND_FAKE, 0xf } }, // when setting volume the first time, this temp is used to set volume on entry (normally it would have been initialized to 's') + { GID_LSL6, -1, 85, 0, "washcloth", "doVerb", -1, 0, { WORKAROUND_FAKE, 0 } }, // washcloth in inventory + { GID_LSL6, -1, 928, -1, "Narrator", "startText", -1, 0, { WORKAROUND_FAKE, 0 } }, // used by various objects that are even translated in foreign versions, that's why we use the base-class + { GID_LSL6HIRES, 0, 85, 0, "LL6Inv", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // on startup + { GID_LSL6HIRES, -1, 64950, 1, "Feature", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // at least when entering swimming pool area + { GID_LSL6HIRES, -1, 64964, 0, "DPath", "init", -1, 1, { WORKAROUND_FAKE, 0 } }, // during the game + { GID_MOTHERGOOSE, 18, 992, 0, "AIPath", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // DEMO: Called when walking north from mother goose's house two screens + { GID_MOTHERGOOSEHIRES,-1,64950, 1, "Feature", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // right when clicking on a child at the start and probably also later + { GID_MOTHERGOOSEHIRES,-1,64950, 1, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // see above + { GID_QFG2, -1, 71, 0, "theInvSheet", "doit", -1, 1, { WORKAROUND_FAKE, 0 } }, // accessing the inventory + { GID_QFG2, -1, 701, 0, "Alley", "at", -1, 0, { WORKAROUND_FAKE, 0 } }, // when walking inside the alleys in the town - bug #3035835 + { GID_QFG3, 330, 330, 0, "rajahTeller", "doChild", -1, 0, { WORKAROUND_FAKE, 0 } }, // when talking to King Rajah about "Tarna" + { GID_QFG3, 330, 330, 0, "rajahTeller", "doChild", -1, 1, { WORKAROUND_FAKE, 0 } }, // when talking to King Rajah about "Rajah" - bug #3036390 + { GID_SQ1, 103, 103, 0, "hand", "internalEvent", -1, 1, { WORKAROUND_FAKE, 0 } }, // spanish (and maybe early versions?) only: when moving cursor over input pad + { GID_SQ1, 103, 103, 0, "hand", "internalEvent", -1, 2, { WORKAROUND_FAKE, 0 } }, // spanish (and maybe early versions?) only: when moving cursor over input pad + { GID_SQ1, -1, 703, 0, "", "export 1", -1, 0, { WORKAROUND_FAKE, 0 } }, // sub that's called from several objects while on sarien battle cruiser + { GID_SQ1, -1, 703, 0, "firePulsar", "changeState", 0x18a, 0, { WORKAROUND_FAKE, 0 } }, // export 1, but called locally (when shooting at aliens) + { GID_SQ4, -1, 398, 0, "showBox", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // sq4cd: called when rummaging in Software Excess bargain bin + { GID_SQ4, -1, 928, 0, "Narrator", "startText", -1, 1000, { WORKAROUND_FAKE, 1 } }, // sq4cd: method returns this to the caller + { GID_SQ6, 100, 0, 0, "SQ6", "init", -1, 2, { WORKAROUND_FAKE, 0 } }, // called when the game starts + { GID_SQ6, 100, 64950, 0, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu + { GID_SQ6, -1, 64964, 0, "DPath", "init", -1, 1, { WORKAROUND_FAKE, 0 } }, // during the game + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kAbs_workarounds[] = { + { GID_HOYLE1, 1, 1, 0, "room1", "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // crazy eights - called with objects instead of integers + { GID_HOYLE1, 2, 2, 0, "room2", "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // old maid - called with objects instead of integers + { GID_HOYLE1, 3, 3, 0, "room3", "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // hearts - called with objects instead of integers + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kCelHigh_workarounds[] = { + { GID_SQ1, 1, 255, 0, "DIcon", "setSize", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // DEMO: Called with 2nd/3rd parameters as objects when clicking on the menu + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kCelWide_workarounds[] = { + { GID_SQ1, 1, 255, 0, "DIcon", "setSize", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // DEMO: Called with 2nd/3rd parameters as objects when clicking on the menu - bug #3035720 + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kDisplay_workarounds[] = { + { GID_ISLANDBRAIN, 300, 300, 0, "geneDude", "show", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when looking at the gene explanation chart - a parameter is an object + { GID_SQ4, 391, 391, 0, "doCatalog", "mode", 0x84, 0, { WORKAROUND_IGNORE, 0 } }, // clicking on catalog in roboter sale - a parameter is an object + { GID_SQ4, 391, 391, 0, "choosePlug", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // ordering connector in roboter sale - a parameter is an object + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kDisposeScript_workarounds[] = { + { GID_LAURABOW, 777, 777, 0, "myStab", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: after the will is signed, parameter 0 is an object - bug #3034907 + { GID_QFG1, -1, 64, 0, "rm64", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when leaving graveyard, parameter 0 is an object + { GID_SQ4, 150, 151, 0, "fightScript", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during fight with vohaul, parameter 0 is an object + { GID_SQ4, 150, 152, 0, "driveCloseUp", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when choosing "beam download", parameter 0 is an object + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kDoSoundFade_workarounds[] = { + { GID_CAMELOT, -1, 989, 0, "rmMusic", "fade", -1, 0, { WORKAROUND_IGNORE, 0 } }, // gets called frequently with a NULL reference (i.e. 0:0) - bug #3035149 + { GID_KQ1, -1, 989, 0, "gameSound", "fade", -1, 0, { WORKAROUND_IGNORE, 0 } }, // gets called in several scenes (e.g. graham cracker) with 0:0 + { GID_KQ6, 105, 989, 0, "globalSound", "fade", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // floppy: during intro, parameter 4 is an object + { GID_KQ6, 460, 989, 0, "globalSound2", "fade", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // after pulling the black widow's web on the isle of wonder, parameter 4 is an object - bug #3034567 + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kGetAngle_workarounds[] = { + { GID_KQ6, 740, 752, 0, "throwDazzle", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // after the Genie is exposed in the Palace (short and long ending), it starts shooting lightning bolts around. An extra 5th parameter is passed - bug #3034610 + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kFindKey_workarounds[] = { + { GID_ECOQUEST2, 100, 999, 0, "myList", "contains", -1, 0, { WORKAROUND_FAKE, 0 } }, // When Noah Greene gives Adam the Ecorder, and just before the game gives a demonstration, a null reference to a list is passed - bug #3035186 + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kGraphDrawLine_workarounds[] = { + { GID_ISLANDBRAIN, 300, 300, 0, "dudeViewer", "show", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when looking at the gene explanation chart, gets called with 1 extra parameter + { GID_SQ1, 43, 43, 0, "someoneDied", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when ordering beer, gets called with 1 extra parameter + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kGraphSaveBox_workarounds[] = { + { GID_CASTLEBRAIN, 420, 427, 0, "alienIcon", "select", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when selecting a card during the alien card game, gets called with 1 extra parameter + { GID_ISLANDBRAIN, 290, 291, 0, "upElevator", "changeState",0x201f, 0, { WORKAROUND_STILLCALL, 0 } }, // when testing in the elevator puzzle, gets called with 1 argument less - 15 is on stack - bug #3034485 + { GID_ISLANDBRAIN, 290, 291, 0, "downElevator", "changeState",0x201f, 0, { WORKAROUND_STILLCALL, 0 } }, // see above + { GID_ISLANDBRAIN, 290, 291, 0, "correctElevator", "changeState",0x201f, 0, { WORKAROUND_STILLCALL, 0 } }, // see above (when testing the correct solution) + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kGraphRestoreBox_workarounds[] = { + { GID_LSL6, -1, 85, 0, "rScroller", "hide", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens when restoring (sometimes), same as the one below + { GID_LSL6, -1, 85, 0, "lScroller", "hide", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens when restoring (sometimes), same as the one below + { GID_LSL6, -1, 86, 0, "LL6Inv", "show", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens when restoring, is called with hunk segment, but hunk is not allocated at that time + // ^^ TODO: check, if this is really a script error or an issue with our restore code + { GID_LSL6, -1, 86, 0, "LL6Inv", "hide", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens during the game, gets called with 1 extra parameter + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kGraphFillBoxForeground_workarounds[] = { + { GID_LSL6, -1, 0, 0, "LSL6", "hideControls", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens when giving the bungee key to merrily (room 240) and at least in room 650 too - gets called with additional 5th parameter + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kGraphFillBoxAny_workarounds[] = { + { GID_SQ4, -1, 818, 0, "iconTextSwitch", "show", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // game menu "text/speech" display - parameter 5 is missing, but the right color number is on the stack + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kGraphRedrawBox_workarounds[] = { + { GID_SQ4, 405, 405, 0, "swimAfterEgo", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified + { GID_SQ4, 406, 406, 0, "swimAndShoot", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified + { GID_SQ4, 410, 410, 0, "swimAfterEgo", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified + { GID_SQ4, 411, 411, 0, "swimAndShoot", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified + { GID_SQ4, -1, 704, 0, "shootEgo", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // When shot by Droid in Super Computer Maze (Rooms 500, 505, 510...) - accidental additional parameter specified + { GID_KQ5, -1, 981, 0, "myWindow", "dispose", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // Happens in the floppy version, when closing any dialog box, accidental additional parameter specified - bug #3036331 + { GID_KQ5, -1, 995, 0, "invW", "doit", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // Happens in the floppy version, when closing the inventory window, accidental additional parameter specified + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kIsObject_workarounds[] = { + { GID_GK1, 50, 999, 0, "List", "eachElementDo", -1, 0, { WORKAROUND_FAKE, 0 } }, // GK1 demo, when asking Grace for messages it gets called with an invalid parameter (type "error") - bug #3034519 + { GID_ISLANDBRAIN, -1, 999, 0, "List", "eachElementDo", -1, 0, { WORKAROUND_FAKE, 0 } }, // when going to the game options, choosing "Info" and selecting anything from the list, gets called with an invalid parameter (type "error") - bug #3035262 + { GID_QFG3, -1, 999, 0, "List", "eachElementDo", -1, 0, { WORKAROUND_FAKE, 0 } }, // when asking for something, gets called with type error parameter + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kMemory_workarounds[] = { + { GID_LAURABOW2, -1, 999, 0, "", "export 6", -1, 0, { WORKAROUND_FAKE, 0 } }, // during the intro, when exiting the train, talking to Mr. Augustini, etc. - bug #3034490 + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kNewWindow_workarounds[] = { + { GID_ECOQUEST, -1, 981, 0, "SysWindow", "<noname 178>", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // EcoQuest 1 demo uses an in-between interpreter from SCI1 to SCI1.1. It's SCI1.1, but uses the SCI1 semantics for this call - bug #3035057 + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[] = { + { GID_QFG4, 100, 100, 0, "doMovie", "<noname 144>", -1, 0, { WORKAROUND_IGNORE, 0 } }, // after the Sierra logo, no flags are passed, thus the call is meaningless - bug #3034506 + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kSetPort_workarounds[] = { + { GID_LSL6, 740, 740, 0, "rm740", "drawPic", -1, 0, { WORKAROUND_IGNORE, 0 } }, // ending scene, is called with additional 3 (!) parameters + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, call,index, workaround +const SciWorkaroundEntry kUnLoad_workarounds[] = { + { GID_CAMELOT, 921, 921, 1, "Script", "changeState", 0x36, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: While showing Camelot (and other places), the reference is invalid - bug #3035000 + { GID_CAMELOT, 921, 921, 1, "Script", "init", 0x36, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: When being attacked by the boar (and other places), the reference is invalid - bug #3035000 + { GID_CASTLEBRAIN, 320, 377, 0, "SWord", "upDate", -1, 0, { WORKAROUND_IGNORE, 0 } }, // after solving the cross-word-puzzle, trying to unload invalid reference + { GID_CASTLEBRAIN, 320, 377, 0, "theWord", "show", -1, 0, { WORKAROUND_IGNORE, 0 } }, // 2nd word puzzle, when exiting before solving, trying to unload invalid reference - bug #3034473 + { GID_ECOQUEST, 380, 61, 0, "gotIt", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // after talking to the dolphin the first time + { GID_LAURABOW2, 1, 1, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during the intro, a 3rd parameter is passed by accident - bug #3034902 + { GID_LAURABOW2, 2, 2, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during the intro, a 3rd parameter is passed by accident - bug #3034902 + { GID_LAURABOW2, 4, 4, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: inside the museum, a 3rd parameter is passed by accident - bug #3034902 + { GID_LAURABOW2, 6, 6, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: after the murder, a 3rd parameter is passed by accident - bug #3034902 + { GID_LAURABOW2, 7, 7, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: after the logo is shown, a 3rd parameter is passed by accident - bug #3034902 + { GID_LSL6, 130, 130, 0, "recruitLarryScr", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident + { GID_LSL6, 740, 740, 0, "showCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during ending, 4 additional parameters are passed by accident + { GID_LSL6HIRES, 130, 130, 0, "recruitLarryScr", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident + { GID_SQ1, 43, 303, 0, "slotGuy", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when leaving ulence flats bar, parameter 1 is not passed - script error + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin) { + EngineState *state = g_sci->getEngineState(); + ExecStack *lastCall = state->xs; + Script *local_script = state->_segMan->getScriptIfLoaded(lastCall->local_segment); + int curScriptNr = local_script->getScriptNumber(); + + if (lastCall->debugLocalCallOffset != -1) { + // if lastcall was actually a local call search back for a real call + Common::List<ExecStack>::iterator callIterator = state->_executionStack.end(); + while (callIterator != state->_executionStack.begin()) { + callIterator--; + ExecStack loopCall = *callIterator; + if ((loopCall.debugSelector != -1) || (loopCall.debugExportId != -1)) { + lastCall->debugSelector = loopCall.debugSelector; + lastCall->debugExportId = loopCall.debugExportId; + break; + } + } + } + + Common::String curObjectName = state->_segMan->getObjectName(lastCall->sendp); + Common::String curMethodName; + const SciGameId gameId = g_sci->getGameId(); + const int curRoomNumber = state->currentRoomNumber(); + + if (lastCall->type == EXEC_STACK_TYPE_CALL) { + if (lastCall->debugSelector != -1) { + curMethodName = g_sci->getKernel()->getSelectorName(lastCall->debugSelector); + } else if (lastCall->debugExportId != -1) { + curObjectName = ""; + curMethodName = curMethodName.printf("export %d", lastCall->debugExportId); + } + } + + if (workaroundList) { + // Search if there is a workaround for this one + const SciWorkaroundEntry *workaround; + int16 inheritanceLevel = 0; + Common::String searchObjectName = curObjectName; + reg_t searchObject = lastCall->sendp; + do { + workaround = workaroundList; + while (workaround->methodName) { + if (workaround->gameId == gameId + && ((workaround->scriptNr == -1) || (workaround->scriptNr == curScriptNr)) + && ((workaround->roomNr == -1) || (workaround->roomNr == curRoomNumber)) + && ((workaround->inheritanceLevel == -1) || (workaround->inheritanceLevel == inheritanceLevel)) + && ((workaround->objectName == NULL) || (workaround->objectName == searchObjectName)) + && workaround->methodName == curMethodName && workaround->localCallOffset == lastCall->debugLocalCallOffset + && ((workaround->index == -1) || (workaround->index == index))) { + // Workaround found + return workaround->newValue; + } + workaround++; + } + + // Go back to the parent + inheritanceLevel++; + searchObject = state->_segMan->getObject(searchObject)->getSuperClassSelector(); + if (!searchObject.isNull()) + searchObjectName = state->_segMan->getObjectName(searchObject); + } while (!searchObject.isNull()); // no parent left? + } + + // give caller origin data + trackOrigin->objectName = curObjectName; + trackOrigin->methodName = curMethodName; + trackOrigin->scriptNr = curScriptNr; + trackOrigin->localCallOffset = lastCall->debugLocalCallOffset; + + SciWorkaroundSolution noneFound; + noneFound.type = WORKAROUND_NONE; + noneFound.value = 0; + return noneFound; +} + +} // End of namespace Sci diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h new file mode 100644 index 0000000000..d509d300d7 --- /dev/null +++ b/engines/sci/engine/workarounds.h @@ -0,0 +1,100 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SCI_ENGINE_WORKAROUNDS_H +#define SCI_ENGINE_WORKAROUNDS_H + +#include "sci/engine/vm_types.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 SciTrackOriginReply { + int scriptNr; + Common::String objectName; + Common::String methodName; + int localCallOffset; +}; + +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; + int localCallOffset; + int index; + SciWorkaroundSolution newValue; +}; + +extern const SciWorkaroundEntry opcodeDivWorkarounds[]; +extern const SciWorkaroundEntry opcodeOrWorkarounds[]; +extern const SciWorkaroundEntry opcodeDptoaWorkarounds[]; +extern const SciWorkaroundEntry uninitializedReadWorkarounds[]; +extern const SciWorkaroundEntry kAbs_workarounds[]; +extern const SciWorkaroundEntry kCelHigh_workarounds[]; +extern const SciWorkaroundEntry kCelWide_workarounds[]; +extern const SciWorkaroundEntry kDisplay_workarounds[]; +extern const SciWorkaroundEntry kDisposeScript_workarounds[]; +extern const SciWorkaroundEntry kDoSoundFade_workarounds[]; +extern const SciWorkaroundEntry kFindKey_workarounds[]; +extern const SciWorkaroundEntry kGetAngle_workarounds[]; +extern const SciWorkaroundEntry kGraphDrawLine_workarounds[]; +extern const SciWorkaroundEntry kGraphSaveBox_workarounds[]; +extern const SciWorkaroundEntry kGraphRestoreBox_workarounds[]; +extern const SciWorkaroundEntry kGraphFillBoxForeground_workarounds[]; +extern const SciWorkaroundEntry kGraphFillBoxAny_workarounds[]; +extern const SciWorkaroundEntry kGraphRedrawBox_workarounds[]; +extern const SciWorkaroundEntry kIsObject_workarounds[]; +extern const SciWorkaroundEntry kMemory_workarounds[]; +extern const SciWorkaroundEntry kNewWindow_workarounds[]; +extern const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[]; +extern const SciWorkaroundEntry kSetPort_workarounds[]; +extern const SciWorkaroundEntry kUnLoad_workarounds[]; + +extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin); + +} // End of namespace Sci + +#endif // SCI_ENGINE_WORKAROUNDS_H |