aboutsummaryrefslogtreecommitdiff
path: root/engines/sci
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci')
-rw-r--r--engines/sci/console.cpp443
-rw-r--r--engines/sci/console.h6
-rw-r--r--engines/sci/detection.cpp49
-rw-r--r--engines/sci/detection_tables.h73
-rw-r--r--engines/sci/engine/file.cpp3
-rw-r--r--engines/sci/engine/kernel.cpp45
-rw-r--r--engines/sci/engine/kernel.h6
-rw-r--r--engines/sci/engine/kernel_tables.h4
-rw-r--r--engines/sci/engine/kevent.cpp13
-rw-r--r--engines/sci/engine/kfile.cpp20
-rw-r--r--engines/sci/engine/kgraphics.cpp43
-rw-r--r--engines/sci/engine/kmovement.cpp27
-rw-r--r--engines/sci/engine/kparse.cpp3
-rw-r--r--engines/sci/engine/kstring.cpp10
-rw-r--r--engines/sci/engine/kvideo.cpp2
-rw-r--r--engines/sci/engine/savegame.cpp86
-rw-r--r--engines/sci/engine/savegame.h8
-rw-r--r--engines/sci/engine/script.cpp403
-rw-r--r--engines/sci/engine/script.h36
-rw-r--r--engines/sci/engine/script_patches.cpp804
-rw-r--r--engines/sci/engine/script_patches.h11
-rw-r--r--engines/sci/engine/state.cpp119
-rw-r--r--engines/sci/engine/state.h4
-rw-r--r--engines/sci/engine/vm.cpp18
-rw-r--r--engines/sci/engine/workarounds.cpp954
-rw-r--r--engines/sci/engine/workarounds.h2
-rw-r--r--engines/sci/event.cpp2
-rw-r--r--engines/sci/graphics/controls16.cpp12
-rw-r--r--engines/sci/graphics/controls16.h6
-rw-r--r--engines/sci/graphics/cursor.cpp8
-rw-r--r--engines/sci/graphics/font.cpp8
-rw-r--r--engines/sci/graphics/font.h4
-rw-r--r--engines/sci/graphics/frameout.cpp10
-rw-r--r--engines/sci/graphics/paint16.cpp25
-rw-r--r--engines/sci/graphics/paint16.h2
-rw-r--r--engines/sci/graphics/paint32.cpp2
-rw-r--r--engines/sci/graphics/palette.cpp87
-rw-r--r--engines/sci/graphics/palette.h6
-rw-r--r--engines/sci/graphics/picture.cpp88
-rw-r--r--engines/sci/graphics/portrait.cpp105
-rw-r--r--engines/sci/graphics/portrait.h2
-rw-r--r--engines/sci/graphics/ports.cpp16
-rw-r--r--engines/sci/graphics/screen.cpp399
-rw-r--r--engines/sci/graphics/screen.h95
-rw-r--r--engines/sci/graphics/text16.cpp225
-rw-r--r--engines/sci/graphics/text16.h15
-rw-r--r--engines/sci/graphics/transitions.cpp6
-rw-r--r--engines/sci/graphics/view.cpp3
-rw-r--r--engines/sci/module.mk4
-rw-r--r--engines/sci/parser/vocabulary.cpp82
-rw-r--r--engines/sci/parser/vocabulary.h12
-rw-r--r--engines/sci/resource.cpp55
-rw-r--r--engines/sci/resource.h18
-rw-r--r--engines/sci/resource_audio.cpp6
-rw-r--r--engines/sci/sci.cpp23
-rw-r--r--engines/sci/sci.h16
-rw-r--r--engines/sci/sound/midiparser_sci.cpp19
-rw-r--r--engines/sci/sound/music.cpp114
-rw-r--r--engines/sci/sound/music.h16
-rw-r--r--engines/sci/sound/soundcmd.cpp46
-rw-r--r--engines/sci/sound/soundcmd.h2
61 files changed, 3586 insertions, 1145 deletions
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 565e9752c3..1e95393e4d 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -209,6 +209,11 @@ Console::Console(SciEngine *engine) : GUI::Debugger(),
registerCmd("bpe", WRAP_METHOD(Console, cmdBreakpointFunction)); // alias
// VM
registerCmd("script_steps", WRAP_METHOD(Console, cmdScriptSteps));
+ registerCmd("script_objects", WRAP_METHOD(Console, cmdScriptObjects));
+ registerCmd("scro", WRAP_METHOD(Console, cmdScriptObjects));
+ registerCmd("script_strings", WRAP_METHOD(Console, cmdScriptStrings));
+ registerCmd("scrs", WRAP_METHOD(Console, cmdScriptStrings));
+ registerCmd("script_said", WRAP_METHOD(Console, cmdScriptSaid));
registerCmd("vm_varlist", WRAP_METHOD(Console, cmdVMVarlist));
registerCmd("vmvarlist", WRAP_METHOD(Console, cmdVMVarlist)); // alias
registerCmd("vl", WRAP_METHOD(Console, cmdVMVarlist)); // alias
@@ -485,6 +490,7 @@ bool Console::cmdGetVersion(int argc, const char **argv) {
#endif
debugPrintf("View type: %s\n", viewTypeDesc[g_sci->getResMan()->getViewType()]);
debugPrintf("Uses palette merging: %s\n", g_sci->_gfxPalette->isMerging() ? "yes" : "no");
+ debugPrintf("Uses 16 bit color matching: %s\n", g_sci->_gfxPalette->isUsing16bitColorMatch() ? "yes" : "no");
debugPrintf("Resource volume version: %s\n", g_sci->getResMan()->getVolVersionDesc());
debugPrintf("Resource map version: %s\n", g_sci->getResMan()->getMapVersionDesc());
debugPrintf("Contains selector vocabulary (vocab.997): %s\n", hasVocab997 ? "yes" : "no");
@@ -659,11 +665,34 @@ bool Console::cmdRegisters(int argc, const char **argv) {
return true;
}
-bool Console::cmdDiskDump(int argc, const char **argv) {
- int resNumFrom = 0;
- int resNumTo = 0;
- int resNumCur = 0;
+bool Console::parseResourceNumber36(const char *userParameter, uint16 &resourceNumber, uint32 &resourceTuple) {
+ int userParameterLen = strlen(userParameter);
+ if (userParameterLen != 10) {
+ debugPrintf("Audio36/Sync36 resource numbers must be specified as RRRNNVVCCS\n");
+ debugPrintf("where RRR is the resource number/map\n");
+ debugPrintf(" NN is the noun\n");
+ debugPrintf(" VV is the verb\n");
+ debugPrintf(" CC is the cond\n");
+ debugPrintf(" S is the seq\n");
+ return false;
+ }
+
+ // input: RRRNNVVCCS
+ resourceNumber = strtol(Common::String(userParameter, 3).c_str(), 0, 36);
+ uint16 noun = strtol(Common::String(userParameter + 3, 2).c_str(), 0, 36);
+ uint16 verb = strtol(Common::String(userParameter + 5, 2).c_str(), 0, 36);
+ uint16 cond = strtol(Common::String(userParameter + 7, 2).c_str(), 0, 36);
+ uint16 seq = strtol(Common::String(userParameter + 9, 1).c_str(), 0, 36);
+ resourceTuple = ((noun & 0xff) << 24) | ((verb & 0xff) << 16) | ((cond & 0xff) << 8) | (seq & 0xff);
+ return true;
+}
+
+bool Console::cmdDiskDump(int argc, const char **argv) {
+ bool resourceAll = false;
+ uint16 resourceNumber = 0;
+ uint32 resourceTuple = 0;
+
if (argc != 3) {
debugPrintf("Dumps the specified resource to disk as a patch file\n");
debugPrintf("Usage: %s <resource type> <resource number>\n", argv[0]);
@@ -671,43 +700,91 @@ bool Console::cmdDiskDump(int argc, const char **argv) {
cmdResourceTypes(argc, argv);
return true;
}
-
+
+ ResourceType resourceType = parseResourceType(argv[1]);
+ if (resourceType == kResourceTypeInvalid) {
+ debugPrintf("Resource type '%s' is not valid\n", argv[1]);
+ return true;
+ }
+
if (strcmp(argv[2], "*") == 0) {
- resNumFrom = 0;
- resNumTo = 65535;
+ resourceAll = true;
} else {
- resNumFrom = atoi(argv[2]);
- resNumTo = resNumFrom;
+ switch (resourceType) {
+ case kResourceTypeAudio36:
+ case kResourceTypeSync36:
+ if (!parseResourceNumber36(argv[2], resourceNumber, resourceTuple)) {
+ return true;
+ }
+ break;
+ default:
+ resourceNumber = atoi(argv[2]);
+ break;
+ }
}
- ResourceType res = parseResourceType(argv[1]);
-
- if (res == kResourceTypeInvalid)
+ if (resourceType == kResourceTypeInvalid) {
debugPrintf("Resource type '%s' is not valid\n", argv[1]);
- else {
- for (resNumCur = resNumFrom; resNumCur <= resNumTo; resNumCur++) {
- Resource *resource = _engine->getResMan()->findResource(ResourceId(res, resNumCur), 0);
- if (resource) {
- char outFileName[50];
- sprintf(outFileName, "%s.%03d", getResourceTypeName(res), resNumCur);
- Common::DumpFile *outFile = new Common::DumpFile();
- outFile->open(outFileName);
- resource->writeToStream(outFile);
- outFile->finalize();
- outFile->close();
- delete outFile;
- debugPrintf("Resource %s.%03d (located in %s) has been dumped to disk\n", argv[1], resNumCur, resource->getResourceLocation().c_str());
- } else {
- if (resNumFrom == resNumTo) {
- debugPrintf("Resource %s.%03d not found\n", argv[1], resNumCur);
- }
- }
+ return true;
+ }
+
+ if (resourceAll) {
+ // "*" used, dump everything of that type
+ Common::List<ResourceId> resources = _engine->getResMan()->listResources(resourceType, -1);
+ Common::sort(resources.begin(), resources.end());
+
+ Common::List<ResourceId>::iterator itr;
+ for (itr = resources.begin(); itr != resources.end(); ++itr) {
+ resourceNumber = itr->getNumber();
+ resourceTuple = itr->getTuple();
+ cmdDiskDumpWorker(resourceType, resourceNumber, resourceTuple);
}
+ } else {
+ // id was given, dump only this resource
+ cmdDiskDumpWorker(resourceType, resourceNumber, resourceTuple);
}
return true;
}
+void Console::cmdDiskDumpWorker(ResourceType resourceType, int resourceNumber, uint32 resourceTuple) {
+ const char *resourceTypeName = getResourceTypeName(resourceType);
+ ResourceId resourceId;
+ Resource *resource = NULL;
+ char outFileName[50];
+
+ switch (resourceType) {
+ case kResourceTypeAudio36:
+ case kResourceTypeSync36: {
+ resourceId = ResourceId(resourceType, resourceNumber, resourceTuple);
+ resource = _engine->getResMan()->findResource(resourceId, 0);
+ sprintf(outFileName, "%s", resourceId.toPatchNameBase36().c_str());
+ // patch filename is: [type:1 char] [map:3 chars] [noun:2 chars] [verb:2 chars] "." [cond: 2 chars] [seq:1 char]
+ // e.g. "@5EG0000.014"
+ break;
+ }
+ default:
+ resourceId = ResourceId(resourceType, resourceNumber);
+ resource = _engine->getResMan()->findResource(resourceId, 0);
+ sprintf(outFileName, "%s.%03d", resourceTypeName, resourceNumber);
+ // patch filename is: [resourcetype].[resourcenumber]
+ // e.g. "Script.0"
+ break;
+ }
+
+ if (resource) {
+ Common::DumpFile *outFile = new Common::DumpFile();
+ outFile->open(outFileName);
+ resource->writeToStream(outFile);
+ outFile->finalize();
+ outFile->close();
+ delete outFile;
+ debugPrintf("Resource %s (located in %s) has been dumped to disk\n", outFileName, resource->getResourceLocation().c_str());
+ } else {
+ debugPrintf("Resource %s not found\n", outFileName);
+ }
+}
+
bool Console::cmdHexDump(int argc, const char **argv) {
if (argc != 3) {
debugPrintf("Dumps the specified resource to standard output\n");
@@ -747,6 +824,77 @@ bool Console::cmdResourceId(int argc, const char **argv) {
return true;
}
+bool Console::cmdList(int argc, const char **argv) {
+ int selectedMapNumber = -1;
+ Common::List<ResourceId> resources;
+ Common::List<ResourceId>::iterator itr;
+ int displayCount = 0;
+ int currentMap = -1;
+
+ if (argc < 2) {
+ debugPrintf("Lists all the resources of a given type\n");
+ cmdResourceTypes(argc, argv);
+ return true;
+ }
+
+ ResourceType resourceType = parseResourceType(argv[1]);
+ if (resourceType == kResourceTypeInvalid) {
+ debugPrintf("Unknown resource type: '%s'\n", argv[1]);
+ return true;
+ }
+
+ switch (resourceType) {
+ case kResourceTypeAudio36:
+ case kResourceTypeSync36:
+ if (argc != 3) {
+ debugPrintf("Please specify map number (-1: all maps)\n");
+ return true;
+ }
+ selectedMapNumber = atoi(argv[2]);
+ resources = _engine->getResMan()->listResources(resourceType, selectedMapNumber);
+ Common::sort(resources.begin(), resources.end());
+
+ for (itr = resources.begin(); itr != resources.end(); ++itr) {
+ const uint16 map = itr->getNumber();
+ const uint32 resourceTuple = itr->getTuple();
+ const uint16 noun = (resourceTuple >> 24) & 0xff;
+ const uint16 verb = (resourceTuple >> 16) & 0xff;
+ const uint16 cond = (resourceTuple >> 8) & 0xff;
+ const uint16 seq = resourceTuple & 0xff;
+
+ if (currentMap != map) {
+ if (displayCount % 3)
+ debugPrintf("\n");
+ debugPrintf("Map %04x (%i):\n", map, map);
+ currentMap = map;
+ displayCount = 0;
+ }
+
+ if (displayCount % 3 == 0)
+ debugPrintf(" ");
+
+ debugPrintf("%02x %02x %02x %02x (%3i %3i %3i %3i) ", noun, verb, cond, seq, noun, verb, cond, seq);
+
+ if (++displayCount % 3 == 0)
+ debugPrintf("\n");
+ }
+ break;
+ default:
+ resources = _engine->getResMan()->listResources(resourceType);
+ Common::sort(resources.begin(), resources.end());
+
+ for (itr = resources.begin(); itr != resources.end(); ++itr) {
+ debugPrintf("%8i", itr->getNumber());
+ if (++displayCount % 10 == 0)
+ debugPrintf("\n");
+ }
+ break;
+ }
+
+ debugPrintf("\n");
+ return true;
+}
+
bool Console::cmdDissectScript(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Examines a script\n");
@@ -1123,52 +1271,6 @@ bool Console::cmdMapInstrument(int argc, const char **argv) {
return true;
}
-bool Console::cmdList(int argc, const char **argv) {
- if (argc < 2) {
- debugPrintf("Lists all the resources of a given type\n");
- cmdResourceTypes(argc, argv);
- return true;
- }
-
-
- ResourceType res = parseResourceType(argv[1]);
- if (res == kResourceTypeInvalid)
- debugPrintf("Unknown resource type: '%s'\n", argv[1]);
- else {
- int number = -1;
-
- if ((res == kResourceTypeAudio36) || (res == kResourceTypeSync36)) {
- if (argc != 3) {
- debugPrintf("Please specify map number (-1: all maps)\n");
- return true;
- }
- number = atoi(argv[2]);
- }
-
- Common::List<ResourceId> resources = _engine->getResMan()->listResources(res, number);
- Common::sort(resources.begin(), resources.end());
-
- int cnt = 0;
- Common::List<ResourceId>::iterator itr;
- for (itr = resources.begin(); itr != resources.end(); ++itr) {
- if (number == -1) {
- debugPrintf("%8i", itr->getNumber());
- if (++cnt % 10 == 0)
- debugPrintf("\n");
- } else if (number == (int)itr->getNumber()) {
- const uint32 tuple = itr->getTuple();
- debugPrintf("(%3i, %3i, %3i, %3i) ", (tuple >> 24) & 0xff, (tuple >> 16) & 0xff,
- (tuple >> 8) & 0xff, tuple & 0xff);
- if (++cnt % 4 == 0)
- debugPrintf("\n");
- }
- }
- debugPrintf("\n");
- }
-
- return true;
-}
-
bool Console::cmdSaveGame(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Saves the current game state to the hard disk\n");
@@ -2171,6 +2273,7 @@ bool Console::cmdStartSound(int argc, const char **argv) {
return true;
}
+ // TODO: Maybe also add a playBed option.
g_sci->_soundCmd->startNewSound(number);
return cmdExit(0, 0);
}
@@ -2197,9 +2300,10 @@ bool Console::cmdToggleSound(int argc, const char **argv) {
Common::String newState = argv[2];
newState.toLowercase();
- if (newState == "play")
- g_sci->_soundCmd->processPlaySound(id);
- else if (newState == "stop")
+ if (newState == "play") {
+ // Maybe also have a 'playbed' option. (Second argument to processPlaySound.)
+ g_sci->_soundCmd->processPlaySound(id, false);
+ } else if (newState == "stop")
g_sci->_soundCmd->processStopSound(id, false);
else
debugPrintf("New state can either be 'play' or 'stop'");
@@ -2729,6 +2833,186 @@ bool Console::cmdScriptSteps(int argc, const char **argv) {
return true;
}
+bool Console::cmdScriptObjects(int argc, const char **argv) {
+ int curScriptNr = -1;
+
+ if (argc < 2) {
+ debugPrintf("Shows all objects inside a specified script.\n");
+ debugPrintf("Usage: %s <script number>\n", argv[0]);
+ debugPrintf("Example: %s 999\n", argv[0]);
+ debugPrintf("<script number> may be * to show objects inside all loaded scripts\n");
+ return true;
+ }
+
+ if (strcmp(argv[1], "*") == 0) {
+ // get said-strings of all currently loaded scripts
+ curScriptNr = -1;
+ } else {
+ curScriptNr = atoi(argv[1]);
+ }
+
+ printOffsets(curScriptNr, SCI_SCR_OFFSET_TYPE_OBJECT);
+ return true;
+}
+
+bool Console::cmdScriptStrings(int argc, const char **argv) {
+ int curScriptNr = -1;
+
+ if (argc < 2) {
+ debugPrintf("Shows all strings inside a specified script.\n");
+ debugPrintf("Usage: %s <script number>\n", argv[0]);
+ debugPrintf("Example: %s 999\n", argv[0]);
+ debugPrintf("<script number> may be * to show strings inside all loaded scripts\n");
+ return true;
+ }
+
+ if (strcmp(argv[1], "*") == 0) {
+ // get strings of all currently loaded scripts
+ curScriptNr = -1;
+ } else {
+ curScriptNr = atoi(argv[1]);
+ }
+
+ printOffsets(curScriptNr, SCI_SCR_OFFSET_TYPE_STRING);
+ return true;
+}
+
+bool Console::cmdScriptSaid(int argc, const char **argv) {
+ int curScriptNr = -1;
+
+ if (argc < 2) {
+ debugPrintf("Shows all said-strings inside a specified script.\n");
+ debugPrintf("Usage: %s <script number>\n", argv[0]);
+ debugPrintf("Example: %s 999\n", argv[0]);
+ debugPrintf("<script number> may be * to show said-strings inside all loaded scripts\n");
+ return true;
+ }
+
+ if (strcmp(argv[1], "*") == 0) {
+ // get said-strings of all currently loaded scripts
+ curScriptNr = -1;
+ } else {
+ curScriptNr = atoi(argv[1]);
+ }
+
+ printOffsets(curScriptNr, SCI_SCR_OFFSET_TYPE_SAID);
+ return true;
+}
+
+void Console::printOffsets(int scriptNr, uint16 showType) {
+ SegManager *segMan = _engine->_gamestate->_segMan;
+ Vocabulary *vocab = _engine->_vocabulary;
+ SegmentId curSegmentNr;
+ Common::List<SegmentId> segmentNrList;
+
+ SegmentType curSegmentType = SEG_TYPE_INVALID;
+ SegmentObj *curSegmentObj = NULL;
+ Script *curScriptObj = NULL;
+ const byte *curScriptData = NULL;
+
+ segmentNrList.clear();
+ if (scriptNr < 0) {
+ // get offsets of all currently loaded scripts
+ for (curSegmentNr = 0; curSegmentNr < segMan->_heap.size(); curSegmentNr++) {
+ curSegmentObj = segMan->_heap[curSegmentNr];
+ if (curSegmentObj && curSegmentObj->getType() == SEG_TYPE_SCRIPT) {
+ segmentNrList.push_back(curSegmentNr);
+ }
+ }
+
+ } else {
+ curSegmentNr = segMan->getScriptSegment(scriptNr);
+ if (!curSegmentNr) {
+ debugPrintf("Script %d is currently not loaded/available\n", scriptNr);
+ return;
+ }
+ segmentNrList.push_back(curSegmentNr);
+ }
+
+ const offsetLookupArrayType *scriptOffsetLookupArray;
+ offsetLookupArrayType::const_iterator arrayIterator;
+ int showTypeCount = 0;
+
+ reg_t objectPos;
+ const char *objectNamePtr = NULL;
+ const byte *stringPtr = NULL;
+ const byte *saidPtr = NULL;
+
+ Common::List<SegmentId>::iterator it;
+ const Common::List<SegmentId>::iterator end = segmentNrList.end();
+
+ for (it = segmentNrList.begin(); it != end; it++) {
+ curSegmentNr = *it;
+ // get object of this segment
+ curSegmentObj = segMan->getSegmentObj(curSegmentNr);
+ if (!curSegmentObj)
+ continue;
+
+ curSegmentType = curSegmentObj->getType();
+ if (curSegmentType != SEG_TYPE_SCRIPT) // safety check
+ continue;
+
+ curScriptObj = (Script *)curSegmentObj;
+ debugPrintf("=== SCRIPT %d inside Segment %d ===\n", curScriptObj->getScriptNumber(), curSegmentNr);
+ debugN("=== SCRIPT %d inside Segment %d ===\n", curScriptObj->getScriptNumber(), curSegmentNr);
+
+ // now print the list
+ scriptOffsetLookupArray = curScriptObj->getOffsetArray();
+ curScriptData = curScriptObj->getBuf();
+ showTypeCount = 0;
+
+ for (arrayIterator = scriptOffsetLookupArray->begin(); arrayIterator != scriptOffsetLookupArray->end(); arrayIterator++) {
+ if (arrayIterator->type == showType) {
+ switch (showType) {
+ case SCI_SCR_OFFSET_TYPE_OBJECT:
+ objectPos = make_reg(curSegmentNr, arrayIterator->offset);
+ objectNamePtr = segMan->getObjectName(objectPos);
+ debugPrintf(" %03d:%04x: %s\n", arrayIterator->id, arrayIterator->offset, objectNamePtr);
+ debugN(" %03d:%04x: %s\n", arrayIterator->id, arrayIterator->offset, objectNamePtr);
+ break;
+ case SCI_SCR_OFFSET_TYPE_STRING:
+ stringPtr = curScriptData + arrayIterator->offset;
+ debugPrintf(" %03d:%04x: '%s' (size %d)\n", arrayIterator->id, arrayIterator->offset, stringPtr, arrayIterator->stringSize);
+ debugN(" %03d:%04x: '%s' (size %d)\n", arrayIterator->id, arrayIterator->offset, stringPtr, arrayIterator->stringSize);
+ break;
+ case SCI_SCR_OFFSET_TYPE_SAID:
+ saidPtr = curScriptData + arrayIterator->offset;
+ debugPrintf(" %03d:%04x:\n", arrayIterator->id, arrayIterator->offset);
+ debugN(" %03d:%04x: ", arrayIterator->id, arrayIterator->offset);
+ vocab->debugDecipherSaidBlock(saidPtr);
+ debugN("\n");
+ break;
+ default:
+ break;
+ }
+ showTypeCount++;
+ }
+ }
+
+ if (showTypeCount == 0) {
+ switch (showType) {
+ case SCI_SCR_OFFSET_TYPE_OBJECT:
+ debugPrintf(" no objects\n");
+ debugN(" no objects\n");
+ break;
+ case SCI_SCR_OFFSET_TYPE_STRING:
+ debugPrintf(" no strings\n");
+ debugN(" no strings\n");
+ break;
+ case SCI_SCR_OFFSET_TYPE_SAID:
+ debugPrintf(" no said-strings\n");
+ debugN(" no said-strings\n");
+ break;
+ default:
+ break;
+ }
+ }
+
+ debugPrintf("\n");
+ debugN("\n");
+ }
+}
+
bool Console::cmdBacktrace(int argc, const char **argv) {
debugPrintf("Call stack (current base: 0x%x):\n", _engine->_gamestate->executionStackBase);
Common::List<ExecStack>::const_iterator iter;
@@ -3208,6 +3492,7 @@ bool Console::cmdSend(int argc, const char **argv) {
// We call run_engine explictly so we can restore the value of r_acc
// after execution.
run_vm(_engine->_gamestate);
+ _engine->_gamestate->xs = old_xstack;
}
diff --git a/engines/sci/console.h b/engines/sci/console.h
index c8e99f78f7..8b10912fbe 100644
--- a/engines/sci/console.h
+++ b/engines/sci/console.h
@@ -68,6 +68,7 @@ private:
bool cmdSaid(int argc, const char **argv);
// Resources
bool cmdDiskDump(int argc, const char **argv);
+ void cmdDiskDumpWorker(ResourceType resourceType, int resourceNumber, uint32 resourceTuple);
bool cmdHexDump(int argc, const char **argv);
bool cmdResourceId(int argc, const char **argv);
bool cmdResourceInfo(int argc, const char **argv);
@@ -146,6 +147,9 @@ private:
bool cmdBreakpointFunction(int argc, const char **argv);
// VM
bool cmdScriptSteps(int argc, const char **argv);
+ bool cmdScriptObjects(int argc, const char **argv);
+ bool cmdScriptStrings(int argc, const char **argv);
+ bool cmdScriptSaid(int argc, const char **argv);
bool cmdVMVarlist(int argc, const char **argv);
bool cmdVMVars(int argc, const char **argv);
bool cmdStack(int argc, const char **argv);
@@ -157,6 +161,7 @@ private:
bool cmdViewAccumulatorObject(int argc, const char **argv);
bool parseInteger(const char *argument, int &result);
+ bool parseResourceNumber36(const char *userParameter, uint16 &resourceNumber, uint32 &resourceTuple);
void printBasicVarInfo(reg_t variable);
@@ -164,6 +169,7 @@ private:
void printList(List *list);
int printNode(reg_t addr);
void hexDumpReg(const reg_t *data, int len, int regsPerLine = 4, int startOffset = 0, bool isArray = false);
+ void printOffsets(int scriptNr, uint16 showType);
private:
/**
diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index 4f28738508..9a41127f6d 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -563,31 +563,28 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
// If these files aren't found, it can't be SCI
- if (!foundResMap && !foundRes000) {
+ if (!foundResMap && !foundRes000)
return 0;
- }
ResourceManager resMan;
- resMan.addAppropriateSources(fslist);
- resMan.init(true);
+ resMan.addAppropriateSourcesForDetection(fslist);
+ resMan.initForDetection();
// TODO: Add error handling.
#ifndef ENABLE_SCI32
// Is SCI32 compiled in? If not, and this is a SCI32 game,
// stop here
- if (getSciVersion() >= SCI_VERSION_2) {
- return (const ADGameDescription *)&s_fallbackDesc;
- }
+ if (getSciVersionForDetection() >= SCI_VERSION_2)
+ return 0;
#endif
ViewType gameViews = resMan.getViewType();
// Have we identified the game views? If not, stop here
- // Can't be SCI (or unsupported SCI views). Pinball Creep by sierra also uses resource.map/resource.000 files
- // but doesnt share sci format at all, if we dont return 0 here we will detect this game as SCI
- if (gameViews == kViewUnknown) {
+ // Can't be SCI (or unsupported SCI views). Pinball Creep by Sierra also uses resource.map/resource.000 files
+ // but doesn't share SCI format at all
+ if (gameViews == kViewUnknown)
return 0;
- }
// Set the platform to Amiga if the game is using Amiga views
if (gameViews == kViewAmiga)
@@ -597,9 +594,8 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
Common::String sierraGameId = resMan.findSierraGameId();
// If we don't have a game id, the game is not SCI
- if (sierraGameId.empty()) {
+ if (sierraGameId.empty())
return 0;
- }
Common::String gameId = convertSierraGameId(sierraGameId, &s_fallbackDesc.flags, resMan);
strncpy(s_fallbackGameIdBuf, gameId.c_str(), sizeof(s_fallbackGameIdBuf) - 1);
@@ -795,22 +791,9 @@ void SciMetaEngine::removeSaveState(const char *target, int slot) const {
}
Common::Error SciEngine::loadGameState(int slot) {
- Common::String fileName = Common::String::format("%s.%03d", _targetName.c_str(), slot);
- Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
- Common::SeekableReadStream *in = saveFileMan->openForLoading(fileName);
-
- if (in) {
- // found a savegame file
- gamestate_restore(_gamestate, in);
- delete in;
- }
-
- if (_gamestate->r_acc != make_reg(0, 1)) {
- return Common::kNoError;
- } else {
- warning("Restoring gamestate '%s' failed", fileName.c_str());
- return Common::kUnknownError;
- }
+ _gamestate->_delayedRestoreGameId = slot;
+ _gamestate->_delayedRestoreGame = true;
+ return Common::kNoError;
}
Common::Error SciEngine::saveGameState(int slot, const Common::String &desc) {
@@ -838,16 +821,12 @@ Common::Error SciEngine::saveGameState(int slot, const Common::String &desc) {
return Common::kNoError;
}
-// Before enabling the load option in the ScummVM menu, the main game loop must
-// have run at least once. When the game loop runs, kGameIsRestarting is invoked,
-// thus the speed throttler is initialized. Hopefully fixes bug #3565505.
-
bool SciEngine::canLoadGameStateCurrently() {
- return !_gamestate->executionStackBase && (_gamestate->_throttleLastTime > 0 || _gamestate->_throttleTrigger);
+ return !_gamestate->executionStackBase;
}
bool SciEngine::canSaveGameStateCurrently() {
- return !_gamestate->executionStackBase && (_gamestate->_throttleLastTime > 0 || _gamestate->_throttleTrigger);
+ return !_gamestate->executionStackBase;
}
} // End of namespace Sci
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index 0fddaa51b4..7cadcfc27e 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -904,16 +904,14 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
-#if 0 // TODO: unknown if these files are corrupt
- // Hoyle 1 - English Amiga (from www.back2roots.org)
- // SCI interpreter version 0.000.519 - FIXME: some have 0.000.530, others x.yyy.zzz
+ // Hoyle 1 - English Amiga (from www.back2roots.org - verified by waltervn in bug report #6870)
+ // Game version 1.000.139, SCI interpreter version x.yyy.zzz
{"hoyle1", "", {
{"resource.map", 0, "2a72b1aba65fa6e339370eb86d8601d1", 5166},
{"resource.001", 0, "e0dd44069a62a463fd124974b915f10d", 218755},
{"resource.002", 0, "e0dd44069a62a463fd124974b915f10d", 439502},
AD_LISTEND},
Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
-#endif
// Hoyle 2 - English DOS
// SCI interpreter version 0.000.572
@@ -1552,6 +1550,17 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // King's Quest 6 - English DOS Playable CD "Sneak Peaks" Demo (first island fully playable)
+ // (supplied by KQ5 G5 in bug report #6824)
+ // Executable scanning reports "1.cfs.158 Not a release version", VERSION file reports "1.000.000"
+ // SCI interpreter version ???
+ {"kq6", "Demo/CD", {
+ {"resource.000", 0, "233394a5f33b475ae5975e7e9a420865", 8345598},
+ {"resource.map", 0, "eb9e177281b7cde188dc0d83194cd365", 8960},
+ {"resource.msg", 0, "3cf5de44de36191f109d425b8450efc8", 259510},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_DEMO, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// King's Quest 6 - English DOS Floppy
// SCI interpreter version 1.001.054
{"kq6", "", {
@@ -2069,6 +2078,20 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::DE_DEU, Common::kPlatformDOS, ADGF_ADDENGLISH, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Larry 3 - German DOS (German+English, 5 1/4" floppies)
+ // SCI interpreter version S.old.114 (executable), VERSION is "1.056"
+ {"lsl3", "", {
+ {"resource.map", 0, "2468da5d664bb6ca3df866074a05e43c", 8910},
+ {"resource.001", 0, "3827a9b17b926e12dcc336860f50612a", 163326},
+ {"resource.002", 0, "3827a9b17b926e12dcc336860f50612a", 312436},
+ {"resource.003", 0, "3827a9b17b926e12dcc336860f50612a", 347307},
+ {"resource.004", 0, "3827a9b17b926e12dcc336860f50612a", 332369},
+ {"resource.005", 0, "3827a9b17b926e12dcc336860f50612a", 347654},
+ {"resource.006", 0, "3827a9b17b926e12dcc336860f50612a", 326011},
+ {"resource.007", 0, "3827a9b17b926e12dcc336860f50612a", 309553},
+ AD_LISTEND},
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_ADDENGLISH, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Larry 3 - French DOS (provided by richiefs in bug report #2670691, also includes english language)
// Executable scanning reports "S.old.123"
// SCI interpreter version 0.000.572 (just a guess)
@@ -2603,6 +2626,29 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Phantasmagoria - German DOS/Windows
+ // Windows executable scanning reports "unknown" - "Sep 19 1995 09:39:48"
+ // DOS executable scanning reports "unknown" - "Sep 19 1995 16:18:34"
+ // VERSION file reports "1.100.000"
+ // Supplied by AReim1982
+ {"phantasmagoria", "", {
+ {"resmap.001", 0, "d5048f972d2e1abd5f6b6a3ea8a466b0", 11524},
+ {"ressci.001", 0, "3aae6559aa1df273bc542d5ac6330d75", 71063082},
+ {"resmap.002", 0, "ae0105261e04826324daf7dd2d534465", 12064},
+ {"ressci.002", 0, "3aae6559aa1df273bc542d5ac6330d75", 80283368},
+ {"resmap.003", 0, "50786a4f54c6432ec31b52be90b3a8ba", 12340},
+ {"ressci.003", 0, "3aae6559aa1df273bc542d5ac6330d75", 82360370},
+ {"resmap.004", 0, "4cd3bbff8b81bad85db52c0c8407bd81", 12562},
+ {"ressci.004", 0, "3aae6559aa1df273bc542d5ac6330d75", 84453560},
+ {"resmap.005", 0, "779bd12802da6cfe54ce482140824a46", 12616},
+ {"ressci.005", 0, "3aae6559aa1df273bc542d5ac6330d75", 85113663},
+ {"resmap.006", 0, "2299f97876493cc29b6a48e1cfe9619d", 12538},
+ {"ressci.006", 0, "3aae6559aa1df273bc542d5ac6330d75", 87602346},
+ {"resmap.007", 0, "06309b8043aecb85bd507b15d16cb544", 7984},
+ //{"ressci.007", 0, "3aae6559aa1df273bc542d5ac6330d75", 26898681},
+ AD_LISTEND},
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Phantasmagoria - French DOS
// Supplied by Kervala in bug #6574
{"phantasmagoria", "", {
@@ -2674,6 +2720,25 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Phantasmagoria 2 - German DOS/Windows
+ // Windows executable scanning reports "unknown" - "Dec 07 1996 15:42:02"
+ // DOS executable scanning reports "unknown" - "Dec 07 1996 08:35:12"
+ // VERSION file reports "000.1.0vu" (HEX: 30 30 30 2E 31 00 2E 30 76 FA 0D 0A)
+ // Supplied by AReim1982
+ {"phantasmagoria2", "", {
+ {"resmap.001", 0, "d62f48ff8bddb39503b97e33439482c9", 1114},
+ {"ressci.001", 0, "4ebc2b8455c74ad205ae592eec27313a", 24590716},
+ {"resmap.002", 0, "642a1f85ad8a4ce1d3850b10ad082200", 1138},
+ {"ressci.002", 0, "4ebc2b8455c74ad205ae592eec27313a", 34681672},
+ {"resmap.003", 0, "e08316864ef77735bb7f8f208110c43b", 1174},
+ {"ressci.003", 0, "4ebc2b8455c74ad205ae592eec27313a", 38930933},
+ {"resmap.004", 0, "875cf07df77fbaa1518a06ffed616c5f", 1300},
+ {"ressci.004", 0, "4ebc2b8455c74ad205ae592eec27313a", 42750325},
+ {"resmap.005", 0, "2fc48a4a5a73b726994f189da51a8b2a", 1954},
+ {"ressci.005", 0, "e94005890d22dd3b7f605a2a7c025803", 68232146},
+ AD_LISTEND},
+ Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Phantasmagoria 2 - English DOS (GOG version) - ressci.* merged in ressci.000
// Executable scanning reports "3.000.000" - "Dec 07 1996 09:29:03"
// VERSION file reports "001.0.06"
diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index 2ba7d15ac0..623caec856 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -252,6 +252,9 @@ void DirSeeker::addAsVirtualFiles(Common::String title, Common::String fileMask)
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
Common::StringArray foundFiles = saveFileMan->listSavefiles(fileMask);
if (!foundFiles.empty()) {
+ // Sort all filenames alphabetically
+ Common::sort(foundFiles.begin(), foundFiles.end());
+
_files.push_back(title);
_virtualFiles.push_back("");
Common::StringArray::iterator it;
diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp
index 2b16bb3d99..bfb7bfcd08 100644
--- a/engines/sci/engine/kernel.cpp
+++ b/engines/sci/engine/kernel.cpp
@@ -432,57 +432,66 @@ static const SignatureDebugType signatureDebugTypeList[] = {
{ 0, NULL }
};
-static void kernelSignatureDebugType(const uint16 type) {
+static void kernelSignatureDebugType(Common::String &signatureDetailsStr, const uint16 type) {
bool firstPrint = true;
const SignatureDebugType *list = signatureDebugTypeList;
while (list->typeCheck) {
if (type & list->typeCheck) {
if (!firstPrint)
- debugN(", ");
- debugN("%s", list->text);
+// debugN(", ");
+ signatureDetailsStr += ", ";
+// debugN("%s", list->text);
+// signatureDetailsStr += signatureDetailsStr.format("%s", list->text);
+ signatureDetailsStr += list->text;
firstPrint = false;
}
list++;
}
}
-// Shows kernel call signature and current arguments for debugging purposes
-void Kernel::signatureDebug(const uint16 *sig, int argc, const reg_t *argv) {
+// Create string, that holds the details of a kernel call signature and current arguments
+// For debugging purposes
+void Kernel::signatureDebug(Common::String &signatureDetailsStr, const uint16 *sig, int argc, const reg_t *argv) {
int argnr = 0;
+
+ // add ERROR: to debug output
+ debugN("ERROR:");
+
while (*sig || argc) {
- debugN("parameter %d: ", argnr++);
+ // add leading spaces for additional parameters
+ signatureDetailsStr += signatureDetailsStr.format("parameter %d: ", argnr++);
if (argc) {
reg_t parameter = *argv;
- debugN("%04x:%04x (", PRINT_REG(parameter));
+ signatureDetailsStr += signatureDetailsStr.format("%04x:%04x (", PRINT_REG(parameter));
int regType = findRegType(parameter);
if (regType)
- kernelSignatureDebugType(regType);
+ kernelSignatureDebugType(signatureDetailsStr, regType);
else
- debugN("unknown type of %04x:%04x", PRINT_REG(parameter));
- debugN(")");
+ signatureDetailsStr += signatureDetailsStr.format("unknown type of %04x:%04x", PRINT_REG(parameter));
+ signatureDetailsStr += ")";
argv++;
argc--;
} else {
- debugN("not passed");
+ signatureDetailsStr += "not passed";
}
if (*sig) {
const uint16 signature = *sig;
if ((signature & SIG_MAYBE_ANY) == SIG_MAYBE_ANY) {
- debugN(", may be any");
+ signatureDetailsStr += ", may be any";
} else {
- debugN(", should be ");
- kernelSignatureDebugType(signature);
+ signatureDetailsStr += ", should be ";
+ kernelSignatureDebugType(signatureDetailsStr, signature);
}
if (signature & SIG_IS_OPTIONAL)
- debugN(" (optional)");
+ signatureDetailsStr += " (optional)";
if (signature & SIG_NEEDS_MORE)
- debugN(" (needs more)");
+ signatureDetailsStr += " (needs more)";
if (signature & SIG_MORE_MAY_FOLLOW)
- debugN(" (more may follow)");
+ signatureDetailsStr += " (more may follow)";
sig++;
}
- debugN("\n");
+ signatureDetailsStr += "\n";
}
}
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index dddf845222..57b4d9455b 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -145,7 +145,7 @@ public:
*/
Kernel(ResourceManager *resMan, SegManager *segMan);
~Kernel();
-
+
void init();
uint getSelectorNamesSize() const;
@@ -161,7 +161,7 @@ public:
* @return The appropriate selector ID, or -1 on error
*/
int findSelector(const char *selectorName) const;
-
+
bool selectorNamesAvailable();
// Script dissection/dumping functions
@@ -186,7 +186,7 @@ public:
bool signatureMatch(const uint16 *sig, int argc, const reg_t *argv);
// Prints out debug information in case a signature check fails
- void signatureDebug(const uint16 *sig, int argc, const reg_t *argv);
+ void signatureDebug(Common::String &signatureDetails, const uint16 *sig, int argc, const reg_t *argv);
/**
* Determines the type of the object indicated by reg.
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 0c2fd4e3ea..2cbd79366d 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -105,10 +105,6 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
{ SIG_SOUNDSCI1EARLY, 5, MAP_CALL(DoSoundInit), NULL, NULL },
{ SIG_SOUNDSCI1EARLY, 6, MAP_CALL(DoSoundDispose), NULL, NULL },
{ SIG_SOUNDSCI1EARLY, 7, MAP_CALL(DoSoundPlay), "oi", NULL },
- // ^^ TODO: In SCI1-SCI1.1 DoSound (play) is called by 2 methods of the Sound object: play and
- // playBed. The methods are the same, apart from the second integer parameter: it's 0 in
- // play and 1 in playBed, to distinguish the caller. It's passed on, we should find out what
- // it actually does internally
{ SIG_SOUNDSCI1EARLY, 8, MAP_CALL(DoSoundStop), NULL, NULL },
{ SIG_SOUNDSCI1EARLY, 9, MAP_CALL(DoSoundPause), "[o0]i", NULL },
{ SIG_SOUNDSCI1EARLY, 10, MAP_CALL(DoSoundFade), "oiiii", kDoSoundFade_workarounds },
diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp
index cb81da2279..8e16e0a07a 100644
--- a/engines/sci/engine/kevent.cpp
+++ b/engines/sci/engine/kevent.cpp
@@ -24,9 +24,10 @@
#include "sci/sci.h"
#include "sci/engine/features.h"
-#include "sci/engine/state.h"
-#include "sci/engine/selector.h"
#include "sci/engine/kernel.h"
+#include "sci/engine/savegame.h"
+#include "sci/engine/selector.h"
+#include "sci/engine/state.h"
#include "sci/console.h"
#include "sci/debug.h" // for g_debug_simulated_key
#include "sci/event.h"
@@ -71,9 +72,15 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
g_debug_simulated_key = 0;
return make_reg(0, 1);
}
-
+
curEvent = g_sci->getEventManager()->getSciEvent(mask);
+ if (s->_delayedRestoreGame) {
+ // delayed restore game from ScummVM menu got triggered
+ gamestate_delayedrestore(s);
+ return NULL_REG;
+ }
+
// For a real event we use its associated mouse position
mousePos = curEvent.mousePos;
#ifdef ENABLE_SCI32
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 61fb717567..c56eb09482 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -37,6 +37,7 @@
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
#include "sci/engine/savegame.h"
+#include "sci/graphics/menu.h"
#include "sci/sound/audio.h"
#include "sci/console.h"
@@ -913,6 +914,25 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
// code concerning this via script patch.
s->variables[VAR_GLOBAL][0xB3].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId);
break;
+ case GID_JONES:
+ // HACK: The code that enables certain menu items isn't called when a game is restored from the
+ // launcher, or the "Restore game" option in the game's main menu - bugs #6537 and #6723.
+ // These menu entries are disabled when the game is launched, and are enabled when a new game is
+ // started. The code for enabling these entries is is all in script 1, room1::init, but that code
+ // path is never followed in these two cases (restoring game from the menu, or restoring a game
+ // from the ScummVM launcher). Thus, we perform the calls to enable the menus ourselves here.
+ // These two are needed when restoring from the launcher
+ // FIXME: The original interpreter saves and restores the menu state, so these attributes
+ // are automatically reset there. We may want to do the same.
+ g_sci->_gfxMenu->kernelSetAttribute(257 >> 8, 257 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> About Jones
+ g_sci->_gfxMenu->kernelSetAttribute(258 >> 8, 258 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> Help
+ // The rest are normally enabled from room1::init
+ g_sci->_gfxMenu->kernelSetAttribute(769 >> 8, 769 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Options -> Delete current player
+ g_sci->_gfxMenu->kernelSetAttribute(513 >> 8, 513 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game
+ g_sci->_gfxMenu->kernelSetAttribute(515 >> 8, 515 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Restore Game
+ g_sci->_gfxMenu->kernelSetAttribute(1025 >> 8, 1025 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Statistics
+ g_sci->_gfxMenu->kernelSetAttribute(1026 >> 8, 1026 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Goals
+ break;
default:
break;
}
diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp
index c2089bcd4d..8b790e6a58 100644
--- a/engines/sci/engine/kgraphics.cpp
+++ b/engines/sci/engine/kgraphics.cpp
@@ -32,6 +32,7 @@
#include "sci/event.h"
#include "sci/resource.h"
#include "sci/engine/features.h"
+#include "sci/engine/savegame.h"
#include "sci/engine/state.h"
#include "sci/engine/selector.h"
#include "sci/engine/kernel.h"
@@ -354,13 +355,16 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) {
}
textWidth = dest[3].toUint16(); textHeight = dest[2].toUint16();
+
+ uint16 languageSplitter = 0;
+ Common::String splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter, sep);
#ifdef ENABLE_SCI32
if (g_sci->_gfxText32)
- g_sci->_gfxText32->kernelTextSize(g_sci->strSplit(text.c_str(), sep).c_str(), font_nr, maxwidth, &textWidth, &textHeight);
+ g_sci->_gfxText32->kernelTextSize(splitText.c_str(), font_nr, maxwidth, &textWidth, &textHeight);
else
#endif
- g_sci->_gfxText16->kernelTextSize(g_sci->strSplit(text.c_str(), sep).c_str(), font_nr, maxwidth, &textWidth, &textHeight);
+ g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight);
// One of the game texts in LB2 German contains loads of spaces in
// its end. We trim the text here, otherwise the graphics code will
@@ -376,7 +380,7 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) {
// Copy over the trimmed string...
s->_segMan->strcpy(argv[1], text.c_str());
// ...and recalculate bounding box dimensions
- g_sci->_gfxText16->kernelTextSize(g_sci->strSplit(text.c_str(), sep).c_str(), font_nr, maxwidth, &textWidth, &textHeight);
+ g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight);
}
}
@@ -397,6 +401,12 @@ reg_t kWait(EngineState *s, int argc, reg_t *argv) {
s->wait(sleep_time);
+ if (s->_delayedRestoreGame) {
+ // delayed restore game from ScummVM menu got triggered
+ gamestate_delayedrestore(s);
+ return NULL_REG;
+ }
+
return s->r_acc;
}
@@ -818,16 +828,29 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) {
if (!textReference.isNull())
text = s->_segMan->getString(textReference);
+ uint16 languageSplitter = 0;
+ Common::String splitText;
+
+ switch (type) {
+ case SCI_CONTROLS_TYPE_BUTTON:
+ case SCI_CONTROLS_TYPE_TEXTEDIT:
+ splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter, NULL);
+ break;
+ case SCI_CONTROLS_TYPE_TEXT:
+ splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter);
+ break;
+ }
+
switch (type) {
case SCI_CONTROLS_TYPE_BUTTON:
debugC(kDebugLevelGraphics, "drawing button %04x:%04x to %d,%d", PRINT_REG(controlObject), x, y);
- g_sci->_gfxControls16->kernelDrawButton(rect, controlObject, g_sci->strSplit(text.c_str(), NULL).c_str(), fontId, style, hilite);
+ g_sci->_gfxControls16->kernelDrawButton(rect, controlObject, splitText.c_str(), languageSplitter, fontId, style, hilite);
return;
case SCI_CONTROLS_TYPE_TEXT:
alignment = readSelectorValue(s->_segMan, controlObject, SELECTOR(mode));
debugC(kDebugLevelGraphics, "drawing text %04x:%04x ('%s') to %d,%d, mode=%d", PRINT_REG(controlObject), text.c_str(), x, y, alignment);
- g_sci->_gfxControls16->kernelDrawText(rect, controlObject, g_sci->strSplit(text.c_str()).c_str(), fontId, alignment, style, hilite);
+ g_sci->_gfxControls16->kernelDrawText(rect, controlObject, splitText.c_str(), languageSplitter, fontId, alignment, style, hilite);
s->r_acc = g_sci->_gfxText16->allocAndFillReferenceRectArray();
return;
@@ -841,7 +864,7 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) {
writeSelectorValue(s->_segMan, controlObject, SELECTOR(cursor), cursorPos);
}
debugC(kDebugLevelGraphics, "drawing edit control %04x:%04x (text %04x:%04x, '%s') to %d,%d", PRINT_REG(controlObject), PRINT_REG(textReference), text.c_str(), x, y);
- g_sci->_gfxControls16->kernelDrawTextEdit(rect, controlObject, g_sci->strSplit(text.c_str(), NULL).c_str(), fontId, mode, style, cursorPos, maxChars, hilite);
+ g_sci->_gfxControls16->kernelDrawTextEdit(rect, controlObject, splitText.c_str(), languageSplitter, fontId, mode, style, cursorPos, maxChars, hilite);
return;
case SCI_CONTROLS_TYPE_ICON:
@@ -939,8 +962,9 @@ reg_t kDrawControl(EngineState *s, int argc, reg_t *argv) {
reg_t textReference = readSelector(s->_segMan, controlObject, SELECTOR(text));
if (!textReference.isNull()) {
Common::String text = s->_segMan->getString(textReference);
- if ((text == "a:hq1_hero.sav") || (text == "a:glory1.sav") || (text == "a:glory2.sav") || (text == "a:glory3.sav")) {
+ if ((text == "a:hq1_hero.sav") || (text == "a:glory1.sav") || (text == "a:glory2.sav") || (text == "a:glory3.sav") || (text == "a:gloire3.sauv")) {
// Remove "a:" from hero quest / quest for glory export default filenames
+ // The french version of Quest For Glory 3 uses "gloire3.sauv". It seems a translator translated the filename.
text.deleteChar(0);
text.deleteChar(0);
s->_segMan->strcpy(textReference, text.c_str());
@@ -1165,8 +1189,11 @@ reg_t kDisplay(EngineState *s, int argc, reg_t *argv) {
argc--; argc--; argv++; argv++;
text = g_sci->getKernel()->lookupText(textp, index);
}
+
+ uint16 languageSplitter = 0;
+ Common::String splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter);
- return g_sci->_gfxPaint16->kernelDisplay(g_sci->strSplit(text.c_str()).c_str(), argc, argv);
+ return g_sci->_gfxPaint16->kernelDisplay(splitText.c_str(), languageSplitter, argc, argv);
}
reg_t kSetVideoMode(EngineState *s, int argc, reg_t *argv) {
diff --git a/engines/sci/engine/kmovement.cpp b/engines/sci/engine/kmovement.cpp
index 51d49eea9f..9b83dbc52d 100644
--- a/engines/sci/engine/kmovement.cpp
+++ b/engines/sci/engine/kmovement.cpp
@@ -305,12 +305,23 @@ reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) {
for (uint i = 0; i < clientVarNum; ++i)
clientBackup[i] = clientObject->getVariable(i);
- if (mover_xAxis) {
- if (ABS(mover_x - client_x) < ABS(mover_dx))
- completed = true;
+ if ((getSciVersion() <= SCI_VERSION_1_EGA_ONLY)) {
+ if (mover_xAxis) {
+ if (ABS(mover_x - client_x) < ABS(mover_dx))
+ completed = true;
+ } else {
+ if (ABS(mover_y - client_y) < ABS(mover_dy))
+ completed = true;
+ }
} else {
- if (ABS(mover_y - client_y) < ABS(mover_dy))
- completed = true;
+ // SCI1EARLY+ code
+ if (mover_xAxis) {
+ if (ABS(mover_x - client_x) <= ABS(mover_dx))
+ completed = true;
+ } else {
+ if (ABS(mover_y - client_y) <= ABS(mover_dy))
+ completed = true;
+ }
}
if (completed) {
client_x = mover_x;
@@ -336,10 +347,10 @@ reg_t kDoBresen(EngineState *s, int argc, reg_t *argv) {
bool collision = false;
reg_t cantBeHere = NULL_REG;
+ // adding this here for hoyle 3 to get happy. CantBeHere is a dummy in hoyle 3 and acc is != 0 so we would
+ // get a collision otherwise. Resetting the result was always done in SSCI
+ s->r_acc = NULL_REG;
if (SELECTOR(cantBeHere) != -1) {
- // 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;
diff --git a/engines/sci/engine/kparse.cpp b/engines/sci/engine/kparse.cpp
index aa89b963cc..f85f33e3e8 100644
--- a/engines/sci/engine/kparse.cpp
+++ b/engines/sci/engine/kparse.cpp
@@ -117,6 +117,8 @@ reg_t kParse(EngineState *s, int argc, reg_t *argv) {
}
#endif
+ voc->replacePronouns(words);
+
int syntax_fail = voc->parseGNF(words);
if (syntax_fail) {
@@ -130,6 +132,7 @@ reg_t kParse(EngineState *s, int argc, reg_t *argv) {
} else {
voc->parserIsValid = true;
+ voc->storePronounReference();
writeSelectorValue(segMan, event, SELECTOR(claimed), 0);
#ifdef DEBUG_PARSER
diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp
index 56dad583e4..eef758a0d9 100644
--- a/engines/sci/engine/kstring.cpp
+++ b/engines/sci/engine/kstring.cpp
@@ -42,10 +42,12 @@ reg_t kStrCat(EngineState *s, int argc, reg_t *argv) {
Common::String s1 = s->_segMan->getString(argv[0]);
Common::String s2 = s->_segMan->getString(argv[1]);
- // The Japanese version of PQ2 splits the two strings here
- // (check bug #3396887).
- if (g_sci->getGameId() == GID_PQ2 &&
- g_sci->getLanguage() == Common::JA_JPN) {
+ // Japanese PC-9801 interpreter splits strings here
+ // see bug #5834
+ // Verified for Police Quest 2 + Quest For Glory 1
+ // However Space Quest 4 PC-9801 doesn't
+ if ((g_sci->getLanguage() == Common::JA_JPN)
+ && (getSciVersion() <= SCI_VERSION_01)) {
s1 = g_sci->strSplit(s1.c_str(), NULL);
s2 = g_sci->strSplit(s2.c_str(), NULL);
}
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index 319469cb08..f925111fc9 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -123,6 +123,8 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) {
if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP)
skipVideo = true;
}
+ if (g_sci->getEngineState()->_delayedRestoreGame)
+ skipVideo = true;
g_system->delayMillis(10);
}
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index cdcdcc41e5..93b3a997cc 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "common/savefile.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/func.h"
@@ -40,6 +41,7 @@
#include "sci/graphics/helpers.h"
#include "sci/graphics/palette.h"
#include "sci/graphics/ports.h"
+#include "sci/graphics/screen.h"
#include "sci/parser/vocabulary.h"
#include "sci/sound/audio.h"
#include "sci/sound/music.h"
@@ -132,13 +134,6 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
// Reset _scriptSegMap, to be restored below
_scriptSegMap.clear();
-
-#ifdef ENABLE_SCI32
- // Clear any planes/screen items currently showing so they
- // don't show up after the load.
- if (getSciVersion() >= SCI_VERSION_2)
- g_sci->_gfxFrameout->clear();
-#endif
}
s.skip(4, VER(14), VER(18)); // OBSOLETE: Used to be _exportsAreWide
@@ -617,6 +612,14 @@ void MusicEntry::saveLoadWithSerializer(Common::Serializer &s) {
s.syncAsSint32LE(fadeTicker);
s.syncAsSint32LE(fadeTickerStep);
s.syncAsByte(status);
+ if (s.getVersion() >= 32)
+ s.syncAsByte(playBed);
+ else if (s.isLoading())
+ playBed = false;
+ if (s.getVersion() >= 33)
+ s.syncAsByte(overridePriority);
+ else if (s.isLoading())
+ overridePriority = false;
// pMidiParser and pStreamAud will be initialized when the
// sound list is reconstructed in gamestate_restore()
@@ -635,20 +638,26 @@ void SoundCommandParser::syncPlayList(Common::Serializer &s) {
void SoundCommandParser::reconstructPlayList() {
Common::StackLock lock(_music->_mutex);
- const MusicList::iterator end = _music->getPlayListEnd();
- for (MusicList::iterator i = _music->getPlayListStart(); i != end; ++i) {
+ // We store all songs here because starting songs may re-shuffle their order
+ MusicList songs;
+ for (MusicList::iterator i = _music->getPlayListStart(); i != _music->getPlayListEnd(); ++i)
+ songs.push_back(*i);
+
+ for (MusicList::iterator i = songs.begin(); i != songs.end(); ++i) {
initSoundResource(*i);
if ((*i)->status == kSoundPlaying) {
- // Sync the sound object's selectors related to playing with the stored
- // ones in the playlist, as they may have been invalidated when loading.
- // Refer to bug #3104624.
+ // WORKAROUND: PQ3 (German?) scripts can set volume negative in the
+ // sound object directly without going through DoSound.
+ // Since we re-read this selector when re-playing the sound after loading,
+ // this will lead to unexpected behaviour. As a workaround we
+ // sync the sound object's selectors here. (See bug #5501)
writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(loop), (*i)->loop);
writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(priority), (*i)->priority);
if (_soundVersion >= SCI_VERSION_1_EARLY)
writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(vol), (*i)->volume);
- processPlaySound((*i)->soundObj);
+ processPlaySound((*i)->soundObj, (*i)->playBed);
}
}
}
@@ -713,9 +722,7 @@ void GfxPalette::saveLoadWithSerializer(Common::Serializer &s) {
}
void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) {
- if (s.isLoading())
- reset(); // remove all script generated windows
-
+ // reset() is called directly way earlier in gamestate_restore()
if (s.getVersion() >= 27) {
uint windowCount = 0;
uint id = PORTS_FIRSTSCRIPTWINDOWID;
@@ -758,10 +765,17 @@ void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) {
if (window->counterTillFree) {
_freeCounter++;
} else {
- if (window->wndStyle & SCI_WINDOWMGR_STYLE_TOPMOST)
- _windowList.push_front(window);
- else
- _windowList.push_back(window);
+ // we don't put the saved script windows into _windowList[], so that they aren't used
+ // by kernel functions. This is important and would cause issues otherwise.
+ // see Conquests of Camelot - bug #6744 - when saving on the map screen (room 103),
+ // restoring would result in a black window in place
+ // where the area name was displayed before
+ // In Sierra's SCI the behaviour is identical to us
+ // Sierra's SCI won't show those windows after restoring
+ // If this should cause issues in another game, we would have to add a flag to simply
+ // avoid any drawing operations for such windows
+ // We still have to restore script windows, because for example Conquests of Camelot
+ // will immediately delete windows, that were created before saving the game.
}
windowCount--;
@@ -844,11 +858,29 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin
if (voc)
voc->saveLoadWithSerializer(ser);
+ // TODO: SSCI (at least JonesCD, presumably more) also stores the Menu state
+
return true;
}
extern void showScummVMDialog(const Common::String &message);
+void gamestate_delayedrestore(EngineState *s) {
+ Common::String fileName = g_sci->getSavegameName(s->_delayedRestoreGameId);
+ Common::SeekableReadStream *in = g_sci->getSaveFileManager()->openForLoading(fileName);
+
+ if (in) {
+ // found a savegame file
+ gamestate_restore(s, in);
+ delete in;
+ if (s->r_acc != make_reg(0, 1)) {
+ return;
+ }
+ }
+
+ error("Restoring gamestate '%s' failed", fileName.c_str());
+}
+
void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
SavegameMetadata meta;
@@ -885,6 +917,20 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
// We don't need the thumbnail here, so just read it and discard it
Graphics::skipThumbnail(*fh);
+ // reset ports is one of the first things we do, because that may free() some hunk memory
+ // and we don't want to do that after we read in the saved game hunk memory
+ if (g_sci->_gfxPorts)
+ g_sci->_gfxPorts->reset();
+ // clear screen
+ if (g_sci->_gfxScreen)
+ g_sci->_gfxScreen->clearForRestoreGame();
+#ifdef ENABLE_SCI32
+ // Also clear any SCI32 planes/screen items currently showing so they
+ // don't show up after the load.
+ if (getSciVersion() >= SCI_VERSION_2)
+ g_sci->_gfxFrameout->clear();
+#endif
+
s->reset(true);
s->saveLoadWithSerializer(ser); // FIXME: Error handling?
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index 8f2835654b..5512b90fa8 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -37,6 +37,8 @@ struct EngineState;
*
* Version - new/changed feature
* =============================
+ * 33 - new overridePriority flag in MusicEntry
+ * 32 - new playBed flag in MusicEntry
* 31 - priority for sound effects/music is now a signed int16, instead of a byte
* 30 - synonyms
* 29 - system strings
@@ -56,7 +58,7 @@ struct EngineState;
*/
enum {
- CURRENT_SAVEGAME_VERSION = 31,
+ CURRENT_SAVEGAME_VERSION = 33,
MINIMUM_SAVEGAME_VERSION = 14
};
@@ -72,7 +74,6 @@ struct SavegameMetadata {
uint16 script0Size;
};
-
/**
* Saves a game state to the hard disk in a portable way.
* @param s The state to save
@@ -82,6 +83,9 @@ struct SavegameMetadata {
*/
bool gamestate_save(EngineState *s, Common::WriteStream *save, const Common::String &savename, const Common::String &version);
+// does a delayed saved game restore, used by ScummVM game menu - see detection.cpp / SciEngine::loadGameState()
+void gamestate_delayedrestore(EngineState *s);
+
/**
* Restores a game state from a directory.
* @param s An older state from the same game
diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp
index 2fe1aba975..6034378ef6 100644
--- a/engines/sci/engine/script.cpp
+++ b/engines/sci/engine/script.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "sci/console.h"
#include "sci/sci.h"
#include "sci/resource.h"
#include "sci/util.h"
@@ -64,6 +65,11 @@ void Script::freeScript() {
_lockers = 1;
_markedAsDeleted = false;
_objects.clear();
+
+ _offsetLookupArray.clear();
+ _offsetLookupObjectCount = 0;
+ _offsetLookupStringCount = 0;
+ _offsetLookupSaidCount = 0;
}
void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptPatcher) {
@@ -136,9 +142,6 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP
assert(_bufSize >= script->size);
memcpy(_buf, script->data, script->size);
- // Check scripts for matching signatures and patch those, if found
- scriptPatcher->processScript(_nr, _buf, script->size);
-
if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, _nr), 0);
assert(heap != 0);
@@ -149,6 +152,9 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP
memcpy(_heapStart, heap->data, heap->size);
}
+ // Check scripts (+ possibly SCI 1.1 heap) for matching signatures and patch those, if found
+ scriptPatcher->processScript(_nr, _buf, _bufSize);
+
if (getSciVersion() <= SCI_VERSION_1_LATE) {
_exportTable = (const uint16 *)findBlockSCI0(SCI_OBJ_EXPORTS);
if (_exportTable) {
@@ -204,6 +210,397 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP
//_localsCount = (_bufSize - _localsOffset) >> 1;
}
}
+
+ // find all strings of this script
+ identifyOffsets();
+}
+
+void Script::identifyOffsets() {
+ offsetLookupArrayEntry arrayEntry;
+ const byte *scriptDataPtr = NULL;
+ const byte *stringStartPtr = NULL;
+ const byte *stringDataPtr = NULL;
+ uint32 scriptDataLeft = 0;
+ uint32 stringDataLeft = 0;
+ byte stringDataByte = 0;
+ uint16 typeObject_id = 0;
+ uint16 typeString_id = 0;
+ uint16 typeSaid_id = 0;
+
+ uint16 blockType = 0;
+ uint16 blockSize = 0;
+
+ _offsetLookupArray.clear();
+ _offsetLookupObjectCount = 0;
+ _offsetLookupStringCount = 0;
+ _offsetLookupSaidCount = 0;
+
+ if (getSciVersion() < SCI_VERSION_1_1) {
+ // SCI0 + SCI1
+ scriptDataPtr = _buf;
+ scriptDataLeft = _bufSize;
+
+ // Go through all blocks
+ if (getSciVersion() == SCI_VERSION_0_EARLY) {
+ if (scriptDataLeft < 2)
+ error("Script::identifyOffsets(): unexpected end of script %d", _nr);
+ scriptDataPtr += 2;
+ scriptDataLeft -= 2;
+ }
+
+ do {
+ if (scriptDataLeft < 2)
+ error("Script::identifyOffsets(): unexpected end of script %d", _nr);
+
+ blockType = READ_LE_UINT16(scriptDataPtr);
+ scriptDataPtr += 2;
+ scriptDataLeft -= 2;
+ if (blockType == 0) // end of blocks detected
+ break;
+
+ if (scriptDataLeft < 2)
+ error("Script::identifyOffsets(): unexpected end of script %d", _nr);
+
+ blockSize = READ_LE_UINT16(scriptDataPtr);
+ if (blockSize < 4)
+ error("Script::identifyOffsets(): invalid block size in script %d", _nr);
+ blockSize -= 4; // block size includes block-type UINT16 and block-size UINT16
+ scriptDataPtr += 2;
+ scriptDataLeft -= 2;
+
+ if (scriptDataLeft < blockSize)
+ error("Script::identifyOffsets(): invalid block size in script %d", _nr);
+
+ switch (blockType) {
+ case SCI_OBJ_OBJECT:
+ case SCI_OBJ_CLASS:
+ typeObject_id++;
+ arrayEntry.type = SCI_SCR_OFFSET_TYPE_OBJECT;
+ arrayEntry.id = typeObject_id;
+ arrayEntry.offset = scriptDataPtr - _buf + 8; // Calculate offset inside script data (VM uses +8)
+ arrayEntry.stringSize = 0;
+ _offsetLookupArray.push_back(arrayEntry);
+ _offsetLookupObjectCount++;
+ break;
+
+ case SCI_OBJ_STRINGS:
+ // string block detected, we now grab all NUL terminated strings out of this block
+ stringDataPtr = scriptDataPtr;
+ stringDataLeft = blockSize;
+
+ arrayEntry.type = SCI_SCR_OFFSET_TYPE_STRING;
+
+ do {
+ if (stringDataLeft < 1) // no more bytes left
+ break;
+
+ stringStartPtr = stringDataPtr;
+
+ if (stringDataLeft == 1) {
+ // only 1 byte left and that byte is a [00], in that case we also exit
+ stringDataByte = *stringStartPtr;
+ if (stringDataByte == 0x00)
+ break;
+ }
+
+ // now look for terminating [NUL]
+ do {
+ stringDataByte = *stringDataPtr;
+ stringDataPtr++;
+ stringDataLeft--;
+ if (!stringDataByte) // NUL found, exit this loop
+ break;
+ if (stringDataLeft < 1) {
+ // no more bytes left
+ warning("Script::identifyOffsets(): string without terminating NUL in script %d", _nr);
+ break;
+ }
+ } while (1);
+
+ if (stringDataByte)
+ break;
+
+ typeString_id++;
+ arrayEntry.id = typeString_id;
+ arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data
+ arrayEntry.stringSize = stringDataPtr - stringStartPtr;
+ _offsetLookupArray.push_back(arrayEntry);
+ _offsetLookupStringCount++;
+ } while (1);
+ break;
+
+ case SCI_OBJ_SAID:
+ // said block detected, we now try to find every single said "string" inside this block
+ // said strings are terminated with a 0xFF, the string itself may contain words (2 bytes), where
+ // the second byte of a word may also be a 0xFF.
+ stringDataPtr = scriptDataPtr;
+ stringDataLeft = blockSize;
+
+ arrayEntry.type = SCI_SCR_OFFSET_TYPE_SAID;
+
+ do {
+ if (stringDataLeft < 1) // no more bytes left
+ break;
+
+ stringStartPtr = stringDataPtr;
+ if (stringDataLeft == 1) {
+ // only 1 byte left and that byte is a [00], in that case we also exit
+ // happens in some scripts, for example Conquests of Camelot, script 997
+ // may have been a bug in the compiler or just an intentional filler byte
+ stringDataByte = *stringStartPtr;
+ if (stringDataByte == 0x00)
+ break;
+ }
+
+ // now look for terminating 0xFF
+ do {
+ stringDataByte = *stringDataPtr;
+ stringDataPtr++;
+ stringDataLeft--;
+ if (stringDataByte == 0xFF) // Terminator found, exit this loop
+ break;
+ if (stringDataLeft < 1) // no more bytes left
+ error("Script::identifyOffsets(): said-string without terminator in script %d", _nr);
+ if (stringDataByte < 0xF0) {
+ // Part of a word, skip second byte
+ stringDataPtr++;
+ stringDataLeft--;
+ if (stringDataLeft < 1) // no more bytes left
+ error("Script::identifyOffsets(): said-string without terminator in script %d", _nr);
+ }
+ } while (1);
+
+ typeSaid_id++;
+ arrayEntry.id = typeSaid_id;
+ arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data
+ arrayEntry.stringSize = 0;
+ _offsetLookupArray.push_back(arrayEntry);
+ _offsetLookupSaidCount++;
+ } while (1);
+ break;
+
+ default:
+ break;
+ }
+
+ scriptDataPtr += blockSize;
+ scriptDataLeft -= blockSize;
+ } while (1);
+
+ } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+ // Strings in SCI1.1 up to SCI2 come after the object instances
+ scriptDataPtr = _heapStart;
+ scriptDataLeft = _heapSize;
+
+ if (scriptDataLeft < 4)
+ error("Script::identifyOffsets(): unexpected end of script in script %d", _nr);
+
+ uint16 endOfStringOffset = READ_SCI11ENDIAN_UINT16(scriptDataPtr);
+ uint16 objectStartOffset = READ_SCI11ENDIAN_UINT16(scriptDataPtr + 2) * 2 + 4;
+
+ if (scriptDataLeft < objectStartOffset)
+ error("Script::identifyOffsets(): object start is beyond heap size in script %d", _nr);
+ if (scriptDataLeft < endOfStringOffset)
+ error("Script::identifyOffsets(): end of string is beyond heap size in script %d", _nr);
+
+ const byte *endOfStringPtr = scriptDataPtr + endOfStringOffset;
+
+ scriptDataPtr += objectStartOffset;
+ scriptDataLeft -= objectStartOffset;
+
+ // go through all objects
+ do {
+ if (scriptDataLeft < 2)
+ error("Script::identifyOffsets(): unexpected end of script %d", _nr);
+
+ blockType = READ_SCI11ENDIAN_UINT16(scriptDataPtr);
+ scriptDataPtr += 2;
+ scriptDataLeft -= 2;
+ if (blockType != SCRIPT_OBJECT_MAGIC_NUMBER)
+ break;
+
+ // Object found, add offset of object
+ typeObject_id++;
+ arrayEntry.type = SCI_SCR_OFFSET_TYPE_OBJECT;
+ arrayEntry.id = typeObject_id;
+ arrayEntry.offset = scriptDataPtr - _buf - 2; // the VM uses a pointer to the Magic-Number
+ arrayEntry.stringSize = 0;
+ _offsetLookupArray.push_back(arrayEntry);
+ _offsetLookupObjectCount++;
+
+ if (scriptDataLeft < 2)
+ error("Script::identifyOffsets(): unexpected end of script in script %d", _nr);
+
+ blockSize = READ_SCI11ENDIAN_UINT16(scriptDataPtr) * 2;
+ if (blockSize < 4)
+ error("Script::identifyOffsets(): invalid block size in script %d", _nr);
+ scriptDataPtr += 2;
+ scriptDataLeft -= 2;
+ blockSize -= 4; // blocksize contains UINT16 type and UINT16 size
+ if (scriptDataLeft < blockSize)
+ error("Script::identifyOffsets(): invalid block size in script %d", _nr);
+
+ scriptDataPtr += blockSize;
+ scriptDataLeft -= blockSize;
+ } while (1);
+
+ // now scriptDataPtr points to right at the start of the strings
+ if (scriptDataPtr > endOfStringPtr)
+ error("Script::identifyOffsets(): string block / end-of-string block mismatch in script %d", _nr);
+
+ stringDataPtr = scriptDataPtr;
+ stringDataLeft = endOfStringPtr - scriptDataPtr; // Calculate byte count within string-block
+
+ arrayEntry.type = SCI_SCR_OFFSET_TYPE_STRING;
+ do {
+ if (stringDataLeft < 1) // no more bytes left
+ break;
+
+ stringStartPtr = stringDataPtr;
+ // now look for terminating [NUL]
+ do {
+ stringDataByte = *stringDataPtr;
+ stringDataPtr++;
+ stringDataLeft--;
+ if (!stringDataByte) // NUL found, exit this loop
+ break;
+ if (stringDataLeft < 1) {
+ // no more bytes left
+ warning("Script::identifyOffsets(): string without terminating NUL in script %d", _nr);
+ break;
+ }
+ } while (1);
+
+ if (stringDataByte)
+ break;
+
+ typeString_id++;
+ arrayEntry.id = typeString_id;
+ arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data
+ arrayEntry.stringSize = stringDataPtr - stringStartPtr;
+ _offsetLookupArray.push_back(arrayEntry);
+ _offsetLookupStringCount++;
+ } while (1);
+
+ } else if (getSciVersion() == SCI_VERSION_3) {
+ // SCI3
+ uint32 sci3StringOffset = 0;
+ uint32 sci3RelocationOffset = 0;
+ uint32 sci3BoundaryOffset = 0;
+
+ if (_bufSize < 22)
+ error("Script::identifyOffsets(): script %d smaller than expected SCI3-header", _nr);
+
+ sci3StringOffset = READ_LE_UINT32(_buf + 4);
+ sci3RelocationOffset = READ_LE_UINT32(_buf + 8);
+
+ if (sci3RelocationOffset > _bufSize)
+ error("Script::identifyOffsets(): relocation offset is beyond end of script %d", _nr);
+
+ // First we get all the objects
+ scriptDataPtr = getSci3ObjectsPointer();
+ scriptDataLeft = _bufSize - (scriptDataPtr - _buf);
+ do {
+ if (scriptDataLeft < 2)
+ error("Script::identifyOffsets(): unexpected end of script %d", _nr);
+
+ blockType = READ_SCI11ENDIAN_UINT16(scriptDataPtr);
+ scriptDataPtr += 2;
+ scriptDataLeft -= 2;
+ if (blockType != SCRIPT_OBJECT_MAGIC_NUMBER)
+ break;
+
+ // Object found, add offset of object
+ typeObject_id++;
+ arrayEntry.type = SCI_SCR_OFFSET_TYPE_OBJECT;
+ arrayEntry.id = typeObject_id;
+ arrayEntry.offset = scriptDataPtr - _buf - 2; // the VM uses a pointer to the Magic-Number
+ arrayEntry.stringSize = 0;
+ _offsetLookupArray.push_back(arrayEntry);
+ _offsetLookupObjectCount++;
+
+ if (scriptDataLeft < 2)
+ error("Script::identifyOffsets(): unexpected end of script in script %d", _nr);
+
+ blockSize = READ_SCI11ENDIAN_UINT16(scriptDataPtr);
+ if (blockSize < 4)
+ error("Script::identifyOffsets(): invalid block size in script %d", _nr);
+ scriptDataPtr += 2;
+ scriptDataLeft -= 2;
+ blockSize -= 4; // blocksize contains UINT16 type and UINT16 size
+ if (scriptDataLeft < blockSize)
+ error("Script::identifyOffsets(): invalid block size in script %d", _nr);
+
+ scriptDataPtr += blockSize;
+ scriptDataLeft -= blockSize;
+ } while (1);
+
+ // And now we get all the strings
+ if (sci3StringOffset > 0) {
+ // string offset set, we expect strings
+ if (sci3StringOffset > _bufSize)
+ error("Script::identifyOffsets(): string offset is beyond end of script %d", _nr);
+
+ if (sci3RelocationOffset < sci3StringOffset)
+ error("Script::identifyOffsets(): string offset points beyond relocation offset in script %d", _nr);
+
+ stringDataPtr = _buf + sci3StringOffset;
+ stringDataLeft = sci3RelocationOffset - sci3StringOffset;
+
+ arrayEntry.type = SCI_SCR_OFFSET_TYPE_STRING;
+
+ do {
+ if (stringDataLeft < 1) // no more bytes left
+ break;
+
+ stringStartPtr = stringDataPtr;
+
+ if (stringDataLeft == 1) {
+ // only 1 byte left and that byte is a [00], in that case we also exit
+ stringDataByte = *stringStartPtr;
+ if (stringDataByte == 0x00)
+ break;
+ }
+
+ // now look for terminating [NUL]
+ do {
+ stringDataByte = *stringDataPtr;
+ stringDataPtr++;
+ stringDataLeft--;
+ if (!stringDataByte) // NUL found, exit this loop
+ break;
+ if (stringDataLeft < 1) {
+ // no more bytes left
+ warning("Script::identifyOffsets(): string without terminating NUL in script %d", _nr);
+ break;
+ }
+ } while (1);
+
+ if (stringDataByte)
+ break;
+
+ typeString_id++;
+ arrayEntry.id = typeString_id;
+ arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data
+ arrayEntry.stringSize = stringDataPtr - stringStartPtr;
+ _offsetLookupArray.push_back(arrayEntry);
+ _offsetLookupStringCount++;
+
+ // SCI3 seems to have aligned all string on DWORD boundaries
+ sci3BoundaryOffset = stringDataPtr - _buf; // Calculate current offset inside script data
+ sci3BoundaryOffset = sci3BoundaryOffset & 3; // Check boundary offset
+ if (sci3BoundaryOffset) {
+ // lower 2 bits are set? Then we have to adjust the offset
+ sci3BoundaryOffset = 4 - sci3BoundaryOffset;
+ if (stringDataLeft < sci3BoundaryOffset)
+ error("Script::identifyOffsets(): SCI3 string boundary adjustment goes beyond end of string block in script %d", _nr);
+ stringDataLeft -= sci3BoundaryOffset;
+ stringDataPtr += sci3BoundaryOffset;
+ }
+ } while (1);
+ }
+ return;
+ }
}
const byte *Script::getSci3ObjectsPointer() {
diff --git a/engines/sci/engine/script.h b/engines/sci/engine/script.h
index 46d6ace917..755e2f3698 100644
--- a/engines/sci/engine/script.h
+++ b/engines/sci/engine/script.h
@@ -49,6 +49,21 @@ enum ScriptObjectTypes {
typedef Common::HashMap<uint16, Object> ObjMap;
+enum ScriptOffsetEntryTypes {
+ SCI_SCR_OFFSET_TYPE_OBJECT = 0, // classes are handled by this type as well
+ SCI_SCR_OFFSET_TYPE_STRING,
+ SCI_SCR_OFFSET_TYPE_SAID
+};
+
+struct offsetLookupArrayEntry {
+ uint16 type; // type of entry
+ uint16 id; // id of this type, first item inside script data is 1, second item is 2, etc.
+ uint32 offset; // offset of entry within script resource data
+ uint16 stringSize; // size of string, including terminating [NUL]
+};
+
+typedef Common::Array<offsetLookupArrayEntry> offsetLookupArrayType;
+
class Script : public SegmentObj {
private:
int _nr; /**< Script number */
@@ -75,6 +90,14 @@ private:
ObjMap _objects; /**< Table for objects, contains property variables */
+protected:
+ offsetLookupArrayType _offsetLookupArray; // Table of all elements of currently loaded script, that may get pointed to
+
+private:
+ uint16 _offsetLookupObjectCount;
+ uint16 _offsetLookupStringCount;
+ uint16 _offsetLookupSaidCount;
+
public:
int getLocalsOffset() const { return _localsOffset; }
uint16 getLocalsCount() const { return _localsCount; }
@@ -248,6 +271,14 @@ public:
*/
int getCodeBlockOffsetSci3() { return READ_SCI11ENDIAN_UINT32(_buf); }
+ /**
+ * Get the offset array
+ */
+ const offsetLookupArrayType *getOffsetArray() { return &_offsetLookupArray; };
+ uint16 getOffsetObjectCount() { return _offsetLookupObjectCount; };
+ uint16 getOffsetStringCount() { return _offsetLookupStringCount; };
+ uint16 getOffsetSaidCount() { return _offsetLookupSaidCount; };
+
private:
/**
* Processes a relocation block within a SCI0-SCI2.1 script
@@ -294,6 +325,11 @@ private:
void initializeObjectsSci3(SegManager *segMan, SegmentId segmentId);
LocalVariables *allocLocalsSegment(SegManager *segMan);
+
+ /**
+ * Identifies certain offsets within script data and set up lookup-table
+ */
+ void identifyOffsets();
};
} // End of namespace Sci
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 8bbbd713a6..6915e12a0e 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -94,6 +94,8 @@ static const char *const selectorNameTable[] = {
"deskSarg", // Gabriel Knight
"localize", // Freddy Pharkas
"put", // Police Quest 1 VGA
+ "say", // Quest For Glory 1 VGA
+ "contains", // Quest For Glory 2
"solvePuzzle", // Quest For Glory 3
"timesShownID", // Space Quest 1 VGA
"startText", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support
@@ -119,6 +121,8 @@ enum ScriptPatcherSelectors {
SELECTOR_deskSarg,
SELECTOR_localize,
SELECTOR_put,
+ SELECTOR_say,
+ SELECTOR_contains,
SELECTOR_solvePuzzle,
SELECTOR_timesShownID,
SELECTOR_startText,
@@ -562,12 +566,12 @@ static const uint16 gk1SignatureDay6PoliceSleep[] = {
0x34, SIG_UINT16(0x00dc), // ldi 220
0x65, SIG_ADDTOOFFSET(+1), // aTop cycles (1a for PC, 1c for Mac)
0x32, // jmp [end]
- 0
+ SIG_END
};
static const uint16 gk1PatchDay6PoliceSleep[] = {
PATCH_ADDTOOFFSET(+5),
- 0x34, SIG_UINT16(0x002a), // ldi 42
+ 0x34, PATCH_UINT16(0x002a), // ldi 42
0x65, PATCH_GETORIGINALBYTEADJUST(+9, +2), // aTop seconds (1c for PC, 1e for Mac)
PATCH_END
};
@@ -1214,6 +1218,26 @@ static const uint16 kq6CDPatchAudioTextSupportGirlInTheTower[] = {
PATCH_END
};
+// Fixes dual mode for scenes with Azure and Ariel (room 370)
+// Effectively same patch as the one for fixing "Girl In The Tower"
+// Applies to at least: PC-CD
+// Patched methods: rm370::init, caughtAtGateCD::changeState, caughtAtGateTXT::changeState, toLabyrinth::changeState
+// Fixes bug: #6750
+static const uint16 kq6CDSignatureAudioTextSupportAzureAriel[] = {
+ SIG_MAGICDWORD,
+ 0x89, 0x5a, // lsg global[5a]
+ 0x35, 0x02, // ldi 02
+ 0x1a, // eq?
+ 0x31, // bnt [jump-for-text-code]
+ SIG_END
+};
+
+static const uint16 kq6CDPatchAudioTextSupportAzureAriel[] = {
+ PATCH_ADDTOOFFSET(+4),
+ 0x12, // and
+ PATCH_END
+};
+
// Additional patch specifically for King's Quest 6
// Adds another button state for the text/audio button. We currently use the "speech" view for "dual" mode.
// View 947, loop 9, cel 0+1 -> "text"
@@ -1306,6 +1330,7 @@ static const SciScriptPatcherEntry kq6Signatures[] = {
{ false, 1009, "CD: audio + text support KQ6 Guards", 2, kq6CDSignatureAudioTextSupportGuards, kq6CDPatchAudioTextSupportGuards },
{ false, 1027, "CD: audio + text support KQ6 Stepmother", 1, kq6CDSignatureAudioTextSupportStepmother, kq6CDPatchAudioTextSupportJumpAlways },
{ false, 740, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower },
+ { false, 370, "CD: audio + text support KQ6 Azure & Ariel", 6, kq6CDSignatureAudioTextSupportAzureAriel, kq6CDPatchAudioTextSupportAzureAriel },
{ false, 903, "CD: audio + text support KQ6 menu", 1, kq6CDSignatureAudioTextMenuSupport, kq6CDPatchAudioTextMenuSupport },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -1400,6 +1425,37 @@ static const SciScriptPatcherEntry larry2Signatures[] = {
// ===========================================================================
// Leisure Suit Larry 5
+// In Miami the player can call the green card telephone number and get
+// green card including limo at the same time in the English 1.000 PC release.
+// This results later in a broken game in case the player doesn't read
+// the second telephone number for the actual limousine service, because
+// in that case it's impossible for the player to get back to the airport.
+//
+// We disable the code, that is responsible to make the limo arrive.
+//
+// This bug was fixed in the European (dual language) versions of the game.
+//
+// Applies to at least: English PC floppy (1.000)
+// Responsible method: sPhone::changeState(40)
+static const uint16 larry5SignatureGreenCardLimoBug[] = {
+ 0x7a, // push2
+ SIG_MAGICDWORD,
+ 0x39, 0x07, // pushi 07
+ 0x39, 0x0c, // pushi 0Ch
+ 0x45, 0x0a, 0x04, // call export 10 of script 0
+ 0x78, // push1
+ 0x39, 0x26, // pushi 26h (limo arrived flag)
+ 0x45, 0x07, 0x02, // call export 7 of script 0 (sets flag)
+ SIG_END
+};
+
+static const uint16 larry5PatchGreenCardLimoBug[] = {
+ PATCH_ADDTOOFFSET(+8),
+ 0x34, PATCH_UINT16(0), // ldi 0000 (dummy)
+ 0x34, PATCH_UINT16(0), // ldi 0000 (dummy)
+ PATCH_END
+};
+
// In one of the conversations near the end (to be exact - room 380 and the text
// about using champagne on Reverse Biaz - only used when you actually did that
// in the game), the German text is too large, causing the textbox to get too large.
@@ -1423,6 +1479,7 @@ static const uint16 larry5PatchGermanEndingPattiTalker[] = {
// script, description, signature patch
static const SciScriptPatcherEntry larry5Signatures[] = {
+ { true, 280, "English-only: fix green card limo bug", 1, larry5SignatureGreenCardLimoBug, larry5PatchGreenCardLimoBug },
{ true, 380, "German-only: Enlarge Patti Textbox", 1, larry5SignatureGermanEndingPattiTalker, larry5PatchGermanEndingPattiTalker },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -1917,6 +1974,43 @@ static const SciScriptPatcherEntry pq1vgaSignatures[] = {
};
// ===========================================================================
+// At the healer's house there is a bird's nest up on the tree.
+// The player can throw rocks at it until it falls to the ground.
+// The hero will then grab the item, that is in the nest.
+//
+// When running is active, the hero will not reach the actual destination
+// and because of that, the game will get stuck.
+//
+// We just change the coordinate of the destination slightly, so that walking,
+// sneaking and running work.
+//
+// This bug was fixed by Sierra at least in the Japanese PC-9801 version.
+// Applies to at least: English floppy (1.000, 1.012)
+// Responsible method: pickItUp::changeState (script 54)
+// Fixes bug: #6407
+static const uint16 qfg1egaSignatureThrowRockAtNest[] = {
+ 0x4a, 0x04, // send 04 (nest::x)
+ 0x36, // push
+ SIG_MAGICDWORD,
+ 0x35, 0x0f, // ldi 0f (15d)
+ 0x02, // add
+ 0x36, // push
+ SIG_END
+};
+
+static const uint16 qfg1egaPatchThrowRockAtNest[] = {
+ PATCH_ADDTOOFFSET(+3),
+ 0x35, 0x12, // ldi 12 (18d)
+ PATCH_END
+};
+
+// script, description, signature patch
+static const SciScriptPatcherEntry qfg1egaSignatures[] = {
+ { true, 54, "throw rock at nest while running", 1, qfg1egaSignatureThrowRockAtNest, qfg1egaPatchThrowRockAtNest },
+ 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
@@ -2029,21 +2123,30 @@ static const uint16 qfg1vgaPatchMoveToCrusher[] = {
// Same pathfinding bug as above, where Ego is set to move to an impossible
// spot when sneaking. In GuardsTrumpet::changeState, we change the final
-// location where Ego is moved from 111, 111 to 114, 114.
+// location where Ego is moved from 111, 111 to 116, 116.
+// target coordinate is really problematic here.
+//
+// 114, 114 works when the speed slider is all the way up, but doesn't work
+// when the speed slider is not.
+//
+// It seems that this bug was fixed by Sierra for the Macintosh version.
+//
+// Applies to at least: English PC floppy
+// Responsible method: GuardsTrumpet::changeState(8)
// Fixes bug: #6248
static const uint16 qfg1vgaSignatureMoveToCastleGate[] = {
+ 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo
SIG_MAGICDWORD,
- 0x51, 0x1f, // class MoveTo
0x36, // push
- 0x39, 0x6f, // pushi 6f (111 - x)
- 0x3c, // dup (111 - y)
+ 0x39, 0x6f, // pushi 6f (111d)
+ 0x3c, // dup (111d) - coordinates 111, 111
0x7c, // pushSelf
SIG_END
};
static const uint16 qfg1vgaPatchMoveToCastleGate[] = {
PATCH_ADDTOOFFSET(+3),
- 0x39, 0x72, // pushi 72 (114 - x)
+ 0x39, 0x74, // pushi 74 (116d), changes coordinates to 116, 116
PATCH_END
};
@@ -2058,7 +2161,7 @@ static const uint16 qfg1vgaSignatureCheetaurDescription[] = {
0x34, SIG_UINT16(0x01b8), // ldi 01b8
0x1a, // eq?
0x31, 0x16, // bnt 16
- 0x38, SIG_UINT16(0x0127), // pushi 0127
+ 0x38, SIG_SELECTOR16(say), // pushi 0127h (selector "say")
0x39, 0x06, // pushi 06
0x39, 0x03, // pushi 03
0x78, // push1
@@ -2082,6 +2185,7 @@ static const uint16 qfg1vgaPatchCheetaurDescription[] = {
// Local 5 of that room is a timer, that closes the door (object door11).
// Setting it to 1 during happyFace::changeState(0) stops door11::doit from
// calling goTo6::init, so the whole issue is stopped from happening.
+//
// Applies to at least: English floppy
// Responsible method: happyFace::changeState, door11::doit
// Fixes bug #6181
@@ -2108,20 +2212,172 @@ static const uint16 qfg1vgaPatchFunnyRoomFix[] = {
PATCH_END
};
+// The player is able to buy (and also steal) potions in the healer's hut
+// Strangely Sierra delays the actual buy/get potion code for 60 ticks
+// Why they did that is unknown. The code is triggered anyway only after
+// the relevant dialog boxes are closed.
+//
+// This delay causes problems in case the user quickly enters the inventory.
+// That's why we change the amount of ticks to 1, so that the remaining states
+// are executed right after the dialog boxes are closed.
+//
+// Applies to at least: English floppy
+// Responsible method: cueItScript::changeState
+// Fixes bug #6706
+static const uint16 qfg1vgaSignatureHealerHutNoDelay[] = {
+ 0x65, 0x14, // aTop 14 (state)
+ 0x36, // push
+ 0x3c, // dup
+ 0x35, 0x00, // ldi 00
+ 0x1a, // eq?
+ 0x31, 0x07, // bnt 07 [-> next state]
+ SIG_MAGICDWORD,
+ 0x35, 0x3c, // ldi 3c (60 ticks)
+ 0x65, 0x20, // aTop ticks
+ 0x32, // jmp [-> end of method]
+ SIG_END
+};
+
+static const uint16 qfg1vgaPatchHealerHutNoDelay[] = {
+ PATCH_ADDTOOFFSET(+9),
+ 0x35, 0x01, // ldi 01 (1 tick only, so that execution will resume as soon as dialog box is closed)
+ PATCH_END
+};
+
+// When following the white stag, you can actually enter the 2nd room from the mushroom/fairy location,
+// which results in ego entering from the top. When you then throw a dagger at the stag, one animation
+// frame will stay on screen, because of a script bug.
+//
+// Applies to at least: English floppy, Mac floppy
+// Responsible method: stagHurt::changeState
+// Fixes bug #6135
+static const uint16 qfg1vgaSignatureWhiteStagDagger[] = {
+ 0x87, 0x01, // lap param[1]
+ 0x65, 0x14, // aTop state
+ 0x36, // push
+ 0x3c, // dup
+ 0x35, 0x00, // ldi 0
+ 0x1a, // eq?
+ 0x31, 0x16, // bnt [next parameter check]
+ 0x76, // push0
+ 0x45, 0x02, 0x00, // callb export 2 from script 0, 0
+ SIG_MAGICDWORD,
+ 0x38, SIG_SELECTOR16(say), // pushi 0127h (selector "say")
+ 0x39, 0x05, // pushi 05
+ 0x39, 0x03, // pushi 03
+ 0x39, 0x51, // pushi 51h
+ 0x76, // push0
+ 0x76, // push0
+ 0x7c, // pushSelf
+ 0x81, 0x5b, // lag global[5Bh] -> qg1Messager
+ 0x4a, 0x0e, // send 0Eh -> qg1Messager::say(3, 51h, 0, 0, stagHurt)
+ 0x33, 0x12, // jmp -> [ret]
+ 0x3c, // dup
+ 0x35, 0x01, // ldi 1
+ 0x1a, // eq?
+ 0x31, 0x0c, // bnt [ret]
+ 0x38, // pushi...
+ SIG_ADDTOOFFSET(+11),
+ 0x3a, // toss
+ 0x48, // ret
+ SIG_END
+};
+
+static const uint16 qfg1vgaPatchWhiteStagDagger[] = {
+ PATCH_ADDTOOFFSET(+4),
+ 0x2f, 0x05, // bt [next check] (state != 0)
+ // state = 0 code
+ 0x35, 0x01, // ldi 1
+ 0x65, 0x1a, // aTop cycles
+ 0x48, // ret
+ 0x36, // push
+ 0x35, 0x01, // ldi 1
+ 0x1a, // eq?
+ 0x31, 0x16, // bnt [state = 2 code]
+ // state = 1 code
+ 0x76, // push0
+ 0x45, 0x02, 0x00, // callb export 2 from script 0, 0
+ 0x38, PATCH_SELECTOR16(say), // pushi 0127h (selector "say")
+ 0x39, 0x05, // pushi 05
+ 0x39, 0x03, // pushi 03
+ 0x39, 0x51, // pushi 51h
+ 0x76, // push0
+ 0x76, // push0
+ 0x7c, // pushSelf
+ 0x81, 0x5b, // lag global[5Bh] -> qg1Messager
+ 0x4a, 0x0e, // send 0Eh -> qg1Messager::say(3, 51h, 0, 0, stagHurt)
+ 0x48, // ret
+ // state = 2 code
+ PATCH_ADDTOOFFSET(+13),
+ 0x48, // ret (remove toss)
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry qfg1vgaSignatures[] = {
+ { true, 41, "moving to castle gate", 1, qfg1vgaSignatureMoveToCastleGate, qfg1vgaPatchMoveToCastleGate },
+ { true, 55, "healer's hut, no delay for buy/steal", 1, qfg1vgaSignatureHealerHutNoDelay, qfg1vgaPatchHealerHutNoDelay },
+ { true, 77, "white stag dagger throw animation glitch", 1, qfg1vgaSignatureWhiteStagDagger, qfg1vgaPatchWhiteStagDagger },
+ { true, 96, "funny room script bug fixed", 1, qfg1vgaSignatureFunnyRoomFix, qfg1vgaPatchFunnyRoomFix },
+ { true, 210, "cheetaur description fixed", 1, qfg1vgaSignatureCheetaurDescription, qfg1vgaPatchCheetaurDescription },
{ true, 215, "fight event issue", 1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents },
{ true, 216, "weapon master event issue", 1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents },
+ { true, 331, "moving to crusher", 1, qfg1vgaSignatureMoveToCrusher, qfg1vgaPatchMoveToCrusher },
{ true, 814, "window text temp space", 1, qfg1vgaSignatureTempSpace, qfg1vgaPatchTempSpace },
{ true, 814, "dialog header offset", 3, qfg1vgaSignatureDialogHeader, qfg1vgaPatchDialogHeader },
- { true, 331, "moving to crusher", 1, qfg1vgaSignatureMoveToCrusher, qfg1vgaPatchMoveToCrusher },
- { true, 41, "moving to castle gate", 1, qfg1vgaSignatureMoveToCastleGate, qfg1vgaPatchMoveToCastleGate },
- { true, 210, "cheetaur description fixed", 1, qfg1vgaSignatureCheetaurDescription, qfg1vgaPatchCheetaurDescription },
- { true, 96, "funny room script bug fixed", 1, qfg1vgaSignatureFunnyRoomFix, qfg1vgaPatchFunnyRoomFix },
SCI_SIGNATUREENTRY_TERMINATOR
};
// ===========================================================================
+
+// This is a very complicated bug.
+// When the player encounters an enemy in the desert while riding a saurus and later
+// tries to get back on it by entering "ride", the game will not give control back
+// to the player.
+//
+// This is caused by script mountSaurus getting triggered twice.
+// Once by entering the command "ride" and then a second time by a proximity check.
+//
+// Both are calling mountSaurus::init() in script 20, this one disables controls
+// then mountSaurus::changeState() from script 660 is triggered
+// mountSaurus::changeState(5) finally calls mountSaurus::dispose(), which is also in script 20
+// which finally re-enables controls
+//
+// A fix is difficult to implement. The code in script 20 is generic and used by multiple objects
+//
+// Originally I decided to change the responsible globals (66h and A1h) during mountSaurus::changeState(5).
+// This worked as far as for controls, but mountSaurus::init changes a few selectors of ego as well, which
+// won't get restored in that situation, which then messes up room changes and other things.
+//
+// I have now decided to change sheepScript::changeState(2) in script 665 instead.
+//
+// This fix could cause issues in case there is a cutscene, where ego is supposed to get onto the saurus using
+// sheepScript.
+//
+// Applies to at least: English PC Floppy, English Amiga Floppy
+// Responsible method: mountSaurus::changeState(), mountSaurus::init(), mountSaurus::dispose()
+// Fixes bug: #5156
+static const uint16 qfg2SignatureSaurusFreeze[] = {
+ 0x3c, // dup
+ 0x35, 0x02, // ldi 5
+ SIG_MAGICDWORD,
+ 0x1a, // eq?
+ 0x30, SIG_UINT16(0x0043), // bnt [ret]
+ 0x76, // push0
+ SIG_ADDTOOFFSET(+61), // skip to dispose code
+ 0x39, SIG_SELECTOR8(dispose), // pushi "dispose"
+ 0x76, // push0
+ 0x54, 0x04, // self 04
+ SIG_END
+};
+
+static const uint16 qfg2PatchSaurusFreeze[] = {
+ 0x81, 0x66, // lag 66h
+ 0x2e, SIG_UINT16(0x0040), // bt [to dispose code]
+ 0x35, 0x00, // ldi 0 (waste 2 bytes)
+ PATCH_END
+};
+
// Script 944 in QFG2 contains the FileSelector system class, used in the
// character import screen. This gets incorrectly called constantly, whenever
// the user clicks on a button in order to refresh the file list. This was
@@ -2153,9 +2409,58 @@ static const uint16 qfg2PatchImportDialog[] = {
PATCH_END
};
-// script, description, signature patch
+// Quest For Glory 2 character import doesn't properly set the character type
+// in versions 1.102 and below, which makes all importerted characters a fighter.
+//
+// Sierra released an official patch. However the fix is really easy to
+// implement on our side, so we also patch the flaw in here in case we find it.
+//
+// The version released on GOG is 1.102 without this patch applied, so us
+// patching it is quite useful.
+//
+// Applies to at least: English Floppy
+// Responsible method: importHero::changeState
+// Fixes bug: inside versions 1.102 and below
+static const uint16 qfg2SignatureImportCharType[] = {
+ 0x35, 0x04, // ldi 04
+ 0x90, SIG_UINT16(0x023b), // lagi global[23Bh]
+ 0x02, // add
+ 0x36, // push
+ 0x35, 0x04, // ldi 04
+ 0x08, // div
+ 0x36, // push
+ 0x35, 0x0d, // ldi 0D
+ 0xb0, SIG_UINT16(0x023b), // sagi global[023Bh]
+ 0x8b, 0x1f, // lsl local[1Fh]
+ 0x35, 0x05, // ldi 05
+ SIG_MAGICDWORD,
+ 0xb0, SIG_UINT16(0x0150), // sagi global[0150h]
+ 0x8b, 0x02, // lsl local[02h]
+ SIG_END
+};
+
+static const uint16 qfg2PatchImportCharType[] = {
+ 0x80, PATCH_UINT16(0x023f), // lag global[23Fh] <-- patched to save 2 bytes
+ 0x02, // add
+ 0x36, // push
+ 0x35, 0x04, // ldi 04
+ 0x08, // div
+ 0x36, // push
+ 0xa8, SIG_UINT16(0x0248), // ssg global[0248h] <-- patched to save 2 bytes
+ 0x8b, 0x1f, // lsl local[1Fh]
+ 0xa8, SIG_UINT16(0x0155), // ssg global[0155h] <-- patched to save 2 bytes
+ // new code, directly from the official sierra patch file
+ 0x83, 0x01, // lal local[01h]
+ 0xa1, 0xbb, // sag global[BBh]
+ 0xa1, 0x73, // sag global[73h]
+ PATCH_END
+};
+
+// script, description, signature patch
static const SciScriptPatcherEntry qfg2Signatures[] = {
- { true, 944, "import dialog continuous calls", 1, qfg2SignatureImportDialog, qfg2PatchImportDialog },
+ { true, 665, "getting back on saurus freeze fix", 1, qfg2SignatureSaurusFreeze, qfg2PatchSaurusFreeze },
+ { true, 805, "import character type fix", 1, qfg2SignatureImportCharType, qfg2PatchImportCharType },
+ { true, 944, "import dialog continuous calls", 1, qfg2SignatureImportDialog, qfg2PatchImportDialog },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -2224,10 +2529,158 @@ static const uint16 qfg3PatchWooDialog[] = {
PATCH_END
};
-// script, description, signature patch
+// Alternative version, with uint16 offsets, for GOG release of QfG3.
+static const uint16 qfg3SignatureWooDialogAlt[] = {
+ SIG_MAGICDWORD,
+ 0x67, 0x12, // pTos 12 (query)
+ 0x35, 0xb6, // ldi b6
+ 0x1a, // eq?
+ 0x2e, SIG_UINT16(0x0005), // bt 05
+ 0x67, 0x12, // pTos 12 (query)
+ 0x35, 0x9b, // ldi 9b
+ 0x1a, // eq?
+ 0x30, SIG_UINT16(0x000c), // bnt 0c
+ 0x38, SIG_SELECTOR16(solvePuzzle), // pushi 0297
+ 0x7a, // push2
+ 0x38, SIG_UINT16(0x010c), // pushi 010c
+ 0x7a, // push2
+ 0x81, 0x00, // lag 00
+ 0x4a, 0x08, // send 08
+ 0x67, 0x12, // pTos 12 (query)
+ 0x35, 0xb5, // ldi b5
+ SIG_END
+};
+
+static const uint16 qfg3PatchWooDialogAlt[] = {
+ PATCH_ADDTOOFFSET(+0x2C),
+ 0x33, 0x12, // jmp to 0x708, the call to hero::solvePuzzle for 0xFFFC
+ PATCH_END
+};
+
+// When exporting characters at the end of Quest for Glory 3, the underlying
+// code has issues with values, that are above 9999.
+// For further study: https://github.com/Blazingstix/QFGImporter/blob/master/QFGImporter/QFGImporter/QFG3.txt
+//
+// If a value is above 9999, parts or even the whole character file will get corrupted.
+//
+// We are fixing the code because of that. We are patching code, that is calculating the checksum
+// and add extra code to lower such values to 9999.
+//
+// Applies to at least: English, French, German, Italian, Spanish floppy
+// Responsible method: saveHero::changeState
+// Fixes bug #6807
+static const uint16 qfg3SignatureExportChar[] = {
+ 0x35, SIG_ADDTOOFFSET(+1), // ldi 00 / ldi 01 (2 loops, we patch both)
+ 0xa5, 0x00, // sat temp[0] [contains index to data]
+ 0x8d, 0x00, // lst temp[0]
+ SIG_MAGICDWORD,
+ 0x35, 0x2c, // ldi 2c
+ 0x22, // lt? [index above or equal 2Ch (44d)?
+ 0x31, 0x23, // bnt [exit loop]
+ // from this point it's actually useless code, maybe a sci compiler bug
+ 0x8d, 0x00, // lst temp[0]
+ 0x35, 0x01, // ldi 01
+ 0x02, // add
+ 0x9b, 0x00, // lsli local[0] ---------- load local[0 + ACC] onto stack
+ 0x8d, 0x00, // lst temp[0]
+ 0x35, 0x01, // ldi 01
+ 0x02, // add
+ 0xb3, 0x00, // sali local[0] ---------- save stack to local[0 + ACC]
+ // end of useless code
+ 0x8b, SIG_ADDTOOFFSET(+1), // lsl local[36h/37h] ---- load local[36h/37h] onto stack
+ 0x8d, 0x00, // lst temp[0]
+ 0x35, 0x01, // ldi 01
+ 0x02, // add
+ 0x93, 0x00, // lali local[0] ---------- load local[0 + ACC] into ACC
+ 0x02, // add -------------------- add ACC + stack and put into ACC
+ 0xa3, SIG_ADDTOOFFSET(+1), // sal local[36h/37h] ---- save ACC to local[36h/37h]
+ 0x8d, 0x00, // lst temp[0] ------------ temp[0] to stack
+ 0x35, 0x02, // ldi 02
+ 0x02, // add -------------------- add 2 to stack
+ 0xa5, 0x00, // sat temp[0] ------------ save ACC to temp[0]
+ 0x33, 0xd6, // jmp [loop]
+ SIG_END
+};
+
+static const uint16 qfg3PatchExportChar[] = {
+ PATCH_ADDTOOFFSET(+11),
+ 0x85, 0x00, // lat temp[0]
+ 0x9b, 0x01, // lsli local[0] + 1 ------ load local[ ACC + 1] onto stack
+ 0x3c, // dup
+ 0x34, PATCH_UINT16(0x2710), // ldi 2710h (10000d)
+ 0x2c, // ult? ------------------- is value smaller than 10000?
+ 0x2f, 0x0a, // bt [jump over]
+ 0x3a, // toss
+ 0x38, PATCH_UINT16(0x270f), // pushi 270fh (9999d)
+ 0x3c, // dup
+ 0x85, 0x00, // lat temp[0]
+ 0xba, PATCH_UINT16(0x0001), // ssli local[0] + 1 ------ save stack to local[ ACC + 1] (UINT16 to waste 1 byte)
+ // jump offset
+ 0x83, PATCH_GETORIGINALBYTE(+26), // lal local[37h/36h] ---- load local[37h/36h] into ACC
+ 0x02, // add -------------------- add local[37h/36h] + data value
+ PATCH_END
+};
+
+// Quest for Glory 3 doesn't properly import the character type of Quest for Glory 1 character files.
+// This issue was never addressed. It's caused by Sierra reading data directly from the local
+// area, which is only set by Quest For Glory 2 import data, instead of reading the properly set global variable.
+//
+// We fix it, by also directly setting the local variable.
+//
+// Applies to at least: English, French, German, Italian, Spanish floppy
+// Responsible method: importHero::changeState(4)
+static const uint16 qfg3SignatureImportQfG1Char[] = {
+ SIG_MAGICDWORD,
+ 0x82, SIG_UINT16(0x0238), // lal local[0x0238]
+ 0xa0, SIG_UINT16(0x016a), // sag global[0x016a]
+ 0xa1, 0x7d, // sag global[0x7d]
+ 0x35, 0x01, // ldi 01
+ 0x99, 0xfb, // lsgi global[0xfb]
+ SIG_END
+};
+
+static const uint16 qfg3PatchImportQfG1Char[] = {
+ PATCH_ADDTOOFFSET(+8),
+ 0xa3, 0x01, // sal 01 -> also set local[01]
+ 0x89, 0xfc, // lsg global[0xFD] -> save 2 bytes
+ PATCH_END
+};
+
+// The chief in his hut (room 640) is not drawn using the correct priority,
+// which results in a graphical glitch. This is a game bug and also happens
+// in Sierra's SCI. We adjust priority accordingly to fix it.
+//
+// Applies to at least: English, French, German, Italian, Spanish floppy
+// Responsible method: heap in script 640
+// Fixes bug #5173
+static const uint16 qfg3SignatureChiefPriority[] = {
+ SIG_MAGICDWORD,
+ SIG_UINT16(0x0002), // yStep 0x0002
+ SIG_UINT16(0x0281), // view 0x0281
+ SIG_UINT16(0x0000), // loop 0x0000
+ SIG_UINT16(0x0000), // cel 0x0000
+ SIG_UINT16(0x0000), // priority 0x0000
+ SIG_UINT16(0x0000), // underbits 0x0000
+ SIG_UINT16(0x1000), // signal 0x1000
+ SIG_END
+};
+
+static const uint16 qfg3PatchChiefPriority[] = {
+ PATCH_ADDTOOFFSET(+8),
+ PATCH_UINT16(0x000A), // new priority 0x000A (10d)
+ PATCH_ADDTOOFFSET(+2),
+ PATCH_UINT16(0x1010), // signal 0x1010 (set fixed priority flag)
+ PATCH_END
+};
+
+// script, description, signature patch
static const SciScriptPatcherEntry qfg3Signatures[] = {
- { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog },
- { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog },
+ { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog },
+ { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog },
+ { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialogAlt, qfg3PatchWooDialogAlt },
+ { true, 52, "export character save bug", 2, qfg3SignatureExportChar, qfg3PatchExportChar },
+ { true, 54, "import character from QfG1 bug", 1, qfg3SignatureImportQfG1Char, qfg3PatchImportQfG1Char },
+ { true, 640, "chief in hut priority fix", 1, qfg3SignatureChiefPriority, qfg3PatchChiefPriority },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -2287,6 +2740,102 @@ static const uint16 sq4FloppyPatchThrowStuffAtSequelPoliceBug[] = {
PATCH_END
};
+// Right at the start of Space Quest 4 CD, when walking up in the first room, ego will
+// immediately walk down just after entering the upper room.
+//
+// This is caused by the scripts setting ego's vertical coordinate to 189 (BDh), which is the
+// trigger in rooms to walk to the room below it. Sometimes this isn't triggered, because
+// the scripts also initiate a motion to vertical coordinate 188 (BCh). When you lower the game's speed,
+// this bug normally always triggers. And it triggers of course also in the original interpreter.
+//
+// It doesn't happen in PC floppy, because nsRect is not the same as in CD.
+//
+// We fix it by setting ego's vertical coordinate to 188 and we also initiate a motion to 187.
+//
+// Applies to at least: English PC CD
+// Responsible method: rm045::doit
+// Fixes bug: #5468
+static const uint16 sq4CdSignatureWalkInFromBelowRoom45[] = {
+ 0x76, // push0
+ SIG_MAGICDWORD,
+ 0x78, // push1
+ 0x38, SIG_UINT16(0x00bd), // pushi 00BDh
+ 0x38, SIG_ADDTOOFFSET(+2), // pushi [setMotion selector]
+ 0x39, 0x03, // pushi 3
+ 0x51, SIG_ADDTOOFFSET(+1), // class [MoveTo]
+ 0x36, // push
+ 0x78, // push1
+ 0x76, // push0
+ 0x81, 0x00, // lag global[0]
+ 0x4a, 0x04, // send 04 -> get ego::x
+ 0x36, // push
+ 0x38, SIG_UINT16(0x00bc), // pushi 00BCh
+ SIG_END
+};
+
+static const uint16 sq4CdPatchWalkInFromBelowRoom45[] = {
+ PATCH_ADDTOOFFSET(+2),
+ 0x38, PATCH_UINT16(0x00bc), // pushi 00BCh
+ PATCH_ADDTOOFFSET(+15),
+ 0x38, PATCH_UINT16(0x00bb), // pushi 00BBh
+ PATCH_END
+};
+
+// It seems that Sierra forgot to set a script flag, when cleaning out the bank account
+// in Space Quest 4 CD. This was probably caused by the whole bank account interaction
+// getting a rewrite and polish in the CD version.
+//
+// Because of this bug, points for changing back clothes will not get awarded, which
+// makes it impossible to get a perfect point score in the CD version of the game.
+// The points are awarded by rm371::doit in script 371.
+//
+// We fix this. Bug also happened, when using the original interpreter.
+// Bug does not happen for PC floppy.
+//
+// Attention: Some Let's Plays on youtube show that points are in fact awarded. Which is true.
+// But those Let's Plays were actually created by playing a hacked Space Quest 4 version
+// (which is part Floppy, part CD version - we consider it to be effectively pirated)
+// and not the actual CD version of Space Quest 4.
+// It's easy to identify - talkie + store called "Radio Shack" -> is hacked version.
+//
+// Applies to at least: English PC CD
+// Responsible method: but2Script::changeState(2)
+// Fixes bug: #6866
+static const uint16 sq4CdSignatureGetPointsForChangingBackClothes[] = {
+ 0x35, 0x02, // ldi 02
+ SIG_MAGICDWORD,
+ 0x1a, // eq?
+ 0x30, SIG_UINT16(0x006a), // bnt [state 3]
+ 0x76,
+ SIG_ADDTOOFFSET(+46), // jump over "withdraw funds" code
+ 0x33, 0x33, // jmp [end of state 2, set cycles code]
+ SIG_ADDTOOFFSET(+51), // jump over "clean bank account" code
+ 0x35, 0x02, // ldi 02
+ 0x65, 0x1a, // aTop cycles
+ 0x33, 0x0b, // jmp [toss/ret]
+ 0x3c, // dup
+ 0x35, 0x03, // ldi 03
+ 0x1a, // eq?
+ 0x31, 0x05, // bnt [toss/ret]
+ SIG_END
+};
+
+static const uint16 sq4CdPatchGetPointsForChangingBackClothes[] = {
+ PATCH_ADDTOOFFSET(+3),
+ 0x30, PATCH_UINT16(0x0070), // bnt [state 3]
+ PATCH_ADDTOOFFSET(+47), // "withdraw funds" code
+ 0x33, 0x39, // jmp [end of state 2, set cycles code]
+ PATCH_ADDTOOFFSET(+51),
+ 0x78, // push1
+ 0x39, 0x1d, // ldi 1Dh
+ 0x45, 0x07, 0x02, // call export 7 of script 0 (set flag) -> effectively sets global 73h, bit 2
+ 0x35, 0x02, // ldi 02
+ 0x65, 0x1c, // aTop cycles
+ 0x33, 0x05, // jmp [toss/ret]
+ // check for state 3 code removed to save 6 bytes
+ PATCH_END
+};
+
// The scripts in SQ4CD support simultaneous playing of speech and subtitles,
// but this was not available as an option. The following two patches enable
// this functionality in the game's GUI options dialog.
@@ -2381,8 +2930,10 @@ static const uint16 sq4CdPatchTextOptions[] = {
static const SciScriptPatcherEntry sq4Signatures[] = {
{ true, 298, "Floppy: endless flight", 1, sq4FloppySignatureEndlessFlight, sq4FloppyPatchEndlessFlight },
{ true, 700, "Floppy: throw stuff at sequel police bug", 1, sq4FloppySignatureThrowStuffAtSequelPoliceBug, sq4FloppyPatchThrowStuffAtSequelPoliceBug },
- { true, 818, "CD: Speech and subtitles option", 1, sq4CdSignatureTextOptions, sq4CdPatchTextOptions },
+ { true, 45, "CD: walk in from below for room 45 fix", 1, sq4CdSignatureWalkInFromBelowRoom45, sq4CdPatchWalkInFromBelowRoom45 },
+ { true, 396, "CD: get points for changing back clothes fix",1, sq4CdSignatureGetPointsForChangingBackClothes, sq4CdPatchGetPointsForChangingBackClothes },
{ true, 0, "CD: Babble icon speech and subtitles fix", 1, sq4CdSignatureBabbleIcon, sq4CdPatchBabbleIcon },
+ { true, 818, "CD: Speech and subtitles option", 1, sq4CdSignatureTextOptions, sq4CdPatchTextOptions },
{ true, 818, "CD: Speech and subtitles option button", 1, sq4CdSignatureTextOptionsButton, sq4CdPatchTextOptionsButton },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -2413,6 +2964,29 @@ static const uint16 sq1vgaPatchUlenceFlatsTimepodGfxGlitch[] = {
PATCH_END
};
+// In Ulence Flats, there is a space ship, that you will use at some point.
+// Near that space ship are 2 force field generators.
+// When you look at the top of those generators, the game will crash.
+// This happens also in Sierra SCI. It's caused by a jump, that goes out of bounds.
+// We currently do not know if this was caused by a compiler glitch or if it was a developer error.
+// Anyway we patch this glitchy code, so that the game won't crash anymore.
+//
+// Applies to at least: English Floppy
+// Responsible method: radar1::doVerb
+// Fixes bug: #6816
+static const uint16 sq1vgaSignatureUlenceFlatsGeneratorGlitch[] = {
+ SIG_MAGICDWORD, 0x1a, // eq?
+ 0x30, SIG_UINT16(0xcdf4), // bnt absolute 0xf000
+ SIG_END
+};
+
+static const uint16 sq1vgaPatchUlenceFlatsGeneratorGlitch[] = {
+ PATCH_ADDTOOFFSET(+1),
+ 0x32, PATCH_UINT16(0x0000), // jmp 0x0000 (waste bytes)
+ PATCH_END
+};
+
+// No documentation for this patch (TODO)
static const uint16 sq1vgaSignatureEgoShowsCard[] = {
SIG_MAGICDWORD,
0x38, SIG_SELECTOR16(timesShownID), // push "timesShownID"
@@ -2537,6 +3111,7 @@ static const uint16 sq1vgaPatchSpiderDroidTiming[] = {
// script, description, signature patch
static const SciScriptPatcherEntry sq1vgaSignatures[] = {
{ true, 45, "Ulence Flats: timepod graphic glitch", 1, sq1vgaSignatureUlenceFlatsTimepodGfxGlitch, sq1vgaPatchUlenceFlatsTimepodGfxGlitch },
+ { true, 45, "Ulence Flats: force field generator glitch", 1, sq1vgaSignatureUlenceFlatsGeneratorGlitch, sq1vgaPatchUlenceFlatsGeneratorGlitch },
{ true, 58, "Sarien armory droid zapping ego first time", 1, sq1vgaSignatureEgoShowsCard, sq1vgaPatchEgoShowsCard },
{ true, 704, "spider droid timing issue", 1, sq1vgaSignatureSpiderDroidTiming, sq1vgaPatchSpiderDroidTiming },
SCI_SIGNATUREENTRY_TERMINATOR
@@ -2612,6 +3187,7 @@ ScriptPatcher::ScriptPatcher() {
_selectorIdTable[selectorNr] = -1;
_runtimeTable = NULL;
+ _isMacSci11 = false;
}
ScriptPatcher::~ScriptPatcher() {
@@ -2620,7 +3196,7 @@ ScriptPatcher::~ScriptPatcher() {
}
// will actually patch previously found signature area
-void ScriptPatcher::applyPatch(const SciScriptPatcherEntry *patchEntry, byte *scriptData, const uint32 scriptSize, int32 signatureOffset, const bool isMacSci11) {
+void ScriptPatcher::applyPatch(const SciScriptPatcherEntry *patchEntry, byte *scriptData, const uint32 scriptSize, int32 signatureOffset) {
const uint16 *patchData = patchEntry->patchData;
byte orgData[PATCH_VALUELIMIT];
int32 offset = signatureOffset;
@@ -2684,7 +3260,7 @@ void ScriptPatcher::applyPatch(const SciScriptPatcherEntry *patchEntry, byte *sc
default:
byte1 = 0; byte2 = 0;
}
- if (!isMacSci11) {
+ if (!_isMacSci11) {
scriptData[offset++] = byte1;
scriptData[offset++] = byte2;
} else {
@@ -2711,8 +3287,94 @@ void ScriptPatcher::applyPatch(const SciScriptPatcherEntry *patchEntry, byte *sc
}
}
+bool ScriptPatcher::verifySignature(uint32 byteOffset, const uint16 *signatureData, const char *signatureDescription, const byte *scriptData, const uint32 scriptSize) {
+ uint16 sigSelector = 0;
+
+ uint16 sigWord = *signatureData;
+ while (sigWord != SIG_END) {
+ uint16 sigCommand = sigWord & SIG_COMMANDMASK;
+ uint16 sigValue = sigWord & SIG_VALUEMASK;
+ switch (sigCommand) {
+ case SIG_CODE_ADDTOOFFSET: {
+ // add value to offset
+ byteOffset += sigValue;
+ break;
+ }
+ case SIG_CODE_UINT16:
+ case SIG_CODE_SELECTOR16: {
+ if ((byteOffset + 1) < scriptSize) {
+ byte byte1;
+ byte byte2;
+
+ switch (sigCommand) {
+ case SIG_CODE_UINT16: {
+ byte1 = sigValue & SIG_BYTEMASK;
+ signatureData++; sigWord = *signatureData;
+ if (sigWord & SIG_COMMANDMASK)
+ error("Script-Patcher: signature inconsistent\nFaulty signature: '%s'", signatureDescription);
+ byte2 = sigWord & SIG_BYTEMASK;
+ break;
+ }
+ case SIG_CODE_SELECTOR16: {
+ sigSelector = _selectorIdTable[sigValue];
+ byte1 = sigSelector & 0xFF;
+ byte2 = sigSelector >> 8;
+ break;
+ }
+ default:
+ byte1 = 0; byte2 = 0;
+ }
+ if (!_isMacSci11) {
+ if ((scriptData[byteOffset] != byte1) || (scriptData[byteOffset + 1] != byte2))
+ sigWord = SIG_MISMATCH;
+ } else {
+ // SCI1.1+ on macintosh had uint16s in script in BE-order
+ if ((scriptData[byteOffset] != byte2) || (scriptData[byteOffset + 1] != byte1))
+ sigWord = SIG_MISMATCH;
+ }
+ byteOffset += 2;
+ } else {
+ sigWord = SIG_MISMATCH;
+ }
+ break;
+ }
+ case SIG_CODE_SELECTOR8: {
+ if (byteOffset < scriptSize) {
+ sigSelector = _selectorIdTable[sigValue];
+ if (sigSelector & 0xFF00)
+ error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty signature: '%s'", signatureDescription);
+ if (scriptData[byteOffset] != (sigSelector & 0xFF))
+ sigWord = SIG_MISMATCH;
+ byteOffset++;
+ } else {
+ sigWord = SIG_MISMATCH; // out of bounds
+ }
+ break;
+ }
+ case SIG_CODE_BYTE:
+ if (byteOffset < scriptSize) {
+ if (scriptData[byteOffset] != sigWord)
+ sigWord = SIG_MISMATCH;
+ byteOffset++;
+ } else {
+ sigWord = SIG_MISMATCH; // out of bounds
+ }
+ }
+
+ if (sigWord == SIG_MISMATCH)
+ break;
+
+ signatureData++;
+ sigWord = *signatureData;
+ }
+
+ if (sigWord == SIG_END) // signature fully matched?
+ return true;
+ return false;
+}
+
// will return -1 if no match was found, otherwise an offset to the start of the signature match
-int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize, const bool isMacSci11) {
+int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize) {
if (scriptSize < 4) // we need to find a DWORD, so less than 4 bytes is not okay
return -1;
@@ -2724,89 +3386,8 @@ int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciS
if (magicDWord == READ_UINT32(scriptData + DWordOffset)) {
// magic DWORD found, check if actual signature matches
uint32 offset = DWordOffset + runtimeEntry->magicOffset;
- uint32 byteOffset = offset;
- const uint16 *signatureData = patchEntry->signatureData;
- uint16 sigSelector = 0;
-
- uint16 sigWord = *signatureData;
- while (sigWord != SIG_END) {
- uint16 sigCommand = sigWord & SIG_COMMANDMASK;
- uint16 sigValue = sigWord & SIG_VALUEMASK;
- switch (sigCommand) {
- case SIG_CODE_ADDTOOFFSET: {
- // add value to offset
- byteOffset += sigValue;
- break;
- }
- case SIG_CODE_UINT16:
- case SIG_CODE_SELECTOR16: {
- if ((byteOffset + 1) < scriptSize) {
- byte byte1;
- byte byte2;
-
- switch (sigCommand) {
- case SIG_CODE_UINT16: {
- byte1 = sigValue & SIG_BYTEMASK;
- signatureData++; sigWord = *signatureData;
- if (sigWord & SIG_COMMANDMASK)
- error("Script-Patcher: signature inconsistent\nFaulty patch: '%s'", patchEntry->description);
- byte2 = sigWord & SIG_BYTEMASK;
- break;
- }
- case SIG_CODE_SELECTOR16: {
- sigSelector = _selectorIdTable[sigValue];
- byte1 = sigSelector & 0xFF;
- byte2 = sigSelector >> 8;
- break;
- }
- default:
- byte1 = 0; byte2 = 0;
- }
- if (!isMacSci11) {
- if ((scriptData[byteOffset] != byte1) || (scriptData[byteOffset + 1] != byte2))
- sigWord = SIG_MISMATCH;
- } else {
- // SCI1.1+ on macintosh had uint16s in script in BE-order
- if ((scriptData[byteOffset] != byte2) || (scriptData[byteOffset + 1] != byte1))
- sigWord = SIG_MISMATCH;
- }
- byteOffset += 2;
- } else {
- sigWord = SIG_MISMATCH;
- }
- break;
- }
- case SIG_CODE_SELECTOR8: {
- if (byteOffset < scriptSize) {
- sigSelector = _selectorIdTable[sigValue];
- if (sigSelector & 0xFF00)
- error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty patch: '%s'", patchEntry->description);
- if (scriptData[byteOffset] != (sigSelector & 0xFF))
- sigWord = SIG_MISMATCH;
- byteOffset++;
- } else {
- sigWord = SIG_MISMATCH; // out of bounds
- }
- break;
- }
- case SIG_CODE_BYTE:
- if (byteOffset < scriptSize) {
- if (scriptData[byteOffset] != sigWord)
- sigWord = SIG_MISMATCH;
- byteOffset++;
- } else {
- sigWord = SIG_MISMATCH; // out of bounds
- }
- }
- if (sigWord == SIG_MISMATCH)
- break;
-
- signatureData++;
- sigWord = *signatureData;
- }
-
- if (sigWord == SIG_END) // signature fully matched?
+ if (verifySignature(offset, patchEntry->signatureData, patchEntry->description, scriptData, scriptSize))
return offset;
}
DWordOffset++;
@@ -2817,7 +3398,7 @@ int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciS
// This method calculates the magic DWORD for each entry in the signature table
// and it also initializes the selector table for selectors used in the signatures/patches of the current game
-void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable, bool isMacSci11) {
+void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) {
const SciScriptPatcherEntry *curEntry = patchTable;
SciScriptPatcherRuntimeEntry *curRuntimeEntry;
Selector curSelector = -1;
@@ -2885,7 +3466,7 @@ void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable, bool
curData++; curWord = *curData;
if (curWord & SIG_COMMANDMASK)
error("Script-Patcher: signature entry inconsistent\nFaulty patch: '%s'", curEntry->description);
- if (!isMacSci11) {
+ if (!_isMacSci11) {
byte1 = curValue;
byte2 = curWord & SIG_BYTEMASK;
} else {
@@ -2900,7 +3481,7 @@ void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable, bool
curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]);
_selectorIdTable[curValue] = curSelector;
}
- if (!isMacSci11) {
+ if (!_isMacSci11) {
byte1 = curSelector & 0x00FF;
byte2 = curSelector >> 8;
} else {
@@ -3036,6 +3617,9 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
case GID_PQ1:
signatureTable = pq1vgaSignatures;
break;
+ case GID_QFG1:
+ signatureTable = qfg1egaSignatures;
+ break;
case GID_QFG1VGA:
signatureTable = qfg1vgaSignatures;
break;
@@ -3059,7 +3643,7 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
}
if (signatureTable) {
- bool isMacSci11 = (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1);
+ _isMacSci11 = (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1);
if (!_runtimeTable) {
// Abort, in case selectors are not yet initialized (happens for games w/o selector-dictionary)
@@ -3067,7 +3651,7 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
return;
// signature table needs to get initialized (Magic DWORD set, selector table set)
- initSignature(signatureTable, isMacSci11);
+ initSignature(signatureTable);
// Do additional game-specific initialization
switch (gameId) {
@@ -3102,11 +3686,11 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
int32 foundOffset = 0;
int16 applyCount = curEntry->applyCount;
do {
- foundOffset = findSignature(curEntry, curRuntimeEntry, scriptData, scriptSize, isMacSci11);
+ foundOffset = findSignature(curEntry, curRuntimeEntry, scriptData, scriptSize);
if (foundOffset != -1) {
// found, so apply the patch
debugC(kDebugLevelScriptPatcher, "Script-Patcher: '%s' on script %d offset %d", curEntry->description, scriptNr, foundOffset);
- applyPatch(curEntry, scriptData, scriptSize, foundOffset, isMacSci11);
+ applyPatch(curEntry, scriptData, scriptSize, foundOffset);
}
applyCount--;
} while ((foundOffset != -1) && (applyCount));
diff --git a/engines/sci/engine/script_patches.h b/engines/sci/engine/script_patches.h
index 0b35792949..d15fce321b 100644
--- a/engines/sci/engine/script_patches.h
+++ b/engines/sci/engine/script_patches.h
@@ -74,7 +74,6 @@ struct SciScriptPatcherEntry {
const uint16 *patchData;
};
-//#define SCI_SIGNATUREENTRY_TERMINATOR { false, 0, NULL, 0, 0, 0, NULL, NULL }
#define SCI_SIGNATUREENTRY_TERMINATOR { false, 0, NULL, 0, NULL, NULL }
struct SciScriptPatcherRuntimeEntry {
@@ -92,15 +91,17 @@ public:
~ScriptPatcher();
void processScript(uint16 scriptNr, byte *scriptData, const uint32 scriptSize);
+ bool verifySignature(uint32 byteOffset, const uint16 *signatureData, const char *signatureDescription, const byte *scriptData, const uint32 scriptSize);
private:
- void initSignature(const SciScriptPatcherEntry *patchTable, bool isMacSci11);
+ void initSignature(const SciScriptPatcherEntry *patchTable);
void enablePatch(const SciScriptPatcherEntry *patchTable, const char *searchDescription);
- int32 findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize, bool isMacSci11);
- void applyPatch(const SciScriptPatcherEntry *patchEntry, byte *scriptData, const uint32 scriptSize, int32 signatureOffset, bool isMacSci11);
-
+ int32 findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize);
+ void applyPatch(const SciScriptPatcherEntry *patchEntry, byte *scriptData, const uint32 scriptSize, int32 signatureOffset);
+
Selector *_selectorIdTable;
SciScriptPatcherRuntimeEntry *_runtimeTable;
+ bool _isMacSci11;
};
} // End of namespace Sci
diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index 7701822f6d..417d98e2e2 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -92,6 +92,10 @@ void EngineState::reset(bool isRestoring) {
abortScriptProcessing = kAbortNone;
}
+ // reset delayed restore game functionality
+ _delayedRestoreGame = false;
+ _delayedRestoreGameId = 0;
+
executionStackBase = 0;
_executionStackPosChanged = false;
stack_base = 0;
@@ -203,59 +207,94 @@ static kLanguage charToLanguage(const char c) {
}
}
-Common::String SciEngine::getSciLanguageString(const Common::String &str, kLanguage lang, kLanguage *lang2) const {
- kLanguage secondLang = K_LANG_NONE;
-
- const char *seeker = str.c_str();
- while (*seeker) {
- if ((*seeker == '%') || (*seeker == '#')) {
- secondLang = charToLanguage(*(seeker + 1));
-
- if (secondLang != K_LANG_NONE)
+Common::String SciEngine::getSciLanguageString(const Common::String &str, kLanguage requestedLanguage, kLanguage *secondaryLanguage, uint16 *languageSplitter) const {
+ kLanguage foundLanguage = K_LANG_NONE;
+ const byte *textPtr = (const byte *)str.c_str();
+ byte curChar = 0;
+ byte curChar2 = 0;
+
+ while (1) {
+ curChar = *textPtr;
+ if (!curChar)
+ break;
+
+ if ((curChar == '%') || (curChar == '#')) {
+ curChar2 = *(textPtr + 1);
+ foundLanguage = charToLanguage(curChar2);
+
+ if (foundLanguage != K_LANG_NONE) {
+ // Return language splitter
+ if (languageSplitter)
+ *languageSplitter = curChar | ( curChar2 << 8 );
+ // Return the secondary language found in the string
+ if (secondaryLanguage)
+ *secondaryLanguage = foundLanguage;
break;
+ }
}
-
- ++seeker;
+ textPtr++;
}
- // Return the secondary language found in the string
- if (lang2)
- *lang2 = secondLang;
-
- if (secondLang == lang) {
- if (*(++seeker) == 'J') {
+ if (foundLanguage == requestedLanguage) {
+ if (curChar2 == 'J') {
// Japanese including Kanji, displayed with system font
// Convert half-width characters to full-width equivalents
Common::String fullWidth;
- byte c;
+ uint16 mappedChar;
+
+ textPtr += 2; // skip over language splitter
+
+ while (1) {
+ curChar = *textPtr;
+
+ switch (curChar) {
+ case 0: // Terminator NUL
+ return fullWidth;
+ case '\\':
+ // "\n", "\N", "\r" and "\R" were overwritten with SPACE + 0x0D in PC-9801 SSCI
+ // inside GetLongest() (text16). We do it here, because it's much cleaner and
+ // we have to process the text here anyway.
+ // Occurs for example in Police Quest 2 intro
+ curChar2 = *(textPtr + 1);
+ switch (curChar2) {
+ case 'n':
+ case 'N':
+ case 'r':
+ case 'R':
+ fullWidth += ' ';
+ fullWidth += 0x0D; // CR
+ textPtr += 2;
+ continue;
+ }
+ }
+
+ textPtr++;
- while ((c = *(++seeker))) {
- uint16 mappedChar = s_halfWidthSJISMap[c];
+ mappedChar = s_halfWidthSJISMap[curChar];
if (mappedChar) {
fullWidth += mappedChar >> 8;
fullWidth += mappedChar & 0xFF;
} else {
// Copy double-byte character
- char c2 = *(++seeker);
- if (!c2) {
- error("SJIS character %02X is missing second byte", c);
+ curChar2 = *(textPtr++);
+ if (!curChar) {
+ error("SJIS character %02X is missing second byte", curChar);
break;
}
- fullWidth += c;
- fullWidth += c2;
+ fullWidth += curChar;
+ fullWidth += curChar2;
}
}
- return fullWidth;
} else {
- return Common::String(seeker + 1);
+ return Common::String((const char *)(textPtr + 2));
}
}
- if (*seeker)
- return Common::String(str.c_str(), seeker - str.c_str());
- else
- return str;
+ if (curChar)
+ return Common::String(str.c_str(), (const char *)textPtr - str.c_str());
+
+ return str;
}
kLanguage SciEngine::getSciLanguage() {
@@ -314,25 +353,25 @@ void SciEngine::setSciLanguage() {
setSciLanguage(getSciLanguage());
}
-Common::String SciEngine::strSplit(const char *str, const char *sep) {
- kLanguage lang = getSciLanguage();
- kLanguage subLang = K_LANG_NONE;
+Common::String SciEngine::strSplitLanguage(const char *str, uint16 *languageSplitter, const char *sep) {
+ kLanguage activeLanguage = getSciLanguage();
+ kLanguage subtitleLanguage = K_LANG_NONE;
if (SELECTOR(subtitleLang) != -1)
- subLang = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(subtitleLang));
+ subtitleLanguage = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(subtitleLang));
- kLanguage secondLang;
- Common::String retval = getSciLanguageString(str, lang, &secondLang);
+ kLanguage foundLanguage;
+ Common::String retval = getSciLanguageString(str, activeLanguage, &foundLanguage, languageSplitter);
// Don't add subtitle when separator is not set, subtitle language is not set, or
// string contains only one language
- if ((sep == NULL) || (subLang == K_LANG_NONE) || (secondLang == K_LANG_NONE))
+ if ((sep == NULL) || (subtitleLanguage == K_LANG_NONE) || (foundLanguage == K_LANG_NONE))
return retval;
// Add subtitle, unless the subtitle language doesn't match the languages in the string
- if ((subLang == K_LANG_ENGLISH) || (subLang == secondLang)) {
+ if ((subtitleLanguage == K_LANG_ENGLISH) || (subtitleLanguage == foundLanguage)) {
retval += sep;
- retval += getSciLanguageString(str, subLang);
+ retval += getSciLanguageString(str, subtitleLanguage);
}
return retval;
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index ecc8cb7dfe..e7499e66c9 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -131,6 +131,10 @@ public:
VirtualIndexFile *_virtualIndexFile;
#endif
+ // see detection.cpp / SciEngine::loadGameState()
+ bool _delayedRestoreGame; // boolean, that triggers delayed restore (triggered by ScummVM menu)
+ int _delayedRestoreGameId; // the saved game id, that it supposed to get restored (triggered by ScummVM menu)
+
uint _chosenQfGImportItem; // Remembers the item selected in QfG import rooms
bool _cursorWorkaroundActive; // Refer to GfxCursor::setPosition()
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 06858540ec..6f02c96de8 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -358,12 +358,15 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
SciTrackOriginReply originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelCall.workarounds, &originReply);
switch (solution.type) {
- case WORKAROUND_NONE:
- kernel->signatureDebug(kernelCall.signature, argc, argv);
- error("[VM] k%s[%x]: signature mismatch via method %s::%s (room %d, script %d, localCall 0x%x)",
+ case WORKAROUND_NONE: {
+ Common::String signatureDetailsStr;
+ kernel->signatureDebug(signatureDetailsStr, kernelCall.signature, argc, argv);
+ error("\n%s[VM] k%s[%x]: signature mismatch in method %s::%s (room %d, script %d, localCall 0x%x)",
+ signatureDetailsStr.c_str(),
kernelCall.name, kernelCallNr, originReply.objectName.c_str(), originReply.methodName.c_str(),
s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset);
break;
+ }
case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone
return;
case WORKAROUND_STILLCALL: // call kernel anyway
@@ -408,15 +411,18 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelSubCall.workarounds, &originReply);
switch (solution.type) {
case WORKAROUND_NONE: {
- kernel->signatureDebug(kernelSubCall.signature, argc, argv);
+ Common::String signatureDetailsStr;
+ kernel->signatureDebug(signatureDetailsStr, kernelSubCall.signature, argc, argv);
int callNameLen = strlen(kernelCall.name);
if (strncmp(kernelCall.name, kernelSubCall.name, callNameLen) == 0) {
const char *subCallName = kernelSubCall.name + callNameLen;
- error("[VM] k%s(%s): signature mismatch via method %s::%s (room %d, script %d, localCall %x)",
+ error("\n%s[VM] k%s(%s): signature mismatch in method %s::%s (room %d, script %d, localCall %x)",
+ signatureDetailsStr.c_str(),
kernelCall.name, subCallName, originReply.objectName.c_str(), originReply.methodName.c_str(),
s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset);
}
- error("[VM] k%s: signature mismatch via method %s::%s (room %d, script %d, localCall %x)",
+ error("\n%s[VM] k%s: signature mismatch in method %s::%s (room %d, script %d, localCall %x)",
+ signatureDetailsStr.c_str(),
kernelSubCall.name, originReply.objectName.c_str(), originReply.methodName.c_str(),
s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset);
break;
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index ea4dc2fe71..aab32032f7 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -24,425 +24,716 @@
#include "sci/engine/object.h"
#include "sci/engine/state.h"
#include "sci/engine/vm.h"
+#include "sci/engine/script_patches.h"
#include "sci/engine/workarounds.h"
-#define SCI_WORKAROUNDENTRY_TERMINATOR { (SciGameId)0, -1, -1, 0, NULL, NULL, -1, 0, { WORKAROUND_NONE, 0 } }
+#define SCI_WORKAROUNDENTRY_TERMINATOR { (SciGameId)0, -1, -1, 0, NULL, NULL, NULL, 0, { WORKAROUND_NONE, 0 } }
namespace Sci {
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// Attention:
+// To identify local-call-subroutines code signatures are used.
+// Offsets change a lot between different versions of games, especially between different language releases.
+// That's why it isn't good to hardcode the offsets of those subroutines.
+//
+// Those signatures are just like the script patcher signatures (for further study: engine\script_patches.cpp)
+// However you may NOT use command SIG_SELECTOR8 nor SIG_SELECTOR16 atm. Proper support for those may be added later.
+
+// Game: Conquests of Camelot
+// Calling method: endingCartoon2::changeState
+// Subroutine offset: English 0x020d (script 92)
+// Applies to at least: English PC floppy
+static const uint16 sig_arithmetic_camelot_1[] = {
+ 0x83, 0x32, // lal local[32h]
+ 0x30, SIG_UINT16(0x001d), // bnt [...]
+ 0x7a, // push2
+ 0x39, 0x08, // pushi 08
+ 0x36, // push
+ 0x43, // callk Graph
+ SIG_END
+};
+
+// Game: Eco Quest 2
+// Calling method: Rain::points
+// Subroutine offset: English 0x0cc6, French/Spanish 0x0ce0 (script 0)
+// Applies to at least: English/French/Spanish PC floppy
+static const uint16 sig_arithmetic_ecoq2_1[] = {
+ 0x8f, 0x01, // lsp param[1]
+ 0x35, 0x10, // ldi 10h
+ 0x08, // div
+ 0x99, 0x6e, // lsgi global[6Eh]
+ 0x38, SIG_UINT16(0x8000), // pushi 8000h
+ 0x8f, 0x01, // lsp param[1]
+ 0x35, 0x10, // ldi 10h
+ 0x0a, // mod
+ 0x0c, // shr
+ 0x14, // or
+ 0x36, // push
+ SIG_END
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry arithmeticWorkarounds[] = {
- { GID_CAMELOT, 92, 92, 0, "endingCartoon2", "changeState", 0x20d, 0, { WORKAROUND_FAKE, 0 } }, // op_lai: during the ending, sub gets called with no parameters, uses parameter 1 which is theGrail in this case - bug #5237
- { GID_ECOQUEST2, 100, 0, 0, "Rain", "points", 0xcc6, 0, { WORKAROUND_FAKE, 0 } }, // op_or: when giving the papers to the customs officer, gets called against a pointer instead of a number - bug #4939
- { GID_ECOQUEST2, 100, 0, 0, "Rain", "points", 0xce0, 0, { WORKAROUND_FAKE, 0 } }, // Same as above, for the Spanish version - bug #5750
- { GID_FANMADE, 516, 983, 0, "Wander", "setTarget", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_mul: The Legend of the Lost Jewel Demo (fan made): called with object as second parameter when attacked by insects - bug #5124
- { GID_GK1, 800,64992, 0, "Fwd", "doit", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when Mosely finds Gabriel and Grace near the end of the game, compares the Grooper object with 7
- { GID_HOYLE4, 700, -1, 1, "Code", "doit", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_add: while bidding in Bridge, an object ("Bid") is added to an object in another segment ("hand3")
- { GID_ICEMAN, 199, 977, 0, "Grooper", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_add: While dancing with the girl
- { GID_MOTHERGOOSE256, -1, 999, 0, "Event", "new", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_and: constantly during the game (SCI1 version)
- { GID_MOTHERGOOSE256, -1, 4, 0, "rm004", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_or: when going north and reaching the castle (rooms 4 and 37) - bug #5101
- { GID_MOTHERGOOSEHIRES,90, 90, 0, "newGameButton", "select", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_ge: MUMG Deluxe, when selecting "New Game" in the main menu. It tries to compare an integer with a list. Needs to return false for the game to continue.
- { GID_PHANTASMAGORIA, 902, 0, 0, "", "export 7", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_shr: when starting a chapter in Phantasmagoria
- { GID_QFG1VGA, 301, 928, 0, "Blink", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_div: when entering the inn, gets called with 1 parameter, but 2nd parameter is used for div which happens to be an object
- { GID_QFG2, 200, 200, 0, "astro", "messages", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer - bug #5152
- { GID_QFG3, 780, 999, 0, "", "export 6", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_add: trying to talk to yourself at the top of the giant tree - bug #6692
- { GID_QFG4, 710,64941, 0, "RandCycle", "doit", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when the tentacle appears in the third room of the caves
- SCI_WORKAROUNDENTRY_TERMINATOR
-};
-
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+ { GID_CAMELOT, 92, 92, 0, "endingCartoon2", "changeState", sig_arithmetic_camelot_1, 0, { WORKAROUND_FAKE, 0 } }, // op_lai: during the ending, sub gets called with no parameters, uses parameter 1 which is theGrail in this case - bug #5237
+ { GID_ECOQUEST2, 100, 0, 0, "Rain", "points", sig_arithmetic_ecoq2_1, 0, { WORKAROUND_FAKE, 0 } }, // op_or: when giving the papers to the customs officer, gets called against a pointer instead of a number - bug #4939, Spanish version - bug #5750
+ { GID_FANMADE, 516, 983, 0, "Wander", "setTarget", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_mul: The Legend of the Lost Jewel Demo (fan made): called with object as second parameter when attacked by insects - bug #5124
+ { GID_GK1, 800,64992, 0, "Fwd", "doit", NULL, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when Mosely finds Gabriel and Grace near the end of the game, compares the Grooper object with 7
+ { GID_HOYLE4, 700, -1, 1, "Code", "doit", NULL, 0, { WORKAROUND_FAKE, 1 } }, // op_add: while bidding in Bridge, an object ("Bid") is added to an object in another segment ("hand3")
+ { GID_ICEMAN, 199, 977, 0, "Grooper", "doit", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_add: While dancing with the girl
+ { GID_MOTHERGOOSE256, -1, 999, 0, "Event", "new", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_and: constantly during the game (SCI1 version)
+ { GID_MOTHERGOOSE256, -1, 4, 0, "rm004", "doit", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_or: when going north and reaching the castle (rooms 4 and 37) - bug #5101
+ { GID_MOTHERGOOSEHIRES,90, 90, 0, "newGameButton", "select", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_ge: MUMG Deluxe, when selecting "New Game" in the main menu. It tries to compare an integer with a list. Needs to return false for the game to continue.
+ { GID_PHANTASMAGORIA, 902, 0, 0, "", "export 7", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_shr: when starting a chapter in Phantasmagoria
+ { GID_QFG1VGA, 301, 928, 0, "Blink", "init", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_div: when entering the inn, gets called with 1 parameter, but 2nd parameter is used for div which happens to be an object
+ { GID_QFG2, 200, 200, 0, "astro", "messages", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer - bug #5152
+ { GID_QFG3, 780, 999, 0, "", "export 6", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_add: trying to talk to yourself at the top of the giant tree - bug #6692
+ { GID_QFG4, 710,64941, 0, "RandCycle", "doit", NULL, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when the tentacle appears in the third room of the caves
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// Game: Fan-Made "Ocean Battle"
+// Calling method: RoomScript::doit
+// Subroutine offset: 0x1f17
+// Applies to at least: Ocean Battle
+static const uint16 sig_uninitread_fanmade_1[] = {
+ 0x3f, 0x04, // link 04
+ 0x88, SIG_UINT16(0x023b), // lsg global[23Bh]
+ SIG_END
+};
+
+// Game: Hoyle 1
+// Calling method: export 0
+// Subroutine offset: 0x037c (script 16)
+// Applies to at least: English PC floppy
+static const uint16 sig_uninitread_hoyle1_1[] = {
+ 0x3f, 0x05, // link 05
+ 0x78, // push1
+ 0x76, // push0
+ 0x40, // call [...]
+ SIG_END
+};
+
+// Game: Hoyle 4
+// Calling method: export 2
+// Subroutine offset: 0x1d4d (script 300)
+// Applies to at least: English PC floppy
+static const uint16 sig_uninitread_hoyle4_1[] = {
+ 0x3f, 0x01, // link 01
+ 0x39, 0x40, // pushi 40h
+ 0x78, // push1
+ SIG_END
+};
+
+// Game: Jones in the fast lane
+// Calling method: weekendText::draw
+// Subroutine offset: 0x03d3 (script 232)
+// Applies to at least: English PC CD
+static const uint16 sig_uninitread_jones_1[] = {
+ 0x3f, 0x02, // link 02
+ 0x8d, 0x00, // lst temp[0]
+ 0x35, 0x01, // ldi 01
+ 0x22, // lt?
+ SIG_END
+};
+
+// Game: Conquests of the Longbow
+// Calling method: letter::handleEvent
+// Subroutine offset: English PC/Amiga 0x00a8 (script 213)
+// Applies to at least: English PC floppy, English Amiga floppy
+static const uint16 sig_uninitread_longbow_1[] = {
+ 0x3f, 0x02, // link 02
+ 0x35, 0x00, // ldi 00
+ 0xa5, 0x00, // sat temp[0]
+ 0x8d, 0x00, // lst temp[0]
+ SIG_END
+};
+
+// Game: Quest for Glory 1 / Hero's Quest 1
+// Calling method: Encounter::init
+// Subroutine offset: English Hero's Quest 0x0bd0, English Quest for Glory 1 0x0be4 (script 210)
+// Applies to at least: English PC floppy (Hero's Quest, Quest For Glory 1), Japanese PC-9801 floppy
+static const uint16 sig_uninitread_qfg1_1[] = {
+ 0x3f, 0x02, // link 02
+ 0x87, 0x00, // lap param[0]
+ 0x30, SIG_UINT16(0x000c), // bnt [...]
+ 0x87, 0x01, // lap param[1]
+ 0x30, SIG_UINT16(0x0007), // bnt [...]
+ 0x87, 0x01, // lap param[1]
+ 0xa5, 0x01, // sat temp[1]
+ SIG_END
+};
+
+// Game: Quest for Glory 1 VGA
+// Calling method: Encounter::init
+// Subroutine offset: English w/o patch 0x0cee, w/ patch 0x0ce7 (script 210)
+// Applies to at least: English PC floppy
+static const uint16 sig_uninitread_qfg1vga_1[] = {
+ 0x3f, 0x02, // link 02
+ 0x87, 0x00, // lap param[0]
+ 0x31, 0x0b, // bnt [...]
+ 0x87, 0x01, // lap param[1]
+ 0x31, 0x07, // bnt [...]
+ 0x87, 0x01, // lap param[1]
+ 0xa5, 0x01, // sat temp[1]
+ // following jump is different for patched and unpatched game
+ SIG_END
+};
+
+// Game: Quest for Glory 2
+// Calling method: abdulS::changeState, jabbarS::changeState
+// Subroutine offset: English 0x2d22 (script 260)
+// Applies to at least: English PC floppy
+static const uint16 sig_uninitread_qfg2_1[] = {
+ 0x3f, 0x03, // link 03
+ 0x39, 0x3b, // pushi 3Bh
+ 0x76, // push0
+ 0x81, 0x00, // lag global[0]
+ 0x4a, 0x04, // send 04
+ SIG_END
+};
+
+// Game: Quest for Glory 3
+// Calling method: rm140::init
+// Subroutine offset: English 0x1008 (script 140)
+// Applies to at least: English, French, German, Italian, Spanish PC floppy
+static const uint16 sig_uninitread_qfg3_1[] = {
+ 0x3f, 0x01, // link 01
+ 0x89, 0x7d, // lsg global[7Dh]
+ 0x35, 0x03, // ldi 03
+ SIG_END
+};
+
+// Game: Quest for Glory 3
+// Calling method: computersMove::changeState
+// Subroutine offset: English/etc. 0x0f53 (script 490)
+// Applies to at least: English, French, German, Italian, Spanish PC floppy
+static const uint16 sig_uninitread_qfg3_2[] = {
+ 0x3f, 0x1d, // link 1Dh
+ 0x35, 0x01, // ldi 01
+ 0xa5, 0x18, // sat temp[18h]
+ 0x35, 0xce, // ldi CEh
+ 0xa5, 0x19, // sat temp[19h]
+ 0x35, 0x00, // ldi 00
+ 0xa5, 0x00, // sat temp[0]
+ SIG_END
+};
+
+// Game: Space Quest 1
+// Calling method: firePulsar::changeState
+// Subroutine offset: English 0x018a (script 703)
+// Applies to at least: English PC floppy, English Amiga floppy
+static const uint16 sig_uninitread_sq1_1[] = {
+ 0x3f, 0x01, // link 01
+ 0x38, SIG_ADDTOOFFSET(+2), // pushi 0242 (selector egoStatus)
+ 0x76, // push0
+ 0x72, SIG_ADDTOOFFSET(+2), // lofsa DeltaurRegion
+ 0x4a, 0x04, // send 04
+ SIG_END
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
- { GID_CAMELOT, 40, 40, 0, "Rm40", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // when looking at the ground at the pool of Siloam - bug #6401
- { 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 #5143). Moves the cursor to the view with the ID returned (in this case, the robot hand)
- { GID_CNICK_KQ, -1, 0, 1, "Character", "say", -1, -1, { WORKAROUND_FAKE, 0 } }, // checkers/backgammon, like in hoyle 3 - temps 504 and 505 - bug #6255
- { GID_CNICK_KQ, -1, 700, 0, "gcWindow", "open", -1, -1, { WORKAROUND_FAKE, 0 } }, // when entering the control menu, like in hoyle 3
- { GID_CNICK_KQ, 300, 303, 0, "theDoubleCube", "<noname520>", -1, 5, { WORKAROUND_FAKE, 0 } }, // while playing backgammon with doubling enabled - bug #6426 (same as the theDoubleCube::make workaround for Hoyle 3)
- { GID_CNICK_KQ, 300, 303, 0, "theDoubleCube", "<noname519>", -1, 9, { WORKAROUND_FAKE, 0 } }, // when accepting a double, while playing backgammon with doubling enabled (same as the theDoubleCube::accept workaround for Hoyle 3)
- { GID_CNICK_LAURABOW, -1, 0, 1, "Character", "say", -1, -1, { WORKAROUND_FAKE, 0 } }, // Yatch, like in hoyle 3 - temps 504 and 505 - bug #6424
- { GID_CNICK_LAURABOW, -1, 700, 0, NULL, "open", -1, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu - bug #6423 (same as the gcWindow workaround for Hoyle 3)
- { GID_CNICK_LAURABOW,100, 100, 0, NULL, "<noname144>", -1, 1, { WORKAROUND_FAKE, 0 } }, // while playing domino - bug #6429 (same as the dominoHand2 workaround for Hoyle 3)
- { GID_CNICK_LAURABOW,100, 110, 0, NULL, "doit", -1, -1, { WORKAROUND_FAKE, 0 } }, // when changing the "Dominoes per hand" setting - bug #6430
- { 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 #5116
- { GID_FANMADE, 488, 1, 0, "RoomScript", "doit", 0x1f17, 1, { WORKAROUND_FAKE, 0 } }, // Happens in Ocean Battle while playing - bug #5335
- { 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 #5232
- { 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_HOYLE1, 3, 16, 0, "", "export 0", 0x37c, 3, { WORKAROUND_FAKE, 0 } }, // Hearts / during the game - bug #5299
- { GID_HOYLE1, -1, 997, 0, "MenuBar", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // When changing game speed settings - bug #5512
- { 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_HOYLE3, 100, 100, 0, "dominoHand2", "cue", -1, 1, { WORKAROUND_FAKE, 0 } }, // while playing domino - bug #5042
- { GID_HOYLE3, 100, 110, 0, "OKButton", "doit", -1, -1, { WORKAROUND_FAKE, 0 } }, // when changing the "Dominoes per hand" setting - bug #6430
- { GID_HOYLE3, 300, 303, 0, "theDoubleCube", "make", -1, 5, { WORKAROUND_FAKE, 0 } }, // while playing backgammon with doubling enabled
- { GID_HOYLE3, 300, 303, 0, "theDoubleCube", "accept", -1, 9, { WORKAROUND_FAKE, 0 } }, // when accepting a double, while playing backgammon with doubling enabled
- { GID_HOYLE4, -1, 0, 0, NULL, "open", -1, -1, { WORKAROUND_FAKE, 0 } }, // when selecting "Control" from the menu (temp vars 0-3) - bug #5132
- { GID_HOYLE4, 910, 18, 0, NULL, "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // during tutorial - bug #5213
- { GID_HOYLE4, 910, 910, 0, NULL, "setup", -1, 3, { WORKAROUND_FAKE, 0 } }, // when selecting "Tutorial" from the main menu - bug #5132
- { GID_HOYLE4, 700, 700, 1, "BridgeHand", "calcQTS", -1, 3, { WORKAROUND_FAKE, 0 } }, // when placing a bid in bridge (always)
- { GID_HOYLE4, 700, 710, 1, "BridgeStrategyPlay", "checkSplitTops", -1, 10, { WORKAROUND_FAKE, 0 } }, // while playing bridge, objects LeadReturn_Trump, SecondSeat_Trump, ThirdSeat_Trump and others - bug #5794
- { GID_HOYLE4, 700, -1, 1, "BridgeDefense", "think", -1, -1, { WORKAROUND_FAKE, 0 } }, // sometimes while playing bridge, temp var 3, 17 and others, objects LeadReturn_Trump, ThirdSeat_Trump and others
- { GID_HOYLE4, 700, 730, 1, "BridgeDefense", "beatTheirBest", -1, 3, { WORKAROUND_FAKE, 0 } }, // rarely while playing bridge
- { GID_HOYLE4, 700, -1, 1, "Code", "doit", -1, -1, { WORKAROUND_FAKE, 0 } }, // when placing a bid in bridge (always), temp var 11, 24, 27, 46, 75, objects compete_tree, compwe_tree, other1_tree, b1 - bugs #5663 and #5794
- { GID_HOYLE4, 700, 921, 0, "Print", "addEdit", -1, -1, { WORKAROUND_FAKE, 0 } }, // when saving the game (may also occur in other situations) - bug #6601
- { GID_HOYLE4, 300, 300, 0, "", "export 2", 0x1d4d, 0, { WORKAROUND_FAKE, 0 } }, // after passing around cards in hearts
- { GID_HOYLE4, 400, 400, 1, "GinHand", "calcRuns", -1, 4, { WORKAROUND_FAKE, 0 } }, // sometimes while playing Gin Rummy (e.g. when knocking and placing a card) - bug #5665
- { GID_HOYLE4, 500, 17, 1, "Character", "say", -1, 504, { WORKAROUND_FAKE, 0 } }, // sometimes while playing Cribbage (e.g. when the opponent says "Last Card") - bug #5662
- { GID_HOYLE4, -1, 937, 0, "IconBar", "dispatchEvent", -1, 408, { WORKAROUND_FAKE, 0 } }, // pressing ENTER on scoreboard while mouse is not on OK button, may not happen all the time - bug #6603
- { GID_ISLANDBRAIN, 100, 937, 0, "IconBar", "dispatchEvent", -1, 58, { WORKAROUND_FAKE, 0 } }, // when using ENTER at the startup menu - bug #5241
- { 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, -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 #4961
+ { GID_CAMELOT, 40, 40, 0, "Rm40", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when looking at the ground at the pool of Siloam - bug #6401
+ { GID_CASTLEBRAIN, 280, 280, 0, "programmer", "dispatchEvent", NULL, 0, { WORKAROUND_FAKE, 0xf } }, // pressing 'q' on the computer screen in the robot room, and closing the help dialog that pops up (bug #5143). Moves the cursor to the view with the ID returned (in this case, the robot hand)
+ { GID_CNICK_KQ, -1, 0, 1, "Character", "say", NULL, -1, { WORKAROUND_FAKE, 0 } }, // checkers/backgammon, like in hoyle 3 - temps 504 and 505 - bug #6255
+ { GID_CNICK_KQ, -1, 700, 0, "gcWindow", "open", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when entering the control menu, like in hoyle 3
+ { GID_CNICK_KQ, 300, 303, 0, "theDoubleCube", "<noname520>", NULL, 5, { WORKAROUND_FAKE, 0 } }, // while playing backgammon with doubling enabled - bug #6426 (same as the theDoubleCube::make workaround for Hoyle 3)
+ { GID_CNICK_KQ, 300, 303, 0, "theDoubleCube", "<noname519>", NULL, 9, { WORKAROUND_FAKE, 0 } }, // when accepting a double, while playing backgammon with doubling enabled (same as the theDoubleCube::accept workaround for Hoyle 3)
+ { GID_CNICK_LAURABOW, -1, 0, 1, "Character", "say", NULL, -1, { WORKAROUND_FAKE, 0 } }, // Yatch, like in hoyle 3 - temps 504 and 505 - bug #6424
+ { GID_CNICK_LAURABOW, -1, 700, 0, NULL, "open", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu - bug #6423 (same as the gcWindow workaround for Hoyle 3)
+ { GID_CNICK_LAURABOW,100, 100, 0, NULL, "<noname144>", NULL, 1, { WORKAROUND_FAKE, 0 } }, // while playing domino - bug #6429 (same as the dominoHand2 workaround for Hoyle 3)
+ { GID_CNICK_LAURABOW,100, 110, 0, NULL, "doit", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when changing the "Dominoes per hand" setting - bug #6430
+ { GID_CNICK_LONGBOW, 0, 0, 0, "RH Budget", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // when starting the game
+ { GID_ECOQUEST, -1, -1, 0, NULL, "doVerb", NULL, 0, { WORKAROUND_FAKE, 0 } }, // almost clicking anywhere triggers this in almost all rooms
+ { GID_FANMADE, 516, 979, 0, "", "export 0", NULL, 20, { WORKAROUND_FAKE, 0 } }, // Happens in Grotesteing after the logos
+ { GID_FANMADE, 528, 990, 0, "GDialog", "doit", NULL, 4, { WORKAROUND_FAKE, 0 } }, // Happens in Cascade Quest when closing the glossary - bug #5116
+ { GID_FANMADE, 488, 1, 0, "RoomScript", "doit", sig_uninitread_fanmade_1, 1, { WORKAROUND_FAKE, 0 } }, // Happens in Ocean Battle while playing - bug #5335
+ { GID_FREDDYPHARKAS, -1, 24, 0, "gcWin", "open", NULL, 5, { WORKAROUND_FAKE, 0xf } }, // is used as priority for game menu
+ { GID_FREDDYPHARKAS, -1, 31, 0, "quitWin", "open", NULL, 5, { WORKAROUND_FAKE, 0xf } }, // is used as priority for game menu
+ { GID_FREDDYPHARKAS, 540, 540, 0, "WaverCode", "init", NULL, -1, { WORKAROUND_FAKE, 0 } }, // Gun pratice mini-game - bug #5232
+ { GID_GK1, -1, 64950, -1, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // sometimes when walk-clicking
+ { GID_GK2, -1, 11, 0, "", "export 10", NULL, 3, { WORKAROUND_FAKE, 0 } }, // called when the game starts
+ { GID_GK2, -1, 11, 0, "", "export 10", NULL, 4, { WORKAROUND_FAKE, 0 } }, // called during the game
+ { GID_HOYLE1, 4, 104, 0, "GinRummyCardList", "calcRuns", NULL, 4, { WORKAROUND_FAKE, 0 } }, // Gin Rummy / right when the game starts
+ { GID_HOYLE1, 5, 204, 0, "tableau", "checkRuns", NULL, 2, { WORKAROUND_FAKE, 0 } }, // Cribbage / during the game
+ { GID_HOYLE1, 3, 16, 0, "", "export 0", sig_uninitread_hoyle1_1, 3, { WORKAROUND_FAKE, 0 } }, // Hearts / during the game - bug #5299
+ { GID_HOYLE1, -1, 997, 0, "MenuBar", "doit", NULL, 0, { WORKAROUND_FAKE, 0 } }, // When changing game speed settings - bug #5512
+ { GID_HOYLE3, -1, 0, 1, "Character", "say", NULL, -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", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu
+ { GID_HOYLE3, 100, 100, 0, "dominoHand2", "cue", NULL, 1, { WORKAROUND_FAKE, 0 } }, // while playing domino - bug #5042
+ { GID_HOYLE3, 100, 110, 0, "OKButton", "doit", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when changing the "Dominoes per hand" setting - bug #6430
+ { GID_HOYLE3, 300, 303, 0, "theDoubleCube", "make", NULL, 5, { WORKAROUND_FAKE, 0 } }, // while playing backgammon with doubling enabled
+ { GID_HOYLE3, 300, 303, 0, "theDoubleCube", "accept", NULL, 9, { WORKAROUND_FAKE, 0 } }, // when accepting a double, while playing backgammon with doubling enabled
+ { GID_HOYLE4, -1, 0, 0, NULL, "open", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when selecting "Control" from the menu (temp vars 0-3) - bug #5132
+ { GID_HOYLE4, 910, 18, 0, NULL, "init", NULL, 0, { WORKAROUND_FAKE, 0 } }, // during tutorial - bug #5213
+ { GID_HOYLE4, 910, 910, 0, NULL, "setup", NULL, 3, { WORKAROUND_FAKE, 0 } }, // when selecting "Tutorial" from the main menu - bug #5132
+ { GID_HOYLE4, 700, 700, 1, "BridgeHand", "calcQTS", NULL, 3, { WORKAROUND_FAKE, 0 } }, // when placing a bid in bridge (always)
+ { GID_HOYLE4, 700, 710, 1, "BridgeStrategyPlay", "checkSplitTops", NULL, 10, { WORKAROUND_FAKE, 0 } }, // while playing bridge, objects LeadReturn_Trump, SecondSeat_Trump, ThirdSeat_Trump and others - bug #5794
+ { GID_HOYLE4, 700, -1, 1, "BridgeDefense", "think", NULL, -1, { WORKAROUND_FAKE, 0 } }, // sometimes while playing bridge, temp var 3, 17 and others, objects LeadReturn_Trump, ThirdSeat_Trump and others
+ { GID_HOYLE4, 700, 730, 1, "BridgeDefense", "beatTheirBest", NULL, 3, { WORKAROUND_FAKE, 0 } }, // rarely while playing bridge
+ { GID_HOYLE4, 700, -1, 1, "Code", "doit", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when placing a bid in bridge (always), temp var 11, 24, 27, 46, 75, objects compete_tree, compwe_tree, other1_tree, b1 - bugs #5663 and #5794
+ { GID_HOYLE4, 700, 921, 0, "Print", "addEdit", NULL, 0, { WORKAROUND_FAKE, 118 } }, // when saving the game (may also occur in other situations) - bug #6601, bug #6614
+ { GID_HOYLE4, 700, 921, 0, "Print", "addEdit", NULL, 1, { WORKAROUND_FAKE, 1 } }, // see above, Text-control saves its coordinates to temp[0] and temp[1], Edit-control adjusts to those uninitialized temps, who by accident were left over from the Text-control
+ { GID_HOYLE4, 300, 300, 0, "", "export 2", sig_uninitread_hoyle4_1, 0, { WORKAROUND_FAKE, 0 } }, // after passing around cards in hearts
+ { GID_HOYLE4, 400, 400, 1, "GinHand", "calcRuns", NULL, 4, { WORKAROUND_FAKE, 0 } }, // sometimes while playing Gin Rummy (e.g. when knocking and placing a card) - bug #5665
+ { GID_HOYLE4, 500, 17, 1, "Character", "say", NULL, 504, { WORKAROUND_FAKE, 0 } }, // sometimes while playing Cribbage (e.g. when the opponent says "Last Card") - bug #5662
+ { GID_HOYLE4, 800, 870, 0, "EuchreStrategy", "thinkLead", NULL, 0, { WORKAROUND_FAKE, 0 } }, // while playing Euchre, happens at least on 2nd or 3rd turn - bug #6602
+ { GID_HOYLE4, -1, 937, 0, "IconBar", "dispatchEvent", NULL, 408, { WORKAROUND_FAKE, 0 } }, // pressing ENTER on scoreboard while mouse is not on OK button, may not happen all the time - bug #6603
+ { GID_ISLANDBRAIN, 100, 937, 0, "IconBar", "dispatchEvent", NULL, 58, { WORKAROUND_FAKE, 0 } }, // when using ENTER at the startup menu - bug #5241
+ { GID_ISLANDBRAIN, 140, 140, 0, "piece", "init", NULL, 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", NULL, 0, { WORKAROUND_FAKE, 0 } }, // elements puzzle, gets used before super TextIcon
+ { GID_JONES, 1, 232, 0, "weekendText", "draw", sig_uninitread_jones_1, 0, { WORKAROUND_FAKE, 0 } }, // jones/cd only - gets called during the game
+ { GID_JONES, 1, 255, 0, "", "export 0", NULL, -1, { WORKAROUND_FAKE, 0 } }, // jones/cd only - called when a game ends, temps 13 and 14
+ { GID_JONES, 764, 255, 0, "", "export 0", NULL, -1, { WORKAROUND_FAKE, 0 } }, // jones/ega&vga only - called when the game starts, temps 13 and 14
+ //{ GID_KQ5, -1, 0, 0, "", "export 29", NULL, 3, { WORKAROUND_FAKE, 0xf } }, // called when playing harp for the harpies or when aborting dialog in toy shop, is used for kDoAudio - bug #4961
// ^^ 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 #5198
- { GID_KQ5, -1, 755, 0, "gcWin", "open", -1, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu in the FM-Towns version
- { GID_KQ6, -1, 30, 0, "rats", "changeState", -1, -1, { WORKAROUND_FAKE, 0 } }, // rats in the catacombs (temps 1 - 5) - bugs #4958, #4998, #5017
- { GID_KQ6, 210, 210, 0, "rm210", "scriptCheck", -1, 0, { WORKAROUND_FAKE, 1 } }, // using inventory in that room - bug #4953
- { 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 #5331
- { GID_KQ6, -1, 928, 0, NULL, "startText", -1, 0, { WORKAROUND_FAKE, 0 } }, // gets caused by Text+Audio support (see script patcher)
- { GID_KQ7, -1, 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 #5084
- { GID_LAURABOW, -1, 967, 0, "myIcon", "cycle", -1, 1, { WORKAROUND_FAKE, 0 } }, // having any portrait conversation coming up - initial bug #4971
- { GID_LAURABOW2, -1, 24, 0, "gcWin", "open", -1, 5, { WORKAROUND_FAKE, 0xf } }, // is used as priority for game menu
- { GID_LAURABOW2, -1, 21, 0, "dropCluesCode", "doit", -1, 1, { WORKAROUND_FAKE, 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 #4979, #5026
- { GID_LAURABOW2, -1, 90, 1, "MuseumActor", "init", -1, 6, { WORKAROUND_FAKE, 0 } }, // Random actors in museum - bug #5197
- { GID_LAURABOW2, 240, 240, 0, "sSteveAnimates", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // Steve Dorian's idle animation at the docks - bug #5028
- { GID_LAURABOW2, -1, 928, 0, NULL, "startText", -1, 0, { WORKAROUND_FAKE, 0 } }, // gets caused by Text+Audio support (see script patcher)
- { GID_LONGBOW, -1, 0, 0, "Longbow", "restart", -1, 0, { WORKAROUND_FAKE, 0 } }, // When canceling a restart game - bug #5244
- { 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 #5035
- { GID_LSL1, 250, 250, 0, "increase", "handleEvent", -1, 2, { WORKAROUND_FAKE, 0 } }, // casino, playing game, increasing bet
- { GID_LSL1, 720, 720, 0, "rm720", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // age check room
- { GID_LSL2, 38, 38, 0, "cloudScript", "changeState", -1, 1, { WORKAROUND_FAKE, 0 } }, // entering the room in the middle deck of the ship - bug #5034
- { GID_LSL3, 340, 340, 0, "ComicScript", "changeState", -1, -1, { WORKAROUND_FAKE, 0 } }, // right after entering the 3 ethnic groups inside comedy club (temps 200, 201, 202, 203)
- { GID_LSL3, -1, 997, 0, "TheMenuBar", "handleEvent", -1, 1, { WORKAROUND_FAKE, 0xf } }, // when setting volume the first time, this temp is used to set volume on entry (normally it would have been initialized to 's')
- { GID_LSL6, 820, 82, 0, "", "export 0", -1, -1, { WORKAROUND_FAKE, 0 } }, // when touching the electric fence - bug #5103
- { GID_LSL6, -1, 85, 0, "washcloth", "doVerb", -1, 0, { WORKAROUND_FAKE, 0 } }, // washcloth in inventory
- { GID_LSL6, -1, 928, -1, "Narrator", "startText", -1, 0, { WORKAROUND_FAKE, 0 } }, // used by various objects that are even translated in foreign versions, that's why we use the base-class
- { GID_LSL6HIRES, 0, 85, 0, "LL6Inv", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // on startup
- { GID_LSL6HIRES, -1, 64950, 1, "Feature", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // at least when entering swimming pool area
- { GID_LSL6HIRES, -1, 64964, 0, "DPath", "init", -1, 1, { WORKAROUND_FAKE, 0 } }, // during the game
- { GID_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 #5224
- { 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 #5269
- { GID_MOTHERGOOSE256, 90, 90, 0, "introScript", "changeState", -1, 65, { WORKAROUND_FAKE, 0 } }, // SCI1(CD): At the very end, after the game is completed and restarted - bug #5626
- { GID_MOTHERGOOSE256, 94, 94, 0, "sunrise", "changeState", -1, 367, { WORKAROUND_FAKE, 0 } }, // At the very end, after the game is completed - bug #5294
- { 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 #5154
- { GID_PEPPER, 150, 928, 0, "Narrator", "startText", -1, 0, { WORKAROUND_FAKE, 0 } }, // happens during the non-interactive demo of Pepper
- { GID_PQ4, -1, 25, 0, "iconToggle", "select", -1, 1, { WORKAROUND_FAKE, 0 } }, // when toggling the icon bar to auto-hide or not
- { GID_PQSWAT, -1, 64950, 0, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // Using the menu in the beginning
- { 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 #5309
- { GID_QFG1VGA, -1, 210, 0, "Encounter", "init", 0xcee, 0, { WORKAROUND_FAKE, 0 } }, // qfg1vga: going to the brigands hideout - bug #5515
- { GID_QFG1VGA, -1, 210, 0, "Encounter", "init", 0xce7, 0, { WORKAROUND_FAKE, 0 } }, // qfg1vga: going to room 92
- { 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 #5019 & #5106
- { 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 #5153, 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 #5164, temps 1 and 2
- { GID_QFG2, 500, 500, 0, "lightNextCandleS", "changeState", -1, -1, { WORKAROUND_FAKE, 0 } }, // Inside the last room, while Ad Avis performs the ritual to summon the genie - bug #5566
- { GID_QFG2, -1, 700, 0, NULL, "showSign", -1, 10, { WORKAROUND_FAKE, 0 } }, // Occurs sometimes when reading a sign in Raseir, Shapeir et al - bugs #5627, #5635
- { GID_QFG3, 510, 510, 0, "awardPrize", "changeState", -1, 0, { WORKAROUND_FAKE, 1 } }, // Simbani warrior challenge, after throwing the spears and retrieving the ring - bug #5277. Must be non-zero, otherwise the prize is awarded twice - bug #6160
- { GID_QFG3, 140, 140, 0, "rm140", "init", 0x1008, 0, { WORKAROUND_FAKE, 0 } }, // when importing a character and selecting the previous profession - bug #5163
- { GID_QFG3, 330, 330, -1, "Teller", "doChild", -1, -1, { WORKAROUND_FAKE, 0 } }, // when talking to King Rajah about "Rajah" (bug #5033, temp 1) or "Tarna" (temp 0), or when clicking on yourself and saying "Greet" (bug #5148, temp 1)
- { GID_QFG3, 700, 700, -1, "monsterIsDead", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // in the jungle, after winning any fight, bug #5169
- { 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 #5165
- { GID_QFG3, 490, 490, -1, "computersMove", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // when finishing awari game, bug #5167
- { 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 #5282
- { 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, happens sometimes in fight scenes
- { GID_QFG4, 520, 64950, 0, "fLake2", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // CD version, at the lake, when meeting the Rusalka and attempting to leave
- { GID_QFG4, 800, 64950, 0, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // CD version, in the room with the spider pillar, when climbing on the pillar
- { GID_RAMA, 12, 64950, -1, "InterfaceFeature", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // Demo, right when it starts
- { GID_RAMA, 12, 64950, -1, "hiliteOptText", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // Demo, right when it starts
- { GID_RAMA, 12, 64950, -1, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // Demo, right when it starts
- { GID_SHIVERS, -1, 952, 0, "SoundManager", "stop", -1, 2, { WORKAROUND_FAKE, 0 } }, // Just after Sierra logo
- { GID_SHIVERS, -1, 64950, 0, "Feature", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // When clicking on the locked door at the beginning
- { GID_SHIVERS, -1, 64950, 0, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // When clicking on the gargoyle eye at the beginning
- { GID_SHIVERS, 20311, 64964, 0, "DPath", "init", -1, 1, { WORKAROUND_FAKE, 0 } }, // Just after door puzzle is solved and the metal balls start to roll
- { GID_SHIVERS, 29260, 29260, 0, "spMars", "handleEvent", -1, 4, { WORKAROUND_FAKE, 0 } }, // When clicking mars after seeing fortune to align earth etc...
- { GID_SHIVERS, 29260, 29260, 0, "spVenus", "handleEvent", -1, 4, { WORKAROUND_FAKE, 0 } }, // When clicking venus after seeing fortune to align earth etc...
- { GID_SQ1, 103, 103, 0, "hand", "internalEvent", -1, -1, { WORKAROUND_FAKE, 0 } }, // Spanish (and maybe early versions?) only: when moving cursor over input pad, temps 1 and 2
- { GID_SQ1, -1, 703, 0, "", "export 1", -1, 0, { WORKAROUND_FAKE, 0 } }, // sub that's called from several objects while on sarien battle cruiser
- { GID_SQ1, -1, 703, 0, "firePulsar", "changeState", 0x18a, 0, { WORKAROUND_FAKE, 0 } }, // export 1, but called locally (when shooting at aliens)
- { GID_SQ4, -1, 398, 0, "showBox", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // CD: called when rummaging in Software Excess bargain bin
- { GID_SQ4, -1, 928, -1, "Narrator", "startText", -1, 1000, { WORKAROUND_FAKE, 1 } }, // CD: happens in the options dialog and in-game when speech and subtitles are used simultaneously
- { GID_SQ4, -1, 708, -1, "exitBut", "doVerb", -1, 0, { WORKAROUND_FAKE, 0 } }, // Floppy: happens, when looking at the "close" button in the sq4 hintbook - bug #6447
- { GID_SQ4, -1, 708, -1, "prevBut", "doVerb", -1, 0, { WORKAROUND_FAKE, 0 } }, // Floppy: happens, when looking at the "previous" button in the sq4 hintbook - bug #6447
- { GID_SQ4, -1, 708, -1, "nextBut", "doVerb", -1, 0, { WORKAROUND_FAKE, 0 } }, // Floppy: happens, when looking at the "next" button in the sq4 hintbook - bug #6447
- { GID_SQ5, 201, 201, 0, "buttonPanel", "doVerb", -1, 0, { WORKAROUND_FAKE, 1 } }, // when looking at the orange or red button - bug #5112
- { 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, -1, 64950, -1, "Feature", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu, when entering the Orion's Belt bar (room 300), and perhaps other places
- { GID_SQ6, -1, 64964, 0, "DPath", "init", -1, 1, { WORKAROUND_FAKE, 0 } }, // during the game
- { GID_TORIN, -1, 64017, 0, "oFlags", "clear", -1, 0, { WORKAROUND_FAKE, 0 } }, // entering Torin's home in the French version
- SCI_WORKAROUNDENTRY_TERMINATOR
-};
-
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+ { GID_KQ5, 25, 25, 0, "rm025", "doit", NULL, 0, { WORKAROUND_FAKE, 0 } }, // inside witch forest, when going to the room where the walking rock is
+ { GID_KQ5, 55, 55, 0, "helpScript", "doit", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when giving the tambourine to the monster in the labyrinth (only happens at one of the locations) - bug #5198
+ { GID_KQ5, -1, 755, 0, "gcWin", "open", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu in the FM-Towns version
+ { GID_KQ6, -1, 30, 0, "rats", "changeState", NULL, -1, { WORKAROUND_FAKE, 0 } }, // rats in the catacombs (temps 1 - 5) - bugs #4958, #4998, #5017
+ { GID_KQ6, 210, 210, 0, "rm210", "scriptCheck", NULL, 0, { WORKAROUND_FAKE, 1 } }, // using inventory in that room - bug #4953
+ { GID_KQ6, 500, 500, 0, "rm500", "init", NULL, 0, { WORKAROUND_FAKE, 0 } }, // going to island of the beast
+ { GID_KQ6, 520, 520, 0, "rm520", "init", NULL, 0, { WORKAROUND_FAKE, 0 } }, // going to boiling water trap on beast isle
+ { GID_KQ6, -1, 903, 0, "controlWin", "open", NULL, 4, { WORKAROUND_FAKE, 0 } }, // when opening the controls window (save, load etc)
+ { GID_KQ6, -1, 907, 0, "tomato", "doVerb", NULL, 2, { WORKAROUND_FAKE, 0 } }, // when looking at the rotten tomato in the inventory - bug #5331
+ { GID_KQ6, -1, 928, 0, NULL, "startText", NULL, 0, { WORKAROUND_FAKE, 0 } }, // gets caused by Text+Audio support (see script patcher)
+ { GID_KQ7, -1, 64996, 0, "User", "handleEvent", NULL, 1, { WORKAROUND_FAKE, 0 } }, // called when pushing a keyboard key
+ { GID_LAURABOW, 37, 0, 0, "CB1", "doit", NULL, 1, { WORKAROUND_FAKE, 0 } }, // when going up the stairs - bug #5084
+ { GID_LAURABOW, -1, 967, 0, "myIcon", "cycle", NULL, 1, { WORKAROUND_FAKE, 0 } }, // having any portrait conversation coming up - initial bug #4971
+ { GID_LAURABOW2, -1, 24, 0, "gcWin", "open", NULL, 5, { WORKAROUND_FAKE, 0xf } }, // is used as priority for game menu
+ { GID_LAURABOW2, -1, 21, 0, "dropCluesCode", "doit", NULL, 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 #4979, #5026
+ { GID_LAURABOW2, -1, 90, 1, "MuseumActor", "init", NULL, 6, { WORKAROUND_FAKE, 0 } }, // Random actors in museum - bug #5197
+ { GID_LAURABOW2, 240, 240, 0, "sSteveAnimates", "changeState", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Steve Dorian's idle animation at the docks - bug #5028
+ { GID_LAURABOW2, -1, 928, 0, NULL, "startText", NULL, 0, { WORKAROUND_FAKE, 0 } }, // gets caused by Text+Audio support (see script patcher)
+ { GID_LONGBOW, -1, 0, 0, "Longbow", "restart", NULL, 0, { WORKAROUND_FAKE, 0 } }, // When canceling a restart game - bug #5244
+ { GID_LONGBOW, -1, 213, 0, "clear", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // When giving an answer using the druid hand sign code in any room
+ { GID_LONGBOW, -1, 213, 0, "letter", "handleEvent", sig_uninitread_longbow_1, 1, { WORKAROUND_FAKE, 0 } }, // When using the druid hand sign code in any room - bug #5035
+ { GID_LSL1, 250, 250, 0, "increase", "handleEvent", NULL, 2, { WORKAROUND_FAKE, 0 } }, // casino, playing game, increasing bet
+ { GID_LSL1, 720, 720, 0, "rm720", "init", NULL, 0, { WORKAROUND_FAKE, 0 } }, // age check room
+ { GID_LSL2, 38, 38, 0, "cloudScript", "changeState", NULL, 1, { WORKAROUND_FAKE, 0 } }, // entering the room in the middle deck of the ship - bug #5034
+ { GID_LSL3, 340, 340, 0, "ComicScript", "changeState", NULL, -1, { WORKAROUND_FAKE, 0 } }, // right after entering the 3 ethnic groups inside comedy club (temps 200, 201, 202, 203)
+ { GID_LSL3, -1, 997, 0, "TheMenuBar", "handleEvent", NULL, 1, { WORKAROUND_FAKE, 0xf } }, // when setting volume the first time, this temp is used to set volume on entry (normally it would have been initialized to 's')
+ { GID_LSL6, 820, 82, 0, "", "export 0", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when touching the electric fence - bug #5103
+ { GID_LSL6, -1, 85, 0, "washcloth", "doVerb", NULL, 0, { WORKAROUND_FAKE, 0 } }, // washcloth in inventory
+ { GID_LSL6, -1, 928, -1, "Narrator", "startText", NULL, 0, { WORKAROUND_FAKE, 0 } }, // used by various objects that are even translated in foreign versions, that's why we use the base-class
+ { GID_LSL6HIRES, 0, 85, 0, "LL6Inv", "init", NULL, 0, { WORKAROUND_FAKE, 0 } }, // on startup
+ { GID_LSL6HIRES, -1, 64950, 1, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // at least when entering swimming pool area
+ { GID_LSL6HIRES, -1, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // during the game
+ { GID_MOTHERGOOSE256, -1, 0, 0, "MG", "doit", NULL, 5, { WORKAROUND_FAKE, 0 } }, // SCI1.1: When moving the cursor all the way to the left during the game - bug #5224
+ { GID_MOTHERGOOSE256, -1, 992, 0, "AIPath", "init", NULL, 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 #5269
+ { GID_MOTHERGOOSE256, 90, 90, 0, "introScript", "changeState", NULL, 65, { WORKAROUND_FAKE, 0 } }, // SCI1(CD): At the very end, after the game is completed and restarted - bug #5626
+ { GID_MOTHERGOOSE256, 94, 94, 0, "sunrise", "changeState", NULL, 367, { WORKAROUND_FAKE, 0 } }, // At the very end, after the game is completed - bug #5294
+ { GID_MOTHERGOOSEHIRES,-1,64950, 1, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // right when clicking on a child at the start and probably also later
+ { GID_MOTHERGOOSEHIRES,-1,64950, 1, "View", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // see above
+ { GID_PEPPER, -1, 894, 0, "Package", "doVerb", NULL, 3, { WORKAROUND_FAKE, 0 } }, // using the hand on the book in the inventory - bug #5154
+ { GID_PEPPER, 150, 928, 0, "Narrator", "startText", NULL, 0, { WORKAROUND_FAKE, 0 } }, // happens during the non-interactive demo of Pepper
+ { GID_PQ4, -1, 25, 0, "iconToggle", "select", NULL, 1, { WORKAROUND_FAKE, 0 } }, // when toggling the icon bar to auto-hide or not
+ { GID_PQSWAT, -1, 64950, 0, "View", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Using the menu in the beginning
+ { GID_QFG1, -1, 210, 0, "Encounter", "init", sig_uninitread_qfg1_1, 0, { WORKAROUND_FAKE, 0 } }, // qfg1/hq1: going to the brigands hideout
+ { GID_QFG1VGA, 16, 16, 0, "lassoFailed", "changeState", NULL, -1, { WORKAROUND_FAKE, 0 } }, // qfg1vga: casting the "fetch" spell in the screen with the flowers, temps 0 and 1 - bug #5309
+ { GID_QFG1VGA, -1, 210, 0, "Encounter", "init", sig_uninitread_qfg1vga_1, 0, { WORKAROUND_FAKE, 0 } }, // qfg1vga: going to the brigands hideout - bug #5515
+ { GID_QFG2, -1, 71, 0, "theInvSheet", "doit", NULL, 1, { WORKAROUND_FAKE, 0 } }, // accessing the inventory
+ { GID_QFG2, -1, 79, 0, "TryToMoveTo", "onTarget", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when throwing pot at air elemental, happens when client coordinates are the same as airElemental coordinates. happened to me right after room change - bug #6859
+ { GID_QFG2, -1, 701, -1, "Alley", "at", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when walking inside the alleys in the town - bug #5019 & #5106
+ { GID_QFG2, -1, 990, 0, "Restore", "doit", NULL, 364, { WORKAROUND_FAKE, 0 } }, // when pressing enter in restore dialog w/o any saved games present
+ { GID_QFG2, 260, 260, 0, "abdulS", "changeState", sig_uninitread_qfg2_1, -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 #5153, temps 1 and 2
+ { GID_QFG2, 260, 260, 0, "jabbarS", "changeState", sig_uninitread_qfg2_1, -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 #5164, temps 1 and 2
+ { GID_QFG2, 500, 500, 0, "lightNextCandleS", "changeState", NULL, -1, { WORKAROUND_FAKE, 0 } }, // Inside the last room, while Ad Avis performs the ritual to summon the genie - bug #5566
+ { GID_QFG2, -1, 700, 0, NULL, "showSign", NULL, 10, { WORKAROUND_FAKE, 0 } }, // Occurs sometimes when reading a sign in Raseir, Shapeir et al - bugs #5627, #5635
+ { GID_QFG3, 510, 510, 0, "awardPrize", "changeState", NULL, 0, { WORKAROUND_FAKE, 1 } }, // Simbani warrior challenge, after throwing the spears and retrieving the ring - bug #5277. Must be non-zero, otherwise the prize is awarded twice - bug #6160
+ { GID_QFG3, 140, 140, 0, "rm140", "init", sig_uninitread_qfg3_1, 0, { WORKAROUND_FAKE, 0 } }, // when importing a character and selecting the previous profession - bug #5163
+ { GID_QFG3, 330, 330, -1, "Teller", "doChild", NULL, -1, { WORKAROUND_FAKE, 0 } }, // when talking to King Rajah about "Rajah" (bug #5033, temp 1) or "Tarna" (temp 0), or when clicking on yourself and saying "Greet" (bug #5148, temp 1)
+ { GID_QFG3, 700, 700, -1, "monsterIsDead", "changeState", NULL, 0, { WORKAROUND_FAKE, 0 } }, // in the jungle, after winning any fight, bug #5169
+ { GID_QFG3, 470, 470, -1, "rm470", "notify", NULL, 0, { WORKAROUND_FAKE, 0 } }, // closing the character screen in the Simbani village in the room with the bridge, bug #5165
+ { GID_QFG3, 490, 490, -1, "computersMove", "changeState", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when finishing awari game, bug #5167
+ { GID_QFG3, 490, 490, -1, "computersMove", "changeState", sig_uninitread_qfg3_2, 4, { WORKAROUND_FAKE, 0 } }, // also when finishing awari game
+ { GID_QFG3, 851, 32, -1, "ProjObj", "doit", NULL, 1, { WORKAROUND_FAKE, 0 } }, // near the end, when throwing the spear of death, bug #5282
+ { GID_QFG4, -1, 15, -1, "charInitScreen", "dispatchEvent", NULL, 5, { WORKAROUND_FAKE, 0 } }, // floppy version, when viewing the character screen
+ { GID_QFG4, -1, 64917, -1, "controlPlane", "setBitmap", NULL, 3, { WORKAROUND_FAKE, 0 } }, // floppy version, when entering the game menu
+ { GID_QFG4, -1, 64917, -1, "Plane", "setBitmap", NULL, 3, { WORKAROUND_FAKE, 0 } }, // floppy version, happens sometimes in fight scenes
+ { GID_QFG4, 520, 64950, 0, "fLake2", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // CD version, at the lake, when meeting the Rusalka and attempting to leave
+ { GID_QFG4, 800, 64950, 0, "View", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // CD version, in the room with the spider pillar, when climbing on the pillar
+ { GID_RAMA, 12, 64950, -1, "InterfaceFeature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Demo, right when it starts
+ { GID_RAMA, 12, 64950, -1, "hiliteOptText", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Demo, right when it starts
+ { GID_RAMA, 12, 64950, -1, "View", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Demo, right when it starts
+ { GID_SHIVERS, -1, 952, 0, "SoundManager", "stop", NULL, 2, { WORKAROUND_FAKE, 0 } }, // Just after Sierra logo
+ { GID_SHIVERS, -1, 64950, 0, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // When clicking on the locked door at the beginning
+ { GID_SHIVERS, -1, 64950, 0, "View", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // When clicking on the gargoyle eye at the beginning
+ { GID_SHIVERS, 20311, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // Just after door puzzle is solved and the metal balls start to roll
+ { GID_SHIVERS, 29260, 29260, 0, "spMars", "handleEvent", NULL, 4, { WORKAROUND_FAKE, 0 } }, // When clicking mars after seeing fortune to align earth etc...
+ { GID_SHIVERS, 29260, 29260, 0, "spVenus", "handleEvent", NULL, 4, { WORKAROUND_FAKE, 0 } }, // When clicking venus after seeing fortune to align earth etc...
+ { GID_SQ1, 103, 103, 0, "hand", "internalEvent", NULL, -1, { WORKAROUND_FAKE, 0 } }, // Spanish (and maybe early versions?) only: when moving cursor over input pad, temps 1 and 2
+ { GID_SQ1, -1, 703, 0, "", "export 1", NULL, 0, { WORKAROUND_FAKE, 0 } }, // sub that's called from several objects while on sarien battle cruiser
+ { GID_SQ1, -1, 703, 0, "firePulsar", "changeState", sig_uninitread_sq1_1, 0, { WORKAROUND_FAKE, 0 } }, // export 1, but called locally (when shooting at aliens)
+ { GID_SQ4, -1, 398, 0, "showBox", "changeState", NULL, 0, { WORKAROUND_FAKE, 0 } }, // CD: called when rummaging in Software Excess bargain bin
+ { GID_SQ4, -1, 928, -1, "Narrator", "startText", NULL, 1000, { WORKAROUND_FAKE, 1 } }, // CD: happens in the options dialog and in-game when speech and subtitles are used simultaneously
+ { GID_SQ4, -1, 708, -1, "exitBut", "doVerb", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Floppy: happens, when looking at the "close" button in the sq4 hintbook - bug #6447
+ { GID_SQ4, -1, 708, -1, "", "doVerb", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Floppy: happens, when looking at the "close" button... in Russian version - bug #5573
+ { GID_SQ4, -1, 708, -1, "prevBut", "doVerb", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Floppy: happens, when looking at the "previous" button in the sq4 hintbook - bug #6447
+ { GID_SQ4, -1, 708, -1, "\xA8\xE6\xE3 \xAD\xA0\xA7\xA0\xA4.", "doVerb", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Floppy: happens, when looking at the "previous" button... in Russian version - bug #5573
+ { GID_SQ4, -1, 708, -1, "nextBut", "doVerb", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Floppy: happens, when looking at the "next" button in the sq4 hintbook - bug #6447
+ { GID_SQ4, -1, 708, -1, ".", "doVerb", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Floppy: happens, when looking at the "next" button... in Russian version - bug #5573
+ { GID_SQ5, 201, 201, 0, "buttonPanel", "doVerb", NULL, 0, { WORKAROUND_FAKE, 1 } }, // when looking at the orange or red button - bug #5112
+ { GID_SQ6, -1, 0, 0, "SQ6", "init", NULL, 2, { WORKAROUND_FAKE, 0 } }, // Demo and full version: called when the game starts (demo: room 0, full: room 100)
+ { GID_SQ6, -1, 64950, -1, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu, when entering the Orion's Belt bar (room 300), and perhaps other places
+ { GID_SQ6, -1, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // during the game
+ { GID_TORIN, -1, 64017, 0, "oFlags", "clear", NULL, 0, { WORKAROUND_FAKE, 0 } }, // entering Torin's home in the French version
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kAbs_workarounds[] = {
- { GID_HOYLE1, 1, 1, 0, "room1", "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // crazy eights - called with objects instead of integers
- { GID_HOYLE1, 2, 2, 0, "room2", "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // old maid - called with objects instead of integers
- { GID_HOYLE1, 3, 3, 0, "room3", "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // hearts - called with objects instead of integers
- { GID_QFG1VGA, -1, -1, 0, NULL, "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // when the game is patched with the NRS patch
- { GID_QFG3 , -1, -1, 0, NULL, "doit", -1, 0, { WORKAROUND_FAKE, 0x3e9 } }, // when the game is patched with the NRS patch - bugs #6042, #6043
+ { GID_HOYLE1, 1, 1, 0, "room1", "doit", NULL, 0, { WORKAROUND_FAKE, 0x3e9 } }, // crazy eights - called with objects instead of integers
+ { GID_HOYLE1, 2, 2, 0, "room2", "doit", NULL, 0, { WORKAROUND_FAKE, 0x3e9 } }, // old maid - called with objects instead of integers
+ { GID_HOYLE1, 3, 3, 0, "room3", "doit", NULL, 0, { WORKAROUND_FAKE, 0x3e9 } }, // hearts - called with objects instead of integers
+ { GID_QFG1VGA, -1, -1, 0, NULL, "doit", NULL, 0, { WORKAROUND_FAKE, 0x3e9 } }, // when the game is patched with the NRS patch
+ { GID_QFG3 , -1, -1, 0, NULL, "doit", NULL, 0, { WORKAROUND_FAKE, 0x3e9 } }, // when the game is patched with the NRS patch - bugs #6042, #6043
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
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 #5049
- { 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 #5012
- { 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 #5144
+ { GID_KQ5, -1, 255, 0, "deathIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when getting beaten up in the inn and probably more, called with 2nd parameter as object - bug #5049
+ { GID_PQ2, -1, 255, 0, "DIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when showing picture within windows, called with 2nd/3rd parameters as objects
+ { GID_SQ1, 1, 255, 0, "DIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // DEMO: Called with 2nd/3rd parameters as objects when clicking on the menu - bug #5012
+ { GID_FANMADE, -1, 979, 0, "DIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // In The Gem Scenario and perhaps other fanmade games, this is called with 2nd/3rd parameters as objects - bug #5144
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
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 #5049
- { 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 #5012
- { 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 #5144
+ { GID_KQ5, -1, 255, 0, "deathIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when getting beaten up in the inn and probably more, called with 2nd parameter as object - bug #5049
+ { GID_PQ2, -1, 255, 0, "DIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when showing picture within windows, called with 2nd/3rd parameters as objects
+ { GID_SQ1, 1, 255, 0, "DIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // DEMO: Called with 2nd/3rd parameters as objects when clicking on the menu - bug #5012
+ { GID_FANMADE, -1, 979, 0, "DIcon", "setSize", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // In The Gem Scenario and perhaps other fanmade games, this is called with 2nd/3rd parameters as objects - bug #5144
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kDeleteKey_workarounds[] = {
- { GID_HOYLE4, 300, 999, 0, "handleEventList", "delete", -1, 0, { WORKAROUND_IGNORE, 0 } }, // restarting hearts, while tray is shown - bug #6604
- { GID_HOYLE4, 500, 999, 0, "handleEventList", "delete", -1, 0, { WORKAROUND_IGNORE, 0 } }, // restarting cribbage, while tray is shown - bug #6604
- { GID_HOYLE4, 975, 999, 0, "handleEventList", "delete", -1, 0, { WORKAROUND_IGNORE, 0 } }, // going back to gamelist from hearts/cribbage, while tray is shown - bug #6604
- SCI_WORKAROUNDENTRY_TERMINATOR
-};
-
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+ { GID_HOYLE4, 300, 999, 0, "handleEventList", "delete", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // restarting hearts, while tray is shown - bug #6604
+ { GID_HOYLE4, 500, 999, 0, "handleEventList", "delete", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // restarting cribbage, while tray is shown - bug #6604
+ { GID_HOYLE4, 975, 999, 0, "handleEventList", "delete", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // going back to gamelist from hearts/cribbage, while tray is shown - bug #6604
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// Game: Fan-Made games (SCI Studio)
+// Calling method: Game::save, Game::restore
+// Subroutine offset: Al Pond 2: 0x0e5c (ldi 0001)
+// Black Cauldron: 0x000a (ldi 01)
+// Cascade Quest: 0x0d1c (ldi 0001)
+// Demo Quest: 0x0e55 (ldi 0001)
+// I want my C64 back: 0x0e57 (ldi 0001)
+// Applies to at least: games listed above
+static const uint16 sig_kDeviceInfo_Fanmade_1[] = {
+ 0x3f, 0x79, // link 79h
+ 0x34, SIG_UINT16(0x0001), // ldi 0001
+ 0xa5, 0x00, // sat temp[0]
+ SIG_END
+};
+static const uint16 sig_kDeviceInfo_Fanmade_2[] = {
+ 0x3f, 0x79, // link 79h
+ 0x35, 0x01, // ldi 01
+ 0xa5, 0x00, // sat temp[0]
+ SIG_END
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kDeviceInfo_workarounds[] = {
- { GID_FANMADE, -1, 994, 1, "Game", "save", 0xd1c, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games, this is called with one parameter for CurDevice (Cascade Quest)
- { GID_FANMADE, -1, 994, 1, "Game", "save", 0xe55, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games, this is called with one parameter for CurDevice (Demo Quest)
- { GID_FANMADE, -1, 994, 1, "Game", "save", 0xe57, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games, this is called with one parameter for CurDevice (I Want My C64 Back)
- { GID_FANMADE, -1, 994, 0, "Black", "save", 0xa, 0, { WORKAROUND_IGNORE, 0 } }, // In fanmade games, this is called with one parameter for CurDevice (Black Cauldron Remake)
- { GID_FANMADE, -1, 994, 1, "Game", "save", 0xe5c, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games, this is called with one parameter for CurDevice (Most of them)
- { GID_FANMADE, -1, 994, 1, "Game", "restore", 0xd1c, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games, this is called with one parameter for CurDevice (Cascade Quest)
- { GID_FANMADE, -1, 994, 1, "Game", "restore", 0xe55, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games, this is called with one parameter for CurDevice (Demo Quest)
- { GID_FANMADE, -1, 994, 1, "Game", "restore", 0xe57, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games, this is called with one parameter for CurDevice (I Want My C64 Back)
- { GID_FANMADE, -1, 994, 1, "Game", "restore", 0xe5c, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games, this is called with one parameter for CurDevice (Most of them)
- SCI_WORKAROUNDENTRY_TERMINATOR
-};
-
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+ { GID_FANMADE, -1, 994, 1, "Game", "save", sig_kDeviceInfo_Fanmade_1, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games (SCI Studio), this is called with one parameter for CurDevice LDI 01 variant
+ { GID_FANMADE, -1, 994, 1, "Game", "save", sig_kDeviceInfo_Fanmade_2, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games (SCI Studio), this is called with one parameter for CurDevice LDI 0001 variant
+ { GID_FANMADE, -1, 994, 0, "Black", "save", sig_kDeviceInfo_Fanmade_1, 0, { WORKAROUND_IGNORE, 0 } }, // In fanmade games (SCI Studio), this is called with one parameter for CurDevice (Black Cauldron Remake)
+ { GID_FANMADE, -1, 994, 1, "Game", "restore", sig_kDeviceInfo_Fanmade_1, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games (SCI Studio), this is called with one parameter for CurDevice LDI 01 variant
+ { GID_FANMADE, -1, 994, 1, "Game", "restore", sig_kDeviceInfo_Fanmade_2, 0, { WORKAROUND_STILLCALL, 0 } }, // In fanmade games (SCI Studio), this is called with one parameter for CurDevice LDI 0001 variant
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// Game: Police Quest 2
+// Calling method: rm23Script::elements
+// Subroutine offset: English 1.001.000: 0x04ae, English 1.002.011: 0x04ca, Japanese: 0x04eb (script 23)
+// Applies to at least: English PC floppy, Japanese PC-9801
+static const uint16 sig_kDisplay_pq2_1[] = {
+ 0x35, 0x00, // ldi 00
+ 0xa3, 0x09, // sal local[9]
+ 0x35, 0x01, // ldi 01
+ 0xa3, 0x0a, // sal local[0Ah]
+ 0x38, SIG_ADDTOOFFSET(+2), // pushi selector[drawPic] TODO: implement selectors
+ 0x7a, // push2
+ 0x39, 0x5a, // pushi 5Ah
+ 0x7a, // push2
+ 0x81, 0x02, // lag global[2]
+ 0x4a, 0x08, // send 08
+ SIG_END
+};
+
+// Game: Space Quest 4
+// Calling method: doCatalog::mode
+// Subroutine offset: English PC CD: 0x0084 (script 391)
+// Applies to at least: English PC CD
+static const uint16 sig_kDisplay_sq4_1[] = {
+ 0x38, SIG_UINT16(0x0187), // pushi 0187h (drawPic)
+ 0x78, // push1
+ 0x38, SIG_UINT16(0x0189), // pushi 0189h (reflectPosn)
+ 0x81, 0x02, // lag global[2]
+ 0x4a, 0x06, // send 06
+ SIG_END
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kDisplay_workarounds[] = {
- { GID_ISLANDBRAIN, 300, 300, 0, "geneDude", "show", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when looking at the gene explanation chart - a parameter is an object
- { GID_PQ2, 23, 23, 0, "rm23Script", "elements", 0x4ae, 0, { WORKAROUND_IGNORE, 0 } }, // when looking at the 2nd page of pate's file - 0x75 as id
- { 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 #5223)
- { GID_QFG1, 11, 11, 0, "battle", "<noname90>", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: When entering battle, 0x75 as id
- { GID_SQ4, 397, 0, 0, "", "export 12", -1, 0, { WORKAROUND_IGNORE, 0 } }, // FLOPPY: when going into the computer store - bug #5227
- { 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
+ { GID_ISLANDBRAIN, 300, 300, 0, "geneDude", "show", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // when looking at the gene explanation chart - a parameter is an object
+ { GID_LONGBOW, 95, 95, 0, "countDown", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during title screen "Robin Hood! Your bow is needed"
+ { GID_LONGBOW, 220, 220, 0, "moveOn", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during second room "Outwit and outfight..."
+ { GID_LONGBOW, 210, 210, 0, "mama", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during third room "Fall under the spell..."
+ { GID_LONGBOW, 320, 320, 0, "flyin", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during fourth room "Conspiracies, love..."
+ { GID_PQ2, 23, 23, 0, "rm23Script", "elements", sig_kDisplay_pq2_1, 0, { WORKAROUND_IGNORE, 0 } }, // when looking at the 2nd page of pate's file - 0x75 as id - bug #5223
+ { GID_QFG1, 11, 11, 0, "battle", "init", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: When entering battle, 0x75 as id
+ { GID_SQ4, 397, 0, 0, "", "export 12", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // FLOPPY: when going into the computer store - bug #5227
+ { GID_SQ4, 391, 391, 0, "doCatalog", "mode", sig_kDisplay_sq4_1, 0, { WORKAROUND_IGNORE, 0 } }, // CD: clicking on catalog in roboter sale - a parameter is an object
+ { GID_SQ4, 391, 391, 0, "choosePlug", "changeState", NULL, 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
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, 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 #5217
+ { GID_KQ4, 4, 992, 0, "Avoid", "doit", NULL, 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 #5217
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, 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 #4967
- { GID_QFG1, -1, 64, 0, "rm64", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when leaving graveyard, parameter 0 is an object
- { GID_SQ4, 150, 151, 0, "fightScript", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during fight with Vohaul, parameter 0 is an object
- { GID_SQ4, 150, 152, 0, "driveCloseUp", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when choosing "beam download", parameter 0 is an object
+ { GID_LAURABOW, 777, 777, 0, "myStab", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: after the will is signed, parameter 0 is an object - bug #4967
+ { GID_LSL2, -1, 54, 0, "rm54", "dispose", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // Amiga: room 55, script tries to kDisposeScript an object (does not happen for DOS) - bug #6818
+ { GID_QFG1, -1, 64, 0, "rm64", "dispose", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // when leaving graveyard, parameter 0 is an object
+ { GID_SQ4, 150, 151, 0, "fightScript", "dispose", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // during fight with Vohaul, parameter 0 is an object
+ { GID_SQ4, 150, 152, 0, "driveCloseUp", "dispose", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // when choosing "beam download", parameter 0 is an object
+ { GID_SQ4, 150, 152, 0, "", "dispose", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // when choosing "beam download"... in Russian version - bug #5573
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kDoSoundFade_workarounds[] = {
- { GID_KQ5, 213, 989, 0, "globalSound3", "fade", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when bandits leave the secret temple, parameter 4 is an object - bug #5078
- { GID_KQ6, 105, 989, 0, "globalSound", "fade", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // floppy: during intro, parameter 4 is an object
- { GID_KQ6, 460, 989, 0, "globalSound2", "fade", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // after pulling the black widow's web on the isle of wonder, parameter 4 is an object - bug #4954
- { GID_QFG4, -1, 64989, 0, "longSong", "fade", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // CD version: many places, parameter 4 is an object (longSong)
- { GID_SQ5, 800, 989, 0, "sq5Music1", "fade", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when cutting the wrong part of Goliath with the laser - bug #6341
+ { GID_KQ5, 213, 989, 0, "globalSound3", "fade", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when bandits leave the secret temple, parameter 4 is an object - bug #5078
+ { GID_KQ6, 105, 989, 0, "globalSound", "fade", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // floppy: during intro, parameter 4 is an object
+ { GID_KQ6, 460, 989, 0, "globalSound2", "fade", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // after pulling the black widow's web on the isle of wonder, parameter 4 is an object - bug #4954
+ { GID_QFG4, -1, 64989, 0, "longSong", "fade", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // CD version: many places, parameter 4 is an object (longSong)
+ { GID_SQ5, 800, 989, 0, "sq5Music1", "fade", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when cutting the wrong part of Goliath with the laser - bug #6341
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kGetAngle_workarounds[] = {
- { GID_FANMADE, 516, 992, 0, "Motion", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // The Legend of the Lost Jewel Demo (fan made): called with third/fourth parameters as objects
- { GID_KQ6, -1, 752, 0, "throwDazzle", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // room 740/790 after the Genie is exposed in the Palace (short and long ending), it starts shooting lightning bolts around. An extra 5th parameter is passed - bug #4959 & #5203
- { GID_SQ1, -1, 927, 0, "PAvoider", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // all rooms in Ulence Flats after getting the Pilot Droid: called with a single parameter when the droid is in Roger's path - bug #6016
+ { GID_FANMADE, 516, 992, 0, "Motion", "init", NULL, 0, { WORKAROUND_FAKE, 0 } }, // The Legend of the Lost Jewel Demo (fan made): called with third/fourth parameters as objects
+ { GID_KQ6, -1, 752, 0, "throwDazzle", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // room 740/790 after the Genie is exposed in the Palace (short and long ending), it starts shooting lightning bolts around. An extra 5th parameter is passed - bug #4959 & #5203
+ { GID_SQ1, -1, 927, 0, "PAvoider", "doit", NULL, 0, { WORKAROUND_FAKE, 0 } }, // all rooms in Ulence Flats after getting the Pilot Droid: called with a single parameter when the droid is in Roger's path - bug #6016
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kFindKey_workarounds[] = {
- { GID_ECOQUEST2, 100, 999, 0, "myList", "contains", -1, 0, { WORKAROUND_FAKE, 0 } }, // When Noah Greene gives Adam the Ecorder, and just before the game gives a demonstration, a null reference to a list is passed - bug #4987
- { GID_HOYLE4, 300, 999, 0, "Piles", "contains", -1, 0, { WORKAROUND_FAKE, 0 } }, // When passing the three cards in Hearts, a null reference to a list is passed - bug #5664
+ { GID_ECOQUEST2, 100, 999, 0, "myList", "contains", NULL, 0, { WORKAROUND_FAKE, 0 } }, // When Noah Greene gives Adam the Ecorder, and just before the game gives a demonstration, a null reference to a list is passed - bug #4987
+ { GID_HOYLE4, 300, 999, 0, "Piles", "contains", NULL, 0, { WORKAROUND_FAKE, 0 } }, // When passing the three cards in Hearts, a null reference to a list is passed - bug #5664
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kGraphDrawLine_workarounds[] = {
- { GID_ISLANDBRAIN, 300, 300, 0, "dudeViewer", "show", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when looking at the gene explanation chart, gets called with 1 extra parameter
- { GID_SQ1, 43, 43, 0, "someoneDied", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when ordering beer, gets called with 1 extra parameter
- { GID_SQ1, 71, 71, 0, "destroyXenon", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // during the Xenon destruction cutscene (which results in death), gets called with 1 extra parameter - bug #5176
- { GID_SQ1, 53, 53, 0, "blastEgo", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when Roger is found and zapped by the cleaning robot, gets called with 1 extra parameter - bug #5177
+ { GID_ISLANDBRAIN, 300, 300, 0, "dudeViewer", "show", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when looking at the gene explanation chart, gets called with 1 extra parameter
+ { GID_SQ1, 43, 43, 0, "someoneDied", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when ordering beer, gets called with 1 extra parameter
+ { GID_SQ1, 71, 71, 0, "destroyXenon", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // during the Xenon destruction cutscene (which results in death), gets called with 1 extra parameter - bug #5176
+ { GID_SQ1, 53, 53, 0, "blastEgo", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when Roger is found and zapped by the cleaning robot, gets called with 1 extra parameter - bug #5177
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// Game: Island of Dr. Brain
+// Calling method: upElevator::changeState, downElevator::changeState, correctElevator::changeState
+// Subroutine offset: 0x201f (script 291)
+// Applies to at least: English PC floppy
+static const uint16 sig_kGraphSaveBox_ibrain_1[] = {
+ 0x3f, 0x01, // link 01
+ 0x87, 0x01, // lap param[1]
+ 0x30, SIG_UINT16(0x0043), // bnt [...]
+ 0x76, // push0
+ SIG_END
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kGraphSaveBox_workarounds[] = {
- { GID_CASTLEBRAIN, 420, 427, 0, "alienIcon", "select", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when selecting a card during the alien card game, gets called with 1 extra parameter
- { GID_ISLANDBRAIN, 290, 291, 0, "upElevator", "changeState",0x201f, 0, { WORKAROUND_STILLCALL, 0 } }, // when testing in the elevator puzzle, gets called with 1 argument less - 15 is on stack - bug #4943
- { GID_ISLANDBRAIN, 290, 291, 0, "downElevator", "changeState",0x201f, 0, { WORKAROUND_STILLCALL, 0 } }, // see above
- { GID_ISLANDBRAIN, 290, 291, 0, "correctElevator", "changeState",0x201f, 0, { WORKAROUND_STILLCALL, 0 } }, // see above (when testing the correct solution)
- { GID_PQ3, 202, 202, 0, "MapEdit", "movePt", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #5099
+ { GID_CASTLEBRAIN, 420, 427, 0, "alienIcon", "select", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when selecting a card during the alien card game, gets called with 1 extra parameter
+ { GID_ISLANDBRAIN, 290, 291, 0, "upElevator", "changeState", sig_kGraphSaveBox_ibrain_1, 0, { WORKAROUND_STILLCALL, 0 } }, // when testing in the elevator puzzle, gets called with 1 argument less - 15 is on stack - bug #4943
+ { GID_ISLANDBRAIN, 290, 291, 0, "downElevator", "changeState", sig_kGraphSaveBox_ibrain_1, 0, { WORKAROUND_STILLCALL, 0 } }, // see above
+ { GID_ISLANDBRAIN, 290, 291, 0, "correctElevator", "changeState", sig_kGraphSaveBox_ibrain_1, 0, { WORKAROUND_STILLCALL, 0 } }, // see above (when testing the correct solution)
+ { GID_PQ3, 202, 202, 0, "MapEdit", "movePt", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #5099
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kGraphRestoreBox_workarounds[] = {
- { GID_LSL6, -1, 86, 0, "LL6Inv", "hide", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens during the game, gets called with 1 extra parameter
+ { GID_LSL6, -1, 86, 0, "LL6Inv", "hide", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // happens during the game, gets called with 1 extra parameter
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kGraphFillBoxForeground_workarounds[] = {
- { GID_LSL6, -1, 0, 0, "LSL6", "hideControls", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens when giving the bungee key to merrily (room 240) and at least in room 650 too - gets called with additional 5th parameter
+ { GID_LSL6, -1, 0, 0, "LSL6", "hideControls", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // happens when giving the bungee key to merrily (room 240) and at least in room 650 too - gets called with additional 5th parameter
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kGraphFillBoxAny_workarounds[] = {
- { GID_ECOQUEST2, 100, 333, 0, "showEcorder", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // necessary workaround for our ecorder script patch, because there isn't enough space to patch the function
- { GID_SQ4, -1, 818, 0, "iconTextSwitch", "show", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // CD: game menu "text/speech" display - parameter 5 is missing, but the right color number is on the stack
+ { GID_ECOQUEST2, 100, 333, 0, "showEcorder", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // necessary workaround for our ecorder script patch, because there isn't enough space to patch the function
+ { GID_SQ4, -1, 818, 0, "iconTextSwitch", "show", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // CD: game menu "text/speech" display - parameter 5 is missing, but the right color number is on the stack
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// Game: Space Quest 4
+// Calling method: laserScript::changeState
+// Subroutine offset: English/German/French/Russian PC floppy, Japanese PC-9801: 0x0016, English PC CD: 0x00b2 (script 150)
+// Applies to at least: English/German/French/Russian PC floppy, English PC CD, Japanese PC-9801
+static const uint16 sig_kGraphRedrawBox_sq4_1[] = {
+ 0x3f, 0x07, // link 07
+ 0x39, SIG_ADDTOOFFSET(+1), // pushi 2Ah for PC floppy, pushi 27h for PC CD
+ 0x76, // push0
+ 0x72, // lofsa laserSound
+ SIG_END
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kGraphRedrawBox_workarounds[] = {
- { GID_SQ4, 405, 405, 0, "swimAfterEgo", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified
- { GID_SQ4, 406, 406, 0, "egoFollowed", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // FLOPPY: when getting shot by the police - accidental additional parameter specified
- { GID_SQ4, 406, 406, 0, "swimAndShoot", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified
- { GID_SQ4, 410, 410, 0, "swimAfterEgo", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified
- { GID_SQ4, 411, 411, 0, "swimAndShoot", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified
- { GID_SQ4, 150, 150, 0, "laserScript", "changeState", 0xb2, 0, { WORKAROUND_STILLCALL, 0 } }, // when visiting the pedestral where Roger Jr. is trapped, before trashing the brain icon in the programming chapter, accidental additional parameter specified - bug #5479
- { GID_SQ4, 150, 150, 0, "laserScript", "changeState", 0x16, 0, { WORKAROUND_STILLCALL, 0 } }, // same as above, for the German version - bug #5527
- { GID_SQ4, -1, 704, 0, "shootEgo", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // When shot by Droid in Super Computer Maze (Rooms 500, 505, 510...) - accidental additional parameter specified
- { GID_KQ5, -1, 981, 0, "myWindow", "dispose", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // Happens in the floppy version, when closing any dialog box, accidental additional parameter specified - bug #5031
- { GID_KQ5, -1, 995, 0, "invW", "doit", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // Happens in the floppy version, when closing the inventory window, accidental additional parameter specified
- { GID_KQ5, -1, 995, 0, "", "export 0", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // Happens in the floppy version, when opening the gem pouch, accidental additional parameter specified - bug #5138
- { GID_KQ5, -1, 403, 0, "KQ5Window", "dispose", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // Happens in the FM Towns version when closing any dialog box, accidental additional parameter specified
- SCI_WORKAROUNDENTRY_TERMINATOR
-};
-
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+ { GID_SQ4, 405, 405, 0, "swimAfterEgo", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified
+ { GID_SQ4, 405, 405, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573
+ { GID_SQ4, 406, 406, 0, "egoFollowed", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // FLOPPY: when getting shot by the police - accidental additional parameter specified
+ { GID_SQ4, -1, 406, 0, "swimAndShoot", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified
+ { GID_SQ4, -1, 406, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573 (is for both egoFollowed and swimAndShoot)
+ { GID_SQ4, 410, 410, 0, "swimAfterEgo", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified
+ { GID_SQ4, 410, 410, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573
+ { GID_SQ4, 411, 411, 0, "swimAndShoot", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified
+ { GID_SQ4, 411, 411, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573
+ { GID_SQ4, 150, 150, 0, "laserScript", "changeState", sig_kGraphRedrawBox_sq4_1, 0, { WORKAROUND_STILLCALL, 0 } }, // when visiting the pedestral where Roger Jr. is trapped, before trashing the brain icon in the programming chapter, accidental additional parameter specified - bug #5479, German - bug #5527
+ { GID_SQ4, 150, 150, 0, "", "changeState", sig_kGraphRedrawBox_sq4_1, 0, { WORKAROUND_STILLCALL, 0 } }, // same as above, for the Russian version - bug #5573
+ { GID_SQ4, -1, 704, 0, "shootEgo", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When shot by Droid in Super Computer Maze (Rooms 500, 505, 510...) - accidental additional parameter specified
+ { GID_KQ5, -1, 981, 0, "myWindow", "dispose", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // Happens in the floppy version, when closing any dialog box, accidental additional parameter specified - bug #5031
+ { GID_KQ5, -1, 995, 0, "invW", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // Happens in the floppy version, when closing the inventory window, accidental additional parameter specified
+ { GID_KQ5, -1, 995, 0, "", "export 0", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // Happens in the floppy version, when opening the gem pouch, accidental additional parameter specified - bug #5138
+ { GID_KQ5, -1, 403, 0, "KQ5Window", "dispose", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // Happens in the FM Towns version when closing any dialog box, accidental additional parameter specified
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kGraphUpdateBox_workarounds[] = {
- { GID_ECOQUEST2, 100, 333, 0, "showEcorder", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // necessary workaround for our ecorder script patch, because there isn't enough space to patch the function
- { GID_PQ3, 202, 202, 0, "MapEdit", "addPt", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #5099
- { GID_PQ3, 202, 202, 0, "MapEdit", "movePt", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #5099
- { GID_PQ3, 202, 202, 0, "MapEdit", "dispose", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters
+ { GID_ECOQUEST2, 100, 333, 0, "showEcorder", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // necessary workaround for our ecorder script patch, because there isn't enough space to patch the function
+ { GID_PQ3, 202, 202, 0, "MapEdit", "addPt", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #5099
+ { GID_PQ3, 202, 202, 0, "MapEdit", "movePt", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters - bug #5099
+ { GID_PQ3, 202, 202, 0, "MapEdit", "dispose", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when plotting crimes, gets called with 2 extra parameters
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kIsObject_workarounds[] = {
- { GID_GK1, 50, 999, 0, "List", "eachElementDo", -1, 0, { WORKAROUND_FAKE, 0 } }, // GK1 demo, when asking Grace for messages it gets called with an invalid parameter (type "error") - bug #4950
- { GID_ISLANDBRAIN, -1, 999, 0, "List", "eachElementDo", -1, 0, { WORKAROUND_FAKE, 0 } }, // when going to the game options, choosing "Info" and selecting anything from the list, gets called with an invalid parameter (type "error") - bug #4989
- { GID_QFG3, -1, 999, 0, "List", "eachElementDo", -1, 0, { WORKAROUND_FAKE, 0 } }, // when asking for something, gets called with type error parameter
+ { GID_GK1, 50, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // GK1 demo, when asking Grace for messages it gets called with an invalid parameter (type "error") - bug #4950
+ { GID_ISLANDBRAIN, -1, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when going to the game options, choosing "Info" and selecting anything from the list, gets called with an invalid parameter (type "error") - bug #4989
+ { GID_QFG3, -1, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when asking for something, gets called with type error parameter
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kMemory_workarounds[] = {
- { GID_LAURABOW2, -1, 999, 0, "", "export 6", -1, 0, { WORKAROUND_FAKE, 0 } }, // during the intro, when exiting the train (room 160), talking to Mr. Augustini, etc. - bug #4944
- { GID_SQ1, -1, 999, 0, "", "export 6", -1, 0, { WORKAROUND_FAKE, 0 } }, // during walking Roger around Ulence Flats - bug #6017
+ { GID_LAURABOW2, -1, 999, 0, "", "export 6", NULL, 0, { WORKAROUND_FAKE, 0 } }, // during the intro, when exiting the train (room 160), talking to Mr. Augustini, etc. - bug #4944
+ { GID_SQ1, -1, 999, 0, "", "export 6", NULL, 0, { WORKAROUND_FAKE, 0 } }, // during walking Roger around Ulence Flats - bug #6017
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kMoveCursor_workarounds[] = {
- { GID_KQ5, -1, 937, 0, "IconBar", "handleEvent", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when pressing escape to open the menu, gets called with one parameter instead of 2 - bug #5575
+ { GID_KQ5, -1, 937, 0, "IconBar", "handleEvent", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // when pressing escape to open the menu, gets called with one parameter instead of 2 - bug #5575
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kNewWindow_workarounds[] = {
- { 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 #4976
+ { GID_ECOQUEST, -1, 981, 0, "SysWindow", "open", NULL, 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 #4976
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kReadNumber_workarounds[] = {
- { GID_CNICK_LAURABOW,100, 101, 0, "dominoes.opt", "doit", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425
- { GID_HOYLE3, 100, 101, 0, "dominoes.opt", "doit", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425
+ { GID_CNICK_LAURABOW,100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425
+ { GID_HOYLE3, 100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[] = {
- { GID_QFG4, 100, 100, 0, "doMovie", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // after the Sierra logo, no flags are passed, thus the call is meaningless - bug #4947
+ { GID_QFG4, 100, 100, 0, "doMovie", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // after the Sierra logo, no flags are passed, thus the call is meaningless - bug #4947
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kSetCursor_workarounds[] = {
- { GID_KQ5, -1, 768, 0, "KQCursor", "init", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // CD: gets called with 4 additional "900d" parameters
+ { GID_KQ5, -1, 768, 0, "KQCursor", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // CD: gets called with 4 additional "900d" parameters
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kSetPort_workarounds[] = {
- { GID_LSL6, 740, 740, 0, "rm740", "drawPic", -1, 0, { WORKAROUND_IGNORE, 0 } }, // ending scene, is called with additional 3 (!) parameters
- { GID_QFG3, 830, 830, 0, "portalOpens", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when the portal appears during the end, gets called with 4 parameters - bug #5174
+ { GID_LSL6, 740, 740, 0, "rm740", "drawPic", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // ending scene, is called with additional 3 (!) parameters
+ { GID_QFG3, 830, 830, 0, "portalOpens", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // when the portal appears during the end, gets called with 4 parameters - bug #5174
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// Game: Island of Dr. Brain
+// Calling method: childBreed::changeState
+// Subroutine offset: 0x1c7c (script 310)
+// Applies to at least: English PC floppy
+static const uint16 sig_kStrAt_ibrain_1[] = {
+ 0x3f, 0x16, // link 16
+ 0x78, // push1
+ 0x8f, 0x01, // lsp param[1]
+ 0x43, // callk StrLen
+ SIG_END
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kStrAt_workarounds[] = {
- { GID_CASTLEBRAIN, 220, 220, 0, "robotJokes", "animateOnce", -1, 0, { WORKAROUND_FAKE, 0 } }, // when trying to view the terminal at the end of the maze without having collected any robot jokes - bug #5127
- { GID_ISLANDBRAIN, 300, 310, 0, "childBreed", "changeState",0x1c7c, 0, { WORKAROUND_FAKE, 0 } }, // when clicking Breed to get the second-generation cyborg hybrid (Standard difficulty), the two parameters are swapped - bug #5088
+ { GID_CASTLEBRAIN, 220, 220, 0, "robotJokes", "animateOnce", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when trying to view the terminal at the end of the maze without having collected any robot jokes - bug #5127
+ { GID_ISLANDBRAIN, 300, 310, 0, "childBreed", "changeState", sig_kStrAt_ibrain_1, 0, { WORKAROUND_FAKE, 0 } }, // when clicking Breed to get the second-generation cyborg hybrid (Standard difficulty), the two parameters are swapped - bug #5088
SCI_WORKAROUNDENTRY_TERMINATOR
};
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kStrCpy_workarounds[] = {
- { GID_MOTHERGOOSE, 23, 23, 0, "talkScript", "changeState", -1, 0, { WORKAROUND_FAKE, 0 } }, // when talking to the girl in scene 23, there's no destination parameter (script bug - wrong instruction order). The original source is used directly afterwards in kDisplay, to show the girl's text - bug #6485
+ { GID_MOTHERGOOSE, 23, 23, 0, "talkScript", "changeState", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when talking to the girl in scene 23, there's no destination parameter (script bug - wrong instruction order). The original source is used directly afterwards in kDisplay, to show the girl's text - bug #6485
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// Game: Quest for Glory 2
+// Calling method: export 21 of script 2
+// Subroutine offset: English 0x0deb (script 2)
+// Applies to at least: English PC floppy
+static const uint16 sig_kStrLen_qfg2_1[] = {
+ 0x3f, 0x04, // link 04
+ 0x78, // push1
+ 0x8f, 0x02, // lsp param[2]
+ 0x43, // callk StrLen
+ SIG_END
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kStrLen_workarounds[] = {
- { GID_QFG2, 210, 2, 0, "", "export 21", 0xdeb, 0, { WORKAROUND_FAKE, 0 } }, // When saying something incorrect at the WIT, an integer is passed instead of a reference - bug #5489
+ { GID_QFG2, 210, 2, 0, "", "export 21", sig_kStrLen_qfg2_1, 0, { WORKAROUND_FAKE, 0 } }, // When saying something incorrect at the WIT, an integer is passed instead of a reference - bug #5489
SCI_WORKAROUNDENTRY_TERMINATOR
};
-// gameID, room,script,lvl, object-name, method-name, call,index, workaround
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kUnLoad_workarounds[] = {
- { GID_ECOQUEST, 380, 61, 0, "gotIt", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // CD version: after talking to the dolphin the first time, a 3rd parameter is passed by accident
- { GID_ECOQUEST, 380, 69, 0, "lookAtBlackBoard", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // German version, when closing the blackboard closeup in the dolphin room, a 3rd parameter is passed by accident - bug #5483
- { GID_LAURABOW2, -1, -1, 0, "sCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during the intro, a 3rd parameter is passed by accident - bug #4966
- { GID_LSL6, 130, 130, 0, "recruitLarryScr", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident
- { GID_LSL6, 740, 740, 0, "showCartoon", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during ending, 4 additional parameters are passed by accident
- { GID_LSL6HIRES, 130, 130, 0, "recruitLarryScr", "changeState", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident
- { GID_SQ1, 43, 303, 0, "slotGuy", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // when leaving ulence flats bar, parameter 1 is not passed - script error
- { GID_QFG4, -1, 110, 0, "dreamer", "dispose", -1, 0, { WORKAROUND_IGNORE, 0 } }, // during the dream sequence, a 3rd parameter is passed by accident
+ { GID_ECOQUEST, 380, 61, 0, "gotIt", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // CD version: after talking to the dolphin the first time, a 3rd parameter is passed by accident
+ { GID_ECOQUEST, 380, 69, 0, "lookAtBlackBoard", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // German version, when closing the blackboard closeup in the dolphin room, a 3rd parameter is passed by accident - bug #5483
+ { GID_LAURABOW2, -1, -1, 0, "sCartoon", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // DEMO: during the intro, a 3rd parameter is passed by accident - bug #4966
+ { GID_LSL6, 130, 130, 0, "recruitLarryScr", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident
+ { GID_LSL6, 740, 740, 0, "showCartoon", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // during ending, 4 additional parameters are passed by accident
+ { GID_LSL6HIRES, 130, 130, 0, "recruitLarryScr", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // during intro, a 3rd parameter is passed by accident
+ { GID_SQ1, 43, 303, 0, "slotGuy", "dispose", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // when leaving ulence flats bar, parameter 1 is not passed - script error
+ { GID_QFG4, -1, 110, 0, "dreamer", "dispose", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // during the dream sequence, a 3rd parameter is passed by accident
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -460,8 +751,9 @@ SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroun
ExecStack *lastCall = state->xs;
const Script *localScript = state->_segMan->getScriptIfLoaded(lastCall->local_segment);
int curScriptNr = localScript->getScriptNumber();
+ int curLocalCallOffset = lastCall->debugLocalCallOffset;
- if (lastCall->debugLocalCallOffset != -1) {
+ if (curLocalCallOffset != -1) {
// if lastcall was actually a local call search back for a real call
Common::List<ExecStack>::const_iterator callIterator = state->_executionStack.end();
while (callIterator != state->_executionStack.begin()) {
@@ -493,19 +785,17 @@ SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroun
// Search if there is a workaround for this one
const SciWorkaroundEntry *workaround;
int16 inheritanceLevel = 0;
- Common::String searchObjectName = curObjectName;
+ Common::String searchObjectName = g_sci->getSciLanguageString(curObjectName, K_LANG_ENGLISH);
reg_t searchObject = lastCall->sendp;
+ const byte *curScriptPtr = NULL;
+ uint32 curScriptSize = 0;
+ bool matched = false;
+
do {
workaround = workaroundList;
while (workaround->methodName) {
bool objectNameMatches = (workaround->objectName == NULL) ||
- (workaround->objectName == g_sci->getSciLanguageString(searchObjectName, K_LANG_ENGLISH));
-
- // Special case: in the fanmade Russian translation of SQ4, all
- // of the object names have been deleted or renamed to Russian,
- // thus we disable checking of the object name. Fixes bug #5573.
- if (g_sci->getLanguage() == Common::RU_RUS && g_sci->getGameId() == GID_SQ4)
- objectNameMatches = true;
+ (workaround->objectName == searchObjectName);
if (workaround->gameId == gameId
&& ((workaround->scriptNr == -1) || (workaround->scriptNr == curScriptNr))
@@ -513,10 +803,46 @@ SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroun
&& ((workaround->inheritanceLevel == -1) || (workaround->inheritanceLevel == inheritanceLevel))
&& objectNameMatches
&& workaround->methodName == g_sci->getSciLanguageString(curMethodName, K_LANG_ENGLISH)
- && workaround->localCallOffset == lastCall->debugLocalCallOffset
&& ((workaround->index == -1) || (workaround->index == index))) {
// Workaround found
- return workaround->newValue;
+ if ((workaround->localCallSignature) || (curLocalCallOffset >= 0)) {
+ // local call signature found and/or subcall was made
+ if ((workaround->localCallSignature) && (curLocalCallOffset >= 0)) {
+ // local call signature found and subcall was made -> check signature accordingly
+ if (!curScriptPtr) {
+ // get script data
+ int segmentId = g_sci->getEngineState()->_segMan->getScriptSegment(curScriptNr);
+ SegmentObj *segmentObj = NULL;
+ if (segmentId) {
+ segmentObj = g_sci->getEngineState()->_segMan->getScriptIfLoaded(segmentId);
+ }
+ if (!segmentObj) {
+ workaround++;
+ continue;
+ }
+ Script *scriptObj = (Script *)segmentObj;
+ curScriptPtr = scriptObj->getBuf();
+ curScriptSize = scriptObj->getScriptSize();
+ }
+
+ // now actually check for signature match
+ if (g_sci->getScriptPatcher()->verifySignature(curLocalCallOffset, workaround->localCallSignature, "workaround signature", curScriptPtr, curScriptSize)) {
+ matched = true;
+ }
+
+ } else {
+ // mismatch, so workaround doesn't match
+ workaround++;
+ continue;
+ }
+ } else {
+ // no localcalls involved -> workaround matches
+ matched = true;
+ }
+ if (matched) {
+ debugC(kDebugLevelWorkarounds, "Workaround: '%s:%s' in script %d, localcall %x", workaround->objectName, workaround->methodName, curScriptNr, curLocalCallOffset);
+ return workaround->newValue;
+ }
}
workaround++;
}
diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h
index 9cad618481..46059a175c 100644
--- a/engines/sci/engine/workarounds.h
+++ b/engines/sci/engine/workarounds.h
@@ -60,7 +60,7 @@ struct SciWorkaroundEntry {
int16 inheritanceLevel;
const char *objectName;
const char *methodName;
- int localCallOffset;
+ const uint16 *localCallSignature;
int index;
SciWorkaroundSolution newValue;
};
diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp
index 511d2014bd..b1c002413d 100644
--- a/engines/sci/event.cpp
+++ b/engines/sci/event.cpp
@@ -262,7 +262,7 @@ SciEvent EventManager::getScummVMEvent() {
// Scancodify if appropriate
if (modifiers & Common::KBD_ALT)
input.character = altify(input.character);
- else if ((modifiers & Common::KBD_CTRL) && input.character > 0 && input.character < 27)
+ if (getSciVersion() <= SCI_VERSION_1_MIDDLE && (modifiers & Common::KBD_CTRL) && input.character > 0 && input.character < 27)
input.character += 96; // 0x01 -> 'a'
// If no actual key was pressed (e.g. if only a modifier key was pressed),
diff --git a/engines/sci/graphics/controls16.cpp b/engines/sci/graphics/controls16.cpp
index f2b2ccdfe6..e2e250cf9d 100644
--- a/engines/sci/graphics/controls16.cpp
+++ b/engines/sci/graphics/controls16.cpp
@@ -280,7 +280,7 @@ int GfxControls16::getPicNotValid() {
return _screen->_picNotValid;
}
-void GfxControls16::kernelDrawButton(Common::Rect rect, reg_t obj, const char *text, int16 fontId, int16 style, bool hilite) {
+void GfxControls16::kernelDrawButton(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 style, bool hilite) {
int16 sci0EarlyPen = 0, sci0EarlyBack = 0;
if (!hilite) {
if (getSciVersion() == SCI_VERSION_0_EARLY) {
@@ -295,7 +295,7 @@ void GfxControls16::kernelDrawButton(Common::Rect rect, reg_t obj, const char *t
_paint16->frameRect(rect);
rect.grow(-2);
_ports->textGreyedOutput(!(style & SCI_CONTROLS_STYLE_ENABLED));
- _text16->Box(text, false, rect, SCI_TEXT16_ALIGNMENT_CENTER, fontId);
+ _text16->Box(text, languageSplitter, false, rect, SCI_TEXT16_ALIGNMENT_CENTER, fontId);
_ports->textGreyedOutput(false);
rect.grow(1);
if (style & SCI_CONTROLS_STYLE_SELECTED)
@@ -318,12 +318,12 @@ void GfxControls16::kernelDrawButton(Common::Rect rect, reg_t obj, const char *t
}
}
-void GfxControls16::kernelDrawText(Common::Rect rect, reg_t obj, const char *text, int16 fontId, TextAlignment alignment, int16 style, bool hilite) {
+void GfxControls16::kernelDrawText(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, TextAlignment alignment, int16 style, bool hilite) {
if (!hilite) {
rect.grow(1);
_paint16->eraseRect(rect);
rect.grow(-1);
- _text16->Box(text, false, rect, alignment, fontId);
+ _text16->Box(text, languageSplitter, false, rect, alignment, fontId);
if (style & SCI_CONTROLS_STYLE_SELECTED) {
_paint16->frameRect(rect);
}
@@ -335,7 +335,7 @@ void GfxControls16::kernelDrawText(Common::Rect rect, reg_t obj, const char *tex
}
}
-void GfxControls16::kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char *text, int16 fontId, int16 mode, int16 style, int16 cursorPos, int16 maxChars, bool hilite) {
+void GfxControls16::kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 mode, int16 style, int16 cursorPos, int16 maxChars, bool hilite) {
Common::Rect textRect = rect;
uint16 oldFontId = _text16->GetFontId();
@@ -343,7 +343,7 @@ void GfxControls16::kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char
_texteditCursorVisible = false;
texteditCursorErase();
_paint16->eraseRect(rect);
- _text16->Box(text, false, textRect, SCI_TEXT16_ALIGNMENT_LEFT, fontId);
+ _text16->Box(text, languageSplitter, false, textRect, SCI_TEXT16_ALIGNMENT_LEFT, fontId);
_paint16->frameRect(rect);
if (style & SCI_CONTROLS_STYLE_SELECTED) {
_text16->SetFont(fontId);
diff --git a/engines/sci/graphics/controls16.h b/engines/sci/graphics/controls16.h
index 6a70c71aae..39ffa243fb 100644
--- a/engines/sci/graphics/controls16.h
+++ b/engines/sci/graphics/controls16.h
@@ -55,9 +55,9 @@ public:
GfxControls16(SegManager *segMan, GfxPorts *ports, GfxPaint16 *paint16, GfxText16 *text16, GfxScreen *screen);
~GfxControls16();
- void kernelDrawButton(Common::Rect rect, reg_t obj, const char *text, int16 fontId, int16 style, bool hilite);
- void kernelDrawText(Common::Rect rect, reg_t obj, const char *text, int16 fontId, int16 alignment, int16 style, bool hilite);
- void kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char *text, int16 fontId, int16 mode, int16 style, int16 cursorPos, int16 maxChars, bool hilite);
+ void kernelDrawButton(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 style, bool hilite);
+ void kernelDrawText(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 alignment, int16 style, bool hilite);
+ void kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 mode, int16 style, int16 cursorPos, int16 maxChars, bool hilite);
void kernelDrawIcon(Common::Rect rect, reg_t obj, GuiResourceId viewId, int16 loopNo, int16 celNo, int16 priority, int16 style, bool hilite);
void kernelDrawList(Common::Rect rect, reg_t obj, int16 maxChars, int16 count, const char **entries, GuiResourceId fontId, int16 style, int16 upperPos, int16 cursorPos, bool isAlias, bool hilite);
void kernelTexteditChange(reg_t controlObject, reg_t eventObject);
diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp
index 048ec1e9b9..1a58de073c 100644
--- a/engines/sci/graphics/cursor.cpp
+++ b/engines/sci/graphics/cursor.cpp
@@ -47,7 +47,7 @@ GfxCursor::GfxCursor(ResourceManager *resMan, GfxPalette *palette, GfxScreen *sc
_isVisible = true;
// center mouse cursor
- setPosition(Common::Point(_screen->getWidth() / 2, _screen->getHeight() / 2));
+ setPosition(Common::Point(_screen->getScriptWidth() / 2, _screen->getScriptHeight() / 2));
_moveZoneActive = false;
_zoomZoneActive = false;
@@ -151,14 +151,14 @@ void GfxCursor::kernelSetShape(GuiResourceId resourceId) {
colorMapping[0] = 0; // Black is hardcoded
colorMapping[1] = _screen->getColorWhite(); // White is also hardcoded
colorMapping[2] = SCI_CURSOR_SCI0_TRANSPARENCYCOLOR;
- colorMapping[3] = _palette->matchColor(170, 170, 170); // Grey
+ colorMapping[3] = _palette->matchColor(170, 170, 170) & SCI_PALETTE_MATCH_COLORMASK; // Grey
// TODO: Figure out if the grey color is hardcoded
// HACK for the magnifier cursor in LB1, fixes its color (bug #3487092)
if (g_sci->getGameId() == GID_LAURABOW && resourceId == 1)
colorMapping[3] = _screen->getColorWhite();
// HACK for Longbow cursors, fixes the shade of grey they're using (bug #3489101)
if (g_sci->getGameId() == GID_LONGBOW)
- colorMapping[3] = _palette->matchColor(223, 223, 223); // Light Grey
+ colorMapping[3] = _palette->matchColor(223, 223, 223) & SCI_PALETTE_MATCH_COLORMASK; // Light Grey
// Seek to actual data
resourceData += 4;
@@ -481,7 +481,7 @@ void GfxCursor::kernelSetPos(Common::Point pos) {
void GfxCursor::kernelMoveCursor(Common::Point pos) {
_coordAdjuster->moveCursor(pos);
- if (pos.x > _screen->getWidth() || pos.y > _screen->getHeight()) {
+ if (pos.x > _screen->getScriptWidth() || pos.y > _screen->getScriptHeight()) {
warning("attempt to place cursor at invalid coordinates (%d, %d)", pos.y, pos.x);
return;
}
diff --git a/engines/sci/graphics/font.cpp b/engines/sci/graphics/font.cpp
index e4684ff134..2268ec0459 100644
--- a/engines/sci/graphics/font.cpp
+++ b/engines/sci/graphics/font.cpp
@@ -48,8 +48,8 @@ GfxFontFromResource::GfxFontFromResource(ResourceManager *resMan, GfxScreen *scr
// filling info for every char
for (int16 i = 0; i < _numChars; i++) {
_chars[i].offset = READ_SCI32ENDIAN_UINT16(_resourceData + 6 + i * 2);
- _chars[i].w = _resourceData[_chars[i].offset];
- _chars[i].h = _resourceData[_chars[i].offset + 1];
+ _chars[i].width = _resourceData[_chars[i].offset];
+ _chars[i].height = _resourceData[_chars[i].offset + 1];
}
}
@@ -66,10 +66,10 @@ byte GfxFontFromResource::getHeight() {
return _fontHeight;
}
byte GfxFontFromResource::getCharWidth(uint16 chr) {
- return chr < _numChars ? _chars[chr].w : 0;
+ return chr < _numChars ? _chars[chr].width : 0;
}
byte GfxFontFromResource::getCharHeight(uint16 chr) {
- return chr < _numChars ? _chars[chr].h : 0;
+ return chr < _numChars ? _chars[chr].height : 0;
}
byte *GfxFontFromResource::getCharData(uint16 chr) {
return chr < _numChars ? _resourceData + _chars[chr].offset + 2 : 0;
diff --git a/engines/sci/graphics/font.h b/engines/sci/graphics/font.h
index 58b2ba4813..451261f315 100644
--- a/engines/sci/graphics/font.h
+++ b/engines/sci/graphics/font.h
@@ -71,9 +71,11 @@ private:
byte *_resourceData;
struct Charinfo {
- byte w, h;
+ byte width;
+ byte height;
int16 offset;
};
+
byte _fontHeight;
uint16 _numChars;
Charinfo *_chars;
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index fafc734895..ccc362dc37 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -524,6 +524,10 @@ void GfxFrameout::showVideo() {
RobotDecoder *videoDecoder = g_sci->_robotDecoder;
uint16 x = videoDecoder->getPos().x;
uint16 y = videoDecoder->getPos().y;
+ uint16 screenWidth = _screen->getWidth();
+ uint16 screenHeight = _screen->getHeight();
+ uint16 outputWidth;
+ uint16 outputHeight;
if (videoDecoder->hasDirtyPalette())
g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
@@ -532,7 +536,11 @@ void GfxFrameout::showVideo() {
if (videoDecoder->needsUpdate()) {
const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
if (frame) {
- g_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, frame->w, frame->h);
+ // We need to clip here
+ // At least Phantasmagoria shows a 640x390 video on a 630x450 screen during the intro
+ outputWidth = frame->w > screenWidth ? screenWidth : frame->w;
+ outputHeight = frame->h > screenHeight ? screenHeight : frame->h;
+ g_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, outputWidth, outputHeight);
if (videoDecoder->hasDirtyPalette())
g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp
index b835eb92ca..6004e9ce7a 100644
--- a/engines/sci/graphics/paint16.cpp
+++ b/engines/sci/graphics/paint16.cpp
@@ -471,12 +471,9 @@ void GfxPaint16::kernelGraphRedrawBox(Common::Rect rect) {
#define SCI_DISPLAY_WIDTH 106
#define SCI_DISPLAY_SAVEUNDER 107
#define SCI_DISPLAY_RESTOREUNDER 108
-#define SCI_DISPLAY_DUMMY1 114
-#define SCI_DISPLAY_DUMMY2 115
-#define SCI_DISPLAY_DUMMY3 117
#define SCI_DISPLAY_DONTSHOWBITS 121
-reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) {
+reg_t GfxPaint16::kernelDisplay(const char *text, uint16 languageSplitter, int argc, reg_t *argv) {
reg_t displayArg;
TextAlignment alignment = SCI_TEXT16_ALIGNMENT_LEFT;
int16 colorPen = -1, colorBack = -1, width = -1, bRedraw = 1;
@@ -543,22 +540,6 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) {
bRedraw = 0;
break;
- // The following three dummy calls are not supported by the Sierra SCI
- // interpreter, but are erroneously called in some game scripts.
- case SCI_DISPLAY_DUMMY1: // Longbow demo (all rooms) and QFG1 EGA demo (room 11)
- case SCI_DISPLAY_DUMMY2: // Longbow demo (all rooms)
- case SCI_DISPLAY_DUMMY3: // QFG1 EGA demo (room 11) and PQ2 (room 23)
- if (!(g_sci->getGameId() == GID_LONGBOW && g_sci->isDemo()) &&
- !(g_sci->getGameId() == GID_QFG1 && g_sci->isDemo()) &&
- !(g_sci->getGameId() == GID_PQ2))
- error("Unknown kDisplay argument %d", displayArg.getOffset());
-
- if (displayArg.getOffset() == SCI_DISPLAY_DUMMY2) {
- if (!argc)
- error("No parameter left for kDisplay(115)");
- argc--; argv++;
- }
- break;
default:
SciTrackOriginReply originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kDisplay_workarounds, &originReply);
@@ -572,7 +553,7 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) {
}
// now drawing the text
- _text16->Size(rect, text, -1, width);
+ _text16->Size(rect, text, languageSplitter, -1, width);
rect.moveTo(_ports->getPort()->curLeft, _ports->getPort()->curTop);
// Note: This code has been found in SCI1 middle and newer games. It was
// previously only for SCI1 late and newer, but the LSL1 interpreter contains
@@ -588,7 +569,7 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) {
result = bitsSave(rect, GFX_SCREEN_MASK_VISUAL);
if (colorBack != -1)
fillRect(rect, GFX_SCREEN_MASK_VISUAL, colorBack, 0, 0);
- _text16->Box(text, false, rect, alignment, -1);
+ _text16->Box(text, languageSplitter, false, rect, alignment, -1);
if (_screen->_picNotValid == 0 && bRedraw)
bitsShow(rect);
// restoring port and cursor pos
diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h
index 882f311a5b..955cfdec8f 100644
--- a/engines/sci/graphics/paint16.h
+++ b/engines/sci/graphics/paint16.h
@@ -80,7 +80,7 @@ public:
void kernelGraphUpdateBox(const Common::Rect &rect, bool hiresMode);
void kernelGraphRedrawBox(Common::Rect rect);
- reg_t kernelDisplay(const char *text, int argc, reg_t *argv);
+ reg_t kernelDisplay(const char *text, uint16 languageSplitter, int argc, reg_t *argv);
reg_t kernelPortraitLoad(const Common::String &resourceName);
void kernelPortraitShow(const Common::String &resourceName, Common::Point position, uint16 resourceNum, uint16 noun, uint16 verb, uint16 cond, uint16 seq);
diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp
index 7d106b5b02..a210a469f1 100644
--- a/engines/sci/graphics/paint32.cpp
+++ b/engines/sci/graphics/paint32.cpp
@@ -46,7 +46,7 @@ void GfxPaint32::fillRect(Common::Rect rect, byte color) {
Common::Rect clipRect = rect;
clipRect.clip(_screen->getWidth(), _screen->getHeight());
-
+
for (y = clipRect.top; y < clipRect.bottom; y++) {
for (x = clipRect.left; x < clipRect.right; x++) {
_screen->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, color, 0, 0);
diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp
index a3624c7959..59abef5550 100644
--- a/engines/sci/graphics/palette.cpp
+++ b/engines/sci/graphics/palette.cpp
@@ -65,14 +65,21 @@ GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen)
// the real merging done in earlier games. If we use the copying over, we
// will get issues because some views have marked all colors as being used
// and those will overwrite the current palette in that case
- if (getSciVersion() < SCI_VERSION_1_1)
+ if (getSciVersion() < SCI_VERSION_1_1) {
_useMerging = true;
- else if (getSciVersion() == SCI_VERSION_1_1)
+ _use16bitColorMatch = true;
+ } else if (getSciVersion() == SCI_VERSION_1_1) {
// there are some games that use inbetween SCI1.1 interpreter, so we have
// to detect if the current game is merging or copying
_useMerging = _resMan->detectPaletteMergingSci11();
- else // SCI32
+ _use16bitColorMatch = _useMerging;
+ // Note: Laura Bow 2 floppy uses the new palette format and is detected
+ // as 8 bit color matching because of that.
+ } else {
+ // SCI32
_useMerging = false;
+ _use16bitColorMatch = false; // not verified that SCI32 uses 8-bit color matching
+ }
palVaryInit();
@@ -120,6 +127,10 @@ bool GfxPalette::isMerging() {
return _useMerging;
}
+bool GfxPalette::isUsing16bitColorMatch() {
+ return _use16bitColorMatch;
+}
+
// meant to get called only once during init of engine
void GfxPalette::setDefault() {
if (_resMan->getViewType() == kViewEga)
@@ -464,8 +475,8 @@ bool GfxPalette::merge(Palette *newPalette, bool force, bool forceRealMerge) {
// check if exact color could be matched
res = matchColor(newPalette->colors[i].r, newPalette->colors[i].g, newPalette->colors[i].b);
- if (res & 0x8000) { // exact match was found
- newPalette->mapping[i] = res & 0xFF;
+ if (res & SCI_PALETTE_MATCH_PERFECT) { // exact match was found
+ newPalette->mapping[i] = res & SCI_PALETTE_MATCH_COLORMASK;
continue;
}
@@ -486,8 +497,8 @@ bool GfxPalette::merge(Palette *newPalette, bool force, bool forceRealMerge) {
// if still no luck - set an approximate color
if (j == 256) {
- newPalette->mapping[i] = res & 0xFF;
- _sysPalette.colors[res & 0xFF].used |= 0x10;
+ newPalette->mapping[i] = res & SCI_PALETTE_MATCH_COLORMASK;
+ _sysPalette.colors[res & SCI_PALETTE_MATCH_COLORMASK].used |= 0x10;
}
}
@@ -509,29 +520,47 @@ void GfxPalette::drewPicture(GuiResourceId pictureId) {
}
}
-uint16 GfxPalette::matchColor(byte r, byte g, byte b) {
- byte found = 0xFF;
- int diff = 0x2FFFF, cdiff;
- int16 dr,dg,db;
-
- for (int i = 1; i < 255; i++) {
- if ((!_sysPalette.colors[i].used))
- continue;
- dr = _sysPalette.colors[i].r - r;
- dg = _sysPalette.colors[i].g - g;
- db = _sysPalette.colors[i].b - b;
-// minimum squares match
- cdiff = (dr*dr) + (dg*dg) + (db*db);
-// minimum sum match (Sierra's)
-// cdiff = ABS(dr) + ABS(dg) + ABS(db);
- if (cdiff < diff) {
- if (cdiff == 0)
- return i | 0x8000; // setting this flag to indicate exact match
- found = i;
- diff = cdiff;
+uint16 GfxPalette::matchColor(byte matchRed, byte matchGreen, byte matchBlue) {
+ int16 colorNr;
+ int16 differenceRed, differenceGreen, differenceBlue;
+ int16 differenceTotal = 0;
+ int16 bestDifference = 0x7FFF;
+ uint16 bestColorNr = 255;
+
+ if (_use16bitColorMatch) {
+ // used by SCI0 to SCI1, also by the first few SCI1.1 games
+ for (colorNr = 0; colorNr < 256; colorNr++) {
+ if ((!_sysPalette.colors[colorNr].used))
+ continue;
+ differenceRed = ABS(_sysPalette.colors[colorNr].r - matchRed);
+ differenceGreen = ABS(_sysPalette.colors[colorNr].g - matchGreen);
+ differenceBlue = ABS(_sysPalette.colors[colorNr].b - matchBlue);
+ differenceTotal = differenceRed + differenceGreen + differenceBlue;
+ if (differenceTotal <= bestDifference) {
+ bestDifference = differenceTotal;
+ bestColorNr = colorNr;
+ }
+ }
+ } else {
+ // SCI1.1, starting with QfG3 introduced a bug in the matching code
+ // we have to implement it as well, otherwise some colors will be "wrong" in comparison to the original interpreter
+ // See Space Quest 5 bug #6455
+ for (colorNr = 0; colorNr < 256; colorNr++) {
+ if ((!_sysPalette.colors[colorNr].used))
+ continue;
+ differenceRed = (uint8)ABS<int8>(_sysPalette.colors[colorNr].r - matchRed);
+ differenceGreen = (uint8)ABS<int8>(_sysPalette.colors[colorNr].g - matchGreen);
+ differenceBlue = (uint8)ABS<int8>(_sysPalette.colors[colorNr].b - matchBlue);
+ differenceTotal = differenceRed + differenceGreen + differenceBlue;
+ if (differenceTotal <= bestDifference) {
+ bestDifference = differenceTotal;
+ bestColorNr = colorNr;
+ }
}
}
- return found;
+ if (differenceTotal == 0) // original interpreter does not do this, instead it does 2 calls for merges in the worst case
+ return bestColorNr | SCI_PALETTE_MATCH_PERFECT; // we set this flag, so that we can optimize during palette merge
+ return bestColorNr;
}
void GfxPalette::getSys(Palette *pal) {
@@ -621,7 +650,7 @@ void GfxPalette::kernelSetIntensity(uint16 fromColor, uint16 toColor, uint16 int
}
int16 GfxPalette::kernelFindColor(uint16 r, uint16 g, uint16 b) {
- return matchColor(r, g, b) & 0xFF;
+ return matchColor(r, g, b) & SCI_PALETTE_MATCH_COLORMASK;
}
// Returns true, if palette got changed
diff --git a/engines/sci/graphics/palette.h b/engines/sci/graphics/palette.h
index 347695deb8..500a45eccf 100644
--- a/engines/sci/graphics/palette.h
+++ b/engines/sci/graphics/palette.h
@@ -31,6 +31,10 @@ namespace Sci {
class ResourceManager;
class GfxScreen;
+// Special flag implemented by us for optimization in palette merge
+#define SCI_PALETTE_MATCH_PERFECT 0x8000
+#define SCI_PALETTE_MATCH_COLORMASK 0xFF
+
enum ColorRemappingType {
kRemappingNone = 0,
kRemappingByRange = 1,
@@ -46,6 +50,7 @@ public:
~GfxPalette();
bool isMerging();
+ bool isUsing16bitColorMatch();
void setDefault();
void createFromData(byte *data, int bytesLeft, Palette *paletteOut);
@@ -124,6 +129,7 @@ private:
bool _sysPaletteChanged;
bool _useMerging;
+ bool _use16bitColorMatch;
Common::Array<PalSchedule> _schedules;
diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp
index 434a490109..d7ef84dc1e 100644
--- a/engines/sci/graphics/picture.cpp
+++ b/engines/sci/graphics/picture.cpp
@@ -88,10 +88,13 @@ void GfxPicture::draw(int16 animationNr, bool mirroredFlag, bool addToFlag, int1
}
void GfxPicture::reset() {
+ int16 startY = _ports->getPort()->top;
+ int16 startX = 0;
int16 x, y;
- for (y = _ports->getPort()->top; y < _screen->getHeight(); y++) {
- for (x = 0; x < _screen->getWidth(); x++) {
- _screen->putPixel(x, y, GFX_SCREEN_MASK_ALL, 255, 0, 0);
+ _screen->vectorAdjustCoordinate(&startX, &startY);
+ for (y = startY; y < _screen->getHeight(); y++) {
+ for (x = startX; x < _screen->getWidth(); x++) {
+ _screen->vectorPutPixel(x, y, GFX_SCREEN_MASK_ALL, 255, 0, 0);
}
}
}
@@ -246,7 +249,7 @@ void GfxPicture::drawCelData(byte *inbuffer, int size, int headerPos, int rlePos
int16 y, lastY, x, leftX, rightX;
int pixelCount;
uint16 width, height;
-
+
// if the picture is not an overlay and we are also not in EGA mode, use priority 0
if (!isEGA && !_addToFlag)
priority = 0;
@@ -362,7 +365,7 @@ void GfxPicture::drawCelData(byte *inbuffer, int size, int headerPos, int rlePos
ptr = celBitmap;
ptr += skipCelBitmapPixels;
ptr += skipCelBitmapLines * width;
-
+
if ((!isEGA) || (priority < 16)) {
// VGA + EGA, EGA only checks priority, when given priority is below 16
if (!_mirroredFlag) {
@@ -482,6 +485,8 @@ enum {
PIC_OPX_VGA_PRIORITY_TABLE_EXPLICIT = 4
};
+//#define DEBUG_PICTURE_DRAW 1
+
#ifdef DEBUG_PICTURE_DRAW
const char *picOpcodeNames[] = {
"Set color",
@@ -589,6 +594,9 @@ void GfxPicture::drawVectorData(byte *data, int dataSize) {
while (curPos < dataSize) {
#ifdef DEBUG_PICTURE_DRAW
debug("Picture op: %X (%s) at %d", data[curPos], picOpcodeNames[data[curPos] - 0xF0], curPos);
+ _screen->copyToScreen();
+ g_system->updateScreen();
+ g_system->delayMillis(400);
#endif
switch (pic_op = data[curPos++]) {
case PIC_OP_SET_COLOR:
@@ -934,17 +942,17 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by
Common::Point p, p1;
byte screenMask = _screen->getDrawingMask(color, priority, control);
byte matchedMask, matchMask;
- int16 w, e, a_set, b_set;
bool isEGA = (_resMan->getViewType() == kViewEga);
p.x = x + curPort->left;
p.y = y + curPort->top;
- stack.push(p);
- byte searchColor = _screen->getVisual(p.x, p.y);
- byte searchPriority = _screen->getPriority(p.x, p.y);
- byte searchControl = _screen->getControl(p.x, p.y);
+ _screen->vectorAdjustCoordinate(&p.x, &p.y);
+
+ byte searchColor = _screen->vectorGetVisual(p.x, p.y);
+ byte searchPriority = _screen->vectorGetPriority(p.x, p.y);
+ byte searchControl = _screen->vectorGetControl(p.x, p.y);
if (isEGA) {
// In EGA games a pixel in the framebuffer is only 4 bits. We store
@@ -991,22 +999,31 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by
}
// hard borders for filling
- int l = curPort->rect.left + curPort->left;
- int t = curPort->rect.top + curPort->top;
- int r = curPort->rect.right + curPort->left - 1;
- int b = curPort->rect.bottom + curPort->top - 1;
+ int16 borderLeft = curPort->rect.left + curPort->left;
+ int16 borderTop = curPort->rect.top + curPort->top;
+ int16 borderRight = curPort->rect.right + curPort->left - 1;
+ int16 borderBottom = curPort->rect.bottom + curPort->top - 1;
+ int16 curToLeft, curToRight, a_set, b_set;
+
+ // Translate coordinates, if required (needed for Macintosh 480x300)
+ _screen->vectorAdjustCoordinate(&borderLeft, &borderTop);
+ _screen->vectorAdjustCoordinate(&borderRight, &borderBottom);
+ //return;
+
+ stack.push(p);
+
while (stack.size()) {
p = stack.pop();
- if ((matchedMask = _screen->isFillMatch(p.x, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA)) == 0) // already filled
+ if ((matchedMask = _screen->vectorIsFillMatch(p.x, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA)) == 0) // already filled
continue;
- _screen->putPixel(p.x, p.y, screenMask, color, priority, control);
- w = p.x;
- e = p.x;
+ _screen->vectorPutPixel(p.x, p.y, screenMask, color, priority, control);
+ curToLeft = p.x;
+ curToRight = p.x;
// moving west and east pointers as long as there is a matching color to fill
- while (w > l && (matchedMask = _screen->isFillMatch(w - 1, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA)))
- _screen->putPixel(--w, p.y, screenMask, color, priority, control);
- while (e < r && (matchedMask = _screen->isFillMatch(e + 1, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA)))
- _screen->putPixel(++e, p.y, screenMask, color, priority, control);
+ while (curToLeft > borderLeft && (matchedMask = _screen->vectorIsFillMatch(curToLeft - 1, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA)))
+ _screen->vectorPutPixel(--curToLeft, p.y, screenMask, color, priority, control);
+ while (curToRight < borderRight && (matchedMask = _screen->vectorIsFillMatch(curToRight + 1, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA)))
+ _screen->vectorPutPixel(++curToRight, p.y, screenMask, color, priority, control);
#if 0
// debug code for floodfill
_screen->copyToScreen();
@@ -1015,10 +1032,10 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by
#endif
// checking lines above and below for possible flood targets
a_set = b_set = 0;
- while (w <= e) {
- if (p.y > t && (matchedMask = _screen->isFillMatch(w, p.y - 1, matchMask, searchColor, searchPriority, searchControl, isEGA))) { // one line above
+ while (curToLeft <= curToRight) {
+ if (p.y > borderTop && (matchedMask = _screen->vectorIsFillMatch(curToLeft, p.y - 1, matchMask, searchColor, searchPriority, searchControl, isEGA))) { // one line above
if (a_set == 0) {
- p1.x = w;
+ p1.x = curToLeft;
p1.y = p.y - 1;
stack.push(p1);
a_set = 1;
@@ -1026,16 +1043,16 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by
} else
a_set = 0;
- if (p.y < b && (matchedMask = _screen->isFillMatch(w, p.y + 1, matchMask, searchColor, searchPriority, searchControl, isEGA))) { // one line below
+ if (p.y < borderBottom && (matchedMask = _screen->vectorIsFillMatch(curToLeft, p.y + 1, matchMask, searchColor, searchPriority, searchControl, isEGA))) { // one line below
if (b_set == 0) {
- p1.x = w;
+ p1.x = curToLeft;
p1.y = p.y + 1;
stack.push(p1);
b_set = 1;
}
} else
b_set = 0;
- w++;
+ curToLeft++;
}
}
}
@@ -1173,7 +1190,7 @@ void GfxPicture::vectorPatternBox(Common::Rect box, byte color, byte prio, byte
for (y = box.top; y < box.bottom; y++) {
for (x = box.left; x < box.right; x++) {
- _screen->putPixel(x, y, flag, color, prio, control);
+ _screen->vectorPutPixel(x, y, flag, color, prio, control);
}
}
}
@@ -1186,7 +1203,7 @@ void GfxPicture::vectorPatternTexturedBox(Common::Rect box, byte color, byte pri
for (y = box.top; y < box.bottom; y++) {
for (x = box.left; x < box.right; x++) {
if (*textureData) {
- _screen->putPixel(x, y, flag, color, prio, control);
+ _screen->vectorPutPixel(x, y, flag, color, prio, control);
}
textureData++;
}
@@ -1203,7 +1220,7 @@ void GfxPicture::vectorPatternCircle(Common::Rect box, byte size, byte color, by
for (y = box.top; y < box.bottom; y++) {
for (x = box.left; x < box.right; x++) {
if (bitmap & 1) {
- _screen->putPixel(x, y, flag, color, prio, control);
+ _screen->vectorPutPixel(x, y, flag, color, prio, control);
}
bitNo++;
if (bitNo == 8) {
@@ -1222,12 +1239,12 @@ void GfxPicture::vectorPatternTexturedCircle(Common::Rect box, byte size, byte c
byte bitNo = 0;
const bool *textureData = &vectorPatternTextures[vectorPatternTextureOffset[texture]];
int y, x;
-
+
for (y = box.top; y < box.bottom; y++) {
for (x = box.left; x < box.right; x++) {
if (bitmap & 1) {
if (*textureData) {
- _screen->putPixel(x, y, flag, color, prio, control);
+ _screen->vectorPutPixel(x, y, flag, color, prio, control);
}
textureData++;
}
@@ -1252,7 +1269,10 @@ void GfxPicture::vectorPattern(int16 x, int16 y, byte color, byte priority, byte
rect.top = y; rect.left = x;
rect.setHeight((size*2)+1); rect.setWidth((size*2)+2);
_ports->offsetRect(rect);
- rect.clip(_screen->getWidth(), _screen->getHeight());
+ rect.clip(_screen->getScriptWidth(), _screen->getScriptHeight());
+
+ _screen->vectorAdjustCoordinate(&rect.left, &rect.top);
+ _screen->vectorAdjustCoordinate(&rect.right, &rect.bottom);
if (code & SCI_PATTERN_CODE_RECTANGLE) {
// Rectangle
diff --git a/engines/sci/graphics/portrait.cpp b/engines/sci/graphics/portrait.cpp
index 488450485d..cb425f3be9 100644
--- a/engines/sci/graphics/portrait.cpp
+++ b/engines/sci/graphics/portrait.cpp
@@ -57,13 +57,39 @@ void Portrait::init() {
// 4 bytes paletteSize (base 1)
// -> 17 bytes
// paletteSize bytes paletteData
- // 14 bytes bitmap header
- // -> 4 bytes unknown
- // -> 2 bytes height
- // -> 2 bytes width
- // -> 6 bytes unknown
- // height * width bitmap data
- // another animation count times bitmap header and data
+ //
+ // bitmap-data follows, total of [animation count]
+ // 14 bytes bitmap header
+ // -> 4 bytes unknown
+ // -> 2 bytes height
+ // -> 2 bytes width
+ // -> 6 bytes unknown
+ // height * width bitmap data
+ //
+ // 4 bytes offset table size (may be larger than the actual known entries?!)
+ // 14 bytes all zeroes (dummy entry?!)
+ //
+ // 14 bytes for each entry
+ // -> 2 bytes displace X
+ // -> 2 bytes displace Y
+ // -> 2 bytes height (again)
+ // -> 2 bytes width (again)
+ // -> 6 bytes unknown (normally 01 00 00 00 00 00 for delta bitmaps, 00 00 00 00 00 00 for first bitmap)
+ // random data may be used as filler
+ //
+ // 4 bytes lip sync id table size (is [lip sync id count] * 4, should be 0x2E0 for all actors)
+ // 4 bytes per lip sync id
+ // -> 1 byte length of ID
+ // -> 3 bytes actual ID
+ //
+ // 4 bytes lip sync id data table size (seems to be the same for all actors, always 0x220 in size)
+ // 1 byte animation number or 0xFF as terminator
+ // 1 byte delay, if last byte was not terminator
+ // one array for every lip sync id
+ //
+ // 4 bytes appended, seem to be random
+ // 9E11120E for alex
+ // 9E9E9E9E for vizier
int32 fileSize = 0;
Common::SeekableReadStream *file =
SearchMan.createReadStreamForMember("actors/" + _resourceName + ".bin");
@@ -134,34 +160,34 @@ void Portrait::init() {
// raw lip-sync ID table follows
uint32 lipSyncIDTableSize;
-
+
lipSyncIDTableSize = READ_LE_UINT32(data);
data += 4;
assert( lipSyncIDTableSize == (_lipSyncIDCount * 4) );
_lipSyncIDTable = data;
data += lipSyncIDTableSize;
-
+
// raw lip-sync frame table follows
uint32 lipSyncDataTableSize;
uint32 lipSyncDataTableLastOffset;
byte lipSyncData;
uint16 lipSyncDataNr;
uint16 lipSyncCurOffset;
-
+
lipSyncDataTableSize = READ_LE_UINT32(data);
data += 4;
assert( lipSyncDataTableSize == 0x220 ); // always this size, just a safety-check
-
+
_lipSyncData = data;
lipSyncDataTableLastOffset = lipSyncDataTableSize - 1;
_lipSyncDataOffsetTable = new uint16[ _lipSyncIDCount ];
-
+
lipSyncDataNr = 0;
lipSyncCurOffset = 0;
while ( (lipSyncCurOffset < lipSyncDataTableSize) && (lipSyncDataNr < _lipSyncIDCount) ) {
// We are currently at the start of ID-frame data
_lipSyncDataOffsetTable[lipSyncDataNr] = lipSyncCurOffset;
-
+
// Look for end of ID-frame data
lipSyncData = *data++; lipSyncCurOffset++;
while ( (lipSyncData != 0xFF) && (lipSyncCurOffset < lipSyncDataTableLastOffset) ) {
@@ -195,15 +221,16 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
Resource *syncResource = _resMan->findResource(syncResourceId, true);
uint syncOffset = 0;
#endif
-
+
#ifdef DEBUG_PORTRAIT
// prints out the current lip sync ASCII data
char debugPrint[4000];
if (raveResource->size < 4000) {
memcpy(debugPrint, raveResource->data, raveResource->size);
debugPrint[raveResource->size] = 0; // set terminating NUL
+ debug("kPortrait: using actor %s", _resourceName.c_str());
debug("kPortrait (noun %d, verb %d, cond %d, seq %d)", noun, verb, cond, seq);
- debug("kPortrait: %s", debugPrint);
+ debug("kPortrait: rave data is '%s'", debugPrint);
}
#endif
@@ -246,7 +273,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
warning("kPortrait: no rave resource %d %X", resourceId, audioNumber);
return;
}
-
+
// Do animation depending on rave resource till audio is done playing
int16 raveTicks;
uint16 raveID;
@@ -264,7 +291,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
raveTicks = raveGetTicks(raveResource, &raveOffset);
if (raveTicks < 0)
break;
-
+
// get lipSyncID
raveID = raveGetID(raveResource, &raveOffset);
if (raveID) {
@@ -272,7 +299,15 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
} else {
raveLipSyncData = NULL;
}
-
+
+#ifdef DEBUG_PORTRAIT
+ if (raveID & 0x0ff) {
+ debug("kPortrait: rave '%c%c' after %d ticks", raveID >> 8, raveID & 0x0ff, raveTicks);
+ } else if (raveID) {
+ debug("kPortrait: rave '%c' after %d ticks", raveID >> 8, raveTicks);
+ }
+#endif
+
timerPosition += raveTicks;
// Wait till syncTime passed, then show specific animation bitmap
@@ -282,12 +317,13 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
curEvent = _event->getSciEvent(SCI_EVENT_ANY);
if (curEvent.type == SCI_EVENT_MOUSE_PRESS ||
(curEvent.type == SCI_EVENT_KEYBOARD && curEvent.data == SCI_KEY_ESC) ||
- g_sci->getEngineState()->abortScriptProcessing == kAbortQuitGame)
+ g_sci->getEngineState()->abortScriptProcessing == kAbortQuitGame ||
+ g_sci->getEngineState()->_delayedRestoreGame)
userAbort = true;
curPosition = _audio->getAudioPosition();
} while ((curPosition != -1) && (curPosition < timerPosition) && (!userAbort));
}
-
+
if (raveLipSyncData) {
// lip sync data is
// Tick:Byte, Bitmap-Nr:BYTE
@@ -295,6 +331,8 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
timerPositionWithin = timerPosition;
raveLipSyncTicks = *raveLipSyncData++;
while ( (raveLipSyncData < _lipSyncDataOffsetTableEnd) && (raveLipSyncTicks != 0xFF) ) {
+ if (raveLipSyncTicks)
+ raveLipSyncTicks--; // 1 -> wait 0 ticks, 2 -> wait 1 tick, etc.
timerPositionWithin += raveLipSyncTicks;
do {
@@ -308,7 +346,14 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
} while ((curPosition != -1) && (curPosition < timerPositionWithin) && (!userAbort));
raveLipSyncBitmapNr = *raveLipSyncData++;
-
+#ifdef DEBUG_PORTRAIT
+ if (!raveLipSyncTicks) {
+ debug("kPortrait: showing frame %d", raveLipSyncBitmapNr);
+ } else {
+ debug("kPortrait: showing frame %d after %d ticks", raveLipSyncBitmapNr, raveLipSyncTicks);
+ }
+#endif
+
// bitmap nr within sync data is base 1, we need base 0
raveLipSyncBitmapNr--;
@@ -319,7 +364,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
} else {
warning("kPortrait: rave lip sync data tried to draw non-existent bitmap %d", raveLipSyncBitmapNr);
}
-
+
raveLipSyncTicks = *raveLipSyncData++;
}
}
@@ -372,7 +417,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
}
}
#endif
-
+
// Reset the portrait bitmap to "closed mouth" state (rave.dll seems to do the same)
drawBitmap(0);
bitsShow();
@@ -393,10 +438,10 @@ int16 Portrait::raveGetTicks(Resource *resource, uint *offset) {
byte *curData = resource->data + curOffset;
byte curByte;
uint16 curValue = 0;
-
+
if (curOffset >= resource->size)
return -1;
-
+
while (curOffset < resource->size) {
curByte = *curData++; curOffset++;
if ( curByte == ' ' )
@@ -418,7 +463,7 @@ uint16 Portrait::raveGetID(Resource *resource, uint *offset) {
byte *curData = resource->data + curOffset;
byte curByte = 0;
uint16 curValue = 0;
-
+
while (curOffset < resource->size) {
curByte = *curData++; curOffset++;
if ( curByte == ' ' )
@@ -429,7 +474,7 @@ uint16 Portrait::raveGetID(Resource *resource, uint *offset) {
curValue |= curByte;
}
}
-
+
*offset = curOffset;
return curValue;
}
@@ -440,17 +485,17 @@ byte *Portrait::raveGetLipSyncData(uint16 raveID) {
byte *lipSyncIDPtr = _lipSyncIDTable;
byte lipSyncIDByte1, lipSyncIDByte2;
uint16 lipSyncID;
-
+
lipSyncIDPtr++; // skip over first byte
while (lipSyncIDNr < _lipSyncIDCount) {
lipSyncIDByte1 = *lipSyncIDPtr++;
lipSyncIDByte2 = *lipSyncIDPtr++;
lipSyncID = ( lipSyncIDByte1 << 8 ) | lipSyncIDByte2;
-
+
if ( lipSyncID == raveID ) {
return _lipSyncData + _lipSyncDataOffsetTable[lipSyncIDNr];
}
-
+
lipSyncIDNr++;
lipSyncIDPtr += 2; // ID is every 4 bytes
}
diff --git a/engines/sci/graphics/portrait.h b/engines/sci/graphics/portrait.h
index 877b253bcf..e0888daa86 100644
--- a/engines/sci/graphics/portrait.h
+++ b/engines/sci/graphics/portrait.h
@@ -72,7 +72,7 @@ private:
Common::String _resourceName;
byte *_fileData;
-
+
uint32 _lipSyncIDCount;
byte *_lipSyncIDTable;
diff --git a/engines/sci/graphics/ports.cpp b/engines/sci/graphics/ports.cpp
index 56c63a7b12..bcc991081e 100644
--- a/engines/sci/graphics/ports.cpp
+++ b/engines/sci/graphics/ports.cpp
@@ -63,10 +63,10 @@ void GfxPorts::init(bool usesOldGfxFunctions, GfxPaint16 *paint16, GfxText16 *te
openPort(_menuPort);
setPort(_menuPort);
_text16->SetFont(0);
- _menuPort->rect = Common::Rect(0, 0, _screen->getWidth(), _screen->getHeight());
- _menuBarRect = Common::Rect(0, 0, _screen->getWidth(), 9);
- _menuRect = Common::Rect(0, 0, _screen->getWidth(), 10);
- _menuLine = Common::Rect(0, 9, _screen->getWidth(), 10);
+ _menuPort->rect = Common::Rect(0, 0, _screen->getScriptWidth(), _screen->getScriptHeight());
+ _menuBarRect = Common::Rect(0, 0, _screen->getScriptWidth(), 9);
+ _menuRect = Common::Rect(0, 0, _screen->getScriptWidth(), 10);
+ _menuLine = Common::Rect(0, 9, _screen->getScriptWidth(), 10);
_wmgrPort = new Port(1);
_windowsById.resize(2);
@@ -122,13 +122,13 @@ void GfxPorts::init(bool usesOldGfxFunctions, GfxPaint16 *paint16, GfxText16 *te
} else {
_wmgrPort->rect.bottom = _screen->getHeight();
}
- _wmgrPort->rect.right = _screen->getWidth();
+ _wmgrPort->rect.right = _screen->getScriptWidth();
_wmgrPort->rect.moveTo(0, 0);
_wmgrPort->curTop = 0;
_wmgrPort->curLeft = 0;
_windowList.push_front(_wmgrPort);
- _picWind = addWindow(Common::Rect(0, offTop, _screen->getWidth(), _screen->getHeight()), 0, 0, SCI_WINDOWMGR_STYLE_TRANSPARENT | SCI_WINDOWMGR_STYLE_NOFRAME, 0, true);
+ _picWind = addWindow(Common::Rect(0, offTop, _screen->getScriptWidth(), _screen->getScriptHeight()), 0, 0, SCI_WINDOWMGR_STYLE_TRANSPARENT | SCI_WINDOWMGR_STYLE_NOFRAME, 0, true);
// For SCI0 games till kq4 (.502 - not including) we set _picWind top to offTop instead
// Because of the menu/status bar
if (_usesOldGfxFunctions)
@@ -321,13 +321,13 @@ Window *GfxPorts::addWindow(const Common::Rect &dims, const Common::Rect *restor
// their interpreter even in the newer VGA games.
r.left = r.left & 0xFFFE;
- if (r.width() > _screen->getWidth()) {
+ if (r.width() > _screen->getScriptWidth()) {
// We get invalid dimensions at least at the end of sq3 (script bug!).
// Same happens very often in lsl5, sierra sci didnt fix it but it looked awful.
// Also happens frequently in the demo of GK1.
warning("Fixing too large window, left: %d, right: %d", dims.left, dims.right);
r.left = 0;
- r.right = _screen->getWidth() - 1;
+ r.right = _screen->getScriptWidth() - 1;
if ((style != _styleUser) && !(style & SCI_WINDOWMGR_STYLE_NOFRAME))
r.right--;
}
diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp
index c5c94d7991..ca5b5b3b8c 100644
--- a/engines/sci/graphics/screen.cpp
+++ b/engines/sci/graphics/screen.cpp
@@ -37,7 +37,15 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
// Scale the screen, if needed
_upscaledHires = GFX_SCREEN_UPSCALED_DISABLED;
-
+
+ // we default to scripts running at 320x200
+ _scriptWidth = 320;
+ _scriptHeight = 200;
+ _width = 0;
+ _height = 0;
+ _displayWidth = 0;
+ _displayHeight = 0;
+
// King's Quest 6 and Gabriel Knight 1 have hires content, gk1/cd was able
// to provide that under DOS as well, but as gk1/floppy does support
// upscaled hires scriptswise, but doesn't actually have the hires content
@@ -50,10 +58,34 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
_upscaledHires = GFX_SCREEN_UPSCALED_640x480;
#endif
}
+
+ // Japanese versions of games use hi-res font on upscaled version of the game.
+ if ((g_sci->getLanguage() == Common::JA_JPN) && (getSciVersion() <= SCI_VERSION_1_1))
+ _upscaledHires = GFX_SCREEN_UPSCALED_640x400;
+ // Macintosh SCI0 games used 480x300, while the scripts were running at 320x200
if (g_sci->getPlatform() == Common::kPlatformMacintosh) {
- if (getSciVersion() <= SCI_VERSION_01)
+ if (getSciVersion() <= SCI_VERSION_01) {
_upscaledHires = GFX_SCREEN_UPSCALED_480x300;
+ _width = 480;
+ _height = 300; // regular visual, priority and control map are 480x300 (this is different than other upscaled SCI games)
+ }
+
+ // Some Mac SCI1/1.1 games only take up 190 rows and do not
+ // have the menu bar.
+ // TODO: Verify that LSL1 and LSL5 use height 190
+ switch (g_sci->getGameId()) {
+ case GID_FREDDYPHARKAS:
+ case GID_KQ5:
+ case GID_KQ6:
+ case GID_LSL1:
+ case GID_LSL5:
+ case GID_SQ1:
+ _scriptHeight = 190;
+ break;
+ default:
+ break;
+ }
}
#ifdef ENABLE_SCI32
@@ -65,76 +97,77 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
#endif
if (_resMan->detectHires()) {
- _width = 640;
- _pitch = 640;
- _height = 480;
- } else {
- _width = 320;
- _pitch = 320;
- _height = getLowResScreenHeight();
+ _scriptWidth = 640;
+ _scriptHeight = 480;
}
#ifdef ENABLE_SCI32
- // Phantasmagoria 1 sets a window area of 630x450
+ // Phantasmagoria 1 effectively outputs 630x450
+ // Coordinate translation has to use this resolution as well
if (g_sci->getGameId() == GID_PHANTASMAGORIA) {
_width = 630;
_height = 450;
}
#endif
- // Japanese versions of games use hi-res font on upscaled version of the game.
- if ((g_sci->getLanguage() == Common::JA_JPN) && (getSciVersion() <= SCI_VERSION_1_1))
- _upscaledHires = GFX_SCREEN_UPSCALED_640x400;
+ // if not yet set, set those to script-width/height
+ if (!_width)
+ _width = _scriptWidth;
+ if (!_height)
+ _height = _scriptHeight;
- _pixels = _pitch * _height;
+ _pixels = _width * _height;
switch (_upscaledHires) {
case GFX_SCREEN_UPSCALED_480x300:
// Space Quest 3, Hoyle 1+2 on MAC use this one
- // TODO: Sierra's upscaling worked differently. We need to figure out the exact algo
_displayWidth = 480;
_displayHeight = 300;
- for (int i = 0; i <= _height; i++)
+ for (int i = 0; i <= _scriptHeight; i++)
_upscaledHeightMapping[i] = (i * 3) >> 1;
- for (int i = 0; i <= _width; i++)
+ for (int i = 0; i <= _scriptWidth; i++)
_upscaledWidthMapping[i] = (i * 3) >> 1;
break;
case GFX_SCREEN_UPSCALED_640x400:
// Police Quest 2 and Quest For Glory on PC9801 (Japanese)
_displayWidth = 640;
_displayHeight = 400;
- for (int i = 0; i <= _height; i++)
+ for (int i = 0; i <= _scriptHeight; i++)
_upscaledHeightMapping[i] = i * 2;
- for (int i = 0; i <= _width; i++)
+ for (int i = 0; i <= _scriptWidth; i++)
_upscaledWidthMapping[i] = i * 2;
break;
case GFX_SCREEN_UPSCALED_640x440:
// used by King's Quest 6 on Windows
_displayWidth = 640;
_displayHeight = 440;
- for (int i = 0; i <= _height; i++)
+ for (int i = 0; i <= _scriptHeight; i++)
_upscaledHeightMapping[i] = (i * 11) / 5;
- for (int i = 0; i <= _width; i++)
+ for (int i = 0; i <= _scriptWidth; i++)
_upscaledWidthMapping[i] = i * 2;
break;
case GFX_SCREEN_UPSCALED_640x480:
// Gabriel Knight 1 (VESA, Mac)
_displayWidth = 640;
_displayHeight = 480;
- for (int i = 0; i <= _height; i++)
+ for (int i = 0; i <= _scriptHeight; i++)
_upscaledHeightMapping[i] = (i * 12) / 5;
- for (int i = 0; i <= _width; i++)
+ for (int i = 0; i <= _scriptWidth; i++)
_upscaledWidthMapping[i] = i * 2;
break;
default:
- _displayWidth = _pitch;
- _displayHeight = _height;
+ if (!_displayWidth)
+ _displayWidth = _width;
+ if (!_displayHeight)
+ _displayHeight = _height;
memset(&_upscaledHeightMapping, 0, sizeof(_upscaledHeightMapping) );
memset(&_upscaledWidthMapping, 0, sizeof(_upscaledWidthMapping) );
break;
}
_displayPixels = _displayWidth * _displayHeight;
+
+ // Allocate visual, priority, control and display screen
_visualScreen = (byte *)calloc(_pixels, 1);
_priorityScreen = (byte *)calloc(_pixels, 1);
_controlScreen = (byte *)calloc(_pixels, 1);
@@ -179,6 +212,36 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
error("Unknown SCI1.1 Mac game");
} else
initGraphics(_displayWidth, _displayHeight, _displayWidth > 320);
+
+ // Initialize code pointers
+ _vectorAdjustCoordinatePtr = &GfxScreen::vectorAdjustCoordinateNOP;
+ _vectorAdjustLineCoordinatesPtr = &GfxScreen::vectorAdjustLineCoordinatesNOP;
+ _vectorIsFillMatchPtr = &GfxScreen::vectorIsFillMatchNormal;
+ _vectorPutPixelPtr = &GfxScreen::putPixelNormal;
+ _vectorPutLinePixelPtr = &GfxScreen::putPixel;
+ _vectorGetPixelPtr = &GfxScreen::getPixelNormal;
+ _putPixelPtr = &GfxScreen::putPixelNormal;
+ _getPixelPtr = &GfxScreen::getPixelNormal;
+
+ switch (_upscaledHires) {
+ case GFX_SCREEN_UPSCALED_480x300:
+ _vectorAdjustCoordinatePtr = &GfxScreen::vectorAdjustCoordinate480x300Mac;
+ _vectorAdjustLineCoordinatesPtr = &GfxScreen::vectorAdjustLineCoordinates480x300Mac;
+ // vectorPutPixel -> we already adjust coordinates for vector code, that's why we can set pixels directly
+ // vectorGetPixel -> see vectorPutPixel
+ _vectorPutLinePixelPtr = &GfxScreen::vectorPutLinePixel480x300Mac;
+ _putPixelPtr = &GfxScreen::putPixelAllUpscaled;
+ _getPixelPtr = &GfxScreen::getPixelUpscaled;
+ break;
+ case GFX_SCREEN_UPSCALED_640x400:
+ case GFX_SCREEN_UPSCALED_640x440:
+ case GFX_SCREEN_UPSCALED_640x480:
+ _vectorPutPixelPtr = &GfxScreen::putPixelDisplayUpscaled;
+ _putPixelPtr = &GfxScreen::putPixelDisplayUpscaled;
+ break;
+ case GFX_SCREEN_UPSCALED_DISABLED:
+ break;
+ }
}
GfxScreen::~GfxScreen() {
@@ -188,6 +251,18 @@ GfxScreen::~GfxScreen() {
free(_displayScreen);
}
+// should not be used regularly; only meant for restore game
+void GfxScreen::clearForRestoreGame() {
+ // reset all screen data
+ memset(_visualScreen, 0, _pixels);
+ memset(_priorityScreen, 0, _pixels);
+ memset(_controlScreen, 0, _pixels);
+ memset(_displayScreen, 0, _displayPixels);
+ memset(&_ditheredPicColors, 0, sizeof(_ditheredPicColors));
+ _fontIsUpscaled = false;
+ copyToScreen();
+}
+
void GfxScreen::copyToScreen() {
g_system->copyRectToScreen(_activeScreen, _displayWidth, 0, 0, _displayWidth, _displayHeight);
}
@@ -232,7 +307,7 @@ void GfxScreen::copyRectToScreen(const Common::Rect &rect, int16 x, int16 y) {
} else {
int rectHeight = _upscaledHeightMapping[rect.bottom] - _upscaledHeightMapping[rect.top];
int rectWidth = _upscaledWidthMapping[rect.right] - _upscaledWidthMapping[rect.left];
-
+
g_system->copyRectToScreen(_activeScreen + _upscaledHeightMapping[rect.top] * _displayWidth + _upscaledWidthMapping[rect.left], _displayWidth, _upscaledWidthMapping[x], _upscaledHeightMapping[y], rectWidth, rectHeight);
}
}
@@ -248,43 +323,162 @@ byte GfxScreen::getDrawingMask(byte color, byte prio, byte control) {
return flag;
}
-void GfxScreen::putPixel(int x, int y, byte drawMask, byte color, byte priority, byte control) {
- int offset = y * _pitch + x;
+void GfxScreen::vectorAdjustCoordinateNOP(int16 *x, int16 *y) {
+}
- if (drawMask & GFX_SCREEN_MASK_VISUAL) {
- _visualScreen[offset] = color;
- if (!_upscaledHires) {
- _displayScreen[offset] = color;
+void GfxScreen::vectorAdjustCoordinate480x300Mac(int16 *x, int16 *y) {
+ *x = _upscaledWidthMapping[*x];
+ *y = _upscaledHeightMapping[*y];
+}
+
+void GfxScreen::vectorAdjustLineCoordinatesNOP(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) {
+}
+
+void GfxScreen::vectorAdjustLineCoordinates480x300Mac(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) {
+ int16 displayLeft = _upscaledWidthMapping[*left];
+ int16 displayRight = _upscaledWidthMapping[*right];
+ int16 displayTop = _upscaledHeightMapping[*top];
+ int16 displayBottom = _upscaledHeightMapping[*bottom];
+
+ if (displayLeft < displayRight) {
+ // one more pixel to the left, one more pixel to the right
+ if (displayLeft > 0)
+ vectorPutLinePixel(displayLeft - 1, displayTop, drawMask, color, priority, control);
+ vectorPutLinePixel(displayRight + 1, displayBottom, drawMask, color, priority, control);
+ } else if (displayLeft > displayRight) {
+ if (displayRight > 0)
+ vectorPutLinePixel(displayRight - 1, displayBottom, drawMask, color, priority, control);
+ vectorPutLinePixel(displayLeft + 1, displayTop, drawMask, color, priority, control);
+ }
+ *left = displayLeft;
+ *top = displayTop;
+ *right = displayRight;
+ *bottom = displayBottom;
+}
+
+byte GfxScreen::vectorIsFillMatchNormal(int16 x, int16 y, byte screenMask, byte checkForColor, byte checkForPriority, byte checkForControl, bool isEGA) {
+ int offset = y * _width + x;
+ byte match = 0;
+
+ if (screenMask & GFX_SCREEN_MASK_VISUAL) {
+ if (!isEGA) {
+ if (*(_visualScreen + offset) == checkForColor)
+ match |= GFX_SCREEN_MASK_VISUAL;
} else {
- putScaledPixelOnDisplay(x, y, color);
+ // In EGA games a pixel in the framebuffer is only 4 bits. We store
+ // a full byte per pixel to allow undithering, but when comparing
+ // pixels for flood-fill purposes, we should only compare the
+ // visible color of a pixel.
+
+ byte EGAcolor = *(_visualScreen + offset);
+ if ((x ^ y) & 1)
+ EGAcolor = (EGAcolor ^ (EGAcolor >> 4)) & 0x0F;
+ else
+ EGAcolor = EGAcolor & 0x0F;
+ if (EGAcolor == checkForColor)
+ match |= GFX_SCREEN_MASK_VISUAL;
}
}
+ if ((screenMask & GFX_SCREEN_MASK_PRIORITY) && *(_priorityScreen + offset) == checkForPriority)
+ match |= GFX_SCREEN_MASK_PRIORITY;
+ if ((screenMask & GFX_SCREEN_MASK_CONTROL) && *(_controlScreen + offset) == checkForControl)
+ match |= GFX_SCREEN_MASK_CONTROL;
+ return match;
+}
+
+// Special 480x300 Mac putPixel for vector line drawing, also draws an additional pixel below the actual one
+void GfxScreen::vectorPutLinePixel480x300Mac(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ int offset = y * _width + x;
+
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ _visualScreen[offset] = color;
+ _visualScreen[offset + _width] = color;
+ _displayScreen[offset] = color;
+ // also set pixel below actual pixel
+ _displayScreen[offset + _displayWidth] = color;
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
+ _priorityScreen[offset] = priority;
+ _priorityScreen[offset + _width] = priority;
+ }
+ if (drawMask & GFX_SCREEN_MASK_CONTROL) {
+ _controlScreen[offset] = control;
+ _controlScreen[offset + _width] = control;
+ }
+}
+
+// Directly sets a pixel on various screens, display is not upscaled
+void GfxScreen::putPixelNormal(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ int offset = y * _width + x;
+
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ _visualScreen[offset] = color;
+ _displayScreen[offset] = color;
+ }
if (drawMask & GFX_SCREEN_MASK_PRIORITY)
_priorityScreen[offset] = priority;
if (drawMask & GFX_SCREEN_MASK_CONTROL)
_controlScreen[offset] = control;
}
+// Directly sets a pixel on various screens, display IS upscaled
+void GfxScreen::putPixelDisplayUpscaled(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ int offset = y * _width + x;
+
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ _visualScreen[offset] = color;
+ putScaledPixelOnScreen(_displayScreen, x, y, color);
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY)
+ _priorityScreen[offset] = priority;
+ if (drawMask & GFX_SCREEN_MASK_CONTROL)
+ _controlScreen[offset] = control;
+}
+
+// Directly sets a pixel on various screens, ALL screens ARE upscaled
+void GfxScreen::putPixelAllUpscaled(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ if (drawMask & GFX_SCREEN_MASK_VISUAL) {
+ putScaledPixelOnScreen(_visualScreen, x, y, color);
+ putScaledPixelOnScreen(_displayScreen, x, y, color);
+ }
+ if (drawMask & GFX_SCREEN_MASK_PRIORITY)
+ putScaledPixelOnScreen(_priorityScreen, x, y, priority);
+ if (drawMask & GFX_SCREEN_MASK_CONTROL)
+ putScaledPixelOnScreen(_controlScreen, x, y, control);
+}
+
/**
* This is used to put font pixels onto the screen - we adjust differently, so that we won't
* do triple pixel lines in any case on upscaled hires. That way the font will not get distorted
* Sierra SCI didn't do this
*/
-void GfxScreen::putFontPixel(int startingY, int x, int y, byte color) {
- int actualY = startingY + y;
+void GfxScreen::putFontPixel(int16 startingY, int16 x, int16 y, byte color) {
+ int16 actualY = startingY + y;
if (_fontIsUpscaled) {
// Do not scale ourselves, but put it on the display directly
putPixelOnDisplay(x, actualY, color);
} else {
- int offset = actualY * _pitch + x;
+ int offset = actualY * _width + x;
_visualScreen[offset] = color;
switch (_upscaledHires) {
case GFX_SCREEN_UPSCALED_DISABLED:
_displayScreen[offset] = color;
break;
+ case GFX_SCREEN_UPSCALED_640x400:
+ case GFX_SCREEN_UPSCALED_640x440:
+ case GFX_SCREEN_UPSCALED_640x480: {
+ // to 1-> 4 pixels upscaling for all of those, so that fonts won't look weird
+ int displayOffset = (_upscaledHeightMapping[startingY] + y * 2) * _displayWidth + x * 2;
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ displayOffset += _displayWidth;
+ _displayScreen[displayOffset] = color;
+ _displayScreen[displayOffset + 1] = color;
+ break;
+ }
default:
- putScaledPixelOnDisplay(x, actualY, color);
+ putScaledPixelOnScreen(_displayScreen, x, actualY, color);
break;
}
}
@@ -295,12 +489,15 @@ void GfxScreen::putFontPixel(int startingY, int x, int y, byte color) {
* only used on upscaled-Hires games where hires content needs to get drawn ONTO
* the upscaled display screen (like japanese fonts, hires portraits, etc.).
*/
-void GfxScreen::putPixelOnDisplay(int x, int y, byte color) {
+void GfxScreen::putPixelOnDisplay(int16 x, int16 y, byte color) {
int offset = y * _displayWidth + x;
_displayScreen[offset] = color;
}
-void GfxScreen::putScaledPixelOnDisplay(int x, int y, byte color) {
+//void GfxScreen::putScaledPixelOnDisplay(int16 x, int16 y, byte color) {
+//}
+
+void GfxScreen::putScaledPixelOnScreen(byte *screen, int16 x, int16 y, byte data) {
int displayOffset = _upscaledHeightMapping[y] * _displayWidth + _upscaledWidthMapping[x];
int heightOffsetBreak = (_upscaledHeightMapping[y + 1] - _upscaledHeightMapping[y]) * _displayWidth;
int heightOffset = 0;
@@ -308,7 +505,7 @@ void GfxScreen::putScaledPixelOnDisplay(int x, int y, byte color) {
do {
int widthOffset = 0;
do {
- _displayScreen[displayOffset + heightOffset + widthOffset] = color;
+ screen[displayOffset + heightOffset + widthOffset] = data;
widthOffset++;
} while (widthOffset != widthOffsetBreak);
heightOffset += _displayWidth;
@@ -329,16 +526,18 @@ void GfxScreen::drawLine(Common::Point startPoint, Common::Point endPoint, byte
int16 top = CLIP<int16>(startPoint.y, 0, maxHeight);
int16 right = CLIP<int16>(endPoint.x, 0, maxWidth);
int16 bottom = CLIP<int16>(endPoint.y, 0, maxHeight);
-
+
//set_drawing_flag
byte drawMask = getDrawingMask(color, priority, control);
+ vectorAdjustLineCoordinates(&left, &top, &right, &bottom, drawMask, color, priority, control);
+
// horizontal line
if (top == bottom) {
if (right < left)
SWAP(right, left);
for (int i = left; i <= right; i++)
- putPixel(i, top, drawMask, color, priority, control);
+ vectorPutLinePixel(i, top, drawMask, color, priority, control);
return;
}
// vertical line
@@ -346,20 +545,20 @@ void GfxScreen::drawLine(Common::Point startPoint, Common::Point endPoint, byte
if (top > bottom)
SWAP(top, bottom);
for (int i = top; i <= bottom; i++)
- putPixel(left, i, drawMask, color, priority, control);
+ vectorPutLinePixel(left, i, drawMask, color, priority, control);
return;
}
// sloped line - draw with Bresenham algorithm
- int dy = bottom - top;
- int dx = right - left;
- int stepy = dy < 0 ? -1 : 1;
- int stepx = dx < 0 ? -1 : 1;
+ int16 dy = bottom - top;
+ int16 dx = right - left;
+ int16 stepy = dy < 0 ? -1 : 1;
+ int16 stepx = dx < 0 ? -1 : 1;
dy = ABS(dy) << 1;
dx = ABS(dx) << 1;
// setting the 1st and last pixel
- putPixel(left, top, drawMask, color, priority, control);
- putPixel(right, bottom, drawMask, color, priority, control);
+ vectorPutLinePixel(left, top, drawMask, color, priority, control);
+ vectorPutLinePixel(right, bottom, drawMask, color, priority, control);
// drawing the line
if (dx > dy) { // going horizontal
int fraction = dy - (dx >> 1);
@@ -370,7 +569,7 @@ void GfxScreen::drawLine(Common::Point startPoint, Common::Point endPoint, byte
}
left += stepx;
fraction += dy;
- putPixel(left, top, drawMask, color, priority, control);
+ vectorPutLinePixel(left, top, drawMask, color, priority, control);
}
} else { // going vertical
int fraction = dx - (dy >> 1);
@@ -381,7 +580,7 @@ void GfxScreen::drawLine(Common::Point startPoint, Common::Point endPoint, byte
}
top += stepy;
fraction += dx;
- putPixel(left, top, drawMask, color, priority, control);
+ vectorPutLinePixel(left, top, drawMask, color, priority, control);
}
}
}
@@ -394,46 +593,14 @@ void GfxScreen::putKanjiChar(Graphics::FontSJIS *commonFont, int16 x, int16 y, u
commonFont->drawChar(displayPtr, chr, _displayWidth, 1, color, 0, -1, -1);
}
-byte GfxScreen::getVisual(int x, int y) {
- return _visualScreen[y * _pitch + x];
+byte GfxScreen::getPixelNormal(byte *screen, int16 x, int16 y) {
+ return screen[y * _width + x];
}
-byte GfxScreen::getPriority(int x, int y) {
- return _priorityScreen[y * _pitch + x];
-}
-
-byte GfxScreen::getControl(int x, int y) {
- return _controlScreen[y * _pitch + x];
-}
-
-byte GfxScreen::isFillMatch(int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA) {
- int offset = y * _pitch + x;
- byte match = 0;
-
- if (screenMask & GFX_SCREEN_MASK_VISUAL) {
- if (!isEGA) {
- if (*(_visualScreen + offset) == t_color)
- match |= GFX_SCREEN_MASK_VISUAL;
- } else {
- // In EGA games a pixel in the framebuffer is only 4 bits. We store
- // a full byte per pixel to allow undithering, but when comparing
- // pixels for flood-fill purposes, we should only compare the
- // visible color of a pixel.
-
- byte c = *(_visualScreen + offset);
- if ((x ^ y) & 1)
- c = (c ^ (c >> 4)) & 0x0F;
- else
- c = c & 0x0F;
- if (c == t_color)
- match |= GFX_SCREEN_MASK_VISUAL;
- }
- }
- if ((screenMask & GFX_SCREEN_MASK_PRIORITY) && *(_priorityScreen + offset) == t_pri)
- match |= GFX_SCREEN_MASK_PRIORITY;
- if ((screenMask & GFX_SCREEN_MASK_CONTROL) && *(_controlScreen + offset) == t_con)
- match |= GFX_SCREEN_MASK_CONTROL;
- return match;
+byte GfxScreen::getPixelUpscaled(byte *screen, int16 x, int16 y) {
+ int16 mappedX = _upscaledWidthMapping[x];
+ int16 mappedY = _upscaledHeightMapping[y];
+ return screen[mappedY * _width + mappedX];
}
int GfxScreen::bitsGetDataSize(Common::Rect rect, byte mask) {
@@ -469,14 +636,14 @@ void GfxScreen::bitsSave(Common::Rect rect, byte mask, byte *memoryPtr) {
memcpy(memoryPtr, (void *)&mask, sizeof(mask)); memoryPtr += sizeof(mask);
if (mask & GFX_SCREEN_MASK_VISUAL) {
- bitsSaveScreen(rect, _visualScreen, _pitch, memoryPtr);
+ bitsSaveScreen(rect, _visualScreen, _width, memoryPtr);
bitsSaveDisplayScreen(rect, memoryPtr);
}
if (mask & GFX_SCREEN_MASK_PRIORITY) {
- bitsSaveScreen(rect, _priorityScreen, _pitch, memoryPtr);
+ bitsSaveScreen(rect, _priorityScreen, _width, memoryPtr);
}
if (mask & GFX_SCREEN_MASK_CONTROL) {
- bitsSaveScreen(rect, _controlScreen, _pitch, memoryPtr);
+ bitsSaveScreen(rect, _controlScreen, _width, memoryPtr);
}
if (mask & GFX_SCREEN_MASK_DISPLAY) {
if (!_upscaledHires)
@@ -530,14 +697,14 @@ void GfxScreen::bitsRestore(byte *memoryPtr) {
memcpy((void *)&mask, memoryPtr, sizeof(mask)); memoryPtr += sizeof(mask);
if (mask & GFX_SCREEN_MASK_VISUAL) {
- bitsRestoreScreen(rect, memoryPtr, _visualScreen, _pitch);
+ bitsRestoreScreen(rect, memoryPtr, _visualScreen, _width);
bitsRestoreDisplayScreen(rect, memoryPtr);
}
if (mask & GFX_SCREEN_MASK_PRIORITY) {
- bitsRestoreScreen(rect, memoryPtr, _priorityScreen, _pitch);
+ bitsRestoreScreen(rect, memoryPtr, _priorityScreen, _width);
}
if (mask & GFX_SCREEN_MASK_CONTROL) {
- bitsRestoreScreen(rect, memoryPtr, _controlScreen, _pitch);
+ bitsRestoreScreen(rect, memoryPtr, _controlScreen, _width);
}
if (mask & GFX_SCREEN_MASK_DISPLAY) {
if (!_upscaledHires)
@@ -612,21 +779,22 @@ void GfxScreen::dither(bool addToFlag) {
byte color, ditheredColor;
byte *visualPtr = _visualScreen;
byte *displayPtr = _displayScreen;
-
+
if (!_unditheringEnabled) {
// Do dithering on visual and display-screen
for (y = 0; y < _height; y++) {
- for (x = 0; x < _pitch; x++) {
+ for (x = 0; x < _width; x++) {
color = *visualPtr;
if (color & 0xF0) {
color ^= color << 4;
color = ((x^y) & 1) ? color >> 4 : color & 0x0F;
switch (_upscaledHires) {
case GFX_SCREEN_UPSCALED_DISABLED:
+ case GFX_SCREEN_UPSCALED_480x300:
*displayPtr = color;
break;
default:
- putScaledPixelOnDisplay(x, y, color);
+ putScaledPixelOnScreen(_displayScreen, x, y, color);
break;
}
*visualPtr = color;
@@ -639,7 +807,7 @@ void GfxScreen::dither(bool addToFlag) {
memset(&_ditheredPicColors, 0, sizeof(_ditheredPicColors));
// Do dithering on visual screen and put decoded but undithered byte onto display-screen
for (y = 0; y < _height; y++) {
- for (x = 0; x < _pitch; x++) {
+ for (x = 0; x < _width; x++) {
color = *visualPtr;
if (color & 0xF0) {
color ^= color << 4;
@@ -654,10 +822,11 @@ void GfxScreen::dither(bool addToFlag) {
}
switch (_upscaledHires) {
case GFX_SCREEN_UPSCALED_DISABLED:
+ case GFX_SCREEN_UPSCALED_480x300:
*displayPtr = ditheredColor;
break;
default:
- putScaledPixelOnDisplay(x, y, ditheredColor);
+ putScaledPixelOnScreen(_displayScreen, x, y, ditheredColor);
break;
}
color = ((x^y) & 1) ? color >> 4 : color & 0x0F;
@@ -685,8 +854,8 @@ int16 *GfxScreen::unditherGetDitheredBgColors() {
}
void GfxScreen::debugShowMap(int mapNo) {
- // We cannot really support changing maps when in upscaledHires mode
- if (_upscaledHires)
+ // We cannot really support changing maps when display screen has a different resolution than visual screen
+ if ((_width != _displayWidth) || (_height != _displayHeight))
return;
switch (mapNo) {
@@ -779,8 +948,8 @@ void GfxScreen::adjustBackUpscaledCoordinates(int16 &y, int16 &x, Sci32ViewNativ
switch (_upscaledHires) {
case GFX_SCREEN_UPSCALED_480x300:
- x = (x << 1) / 3;
- y = (y << 1) / 3;
+ x = (x * 4) / 6;
+ y = (y * 4) / 6;
break;
case GFX_SCREEN_UPSCALED_640x400:
x /= 2;
@@ -816,26 +985,4 @@ int16 GfxScreen::kernelPicNotValid(int16 newPicNotValid) {
return oldPicNotValid;
}
-uint16 GfxScreen::getLowResScreenHeight() {
- // Some Mac SCI1/1.1 games only take up 190 rows and do not
- // have the menu bar.
- // TODO: Verify that LSL1 and LSL5 use height 190
- if (g_sci->getPlatform() == Common::kPlatformMacintosh) {
- switch (g_sci->getGameId()) {
- case GID_FREDDYPHARKAS:
- case GID_KQ5:
- case GID_KQ6:
- case GID_LSL1:
- case GID_LSL5:
- case GID_SQ1:
- return 190;
- default:
- break;
- }
- }
-
- // Everything else is 200
- return 200;
-}
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h
index e266a4ed16..1c946ef02f 100644
--- a/engines/sci/graphics/screen.h
+++ b/engines/sci/graphics/screen.h
@@ -69,11 +69,14 @@ public:
uint16 getWidth() { return _width; }
uint16 getHeight() { return _height; }
+ uint16 getScriptWidth() { return _scriptWidth; }
+ uint16 getScriptHeight() { return _scriptHeight; }
uint16 getDisplayWidth() { return _displayWidth; }
uint16 getDisplayHeight() { return _displayHeight; }
byte getColorWhite() { return _colorWhite; }
byte getColorDefaultVectorData() { return _colorDefaultVectorData; }
+ void clearForRestoreGame();
void copyToScreen();
void copyFromScreen(byte *buffer);
void kernelSyncWithFramebuffer();
@@ -81,11 +84,51 @@ public:
void copyDisplayRectToScreen(const Common::Rect &rect);
void copyRectToScreen(const Common::Rect &rect, int16 x, int16 y);
+ // calls to code pointers
+ void inline vectorAdjustCoordinate (int16 *x, int16 *y) {
+ (this->*_vectorAdjustCoordinatePtr)(x, y);
+ }
+ void inline vectorAdjustLineCoordinates (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) {
+ (this->*_vectorAdjustLineCoordinatesPtr)(left, top, right, bottom, drawMask, color, priority, control);
+ }
+ byte inline vectorIsFillMatch (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA) {
+ return (this->*_vectorIsFillMatchPtr)(x, y, screenMask, t_color, t_pri, t_con, isEGA);
+ }
+ void inline vectorPutPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ (this->*_vectorPutPixelPtr)(x, y, drawMask, color, priority, control);
+ }
+ void inline vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ (this->*_vectorPutLinePixelPtr)(x, y, drawMask, color, priority, control);
+ }
+ byte inline vectorGetVisual(int16 x, int16 y) {
+ return (this->*_vectorGetPixelPtr)(_visualScreen, x, y);
+ }
+ byte inline vectorGetPriority(int16 x, int16 y) {
+ return (this->*_vectorGetPixelPtr)(_priorityScreen, x, y);
+ }
+ byte inline vectorGetControl(int16 x, int16 y) {
+ return (this->*_vectorGetPixelPtr)(_controlScreen, x, y);
+ }
+
+
+ void inline putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
+ (this->*_putPixelPtr)(x, y, drawMask, color, priority, control);
+ }
+
+ byte inline getVisual(int16 x, int16 y) {
+ return (this->*_getPixelPtr)(_visualScreen, x, y);
+ }
+ byte inline getPriority(int16 x, int16 y) {
+ return (this->*_getPixelPtr)(_priorityScreen, x, y);
+ }
+ byte inline getControl(int16 x, int16 y) {
+ return (this->*_getPixelPtr)(_controlScreen, x, y);
+ }
+
byte getDrawingMask(byte color, byte prio, byte control);
- void putPixel(int x, int y, byte drawMask, byte color, byte prio, byte control);
- void putFontPixel(int startingY, int x, int y, byte color);
- void putPixelOnDisplay(int x, int y, byte color);
- void putScaledPixelOnDisplay(int x, int y, byte color);
+ //void putPixel(int16 x, int16 y, byte drawMask, byte color, byte prio, byte control);
+ void putFontPixel(int16 startingY, int16 x, int16 y, byte color);
+ void putPixelOnDisplay(int16 x, int16 y, byte color);
void drawLine(Common::Point startPoint, Common::Point endPoint, byte color, byte prio, byte control);
void drawLine(int16 left, int16 top, int16 right, int16 bottom, byte color, byte prio, byte control) {
drawLine(Common::Point(left, top), Common::Point(right, bottom), color, prio, control);
@@ -101,10 +144,6 @@ public:
void enableUndithering(bool flag);
void putKanjiChar(Graphics::FontSJIS *commonFont, int16 x, int16 y, uint16 chr, byte color);
- byte getVisual(int x, int y);
- byte getPriority(int x, int y);
- byte getControl(int x, int y);
- byte isFillMatch(int16 x, int16 y, byte drawMask, byte t_color, byte t_pri, byte t_con, bool isEGA);
int bitsGetDataSize(Common::Rect rect, byte mask);
void bitsSave(Common::Rect rect, byte mask, byte *memoryPtr);
@@ -135,9 +174,10 @@ public:
private:
uint16 _width;
- uint16 _pitch;
uint16 _height;
uint _pixels;
+ uint16 _scriptWidth;
+ uint16 _scriptHeight;
uint16 _displayWidth;
uint16 _displayHeight;
uint _displayPixels;
@@ -190,8 +230,8 @@ private:
* This here holds a translation for vertical+horizontal coordinates between native
* (visual) and actual (display) screen.
*/
- int _upscaledHeightMapping[SCI_SCREEN_UPSCALEDMAXHEIGHT + 1];
- int _upscaledWidthMapping[SCI_SCREEN_UPSCALEDMAXWIDTH + 1];
+ int16 _upscaledHeightMapping[SCI_SCREEN_UPSCALEDMAXHEIGHT + 1];
+ int16 _upscaledWidthMapping[SCI_SCREEN_UPSCALEDMAXWIDTH + 1];
/**
* This defines whether or not the font we're drawing is already scaled
@@ -199,7 +239,38 @@ private:
*/
bool _fontIsUpscaled;
- uint16 getLowResScreenHeight();
+ // dynamic code
+ void (GfxScreen::*_vectorAdjustCoordinatePtr) (int16 *x, int16 *y);
+ void vectorAdjustCoordinateNOP (int16 *x, int16 *y);
+ void vectorAdjustCoordinate480x300Mac (int16 *x, int16 *y);
+
+ void (GfxScreen::*_vectorAdjustLineCoordinatesPtr) (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control);
+ void vectorAdjustLineCoordinatesNOP (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control);
+ void vectorAdjustLineCoordinates480x300Mac (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control);
+
+ byte (GfxScreen::*_vectorIsFillMatchPtr) (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA);
+ byte vectorIsFillMatchNormal (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA);
+ byte vectorIsFillMatch480x300Mac (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA);
+
+ void (GfxScreen::*_vectorPutPixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ void vectorPutPixel480x300Mac (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+
+ void (GfxScreen::*_vectorPutLinePixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ void vectorPutLinePixel480x300Mac (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+
+ byte (GfxScreen::*_vectorGetPixelPtr) (byte *screen, int16 x, int16 y);
+
+ void (GfxScreen::*_putPixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ void putPixelNormal (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ void putPixelDisplayUpscaled (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+ void putPixelAllUpscaled (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control);
+
+ byte (GfxScreen::*_getPixelPtr) (byte *screen, int16 x, int16 y);
+ byte getPixelNormal (byte *screen, int16 x, int16 y);
+ byte getPixelUpscaled (byte *screen, int16 x, int16 y);
+
+ // pixel helper
+ void putScaledPixelOnScreen(byte *screen, int16 x, int16 y, byte color);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/text16.cpp b/engines/sci/graphics/text16.cpp
index 245d6996cb..f463dff4b1 100644
--- a/engines/sci/graphics/text16.cpp
+++ b/engines/sci/graphics/text16.cpp
@@ -143,18 +143,30 @@ int16 GfxText16::CodeProcessing(const char *&text, GuiResourceId orgFontId, int1
return textCodeSize;
}
-static const uint16 text16_punctuationSjis[] = {
- 0x9F82, 0xA182, 0xA382, 0xA582, 0xA782, 0xC182, 0xA782, 0xC182, 0xE182, 0xE382, 0xE582, 0xEC82,
- 0x4083, 0x4283, 0x4483, 0x4683, 0x4883, 0x6283, 0x8383, 0x8583, 0x8783, 0x8E83, 0x9583, 0x9683,
- 0x5B81, 0x4181, 0x4281, 0x7681, 0x7881, 0x4981, 0x4881, 0
+// Has actually punctuation and characters in it, that may not be the first in a line
+static const uint16 text16_shiftJIS_punctuation[] = {
+ 0x9F82, 0xA182, 0xA382, 0xA582, 0xA782, 0xC182, 0xE182, 0xE382, 0xE582, 0xEC82, 0x4083, 0x4283,
+ 0x4483, 0x4683, 0x4883, 0x6283, 0x8383, 0x8583, 0x8783, 0x8E83, 0x9583, 0x9683, 0x5B81, 0x4181,
+ 0x4281, 0x7681, 0x7881, 0x4981, 0x4881, 0
};
// return max # of chars to fit maxwidth with full words, does not include
// breaking space
-int16 GfxText16::GetLongest(const char *text, int16 maxWidth, GuiResourceId orgFontId) {
+// Also adjusts text pointer to the new position for the caller
+//
+// Special cases in games:
+// Laura Bow 2 - Credits in the game menu - all the text lines start with spaces (bug #5159)
+// Act 6 Coroner questionaire - the text of all control buttons has trailing spaces
+// "Detective Ryan Hanrahan O'Riley" contains even more spaces (bug #5334)
+// Conquests of Camelot - talking with Cobb - one text box of the dialogue contains a longer word,
+// that will be broken into 2 lines (bug #5159)
+int16 GfxText16::GetLongest(const char *&textPtr, int16 maxWidth, GuiResourceId orgFontId) {
uint16 curChar = 0;
- int16 maxChars = 0, curCharCount = 0;
- uint16 width = 0;
+ const char *textStartPtr = textPtr;
+ const char *lastSpacePtr = NULL;
+ int16 lastSpaceCharCount = 0;
+ int16 curCharCount = 0, resultCharCount = 0;
+ uint16 curWidth = 0, tempWidth = 0;
GuiResourceId previousFontId = GetFontId();
int16 previousPenColor = _ports->_curPort->penClr;
@@ -162,35 +174,38 @@ int16 GfxText16::GetLongest(const char *text, int16 maxWidth, GuiResourceId orgF
if (!_font)
return 0;
- while (width <= maxWidth) {
- curChar = (*(const byte *)text++);
+ while (1) {
+ curChar = (*(const byte *)textPtr);
if (_font->isDoubleByte(curChar)) {
- curChar |= (*(const byte *)text++) << 8;
- curCharCount++;
+ curChar |= (*(const byte *)(textPtr + 1)) << 8;
}
switch (curChar) {
case 0x7C:
if (getSciVersion() >= SCI_VERSION_1_1) {
- curCharCount++;
- curCharCount += CodeProcessing(text, orgFontId, previousPenColor, false);
+ curCharCount++; textPtr++;
+ curCharCount += CodeProcessing(textPtr, orgFontId, previousPenColor, false);
continue;
}
break;
// We need to add 0xD, 0xA and 0xD 0xA to curCharCount and then exit
- // which means, we split text like
- // 'Mature, experienced software analyst available.' 0xD 0xA
- // 'Bug installation a proven speciality. "No version too clean."' (normal game text, this is from lsl2)
- // and 0xA '-------' 0xA (which is the official sierra subtitle separator)
+ // which means, we split text like for example
+ // - 'Mature, experienced software analyst available.' 0xD 0xA
+ // 'Bug installation a proven speciality. "No version too clean."' (normal game text, this is from lsl2)
+ // - 0xA '-------' 0xA (which is the official sierra subtitle separator) (found in multilingual versions)
// Sierra did it the same way.
case 0xD:
// Check, if 0xA is following, if so include it as well
- if ((*(const unsigned char *)text) == 0xA)
- curCharCount++;
+ if ((*(const byte *)(textPtr + 1)) == 0xA) {
+ curCharCount++; textPtr++;
+ }
// it's meant to pass through here
case 0xA:
case 0x9781: // this one is used by SQ4/japanese as line break as well
- curCharCount++;
+ curCharCount++; textPtr++;
+ if (curChar > 0xFF) {
+ curCharCount++; textPtr++;
+ }
// and it's also meant to pass through here
case 0:
SetFont(previousFontId);
@@ -198,55 +213,86 @@ int16 GfxText16::GetLongest(const char *text, int16 maxWidth, GuiResourceId orgF
return curCharCount;
case ' ':
- maxChars = curCharCount; // return count up to (but not including) breaking space
+ lastSpaceCharCount = curCharCount; // return count up to (but not including) breaking space
+ lastSpacePtr = textPtr + 1; // remember position right after the current space
break;
}
- // Sometimes this can go off the screen, like for example bug #3040161.
- // However, we only perform this for non-Japanese games, as these require
- // special handling, done after this loop.
- if (width + _font->getCharWidth(curChar) > maxWidth && g_sci->getLanguage() != Common::JA_JPN)
+ tempWidth += _font->getCharWidth(curChar);
+
+ // Width is too large? -> break out
+ if (tempWidth > maxWidth)
break;
- width += _font->getCharWidth(curChar);
- curCharCount++;
+
+ // still fits, remember width
+ curWidth = tempWidth;
+
+ // go to next character
+ curCharCount++; textPtr++;
+ if (curChar > 0xFF) {
+ // Double-Byte
+ curCharCount++; textPtr++;
+ }
}
- // Text without spaces, probably Kanji/Japanese
- if (maxChars == 0) {
- maxChars = curCharCount;
+ if (lastSpaceCharCount) {
+ // Break and at least one space was found before that
+ resultCharCount = lastSpaceCharCount;
- uint16 nextChar;
+ // additionally skip over all spaces, that are following that space, but don't count them for displaying purposes
+ textPtr = lastSpacePtr;
+ while (*textPtr == ' ')
+ textPtr++;
- // We remove the last char only, if maxWidth was actually equal width
- // before adding the last char. Otherwise we won't get the same cutting
- // as in sierra pc98 sci.
- if (maxWidth == (width - _font->getCharWidth(curChar))) {
- maxChars--;
- if (curChar > 0xFF)
- maxChars--;
- nextChar = curChar;
- } else {
- nextChar = (*(const byte *)text++);
- if (_font->isDoubleByte(nextChar))
- nextChar |= (*(const byte *)text++) << 8;
- }
- // sierra checked the following character against a punctuation kanji table
- if (nextChar > 0xFF) {
- // if the character is punctuation, we go back one character
- uint nonBreakingNr = 0;
- while (text16_punctuationSjis[nonBreakingNr]) {
- if (text16_punctuationSjis[nonBreakingNr] == nextChar) {
- maxChars--;
- if (curChar > 0xFF)
- maxChars--; // go back 2 chars, when last char was double byte
+ } else {
+ // Break without spaces found, we split the very first word - may also be Kanji/Japanese
+ if (curChar > 0xFF) {
+ // current charracter is Japanese
+
+ // PC-9801 SCI actually added the last character, which shouldn't fit anymore, still onto the
+ // screen in case maxWidth wasn't fully reached with the last character
+ if (( maxWidth - 1 ) > curWidth) {
+ curCharCount += 2; textPtr += 2;
+
+ curChar = (*(const byte *)textPtr);
+ if (_font->isDoubleByte(curChar)) {
+ curChar |= (*(const byte *)(textPtr + 1)) << 8;
+ }
+ }
+
+ // But it also checked, if the current character is not inside a punctuation table and it even
+ // went backwards in case it found multiple ones inside that table.
+ uint nonBreakingPos = 0;
+
+ while (1) {
+ // Look up if character shouldn't be the first on a new line
+ nonBreakingPos = 0;
+ while (text16_shiftJIS_punctuation[nonBreakingPos]) {
+ if (text16_shiftJIS_punctuation[nonBreakingPos] == curChar)
+ break;
+ nonBreakingPos++;
+ }
+ if (!text16_shiftJIS_punctuation[nonBreakingPos]) {
+ // character is fine
break;
}
- nonBreakingNr++;
+ // Character is not acceptable, seek backward in the text
+ curCharCount -= 2; textPtr -= 2;
+ if (textPtr < textStartPtr)
+ error("Seeking back went too far, data corruption?");
+
+ curChar = (*(const byte *)textPtr);
+ if (!_font->isDoubleByte(curChar))
+ error("Non double byte while seeking back");
+ curChar |= (*(const byte *)(textPtr + 1)) << 8;
}
}
+
+ // We split the word in that case
+ resultCharCount = curCharCount;
}
SetFont(previousFontId);
_ports->penColor(previousPenColor);
- return maxChars;
+ return resultCharCount;
}
void GfxText16::Width(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight, bool restoreFont) {
@@ -303,7 +349,7 @@ void GfxText16::DrawString(const char *str, GuiResourceId orgFontId, int16 orgPe
Draw(str, 0, (int16)strlen(str), orgFontId, orgPenColor);
}
-int16 GfxText16::Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth) {
+int16 GfxText16::Size(Common::Rect &rect, const char *text, uint16 languageSplitter, GuiResourceId fontId, int16 maxWidth) {
GuiResourceId previousFontId = GetFontId();
int16 previousPenColor = _ports->_curPort->penClr;
int16 charCount;
@@ -315,12 +361,12 @@ int16 GfxText16::Size(Common::Rect &rect, const char *text, GuiResourceId fontId
else
fontId = previousFontId;
- if (g_sci->getLanguage() == Common::JA_JPN)
- SwitchToFont900OnSjis(text);
-
rect.top = rect.left = 0;
if (maxWidth < 0) { // force output as single line
+ if (g_sci->getLanguage() == Common::JA_JPN)
+ SwitchToFont900OnSjis(text, languageSplitter);
+
StringWidth(text, fontId, textWidth, textHeight);
rect.bottom = textHeight;
rect.right = textWidth;
@@ -328,17 +374,20 @@ int16 GfxText16::Size(Common::Rect &rect, const char *text, GuiResourceId fontId
// rect.right=found widest line with RTextWidth and GetLongest
// rect.bottom=num. lines * GetPointSize
rect.right = (maxWidth ? maxWidth : 192);
- const char *curPos = text;
- while (*curPos) {
- charCount = GetLongest(curPos, rect.right, fontId);
+ const char *curTextPos = text; // in work position for GetLongest()
+ const char *curTextLine = text; // starting point of current line
+ while (*curTextPos) {
+ // We need to check for Shift-JIS every line
+ if (g_sci->getLanguage() == Common::JA_JPN)
+ SwitchToFont900OnSjis(curTextPos, languageSplitter);
+
+ charCount = GetLongest(curTextPos, rect.right, fontId);
if (charCount == 0)
break;
- Width(curPos, 0, charCount, fontId, textWidth, textHeight, false);
+ Width(curTextLine, 0, charCount, fontId, textWidth, textHeight, false);
maxTextWidth = MAX(textWidth, maxTextWidth);
totalHeight += textHeight;
- curPos += charCount;
- while (*curPos == ' ')
- curPos++; // skip over breaking spaces
+ curTextLine = curTextPos;
}
rect.bottom = totalHeight;
rect.right = maxWidth ? maxWidth : MIN(rect.right, maxTextWidth);
@@ -405,34 +454,38 @@ void GfxText16::Show(const char *text, int16 from, int16 len, GuiResourceId orgF
}
// Draws a text in rect.
-void GfxText16::Box(const char *text, bool show, const Common::Rect &rect, TextAlignment alignment, GuiResourceId fontId) {
+void GfxText16::Box(const char *text, uint16 languageSplitter, bool show, const Common::Rect &rect, TextAlignment alignment, GuiResourceId fontId) {
int16 textWidth, maxTextWidth, textHeight, charCount;
int16 offset = 0;
int16 hline = 0;
GuiResourceId previousFontId = GetFontId();
int16 previousPenColor = _ports->_curPort->penClr;
bool doubleByteMode = false;
+ const char *curTextPos = text;
+ const char *curTextLine = text;
if (fontId != -1)
SetFont(fontId);
else
fontId = previousFontId;
- if (g_sci->getLanguage() == Common::JA_JPN) {
- if (SwitchToFont900OnSjis(text))
- doubleByteMode = true;
- }
-
// Reset reference code rects
_codeRefRects.clear();
_codeRefTempRect.left = _codeRefTempRect.top = -1;
maxTextWidth = 0;
- while (*text) {
- charCount = GetLongest(text, rect.width(), fontId);
+ while (*curTextPos) {
+ // We need to check for Shift-JIS every line
+ // Police Quest 2 PC-9801 often draws English + Japanese text during the same call
+ if (g_sci->getLanguage() == Common::JA_JPN) {
+ if (SwitchToFont900OnSjis(curTextPos, languageSplitter))
+ doubleByteMode = true;
+ }
+
+ charCount = GetLongest(curTextPos, rect.width(), fontId);
if (charCount == 0)
break;
- Width(text, 0, charCount, fontId, textWidth, textHeight, true);
+ Width(curTextLine, 0, charCount, fontId, textWidth, textHeight, true);
maxTextWidth = MAX<int16>(maxTextWidth, textWidth);
switch (alignment) {
case SCI_TEXT16_ALIGNMENT_RIGHT:
@@ -451,15 +504,13 @@ void GfxText16::Box(const char *text, bool show, const Common::Rect &rect, TextA
_ports->moveTo(rect.left + offset, rect.top + hline);
if (show) {
- Show(text, 0, charCount, fontId, previousPenColor);
+ Show(curTextLine, 0, charCount, fontId, previousPenColor);
} else {
- Draw(text, 0, charCount, fontId, previousPenColor);
+ Draw(curTextLine, 0, charCount, fontId, previousPenColor);
}
hline += textHeight;
- text += charCount;
- while (*text == ' ')
- text++; // skip over breaking spaces
+ curTextLine = curTextPos;
}
SetFont(previousFontId);
_ports->penColor(previousPenColor);
@@ -521,11 +572,13 @@ void GfxText16::DrawStatus(const char *text) {
// Sierra did this in their PC98 interpreter only, they identify a text as being
// sjis and then switch to font 900
-bool GfxText16::SwitchToFont900OnSjis(const char *text) {
+bool GfxText16::SwitchToFont900OnSjis(const char *text, uint16 languageSplitter) {
byte firstChar = (*(const byte *)text++);
- if (((firstChar >= 0x81) && (firstChar <= 0x9F)) || ((firstChar >= 0xE0) && (firstChar <= 0xEF))) {
- SetFont(900);
- return true;
+ if (languageSplitter != 0x6a23) { // #j prefix as language splitter
+ if (((firstChar >= 0x81) && (firstChar <= 0x9F)) || ((firstChar >= 0xE0) && (firstChar <= 0xEF))) {
+ SetFont(900);
+ return true;
+ }
}
return false;
}
@@ -554,9 +607,9 @@ reg_t GfxText16::allocAndFillReferenceRectArray() {
return NULL_REG;
}
-void GfxText16::kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) {
+void GfxText16::kernelTextSize(const char *text, uint16 languageSplitter, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) {
Common::Rect rect(0, 0, 0, 0);
- Size(rect, text, font, maxWidth);
+ Size(rect, text, languageSplitter, font, maxWidth);
*textWidth = rect.width();
*textHeight = rect.height();
}
diff --git a/engines/sci/graphics/text16.h b/engines/sci/graphics/text16.h
index ab0cb13a64..2724d97347 100644
--- a/engines/sci/graphics/text16.h
+++ b/engines/sci/graphics/text16.h
@@ -51,15 +51,20 @@ public:
void ClearChar(int16 chr);
- int16 GetLongest(const char *text, int16 maxWidth, GuiResourceId orgFontId);
+ int16 GetLongest(const char *&text, int16 maxWidth, GuiResourceId orgFontId);
void Width(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight, bool restoreFont);
void StringWidth(const char *str, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight);
void ShowString(const char *str, GuiResourceId orgFontId, int16 orgPenColor);
void DrawString(const char *str, GuiResourceId orgFontId, int16 orgPenColor);
- int16 Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth);
+ int16 Size(Common::Rect &rect, const char *text, uint16 textLanguage, GuiResourceId fontId, int16 maxWidth);
void Draw(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 orgPenColor);
void Show(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 orgPenColor);
- void Box(const char *text, bool show, const Common::Rect &rect, TextAlignment alignment, GuiResourceId fontId);
+ void Box(const char *text, uint16 languageSplitter, bool show, const Common::Rect &rect, TextAlignment alignment, GuiResourceId fontId);
+
+ void Box(const char *text, bool show, const Common::Rect &rect, TextAlignment alignment, GuiResourceId fontId) {
+ Box(text, 0, show, rect, alignment, fontId);
+ }
+
void DrawString(const char *text);
void DrawStatus(const char *text);
@@ -67,13 +72,13 @@ public:
reg_t allocAndFillReferenceRectArray();
- void kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight);
+ void kernelTextSize(const char *text, uint16 textLanguage, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight);
void kernelTextFonts(int argc, reg_t *argv);
void kernelTextColors(int argc, reg_t *argv);
private:
void init();
- bool SwitchToFont900OnSjis(const char *text);
+ bool SwitchToFont900OnSjis(const char *text, uint16 languageSplitter);
GfxCache *_cache;
GfxPorts *_ports;
diff --git a/engines/sci/graphics/transitions.cpp b/engines/sci/graphics/transitions.cpp
index 5e7dbc6c15..ccc7a4389a 100644
--- a/engines/sci/graphics/transitions.cpp
+++ b/engines/sci/graphics/transitions.cpp
@@ -339,10 +339,10 @@ void GfxTransitions::pixelation(bool blackoutFlag) {
do {
mask = (mask & 1) ? (mask >> 1) ^ 0xB400 : mask >> 1;
- if (mask >= _screen->getWidth() * _screen->getHeight())
+ if (mask >= _screen->getScriptWidth() * _screen->getScriptHeight())
continue;
- pixelRect.left = mask % _screen->getWidth(); pixelRect.right = pixelRect.left + 1;
- pixelRect.top = mask / _screen->getWidth(); pixelRect.bottom = pixelRect.top + 1;
+ pixelRect.left = mask % _screen->getScriptWidth(); pixelRect.right = pixelRect.left + 1;
+ pixelRect.top = mask / _screen->getScriptWidth(); pixelRect.bottom = pixelRect.top + 1;
pixelRect.clip(_picRect);
if (!pixelRect.isEmpty())
copyRectToScreen(pixelRect, blackoutFlag);
diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp
index f3f352e5b8..da61ecf4c3 100644
--- a/engines/sci/graphics/view.cpp
+++ b/engines/sci/graphics/view.cpp
@@ -283,6 +283,7 @@ void GfxView::initData(GuiResourceId resourceId) {
_isScaleable = false;
break;
case 0x40:
+ case 0x4F: // LSL6 Polish, seems to be garbage - bug #6718
case 0:
break; // don't do anything, we already have _isScaleable set
default:
@@ -366,7 +367,7 @@ void GfxView::initData(GuiResourceId resourceId) {
default:
error("ViewType was not detected, can't continue");
}
-
+
// Inject our own views
// Currently only used for Dual mode (speech + text) for games, that do not have a "dual" icon already
// Which is Laura Bow 2 + King's Quest 6
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index 6b6058c819..33392e3b42 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -76,8 +76,8 @@ MODULE_OBJS := \
sound/drivers/midi.o \
sound/drivers/pcjr.o \
video/seq_decoder.o
-
-
+
+
ifdef ENABLE_SCI32
MODULE_OBJS += \
engine/kgraphics32.o \
diff --git a/engines/sci/parser/vocabulary.cpp b/engines/sci/parser/vocabulary.cpp
index 3344b79e26..828a57abeb 100644
--- a/engines/sci/parser/vocabulary.cpp
+++ b/engines/sci/parser/vocabulary.cpp
@@ -74,6 +74,8 @@ Vocabulary::Vocabulary(ResourceManager *resMan, bool foreign) : _resMan(resMan),
parser_event = NULL_REG;
parserIsValid = false;
+
+ _pronounReference = 0x1000; // Non-existent word
}
Vocabulary::~Vocabulary() {
@@ -535,10 +537,7 @@ bool Vocabulary::tokenizeString(ResultWordListList &retval, const char *sentence
if (Common::isAlnum(c) || (c == '-' && wordLen) || (c >= 0x80)) {
currentWord[wordLen] = lowerCaseMap[c];
++wordLen;
- } else if (c == '\'' && wordLen && (sentence[pos_in_sentence] == 's' || sentence[pos_in_sentence] == 'S')) {
- // Skip apostrophe-s at the end of the word, if it exists
- pos_in_sentence++; // skip the 's'
- } else {
+ } else if (c == ' ' || c == '\0') {
// Continue on this word. Words may contain a '-', but may not start with
// one.
@@ -741,4 +740,79 @@ int Vocabulary::parseNodes(int *i, int *pos, int type, int nr, int argc, const c
return oldPos;
}
+
+// FIXME: Duplicated from said.cpp
+static int node_major(ParseTreeNode* node) {
+ assert(node->type == kParseTreeBranchNode);
+ assert(node->left->type == kParseTreeLeafNode);
+ return node->left->value;
+}
+static bool node_is_terminal(ParseTreeNode* node) {
+ return (node->right->right &&
+ node->right->right->type != kParseTreeBranchNode);
+}
+static int node_terminal_value(ParseTreeNode* node) {
+ assert(node_is_terminal(node));
+ return node->right->right->value;
+}
+
+static ParseTreeNode* scanForMajor(ParseTreeNode *tree, int major) {
+ assert(tree);
+
+ if (node_is_terminal(tree)) {
+ if (node_major(tree) == major)
+ return tree;
+ else
+ return 0;
+ }
+
+ ParseTreeNode* ptr = tree->right;
+
+ // Scan children
+ while (ptr->right) {
+ ptr = ptr->right;
+
+ if (node_major(ptr->left) == major)
+ return ptr->left;
+ }
+
+ if (major == 0x141)
+ return 0;
+
+ // If not found, go into a 0x141 and try again
+ tree = scanForMajor(tree, 0x141);
+ if (!tree)
+ return 0;
+ return scanForMajor(tree, major);
+}
+
+bool Vocabulary::storePronounReference() {
+ assert(parserIsValid);
+
+ ParseTreeNode *ptr = scanForMajor(_parserNodes, 0x142); // 0x142 = object?
+
+ while (ptr && !node_is_terminal(ptr))
+ ptr = scanForMajor(ptr, 0x141);
+
+ if (!ptr)
+ return false;
+
+ _pronounReference = node_terminal_value(ptr);
+
+ debugC(kDebugLevelParser, "Stored pronoun reference: %x", _pronounReference);
+ return true;
+}
+
+void Vocabulary::replacePronouns(ResultWordListList &words) {
+ if (_pronounReference == 0x1000)
+ return;
+
+ for (ResultWordListList::iterator i = words.begin(); i != words.end(); ++i)
+ for (ResultWordList::iterator j = i->begin(); j != i->end(); ++j)
+ if (j->_class & (VOCAB_CLASS_PRONOUN << 4)) {
+ j->_class = VOCAB_CLASS_NOUN << 4;
+ j->_group = _pronounReference;
+ }
+}
+
} // End of namespace Sci
diff --git a/engines/sci/parser/vocabulary.h b/engines/sci/parser/vocabulary.h
index 09499946cb..f4adee6e55 100644
--- a/engines/sci/parser/vocabulary.h
+++ b/engines/sci/parser/vocabulary.h
@@ -233,6 +233,16 @@ public:
int parseGNF(const ResultWordListList &words, bool verbose = false);
/**
+ * Find and store reference for future pronouns
+ */
+ bool storePronounReference();
+
+ /**
+ * Replace pronouns by stored reference
+ */
+ void replacePronouns(ResultWordListList &words);
+
+ /**
* Constructs the Greibach Normal Form of the grammar supplied in 'branches'.
* @param verbose Set to true for debugging. If true, the list is
* freed before the function ends
@@ -360,6 +370,8 @@ private:
SynonymList _synonyms; /**< The list of synonyms */
Common::Array<Common::List<AltInput> > _altInputs;
+ int _pronounReference;
+
public:
// Accessed by said()
ParseTreeNode _parserNodes[VOCAB_TREE_NODES]; /**< The parse tree */
diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index 17195c14b8..10740a8b7b 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -55,6 +55,11 @@ SciVersion getSciVersion() {
return s_sciVersion;
}
+SciVersion getSciVersionForDetection() {
+ assert(!g_sci);
+ return s_sciVersion;
+}
+
const char *getSciVersionDesc(SciVersion version) {
switch (version) {
case SCI_VERSION_NONE:
@@ -88,9 +93,6 @@ const char *getSciVersionDesc(SciVersion version) {
//////////////////////////////////////////////////////////////////////
-
-#undef SCI_REQUIRE_RESOURCE_FILES
-
//#define SCI_VERBOSE_RESMAN 1
static const char *const s_errorDescriptions[] = {
@@ -639,7 +641,7 @@ int ResourceManager::addAppropriateSources() {
return 1;
}
-int ResourceManager::addAppropriateSources(const Common::FSList &fslist) {
+int ResourceManager::addAppropriateSourcesForDetection(const Common::FSList &fslist) {
ResourceSource *map = 0;
Common::Array<ResourceSource *> sci21Maps;
@@ -858,7 +860,7 @@ void ResourceManager::freeResourceSources() {
ResourceManager::ResourceManager() {
}
-void ResourceManager::init(bool initFromFallbackDetector) {
+void ResourceManager::init() {
_memoryLocked = 0;
_memoryLRU = 0;
_LRU.clear();
@@ -890,25 +892,24 @@ void ResourceManager::init(bool initFromFallbackDetector) {
debugC(1, kDebugLevelResMan, "resMan: Detected volume version %d: %s", _volVersion, versionDescription(_volVersion));
if ((_mapVersion == kResVersionUnknown) && (_volVersion == kResVersionUnknown)) {
- warning("Volume and map version not detected, assuming that this is not a sci game");
+ warning("Volume and map version not detected, assuming that this is not a SCI game");
_viewType = kViewUnknown;
return;
}
scanNewSources();
- if (!initFromFallbackDetector) {
- if (!addAudioSources()) {
- // FIXME: This error message is not always correct.
- // OTOH, it is nice to be able to detect missing files/sources
- // So we should definitely fix addAudioSources so this error
- // only pops up when necessary. Disabling for now.
- //error("Somehow I can't seem to find the sound files I need (RESOURCE.AUD/RESOURCE.SFX), aborting");
- }
- addScriptChunkSources();
- scanNewSources();
+ if (!addAudioSources()) {
+ // FIXME: This error message is not always correct.
+ // OTOH, it is nice to be able to detect missing files/sources
+ // So we should definitely fix addAudioSources so this error
+ // only pops up when necessary. Disabling for now.
+ //error("Somehow I can't seem to find the sound files I need (RESOURCE.AUD/RESOURCE.SFX), aborting");
}
+ addScriptChunkSources();
+ scanNewSources();
+
detectSciVersion();
debugC(1, kDebugLevelResMan, "resMan: Detected %s", getSciVersionDesc(getSciVersion()));
@@ -943,6 +944,22 @@ void ResourceManager::init(bool initFromFallbackDetector) {
}
}
+void ResourceManager::initForDetection() {
+ assert(!g_sci);
+
+ _memoryLocked = 0;
+ _memoryLRU = 0;
+ _LRU.clear();
+ _resMap.clear();
+ _audioMapSCI1 = NULL;
+
+ _mapVersion = detectMapVersion();
+ _volVersion = detectVolVersion();
+
+ scanNewSources();
+ detectSciVersion();
+}
+
ResourceManager::~ResourceManager() {
// freeing resources
ResourceMap::iterator itr = _resMap.begin();
@@ -1645,6 +1662,9 @@ int ResourceManager::readResourceMapSCI1(ResourceSource *map) {
do {
type = fileStream->readByte() & 0x1F;
resMap[type].wOffset = fileStream->readUint16LE();
+ if (fileStream->eos())
+ return SCI_ERROR_RESMAP_NOT_FOUND;
+
resMap[prevtype].wSize = (resMap[type].wOffset
- resMap[prevtype].wOffset) / nEntrySize;
prevtype = type;
@@ -2330,6 +2350,9 @@ bool ResourceManager::detectPaletteMergingSci11() {
// Old palette format used in palette resource? -> it's merging
if ((data[0] == 0 && data[1] == 1) || (data[0] == 0 && data[1] == 0 && READ_LE_UINT16(data + 29) == 0))
return true;
+ // Hardcoded: Laura Bow 2 floppy uses new palette resource, but still palette merging + 16 bit color matching
+ if ((g_sci->getGameId() == GID_LAURABOW2) && (!g_sci->isCD()) && (!g_sci->isDemo()))
+ return true;
return false;
}
return false;
diff --git a/engines/sci/resource.h b/engines/sci/resource.h
index e90f52a3ce..ef48998b04 100644
--- a/engines/sci/resource.h
+++ b/engines/sci/resource.h
@@ -312,10 +312,22 @@ public:
/**
* Initializes the resource manager.
*/
- void init(bool initFromFallbackDetector = false);
+ void init();
+ /**
+ * Similar to the function above, only called from the fallback detector
+ */
+ void initForDetection();
+
+ /**
+ * Adds all of the resource files for a game
+ */
int addAppropriateSources();
- int addAppropriateSources(const Common::FSList &fslist); // TODO: Switch from FSList to Common::Archive?
+
+ /**
+ * Similar to the function above, only called from the fallback detector
+ */
+ int addAppropriateSourcesForDetection(const Common::FSList &fslist); // TODO: Switch from FSList to Common::Archive?
/**
* Looks up a resource's data.
@@ -584,6 +596,7 @@ public:
Track *getDigitalTrack();
int getChannelFilterMask(int hardwareMask, bool wantsRhythm);
byte getInitialVoiceCount(byte channel);
+ byte getSoundPriority() const { return _soundPriority; }
private:
SciVersion _soundVersion;
@@ -591,6 +604,7 @@ private:
Track *_tracks;
Resource *_innerResource;
ResourceManager *_resMan;
+ byte _soundPriority;
};
} // End of namespace Sci
diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp
index 66778f0914..3a43774492 100644
--- a/engines/sci/resource_audio.cpp
+++ b/engines/sci/resource_audio.cpp
@@ -110,7 +110,7 @@ bool Resource::loadFromAudioVolumeSCI11(Common::SeekableReadStream *file) {
unalloc();
return false;
}
-
+
_headerSize = file->readByte();
if (type == kResourceTypeAudio) {
@@ -579,6 +579,7 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers
return;
_innerResource = resource;
+ _soundPriority = 0xFF;
byte *data, *data2;
byte *dataEnd;
@@ -725,6 +726,9 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers
data += 6;
}
} else {
+ // The first byte of the 0xF0 track's channel list is priority
+ _soundPriority = *data;
+
// Skip over digital track
data += 6;
}
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index fc723f18cf..668ad053cc 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -114,6 +114,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam
DebugMan.addDebugChannel(kDebugLevelVM, "VM", "VM debugging");
DebugMan.addDebugChannel(kDebugLevelScripts, "Scripts", "Notifies when scripts are unloaded");
DebugMan.addDebugChannel(kDebugLevelScriptPatcher, "ScriptPatcher", "Notifies when scripts are patched");
+ DebugMan.addDebugChannel(kDebugLevelWorkarounds, "Workarounds", "Notifies when workarounds are triggered");
DebugMan.addDebugChannel(kDebugLevelGC, "GC", "Garbage Collector debugging");
DebugMan.addDebugChannel(kDebugLevelResMan, "ResMan", "Resource manager debugging");
DebugMan.addDebugChannel(kDebugLevelOnStartup, "OnStartup", "Enter debugger at start of game");
@@ -219,7 +220,7 @@ Common::Error SciEngine::run() {
// Add the after market GM patches for the specified game, if they exist
_resMan->addNewGMPatch(_gameId);
_gameObjectAddress = _resMan->findGameObject();
-
+
_scriptPatcher = new ScriptPatcher();
SegManager *segMan = new SegManager(_resMan, _scriptPatcher);
@@ -281,25 +282,13 @@ Common::Error SciEngine::run() {
// Check whether loading a savestate was requested
int directSaveSlotLoading = ConfMan.getInt("save_slot");
if (directSaveSlotLoading >= 0) {
- // call GameObject::play (like normally)
- initStackBaseWithSelector(SELECTOR(play));
- // We set this, so that the game automatically quit right after init
- _gamestate->variables[VAR_GLOBAL][4] = TRUE_REG;
+ _gamestate->_delayedRestoreGame = true;
+ _gamestate->_delayedRestoreGameId = directSaveSlotLoading;
// Jones only initializes its menus when restarting/restoring, thus set
// the gameIsRestarting flag here before initializing. Fixes bug #6536.
if (g_sci->getGameId() == GID_JONES)
_gamestate->gameIsRestarting = GAMEISRESTARTING_RESTORE;
-
- _gamestate->_executionStackPosChanged = false;
- run_vm(_gamestate);
-
- // As soon as we get control again, actually restore the game
- reg_t restoreArgv[2] = { NULL_REG, make_reg(0, directSaveSlotLoading) }; // special call (argv[0] is NULL)
- kRestoreGame(_gamestate, 2, restoreArgv);
-
- // this indirectly calls GameObject::init, which will setup menu, text font/color codes etc.
- // without this games would be pretty badly broken
}
// Show any special warnings for buggy scripts with severe game bugs,
@@ -896,7 +885,7 @@ void SciEngine::syncSoundSettings() {
bool SciEngine::speechAndSubtitlesEnabled() {
bool subtitlesOn = ConfMan.getBool("subtitles");
bool speechOn = !ConfMan.getBool("speech_mute");
-
+
if (isCD() && subtitlesOn && speechOn)
return true;
return false;
@@ -936,7 +925,7 @@ void SciEngine::updateScummVMAudioOptions() {
// depending on the in-game settings
if (isCD() && getSciVersion() == SCI_VERSION_1_1) {
uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][90].getOffset();
-
+
switch (ingameSetting) {
case 1:
// subtitles
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index 48bc4819d2..c6813aa07c 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -105,7 +105,8 @@ enum kDebugLevels {
kDebugLevelResMan = 1 << 19,
kDebugLevelOnStartup = 1 << 20,
kDebugLevelDebugMode = 1 << 21,
- kDebugLevelScriptPatcher = 1 << 22
+ kDebugLevelScriptPatcher = 1 << 22,
+ kDebugLevelWorkarounds = 1 << 23
};
enum SciGameId {
@@ -314,13 +315,16 @@ public:
* if NULL is passed no subtitle will be added to the returned string
* @return processed string
*/
- Common::String strSplit(const char *str, const char *sep = "\r----------\r");
+ Common::String strSplitLanguage(const char *str, uint16 *splitLanguage, const char *sep = "\r----------\r");
+ Common::String strSplit(const char *str, const char *sep = "\r----------\r") {
+ return strSplitLanguage(str, NULL, sep);
+ }
kLanguage getSciLanguage();
void setSciLanguage(kLanguage lang);
void setSciLanguage();
- Common::String getSciLanguageString(const Common::String &str, kLanguage lang, kLanguage *lang2 = NULL) const;
+ Common::String getSciLanguageString(const Common::String &str, kLanguage lang, kLanguage *lang2 = NULL, uint16 *languageSplitter = NULL) const;
// Check if vocabulary needs to get switched (in multilingual parser games)
void checkVocabularySwitch();
@@ -429,6 +433,12 @@ extern SciEngine *g_sci;
SciVersion getSciVersion();
/**
+ * Same as above, but this version doesn't assert on unknown SCI versions.
+ * Only used by the fallback detector
+ */
+SciVersion getSciVersionForDetection();
+
+/**
* Convenience function converting an SCI version into a human-readable string.
*/
const char *getSciVersionDesc(SciVersion version);
diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp
index c0b4f3122e..9f0d8d150f 100644
--- a/engines/sci/sound/midiparser_sci.cpp
+++ b/engines/sci/sound/midiparser_sci.cpp
@@ -360,6 +360,13 @@ void MidiParser_SCI::sendInitCommands() {
sendToDriver(0xB0 | i, 0x4B, voiceCount);
}
}
+ } else {
+ for (int i = 0; i < _track->channelCount; ++i) {
+ byte voiceCount = _track->channels[i].poly;
+ byte num = _track->channels[i].number;
+ // TODO: Should we skip the control channel?
+ sendToDriver(0xB0 | num, 0x4B, voiceCount);
+ }
}
}
@@ -480,19 +487,25 @@ void MidiParser_SCI::trackState(uint32 b) {
s._sustain = (op2 != 0);
break;
case 0x4B: // voices
+ if (s._voices != op2) {
+ // CHECKME: Should we directly call remapChannels() if _mainThreadCalled?
+ debugC(2, kDebugLevelSound, "Dynamic voice change (%d to %d)", s._voices, op2);
+ _music->needsRemap();
+ }
s._voices = op2;
_pSnd->_chan[channel]._voices = op2; // Also sync our MusicEntry
break;
case 0x4E: // mute
// This is channel mute only for sci1.
// (It's velocity control for sci0, but we don't need state in sci0)
- if (_soundVersion >= SCI_VERSION_1_EARLY) {
+ if (_soundVersion > SCI_VERSION_1_EARLY) {
// FIXME: mute is a level, not a bool, in some SCI versions
bool m = op2;
if (_pSnd->_chan[channel]._mute != m) {
_pSnd->_chan[channel]._mute = m;
- // TODO: If muting/unmuting a channel, remap channels.
- warning("Mute change without immediate remapping (mainThread = %d)", _mainThreadCalled);
+ // CHECKME: Should we directly call remapChannels() if _mainThreadCalled?
+ _music->needsRemap();
+ debugC(2, kDebugLevelSound, "Dynamic mute change (arg = %d, mainThread = %d)", m, _mainThreadCalled);
}
}
break;
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
index 362cca699d..dca73c3f51 100644
--- a/engines/sci/sound/music.cpp
+++ b/engines/sci/sound/music.cpp
@@ -142,8 +142,10 @@ void SciMusic::init() {
_driverLastChannel = _pMidiDrv->getLastChannel();
if (getSciVersion() <= SCI_VERSION_0_LATE)
_globalReverb = _pMidiDrv->getReverb(); // Init global reverb for SCI0
-
+
_currentlyPlayingSample = NULL;
+ _timeCounter = 0;
+ _needsRemap = false;
}
void SciMusic::miditimerCallback(void *p) {
@@ -158,6 +160,11 @@ void SciMusic::onTimer() {
// sending out queued commands that were "sent" via main thread
sendMidiCommandsFromQueue();
+ // remap channels, if requested
+ if (_needsRemap)
+ remapChannels(false);
+ _needsRemap = false;
+
for (MusicList::iterator i = _playList.begin(); i != end; ++i)
(*i)->onTimer();
}
@@ -285,8 +292,10 @@ byte SciMusic::getCurrentReverb() {
return _pMidiDrv->getReverb();
}
+// A larger priority value has higher priority. For equal priority values,
+// songs that have been added later have higher priority.
static bool musicEntryCompare(const MusicEntry *l, const MusicEntry *r) {
- return (l->priority > r->priority);
+ return (l->priority > r->priority) || (l->priority == r->priority && l->time > r->time);
}
void SciMusic::sortPlayList() {
@@ -317,6 +326,8 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) {
track = digital;
}
+ pSnd->time = ++_timeCounter;
+
if (track) {
// Play digital sample
if (track->digitalChannelNr != -1) {
@@ -334,6 +345,8 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) {
pSnd->pLoopStream = 0;
pSnd->soundType = Audio::Mixer::kSFXSoundType;
pSnd->hCurrentAud = Audio::SoundHandle();
+ pSnd->playBed = false;
+ pSnd->overridePriority = false;
} else {
// play MIDI track
Common::StackLock lock(_mutex);
@@ -380,6 +393,8 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) {
int16 prevHold = pSnd->hold;
pSnd->loop = 0;
pSnd->hold = -1;
+ pSnd->playBed = false;
+ pSnd->overridePriority = false;
pSnd->pMidiParser->loadMusic(track, pSnd, channelFilterMask, _soundVersion);
pSnd->reverb = pSnd->pMidiParser->getSongReverb();
@@ -395,6 +410,23 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) {
void SciMusic::soundPlay(MusicEntry *pSnd) {
_mutex.lock();
+ if (_soundVersion <= SCI_VERSION_1_EARLY && pSnd->playBed) {
+ // If pSnd->playBed, and version <= SCI1_EARLY, then kill
+ // existing sounds with playBed enabled.
+
+ uint playListCount = _playList.size();
+ for (uint i = 0; i < playListCount; i++) {
+ if (_playList[i] != pSnd && _playList[i]->playBed) {
+ debugC(2, kDebugLevelSound, "Automatically stopping old playBed song from soundPlay");
+ MusicEntry *old = _playList[i];
+ _mutex.unlock();
+ soundStop(old);
+ _mutex.lock();
+ break;
+ }
+ }
+ }
+
uint playListCount = _playList.size();
uint playListNo = playListCount;
MusicEntry *alreadyPlaying = NULL;
@@ -408,9 +440,11 @@ void SciMusic::soundPlay(MusicEntry *pSnd) {
}
if (playListNo == playListCount) { // not found
_playList.push_back(pSnd);
- sortPlayList();
}
+ pSnd->time = ++_timeCounter;
+ sortPlayList();
+
_mutex.unlock(); // unlock to perform mixer-related calls
if (pSnd->pMidiParser) {
@@ -554,6 +588,7 @@ void SciMusic::soundSetPriority(MusicEntry *pSnd, byte prio) {
Common::StackLock lock(_mutex);
pSnd->priority = prio;
+ pSnd->time = ++_timeCounter;
sortPlayList();
}
@@ -905,12 +940,12 @@ int ChannelRemapping::lowestPrio() const {
}
-void SciMusic::remapChannels() {
+void SciMusic::remapChannels(bool mainThread) {
if (_soundVersion <= SCI_VERSION_0_LATE)
return;
- // NB: This function should only be called from the main thread,
- // with _mutex locked
+ // NB: This function should only be called with _mutex locked
+ // Make sure to set the mainThread argument correctly.
ChannelRemapping *map = determineChannelMap();
@@ -963,9 +998,9 @@ void SciMusic::remapChannels() {
for (int j = 0; j < 16; ++j) {
if (!channelMapped[j]) {
- song->pMidiParser->mainThreadBegin();
+ if (mainThread) song->pMidiParser->mainThreadBegin();
song->pMidiParser->remapChannel(j, -1);
- song->pMidiParser->mainThreadEnd();
+ if (mainThread) song->pMidiParser->mainThreadEnd();
#ifdef DEBUG_REMAP
if (channelUsed[j])
debug(" Unmapping song %d, channel %d", songIndex, j);
@@ -997,9 +1032,9 @@ void SciMusic::remapChannels() {
#ifdef DEBUG_REMAP
debug(" Mapping (dontRemap) song %d, channel %d to device channel %d", songIndex, _channelMap[i]._channel, i);
#endif
- _channelMap[i]._song->pMidiParser->mainThreadBegin();
+ if (mainThread) _channelMap[i]._song->pMidiParser->mainThreadBegin();
_channelMap[i]._song->pMidiParser->remapChannel(_channelMap[i]._channel, i);
- _channelMap[i]._song->pMidiParser->mainThreadEnd();
+ if (mainThread) _channelMap[i]._song->pMidiParser->mainThreadEnd();
}
}
@@ -1052,9 +1087,9 @@ void SciMusic::remapChannels() {
#ifdef DEBUG_REMAP
debug(" Mapping song %d, channel %d to device channel %d", songIndex, _channelMap[j]._channel, j);
#endif
- _channelMap[j]._song->pMidiParser->mainThreadBegin();
+ if (mainThread) _channelMap[j]._song->pMidiParser->mainThreadBegin();
_channelMap[j]._song->pMidiParser->remapChannel(_channelMap[j]._channel, j);
- _channelMap[j]._song->pMidiParser->mainThreadEnd();
+ if (mainThread) _channelMap[j]._song->pMidiParser->mainThreadEnd();
break;
}
}
@@ -1062,9 +1097,9 @@ void SciMusic::remapChannels() {
}
// And finally, stop any empty channels
- for (int i = _driverFirstChannel; i <= _driverLastChannel; ++i) {
- if (!_channelMap[i]._song)
- resetDeviceChannel(i);
+ for (int i = _driverLastChannel; i >= _driverFirstChannel; --i) {
+ if (!_channelMap[i]._song && currentMap[i]._song)
+ resetDeviceChannel(i, mainThread);
}
delete map;
@@ -1105,7 +1140,8 @@ ChannelRemapping *SciMusic::determineChannelMap() {
#ifdef DEBUG_REMAP
- debug(" Song %d (%p), prio %d", songIndex, (void*)song, song->priority);
+ const char* name = g_sci->getEngineState()->_segMan->getObjectName(song->soundObj);
+ debug(" Song %d (%p) [%s], prio %d%s", songIndex, (void*)song, name, song->priority, song->playBed ? ", bed" : "");
#endif
// Store backup. If we fail to map this song, we will revert to this.
@@ -1118,13 +1154,23 @@ ChannelRemapping *SciMusic::determineChannelMap() {
if (c == 0xFF || c == 0xFE || c == 0x0F)
continue;
const MusicEntryChannel &channel = song->_chan[c];
- if (channel._dontMap)
+ if (channel._dontMap) {
+#ifdef DEBUG_REMAP
+ debug(" Channel %d dontMap, skipping", c);
+#endif
continue;
- if (channel._mute)
+ }
+ if (channel._mute) {
+#ifdef DEBUG_REMAP
+ debug(" Channel %d muted, skipping", c);
+#endif
continue;
+ }
+
+ bool dontRemap = channel._dontRemap || song->playBed;
#ifdef DEBUG_REMAP
- debug(" Channel %d: prio %d, %d voice%s%s", c, channel._prio, channel._voices, channel._voices == 1 ? "" : "s", channel._dontRemap ? ", dontRemap" : "" );
+ debug(" Channel %d: prio %d, %d voice%s%s", c, channel._prio, channel._voices, channel._voices == 1 ? "" : "s", dontRemap ? ", dontRemap" : "" );
#endif
DeviceChannelUsage dc = { song, c };
@@ -1132,7 +1178,7 @@ ChannelRemapping *SciMusic::determineChannelMap() {
// our target
int devChannel = -1;
- if (channel._dontRemap && map->_map[c]._song == 0) {
+ if (dontRemap && map->_map[c]._song == 0) {
// unremappable channel, with channel still free
devChannel = c;
}
@@ -1192,8 +1238,12 @@ ChannelRemapping *SciMusic::determineChannelMap() {
int neededVoices = channel._voices;
// do we have enough free voices?
if (map->_freeVoices < neededVoices) {
- // We only care for essential channels
- if (prio > 0) {
+ // We only care for essential channels.
+ // Note: In early SCI1 interpreters, a song started by 'playBed'
+ // would not be skipped even if some channels couldn't be
+ // mapped due to voice limits. So, we treat all channels as
+ // non-essential here for playBed songs.
+ if (prio > 0 || (song->playBed && _soundVersion <= SCI_VERSION_1_EARLY)) {
#ifdef DEBUG_REMAP
debug(" not enough voices; need %d, have %d. Skipping this channel.", neededVoices, map->_freeVoices);
#endif
@@ -1229,10 +1279,10 @@ ChannelRemapping *SciMusic::determineChannelMap() {
map->_map[devChannel] = dc;
map->_voices[devChannel] = neededVoices;
map->_prio[devChannel] = prio;
- map->_dontRemap[devChannel] = channel._dontRemap;
+ map->_dontRemap[devChannel] = dontRemap;
map->_freeVoices -= neededVoices;
- if (!channel._dontRemap || devChannel == c) {
+ if (!dontRemap || devChannel == c) {
// If this channel fits here, we're done.
#ifdef DEBUG_REMAP
debug(" OK");
@@ -1285,14 +1335,18 @@ ChannelRemapping *SciMusic::determineChannelMap() {
return map;
}
-void SciMusic::resetDeviceChannel(int devChannel) {
- // NB: This function should only be called from the main thread
-
+void SciMusic::resetDeviceChannel(int devChannel, bool mainThread) {
assert(devChannel >= 0 && devChannel <= 0x0F);
- putMidiCommandInQueue(0x0040B0 | devChannel); // sustain off
- putMidiCommandInQueue(0x007BB0 | devChannel); // notes off
- putMidiCommandInQueue(0x004BB0 | devChannel); // release voices
+ if (mainThread) {
+ putMidiCommandInQueue(0x0040B0 | devChannel); // sustain off
+ putMidiCommandInQueue(0x007BB0 | devChannel); // notes off
+ putMidiCommandInQueue(0x004BB0 | devChannel); // release voices
+ } else {
+ _pMidiDrv->send(0x0040B0 | devChannel); // sustain off
+ _pMidiDrv->send(0x007BB0 | devChannel); // notes off
+ _pMidiDrv->send(0x004BB0 | devChannel); // release voices
+ }
}
diff --git a/engines/sci/sound/music.h b/engines/sci/sound/music.h
index 6149bb799e..a610f32d89 100644
--- a/engines/sci/sound/music.h
+++ b/engines/sci/sound/music.h
@@ -75,6 +75,8 @@ public:
SoundResource *soundRes;
uint16 resourceId;
+ int time; // "tim"estamp to indicate in which order songs have been added
+
bool isQueued; // for SCI0 only!
uint16 dataInc;
@@ -85,6 +87,8 @@ public:
int16 volume;
int16 hold;
int8 reverb;
+ bool playBed;
+ bool overridePriority; // Use soundObj's priority instead of resource's
int16 pauseCounter;
uint sampleLoopCounter;
@@ -224,6 +228,8 @@ public:
byte getCurrentReverb();
+ void needsRemap() { _needsRemap = true; }
+
virtual void saveLoadWithSerializer(Common::Serializer &ser);
// Mutex for music code. Used to guard access to the song playlist, to the
@@ -245,9 +251,9 @@ protected:
bool _useDigitalSFX;
// remapping:
- void remapChannels();
+ void remapChannels(bool mainThread = true);
ChannelRemapping *determineChannelMap();
- void resetDeviceChannel(int devChannel);
+ void resetDeviceChannel(int devChannel, bool mainThread);
private:
MusicList _playList;
@@ -256,6 +262,7 @@ private:
MusicEntry *_usedChannel[16];
int8 _channelRemap[16];
int8 _globalReverb;
+ bool _needsRemap;
DeviceChannelUsage _channelMap[16];
@@ -264,8 +271,11 @@ private:
int _driverFirstChannel;
int _driverLastChannel;
-
+
MusicEntry *_currentlyPlayingSample;
+
+ int _timeCounter; // Used to keep track of the order in which MusicEntries
+ // are added, for priority purposes.
};
} // End of namespace Sci
diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp
index 73e0a23a6a..ee5903fda2 100644
--- a/engines/sci/sound/soundcmd.cpp
+++ b/engines/sci/sound/soundcmd.cpp
@@ -142,11 +142,14 @@ void SoundCommandParser::processInitSound(reg_t obj) {
reg_t SoundCommandParser::kDoSoundPlay(int argc, reg_t *argv, reg_t acc) {
debugC(kDebugLevelSound, "kDoSound(play): %04x:%04x", PRINT_REG(argv[0]));
- processPlaySound(argv[0]);
+ bool playBed = false;
+ if (argc >= 2 && !argv[1].isNull())
+ playBed = true;
+ processPlaySound(argv[0], playBed);
return acc;
}
-void SoundCommandParser::processPlaySound(reg_t obj) {
+void SoundCommandParser::processPlaySound(reg_t obj, bool playBed) {
MusicEntry *musicSlot = _music->getSlot(obj);
if (!musicSlot) {
warning("kDoSound(play): Slot not found (%04x:%04x), initializing it manually", PRINT_REG(obj));
@@ -181,15 +184,26 @@ void SoundCommandParser::processPlaySound(reg_t obj) {
}
musicSlot->loop = readSelectorValue(_segMan, obj, SELECTOR(loop));
- musicSlot->priority = readSelectorValue(_segMan, obj, SELECTOR(priority));
+
+ // Get song priority from either obj or soundRes
+ byte resourcePriority = 0xFF;
+ if (musicSlot->soundRes)
+ resourcePriority = musicSlot->soundRes->getSoundPriority();
+ if (!musicSlot->overridePriority && resourcePriority != 0xFF) {
+ musicSlot->priority = resourcePriority;
+ } else {
+ musicSlot->priority = readSelectorValue(_segMan, obj, SELECTOR(priority));
+ }
+
// Reset hold when starting a new song. kDoSoundSetHold is always called after
// kDoSoundPlay to set it properly, if needed. Fixes bug #3413589.
musicSlot->hold = -1;
+ musicSlot->playBed = playBed;
if (_soundVersion >= SCI_VERSION_1_EARLY)
musicSlot->volume = readSelectorValue(_segMan, obj, SELECTOR(vol));
- debugC(kDebugLevelSound, "kDoSound(play): %04x:%04x number %d, loop %d, prio %d, vol %d", PRINT_REG(obj),
- resourceId, musicSlot->loop, musicSlot->priority, musicSlot->volume);
+ debugC(kDebugLevelSound, "kDoSound(play): %04x:%04x number %d, loop %d, prio %d, vol %d, bed %d", PRINT_REG(obj),
+ resourceId, musicSlot->loop, musicSlot->priority, musicSlot->volume, playBed ? 1 : 0);
_music->soundPlay(musicSlot);
@@ -538,7 +552,7 @@ void SoundCommandParser::processUpdateCues(reg_t obj) {
if (_soundVersion >= SCI_VERSION_1_EARLY) {
writeSelectorValue(_segMan, obj, SELECTOR(min), musicSlot->ticker / 3600);
writeSelectorValue(_segMan, obj, SELECTOR(sec), musicSlot->ticker % 3600 / 60);
- writeSelectorValue(_segMan, obj, SELECTOR(frame), musicSlot->ticker);
+ writeSelectorValue(_segMan, obj, SELECTOR(frame), musicSlot->ticker % 60 / 2);
}
}
@@ -673,23 +687,19 @@ reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc)
}
if (value == -1) {
- uint16 resourceId = musicSlot->resourceId;
+ musicSlot->overridePriority = false;
+ musicSlot->priority = 0;
- // Set priority from the song data
- Resource *song = _resMan->findResource(ResourceId(kResourceTypeSound, resourceId), 0);
- if (song->data[0] == 0xf0)
- _music->soundSetPriority(musicSlot, song->data[1]);
- else
- warning("kDoSound(setPriority): Attempt to unset song priority when there is no built-in value");
+ // NB: It seems SSCI doesn't actually reset the priority here.
- //pSnd->prio=0;field_15B=0
writeSelectorValue(_segMan, obj, SELECTOR(flags), readSelectorValue(_segMan, obj, SELECTOR(flags)) & 0xFD);
} else {
// Scripted priority
+ musicSlot->overridePriority = true;
- //pSnd->field_15B=1;
writeSelectorValue(_segMan, obj, SELECTOR(flags), readSelectorValue(_segMan, obj, SELECTOR(flags)) | 2);
- //DoSOund(0xF,hobj,w)
+
+ _music->soundSetPriority(musicSlot, value);
}
return acc;
}
@@ -777,6 +787,8 @@ void SoundCommandParser::stopAllSounds() {
}
void SoundCommandParser::startNewSound(int number) {
+ // NB: This is only used by the debugging console.
+
Common::StackLock lock(_music->_mutex);
// Overwrite the first sound in the playlist
@@ -785,7 +797,7 @@ void SoundCommandParser::startNewSound(int number) {
processDisposeSound(soundObj);
writeSelectorValue(_segMan, soundObj, SELECTOR(number), number);
processInitSound(soundObj);
- processPlaySound(soundObj);
+ processPlaySound(soundObj, false);
}
void SoundCommandParser::setMasterVolume(int vol) {
diff --git a/engines/sci/sound/soundcmd.h b/engines/sci/sound/soundcmd.h
index 4effda68e4..5bb7cf2cb1 100644
--- a/engines/sci/sound/soundcmd.h
+++ b/engines/sci/sound/soundcmd.h
@@ -63,7 +63,7 @@ public:
void printPlayList(Console *con);
void printSongInfo(reg_t obj, Console *con);
- void processPlaySound(reg_t obj);
+ void processPlaySound(reg_t obj, bool playBed);
void processStopSound(reg_t obj, bool sampleFinishedPlaying);
void initSoundResource(MusicEntry *newSound);