aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/engine
diff options
context:
space:
mode:
authorJohannes Schickel2010-10-13 03:57:44 +0000
committerJohannes Schickel2010-10-13 03:57:44 +0000
commit75e8452b6e6a2bf4fb2f588aa00b428a60d873b5 (patch)
treef29541d55309487a94bd1d38e8b53bb3dde9aec6 /engines/sci/engine
parent48ee83b88957dab86bc763e9ef21a70179fa8679 (diff)
parente9f50882ea5b6beeefa994040be9d3bab6a1f107 (diff)
downloadscummvm-rg350-75e8452b6e6a2bf4fb2f588aa00b428a60d873b5.tar.gz
scummvm-rg350-75e8452b6e6a2bf4fb2f588aa00b428a60d873b5.tar.bz2
scummvm-rg350-75e8452b6e6a2bf4fb2f588aa00b428a60d873b5.zip
OPENGL: Merged from trunk, from rev 52105 to 53396.
This includes an rather hacky attempt to merge all the recent gp2x backend changes into the branch. I suppose the gp2x backend and probably all new backends, i.e. gph, dingux etc., might not compile anymore. Since I have no way of testing those it would be nice if porters could look into getting those up to speed in this branch. svn-id: r53399
Diffstat (limited to 'engines/sci/engine')
-rw-r--r--engines/sci/engine/features.cpp17
-rw-r--r--engines/sci/engine/gc.cpp13
-rw-r--r--engines/sci/engine/kernel.cpp8
-rw-r--r--engines/sci/engine/kernel.h12
-rw-r--r--engines/sci/engine/kernel_tables.h142
-rw-r--r--engines/sci/engine/kevent.cpp33
-rw-r--r--engines/sci/engine/kfile.cpp411
-rw-r--r--engines/sci/engine/kgraphics.cpp374
-rw-r--r--engines/sci/engine/klists.cpp3
-rw-r--r--engines/sci/engine/kmath.cpp66
-rw-r--r--engines/sci/engine/kmenu.cpp12
-rw-r--r--engines/sci/engine/kmisc.cpp16
-rw-r--r--engines/sci/engine/kmovement.cpp495
-rw-r--r--engines/sci/engine/kparse.cpp24
-rw-r--r--engines/sci/engine/kpathing.cpp64
-rw-r--r--engines/sci/engine/kscripts.cpp74
-rw-r--r--engines/sci/engine/kstring.cpp32
-rw-r--r--engines/sci/engine/kvideo.cpp2
-rw-r--r--engines/sci/engine/savegame.cpp58
-rw-r--r--engines/sci/engine/savegame.h2
-rw-r--r--engines/sci/engine/script.cpp29
-rw-r--r--engines/sci/engine/script.h23
-rw-r--r--engines/sci/engine/script_patches.cpp606
-rw-r--r--engines/sci/engine/seg_manager.cpp22
-rw-r--r--engines/sci/engine/segment.h9
-rw-r--r--engines/sci/engine/selector.cpp6
-rw-r--r--engines/sci/engine/selector.h7
-rw-r--r--engines/sci/engine/state.cpp12
-rw-r--r--engines/sci/engine/state.h28
-rw-r--r--engines/sci/engine/static_selectors.cpp128
-rw-r--r--engines/sci/engine/vm.cpp148
-rw-r--r--engines/sci/engine/workarounds.cpp72
-rw-r--r--engines/sci/engine/workarounds.h3
33 files changed, 1998 insertions, 953 deletions
diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp
index f99d412c64..97eec38caf 100644
--- a/engines/sci/engine/features.cpp
+++ b/engines/sci/engine/features.cpp
@@ -138,9 +138,9 @@ bool GameFeatures::autoDetectSoundType() {
SciVersion GameFeatures::detectDoSoundType() {
if (_doSoundType == SCI_VERSION_NONE) {
if (getSciVersion() == SCI_VERSION_0_EARLY) {
- // This game is using early SCI0 sound code (different headers than
- // SCI0 late)
- _doSoundType = SCI_VERSION_0_EARLY;
+ // Almost all of the SCI0EARLY games use different sound resources than
+ // SCI0LATE. Although the last SCI0EARLY game (lsl2) uses SCI0LATE resources
+ _doSoundType = g_sci->getResMan()->detectEarlySound() ? SCI_VERSION_0_EARLY : SCI_VERSION_0_LATE;
#ifdef ENABLE_SCI32
} else if (getSciVersion() >= SCI_VERSION_2_1) {
_doSoundType = SCI_VERSION_2_1;
@@ -275,15 +275,8 @@ SciVersion GameFeatures::detectLofsType() {
return _lofsType;
}
- // Find the "Game" object, super class of the actual game-object
- const reg_t game = g_sci->getGameObject();
- const Object *gameObject = _segMan->getObject(game);
- reg_t gameSuperClass = NULL_REG;
- if (gameObject) {
- gameSuperClass = gameObject->getSuperClassSelector();
- }
-
- // Find a function of the game object which invokes lofsa/lofss
+ // Find a function of the "Game" object (which is the game super class) which invokes lofsa/lofss
+ reg_t gameSuperClass = g_sci->getGameSuperClassAddress();
bool found = false;
if (!gameSuperClass.isNull()) {
Common::String gameSuperClassName = _segMan->getObjectName(gameSuperClass);
diff --git a/engines/sci/engine/gc.cpp b/engines/sci/engine/gc.cpp
index 936b83d760..c1939c6566 100644
--- a/engines/sci/engine/gc.cpp
+++ b/engines/sci/engine/gc.cpp
@@ -28,6 +28,8 @@
namespace Sci {
+//#define GC_DEBUG_CODE
+
struct WorklistManager {
Common::Array<reg_t> _worklist;
AddrSet _map;
@@ -153,10 +155,12 @@ void run_gc(EngineState *s) {
// Some debug stuff
debugC(2, kDebugLevelGC, "[GC] Running...");
+#ifdef GC_DEBUG_CODE
const char *segnames[SEG_TYPE_MAX + 1];
int segcount[SEG_TYPE_MAX + 1];
memset(segnames, 0, sizeof(segnames));
memset(segcount, 0, sizeof(segcount));
+#endif
// Compute the set of all segments references currently in use.
AddrSet *activeRefs = findAllActiveReferences(s);
@@ -166,10 +170,13 @@ void run_gc(EngineState *s) {
const Common::Array<SegmentObj *> &heap = segMan->getSegments();
for (uint seg = 1; seg < heap.size(); seg++) {
SegmentObj *mobj = heap[seg];
+
if (mobj != NULL) {
+#ifdef GC_DEBUG_CODE
const SegmentType type = mobj->getType();
segnames[type] = SegmentObj::getSegmentTypeName(type);
-
+#endif
+
// Get a list of all deallocatable objects in this segment,
// then free any which are not referenced from somewhere.
const Common::Array<reg_t> tmp = mobj->listAllDeallocatable(seg);
@@ -179,7 +186,9 @@ void run_gc(EngineState *s) {
// Not found -> we can free it
mobj->freeAtAddress(segMan, addr);
debugC(2, kDebugLevelGC, "[GC] Deallocating %04x:%04x", PRINT_REG(addr));
+#ifdef GC_DEBUG_CODE
segcount[type]++;
+#endif
}
}
@@ -188,11 +197,13 @@ void run_gc(EngineState *s) {
delete activeRefs;
+#ifdef GC_DEBUG_CODE
// Output debug summary of garbage collection
debugC(2, kDebugLevelGC, "[GC] Summary:");
for (int i = 0; i <= SEG_TYPE_MAX; i++)
if (segcount[i])
debugC(2, kDebugLevelGC, "\t%d\t* %s", segcount[i], segnames[i]);
+#endif
}
} // End of namespace Sci
diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp
index 157884fac3..ff327a0049 100644
--- a/engines/sci/engine/kernel.cpp
+++ b/engines/sci/engine/kernel.cpp
@@ -744,11 +744,9 @@ 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) {
- // hoyle 3 has cantBeHere selector but is assuming to call kCanBeHere
- if (g_sci->getGameId() != GID_HOYLE3)
- _kernelNames[0x4d] = "CantBeHere";
- }
+ // If vocab.999 exists, the kernel function is still named CanBeHere
+ if (_selectorCache.cantBeHere != -1)
+ _kernelNames[0x4d] = "CantBeHere";
switch (getSciVersion()) {
case SCI_VERSION_0_EARLY:
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index b6247b46f1..e50c6aaae2 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -278,7 +278,6 @@ private:
/******************** Kernel functions ********************/
-// New kernel functions
reg_t kStrLen(EngineState *s, int argc, reg_t *argv);
reg_t kGetFarText(EngineState *s, int argc, reg_t *argv);
reg_t kReadNumber(EngineState *s, int argc, reg_t *argv);
@@ -419,6 +418,8 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv);
reg_t kPlatform(EngineState *s, int argc, reg_t *argv);
reg_t kTextColors(EngineState *s, int argc, reg_t *argv);
reg_t kTextFonts(EngineState *s, int argc, reg_t *argv);
+reg_t kShow(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColors(EngineState *s, int argc, reg_t *argv);
reg_t 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);
@@ -443,13 +444,14 @@ reg_t kAddPlane(EngineState *s, int argc, reg_t *argv);
reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv);
reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv);
reg_t kRepaintPlane(EngineState *s, int argc, reg_t *argv);
+reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv);
reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv);
reg_t kFrameOut(EngineState *s, int argc, reg_t *argv);
+reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv); // kOnMe for SCI2, kIsOnMe for SCI2.1
reg_t kListIndexOf(EngineState *s, int argc, reg_t *argv);
reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv);
reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv);
reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv);
-reg_t kOnMe(EngineState *s, int argc, reg_t *argv);
reg_t kInPolygon(EngineState *s, int argc, reg_t *argv);
// SCI2.1 Kernel Functions
@@ -458,13 +460,15 @@ reg_t kSave(EngineState *s, int argc, reg_t *argv);
reg_t kList(EngineState *s, int argc, reg_t *argv);
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);
+reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv);
+reg_t kWinHelp(EngineState *s, int argc, reg_t *argv);
+reg_t kWinDLL(EngineState *s, int argc, reg_t *argv);
+reg_t kPrintDebug(EngineState *s, int argc, reg_t *argv);
#endif
reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index e71e97e95d..d2c95053d5 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -81,6 +81,8 @@ struct SciKernelMapSubEntry {
#define SIG_EVERYWHERE SIG_SCIALL, SIGFOR_ALL
#define MAP_CALL(_name_) #_name_, k##_name_
+#define MAP_EMPTY(_name_) #_name_, kEmpty
+#define MAP_DUMMY(_name_) #_name_, kDummy
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kDoSound_subops[] = {
@@ -248,7 +250,7 @@ static const SciKernelMapSubEntry kFileIO_subops[] = {
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, 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 },
@@ -257,20 +259,14 @@ static const SciKernelMapSubEntry kList_subops[] = {
{ 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, 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, 18, MAP_CALL(ListIndexOf) , "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 },
@@ -323,13 +319,13 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(Display), SIG_EVERYWHERE, "[ir]([ir!]*)", NULL, kDisplay_workarounds },
// ^ 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(DirLoop), SIG_EVERYWHERE, "oi", NULL, kDirLoop_workarounds },
{ 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(DoAvoider), SIG_EVERYWHERE, "o(i)", 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
@@ -409,12 +405,13 @@ static SciKernelMapEntry s_kernelMap[] = {
{ 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(RemapColors), SIG_EVERYWHERE, "i(i)(i)(i)(i)(i)", 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(RestoreGame), SIG_EVERYWHERE, "[r0]i[r0]", NULL, NULL },
{ MAP_CALL(Said), SIG_EVERYWHERE, "[r0]", NULL, NULL },
- { MAP_CALL(SaveGame), SIG_EVERYWHERE, "rir(r)", NULL, NULL },
+ { MAP_CALL(SaveGame), SIG_EVERYWHERE, "[r0]i[r0](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
@@ -430,11 +427,12 @@ static SciKernelMapEntry s_kernelMap[] = {
{ 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(Show), SIG_EVERYWHERE, "i", 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, kStrAt_workarounds },
- { MAP_CALL(StrCat), SIG_EVERYWHERE, "rr", NULL, NULL },
+ { MAP_CALL(StrCat), SIG_EVERYWHERE, "rr", NULL, kStrCat_workarounds },
{ 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 },
@@ -454,8 +452,25 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(ValidPath), SIG_EVERYWHERE, "r", NULL, NULL },
{ MAP_CALL(Wait), SIG_EVERYWHERE, "i", NULL, NULL },
+ // Unimplemented SCI0-SCI1.1 unused functions, always mapped to kDummy
+ { MAP_DUMMY(InspectObj), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(ShowSends), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(ShowObjs), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(ShowFree), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(StackUsage), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(Profiler), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(ShiftScreen), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(ListOps), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(ATan), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(Record), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(PlayBack), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(DbugStr), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+
+ // =======================================================================================================
+
#ifdef ENABLE_SCI32
// SCI2 Kernel Functions
+ // TODO: whoever knows his way through those calls, fix the signatures.
{ MAP_CALL(AddPlane), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(AddScreenItem), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(Array), SIG_EVERYWHERE, "(.*)", NULL, NULL },
@@ -471,15 +486,22 @@ static SciKernelMapEntry s_kernelMap[] = {
{ 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 },
+ { "OnMe", kIsOnMe, SIG_EVERYWHERE, "iioi", NULL, NULL },
{ MAP_CALL(RepaintPlane), SIG_EVERYWHERE, "o", NULL, NULL },
+ { MAP_CALL(SetShowStyle), SIG_EVERYWHERE, "ioiiiii(i)", 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 empty functions
+
+ // Purge is used by the memory manager in SSCI to ensure that X number of bytes (the so called "unmovable
+ // memory") are available. We have our own memory manager and garbage collector, thus we ignore this call.
+ { MAP_EMPTY(Purge), SIG_EVERYWHERE, "i", NULL, NULL },
+
// SCI2.1 Kernel Functions
- { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL },
- { MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iio(.*)", NULL, NULL },
+ { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iioi", 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 },
@@ -487,8 +509,32 @@ static SciKernelMapEntry s_kernelMap[] = {
{ 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 }
+ { MAP_CALL(GetWindowsOption), SIG_EVERYWHERE, "i", NULL, NULL },
+ { MAP_CALL(WinHelp), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(WinDLL), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(PrintDebug), SIG_EVERYWHERE, "ri", NULL, NULL },
+
+ // SCI2.1 empty functions
+
+ // SetWindowsOption is used to set Windows specific options, like for example the title bar visibility of
+ // the game window in Phantasmagoria 2. We ignore these settings completely.
+ { MAP_EMPTY(SetWindowsOption), SIG_EVERYWHERE, "ii", NULL, NULL },
+
+ // Unimplemented SCI2.1 unused functions, always mapped to kDummy
+ { MAP_DUMMY(InspectObject), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ // Profiler (same as SCI0-SCI1.1)
+ // Record (same as SCI0-SCI1.1)
+ // PlayBack (same as SCI0-SCI1.1)
+ { MAP_DUMMY(MonoOut), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(SetFatalStr), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(IntegrityChecking),SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(CheckIntegrity), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(MarkMemory), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(GetSierraProfileInt), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(GetSierraProfileString), SIG_EVERYWHERE, "(.*)", NULL, NULL },
#endif
+
+ { NULL, NULL, SIG_EVERYWHERE, NULL, NULL, NULL }
};
/** Default kernel name table. */
@@ -502,7 +548,7 @@ static const char *s_defaultKernelNames[] = {
/*0x06*/ "IsObject",
/*0x07*/ "RespondsTo",
/*0x08*/ "DrawPic",
- /*0x09*/ "Dummy", // Show
+ /*0x09*/ "Show",
/*0x0a*/ "PicNotValid",
/*0x0b*/ "Animate",
/*0x0c*/ "SetNowSeen",
@@ -574,20 +620,20 @@ static const char *s_defaultKernelNames[] = {
/*0x4a*/ "ReadNumber",
/*0x4b*/ "BaseSetter",
/*0x4c*/ "DirLoop",
- /*0x4d*/ "CanBeHere", // CantBeHere in newer SCI versions
+ /*0x4d*/ "CanBeHere", // CantBeHere in newer SCI versions
/*0x4e*/ "OnControl",
/*0x4f*/ "InitBresen",
/*0x50*/ "DoBresen",
- /*0x51*/ "Platform", // DoAvoider (SCI0)
+ /*0x51*/ "Platform", // DoAvoider (SCI0)
/*0x52*/ "SetJump",
- /*0x53*/ "SetDebug",
- /*0x54*/ "Dummy", // InspectObj
- /*0x55*/ "Dummy", // ShowSends
- /*0x56*/ "Dummy", // ShowObjs
- /*0x57*/ "Dummy", // ShowFree
+ /*0x53*/ "SetDebug", // for debugging
+ /*0x54*/ "InspectObj", // for debugging
+ /*0x55*/ "ShowSends", // for debugging
+ /*0x56*/ "ShowObjs", // for debugging
+ /*0x57*/ "ShowFree", // for debugging
/*0x58*/ "MemoryInfo",
- /*0x59*/ "Dummy", // StackUsage
- /*0x5a*/ "Dummy", // Profiler
+ /*0x59*/ "StackUsage", // for debugging
+ /*0x5a*/ "Profiler", // for debugging
/*0x5b*/ "GetMenu",
/*0x5c*/ "SetMenu",
/*0x5d*/ "GetSaveFiles",
@@ -608,33 +654,33 @@ static const char *s_defaultKernelNames[] = {
/*0x6c*/ "Graph",
/*0x6d*/ "Joystick",
// End of kernel function table for SCI0
- /*0x6e*/ "Dummy", // ShiftScreen
+ /*0x6e*/ "ShiftScreen", // never called?
/*0x6f*/ "Palette",
/*0x70*/ "MemorySegment",
/*0x71*/ "Intersections", // MoveCursor (SCI1 late), PalVary (SCI1.1)
/*0x72*/ "Memory",
- /*0x73*/ "Dummy", // ListOps
+ /*0x73*/ "ListOps", // never called?
/*0x74*/ "FileIO",
/*0x75*/ "DoAudio",
/*0x76*/ "DoSync",
/*0x77*/ "AvoidPath",
- /*0x78*/ "Sort", // StrSplit (SCI01)
- /*0x79*/ "Dummy", // ATan
+ /*0x78*/ "Sort", // StrSplit (SCI01)
+ /*0x79*/ "ATan", // never called?
/*0x7a*/ "Lock",
/*0x7b*/ "StrSplit",
- /*0x7c*/ "GetMessage", // Message (SCI1.1)
+ /*0x7c*/ "GetMessage", // Message (SCI1.1)
/*0x7d*/ "IsItSkip",
/*0x7e*/ "MergePoly",
/*0x7f*/ "ResCheck",
/*0x80*/ "AssertPalette",
/*0x81*/ "TextColors",
/*0x82*/ "TextFonts",
- /*0x83*/ "Dummy", // Record
- /*0x84*/ "Dummy", // PlayBack
+ /*0x83*/ "Record", // for debugging
+ /*0x84*/ "PlayBack", // for debugging
/*0x85*/ "ShowMovie",
/*0x86*/ "SetVideoMode",
/*0x87*/ "SetQuitStr",
- /*0x88*/ "Dummy" // DbugStr
+ /*0x88*/ "DbugStr" // for debugging
};
#ifdef ENABLE_SCI32
@@ -757,13 +803,13 @@ static const char *sci2_default_knames[] = {
/*0x70*/ "InPolygon",
/*0x71*/ "MergePoly",
/*0x72*/ "SetDebug",
- /*0x73*/ "InspectObject",
+ /*0x73*/ "InspectObject", // for debugging
/*0x74*/ "MemoryInfo",
- /*0x75*/ "Profiler",
- /*0x76*/ "Record",
- /*0x77*/ "PlayBack",
- /*0x78*/ "MonoOut",
- /*0x79*/ "SetFatalStr",
+ /*0x75*/ "Profiler", // for debugging
+ /*0x76*/ "Record", // for debugging
+ /*0x77*/ "PlayBack", // for debugging
+ /*0x78*/ "MonoOut", // for debugging
+ /*0x79*/ "SetFatalStr", // for debugging
/*0x7a*/ "GetCWD",
/*0x7b*/ "ValidPath",
/*0x7c*/ "FileIO",
@@ -775,10 +821,10 @@ static const char *sci2_default_knames[] = {
/*0x82*/ "Array",
/*0x83*/ "String",
/*0x84*/ "RemapColors",
- /*0x85*/ "IntegrityChecking",
- /*0x86*/ "CheckIntegrity",
+ /*0x85*/ "IntegrityChecking", // for debugging
+ /*0x86*/ "CheckIntegrity", // for debugging
/*0x87*/ "ObjectIntersect",
- /*0x88*/ "MarkMemory",
+ /*0x88*/ "MarkMemory", // for debugging
/*0x89*/ "TextWidth",
/*0x8a*/ "PointSize",
@@ -934,7 +980,7 @@ static const char *sci21_default_knames[] = {
/*0x7c*/ "SetQuitStr",
/*0x7d*/ "GetConfig",
/*0x7e*/ "Table",
- /*0x7f*/ "WinHelp", // Windows only
+ /*0x7f*/ "WinHelp", // Windows only
/*0x80*/ "Dummy",
/*0x81*/ "Dummy",
/*0x82*/ "Dummy",
@@ -956,8 +1002,8 @@ static const char *sci21_default_knames[] = {
/*0x92*/ "PlayVMD",
/*0x93*/ "SetHotRectangles",
/*0x94*/ "MulDiv",
- /*0x95*/ "GetSierraProfileInt", // Windows only
- /*0x96*/ "GetSierraProfileString", // Windows only
+ /*0x95*/ "GetSierraProfileInt", // , Windows only
+ /*0x96*/ "GetSierraProfileString", // , Windows only
/*0x97*/ "SetWindowsOption", // Windows only
/*0x98*/ "GetWindowsOption", // Windows only
/*0x99*/ "WinDLL", // Windows only
diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp
index 3395811700..9d48174078 100644
--- a/engines/sci/engine/kevent.cpp
+++ b/engines/sci/engine/kevent.cpp
@@ -23,6 +23,8 @@
*
*/
+#include "common/system.h"
+
#include "sci/sci.h"
#include "sci/engine/features.h"
#include "sci/engine/state.h"
@@ -42,7 +44,6 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
int mask = argv[0].toUint16();
reg_t obj = argv[1];
SciEvent curEvent;
- int oldx, oldy;
int modifier_mask = getSciVersion() <= SCI_VERSION_01 ? SCI_KEYMOD_ALL : SCI_KEYMOD_NO_FOOLOCK;
SegManager *segMan = s->_segMan;
Common::Point mousePos;
@@ -67,13 +68,24 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, 1);
}
- oldx = mousePos.x;
- oldy = mousePos.y;
curEvent = g_sci->getEventManager()->getSciEvent(mask);
if (g_sci->getVocabulary())
g_sci->getVocabulary()->parser_event = NULL_REG; // Invalidate parser event
+ if (s->_cursorWorkaroundActive) {
+ // ffs: GfxCursor::setPosition()
+ // we check, if actual cursor position is inside given rect
+ // if that's the case, we switch ourself off. Otherwise
+ // we simulate the original set position to the scripts
+ if (s->_cursorWorkaroundRect.contains(mousePos.x, mousePos.y)) {
+ s->_cursorWorkaroundActive = false;
+ } else {
+ mousePos.x = s->_cursorWorkaroundPoint.x;
+ mousePos.y = s->_cursorWorkaroundPoint.y;
+ }
+ }
+
writeSelectorValue(segMan, obj, SELECTOR(x), mousePos.x);
writeSelectorValue(segMan, obj, SELECTOR(y), mousePos.y);
@@ -162,6 +174,21 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
g_sci->_soundCmd->updateSci0Cues();
}
+ // Wait a bit here, so that the CPU isn't maxed out when the game
+ // is waiting for user input (e.g. when showing text boxes) - bug
+ // #3037874. This works when games do benchmarking at the beginning,
+ // because most of them call kAnimate for benchmarking without
+ // calling kGetEvent in between (rightly so).
+ if (g_sci->getGameId() == GID_JONES && g_sci->getEngineState()->currentRoomNumber() == 764) {
+ // Jones CD is an exception, as it incorrectly calls GetEvent
+ // while benchmarking. Thus, don't delay here for room 764 in
+ // Jones (the speed test room), otherwise speed testing will
+ // fail and the game won't show any views, as it will think that
+ // the user has a slow machine - bug #3058865
+ } else {
+ g_system->delayMillis(10);
+ }
+
return s->r_acc;
}
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 39c32ccc68..b6d67513d2 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -24,9 +24,13 @@
*/
#include "common/archive.h"
+#include "common/config-manager.h"
#include "common/file.h"
#include "common/str.h"
#include "common/savefile.h"
+#include "common/translation.h"
+
+#include "gui/saveload.h"
#include "sci/sci.h"
#include "sci/engine/state.h"
@@ -37,7 +41,7 @@
namespace Sci {
struct SavegameDesc {
- uint id;
+ int16 id;
int virtualId; // straight numbered, according to id but w/o gaps
int date;
int time;
@@ -98,13 +102,9 @@ enum {
-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;
-
+reg_t file_open(EngineState *s, const char *filename, int mode, bool unwrapFilename) {
Common::String englishName = g_sci->getSciLanguageString(filename, K_LANG_ENGLISH);
- const Common::String wrappedName = g_sci->wrapFilename(englishName);
+ Common::String wrappedName = unwrapFilename ? g_sci->wrapFilename(englishName) : englishName;
Common::SeekableReadStream *inFile = 0;
Common::WriteStream *outFile = 0;
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
@@ -180,7 +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);
- return file_open(s, name.c_str(), mode);
+ return file_open(s, name.c_str(), mode, true);
}
static FileHandle *getFileFromHandle(EngineState *s, uint handle) {
@@ -218,29 +218,31 @@ reg_t kFPuts(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
-static void fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) {
+static int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) {
FileHandle *f = getFileFromHandle(s, handle);
if (!f)
- return;
+ return 0;
if (!f->_in) {
error("fgets_wrapper: Trying to read from file '%s' opened for writing", f->_name.c_str());
- return;
+ return 0;
}
+ int readBytes = 0;
if (maxsize > 1) {
memset(dest, 0, maxsize);
f->_in->readLine(dest, maxsize);
+ readBytes = strlen(dest); // FIXME: sierra sci returned byte count and didn't react on NUL characters
// The returned string must not have an ending LF
- int strSize = strlen(dest);
- if (strSize > 0) {
- if (dest[strSize - 1] == 0x0A)
- dest[strSize - 1] = 0;
+ if (readBytes > 0) {
+ if (dest[readBytes - 1] == 0x0A)
+ dest[readBytes - 1] = 0;
}
} else {
- *dest = f->_in->readByte();
+ *dest = 0;
}
debugC(2, kDebugLevelFile, " -> FGets'ed \"%s\"", dest);
+ return readBytes;
}
reg_t kFGets(EngineState *s, int argc, reg_t *argv) {
@@ -249,9 +251,9 @@ reg_t kFGets(EngineState *s, int argc, reg_t *argv) {
int handle = argv[2].toUint16();
debugC(2, kDebugLevelFile, "kFGets(%d, %d)", handle, maxsize);
- fgets_wrapper(s, buf, maxsize, handle);
+ int readBytes = fgets_wrapper(s, buf, maxsize, handle);
s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize);
- return argv[0];
+ return readBytes ? argv[0] : NULL_REG;
}
/**
@@ -269,7 +271,7 @@ reg_t kGetCWD(EngineState *s, int argc, reg_t *argv) {
}
static void listSavegames(Common::Array<SavegameDesc> &saves);
-static int findSavegame(Common::Array<SavegameDesc> &saves, uint saveId);
+static int findSavegame(Common::Array<SavegameDesc> &saves, int16 saveId);
enum {
K_DEVICE_INFO_GET_DEVICE = 0,
@@ -452,7 +454,7 @@ static void listSavegames(Common::Array<SavegameDesc> &saves) {
}
// Find a savedgame according to virtualId and return the position within our array
-static int findSavegame(Common::Array<SavegameDesc> &saves, uint savegameId) {
+static int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId) {
for (uint saveNr = 0; saveNr < saves.size(); saveNr++) {
if (saves[saveNr].id == savegameId)
return saveNr;
@@ -460,7 +462,7 @@ static int findSavegame(Common::Array<SavegameDesc> &saves, uint savegameId) {
return -1;
}
-// The scripts get IDs ranging from 1000->1999, because the scripts require us to assign unique ids THAT EVEN STAY BETWEEN
+// The scripts get IDs ranging from 100->199, because the scripts require us to assign unique ids THAT EVEN STAY BETWEEN
// SAVES and the scripts also use "saves-count + 1" to create a new savedgame slot.
// SCI1.1 actually recycles ids, in that case we will currently get "0".
// This behaviour is required especially for LSL6. In this game, it's possible to quick save. The scripts will use
@@ -494,7 +496,7 @@ reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) {
// Find saved-game
if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END))
- error("kCheckSaveGame: called with invalid savegameId!");
+ error("kCheckSaveGame: called with invalid savegameId");
uint savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START;
int savegameNr = findSavegame(saves, savegameId);
if (savegameNr == -1)
@@ -548,78 +550,110 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
}
reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
- Common::String game_id = s->_segMan->getString(argv[0]);
- uint virtualId = argv[1].toUint16();
- Common::String game_description = s->_segMan->getString(argv[2]);
+ Common::String game_id;
+ int16 virtualId = argv[1].toSint16();
+ int16 savegameId = -1;
+ Common::String game_description;
Common::String version;
+
if (argc > 3)
version = s->_segMan->getString(argv[3]);
- 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);
-
- uint savegameId;
- 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 (argv[0].isNull()) {
+ // Direct call, from a patched Game::save
+ if ((argv[1] != SIGNAL_REG) || (!argv[2].isNull()))
+ error("kSaveGame: assumed patched call isn't accurate");
+
+ // we are supposed to show a dialog for the user and let him choose where to save
+ g_sci->_soundCmd->pauseAll(true); // pause music
+ const EnginePlugin *plugin = NULL;
+ EngineMan.findGame(g_sci->getGameIdStr(), &plugin);
+ GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"));
+ dialog->setSaveMode(true);
+ savegameId = dialog->runModal(plugin, ConfMan.getActiveDomainName());
+ game_description = dialog->getResultString();
+ if (game_description.empty()) {
+ // create our own description for the saved game, the user didnt enter it
+ TimeDate curTime;
+ g_system->getTimeAndDate(curTime);
+ curTime.tm_year += 1900; // fixup year
+ curTime.tm_mon++; // fixup month
+ game_description = Common::String::printf("%02d.%02d.%04d / %02d:%02d:%02d", curTime.tm_mday, curTime.tm_mon, curTime.tm_year, curTime.tm_hour, curTime.tm_min, curTime.tm_sec);
+ }
+ delete dialog;
+ g_sci->_soundCmd->pauseAll(false); // unpause music ( we can't have it paused during save)
+ if (savegameId < 0)
return NULL_REG;
- } else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) {
- // virtualId is low, we assume that scripts expect us to create new slot
- if (virtualId == s->_lastSaveVirtualId) {
- // if last virtual id is the same as this one, we assume that caller wants to overwrite last save
- savegameId = s->_lastSaveNewId;
- } else {
- uint savegameNr;
- // savegameId is in lower range, scripts expect us to create a new slot
- for (savegameId = 0; savegameId < SAVEGAMEID_OFFICIALRANGE_START; savegameId++) {
- for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) {
- if (savegameId == saves[savegameNr].id)
+
+ } else {
+ // Real call from script
+ game_id = s->_segMan->getString(argv[0]);
+ if (argv[2].isNull())
+ error("kSaveGame: called with description being NULL");
+ game_description = s->_segMan->getString(argv[2]);
+
+ debug(3, "kSaveGame(%s,%d,%s,%s)", game_id.c_str(), virtualId, game_description.c_str(), version.c_str());
+
+ Common::Array<SavegameDesc> saves;
+ listSavegames(saves);
+
+ 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)
+ return NULL_REG;
+ } else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) {
+ // virtualId is low, we assume that scripts expect us to create new slot
+ if (virtualId == s->_lastSaveVirtualId) {
+ // if last virtual id is the same as this one, we assume that caller wants to overwrite last save
+ savegameId = s->_lastSaveNewId;
+ } else {
+ uint savegameNr;
+ // savegameId is in lower range, scripts expect us to create a new slot
+ for (savegameId = 0; savegameId < SAVEGAMEID_OFFICIALRANGE_START; savegameId++) {
+ for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) {
+ if (savegameId == saves[savegameNr].id)
+ break;
+ }
+ if (savegameNr == saves.size())
break;
}
- if (savegameNr == saves.size())
- break;
+ if (savegameId == SAVEGAMEID_OFFICIALRANGE_START)
+ error("kSavegame: no more savegame slots available");
}
- if (savegameId == SAVEGAMEID_OFFICIALRANGE_START)
- error("kSavegame: no more savegame slots available");
+ } else {
+ error("kSaveGame: invalid savegameId used");
}
- } else {
- error("kSaveGame: invalid savegameId used");
+
+ // Save in case caller wants to overwrite last newly created save
+ s->_lastSaveVirtualId = virtualId;
+ s->_lastSaveNewId = savegameId;
}
- // Save in case caller wants to overwrite last newly created save
- s->_lastSaveVirtualId = virtualId;
- s->_lastSaveNewId = savegameId;
+ s->r_acc = NULL_REG;
Common::String filename = g_sci->getSavegameName(savegameId);
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
Common::OutSaveFile *out;
if (!(out = saveFileMan->openForSaving(filename))) {
warning("Error opening savegame \"%s\" for writing", filename.c_str());
- s->r_acc = NULL_REG;
- return NULL_REG;
- }
-
- if (!gamestate_save(s, out, game_description.c_str(), version.c_str())) {
- warning("Saving the game failed.");
- s->r_acc = NULL_REG;
} else {
- out->finalize();
- if (out->err()) {
- delete out;
- warning("Writing the savegame failed.");
- s->r_acc = NULL_REG;
+ if (!gamestate_save(s, out, game_description.c_str(), version.c_str())) {
+ warning("Saving the game failed");
} else {
+ out->finalize();
+ if (out->err()) {
+ warning("Writing the savegame failed");
+ } else {
+ s->r_acc = TRUE_REG; // success
+ }
delete out;
- s->r_acc = make_reg(0, 1);
}
}
@@ -628,41 +662,75 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : "";
- uint savegameId = argv[1].toUint16();
+ int16 savegameId = argv[1].toSint16();
+ bool pausedMusic = false;
debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId);
if (argv[0].isNull()) {
- // Loading from the launcher, don't adjust the ID of the saved game
+ // Direct call, either from launcher or from a patched Game::restore
+ if (savegameId == -1) {
+ // we are supposed to show a dialog for the user and let him choose a saved game
+ g_sci->_soundCmd->pauseAll(true); // pause music
+ const EnginePlugin *plugin = NULL;
+ EngineMan.findGame(g_sci->getGameIdStr(), &plugin);
+ GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"));
+ dialog->setSaveMode(false);
+ savegameId = dialog->runModal(plugin, ConfMan.getActiveDomainName());
+ delete dialog;
+ if (savegameId < 0) {
+ g_sci->_soundCmd->pauseAll(false); // unpause music
+ return s->r_acc;
+ }
+ pausedMusic = true;
+ }
+ // don't adjust ID of the saved game, it's already correct
} else {
- if ((savegameId < 1000) || (savegameId > 1999)) {
+ if (argv[2].isNull())
+ error("kRestoreGame: called with parameter 2 being NULL");
+ // Real call from script, we need to adjust ID
+ if ((savegameId < SAVEGAMEID_OFFICIALRANGE_START) || (savegameId > SAVEGAMEID_OFFICIALRANGE_END)) {
warning("Savegame ID %d is not allowed", savegameId);
return TRUE_REG;
}
- savegameId -= 1000;
+ savegameId -= SAVEGAMEID_OFFICIALRANGE_START;
}
+ s->r_acc = NULL_REG; // signals success
+
Common::Array<SavegameDesc> saves;
listSavegames(saves);
if (findSavegame(saves, savegameId) == -1) {
+ s->r_acc = TRUE_REG;
warning("Savegame ID %d not found", savegameId);
- return TRUE_REG;
- }
-
- Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
- Common::String filename = g_sci->getSavegameName(savegameId);
- Common::SeekableReadStream *in;
- if ((in = saveFileMan->openForLoading(filename))) {
- // found a savegame file
+ } else {
+ Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
+ Common::String filename = g_sci->getSavegameName(savegameId);
+ Common::SeekableReadStream *in;
+ if ((in = saveFileMan->openForLoading(filename))) {
+ // found a savegame file
- gamestate_restore(s, in);
- delete in;
+ gamestate_restore(s, in);
+ delete in;
- return s->r_acc;
+ if (g_sci->getGameId() == GID_MOTHERGOOSE256) {
+ // WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for
+ // saving a previously restored game.
+ // We set the current savedgame-id directly and remove the script
+ // code concerning this via script patch.
+ s->variables[VAR_GLOBAL][0xB3].offset = SAVEGAMEID_OFFICIALRANGE_START + savegameId;
+ }
+ } else {
+ s->r_acc = TRUE_REG;
+ warning("Savegame #%d not found", savegameId);
+ }
}
- s->r_acc = TRUE_REG;
- warning("Savegame #%d not found", savegameId);
+ if (!s->r_acc.isNull()) {
+ // no success?
+ if (pausedMusic)
+ g_sci->_soundCmd->pauseAll(false); // unpause music
+ }
return s->r_acc;
}
@@ -676,45 +744,6 @@ reg_t kValidPath(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, 1);
}
-reg_t DirSeeker::firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan) {
- // Verify that we are given a valid buffer
- if (!buffer.segment) {
- error("DirSeeker::firstFile('%s') invoked with invalid buffer", mask.c_str());
- return NULL_REG;
- }
- _outbuffer = buffer;
-
- // Prefix the mask
- const Common::String wrappedMask = g_sci->wrapFilename(mask);
-
- // Obtain a list of all savefiles matching the given mask
- Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
- _savefiles = saveFileMan->listSavefiles(wrappedMask);
-
- // Reset the list iterator and write the first match to the output buffer,
- // if any.
- _iter = _savefiles.begin();
- return nextFile(segMan);
-}
-
-reg_t DirSeeker::nextFile(SegManager *segMan) {
- if (_iter == _savefiles.end()) {
- return NULL_REG;
- }
-
- const Common::String wrappedString = *_iter;
-
- // Strip the prefix
- Common::String string = g_sci->unwrapFilename(wrappedString);
- if (string.size() > 12)
- string = Common::String(string.c_str(), 12);
- segMan->strcpy(_outbuffer, string.c_str());
-
- // Return the result and advance the list iterator :)
- ++_iter;
- return _outbuffer;
-}
-
reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
if (!s)
return make_reg(0, getSciVersion());
@@ -727,6 +756,7 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
// 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();
+ bool unwrapFilename = true;
// SQ4 floppy prepends /\ to the filenames
if (name.hasPrefix("/\\")) {
@@ -744,11 +774,21 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
}
if (name.empty()) {
- warning("Attempted to open a file with an empty filename");
+ // Happens many times during KQ1 (e.g. when typing something)
+ debugC(2, kDebugLevelFile, "Attempted to open a file with an empty filename");
return SIGNAL_REG;
}
debugC(2, kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode);
- return file_open(s, name.c_str(), mode);
+
+ // QFG import rooms get a virtual filelisting instead of an actual one
+ if (g_sci->inQfGImportRoom()) {
+ // we need to find out what the user actually selected, "savedHeroes" is already destroyed
+ // when we get here. That's why we need to remember selection via kDrawControl
+ name = s->_dirseeker.getVirtualFilename(s->_chosenQfGImportItem);
+ unwrapFilename = false;
+ }
+
+ return file_open(s, name.c_str(), mode, unwrapFilename);
}
reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {
@@ -840,10 +880,10 @@ reg_t kFileIOReadString(EngineState *s, int argc, reg_t *argv) {
int handle = argv[2].toUint16();
debugC(2, kDebugLevelFile, "kFileIO(readString): %d, %d", handle, size);
- fgets_wrapper(s, buf, size, handle);
+ int readBytes = fgets_wrapper(s, buf, size, handle);
s->_segMan->memcpy(argv[0], (const byte*)buf, size);
delete[] buf;
- return argv[0];
+ return readBytes ? argv[0] : NULL_REG;
}
reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) {
@@ -852,9 +892,12 @@ reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) {
debugC(2, kDebugLevelFile, "kFileIO(writeString): %d", handle);
FileHandle *f = getFileFromHandle(s, handle);
- if (f)
+
+ if (f) {
f->_out->write(str.c_str(), str.size());
return NULL_REG;
+ }
+
return make_reg(0, 6); // DOS - invalid handle
}
@@ -865,23 +908,117 @@ reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv) {
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));
+
return SIGNAL_REG;
}
+void DirSeeker::addAsVirtualFiles(Common::String title, Common::String fileMask) {
+ Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
+ Common::StringArray foundFiles = saveFileMan->listSavefiles(fileMask);
+ if (!foundFiles.empty()) {
+ _files.push_back(title);
+ _virtualFiles.push_back("");
+ Common::StringArray::iterator it;
+ Common::StringArray::iterator it_end = foundFiles.end();
+
+ for (it = foundFiles.begin(); it != it_end; it++) {
+ Common::String regularFilename = *it;
+ Common::String wrappedFilename = Common::String(regularFilename.c_str() + fileMask.size() - 1);
+
+ Common::SeekableReadStream *testfile = saveFileMan->openForLoading(regularFilename);
+ int32 testfileSize = testfile->size();
+ delete testfile;
+ if (testfileSize > 1024) // check, if larger than 1k. in that case its a saved game.
+ continue; // and we dont want to have those in the list
+ // We need to remove the prefix for display purposes
+ _files.push_back(wrappedFilename);
+ // but remember the actual name as well
+ _virtualFiles.push_back(regularFilename);
+ }
+ }
+}
+
+Common::String DirSeeker::getVirtualFilename(uint fileNumber) {
+ if (fileNumber >= _virtualFiles.size())
+ error("invalid virtual filename access");
+ return _virtualFiles[fileNumber];
+}
+
+reg_t DirSeeker::firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan) {
+ // Verify that we are given a valid buffer
+ if (!buffer.segment) {
+ error("DirSeeker::firstFile('%s') invoked with invalid buffer", mask.c_str());
+ return NULL_REG;
+ }
+ _outbuffer = buffer;
+ _files.clear();
+ _virtualFiles.clear();
+
+ int QfGImport = g_sci->inQfGImportRoom();
+ if (QfGImport) {
+ _files.clear();
+ addAsVirtualFiles("-QfG1-", "qfg1-*");
+ addAsVirtualFiles("-QfG1VGA-", "qfg1vga-*");
+ if (QfGImport > 2)
+ addAsVirtualFiles("-QfG2-", "qfg2-*");
+ if (QfGImport > 3)
+ addAsVirtualFiles("-QfG3-", "qfg3-*");
+
+ if (QfGImport == 3) {
+ // QfG3 sorts the filelisting itself, we can't let that happen otherwise our
+ // virtual list would go out-of-sync
+ reg_t savedHeros = segMan->findObjectByName("savedHeros");
+ if (!savedHeros.isNull())
+ writeSelectorValue(segMan, savedHeros, SELECTOR(sort), 0);
+ }
+
+ } else {
+ // Prefix the mask
+ const Common::String wrappedMask = g_sci->wrapFilename(mask);
+
+ // Obtain a list of all files matching the given mask
+ Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
+ _files = saveFileMan->listSavefiles(wrappedMask);
+ }
+
+ // Reset the list iterator and write the first match to the output buffer,
+ // if any.
+ _iter = _files.begin();
+ return nextFile(segMan);
+}
+
+reg_t DirSeeker::nextFile(SegManager *segMan) {
+ if (_iter == _files.end()) {
+ return NULL_REG;
+ }
+
+ Common::String string;
+
+ if (_virtualFiles.empty()) {
+ // Strip the prefix, if we don't got a virtual filelisting
+ const Common::String wrappedString = *_iter;
+ string = g_sci->unwrapFilename(wrappedString);
+ } else {
+ string = *_iter;
+ }
+ if (string.size() > 12)
+ string = Common::String(string.c_str(), 12);
+ segMan->strcpy(_outbuffer, string.c_str());
+
+ // Return the result and advance the list iterator :)
+ ++_iter;
+ return _outbuffer;
+}
+
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);
- }
-
// We remove ".*". mask will get prefixed, so we will return all additional files for that gameid
if (mask == "*.*")
mask = "*";
@@ -960,7 +1097,7 @@ 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?
+ return s->r_acc; // FIXME: does this really not return anything?
}
reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv) {
@@ -974,7 +1111,7 @@ 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?
+ return s->r_acc; // FIXME: does this really not return anything?
}
reg_t kCD(EngineState *s, int argc, reg_t *argv) {
diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp
index a3f7a90da3..07385fd3f9 100644
--- a/engines/sci/engine/kgraphics.cpp
+++ b/engines/sci/engine/kgraphics.cpp
@@ -57,48 +57,46 @@
namespace Sci {
-void _k_dirloop(reg_t object, uint16 angle, EngineState *s, int argc, reg_t *argv) {
+void showScummVMDialog(const Common::String &message) {
+ GUI::MessageDialog dialog(message, "OK");
+ dialog.runModal();
+}
+
+void kDirLoopWorker(reg_t object, uint16 angle, EngineState *s, int argc, reg_t *argv) {
GuiResourceId viewId = readSelectorValue(s->_segMan, object, SELECTOR(view));
uint16 signal = readSelectorValue(s->_segMan, object, SELECTOR(signal));
- int16 loopNo;
- int16 maxLoops;
- bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);
if (signal & kSignalDoesntTurn)
return;
- angle %= 360;
-
- if (!oldScriptHeader) {
- if (angle < 45)
- loopNo = 3;
- else if (angle < 136)
- loopNo = 0;
- else if (angle < 225)
- loopNo = 2;
- else if (angle < 316)
- loopNo = 1;
- else
- loopNo = 3;
+ int16 useLoop = -1;
+ if (getSciVersion() > SCI_VERSION_0_EARLY) {
+ if ((angle > 315) || (angle < 45)) {
+ useLoop = 3;
+ } else if ((angle > 135) && (angle < 225)) {
+ useLoop = 2;
+ }
} else {
- if (angle >= 330 || angle <= 30)
- loopNo = 3;
- else if (angle <= 150)
- loopNo = 0;
- else if (angle <= 210)
- loopNo = 2;
- else if (angle < 330)
- loopNo = 1;
- else loopNo = -1;
+ // SCI0EARLY
+ if ((angle > 330) || (angle < 30)) {
+ useLoop = 3;
+ } else if ((angle > 150) && (angle < 210)) {
+ useLoop = 2;
+ }
+ }
+ if (useLoop == -1) {
+ if (angle >= 180) {
+ useLoop = 1;
+ } else {
+ useLoop = 0;
+ }
+ } else {
+ int16 loopCount = g_sci->_gfxCache->kernelViewGetLoopCount(viewId);
+ if (loopCount < 4)
+ return;
}
- maxLoops = g_sci->_gfxCache->kernelViewGetLoopCount(viewId);
-
-
- if ((loopNo > 1) && (maxLoops < 4))
- return;
-
- writeSelectorValue(s->_segMan, object, SELECTOR(loop), loopNo);
+ writeSelectorValue(s->_segMan, object, SELECTOR(loop), useLoop);
}
static reg_t kSetCursorSci0(EngineState *s, int argc, reg_t *argv) {
@@ -131,8 +129,7 @@ static reg_t kSetCursorSci11(EngineState *s, int argc, reg_t *argv) {
g_sci->_gfxCursor->kernelHide();
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
+ g_sci->_gfxCursor->kernelClearZoomZone();
break;
case -2:
g_sci->_gfxCursor->kernelResetMoveZone();
@@ -186,15 +183,10 @@ static reg_t kSetCursorSci11(EngineState *s, int argc, reg_t *argv) {
break;
case 10:
// Freddy pharkas, when using the whiskey glass to read the prescription (bug #3034973)
- // magnifier support, disabled using argc == 1, argv == -1
- warning("kSetCursor: unsupported magnifier");
- // we just set the view cursor currently
- g_sci->_gfxCursor->kernelSetView(argv[5].toUint16(), argv[6].toUint16(), argv[7].toUint16(), hotspot);
- // argv[0] -> 1, 2, 4 -> maybe magnification multiplier
- // argv[1-4] -> rect for magnification
- // argv[5, 6, 7] -> view resource for cursor
- // argv[8] -> picture resource for mag
- // argv[9] -> color for magnifier replacement
+ g_sci->_gfxCursor->kernelSetZoomZone(argv[0].toUint16(),
+ Common::Rect(argv[1].toUint16(), argv[2].toUint16(), argv[3].toUint16(), argv[4].toUint16()),
+ argv[5].toUint16(), argv[6].toUint16(), argv[7].toUint16(),
+ argv[8].toUint16(), argv[9].toUint16());
break;
default :
error("kSetCursor: Unhandled case: %d arguments given", argc);
@@ -264,7 +256,9 @@ reg_t kGraphDrawLine(EngineState *s, int argc, reg_t *argv) {
int16 priority = (argc > 5) ? argv[5].toSint16() : -1;
int16 control = (argc > 6) ? argv[6].toSint16() : -1;
- // TODO: Find out why we get >15 for color in EGA
+ // TODO: Find out why we get > 15 for color in EGA
+ // FIXME: EGA? Which EGA? SCI0 or SCI1? Check the
+ // workarounds inside kGraphFillBoxAny and kNewWindow
if (!g_sci->getResMan()->isVGA() && !g_sci->getResMan()->isAmiga32color())
color &= 0x0F;
@@ -303,6 +297,13 @@ reg_t kGraphFillBoxAny(EngineState *s, int argc, reg_t *argv) {
int16 priority = argv[6].toSint16(); // yes, we may read from stack sometimes here
int16 control = argv[7].toSint16(); // sierra did the same
+ // WORKAROUND: PQ3 EGA is setting invalid colors (above 0 - 15).
+ // Colors above 15 are all white in SCI1 EGA games, which is why this was never
+ // observed. We clip them all to (0, 15) instead, as colors above 15 are used
+ // for the undithering algorithm in EGA games - bug #3048908.
+ if (g_sci->getResMan()->getViewType() == kViewEga && getSciVersion() >= SCI_VERSION_1_EARLY)
+ color &= 0x0F;
+
g_sci->_gfxPaint16->kernelGraphFillBox(rect, colorMask, color, priority, control);
return s->r_acc;
}
@@ -397,7 +398,7 @@ reg_t kPriCoord(EngineState *s, int argc, reg_t *argv) {
}
reg_t kDirLoop(EngineState *s, int argc, reg_t *argv) {
- _k_dirloop(argv[0], argv[1].toUint16(), s, argc, argv);
+ kDirLoopWorker(argv[0], argv[1].toUint16(), s, argc, argv);
return s->r_acc;
}
@@ -801,6 +802,7 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) {
alignment = readSelectorValue(s->_segMan, controlObject, SELECTOR(mode));
debugC(2, kDebugLevelGraphics, "drawing text %04x:%04x ('%s') to %d,%d, mode=%d", PRINT_REG(controlObject), text.c_str(), x, y, alignment);
g_sci->_gfxControls->kernelDrawText(rect, controlObject, g_sci->strSplit(text.c_str()).c_str(), fontId, alignment, style, hilite);
+ s->r_acc = g_sci->_gfxText16->allocAndFillReferenceRectArray();
return;
case SCI_CONTROLS_TYPE_TEXTEDIT:
@@ -896,6 +898,10 @@ reg_t kDrawControl(EngineState *s, int argc, reg_t *argv) {
reg_t controlObject = argv[0];
Common::String objName = s->_segMan->getObjectName(controlObject);
+ // Most of the time, we won't return anything to the caller
+ // but |r| textcodes will trigger creation of rects in memory and will then set s->r_acc
+ s->r_acc = NULL_REG;
+
// Disable the "Change Directory" button, as we don't allow the game engine to
// change the directory where saved games are placed
// "changeDirItem" is used in the import windows of QFG2&3
@@ -922,20 +928,19 @@ reg_t kDrawControl(EngineState *s, int argc, reg_t *argv) {
if (!changeDirButton.isNull()) {
// check if checkDirButton is still enabled, in that case we are called the first time during that room
if (!(readSelectorValue(s->_segMan, changeDirButton, SELECTOR(state)) & SCI_CONTROLS_STYLE_DISABLED)) {
- GUI::MessageDialog dialog("Characters saved inside ScummVM are shown "
+ showScummVMDialog("Characters saved inside ScummVM are shown "
"automatically. Character files saved in the original "
"interpreter need to be put inside ScummVM's saved games "
"directory and a prefix needs to be added depending on which "
"game it was saved in: 'qfg1-' for Quest for Glory 1, 'qfg2-' "
- "for Quest for Glory 2. Example: 'qfg2-thief.sav'.",
- "OK");
- dialog.runModal();
+ "for Quest for Glory 2. Example: 'qfg2-thief.sav'.");
}
}
+ s->_chosenQfGImportItem = readSelectorValue(s->_segMan, controlObject, SELECTOR(mark));
}
_k_GenericDrawControl(s, controlObject, false);
- return NULL_REG;
+ return s->r_acc;
}
reg_t kHiliteControl(EngineState *s, int argc, reg_t *argv) {
@@ -1078,6 +1083,15 @@ reg_t kNewWindow(EngineState *s, int argc, reg_t *argv) {
int colorPen = (argc > 7 + argextra) ? argv[7 + argextra].toSint16() : 0;
int colorBack = (argc > 8 + argextra) ? argv[8 + argextra].toSint16() : 255;
+ // WORKAROUND: PQ3 EGA is setting invalid colors (above 0 - 15).
+ // Colors above 15 are all white in SCI1 EGA games, which is why this was never
+ // observed. We clip them all to (0, 15) instead, as colors above 15 are used
+ // for the undithering algorithm in EGA games - bug #3048908.
+ if (g_sci->getResMan()->getViewType() == kViewEga && getSciVersion() >= SCI_VERSION_1_EARLY) {
+ colorPen &= 0x0F;
+ colorBack &= 0x0F;
+ }
+
// const char *title = argv[4 + argextra].segment ? kernel_dereference_char_pointer(s, argv[4 + argextra], 0) : NULL;
if (argc>=13) {
rect2 = Common::Rect (argv[5].toSint16(), argv[4].toSint16(), argv[7].toSint16(), argv[6].toSint16());
@@ -1152,6 +1166,81 @@ reg_t kTextColors(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
+/**
+ * Debug command, used by the SCI builtin debugger
+ */
+reg_t kShow(EngineState *s, int argc, reg_t *argv) {
+ uint16 map = argv[0].toUint16();
+
+ switch (map) {
+ case 1: // Visual, substituted by display for us
+ g_sci->_gfxScreen->debugShowMap(3);
+ break;
+ case 2: // Priority
+ g_sci->_gfxScreen->debugShowMap(1);
+ break;
+ case 3: // Control
+ case 4: // Control
+ g_sci->_gfxScreen->debugShowMap(2);
+ break;
+ default:
+ warning("Map %d is not available", map);
+ }
+
+ return s->r_acc;
+}
+
+reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) {
+ // TODO: This is all a stub/skeleton, thus we're invoking kStub() for now
+ kStub(s, argc, argv);
+
+ uint16 operation = argv[0].toUint16();
+
+ switch (operation) {
+ case 0: { // Initialize remapping to base. 0 turns remapping off.
+ //int16 unk1 = (argc >= 2) ? argv[1].toSint16() : 0;
+ }
+ break;
+ case 1: { // unknown
+ // The demo of QFG4 calls this with 1+3 parameters, thus there are differences here
+ //int16 unk1 = argv[1].toSint16();
+ //int16 unk2 = argv[2].toSint16();
+ //int16 unk3 = argv[3].toSint16();
+ //uint16 unk4 = argv[4].toUint16();
+ //uint16 unk5 = (argc >= 6) ? argv[5].toUint16() : 0;
+ }
+ break;
+ case 2: { // remap by percent
+ //int16 unk1 = argv[1].toSint16();
+ //uint16 percent = argv[2].toUint16();
+ //uint16 unk3 = (argc >= 4) ? argv[3].toUint16() : 0;
+ }
+ break;
+ case 3: { // remap to gray
+ //int16 unk1 = argv[1].toSint16();
+ //int16 percent = argv[2].toSint16(); // 0 - 100
+ //uint16 unk3 = (argc >= 4) ? argv[3].toUint16() : 0;
+ }
+ break;
+ case 4: { // unknown
+ //int16 unk1 = argv[1].toSint16();
+ //uint16 unk2 = argv[2].toUint16();
+ //uint16 unk3 = argv[3].toUint16();
+ //uint16 unk4 = (argc >= 5) ? argv[4].toUint16() : 0;
+ }
+ break;
+ case 5: { // increment color
+ //int16 unk1 = argv[1].toSint16();
+ //uint16 unk2 = argv[2].toUint16();
+ }
+ break;
+ default:
+ break;
+ }
+
+ return s->r_acc;
+}
+
#ifdef ENABLE_SCI32
reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) {
@@ -1172,69 +1261,38 @@ reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv) {
}
reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) {
- reg_t viewObj = argv[0];
-
- g_sci->_gfxFrameout->kernelAddScreenItem(viewObj);
- return NULL_REG;
+ g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]);
+ return s->r_acc;
}
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;
+ g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]);
+ return s->r_acc;
}
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;
+ g_sci->_gfxFrameout->kernelDeleteScreenItem(argv[0]);
+ return s->r_acc;
}
reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) {
- reg_t planeObj = argv[0];
-
- g_sci->_gfxFrameout->kernelAddPlane(planeObj);
- return NULL_REG;
+ g_sci->_gfxFrameout->kernelAddPlane(argv[0]);
+ return s->r_acc;
}
reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv) {
- reg_t planeObj = argv[0];
-
- g_sci->_gfxFrameout->kernelDeletePlane(planeObj);
- return NULL_REG;
+ g_sci->_gfxFrameout->kernelDeletePlane(argv[0]);
+ return s->r_acc;
}
reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) {
- reg_t planeObj = argv[0];
-
- g_sci->_gfxFrameout->kernelUpdatePlane(planeObj);
+ g_sci->_gfxFrameout->kernelUpdatePlane(argv[0]);
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;
+ g_sci->_gfxFrameout->kernelRepaintPlane(argv[0]);
+ return s->r_acc;
}
reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv) {
@@ -1261,9 +1319,8 @@ reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
-reg_t kOnMe(EngineState *s, int argc, reg_t *argv) {
- // Tests if the cursor is on the passed object
-
+// Tests if the coordinate is on the passed object
+reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv) {
uint16 x = argv[0].toUint16();
uint16 y = argv[1].toUint16();
reg_t targetObject = argv[2];
@@ -1296,75 +1353,9 @@ reg_t kOnMe(EngineState *s, int argc, reg_t *argv) {
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());
-
- return make_reg(0, nsRect.contains(x, y));
-}
-
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()) {
@@ -1421,6 +1412,61 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
+reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv) {
+ uint16 windowsOption = argv[0].toUint16();
+ switch (windowsOption) {
+ case 0:
+ // Title bar on/off in Phantasmagoria, we return 0 (off)
+ return NULL_REG;
+ default:
+ warning("GetWindowsOption: Unknown option %d", windowsOption);
+ return NULL_REG;
+ }
+}
+
+reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) {
+ switch (argv[0].toUint16()) {
+ case 1:
+ // Load a help file
+ // Maybe in the future we can implement this, but for now this message should suffice
+ showScummVMDialog("Please use an external viewer to open the game's help file: " + s->_segMan->getString(argv[1]));
+ break;
+ case 2:
+ // Looks like some init function
+ break;
+ default:
+ warning("Unknown kWinHelp subop %d", argv[0].toUint16());
+ }
+
+ return s->r_acc;
+}
+
+/**
+ * Used to programmatically mass set properties of the target plane
+ */
+reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
+ // TODO: This is all a stub/skeleton, thus we're invoking kStub() for now
+ kStub(s, argc, argv);
+
+ // showStyle matches the style selector of the associated plane object
+ uint16 showStyle = argv[0].toUint16(); // 0 - 15
+ reg_t planeObj = argv[1];
+ //argv[2]
+ //int16 priority = argv[3].toSint16();
+ //argv[4]
+ //argv[5]
+ //argv[6]
+ //argv[7]
+ //int16 unk8 = (argc >= 9) ? argv[8].toSint16() : 0;
+
+ if (showStyle > 15) {
+ warning("kSetShowStyle: Illegal style %d for plane %04x:%04x", showStyle, PRINT_REG(planeObj));
+ return s->r_acc;
+ }
+
+ return s->r_acc;
+}
+
#endif
} // End of namespace Sci
diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp
index 93e95099f5..2188087b8c 100644
--- a/engines/sci/engine/klists.cpp
+++ b/engines/sci/engine/klists.cpp
@@ -40,6 +40,9 @@ static bool isSaneNodePointer(SegManager *segMan, reg_t 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 if ((g_sci->getGameId() == GID_HOYLE1) && (g_sci->getEngineState()->currentRoomNumber() == 3)) {
+ // HOYLE1: after sorting cards in hearts, in the next round
+ // we get an invalid node - bug #3038433
} else {
error("isSaneNodePointer: Node at %04x:%04x wasn't found", PRINT_REG(addr));
}
diff --git a/engines/sci/engine/kmath.cpp b/engines/sci/engine/kmath.cpp
index 332fbb62f8..792181b832 100644
--- a/engines/sci/engine/kmath.cpp
+++ b/engines/sci/engine/kmath.cpp
@@ -35,19 +35,21 @@ reg_t kRandom(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
case 2: { // get random number
- int fromNumber = argv[0].toUint16();
- int toNumber = argv[1].toUint16();
-
- // TODO/CHECKME: It is propbably not required to check whether
- // toNumber is greater than fromNumber, at least not when one
- // goes by their names, but let us be on the safe side and
- // allow toNumber to be smaller than fromNumber too.
- if (fromNumber > toNumber)
- SWAP(fromNumber, toNumber);
-
- const uint diff = (uint)(toNumber - fromNumber);
-
- const int randomNumber = fromNumber + (int)g_sci->getRNG().getRandomNumber(diff);
+ // numbers are definitely unsigned, for example lsl5 door code in k rap radio is random
+ // and 5-digit - we get called kRandom(10000, 65000)
+ // some codes in sq4 are also random and 5 digit (if i remember correctly)
+ const uint16 fromNumber = argv[0].toUint16();
+ const uint16 toNumber = argv[1].toUint16();
+ uint16 range = toNumber - fromNumber + 1;
+ // calculating range is exactly how sierra sci did it and is required for hoyle 4
+ // where we get called with kRandom(0, -1) and we are supposed to give back values from 0 to 0
+ // the returned value will be used as displace-offset for a background cel
+ // note: i assume that the hoyle4 code is actually buggy and it was never fixed because of
+ // the way sierra sci handled it - "it just worked". It should have called kRandom(0, 0)
+ if (range)
+ range--; // the range value was never returned, our random generator gets 0->range, so fix it
+
+ const int randomNumber = fromNumber + (int)g_sci->getRNG().getRandomNumber(range);
return make_reg(0, randomNumber);
}
@@ -70,30 +72,24 @@ reg_t kSqrt(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, (int16) sqrt((float) ABS(argv[0].toSint16())));
}
-reg_t kGetAngle(EngineState *s, int argc, reg_t *argv) {
- // Based on behavior observed with a test program created with
- // SCI Studio.
- int x1 = argv[0].toSint16();
- int y1 = argv[1].toSint16();
- int x2 = argv[2].toSint16();
- int y2 = argv[3].toSint16();
- int xrel = x2 - x1;
- int yrel = y1 - y2; // y-axis is mirrored.
- int angle;
+uint16 kGetAngleWorker(int16 x1, int16 y1, int16 x2, int16 y2) {
+ int16 xRel = x2 - x1;
+ int16 yRel = y1 - y2; // y-axis is mirrored.
+ int16 angle;
// Move (xrel, yrel) to first quadrant.
if (y1 < y2)
- yrel = -yrel;
+ yRel = -yRel;
if (x2 < x1)
- xrel = -xrel;
+ xRel = -xRel;
// Compute angle in grads.
- if (yrel == 0 && xrel == 0)
- angle = 0;
+ if (yRel == 0 && xRel == 0)
+ return 0;
else
- angle = 100 * xrel / (xrel + yrel);
+ angle = 100 * xRel / (xRel + yRel);
- // Fix up angle for actual quadrant of (xrel, yrel).
+ // Fix up angle for actual quadrant of (xRel, yRel).
if (y1 < y2)
angle = 200 - angle;
if (x2 < x1)
@@ -103,8 +99,18 @@ reg_t kGetAngle(EngineState *s, int argc, reg_t *argv) {
// grad 10 with grad 11, grad 20 with grad 21, etc. This leads to
// "degrees" that equal either one or two grads.
angle -= (angle + 9) / 10;
+ return angle;
+}
+
+reg_t kGetAngle(EngineState *s, int argc, reg_t *argv) {
+ // Based on behavior observed with a test program created with
+ // SCI Studio.
+ int x1 = argv[0].toSint16();
+ int y1 = argv[1].toSint16();
+ int x2 = argv[2].toSint16();
+ int y2 = argv[3].toSint16();
- return make_reg(0, angle);
+ return make_reg(0, kGetAngleWorker(x1, y1, x2, y2));
}
reg_t kGetDistance(EngineState *s, int argc, reg_t *argv) {
diff --git a/engines/sci/engine/kmenu.cpp b/engines/sci/engine/kmenu.cpp
index c8a6e03556..428c27ca73 100644
--- a/engines/sci/engine/kmenu.cpp
+++ b/engines/sci/engine/kmenu.cpp
@@ -46,12 +46,13 @@ reg_t kSetMenu(EngineState *s, int argc, reg_t *argv) {
uint16 itemId = argv[0].toUint16() & 0xFF;
uint16 attributeId;
int argPos = 1;
+ reg_t value;
while (argPos < argc) {
attributeId = argv[argPos].toUint16();
- if ((argPos + 1) >= argc)
- error("Too few parameters for kSetMenu");
- g_sci->_gfxMenu->kernelSetAttribute(menuId, itemId, attributeId, argv[argPos + 1]);
+ // Happens in the fanmade game Cascade Quest when loading - bug #3038767
+ value = (argPos + 1 < argc) ? argv[argPos + 1] : NULL_REG;
+ g_sci->_gfxMenu->kernelSetAttribute(menuId, itemId, attributeId, value);
argPos += 2;
}
return s->r_acc;
@@ -76,6 +77,11 @@ reg_t kDrawStatus(EngineState *s, int argc, reg_t *argv) {
// Sometimes this is called without giving text, if thats the case dont process it.
text = s->_segMan->getString(textReference);
+ if (text == "Replaying sound") {
+ // Happens in the fanmade game Cascade Quest when loading - ignore it
+ return s->r_acc;
+ }
+
g_sci->_gfxMenu->kernelDrawStatus(g_sci->strSplit(text.c_str(), NULL).c_str(), colorPen, colorBack);
}
return s->r_acc;
diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp
index fbe20410de..d8ae1a3418 100644
--- a/engines/sci/engine/kmisc.cpp
+++ b/engines/sci/engine/kmisc.cpp
@@ -188,7 +188,9 @@ reg_t kGetTime(EngineState *s, int argc, reg_t *argv) {
int mode = (argc > 0) ? argv[0].toUint16() : 0;
- if (getSciVersion() <= SCI_VERSION_0_LATE && mode > 1)
+ // Modes 2 and 3 are supported since 0.629.
+ // This condition doesn't check that exactly, but close enough.
+ if (getSciVersion() == SCI_VERSION_0_EARLY && mode > 1)
error("kGetTime called in SCI0 with mode %d (expected 0 or 1)", mode);
switch (mode) {
@@ -387,6 +389,18 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
+#ifdef ENABLE_SCI32
+reg_t kWinDLL(EngineState *s, int argc, reg_t *argv) {
+ kStub(s, argc, argv);
+
+ // TODO: This seems to be loading and calling Windows DLLs. We'll probably
+ // need to either ignore calls made here, or wire each call for each game
+ // that requests it by hand
+
+ error("kWinDLL called");
+}
+#endif
+
reg_t kEmpty(EngineState *s, int argc, reg_t *argv) {
// Placeholder for empty kernel functions which are still called from the
// engine scripts (like the empty kSetSynonyms function in SCI1.1). This
diff --git a/engines/sci/engine/kmovement.cpp b/engines/sci/engine/kmovement.cpp
index 8c43a35fea..db54705694 100644
--- a/engines/sci/engine/kmovement.cpp
+++ b/engines/sci/engine/kmovement.cpp
@@ -129,7 +129,7 @@ reg_t kSetJump(EngineState *s, int argc, reg_t *argv) {
// Compute x step
if (tmp != 0)
- vx = (int)(dx * sqrt(gy / (2.0 * tmp)));
+ vx = (int16)((float)(dx * sqrt(gy / (2.0 * tmp))));
else
vx = 0;
@@ -145,7 +145,7 @@ reg_t kSetJump(EngineState *s, int argc, reg_t *argv) {
// FIXME: This choice of vy makes t roughly (2+sqrt(2))/gy * sqrt(dy);
// so if gy==3, then t is roughly sqrt(dy)...
- vy = (int)sqrt((double)gy * ABS(2 * dy)) + 1;
+ vy = (int)sqrt((float)gy * ABS(2 * dy)) + 1;
} else {
// As stated above, the vertical direction is correlated to the horizontal by the
// (non-zero) factor c.
@@ -166,230 +166,345 @@ reg_t kSetJump(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
-#define _K_BRESEN_AXIS_X 0
-#define _K_BRESEN_AXIS_Y 1
-
-static void initialize_bresen(SegManager *segMan, int argc, reg_t *argv, reg_t mover, int step_factor, int deltax, int deltay) {
- reg_t client = readSelector(segMan, mover, SELECTOR(client));
- int stepx = (int16)readSelectorValue(segMan, client, SELECTOR(xStep)) * step_factor;
- int stepy = (int16)readSelectorValue(segMan, client, SELECTOR(yStep)) * step_factor;
- int numsteps_x = stepx ? (ABS(deltax) + stepx - 1) / stepx : 0;
- int numsteps_y = stepy ? (ABS(deltay) + stepy - 1) / stepy : 0;
- int bdi, i1;
- int numsteps;
- int deltax_step;
- int deltay_step;
-
- if (numsteps_x > numsteps_y) {
- numsteps = numsteps_x;
- deltax_step = (deltax < 0) ? -stepx : stepx;
- deltay_step = numsteps ? deltay / numsteps : deltay;
- } else { // numsteps_x <= numsteps_y
- numsteps = numsteps_y;
- deltay_step = (deltay < 0) ? -stepy : stepy;
- deltax_step = numsteps ? deltax / numsteps : deltax;
- }
-
-/* if (ABS(deltax) > ABS(deltay)) {*/ // Bresenham on y
- if (numsteps_y < numsteps_x) {
-
- writeSelectorValue(segMan, mover, SELECTOR(b_xAxis), _K_BRESEN_AXIS_Y);
- writeSelectorValue(segMan, mover, SELECTOR(b_incr), (deltay < 0) ? -1 : 1);
- //i1 = 2 * (ABS(deltay) - ABS(deltay_step * numsteps)) * ABS(deltax_step);
- //bdi = -ABS(deltax);
- i1 = 2 * (ABS(deltay) - ABS(deltay_step * (numsteps - 1))) * ABS(deltax_step);
- bdi = -ABS(deltax);
- } else { // Bresenham on x
- writeSelectorValue(segMan, mover, SELECTOR(b_xAxis), _K_BRESEN_AXIS_X);
- writeSelectorValue(segMan, mover, SELECTOR(b_incr), (deltax < 0) ? -1 : 1);
- //i1= 2 * (ABS(deltax) - ABS(deltax_step * numsteps)) * ABS(deltay_step);
- //bdi = -ABS(deltay);
- i1 = 2 * (ABS(deltax) - ABS(deltax_step * (numsteps - 1))) * ABS(deltay_step);
- bdi = -ABS(deltay);
-
- }
-
- writeSelectorValue(segMan, mover, SELECTOR(dx), deltax_step);
- writeSelectorValue(segMan, mover, SELECTOR(dy), deltay_step);
-
- debugC(2, kDebugLevelBresen, "Init bresen for mover %04x:%04x: d=(%d,%d)", PRINT_REG(mover), deltax, deltay);
- debugC(2, kDebugLevelBresen, " steps=%d, mv=(%d, %d), i1= %d, i2=%d",
- numsteps, deltax_step, deltay_step, i1, bdi*2);
-
- //writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), numsteps); // Needed for HQ1/Ogre?
- writeSelectorValue(segMan, mover, SELECTOR(b_di), bdi);
- writeSelectorValue(segMan, mover, SELECTOR(b_i1), i1);
- writeSelectorValue(segMan, mover, SELECTOR(b_i2), bdi * 2);
-}
-
reg_t kInitBresen(EngineState *s, int argc, reg_t *argv) {
SegManager *segMan = s->_segMan;
reg_t mover = argv[0];
reg_t client = readSelector(segMan, mover, SELECTOR(client));
+ int16 stepFactor = (argc >= 2) ? argv[1].toUint16() : 1;
+ int16 mover_x = readSelectorValue(segMan, mover, SELECTOR(x));
+ int16 mover_y = readSelectorValue(segMan, mover, SELECTOR(y));
+ int16 client_xStep = readSelectorValue(segMan, client, SELECTOR(xStep)) * stepFactor;
+ int16 client_yStep = readSelectorValue(segMan, client, SELECTOR(yStep)) * stepFactor;
+
+ int16 client_step;
+ if (client_xStep < client_yStep)
+ client_step = client_yStep * 2;
+ else
+ client_step = client_xStep * 2;
+
+ int16 deltaX = mover_x - readSelectorValue(segMan, client, SELECTOR(x));
+ int16 deltaY = mover_y - readSelectorValue(segMan, client, SELECTOR(y));
+ int16 mover_dx = 0;
+ int16 mover_dy = 0;
+ int16 mover_i1 = 0;
+ int16 mover_i2 = 0;
+ int16 mover_di = 0;
+ int16 mover_incr = 0;
+ int16 mover_xAxis = 0;
+
+ while (1) {
+ mover_dx = client_xStep;
+ mover_dy = client_yStep;
+ mover_incr = 1;
+
+ if (ABS(deltaX) >= ABS(deltaY)) {
+ mover_xAxis = 1;
+ if (deltaX < 0)
+ mover_dx = -mover_dx;
+ mover_dy = deltaX ? mover_dx * deltaY / deltaX : 0;
+ mover_i1 = ((mover_dx * deltaY) - (mover_dy * deltaX)) * 2;
+ if (deltaY < 0) {
+ mover_incr = -1;
+ mover_i1 = -mover_i1;
+ }
+ mover_i2 = mover_i1 - (deltaX * 2);
+ mover_di = mover_i1 - deltaX;
+ if (deltaX < 0) {
+ mover_i1 = -mover_i1;
+ mover_i2 = -mover_i2;
+ mover_di = -mover_di;
+ }
+ } else {
+ mover_xAxis = 0;
+ if (deltaY < 0)
+ mover_dy = -mover_dy;
+ mover_dx = deltaY ? mover_dy * deltaX / deltaY : 0;
+ mover_i1 = ((mover_dy * deltaX) - (mover_dx * deltaY)) * 2;
+ if (deltaX < 0) {
+ mover_incr = -1;
+ mover_i1 = -mover_i1;
+ }
+ mover_i2 = mover_i1 - (deltaY * 2);
+ mover_di = mover_i1 - deltaY;
+ if (deltaY < 0) {
+ mover_i1 = -mover_i1;
+ mover_i2 = -mover_i2;
+ mover_di = -mover_di;
+ }
+ break;
+ }
+ if (client_xStep <= client_yStep)
+ break;
+ if (!client_xStep)
+ break;
+ if (client_yStep >= ABS(mover_dy + mover_incr))
+ break;
+
+ client_step--;
+ if (!client_step)
+ error("kInitBresen failed");
+ client_xStep--;
+ }
- int deltax = (int16)readSelectorValue(segMan, mover, SELECTOR(x)) - (int16)readSelectorValue(segMan, client, SELECTOR(x));
- int deltay = (int16)readSelectorValue(segMan, mover, SELECTOR(y)) - (int16)readSelectorValue(segMan, client, SELECTOR(y));
- int step_factor = (argc < 1) ? argv[1].toUint16() : 1;
-
- initialize_bresen(s->_segMan, argc, argv, mover, step_factor, deltax, deltay);
-
+ // set mover
+ writeSelectorValue(segMan, mover, SELECTOR(dx), mover_dx);
+ writeSelectorValue(segMan, mover, SELECTOR(dy), mover_dy);
+ writeSelectorValue(segMan, mover, SELECTOR(b_i1), mover_i1);
+ writeSelectorValue(segMan, mover, SELECTOR(b_i2), mover_i2);
+ writeSelectorValue(segMan, mover, SELECTOR(b_di), mover_di);
+ writeSelectorValue(segMan, mover, SELECTOR(b_incr), mover_incr);
+ writeSelectorValue(segMan, mover, SELECTOR(b_xAxis), mover_xAxis);
return s->r_acc;
}
-#define MOVING_ON_X (((axis == _K_BRESEN_AXIS_X)&&bi1) || dx)
-#define MOVING_ON_Y (((axis == _K_BRESEN_AXIS_Y)&&bi1) || dy)
-
reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) {
SegManager *segMan = s->_segMan;
reg_t mover = argv[0];
reg_t client = readSelector(segMan, mover, SELECTOR(client));
+ bool completed = false;
+ bool handleMoveCount = g_sci->_features->handleMoveCount();
- int x = (int16)readSelectorValue(segMan, client, SELECTOR(x));
- int y = (int16)readSelectorValue(segMan, client, SELECTOR(y));
- int oldx, oldy, destx, desty, dx, dy, bdi, bi1, bi2, movcnt, bdelta, axis;
- uint16 signal = readSelectorValue(segMan, client, SELECTOR(signal));
- int completed = 0;
- int max_movcnt = readSelectorValue(segMan, client, SELECTOR(moveSpeed));
-
- if (getSciVersion() > SCI_VERSION_01)
- signal &= ~kSignalHitObstacle;
-
- writeSelector(segMan, client, SELECTOR(signal), make_reg(0, signal)); // This is a NOP for SCI0
- oldx = x;
- oldy = y;
- destx = (int16)readSelectorValue(segMan, mover, SELECTOR(x));
- desty = (int16)readSelectorValue(segMan, mover, SELECTOR(y));
- dx = (int16)readSelectorValue(segMan, mover, SELECTOR(dx));
- dy = (int16)readSelectorValue(segMan, mover, SELECTOR(dy));
- bdi = (int16)readSelectorValue(segMan, mover, SELECTOR(b_di));
- bi1 = (int16)readSelectorValue(segMan, mover, SELECTOR(b_i1));
- bi2 = (int16)readSelectorValue(segMan, mover, SELECTOR(b_i2));
- movcnt = readSelectorValue(segMan, mover, SELECTOR(b_movCnt));
- 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);
- }
+ if (getSciVersion() >= SCI_VERSION_1_EGA) {
+ uint client_signal = readSelectorValue(segMan, client, SELECTOR(signal));
+ writeSelectorValue(segMan, client, SELECTOR(signal), client_signal & ~kSignalHitObstacle);
+ }
+
+ int16 mover_moveCnt = 1;
+ int16 client_moveSpeed = 0;
+ if (handleMoveCount) {
+ mover_moveCnt = readSelectorValue(segMan, mover, SELECTOR(b_movCnt));
+ client_moveSpeed = readSelectorValue(segMan, client, SELECTOR(moveSpeed));
+ mover_moveCnt++;
}
- //printf("movecnt %d, move speed %d\n", movcnt, max_movcnt);
+ if (client_moveSpeed < mover_moveCnt) {
+ mover_moveCnt = 0;
+ int16 client_x = readSelectorValue(segMan, client, SELECTOR(x));
+ int16 client_y = readSelectorValue(segMan, client, SELECTOR(y));
+ int16 client_org_x = client_x;
+ int16 client_org_y = client_y;
+ int16 mover_x = readSelectorValue(segMan, mover, SELECTOR(x));
+ int16 mover_y = readSelectorValue(segMan, mover, SELECTOR(y));
+ int16 mover_xAxis = readSelectorValue(segMan, mover, SELECTOR(b_xAxis));
+ int16 mover_dx = readSelectorValue(segMan, mover, SELECTOR(dx));
+ int16 mover_dy = readSelectorValue(segMan, mover, SELECTOR(dy));
+ int16 mover_incr = readSelectorValue(segMan, mover, SELECTOR(b_incr));
+ int16 mover_i1 = readSelectorValue(segMan, mover, SELECTOR(b_i1));
+ int16 mover_i2 = readSelectorValue(segMan, mover, SELECTOR(b_i2));
+ int16 mover_di = readSelectorValue(segMan, mover, SELECTOR(b_di));
+ int16 mover_org_i1 = mover_i1;
+ int16 mover_org_i2 = mover_i2;
+ int16 mover_org_di = mover_di;
+
+ if ((getSciVersion() >= SCI_VERSION_1_EGA)) {
+ // save current position into mover
+ writeSelectorValue(segMan, mover, SELECTOR(xLast), client_x);
+ writeSelectorValue(segMan, mover, SELECTOR(yLast), client_y);
+ }
+ // sierra sci saves full client selector variables here
- if (g_sci->_features->handleMoveCount()) {
- if (max_movcnt > movcnt) {
- ++movcnt;
- writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), movcnt); // Needed for HQ1/Ogre?
- return NULL_REG;
+ if (mover_xAxis) {
+ if (ABS(mover_x - client_x) < ABS(mover_dx))
+ completed = true;
} else {
- movcnt = 0;
- writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), movcnt); // Needed for HQ1/Ogre?
+ if (ABS(mover_y - client_y) < ABS(mover_dy))
+ completed = true;
+ }
+ if (completed) {
+ client_x = mover_x;
+ client_y = mover_y;
+ } else {
+ client_x += mover_dx;
+ client_y += mover_dy;
+ if (mover_di < 0) {
+ mover_di += mover_i1;
+ } else {
+ mover_di += mover_i2;
+ if (mover_xAxis == 0) {
+ client_x += mover_incr;
+ } else {
+ client_y += mover_incr;
+ }
+ }
+ }
+ writeSelectorValue(segMan, client, SELECTOR(x), client_x);
+ writeSelectorValue(segMan, client, SELECTOR(y), client_y);
+
+ // Now call client::canBeHere/client::cantBehere to check for collisions
+ bool collision = false;
+ 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);
+ 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 ((bdi += bi1) > 0) {
- bdi += bi2;
+ if (collision) {
+ // sierra restores full client variables here, seems that restoring x/y is enough
+ writeSelectorValue(segMan, client, SELECTOR(x), client_org_x);
+ writeSelectorValue(segMan, client, SELECTOR(y), client_org_y);
+ mover_i1 = mover_org_i1;
+ mover_i2 = mover_org_i2;
+ mover_di = mover_org_di;
- if (axis == _K_BRESEN_AXIS_X)
- dx += bdelta;
+ uint16 client_signal = readSelectorValue(segMan, client, SELECTOR(signal));
+ writeSelectorValue(segMan, client, SELECTOR(signal), client_signal | kSignalHitObstacle);
+ }
+ writeSelectorValue(segMan, mover, SELECTOR(b_i1), mover_i1);
+ writeSelectorValue(segMan, mover, SELECTOR(b_i2), mover_i2);
+ writeSelectorValue(segMan, mover, SELECTOR(b_di), mover_di);
+
+ if ((getSciVersion() >= SCI_VERSION_1_EGA)) {
+ // this calling code here was right before the last return in
+ // sci1ega and got changed to this position since sci1early
+ // this was an uninitialized issue in sierra sci
+ if ((handleMoveCount) && (getSciVersion() >= SCI_VERSION_1_EARLY))
+ writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), mover_moveCnt);
+ // We need to compare directly in here, complete may have happened during
+ // the current move
+ if ((client_x == mover_x) && (client_y == mover_y))
+ invokeSelector(s, mover, SELECTOR(moveDone), argc, argv);
+ if (getSciVersion() >= SCI_VERSION_1_EARLY)
+ return s->r_acc;
+ }
+ }
+ if (handleMoveCount) {
+ if (getSciVersion() <= SCI_VERSION_1_EGA)
+ writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), mover_moveCnt);
else
- dy += bdelta;
+ writeSelectorValue(segMan, mover, SELECTOR(b_movCnt), client_moveSpeed);
}
+ return s->r_acc;
+}
- writeSelectorValue(segMan, mover, SELECTOR(b_di), bdi);
+extern void kDirLoopWorker(reg_t obj, uint16 angle, EngineState *s, int argc, reg_t *argv);
+extern uint16 kGetAngleWorker(int16 x1, int16 y1, int16 x2, int16 y2);
- x += dx;
- y += dy;
+reg_t kDoAvoider(EngineState *s, int argc, reg_t *argv) {
+ SegManager *segMan = s->_segMan;
+ reg_t avoider = argv[0];
+ int16 timesStep = argc > 1 ? argv[1].toUint16() : 1;
- if ((MOVING_ON_X && (((x < destx) && (oldx >= destx)) // Moving left, exceeded?
- || ((x > destx) && (oldx <= destx)) // Moving right, exceeded?
- || ((x == destx) && (ABS(dx) > ABS(dy))) // Moving fast, reached?
- // Treat this last case specially- when doing sub-pixel movements
- // on the other axis, we could still be far away from the destination
- )) || (MOVING_ON_Y && (((y < desty) && (oldy >= desty)) /* Moving upwards, exceeded? */
- || ((y > desty) && (oldy <= desty)) /* Moving downwards, exceeded? */
- || ((y == desty) && (ABS(dy) >= ABS(dx))) /* Moving fast, reached? */
- ))) {
- // Whew... in short: If we have reached or passed our target position
+ if (!s->_segMan->isHeapObject(avoider)) {
+ error("DoAvoider() where avoider %04x:%04x is not an object", PRINT_REG(avoider));
+ return SIGNAL_REG;
+ }
- x = destx;
- y = desty;
- completed = 1;
+ reg_t client = readSelector(segMan, avoider, SELECTOR(client));
+ reg_t mover = readSelector(segMan, client, SELECTOR(mover));
+ if (mover.isNull())
+ return SIGNAL_REG;
- debugC(2, kDebugLevelBresen, "Finished mover %04x:%04x", PRINT_REG(mover));
- }
+ // call mover::doit
+ invokeSelector(s, mover, SELECTOR(doit), argc, argv);
- writeSelectorValue(segMan, client, SELECTOR(x), x);
- writeSelectorValue(segMan, client, SELECTOR(y), y);
+ // Read mover again
+ mover = readSelector(segMan, client, SELECTOR(mover));
+ if (mover.isNull())
+ return SIGNAL_REG;
- debugC(2, kDebugLevelBresen, "New data: (x,y)=(%d,%d), di=%d", x, y, bdi);
+ int16 clientX = readSelectorValue(segMan, client, SELECTOR(x));
+ int16 clientY = readSelectorValue(segMan, client, SELECTOR(y));
+ int16 moverX = readSelectorValue(segMan, mover, SELECTOR(x));
+ int16 moverY = readSelectorValue(segMan, mover, SELECTOR(y));
+ int16 avoiderHeading = readSelectorValue(segMan, avoider, SELECTOR(heading));
- bool collision = false;
- reg_t cantBeHere = NULL_REG;
+ // call client::isBlocked
+ invokeSelector(s, client, SELECTOR(isBlocked), argc, argv);
- 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);
- 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.isNull()) {
+ // not blocked
+ if (avoiderHeading == -1)
+ return SIGNAL_REG;
+ avoiderHeading = -1;
- if (collision) {
- signal = readSelectorValue(segMan, client, SELECTOR(signal));
+ uint16 angle = kGetAngleWorker(clientX, clientY, moverX, moverY);
- writeSelectorValue(segMan, client, SELECTOR(x), oldx);
- writeSelectorValue(segMan, client, SELECTOR(y), oldy);
- writeSelectorValue(segMan, client, SELECTOR(signal), (signal | kSignalHitObstacle));
+ reg_t clientLooper = readSelector(segMan, client, SELECTOR(looper));
+ if (clientLooper.isNull()) {
+ kDirLoopWorker(client, angle, s, argc, argv);
+ } else {
+ // call looper::doit
+ reg_t params[2] = { make_reg(0, angle), client };
+ invokeSelector(s, clientLooper, SELECTOR(doit), argc, argv, 2, params);
+ }
+ s->r_acc = SIGNAL_REG;
+
+ } else {
+ // is blocked
+ if (avoiderHeading == -1)
+ avoiderHeading = g_sci->getRNG().getRandomBit() ? 45 : -45;
+ int16 clientHeading = readSelectorValue(segMan, client, SELECTOR(heading));
+ clientHeading = (clientHeading / 45) * 45;
+
+ int16 clientXstep = readSelectorValue(segMan, client, SELECTOR(xStep)) * timesStep;
+ int16 clientYstep = readSelectorValue(segMan, client, SELECTOR(yStep)) * timesStep;
+ int16 newHeading = clientHeading;
+
+ while (1) {
+ int16 newX = clientX;
+ int16 newY = clientY;
+ switch (newHeading) {
+ case 45:
+ case 90:
+ case 135:
+ newX += clientXstep;
+ break;
+ case 225:
+ case 270:
+ case 315:
+ newX -= clientXstep;
+ }
- debugC(2, kDebugLevelBresen, "Finished mover %04x:%04x by collision", PRINT_REG(mover));
- // 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
- }
+ switch (newHeading) {
+ case 0:
+ case 45:
+ case 315:
+ newY -= clientYstep;
+ break;
+ case 135:
+ case 180:
+ case 225:
+ newY += clientYstep;
+ }
+ writeSelectorValue(segMan, client, SELECTOR(x), newX);
+ writeSelectorValue(segMan, client, SELECTOR(y), newY);
- if ((getSciVersion() >= SCI_VERSION_1_EGA))
- if (completed)
- invokeSelector(s, mover, SELECTOR(moveDone), argc, argv);
+ // call client::canBeHere
+ invokeSelector(s, client, SELECTOR(canBeHere), argc, argv);
- if (SELECTOR(cantBeHere) != -1)
- return cantBeHere;
- return make_reg(0, completed);
-}
+ if (!s->r_acc.isNull()) {
+ s->r_acc = make_reg(0, newHeading);
+ break; // break out
+ }
-extern void _k_dirloop(reg_t obj, uint16 angle, EngineState *s, int argc, reg_t *argv);
-
-int getAngle(int xrel, int yrel) {
- if ((xrel == 0) && (yrel == 0))
- return 0;
- else {
- int val = (int)(180.0 / PI * atan2((double)xrel, (double) - yrel));
- if (val < 0)
- val += 360;
-
- // Take care of OB1 differences between SSCI and
- // FSCI. SCI games sometimes check for equality with
- // "round" angles
- if (val % 45 == 44)
- val++;
- else if (val % 45 == 1)
- val--;
-
- return val;
+ newHeading += avoiderHeading;
+ if (newHeading >= 360)
+ newHeading -= 360;
+ if (newHeading < 0)
+ newHeading += 360;
+ if (newHeading == clientHeading) {
+ // tried everything
+ writeSelectorValue(segMan, client, SELECTOR(x), clientX);
+ writeSelectorValue(segMan, client, SELECTOR(y), clientY);
+ s->r_acc = SIGNAL_REG;
+ break; // break out
+ }
+ }
}
-}
+ writeSelectorValue(segMan, avoider, SELECTOR(heading), avoiderHeading);
+ return s->r_acc;
-reg_t kDoAvoider(EngineState *s, int argc, reg_t *argv) {
- SegManager *segMan = s->_segMan;
- reg_t avoider = argv[0];
+#if 0
reg_t client, looper, mover;
int angle;
int dx, dy;
@@ -486,18 +601,18 @@ reg_t kDoAvoider(EngineState *s, int argc, reg_t *argv) {
s->r_acc = make_reg(0, angle);
- reg_t params[2] = { make_reg(0, angle), client };
-
if (looper.segment) {
+ reg_t params[2] = { make_reg(0, angle), client };
invokeSelector(s, looper, SELECTOR(doit), argc, argv, 2, params);
return s->r_acc;
} else {
// No looper? Fall back to DirLoop
- _k_dirloop(client, (uint16)angle, s, argc, argv);
+ kDirLoopWorker(client, (uint16)angle, s, argc, argv);
}
}
return s->r_acc;
+#endif
}
} // End of namespace Sci
diff --git a/engines/sci/engine/kparse.cpp b/engines/sci/engine/kparse.cpp
index 552e425906..6a052a582d 100644
--- a/engines/sci/engine/kparse.cpp
+++ b/engines/sci/engine/kparse.cpp
@@ -93,13 +93,13 @@ reg_t kParse(EngineState *s, int argc, reg_t *argv) {
reg_t stringpos = argv[0];
Common::String string = s->_segMan->getString(stringpos);
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 };
+ ResultWordListList words;
bool res = voc->tokenizeString(words, string.c_str(), &error);
voc->parserIsValid = false; /* not valid */
@@ -109,10 +109,15 @@ 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:");
-
- for (ResultWordList::const_iterator i = words.begin(); i != words.end(); ++i)
- debugC(2, kDebugLevelParser, " Type[%04x] Group[%04x]", i->_class, i->_group);
+ debugC(2, kDebugLevelParser, "Parsed to the following blocks:");
+
+ for (ResultWordListList::const_iterator i = words.begin(); i != words.end(); ++i) {
+ debugCN(2, kDebugLevelParser, " ");
+ for (ResultWordList::const_iterator j = i->begin(); j != i->end(); ++j) {
+ debugCN(2, kDebugLevelParser, "%sType[%04x] Group[%04x]", j == i->begin() ? "" : " / ", j->_class, j->_group);
+ }
+ debugCN(2, kDebugLevelParser, "\n");
+ }
#endif
int syntax_fail = voc->parseGNF(words);
@@ -138,6 +143,15 @@ reg_t kParse(EngineState *s, int argc, reg_t *argv) {
} else {
s->r_acc = make_reg(0, 0);
+ // FIXME: When typing something wrong in the fanmade game Demo Quest,
+ // after the error dialog, the game checks for claimed to be 0 before
+ // showing a subsequent dialog. The following selector change causes
+ // it to be 1, thus causing the game to hang in an endless loop (bug
+ // #3038870. Thus, this seems to be wrong (since fanmade games use
+ // the original SCI interpreter), but we need to check against
+ // dissassembly. Since kParse is in the process of being dissassembled
+ // again, I'm leaving this FIXME in for now, so that it won't be
+ // forgotten :)
writeSelectorValue(segMan, event, SELECTOR(claimed), 1);
if (error) {
s->_segMan->strcpy(voc->parser_base, error);
diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp
index 07d0a31f0b..faf966af92 100644
--- a/engines/sci/engine/kpathing.cpp
+++ b/engines/sci/engine/kpathing.cpp
@@ -261,13 +261,7 @@ struct PathfindingState {
int findNearPoint(const Common::Point &p, Polygon *polygon, Common::Point *ret);
};
-
-static Common::Point read_point(SegManager *segMan, reg_t list, int offset) {
- SegmentRef list_r = segMan->dereference(list);
- if (!list_r.isValid() || list_r.skipByte) {
- // If this happens, then the code below will probably go OOB and crash
- error("read_point(): Attempt to dereference invalid pointer %04x:%04x", PRINT_REG(list));
- }
+static Common::Point readPoint(SegmentRef list_r, int offset) {
Common::Point point;
if (list_r.isRaw) {
@@ -350,10 +344,16 @@ static void draw_polygon(EngineState *s, reg_t polygon, int width, int height) {
Common::Point first, prev;
int i;
- prev = first = read_point(segMan, points, 0);
+ SegmentRef pointList = segMan->dereference(points);
+ if (!pointList.isValid() || pointList.skipByte) {
+ warning("draw_polygon: Polygon data pointer is invalid, skipping polygon");
+ return;
+ }
+
+ prev = first = readPoint(pointList, 0);
for (i = 1; i < size; i++) {
- Common::Point point = read_point(segMan, points, i);
+ Common::Point point = readPoint(pointList, i);
draw_line(s, prev, point, type, width, height);
prev = point;
}
@@ -401,12 +401,18 @@ static void print_polygon(SegManager *segMan, reg_t polygon) {
debugN(-1, "%i:", type);
+ SegmentRef pointList = segMan->dereference(points);
+ if (!pointList.isValid() || pointList.skipByte) {
+ warning("print_polygon: Polygon data pointer is invalid, skipping polygon");
+ return;
+ }
+
for (i = 0; i < size; i++) {
- point = read_point(segMan, points, i);
+ point = readPoint(pointList, i);
debugN(-1, " (%i, %i)", point.x, point.y);
}
- point = read_point(segMan, points, 0);
+ point = readPoint(pointList, 0);
debug(" (%i, %i);", point.x, point.y);
}
@@ -1092,7 +1098,22 @@ static Polygon *convert_polygon(EngineState *s, reg_t polygon) {
return NULL;
}
- Polygon *poly = new Polygon(readSelectorValue(segMan, polygon, SELECTOR(type)));
+ SegmentRef pointList = segMan->dereference(points);
+ // Check if the target polygon is still valid. It may have been released
+ // in the meantime (e.g. in LSL6, room 700, when using the elevator).
+ // Refer to bug #3034501.
+ if (!pointList.isValid() || pointList.skipByte) {
+ warning("convert_polygon: Polygon data pointer is invalid, skipping polygon");
+ return NULL;
+ }
+
+ // Make sure that we have enough points
+ if (pointList.maxSize < size * POLY_POINT_SIZE) {
+ warning("convert_polygon: Not enough memory allocated for polygon points. "
+ "Expected %d, got %d. Skipping polygon",
+ size * POLY_POINT_SIZE, pointList.maxSize);
+ return NULL;
+ }
int skip = 0;
@@ -1100,14 +1121,16 @@ static Polygon *convert_polygon(EngineState *s, reg_t polygon) {
// Polygon has 17 points but size is set to 19
if ((size == 19) && g_sci->getGameId() == GID_LSL1) {
if ((s->currentRoomNumber() == 350)
- && (read_point(segMan, points, 18) == Common::Point(108, 137))) {
+ && (readPoint(pointList, 18) == Common::Point(108, 137))) {
debug(1, "Applying fix for broken polygon in lsl1sci, room 350");
size = 17;
}
}
+ Polygon *poly = new Polygon(readSelectorValue(segMan, polygon, SELECTOR(type)));
+
for (i = skip; i < size; i++) {
- Vertex *vertex = new Vertex(read_point(segMan, points, i));
+ Vertex *vertex = new Vertex(readPoint(pointList, i));
poly->vertices.insertHead(vertex);
}
@@ -1163,7 +1186,9 @@ static PathfindingState *convert_polygon_set(EngineState *s, reg_t poly_list, Co
Node *node = s->_segMan->lookupNode(list->first);
while (node) {
- polygon = convert_polygon(s, node->value);
+ // The node value might be null, in which case there's no polygon to parse.
+ // Happens in LB2 floppy - refer to bug #3041232
+ polygon = !node->value.isNull() ? convert_polygon(s, node->value) : NULL;
if (polygon) {
pf_s->polygons.push_back(polygon);
@@ -1402,8 +1427,15 @@ static reg_t output_path(PathfindingState *p, EngineState *s) {
if (DebugMan.isDebugChannelEnabled(kDebugLevelAvoidPath)) {
debug("\nReturning path:");
+
+ SegmentRef outputList = s->_segMan->dereference(output);
+ if (!outputList.isValid() || outputList.skipByte) {
+ warning("output_path: Polygon data pointer is invalid, skipping polygon");
+ return output;
+ }
+
for (int i = 0; i < offset; i++) {
- Common::Point pt = read_point(s->_segMan, output, i);
+ Common::Point pt = readPoint(outputList, i);
debugN(-1, " (%i, %i)", pt.x, pt.y);
}
debug(";\n");
diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp
index a5501c160f..e7f466f9a2 100644
--- a/engines/sci/engine/kscripts.cpp
+++ b/engines/sci/engine/kscripts.cpp
@@ -143,56 +143,72 @@ reg_t kResCheck(EngineState *s, int argc, reg_t *argv) {
}
reg_t kClone(EngineState *s, int argc, reg_t *argv) {
- reg_t parent_addr = argv[0];
- const Object *parent_obj = s->_segMan->getObject(parent_addr);
- reg_t clone_addr;
- Clone *clone_obj; // same as Object*
+ reg_t parentAddr = argv[0];
+ const Object *parentObj = s->_segMan->getObject(parentAddr);
+ reg_t cloneAddr;
+ Clone *cloneObj; // same as Object*
- if (!parent_obj) {
- error("Attempt to clone non-object/class at %04x:%04x failed", PRINT_REG(parent_addr));
+ if (!parentObj) {
+ error("Attempt to clone non-object/class at %04x:%04x failed", PRINT_REG(parentAddr));
return NULL_REG;
}
- debugC(2, kDebugLevelMemory, "Attempting to clone from %04x:%04x", PRINT_REG(parent_addr));
+ debugC(2, kDebugLevelMemory, "Attempting to clone from %04x:%04x", PRINT_REG(parentAddr));
- clone_obj = s->_segMan->allocateClone(&clone_addr);
+ uint16 infoSelector = readSelectorValue(s->_segMan, parentAddr, SELECTOR(_info_));
+ cloneObj = s->_segMan->allocateClone(&cloneAddr);
- if (!clone_obj) {
- error("Cloning %04x:%04x failed-- internal error", PRINT_REG(parent_addr));
+ if (!cloneObj) {
+ error("Cloning %04x:%04x failed-- internal error", PRINT_REG(parentAddr));
return NULL_REG;
}
- *clone_obj = *parent_obj;
+ // In case the parent object is a clone itself we need to refresh our
+ // pointer to it here. This is because calling allocateClone might
+ // invalidate all pointers, references and iterators to data in the clones
+ // segment.
+ //
+ // The reason why it might invalidate those is, that the segment code
+ // (Table) uses Common::Array for internal storage. Common::Array now
+ // might invalidate references to its contained data, when it has to
+ // extend the internal storage size.
+ if (infoSelector & kInfoFlagClone)
+ parentObj = s->_segMan->getObject(parentAddr);
+
+ *cloneObj = *parentObj;
// Mark as clone
- clone_obj->markAsClone();
- clone_obj->setSpeciesSelector(clone_obj->getPos());
- if (parent_obj->isClass())
- clone_obj->setSuperClassSelector(parent_obj->getPos());
- s->_segMan->getScript(parent_obj->getPos().segment)->incrementLockers();
- s->_segMan->getScript(clone_obj->getPos().segment)->incrementLockers();
-
- return clone_addr;
+ infoSelector &= ~kInfoFlagClass; // remove class bit
+ writeSelectorValue(s->_segMan, cloneAddr, SELECTOR(_info_), infoSelector | kInfoFlagClone);
+
+ cloneObj->setSpeciesSelector(cloneObj->getPos());
+ if (parentObj->isClass())
+ cloneObj->setSuperClassSelector(parentObj->getPos());
+ s->_segMan->getScript(parentObj->getPos().segment)->incrementLockers();
+ s->_segMan->getScript(cloneObj->getPos().segment)->incrementLockers();
+
+ return cloneAddr;
}
extern void _k_view_list_mark_free(EngineState *s, reg_t off);
reg_t kDisposeClone(EngineState *s, int argc, reg_t *argv) {
- reg_t victim_addr = argv[0];
- Clone *victim_obj = s->_segMan->getObject(victim_addr);
+ reg_t obj = argv[0];
+ Clone *object = s->_segMan->getObject(obj);
- if (!victim_obj) {
+ if (!object) {
error("Attempt to dispose non-class/object at %04x:%04x",
- PRINT_REG(victim_addr));
- return s->r_acc;
- }
-
- if (!victim_obj->isClone()) {
- // SCI silently ignores this behaviour; some games actually depend on it
+ PRINT_REG(obj));
return s->r_acc;
}
- victim_obj->markAsFreed();
+ // SCI uses this technique to find out, if it's a clone and if it's supposed to get freed
+ // At least kq4early relies on this behaviour. The scripts clone "Sound", then set bit 1 manually
+ // and call kDisposeClone later. In that case we may not free it, otherwise we will run into issues
+ // later, because kIsObject would then return false and Sound object wouldn't get checked.
+ uint16 infoSelector = readSelectorValue(s->_segMan, obj, SELECTOR(_info_));
+ if ((infoSelector & 3) == kInfoFlagClone)
+ object->markAsFreed();
return s->r_acc;
}
diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp
index 9254bce9c1..5ea3178ae5 100644
--- a/engines/sci/engine/kstring.cpp
+++ b/engines/sci/engine/kstring.cpp
@@ -275,7 +275,7 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) {
reg = readSelector(s->_segMan, reg, SELECTOR(data));
#endif
- Common::String tempsource = (reg == NULL_REG) ? "" : g_sci->getKernel()->lookupText(reg,
+ Common::String tempsource = g_sci->getKernel()->lookupText(reg,
arguments[paramindex + 1]);
int slen = strlen(tempsource.c_str());
int extralen = str_leng - slen;
@@ -486,10 +486,12 @@ reg_t kMessage(EngineState *s, int argc, reg_t *argv) {
}
#endif
- if ((func != K_MESSAGE_NEXT) && (argc < 2)) {
- warning("Message: not enough arguments passed to subfunction %d", func);
- return NULL_REG;
- }
+// TODO: Perhaps fix this check, currently doesn't work with PUSH and POP subfunctions
+// Pepper uses them to to handle the glossary
+// if ((func != K_MESSAGE_NEXT) && (argc < 2)) {
+// warning("Message: not enough arguments passed to subfunction %d", func);
+// return NULL_REG;
+// }
MessageTuple tuple;
@@ -558,6 +560,12 @@ reg_t kMessage(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
+ case K_MESSAGE_PUSH:
+ s->_msgState->pushCursorStack();
+ break;
+ case K_MESSAGE_POP:
+ s->_msgState->popCursorStack();
+ break;
default:
warning("Message: subfunction %i invoked (not implemented)", func);
}
@@ -779,6 +787,20 @@ reg_t kString(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
+/**
+ * Debug function, used in the demo of Shivers. It's marked as a stub
+ * in the original interpreter, but it gets called by the game scripts.
+ */
+reg_t kPrintDebug(EngineState *s, int argc, reg_t *argv) {
+ Common::String debugTemplate = s->_segMan->getString(argv[0]);
+ char debugString[500];
+
+ sprintf(debugString, debugTemplate.c_str(), argv[1].toUint16());
+ debugC(2, "kPrintDebug: \"%s\"\n", debugString);
+
+ return s->r_acc;
+}
+
#endif
} // End of namespace Sci
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index ac6cfb6835..e97ae38702 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -117,7 +117,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
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.");
+ error("This video requires >8bpp color to be displayed, but could not switch to RGB color mode");
return NULL_REG;
}
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index dfc41cc56a..87e328592f 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -45,8 +45,6 @@
#include "sci/sound/audio.h"
#include "sci/sound/music.h"
-#include "gui/message.h"
-
namespace Sci {
@@ -568,26 +566,32 @@ void GfxPalette::palVarySaveLoadPalette(Common::Serializer &s, Palette *palette)
}
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.getVersion() >= 25) {
+ // We need to save intensity of the _sysPalette at least for kq6 when entering the dark cave (room 390)
+ // from room 340. scripts will set intensity to 60 for this room and restore them when leaving.
+ // Sierra SCI is also doing this (although obviously not for SCI0->SCI01 games, still it doesn't hurt
+ // to save it everywhere). ffs. bug #3072868
+ s.syncBytes(_sysPalette.intensity, 256);
}
+ if (s.getVersion() >= 24) {
+ 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();
+ if (s.isLoading() && _palVaryResourceId != -1) {
+ _palVarySignal = 0;
+ palVaryInstallTimer();
+ }
}
}
@@ -701,6 +705,8 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const char* savenam
return true;
}
+extern void showScummVMDialog(const Common::String &message);
+
void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
SavegameMetadata meta;
@@ -708,7 +714,7 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
sync_SavegameMetadata(ser, meta);
if (fh->eos()) {
- s->r_acc = make_reg(0, 1); // signal failure
+ s->r_acc = TRUE_REG; // signal failure
return;
}
@@ -721,10 +727,9 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
warning("Savegame version is %d, maximum supported is %0d", meta.savegame_version, CURRENT_SAVEGAME_VERSION);
*/
- GUI::MessageDialog dialog("The format of this saved game is obsolete, unable to load it", "OK");
- dialog.runModal();
+ showScummVMDialog("The format of this saved game is obsolete, unable to load it");
- s->r_acc = make_reg(0, 1); // signal failure
+ s->r_acc = TRUE_REG; // signal failure
return;
}
@@ -733,10 +738,9 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
if (script0->size != meta.script0_size || g_sci->getGameObject().offset != meta.game_object_offset) {
//warning("This saved game was created with a different version of the game, unable to load it");
- GUI::MessageDialog dialog("This saved game was created with a different version of the game, unable to load it", "OK");
- dialog.runModal();
+ showScummVMDialog("This saved game was created with a different version of the game, unable to load it");
- s->r_acc = make_reg(0, 1); // signal failure
+ s->r_acc = TRUE_REG; // signal failure
return;
}
}
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index fc254ba33d..14eec4aafc 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -36,7 +36,7 @@ namespace Sci {
struct EngineState;
enum {
- CURRENT_SAVEGAME_VERSION = 24,
+ CURRENT_SAVEGAME_VERSION = 25,
MINIMUM_SAVEGAME_VERSION = 14
};
diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp
index f4129bb1ea..da9ab5106d 100644
--- a/engines/sci/engine/script.cpp
+++ b/engines/sci/engine/script.cpp
@@ -69,6 +69,9 @@ void Script::freeScript() {
void Script::init(int script_nr, ResourceManager *resMan) {
Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, script_nr), 0);
+ if (!script)
+ error("Script %d not found\n", script_nr);
+
_localsOffset = 0;
_localsBlock = NULL;
_localsCount = 0;
@@ -288,7 +291,8 @@ void Script::relocate(reg_t block) {
// code blocks. In SCI1.1 and newer versions, only locals and objects
// are relocated.
if (!relocateLocal(block.segment, pos)) {
- // Not a local? It's probably an object or code block. If it's an object, relocate it.
+ // Not a local? It's probably an object or code block. If it's an
+ // object, relocate it.
const ObjMap::iterator end = _objects.end();
for (ObjMap::iterator it = _objects.begin(); it != end; ++it)
if (it->_value.relocate(block.segment, pos, _scriptSize))
@@ -329,13 +333,13 @@ uint16 Script::validateExportFunc(int pubfunct) {
uint16 offset = READ_SCI11ENDIAN_UINT16(_exportTable + pubfunct);
VERIFY(offset < _bufSize, "invalid export function pointer");
- if (offset == 0) {
- // Check if the game has a second export table (e.g. script 912 in Camelot)
- // Fixes bug #3039785
- if (g_sci->getGameId() != GID_CAMELOT) // cheap fix
- return offset;
- // we are getting assert()s in eco quest 1 (right on startup) and kq6 and maybe more
- // [md5] plz look into this TODO FIXME
+ // Check if the offset found points to a second export table (e.g. script 912
+ // in Camelot and script 306 in KQ4). Such offsets are usually small (i.e. < 10),
+ // thus easily distinguished from actual code offsets.
+ // This only makes sense for SCI0-SCI1, as the export table in SCI1.1+ games
+ // is located at a specific address, thus findBlock() won't work.
+ // Fixes bugs #3039785 and #3037595.
+ if (offset < 10 && getSciVersion() <= SCI_VERSION_1_LATE) {
const uint16 *secondExportTable = (const uint16 *)findBlock(SCI_OBJ_EXPORTS, 0);
if (secondExportTable) {
@@ -532,7 +536,7 @@ void Script::initialiseObjectsSci11(SegManager *segMan, SegmentId segmentId) {
// If object is instance, get -propDict- from class and set it for this
// object. This is needed for ::isMemberOf() to work.
- // Example testcase - room 381 of sq4cd - if isMemberOf() doesn't work,
+ // Example test case - room 381 of sq4cd - if isMemberOf() doesn't work,
// talk-clicks on the robot will act like clicking on ego
if (!obj->isClass()) {
reg_t classObject = obj->getSuperClassSelector();
@@ -554,6 +558,13 @@ void Script::initialiseObjectsSci11(SegManager *segMan, SegmentId segmentId) {
relocate(make_reg(segmentId, READ_SCI11ENDIAN_UINT16(_heapStart)));
}
+void Script::initialiseObjects(SegManager *segMan, SegmentId segmentId) {
+ if (getSciVersion() >= SCI_VERSION_1_1)
+ initialiseObjectsSci11(segMan, segmentId);
+ else
+ initialiseObjectsSci0(segMan, segmentId);
+}
+
reg_t Script::findCanonicAddress(SegManager *segMan, reg_t addr) const {
addr.offset = 0;
return addr;
diff --git a/engines/sci/engine/script.h b/engines/sci/engine/script.h
index c60cc4b19f..e316fc0c8d 100644
--- a/engines/sci/engine/script.h
+++ b/engines/sci/engine/script.h
@@ -159,14 +159,7 @@ public:
* @param segMan A reference to the segment manager
* @param segmentId The script's segment id
*/
- void initialiseObjectsSci0(SegManager *segMan, SegmentId segmentId);
-
- /**
- * Initializes the script's objects (SCI1.1+)
- * @param segMan A reference to the segment manager
- * @param segmentId The script's segment id
- */
- void initialiseObjectsSci11(SegManager *segMan, SegmentId segmentId);
+ void initialiseObjects(SegManager *segMan, SegmentId segmentId);
// script lock operations
@@ -260,6 +253,20 @@ private:
void relocate(reg_t block);
bool relocateLocal(SegmentId segment, int location);
+
+ /**
+ * Initializes the script's objects (SCI0)
+ * @param segMan A reference to the segment manager
+ * @param segmentId The script's segment id
+ */
+ void initialiseObjectsSci0(SegManager *segMan, SegmentId segmentId);
+
+ /**
+ * Initializes the script's objects (SCI1.1+)
+ * @param segMan A reference to the segment manager
+ * @param segmentId The script's segment id
+ */
+ void initialiseObjectsSci11(SegManager *segMan, SegmentId segmentId);
};
} // End of namespace Sci
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 77818dd138..42d7c4d9cd 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -25,25 +25,34 @@
#include "sci/sci.h"
#include "sci/engine/script.h"
+#include "sci/engine/state.h"
#include "common/util.h"
namespace Sci {
#define PATCH_END 0xFFFF
-#define PATCH_ADDTOOFFSET 0x8000
-#define PATCH_GETORIGINALBYTE 0x4000
+#define PATCH_COMMANDMASK 0xF000
+#define PATCH_VALUEMASK 0x0FFF
+#define PATCH_ADDTOOFFSET 0xE000
+#define PATCH_GETORIGINALBYTE 0xD000
+#define PATCH_ADJUSTWORD 0xC000
+#define PATCH_ADJUSTWORD_NEG 0xB000
#define PATCH_MAGICDWORD(a, b, c, d) CONSTANT_LE_32(a | (b << 8) | (c << 16) | (d << 24))
+#define PATCH_VALUELIMIT 4096
struct SciScriptSignature {
uint16 scriptNr;
const char *description;
+ int16 applyCount;
uint32 magicDWord;
int magicOffset;
const byte *data;
const uint16 *patch;
};
+#define SCI_SIGNATUREENTRY_TERMINATOR { 0, NULL, 0, 0, 0, NULL, NULL }
+
// signatures are built like this:
// - first a counter of the bytes that follow
// - then the actual bytes that need to get matched
@@ -52,6 +61,51 @@ struct SciScriptSignature {
// - rinse and repeat
// ===========================================================================
+// Castle of Dr. Brain
+// cipher::init (script 391) is called on room 380 init. This resets the word
+// cipher puzzle. The puzzle sadly operates on some hep strings, which aren't
+// saved in our sci. So saving/restoring in this room will break the puzzle
+// Because of this issue, we just init the puzzle each time it's accessed.
+// this is not 100% sierra behaviour, in fact we will actually reset the puzzle
+// during each access which makes it impossible to cheat.
+const byte castlebrainSignatureCipherPuzzle[] = {
+ 22,
+ 0x35, 0x00, // ldi 00
+ 0xa3, 0x26, // sal local[26]
+ 0xa3, 0x25, // sal local[25]
+ 0x35, 0x00, // ldi 00
+ 0xa3, 0x2a, // sal local[2a] (local is not used)
+ 0xa3, 0x29, // sal local[29] (local is not used)
+ 0x35, 0xff, // ldi ff
+ 0xa3, 0x2c, // sal local[2c]
+ 0xa3, 0x2b, // sal local[2b]
+ 0x35, 0x00, // ldi 00
+ 0x65, 0x16, // aTop highlightedIcon
+ 0
+};
+
+const uint16 castlebrainPatchCipherPuzzle[] = {
+ 0x39, 0x6b, // pushi 6b (selector init)
+ 0x76, // push0
+ 0x55, 0x04, // self 04
+ 0x35, 0x00, // ldi 00
+ 0xa3, 0x25, // sal local[25]
+ 0xa3, 0x26, // sal local[26]
+ 0xa3, 0x29, // sal local[29]
+ 0x65, 0x16, // aTop highlightedIcon
+ 0x34, 0xff, 0xff, // ldi ffff
+ 0xa3, 0x2b, // sal local[2b]
+ 0xa3, 0x2c, // sal local[2c]
+ PATCH_END
+};
+
+// script, description, magic DWORD, adjust
+const SciScriptSignature castlebrainSignatures[] = {
+ { 391, "cipher puzzle save/restore break", 1, PATCH_MAGICDWORD(0xa3, 0x26, 0xa3, 0x25), -2, castlebrainSignatureCipherPuzzle, castlebrainPatchCipherPuzzle },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+// ===========================================================================
// stayAndHelp::changeState (0) is called when ego swims to the left or right
// boundaries of room 660. Normally a textbox is supposed to get on screen
// but the call is wrong, so not only do we get an error message the script
@@ -73,7 +127,7 @@ const byte ecoquest1SignatureStayAndHelp[] = {
0x78, // push1
0x76, // push0
0x81, 0x00, // lag global[0]
- 0x4a, 0x06, // send 06 - ego::setMotion(0)
+ 0x4a, 0x06, // send 06 - call ego::setMotion(0)
0x39, 0x6e, // pushi 6e (selector init)
0x39, 0x04, // pushi 04
0x76, // push0
@@ -81,7 +135,7 @@ const byte ecoquest1SignatureStayAndHelp[] = {
0x39, 0x17, // pushi 17
0x7c, // pushSelf
0x51, 0x82, // class EcoNarrator
- 0x4a, 0x0c, // send 0c - EcoNarrator::init(0, 0, 23, self) (BADLY BROKEN!)
+ 0x4a, 0x0c, // send 0c - call EcoNarrator::init(0, 0, 23, self) (BADLY BROKEN!)
0x33, // jmp [end]
0
};
@@ -97,7 +151,7 @@ const uint16 ecoquest1PatchStayAndHelp[] = {
0x78, // push1
0x76, // push0
0x81, 0x00, // lag global[0]
- 0x4a, 0x06, // send 06 - ego::setMotion(0)
+ 0x4a, 0x06, // send 06 - call ego::setMotion(0)
0x39, 0x6e, // pushi 6e (selector init)
0x39, 0x06, // pushi 06
0x39, 0x02, // pushi 02 (additional 2 bytes)
@@ -107,16 +161,174 @@ const uint16 ecoquest1PatchStayAndHelp[] = {
0x7c, // pushSelf
0x38, 0x80, 0x02, // pushi 280 (additional 3 bytes)
0x51, 0x82, // class EcoNarrator
- 0x4a, 0x10, // send 10 - EcoNarrator::init(2, 0, 0, 23, self, 640)
+ 0x4a, 0x10, // send 10 - call EcoNarrator::init(2, 0, 0, 23, self, 640)
PATCH_END
};
-// script, description, magic DWORD, adjust
+// script, description, magic DWORD, adjust
const SciScriptSignature ecoquest1Signatures[] = {
- { 660, "CD: bad messagebox and freeze", PATCH_MAGICDWORD(0x38, 0x22, 0x01, 0x78), -17, ecoquest1SignatureStayAndHelp, ecoquest1PatchStayAndHelp },
- { 0, NULL, 0, 0, NULL, NULL }
+ { 660, "CD: bad messagebox and freeze", 1, PATCH_MAGICDWORD(0x38, 0x22, 0x01, 0x78), -17, ecoquest1SignatureStayAndHelp, ecoquest1PatchStayAndHelp },
+ SCI_SIGNATUREENTRY_TERMINATOR
};
+// ===========================================================================
+// doMyThing::changeState (2) is supposed to remove the initial text on the
+// ecorder. This is done by reusing temp-space, that was filled on state 1.
+// this worked in sierra sci just by accident. In our sci, the temp space
+// is resetted every time, which means the previous text isn't available
+// anymore. We have to patch the code because of that ffs. bug #3035386
+const byte ecoquest2SignatureEcorder[] = {
+ 35,
+ 0x31, 0x22, // bnt [next state]
+ 0x39, 0x0a, // pushi 0a
+ 0x5b, 0x04, 0x1e, // lea temp[1e]
+ 0x36, // push
+ 0x39, 0x64, // pushi 64
+ 0x39, 0x7d, // pushi 7d
+ 0x39, 0x32, // pushi 32
+ 0x39, 0x66, // pushi 66
+ 0x39, 0x17, // pushi 17
+ 0x39, 0x69, // pushi 69
+ 0x38, 0x31, 0x26, // pushi 2631
+ 0x39, 0x6a, // pushi 6a
+ 0x39, 0x64, // pushi 64
+ 0x43, 0x1b, 0x14, // call kDisplay
+ 0x35, 0x0a, // ldi 0a
+ 0x65, 0x20, // aTop ticks
+ 0x33, // jmp [end]
+ +1, 5, // [skip 1 byte]
+ 0x3c, // dup
+ 0x35, 0x03, // ldi 03
+ 0x1a, // eq?
+ 0x31, // bnt [end]
+ 0
+};
+
+const uint16 ecoquest2PatchEcorder[] = {
+ 0x2f, 0x02, // bt [to pushi 07]
+ 0x3a, // toss
+ 0x48, // ret
+ 0x38, 0x07, 0x00, // pushi 07 (parameter count) (waste 1 byte)
+ 0x39, 0x0b, // push (FillBoxAny)
+ 0x39, 0x1d, // pushi 29d
+ 0x39, 0x73, // pushi 115d
+ 0x39, 0x5e, // pushi 94d
+ 0x38, 0xd7, 0x00, // pushi 215d
+ 0x78, // push1 (visual screen)
+ 0x38, 0x17, 0x00, // pushi 17 (color) (waste 1 byte)
+ 0x43, 0x6c, 0x0e, // call kGraph
+ 0x38, 0x05, 0x00, // pushi 05 (parameter count) (waste 1 byte)
+ 0x39, 0x0c, // pushi 12d (UpdateBox)
+ 0x39, 0x1d, // pushi 29d
+ 0x39, 0x73, // pushi 115d
+ 0x39, 0x5e, // pushi 94d
+ 0x38, 0xd7, 0x00, // pushi 215d
+ 0x43, 0x6c, 0x0a, // call kGraph
+ PATCH_END
+};
+
+// script, description, magic DWORD, adjust
+const SciScriptSignature ecoquest2Signatures[] = {
+ { 50, "initial text not removed on ecorder", 1, PATCH_MAGICDWORD(0x39, 0x64, 0x39, 0x7d), -8, ecoquest2SignatureEcorder, ecoquest2PatchEcorder },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+// ===========================================================================
+// script 0 of freddy pharkas/CD PointsSound::check waits for a signal and if
+// no signal received will call kDoSound(0xD) which is a dummy in sierra sci
+// and ScummVM and will use acc (which is not set by the dummy) to trigger
+// sound disposal. This somewhat worked in sierra sci, because the sample
+// was already playing in the sound driver. In our case we would also stop
+// the sample from playing, so we patch it out
+// The "score" code is already buggy and sets volume to 0 when playing
+const byte freddypharkasSignatureScoreDisposal[] = {
+ 10,
+ 0x67, 0x32, // pTos 32 (selector theAudCount)
+ 0x78, // push1
+ 0x39, 0x0d, // pushi 0d
+ 0x43, 0x75, 0x02, // call kDoAudio
+ 0x1c, // ne?
+ 0x31, // bnt (-> to skip disposal)
+ 0
+};
+
+const uint16 freddypharkasPatchScoreDisposal[] = {
+ 0x34, 0x00, 0x00, // ldi 0000
+ 0x34, 0x00, 0x00, // ldi 0000
+ 0x34, 0x00, 0x00, // ldi 0000
+ PATCH_END
+};
+
+// script 235 of freddy pharkas rm235::init and sEnterFrom500::changeState
+// disable icon 7+8 of iconbar (CD only). When picking up the cannister after
+// placing it down, the scripts will disable all the other icons. This results
+// in IconBar::disable doing endless loops even in sierra sci, because there
+// is no enabled icon left. We remove disabling of icon 8 (which is help),
+// this fixes the issue.
+const byte freddypharkasSignatureCannisterHang[] = {
+ 12,
+ 0x38, 0xf1, 0x00, // pushi f1 (selector disable)
+ 0x7a, // push2
+ 0x39, 0x07, // pushi 07
+ 0x39, 0x08, // pushi 08
+ 0x81, 0x45, // lag 45
+ 0x4a, 0x08, // send 08 - call IconBar::disable(7, 8)
+ 0
+};
+
+const uint16 freddypharkasPatchCannisterHang[] = {
+ PATCH_ADDTOOFFSET | +3,
+ 0x78, // push1
+ PATCH_ADDTOOFFSET | +2,
+ 0x33, 0x00, // ldi 00 (waste 2 bytes)
+ PATCH_ADDTOOFFSET | +3,
+ 0x06, // send 06 - call IconBar::disable(7)
+ PATCH_END
+};
+
+// script 215 of freddy pharkas lowerLadder::doit and highLadder::doit actually
+// process keyboard-presses when the ladder is on the screen in that room.
+// They strangely also call kGetEvent. Because the main User::doit also calls
+// kGetEvent, it's pure luck, where the event will hit. It's the same issue
+// as in QfG1VGA and if you turn dos-box to max cycles, and click around for
+// ego, sometimes clicks also won't get registered. Strangely it's not nearly
+// as bad as in our sci, but these differences may be caused by timing.
+// We just reuse the active event, thus removing the duplicate kGetEvent call.
+const byte freddypharkasSignatureLadderEvent[] = {
+ 21,
+ 0x39, 0x6d, // pushi 6d (selector new)
+ 0x76, // push0
+ 0x38, 0xf5, 0x00, // pushi f5 (selector curEvent)
+ 0x76, // push0
+ 0x81, 0x50, // lag global[50]
+ 0x4a, 0x04, // send 04 - read User::curEvent
+ 0x4a, 0x04, // send 04 - call curEvent::new
+ 0xa5, 0x00, // sat temp[0]
+ 0x38, 0x94, 0x00, // pushi 94 (selector localize)
+ 0x76, // push0
+ 0x4a, 0x04, // send 04 - call curEvent::localize
+ 0
+};
+
+const uint16 freddypharkasPatchLadderEvent[] = {
+ 0x34, 0x00, 0x00, // ldi 0000 (waste 3 bytes, overwrites first 2 pushes)
+ PATCH_ADDTOOFFSET | +8,
+ 0xa5, 0x00, // sat temp[0] (waste 2 bytes, overwrites 2nd send)
+ PATCH_ADDTOOFFSET | +2,
+ 0x34, 0x00, 0x00, // ldi 0000
+ 0x34, 0x00, 0x00, // ldi 0000 (waste 6 bytes, overwrites last 3 opcodes)
+ PATCH_END
+};
+
+// script, description, magic DWORD, adjust
+const SciScriptSignature freddypharkasSignatures[] = {
+ { 0, "CD: score early disposal", 1, PATCH_MAGICDWORD(0x39, 0x0d, 0x43, 0x75), -3, freddypharkasSignatureScoreDisposal, freddypharkasPatchScoreDisposal },
+ { 235, "CD: cannister pickup hang", 3, PATCH_MAGICDWORD(0x39, 0x07, 0x39, 0x08), -4, freddypharkasSignatureCannisterHang, freddypharkasPatchCannisterHang },
+ { 320, "ladder event issue", 2, PATCH_MAGICDWORD(0x6d, 0x76, 0x38, 0xf5), -1, freddypharkasSignatureLadderEvent, freddypharkasPatchLadderEvent },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+// ===========================================================================
// daySixBeignet::changeState (4) is called when the cop goes out and sets cycles to 220.
// this is not enough time to get to the door, so we patch that to 23 seconds
const byte gk1SignatureDay6PoliceBeignet[] = {
@@ -182,12 +394,12 @@ const uint16 gk1PatchDay5PhoneFreeze[] = {
PATCH_END
};
-// script, description, magic DWORD, adjust
+// script, description, magic DWORD, adjust
const SciScriptSignature gk1Signatures[] = {
- { 212, "day 5 phone freeze", PATCH_MAGICDWORD(0x35, 0x03, 0x65, 0x1a), 0, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze },
- { 230, "day 6 police beignet timer issue", PATCH_MAGICDWORD(0x34, 0xdc, 0x00, 0x65), -16, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet },
- { 230, "day 6 police sleep timer issue", PATCH_MAGICDWORD(0x34, 0xdc, 0x00, 0x65), -5, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep },
- { 0, NULL, 0, 0, NULL, NULL }
+ { 212, "day 5 phone freeze", 1, PATCH_MAGICDWORD(0x35, 0x03, 0x65, 0x1a), 0, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze },
+ { 230, "day 6 police beignet timer issue", 1, PATCH_MAGICDWORD(0x34, 0xdc, 0x00, 0x65), -16, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet },
+ { 230, "day 6 police sleep timer issue", 1, PATCH_MAGICDWORD(0x34, 0xdc, 0x00, 0x65), -5, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep },
+ SCI_SIGNATUREENTRY_TERMINATOR
};
// ===========================================================================
@@ -196,63 +408,63 @@ const SciScriptSignature gk1Signatures[] = {
// we would get an invalid port handle to a kSetPort call. We just patch in
// resetting of the port selector. We destroy the stop/fade code in there,
// it seems it isn't used at all in the game.
-const byte hoyle4SignaturePortFix[] = {
- 28,
- 0x39, 0x09, // pushi 09
- 0x89, 0x0b, // lsg 0b
- 0x39, 0x64, // pushi 64
- 0x38, 0xc8, 0x00, // pushi 00c8
- 0x38, 0x2c, 0x01, // pushi 012c
- 0x38, 0x90, 0x01, // pushi 0190
- 0x38, 0xf4, 0x01, // pushi 01f4
- 0x38, 0x58, 0x02, // pushi 0258
- 0x38, 0xbc, 0x02, // pushi 02bc
- 0x38, 0x20, 0x03, // pushi 0320
- 0x46, // calle [xxxx] [xxxx] [xx]
- +5, 43, // [skip 5 bytes]
- 0x30, 0x27, 0x00, // bnt 0027 -> end of routine
- 0x87, 0x00, // lap 00
- 0x30, 0x19, 0x00, // bnt 0019 -> fade out
- 0x87, 0x01, // lap 01
- 0x30, 0x14, 0x00, // bnt 0014 -> fade out
- 0x38, 0xa7, 0x00, // pushi 00a7
- 0x76, // push0
- 0x80, 0x29, 0x01, // lag 0129
- 0x4a, 0x04, // send 04 (song::stop)
- 0x39, 0x27, // pushi 27
- 0x78, // push1
- 0x8f, 0x01, // lsp 01
- 0x51, 0x54, // class 54
- 0x4a, 0x06, // send 06 (PlaySong::play)
- 0x33, 0x09, // jmp 09 -> end of routine
- 0x38, 0xaa, 0x00, // pushi 00aa
- 0x76, // push0
- 0x80, 0x29, 0x01, // lag 0129
- 0x4a, 0x04, // send 04
- 0x48, // ret
- 0
-};
-
-const uint16 hoyle4PatchPortFix[] = {
- PATCH_ADDTOOFFSET | +33,
- 0x38, 0x31, 0x01, // pushi 0131 (selector curEvent)
- 0x76, // push0
- 0x80, 0x50, 0x00, // lag 0050 (global var 80h, "User")
- 0x4a, 0x04, // send 04 (read User::curEvent)
-
- 0x38, 0x93, 0x00, // pushi 0093 (selector port)
- 0x78, // push1
- 0x76, // push0
- 0x4a, 0x06, // send 06 (write 0 to that object::port)
- 0x48, // ret
- PATCH_END
-};
+//const byte hoyle4SignaturePortFix[] = {
+// 28,
+// 0x39, 0x09, // pushi 09
+// 0x89, 0x0b, // lsg 0b
+// 0x39, 0x64, // pushi 64
+// 0x38, 0xc8, 0x00, // pushi 00c8
+// 0x38, 0x2c, 0x01, // pushi 012c
+// 0x38, 0x90, 0x01, // pushi 0190
+// 0x38, 0xf4, 0x01, // pushi 01f4
+// 0x38, 0x58, 0x02, // pushi 0258
+// 0x38, 0xbc, 0x02, // pushi 02bc
+// 0x38, 0x20, 0x03, // pushi 0320
+// 0x46, // calle [xxxx] [xxxx] [xx]
+// +5, 43, // [skip 5 bytes]
+// 0x30, 0x27, 0x00, // bnt 0027 -> end of routine
+// 0x87, 0x00, // lap 00
+// 0x30, 0x19, 0x00, // bnt 0019 -> fade out
+// 0x87, 0x01, // lap 01
+// 0x30, 0x14, 0x00, // bnt 0014 -> fade out
+// 0x38, 0xa7, 0x00, // pushi 00a7
+// 0x76, // push0
+// 0x80, 0x29, 0x01, // lag 0129
+// 0x4a, 0x04, // send 04 - call song::stop
+// 0x39, 0x27, // pushi 27
+// 0x78, // push1
+// 0x8f, 0x01, // lsp 01
+// 0x51, 0x54, // class 54
+// 0x4a, 0x06, // send 06 - call PlaySong::play
+// 0x33, 0x09, // jmp 09 -> end of routine
+// 0x38, 0xaa, 0x00, // pushi 00aa
+// 0x76, // push0
+// 0x80, 0x29, 0x01, // lag 0129
+// 0x4a, 0x04, // send 04
+// 0x48, // ret
+// 0
+//};
+
+//const uint16 hoyle4PatchPortFix[] = {
+// PATCH_ADDTOOFFSET | +33,
+// 0x38, 0x31, 0x01, // pushi 0131 (selector curEvent)
+// 0x76, // push0
+// 0x80, 0x50, 0x00, // lag 0050 (global var 80h, "User")
+// 0x4a, 0x04, // send 04 - read User::curEvent
+//
+// 0x38, 0x93, 0x00, // pushi 0093 (selector port)
+// 0x78, // push1
+// 0x76, // push0
+// 0x4a, 0x06, // send 06 - write 0 to that object::port
+// 0x48, // ret
+// PATCH_END
+//};
// script, description, magic DWORD, adjust
-const SciScriptSignature hoyle4Signatures[] = {
- { 0, "port fix when disposing windows", PATCH_MAGICDWORD(0x64, 0x38, 0xC8, 0x00), -5, hoyle4SignaturePortFix, hoyle4PatchPortFix },
- { 0, NULL, 0, 0, NULL, NULL }
-};
+//const SciScriptSignature hoyle4Signatures[] = {
+// { 0, "port fix when disposing windows", PATCH_MAGICDWORD(0x64, 0x38, 0xC8, 0x00), -5, hoyle4SignaturePortFix, hoyle4PatchPortFix },
+// { 0, NULL, 0, 0, NULL, NULL }
+//};
// ===========================================================================
// at least during harpy scene export 29 of script 0 is called in kq5cd and
@@ -270,12 +482,12 @@ const byte kq5SignatureCdHarpyVolume[] = {
0x38, 0x7b, 0x01, // pushi 017b
0x76, // push0
0x81, 0x01, // lag global[1]
- 0x4a, 0x04, // send 04 (getting KQ5::masterVolume)
+ 0x4a, 0x04, // send 04 - read KQ5::masterVolume
0xa5, 0x03, // sat temp[3] (store volume in temp 3)
0x38, 0x7b, 0x01, // pushi 017b
0x76, // push0
0x81, 0x01, // lag global[1]
- 0x4a, 0x04, // send 04 (getting KQ5::masterVolume)
+ 0x4a, 0x04, // send 04 - read KQ5::masterVolume
0x36, // push
0x35, 0x04, // ldi 04
0x20, // ge? (followed by bnt)
@@ -296,7 +508,7 @@ const uint16 kq5PatchCdHarpyVolume[] = {
0x38, 0x7b, 0x01, // pushi 017b
0x76, // push0
0x81, 0x01, // lag global[1]
- 0x4a, 0x04, // send 04 (getting KQ5::masterVolume)
+ 0x4a, 0x04, // send 04 - read KQ5::masterVolume
0xa5, 0x03, // sat temp[3] (store volume in temp 3)
// saving 8 bytes due removing of duplicate code
0x39, 0x04, // pushi 04 (saving 1 byte due swapping)
@@ -304,10 +516,10 @@ const uint16 kq5PatchCdHarpyVolume[] = {
PATCH_END
};
-// script, description, magic DWORD, adjust
+// script, description, magic DWORD, adjust
const SciScriptSignature kq5Signatures[] = {
- { 0, "CD: harpy volume change", PATCH_MAGICDWORD(0x80, 0x91, 0x01, 0x18), 0, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume },
- { 0, NULL, 0, 0, NULL, NULL }
+ { 0, "CD: harpy volume change", 1, PATCH_MAGICDWORD(0x80, 0x91, 0x01, 0x18), 0, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume },
+ SCI_SIGNATUREENTRY_TERMINATOR
};
// ===========================================================================
@@ -355,10 +567,10 @@ const uint16 larry6PatchDeathDialog[] = {
PATCH_END
};
-// script, description, magic DWORD, adjust
+// script, description, magic DWORD, adjust
const SciScriptSignature larry6Signatures[] = {
- { 82, "death dialog memory corruption", PATCH_MAGICDWORD(0x3e, 0x33, 0x01, 0x35), 0, larry6SignatureDeathDialog, larry6PatchDeathDialog },
- { 0, NULL, 0, 0, NULL, NULL }
+ { 82, "death dialog memory corruption", 1, PATCH_MAGICDWORD(0x3e, 0x33, 0x01, 0x35), 0, larry6SignatureDeathDialog, larry6PatchDeathDialog },
+ SCI_SIGNATUREENTRY_TERMINATOR
};
// ===========================================================================
@@ -367,7 +579,7 @@ const SciScriptSignature larry6Signatures[] = {
// is not in the room. We fix that.
const byte laurabow2SignaturePaintingClosing[] = {
17,
- 0x4a, 0x04, // send 04 (gets aHeimlich::room)
+ 0x4a, 0x04, // send 04 - read aHeimlich::room
0x36, // push
0x81, 0x0b, // lag global[11d] -> current room
0x1c, // ne?
@@ -386,10 +598,110 @@ const uint16 laurabow2PatchPaintingClosing[] = {
PATCH_END
};
-// script, description, magic DWORD, adjust
+// script, description, magic DWORD, adjust
const SciScriptSignature laurabow2Signatures[] = {
- { 560, "painting closing immediately", PATCH_MAGICDWORD(0x36, 0x81, 0x0b, 0x1c), -2, laurabow2SignaturePaintingClosing, laurabow2PatchPaintingClosing },
- { 0, NULL, 0, 0, NULL, NULL }
+ { 560, "painting closing immediately", 1, PATCH_MAGICDWORD(0x36, 0x81, 0x0b, 0x1c), -2, laurabow2SignaturePaintingClosing, laurabow2PatchPaintingClosing },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+// ===========================================================================
+// Mother Goose SCI1/SCI1.1
+// MG::replay somewhat calculates the savedgame-id used when saving again
+// this doesn't work right and we remove the code completely.
+// We set the savedgame-id directly right after restoring in kRestoreGame.
+const byte mothergoose256SignatureReplay[] = {
+ 6,
+ 0x36, // push
+ 0x35, 0x20, // ldi 20
+ 0x04, // sub
+ 0xa1, 0xb3, // sag global[b3]
+ 0
+};
+
+const uint16 mothergoose256PatchReplay[] = {
+ 0x34, 0x00, 0x00, // ldi 0000 (dummy)
+ 0x34, 0x00, 0x00, // ldi 0000 (dummy)
+ PATCH_END
+};
+
+// when saving, it also checks if the savegame-id is below 13.
+// we change this to check if below 113 instead
+const byte mothergoose256SignatureSaveLimit[] = {
+ 5,
+ 0x89, 0xb3, // lsg global[b3]
+ 0x35, 0x0d, // ldi 0d
+ 0x20, // ge?
+ 0
+};
+
+const uint16 mothergoose256PatchSaveLimit[] = {
+ PATCH_ADDTOOFFSET | +2,
+ 0x35, 0x0d + SAVEGAMEID_OFFICIALRANGE_START, // ldi 113d
+ PATCH_END
+};
+
+// script, description, magic DWORD, adjust
+const SciScriptSignature mothergoose256Signatures[] = {
+ { 0, "replay save issue", 1, PATCH_MAGICDWORD(0x20, 0x04, 0xa1, 0xb3), -2, mothergoose256SignatureReplay, mothergoose256PatchReplay },
+ { 0, "save limit dialog (SCI1.1)", 1, PATCH_MAGICDWORD(0xb3, 0x35, 0x0d, 0x20), -1, mothergoose256SignatureSaveLimit, mothergoose256PatchSaveLimit },
+ { 994, "save limit dialog (SCI1)", 1, PATCH_MAGICDWORD(0xb3, 0x35, 0x0d, 0x20), -1, mothergoose256SignatureSaveLimit, mothergoose256PatchSaveLimit },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+// ===========================================================================
+// script 215 of qfg1vga pointBox::doit actually processes button-presses
+// during fighting with monsters. It strangely also calls kGetEvent. Because
+// the main User::doit also calls kGetEvent it's pure luck, where the event
+// will hit. It's the same issue as in freddy pharkas and if you turn dos-box
+// to max cycles, sometimes clicks also won't get registered. Strangely it's
+// not nearly as bad as in our sci, but these differences may be caused by
+// timing.
+// We just reuse the active event, thus removing the duplicate kGetEvent call.
+const byte qfg1vgaSignatureFightEvents[] = {
+ 25,
+ 0x39, 0x6d, // pushi 6d (selector new)
+ 0x76, // push0
+ 0x51, 0x07, // class Event
+ 0x4a, 0x04, // send 04 - call Event::new
+ 0xa5, 0x00, // sat temp[0]
+ 0x78, // push1
+ 0x76, // push0
+ 0x4a, 0x04, // send 04 - read Event::x
+ 0xa5, 0x03, // sat temp[3]
+ 0x76, // push0 (selector y)
+ 0x76, // push0
+ 0x85, 0x00, // lat temp[0]
+ 0x4a, 0x04, // send 04 - read Event::y
+ 0x36, // push
+ 0x35, 0x0a, // ldi 0a
+ 0x04, // sub (poor mans localization) ;-)
+ 0
+};
+
+const uint16 qfg1vgaPatchFightEvents[] = {
+ 0x38, 0x5a, 0x01, // pushi 15a (selector curEvent)
+ 0x76, // push0
+ 0x81, 0x50, // lag global[50]
+ 0x4a, 0x04, // send 04 - read User::curEvent -> needs one byte more than previous code
+ 0xa5, 0x00, // sat temp[0]
+ 0x78, // push1
+ 0x76, // push0
+ 0x4a, 0x04, // send 04 - read Event::x
+ 0xa5, 0x03, // sat temp[3]
+ 0x76, // push0 (selector y)
+ 0x76, // push0
+ 0x85, 0x00, // lat temp[0]
+ 0x4a, 0x04, // send 04 - read Event::y
+ 0x39, 0x00, // pushi 00
+ 0x02, // add (waste 3 bytes) - we don't need localization, User::doit has already done it
+ PATCH_END
+};
+
+// script, description, magic DWORD, adjust
+const SciScriptSignature qfg1vgaSignatures[] = {
+ { 215, "fight event issue", 1, PATCH_MAGICDWORD(0x6d, 0x76, 0x51, 0x07), -1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents },
+ { 216, "weapon master event issue", 1, PATCH_MAGICDWORD(0x6d, 0x76, 0x51, 0x07), -1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents },
+ SCI_SIGNATUREENTRY_TERMINATOR
};
// ===========================================================================
@@ -415,10 +727,10 @@ const uint16 sq4FloppyPatchEndlessFlight[] = {
PATCH_END
};
-// script, description, magic DWORD, adjust
+// script, description, magic DWORD, adjust
const SciScriptSignature sq4Signatures[] = {
- { 298, "Floppy: endless flight", PATCH_MAGICDWORD(0x67, 0x08, 0x63, 0x44), -3, sq4FloppySignatureEndlessFlight, sq4FloppyPatchEndlessFlight },
- { 0, NULL, 0, 0, NULL, NULL }
+ { 298, "Floppy: endless flight", 1, PATCH_MAGICDWORD(0x67, 0x08, 0x63, 0x44), -3, sq4FloppySignatureEndlessFlight, sq4FloppyPatchEndlessFlight },
+ SCI_SIGNATUREENTRY_TERMINATOR
};
// ===========================================================================
@@ -438,8 +750,8 @@ const byte sq5SignatureScrubbing[] = {
0x39, 0x38, // pushi 38 (selector mover)
0x76, // push0
0x81, 0x00, // lag 00
- 0x4a, 0x04, // send 04 (read ego::mover)
- 0x4a, 0x04, // send 04 (read ego::mover::x)
+ 0x4a, 0x04, // send 04 - read ego::mover
+ 0x4a, 0x04, // send 04 - read ego::mover::x
0x36, // push
0x34, 0xa0, 0x00, // ldi 00a0
0x1c, // ne?
@@ -453,35 +765,67 @@ const uint16 sq5PatchScrubbing[] = {
0x39, 0x38, // pushi 38 (selector mover)
0x76, // push0
0x81, 0x00, // lag 00
- 0x4a, 0x04, // send 04 (read ego::mover)
+ 0x4a, 0x04, // send 04 - read ego::mover
0x31, 0x2e, // bnt 2e (jump if ego::mover is 0)
0x78, // push1 (selector x)
0x76, // push0
- 0x4a, 0x04, // send 04 (read ego::mover::x)
+ 0x4a, 0x04, // send 04 - read ego::mover::x
0x39, 0xa0, // pushi a0 (saving 2 bytes)
0x1c, // ne?
PATCH_END
};
-// script, description, magic DWORD, adjust
+// script, description, magic DWORD, adjust
const SciScriptSignature sq5Signatures[] = {
- { 119, "scrubbing send crash", PATCH_MAGICDWORD(0x18, 0x31, 0x37, 0x78), 0, sq5SignatureScrubbing, sq5PatchScrubbing },
- { 0, NULL, 0, 0, NULL, NULL }
+ { 119, "scrubbing send crash", 1, PATCH_MAGICDWORD(0x18, 0x31, 0x37, 0x78), 0, sq5SignatureScrubbing, sq5PatchScrubbing },
+ SCI_SIGNATUREENTRY_TERMINATOR
};
// will actually patch previously found signature area
void Script::applyPatch(const uint16 *patch, byte *scriptData, const uint32 scriptSize, int32 signatureOffset) {
+ byte orgData[PATCH_VALUELIMIT];
int32 offset = signatureOffset;
uint16 patchWord = *patch;
+ // Copy over original bytes from script
+ uint32 orgDataSize = scriptSize - offset;
+ if (orgDataSize > PATCH_VALUELIMIT)
+ orgDataSize = PATCH_VALUELIMIT;
+ memcpy(&orgData, &scriptData[offset], orgDataSize);
+
while (patchWord != PATCH_END) {
- if (patchWord & PATCH_ADDTOOFFSET) {
- offset += patchWord & ~PATCH_ADDTOOFFSET;
- } else if (patchWord & PATCH_GETORIGINALBYTE) {
- // TODO: implement this
- } else {
- scriptData[offset] = patchWord & 0xFF;
+ uint16 patchValue = patchWord & PATCH_VALUEMASK;
+ switch (patchWord & PATCH_COMMANDMASK) {
+ case PATCH_ADDTOOFFSET:
+ // add value to offset
+ offset += patchValue & ~PATCH_ADDTOOFFSET;
+ break;
+ case PATCH_GETORIGINALBYTE:
+ // get original byte from script
+ if (patchValue >= orgDataSize)
+ error("patching: can not get requested original byte from script");
+ scriptData[offset] = orgData[patchValue];
+ offset++;
+ break;
+ case PATCH_ADJUSTWORD: {
+ // Adjust word right before current position
+ byte *adjustPtr = &scriptData[offset - 2];
+ uint16 adjustWord = READ_LE_UINT16(adjustPtr);
+ adjustWord += patchValue;
+ WRITE_LE_UINT16(adjustPtr, adjustWord);
+ break;
+ }
+ case PATCH_ADJUSTWORD_NEG: {
+ // Adjust word right before current position (negative way)
+ byte *adjustPtr = &scriptData[offset - 2];
+ uint16 adjustWord = READ_LE_UINT16(adjustPtr);
+ adjustWord -= patchValue;
+ WRITE_LE_UINT16(adjustPtr, adjustWord);
+ break;
+ }
+ default:
+ scriptData[offset] = patchValue & 0xFF;
offset++;
}
patch++;
@@ -499,7 +843,7 @@ int32 Script::findSignature(const SciScriptSignature *signature, const byte *scr
uint32 DWordOffset = 0;
// first search for the magic DWORD
while (DWordOffset < searchLimit) {
- if (magicDWord == *(const uint32 *)(scriptData + DWordOffset)) {
+ if (magicDWord == READ_UINT32(scriptData + DWordOffset)) {
// magic DWORD found, check if actual signature matches
uint32 offset = DWordOffset + signature->magicOffset;
uint32 byteOffset = offset;
@@ -529,33 +873,65 @@ int32 Script::findSignature(const SciScriptSignature *signature, const byte *scr
void Script::matchSignatureAndPatch(uint16 scriptNr, byte *scriptData, const uint32 scriptSize) {
const SciScriptSignature *signatureTable = NULL;
- if (g_sci->getGameId() == GID_ECOQUEST)
+ switch (g_sci->getGameId()) {
+ case GID_CASTLEBRAIN:
+ signatureTable = castlebrainSignatures;
+ break;
+ case GID_ECOQUEST:
signatureTable = ecoquest1Signatures;
- if (g_sci->getGameId() == GID_GK1)
+ break;
+ case GID_ECOQUEST2:
+ signatureTable = ecoquest2Signatures;
+ break;
+ case GID_FREDDYPHARKAS:
+ signatureTable = freddypharkasSignatures;
+ break;
+ case GID_GK1:
signatureTable = gk1Signatures;
-// hoyle4 now works due workaround inside GfxPorts
-// if (g_sci->getGameId() == GID_HOYLE4)
-// signatureTable = hoyle4Signatures;
- if (g_sci->getGameId() == GID_KQ5)
+ break;
+ // hoyle4 now works due to workaround inside GfxPorts
+ //case GID_HOYLE4:
+ // signatureTable = hoyle4Signatures;
+ // break;
+ case GID_KQ5:
signatureTable = kq5Signatures;
- if (g_sci->getGameId() == GID_LAURABOW2)
+ break;
+ case GID_LAURABOW2:
signatureTable = laurabow2Signatures;
- if (g_sci->getGameId() == GID_LSL6)
+ break;
+ case GID_LSL6:
signatureTable = larry6Signatures;
- if (g_sci->getGameId() == GID_SQ4)
+ break;
+ case GID_MOTHERGOOSE256:
+ signatureTable = mothergoose256Signatures;
+ break;
+ case GID_QFG1VGA:
+ signatureTable = qfg1vgaSignatures;
+ break;
+ case GID_SQ4:
signatureTable = sq4Signatures;
- if (g_sci->getGameId() == GID_SQ5)
+ break;
+ case GID_SQ5:
signatureTable = sq5Signatures;
+ break;
+ default:
+ break;
+ }
if (signatureTable) {
while (signatureTable->data) {
if (scriptNr == signatureTable->scriptNr) {
- int32 foundOffset = findSignature(signatureTable, scriptData, scriptSize);
- if (foundOffset != -1) {
- // found, so apply the patch
- warning("matched and patched %s on script %d offset %d", signatureTable->description, scriptNr, foundOffset);
- applyPatch(signatureTable->patch, scriptData, scriptSize, foundOffset);
- }
+ int32 foundOffset = 0;
+ int16 applyCount = signatureTable->applyCount;
+ do {
+ foundOffset = findSignature(signatureTable, scriptData, scriptSize);
+ if (foundOffset != -1) {
+ // found, so apply the patch
+ warning("matched and patched %s on script %d offset %d", signatureTable->description, scriptNr, foundOffset);
+ applyPatch(signatureTable->patch, scriptData, scriptSize, foundOffset);
+ }
+ applyCount--;
+ } while ((foundOffset != -1) && (applyCount));
}
signatureTable++;
}
diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp
index 1fb37f458d..1cbe9a56f4 100644
--- a/engines/sci/engine/seg_manager.cpp
+++ b/engines/sci/engine/seg_manager.cpp
@@ -1004,12 +1004,7 @@ int SegManager::instantiateScript(int scriptNum) {
scr->load(_resMan);
scr->initialiseLocals(this);
scr->initialiseClasses(this);
-
- if (getSciVersion() >= SCI_VERSION_1_1) {
- scr->initialiseObjectsSci11(this, segmentId);
- } else {
- scr->initialiseObjectsSci0(this, segmentId);
- }
+ scr->initialiseObjects(this, segmentId);
return segmentId;
}
@@ -1052,7 +1047,7 @@ void SegManager::uninstantiateScriptSci0(int script_nr) {
reg_t reg = make_reg(segmentId, oldScriptHeader ? 2 : 0);
int objType, objLength = 0;
- // Make a pass over the object in order uninstantiate all superclasses
+ // Make a pass over the object in order to uninstantiate all superclasses
do {
reg.offset += objLength; // Step over the last checked object
@@ -1074,8 +1069,17 @@ void SegManager::uninstantiateScriptSci0(int script_nr) {
if (superclass_script == script_nr) {
if (scr->getLockers())
scr->decrementLockers(); // Decrease lockers if this is us ourselves
- } else
- uninstantiateScript(superclass_script);
+ } else {
+ if (g_sci->getGameId() == GID_HOYLE3 && (superclass_script == 0 || superclass_script >= 990)) {
+ // HACK for Hoyle 3: when exiting Checkers or Pachisi, scripts 0, 999 and some others
+ // are deleted but are never instantiated again. We ignore deletion of these scripts
+ // here for Hoyle 3 - bug #3038837
+ // TODO/FIXME: find out why this happens, seems like there is a problem with the object
+ // lock code
+ } else {
+ uninstantiateScript(superclass_script);
+ }
+ }
// Recurse to assure that the superclass lockers number gets decreased
}
diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h
index c8cb4cd203..6eca708e2e 100644
--- a/engines/sci/engine/segment.h
+++ b/engines/sci/engine/segment.h
@@ -227,8 +227,12 @@ enum ObjectOffsets {
class Object {
public:
Object() {
- _flags = 0;
_offset = getSciVersion() < SCI_VERSION_1_1 ? 0 : 5;
+ _flags = 0;
+ _baseObj = 0;
+ _baseVars = 0;
+ _baseMethod = 0;
+ _methodCount = 0;
}
~Object() { }
@@ -286,9 +290,6 @@ public:
bool isClass() const { return (getInfoSelector().offset & kInfoFlagClass); }
const Object *getClass(SegManager *segMan) const;
- void markAsClone() { setInfoSelector(make_reg(0, kInfoFlagClone)); }
- bool isClone() const { return (getInfoSelector().offset & kInfoFlagClone); }
-
void markAsFreed() { _flags |= OBJECT_FLAG_FREED; }
bool isFreed() const { return _flags & OBJECT_FLAG_FREED; }
diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp
index f99a41e088..b31f52aa13 100644
--- a/engines/sci/engine/selector.cpp
+++ b/engines/sci/engine/selector.cpp
@@ -50,7 +50,7 @@ namespace Sci {
void Kernel::mapSelectors() {
// species
// superClass
- // -info-
+ FIND_SELECTOR2(_info_, "-info-");
FIND_SELECTOR(y);
FIND_SELECTOR(x);
FIND_SELECTOR(view);
@@ -86,7 +86,8 @@ void Kernel::mapSelectors() {
// window
FIND_SELECTOR(cursor);
FIND_SELECTOR(max);
- // mark
+ FIND_SELECTOR(mark);
+ FIND_SELECTOR(sort);
// who
FIND_SELECTOR(message);
// edit
@@ -164,7 +165,6 @@ void Kernel::mapSelectors() {
FIND_SELECTOR(vanishingX);
FIND_SELECTOR(vanishingY);
FIND_SELECTOR(iconIndex);
- FIND_SELECTOR(port);
#ifdef ENABLE_SCI32
FIND_SELECTOR(data);
diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h
index 00e795c1b9..98157c3eaf 100644
--- a/engines/sci/engine/selector.h
+++ b/engines/sci/engine/selector.h
@@ -40,6 +40,7 @@ struct SelectorCache {
}
// Statically defined selectors, (almost the) same in all SCI versions
+ Selector _info_;
Selector y;
Selector x;
Selector view, loop, cel; ///< Description of a specific image
@@ -58,7 +59,9 @@ struct SelectorCache {
Selector state, font, type;///< Used by controls
// window
Selector cursor, max; ///< Used by EditControl
- // mark, who
+ Selector mark; //< Used by list controls
+ Selector sort; //< Used by list controls (script internal, is needed by us for QfG3 import room)
+ // who
Selector message; ///< Used by GetEvent
// edit
Selector play; ///< Play function (first function to be called)
@@ -127,8 +130,6 @@ struct SelectorCache {
// SCI1.1 Mac icon bar selectors
Selector iconIndex; ///< Used to index icon bar objects
- Selector port; // used by a hoyle 4 workaround
-
#ifdef ENABLE_SCI32
Selector data; // Used by Array()/String()
Selector picture; // Used to hold the picture ID for SCI32 pictures
diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index a069344d61..732f075257 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -110,6 +110,10 @@ void EngineState::reset(bool isRestoring) {
_lastSaveVirtualId = SAVEGAMEID_OFFICIALRANGE_START;
_lastSaveNewId = 0;
+ _chosenQfGImportItem = 0;
+
+ _cursorWorkaroundActive = false;
+
scriptStepCounter = 0;
scriptGCInterval = GC_INTERVAL;
}
@@ -251,7 +255,7 @@ kLanguage SciEngine::getSciLanguage() {
lang = K_LANG_ENGLISH;
if (SELECTOR(printLang) != -1) {
- lang = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObj, SELECTOR(printLang));
+ lang = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(printLang));
if ((getSciVersion() >= SCI_VERSION_1_1) || (lang == K_LANG_NONE)) {
// If language is set to none, we use the language from the game detector.
@@ -292,7 +296,7 @@ kLanguage SciEngine::getSciLanguage() {
void SciEngine::setSciLanguage(kLanguage lang) {
if (SELECTOR(printLang) != -1)
- writeSelectorValue(_gamestate->_segMan, _gameObj, SELECTOR(printLang), lang);
+ writeSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(printLang), lang);
}
void SciEngine::setSciLanguage() {
@@ -304,7 +308,7 @@ Common::String SciEngine::strSplit(const char *str, const char *sep) {
kLanguage subLang = K_LANG_NONE;
if (SELECTOR(subtitleLang) != -1) {
- subLang = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObj, SELECTOR(subtitleLang));
+ subLang = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(subtitleLang));
}
kLanguage secondLang;
@@ -327,7 +331,7 @@ Common::String SciEngine::strSplit(const char *str, const char *sep) {
void SciEngine::checkVocabularySwitch() {
uint16 parserLanguage = 1;
if (SELECTOR(parseLang) != -1)
- parserLanguage = readSelectorValue(_gamestate->_segMan, _gameObj, SELECTOR(parseLang));
+ parserLanguage = readSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(parseLang));
if (parserLanguage != _vocabularyLanguage) {
delete _vocabulary;
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index 4f1d686b17..d0ddd5ca06 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -59,17 +59,23 @@ enum AbortGameState {
class DirSeeker {
protected:
reg_t _outbuffer;
- Common::StringArray _savefiles;
+ Common::StringArray _files;
+ Common::StringArray _virtualFiles;
Common::StringArray::const_iterator _iter;
public:
DirSeeker() {
_outbuffer = NULL_REG;
- _iter = _savefiles.begin();
+ _iter = _files.begin();
}
reg_t firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan);
reg_t nextFile(SegManager *segMan);
+
+ Common::String getVirtualFilename(uint fileNumber);
+
+private:
+ void addAsVirtualFiles(Common::String title, Common::String fileMask);
};
enum {
@@ -80,11 +86,11 @@ enum {
MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */
};
-// We assume that scripts give us savegameId 0->999 for creating a new save slot
-// and savegameId 1000->1999 for existing save slots ffs. kfile.cpp
+// We assume that scripts give us savegameId 0->99 for creating a new save slot
+// and savegameId 100->199 for existing save slots ffs. kfile.cpp
enum {
- SAVEGAMEID_OFFICIALRANGE_START = 1000,
- SAVEGAMEID_OFFICIALRANGE_END = 1999
+ SAVEGAMEID_OFFICIALRANGE_START = 100,
+ SAVEGAMEID_OFFICIALRANGE_END = 199
};
enum {
@@ -136,8 +142,14 @@ public:
DirSeeker _dirseeker;
- uint _lastSaveVirtualId; // last virtual id fed to kSaveGame, if no kGetSaveFiles was called inbetween
- uint _lastSaveNewId; // last newly created filename-id by kSaveGame
+ int16 _lastSaveVirtualId; // last virtual id fed to kSaveGame, if no kGetSaveFiles was called inbetween
+ int16 _lastSaveNewId; // last newly created filename-id by kSaveGame
+
+ uint _chosenQfGImportItem; // Remembers the item selected in QfG import rooms
+
+ bool _cursorWorkaroundActive; // ffs. GfxCursor::setPosition()
+ Common::Point _cursorWorkaroundPoint;
+ Common::Rect _cursorWorkaroundRect;
public:
/* VM Information */
diff --git a/engines/sci/engine/static_selectors.cpp b/engines/sci/engine/static_selectors.cpp
index aae6de01f1..ed8d4193a9 100644
--- a/engines/sci/engine/static_selectors.cpp
+++ b/engines/sci/engine/static_selectors.cpp
@@ -27,6 +27,7 @@
// them. This includes the King's Quest IV Demo and LSL3 Demo.
#include "sci/engine/kernel.h"
+#include "sci/engine/seg_manager.h"
namespace Sci {
@@ -118,6 +119,7 @@ static const SelectorRemap sciSelectorRemap[] = {
{ SCI_VERSION_1_1, SCI_VERSION_1_1, "maxScale", 106 },
{ SCI_VERSION_1_1, SCI_VERSION_1_1, "vanishingX", 107 },
{ SCI_VERSION_1_1, SCI_VERSION_1_1, "vanishingY", 108 },
+ { SCI_VERSION_1_1, SCI_VERSION_2_1, "-info-",4103 },
{ SCI_VERSION_NONE, SCI_VERSION_NONE, 0, 0 }
};
@@ -133,8 +135,12 @@ Common::StringArray Kernel::checkStaticSelectorNames() {
// Resize the list of selector names and fill in the SCI 0 names.
names.resize(count);
- for (int i = 0; i < offset; i++)
- names[i].clear();
+ if (getSciVersion() < SCI_VERSION_1_1) {
+ // Fill selectors 0 - 2 for SCI0 - SCI1 late
+ names[0] = "species";
+ names[1] = "superClass";
+ names[2] = "-info-";
+ }
if (getSciVersion() <= SCI_VERSION_1_1) {
// SCI0 - SCI11
@@ -149,14 +155,82 @@ Common::StringArray Kernel::checkStaticSelectorNames() {
names[i] = sci1Selectors[i - count];
}
- for (const SelectorRemap *selectorRemap = sciSelectorRemap; selectorRemap->slot; ++selectorRemap) {
- if (getSciVersion() >= selectorRemap->minVersion && getSciVersion() <= selectorRemap->maxVersion) {
- const uint32 slot = selectorRemap->slot;
- if (slot >= names.size())
- names.resize(slot + 1);
- names[slot] = selectorRemap->name;
+ // Now, we need to find out selectors which keep changing place...
+ // We do that by dissecting game objects, and looking for selectors at
+ // specified locations.
+
+ // We need to initialize script 0 here, to make sure that it's always
+ // located at segment 1.
+ _segMan->instantiateScript(0);
+
+ // The Actor class contains the init, xLast and yLast selectors, which
+ // we reference directly. It's always in script 998, so we need to
+ // explicitly load it here.
+ if (_resMan->testResource(ResourceId(kResourceTypeScript, 998))) {
+ _segMan->instantiateScript(998);
+
+ const Object *actorClass = _segMan->getObject(_segMan->findObjectByName("Actor"));
+
+ if (actorClass) {
+ // The init selector is always the first function
+ int initSelectorPos = actorClass->getFuncSelector(0);
+
+ if (names.size() < (uint32)initSelectorPos + 2)
+ names.resize((uint32)initSelectorPos + 2);
+
+ names[initSelectorPos] = "init";
+ // dispose comes right after init
+ names[initSelectorPos + 1] = "dispose";
+
+ if ((getSciVersion() >= SCI_VERSION_1_EGA)) {
+ // Find the xLast and yLast selectors, used in kDoBresen
+
+ // xLast and yLast always come between illegalBits and xStep
+ int illegalBitsSelectorPos = actorClass->locateVarSelector(_segMan, 15 + offset); // illegalBits
+ int xStepSelectorPos = actorClass->locateVarSelector(_segMan, 51 + offset); // xStep
+ if (xStepSelectorPos - illegalBitsSelectorPos != 3) {
+ error("illegalBits and xStep selectors aren't found in "
+ "known locations. illegalBits = %d, xStep = %d",
+ illegalBitsSelectorPos, xStepSelectorPos);
+ }
+
+ int xLastSelectorPos = actorClass->getVarSelector(illegalBitsSelectorPos + 1);
+ int yLastSelectorPos = actorClass->getVarSelector(illegalBitsSelectorPos + 2);
+
+ if (names.size() < (uint32)yLastSelectorPos + 1)
+ names.resize((uint32)yLastSelectorPos + 1);
+
+ names[xLastSelectorPos] = "xLast";
+ names[yLastSelectorPos] = "yLast";
+ } // if ((getSciVersion() >= SCI_VERSION_1_EGA))
+ } // if (actorClass)
+
+ _segMan->uninstantiateScript(998);
+ } // if (_resMan->testResource(ResourceId(kResourceTypeScript, 998)))
+
+ if (_resMan->testResource(ResourceId(kResourceTypeScript, 981))) {
+ // The SysWindow class contains the open selectors, which we
+ // reference directly. It's always in script 981, so we need to
+ // explicitly load it here
+ _segMan->instantiateScript(981);
+
+ const Object *sysWindowClass = _segMan->getObject(_segMan->findObjectByName("SysWindow"));
+
+ if (sysWindowClass) {
+ if (sysWindowClass->getMethodCount() < 2)
+ error("The SysWindow class has less than 2 methods");
+
+ // The open selector is always the second function
+ int openSelectorPos = sysWindowClass->getFuncSelector(1);
+
+ if (names.size() < (uint32)openSelectorPos + 1)
+ names.resize((uint32)openSelectorPos + 1);
+
+ names[openSelectorPos] = "open";
}
- }
+
+ _segMan->uninstantiateScript(981);
+ } // if (_resMan->testResource(ResourceId(kResourceTypeScript, 981)))
if (g_sci->getGameId() == GID_HOYLE4) {
// The demo of Hoyle 4 is one of the few demos with lip syncing and no selector vocabulary.
@@ -168,21 +242,26 @@ 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);
+ } else if (g_sci->getGameId() == GID_PEPPER) {
+ // Same as above for the non-interactive demo of Pepper
+ if (names.size() < 539)
+ names.resize(539);
- names[110] = "init";
+ names[263] = "syncTime";
+ names[264] = "syncCue";
+ names[538] = "startText";
} else if (g_sci->getGameId() == GID_LAURABOW2) {
- // The floppy of version needs the open and changeState selectors set to match up with the
- // CD version's workarounds - bugs #3035694 and #3036291
- if (names.size() < 190)
- names.resize(190);
+ // The floppy of version needs the changeState selector set to match up with the
+ // CD version's workarounds.
+ if (names.size() < 251)
+ names.resize(251);
names[144] = "changeState";
- names[189] = "open";
+ } else if (g_sci->getGameId() == GID_CNICK_KQ) {
+ if (names.size() < 447)
+ names.resize(447);
+
+ names[446] = "say";
}
#ifdef ENABLE_SCI32
@@ -193,6 +272,15 @@ Common::StringArray Kernel::checkStaticSelectorNames() {
#endif
}
+ for (const SelectorRemap *selectorRemap = sciSelectorRemap; selectorRemap->slot; ++selectorRemap) {
+ if (getSciVersion() >= selectorRemap->minVersion && getSciVersion() <= selectorRemap->maxVersion) {
+ const uint32 slot = selectorRemap->slot;
+ if (slot >= names.size())
+ names.resize(slot + 1);
+ names[slot] = selectorRemap->name;
+ }
+ }
+
return names;
}
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 0500cc601b..7342f8ca7b 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -207,10 +207,21 @@ static reg_t validate_read_var(reg_t *r, reg_t *stack_base, int type, int max, i
// We need to find correct replacements for each situation manually
SciTrackOriginReply originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(index, uninitializedReadWorkarounds, &originReply);
- if (solution.type == WORKAROUND_NONE)
+ if (solution.type == WORKAROUND_NONE) {
+#ifdef RELEASE_BUILD
+ // If we are running an official ScummVM release -> fake 0 in unknown cases
+ warning("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);
+
+ r[index] = NULL_REG;
+ break;
+#else
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);
+#endif
+ }
assert(solution.type == WORKAROUND_FAKE);
r[index] = make_reg(0, solution.value);
break;
@@ -295,7 +306,7 @@ bool SciEngine::checkExportBreakpoint(uint16 script, uint16 pubfunct) {
_console->DebugPrintf("Break on script %d, export %d\n", script, pubfunct);
_debugState.debugging = true;
_debugState.breakpointWasHit = true;
- return true;;
+ return true;
}
}
}
@@ -318,10 +329,10 @@ ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct, StackP
// HACK: Temporarily switch to a warning in SCI32 games until we can figure out why Torin has
// an invalid exported function.
if (getSciVersion() >= SCI_VERSION_2)
- warning("Request for invalid exported function 0x%x of script 0x%x", pubfunct, script);
+ warning("Request for invalid exported function 0x%x of script %d", pubfunct, script);
else
#endif
- error("Request for invalid exported function 0x%x of script 0x%x", pubfunct, script);
+ error("Request for invalid exported function 0x%x of script %d", pubfunct, script);
return NULL;
}
@@ -366,27 +377,24 @@ struct CallsStruct {
int type; /**< Same as ExecStack.type */
};
-bool SciEngine::checkSelectorBreakpoint(reg_t send_obj, int selector) {
- if (_debugState._activeBreakpointTypes & BREAK_SELECTOR) {
- char method_name[256];
+bool SciEngine::checkSelectorBreakpoint(BreakpointType breakpointType, reg_t send_obj, int selector) {
+ char method_name[256];
- sprintf(method_name, "%s::%s", _gamestate->_segMan->getObjectName(send_obj), getKernel()->getSelectorName(selector).c_str());
+ sprintf(method_name, "%s::%s", _gamestate->_segMan->getObjectName(send_obj), getKernel()->getSelectorName(selector).c_str());
- Common::List<Breakpoint>::const_iterator bp;
- for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) {
- int cmplen = bp->name.size();
- if (bp->name.lastChar() != ':')
- cmplen = 256;
+ Common::List<Breakpoint>::const_iterator bp;
+ for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) {
+ int cmplen = bp->name.size();
+ if (bp->name.lastChar() != ':')
+ cmplen = 256;
- if (bp->type == BREAK_SELECTOR && !strncmp(bp->name.c_str(), method_name, cmplen)) {
- _console->DebugPrintf("Break on %s (in [%04x:%04x])\n", method_name, PRINT_REG(send_obj));
- _debugState.debugging = true;
- _debugState.breakpointWasHit = true;
- return true;
- }
+ if (bp->type == breakpointType && !strncmp(bp->name.c_str(), method_name, cmplen)) {
+ _console->DebugPrintf("Break on %s (in [%04x:%04x])\n", method_name, PRINT_REG(send_obj));
+ _debugState.debugging = true;
+ _debugState.breakpointWasHit = true;
+ return true;
}
}
-
return false;
}
@@ -399,12 +407,13 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt
int selector;
int argc;
int origin = s->_executionStack.size()-1; // Origin: Used for debugging
- bool printSendActions = false;
// We return a pointer to the new active ExecStack
// The selector calls we catch are stored below:
Common::Stack<CallsStruct> sendCalls;
+ int activeBreakpointTypes = g_sci->_debugState._activeBreakpointTypes;
+
while (framesize > 0) {
selector = validate_arithmetic(*argp++);
argc = validate_arithmetic(*argp);
@@ -413,9 +422,6 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt
error("send_selector(): More than 0x800 arguments to function call");
}
- // Check if a breakpoint is set on this method
- printSendActions = g_sci->checkSelectorBreakpoint(send_obj, selector);
-
#ifdef VM_DEBUG_SEND
printf("Send to %04x:%04x (%s), selector %04x (%s):", PRINT_REG(send_obj),
s->_segMan->getObjectName(send_obj), selector,
@@ -439,18 +445,23 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt
// argc == 0: read selector
// argc != 0: write selector
- if (printSendActions && !argc) { // read selector
- debug("[read selector]\n");
- printSendActions = false;
- }
-
- if (printSendActions && argc) {
- reg_t oldReg = *varp.getPointer(s->_segMan);
- reg_t newReg = argp[1];
- warning("[write to selector (%s:%s): change %04x:%04x to %04x:%04x]\n",
- s->_segMan->getObjectName(send_obj), g_sci->getKernel()->getSelectorName(selector).c_str(),
- PRINT_REG(oldReg), PRINT_REG(newReg));
- printSendActions = false;
+ if (!argc) {
+ // read selector
+ if (activeBreakpointTypes & BREAK_SELECTORREAD) {
+ if (g_sci->checkSelectorBreakpoint(BREAK_SELECTORREAD, send_obj, selector))
+ debug("[read selector]\n");
+ }
+ } else {
+ // write selector
+ if (activeBreakpointTypes & BREAK_SELECTORWRITE) {
+ if (g_sci->checkSelectorBreakpoint(BREAK_SELECTORWRITE, send_obj, selector)) {
+ reg_t oldReg = *varp.getPointer(s->_segMan);
+ reg_t newReg = argp[1];
+ warning("[write to selector (%s:%s): change %04x:%04x to %04x:%04x]\n",
+ s->_segMan->getObjectName(send_obj), g_sci->getKernel()->getSelectorName(selector).c_str(),
+ PRINT_REG(oldReg), PRINT_REG(newReg));
+ }
+ }
}
if (argc > 1) {
@@ -482,7 +493,35 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt
case kSelectorMethod:
-#ifdef VM_DEBUG_SEND
+#ifndef VM_DEBUG_SEND
+ if (activeBreakpointTypes & BREAK_SELECTOREXEC) {
+ if (g_sci->checkSelectorBreakpoint(BREAK_SELECTOREXEC, send_obj, selector)) {
+ printf("[execute selector]");
+
+ int displaySize = 0;
+ for (int argNr = 1; argNr <= argc; argNr++) {
+ if (argNr == 1)
+ printf(" - ");
+ reg_t curParam = argp[argNr];
+ if (curParam.segment) {
+ printf("[%04x:%04x] ", PRINT_REG(curParam));
+ displaySize += 12;
+ } else {
+ printf("[%04x] ", curParam.offset);
+ displaySize += 7;
+ }
+ if (displaySize > 50) {
+ if (argNr < argc)
+ printf("...");
+ break;
+ }
+ }
+ printf("\n");
+ }
+ }
+#else // VM_DEBUG_SEND
+ if (activeBreakpointTypes & BREAK_SELECTOREXEC)
+ g_sci->checkSelectorBreakpoint(BREAK_SELECTOREXEC, send_obj, selector);
printf("Funcselector(");
for (int i = 0; i < argc; i++) {
printf("%04x:%04x", PRINT_REG(argp[i+1]));
@@ -491,31 +530,6 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt
}
printf(") at %04x:%04x\n", PRINT_REG(funcp));
#endif // VM_DEBUG_SEND
- if (printSendActions) {
- printf("[invoke selector]");
-#ifndef VM_DEBUG_SEND
- int displaySize = 0;
- for (int argNr = 1; argNr <= argc; argNr++) {
- if (argNr == 1)
- printf(" - ");
- reg_t curParam = argp[argNr];
- if (curParam.segment) {
- printf("[%04x:%04x] ", PRINT_REG(curParam));
- displaySize += 12;
- } else {
- printf("[%04x] ", curParam.offset);
- displaySize += 7;
- }
- if (displaySize > 50) {
- if (argNr < argc)
- printf("...");
- break;
- }
- }
-#endif
- printf("\n");
- printSendActions = false;
- }
{
CallsStruct call;
@@ -817,7 +831,7 @@ int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4])
for (int i = 0; g_opcode_formats[opcode][i]; ++i) {
//printf("Opcode: 0x%x, Opnumber: 0x%x, temp: %d\n", opcode, opcode, temp);
- assert(i < 4);
+ assert(i < 3);
switch (g_opcode_formats[opcode][i]) {
case Script_Byte:
@@ -911,7 +925,7 @@ void run_vm(EngineState *s) {
g_sci->_debugState.old_pc_offset = s->xs->addr.pc.offset;
g_sci->_debugState.old_sp = s->xs->sp;
- if (s->abortScriptProcessing != kAbortNone || g_engine->shouldQuit())
+ if (s->abortScriptProcessing != kAbortNone)
return; // Stop processing
if (s->_executionStackPosChanged) {
@@ -942,7 +956,7 @@ void run_vm(EngineState *s) {
s->variables[VAR_PARAM] = s->xs->variables_argp;
}
- if (s->abortScriptProcessing != kAbortNone || g_engine->shouldQuit())
+ if (s->abortScriptProcessing != kAbortNone)
return; // Stop processing
// Debug if this has been requested:
@@ -1319,7 +1333,7 @@ void run_vm(EngineState *s) {
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);
+ s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeUltWorkarounds, r_temp, s->r_acc);
}
break;
@@ -1442,7 +1456,7 @@ void run_vm(EngineState *s) {
s->_executionStackPosChanged = true;
// If a game is being loaded, stop processing
- if (s->abortScriptProcessing != kAbortNone || g_engine->shouldQuit())
+ if (s->abortScriptProcessing != kAbortNone)
return; // Stop processing
break;
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 95674ceaad..d332c64a9d 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -48,6 +48,8 @@ const SciWorkaroundEntry opcodeDptoaWorkarounds[] = {
// gameID, room,script,lvl, object-name, method-name, call,index, workaround
const SciWorkaroundEntry opcodeGeWorkarounds[] = {
+ { GID_HOYLE1, 5, 213, 0, "", "export 0", -1, 0, { WORKAROUND_FAKE, 1 } }, // happens sometimes during cribbage - bug #3038433
+ { GID_MOTHERGOOSE256, 4, 998, 0, "door", "setCel", -1, 0, { WORKAROUND_FAKE, 1 } }, // after giving the king his pipe back, listening to his song and leaving the castle - bug #3051475
{ GID_PQ3, 31, 31, 0, "rm031", "init", -1, 0, { WORKAROUND_FAKE, 1 } }, // pq3 english: when exiting the car, while morales is making phonecalls - bug #3037565
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -59,6 +61,12 @@ const SciWorkaroundEntry opcodeLeWorkarounds[] = {
};
// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+const SciWorkaroundEntry opcodeUltWorkarounds[] = {
+ { GID_HOYLE3, 400, 0, 1, "Character", "say", -1, 0, { WORKAROUND_FAKE, 0 } }, // While playing Pachisi, when any character starts to talk - bug #3038837
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// gameID, room,script,lvl, object-name, method-name, call,index, workaround
const SciWorkaroundEntry opcodeLaiWorkarounds[] = {
{ GID_CAMELOT, 92, 92, 0, "endingCartoon2", "changeState", 0x20d, 0, { WORKAROUND_FAKE, 0 } }, // during the ending, sub gets called with no parameters, uses parameter 1 which is theGrail in this case - bug #3044734
SCI_WORKAROUNDENTRY_TERMINATOR
@@ -78,37 +86,38 @@ const SciWorkaroundEntry opcodeMulWorkarounds[] = {
// gameID, room,script,lvl, object-name, method-name, call,index, workaround
const SciWorkaroundEntry opcodeAndWorkarounds[] = {
- { GID_MOTHERGOOSE, -1, 999, 0, "Event", "new", -1, 0, { WORKAROUND_FAKE, 0 } }, // constantly during the game
- // ^^ TODO: which of the mother goose versions is affected by this? EGA? SCI1? SCI1.1?
+ { GID_MOTHERGOOSE256, -1, 999, 0, "Event", "new", -1, 0, { WORKAROUND_FAKE, 0 } }, // constantly during the game
+ // ^^ TODO: which of the mother goose versions is affected by this? EGA? SCI1? SCI1.1?
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
+ { 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
+ { GID_MOTHERGOOSE256, -1, 4, 0, "rm004", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // when going north and reaching the castle (rooms 4 and 37) - bug #3038228
SCI_WORKAROUNDENTRY_TERMINATOR
};
// gameID, room,script,lvl, object-name, method-name, call,index, workaround
const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ GID_CASTLEBRAIN, 280, 280, 0, "programmer", "dispatchEvent", -1, 0, { WORKAROUND_FAKE, 0xf } }, // pressing 'q' on the computer screen in the robot room, and closing the help dialog that pops up (bug #3039656). Moves the cursor to the view with the ID returned (in this case, the robot hand)
- { GID_CNICK_KQ, 200, 0, 1, "Character", "<noname446>", -1, 504, { WORKAROUND_FAKE, 0 } }, // checkers, like in hoyle 3
- { GID_CNICK_KQ, 200, 0, 1, "Character", "<noname446>", -1, 505, { WORKAROUND_FAKE, 0 } }, // checkers, like in hoyle 3
- { GID_CNICK_KQ, -1, 700, 0, "gcWindow", "<noname183>", -1, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu, like in hoyle 3
- { GID_CNICK_LONGBOW, 0, 0, 0, "RH Budget", "<noname110>", -1, 1, { WORKAROUND_FAKE, 0 } }, // when starting the game
+ { GID_CNICK_KQ, 200, 0, 1, "Character", "say", -1, -1, { WORKAROUND_FAKE, 0 } }, // checkers, like in hoyle 3 - temps 504 and 505
+ { GID_CNICK_KQ, -1, 700, 0, "gcWindow", "open", -1, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu, like in hoyle 3
+ { GID_CNICK_LONGBOW, 0, 0, 0, "RH Budget", "init", -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_FANMADE, 516, 979, 0, "", "export 0", -1, 20, { WORKAROUND_FAKE, 0 } }, // Happens in Grotesteing after the logos
{ GID_FANMADE, 528, 990, 0, "GDialog", "doit", -1, 4, { WORKAROUND_FAKE, 0 } }, // Happens in Cascade Quest when closing the glossary - bug #3038757
+ { GID_FANMADE, 488, 1, 0, "RoomScript", "doit", 0x1f17, 1, { WORKAROUND_FAKE, 0 } }, // Happens in Ocean Battle while playing - bug #3059871
{ 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_FREDDYPHARKAS, 540, 540, 0, "WaverCode", "init", -1, -1, { WORKAROUND_FAKE, 0 } }, // Gun pratice mini-game (bug #3044218)
+ { GID_FREDDYPHARKAS, 540, 540, 0, "WaverCode", "init", -1, -1, { WORKAROUND_FAKE, 0 } }, // Gun pratice mini-game (bug #3044218)
{ 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_HOYLE1, 3, 16, 0, "", "export 0", 0x37c, 3, { WORKAROUND_FAKE, 0 } }, // Hearts / during the game - bug #3052359
+ { GID_HOYLE3, -1, 0, 1, "Character", "say", -1, -1, { WORKAROUND_FAKE, 0 } }, // when starting checkers or dominoes, first time a character says something - temps 504 and 505
{ GID_HOYLE3, -1, 700, 0, "gcWindow", "open", -1, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu
{ GID_HOYLE4, -1, 0, 0, "gcWindow", "open", -1, -1, { WORKAROUND_FAKE, 0 } }, // when selecting "Control" from the menu (temp vars 0-3) - bug #3039294
{ GID_HOYLE4, 910, 18, 0, "Tray", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // during tutorial - bug #3042756
@@ -117,19 +126,18 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ 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_JONES, 1, 255, 0, "", "export 0", -1, -1, { WORKAROUND_FAKE, 0 } }, // jones/cd only - called when a game ends, temps 13 and 14
+ { GID_JONES, 764, 255, 0, "", "export 0", -1, -1, { WORKAROUND_FAKE, 0 } }, // jones/ega&vga only - called when the game starts, temps 13 and 14
//{ GID_KQ5, -1, 0, 0, "", "export 29", -1, 3, { WORKAROUND_FAKE, 0xf } }, // called when playing harp for the harpies or when aborting dialog in toy shop, is used for kDoAudio - bug #3034700
- // ^^ shouldn't be needed anymore, we got a script patch instead (kq5PatchCdHarpyVolume)
+ // ^^ shouldn't be needed anymore, we got a script patch instead (kq5PatchCdHarpyVolume)
{ 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_KQ5, 55, 55, 0, "helpScript", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // when giving the tambourine to the monster in the labyrinth (only happens at one of the locations) - bug #3041262
+ { GID_KQ5, 55, 55, 0, "helpScript", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // when giving the tambourine to the monster in the labyrinth (only happens at one of the locations) - bug #3041262
{ 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_KQ6, -1, 907, 0, "tomato", "doVerb", -1, 2, { WORKAROUND_FAKE, 0 } }, // when looking at the rotten tomato in the inventory - bug #3059544
{ GID_KQ7, 30, 64996, 0, "User", "handleEvent", -1, 1, { WORKAROUND_FAKE, 0 } }, // called when pushing a keyboard key
{ GID_LAURABOW, 37, 0, 0, "CB1", "doit", -1, 1, { WORKAROUND_FAKE, 0 } }, // when going up the stairs (bug #3037694)
{ GID_LAURABOW, -1, 967, 0, "myIcon", "cycle", -1, 1, { WORKAROUND_FAKE, 0 } }, // having any portrait conversation coming up (initial bug #3034985)
@@ -137,6 +145,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ GID_LAURABOW2, -1, 21, 0, "dropCluesCode", "doit", -1, 1, { WORKAROUND_FAKE, 0x7fff } }, // when asking some questions (e.g. the reporter about the burglary, or the policeman about Ziggy). Must be big, as the game scripts perform lt on it and start deleting journal entries - bugs #3035068, #3036274
{ GID_LAURABOW2, -1, 90, 1, "MuseumActor", "init", -1, 6, { WORKAROUND_FAKE, 0 } }, // Random actors in museum (bug #3041257)
{ 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, 0, 0, "Longbow", "restart", -1, 0, { WORKAROUND_FAKE, 0 } }, // When canceling a restart game - bug #3046200
{ GID_LONGBOW, -1, 213, 0, "clear", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // When giving an answer 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
@@ -150,23 +159,29 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ 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, -1, 0, 0, "MG", "doit", -1, 5, { WORKAROUND_FAKE, 0 } }, // SCI1.1: When moving the cursor all the way to the left during the game (bug #3043955)
- { 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_MOTHERGOOSE256, -1, 0, 0, "MG", "doit", -1, 5, { WORKAROUND_FAKE, 0 } }, // SCI1.1: When moving the cursor all the way to the left during the game (bug #3043955)
+ { GID_MOTHERGOOSE256, -1, 992, 0, "AIPath", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // Happens in the demo and full version. In the demo, it happens when walking two screens from mother goose's house to the north. In the full version, it happens in rooms 7 and 23 - bug #3049146
+ { GID_MOTHERGOOSE256, 94, 94, 0, "sunrise", "changeState", -1, 367, { WORKAROUND_FAKE, 0 } }, // At the very end, after the game is completed - bug #3051163
{ 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_PEPPER, -1, 894, 0, "Package", "doVerb", -1, 3, { WORKAROUND_FAKE, 0 } }, // using the hand on the book in the inventory - bug #3040012
+ { GID_PEPPER, 150, 928, 0, "Narrator", "startText", -1, 0, { WORKAROUND_FAKE, 0 } }, // happens during the non-interactive demo of Pepper
{ GID_QFG1, -1, 210, 0, "Encounter", "init", 0xbd0, 0, { WORKAROUND_FAKE, 0 } }, // hq1: going to the brigands hideout
{ GID_QFG1, -1, 210, 0, "Encounter", "init", 0xbe4, 0, { WORKAROUND_FAKE, 0 } }, // qfg1: going to the brigands hideout
+ { GID_QFG1VGA, 16, 16, 0, "lassoFailed", "changeState", -1, -1, { WORKAROUND_FAKE, 0 } }, // qfg1vga: casting the "fetch" spell in the screen with the flowers, temps 0 and 1 - bug #3053268
{ GID_QFG2, -1, 71, 0, "theInvSheet", "doit", -1, 1, { WORKAROUND_FAKE, 0 } }, // accessing the inventory
{ GID_QFG2, -1, 701, -1, "Alley", "at", -1, 0, { WORKAROUND_FAKE, 0 } }, // when walking inside the alleys in the town - bug #3035835 & #3038367
{ GID_QFG2, -1, 990, 0, "Restore", "doit", -1, 364, { WORKAROUND_FAKE, 0 } }, // when pressing enter in restore dialog w/o any saved games present
{ GID_QFG2, 260, 260, 0, "abdulS", "changeState",0x2d22, -1, { WORKAROUND_FAKE, 0 } }, // During the thief's first mission (in the house), just before Abdul is about to enter the house (where you have to hide in the wardrobe), bug #3039891, temps 1 and 2
{ GID_QFG2, 260, 260, 0, "jabbarS", "changeState",0x2d22, -1, { WORKAROUND_FAKE, 0 } }, // During the thief's first mission (in the house), just before Jabbar is about to enter the house (where you have to hide in the wardrobe), bug #3040469, temps 1 and 2
+ { GID_QFG3, 510, 510, 0, "awardPrize", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // Simbani warrior challenge, after throwing the spears and retrieving the ring - bug #3049435
{ GID_QFG3, 140, 140, 0, "rm140", "init", 0x1008, 0, { WORKAROUND_FAKE, 0 } }, // when importing a character and selecting the previous profession - bug #3040460
{ GID_QFG3, 330, 330, -1, "Teller", "doChild", -1, -1, { WORKAROUND_FAKE, 0 } }, // when talking to King Rajah about "Rajah" (bug #3036390, temp 1) or "Tarna" (temp 0), or when clicking on yourself and saying "Greet" (bug #3039774, temp 1)
{ GID_QFG3, 700, 700, -1, "monsterIsDead", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // in the jungle, after winning any fight, bug #3040624
{ GID_QFG3, 470, 470, -1, "rm470", "notify", -1, 0, { WORKAROUND_FAKE, 0 } }, // closing the character screen in the Simbani village in the room with the bridge, bug #3040565
{ GID_QFG3, 490, 490, -1, "computersMove", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // when finishing awari game, bug #3040579
+ { GID_QFG3, 490, 490, -1, "computersMove", "changeState", 0xf53, 4, { WORKAROUND_FAKE, 0 } }, // also when finishing awari game
+ { GID_QFG3, 851, 32, -1, "ProjObj", "doit", -1, 1, { WORKAROUND_FAKE, 0 } }, // near the end, when throwing the spear of death, bug #3050122
{ GID_QFG4, -1, 15, -1, "charInitScreen", "dispatchEvent", -1, 5, { WORKAROUND_FAKE, 0 } }, // floppy version, when viewing the character screen
{ GID_QFG4, -1, 64917, -1, "controlPlane", "setBitmap", -1, 3, { WORKAROUND_FAKE, 0 } }, // floppy version, when entering the game menu
{ GID_QFG4, -1, 64917, -1, "Plane", "setBitmap", -1, 3, { WORKAROUND_FAKE, 0 } }, // floppy version, happen sometimes in fights
@@ -176,7 +191,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ GID_SQ4, -1, 398, 0, "showBox", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // CD: called when rummaging in Software Excess bargain bin
{ GID_SQ4, -1, 928, 0, "Narrator", "startText", -1, 1000, { WORKAROUND_FAKE, 1 } }, // CD: method returns this to the caller
{ GID_SQ5, 201, 201, 0, "buttonPanel", "doVerb", -1, 0, { WORKAROUND_FAKE, 1 } }, // when looking at the orange or red button - bug #3038563
- { GID_SQ6, 100, 0, 0, "SQ6", "init", -1, 2, { WORKAROUND_FAKE, 0 } }, // called when the game starts
+ { GID_SQ6, -1, 0, 0, "SQ6", "init", -1, 2, { WORKAROUND_FAKE, 0 } }, // Demo and full version: called when the game starts (demo: room 0, full: room 100)
{ 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
@@ -196,6 +211,7 @@ const SciWorkaroundEntry kCelHigh_workarounds[] = {
{ GID_KQ5, -1, 255, 0, "deathIcon", "setSize", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when getting beaten up in the inn and probably more, called with 2nd parameter as object - bug #3037003
{ GID_PQ2, -1, 255, 0, "DIcon", "setSize", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when showing picture within windows, called with 2nd/3rd parameters as objects
{ 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
+ { GID_FANMADE, -1, 979, 0, "DIcon", "setSize", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // In The Gem Scenario and perhaps other fanmade games, this is called with 2nd/3rd parameters as objects - bug #3039679
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -204,6 +220,7 @@ const SciWorkaroundEntry kCelWide_workarounds[] = {
{ GID_KQ5, -1, 255, 0, "deathIcon", "setSize", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when getting beaten up in the inn and probably more, called with 2nd parameter as object - bug #3037003
{ GID_PQ2, -1, 255, 0, "DIcon", "setSize", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when showing picture within windows, called with 2nd/3rd parameters as objects
{ 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
+ { GID_FANMADE, -1, 979, 0, "DIcon", "setSize", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // In The Gem Scenario and perhaps other fanmade games, this is called with 2nd/3rd parameters as objects - bug #3039679
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -227,13 +244,18 @@ const SciWorkaroundEntry kDisplay_workarounds[] = {
{ GID_PQ2, 23, 23, 0, "rm23Script", "elements", 0x4c1, 0, { WORKAROUND_IGNORE, 0 } }, // when looking at the 2nd page of pate's file - 0x75 as id (another pq2 version, bug #3043904)
{ GID_QFG1, 11, 11, 0, "battle", "<noname90>", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: When entering battle, 0x75 as id
{ GID_SQ1, -1, 700, 0, "arcadaRegion", "doit", -1, 0, { WORKAROUND_IGNORE, 0 } }, // restoring in some rooms of the arcada (right at the start)
- { GID_SQ4, 397, 0, 0, "", "export 12", -1, 0, { WORKAROUND_IGNORE, 0 } }, // FLOPPY: when going into the computer store (bug #3044044)
+ { GID_SQ4, 397, 0, 0, "", "export 12", -1, 0, { WORKAROUND_IGNORE, 0 } }, // FLOPPY: when going into the computer store (bug #3044044)
{ GID_SQ4, 391, 391, 0, "doCatalog", "mode", 0x84, 0, { WORKAROUND_IGNORE, 0 } }, // CD: clicking on catalog in roboter sale - a parameter is an object
{ GID_SQ4, 391, 391, 0, "choosePlug", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // CD: 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 kDirLoop_workarounds[] = {
+ { GID_KQ4, 4, 992, 0, "Avoid", "doit", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when the ogre catches you in front of his house, second parameter points to the same object as the first parameter, instead of being an integer (the angle) - bug #3042964
+};
+
+// 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
@@ -292,6 +314,7 @@ const SciWorkaroundEntry kGraphRestoreBox_workarounds[] = {
{ 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
+ { GID_SQ5, 850, 850, 0, "quirksTurn", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens while playing Battle Cruiser (invalid segment) - bug #3056811
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -344,7 +367,7 @@ const SciWorkaroundEntry kMemory_workarounds[] = {
// gameID, room,script,lvl, object-name, method-name, call,index, workaround
const SciWorkaroundEntry kNewWindow_workarounds[] = {
- { GID_ECOQUEST, -1, 981, 0, "SysWindow", "<noname178>", -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
+ { GID_ECOQUEST, -1, 981, 0, "SysWindow", "open", -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
};
@@ -375,6 +398,12 @@ const SciWorkaroundEntry kStrAt_workarounds[] = {
};
// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+const SciWorkaroundEntry kStrCat_workarounds[] = {
+ { GID_LONGBOW, 210, 210, 0, "giveScroll", "changeState",0x3294, 0, { WORKAROUND_FAKE, 0 } }, // German version, when handing the scroll with the druid hand code to Marion - bug #3048054
+ 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
@@ -391,6 +420,7 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = {
{ GID_LSL6HIRES, 130, 130, 0, "recruitLarryScr", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident
{ GID_PQ3, 877, 998, 0, "View", "delete", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when getting run over on the freeway, the reference is invalid
{ GID_SQ1, 43, 303, 0, "slotGuy", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when leaving ulence flats bar, parameter 1 is not passed - script error
+ { GID_SQ3, 2, 998, 0, "View", "delete", -1, 0, { WORKAROUND_IGNORE, 0 } }, // clicking the mouse button during the intro, after the escape pod gets pulled into the garbage freighter, the reference is invalid - bug #3050856
SCI_WORKAROUNDENTRY_TERMINATOR
};
diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h
index 55a4b8f885..bf1ac3a445 100644
--- a/engines/sci/engine/workarounds.h
+++ b/engines/sci/engine/workarounds.h
@@ -72,6 +72,7 @@ extern const SciWorkaroundEntry opcodeDivWorkarounds[];
extern const SciWorkaroundEntry opcodeDptoaWorkarounds[];
extern const SciWorkaroundEntry opcodeGeWorkarounds[];
extern const SciWorkaroundEntry opcodeLeWorkarounds[];
+extern const SciWorkaroundEntry opcodeUltWorkarounds[];
extern const SciWorkaroundEntry opcodeLaiWorkarounds[];
extern const SciWorkaroundEntry opcodeLsiWorkarounds[];
extern const SciWorkaroundEntry opcodeMulWorkarounds[];
@@ -83,6 +84,7 @@ extern const SciWorkaroundEntry kCelHigh_workarounds[];
extern const SciWorkaroundEntry kCelWide_workarounds[];
extern const SciWorkaroundEntry kDeviceInfo_workarounds[];
extern const SciWorkaroundEntry kDisplay_workarounds[];
+extern const SciWorkaroundEntry kDirLoop_workarounds[];
extern const SciWorkaroundEntry kDisposeScript_workarounds[];
extern const SciWorkaroundEntry kDoSoundFade_workarounds[];
extern const SciWorkaroundEntry kFindKey_workarounds[];
@@ -101,6 +103,7 @@ extern const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[];
extern const SciWorkaroundEntry kSetCursor_workarounds[];
extern const SciWorkaroundEntry kSetPort_workarounds[];
extern const SciWorkaroundEntry kStrAt_workarounds[];
+extern const SciWorkaroundEntry kStrCat_workarounds[];
extern const SciWorkaroundEntry kUnLoad_workarounds[];
extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin);