aboutsummaryrefslogtreecommitdiff
path: root/engines/sci
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci')
-rw-r--r--engines/sci/POTFILES1
-rw-r--r--engines/sci/configure.engine2
-rw-r--r--engines/sci/console.cpp503
-rw-r--r--engines/sci/console.h15
-rw-r--r--engines/sci/debug.h8
-rw-r--r--engines/sci/decompressor.cpp2
-rw-r--r--engines/sci/detection.cpp75
-rw-r--r--engines/sci/detection_tables.h724
-rw-r--r--engines/sci/engine/features.cpp47
-rw-r--r--engines/sci/engine/features.h64
-rw-r--r--engines/sci/engine/file.cpp357
-rw-r--r--engines/sci/engine/file.h72
-rw-r--r--engines/sci/engine/gc.cpp31
-rw-r--r--engines/sci/engine/kernel.cpp31
-rw-r--r--engines/sci/engine/kernel.h180
-rw-r--r--engines/sci/engine/kernel_tables.h621
-rw-r--r--engines/sci/engine/kevent.cpp133
-rw-r--r--engines/sci/engine/kfile.cpp784
-rw-r--r--engines/sci/engine/kgraphics.cpp34
-rw-r--r--engines/sci/engine/kgraphics32.cpp1343
-rw-r--r--engines/sci/engine/klists.cpp454
-rw-r--r--engines/sci/engine/kmisc.cpp147
-rw-r--r--engines/sci/engine/kpathing.cpp18
-rw-r--r--engines/sci/engine/kscripts.cpp7
-rw-r--r--engines/sci/engine/ksound.cpp238
-rw-r--r--engines/sci/engine/kstring.cpp390
-rw-r--r--engines/sci/engine/kvideo.cpp416
-rw-r--r--engines/sci/engine/message.cpp29
-rw-r--r--engines/sci/engine/message.h2
-rw-r--r--engines/sci/engine/object.cpp9
-rw-r--r--engines/sci/engine/object.h7
-rw-r--r--engines/sci/engine/savegame.cpp438
-rw-r--r--engines/sci/engine/savegame.h14
-rw-r--r--engines/sci/engine/script.cpp61
-rw-r--r--engines/sci/engine/script.h4
-rw-r--r--engines/sci/engine/script_patches.cpp1255
-rw-r--r--engines/sci/engine/script_patches.h12
-rw-r--r--engines/sci/engine/scriptdebug.cpp113
-rw-r--r--engines/sci/engine/seg_manager.cpp135
-rw-r--r--engines/sci/engine/seg_manager.h21
-rw-r--r--engines/sci/engine/segment.cpp84
-rw-r--r--engines/sci/engine/segment.h853
-rw-r--r--engines/sci/engine/selector.cpp56
-rw-r--r--engines/sci/engine/selector.h17
-rw-r--r--engines/sci/engine/state.cpp64
-rw-r--r--engines/sci/engine/state.h32
-rw-r--r--engines/sci/engine/vm.cpp178
-rw-r--r--engines/sci/engine/vm.h42
-rw-r--r--engines/sci/engine/vm_types.cpp31
-rw-r--r--engines/sci/engine/vm_types.h4
-rw-r--r--engines/sci/engine/workarounds.cpp162
-rw-r--r--engines/sci/engine/workarounds.h20
-rw-r--r--engines/sci/event.cpp113
-rw-r--r--engines/sci/event.h34
-rw-r--r--engines/sci/graphics/animate.cpp4
-rw-r--r--engines/sci/graphics/cache.cpp14
-rw-r--r--engines/sci/graphics/cache.h2
-rw-r--r--engines/sci/graphics/celobj32.cpp790
-rw-r--r--engines/sci/graphics/celobj32.h101
-rw-r--r--engines/sci/graphics/compare.cpp74
-rw-r--r--engines/sci/graphics/compare.h7
-rw-r--r--engines/sci/graphics/controls32.cpp910
-rw-r--r--engines/sci/graphics/controls32.h466
-rw-r--r--engines/sci/graphics/coordadjuster.cpp52
-rw-r--r--engines/sci/graphics/coordadjuster.h48
-rw-r--r--engines/sci/graphics/cursor.cpp46
-rw-r--r--engines/sci/graphics/cursor.h11
-rw-r--r--engines/sci/graphics/cursor32.cpp449
-rw-r--r--engines/sci/graphics/cursor32.h255
-rw-r--r--engines/sci/graphics/font.h2
-rw-r--r--engines/sci/graphics/frameout.cpp2120
-rw-r--r--engines/sci/graphics/frameout.h413
-rw-r--r--engines/sci/graphics/helpers.h45
-rw-r--r--engines/sci/graphics/lists32.h13
-rw-r--r--engines/sci/graphics/paint16.cpp8
-rw-r--r--engines/sci/graphics/paint16.h8
-rw-r--r--engines/sci/graphics/paint32.cpp175
-rw-r--r--engines/sci/graphics/paint32.h44
-rw-r--r--engines/sci/graphics/palette.cpp88
-rw-r--r--engines/sci/graphics/palette.h31
-rw-r--r--engines/sci/graphics/palette32.cpp792
-rw-r--r--engines/sci/graphics/palette32.h532
-rw-r--r--engines/sci/graphics/picture.cpp32
-rw-r--r--engines/sci/graphics/picture.h6
-rw-r--r--engines/sci/graphics/plane32.cpp730
-rw-r--r--engines/sci/graphics/plane32.h270
-rw-r--r--engines/sci/graphics/remap.cpp99
-rw-r--r--engines/sci/graphics/remap.h (renamed from engines/sci/graphics/paint.h)44
-rw-r--r--engines/sci/graphics/remap32.cpp469
-rw-r--r--engines/sci/graphics/remap32.h405
-rw-r--r--engines/sci/graphics/screen.cpp46
-rw-r--r--engines/sci/graphics/screen.h2
-rw-r--r--engines/sci/graphics/screen_item32.cpp476
-rw-r--r--engines/sci/graphics/screen_item32.h128
-rw-r--r--engines/sci/graphics/text16.cpp8
-rw-r--r--engines/sci/graphics/text16.h2
-rw-r--r--engines/sci/graphics/text32.cpp976
-rw-r--r--engines/sci/graphics/text32.h191
-rw-r--r--engines/sci/graphics/transitions32.cpp1040
-rw-r--r--engines/sci/graphics/transitions32.h488
-rw-r--r--engines/sci/graphics/video32.cpp893
-rw-r--r--engines/sci/graphics/video32.h552
-rw-r--r--engines/sci/graphics/view.cpp44
-rw-r--r--engines/sci/graphics/view.h5
-rw-r--r--engines/sci/module.mk9
-rw-r--r--engines/sci/parser/vocabulary.cpp16
-rw-r--r--engines/sci/parser/vocabulary.h2
-rw-r--r--engines/sci/resource.cpp185
-rw-r--r--engines/sci/resource.h49
-rw-r--r--engines/sci/resource_audio.cpp67
-rw-r--r--engines/sci/resource_intern.h5
-rw-r--r--engines/sci/sci.cpp377
-rw-r--r--engines/sci/sci.h58
-rw-r--r--engines/sci/sound/audio.cpp54
-rw-r--r--engines/sci/sound/audio.h13
-rw-r--r--engines/sci/sound/audio32.cpp1107
-rw-r--r--engines/sci/sound/audio32.h591
-rw-r--r--engines/sci/sound/decoders/sol.cpp286
-rw-r--r--engines/sci/sound/decoders/sol.h89
-rw-r--r--engines/sci/sound/drivers/amigamac.cpp16
-rw-r--r--engines/sci/sound/drivers/midi.cpp6
-rw-r--r--engines/sci/sound/music.cpp77
-rw-r--r--engines/sci/sound/music.h10
-rw-r--r--engines/sci/sound/soundcmd.cpp120
-rw-r--r--engines/sci/sound/sync.cpp76
-rw-r--r--engines/sci/sound/sync.h (renamed from engines/sci/graphics/paint.cpp)41
-rw-r--r--engines/sci/video/robot_decoder.cpp1806
-rw-r--r--engines/sci/video/robot_decoder.h1459
128 files changed, 23647 insertions, 7765 deletions
diff --git a/engines/sci/POTFILES b/engines/sci/POTFILES
index f076c292d9..22af1e0703 100644
--- a/engines/sci/POTFILES
+++ b/engines/sci/POTFILES
@@ -1,2 +1,3 @@
engines/sci/detection.cpp
engines/sci/engine/kfile.cpp
+engines/sci/graphics/controls32.cpp
diff --git a/engines/sci/configure.engine b/engines/sci/configure.engine
index d1c45a4654..f8a519002e 100644
--- a/engines/sci/configure.engine
+++ b/engines/sci/configure.engine
@@ -1,4 +1,4 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
add_engine sci "SCI" yes "sci32" "SCI 0-1.1 games"
-add_engine sci32 "SCI32 games" no
+add_engine sci32 "SCI32 games" no "" "" "highres"
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index cbc6dfaf74..f1bb8d3e7f 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -41,9 +41,7 @@
#include "sci/graphics/cache.h"
#include "sci/graphics/cursor.h"
#include "sci/graphics/screen.h"
-#include "sci/graphics/paint.h"
#include "sci/graphics/paint16.h"
-#include "sci/graphics/paint32.h"
#include "sci/graphics/palette.h"
#include "sci/graphics/ports.h"
#include "sci/graphics/view.h"
@@ -54,8 +52,9 @@
#include "sci/video/seq_decoder.h"
#ifdef ENABLE_SCI32
#include "sci/graphics/frameout.h"
+#include "sci/graphics/paint32.h"
+#include "sci/graphics/palette32.h"
#include "video/coktel_decoder.h"
-#include "sci/video/robot_decoder.h"
#endif
#include "common/file.h"
@@ -141,6 +140,8 @@ Console::Console(SciEngine *engine) : GUI::Debugger(),
registerCmd("vpl", WRAP_METHOD(Console, cmdVisiblePlaneList)); // alias
registerCmd("plane_items", WRAP_METHOD(Console, cmdPlaneItemList));
registerCmd("pi", WRAP_METHOD(Console, cmdPlaneItemList)); // alias
+ registerCmd("visible_plane_items", WRAP_METHOD(Console, cmdVisiblePlaneItemList));
+ registerCmd("vpi", WRAP_METHOD(Console, cmdVisiblePlaneItemList)); // alias
registerCmd("saved_bits", WRAP_METHOD(Console, cmdSavedBits));
registerCmd("show_saved_bits", WRAP_METHOD(Console, cmdShowSavedBits));
// Segments
@@ -192,6 +193,7 @@ Console::Console(SciEngine *engine) : GUI::Debugger(),
registerCmd("send", WRAP_METHOD(Console, cmdSend));
registerCmd("go", WRAP_METHOD(Console, cmdGo));
registerCmd("logkernel", WRAP_METHOD(Console, cmdLogKernel));
+ registerCmd("vocab994", WRAP_METHOD(Console, cmdMapVocab994));
// Breakpoints
registerCmd("bp_list", WRAP_METHOD(Console, cmdBreakpointList));
registerCmd("bplist", WRAP_METHOD(Console, cmdBreakpointList)); // alias
@@ -199,6 +201,8 @@ Console::Console(SciEngine *engine) : GUI::Debugger(),
registerCmd("bp_del", WRAP_METHOD(Console, cmdBreakpointDelete));
registerCmd("bpdel", WRAP_METHOD(Console, cmdBreakpointDelete)); // alias
registerCmd("bc", WRAP_METHOD(Console, cmdBreakpointDelete)); // alias
+ registerCmd("bp_address", WRAP_METHOD(Console, cmdBreakpointAddress));
+ registerCmd("bpa", WRAP_METHOD(Console, cmdBreakpointAddress)); // alias
registerCmd("bp_method", WRAP_METHOD(Console, cmdBreakpointMethod));
registerCmd("bpx", WRAP_METHOD(Console, cmdBreakpointMethod)); // alias
registerCmd("bp_read", WRAP_METHOD(Console, cmdBreakpointRead));
@@ -227,6 +231,8 @@ Console::Console(SciEngine *engine) : GUI::Debugger(),
registerCmd("view_listnode", WRAP_METHOD(Console, cmdViewListNode));
registerCmd("view_reference", WRAP_METHOD(Console, cmdViewReference));
registerCmd("vr", WRAP_METHOD(Console, cmdViewReference)); // alias
+ registerCmd("dump_reference", WRAP_METHOD(Console, cmdDumpReference));
+ registerCmd("dr", WRAP_METHOD(Console, cmdDumpReference)); // alias
registerCmd("view_object", WRAP_METHOD(Console, cmdViewObject));
registerCmd("vo", WRAP_METHOD(Console, cmdViewObject)); // alias
registerCmd("active_object", WRAP_METHOD(Console, cmdViewActiveObject));
@@ -264,8 +270,6 @@ void Console::postEnter() {
#ifdef ENABLE_SCI32
} else if (_videoFile.hasSuffix(".vmd")) {
videoDecoder = new Video::AdvancedVMDDecoder();
- } else if (_videoFile.hasSuffix(".rbt")) {
- videoDecoder = new RobotDecoder(_engine->getPlatform() == Common::kPlatformMacintosh);
} else if (_videoFile.hasSuffix(".duk")) {
duckMode = true;
videoDecoder = new Video::AVIDecoder();
@@ -385,6 +389,7 @@ bool Console::cmdHelp(int argc, const char **argv) {
debugPrintf(" plane_list / pl - Shows a list of all the planes in the draw list (SCI2+)\n");
debugPrintf(" visible_plane_list / vpl - Shows a list of all the planes in the visible draw list (SCI2+)\n");
debugPrintf(" plane_items / pi - Shows a list of all items for a plane (SCI2+)\n");
+ debugPrintf(" visible_plane_items / vpi - Shows a list of all items for a plane in the visible draw list (SCI2+)\n");
debugPrintf(" saved_bits - List saved bits on the hunk\n");
debugPrintf(" show_saved_bits - Display saved bits\n");
debugPrintf("\n");
@@ -432,6 +437,7 @@ bool Console::cmdHelp(int argc, const char **argv) {
debugPrintf("Breakpoints:\n");
debugPrintf(" bp_list / bplist / bl - Lists the current breakpoints\n");
debugPrintf(" bp_del / bpdel / bc - Deletes a breakpoint with the specified index\n");
+ debugPrintf(" bp_address / bpa - Sets a breakpoint on a script address\n");
debugPrintf(" bp_method / bpx - Sets a breakpoint on the execution of a specified method/selector\n");
debugPrintf(" bp_read / bpr - Sets a breakpoint on reading of a specified selector\n");
debugPrintf(" bp_write / bpw - Sets a breakpoint on writing to a specified selector\n");
@@ -446,6 +452,7 @@ bool Console::cmdHelp(int argc, const char **argv) {
debugPrintf(" value_type - Determines the type of a value\n");
debugPrintf(" view_listnode - Examines the list node at the given address\n");
debugPrintf(" view_reference / vr - Examines an arbitrary reference\n");
+ debugPrintf(" dump_reference / dr - Dumps an arbitrary reference to disk\n");
debugPrintf(" view_object / vo - Examines the object at the given address\n");
debugPrintf(" active_object - Shows information on the currently active object or class\n");
debugPrintf(" acc_object - Shows information on the object or class at the address indexed by the accumulator\n");
@@ -486,6 +493,7 @@ bool Console::cmdGetVersion(int argc, const char **argv) {
debugPrintf("Lofs type: %s\n", getSciVersionDesc(_engine->_features->detectLofsType()));
debugPrintf("Move count type: %s\n", (_engine->_features->handleMoveCount()) ? "increment" : "ignore");
debugPrintf("SetCursor type: %s\n", getSciVersionDesc(_engine->_features->detectSetCursorType()));
+ debugPrintf("PseudoMouse ability: %s\n", _engine->_features->detectPseudoMouseAbility() == kPseudoMouseAbilityTrue ? "yes" : "no");
#ifdef ENABLE_SCI32
if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE))
debugPrintf("SCI2.1 kernel table: %s\n", (_engine->_features->detectSci21KernelType() == SCI_VERSION_2) ? "modified SCI2 (old)" : "SCI2.1 (new)");
@@ -494,8 +502,10 @@ bool Console::cmdGetVersion(int argc, const char **argv) {
if (getSciVersion() <= SCI_VERSION_1_1) {
debugPrintf("kAnimate fastCast enabled: %s\n", g_sci->_gfxAnimate->isFastCastEnabled() ? "yes" : "no");
}
- debugPrintf("Uses palette merging: %s\n", g_sci->_gfxPalette16->isMerging() ? "yes" : "no");
- debugPrintf("Uses 16 bit color matching: %s\n", g_sci->_gfxPalette16->isUsing16bitColorMatch() ? "yes" : "no");
+ if (getSciVersion() < SCI_VERSION_2) {
+ debugPrintf("Uses palette merging: %s\n", g_sci->_gfxPalette16->isMerging() ? "yes" : "no");
+ debugPrintf("Uses 16 bit color matching: %s\n", g_sci->_gfxPalette16->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");
@@ -672,7 +682,7 @@ bool Console::cmdRegisters(int argc, const char **argv) {
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");
@@ -757,7 +767,7 @@ void Console::cmdDiskDumpWorker(ResourceType resourceType, int resourceNumber, u
ResourceId resourceId;
Resource *resource = NULL;
char outFileName[50];
-
+
switch (resourceType) {
case kResourceTypeAudio36:
case kResourceTypeSync36: {
@@ -874,7 +884,7 @@ bool Console::cmdList(int argc, const char **argv) {
currentMap = map;
displayCount = 0;
}
-
+
if (displayCount % 3 == 0)
debugPrintf(" ");
@@ -1001,7 +1011,7 @@ bool Console::cmdHexgrep(int argc, const char **argv) {
for (; resNumber <= resMax; resNumber++) {
script = _engine->getResMan()->findResource(ResourceId(restype, resNumber), 0);
if (script) {
- unsigned int seeker = 0, seekerold = 0;
+ uint32 seeker = 0, seekerold = 0;
uint32 comppos = 0;
int output_script_name = 0;
@@ -1507,7 +1517,7 @@ bool Console::cmdSaid(int argc, const char **argv) {
}
// TODO: Maybe turn this into a proper said spec compiler
- unsigned int len = 0;
+ uint32 len = 0;
for (p++; p < argc; p++) {
if (strcmp(argv[p], ",") == 0) {
spec[len++] = 0xf0;
@@ -1544,7 +1554,7 @@ bool Console::cmdSaid(int argc, const char **argv) {
spec[len++] = 0xfe;
spec[len++] = 0xf6;
} else {
- unsigned int s = strtol(argv[p], 0, 16);
+ uint32 s = strtol(argv[p], 0, 16);
if (s >= 0xf0 && s <= 0xff) {
spec[len++] = s;
} else {
@@ -1617,7 +1627,7 @@ bool Console::cmdParserNodes(int argc, const char **argv) {
bool Console::cmdSetPalette(int argc, const char **argv) {
if (argc < 2) {
- debugPrintf("Sets a palette resource\n");
+ debugPrintf("Sets a palette resource (SCI16)\n");
debugPrintf("Usage: %s <resourceId>\n", argv[0]);
debugPrintf("where <resourceId> is the number of the palette resource to set\n");
return true;
@@ -1625,6 +1635,13 @@ bool Console::cmdSetPalette(int argc, const char **argv) {
uint16 resourceId = atoi(argv[1]);
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ debugPrintf("This SCI version does not support this command\n");
+ return true;
+ }
+#endif
+
_engine->_gfxPalette16->kernelSetFromResource(resourceId, true);
return true;
}
@@ -1644,7 +1661,7 @@ bool Console::cmdDrawPic(int argc, const char **argv) {
#endif
uint16 resourceId = atoi(argv[1]);
- _engine->_gfxPaint->kernelDrawPicture(resourceId, 100, false, false, false, 0);
+ _engine->_gfxPaint16->kernelDrawPicture(resourceId, 100, false, false, false, 0);
_engine->_gfxScreen->copyToScreen();
_engine->sleep(2000);
@@ -1816,6 +1833,34 @@ bool Console::cmdPlaneItemList(int argc, const char **argv) {
return true;
}
+bool Console::cmdVisiblePlaneItemList(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Shows the list of items for a plane\n");
+ debugPrintf("Usage: %s <plane address>\n", argv[0]);
+ return true;
+ }
+
+ reg_t planeObject = NULL_REG;
+
+ if (parse_reg_t(_engine->_gamestate, argv[1], &planeObject, false)) {
+ debugPrintf("Invalid address passed.\n");
+ debugPrintf("Check the \"addresses\" command on how to use addresses\n");
+ return true;
+ }
+
+#ifdef ENABLE_SCI32
+ if (_engine->_gfxFrameout) {
+ debugPrintf("Visible plane item list:\n");
+ _engine->_gfxFrameout->printVisiblePlaneItemList(this, planeObject);
+ } else {
+ debugPrintf("This SCI version does not have a list of plane items\n");
+ }
+#else
+ debugPrintf("SCI32 isn't included in this compiled executable\n");
+#endif
+ return true;
+}
+
bool Console::cmdSavedBits(int argc, const char **argv) {
SegManager *segman = _engine->_gamestate->_segMan;
SegmentId id = segman->findSegmentByType(SEG_TYPE_HUNK);
@@ -1829,7 +1874,7 @@ bool Console::cmdSavedBits(int argc, const char **argv) {
for (uint i = 0; i < entries.size(); ++i) {
uint16 offset = entries[i].getOffset();
- const Hunk& h = hunks->_table[offset];
+ const Hunk& h = hunks->at(offset);
if (strcmp(h.type, "SaveBits()") == 0) {
byte* memoryPtr = (byte *)h.mem;
@@ -1896,7 +1941,7 @@ bool Console::cmdShowSavedBits(int argc, const char **argv) {
return true;
}
- const Hunk& h = hunks->_table[memoryHandle.getOffset()];
+ const Hunk& h = hunks->at(memoryHandle.getOffset());
if (strcmp(h.type, "SaveBits()") != 0) {
debugPrintf("Invalid address.\n");
@@ -2037,8 +2082,8 @@ bool Console::cmdPrintSegmentTable(int argc, const char **argv) {
debugPrintf("A SCI32 arrays (%d)", (*(ArrayTable *)mobj).entries_used);
break;
- case SEG_TYPE_STRING:
- debugPrintf("T SCI32 strings (%d)", (*(StringTable *)mobj).entries_used);
+ case SEG_TYPE_BITMAP:
+ debugPrintf("T SCI32 bitmaps (%d)", (*(BitmapTable *)mobj).entries_used);
break;
#endif
@@ -2112,32 +2157,32 @@ bool Console::segmentInfo(int nr) {
break;
case SEG_TYPE_CLONES: {
- CloneTable *ct = (CloneTable *)mobj;
+ CloneTable &ct = *(CloneTable *)mobj;
debugPrintf("clones\n");
- for (uint i = 0; i < ct->_table.size(); i++)
- if (ct->isValidEntry(i)) {
+ for (uint i = 0; i < ct.size(); i++)
+ if (ct.isValidEntry(i)) {
reg_t objpos = make_reg(nr, i);
debugPrintf(" [%04x] %s; copy of ", i, _engine->_gamestate->_segMan->getObjectName(objpos));
// Object header
- const Object *obj = _engine->_gamestate->_segMan->getObject(ct->_table[i].getPos());
+ const Object *obj = _engine->_gamestate->_segMan->getObject(ct[i].getPos());
if (obj)
- debugPrintf("[%04x:%04x] %s : %3d vars, %3d methods\n", PRINT_REG(ct->_table[i].getPos()),
- _engine->_gamestate->_segMan->getObjectName(ct->_table[i].getPos()),
+ debugPrintf("[%04x:%04x] %s : %3d vars, %3d methods\n", PRINT_REG(ct[i].getPos()),
+ _engine->_gamestate->_segMan->getObjectName(ct[i].getPos()),
obj->getVarCount(), obj->getMethodCount());
}
}
break;
case SEG_TYPE_LISTS: {
- ListTable *lt = (ListTable *)mobj;
+ ListTable &lt = *(ListTable *)mobj;
debugPrintf("lists\n");
- for (uint i = 0; i < lt->_table.size(); i++)
- if (lt->isValidEntry(i)) {
+ for (uint i = 0; i < lt.size(); i++)
+ if (lt.isValidEntry(i)) {
debugPrintf(" [%04x]: ", i);
- printList(&(lt->_table[i]));
+ printList(lt[i]);
}
}
break;
@@ -2148,13 +2193,13 @@ bool Console::segmentInfo(int nr) {
}
case SEG_TYPE_HUNK: {
- HunkTable *ht = (HunkTable *)mobj;
+ HunkTable &ht = *(HunkTable *)mobj;
- debugPrintf("hunk (total %d)\n", ht->entries_used);
- for (uint i = 0; i < ht->_table.size(); i++)
- if (ht->isValidEntry(i)) {
+ debugPrintf("hunk (total %d)\n", ht.entries_used);
+ for (uint i = 0; i < ht.size(); i++)
+ if (ht.isValidEntry(i)) {
debugPrintf(" [%04x] %d bytes at %p, type=%s\n",
- i, ht->_table[i].size, ht->_table[i].mem, ht->_table[i].type);
+ i, ht[i].size, ht[i].mem, ht[i].type);
}
}
break;
@@ -2168,12 +2213,27 @@ bool Console::segmentInfo(int nr) {
break;
#ifdef ENABLE_SCI32
- case SEG_TYPE_STRING:
- debugPrintf("SCI32 strings\n");
- break;
- case SEG_TYPE_ARRAY:
+ case SEG_TYPE_ARRAY: {
+ ArrayTable &table = *(ArrayTable *)mobj;
debugPrintf("SCI32 arrays\n");
+ for (uint i = 0; i < table.size(); ++i) {
+ if (table.isValidEntry(i)) {
+ debugPrintf(" [%04x] %s\n", i, table[i].toDebugString().c_str());
+ }
+ }
break;
+ }
+
+ case SEG_TYPE_BITMAP: {
+ BitmapTable &table = *(BitmapTable *)mobj;
+ debugPrintf("SCI32 bitmaps (total %d)\n", table.entries_used);
+ for (uint i = 0; i < table.size(); ++i) {
+ if (table.isValidEntry(i)) {
+ debugPrintf(" [%04x] %s\n", i, table[i].toString().c_str());
+ }
+ }
+ break;
+ }
#endif
default :
@@ -2533,9 +2593,14 @@ bool Console::cmdVMVars(int argc, const char **argv) {
case 1:
case 2:
case 3: {
- // for global, local, temp and param, we need an index
if (argc < 3) {
- debugPrintf("Variable number must be specified for requested type\n");
+ for (int i = 0; i < s->variablesMax[varType]; ++i) {
+ curValue = &s->variables[varType][i];
+ debugPrintf("%s var %d == %04x:%04x", varNames[varType], i, PRINT_REG(*curValue));
+ printBasicVarInfo(*curValue);
+ debugPrintf("\n");
+ }
+
return true;
}
if (argc > 4) {
@@ -2736,16 +2801,8 @@ bool Console::cmdViewReference(int argc, const char **argv) {
switch (type) {
case 0:
break;
- case SIG_TYPE_LIST: {
- List *list = _engine->_gamestate->_segMan->lookupList(reg);
-
- debugPrintf("list\n");
-
- if (list)
- printList(list);
- else
- debugPrintf("Invalid list.\n");
- }
+ case SIG_TYPE_LIST:
+ printList(reg);
break;
case SIG_TYPE_NODE:
debugPrintf("list node\n");
@@ -2758,16 +2815,12 @@ bool Console::cmdViewReference(int argc, const char **argv) {
case SIG_TYPE_REFERENCE: {
switch (_engine->_gamestate->_segMan->getSegmentType(reg.getSegment())) {
#ifdef ENABLE_SCI32
- case SEG_TYPE_STRING: {
- debugPrintf("SCI32 string\n");
- const SciString *str = _engine->_gamestate->_segMan->lookupString(reg);
- Common::hexdump((const byte *) str->getRawData(), str->getSize(), 16, 0);
+ case SEG_TYPE_ARRAY: {
+ printArray(reg);
break;
}
- case SEG_TYPE_ARRAY: {
- debugPrintf("SCI32 array:\n");
- const SciArray<reg_t> *array = _engine->_gamestate->_segMan->lookupArray(reg);
- hexDumpReg(array->getRawData(), array->getSize(), 4, 0, true);
+ case SEG_TYPE_BITMAP: {
+ printBitmap(reg);
break;
}
#endif
@@ -2812,6 +2865,125 @@ bool Console::cmdViewReference(int argc, const char **argv) {
return true;
}
+bool Console::cmdDumpReference(int argc, const char **argv) {
+ if (argc < 2) {
+ debugPrintf("Dumps an arbitrary reference to disk.\n");
+ debugPrintf("Usage: %s <start address> [<end address>]\n", argv[0]);
+ debugPrintf("Where <start address> is the starting address to dump\n");
+ debugPrintf("<end address>, if provided, is the address where the dump ends\n");
+ debugPrintf("Check the \"addresses\" command on how to use addresses\n");
+ return true;
+ }
+
+ reg_t reg = NULL_REG;
+ reg_t reg_end = NULL_REG;
+
+ if (parse_reg_t(_engine->_gamestate, argv[1], &reg, false)) {
+ debugPrintf("Invalid address passed.\n");
+ debugPrintf("Check the \"addresses\" command on how to use addresses\n");
+ return true;
+ }
+
+ if (argc > 2) {
+ if (parse_reg_t(_engine->_gamestate, argv[2], &reg_end, false)) {
+ debugPrintf("Invalid address passed.\n");
+ debugPrintf("Check the \"addresses\" command on how to use addresses\n");
+ return true;
+ }
+ }
+
+ if (reg.getSegment() == 0 && reg.getOffset() == 0) {
+ debugPrintf("Register is null.\n");
+ return true;
+ }
+
+ if (g_sci->getKernel()->findRegType(reg) != SIG_TYPE_REFERENCE) {
+ debugPrintf("%04x:%04x is not a reference\n", PRINT_REG(reg));
+ return true;
+ }
+
+ if (reg_end.getSegment() != reg.getSegment() && reg_end != NULL_REG) {
+ debugPrintf("Ending segment different from starting segment. Assuming no bound on dump.\n");
+ reg_end = NULL_REG;
+ }
+
+ Common::DumpFile out;
+ Common::String outFileName;
+ uint32 bytesWritten;
+
+ switch (_engine->_gamestate->_segMan->getSegmentType(reg.getSegment())) {
+#ifdef ENABLE_SCI32
+ case SEG_TYPE_BITMAP: {
+ outFileName = Common::String::format("%04x_%04x.tga", PRINT_REG(reg));
+ out.open(outFileName);
+ SciBitmap &bitmap = *_engine->_gamestate->_segMan->lookupBitmap(reg);
+ const Color *color = g_sci->_gfxPalette32->getCurrentPalette().colors;
+ const uint16 numColors = ARRAYSIZE(g_sci->_gfxPalette32->getCurrentPalette().colors);
+
+ out.writeByte(0); // image id length
+ out.writeByte(1); // color map type (present)
+ out.writeByte(1); // image type (uncompressed color-mapped)
+ out.writeSint16LE(0); // index of first color map entry
+ out.writeSint16LE(numColors); // number of color map entries
+ out.writeByte(24); // number of bits per color entry (RGB24)
+ out.writeSint16LE(0); // bottom-left x-origin
+ out.writeSint16LE(bitmap.getHeight() - 1); // bottom-left y-origin
+ out.writeSint16LE(bitmap.getWidth()); // width
+ out.writeSint16LE(bitmap.getHeight()); // height
+ out.writeByte(8); // bits per pixel
+ out.writeByte(1 << 5); // origin of pixel data (top-left)
+
+ bytesWritten = 18;
+
+ for (int i = 0; i < numColors; ++i) {
+ out.writeByte(color->b);
+ out.writeByte(color->g);
+ out.writeByte(color->r);
+ ++color;
+ }
+
+ bytesWritten += numColors * 3;
+ bytesWritten += out.write(bitmap.getPixels(), bitmap.getWidth() * bitmap.getHeight());
+ break;
+ }
+#endif
+
+ default: {
+ const SegmentRef block = _engine->_gamestate->_segMan->dereference(reg);
+ uint32 size = block.maxSize;
+
+ if (size == 0) {
+ debugPrintf("Size of reference is zero.\n");
+ return true;
+ }
+
+ if (reg_end.getSegment() != 0 && (size < reg_end.getOffset() - reg.getOffset())) {
+ debugPrintf("Block end out of bounds (size %d). Resetting.\n", size);
+ reg_end = NULL_REG;
+ }
+
+ if (reg_end.getSegment() != 0 && (size >= reg_end.getOffset() - reg.getOffset())) {
+ size = reg_end.getOffset() - reg.getOffset();
+ }
+
+ if (reg_end.getSegment() != 0) {
+ debugPrintf("Block size less than or equal to %d\n", size);
+ }
+
+ outFileName = Common::String::format("%04x_%04x.dmp", PRINT_REG(reg));
+ out.open(outFileName);
+ bytesWritten = out.write(block.raw, size);
+ break;
+ }
+ }
+
+ out.finalize();
+ out.close();
+
+ debugPrintf("Wrote %u bytes to %s\n", bytesWritten, outFileName.c_str());
+ return true;
+}
+
bool Console::cmdViewObject(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Examines the object at the given address.\n");
@@ -2863,7 +3035,7 @@ bool Console::cmdScriptObjects(int argc, const char **argv) {
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;
@@ -2885,7 +3057,7 @@ bool Console::cmdScriptStrings(int argc, const char **argv) {
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;
@@ -2907,7 +3079,7 @@ bool Console::cmdScriptSaid(int argc, const char **argv) {
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;
@@ -3058,7 +3230,10 @@ bool Console::cmdBacktrace(int argc, const char **argv) {
break;
case EXEC_STACK_TYPE_KERNEL: // Kernel function
- debugPrintf(" %x:[%x] k%s(", i, call.debugOrigin, _engine->getKernel()->getKernelName(call.debugSelector).c_str());
+ if (call.debugKernelSubFunction == -1)
+ debugPrintf(" %x:[%x] k%s(", i, call.debugOrigin, _engine->getKernel()->getKernelName(call.debugKernelFunction).c_str());
+ else
+ debugPrintf(" %x:[%x] k%s(", i, call.debugOrigin, _engine->getKernel()->getKernelName(call.debugKernelFunction, call.debugKernelSubFunction).c_str());
break;
case EXEC_STACK_TYPE_VARSELECTOR:
@@ -3237,7 +3412,7 @@ bool Console::cmdDisassemble(int argc, const char **argv) {
farthestTarget = jumpTarget;
}
// TODO: Use a true 32-bit reg_t for the position (addr)
- addr = disassemble(_engine->_gamestate, make_reg32(addr.getSegment(), addr.getOffset()), printBWTag, printBytecode);
+ addr = disassemble(_engine->_gamestate, make_reg32(addr.getSegment(), addr.getOffset()), objAddr, printBWTag, printBytecode);
if (addr.isNull() && prevAddr < farthestTarget)
addr = prevAddr + 1; // skip past the ret
} while (addr.getOffset() > 0);
@@ -3286,7 +3461,7 @@ bool Console::cmdDisassembleAddress(int argc, const char **argv) {
do {
// TODO: Use a true 32-bit reg_t for the position (vpc)
- vpc = disassemble(_engine->_gamestate, make_reg32(vpc.getSegment(), vpc.getOffset()), printBWTag, printBytes);
+ vpc = disassemble(_engine->_gamestate, make_reg32(vpc.getSegment(), vpc.getOffset()), NULL_REG, printBWTag, printBytes);
} while ((vpc.getOffset() > 0) && (vpc.getOffset() + 6 < size) && (--opCount));
return true;
@@ -3581,6 +3756,8 @@ bool Console::cmdBreakpointList(int argc, const char **argv) {
bpdata = bp->address;
debugPrintf("Execute script %d, export %d\n", bpdata >> 16, bpdata & 0xFFFF);
break;
+ case BREAK_ADDRESS:
+ debugPrintf("Execute address %04x:%04x\n", PRINT_REG(bp->regAddress));
}
i++;
@@ -3719,7 +3896,7 @@ bool Console::cmdBreakpointKernel(int argc, const char **argv) {
bool Console::cmdBreakpointFunction(int argc, const char **argv) {
if (argc != 3) {
debugPrintf("Sets a breakpoint on the execution of the specified exported function.\n");
- debugPrintf("Usage: %s <script number> <export number\n", argv[0]);
+ debugPrintf("Usage: %s <script number> <export number>\n", argv[0]);
return true;
}
@@ -3737,6 +3914,31 @@ bool Console::cmdBreakpointFunction(int argc, const char **argv) {
return true;
}
+bool Console::cmdBreakpointAddress(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Sets a breakpoint on the execution of the specified code address.\n");
+ debugPrintf("Usage: %s <address>\n", argv[0]);
+ return true;
+ }
+
+ reg_t addr;
+
+ if (parse_reg_t(_engine->_gamestate, argv[1], &addr, false)) {
+ debugPrintf("Invalid address passed.\n");
+ debugPrintf("Check the \"addresses\" command on how to use addresses\n");
+ return true;
+ }
+
+ Breakpoint bp;
+ bp.type = BREAK_ADDRESS;
+ bp.regAddress = make_reg32(addr.getSegment(), addr.getOffset());
+
+ _debugState._breakpoints.push_back(bp);
+ _debugState._activeBreakpointTypes |= BREAK_ADDRESS;
+
+ return true;
+}
+
bool Console::cmdSfx01Header(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Dumps the header of a SCI01 song\n");
@@ -3923,6 +4125,55 @@ bool Console::cmdSfx01Track(int argc, const char **argv) {
return true;
}
+bool Console::cmdMapVocab994(int argc, const char **argv) {
+ EngineState *s = _engine->_gamestate; // for the several defines in this function
+ reg_t reg;
+
+ if (argc != 4) {
+ debugPrintf("Attempts to map a range of vocab.994 entries to a given class\n");
+ debugPrintf("Usage: %s <class addr> <first> <last>\n", argv[0]);
+ return true;
+ }
+
+ if (parse_reg_t(_engine->_gamestate, argv[1], &reg, false)) {
+ debugPrintf("Invalid address passed.\n");
+ debugPrintf("Check the \"addresses\" command on how to use addresses\n");
+ return true;
+ }
+
+ Resource *resource = _engine->_resMan->findResource(ResourceId(kResourceTypeVocab, 994), 0);
+ const Object *obj = s->_segMan->getObject(reg);
+ uint16 *data = (uint16 *) resource->data;
+ uint32 first = atoi(argv[2]);
+ uint32 last = atoi(argv[3]);
+ Common::Array<bool> markers;
+
+ markers.resize(_engine->getKernel()->getSelectorNamesSize());
+ if (!obj->isClass() && getSciVersion() != SCI_VERSION_3)
+ obj = s->_segMan->getObject(obj->getSuperClassSelector());
+
+ first = MIN(first, (uint32) (resource->size / 2 - 2));
+ last = MIN(last, (uint32) (resource->size / 2 - 2));
+
+ for (uint32 i = first; i <= last; ++i) {
+ uint16 ofs = data[i];
+
+ if (obj && ofs < obj->getVarCount()) {
+ uint16 varSelector = obj->getVarSelector(ofs);
+ debugPrintf("%d: property at index %04x of %s is %s %s\n", i, ofs,
+ s->_segMan->derefString(obj->getNameSelector()),
+ _engine->getKernel()->getSelectorName(varSelector).c_str(),
+ markers[varSelector] ? "(repeat!)" : "");
+ markers[varSelector] = true;
+ }
+ else {
+ debugPrintf("%d: property at index %04x doesn't match up with %s\n", i, ofs,
+ s->_segMan->derefString(obj->getNameSelector()));
+ }
+ }
+
+ return true;
+}
bool Console::cmdQuit(int argc, const char **argv) {
if (argc != 2) {
}
@@ -4265,8 +4516,28 @@ void Console::printBasicVarInfo(reg_t variable) {
debugPrintf(" IS INVALID!");
}
-void Console::printList(List *list) {
- reg_t pos = list->first;
+void Console::printList(reg_t reg) {
+ SegmentObj *mobj = _engine->_gamestate->_segMan->getSegment(reg.getSegment(), SEG_TYPE_LISTS);
+
+ if (!mobj) {
+ debugPrintf("list:\nCould not find list segment.\n");
+ return;
+ }
+
+ ListTable *table = static_cast<ListTable *>(mobj);
+
+ if (!table->isValidEntry(reg.getOffset())) {
+ debugPrintf("list:\nAddress does not contain a valid list.\n");
+ return;
+ }
+
+ const List &list = table->at(reg.getOffset());
+ debugPrintf("list:\n");
+ printList(list);
+}
+
+void Console::printList(const List &list) {
+ reg_t pos = list.first;
reg_t my_prev = NULL_REG;
debugPrintf("\t<\n");
@@ -4276,26 +4547,24 @@ void Console::printList(List *list) {
NodeTable *nt = (NodeTable *)_engine->_gamestate->_segMan->getSegment(pos.getSegment(), SEG_TYPE_NODES);
if (!nt || !nt->isValidEntry(pos.getOffset())) {
- debugPrintf(" WARNING: %04x:%04x: Doesn't contain list node!\n",
- PRINT_REG(pos));
+ debugPrintf(" WARNING: %04x:%04x: Doesn't contain list node!\n", PRINT_REG(pos));
return;
}
- node = &(nt->_table[pos.getOffset()]);
+ node = &nt->at(pos.getOffset());
debugPrintf("\t%04x:%04x : %04x:%04x -> %04x:%04x\n", PRINT_REG(pos), PRINT_REG(node->key), PRINT_REG(node->value));
if (my_prev != node->pred)
- debugPrintf(" WARNING: current node gives %04x:%04x as predecessor!\n",
- PRINT_REG(node->pred));
+ debugPrintf(" WARNING: current node gives %04x:%04x as predecessor!\n", PRINT_REG(node->pred));
my_prev = pos;
pos = node->succ;
}
- if (my_prev != list->last)
+ if (my_prev != list.last)
debugPrintf(" WARNING: Last node was expected to be %04x:%04x, was %04x:%04x!\n",
- PRINT_REG(list->last), PRINT_REG(my_prev));
+ PRINT_REG(list.last), PRINT_REG(my_prev));
debugPrintf("\t>\n");
}
@@ -4311,7 +4580,7 @@ int Console::printNode(reg_t addr) {
return 1;
}
- list = &(lt->_table[addr.getOffset()]);
+ list = &lt->at(addr.getOffset());
debugPrintf("%04x:%04x : first x last = (%04x:%04x, %04x:%04x)\n", PRINT_REG(addr), PRINT_REG(list->first), PRINT_REG(list->last));
} else {
@@ -4330,7 +4599,7 @@ int Console::printNode(reg_t addr) {
debugPrintf("Address does not contain a node\n");
return 1;
}
- node = &(nt->_table[addr.getOffset()]);
+ node = &nt->at(addr.getOffset());
debugPrintf("%04x:%04x : prev x next = (%04x:%04x, %04x:%04x); maps %04x:%04x -> %04x:%04x\n",
PRINT_REG(addr), PRINT_REG(node->pred), PRINT_REG(node->succ), PRINT_REG(node->key), PRINT_REG(node->value));
@@ -4339,6 +4608,83 @@ int Console::printNode(reg_t addr) {
return 0;
}
+#ifdef ENABLE_SCI32
+void Console::printArray(reg_t reg) {
+ SegmentObj *mobj = _engine->_gamestate->_segMan->getSegment(reg.getSegment(), SEG_TYPE_ARRAY);
+
+ if (!mobj) {
+ debugPrintf("SCI32 array:\nCould not find array segment.\n");
+ return;
+ }
+
+ ArrayTable *table = static_cast<ArrayTable *>(mobj);
+
+ if (!table->isValidEntry(reg.getOffset())) {
+ debugPrintf("SCI32 array:\nAddress does not contain a valid array.\n");
+ return;
+ }
+
+ const SciArray &array = table->at(reg.getOffset());
+
+ const char *arrayType;
+ switch (array.getType()) {
+ case kArrayTypeID:
+ arrayType = "reg_t";
+ break;
+ case kArrayTypeByte:
+ arrayType = "byte";
+ break;
+ case kArrayTypeInt16:
+ arrayType = "int16 (as reg_t)";
+ break;
+ case kArrayTypeString:
+ arrayType = "string";
+ break;
+ default:
+ arrayType = "invalid";
+ break;
+ }
+ debugPrintf("SCI32 %s array (%u entries):\n", arrayType, array.size());
+ switch (array.getType()) {
+ case kArrayTypeInt16:
+ case kArrayTypeID: {
+ hexDumpReg((const reg_t *)array.getRawData(), array.size(), 4, 0, true);
+ break;
+ }
+ case kArrayTypeByte:
+ case kArrayTypeString: {
+ Common::hexdump((const byte *)array.getRawData(), array.size(), 16, 0);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void Console::printBitmap(reg_t reg) {
+ SegmentObj *mobj = _engine->_gamestate->_segMan->getSegment(reg.getSegment(), SEG_TYPE_BITMAP);
+
+ if (!mobj) {
+ debugPrintf("SCI32 bitmap:\nCould not find bitmap segment.\n");
+ return;
+ }
+
+ BitmapTable *table = static_cast<BitmapTable *>(mobj);
+
+ if (!table->isValidEntry(reg.getOffset())) {
+ debugPrintf("SCI32 bitmap:\nAddress does not contain a valid bitmap.\n");
+ return;
+ }
+
+ const SciBitmap &bitmap = table->at(reg.getOffset());
+
+ debugPrintf("SCI32 bitmap (%s):\n", bitmap.toString().c_str());
+
+ Common::hexdump((const byte *) bitmap.getRawData(), bitmap.getRawSize(), 16, 0);
+}
+
+#endif
+
int Console::printObject(reg_t pos) {
EngineState *s = _engine->_gamestate; // for the several defines in this function
const Object *obj = s->_segMan->getObject(pos);
@@ -4346,7 +4692,7 @@ int Console::printObject(reg_t pos) {
uint i;
if (!obj) {
- debugPrintf("[%04x:%04x]: Not an object.", PRINT_REG(pos));
+ debugPrintf("[%04x:%04x]: Not an object.\n", PRINT_REG(pos));
return 1;
}
@@ -4361,7 +4707,8 @@ int Console::printObject(reg_t pos) {
debugPrintf(" ");
if (var_container && i < var_container->getVarCount()) {
uint16 varSelector = var_container->getVarSelector(i);
- debugPrintf("[%03x] %s = ", varSelector, _engine->getKernel()->getSelectorName(varSelector).c_str());
+ // Times two commented out for now for easy parsing of vocab.994
+ debugPrintf("(%04x) [%03x] %s = ", i /* *2 */, varSelector, _engine->getKernel()->getSelectorName(varSelector).c_str());
} else
debugPrintf("p#%x = ", i);
diff --git a/engines/sci/console.h b/engines/sci/console.h
index 7c4de02182..4b630da25b 100644
--- a/engines/sci/console.h
+++ b/engines/sci/console.h
@@ -33,7 +33,7 @@ namespace Sci {
class SciEngine;
struct List;
-reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printBytecode);
+reg_t disassemble(EngineState *s, reg32_t pos, reg_t objAddr, bool printBWTag, bool printBytecode);
bool isJumpOpcode(EngineState *s, reg_t pos, reg_t& jumpOffset);
class Console : public GUI::Debugger {
@@ -41,7 +41,11 @@ public:
Console(SciEngine *engine);
virtual ~Console();
- int printObject(reg_t pos);
+#ifdef ENABLE_SCI32
+ void printArray(reg_t reg);
+ void printBitmap(reg_t reg);
+#endif
+ int printObject(reg_t reg);
private:
virtual void preEnter();
@@ -98,6 +102,7 @@ private:
bool cmdPlaneList(int argc, const char **argv);
bool cmdVisiblePlaneList(int argc, const char **argv);
bool cmdPlaneItemList(int argc, const char **argv);
+ bool cmdVisiblePlaneItemList(int argc, const char **argv);
bool cmdSavedBits(int argc, const char **argv);
bool cmdShowSavedBits(int argc, const char **argv);
// Segments
@@ -138,6 +143,7 @@ private:
bool cmdSend(int argc, const char **argv);
bool cmdGo(int argc, const char **argv);
bool cmdLogKernel(int argc, const char **argv);
+ bool cmdMapVocab994(int argc, const char **argv);
// Breakpoints
bool cmdBreakpointList(int argc, const char **argv);
bool cmdBreakpointDelete(int argc, const char **argv);
@@ -146,6 +152,7 @@ private:
bool cmdBreakpointWrite(int argc, const char **argv);
bool cmdBreakpointKernel(int argc, const char **argv);
bool cmdBreakpointFunction(int argc, const char **argv);
+ bool cmdBreakpointAddress(int argc, const char **argv);
// VM
bool cmdScriptSteps(int argc, const char **argv);
bool cmdScriptObjects(int argc, const char **argv);
@@ -157,6 +164,7 @@ private:
bool cmdValueType(int argc, const char **argv);
bool cmdViewListNode(int argc, const char **argv);
bool cmdViewReference(int argc, const char **argv);
+ bool cmdDumpReference(int argc, const char **argv);
bool cmdViewObject(int argc, const char **argv);
bool cmdViewActiveObject(int argc, const char **argv);
bool cmdViewAccumulatorObject(int argc, const char **argv);
@@ -167,7 +175,8 @@ private:
void printBasicVarInfo(reg_t variable);
bool segmentInfo(int nr);
- void printList(List *list);
+ void printList(reg_t addr);
+ void printList(const 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);
diff --git a/engines/sci/debug.h b/engines/sci/debug.h
index 4fcb757c10..60fad26671 100644
--- a/engines/sci/debug.h
+++ b/engines/sci/debug.h
@@ -42,12 +42,16 @@ enum BreakpointType {
* Break when an exported function is called. Data contains
* script_no << 16 | export_no.
*/
- BREAK_EXPORT = 1 << 3
+ BREAK_EXPORT = 1 << 3,
+ BREAK_ADDRESS = 1 << 4 // break when pc is at this address
};
struct Breakpoint {
BreakpointType type;
- uint32 address; ///< Breakpoints on exports
+ union {
+ uint32 address; ///< Breakpoints on exports
+ reg32_t regAddress; ///< Breakpoints on addresses
+ };
Common::String name; ///< Breakpoints on selector names
};
diff --git a/engines/sci/decompressor.cpp b/engines/sci/decompressor.cpp
index e65ff148de..ca2298e67e 100644
--- a/engines/sci/decompressor.cpp
+++ b/engines/sci/decompressor.cpp
@@ -257,7 +257,7 @@ int DecompressorLZW::unpackLZW1(Common::ReadStream *src, byte *dest, uint32 nPac
init(src, dest, nPacked, nUnpacked);
byte *stak = (byte *)malloc(0x1014);
- unsigned int tokensSize = 0x1004 * sizeof(Tokenlist);
+ uint32 tokensSize = 0x1004 * sizeof(Tokenlist);
Tokenlist *tokens = (Tokenlist *)malloc(tokensSize);
if (!stak || !tokens) {
free(stak);
diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index 985e12a7f6..5270398899 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -89,6 +89,7 @@ static const PlainGameDescriptor s_sciGameTitles[] = {
{"ecoquest2", "EcoQuest II: Lost Secret of the Rainforest"},
{"freddypharkas", "Freddy Pharkas: Frontier Pharmacist"},
{"hoyle4", "Hoyle Classic Card Games"},
+ {"inndemo", "ImagiNation Network (INN) Demo"},
{"kq6", "King's Quest VI: Heir Today, Gone Tomorrow"},
{"laurabow2", "Laura Bow 2: The Dagger of Amon Ra"},
{"qfg1vga", "Quest for Glory I: So You Want to Be a Hero"}, // Note: There was also a SCI0 version of this (further up)
@@ -98,11 +99,18 @@ static const PlainGameDescriptor s_sciGameTitles[] = {
{"lsl6", "Leisure Suit Larry 6: Shape Up or Slip Out!"},
{"pepper", "Pepper's Adventure in Time"},
{"slater", "Slater & Charlie Go Camping"},
+ {"gk1demo", "Gabriel Knight: Sins of the Fathers"},
+ {"qfg4demo", "Quest for Glory IV: Shadows of Darkness"},
+ {"pq4demo", "Police Quest IV: Open Season"},
// === SCI2 games =========================================================
- {"gk1", "Gabriel Knight: Sins of the Fathers"}, // demo is SCI11, full version SCI32
+ {"gk1", "Gabriel Knight: Sins of the Fathers"},
{"pq4", "Police Quest IV: Open Season"}, // floppy is SCI2, CD SCI2.1
{"qfg4", "Quest for Glory IV: Shadows of Darkness"}, // floppy is SCI2, CD SCI2.1
// === SCI2.1 games ========================================================
+ {"hoyle5", "Hoyle Classic Games"},
+ {"hoyle5bridge", "Hoyle Bridge"},
+ {"hoyle5children", "Hoyle Children's Collection"},
+ {"hoyle5solitaire", "Hoyle Solitaire"},
{"chest", "Inside the Chest"}, // aka Behind the Developer's Shield
{"gk2", "The Beast Within: A Gabriel Knight Mystery"},
{"kq7", "King's Quest VII: The Princeless Bride"},
@@ -146,13 +154,19 @@ static const GameIdStrToEnum s_gameIdStrToEnum[] = {
{ "fairytales", GID_FAIRYTALES },
{ "freddypharkas", GID_FREDDYPHARKAS },
{ "funseeker", GID_FUNSEEKER },
+ { "gk1demo", GID_GK1DEMO },
{ "gk1", GID_GK1 },
{ "gk2", GID_GK2 },
{ "hoyle1", GID_HOYLE1 },
{ "hoyle2", GID_HOYLE2 },
{ "hoyle3", GID_HOYLE3 },
{ "hoyle4", GID_HOYLE4 },
+ { "hoyle5", GID_HOYLE5 },
+ { "hoyle5bridge", GID_HOYLE5 },
+ { "hoyle5children", GID_HOYLE5 },
+ { "hoyle5solitaire", GID_HOYLE5 },
{ "iceman", GID_ICEMAN },
+ { "inndemo", GID_INNDEMO },
{ "islandbrain", GID_ISLANDBRAIN },
{ "jones", GID_JONES },
{ "kq1sci", GID_KQ1 },
@@ -183,12 +197,14 @@ static const GameIdStrToEnum s_gameIdStrToEnum[] = {
{ "pq2", GID_PQ2 },
{ "pq3", GID_PQ3 },
{ "pq4", GID_PQ4 },
+ { "pq4demo", GID_PQ4DEMO },
{ "pqswat", GID_PQSWAT },
{ "qfg1", GID_QFG1 },
{ "qfg1vga", GID_QFG1VGA },
{ "qfg2", GID_QFG2 },
{ "qfg3", GID_QFG3 },
{ "qfg4", GID_QFG4 },
+ { "qfg4demo", GID_QFG4DEMO },
{ "rama", GID_RAMA },
{ "sci-fanmade", GID_FANMADE }, // FIXME: Do we really need/want this?
{ "shivers", GID_SHIVERS },
@@ -356,7 +372,7 @@ Common::String convertSierraGameId(Common::String sierraId, uint32 *gameFlags, R
// qfg4 demo has less than 50 scripts
if (resources.size() < 50)
- return "qfg4";
+ return "qfg4demo";
// Otherwise it's qfg3
return "qfg3";
@@ -389,6 +405,16 @@ static const ADExtraGuiOptionsMap optionsList[] = {
},
{
+ GAMEOPTION_ENABLE_BLACK_LINED_VIDEO,
+ {
+ _s("Enable black-lined video"),
+ _s("Draw black lines over videos to increase their apparent sharpness"),
+ "enable_black_lined_video",
+ false
+ }
+ },
+
+ {
GAMEOPTION_PREFER_DIGITAL_SFX,
{
_s("Prefer digital sound effects"),
@@ -402,7 +428,7 @@ static const ADExtraGuiOptionsMap optionsList[] = {
GAMEOPTION_ORIGINAL_SAVELOAD,
{
_s("Use original save/load screens"),
- _s("Use the original save/load screens, instead of the ScummVM ones"),
+ _s("Use the original save/load screens instead of the ScummVM ones"),
"originalsaveload",
false
}
@@ -445,7 +471,7 @@ static const ADExtraGuiOptionsMap optionsList[] = {
GAMEOPTION_SQ4_SILVER_CURSORS,
{
_s("Use silver cursors"),
- _s("Use the alternate set of silver cursors, instead of the normal golden ones"),
+ _s("Use the alternate set of silver cursors instead of the normal golden ones"),
"silver_cursors",
false
}
@@ -473,7 +499,7 @@ static char s_fallbackGameIdBuf[256];
class SciMetaEngine : public AdvancedMetaEngine {
public:
SciMetaEngine() : AdvancedMetaEngine(Sci::SciGameDescriptions, sizeof(ADGameDescription), s_sciGameTitles, optionsList) {
- _singleid = "sci";
+ _singleId = "sci";
}
virtual const char *getName() const {
@@ -526,11 +552,11 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
s_fallbackDesc.language = Common::EN_ANY;
s_fallbackDesc.flags = ADGF_NO_FLAGS;
s_fallbackDesc.platform = Common::kPlatformDOS; // default to PC platform
- s_fallbackDesc.gameid = "sci";
- s_fallbackDesc.guioptions = GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI);
+ s_fallbackDesc.gameId = "sci";
+ s_fallbackDesc.guiOptions = GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI);
if (allFiles.contains("resource.map") || allFiles.contains("Data1")
- || allFiles.contains("resmap.001") || allFiles.contains("resmap.001")) {
+ || allFiles.contains("resmap.000") || allFiles.contains("resmap.001")) {
foundResMap = true;
}
@@ -539,8 +565,8 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
// the file should be over 10MB, as it contains all the game speech and is usually
// around 450MB+. The size check is for some floppy game versions like KQ6 floppy, which
// also have a small resource.aud file
- if (allFiles.contains("resource.aud") || allFiles.contains("audio001.002")) {
- Common::FSNode file = allFiles.contains("resource.aud") ? allFiles["resource.aud"] : allFiles["audio001.002"];
+ if (allFiles.contains("resource.aud") || allFiles.contains("resaud.001") || allFiles.contains("audio001.002")) {
+ Common::FSNode file = allFiles.contains("resource.aud") ? allFiles["resource.aud"] : (allFiles.contains("resaud.001") ? allFiles["resaud.001"] : allFiles["audio001.002"]);
Common::SeekableReadStream *tmpStream = file.createReadStream();
if (tmpStream->size() > 10 * 1024 * 1024) {
// We got a CD version, so set the CD flag accordingly
@@ -610,7 +636,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
Common::String gameId = convertSierraGameId(sierraGameId, &s_fallbackDesc.flags, resMan);
strncpy(s_fallbackGameIdBuf, gameId.c_str(), sizeof(s_fallbackGameIdBuf) - 1);
s_fallbackGameIdBuf[sizeof(s_fallbackGameIdBuf) - 1] = 0; // Make sure string is NULL terminated
- s_fallbackDesc.gameid = s_fallbackGameIdBuf;
+ s_fallbackDesc.gameId = s_fallbackGameIdBuf;
// Try to determine the game language
// Load up text 0 and start looking for "#" characters
@@ -653,7 +679,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
const bool isCD = (s_fallbackDesc.flags & ADGF_CD);
if (!isCD)
- s_fallbackDesc.guioptions = GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI);
+ s_fallbackDesc.guiOptions = GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI);
if (gameId.hasSuffix("sci")) {
s_fallbackDesc.extra = "SCI";
@@ -686,7 +712,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
bool SciMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
const GameIdStrToEnum *g = s_gameIdStrToEnum;
for (; g->gameidStr; ++g) {
- if (0 == strcmp(desc->gameid, g->gameidStr)) {
+ if (0 == strcmp(desc->gameId, g->gameidStr)) {
*engine = new SciEngine(syst, desc, g->gameidEnum);
return true;
}
@@ -746,8 +772,7 @@ SaveStateList SciMetaEngine::listSaves(const char *target) const {
SaveStateDescriptor descriptor(slotNr, meta.name);
if (slotNr == 0) {
- // ScummVM auto-save slot, not used by SCI
- // SCI does not support auto-saving, but slot 0 is reserved for auto-saving in ScummVM.
+ // ScummVM auto-save slot
descriptor.setWriteProtectedFlag(true);
} else {
descriptor.setWriteProtectedFlag(false);
@@ -769,9 +794,8 @@ SaveStateDescriptor SciMetaEngine::querySaveMetaInfos(const char *target, int sl
Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(fileName);
SaveStateDescriptor descriptor(slotNr, "");
- // Do not allow save slot 0 (used for auto-saving) to be deleted or
- // overwritten. SCI does not support auto-saving, but slot 0 is reserved for auto-saving in ScummVM.
if (slotNr == 0) {
+ // ScummVM auto-save slot
descriptor.setWriteProtectedFlag(true);
descriptor.setDeletableFlag(false);
} else {
@@ -781,7 +805,7 @@ SaveStateDescriptor SciMetaEngine::querySaveMetaInfos(const char *target, int sl
if (in) {
SavegameMetadata meta;
-
+
if (!get_savegame_metadata(in, &meta)) {
// invalid
delete in;
@@ -859,11 +883,24 @@ Common::Error SciEngine::saveGameState(int slot, const Common::String &desc) {
}
bool SciEngine::canLoadGameStateCurrently() {
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ switch (getGameId()) {
+ case GID_PHANTASMAGORIA:
+ case GID_HOYLE5:
+ return false;
+ default:
+ break;
+ }
+ }
+#endif
+
return !_gamestate->executionStackBase;
}
bool SciEngine::canSaveGameStateCurrently() {
- return !_gamestate->executionStackBase;
+ // see comment about kSupportsSavingDuringRuntime in SciEngine::hasFeature
+ return false;
}
} // End of namespace Sci
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index 2fd433240b..0709bd4606 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -22,14 +22,7 @@
namespace Sci {
-#define GAMEOPTION_PREFER_DIGITAL_SFX GUIO_GAMEOPTIONS1
-#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS2
-#define GAMEOPTION_FB01_MIDI GUIO_GAMEOPTIONS3
-#define GAMEOPTION_JONES_CDAUDIO GUIO_GAMEOPTIONS4
-#define GAMEOPTION_KQ6_WINDOWS_CURSORS GUIO_GAMEOPTIONS5
-#define GAMEOPTION_SQ4_SILVER_CURSORS GUIO_GAMEOPTIONS6
-#define GAMEOPTION_EGA_UNDITHER GUIO_GAMEOPTIONS7
-#define GAMEOPTION_HIGH_RESOLUTION_GRAPHICS GUIO_GAMEOPTIONS8
+#include "sci/sci.h"
// SCI3 games have a different script format (in CSC files) and are currently unsupported
#define ENABLE_SCI3_GAMES
@@ -239,6 +232,19 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Codename: Iceman - English Atari ST
+ // Game version 1.041
+ // Executable reports "1.002.041"
+ { "iceman", "",{
+ { "resource.map", 0, "066e89b685ad788e06bae0b76d0d37d3", 5718 },
+ { "resource.000", 0, "053278385ce910a3f630f2e45e3c10be", 26987 },
+ { "resource.001", 0, "32b351072fccf76fc82234d73d28c08b", 438880 },
+ { "resource.002", 0, "36670a917550757d57df84c96cf9e6d9", 566667 },
+ { "resource.003", 0, "d97a96f1ab91b41cf46a02cc89b0a04e", 624304 },
+ { "resource.004", 0, "8613c45fc771d658e5a505b9a4a54f31", 670884 },
+ AD_LISTEND },
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Codename: Iceman - English DOS Non-Interactive Demo
// Executable scanning reports "0.000.685"
{"iceman", "Demo", {
@@ -336,6 +342,19 @@ 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) },
+ // Conquests of Camelot - English Atari ST
+ // Game version 1.019.000
+ // Floppy: INT#10.12.90
+ // Executable reports "1.002.038"
+ {"camelot", "", {
+ {"resource.map", 0, "0f80a11867be91a158823887a49cf443", 7290},
+ {"resource.001", 0, "162f66c42e4146ee63f78fba6f1a6757", 596773},
+ {"resource.002", 0, "162f66c42e4146ee63f78fba6f1a6757", 724615},
+ {"resource.003", 0, "162f66c42e4146ee63f78fba6f1a6757", 713351},
+ {"resource.004", 0, "162f66c42e4146ee63f78fba6f1a6757", 718766},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Conquests of Camelot - English DOS
// SCI interpreter version 0.000.685
{"camelot", "", {
@@ -684,30 +703,42 @@ 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) },
- // Gabriel Knight - English DOS CD Demo
+ // Gabriel Knight - English DOS Demo
// SCI interpreter version 1.001.092
- {"gk1", "CD Demo", {
+ // Note: we are not using ADGF_DEMO here, to avoid a game ID like gk1demo-demo
+ {"gk1demo", "Demo", {
{"resource.map", 0, "39645952ae0ed8072c7e838f31b75464", 2490},
{"resource.000", 0, "eb3ed7477ca4110813fe1fcf35928561", 1718450},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
- // Gabriel Knight - English DOS CD Demo (from DrMcCoy)
+ // Gabriel Knight - English DOS Demo (from DrMcCoy)
// SCI interpreter version 1.001.092
- {"gk1", "CD Demo", {
+ // Note: we are not using ADGF_DEMO here, to avoid a game ID like gk1demo-demo
+ {"gk1demo", "Demo", {
{"resource.map", 0, "8cad2a256f41463030cbb7ea1bfb2857", 2490},
{"resource.000", 0, "eb3ed7477ca4110813fe1fcf35928561", 1718450},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
+#define GUIO_GK1_FLOPPY GUIO4(GUIO_NOSPEECH, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_GK1_CD GUIO4(GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_GK1_MAC GUIO_GK1_FLOPPY
+
// Gabriel Knight - English DOS Floppy
// SCI interpreter version 2.000.000
{"gk1", "", {
{"resource.map", 0, "372d059f75856afa6d73dd84cbb8913d", 10783},
{"resource.000", 0, "69b7516962510f780d38519cc15fcc7c", 13022630},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_GK1_FLOPPY },
// Gabriel Knight - English DOS Floppy (supplied my markcoolio in bug report #2723777)
// SCI interpreter version 2.000.000
@@ -715,7 +746,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "65e8c14092e4c9b3b3538b7602c8c5ec", 10783},
{"resource.000", 0, "69b7516962510f780d38519cc15fcc7c", 13022630},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_GK1_FLOPPY },
// Gabriel Knight - English DOS Floppy
// SCI interpreter version 2.000.000, VERSION file reports "1.0\nGabriel Knight\n11/22/10:33 pm\n\x1A"
@@ -723,7 +754,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "ef41df08cf2c1f680216cdbeed0f8311", 10783},
{"resource.000", 0, "69b7516962510f780d38519cc15fcc7c", 13022630},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_GK1_FLOPPY },
// Gabriel Knight - German DOS Floppy (supplied my markcoolio in bug report #2723775)
// SCI interpreter version 2.000.000
@@ -731,7 +762,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "ad6508b0296b25c07b1f58828dc33696", 10789},
{"resource.000", 0, "091cf08910780feabc56f8551b09cb36", 13077029},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_GK1_FLOPPY },
// Gabriel Knight - French DOS Floppy (supplied my kervala in bug report #3611487)
// SCI interpreter version 2.000.000
@@ -739,7 +770,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "236e36cc847cdeafdd5e5fa8cba916ed", 10801},
{"resource.000", 0, "091cf08910780feabc56f8551b09cb36", 13033072},
AD_LISTEND},
- Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_GK1_FLOPPY },
// Gabriel Knight - English DOS CD (from jvprat)
// Executable scanning reports "2.000.000", VERSION file reports "01.100.000"
@@ -747,7 +778,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "372d059f75856afa6d73dd84cbb8913d", 10996},
{"resource.000", 0, "69b7516962510f780d38519cc15fcc7c", 12581736},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_GK1_CD },
// Gabriel Knight - English Windows CD (from jvprat)
// Executable scanning reports "2.000.000", VERSION file reports "01.100.000"
@@ -755,7 +786,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "372d059f75856afa6d73dd84cbb8913d", 10996},
{"resource.000", 0, "69b7516962510f780d38519cc15fcc7c", 12581736},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_CD | ADGF_UNSTABLE, GUIO5(GUIO_NOASPECT, GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_CD | ADGF_UNSTABLE, GUIO_GK1_CD },
// Gabriel Knight - German DOS CD (from Tobis87)
// SCI interpreter version 2.000.000
@@ -763,7 +794,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "a7d3e55114c65647310373cb390815ba", 11392},
{"resource.000", 0, "091cf08910780feabc56f8551b09cb36", 13400497},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_GK1_CD },
// Gabriel Knight - Spanish DOS CD (from jvprat)
// Executable scanning reports "2.000.000", VERSION file reports "1.000.000, April 13, 1995"
@@ -771,7 +802,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "7cb6e9bba15b544ec7a635c45bde9953", 11404},
{"resource.000", 0, "091cf08910780feabc56f8551b09cb36", 13381599},
AD_LISTEND},
- Common::ES_ESP, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::ES_ESP, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_GK1_CD },
// Gabriel Knight - French DOS CD (from Hkz)
// VERSION file reports "1.000.000, May 3, 1994"
@@ -779,7 +810,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "55f909ba93a2515042a08d8a2da8414e", 11392},
{"resource.000", 0, "091cf08910780feabc56f8551b09cb36", 13325145},
AD_LISTEND},
- Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_GK1_CD },
// Gabriel Knight - German Windows CD (from Tobis87)
// SCI interpreter version 2.000.000
@@ -787,7 +818,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "a7d3e55114c65647310373cb390815ba", 11392},
{"resource.000", 0, "091cf08910780feabc56f8551b09cb36", 13400497},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformWindows, ADGF_CD | ADGF_UNSTABLE, GUIO5(GUIO_NOASPECT, GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformWindows, ADGF_CD | ADGF_UNSTABLE, GUIO_GK1_CD },
// Gabriel Knight - Spanish Windows CD (from jvprat)
// Executable scanning reports "2.000.000", VERSION file reports "1.000.000, April 13, 1995"
@@ -795,16 +826,31 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "7cb6e9bba15b544ec7a635c45bde9953", 11404},
{"resource.000", 0, "091cf08910780feabc56f8551b09cb36", 13381599},
AD_LISTEND},
- Common::ES_ESP, Common::kPlatformWindows, ADGF_CD | ADGF_UNSTABLE, GUIO5(GUIO_NOASPECT, GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::ES_ESP, Common::kPlatformWindows, ADGF_CD | ADGF_UNSTABLE, GUIO_GK1_CD },
- // Gabriel Knight - English Macintosh
+ // Gabriel Knight - English Macintosh (Floppy!)
+ // This version is hi-res ONLY, so it should NOT get GAMEOPTION_HIGH_RESOLUTION_GRAPHICS
+ // (which is meant for enforcing hi-res graphics), but instead hi-res mode should be enabled all the time.
+ // Confirmed by [md5] and originally by clone2727.
{"gk1", "", {
{"Data1", 0, "044d3bcd7e5b5bb0393d954ade8053fe", 5814918},
{"Data2", 0, "99a0c63febf9e44e12a00f99c00eae0f", 6685352},
{"Data3", 0, "f25068b408b09275d8b698866462f578", 3677599},
{"Data4", 0, "1cceebbe411b26c860a74f91c337fdf3", 3230086},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_UNSTABLE, GUIO_GK1_MAC },
+
+#define GUIO_GK2_DEMO GUIO5(GUIO_NOSPEECH, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_GK2 GUIO5(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_GK2_MAC GUIO_GK2
// Gabriel Knight 2 - English Windows Non-Interactive Demo
// Executable scanning reports "2.100.002"
@@ -812,7 +858,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "e0effce11c4908f4b91838741716c83d", 1351},
{"resource.000", 0, "d04cfc7f04b6f74d13025378be49ec2b", 4640330},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, GUIO_GK2_DEMO },
// Gabriel Knight 2 - English DOS (GOG version) - ressci.* merged in ressci.000
// using Enrico Rolfi's HD/DVD installer: http://gkpatches.vogons.zetafleet.com/
@@ -820,7 +866,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "b996fa1e57389a1e179a00a0049de1f4", 8110},
{"ressci.000", 0, "a19fc3604c6e5407abcf03d59ee87217", 168522221},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_GK2 },
// Gabriel Knight 2 - English DOS (from jvprat)
// Executable scanning reports "2.100.002", VERSION file reports "1.1"
@@ -838,7 +884,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.006", 0, "ce9359037277b7d7976da185c2fa0aad", 2977},
{"ressci.006", 0, "8e44e03890205a7be12f45aaba9644b4", 60659424},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_GK2 },
// Gabriel Knight 2 - French DOS (6-CDs Sierra Originals reedition)
// Executable scanning reports "2.100.002", VERSION file reports "1.0"
@@ -856,7 +902,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.006", 0, "11b2e722170b8c93fdaa5428e2c7676f", 3001},
{"ressci.006", 0, "4037d941aec39d2e654e20960429aefc", 60568486},
AD_LISTEND},
- Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_GK2 },
// Gabriel Knight 2 - English Macintosh
// NOTE: This only contains disc 1 files (as well as the persistent file:
@@ -868,7 +914,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"Data4", 0, "8b843c62eb53136a855d6e0087e3cb0d", 5889553},
{"Data5", 0, "f9fcf9ab2eb13b2125c33a1cda03a093", 14349984},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_CD | ADGF_UNSTABLE, GUIO_GK2_MAC },
#endif // ENABLE_SCI32
@@ -924,6 +970,22 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Hoyle 1 - English Atari ST
+ // Game version 1.000.104, SCI interpreter version 1.002.024
+ {"hoyle1", "", {
+ {"resource.001", 0, "e0dd44069a62a463fd124974b915f10d", 518127},
+ {"resource.map", 0, "0af9a3dcd72a091960de070432e1f524", 4386},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
+ // Hoyle 1 - English Atari ST
+ // Game version 1.000.108, SCI interpreter version 1.002.026
+ {"hoyle1", "", {
+ {"resource.map", 0, "ed8355f84752e49ffa1f0cf9eca4b28e", 4140},
+ {"resource.001", 0, "e0dd44069a62a463fd124974b915f10d", 517454},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Hoyle 2 - English DOS
// SCI interpreter version 0.000.572
{"hoyle2", "", {
@@ -941,6 +1003,15 @@ 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) },
+ // Hoyle 2 - English DOS (supplied by m_kiewitz)
+ // SCI interpreter version 0.000.668, Ver 1.000.014, 2x5.25"
+ {"hoyle2", "", {
+ {"resource.map", 0, "8cef06c93d17d96f44aacd5902d84b30", 2100},
+ {"resource.001", 0, "8f2dd70abe01112eca464cda818b5eb6", 98289},
+ {"resource.002", 0, "8f2dd70abe01112eca464cda818b5eb6", 197326},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Hoyle 2 - English DOS (supplied by misterhands in bug report #6598)
// Game v1.000.016, interpreter 0.000.668, INT #12.5.90
{"hoyle2", "", {
@@ -958,6 +1029,15 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Hoyle 2 - English Atari ST
+ // Game version 1.001.017
+ // Executable scanning reports "1.002.034"
+ {"hoyle2", "", {
+ {"resource.map", 0, "13c8cc977598b6ad61d24c6296a090fd", 1356},
+ {"resource.001", 0, "8f2dd70abe01112eca464cda818b5eb6", 216280},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Hoyle 2 - English Macintosh
// Executable scanning reports "x.yyy.zzz"
{"hoyle2", "", {
@@ -1048,6 +1128,68 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+#ifdef ENABLE_SCI32
+#define GUIO_HOYLE5 GUIO3(GUIO_NOSPEECH, \
+ GUIO_NOASPECT, \
+ GUIO_NOLAUNCHLOAD)
+
+ // Hoyle 5 (Hoyle Classic Games) - Windows demo
+ {"hoyle5", "Demo", {
+ {"ressci.000", 0, "98a39ae535dd01714ac313f8ba925045", 7260363},
+ {"resmap.000", 0, "10267a1542a73d527e50f0340549088b", 4900},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, GUIO_HOYLE5 },
+
+ // Hoyle 5 (Hoyle Classic Games) - Windows
+ {"hoyle5", "", {
+ {"resource.aud", 0, "cc4a7e21dc864ae21cf823e893c279ad", 257483406},
+ {"ressci.000", 0, "55ae04012a73abc15b93debf60a7df71", 16909704},
+ {"resmap.000", 0, "daf64a91344a7934fe4374765267c2af", 5767},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_HOYLE5 },
+
+ // Hoyle Bridge - Windows
+ {"hoyle5bridge", "", {
+ {"resource.aud", 0, "cc4a7e21dc864ae21cf823e893c279ad", 257585548},
+ {"ressci.000", 0, "b83cba09229d3003df9e0c864843f962", 16842499},
+ {"resmap.000", 0, "7b3e3030b0ad5f341053c18afce7d176", 5647},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_HOYLE5 },
+
+ // Hoyle Children's Collection - Windows
+ {"hoyle5children", "", {
+ {"resource.aud", 0, "cc4a7e21dc864ae21cf823e893c279ad", 257585548},
+ {"ressci.000", 0, "fd1f7dbeebd4510cd37e171a72f2b6ad", 16824349},
+ {"resmap.000", 0, "b0fe1bcc69596e10fe5caa11d0b55b23", 5671},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_HOYLE5 },
+
+ // Hoyle Solitaire (CD version) - Windows
+ {"hoyle5solitaire", "CD", {
+ {"resource.aud", 0, "d41d8cd98f00b204e9800998ecf8427e", 0},
+ {"ressci.000", 0, "fa4eeb24b1fbf6f33739995360554485", 11628203},
+ {"resmap.000", 0, "3f63df73a49800f080775d2a9ad0e949", 3079},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_HOYLE5 },
+
+ // Hoyle Solitaire (Hard Drive version) - Windows
+ {"hoyle5solitaire", "Hard Drive", {
+ {"resource.aud", 0, "d41d8cd98f00b204e9800998ecf8427e", 0},
+ {"ressci.000", 0, "da180c67d54d4208c84a48fcd8709671", 8582335},
+ {"resmap.000", 0, "e2feb47ab16f9e22a9b6a8580d1da3f0", 3055},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_HOYLE5 },
+
+#endif // ENABLE_SCI32
+
+ // ImagiNation Network (INN) Demo
+ // SCI interpreter version 1.001.097
+ {"inndemo", "", {
+ {"resource.000", 0, "535b1b920441ec73f42eaa4ccfd47b89", 514578},
+ {"resource.map", 0, "333daf27c3e8a6d274a3e0061ed7cd5c", 1545},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Jones in the Fast Lane EGA - English DOS
// SCI interpreter version 1.000.172 (not 100% sure FIXME)
{"jones", "EGA", {
@@ -1419,6 +1561,21 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // King's Quest 5 - English DOS Floppy (from telanus, bug report Trac#9624)
+ // Game version 0.000.062
+ {"kq5", "", {
+ {"resource.map", 0, "86a4ae3fafb1bbcc81b78cf427e45ba0", 8184},
+ {"resource.001", 0, "51797b784eccab97d1d4b1f8dc3ef671", 1099768},
+ {"resource.002", 0, "93c6f0fc7682fda52a632f34bcc1c975", 1060941},
+ {"resource.003", 0, "44388574401a25938f660dca90bdd040", 1109594},
+ {"resource.000", 0, "a591bd4b879fc832b8095c0b3befe9e2", 276351},
+ {"resource.007", 0, "a4cb2eba783a7b05f5b005d47bd94936", 1133814},
+ {"resource.004", 0, "464109fa0fd76f722fff73fd26e98271", 1153791},
+ {"resource.005", 0, "3c292d392c3cc3b532e9063d0d1fb7aa", 1032802},
+ {"resource.006", 0, "0380ee8181b39a8d7b66daf61a5b7d51", 921308},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// King's Quest V DOS 0.000.062 EGA (5 x 5.25" disks)
// Supplied by ssburnout in bug report #3046780
{"kq5", "EGA", {
@@ -1639,14 +1796,15 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#ifdef ENABLE_SCI32
-
- // King's Quest 7 - English Windows (from abevi)
- // VERSION 1.65c
- {"kq7", "", {
- {"resource.000", 0, "4948e4e1506f1e1c4e1d47abfa06b7f8", 204385195},
- {"resource.map", 0, "40ccafb2195301504eba2e4f4f2c7f3d", 18925},
- AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+#define GUIO_KQ7_DEMO GUIO5(GUIO_NOSPEECH, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_KQ7 GUIO4(GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
// King's Quest 7 - English Windows (from the King's Quest Collection)
// Executable scanning reports "2.100.002", VERSION file reports "1.4"
@@ -1654,7 +1812,34 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "2be9ab94429c721af8e05c507e048a15", 18697},
{"resource.000", 0, "eb63ea3a2c2469dc2d777d351c626404", 203882535},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_KQ7 },
+
+ // King's Quest 7 - English Windows-interpreter-only (supplied by m_kiewitz)
+ // SCI interpreter version 2.100.002, VERSION file reports "1.51"
+ {"kq7", "", {
+ {"resource.map", 0, "838b9ff132bd6962026fee832e8a7ddb", 18697},
+ {"resource.000", 0, "eb63ea3a2c2469dc2d777d351c626404", 206626576},
+ {"resource.aud", 0, "c2a988a16053eb98c7b73a75139902a0", 217716879},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_KQ7 },
+
+ // King's Quest 7 - German Windows-interpreter-only (supplied by markcoolio in bug report #2727402)
+ // SCI interpreter version 2.100.002, VERSION file reports "1.51"
+ // same as English 1.51, only resource.aud/resource.sfx are different
+ {"kq7", "", {
+ {"resource.map", 0, "838b9ff132bd6962026fee832e8a7ddb", 18697},
+ {"resource.000", 0, "eb63ea3a2c2469dc2d777d351c626404", 206626576},
+ {"resource.aud", 0, "3f17bcaf8a9ff6a6c2d4de1a2078fdcc", 258119621},
+ AD_LISTEND},
+ Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_KQ7 },
+
+ // King's Quest 7 - English Windows (from abevi)
+ // VERSION 1.65c
+ {"kq7", "", {
+ {"resource.000", 0, "4948e4e1506f1e1c4e1d47abfa06b7f8", 204385195},
+ {"resource.map", 0, "40ccafb2195301504eba2e4f4f2c7f3d", 18925},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_KQ7 },
// King's Quest 7 - English DOS (from FRG)
// SCI interpreter version 2.100.002, VERSION file reports "2.00b"
@@ -1662,7 +1847,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "8676b0fbbd7362989a029fe72fea14c6", 18709},
{"resource.000", 0, "51c1ead1163e19a2de8f121c39df7a76", 200764100},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE | ADGF_CD, GUIO_KQ7 },
// King's Quest 7 - English Windows (from FRG)
// SCI interpreter version 2.100.002, VERSION file reports "2.00b"
@@ -1670,15 +1855,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "8676b0fbbd7362989a029fe72fea14c6", 18709},
{"resource.000", 0, "51c1ead1163e19a2de8f121c39df7a76", 200764100},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
-
- // King's Quest 7 - German Windows (supplied by markcoolio in bug report #2727402)
- // SCI interpreter version 2.100.002
- {"kq7", "", {
- {"resource.map", 0, "838b9ff132bd6962026fee832e8a7ddb", 18697},
- {"resource.000", 0, "eb63ea3a2c2469dc2d777d351c626404", 206626576},
- AD_LISTEND},
- Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_KQ7 },
// King's Quest 7 - Spanish DOS (from jvprat)
// Executable scanning reports "2.100.002", VERSION file reports "2.00"
@@ -1686,7 +1863,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "0b62693cbe87e3aaca3e8655a437f27f", 18709},
{"resource.000", 0, "51c1ead1163e19a2de8f121c39df7a76", 200764100},
AD_LISTEND},
- Common::ES_ESP, Common::kPlatformDOS, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::ES_ESP, Common::kPlatformDOS, ADGF_UNSTABLE | ADGF_CD, GUIO_KQ7 },
// King's Quest 7 - English DOS Non-Interactive Demo
// SCI interpreter version 2.100.002
@@ -1694,7 +1871,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "b44f774108d63faa1d021101221c5a54", 1690},
{"resource.000", 0, "d9659d2cf0c269c6a9dc776707f5bea0", 2433827},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO_KQ7_DEMO },
// King's Quest 7 - English Windows Demo (from DrMcCoy)
// SCI interpreter version 2.100.002
@@ -1702,7 +1879,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "38e627a37a975aea40cc72b0518b0709", 18412},
{"resource.000", 0, "bad61d50aaa64298fa57a7c6ccd3bccf", 84020382},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE | ADGF_CD, GUIO_KQ7_DEMO },
// King's Questions mini-game from the King's Quest Collection
// SCI interpreter version 2.000.000
@@ -1942,6 +2119,17 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Larry 2 - English Atari ST
+ // Game version 1.001.006
+ // Executable reports "1.000.159" 1988-12-02 12:22 p.m.
+ {"lsl2", "", {
+ {"resource.map", 0, "2fc3ce7da1346e4dadfee18606d814fc", 4758},
+ {"resource.001", 0, "4a24443a25e2b1492462a52809605dc2", 477342},
+ {"resource.002", 0, "4a24443a25e2b1492462a52809605dc2", 406698},
+ {"resource.003", 0, "4a24443a25e2b1492462a52809605dc2", 592433},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Larry 2 - English DOS Non-Interactive Demo
// Executable scanning reports "x.yyy.zzz"
// SCI interpreter version 0.000.409
@@ -1999,6 +2187,17 @@ 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) },
+ // Larry 2 - English Atari ST (Kixx)
+ // Game version 1.002.000
+ // Executable reports "1.001.008" 1989-01-12 16:30
+ {"lsl2", "", {
+ {"resource.map", 0, "2c9c3b0923e3764f5ab999bcb71c2d47", 4758},
+ {"resource.001", 0, "4a24443a25e2b1492462a52809605dc2", 477625},
+ {"resource.002", 0, "4a24443a25e2b1492462a52809605dc2", 406935},
+ {"resource.003", 0, "4a24443a25e2b1492462a52809605dc2", 592533},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Larry 3 - English Amiga (from www.back2roots.org)
// Executable scanning reports "1.002.032"
// SCI interpreter version 0.000.685
@@ -2013,6 +2212,33 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Larry 3 - English Amiga
+ // Executable scanning reports "1.002.006"
+ // Game version 1.027
+ {"lsl3", "", {
+ {"resource.map", 0, "4c39724c91f53afe08a1350172a2b67a", 5328},
+ {"resource.000", 0, "cdc2e21e297b10fe8fed6377af8c5698", 66472},
+ {"resource.001", 0, "6abbaf8c7e3b36dd868868ed187e8995", 71761},
+ {"resource.002", 0, "a883424fe6d594fec0cd5a79e7ad54c8", 477285},
+ {"resource.003", 0, "5c10e462c8cf589610773e4fe8bfd996", 526871},
+ {"resource.004", 0, "f408e59cbee1457f042e5773b8c53951", 651637},
+ {"resource.005", 0, "433911eb764089d493aed1f958a5615a", 523993},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
+ // Larry 3 - English Atari ST
+ // Game version 1.021, 1990-01-27
+ // Int#6.26.90
+ // Executable scanning reports "1.002.026"
+ {"lsl3", "", {
+ {"resource.map", 0, "0b6bd3e039682830a51c5755c06591db", 5916},
+ {"resource.001", 0, "f18441027154292836b973c655fa3175", 456722},
+ {"resource.002", 0, "f18441027154292836b973c655fa3175", 578024},
+ {"resource.003", 0, "f18441027154292836b973c655fa3175", 506807},
+ {"resource.004", 0, "f18441027154292836b973c655fa3175", 513651},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Larry 3 - English DOS (supplied by ssburnout in bug report #3049193)
// 1.021 8x5.25" (label: Int#5.15.90)
{"lsl3", "", {
@@ -2357,13 +2583,19 @@ static const struct ADGameDescription SciGameDescriptions[] = {
Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
+
+#define GUIO_LSL6HIRES GUIO4(GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+
// Larry 6 - English/German DOS CD - HIRES
// SCI interpreter version 2.100.002
{"lsl6hires", "Hi-res", {
{"resource.map", 0, "0c0804434ea62278dd15032b1947426c", 8872},
{"resource.000", 0, "9a9f4870504444cda863dd14d077a680", 18520872},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL6HIRES },
// Larry 6 - German DOS CD - HIRES (provided by richiefs in bug report #2670691)
// SCI interpreter version 2.100.002
@@ -2371,7 +2603,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "badfdf446ffed569a310d2c63a249421", 8896},
{"resource.000", 0, "bd944d2b06614a5b39f1586906f0ee88", 18534274},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL6HIRES },
// Larry 6 - French DOS CD - HIRES (provided by richiefs in bug report #2670691)
// SCI interpreter version 2.100.002
@@ -2379,7 +2611,18 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "d184e9aa4f2d4b5670ddb3669db82cda", 8896},
{"resource.000", 0, "bd944d2b06614a5b39f1586906f0ee88", 18538987},
AD_LISTEND},
- Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL6HIRES },
+
+#define GUIO_LSL7_DEMO GUIO5(GUIO_NOSPEECH, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_LSL7 GUIO5(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
// Larry 7 - English DOS Demo (provided by richiefs in bug report #2670691)
// SCI interpreter version 2.100.002
@@ -2387,7 +2630,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"ressci.000", 0, "5cc6159688b2dc03790a67c90ccc67f9", 10195878},
{"resmap.000", 0, "6a2b2811eef82e87cde91cf1de845af8", 2695},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO_LSL7_DEMO },
#ifdef ENABLE_SCI3_GAMES
// Larry 7 - English DOS CD (from spookypeanut)
@@ -2396,7 +2639,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "eae93e1b1d1ccc58b4691c371281c95d", 8188},
{"ressci.000", 0, "89353723488219e25589165d73ed663e", 66965678},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 },
// Larry 7 - German DOS (from Tobis87)
// SCI interpreter version 3.000.000
@@ -2404,7 +2647,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "c11e6bfcfc2f2d05da47e5a7df3e9b1a", 8188},
{"ressci.000", 0, "a8c6817bb94f332ff498a71c8b47f893", 66971724},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 },
// Larry 7 - French DOS (provided by richiefs in bug report #2670691)
// SCI interpreter version 3.000.000
@@ -2412,7 +2655,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "4407849fd52fe3efb0c30fba60cd5cd4", 8206},
{"ressci.000", 0, "dc37c3055fffbefb494ff22b145d377b", 66964472},
AD_LISTEND},
- Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 },
// Larry 7 - Italian DOS CD (from glorifindel)
// SCI interpreter version 3.000.000
@@ -2420,7 +2663,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "9852a97141f789413f29bf956052acdb", 8212},
{"ressci.000", 0, "440b9fed89590abb4e4386ed6f948ee2", 67140181},
AD_LISTEND},
- Common::IT_ITA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::IT_ITA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 },
// Larry 7 - Spanish DOS (from the Leisure Suit Larry Collection)
// Executable scanning reports "3.000.000", VERSION file reports "1.0s"
@@ -2428,16 +2671,28 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "8f3d603e1acc834a5d598b30cdfc93f3", 8188},
{"ressci.000", 0, "32792f9bc1bf3633a88b382bb3f6e40d", 67071418},
AD_LISTEND},
- Common::ES_ESP, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::ES_ESP, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 },
#endif
+#define GUIO_LIGHTHOUSE_DEMO GUIO5(GUIO_NOSPEECH, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_LIGHTHOUSE GUIO6(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, \
+ GUIO_NOSPEECH, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+
// Lighthouse - English Windows Demo (from jvprat)
// Executable scanning reports "2.100.002", VERSION file reports "1.00"
{"lighthouse", "Demo", {
{"resource.map", 0, "543124606352bfa5e07696ddf2a669be", 64},
{"resource.000", 0, "5d7714416b612463d750fb9c5690c859", 28952},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO_LIGHTHOUSE_DEMO },
#ifdef ENABLE_SCI3_GAMES
// Lighthouse - English Windows Demo
@@ -2446,7 +2701,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "3bdee7a16926975a4729f75cf6b80a92", 1525},
{"ressci.000", 0, "3c585827fa4a82f4c04a56a0bc52ccee", 11494351},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO_LIGHTHOUSE },
// Lighthouse - English DOS (from jvprat)
// Executable scanning reports "3.000.000", VERSION file reports "1.1"
@@ -2456,7 +2711,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.002", 0, "c68db5333f152fea6ca2dfc75cad8b34", 7573},
{"ressci.002", 0, "175468431a979b9f317c294ce3bc1430", 94628315},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LIGHTHOUSE },
// Lighthouse - Japanese DOS (from m_kiewitz)
// Executable scanning reports "3.000.000", VERSION file reports "1.0C"
@@ -2466,7 +2721,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.002", 0, "723fc742c623d8933e5753a264324cb0", 7657},
{"ressci.002", 0, "175468431a979b9f317c294ce3bc1430", 94627469},
AD_LISTEND},
- Common::JA_JPN, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::JA_JPN, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LIGHTHOUSE },
// Lighthouse - Spanish DOS (from jvprat)
// Executable scanning reports "3.000.000", VERSION file reports "1.1"
@@ -2476,7 +2731,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.002", 0, "e7dc85884a2417e2eff9de0c63dd65fa", 7630},
{"ressci.002", 0, "3c8d627c555b0e3e4f1d9955bc0f0df4", 94631127},
AD_LISTEND},
- Common::ES_ESP, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::ES_ESP, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LIGHTHOUSE },
#endif // ENABLE_SCI3_GAMES
#endif // ENABLE_SCI32
@@ -2598,13 +2853,19 @@ static const struct ADGameDescription SciGameDescriptions[] = {
Common::JA_JPN, Common::kPlatformFMTowns, ADGF_ADDENGLISH, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
+
+#define GUIO_MOTHERGOOSEHIRES GUIO4(GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+
// Mixed-Up Mother Goose Deluxe - English Windows/DOS CD (supplied by markcoolio in bug report #2723810)
// Executable scanning reports "2.100.002"
{"mothergoosehires", "", {
{"resource.map", 0, "5159a1578c4306bfe070a3e4d8c2e1d3", 4741},
{"resource.000", 0, "1926925c95d82f0999590e93b02887c5", 15150768},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_MOTHERGOOSEHIRES },
// Mixed-Up Mother Goose Deluxe - Multilingual Windows CD (English/French/German/Spanish)
// Executable scanning reports "2.100.002"
@@ -2612,7 +2873,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "ef611af561898dcfea87846919ebf3eb", 4969},
{"ressci.000", 0, "227685bc59d90821978d330713e44a7a", 17205800},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_MOTHERGOOSEHIRES },
#endif // ENABLE_SCI32
// Ms. Astro Chicken - English DOS
@@ -2624,6 +2885,42 @@ static const struct ADGameDescription SciGameDescriptions[] = {
Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
+
+#define GUIO_PHANTASMAGORIA_DEMO GUIO6(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, \
+ GUIO_NOSPEECH, \
+ GUIO_NOASPECT, \
+ GUIO_NOLAUNCHLOAD, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_PHANTASMAGORIA GUIO_PHANTASMAGORIA_DEMO
+#define GUIO_PHANTASMAGORIA_MAC GUIO5(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, \
+ GUIO_NOASPECT, \
+ GUIO_NOLAUNCHLOAD, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_FB01_MIDI)
+
+ // Phantasmagoria - English DOS/Windows (from csnover)
+ // Windows executable scanning reports "2.100.002" - "Aug 06 1995"
+ // DOS executable scanning reports "2.100.002" - "May 24 1995"
+ // VERSION file reports "1.000.000"
+ {"phantasmagoria", "", {
+ {"resmap.001", 0, "43c395f312a190e67b90b2c1e93a79e2", 11518},
+ {"ressci.001", 0, "3aae6559aa1df273bc542d5ac6330d75", 65844612},
+ {"resmap.002", 0, "94f142cfe8ec4107b6a42876cb603ed0", 12058},
+ {"ressci.002", 0, "3aae6559aa1df273bc542d5ac6330d75", 71588691},
+ {"resmap.003", 0, "39e9abd4501b5b6168dd07379c0be753", 12334},
+ {"ressci.003", 0, "3aae6559aa1df273bc542d5ac6330d75", 73651084},
+ {"resmap.004", 0, "434f9704658229fef322c863d2422a9a", 12556},
+ {"ressci.004", 0, "3aae6559aa1df273bc542d5ac6330d75", 75811935},
+ {"resmap.005", 0, "3ff9b4f7301800825c0ed008e091205e", 12604},
+ {"ressci.005", 0, "3aae6559aa1df273bc542d5ac6330d75", 78814934},
+ {"resmap.006", 0, "27ad413313e2a3ec3c53250e7ff5b2d1", 12532},
+ {"ressci.006", 0, "3aae6559aa1df273bc542d5ac6330d75", 77901360},
+ {"resmap.007", 0, "aa8175cfc93242af6f5e65bdceaafc0d", 7972},
+ //{"ressci.007", 0, "3aae6559aa1df273bc542d5ac6330d75", 25859038},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
+
// Phantasmagoria - English DOS (from jvprat)
// Executable scanning reports "2.100.002", VERSION file reports "1.100.000UK"
{"phantasmagoria", "", {
@@ -2642,7 +2939,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.007", 0, "afbd16ea77869a720afa1c5371de107d", 7972},
//{"ressci.007", 0, "3aae6559aa1df273bc542d5ac6330d75", 25859038},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
// Phantasmagoria - German DOS/Windows
// Windows executable scanning reports "unknown" - "Sep 19 1995 09:39:48"
@@ -2665,7 +2962,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"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) },
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
// Phantasmagoria - French DOS
// Supplied by Kervala in bug #6574
@@ -2684,7 +2981,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"ressci.006", 0, "3aae6559aa1df273bc542d5ac6330d75", 85415107},
{"resmap.007", 0, "5633960bc106c39ca91d2d8fce18fd2d", 7984},
AD_LISTEND},
- Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
// Phantasmagoria - English DOS Demo
// Executable scanning reports "2.100.002"
@@ -2692,7 +2989,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.001", 0, "416138651ea828219ca454cae18341a3", 11518},
{"ressci.001", 0, "3aae6559aa1df273bc542d5ac6330d75", 65844612},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA_DEMO },
// Phantasmagoria - English DOS/Windows (GOG version) - ressci.* merged in ressci.000
// Windows executable scanning reports "2.100.002" - "Sep 19 1995 15:09:43"
@@ -2703,7 +3000,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"ressci.000", 0, "cd5967f9b9586e3380645961c0765be3", 116822037},
{"resmap.000", 0, "3cafc1c6a53945c1f3babbfd6380c64c", 16468},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
// Phantasmagoria - English Macintosh
// NOTE: This only contains disc 1 files (as well as the two persistent files:
@@ -2719,10 +3016,22 @@ static const struct ADGameDescription SciGameDescriptions[] = {
// Data8-12 are empty
{"Data13", 0, "6d2c450fca19a69b5af74ed5b03c0a17", 14923328},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_CD | ADGF_MACRESFORK | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA_MAC },
#ifdef ENABLE_SCI3_GAMES
- // Phantasmagoria 2 - English Windows (from jvprat)
+
+#define GUIO_PHANTASMAGORIA2 GUIO5(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, \
+ GUIO_NOSPEECH, \
+ GUIO_NOASPECT, \
+ GUIO_NOMIDI, \
+ GAMEOPTION_ORIGINAL_SAVELOAD)
+
+ // Some versions of Phantasmagoria 2 were heavily censored.
+ // Censored versions (data files are currently unknown to us): UK, Australia, first English release in Germany
+
+ // Phantasmagoria 2 - English Windows (from jvprat) - US release
+ // Note: Fully uncensored
+ //
// Executable scanning reports "3.000.000", VERSION file reports "001.0.06"
{"phantasmagoria2", "", {
{"resmap.001", 0, "0a961e135f4f7effb195158325856633", 1108},
@@ -2736,13 +3045,25 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.005", 0, "8bd5ceeedcbe16dfe55d1b90dcd4be84", 1942},
{"ressci.005", 0, "05f9fe2bee749659acb3cd2c90252fc5", 67905112},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_PHANTASMAGORIA2 },
+
+ // Phantasmagoria 2 - English DOS (GOG version) (supplied by littleboy in patch #1360)
+ // Note: Fully uncensored, basically the US release, but ressci.* merged into ressci.000
+ //
+ // Executable scanning reports "3.000.000" - "Dec 07 1996 09:29:03"
+ // VERSION file reports "001.0.06"
+ {"phantasmagoria2", "", {
+ {"ressci.000", 0, "c54f26d9f43f908151263254b6d97053", 108134481},
+ {"resmap.000", 0, "de154a223a9ef4ea7358b76adc38ef5b", 2956},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_PHANTASMAGORIA2 },
- // Phantasmagoria 2 - German DOS/Windows
+ // Phantasmagoria 2 - German DOS/Windows (supplied by AReim1982)
+ // Note: Fully uncensored, but one scene is missing probably because of a mastering error (Curtis + Therese meeting near water cooler)
+ //
// 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},
@@ -2755,17 +3076,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"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"
- // Supplied by littleboy in patch #3112884
- {"phantasmagoria2", "", {
- {"ressci.000", 0, "c54f26d9f43f908151263254b6d97053", 108134481},
- {"resmap.000", 0, "de154a223a9ef4ea7358b76adc38ef5b", 2956},
- AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_PHANTASMAGORIA2 },
#endif // ENABLE_SCI3_GAMES
#endif // ENABLE_SCI32
@@ -2852,6 +3163,17 @@ 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) },
+ // Police Quest 2 - English Atari ST
+ // Game version 1.002.011 DS 1989-07-21
+ // Executable reports "1.002.003"
+ {"pq2", "", {
+ {"resource.map", 0, "28a6f471c7900c2c92da40eecb615d9d", 4584},
+ {"resource.001", 0, "77f02def3094af804fd2371db25b7100", 509525},
+ {"resource.002", 0, "77f02def3094af804fd2371db25b7100", 546000},
+ {"resource.003", 0, "77f02def3094af804fd2371db25b7100", 591851},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Police Quest 2 - English DOS (from FRG)
// SCI interpreter version 0.000.395
{"pq2", "", {
@@ -2871,6 +3193,17 @@ 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) },
+ // Police Quest 2 - English Atari ST
+ // Game version 1.001.006 1989-01-16 13:30
+ // Executable reports "1.001.009"
+ {"pq2", "", {
+ {"resource.map", 0, "8e1161c684b342742d30f938a4839a4b", 4518},
+ {"resource.001", 0, "77f02def3094af804fd2371db25b7100", 506563},
+ {"resource.002", 0, "77f02def3094af804fd2371db25b7100", 541261},
+ {"resource.003", 0, "77f02def3094af804fd2371db25b7100", 587511},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Police Quest 2 - Japanese PC-98 (also includes english language)
// Executable scanning reports "x.yyy.zzz"
// SCI interpreter version unknown
@@ -2967,20 +3300,31 @@ static const struct ADGameDescription SciGameDescriptions[] = {
// Police Quest 4 - English DOS Non-Interactive Demo (from FRG)
// SCI interpreter version 1.001.096
- {"pq4", "Demo", {
+ // Note: we are not using ADGF_DEMO here, to avoid a game ID like pq4demo-demo
+ {"pq4demo", "Demo", {
{"resource.map", 0, "be56f87a1c4a13062a30a362df860c2f", 1472},
{"resource.000", 0, "527d5684016e6816157cd15d9071b11b", 1121310},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
+
+#define GUIO_PQ4_FLOPPY GUIO4(GUIO_NOSPEECH, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_PQ4_CD GUIO4(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+
// Police Quest 4 - English DOS CD (from the Police Quest Collection)
// Executable scanning reports "2.100.002", VERSION file reports "1.100.000"
{"pq4", "CD", {
{"resource.map", 0, "379dfe80ed6bd16c47e4b950c4722eac", 11374},
{"resource.000", 0, "fd316a09b628b7032248139003369022", 18841068},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PQ4_CD },
// Police Quest 4 - German DOS CD (German text, English speech)
// Supplied by markcoolio in bug report #3392955
@@ -2988,7 +3332,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "a398076371ed0e1e706c8f9fb9fc7ac5", 11386},
{"resource.000", 0, "6ff21954e0a2c5992279e7eb787c8d56", 18918747},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PQ4_CD },
// Police Quest 4 - English DOS
// SCI interpreter version 2.000.000 (a guess?)
@@ -2996,7 +3340,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "aed9643158ccf01b71f359db33137f82", 9895},
{"resource.000", 0, "da383857b3be1e4514daeba2524359e0", 15141432},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_PQ4_FLOPPY },
// Police Quest 4 - French DOS (supplied by abevi in bug report #2612718)
// SCI interpreter version 2.000.000
@@ -3004,7 +3348,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "008030846edcc7c5c7a812c7f4ae4ceb", 9256},
{"resource.000", 0, "6ba98bd2e436739d87ecd2a9b99cabb4", 14730153},
AD_LISTEND},
- Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_PQ4_FLOPPY },
// Police Quest 4 - German DOS (supplied by markcoolio in bug report #2723840)
// SCI interpreter version 2.000.000 (a guess?)
@@ -3012,7 +3356,18 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "2393ee728ab930b2762cb5889f9b5aff", 9256},
{"resource.000", 0, "6ba98bd2e436739d87ecd2a9b99cabb4", 14730155},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_PQ4_FLOPPY },
+
+#define GUIO_PQSWAT_DEMO GUIO5(GUIO_NOSPEECH, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_PQSWAT GUIO5(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
// Police Quest: SWAT - English DOS/Windows Demo (from jvprat)
// Executable scanning reports "2.100.002", VERSION file reports "0.001.200"
@@ -3020,7 +3375,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "8c96733ef94c21526792f7ca4e3f2120", 1648},
{"resource.000", 0, "d8892f1b8c56c8f7704325460f49b300", 3676175},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO_PQSWAT_DEMO },
// Police Quest: SWAT - English DOS (from GOG.com)
// Executable scanning reports "2.100.002", VERSION file reports "1.0c"
@@ -3028,7 +3383,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "1c2563fee189885e29d9348f37306d94", 12175},
{"ressci.000", 0, "b2e1826ca81ce2e7e764587f5a14eee9", 127149181},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PQSWAT },
// Police Quest: SWAT - English Windows (from the Police Quest Collection)
// Executable scanning reports "2.100.002", VERSION file reports "1.0c"
@@ -3043,7 +3398,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.004", 0, "4228038906f041623e65789500b22285", 6835},
{"ressci.004", 0, "b7e619e6ecf62fe65d5116a3a422e5f0", 46223872},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_CD | ADGF_UNSTABLE, GUIO_PQSWAT },
#endif // ENABLE_SCI32
// Quest for Glory 1 / Hero's Quest - English DOS 3.5" Floppy (supplied by merkur in bug report #2718784)
@@ -3058,7 +3413,7 @@ 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) },
- // Quest for Glory 1 / Hero's Quest - English DOS 3.5" Floppy (supplied by alonzotg in bug report #3206006)
+ // Quest for Glory 1 / Hero's Quest - English DOS 3.5" Floppy v1.001 Int#0.000.566 (supplied by alonzotg in bug report #3206006)
{"qfg1", "", {
{"resource.map", 0, "85512508ed4e4ef1e3b309adabceeda9", 6486},
{"resource.000", 0, "481b034132106390cb5160fe61dd5f58", 80334},
@@ -3139,6 +3494,19 @@ 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) },
+ // Quest for Glory 1 / Hero's Quest - English Atari ST
+ // Game version 1.137
+ // Executable reports "1.002.028"
+ {"qfg1", "", {
+ {"resource.map", 0, "2a794066ad161acbedac8fa14e46905d", 6438},
+ {"resource.000", 0, "40332d3ebfc70a4b6a6a0443c2763287", 79204},
+ {"resource.001", 0, "f7fc269d3db146830d6427d3e02d4187", 473547},
+ {"resource.002", 0, "e64004e020fdf1813be52b639b08be89", 635687},
+ {"resource.003", 0, "f0af87c60ec869946da442833aa5afa8", 640438},
+ {"resource.004", 0, "f0af87c60ec869946da442833aa5afa8", 644452},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Quest for Glory 1 / Hero's Quest - English DOS Demo
// Executable scanning reports "0.000.685"
{"qfg1", "Demo", {
@@ -3218,6 +3586,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
// Quest for Glory 2 - English Amiga
+ // Game version 1.109
// Executable scanning reports "1.003.004"
// SCI interpreter version 0.001.010
{"qfg2", "", {
@@ -3379,20 +3748,30 @@ static const struct ADGameDescription SciGameDescriptions[] = {
// Quest for Glory 4 - English DOS Non-Interactive Demo (from FRG)
// SCI interpreter version 1.001.069 (just a guess)
- {"qfg4", "Demo", {
+ // Note: we are not using ADGF_DEMO here, to avoid a game ID like qfg4demo-demo
+ {"qfg4demo", "Demo", {
{"resource.map", 0, "1ba7c7ae1efb315326d45cb931569b1b", 922},
{"resource.000", 0, "41ba03f0b188b029132daa3ece0d3e14", 623154},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
+
+#define GUIO_QFG4_FLOPPY GUIO4(GUIO_NOSPEECH, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_QFG4_CD GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+
// Quest for Glory 4 1.1 Floppy - English DOS (supplied by markcool in bug report #2723852)
// SCI interpreter version 2.000.000 (a guess?)
{"qfg4", "", {
{"resource.map", 0, "685bdb1ed47bbbb0e5e25db392da83ce", 9301},
{"resource.000", 0, "f64fd6aa3977939a86ff30783dd677e1", 11004993},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_QFG4_FLOPPY },
// Quest for Glory 4 1.1 Floppy - English DOS (supplied by abevi in bug report #2612718)
// SCI interpreter version 2.000.000
@@ -3400,7 +3779,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "d10a4cc177d2091d744e2ad8c049b0ae", 9295},
{"resource.000", 0, "f64fd6aa3977939a86ff30783dd677e1", 11003589},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_QFG4_FLOPPY },
// Quest for Glory 4 1.1 Floppy - German DOS (supplied by markcool in bug report #2723850)
// Executable scanning reports "2.000.000", VERSION file reports "1.1"
@@ -3408,7 +3787,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "9e0abba8746f40565bc7eb5720522ecd", 9301},
{"resource.000", 0, "57f22cdc54eeb35fce1f26b31b5c3ee1", 11076197},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_QFG4_FLOPPY },
// Quest for Glory 4 CD - English DOS/Windows (from jvprat)
// Executable scanning reports "2.100.002", VERSION file reports "1.0"
@@ -3416,7 +3795,22 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "aba367f2102e81782d961b14fbe3d630", 10246},
{"resource.000", 0, "263dce4aa34c49d3ad29bec889007b1c", 11571394},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_QFG4_CD },
+
+ // Quest for Glory 4 CD - English DOS/Windows (from jvprat)
+ // Executable scanning reports "2.100.002", VERSION file reports "1.0"
+ {"qfg4", "CD", {
+ {"resource.map", 0, "aba367f2102e81782d961b14fbe3d630", 10246},
+ {"resource.000", 0, "263dce4aa34c49d3ad29bec889007b1c", 11571394},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_CD | ADGF_UNSTABLE, GUIO_QFG4_CD },
+
+#define GUIO_RAMA_DEMO GUIO5(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_RAMA GUIO_RAMA_DEMO
// RAMA - English DOS/Windows Demo
// Executable scanning reports "2.100.002", VERSION file reports "000.000.008"
@@ -3424,7 +3818,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.001", 0, "775304e9b2a545156be4d94209550094", 1393},
{"ressci.001", 0, "259437fd75fdf51e8207fda8c01fa4fd", 2334384},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, GUIO_RAMA_DEMO },
#ifdef ENABLE_SCI3_GAMES
// RAMA - English Windows (from jvprat)
@@ -3437,7 +3831,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.003", 0, "31ef4c0621711585d031f0ae81707251", 1636},
{"ressci.003", 0, "2a68edd064e5e4937b5e9c74b38f2082", 6860492},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_RAMA },
// RAMA - English Windows (from Quietust, in bug report #2850645)
{"rama", "", {
@@ -3448,7 +3842,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.003", 0, "48841e4b84ef1b98b48d43566fda9e13", 1636},
{"ressci.003", 0, "2a68edd064e5e4937b5e9c74b38f2082", 6870356},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_RAMA },
// RAMA - German Windows CD (from farmboy0, in pull request 397)
{"rama", "", {
@@ -3459,7 +3853,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.003", 0, "222096000bd83a1d56577114a452cccf", 1636},
{"ressci.003", 0, "2a68edd064e5e4937b5e9c74b38f2082", 6954219},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_RAMA },
// RAMA - Italian Windows CD (from glorifindel)
// SCI interpreter version 3.000.000 (a guess?)
@@ -3467,23 +3861,29 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"ressci.001", 0, "2a68edd064e5e4937b5e9c74b38f2082", 70611091},
{"resmap.001", 0, "70ba2ff04a2b7fb2c52420ba7fbd47c2", 8338},
AD_LISTEND},
- Common::IT_ITA, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::IT_ITA, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_RAMA },
#endif // ENABLE_SCI3_GAMES
+#define GUIO_SHIVERS_DEMO GUIO4(GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_SHIVERS GUIO_SHIVERS_DEMO
+
// Shivers - English Windows (from jvprat)
// Executable scanning reports "2.100.002", VERSION file reports "1.02"
{"shivers", "", {
{"resmap.000", 0, "f2ead37749ed8f6535a2445a7d05a0cc", 46525},
{"ressci.000", 0, "4294c6d7510935f2e0a52e302073c951", 262654836},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_SHIVERS },
// Shivers - German Windows (from Tobis87)
{"shivers", "", {
{"resmap.000", 0, "f483d0a1f78334c18052e92785c3086e", 46537},
{"ressci.000", 0, "6751b144671e2deed919eb9d284b07eb", 262390692},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_SHIVERS },
// Shivers - English Windows Demo
// Executable scanning reports "2.100.002"
@@ -3491,7 +3891,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "d9e0bc5eddefcbe47f528760085d8927", 1186},
{"ressci.000", 0, "3a93c6340b54e07e65d0e5583354d186", 10505469},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, GUIO_SHIVERS },
// Shivers 2 doesn't contain SCI scripts. The whole game logic has
// been reimplemented from SCI in native code placed in DLL files.
@@ -3509,7 +3909,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "d8659188b84beaef076bd869837cd530", 634},
{"ressci.000", 0, "7fbac0807a044c9543e8ac376d200e59", 4925003},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, GUIO5(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
// Shivers 2 - English Windows (from abevi)
// VERSION.TXT Version 1.0 (3/25/97)
@@ -3517,7 +3917,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"ressci.001", 0, "a79d03d6eb75be0a79324f14e3d2ace4", 95346793},
{"resmap.001", 0, "a4804d436d90c4ec2e46b537f5e954db", 6268},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO6(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#endif
@@ -3639,6 +4039,18 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ // Space Quest 3 - English Atari ST
+ // Game version 1.0Q 1989-27-03 17:00
+ // Int#1.002.002
+ // Executable reports "1.002.001"
+ {"sq3", "", {
+ {"resource.map", 0, "c36e322805949affd882a75803a6a54e", 5484},
+ {"resource.001", 0, "ceeda7202b96e5c85ecaa88a40a540fc", 485146},
+ {"resource.002", 0, "ceeda7202b96e5c85ecaa88a40a540fc", 720227},
+ {"resource.003", 0, "ceeda7202b96e5c85ecaa88a40a540fc", 688524},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformAtariST, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Space Quest 3 - German Amiga (also includes english language)
// Executable scanning reports "1.004.006"
// SCI interpreter version 0.000.453 (just a guess)
@@ -3680,6 +4092,20 @@ 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) },
+ // Space Quest 3 - English DOS (from telanus, bug report Trac#9626)
+ // Game version 1.0P 1989-03-22
+ // SCI interpreter version 0.000.453
+ {"sq3", "", {
+ {"resource.map", 0, "6dd8f59dd8a0c8131f34b159044e645e", 5598},
+ {"resource.001", 0, "ceeda7202b96e5c85ecaa88a40a540fc", 170496},
+ {"resource.002", 0, "ceeda7202b96e5c85ecaa88a40a540fc", 312537},
+ {"resource.003", 0, "ceeda7202b96e5c85ecaa88a40a540fc", 325576},
+ {"resource.004", 0, "ceeda7202b96e5c85ecaa88a40a540fc", 321377},
+ {"resource.005", 0, "ceeda7202b96e5c85ecaa88a40a540fc", 328162},
+ {"resource.006", 0, "ceeda7202b96e5c85ecaa88a40a540fc", 356560},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+
// Space Quest 3 - English DOS (from abevi, bug report #2612718)
{"sq3", "", {
{"resource.map", 0, "eca165515c6b62b05fa86b7d8f727660", 5598},
@@ -4037,13 +4463,25 @@ static const struct ADGameDescription SciGameDescriptions[] = {
Common::RU_RUS, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
+
+#define GUIO_SQ6_DEMO GUIO5(GUIO_NOSPEECH, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+#define GUIO_SQ6 GUIO5(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, \
+ GUIO_NOASPECT, \
+ GAMEOPTION_PREFER_DIGITAL_SFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
+ GAMEOPTION_FB01_MIDI)
+
// Space Quest 6 - English DOS/Win3.11 CD (from the Space Quest Collection)
// Executable scanning reports "2.100.002", VERSION file reports "1.0"
{"sq6", "", {
{"resource.map", 0, "6dddfa3a8f3a3a513ec9dfdfae955005", 10528},
{"resource.000", 0, "c4259ab7355aead07773397b1052827d", 41150806},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_SQ6 },
// Space Quest 6 - English DOS/Win3.11 CD ver 1.11 (from FRG)
// SCI interpreter version 2.100.002 (just a guess)
@@ -4051,7 +4489,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "e0615d6e4e10e37ae42e6a2a95aaf145", 10528},
{"resource.000", 0, "c4259ab7355aead07773397b1052827d", 41150806},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_SQ6 },
// Space Quest 6 - French DOS/Win3.11 CD (from French magazine Joystick - September 1997)
// Executable scanning reports "2.100.002", VERSION file reports "1.0"
@@ -4059,7 +4497,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "3c831625931d5079b73ae8c275f52c95", 10534},
{"resource.000", 0, "4195ca940f759424f62b90e262cc1737", 40932397},
AD_LISTEND},
- Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_SQ6 },
// Space Quest 6 - German DOS (from Tobis87, updated info from markcoolio in bug report #2723884)
// SCI interpreter version 2.100.002 (just a guess)
@@ -4067,7 +4505,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "664d797415484f85c90b1b45aedc7686", 10534},
{"resource.000", 0, "ba87ba91e5bdabb4169dd0df75777722", 40933685},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_SQ6 },
// Space Quest 6 - English DOS/Win3.11 Interactive Demo (from FRG)
// SCI interpreter version 2.100.002 (just a guess)
@@ -4075,7 +4513,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "368f07b07433db3f819fa3fa0e5efee5", 2572},
{"resource.000", 0, "ab12724e078dea34b624e0d2a38dcd7c", 2272050},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO_SQ6_DEMO },
#endif // ENABLE_SCI32
// The Island of Dr. Brain - English DOS CD (from jvprat)
@@ -4103,13 +4541,23 @@ static const struct ADGameDescription SciGameDescriptions[] = {
Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
+
+#define GUIO_TORIN_DEMO GUIO3(GUIO_NOASPECT, \
+ GUIO_NOMIDI, \
+ GAMEOPTION_ORIGINAL_SAVELOAD)
+#define GUIO_TORIN GUIO4(GAMEOPTION_ENABLE_BLACK_LINED_VIDEO, \
+ GUIO_NOASPECT, \
+ GUIO_NOMIDI, \
+ GAMEOPTION_ORIGINAL_SAVELOAD)
+#define GUIO_TORIN_MAC GUIO_TORIN
+
// Torin's Passage - English Windows Interactive Demo
// SCI interpreter version 2.100.002
{"torin", "Demo", {
{"resmap.000", 0, "9a3e172cde9963d0a969f26469318cec", 3403},
{"ressci.000", 0, "db3e290481c35c3224e9602e71e4a1f1", 5073868},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE | ADGF_CD, GUIO_TORIN_DEMO },
// Torin's Passage (Multilingual) - English Windows CD
// SCI interpreter version 2.100.002
@@ -4117,7 +4565,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "bb3b0b22ff08df54fbe2d06263409be6", 9799},
{"ressci.000", 0, "693a259d346c9360f4a0c11fdaae430a", 55973887},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_TORIN },
// Torin's Passage (Multilingual) - Spanish Windows CD (from jvprat)
// Executable scanning reports "2.100.002", VERSION file reports "1.0"
@@ -4126,7 +4574,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"ressci.000", 0, "693a259d346c9360f4a0c11fdaae430a", 55973887},
// TODO: depend on one of the patches?
AD_LISTEND},
- Common::ES_ESP, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::ES_ESP, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_TORIN },
// Torin's Passage (Multilingual) - French Windows CD
// SCI interpreter version 2.100.002
@@ -4134,7 +4582,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "bb3b0b22ff08df54fbe2d06263409be6", 9799},
{"ressci.000", 0, "693a259d346c9360f4a0c11fdaae430a", 55973887},
AD_LISTEND},
- Common::FR_FRA, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::FR_FRA, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_TORIN },
// Torin's Passage - German Windows CD (from m_kiewitz)
// SCI interpreter version 2.100.002
@@ -4143,7 +4591,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "e55c3097329b3c53752301e01c6af2fb", 9787},
{"ressci.000", 0, "118f9bec04bfe17c4f87bbb5ddb43c18", 56127540},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_TORIN },
// Torin's Passage (Multilingual) - German Windows CD
// SCI interpreter version 2.100.002
@@ -4151,7 +4599,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "bb3b0b22ff08df54fbe2d06263409be6", 9799},
{"ressci.000", 0, "693a259d346c9360f4a0c11fdaae430a", 55973887},
AD_LISTEND},
- Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_TORIN },
// Torin's Passage (Multilingual) - Italian Windows CD (from glorifindel)
// SCI interpreter version 2.100.002
@@ -4159,7 +4607,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "bb3b0b22ff08df54fbe2d06263409be6", 9799},
{"ressci.000", 0, "693a259d346c9360f4a0c11fdaae430a", 55973887},
AD_LISTEND},
- Common::IT_ITA, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::IT_ITA, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_TORIN },
// Torin's Passage - French Windows (from LePhilousophe)
// SCI interpreter version 2.100.002
@@ -4167,7 +4615,17 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resmap.000", 0, "66ed46e3e56f487e688d52f05b33d0ba", 9787},
{"ressci.000", 0, "118f9bec04bfe17c4f87bbb5ddb43c18", 56126981},
AD_LISTEND},
- Common::FR_FRA, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::FR_FRA, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_TORIN },
+
+ // Torin's Passage - Russian Windows CD (SoftClub official translate)
+ // SCI interpreter version 2.100.002
+ // VERSION file "1.0"
+ { "torin", "",{
+ { "resource.aud", 0, "f66df699be5ed011b16b3f816cee8a04", 210583510 },
+ { "ressci.000", 0, "e672da099fb1663b87c78abc6c8ba2a4", 130622695 },
+ { "resmap.000", 0, "643859f8f2be8e7701611e29b3b65208", 9799 },
+ AD_LISTEND },
+ Common::RU_RUS, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO_TORIN },
// Torin's Passage - English Macintosh
{"torin", "", {
@@ -4179,7 +4637,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"Data6", 0, "b639487c83d1dae0e001e700f3631566", 7594881},
{"Data7", 0, "2afd9b5434102b89610916b904c3f73a", 7627374},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_UNSTABLE | ADGF_CD, GUIO_TORIN_MAC },
#endif // ENABLE_SCI32
// SCI Fanmade Games
diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp
index a993506f7a..e37a1651ef 100644
--- a/engines/sci/engine/features.cpp
+++ b/engines/sci/engine/features.cpp
@@ -45,6 +45,7 @@ GameFeatures::GameFeatures(SegManager *segMan, Kernel *kernel) : _segMan(segMan)
if (!ConfMan.getBool("use_cdaudio"))
_usesCdTrack = false;
_forceDOSTracks = false;
+ _pseudoMouseAbility = kPseudoMouseAbilityUninitialized;
}
reg_t GameFeatures::getDetectionAddr(const Common::String &objName, Selector slc, int methodNum) {
@@ -605,4 +606,50 @@ bool GameFeatures::useAltWinGMSound() {
}
}
+// PseudoMouse was added during SCI1
+// PseudoMouseAbility is about a tiny difference in the keyboard driver, which sets the event type to either
+// 40h (old behaviour) or 44h (the keyboard driver actually added 40h to the existing value).
+// See engine/kevent.cpp, kMapKeyToDir - also script 933
+
+// SCI1EGA:
+// Quest for Glory 2 still used the old way.
+//
+// SCI1EARLY:
+// King's Quest 5 0.000.062 uses the old way.
+// Leisure Suit Larry 1 demo uses the new way, but no PseudoMouse class.
+// Fairy Tales uses the new way.
+// X-Mas 1990 uses the old way, no PseudoMouse class.
+// Space Quest 4 floppy (1.1) uses the new way.
+// Mixed Up Mother Goose uses the old way, no PseudoMouse class.
+//
+// SCI1MIDDLE:
+// Leisure Suit Larry 5 demo uses the new way.
+// Conquests of the Longbow demo uses the new way.
+// Leisure Suit Larry 1 (2.0) uses the new way.
+// Astro Chicken II uses the new way.
+PseudoMouseAbilityType GameFeatures::detectPseudoMouseAbility() {
+ if (_pseudoMouseAbility == kPseudoMouseAbilityUninitialized) {
+ if (getSciVersion() < SCI_VERSION_1_EARLY) {
+ // SCI1 EGA or earlier -> pseudo mouse ability is always disabled
+ _pseudoMouseAbility = kPseudoMouseAbilityFalse;
+
+ } else if (getSciVersion() == SCI_VERSION_1_EARLY) {
+ // For SCI1 early some games had it enabled, some others didn't.
+ // We try to find an object called "PseudoMouse". If it's found, we enable the ability otherwise we don't.
+ reg_t pseudoMouseAddr = _segMan->findObjectByName("PseudoMouse", 0);
+
+ if (pseudoMouseAddr != NULL_REG) {
+ _pseudoMouseAbility = kPseudoMouseAbilityTrue;
+ } else {
+ _pseudoMouseAbility = kPseudoMouseAbilityFalse;
+ }
+
+ } else {
+ // SCI1 middle or later -> pseudo mouse ability is always enabled
+ _pseudoMouseAbility = kPseudoMouseAbilityTrue;
+ }
+ }
+ return _pseudoMouseAbility;
+}
+
} // End of namespace Sci
diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h
index 1c410267e6..ee978be033 100644
--- a/engines/sci/engine/features.h
+++ b/engines/sci/engine/features.h
@@ -34,6 +34,12 @@ enum MoveCountType {
kIncrementMoveCount
};
+enum PseudoMouseAbilityType {
+ kPseudoMouseAbilityUninitialized,
+ kPseudoMouseAbilityFalse,
+ kPseudoMouseAbilityTrue
+};
+
class GameFeatures {
public:
GameFeatures(SegManager *segMan, Kernel *kernel);
@@ -76,6 +82,56 @@ public:
* @return Graphics functions type, SCI_VERSION_2 / SCI_VERSION_2_1
*/
SciVersion detectSci21KernelType();
+
+ inline bool usesModifiedAudioAttenuation() const {
+ switch (g_sci->getGameId()) {
+ // Assuming MGDX uses modified attenuation since SQ6 does and it was
+ // released earlier, but not verified (Phar Lap Windows-only release)
+ case GID_MOTHERGOOSEHIRES:
+ case GID_PQ4:
+ case GID_SQ6:
+ return true;
+ case GID_KQ7:
+ case GID_QFG4:
+ // (1) KQ7 1.51 (SCI2.1early) uses the non-standard attenuation, but
+ // 2.00b (SCI2.1mid) does not
+ // (2) QFG4 CD is SCI2.1early; QFG4 floppy is SCI2 and does not use
+ // the SCI2.1 audio system
+ return getSciVersion() == SCI_VERSION_2_1_EARLY;
+ default:
+ return false;
+ }
+ }
+
+ inline bool hasTransparentPicturePlanes() const {
+ const SciGameId &gid = g_sci->getGameId();
+
+ // NOTE: MGDX is assumed to not have transparent picture planes since it
+ // was released before SQ6, but this has not been verified since it
+ // cannot be disassembled at the moment (Phar Lap Windows-only release)
+ return getSciVersion() >= SCI_VERSION_2_1_MIDDLE &&
+ gid != GID_SQ6 &&
+ gid != GID_MOTHERGOOSEHIRES;
+ }
+
+ inline bool hasNewPaletteCode() const {
+ return getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7;
+ }
+
+ inline bool VMDOpenStopsAudio() const {
+ // Of the games that use VMDs:
+ // Yes: Phant1, Shivers, Torin
+ // No: SQ6
+ // TODO: Optional extra flag to kPlayVMD which defaults to Yes: PQ:SWAT
+ // TODO: SCI3, GK2 (GK2's VMD code is closer to SCI3 than SCI21)
+ return getSciVersion() == SCI_VERSION_2_1_MIDDLE &&
+ g_sci->getGameId() != GID_SQ6 &&
+ g_sci->getGameId() != GID_GK2;
+ }
+
+ inline bool usesAlternateSelectors() const {
+ return g_sci->getGameId() == GID_PHANTASMAGORIA2;
+ }
#endif
/**
@@ -110,6 +166,12 @@ public:
*/
void forceDOSTracks() { _forceDOSTracks = true; }
+ /**
+ * Autodetects, if Pseudo Mouse ability is enabled (different behavior in keyboard driver)
+ * @return kPseudoMouseAbilityTrue or kPseudoMouseAbilityFalse
+ */
+ PseudoMouseAbilityType detectPseudoMouseAbility();
+
private:
reg_t getDetectionAddr(const Common::String &objName, Selector slc, int methodNum = -1);
@@ -130,6 +192,8 @@ private:
bool _usesCdTrack;
bool _forceDOSTracks;
+ PseudoMouseAbilityType _pseudoMouseAbility;
+
SegManager *_segMan;
Kernel *_kernel;
};
diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index 0b1001bfda..da1d00f3bf 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -22,6 +22,7 @@
#include "common/savefile.h"
#include "common/stream.h"
+#include "common/memstream.h"
#include "sci/sci.h"
#include "sci/engine/file.h"
@@ -32,6 +33,118 @@
namespace Sci {
+#ifdef ENABLE_SCI32
+/**
+ * A MemoryWriteStreamDynamic with additional read functionality.
+ * The read and write functions share a single stream position.
+ */
+class MemoryDynamicRWStream : public Common::MemoryWriteStreamDynamic, public Common::SeekableReadStream {
+protected:
+ bool _eos;
+public:
+ MemoryDynamicRWStream(DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) : MemoryWriteStreamDynamic(disposeMemory), _eos(false) { }
+
+ uint32 read(void *dataPtr, uint32 dataSize);
+
+ bool eos() const { return _eos; }
+ int32 pos() const { return _pos; }
+ int32 size() const { return _size; }
+ void clearErr() { _eos = false; Common::MemoryWriteStreamDynamic::clearErr(); }
+ bool seek(int32 offs, int whence = SEEK_SET) { return Common::MemoryWriteStreamDynamic::seek(offs, whence); }
+
+};
+
+uint32 MemoryDynamicRWStream::read(void *dataPtr, uint32 dataSize)
+{
+ // Read at most as many bytes as are still available...
+ if (dataSize > _size - _pos) {
+ dataSize = _size - _pos;
+ _eos = true;
+ }
+ memcpy(dataPtr, _ptr, dataSize);
+
+ _ptr += dataSize;
+ _pos += dataSize;
+
+ return dataSize;
+}
+
+/**
+ * A MemoryDynamicRWStream intended to re-write a file.
+ * It reads the contents of `inFile` in the constructor, and writes back
+ * the changes to `fileName` in the destructor (and when calling commit() ).
+ */
+class SaveFileRewriteStream : public MemoryDynamicRWStream {
+public:
+ SaveFileRewriteStream(Common::String fileName,
+ Common::SeekableReadStream *inFile,
+ kFileOpenMode mode, bool compress);
+ virtual ~SaveFileRewriteStream();
+
+ virtual uint32 write(const void *dataPtr, uint32 dataSize) { _changed = true; return MemoryDynamicRWStream::write(dataPtr, dataSize); }
+
+ void commit(); //< Save back to disk
+
+protected:
+ Common::String _fileName;
+ bool _compress;
+ bool _changed;
+};
+
+SaveFileRewriteStream::SaveFileRewriteStream(Common::String fileName,
+ Common::SeekableReadStream *inFile,
+ kFileOpenMode mode,
+ bool compress)
+: MemoryDynamicRWStream(DisposeAfterUse::YES),
+ _fileName(fileName), _compress(compress)
+{
+ const bool truncate = mode == _K_FILE_MODE_CREATE;
+ const bool seekToEnd = mode == _K_FILE_MODE_OPEN_OR_CREATE;
+
+ if (!truncate && inFile) {
+ unsigned int s = inFile->size();
+ ensureCapacity(s);
+ inFile->read(_data, s);
+ if (seekToEnd) {
+ seek(0, SEEK_END);
+ }
+ _changed = false;
+ } else {
+ _changed = true;
+ }
+}
+
+SaveFileRewriteStream::~SaveFileRewriteStream() {
+ commit();
+}
+
+void SaveFileRewriteStream::commit() {
+ // Write contents of buffer back to file
+
+ if (_changed) {
+ Common::WriteStream *outFile = g_sci->getSaveFileManager()->openForSaving(_fileName, _compress);
+ outFile->write(_data, _size);
+ delete outFile;
+ _changed = false;
+ }
+}
+
+#endif
+
+uint findFreeFileHandle(EngineState *s) {
+ // Find a free file handle
+ uint handle = 1; // Ignore _fileHandles[0]
+ while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen())
+ handle++;
+
+ if (handle == s->_fileHandles.size()) {
+ // Hit size limit => Allocate more space
+ s->_fileHandles.resize(s->_fileHandles.size() + 1);
+ }
+
+ return handle;
+}
+
/*
* Note on how file I/O is implemented: In ScummVM, one can not create/write
* arbitrary data files, simply because many of our target platforms do not
@@ -55,7 +168,7 @@ namespace Sci {
* for reading only.
*/
-reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool unwrapFilename) {
+reg_t file_open(EngineState *s, const Common::String &filename, kFileOpenMode mode, bool unwrapFilename) {
Common::String englishName = g_sci->getSciLanguageString(filename, K_LANG_ENGLISH);
englishName.toLowercase();
@@ -91,6 +204,31 @@ reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool u
break;
}
+#ifdef ENABLE_SCI32
+ if ((g_sci->getGameId() == GID_PHANTASMAGORIA && (filename == "phantsg.dir" || filename == "chase.dat" || filename == "tmp.dat")) ||
+ (g_sci->getGameId() == GID_PQSWAT && filename == "swat.dat")) {
+ debugC(kDebugLevelFile, " -> file_open opening %s for rewriting", wrappedName.c_str());
+
+ inFile = saveFileMan->openForLoading(wrappedName);
+ // If no matching savestate exists: fall back to reading from a regular
+ // file
+ if (!inFile)
+ inFile = SearchMan.createReadStreamForMember(englishName);
+
+ if (mode == _K_FILE_MODE_OPEN_OR_FAIL && !inFile) {
+ debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_OPEN_OR_FAIL): failed to open file '%s'", englishName.c_str());
+ return SIGNAL_REG;
+ }
+
+ SaveFileRewriteStream *stream;
+ stream = new SaveFileRewriteStream(wrappedName, inFile, mode, isCompressed);
+
+ delete inFile;
+
+ inFile = stream;
+ outFile = stream;
+ } else
+#endif
if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
// Try to open file, abort if not possible
inFile = saveFileMan->openForLoading(wrappedName);
@@ -126,15 +264,7 @@ reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool u
return SIGNAL_REG;
}
- // Find a free file handle
- uint handle = 1; // Ignore _fileHandles[0]
- while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen())
- handle++;
-
- if (handle == s->_fileHandles.size()) {
- // Hit size limit => Allocate more space
- s->_fileHandles.resize(s->_fileHandles.size() + 1);
- }
+ uint handle = findFreeFileHandle(s);
s->_fileHandles[handle]._in = inFile;
s->_fileHandles[handle]._out = outFile;
@@ -191,43 +321,65 @@ static bool _savegame_sort_byDate(const SavegameDesc &l, const SavegameDesc &r)
return (l.time > r.time);
}
-// Create a sorted array containing all found savedgames
-void listSavegames(Common::Array<SavegameDesc> &saves) {
+bool fillSavegameDesc(const Common::String &filename, SavegameDesc *desc) {
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+ Common::SeekableReadStream *in;
+ if ((in = saveFileMan->openForLoading(filename)) == nullptr) {
+ return false;
+ }
+
+ SavegameMetadata meta;
+ if (!get_savegame_metadata(in, &meta) || meta.name.empty()) {
+ // invalid
+ delete in;
+ return false;
+ }
+ delete in;
+
+ const int id = strtol(filename.end() - 3, NULL, 10);
+ desc->id = id;
+ desc->date = meta.saveDate;
+ // We need to fix date in here, because we save DDMMYYYY instead of
+ // YYYYMMDD, so sorting wouldn't work
+ desc->date = ((desc->date & 0xFFFF) << 16) | ((desc->date & 0xFF0000) >> 8) | ((desc->date & 0xFF000000) >> 24);
+ desc->time = meta.saveTime;
+ desc->version = meta.version;
+ desc->gameVersion = meta.gameVersion;
+#ifdef ENABLE_SCI32
+ if (g_sci->getGameId() == GID_SHIVERS) {
+ desc->lowScore = meta.lowScore;
+ desc->highScore = meta.highScore;
+ } else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
+ desc->avatarId = meta.avatarId;
+ }
+#endif
+
+ if (meta.name.lastChar() == '\n')
+ meta.name.deleteLastChar();
+
+ Common::strlcpy(desc->name, meta.name.c_str(), SCI_MAX_SAVENAME_LENGTH);
- // Load all saves
+ return desc;
+}
+
+// Create an array containing all found savedgames, sorted by creation date
+void listSavegames(Common::Array<SavegameDesc> &saves) {
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern());
for (Common::StringArray::const_iterator iter = saveNames.begin(); iter != saveNames.end(); ++iter) {
- Common::String filename = *iter;
- Common::SeekableReadStream *in;
- if ((in = saveFileMan->openForLoading(filename))) {
- SavegameMetadata meta;
- if (!get_savegame_metadata(in, &meta) || meta.name.empty()) {
- // invalid
- delete in;
- continue;
- }
- delete in;
-
- SavegameDesc desc;
- desc.id = strtol(filename.end() - 3, NULL, 10);
- desc.date = meta.saveDate;
- // We need to fix date in here, because we save DDMMYYYY instead of
- // YYYYMMDD, so sorting wouldn't work
- desc.date = ((desc.date & 0xFFFF) << 16) | ((desc.date & 0xFF0000) >> 8) | ((desc.date & 0xFF000000) >> 24);
- desc.time = meta.saveTime;
- desc.version = meta.version;
-
- if (meta.name.lastChar() == '\n')
- meta.name.deleteLastChar();
-
- Common::strlcpy(desc.name, meta.name.c_str(), SCI_MAX_SAVENAME_LENGTH);
-
- debug(3, "Savegame in file %s ok, id %d", filename.c_str(), desc.id);
-
- saves.push_back(desc);
+ const Common::String &filename = *iter;
+
+#ifdef ENABLE_SCI32
+ const int id = strtol(filename.end() - 3, NULL, 10);
+ if (id == kNewGameId || id == kAutoSaveId) {
+ continue;
}
+#endif
+
+ SavegameDesc desc;
+ fillSavegameDesc(filename, &desc);
+ saves.push_back(desc);
}
// Sort the list by creation date of the saves
@@ -252,8 +404,12 @@ FileHandle::~FileHandle() {
}
void FileHandle::close() {
- delete _in;
- delete _out;
+ // NB: It is possible _in and _out are both non-null, but
+ // then they point to the same object.
+ if (_in)
+ delete _in;
+ else
+ delete _out;
_in = 0;
_out = 0;
_name.clear();
@@ -365,119 +521,4 @@ reg_t DirSeeker::nextFile(SegManager *segMan) {
return _outbuffer;
}
-
-#ifdef ENABLE_SCI32
-
-VirtualIndexFile::VirtualIndexFile(Common::String fileName) : _fileName(fileName), _changed(false) {
- Common::SeekableReadStream *inFile = g_sci->getSaveFileManager()->openForLoading(fileName);
-
- _bufferSize = inFile->size();
- _buffer = new char[_bufferSize];
- inFile->read(_buffer, _bufferSize);
- _ptr = _buffer;
- delete inFile;
-}
-
-VirtualIndexFile::VirtualIndexFile(uint32 initialSize) : _changed(false) {
- _bufferSize = initialSize;
- _buffer = new char[_bufferSize];
- _ptr = _buffer;
-}
-
-VirtualIndexFile::~VirtualIndexFile() {
- close();
-
- _bufferSize = 0;
- delete[] _buffer;
- _buffer = 0;
-}
-
-uint32 VirtualIndexFile::read(char *buffer, uint32 size) {
- uint32 curPos = _ptr - _buffer;
- uint32 finalSize = MIN<uint32>(size, _bufferSize - curPos);
- char *localPtr = buffer;
-
- for (uint32 i = 0; i < finalSize; i++)
- *localPtr++ = *_ptr++;
-
- return finalSize;
-}
-
-uint32 VirtualIndexFile::write(const char *buffer, uint32 size) {
- _changed = true;
- uint32 curPos = _ptr - _buffer;
-
- // Check if the buffer needs to be resized
- if (curPos + size >= _bufferSize) {
- _bufferSize = curPos + size + 1;
- char *tmp = _buffer;
- _buffer = new char[_bufferSize];
- _ptr = _buffer + curPos;
- memcpy(_buffer, tmp, _bufferSize);
- delete[] tmp;
- }
-
- for (uint32 i = 0; i < size; i++)
- *_ptr++ = *buffer++;
-
- return size;
-}
-
-uint32 VirtualIndexFile::readLine(char *buffer, uint32 size) {
- uint32 startPos = _ptr - _buffer;
- uint32 bytesRead = 0;
- char *localPtr = buffer;
-
- // This is not a full-blown implementation of readLine, but it
- // suffices for Phantasmagoria
- while (startPos + bytesRead < size) {
- bytesRead++;
-
- if (*_ptr == 0 || *_ptr == 0x0A) {
- _ptr++;
- *localPtr = 0;
- return bytesRead;
- } else {
- *localPtr++ = *_ptr++;
- }
- }
-
- return bytesRead;
-}
-
-bool VirtualIndexFile::seek(int32 offset, int whence) {
- uint32 startPos = _ptr - _buffer;
- assert(offset >= 0);
-
- switch (whence) {
- case SEEK_CUR:
- assert(startPos + offset < _bufferSize);
- _ptr += offset;
- break;
- case SEEK_SET:
- assert(offset < (int32)_bufferSize);
- _ptr = _buffer + offset;
- break;
- case SEEK_END:
- assert((int32)_bufferSize - offset >= 0);
- _ptr = _buffer + (_bufferSize - offset);
- break;
- }
-
- return true;
-}
-
-void VirtualIndexFile::close() {
- if (_changed && !_fileName.empty()) {
- Common::WriteStream *outFile = g_sci->getSaveFileManager()->openForSaving(_fileName);
- outFile->write(_buffer, _bufferSize);
- delete outFile;
- }
-
- // Maintain the buffer, and seek to the beginning of it
- _ptr = _buffer;
-}
-
-#endif
-
} // End of namespace Sci
diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h
index 54627d5228..1c9a092063 100644
--- a/engines/sci/engine/file.h
+++ b/engines/sci/engine/file.h
@@ -28,22 +28,31 @@
namespace Sci {
-enum {
+enum kFileOpenMode {
_K_FILE_MODE_OPEN_OR_CREATE = 0,
_K_FILE_MODE_OPEN_OR_FAIL = 1,
_K_FILE_MODE_CREATE = 2
};
-/* Maximum length of a savegame name (including terminator character). */
-#define SCI_MAX_SAVENAME_LENGTH 0x24
+enum {
+ SCI_MAX_SAVENAME_LENGTH = 36, ///< Maximum length of a savegame name (including terminator character).
+ MAX_SAVEGAME_NR = 20 ///< Maximum number of savegames
+};
+#ifdef ENABLE_SCI32
enum {
- MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */
+ kAutoSaveId = 0, ///< The save game slot number for autosaves
+ kNewGameId = 999, ///< The save game slot number for a "new game" save
+
+ // SCI engine expects game IDs to start at 0, but slot 0 in ScummVM is
+ // reserved for autosave, so non-autosave games get their IDs shifted up
+ // when saving or restoring, and shifted down when enumerating save games
+ kSaveIdShift = 1
};
+#endif
#define VIRTUALFILE_HANDLE_START 32000
#define VIRTUALFILE_HANDLE_SCI32SAVE 32100
-#define PHANTASMAGORIA_SAVEGAME_INDEX "phantsg.dir"
#define VIRTUALFILE_HANDLE_SCIAUDIO 32300
#define VIRTUALFILE_HANDLE_END 32300
@@ -54,6 +63,14 @@ struct SavegameDesc {
int time;
int version;
char name[SCI_MAX_SAVENAME_LENGTH];
+ Common::String gameVersion;
+#ifdef ENABLE_SCI32
+ // Used by Shivers 1
+ uint16 lowScore;
+ uint16 highScore;
+ // Used by MGDX
+ uint8 avatarId;
+#endif
};
class FileHandle {
@@ -93,50 +110,7 @@ private:
void addAsVirtualFiles(Common::String title, Common::String fileMask);
};
-
-#ifdef ENABLE_SCI32
-
-/**
- * An implementation of a virtual file that supports basic read and write
- * operations simultaneously.
- *
- * This class has been initially implemented for Phantasmagoria, which has its
- * own custom save/load code. The load code keeps checking for the existence
- * of the save index file and keeps closing and reopening it for each save
- * slot. This is notoriously slow and clumsy, and introduces noticeable delays,
- * especially for non-desktop systems. Also, its game scripts request to open
- * the index file for reading and writing with the same parameters
- * (SaveManager::setCurrentSave and SaveManager::getCurrentSave). Moreover,
- * the game scripts reopen the index file for writing in order to update it
- * and seek within it. We do not support seeking in writeable streams, and the
- * fact that our saved games are ZIP files makes this operation even more
- * expensive. Finally, the savegame index file is supposed to be expanded when
- * a new save slot is added.
- * For the aforementioned reasons, this class has been implemented, which offers
- * the basic functionality needed by the game scripts in Phantasmagoria.
- */
-class VirtualIndexFile {
-public:
- VirtualIndexFile(Common::String fileName);
- VirtualIndexFile(uint32 initialSize);
- ~VirtualIndexFile();
-
- uint32 read(char *buffer, uint32 size);
- uint32 readLine(char *buffer, uint32 size);
- uint32 write(const char *buffer, uint32 size);
- bool seek(int32 offset, int whence);
- void close();
-
-private:
- char *_buffer;
- uint32 _bufferSize;
- char *_ptr;
-
- Common::String _fileName;
- bool _changed;
-};
-
-#endif
+uint findFreeFileHandle(EngineState *s);
} // End of namespace Sci
diff --git a/engines/sci/engine/gc.cpp b/engines/sci/engine/gc.cpp
index 70c8c52bf0..50f7709baf 100644
--- a/engines/sci/engine/gc.cpp
+++ b/engines/sci/engine/gc.cpp
@@ -24,6 +24,10 @@
#include "common/array.h"
#include "sci/graphics/ports.h"
+#ifdef ENABLE_SCI32
+#include "sci/graphics/controls32.h"
+#endif
+
namespace Sci {
//#define GC_DEBUG_CODE
@@ -42,7 +46,7 @@ const char *segmentTypeNames[] = {
"dynmem", // 9
"obsolete", // 10: obsolete string fragments
"array", // 11: SCI32 arrays
- "string" // 12: SCI32 strings
+ "obsolete" // 12: obsolete SCI32 strings
};
#endif
@@ -139,14 +143,29 @@ AddrSet *findAllActiveReferences(EngineState *s) {
const Common::Array<SegmentObj *> &heap = s->_segMan->getSegments();
uint heapSize = heap.size();
- // Init: Explicitly loaded scripts
for (uint i = 1; i < heapSize; i++) {
- if (heap[i] && heap[i]->getType() == SEG_TYPE_SCRIPT) {
- Script *script = (Script *)heap[i];
+ if (heap[i]) {
+ // Init: Explicitly loaded scripts
+ if (heap[i]->getType() == SEG_TYPE_SCRIPT) {
+ Script *script = (Script *)heap[i];
+
+ if (script->getLockers()) { // Explicitly loaded?
+ wm.pushArray(script->listObjectReferences());
+ }
+ }
+
+#ifdef ENABLE_SCI32
+ // Init: Explicitly opted-out bitmaps
+ else if (heap[i]->getType() == SEG_TYPE_BITMAP) {
+ BitmapTable *bt = static_cast<BitmapTable *>(heap[i]);
- if (script->getLockers()) { // Explicitly loaded?
- wm.pushArray(script->listObjectReferences());
+ for (uint j = 0; j < bt->_table.size(); j++) {
+ if (bt->_table[j].data && bt->_table[j].data->getShouldGC() == false) {
+ wm.push(make_reg(i, j));
+ }
+ }
}
+#endif
}
}
diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp
index 0df4701334..85cad99226 100644
--- a/engines/sci/engine/kernel.cpp
+++ b/engines/sci/engine/kernel.cpp
@@ -84,13 +84,19 @@ uint Kernel::getKernelNamesSize() const {
}
const Common::String &Kernel::getKernelName(uint number) const {
- // FIXME: The following check is a temporary workaround for an issue
- // leading to crashes when using the debugger's backtrace command.
- if (number >= _kernelNames.size())
- return _invalid;
+ assert(number < _kernelFuncs.size());
return _kernelNames[number];
}
+Common::String Kernel::getKernelName(uint number, uint subFunction) const {
+ assert(number < _kernelFuncs.size());
+ const KernelFunction &kernelCall = _kernelFuncs[number];
+
+ assert(subFunction < kernelCall.subFunctionCount);
+ return kernelCall.subFunctions[subFunction].name;
+}
+
+
int Kernel::findKernelFuncPos(Common::String kernelFuncName) {
for (uint32 i = 0; i < _kernelNames.size(); i++)
if (_kernelNames[i] == kernelFuncName)
@@ -165,8 +171,11 @@ void Kernel::loadSelectorNames() {
// (io) -> optionally integer AND an object
// (i) -> optional integer
// . -> any type
-// i* -> optional multiple integers
-// .* -> any parameters afterwards (or none)
+// i* -> at least one integer, more integers may follow after that
+// (i*) -> optional multiple integers
+// .* -> at least one parameter of any type and more parameters of any type may follow
+// (.*) -> any parameters afterwards (or none)
+// * -> means "more of the last parameter may follow (or none at all)", must be at the end of a signature. Is not valid anywhere else.
static uint16 *parseKernelSignature(const char *kernelName, const char *writtenSig) {
const char *curPos;
char curChar;
@@ -401,7 +410,7 @@ uint16 Kernel::findRegType(reg_t reg) {
case SEG_TYPE_HUNK:
#ifdef ENABLE_SCI32
case SEG_TYPE_ARRAY:
- case SEG_TYPE_STRING:
+ case SEG_TYPE_BITMAP:
#endif
result |= SIG_TYPE_REFERENCE;
break;
@@ -853,7 +862,7 @@ void Kernel::loadKernelNames(GameFeatures *features) {
_kernelNames[0x26] = "Portrait";
else if (g_sci->getPlatform() == Common::kPlatformMacintosh)
_kernelNames[0x84] = "ShowMovie";
- } else if (g_sci->getGameId() == GID_QFG4 && g_sci->isDemo()) {
+ } else if (g_sci->getGameId() == GID_QFG4DEMO) {
_kernelNames[0x7b] = "RemapColors"; // QFG4 Demo has this SCI2 function instead of StrSplit
}
@@ -879,8 +888,8 @@ void Kernel::loadKernelNames(GameFeatures *features) {
// how kDoSound is called from Sound::play().
// Known games that use this:
// GK2 demo
- // KQ7 1.4
- // PQ4 SWAT demo
+ // KQ7 1.4/1.51
+ // PQ:SWAT demo
// LSL6
// PQ4CD
// QFG4CD
@@ -891,7 +900,7 @@ void Kernel::loadKernelNames(GameFeatures *features) {
_kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesGk2Demo);
// OnMe is IsOnMe here, but they should be compatible
- _kernelNames[0x23] = "Robot"; // Graph in SCI2
+ _kernelNames[0x23] = g_sci->getGameId() == GID_LSL6HIRES ? "Empty" : "Robot"; // Graph in SCI2
_kernelNames[0x2e] = "Priority"; // DisposeTextBitmap in SCI2
} else {
// Normal SCI2.1 kernel table
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 92916ecc68..5fb1ab6c66 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -158,6 +158,7 @@ public:
uint getKernelNamesSize() const;
const Common::String &getKernelName(uint number) const;
+ Common::String getKernelName(uint number, uint subFunction) const;
/**
* Determines the selector ID of a selector by its name.
@@ -420,55 +421,167 @@ reg_t kStubNull(EngineState *s, int argc, reg_t *argv);
#ifdef ENABLE_SCI32
// SCI2 Kernel Functions
+reg_t kSetCursor32(EngineState *s, int argc, reg_t *argv);
+reg_t kSetNowSeen32(EngineState *s, int argc, reg_t *argv);
+reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv);
+reg_t kShakeScreen32(EngineState *s, int argc, reg_t *argv);
+reg_t kPlatform32(EngineState *s, int argc, reg_t *argv);
+reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv);
+reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv);
+
+reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioDistort(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv);
+
+reg_t kRobot(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotGetIsPlaying(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotClose(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotPause(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv);
+
+reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDOpen(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDInit(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDIgnorePalettes(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv);
+
+reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv);
+
+reg_t kSave(EngineState *s, int argc, reg_t *argv);
+reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv);
+reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv);
+reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv);
+reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv);
+reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv);
+reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv);
+
+reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv);
reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
-reg_t kArray(EngineState *s, int argc, reg_t *argv);
reg_t kListAt(EngineState *s, int argc, reg_t *argv);
-reg_t kString(EngineState *s, int argc, reg_t *argv);
+reg_t kArray(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayNew(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayGetSize(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayGetElement(EngineState *s, int argc, reg_t *argv);
+reg_t kArraySetElements(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayFree(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayFill(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayCopy(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayCompare(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayDuplicate(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayGetData(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayByteCopy(EngineState *s, int argc, reg_t *argv);
+
+reg_t kString(EngineState *s, int argc, reg_t *argv);
reg_t kStringNew(EngineState *s, int argc, reg_t *argv);
-reg_t kStringSize(EngineState *s, int argc, reg_t *argv);
-reg_t kStringAt(EngineState *s, int argc, reg_t *argv);
-reg_t kStringPutAt(EngineState *s, int argc, reg_t *argv);
+reg_t kStringGetChar(EngineState *s, int argc, reg_t *argv);
reg_t kStringFree(EngineState *s, int argc, reg_t *argv);
-reg_t kStringFill(EngineState *s, int argc, reg_t *argv);
-reg_t kStringCopy(EngineState *s, int argc, reg_t *argv);
reg_t kStringCompare(EngineState *s, int argc, reg_t *argv);
-reg_t kStringDup(EngineState *s, int argc, reg_t *argv);
reg_t kStringGetData(EngineState *s, int argc, reg_t *argv);
-reg_t kStringLen(EngineState *s, int argc, reg_t *argv);
-reg_t kStringPrintf(EngineState *s, int argc, reg_t *argv);
-reg_t kStringPrintfBuf(EngineState *s, int argc, reg_t *argv);
-reg_t kStringAtoi(EngineState *s, int argc, reg_t *argv);
+reg_t kStringLength(EngineState *s, int argc, reg_t *argv);
+reg_t kStringFormat(EngineState *s, int argc, reg_t *argv);
+reg_t kStringFormatAt(EngineState *s, int argc, reg_t *argv);
+reg_t kStringToInteger(EngineState *s, int argc, reg_t *argv);
reg_t kStringTrim(EngineState *s, int argc, reg_t *argv);
-reg_t kStringUpper(EngineState *s, int argc, reg_t *argv);
-reg_t kStringLower(EngineState *s, int argc, reg_t *argv);
-reg_t kStringTrn(EngineState *s, int argc, reg_t *argv);
-reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv);
+reg_t kStringToUpperCase(EngineState *s, int argc, reg_t *argv);
+reg_t kStringToLowerCase(EngineState *s, int argc, reg_t *argv);
+reg_t kStringReplaceSubstring(EngineState *s, int argc, reg_t *argv);
+reg_t kStringReplaceSubstringEx(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowPageUp(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowPageDown(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowUpArrow(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowDownArrow(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowHome(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowEnd(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowGo(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowModify(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowHide(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv);
reg_t kMulDiv(EngineState *s, int argc, reg_t *argv);
-reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv);
+
reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv);
reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv);
reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv);
reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv);
reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv);
-reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmap(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDrawLine(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDrawColor(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapDrawBitmap(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapInvert(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapSetOrigin(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapCopyPixels(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapClone(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapGetInfo(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapScale(EngineState *s, int argc, reg_t *argv);
+reg_t kBitmapCreateFromUnknown(EngineState *s, int argc, reg_t *argv);
reg_t kAddPlane(EngineState *s, int argc, reg_t *argv);
reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv);
reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv);
+reg_t kMovePlaneItems(EngineState *s, int argc, reg_t *argv);
reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv);
reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv);
reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv);
reg_t kFrameOut(EngineState *s, int argc, reg_t *argv);
+reg_t kCelHigh32(EngineState *s, int argc, reg_t *argv);
+reg_t kCelWide32(EngineState *s, int argc, reg_t *argv);
reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv); // kOnMe for SCI2, kIsOnMe for SCI2.1
reg_t kInPolygon(EngineState *s, int argc, reg_t *argv);
@@ -478,14 +591,23 @@ reg_t kListIndexOf(EngineState *s, int argc, reg_t *argv);
reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv);
reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv);
reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv);
+reg_t kListSort(EngineState *s, int argc, reg_t *argv);
reg_t kEditText(EngineState *s, int argc, reg_t *argv);
-reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv);
-reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv);
reg_t kSetScroll(EngineState *s, int argc, reg_t *argv);
-reg_t kPalCycle(EngineState *s, int argc, reg_t *argv);
+reg_t kPaletteSetFromResource32(EngineState *s, int argc, reg_t *argv);
+reg_t kPaletteFindColor32(EngineState *s, int argc, reg_t *argv);
reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv);
+reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv);
+
+reg_t kPalCycle(EngineState *s, int argc, reg_t *argv);
+reg_t kPalCycleSetCycle(EngineState *s, int argc, reg_t *argv);
+reg_t kPalCycleDoCycle(EngineState *s, int argc, reg_t *argv);
+reg_t kPalCyclePause(EngineState *s, int argc, reg_t *argv);
+reg_t kPalCycleOn(EngineState *s, int argc, reg_t *argv);
+reg_t kPalCycleOff(EngineState *s, int argc, reg_t *argv);
+
reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv);
reg_t kPalVarySetPercent(EngineState *s, int argc, reg_t *argv);
reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv);
@@ -499,26 +621,28 @@ reg_t kPalVaryMergeStart(EngineState *s, int argc, reg_t *argv);
// SCI2.1 Kernel Functions
reg_t kMorphOn(EngineState *s, int argc, reg_t *argv);
reg_t kText(EngineState *s, int argc, reg_t *argv);
-reg_t kSave(EngineState *s, int argc, reg_t *argv);
-reg_t kAutoSave(EngineState *s, int argc, reg_t *argv);
+reg_t kTextSize32(EngineState *s, int argc, reg_t *argv);
+reg_t kTextWidth(EngineState *s, int argc, reg_t *argv);
reg_t kList(EngineState *s, int argc, reg_t *argv);
-reg_t kRobot(EngineState *s, int argc, reg_t *argv);
-reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv);
reg_t kCD(EngineState *s, int argc, reg_t *argv);
+reg_t kCheckCD(EngineState *s, int argc, reg_t *argv);
+reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv);
reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv);
reg_t kAddBefore(EngineState *s, int argc, reg_t *argv);
reg_t kMoveToFront(EngineState *s, int argc, reg_t *argv);
reg_t kMoveToEnd(EngineState *s, int argc, reg_t *argv);
reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv);
reg_t kWinHelp(EngineState *s, int argc, reg_t *argv);
+reg_t kMessageBox(EngineState *s, int argc, reg_t *argv);
reg_t kGetConfig(EngineState *s, int argc, reg_t *argv);
reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv);
+reg_t kPrintDebug(EngineState *s, int argc, reg_t *argv);
reg_t kCelInfo(EngineState *s, int argc, reg_t *argv);
reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv);
+reg_t kSetFontHeight(EngineState *s, int argc, reg_t *argv);
reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv);
reg_t kFont(EngineState *s, int argc, reg_t *argv);
-reg_t kBitmap(EngineState *s, int argc, reg_t *argv);
reg_t kAddLine(EngineState *s, int argc, reg_t *argv);
reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv);
reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv);
@@ -532,7 +656,6 @@ reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundPlay(EngineState *s, int argc, reg_t *argv);
-reg_t kDoSoundRestore(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundDispose(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundMute(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundStop(EngineState *s, int argc, reg_t *argv);
@@ -547,7 +670,6 @@ reg_t kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundSetHold(EngineState *s, int argc, reg_t *argv);
-reg_t kDoSoundDummy(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundSuspend(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv);
@@ -600,7 +722,7 @@ reg_t kFileIOReadByte(EngineState *s, int argc, reg_t *argv);
reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv);
reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv);
reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv);
-reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv);
+reg_t kFileIOGetCWD(EngineState *s, int argc, reg_t *argv);
reg_t kFileIOIsValidDirectory(EngineState *s, int argc, reg_t *argv);
#endif
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index fbd0b13c88..2db3c59e64 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -60,15 +60,19 @@ struct SciKernelMapSubEntry {
#define SCI_SUBOPENTRY_TERMINATOR { SCI_VERSION_NONE, SCI_VERSION_NONE, 0, NULL, NULL, NULL, NULL }
-#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE
-#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01
-#define SIG_SCI1 SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE
-#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1
-#define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE
-#define SIG_SCI21EARLY_ONLY SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_EARLY
-#define SIG_SINCE_SCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3
-#define SIG_UNTIL_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE
-#define SIG_SINCE_SCI21LATE SCI_VERSION_2_1_LATE, SCI_VERSION_3
+#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE
+#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01
+#define SIG_SCI1 SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE
+#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1
+#define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE
+#define SIG_SCI2 SCI_VERSION_2, SCI_VERSION_2
+#define SIG_SCI21EARLY SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_EARLY
+#define SIG_THRU_SCI21EARLY SCI_VERSION_2, SCI_VERSION_2_1_EARLY
+#define SIG_THRU_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE
+#define SIG_SINCE_SCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3
+#define SIG_SINCE_SCI21MID SCI_VERSION_2_1_MIDDLE, SCI_VERSION_3
+#define SIG_SINCE_SCI21LATE SCI_VERSION_2_1_LATE, SCI_VERSION_3
+#define SIG_SCI3 SCI_VERSION_3, SCI_VERSION_3
#define SIG_SCI16 SCI_VERSION_NONE, SCI_VERSION_1_1
#define SIG_SCI32 SCI_VERSION_2, SCI_VERSION_NONE
@@ -98,7 +102,7 @@ struct SciKernelMapSubEntry {
static const SciKernelMapSubEntry kDoSound_subops[] = {
{ SIG_SOUNDSCI0, 0, MAP_CALL(DoSoundInit), "o", NULL },
{ SIG_SOUNDSCI0, 1, MAP_CALL(DoSoundPlay), "o", NULL },
- { SIG_SOUNDSCI0, 2, MAP_CALL(DoSoundRestore), "(o)", NULL },
+ { SIG_SOUNDSCI0, 2, MAP_EMPTY(DoSoundRestore), "(o)", NULL },
{ SIG_SOUNDSCI0, 3, MAP_CALL(DoSoundDispose), "o", NULL },
{ SIG_SOUNDSCI0, 4, MAP_CALL(DoSoundMute), "(i)", NULL },
{ SIG_SOUNDSCI0, 5, MAP_CALL(DoSoundStop), "o", NULL },
@@ -111,7 +115,7 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
{ SIG_SOUNDSCI0, 12, MAP_CALL(DoSoundStopAll), "", NULL },
{ SIG_SOUNDSCI1EARLY, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL },
{ SIG_SOUNDSCI1EARLY, 1, MAP_CALL(DoSoundMute), NULL, NULL },
- { SIG_SOUNDSCI1EARLY, 2, MAP_CALL(DoSoundRestore), NULL, NULL },
+ { SIG_SOUNDSCI1EARLY, 2, MAP_EMPTY(DoSoundRestore), NULL, NULL },
{ SIG_SOUNDSCI1EARLY, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL },
{ SIG_SOUNDSCI1EARLY, 4, MAP_CALL(DoSoundUpdate), NULL, NULL },
{ SIG_SOUNDSCI1EARLY, 5, MAP_CALL(DoSoundInit), NULL, NULL },
@@ -124,11 +128,11 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
{ SIG_SOUNDSCI1EARLY, 12, MAP_CALL(DoSoundSendMidi), "oiii", NULL },
{ SIG_SOUNDSCI1EARLY, 13, MAP_CALL(DoSoundGlobalReverb), "(i)", NULL },
{ SIG_SOUNDSCI1EARLY, 14, MAP_CALL(DoSoundSetHold), "oi", NULL },
- { SIG_SOUNDSCI1EARLY, 15, MAP_CALL(DoSoundDummy), "", NULL },
+ { SIG_SOUNDSCI1EARLY, 15, MAP_EMPTY(DoSoundDummy), "", NULL },
// ^^ Longbow demo
{ SIG_SOUNDSCI1LATE, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL },
{ SIG_SOUNDSCI1LATE, 1, MAP_CALL(DoSoundMute), NULL, NULL },
- { SIG_SOUNDSCI1LATE, 2, MAP_CALL(DoSoundRestore), "", NULL },
+ { SIG_SOUNDSCI1LATE, 2, MAP_EMPTY(DoSoundRestore), "", NULL },
{ SIG_SOUNDSCI1LATE, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL },
{ SIG_SOUNDSCI1LATE, 4, MAP_CALL(DoSoundGetAudioCapability), "", NULL },
{ SIG_SOUNDSCI1LATE, 5, MAP_CALL(DoSoundSuspend), "i", NULL },
@@ -139,7 +143,7 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
{ SIG_SOUNDSCI1LATE, 10, MAP_CALL(DoSoundPause), NULL, NULL },
{ SIG_SOUNDSCI1LATE, 11, MAP_CALL(DoSoundFade), "oiiii(i)", kDoSoundFade_workarounds },
{ SIG_SOUNDSCI1LATE, 12, MAP_CALL(DoSoundSetHold), NULL, NULL },
- { SIG_SOUNDSCI1LATE, 13, MAP_CALL(DoSoundDummy), NULL, NULL },
+ { SIG_SOUNDSCI1LATE, 13, MAP_EMPTY(DoSoundDummy), NULL, NULL },
{ SIG_SOUNDSCI1LATE, 14, MAP_CALL(DoSoundSetVolume), "oi", NULL },
{ SIG_SOUNDSCI1LATE, 15, MAP_CALL(DoSoundSetPriority), "oi", NULL },
{ SIG_SOUNDSCI1LATE, 16, MAP_CALL(DoSoundSetLoop), "oi", NULL },
@@ -148,39 +152,103 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
{ SIG_SOUNDSCI1LATE, 19, MAP_CALL(DoSoundGlobalReverb), NULL, NULL },
{ SIG_SOUNDSCI1LATE, 20, MAP_CALL(DoSoundUpdate), NULL, NULL },
#ifdef ENABLE_SCI32
- { SIG_SOUNDSCI21, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL },
- { SIG_SOUNDSCI21, 1, MAP_CALL(DoSoundMute), NULL, NULL },
- { SIG_SOUNDSCI21, 2, MAP_CALL(DoSoundRestore), NULL, NULL },
- { SIG_SOUNDSCI21, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL },
- { SIG_SOUNDSCI21, 4, MAP_CALL(DoSoundGetAudioCapability), NULL, NULL },
- { SIG_SOUNDSCI21, 5, MAP_CALL(DoSoundSuspend), NULL, NULL },
- { SIG_SOUNDSCI21, 6, MAP_CALL(DoSoundInit), NULL, NULL },
- { SIG_SOUNDSCI21, 7, MAP_CALL(DoSoundDispose), NULL, NULL },
- { SIG_SOUNDSCI21, 8, MAP_CALL(DoSoundPlay), "o(i)", NULL },
+ { SIG_SOUNDSCI21, 0, MAP_CALL(DoSoundMasterVolume), "(i)", NULL },
+ { SIG_SOUNDSCI21, 1, MAP_CALL(DoSoundMute), "(i)", NULL },
+ { SIG_SOUNDSCI21, 2, MAP_EMPTY(DoSoundRestore), NULL, NULL },
+ { SIG_SOUNDSCI21, 3, MAP_CALL(DoSoundGetPolyphony), "", NULL },
+ { SIG_SOUNDSCI21, 4, MAP_CALL(DoSoundGetAudioCapability), "", NULL },
+ { SIG_SOUNDSCI21, 5, MAP_CALL(DoSoundSuspend), "i", NULL },
+ { SIG_SOUNDSCI21, 6, MAP_CALL(DoSoundInit), "o", NULL },
+ { SIG_SOUNDSCI21, 7, MAP_CALL(DoSoundDispose), "o", NULL },
+ { SIG_SOUNDSCI21, 8, MAP_CALL(DoSoundPlay), "o", kDoSoundPlay_workarounds },
// ^^ TODO: if this is really the only change between SCI1LATE AND SCI21, we could rename the
// SIG_SOUNDSCI1LATE #define to SIG_SINCE_SOUNDSCI1LATE and make it being SCI1LATE+. Although
// I guess there are many more changes somewhere
// TODO: Quest for Glory 4 (SCI2.1) uses the old scheme, we need to detect it accordingly
// signature for SCI21 should be "o"
- { SIG_SOUNDSCI21, 9, MAP_CALL(DoSoundStop), NULL, NULL },
- { SIG_SOUNDSCI21, 10, MAP_CALL(DoSoundPause), NULL, NULL },
- { SIG_SOUNDSCI21, 11, MAP_CALL(DoSoundFade), NULL, kDoSoundFade_workarounds },
- { SIG_SOUNDSCI21, 12, MAP_CALL(DoSoundSetHold), NULL, NULL },
- { SIG_SOUNDSCI21, 13, MAP_CALL(DoSoundDummy), NULL, NULL },
- { SIG_SOUNDSCI21, 14, MAP_CALL(DoSoundSetVolume), NULL, NULL },
- { SIG_SOUNDSCI21, 15, MAP_CALL(DoSoundSetPriority), NULL, NULL },
- { SIG_SOUNDSCI21, 16, MAP_CALL(DoSoundSetLoop), NULL, NULL },
- { SIG_SOUNDSCI21, 17, MAP_CALL(DoSoundUpdateCues), NULL, NULL },
- { SIG_SOUNDSCI21, 18, MAP_CALL(DoSoundSendMidi), NULL, NULL },
- { SIG_SOUNDSCI21, 19, MAP_CALL(DoSoundGlobalReverb), NULL, NULL },
- { SIG_SOUNDSCI21, 20, MAP_CALL(DoSoundUpdate), NULL, NULL },
+ { SIG_SOUNDSCI21, 9, MAP_CALL(DoSoundStop), "o", NULL },
+ { SIG_SOUNDSCI21, 10, MAP_CALL(DoSoundPause), "[o0]i", NULL },
+ { SIG_SOUNDSCI21, 11, MAP_CALL(DoSoundFade), "oiiii", kDoSoundFade_workarounds },
+ { SIG_SOUNDSCI21, 12, MAP_CALL(DoSoundSetHold), "oi", NULL },
+ { SIG_SOUNDSCI21, 13, MAP_EMPTY(DoSoundDummy), NULL, NULL },
+ { SIG_SOUNDSCI21, 14, MAP_CALL(DoSoundSetVolume), "oi", NULL },
+ { SIG_SOUNDSCI21, 15, MAP_CALL(DoSoundSetPriority), "oi", NULL },
+ { SIG_SOUNDSCI21, 16, MAP_CALL(DoSoundSetLoop), "oi", NULL },
+ { SIG_SOUNDSCI21, 17, MAP_CALL(DoSoundUpdateCues), "o", NULL },
+ { SIG_SOUNDSCI21, 18, MAP_CALL(DoSoundSendMidi), "oiiii", NULL },
+ { SIG_SOUNDSCI21, 19, MAP_CALL(DoSoundGlobalReverb), "(i)", NULL },
+ { SIG_SOUNDSCI21, 20, MAP_CALL(DoSoundUpdate), "o", NULL },
#endif
SCI_SUBOPENTRY_TERMINATOR
};
+#ifdef ENABLE_SCI32
+// NOTE: In SSCI, some 'unused' kDoAudio subops are actually called indirectly
+// by kDoSound:
+//
+// kDoSoundGetAudioCapability -> kDoAudioGetCapability
+// kDoSoundPlay -> kDoAudioPlay, kDoAudioStop
+// kDoSoundPause -> kDoAudioPause, kDoAudioResume
+// kDoSoundFade -> kDoAudioFade
+// kDoSoundSetVolume -> kDoAudioVolume
+// kDoSoundSetLoop -> kDoAudioSetLoop
+// kDoSoundUpdateCues -> kDoAudioPosition
+//
+// In ScummVM, logic inside these kernel functions has been moved to methods of
+// Audio32, and direct calls to Audio32 are made from kDoSound instead.
+//
+// Some kDoAudio methods are esoteric and appear to be used only by one or two
+// games:
+//
+// - kDoAudioMixing: Phantasmagoria (other games call this function, but only
+// to disable the feature)
+// - kDoAudioHasSignal: SQ6 TalkRandCycle
+// - kDoAudioPan: Rama RegionSFX::pan method
+// - kDoAudioCritical: Phantasmagoria, chapter 3, nursery (room 14200), during
+// the "ghost lullaby" event. It is used to make the
+// lullaby sound exclusive, but it really doesn't make any
+// major difference. Returning 0 means "non-critical", i.e.
+// normal audio behavior.
+//
+// Finally, there is a split in SCI2.1mid audio code. QFG4CD & SQ6 do not have
+// opcodes 18 and 19, but they exist in GK2, KQ7 2.00b, Phantasmagoria 1,
+// PQ:SWAT, and Torin. It is unknown if they exist in MUMG Deluxe or Shivers 1;
+// they are not used in either of these games.
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kDoAudio_subops[] = {
+ { SIG_SCI32, 0, MAP_CALL(DoAudioInit), "", NULL },
+ // SCI2 includes a Sync script that would call
+ // kDoAudioWaitForPlay, but SSCI has no opcode 1 until
+ // SCI2.1early
+ { SIG_SINCE_SCI21, 1, MAP_CALL(DoAudioWaitForPlay), "(i)(i)(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 2, MAP_CALL(DoAudioPlay), "(i)(i)(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 3, MAP_CALL(DoAudioStop), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 4, MAP_CALL(DoAudioPause), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 5, MAP_CALL(DoAudioResume), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 6, MAP_CALL(DoAudioPosition), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 7, MAP_CALL(DoAudioRate), "(i)", NULL },
+ { SIG_SCI32, 8, MAP_CALL(DoAudioVolume), "(i)(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 9, MAP_CALL(DoAudioGetCapability), "", NULL },
+ { SIG_SCI32, 10, MAP_CALL(DoAudioBitDepth), "(i)", NULL },
+ { SIG_SCI32, 11, MAP_DUMMY(DoAudioDistort), "(i)", NULL },
+ { SIG_SCI32, 12, MAP_CALL(DoAudioMixing), "(i)", NULL },
+ { SIG_SCI32, 13, MAP_CALL(DoAudioChannels), "(i)", NULL },
+ { SIG_SCI32, 14, MAP_CALL(DoAudioPreload), "(i)", NULL },
+ { SIG_SINCE_SCI21MID, 15, MAP_CALL(DoAudioFade), "(iiii)(i)(i)", NULL },
+ { SIG_SINCE_SCI21MID, 16, MAP_DUMMY(DoAudioFade36), "iiiii(iii)(i)", NULL },
+ { SIG_SINCE_SCI21MID, 17, MAP_CALL(DoAudioHasSignal), "", NULL },
+ { SIG_SINCE_SCI21MID, 18, MAP_EMPTY(DoAudioCritical), "(i)", NULL },
+ { SIG_SINCE_SCI21MID, 19, MAP_CALL(DoAudioSetLoop), "iii(o)", NULL },
+ { SIG_SCI3, 20, MAP_DUMMY(DoAudioPan), "", NULL },
+ { SIG_SCI3, 21, MAP_DUMMY(DoAudioPanOff), "", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+#endif
+
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kGraph_subops[] = {
- { SIG_SCI32, 1, MAP_CALL(StubNull), "", NULL }, // called by gk1 sci32 right at the start
+ // 1 - load bits
{ SIG_SCIALL, 2, MAP_CALL(GraphGetColorCount), "", NULL },
// 3 - set palette via resource
{ SIG_SCIALL, 4, MAP_CALL(GraphDrawLine), "iiiii(i)(i)", kGraphDrawLine_workarounds },
@@ -210,8 +278,8 @@ static const SciKernelMapSubEntry kPalVary_subops[] = {
{ SIG_SCI16, 5, MAP_CALL(PalVaryChangeTicks), "i", NULL },
{ SIG_SCI16, 6, MAP_CALL(PalVaryPauseResume), "i", NULL },
#ifdef ENABLE_SCI32
- { SIG_SCI32, 0, MAP_CALL(PalVarySetVary), "i(i)(i)(ii)", NULL },
- { SIG_SCI32, 1, MAP_CALL(PalVarySetPercent), "(i)(i)", NULL },
+ { SIG_SCI32, 0, MAP_CALL(PalVarySetVary), "i(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 1, MAP_CALL(PalVarySetPercent), "(i)(i)", kPalVarySetPercent_workarounds },
{ SIG_SCI32, 2, MAP_CALL(PalVaryGetPercent), "", NULL },
{ SIG_SCI32, 3, MAP_CALL(PalVaryOff), "", NULL },
{ SIG_SCI32, 4, MAP_CALL(PalVaryMergeTarget), "i", NULL },
@@ -226,24 +294,26 @@ static const SciKernelMapSubEntry kPalVary_subops[] = {
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kPalette_subops[] = {
- { SIG_SCIALL, 1, MAP_CALL(PaletteSetFromResource), "i(i)", NULL },
+ { SIG_SCI16, 1, MAP_CALL(PaletteSetFromResource), "i(i)", NULL },
{ SIG_SCI16, 2, MAP_CALL(PaletteSetFlag), "iii", NULL },
{ SIG_SCI16, 3, MAP_CALL(PaletteUnsetFlag), "iii", kPaletteUnsetFlag_workarounds },
-#ifdef ENABLE_SCI32
- { SIG_SCI32, 2, MAP_CALL(PaletteSetFade), "iii", NULL },
- { SIG_SCI32, 3, MAP_CALL(PaletteFindColor), "iii", NULL },
-#endif
{ SIG_SCI16, 4, MAP_CALL(PaletteSetIntensity), "iii(i)", NULL },
{ SIG_SCI16, 5, MAP_CALL(PaletteFindColor), "iii", NULL },
{ SIG_SCI16, 6, MAP_CALL(PaletteAnimate), "i*", NULL },
{ SIG_SCI16, 7, MAP_CALL(PaletteSave), "", NULL },
{ SIG_SCI16, 8, MAP_CALL(PaletteRestore), "[r0]", NULL },
+#ifdef ENABLE_SCI32
+ { SIG_SCI32, 1, MAP_CALL(PaletteSetFromResource32), "i(i)", NULL },
+ { SIG_SCI32, 2, MAP_CALL(PaletteSetFade), "iii", NULL },
+ { SIG_SCI32, 3, MAP_CALL(PaletteFindColor32), "iii", NULL },
+ { SIG_SCI3, 4, MAP_CALL(PaletteSetGamma), "i", NULL },
+#endif
SCI_SUBOPENTRY_TERMINATOR
};
+// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kFileIO_subops[] = {
- { SIG_SCI32, 0, MAP_CALL(FileIOOpen), "r(i)", NULL },
- { SIG_SCIALL, 0, MAP_CALL(FileIOOpen), "ri", NULL },
+ { SIG_SCIALL, 0, MAP_CALL(FileIOOpen), "ri", kFileIOOpen_workarounds },
{ SIG_SCIALL, 1, MAP_CALL(FileIOClose), "i", NULL },
{ SIG_SCIALL, 2, MAP_CALL(FileIOReadRaw), "iri", NULL },
{ SIG_SCIALL, 3, MAP_CALL(FileIOWriteRaw), "iri", NULL },
@@ -256,13 +326,13 @@ static const SciKernelMapSubEntry kFileIO_subops[] = {
{ SIG_SCIALL, 10, MAP_CALL(FileIOExists), "r", NULL },
{ SIG_SINCE_SCI11, 11, MAP_CALL(FileIORename), "rr", NULL },
#ifdef ENABLE_SCI32
- { SIG_SCI32, 13, MAP_CALL(FileIOReadByte), "i", NULL },
- { SIG_SCI32, 14, MAP_CALL(FileIOWriteByte), "ii", NULL },
- { SIG_SCI32, 15, MAP_CALL(FileIOReadWord), "i", NULL },
- { SIG_SCI32, 16, MAP_CALL(FileIOWriteWord), "ii", NULL },
- { SIG_SCI32, 17, MAP_CALL(FileIOCreateSaveSlot), "ir", NULL },
- { SIG_SCI32, 18, MAP_EMPTY(FileIOChangeDirectory), "r", NULL }, // for SQ6, when changing the savegame directory in the save/load dialog
- { SIG_SCI32, 19, MAP_CALL(FileIOIsValidDirectory), "r", NULL }, // for Torin / Torin demo
+ { SIG_SINCE_SCI21MID, 13, MAP_CALL(FileIOReadByte), "i", NULL },
+ { SIG_SINCE_SCI21MID, 14, MAP_CALL(FileIOWriteByte), "ii", NULL },
+ { SIG_SINCE_SCI21MID, 15, MAP_CALL(FileIOReadWord), "i", NULL },
+ { SIG_SINCE_SCI21MID, 16, MAP_CALL(FileIOWriteWord), "ii", NULL },
+ { SIG_SINCE_SCI21MID, 17, "FileIOCheckFreeSpace", kCheckFreeSpace, "i(r)", NULL },
+ { SIG_SINCE_SCI21MID, 18, MAP_CALL(FileIOGetCWD), "r", NULL },
+ { SIG_SINCE_SCI21MID, 19, MAP_CALL(FileIOIsValidDirectory), "r", NULL },
#endif
SCI_SUBOPENTRY_TERMINATOR
};
@@ -270,16 +340,73 @@ static const SciKernelMapSubEntry kFileIO_subops[] = {
#ifdef ENABLE_SCI32
// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kPalCycle_subops[] = {
+ { SIG_SCI32, 0, MAP_CALL(PalCycleSetCycle), "iii(i)", NULL },
+ { SIG_SCI32, 1, MAP_CALL(PalCycleDoCycle), "i(i)", NULL },
+ { SIG_SCI32, 2, MAP_CALL(PalCyclePause), "(i)", NULL },
+ { SIG_SCI32, 3, MAP_CALL(PalCycleOn), "(i)", NULL },
+ { SIG_SCI32, 4, MAP_CALL(PalCycleOff), "(i)", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kSave_subops[] = {
- { SIG_SCI32, 0, MAP_CALL(SaveGame), "[r0]i[r0](r0)", NULL },
- { SIG_SCI32, 1, MAP_CALL(RestoreGame), "[r0]i[r0]", NULL },
- { SIG_SCI32, 2, MAP_CALL(GetSaveDir), "(r*)", NULL },
- { SIG_SCI32, 3, MAP_CALL(CheckSaveGame), ".*", NULL },
+ { SIG_SCI32, 0, MAP_CALL(SaveGame32), "[r0]i[r0][r0]", NULL },
+ { SIG_SCI32, 1, MAP_CALL(RestoreGame32), "[r0]i[r0]", NULL },
+ // System script 64994 in several SCI2.1mid games (KQ7 2.00b, Phant1,
+ // PQ:SWAT, SQ6, Torin) calls GetSaveDir with an extra unused argument, and
+ // it is easier to just handle it here than to bother with creating
+ // workarounds
+ { SIG_SCI32, 2, MAP_CALL(GetSaveDir), "(r)", NULL },
+ { SIG_SCI32, 3, MAP_CALL(CheckSaveGame32), "ri[r0]", NULL },
// Subop 4 hasn't been encountered yet
- { SIG_SCI32, 5, MAP_CALL(GetSaveFiles), "rrr", NULL },
+ { SIG_SCI32, 5, MAP_CALL(GetSaveFiles32), "rrr", NULL },
{ SIG_SCI32, 6, MAP_CALL(MakeSaveCatName), "rr", NULL },
{ SIG_SCI32, 7, MAP_CALL(MakeSaveFileName), "rri", NULL },
- { SIG_SCI32, 8, MAP_CALL(AutoSave), "[o0]", NULL },
+ { SIG_SCI32, 8, MAP_EMPTY(GameIsRestarting), "(.*)", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kFont_subops[] = {
+ { SIG_SINCE_SCI21MID, 0, MAP_CALL(SetFontHeight), "i", NULL },
+ { SIG_SINCE_SCI21MID, 1, MAP_CALL(SetFontRes), "ii", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kText_subops[] = {
+ { SIG_SINCE_SCI21MID, 0, MAP_CALL(TextSize32), "r[r0]i(i)(i)", NULL },
+ { SIG_SINCE_SCI21MID, 1, MAP_CALL(TextWidth), "ri", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kBitmap_subops[] = {
+ { SIG_SINCE_SCI21, 0, MAP_CALL(BitmapCreate), "iiii(i)(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 1, MAP_CALL(BitmapDestroy), "[r!]", NULL },
+ { SIG_SINCE_SCI21, 2, MAP_CALL(BitmapDrawLine), "riiiii(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 3, MAP_CALL(BitmapDrawView), "riii(i)(i)(0)(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 4, MAP_CALL(BitmapDrawText), "rriiiiiiiiiii", NULL },
+ { SIG_SINCE_SCI21, 5, MAP_CALL(BitmapDrawColor), "riiiii", NULL },
+ { SIG_SINCE_SCI21, 6, MAP_CALL(BitmapDrawBitmap), "rr(i)(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 7, MAP_CALL(BitmapInvert), "riiiiii", NULL },
+ { SIG_SINCE_SCI21MID, 8, MAP_CALL(BitmapSetOrigin), "rii", NULL },
+ { SIG_SINCE_SCI21MID, 9, MAP_CALL(BitmapCreateFromView), "iii(i)(i)(i)([r0])", NULL },
+ { SIG_SINCE_SCI21MID, 10, MAP_CALL(BitmapCopyPixels), "rr", NULL },
+ { SIG_SINCE_SCI21MID, 11, MAP_CALL(BitmapClone), "r", NULL },
+ { SIG_SINCE_SCI21MID, 12, MAP_CALL(BitmapGetInfo), "r(i)(i)", NULL },
+ { SIG_SINCE_SCI21LATE,13, MAP_CALL(BitmapScale), "r...ii", NULL },
+ { SIG_SCI3, 14, MAP_CALL(BitmapCreateFromUnknown), "......", NULL },
+ { SIG_SCI3, 15, MAP_EMPTY(Bitmap), "(.*)", NULL },
+ { SIG_SCI3, 16, MAP_EMPTY(Bitmap), "(.*)", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kCD_subops[] = {
+ { SIG_SINCE_SCI21MID, 0, MAP_CALL(CheckCD), "(i)", NULL },
+ { SIG_SINCE_SCI21MID, 1, MAP_CALL(GetSavedCD), "", NULL },
SCI_SUBOPENTRY_TERMINATOR
};
@@ -294,87 +421,171 @@ static const SciKernelMapSubEntry kList_subops[] = {
{ SIG_SINCE_SCI21, 6, MAP_CALL(NextNode), "n", NULL },
{ SIG_SINCE_SCI21, 7, MAP_CALL(PrevNode), "n", NULL },
{ SIG_SINCE_SCI21, 8, MAP_CALL(NodeValue), "[n0]", NULL },
- { SIG_SINCE_SCI21, 9, MAP_CALL(AddAfter), "lnn.", NULL },
- { SIG_SINCE_SCI21, 10, MAP_CALL(AddToFront), "ln.", NULL },
+ { SIG_SINCE_SCI21, 9, MAP_CALL(AddAfter), "lnn(.)", NULL },
+ { SIG_SINCE_SCI21, 10, MAP_CALL(AddToFront), "ln(.)", NULL },
{ SIG_SINCE_SCI21, 11, MAP_CALL(AddToEnd), "ln(.)", NULL },
{ SIG_SINCE_SCI21, 12, MAP_CALL(AddBefore), "ln.", NULL },
{ SIG_SINCE_SCI21, 13, MAP_CALL(MoveToFront), "ln", NULL },
{ SIG_SINCE_SCI21, 14, MAP_CALL(MoveToEnd), "ln", NULL },
{ SIG_SINCE_SCI21, 15, MAP_CALL(FindKey), "l.", NULL },
{ SIG_SINCE_SCI21, 16, MAP_CALL(DeleteKey), "l.", NULL },
- { SIG_SINCE_SCI21, 17, MAP_CALL(ListAt), "li", NULL },
+ { SIG_SINCE_SCI21, 17, MAP_CALL(ListAt), "li", kListAt_workarounds },
{ SIG_SINCE_SCI21, 18, MAP_CALL(ListIndexOf) , "l[io]", NULL },
{ SIG_SINCE_SCI21, 19, MAP_CALL(ListEachElementDo), "li(.*)", NULL },
{ SIG_SINCE_SCI21, 20, MAP_CALL(ListFirstTrue), "li(.*)", NULL },
{ SIG_SINCE_SCI21, 21, MAP_CALL(ListAllTrue), "li(.*)", NULL },
- { SIG_SINCE_SCI21, 22, MAP_CALL(Sort), "ooo", NULL },
+ { SIG_SINCE_SCI21, 22, MAP_CALL(ListSort), "li(i)", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kShowMovieWin_subops[] = {
+ { SIG_SCI2, 0, MAP_CALL(ShowMovieWinOpen), "r", NULL },
+ { SIG_SCI2, 1, MAP_CALL(ShowMovieWinInit), "ii(ii)", NULL },
+ { SIG_SCI2, 2, MAP_CALL(ShowMovieWinPlay), "i", NULL },
+ { SIG_SCI2, 6, MAP_CALL(ShowMovieWinClose), "", NULL },
+ { SIG_SINCE_SCI21, 0, MAP_CALL(ShowMovieWinOpen), "ir", NULL },
+ { SIG_SINCE_SCI21, 1, MAP_CALL(ShowMovieWinInit), "iii(ii)", NULL },
+ { SIG_SINCE_SCI21, 2, MAP_CALL(ShowMovieWinPlay), "i(ii)(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 6, MAP_CALL(ShowMovieWinClose), "i", NULL },
+ // Since movies are rendered within the graphics engine in ScummVM,
+ // it is not necessary to copy the palette from SCI to MCI, so this
+ // can be a no-op
+ { SIG_SINCE_SCI21, 7, MAP_EMPTY(ShowMovieWinSetPalette), "i", NULL },
+ { SIG_SINCE_SCI21, 8, MAP_CALL(ShowMovieWinGetDuration), "i", NULL },
+ { SIG_SINCE_SCI21, 11, MAP_CALL(ShowMovieWinCue), "ii", NULL },
+ { SIG_SINCE_SCI21, 14, MAP_CALL(ShowMovieWinPlayUntilEvent), "i(i)", NULL },
+ { SIG_SINCE_SCI21, 15, MAP_CALL(ShowMovieWinInitDouble), "iii", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// There are a lot of subops to PlayVMD, but only a few of them are ever
+// actually used by games
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kPlayVMD_subops[] = {
+ { SIG_SINCE_SCI21, 0, MAP_CALL(PlayVMDOpen), "r(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 1, MAP_CALL(PlayVMDInit), "ii(i)(i)(ii)", NULL },
+ { SIG_SINCE_SCI21, 6, MAP_CALL(PlayVMDClose), "", NULL },
+ { SIG_SINCE_SCI21, 7, MAP_CALL(PlayVMDIgnorePalettes), "", NULL },
+ { SIG_SINCE_SCI21, 10, MAP_CALL(PlayVMDGetStatus), "", NULL },
+ { SIG_SINCE_SCI21, 14, MAP_CALL(PlayVMDPlayUntilEvent), "i(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 16, MAP_CALL(PlayVMDShowCursor), "i", NULL },
+ { SIG_SINCE_SCI21, 17, MAP_DUMMY(PlayVMDStartBlob), "", NULL },
+ { SIG_SINCE_SCI21, 18, MAP_DUMMY(PlayVMDStopBlobs), "", NULL },
+ { SIG_SINCE_SCI21, 21, MAP_CALL(PlayVMDSetBlackoutArea), "iiii", NULL },
+ { SIG_SINCE_SCI21, 23, MAP_CALL(PlayVMDRestrictPalette), "ii", NULL },
+ { SIG_SCI3, 28, MAP_EMPTY(PlayVMDSetPreload), "i", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kRobot_subops[] = {
+ { SIG_SINCE_SCI21, 0, MAP_CALL(RobotOpen), "ioiii(i)", NULL },
+ { SIG_SINCE_SCI21, 1, MAP_CALL(RobotShowFrame), "i(ii)", NULL },
+ { SIG_SINCE_SCI21, 2, MAP_CALL(RobotGetFrameSize), "r", NULL },
+ { SIG_SINCE_SCI21, 4, MAP_CALL(RobotPlay), "", NULL },
+ { SIG_SINCE_SCI21, 5, MAP_CALL(RobotGetIsFinished), "", NULL },
+ { SIG_SINCE_SCI21, 6, MAP_CALL(RobotGetIsPlaying), "", NULL },
+ { SIG_SINCE_SCI21, 7, MAP_CALL(RobotClose), "", NULL },
+ { SIG_SINCE_SCI21, 8, MAP_CALL(RobotGetCue), "o", NULL },
+ { SIG_SINCE_SCI21, 10, MAP_CALL(RobotPause), "", NULL },
+ { SIG_SINCE_SCI21, 11, MAP_CALL(RobotGetFrameNo), "", NULL },
+ { SIG_SINCE_SCI21, 12, MAP_CALL(RobotSetPriority), "i", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kRemapColors_subops[] = {
+ { SIG_SCI32, 0, MAP_CALL(RemapColorsOff), "(i)", NULL },
+ { SIG_SCI32, 1, MAP_CALL(RemapColorsByRange), "iiii(i)", NULL },
+ { SIG_SCI32, 2, MAP_CALL(RemapColorsByPercent), "ii(i)", NULL },
+ { SIG_SCI32, 3, MAP_CALL(RemapColorsToGray), "ii(i)", NULL },
+ { SIG_SCI32, 4, MAP_CALL(RemapColorsToPercentGray), "iii(i)", NULL },
+ { SIG_SCI32, 5, MAP_CALL(RemapColorsBlockRange), "ii", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kArray_subops[] = {
+ { SIG_SCI32, 0, MAP_CALL(ArrayNew), "ii", NULL },
+ { SIG_SCI32, 1, MAP_CALL(ArrayGetSize), "r", NULL },
+ { SIG_SCI32, 2, MAP_CALL(ArrayGetElement), "ri", NULL },
+ { SIG_SCI32, 3, MAP_CALL(ArraySetElements), "ri(.*)", kArraySetElements_workarounds },
+ { SIG_SCI32, 4, MAP_CALL(ArrayFree), "r", NULL },
+ { SIG_SCI32, 5, MAP_CALL(ArrayFill), "riii", NULL },
+ { SIG_SCI32, 6, MAP_CALL(ArrayCopy), "ririi", NULL },
+ // there is no subop 7
+ { SIG_SCI32, 8, MAP_CALL(ArrayDuplicate), "r", NULL },
+ { SIG_SCI32, 9, MAP_CALL(ArrayGetData), "[or]", NULL },
+ { SIG_SCI3, 10, MAP_CALL(ArrayByteCopy), "ririi", NULL },
SCI_SUBOPENTRY_TERMINATOR
};
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kString_subops[] = {
- { SIG_SCI32, 0, MAP_CALL(StringNew), "i(i)", NULL },
- { SIG_SCI32, 1, MAP_CALL(StringSize), "[or]", NULL },
- { SIG_SCI32, 2, MAP_CALL(StringAt), "[or]i", NULL },
- { SIG_SCI32, 3, MAP_CALL(StringPutAt), "[or]i(i*)", NULL },
- // StringFree accepts invalid references
- { SIG_SCI32, 4, MAP_CALL(StringFree), "[or0!]", NULL },
- { SIG_SCI32, 5, MAP_CALL(StringFill), "[or]ii", NULL },
- { SIG_SCI32, 6, MAP_CALL(StringCopy), "[or]i[or]ii", NULL },
- { SIG_SCI32, 7, MAP_CALL(StringCompare), "[or][or](i)", NULL },
-
- // =SCI2, SCI2.1 Early and SCI2.1 Middle=
- { SIG_UNTIL_SCI21MID, 8, MAP_CALL(StringDup), "[or]", NULL },
- // TODO: This gets called with null references in Torin. Check if this is correct, or it's
- // caused by missing functionality
- { SIG_UNTIL_SCI21MID, 9, MAP_CALL(StringGetData), "[or0]", NULL },
- { SIG_UNTIL_SCI21MID, 10, MAP_CALL(StringLen), "[or]", NULL },
- { SIG_UNTIL_SCI21MID, 11, MAP_CALL(StringPrintf), "[or](.*)", NULL },
- { SIG_UNTIL_SCI21MID, 12, MAP_CALL(StringPrintfBuf), "[or](.*)", NULL },
- { SIG_UNTIL_SCI21MID, 13, MAP_CALL(StringAtoi), "[or]", NULL },
- { SIG_UNTIL_SCI21MID, 14, MAP_CALL(StringTrim), "[or]i", NULL },
- { SIG_UNTIL_SCI21MID, 15, MAP_CALL(StringUpper), "[or]", NULL },
- { SIG_UNTIL_SCI21MID, 16, MAP_CALL(StringLower), "[or]", NULL },
- // the following 2 are unknown atm (happen in Phantasmagoria)
- // possibly translate?
- { SIG_UNTIL_SCI21MID, 17, MAP_CALL(StringTrn), "[or]", NULL },
- { SIG_UNTIL_SCI21MID, 18, MAP_CALL(StringTrnExclude), "[or]", NULL },
-
- // SCI2.1 Late + SCI3 - kStringDup + kStringGetData were removed
- { SIG_SINCE_SCI21LATE, 8, MAP_CALL(StringLen), "[or]", NULL },
- { SIG_SINCE_SCI21LATE, 9, MAP_CALL(StringPrintf), "[or](.*)", NULL },
- { SIG_SINCE_SCI21LATE,10, MAP_CALL(StringPrintfBuf), "[or](.*)", NULL },
- { SIG_SINCE_SCI21LATE,11, MAP_CALL(StringAtoi), "[or]", NULL },
- { SIG_SINCE_SCI21LATE,12, MAP_CALL(StringTrim), "[or]i", NULL },
- { SIG_SINCE_SCI21LATE,13, MAP_CALL(StringUpper), "[or]", NULL },
- { SIG_SINCE_SCI21LATE,14, MAP_CALL(StringLower), "[or]", NULL },
- { SIG_SINCE_SCI21LATE,15, MAP_CALL(StringTrn), "[or]", NULL },
- { SIG_SINCE_SCI21LATE,16, MAP_CALL(StringTrnExclude), "[or]", NULL },
+ // every single copy of script 64918 in SCI2 through 2.1mid calls StringNew
+ // with a second type argument which is unused (new strings are always type
+ // 3)
+ { SIG_THRU_SCI21MID, 0, MAP_CALL(StringNew), "i(i)", NULL },
+ { SIG_THRU_SCI21MID, 1, MAP_CALL(ArrayGetSize), "r", NULL },
+ { SIG_THRU_SCI21MID, 2, MAP_CALL(StringGetChar), "ri", NULL },
+ { SIG_THRU_SCI21MID, 3, MAP_CALL(ArraySetElements), "ri(i*)", kArraySetElements_workarounds },
+ { SIG_THRU_SCI21MID, 4, MAP_CALL(StringFree), "[0r]", NULL },
+ { SIG_THRU_SCI21MID, 5, MAP_CALL(ArrayFill), "rii", NULL },
+ { SIG_THRU_SCI21MID, 6, MAP_CALL(ArrayCopy), "ririi", NULL },
+ { SIG_SCI32, 7, MAP_CALL(StringCompare), "rr(i)", NULL },
+
+ { SIG_THRU_SCI21MID, 8, MAP_CALL(ArrayDuplicate), "r", NULL },
+ { SIG_THRU_SCI21MID, 9, MAP_CALL(StringGetData), "[0or]", NULL },
+ { SIG_THRU_SCI21MID, 10, MAP_CALL(StringLength), "r", NULL },
+ { SIG_THRU_SCI21MID, 11, MAP_CALL(StringFormat), "r(.*)", NULL },
+ { SIG_THRU_SCI21MID, 12, MAP_CALL(StringFormatAt), "r[ro](.*)", NULL },
+ { SIG_THRU_SCI21MID, 13, MAP_CALL(StringToInteger), "r", NULL },
+ { SIG_THRU_SCI21MID, 14, MAP_CALL(StringTrim), "ri(i)", NULL },
+ { SIG_THRU_SCI21MID, 15, MAP_CALL(StringToUpperCase), "r", NULL },
+ { SIG_THRU_SCI21MID, 16, MAP_CALL(StringToLowerCase), "r", NULL },
+ { SIG_THRU_SCI21MID, 17, MAP_CALL(StringReplaceSubstring), "rrrr", NULL },
+ { SIG_THRU_SCI21MID, 18, MAP_CALL(StringReplaceSubstringEx), "rrrr", NULL },
+
+ { SIG_SINCE_SCI21LATE, 8, MAP_CALL(StringLength), "r", NULL },
+ { SIG_SINCE_SCI21LATE, 9, MAP_CALL(StringFormat), "r(.*)", NULL },
+ { SIG_SINCE_SCI21LATE,10, MAP_CALL(StringFormatAt), "rr(.*)", NULL },
+ { SIG_SINCE_SCI21LATE,11, MAP_CALL(StringToInteger), "r", NULL },
+ { SIG_SINCE_SCI21LATE,12, MAP_CALL(StringTrim), "ri(i)", NULL },
+ { SIG_SINCE_SCI21LATE,13, MAP_CALL(StringToUpperCase), "r", NULL },
+ { SIG_SINCE_SCI21LATE,14, MAP_CALL(StringToLowerCase), "r", NULL },
+ { SIG_SINCE_SCI21LATE,15, MAP_CALL(StringReplaceSubstring), "rrrr", NULL },
+ { SIG_SINCE_SCI21LATE,16, MAP_CALL(StringReplaceSubstringEx), "rrrr", NULL },
SCI_SUBOPENTRY_TERMINATOR
};
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kScrollWindow_subops[] = {
{ SIG_SCI32, 0, MAP_CALL(ScrollWindowCreate), "oi", NULL },
- { SIG_SCI32, 1, MAP_CALL(ScrollWindowAdd), "o.ii.(.)", NULL },
- { SIG_SCI32, 2, MAP_DUMMY(ScrollWindowClear), "o", NULL },
- { SIG_SCI32, 3, MAP_DUMMY(ScrollWindowPageUp), "o", NULL },
- { SIG_SCI32, 4, MAP_DUMMY(ScrollWindowPageDown), "o", NULL },
- { SIG_SCI32, 5, MAP_DUMMY(ScrollWindowUpArrow), "o", NULL },
- { SIG_SCI32, 6, MAP_DUMMY(ScrollWindowDownArrow), "o", NULL },
- { SIG_SCI32, 7, MAP_DUMMY(ScrollWindowHome), "o", NULL },
- { SIG_SCI32, 8, MAP_DUMMY(ScrollWindowEnd), "o", NULL },
- { SIG_SCI32, 9, MAP_DUMMY(ScrollWindowResize), "o.", NULL },
- { SIG_SCI32, 10, MAP_CALL(ScrollWindowWhere), "oi", NULL },
- { SIG_SCI32, 11, MAP_DUMMY(ScrollWindowGo), "o..", NULL },
- { SIG_SCI32, 12, MAP_DUMMY(ScrollWindowInsert), "o.....", NULL },
- { SIG_SCI32, 13, MAP_DUMMY(ScrollWindowDelete), "o.", NULL },
- { SIG_SCI32, 14, MAP_DUMMY(ScrollWindowModify), "o.....(.)", NULL },
- { SIG_SCI32, 15, MAP_DUMMY(ScrollWindowHide), "o", NULL },
- { SIG_SCI32, 16, MAP_CALL(ScrollWindowShow), "o", NULL },
- { SIG_SCI32, 17, MAP_CALL(ScrollWindowDestroy), "o", NULL },
- { SIG_SCI32, 18, MAP_DUMMY(ScrollWindowText), "o", NULL },
- { SIG_SCI32, 19, MAP_DUMMY(ScrollWindowReconstruct), "o.", NULL },
+ { SIG_SCI32, 1, MAP_CALL(ScrollWindowAdd), "iriii(i)", kScrollWindowAdd_workarounds },
+ { SIG_SCI32, 2, MAP_DUMMY(ScrollWindowClear), "i", NULL },
+ { SIG_SCI32, 3, MAP_CALL(ScrollWindowPageUp), "i", NULL },
+ { SIG_SCI32, 4, MAP_CALL(ScrollWindowPageDown), "i", NULL },
+ { SIG_SCI32, 5, MAP_CALL(ScrollWindowUpArrow), "i", NULL },
+ { SIG_SCI32, 6, MAP_CALL(ScrollWindowDownArrow), "i", NULL },
+ { SIG_SCI32, 7, MAP_CALL(ScrollWindowHome), "i", NULL },
+ { SIG_SCI32, 8, MAP_CALL(ScrollWindowEnd), "i", NULL },
+ { SIG_SCI32, 9, MAP_DUMMY(ScrollWindowResize), "i.", NULL },
+ { SIG_SCI32, 10, MAP_CALL(ScrollWindowWhere), "ii", NULL },
+ { SIG_SCI32, 11, MAP_CALL(ScrollWindowGo), "i..", NULL },
+ { SIG_SCI32, 12, MAP_DUMMY(ScrollWindowInsert), "i.....", NULL },
+ { SIG_SCI32, 13, MAP_DUMMY(ScrollWindowDelete), "i.", NULL },
+ { SIG_SCI32, 14, MAP_CALL(ScrollWindowModify), "iiriii(i)", NULL },
+ { SIG_SCI32, 15, MAP_CALL(ScrollWindowHide), "i", NULL },
+ { SIG_SCI32, 16, MAP_CALL(ScrollWindowShow), "i", NULL },
+ { SIG_SCI32, 17, MAP_CALL(ScrollWindowDestroy), "i", NULL },
+ // LSL6hires uses kScrollWindowText and kScrollWindowReconstruct to try to save
+ // and restore the content of the game's subtitle window, but this feature did not
+ // use the normal save/load functionality of the engine and was actually broken
+ // (all text formatting was missing on restore). Since there is no real reason to
+ // save the subtitle scrollback anyway, we just ignore calls to these two functions.
+ { SIG_SCI32, 18, MAP_EMPTY(ScrollWindowText), "i", NULL },
+ { SIG_SCI32, 19, MAP_EMPTY(ScrollWindowReconstruct), "i.", NULL },
SCI_SUBOPENTRY_TERMINATOR
};
@@ -404,18 +615,28 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(Animate), SIG_EVERYWHERE, "(l0)(i)", NULL, NULL },
{ MAP_CALL(AssertPalette), SIG_EVERYWHERE, "i", NULL, NULL },
{ MAP_CALL(AvoidPath), SIG_EVERYWHERE, "ii(.*)", NULL, NULL },
- { MAP_CALL(BaseSetter), SIG_EVERYWHERE, "o", NULL, NULL },
+ { MAP_CALL(BaseSetter), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "BaseSetter", kBaseSetter32, SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL },
+#endif
{ MAP_CALL(CanBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL },
+ { MAP_CALL(CantBeHere), SIG_SCI16, SIGFOR_ALL, "o(l)", NULL, NULL },
#ifdef ENABLE_SCI32
- { "CantBeHere", kCantBeHere32, SIG_SCI32, SIGFOR_ALL, "ol", NULL, NULL },
+ { MAP_CALL(CantBeHere), SIG_SCI32, SIGFOR_ALL, "ol", NULL, NULL },
#endif
- { MAP_CALL(CantBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL },
- { MAP_CALL(CelHigh), SIG_EVERYWHERE, "ii(i)", NULL, kCelHigh_workarounds },
- { MAP_CALL(CelWide), SIG_EVERYWHERE, "ii(i)", NULL, kCelWide_workarounds },
- { MAP_CALL(CheckFreeSpace), SIG_SCI32, SIGFOR_ALL, "r.*", NULL, NULL },
+ { MAP_CALL(CelHigh), SIG_SCI16, SIGFOR_ALL, "ii(i)", NULL, kCelHigh_workarounds },
+ { MAP_CALL(CelWide), SIG_SCI16, SIGFOR_ALL, "ii(i)", NULL, kCelWide_workarounds },
+#ifdef ENABLE_SCI32
+ { "CelHigh", kCelHigh32, SIG_SCI32, SIGFOR_ALL, "iii", NULL, NULL },
+ { "CelWide", kCelWide32, SIG_SCI32, SIGFOR_ALL, "iii", NULL, kCelWide_workarounds },
+#endif
+ { MAP_CALL(CheckFreeSpace), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "r(i)", NULL, NULL },
{ MAP_CALL(CheckFreeSpace), SIG_SCI11, SIGFOR_ALL, "r(i)", NULL, NULL },
- { MAP_CALL(CheckFreeSpace), SIG_EVERYWHERE, "r", NULL, NULL },
- { MAP_CALL(CheckSaveGame), SIG_EVERYWHERE, ".*", NULL, NULL },
+ { MAP_CALL(CheckFreeSpace), SIG_SCI16, SIGFOR_ALL, "r", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "CheckSaveGame", kCheckSaveGame32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "ri[r0]", NULL, NULL },
+#endif
+ { MAP_CALL(CheckSaveGame), SIG_SCI16, SIGFOR_ALL, ".*", NULL, NULL },
{ MAP_CALL(Clone), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(CoordPri), SIG_EVERYWHERE, "i(i)", NULL, NULL },
{ MAP_CALL(CosDiv), SIG_EVERYWHERE, "ii", NULL, NULL },
@@ -429,7 +650,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(DisposeList), SIG_EVERYWHERE, "l", NULL, NULL },
{ MAP_CALL(DisposeScript), SIG_EVERYWHERE, "i(i*)", NULL, kDisposeScript_workarounds },
{ MAP_CALL(DisposeWindow), SIG_EVERYWHERE, "i(i)", NULL, NULL },
- { MAP_CALL(DoAudio), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop
+ { MAP_CALL(DoAudio), SCI_VERSION_NONE, SCI_VERSION_2, SIGFOR_ALL, "i(.*)", NULL, NULL }, // subop
+#ifdef ENABLE_SCI32
+ { "DoAudio", kDoAudio32, SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kDoAudio_subops, NULL },
+#endif
{ MAP_CALL(DoAvoider), SIG_EVERYWHERE, "o(i)", NULL, NULL },
{ MAP_CALL(DoBresen), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(DoSound), SIG_EVERYWHERE, "i(.*)", kDoSound_subops, NULL },
@@ -462,13 +686,23 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(GetMenu), SIG_EVERYWHERE, "i.", NULL, NULL },
{ MAP_CALL(GetMessage), SIG_EVERYWHERE, "iiir", NULL, NULL },
{ MAP_CALL(GetPort), SIG_EVERYWHERE, "", NULL, NULL },
- { MAP_CALL(GetSaveDir), SIG_SCI32, SIGFOR_ALL, "(r*)", NULL, NULL },
- { MAP_CALL(GetSaveDir), SIG_EVERYWHERE, "", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { MAP_CALL(GetSaveDir), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(r)", NULL, NULL },
+#endif
+ { MAP_CALL(GetSaveDir), SIG_SCI16, SIGFOR_ALL, "", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "GetSaveFiles", kGetSaveFiles32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "rrr", NULL, NULL },
+#endif
{ MAP_CALL(GetSaveFiles), SIG_EVERYWHERE, "rrr", NULL, NULL },
{ MAP_CALL(GetTime), SIG_EVERYWHERE, "(i)", NULL, NULL },
- { MAP_CALL(GlobalToLocal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL },
- { MAP_CALL(GlobalToLocal), SIG_EVERYWHERE, "o", NULL, NULL },
- { MAP_CALL(Graph), SIG_EVERYWHERE, NULL, kGraph_subops, NULL },
+ { MAP_CALL(GlobalToLocal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "GlobalToLocal", kGlobalToLocal32, SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL },
+#endif
+ { MAP_CALL(Graph), SIG_SCI16, SIGFOR_ALL, NULL, kGraph_subops, NULL },
+#ifdef ENABLE_SCI32
+ { MAP_EMPTY(Graph), SIG_SCI32, SIGFOR_ALL, "(.*)", NULL, NULL },
+#endif
{ MAP_CALL(HaveMouse), SIG_EVERYWHERE, "", NULL, NULL },
{ MAP_CALL(HiliteControl), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(InitBresen), SIG_EVERYWHERE, "o(i)", NULL, NULL },
@@ -478,8 +712,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(Joystick), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop
{ MAP_CALL(LastNode), SIG_EVERYWHERE, "l", NULL, NULL },
{ MAP_CALL(Load), SIG_EVERYWHERE, "ii(i*)", NULL, NULL },
- { MAP_CALL(LocalToGlobal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL },
- { MAP_CALL(LocalToGlobal), SIG_EVERYWHERE, "o", NULL, NULL },
+ { MAP_CALL(LocalToGlobal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "LocalToGlobal", kLocalToGlobal32, SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL },
+#endif
{ MAP_CALL(Lock), SIG_EVERYWHERE, "ii(i)", NULL, NULL },
{ MAP_CALL(MapKeyToDir), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(Memory), SIG_EVERYWHERE, "i(.*)", NULL, kMemory_workarounds }, // subop
@@ -504,37 +740,59 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(Palette), SIG_EVERYWHERE, "i(.*)", kPalette_subops, NULL },
{ MAP_CALL(Parse), SIG_EVERYWHERE, "ro", NULL, NULL },
{ MAP_CALL(PicNotValid), SIG_EVERYWHERE, "(i)", NULL, NULL },
- { MAP_CALL(Platform), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(Platform), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "Platform", kPlatform32, SIG_SCI32, SIGFOR_MAC, "(.*)", NULL, NULL },
+ { "Platform", kPlatform32, SIG_SCI32, SIGFOR_ALL, "(i)", NULL, kPlatform32_workarounds },
+#endif
{ MAP_CALL(Portrait), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop
{ MAP_CALL(PrevNode), SIG_EVERYWHERE, "n", NULL, NULL },
{ MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL },
- { MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, NULL },
+ { MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, kRandom_workarounds },
{ MAP_CALL(ReadNumber), SIG_EVERYWHERE, "r", NULL, kReadNumber_workarounds },
{ MAP_CALL(RemapColors), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, NULL },
#ifdef ENABLE_SCI32
- { "RemapColors", kRemapColors32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)(i)(i)", NULL, NULL },
+ { "RemapColors", kRemapColors32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)(i)(i)", kRemapColors_subops, NULL },
#endif
- { MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL },
+ { MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, kResCheck_workarounds },
{ MAP_CALL(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL },
{ MAP_CALL(RestartGame), SIG_EVERYWHERE, "", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "RestoreGame", kRestoreGame32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0]", NULL, NULL },
+#endif
{ MAP_CALL(RestoreGame), SIG_EVERYWHERE, "[r0]i[r0]", NULL, NULL },
{ MAP_CALL(Said), SIG_EVERYWHERE, "[r0]", NULL, NULL },
- { MAP_CALL(SaveGame), SIG_EVERYWHERE, "[r0]i[r0](r0)", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "SaveGame", kSaveGame32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0][r0]", NULL, NULL },
+#endif
+ { MAP_CALL(SaveGame), SIG_SCI16, SIGFOR_ALL, "[r0]i[r0](r0)", NULL, NULL },
{ MAP_CALL(ScriptID), SIG_EVERYWHERE, "[io](i)", NULL, NULL },
- { MAP_CALL(SetCursor), SIG_SINCE_SCI21, SIGFOR_ALL, "i(i)([io])(i*)", NULL, NULL },
- // TODO: SCI2.1 may supply an object optionally (mother goose sci21 right on startup) - find out why
{ MAP_CALL(SetCursor), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(iiiiii)", NULL, NULL },
- { MAP_CALL(SetCursor), SIG_EVERYWHERE, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds },
+ { MAP_CALL(SetCursor), SIG_SCI16, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds },
+#ifdef ENABLE_SCI32
+ { "SetCursor", kSetCursor32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)", NULL, kSetCursor_workarounds },
+#endif
{ MAP_CALL(SetDebug), SIG_EVERYWHERE, "(i*)", NULL, NULL },
{ MAP_CALL(SetJump), SIG_EVERYWHERE, "oiii", NULL, NULL },
{ MAP_CALL(SetMenu), SIG_EVERYWHERE, "i(.*)", NULL, NULL },
- { MAP_CALL(SetNowSeen), SIG_EVERYWHERE, "o(i)", NULL, NULL },
+ { MAP_CALL(SetNowSeen), SIG_SCI16, SIGFOR_ALL, "o(i)", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "SetNowSeen", kSetNowSeen32, SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL },
+#endif
{ MAP_CALL(SetPort), SIG_EVERYWHERE, "i(iiiii)(i)", NULL, kSetPort_workarounds },
{ MAP_CALL(SetQuitStr), SIG_EVERYWHERE, "r", NULL, NULL },
{ MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(SetVideoMode), SIG_EVERYWHERE, "i", NULL, NULL },
- { MAP_CALL(ShakeScreen), SIG_EVERYWHERE, "(i)(i)", NULL, NULL },
- { MAP_CALL(ShowMovie), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(ShakeScreen), SIG_SCI16, SIGFOR_ALL, "(i)(i)", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "ShakeScreen", kShakeScreen32, SIG_SCI32, SIGFOR_ALL, "i(i)", NULL, NULL },
+#endif
+ { MAP_CALL(ShowMovie), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "ShowMovie", kShowMovie32, SIG_SCI32, SIGFOR_DOS, "ri(i)(i)", NULL, NULL },
+ { "ShowMovie", kShowMovie32, SIG_SCI32, SIGFOR_MAC, "ri(i)(i)", NULL, NULL },
+ { "ShowMovie", kShowMovieWin, SIG_SCI32, SIGFOR_WIN, "(.*)", kShowMovieWin_subops, NULL },
+#endif
{ MAP_CALL(Show), SIG_EVERYWHERE, "i", NULL, NULL },
{ MAP_CALL(SinDiv), SIG_EVERYWHERE, "ii", NULL, NULL },
{ MAP_CALL(Sort), SIG_EVERYWHERE, "ooo", NULL, NULL },
@@ -546,10 +804,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(StrEnd), SIG_EVERYWHERE, "r", NULL, NULL },
{ MAP_CALL(StrLen), SIG_EVERYWHERE, "[r0]", NULL, kStrLen_workarounds },
{ MAP_CALL(StrSplit), SIG_EVERYWHERE, "rr[r0]", NULL, NULL },
- { MAP_CALL(TextColors), SIG_EVERYWHERE, "(i*)", NULL, NULL },
- { MAP_CALL(TextFonts), SIG_EVERYWHERE, "(i*)", NULL, NULL },
- { MAP_CALL(TextSize), SIG_SCIALL, SIGFOR_MAC, "r[r0]i(i)(r0)(i)", NULL, NULL },
- { MAP_CALL(TextSize), SIG_EVERYWHERE, "r[r0]i(i)(r0)", NULL, NULL },
+ { MAP_CALL(TextColors), SIG_SCI16, SIGFOR_ALL, "(i*)", NULL, NULL },
+ { MAP_CALL(TextFonts), SIG_SCI16, SIGFOR_ALL, "(i*)", NULL, NULL },
+ { MAP_CALL(TextSize), SIG_SCI16, SIGFOR_MAC, "r[r0]i(i)(r0)(i)", NULL, NULL },
+ { MAP_CALL(TextSize), SIG_SCI16, SIGFOR_ALL, "r[r0]i(i)(r0)", NULL, NULL },
{ MAP_CALL(TimesCos), SIG_EVERYWHERE, "ii", NULL, NULL },
{ "CosMult", kTimesCos, SIG_EVERYWHERE, "ii", NULL, NULL },
{ MAP_CALL(TimesCot), SIG_EVERYWHERE, "ii", NULL, NULL },
@@ -581,13 +839,17 @@ static SciKernelMapEntry s_kernelMap[] = {
#ifdef ENABLE_SCI32
// SCI2 Kernel Functions
// TODO: whoever knows his way through those calls, fix the signatures.
+ { "TextSize", kTextSize32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "r[r0]i(i)", NULL, NULL },
+ { MAP_DUMMY(TextColors), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL },
+ { MAP_DUMMY(TextFonts), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL },
+
{ MAP_CALL(AddPlane), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(AddScreenItem), SIG_EVERYWHERE, "o", NULL, NULL },
- { MAP_CALL(Array), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(Array), SIG_EVERYWHERE, "i(.*)", kArray_subops, NULL },
{ MAP_CALL(CreateTextBitmap), SIG_EVERYWHERE, "i(.*)", NULL, NULL },
{ MAP_CALL(DeletePlane), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(DeleteScreenItem), SIG_EVERYWHERE, "o", NULL, NULL },
- { MAP_CALL(DisposeTextBitmap), SIG_EVERYWHERE, "r", NULL, NULL },
+ { "DisposeTextBitmap", kBitmapDestroy, SIG_SCI2, SIGFOR_ALL, "[r!]", NULL, NULL },
{ MAP_CALL(FrameOut), SIG_EVERYWHERE, "(i)", NULL, NULL },
{ MAP_CALL(GetHighPlanePri), SIG_EVERYWHERE, "", NULL, NULL },
{ MAP_CALL(InPolygon), SIG_EVERYWHERE, "iio", NULL, NULL },
@@ -597,6 +859,8 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(ListEachElementDo), SIG_EVERYWHERE, "li(.*)", NULL, NULL },
{ MAP_CALL(ListFirstTrue), SIG_EVERYWHERE, "li(.*)", NULL, NULL },
{ MAP_CALL(ListIndexOf), SIG_EVERYWHERE, "l[o0]", NULL, NULL },
+ // kMessageBox is used only by KQ7 1.51
+ { MAP_CALL(MessageBox), SIG_SCI32, SIGFOR_ALL, "rri", NULL, NULL },
{ "OnMe", kIsOnMe, SIG_EVERYWHERE, "iioi", NULL, NULL },
// Purge is used by the memory manager in SSCI to ensure that X number of bytes (the so called "unmovable
// memory") are available when the current room changes. This is similar to the SCI0-SCI1.1 FlushResources
@@ -610,10 +874,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(UpdateScreenItem), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(ObjectIntersect), SIG_EVERYWHERE, "oo", NULL, NULL },
{ MAP_CALL(EditText), SIG_EVERYWHERE, "o", NULL, NULL },
- { MAP_CALL(MakeSaveCatName), SIG_EVERYWHERE, "rr", NULL, NULL },
- { MAP_CALL(MakeSaveFileName), SIG_EVERYWHERE, "rri", NULL, NULL },
- { MAP_CALL(SetScroll), SIG_EVERYWHERE, "oiiiii(i)", NULL, NULL },
- { MAP_CALL(PalCycle), SIG_EVERYWHERE, "i(.*)", NULL, NULL },
+ { MAP_CALL(MakeSaveCatName), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "rr", NULL, NULL },
+ { MAP_CALL(MakeSaveFileName), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "rri", NULL, NULL },
+ { MAP_CALL(SetScroll), SIG_EVERYWHERE, "oiiii(i)(i)", NULL, NULL },
+ { MAP_CALL(PalCycle), SIG_EVERYWHERE, "(.*)", kPalCycle_subops, NULL },
// SCI2 Empty functions
@@ -649,34 +913,37 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_DUMMY(MarkMemory), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_DUMMY(GetHighItemPri), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_DUMMY(ShowStylePercent), SIG_EVERYWHERE, "(.*)", NULL, NULL },
- { MAP_DUMMY(InvertRect), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_DUMMY(InvertRect), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL },
{ MAP_DUMMY(InputText), SIG_EVERYWHERE, "(.*)", NULL, NULL },
- { MAP_DUMMY(TextWidth), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(TextWidth), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "ri", NULL, NULL },
{ MAP_DUMMY(PointSize), SIG_EVERYWHERE, "(.*)", NULL, NULL },
// SCI2.1 Kernel Functions
- { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(CD), SIG_SINCE_SCI21MID, SIGFOR_ALL, "(.*)", kCD_subops, NULL },
{ MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iioi", NULL, NULL },
{ MAP_CALL(List), SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kList_subops, NULL },
{ MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL },
- { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", NULL, NULL },
- { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", kPlayVMD_subops, NULL },
+ { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", kRobot_subops, NULL },
{ MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL },
- { MAP_CALL(Text), SIG_EVERYWHERE, "(.*)", NULL, NULL },
- { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii", NULL, NULL },
+ { MAP_CALL(Text), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kText_subops, NULL },
+ { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii(i)(i)", NULL, NULL },
{ MAP_CALL(GetWindowsOption), SIG_EVERYWHERE, "i", NULL, NULL },
{ MAP_CALL(WinHelp), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_CALL(GetConfig), SIG_EVERYWHERE, "ro", NULL, NULL },
{ MAP_CALL(GetSierraProfileInt), SIG_EVERYWHERE, "rri", NULL, NULL },
- { MAP_CALL(CelInfo), SIG_EVERYWHERE, "iiiiii", NULL, NULL },
- { MAP_CALL(SetLanguage), SIG_EVERYWHERE, "r", NULL, NULL },
+ { MAP_CALL(CelInfo), SIG_SINCE_SCI21MID, SIGFOR_ALL, "iiiiii", NULL, NULL },
+ { MAP_CALL(SetLanguage), SIG_SINCE_SCI21MID, SIGFOR_ALL, "r", NULL, NULL },
{ MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "i(.*)", kScrollWindow_subops, NULL },
- { MAP_CALL(SetFontRes), SIG_SCI21EARLY_ONLY, SIGFOR_ALL, "ii", NULL, NULL },
- { MAP_CALL(Font), SIG_EVERYWHERE, "i(.*)", NULL, NULL },
- { MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", NULL, NULL },
- { MAP_CALL(AddLine), SIG_EVERYWHERE, "oiiiiiiiii", NULL, NULL },
- { MAP_CALL(UpdateLine), SIG_EVERYWHERE, "[r0]oiiiiiiiii", NULL, NULL },
- { MAP_CALL(DeleteLine), SIG_EVERYWHERE, "[r0]o", NULL, NULL },
+ { MAP_CALL(SetFontRes), SIG_SCI21EARLY, SIGFOR_ALL, "ii", NULL, NULL },
+ { MAP_CALL(Font), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kFont_subops, NULL },
+ { MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", kBitmap_subops, NULL },
+ { MAP_CALL(AddLine), SIG_EVERYWHERE, "oiiii(iiiii)", NULL, NULL },
+ // The first argument is a ScreenItem instance ID that is created by the
+ // engine, not the VM; as a result, in ScummVM, this argument looks like
+ // an integer and not an object, although it is an object reference.
+ { MAP_CALL(UpdateLine), SIG_EVERYWHERE, "ioiiii(iiiii)", NULL, NULL },
+ { MAP_CALL(DeleteLine), SIG_EVERYWHERE, "io", NULL, NULL },
// SCI2.1 Empty Functions
@@ -684,14 +951,14 @@ static SciKernelMapEntry s_kernelMap[] = {
// stub in the original interpreters, but it gets called by the game scripts.
// Usually, it gets called with a string (which is the output format) and a
// variable number of parameters
- { MAP_EMPTY(PrintDebug), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(PrintDebug), SIG_SCI32, SIGFOR_ALL, "r(.*)", NULL, NULL },
// SetWindowsOption is used to set Windows specific options, like for example the title bar visibility of
// the game window in Phantasmagoria 2. We ignore these settings completely.
- { MAP_EMPTY(SetWindowsOption), SIG_EVERYWHERE, "ii", NULL, NULL },
+ { MAP_EMPTY(SetWindowsOption), SIG_EVERYWHERE, "ii", NULL, NULL },
// Debug function called whenever the current room changes
- { MAP_EMPTY(NewRoom), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_EMPTY(NewRoom), SIG_EVERYWHERE, "(.*)", NULL, NULL },
// Unused / debug SCI2.1 unused functions, always mapped to kDummy
@@ -724,10 +991,11 @@ static SciKernelMapEntry s_kernelMap[] = {
// SetHotRectangles - used by Phantasmagoria 1, script 64981 (used in the chase scene)
// <lskovlun> The idea, if I understand correctly, is that the engine generates events
// of a special HotRect type continuously when the mouse is on that rectangle
+ { MAP_CALL(SetHotRectangles), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(r)", NULL, NULL },
+
+ // Used by SQ6 to scroll through the inventory via the up/down buttons
+ { MAP_CALL(MovePlaneItems), SIG_SINCE_SCI21, SIGFOR_ALL, "oii(i)", NULL, NULL },
- // MovePlaneItems - used by SQ6 to scroll through the inventory via the up/down buttons
- // SetPalStyleRange - 2 integer parameters, start and end. All styles from start-end
- // (inclusive) are set to 0
{ MAP_CALL(SetPalStyleRange), SIG_EVERYWHERE, "ii", NULL, NULL },
{ MAP_CALL(MorphOn), SIG_EVERYWHERE, "", NULL, NULL },
@@ -1030,7 +1298,6 @@ static const char *const sci2_default_knames[] = {
/*0x89*/ "TextWidth", // for debugging(?), only in SCI2, not used in any SCI2 game
/*0x8a*/ "PointSize", // for debugging(?), only in SCI2, not used in any SCI2 game
- // GK2 Demo (and similar) only kernel functions
/*0x8b*/ "AddLine",
/*0x8c*/ "DeleteLine",
/*0x8d*/ "UpdateLine",
diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp
index bb595e9960..a00630622e 100644
--- a/engines/sci/engine/kevent.cpp
+++ b/engines/sci/engine/kevent.cpp
@@ -34,6 +34,9 @@
#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/cursor.h"
#include "sci/graphics/maciconbar.h"
+#ifdef ENABLE_SCI32
+#include "sci/graphics/frameout.h"
+#endif
namespace Sci {
@@ -58,10 +61,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
if (g_debug_simulated_key && (mask & SCI_EVENT_KEYBOARD)) {
// In case we use a simulated event we query the current mouse position
mousePos = g_sci->_gfxCursor->getPosition();
-#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY)
- g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x);
-#endif
+
// Limit the mouse cursor position, if necessary
g_sci->_gfxCursor->refreshPosition();
@@ -73,7 +73,7 @@ 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) {
@@ -83,13 +83,17 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
}
// For a real event we use its associated mouse position
- mousePos = curEvent.mousePos;
#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY)
- g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x);
+ if (getSciVersion() >= SCI_VERSION_2)
+ mousePos = curEvent.mousePosSci;
+ else {
+#endif
+ mousePos = curEvent.mousePos;
+ // Limit the mouse cursor position, if necessary
+ g_sci->_gfxCursor->refreshPosition();
+#ifdef ENABLE_SCI32
+ }
#endif
- // Limit the mouse cursor position, if necessary
- g_sci->_gfxCursor->refreshPosition();
if (g_sci->getVocabulary())
g_sci->getVocabulary()->parser_event = NULL_REG; // Invalidate parser event
@@ -101,7 +105,25 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
// question. Check GfxCursor::setPosition(), for a more detailed
// explanation and a list of cursor position workarounds.
if (s->_cursorWorkaroundRect.contains(mousePos.x, mousePos.y)) {
- s->_cursorWorkaroundActive = false;
+ // For OpenPandora and possibly other platforms, that support analog-stick control + touch screen
+ // control at the same time: in case the cursor is currently at the coordinate set by the scripts,
+ // we will count down instead of immediately disabling the workaround.
+ // On OpenPandora the cursor position is set, but it's overwritten shortly afterwards by the
+ // touch screen. In this case we would sometimes disable the workaround, simply because the touch
+ // screen hasn't yet overwritten the position and thus the workaround would not work anymore.
+ // On OpenPandora it would sometimes work and sometimes not without this.
+ if (s->_cursorWorkaroundPoint == mousePos) {
+ // Cursor is still at the same spot as set by the scripts
+ if (s->_cursorWorkaroundPosCount > 0) {
+ s->_cursorWorkaroundPosCount--;
+ } else {
+ // Was for quite a bit of time at that spot, so disable workaround now
+ s->_cursorWorkaroundActive = false;
+ }
+ } else {
+ // Cursor has moved, but is within the rect -> disable workaround immediately
+ s->_cursorWorkaroundActive = false;
+ }
} else {
mousePos.x = s->_cursorWorkaroundPoint.x;
mousePos.y = s->_cursorWorkaroundPoint.y;
@@ -214,7 +236,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
// check bugs #3058865 and #3127824
if (s->_gameIsBenchmarking) {
// Game is benchmarking, don't add a delay
- } else {
+ } else if (getSciVersion() < SCI_VERSION_2) {
g_system->delayMillis(10);
}
@@ -239,11 +261,12 @@ reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) {
if (readSelectorValue(segMan, obj, SELECTOR(type)) == SCI_EVENT_KEYBOARD) { // Keyboard
uint16 message = readSelectorValue(segMan, obj, SELECTOR(message));
uint16 eventType = SCI_EVENT_DIRECTION;
- // Check if the game is using cursor views. These games allowed control
- // of the mouse cursor via the keyboard controls (the so called
- // "PseudoMouse" functionality in script 933).
- if (g_sci->_features->detectSetCursorType() == SCI_VERSION_1_1)
+ // It seems with SCI1 Sierra started to add the SCI_EVENT_DIRECTION bit instead of setting it directly.
+ // It was done inside the keyboard driver and is required for the PseudoMouse functionality and class
+ // to work (script 933).
+ if (g_sci->_features->detectPseudoMouseAbility() == kPseudoMouseAbilityTrue) {
eventType |= SCI_EVENT_KEYBOARD;
+ }
for (int i = 0; i < 9; i++) {
if (keyToDirMap[i].key == message) {
@@ -261,14 +284,13 @@ reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) {
reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) {
reg_t obj = argv[0];
- reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32
SegManager *segMan = s->_segMan;
if (obj.getSegment()) {
int16 x = readSelectorValue(segMan, obj, SELECTOR(x));
int16 y = readSelectorValue(segMan, obj, SELECTOR(y));
- g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y, planeObject);
+ g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y);
writeSelectorValue(segMan, obj, SELECTOR(x), x);
writeSelectorValue(segMan, obj, SELECTOR(y), y);
@@ -280,14 +302,13 @@ reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) {
reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv) {
reg_t obj = argv[0];
- reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32
SegManager *segMan = s->_segMan;
if (obj.getSegment()) {
int16 x = readSelectorValue(segMan, obj, SELECTOR(x));
int16 y = readSelectorValue(segMan, obj, SELECTOR(y));
- g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y, planeObject);
+ g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y);
writeSelectorValue(segMan, obj, SELECTOR(x), x);
writeSelectorValue(segMan, obj, SELECTOR(y), y);
@@ -302,4 +323,76 @@ reg_t kJoystick(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
+#ifdef ENABLE_SCI32
+reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv) {
+ const reg_t result = argv[0];
+ const reg_t planeObj = argv[1];
+
+ bool visible = true;
+ Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
+ if (plane == nullptr) {
+ plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
+ visible = false;
+ }
+ if (plane == nullptr) {
+ error("kGlobalToLocal: Plane %04x:%04x not found", PRINT_REG(planeObj));
+ }
+
+ const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) - plane->_gameRect.left;
+ const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) - plane->_gameRect.top;
+
+ writeSelectorValue(s->_segMan, result, SELECTOR(x), x);
+ writeSelectorValue(s->_segMan, result, SELECTOR(y), y);
+
+ return make_reg(0, visible);
+}
+
+reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv) {
+ const reg_t result = argv[0];
+ const reg_t planeObj = argv[1];
+
+ bool visible = true;
+ Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
+ if (plane == nullptr) {
+ plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
+ visible = false;
+ }
+ if (plane == nullptr) {
+ error("kLocalToGlobal: Plane %04x:%04x not found", PRINT_REG(planeObj));
+ }
+
+ const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) + plane->_gameRect.left;
+ const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) + plane->_gameRect.top;
+
+ writeSelectorValue(s->_segMan, result, SELECTOR(x), x);
+ writeSelectorValue(s->_segMan, result, SELECTOR(y), y);
+
+ return make_reg(0, visible);
+}
+
+reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv) {
+ if (argc == 1) {
+ g_sci->getEventManager()->setHotRectanglesActive((bool)argv[0].toUint16());
+ return s->r_acc;
+ }
+
+ const int16 numRects = argv[0].toSint16();
+ SciArray &hotRects = *s->_segMan->lookupArray(argv[1]);
+
+ Common::Array<Common::Rect> rects;
+ rects.resize(numRects);
+
+ for (int16 i = 0; i < numRects; ++i) {
+ rects[i].left = hotRects.getAsInt16(i * 4);
+ rects[i].top = hotRects.getAsInt16(i * 4 + 1);
+ rects[i].right = hotRects.getAsInt16(i * 4 + 2) + 1;
+ rects[i].bottom = hotRects.getAsInt16(i * 4 + 3) + 1;
+ }
+
+ g_sci->getEventManager()->setHotRectanglesActive(true);
+ g_sci->getEventManager()->setHotRectangles(rects);
+ return s->r_acc;
+}
+#endif
+
} // End of namespace Sci
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 335763a35f..796eb08450 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -29,6 +29,7 @@
#include "common/savefile.h"
#include "common/system.h"
#include "common/translation.h"
+#include "common/memstream.h"
#include "gui/saveload.h"
@@ -39,14 +40,18 @@
#include "sci/engine/savegame.h"
#include "sci/sound/audio.h"
#include "sci/console.h"
+#ifdef ENABLE_SCI32
+#include "sci/resource.h"
+#endif
namespace Sci {
-extern reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool unwrapFilename);
+extern reg_t file_open(EngineState *s, const Common::String &filename, kFileOpenMode mode, bool unwrapFilename);
extern FileHandle *getFileFromHandle(EngineState *s, uint handle);
extern int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle);
extern void listSavegames(Common::Array<SavegameDesc> &saves);
extern int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId);
+extern bool fillSavegameDesc(const Common::String &filename, SavegameDesc *desc);
/**
* Writes the cwd to the supplied address and returns the address in acc.
@@ -154,33 +159,37 @@ reg_t kDeviceInfo(EngineState *s, int argc, reg_t *argv) {
}
reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) {
- if (argc > 1) {
- // SCI1.1/SCI32
- // TODO: don't know if those are right for SCI32 as well
- // Please note that sierra sci supported both calls either w/ or w/o opcode in SCI1.1
- switch (argv[1].toUint16()) {
- case 0: // return saved game size
- return make_reg(0, 0); // we return 0
-
- case 1: // return free harddisc space (shifted right somehow)
- return make_reg(0, 0x7fff); // we return maximum
-
- case 2: // same as call w/o opcode
- break;
- return make_reg(0, 1);
-
- default:
- error("kCheckFreeSpace: called with unknown sub-op %d", argv[1].toUint16());
- }
+ // A file path to test is also passed to this function as a separate
+ // argument, but we do not actually check anything, so it is unused
+
+ enum {
+ kSaveGameSize = 0,
+ kFreeDiskSpace = 1,
+ kEnoughSpaceToSave = 2
+ };
+
+ int16 subop;
+ // In SCI2.1mid, the call is moved into kFileIO and the arguments are
+ // flipped
+ if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+ subop = argc > 0 ? argv[0].toSint16() : 2;
+ } else {
+ subop = argc > 1 ? argv[1].toSint16() : 2;
}
- Common::String path = s->_segMan->getString(argv[0]);
+ switch (subop) {
+ case kSaveGameSize:
+ return make_reg(0, 0);
- debug(3, "kCheckFreeSpace(%s)", path.c_str());
- // We simply always pretend that there is enough space. The alternative
- // would be to write a big test file, which is not nice on systems where
- // doing so is very slow.
- return make_reg(0, 1);
+ case kFreeDiskSpace: // in KiB; up to 32MiB maximum
+ return make_reg(0, 0x7fff);
+
+ case kEnoughSpaceToSave:
+ return make_reg(0, 1);
+
+ default:
+ error("kCheckFreeSpace: called with unknown sub-op %d", subop);
+ }
}
reg_t kValidPath(EngineState *s, int argc, reg_t *argv) {
@@ -195,32 +204,70 @@ reg_t kValidPath(EngineState *s, int argc, reg_t *argv) {
#ifdef ENABLE_SCI32
reg_t kCD(EngineState *s, int argc, reg_t *argv) {
- // TODO: Stub
- switch (argv[0].toUint16()) {
- case 0:
- if (argc == 1) {
- // Check if a disc is in the drive
- return TRUE_REG;
- } else {
- // Check if the specified disc is in the drive
- // and return the current disc number. We just
- // return the requested disc number.
- return argv[1];
- }
- case 1:
- // Return the current CD number
- return make_reg(0, 1);
- default:
- warning("CD(%d)", argv[0].toUint16());
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kCheckCD(EngineState *s, int argc, reg_t *argv) {
+ const int16 cdNo = argc > 0 ? argv[0].toSint16() : 0;
+
+ if (cdNo) {
+ g_sci->getResMan()->findDisc(cdNo);
}
- return NULL_REG;
+ return make_reg(0, g_sci->getResMan()->getCurrentDiscNo());
+}
+
+reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv) {
+ // TODO: This is wrong, CD number needs to be available prior to
+ // the save game being loaded
+ return make_reg(0, g_sci->getResMan()->getCurrentDiscNo());
}
#endif
// ---- FileIO operations -----------------------------------------------------
+#ifdef ENABLE_SCI32
+static bool isSaveCatalogue(const Common::String &name) {
+ return name == "autosave.cat" || name.hasSuffix("sg.cat");
+}
+
+// SCI32 save game scripts check for, and write directly to, the save game
+// catalogue. Since ScummVM does not use these catalogues, when looking for a
+// catalogue, we instead check for save games within ScummVM that are logically
+// equivalent to the behaviour of SSCI.
+static bool saveCatalogueExists(const Common::String &name) {
+ bool exists = false;
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+
+ // There will always be one save game in some games, the "new game"
+ // game, which should be ignored when deciding if there are any save
+ // games available
+ uint numPermanentSaves;
+ switch (g_sci->getGameId()) {
+ case GID_TORIN:
+ case GID_LSL7:
+ case GID_LIGHTHOUSE:
+ numPermanentSaves = 1;
+ break;
+ default:
+ numPermanentSaves = 0;
+ break;
+ }
+
+ // Torin uses autosave.cat; LSL7 uses autosvsg.cat
+ if (name == "autosave.cat" || name == "autosvsg.cat") {
+ exists = !saveFileMan->listSavefiles(g_sci->getSavegameName(0)).empty();
+ } else {
+ exists = saveFileMan->listSavefiles(g_sci->getSavegamePattern()).size() > numPermanentSaves;
+ }
+
+ return exists;
+}
+#endif
+
reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
if (!s)
return make_reg(0, getSciVersion());
@@ -230,9 +277,13 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
Common::String name = s->_segMan->getString(argv[0]);
- // SCI32 can call K_FILEIO_OPEN with only one argument. It seems to
- // just be checking if it exists.
- int mode = (argc < 2) ? (int)_K_FILE_MODE_OPEN_OR_FAIL : argv[1].toUint16();
+ if (name.empty()) {
+ // Happens many times during KQ1 (e.g. when typing something)
+ debugC(kDebugLevelFile, "Attempted to open a file with an empty filename");
+ return SIGNAL_REG;
+ }
+
+ kFileOpenMode mode = (kFileOpenMode)argv[1].toUint16();
bool unwrapFilename = true;
// SQ4 floppy prepends /\ to the filenames
@@ -250,85 +301,158 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
return SIGNAL_REG;
}
- if (name.empty()) {
- // Happens many times during KQ1 (e.g. when typing something)
- debugC(kDebugLevelFile, "Attempted to open a file with an empty filename");
- return SIGNAL_REG;
- }
- debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode);
-
- if (name.hasPrefix("sciAudio\\")) {
- // fan-made sciAudio extension, don't create those files and instead return a virtual handle
- return make_reg(0, VIRTUALFILE_HANDLE_SCIAUDIO);
- }
-
#ifdef ENABLE_SCI32
- if (name == PHANTASMAGORIA_SAVEGAME_INDEX) {
- if (s->_virtualIndexFile) {
- return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
- } else {
- Common::String englishName = g_sci->getSciLanguageString(name, K_LANG_ENGLISH);
- Common::String wrappedName = g_sci->wrapFilename(englishName);
- if (!g_sci->getSaveFileManager()->listSavefiles(wrappedName).empty()) {
- s->_virtualIndexFile = new VirtualIndexFile(wrappedName);
- return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
- }
+ // GK1, GK2, KQ7, LSL6hires, Phant1, PQ4, PQ:SWAT, and SQ6 read in
+ // their game version from the VERSION file
+ if (name.compareToIgnoreCase("version") == 0) {
+ unwrapFilename = false;
+
+ // LSL6hires version is in a file with an empty extension
+ if (Common::File::exists(name + ".")) {
+ name += ".";
}
}
- // Shivers is trying to store savegame descriptions and current spots in
- // separate .SG files, which are hardcoded in the scripts.
- // Essentially, there is a normal save file, created by the executable
- // and an extra hardcoded save file, created by the game scripts, probably
- // because they didn't want to modify the save/load code to add the extra
- // information.
- // Each slot in the book then has two strings, the save description and a
- // description of the current spot that the player is at. Currently, the
- // spot strings are always empty (probably related to the unimplemented
- // kString subop 14, which gets called right before this call).
- // For now, we don't allow the creation of these files, which means that
- // all the spot descriptions next to each slot description will be empty
- // (they are empty anyway). Until a viable solution is found to handle these
- // extra files and until the spot description strings are initialized
- // correctly, we resort to virtual files in order to make the load screen
- // useable. Without this code it is unusable, as the extra information is
- // always saved to 0.SG for some reason, but on restore the correct file is
- // used. Perhaps the virtual ID is not taken into account when saving.
- //
- // Future TODO: maintain spot descriptions and show them too, ideally without
- // having to return to this logic of extra hardcoded files.
if (g_sci->getGameId() == GID_SHIVERS && name.hasSuffix(".SG")) {
+ // Shivers stores the name and score of save games in separate %d.SG
+ // files, which are used by the save/load screen
if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) {
- // Game scripts are trying to create a file with the save
- // description, stop them here
+ // Suppress creation of the SG file, since it is not necessary
debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str());
return SIGNAL_REG;
} else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
// Create a virtual file containing the save game description
// and slot number, as the game scripts expect.
- int slotNumber;
- sscanf(name.c_str(), "%d.SG", &slotNumber);
+ int saveNo;
+ sscanf(name.c_str(), "%d.SG", &saveNo);
+ saveNo += kSaveIdShift;
- Common::Array<SavegameDesc> saves;
- listSavegames(saves);
- int savegameNr = findSavegame(saves, slotNumber - SAVEGAMEID_OFFICIALRANGE_START);
+ SavegameDesc save;
+ fillSavegameDesc(g_sci->getSavegameName(saveNo), &save);
- if (!s->_virtualIndexFile) {
- // Make the virtual file buffer big enough to avoid having it grow dynamically.
- // 50 bytes should be more than enough.
- s->_virtualIndexFile = new VirtualIndexFile(50);
+ Common::String score;
+ if (!save.highScore) {
+ score = Common::String::format("%u", save.lowScore);
+ } else {
+ score = Common::String::format("%u%03u", save.highScore, save.lowScore);
}
- s->_virtualIndexFile->seek(0, SEEK_SET);
- s->_virtualIndexFile->write(saves[savegameNr].name, strlen(saves[savegameNr].name));
- s->_virtualIndexFile->write("\0", 1);
- s->_virtualIndexFile->write("\0", 1); // Spot description (empty)
- s->_virtualIndexFile->seek(0, SEEK_SET);
+ const uint nameLength = strlen(save.name);
+ const uint size = nameLength + /* \r\n */ 2 + score.size();
+ char *buffer = (char *)malloc(size);
+ memcpy(buffer, save.name, nameLength);
+ buffer[nameLength] = '\r';
+ buffer[nameLength + 1] = '\n';
+ memcpy(buffer + nameLength + 2, score.c_str(), score.size());
+
+ const uint handle = findFreeFileHandle(s);
+
+ s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buffer, size, DisposeAfterUse::YES);
+ s->_fileHandles[handle]._out = nullptr;
+ s->_fileHandles[handle]._name = "";
+
+ return make_reg(0, handle);
+ }
+ } else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES && name.hasSuffix(".DTA")) {
+ // MGDX stores the name and avatar ID in separate %d.DTA files, which
+ // are used by the save/load screen
+ if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) {
+ // Suppress creation of the DTA file, since it is not necessary
+ debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str());
+ return SIGNAL_REG;
+ } else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
+ // Create a virtual file containing the save game description
+ // and slot number, as the game scripts expect.
+ int saveNo;
+ sscanf(name.c_str(), "%d.DTA", &saveNo);
+ saveNo += kSaveIdShift;
+
+ SavegameDesc save;
+ fillSavegameDesc(g_sci->getSavegameName(saveNo), &save);
+
+ const Common::String avatarId = Common::String::format("%02d", save.avatarId);
+ const uint nameLength = strlen(save.name);
+ const uint size = nameLength + /* \r\n */ 2 + avatarId.size() + 1;
+ char *buffer = (char *)malloc(size);
+ memcpy(buffer, save.name, nameLength);
+ buffer[nameLength] = '\r';
+ buffer[nameLength + 1] = '\n';
+ memcpy(buffer + nameLength + 2, avatarId.c_str(), avatarId.size() + 1);
+
+ const uint handle = findFreeFileHandle(s);
+
+ s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buffer, size, DisposeAfterUse::YES);
+ s->_fileHandles[handle]._out = nullptr;
+ s->_fileHandles[handle]._name = "";
+
+ return make_reg(0, handle);
+ }
+ } else if (g_sci->getGameId() == GID_KQ7) {
+ // KQ7 creates a temp.tmp file to perform an atomic rewrite of the
+ // catalogue, but since we do not create catalogues for most SCI32
+ // games, ignore the write
+ if (name == "temp.tmp") {
+ return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+ }
+
+ // KQ7 tries to read out game information from catalogues directly
+ // instead of using the standard kSaveGetFiles function
+ if (name == "kq7cdsg.cat") {
+ if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) {
+ // Suppress creation of the catalogue file, since it is not necessary
+ debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str());
+ return SIGNAL_REG;
+ } else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
+ Common::Array<SavegameDesc> saves;
+ listSavegames(saves);
+
+ const uint recordSize = sizeof(int16) + SCI_MAX_SAVENAME_LENGTH;
+ const uint numSaves = MIN<uint>(saves.size(), 10);
+ const uint size = numSaves * recordSize + /* terminator */ 2;
+ byte *const buffer = (byte *)malloc(size);
+
+ byte *out = buffer;
+ for (uint i = 0; i < numSaves; ++i) {
+ WRITE_UINT16(out, saves[i].id - kSaveIdShift);
+ Common::strlcpy((char *)out + sizeof(int16), saves[i].name, SCI_MAX_SAVENAME_LENGTH);
+ out += recordSize;
+ }
+ WRITE_UINT16(out, 0xFFFF);
+
+ const uint handle = findFreeFileHandle(s);
+ s->_fileHandles[handle]._in = new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES);
+ s->_fileHandles[handle]._out = nullptr;
+ s->_fileHandles[handle]._name = "";
+
+ return make_reg(0, handle);
+ }
+ }
+ }
+
+ // See kMakeSaveCatName
+ if (name == "fake.cat") {
+ return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+ }
+
+ if (isSaveCatalogue(name)) {
+ const bool exists = saveCatalogueExists(name);
+ if (exists) {
+ // Dummy handle is used to represent the catalogue and ignore any
+ // direct game script writes
return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+ } else {
+ return SIGNAL_REG;
}
}
#endif
+ debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode);
+
+ if (name.hasPrefix("sciAudio\\")) {
+ // fan-made sciAudio extension, don't create those files and instead return a virtual handle
+ return make_reg(0, VIRTUALFILE_HANDLE_SCIAUDIO);
+ }
+
// QFG import rooms get a virtual filelisting instead of an actual one
if (g_sci->inQfGImportRoom()) {
// We need to find out what the user actually selected, "savedHeroes" is
@@ -349,16 +473,9 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {
uint16 handle = argv[0].toUint16();
-#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) {
- s->_virtualIndexFile->close();
- return SIGNAL_REG;
- }
-#endif
-
if (handle >= VIRTUALFILE_HANDLE_START) {
// it's a virtual handle? ignore it
- return SIGNAL_REG;
+ return getSciVersion() >= SCI_VERSION_2 ? TRUE_REG : SIGNAL_REG;
}
FileHandle *f = getFileFromHandle(s, handle);
@@ -366,7 +483,7 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {
f->close();
if (getSciVersion() <= SCI_VERSION_0_LATE)
return s->r_acc; // SCI0 semantics: no value returned
- return SIGNAL_REG;
+ return getSciVersion() >= SCI_VERSION_2 ? TRUE_REG : SIGNAL_REG;
}
if (getSciVersion() <= SCI_VERSION_0_LATE)
@@ -381,17 +498,9 @@ reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) {
char *buf = new char[size];
debugC(kDebugLevelFile, "kFileIO(readRaw): %d, %d", handle, size);
-#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) {
- bytesRead = s->_virtualIndexFile->read(buf, size);
- } else {
-#endif
- FileHandle *f = getFileFromHandle(s, handle);
- if (f)
- bytesRead = f->_in->read(buf, size);
-#ifdef ENABLE_SCI32
- }
-#endif
+ FileHandle *f = getFileFromHandle(s, handle);
+ if (f)
+ bytesRead = f->_in->read(buf, size);
// TODO: What happens if less bytes are read than what has
// been requested? (i.e. if bytesRead is non-zero, but still
@@ -406,27 +515,37 @@ reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) {
reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) {
uint16 handle = argv[0].toUint16();
uint16 size = argv[2].toUint16();
+
+#ifdef ENABLE_SCI32
+ if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) {
+ return make_reg(0, size);
+ }
+#endif
+
char *buf = new char[size];
+ uint bytesWritten = 0;
bool success = false;
s->_segMan->memcpy((byte *)buf, argv[1], size);
debugC(kDebugLevelFile, "kFileIO(writeRaw): %d, %d", handle, size);
+ FileHandle *f = getFileFromHandle(s, handle);
+ if (f) {
+ bytesWritten = f->_out->write(buf, size);
+ success = !f->_out->err();
+ }
+
+ delete[] buf;
+
#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) {
- s->_virtualIndexFile->write(buf, size);
- success = true;
- } else {
-#endif
- FileHandle *f = getFileFromHandle(s, handle);
- if (f) {
- f->_out->write(buf, size);
- success = true;
+ if (getSciVersion() >= SCI_VERSION_2) {
+ if (!success) {
+ return SIGNAL_REG;
}
-#ifdef ENABLE_SCI32
+
+ return make_reg(0, bytesWritten);
}
#endif
- delete[] buf;
if (success)
return NULL_REG;
return make_reg(0, 6); // DOS - invalid handle
@@ -456,19 +575,21 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {
int savedir_nr = saves[slotNum].id;
name = g_sci->getSavegameName(savedir_nr);
result = saveFileMan->removeSavefile(name);
+#ifdef ENABLE_SCI32
} else if (getSciVersion() >= SCI_VERSION_2) {
+ // Special case for KQ7, basically identical to the SQ4 case above,
+ // where the game hardcodes its save game names
+ if (name.hasPrefix("kq7cdsg.")) {
+ int saveNo = atoi(name.c_str() + name.size() - 3);
+ name = g_sci->getSavegameName(saveNo + kSaveIdShift);
+ }
+
// The file name may be already wrapped, so check both cases
result = saveFileMan->removeSavefile(name);
if (!result) {
const Common::String wrappedName = g_sci->wrapFilename(name);
result = saveFileMan->removeSavefile(wrappedName);
}
-
-#ifdef ENABLE_SCI32
- if (name == PHANTASMAGORIA_SAVEGAME_INDEX) {
- delete s->_virtualIndexFile;
- s->_virtualIndexFile = 0;
- }
#endif
} else {
const Common::String wrappedName = g_sci->wrapFilename(name);
@@ -476,6 +597,13 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {
}
debugC(kDebugLevelFile, "kFileIO(unlink): %s", name.c_str());
+
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ return make_reg(0, result);
+ }
+#endif
+
if (result)
return NULL_REG;
return make_reg(0, 2); // DOS - file not found error code
@@ -488,12 +616,7 @@ reg_t kFileIOReadString(EngineState *s, int argc, reg_t *argv) {
debugC(kDebugLevelFile, "kFileIO(readString): %d, %d", handle, maxsize);
uint32 bytesRead;
-#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE)
- bytesRead = s->_virtualIndexFile->readLine(buf, maxsize);
- else
-#endif
- bytesRead = fgets_wrapper(s, buf, maxsize, handle);
+ bytesRead = fgets_wrapper(s, buf, maxsize, handle);
s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize);
delete[] buf;
@@ -520,16 +643,9 @@ reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
-#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) {
- s->_virtualIndexFile->write(str.c_str(), str.size());
- return NULL_REG;
- }
-#endif
-
FileHandle *f = getFileFromHandle(s, handle);
- if (f) {
+ if (f && f->_out) {
f->_out->write(str.c_str(), str.size());
if (getSciVersion() <= SCI_VERSION_0_LATE)
return s->r_acc; // SCI0 semantics: no value returned
@@ -543,27 +659,21 @@ reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) {
reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv) {
uint16 handle = argv[0].toUint16();
- uint16 offset = ABS<int16>(argv[1].toSint16()); // can be negative
+ int16 offset = argv[1].toSint16();
uint16 whence = argv[2].toUint16();
debugC(kDebugLevelFile, "kFileIO(seek): %d, %d, %d", handle, offset, whence);
-#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE)
- return make_reg(0, s->_virtualIndexFile->seek(offset, whence));
-#endif
-
FileHandle *f = getFileFromHandle(s, handle);
if (f && f->_in) {
- // Backward seeking isn't supported in zip file streams, thus adapt the
- // parameters accordingly if games ask for such a seek mode. A known
- // case where this is requested is the save file manager in Phantasmagoria
- if (whence == SEEK_END) {
- whence = SEEK_SET;
- offset = f->_in->size() - offset;
+ const bool success = f->_in->seek(offset, whence);
+ if (getSciVersion() >= SCI_VERSION_2) {
+ if (success) {
+ return make_reg(0, f->_in->pos());
+ }
+ return SIGNAL_REG;
}
-
- return make_reg(0, f->_in->seek(offset, whence));
+ return make_reg(0, success);
} else if (f && f->_out) {
error("kFileIOSeek: Unsupported seek operation on a writeable stream (offset: %d, whence: %d)", offset, whence);
}
@@ -591,14 +701,6 @@ reg_t kFileIOFindNext(EngineState *s, int argc, reg_t *argv) {
reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) {
Common::String name = s->_segMan->getString(argv[0]);
-#ifdef ENABLE_SCI32
- // Cache the file existence result for the Phantasmagoria
- // save index file, as the game scripts keep checking for
- // its existence.
- if (name == PHANTASMAGORIA_SAVEGAME_INDEX && s->_virtualIndexFile)
- return TRUE_REG;
-#endif
-
bool exists = false;
if (g_sci->getGameId() == GID_PEPPER) {
@@ -611,6 +713,15 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
+#ifdef ENABLE_SCI32
+ if (isSaveCatalogue(name)) {
+ return saveCatalogueExists(name) ? TRUE_REG : NULL_REG;
+ }
+#endif
+
+ // TODO: It may apparently be worth caching the existence of
+ // phantsg.dir, and possibly even keeping it open persistently
+
// Check for regular file
exists = Common::File::exists(name);
@@ -673,6 +784,15 @@ reg_t kFileIORename(EngineState *s, int argc, reg_t *argv) {
Common::String oldName = s->_segMan->getString(argv[0]);
Common::String newName = s->_segMan->getString(argv[1]);
+ // We don't fully implement all cases that could occur here, and
+ // assume the file to be renamed is a wrapped filename.
+ // Known usage: In Phant1 and KQ7 while deleting savegames.
+ // The scripts rewrite the dir file as a temporary file, and then
+ // rename it to the actual dir file.
+
+ oldName = g_sci->wrapFilename(oldName);
+ newName = g_sci->wrapFilename(newName);
+
// SCI1.1 returns 0 on success and a DOS error code on fail. SCI32
// returns -1 on fail. We just return -1 for all versions.
if (g_sci->getSaveFileManager()->renameSavefile(oldName, newName))
@@ -694,7 +814,7 @@ reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv) {
FileHandle *f = getFileFromHandle(s, argv[0].toUint16());
if (f)
f->_out->writeByte(argv[1].toUint16() & 0xff);
- return s->r_acc; // FIXME: does this really not return anything?
+ return s->r_acc;
}
reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv) {
@@ -708,27 +828,13 @@ reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv) {
FileHandle *f = getFileFromHandle(s, argv[0].toUint16());
if (f)
f->_out->writeUint16LE(argv[1].toUint16());
- return s->r_acc; // FIXME: does this really not return anything?
+ return s->r_acc;
}
-reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv) {
- // Used in Shivers when the user enters his name on the guest book
- // in the beginning to start the game.
-
- // Creates a new save slot, and returns if the operation was successful
-
- // Argument 0 denotes the save slot as a negative integer, 2 means "0"
- // Argument 1 is a string, with the file name, obtained from kSave(5).
- // The interpreter checks if it can be written to (by checking for free
- // disk space and write permissions)
-
- // We don't really use or need any of this...
-
- uint16 saveSlot = argv[0].toUint16();
- char* fileName = s->_segMan->lookupString(argv[1])->getRawData();
- warning("kFileIOCreateSaveSlot(%d, '%s')", saveSlot, fileName);
-
- return TRUE_REG; // slot creation was successful
+reg_t kFileIOGetCWD(EngineState *s, int argc, reg_t *argv) {
+ SciArray &fileName = *s->_segMan->lookupArray(argv[0]);
+ fileName.fromString("C:\\SIERRA\\");
+ return argv[0];
}
reg_t kFileIOIsValidDirectory(EngineState *s, int argc, reg_t *argv) {
@@ -753,7 +859,14 @@ reg_t kSave(EngineState *s, int argc, reg_t *argv) {
#endif
reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
- Common::String game_id;
+ // slot 0 is the ScummVM auto-save slot, which is not used by us, but is
+ // still reserved
+ enum {
+ SAVEGAMESLOT_FIRST = 1,
+ SAVEGAMESLOT_LAST = 99
+ };
+
+ Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : "";
int16 virtualId = argv[1].toSint16();
int16 savegameId = -1;
Common::String game_description;
@@ -786,10 +899,8 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
g_sci->_soundCmd->pauseAll(false); // unpause music (we can't have it paused during save)
if (savegameId < 0)
return NULL_REG;
-
} else {
// Real call from script
- game_id = s->_segMan->getString(argv[0]);
if (argv[2].isNull())
error("kSaveGame: called with description being NULL");
game_description = s->_segMan->getString(argv[2]);
@@ -806,25 +917,47 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
} else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) {
// virtualId is low, we assume that scripts expect us to create new slot
- if (g_sci->getGameId() == GID_JONES) {
+ switch (g_sci->getGameId()) {
+ case GID_JONES:
// Jones has one save slot only
savegameId = 0;
- } else if (virtualId == s->_lastSaveVirtualId) {
- // if last virtual id is the same as this one, we assume that caller wants to overwrite last save
- savegameId = s->_lastSaveNewId;
- } else {
- uint savegameNr;
- // savegameId is in lower range, scripts expect us to create a new slot
- for (savegameId = SAVEGAMESLOT_FIRST; savegameId <= SAVEGAMESLOT_LAST; savegameId++) {
- for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) {
- if (savegameId == saves[savegameNr].id)
+ break;
+ case GID_FANMADE: {
+ // Fanmade game, try to identify the game
+ const char *gameName = g_sci->getGameObjectName();
+
+ if (strcmp(gameName, "CascadeQuest") == 0) {
+ // Cascade Quest calls us directly to auto-save and uses slot 99,
+ // put that save into slot 0 (ScummVM auto-save slot) see bug #7007
+ if (virtualId == (SAVEGAMEID_OFFICIALRANGE_START - 1)) {
+ savegameId = 0;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (savegameId < 0) {
+ // savegameId not set yet
+ if (virtualId == s->_lastSaveVirtualId) {
+ // if last virtual id is the same as this one, we assume that caller wants to overwrite last save
+ savegameId = s->_lastSaveNewId;
+ } else {
+ uint savegameNr;
+ // savegameId is in lower range, scripts expect us to create a new slot
+ for (savegameId = SAVEGAMESLOT_FIRST; savegameId <= SAVEGAMESLOT_LAST; savegameId++) {
+ for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) {
+ if (savegameId == saves[savegameNr].id)
+ break;
+ }
+ if (savegameNr == saves.size()) // Slot not found, seems to be good to go
break;
}
- if (savegameNr == saves.size()) // Slot not found, seems to be good to go
- break;
+ if (savegameId > SAVEGAMESLOT_LAST)
+ error("kSavegame: no more savegame slots available");
}
- if (savegameId > SAVEGAMESLOT_LAST)
- error("kSavegame: no more savegame slots available");
}
} else {
error("kSaveGame: invalid savegameId used");
@@ -982,10 +1115,6 @@ reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) {
}
reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
- Common::String game_id = s->_segMan->getString(argv[0]);
-
- debug(3, "kGetSaveFiles(%s)", game_id.c_str());
-
// Scripts ask for current save files, we can assume that if afterwards they ask us to create a new slot they really
// mean new slot instead of overwriting the old one
s->_lastSaveVirtualId = SAVEGAMEID_OFFICIALRANGE_START;
@@ -994,6 +1123,10 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
listSavegames(saves);
uint totalSaves = MIN<uint>(saves.size(), MAX_SAVEGAME_NR);
+ Common::String game_id = s->_segMan->getString(argv[0]);
+
+ debug(3, "kGetSaveFiles(%s)", game_id.c_str());
+
reg_t *slot = s->_segMan->derefRegPtr(argv[2], totalSaves);
if (!slot) {
@@ -1021,46 +1154,205 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
#ifdef ENABLE_SCI32
-reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) {
- // Normally, this creates the name of the save catalogue/directory to save into.
- // First parameter is the string to save the result into. Second is a string
- // with game parameters. We don't have a use for this at all, as we have our own
- // savegame directory management, thus we always return an empty string.
- return argv[0];
+reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
+ const bool isScummVMSave = argv[0].isNull();
+ Common::String gameName = "";
+ int16 saveNo;
+ Common::String saveDescription;
+ Common::String gameVersion = (argc <= 3 || argv[3].isNull()) ? "" : s->_segMan->getString(argv[3]);
+
+ if (isScummVMSave) {
+ // ScummVM call, from a patched Game::save
+ g_sci->_soundCmd->pauseAll(true);
+ GUI::SaveLoadChooser dialog(_("Save game:"), _("Save"), true);
+ saveNo = dialog.runModalWithCurrentTarget();
+ g_sci->_soundCmd->pauseAll(false);
+
+ if (saveNo < 0) {
+ // User cancelled save
+ return NULL_REG;
+ }
+
+ saveDescription = dialog.getResultString();
+ if (saveDescription.empty()) {
+ saveDescription = dialog.createDefaultSaveDescription(saveNo);
+ }
+ } else {
+ // Native script call
+ gameName = s->_segMan->getString(argv[0]);
+ saveNo = argv[1].toSint16();
+ saveDescription = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
+ }
+
+ debugC(kDebugLevelFile, "Game name %s save %d desc %s ver %s", gameName.c_str(), saveNo, saveDescription.c_str(), gameVersion.c_str());
+
+ // Auto-save system used by Torin and LSL7
+ if (gameName == "Autosave" || gameName == "Autosv") {
+ if (saveNo == 0) {
+ // Autosave slot 0 is the autosave
+ } else {
+ // Autosave slot 1 is a "new game" save
+ saveNo = kNewGameId;
+ }
+ } else if (!isScummVMSave) {
+ // ScummVM save screen will give a pre-corrected save number, but native
+ // save-load will not
+ saveNo += kSaveIdShift;
+ }
+
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+ const Common::String filename = g_sci->getSavegameName(saveNo);
+ Common::OutSaveFile *saveStream = saveFileMan->openForSaving(filename);
+
+ if (saveStream == nullptr) {
+ warning("Error opening savegame \"%s\" for writing", filename.c_str());
+ return NULL_REG;
+ }
+
+ if (!gamestate_save(s, saveStream, saveDescription, gameVersion)) {
+ warning("Saving the game failed");
+ saveStream->finalize();
+ delete saveStream;
+ return NULL_REG;
+ }
+
+ saveStream->finalize();
+ if (saveStream->err()) {
+ warning("Writing the savegame failed");
+ delete saveStream;
+ return NULL_REG;
+ }
+
+ delete saveStream;
+ return TRUE_REG;
}
-reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
- // Creates a savegame name from a slot number. Used when deleting saved games.
- // Param 0: the output buffer (same as in kMakeSaveCatName)
- // Param 1: a string with game parameters, ignored
- // Param 2: the selected slot
+reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv) {
+ const bool isScummVMRestore = argv[0].isNull();
+ Common::String gameName = "";
+ int16 saveNo = argv[1].toSint16();
+ const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
+
+ if (isScummVMRestore && saveNo == -1) {
+ // ScummVM call, either from lancher or a patched Game::restore
+ g_sci->_soundCmd->pauseAll(true);
+ GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false);
+ saveNo = dialog.runModalWithCurrentTarget();
+ g_sci->_soundCmd->pauseAll(false);
+
+ if (saveNo < 0) {
+ // User cancelled restore
+ return s->r_acc;
+ }
+ } else {
+ gameName = s->_segMan->getString(argv[0]);
+ }
+
+ if (gameName == "Autosave" || gameName == "Autosv") {
+ if (saveNo == 0) {
+ // Autosave slot 0 is the autosave
+ } else {
+ // Autosave slot 1 is a "new game" save
+ saveNo = kNewGameId;
+ }
+ } else if (!isScummVMRestore) {
+ // ScummVM save screen will give a pre-corrected save number, but native
+ // save-load will not
+ saveNo += kSaveIdShift;
+ }
+
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+ const Common::String filename = g_sci->getSavegameName(saveNo);
+ Common::SeekableReadStream *saveStream = saveFileMan->openForLoading(filename);
+
+ if (saveStream == nullptr) {
+ warning("Savegame #%d not found", saveNo);
+ return NULL_REG;
+ }
+
+ gamestate_restore(s, saveStream);
+ delete saveStream;
- SciString *resultString = s->_segMan->lookupString(argv[0]);
- uint16 virtualId = argv[2].toUint16();
- if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END))
- error("kMakeSaveFileName: invalid savegame ID specified");
- uint saveSlot = virtualId - SAVEGAMEID_OFFICIALRANGE_START;
+ gamestate_afterRestoreFixUp(s, saveNo);
+ return TRUE_REG;
+}
+
+reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv) {
+ const Common::String gameName = s->_segMan->getString(argv[0]);
+ int16 saveNo = argv[1].toSint16();
+ const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
Common::Array<SavegameDesc> saves;
listSavegames(saves);
- Common::String filename = g_sci->getSavegameName(saveSlot);
- resultString->fromString(filename);
+ if (gameName == "Autosave" || gameName == "Autosv") {
+ if (saveNo == 1) {
+ saveNo = kNewGameId;
+ }
+ } else {
+ saveNo += kSaveIdShift;
+ }
- return argv[0];
-}
+ SavegameDesc save;
+ if (!fillSavegameDesc(g_sci->getSavegameName(saveNo), &save)) {
+ return NULL_REG;
+ }
-reg_t kAutoSave(EngineState *s, int argc, reg_t *argv) {
- // TODO
- // This is a timer callback, with 1 parameter: the timer object
- // (e.g. "timers").
- // It's used for auto-saving (i.e. save every X minutes, by checking
- // the elapsed time from the timer object)
+ if (save.version < MINIMUM_SAVEGAME_VERSION ||
+ save.version > CURRENT_SAVEGAME_VERSION ||
+ save.gameVersion != gameVersion) {
+
+ return NULL_REG;
+ }
- // This function has to return something other than 0 to proceed
return TRUE_REG;
}
+reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv) {
+ // argv[0] is gameName, used in SSCI as the name of the save game catalogue
+ // but unused here since ScummVM does not support multiple catalogues
+ SciArray &descriptions = *s->_segMan->lookupArray(argv[1]);
+ SciArray &saveIds = *s->_segMan->lookupArray(argv[2]);
+
+ Common::Array<SavegameDesc> saves;
+ listSavegames(saves);
+
+ // Normally SSCI limits to 20 games per directory, but ScummVM allows more
+ // than that with games that use the standard save-load dialogue
+ descriptions.resize(SCI_MAX_SAVENAME_LENGTH * saves.size() + 1, true);
+ saveIds.resize(saves.size() + 1, true);
+
+ for (uint i = 0; i < saves.size(); ++i) {
+ const SavegameDesc &save = saves[i];
+ char *target = &descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * i);
+ Common::strlcpy(target, save.name, SCI_MAX_SAVENAME_LENGTH);
+ saveIds.setFromInt16(i, save.id - kSaveIdShift);
+ }
+
+ descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * saves.size()) = '\0';
+ saveIds.setFromInt16(saves.size(), 0);
+
+ return make_reg(0, saves.size());
+}
+
+reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) {
+ // ScummVM does not use SCI catalogues for save games, but game scripts try
+ // to write out catalogues manually after a save game is deleted, so we need
+ // to be able to identify and ignore these IO operations by always giving
+ // back a fixed catalogue name and then ignoring it in kFileIO
+ SciArray &outCatName = *s->_segMan->lookupArray(argv[0]);
+ outCatName.fromString("fake.cat");
+ return argv[0];
+}
+
+reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
+ SciArray &outFileName = *s->_segMan->lookupArray(argv[0]);
+ // argv[1] is the game name, which is not used by ScummVM
+ const int16 saveNo = argv[2].toSint16();
+ outFileName.fromString(g_sci->getSavegameName(saveNo + kSaveIdShift));
+ return argv[0];
+}
+
#endif
} // End of namespace Sci
diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp
index 91d241fb79..c605ba29e1 100644
--- a/engines/sci/engine/kgraphics.cpp
+++ b/engines/sci/engine/kgraphics.cpp
@@ -45,6 +45,7 @@
#include "sci/graphics/paint16.h"
#include "sci/graphics/picture.h"
#include "sci/graphics/ports.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/text16.h"
#include "sci/graphics/view.h"
@@ -355,16 +356,11 @@ 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(splitText.c_str(), font_nr, maxwidth, &textWidth, &textHeight);
- else
-#endif
- g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, 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
@@ -445,8 +441,15 @@ reg_t kCantBeHere(EngineState *s, int argc, reg_t *argv) {
reg_t curObject = argv[0];
reg_t listReference = (argc > 1) ? argv[1] : NULL_REG;
- reg_t canBeHere = g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference);
- return canBeHere;
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ return g_sci->_gfxCompare->kernelCantBeHere32(curObject, listReference);
+ } else {
+#endif
+ return g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference);
+#ifdef ENABLE_SCI32
+ }
+#endif
}
reg_t kIsItSkip(EngineState *s, int argc, reg_t *argv) {
@@ -577,7 +580,6 @@ reg_t kBaseSetter(EngineState *s, int argc, reg_t *argv) {
reg_t kSetNowSeen(EngineState *s, int argc, reg_t *argv) {
g_sci->_gfxCompare->kernelSetNowSeen(argv[0]);
-
return s->r_acc;
}
@@ -830,7 +832,7 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) {
uint16 languageSplitter = 0;
Common::String splitText;
-
+
switch (type) {
case SCI_CONTROLS_TYPE_BUTTON:
case SCI_CONTROLS_TYPE_TEXTEDIT:
@@ -1189,7 +1191,7 @@ 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);
@@ -1253,16 +1255,16 @@ reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) {
switch (operation) {
case 0: { // remap by percent
uint16 percent = argv[1].toUint16();
- g_sci->_gfxPalette16->resetRemapping();
- g_sci->_gfxPalette16->setRemappingPercent(254, percent);
+ g_sci->_gfxRemap16->resetRemapping();
+ g_sci->_gfxRemap16->setRemappingPercent(254, percent);
}
break;
case 1: { // remap by range
uint16 from = argv[1].toUint16();
uint16 to = argv[2].toUint16();
uint16 base = argv[3].toUint16();
- g_sci->_gfxPalette16->resetRemapping();
- g_sci->_gfxPalette16->setRemappingRange(254, from, to, base);
+ g_sci->_gfxRemap16->resetRemapping();
+ g_sci->_gfxRemap16->setRemappingRange(254, from, to, base);
}
break;
case 2: // turn remapping off (unused)
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 4d48ae4e99..880ea0f559 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -26,8 +26,6 @@
#include "graphics/cursorman.h"
#include "graphics/surface.h"
-#include "gui/message.h"
-
#include "sci/sci.h"
#include "sci/event.h"
#include "sci/resource.h"
@@ -39,21 +37,25 @@
#include "sci/graphics/cache.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/controls16.h"
-#include "sci/graphics/coordadjuster.h"
-#include "sci/graphics/cursor.h"
#include "sci/graphics/palette.h"
#include "sci/graphics/paint16.h"
#include "sci/graphics/picture.h"
#include "sci/graphics/ports.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/text16.h"
#include "sci/graphics/view.h"
#ifdef ENABLE_SCI32
-#include "sci/graphics/palette32.h"
+#include "sci/graphics/cursor32.h"
+#include "sci/graphics/celobj32.h"
#include "sci/graphics/controls32.h"
#include "sci/graphics/font.h" // TODO: remove once kBitmap is moved in a separate class
-#include "sci/graphics/text32.h"
#include "sci/graphics/frameout.h"
+#include "sci/graphics/paint32.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
+#include "sci/graphics/text32.h"
+#include "sci/graphics/transitions32.h"
#endif
namespace Sci {
@@ -61,69 +63,168 @@ namespace Sci {
extern void showScummVMDialog(const Common::String &message);
-reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) {
- // Returns 0 if the screen width or height is less than 640 or 400,
- // respectively.
- if (g_system->getWidth() < 640 || g_system->getHeight() < 400)
- return make_reg(0, 0);
+reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv) {
+ reg_t object = argv[0];
- return make_reg(0, 1);
+ const GuiResourceId viewId = readSelectorValue(s->_segMan, object, SELECTOR(view));
+ const int16 loopNo = readSelectorValue(s->_segMan, object, SELECTOR(loop));
+ const int16 celNo = readSelectorValue(s->_segMan, object, SELECTOR(cel));
+ const int16 x = readSelectorValue(s->_segMan, object, SELECTOR(x));
+ const int16 y = readSelectorValue(s->_segMan, object, SELECTOR(y));
+
+ CelObjView celObj(viewId, loopNo, celNo);
+
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const Ratio scaleX(scriptWidth, celObj._xResolution);
+
+ int16 brLeft;
+
+ if (celObj._mirrorX) {
+ brLeft = x - ((celObj._width - celObj._origin.x) * scaleX).toInt();
+ } else {
+ brLeft = x - (celObj._origin.x * scaleX).toInt();
+ }
+
+ const int16 brRight = brLeft + (celObj._width * scaleX).toInt() - 1;
+
+ writeSelectorValue(s->_segMan, object, SELECTOR(brLeft), brLeft);
+ writeSelectorValue(s->_segMan, object, SELECTOR(brRight), brRight);
+ writeSelectorValue(s->_segMan, object, SELECTOR(brBottom), y + 1);
+ writeSelectorValue(s->_segMan, object, SELECTOR(brTop), y + 1 - readSelectorValue(s->_segMan, object, SELECTOR(yStep)));
+
+ return s->r_acc;
}
-// SCI32 variant, can't work like sci16 variants
-reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv) {
- // TODO
-// reg_t curObject = argv[0];
-// reg_t listReference = (argc > 1) ? argv[1] : NULL_REG;
+reg_t kSetNowSeen32(EngineState *s, int argc, reg_t *argv) {
+ const bool found = g_sci->_gfxFrameout->kernelSetNowSeen(argv[0]);
+
+ // NOTE: MGDX is assumed to use the older kSetNowSeen since it was
+ // released before SQ6, but this has not been verified since it cannot be
+ // disassembled at the moment (Phar Lap Windows-only release)
+ if (getSciVersion() <= SCI_VERSION_2_1_EARLY ||
+ g_sci->getGameId() == GID_SQ6 ||
+ g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
+
+ if (!found) {
+ error("kSetNowSeen: Unable to find screen item %04x:%04x", PRINT_REG(argv[0]));
+ }
+ return s->r_acc;
+ }
+
+ if (!found) {
+ warning("kSetNowSeen: Unable to find screen item %04x:%04x", PRINT_REG(argv[0]));
+ }
- return NULL_REG;
+ return make_reg(0, found);
+}
+
+reg_t kSetCursor32(EngineState *s, int argc, reg_t *argv) {
+ switch (argc) {
+ case 1: {
+ if (argv[0].toSint16() == -2) {
+ g_sci->_gfxCursor32->clearRestrictedArea();
+ } else {
+ if (argv[0].isNull()) {
+ g_sci->_gfxCursor32->hide();
+ } else {
+ g_sci->_gfxCursor32->show();
+ }
+ }
+ break;
+ }
+ case 2: {
+ const Common::Point position(argv[0].toSint16(), argv[1].toSint16());
+ g_sci->_gfxCursor32->setPosition(position);
+ break;
+ }
+ case 3: {
+ g_sci->_gfxCursor32->setView(argv[0].toUint16(), argv[1].toSint16(), argv[2].toSint16());
+ break;
+ }
+ case 4: {
+ const Common::Rect restrictRect(argv[0].toSint16(),
+ argv[1].toSint16(),
+ argv[2].toSint16() + 1,
+ argv[3].toSint16() + 1);
+ g_sci->_gfxCursor32->setRestrictedArea(restrictRect);
+ break;
+ }
+ default:
+ error("kSetCursor: Invalid number of arguments (%d)", argc);
+ }
+
+ return s->r_acc;
+}
+
+reg_t kShakeScreen32(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_gfxFrameout->shakeScreen(argv[0].toSint16(), (ShakeDirection)argv[1].toSint16());
+ return s->r_acc;
+}
+
+reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) {
+ const Buffer &buffer = g_sci->_gfxFrameout->getCurrentBuffer();
+ if (buffer.screenWidth < 640 || buffer.screenHeight < 400)
+ return make_reg(0, 0);
+
+ return make_reg(0, 1);
}
reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) {
- debugC(6, kDebugLevelGraphics, "kAddScreenItem %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0]));
+ debugC(6, kDebugLevelGraphics, "kAddScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]);
- return NULL_REG;
+ return s->r_acc;
}
reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) {
- debugC(7, kDebugLevelGraphics, "kUpdateScreenItem %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0]));
+ debugC(7, kDebugLevelGraphics, "kUpdateScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]);
- return NULL_REG;
+ return s->r_acc;
}
reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) {
- debugC(6, kDebugLevelGraphics, "kDeleteScreenItem %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0]));
+ debugC(6, kDebugLevelGraphics, "kDeleteScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
g_sci->_gfxFrameout->kernelDeleteScreenItem(argv[0]);
- return NULL_REG;
+ return s->r_acc;
}
reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) {
- debugC(6, kDebugLevelGraphics, "kAddPlane %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0]));
+ debugC(6, kDebugLevelGraphics, "kAddPlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
g_sci->_gfxFrameout->kernelAddPlane(argv[0]);
return s->r_acc;
}
reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) {
- debugC(7, kDebugLevelGraphics, "kUpdatePlane %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0]));
+ debugC(7, kDebugLevelGraphics, "kUpdatePlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
g_sci->_gfxFrameout->kernelUpdatePlane(argv[0]);
return s->r_acc;
}
reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv) {
- debugC(6, kDebugLevelGraphics, "kDeletePlane %x:%x (%s)", argv[0].getSegment(), argv[0].getOffset(), g_sci->getEngineState()->_segMan->getObjectName(argv[0]));
+ debugC(6, kDebugLevelGraphics, "kDeletePlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0]));
g_sci->_gfxFrameout->kernelDeletePlane(argv[0]);
return s->r_acc;
}
+reg_t kMovePlaneItems(EngineState *s, int argc, reg_t *argv) {
+ const reg_t plane = argv[0];
+ const int16 deltaX = argv[1].toSint16();
+ const int16 deltaY = argv[2].toSint16();
+ const bool scrollPics = argc > 3 ? argv[3].toUint16() : false;
+
+ g_sci->_gfxFrameout->kernelMovePlaneItems(plane, deltaX, deltaY, scrollPics);
+ return s->r_acc;
+}
+
reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv) {
reg_t planeObj = argv[0];
GuiResourceId pictureId = argv[1].toUint16();
int16 x = argv[2].toSint16();
int16 y = argv[3].toSint16();
bool mirrorX = argc > 4 ? argv[4].toSint16() : false;
+ bool deleteDuplicate = argc > 5 ? argv[5].toSint16() : true;
- g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, x, y, mirrorX);
- return NULL_REG;
+ g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, x, y, mirrorX, deleteDuplicate);
+ return s->r_acc;
}
reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) {
@@ -132,15 +233,13 @@ reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) {
reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) {
bool showBits = argc > 0 ? argv[0].toUint16() : true;
- g_sci->_gfxFrameout->kernelFrameout(showBits);
- s->speedThrottler(16);
- s->_throttleTrigger = true;
- return NULL_REG;
+ g_sci->_gfxFrameout->kernelFrameOut(showBits);
+ return s->r_acc;
}
reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv) {
- g_sci->_gfxFrameout->kernelSetPalStyleRange(argv[0].toUint16(), argv[1].toUint16());
- return NULL_REG;
+ g_sci->_gfxTransitions32->kernelSetPalStyleRange(argv[0].toUint16(), argv[1].toUint16());
+ return s->r_acc;
}
reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv) {
@@ -149,14 +248,13 @@ reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, objRect1.intersects(objRect2));
}
-// Tests if the coordinate is on the passed object
reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv) {
- uint16 x = argv[0].toUint16();
- uint16 y = argv[1].toUint16();
- reg_t targetObject = argv[2];
- uint16 checkPixels = argv[3].getOffset();
+ int16 x = argv[0].toSint16();
+ int16 y = argv[1].toSint16();
+ reg_t object = argv[2];
+ bool checkPixel = argv[3].toSint16();
- return make_reg(0, g_sci->_gfxFrameout->kernelIsOnMe(x, y, checkPixels, targetObject));
+ return g_sci->_gfxFrameout->kernelIsOnMe(object, Common::Point(x, y), checkPixel);
}
reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) {
@@ -196,24 +294,53 @@ reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) {
if (subop == 0) {
TextAlign alignment = (TextAlign)readSelectorValue(segMan, object, SELECTOR(mode));
- reg_t out;
- return g_sci->_gfxText32->createFontBitmap(width, height, rect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, 1, &out);
+ return g_sci->_gfxText32->createFontBitmap(width, height, rect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, true, true);
} else {
CelInfo32 celInfo;
celInfo.type = kCelTypeView;
celInfo.resourceId = readSelectorValue(segMan, object, SELECTOR(view));
celInfo.loopNo = readSelectorValue(segMan, object, SELECTOR(loop));
celInfo.celNo = readSelectorValue(segMan, object, SELECTOR(cel));
- reg_t out;
- return g_sci->_gfxText32->createTitledFontBitmap(celInfo, rect, text, foreColor, backColor, fontId, skipColor, borderColor, dimmed, &out);
+ return g_sci->_gfxText32->createFontBitmap(celInfo, rect, text, foreColor, backColor, fontId, skipColor, borderColor, dimmed, true);
}
}
-reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv) {
- g_sci->_gfxText32->disposeTextBitmap(argv[0]);
+reg_t kText(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kTextSize32(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_gfxText32->setFont(argv[2].toUint16());
+
+ SciArray *rect = s->_segMan->lookupArray(argv[0]);
+ if (rect == nullptr) {
+ error("kTextSize: %04x:%04x cannot be dereferenced", PRINT_REG(argv[0]));
+ }
+
+ Common::String text = s->_segMan->getString(argv[1]);
+ int16 maxWidth = argc > 3 ? argv[3].toSint16() : 0;
+ bool doScaling = argc > 4 ? argv[4].toSint16() : true;
+
+ Common::Rect textRect = g_sci->_gfxText32->getTextSize(text, maxWidth, doScaling);
+
+ reg_t value[4] = {
+ make_reg(0, textRect.left),
+ make_reg(0, textRect.top),
+ make_reg(0, textRect.right - 1),
+ make_reg(0, textRect.bottom - 1) };
+
+ rect->setElements(0, 4, value);
return s->r_acc;
}
+reg_t kTextWidth(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_gfxText32->setFont(argv[1].toUint16());
+ Common::String text = s->_segMan->getString(argv[0]);
+ return make_reg(0, g_sci->_gfxText32->getStringWidth(text));
+}
+
reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) {
switch (argv[0].toUint16()) {
case 1:
@@ -231,12 +358,16 @@ reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
+reg_t kMessageBox(EngineState *s, int argc, reg_t *argv) {
+ return g_sci->_gfxControls32->kernelMessageBox(s->_segMan->getString(argv[0]), s->_segMan->getString(argv[1]), argv[2].toUint16());
+}
+
/**
* Causes an immediate plane transition with an optional transition
* effect
*/
reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
- ShowStyleType type = (ShowStyleType)argv[0].toUint16();
+ const uint16 type = argv[0].toUint16();
reg_t planeObj = argv[1];
int16 seconds = argv[2].toSint16();
// NOTE: This value seems to indicate whether the transition is an
@@ -271,6 +402,10 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
divisions = argc > 9 ? argv[9].toSint16() : -1;
}
+ if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() != GID_KQ7 && type == 15) || type > 15) {
+ error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj));
+ }
+
// TODO: Reuse later for SCI2 and SCI3 implementation and then discard
// warning("kSetShowStyle: effect %d, plane: %04x:%04x (%s), sec: %d, "
// "dir: %d, prio: %d, animate: %d, ref frame: %d, black screen: %d, "
@@ -282,33 +417,52 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
// NOTE: The order of planeObj and showStyle are reversed
// because this is how SCI3 called the corresponding method
// on the KernelMgr
- g_sci->_gfxFrameout->kernelSetShowStyle(argc, planeObj, type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen);
+ g_sci->_gfxTransitions32->kernelSetShowStyle(argc, planeObj, (ShowStyleType)type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen);
+
+ return s->r_acc;
+}
+
+reg_t kCelHigh32(EngineState *s, int argc, reg_t *argv) {
+ GuiResourceId resourceId = argv[0].toUint16();
+ int16 loopNo = argv[1].toSint16();
+ int16 celNo = argv[2].toSint16();
+ CelObjView celObj(resourceId, loopNo, celNo);
+ return make_reg(0, mulru(celObj._height, Ratio(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, celObj._yResolution)));
+}
- return NULL_REG;
+reg_t kCelWide32(EngineState *s, int argc, reg_t *argv) {
+ GuiResourceId resourceId = argv[0].toUint16();
+ int16 loopNo = argv[1].toSint16();
+ int16 celNo = argv[2].toSint16();
+ CelObjView celObj(resourceId, loopNo, celNo);
+ return make_reg(0, mulru(celObj._width, Ratio(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth, celObj._xResolution)));
}
reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) {
// Used by Shivers 1, room 23601 to determine what blocks on the red door puzzle board
// are occupied by pieces already
- switch (argv[0].toUint16()) { // subops 0 - 4
- // 0 - return the view
- // 1 - return the loop
- // 2, 3 - nop
- case 4: {
- GuiResourceId viewId = argv[1].toSint16();
- int16 loopNo = argv[2].toSint16();
- int16 celNo = argv[3].toSint16();
- int16 x = argv[4].toUint16();
- int16 y = argv[5].toUint16();
- byte color = g_sci->_gfxCache->kernelViewGetColorAtCoordinate(viewId, loopNo, celNo, x, y);
- return make_reg(0, color);
- }
- default: {
- kStub(s, argc, argv);
- return s->r_acc;
- }
+ CelObjView view(argv[1].toUint16(), argv[2].toSint16(), argv[3].toSint16());
+
+ int16 result = 0;
+
+ switch (argv[0].toUint16()) {
+ case 0:
+ result = view._origin.x;
+ break;
+ case 1:
+ result = view._origin.y;
+ break;
+ case 2:
+ case 3:
+ // null operation
+ break;
+ case 4:
+ result = view.readPixel(argv[4].toSint16(), argv[5].toSint16(), view._mirrorX);
+ break;
}
+
+ return make_reg(0, result);
}
reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
@@ -318,439 +472,515 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
}
reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv) {
- debug("kScrollWindowCreate");
- kStub(s, argc, argv);
- return argv[0];
+ const reg_t object = argv[0];
+ const uint16 maxNumEntries = argv[1].toUint16();
+
+ SegManager *segMan = s->_segMan;
+ const int16 borderColor = readSelectorValue(segMan, object, SELECTOR(borderColor));
+ const TextAlign alignment = (TextAlign)readSelectorValue(segMan, object, SELECTOR(mode));
+ const GuiResourceId fontId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(font));
+ const int16 backColor = readSelectorValue(segMan, object, SELECTOR(back));
+ const int16 foreColor = readSelectorValue(segMan, object, SELECTOR(fore));
+ const reg_t plane = readSelector(segMan, object, SELECTOR(plane));
+
+ Common::Rect rect;
+
+ if (g_sci->_features->usesAlternateSelectors()) {
+ rect.left = readSelectorValue(segMan, object, SELECTOR(left));
+ rect.top = readSelectorValue(segMan, object, SELECTOR(top));
+ rect.right = readSelectorValue(segMan, object, SELECTOR(right)) + 1;
+ rect.bottom = readSelectorValue(segMan, object, SELECTOR(bottom)) + 1;
+ } else {
+ rect.left = readSelectorValue(segMan, object, SELECTOR(nsLeft));
+ rect.top = readSelectorValue(segMan, object, SELECTOR(nsTop));
+ rect.right = readSelectorValue(segMan, object, SELECTOR(nsRight)) + 1;
+ rect.bottom = readSelectorValue(segMan, object, SELECTOR(nsBottom)) + 1;
+ }
+ const Common::Point position(rect.left, rect.top);
+
+ return g_sci->_gfxControls32->makeScrollWindow(rect, position, plane, foreColor, backColor, fontId, alignment, borderColor, maxNumEntries);
}
reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv) {
- debug("kScrollWindowAdd");
- return kStubNull(s, argc, argv);
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ const Common::String text = s->_segMan->getString(argv[1]);
+ const GuiResourceId fontId = argv[2].toSint16();
+ const int16 color = argv[3].toSint16();
+ const TextAlign alignment = (TextAlign)argv[4].toSint16();
+ const bool scrollTo = argc > 5 ? (bool)argv[5].toUint16() : true;
+
+ return scrollWindow->add(text, fontId, color, alignment, scrollTo);
}
reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv) {
- debug("kScrollWindowWhere");
- return kStubNull(s, argc, argv);
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ const uint16 where = (argv[1].toUint16() * scrollWindow->where()).toInt();
+
+ return make_reg(0, where);
+}
+
+reg_t kScrollWindowGo(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ const Ratio scrollTop(argv[1].toSint16(), argv[2].toSint16());
+ scrollWindow->go(scrollTop);
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowModify(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ const reg_t entryId = argv[1];
+ const Common::String newText = s->_segMan->getString(argv[2]);
+ const GuiResourceId fontId = argv[3].toSint16();
+ const int16 color = argv[4].toSint16();
+ const TextAlign alignment = (TextAlign)argv[5].toSint16();
+ const bool scrollTo = argc > 6 ? (bool)argv[6].toUint16() : true;
+
+ return scrollWindow->modify(entryId, newText, fontId, color, alignment, scrollTo);
+}
+
+reg_t kScrollWindowHide(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->hide();
+
+ return s->r_acc;
}
reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv) {
- debug("kScrollWindowShow");
- return kStubNull(s, argc, argv);
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->show();
+
+ return s->r_acc;
}
-reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv) {
- debug("kScrollWindowDestroy");
- return kStubNull(s, argc, argv);
+reg_t kScrollWindowPageUp(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->pageUp();
+
+ return s->r_acc;
}
-#if 0
-reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
- // Used by SQ6 and LSL6 hires for the text area in the bottom of the
- // screen. The relevant scripts also exist in Phantasmagoria 1, but they're
- // unused. This is always called by scripts 64906 (ScrollerWindow) and
- // 64907 (ScrollableWindow).
-
- reg_t kWindow = argv[1];
- uint16 op = argv[0].toUint16();
- switch (op) {
- case 0: // Init
- // TODO: Init reads the nsLeft, nsTop, nsRight, nsBottom,
- // borderColor, fore, back, mode, font, plane selectors
- // from the window in argv[1].
- g_sci->_gfxFrameout->initScrollText(argv[2].toUint16()); // maxItems
- g_sci->_gfxFrameout->clearScrollTexts();
- return argv[1]; // kWindow
- case 1: // Show message, called by ScrollableWindow::addString
- case 14: // Modify message, called by ScrollableWindow::modifyString
- // TODO: The parameters in Modify are shifted by one: the first
- // argument is the handle of the text to modify. The others
- // are as Add.
- {
- Common::String text = s->_segMan->getString(argv[2]);
- uint16 x = 0;
- uint16 y = 0;
- // TODO: argv[3] is font
- // TODO: argv[4] is color
- // TODO: argv[5] is alignment (0 = left, 1 = center, 2 = right)
- // font,color,alignment may also be -1. (Maybe same as previous?)
- // TODO: argv[6] is an optional bool, defaulting to true if not present.
- // If true, the old contents are scrolled out of view.
- // TODO: Return a handle of the inserted text. (Used for modify/insert)
- // This handle looks like it should also be usable by kString.
- g_sci->_gfxFrameout->addScrollTextEntry(text, kWindow, x, y, (op == 14));
- }
- break;
- case 2: // Clear, called by ScrollableWindow::erase
- g_sci->_gfxFrameout->clearScrollTexts();
- break;
- case 3: // Page up, called by ScrollableWindow::scrollTo
- // TODO
- kStub(s, argc, argv);
- break;
- case 4: // Page down, called by ScrollableWindow::scrollTo
- // TODO
- kStub(s, argc, argv);
- break;
- case 5: // Up arrow, called by ScrollableWindow::scrollTo
- g_sci->_gfxFrameout->prevScrollText();
- break;
- case 6: // Down arrow, called by ScrollableWindow::scrollTo
- g_sci->_gfxFrameout->nextScrollText();
- break;
- case 7: // Home, called by ScrollableWindow::scrollTo
- g_sci->_gfxFrameout->firstScrollText();
- break;
- case 8: // End, called by ScrollableWindow::scrollTo
- g_sci->_gfxFrameout->lastScrollText();
- break;
- case 9: // Resize, called by ScrollableWindow::resize and ScrollerWindow::resize
- // TODO: This reads the nsLeft, nsTop, nsRight, nsBottom
- // selectors from the SCI object passed in argv[2].
- kStub(s, argc, argv);
- break;
- case 10: // Where, called by ScrollableWindow::where
- // TODO:
- // Gives the current relative scroll location as a fraction
- // with argv[2] as the denominator. (Return value is the numerator.)
- // Silenced the warnings because of the high amount of console spam
- //kStub(s, argc, argv);
- break;
- case 11: // Go, called by ScrollableWindow::scrollTo
- // TODO:
- // Two arguments provide a fraction: argv[2] is num., argv[3] is denom.
- // Scrolls to the relative location given by the fraction.
- kStub(s, argc, argv);
- break;
- case 12: // Insert, called by ScrollableWindow::insertString
- // 5 extra parameters here:
- // handle of insert location (new string takes that position).
- // text, font, color, alignment
- // TODO
- kStub(s, argc, argv);
- break;
- // case 13 (Delete) is handled below
- // case 14 (Modify) is handled above
- case 15: // Hide, called by ScrollableWindow::hide
- g_sci->_gfxFrameout->toggleScrollText(false);
- break;
- case 16: // Show, called by ScrollableWindow::show
- g_sci->_gfxFrameout->toggleScrollText(true);
- break;
- case 17: // Destroy, called by ScrollableWindow::dispose
- g_sci->_gfxFrameout->clearScrollTexts();
- break;
- case 13: // Delete, unused
- case 18: // Text, unused
- case 19: // Reconstruct, unused
- error("kScrollWindow: Unused subop %d invoked", op);
- break;
- default:
- error("kScrollWindow: unknown subop %d", op);
- break;
- }
+reg_t kScrollWindowPageDown(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->pageDown();
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowUpArrow(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->upArrow();
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowDownArrow(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->downArrow();
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowHome(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->home();
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowEnd(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->end();
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_gfxControls32->destroyScrollWindow(argv[0]);
return s->r_acc;
}
-#endif
reg_t kFont(EngineState *s, int argc, reg_t *argv) {
- // TODO: Handle font settings for SCI2.1
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
- switch (argv[0].toUint16()) {
- case 1:
- g_sci->_gfxText32->_scaledWidth = argv[1].toUint16();
- g_sci->_gfxText32->_scaledHeight = argv[2].toUint16();
- return NULL_REG;
- default:
- error("kFont: unknown subop %d", argv[0].toUint16());
+reg_t kSetFontHeight(EngineState *s, int argc, reg_t *argv) {
+ // TODO: Setting font may have just been for side effect
+ // of setting the fontHeight on the font manager, in
+ // which case we could just get the font directly ourselves.
+ g_sci->_gfxText32->setFont(argv[0].toUint16());
+ g_sci->_gfxText32->_yResolution = (g_sci->_gfxText32->_font->getHeight() * g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight + g_sci->_gfxText32->_yResolution - 1) / g_sci->_gfxText32->_yResolution;
+ return make_reg(0, g_sci->_gfxText32->_yResolution);
+}
+
+reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_gfxText32->_xResolution = argv[0].toUint16();
+ g_sci->_gfxText32->_yResolution = argv[1].toUint16();
+ return s->r_acc;
+}
+
+reg_t kBitmap(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv) {
+ int16 width = argv[0].toSint16();
+ int16 height = argv[1].toSint16();
+ int16 skipColor = argv[2].toSint16();
+ int16 backColor = argv[3].toSint16();
+ int16 xResolution = argc > 4 ? argv[4].toSint16() : g_sci->_gfxText32->_xResolution;
+ int16 yResolution = argc > 5 ? argv[5].toSint16() : g_sci->_gfxText32->_yResolution;
+ bool useRemap = argc > 6 ? argv[6].toSint16() : false;
+
+ reg_t bitmapId;
+ SciBitmap &bitmap = *s->_segMan->allocateBitmap(&bitmapId, width, height, skipColor, 0, 0, xResolution, yResolution, 0, useRemap, true);
+ memset(bitmap.getPixels(), backColor, width * height);
+ return bitmapId;
+}
+
+reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv) {
+ const reg_t &addr = argv[0];
+ const SegmentObj *const segment = s->_segMan->getSegmentObj(addr.getSegment());
+
+ if (segment != nullptr &&
+ segment->getType() == SEG_TYPE_BITMAP &&
+ segment->isValidOffset(addr.getOffset())) {
+
+ s->_segMan->freeBitmap(addr);
}
return s->r_acc;
}
-reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) {
- g_sci->_gfxText32->_scaledWidth = argv[0].toUint16();
- g_sci->_gfxText32->_scaledHeight = argv[1].toUint16();
- return NULL_REG;
+reg_t kBitmapDrawLine(EngineState *s, int argc, reg_t *argv) {
+ // bitmapMemId, (x1, y1, x2, y2) OR (x2, y2, x1, y1), line color, unknown int, unknown int
+ return kStubNull(s, argc + 1, argv - 1);
}
-// TODO: Eventually, all of the kBitmap operations should be put
-// in a separate class
+reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv) {
+ SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]);
+ CelObjView view(argv[1].toUint16(), argv[2].toSint16(), argv[3].toSint16());
-// NOTE: This size is correct only for SCI2.1mid; the size for
-// SCI2/2.1early is 36
-#define BITMAP_HEADER_SIZE 46
+ const int16 x = argc > 4 ? argv[4].toSint16() : 0;
+ const int16 y = argc > 5 ? argv[5].toSint16() : 0;
+ const int16 alignX = argc > 7 ? argv[7].toSint16() : -1;
+ const int16 alignY = argc > 8 ? argv[8].toSint16() : -1;
-reg_t kBitmap(EngineState *s, int argc, reg_t *argv) {
- // Used for bitmap operations in SCI2.1 and SCI3.
- // This is the SCI2.1 version, the functionality seems to have changed in SCI3.
+ Common::Point position(
+ x == -1 ? bitmap.getOrigin().x : x,
+ y == -1 ? bitmap.getOrigin().y : y
+ );
- switch (argv[0].toUint16()) {
- case 0: // init bitmap surface
- {
- // 6 params, called e.g. from TextView::init() in Torin's Passage,
- // script 64890 and TransView::init() in script 64884
- uint16 width = argv[1].toUint16();
- uint16 height = argv[2].toUint16();
- //uint16 skip = argv[3].toUint16();
- uint16 back = argv[4].toUint16(); // usually equals skip
- //uint16 width2 = (argc >= 6) ? argv[5].toUint16() : 0;
- //uint16 height2 = (argc >= 7) ? argv[6].toUint16() : 0;
- //uint16 transparentFlag = (argc >= 8) ? argv[7].toUint16() : 0;
-
- // TODO: skip, width2, height2, transparentFlag
- // (used for transparent bitmaps)
- int entrySize = width * height + BITMAP_HEADER_SIZE;
- reg_t memoryId = s->_segMan->allocateHunkEntry("Bitmap()", entrySize);
- byte *memoryPtr = s->_segMan->getHunkPointer(memoryId);
- memset(memoryPtr, 0, BITMAP_HEADER_SIZE); // zero out the bitmap header
- memset(memoryPtr + BITMAP_HEADER_SIZE, back, width * height);
- // Save totalWidth, totalHeight
- // TODO: Save the whole bitmap header, like SSCI does
- WRITE_SCI11ENDIAN_UINT16(memoryPtr, width);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 2, height);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 4, 0);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 6, 0);
- memoryPtr[8] = 0;
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 10, 0);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 20, BITMAP_HEADER_SIZE);
- WRITE_SCI11ENDIAN_UINT32(memoryPtr + 28, 46);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 36, width);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 38, width);
- return memoryId;
- }
- break;
- case 1: // dispose text bitmap surface
- return kDisposeTextBitmap(s, argc - 1, argv + 1);
- case 2: // dispose bitmap surface, with extra param
- // 2 params, called e.g. from MenuItem::dispose in Torin's Passage,
- // script 64893
- warning("kBitmap(2), unk1 %d, bitmap ptr %04x:%04x", argv[1].toUint16(), PRINT_REG(argv[2]));
- break;
- case 3: // tiled surface
- {
- // 6 params, called e.g. from TiledBitmap::resize() in Torin's Passage,
- // script 64869
- reg_t hunkId = argv[1]; // obtained from kBitmap(0)
- // The tiled view seems to always have 2 loops.
- // These loops need to have 1 cel in loop 0 and 8 cels in loop 1.
- uint16 viewNum = argv[2].toUint16(); // vTiles selector
- uint16 loop = argv[3].toUint16();
- uint16 cel = argv[4].toUint16();
- uint16 x = argv[5].toUint16();
- uint16 y = argv[6].toUint16();
-
- byte *memoryPtr = s->_segMan->getHunkPointer(hunkId);
- // Get totalWidth, totalHeight
- uint16 totalWidth = READ_LE_UINT16(memoryPtr);
- uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2);
- byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE;
-
- GfxView *view = g_sci->_gfxCache->getView(viewNum);
- uint16 tileWidth = view->getWidth(loop, cel);
- uint16 tileHeight = view->getHeight(loop, cel);
- const byte *tileBitmap = view->getBitmap(loop, cel);
- uint16 width = MIN<uint16>(totalWidth - x, tileWidth);
- uint16 height = MIN<uint16>(totalHeight - y, tileHeight);
-
- for (uint16 curY = 0; curY < height; curY++) {
- for (uint16 curX = 0; curX < width; curX++) {
- bitmap[(curY + y) * totalWidth + (curX + x)] = tileBitmap[curY * tileWidth + curX];
- }
- }
+ position.x -= alignX == -1 ? view._origin.x : alignX;
+ position.y -= alignY == -1 ? view._origin.y : alignY;
- }
- break;
- case 4: // add text to bitmap
- {
- // 13 params, called e.g. from TextButton::createBitmap() in Torin's Passage,
- // script 64894
- reg_t hunkId = argv[1]; // obtained from kBitmap(0)
- Common::String text = s->_segMan->getString(argv[2]);
- uint16 textX = argv[3].toUint16();
- uint16 textY = argv[4].toUint16();
- //reg_t unk5 = argv[5];
- //reg_t unk6 = argv[6];
- //reg_t unk7 = argv[7]; // skip?
- //reg_t unk8 = argv[8]; // back?
- //reg_t unk9 = argv[9];
- uint16 fontId = argv[10].toUint16();
- //uint16 mode = argv[11].toUint16();
- uint16 dimmed = argv[12].toUint16();
- //warning("kBitmap(4): bitmap ptr %04x:%04x, font %d, mode %d, dimmed %d - text: \"%s\"",
- // PRINT_REG(bitmapPtr), font, mode, dimmed, text.c_str());
- uint16 foreColor = 255; // TODO
-
- byte *memoryPtr = s->_segMan->getHunkPointer(hunkId);
- // Get totalWidth, totalHeight
- uint16 totalWidth = READ_LE_UINT16(memoryPtr);
- uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2);
- byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE;
-
- GfxFont *font = g_sci->_gfxCache->getFont(fontId);
-
- int16 charCount = 0;
- uint16 curX = textX, curY = textY;
- const char *txt = text.c_str();
-
- while (*txt) {
- charCount = g_sci->_gfxText32->GetLongest(txt, totalWidth, font);
- if (charCount == 0)
- break;
-
- for (int i = 0; i < charCount; i++) {
- unsigned char curChar = txt[i];
- font->drawToBuffer(curChar, curY, curX, foreColor, dimmed, bitmap, totalWidth, totalHeight);
- curX += font->getCharWidth(curChar);
- }
+ Common::Rect drawRect(
+ position.x,
+ position.y,
+ position.x + view._width,
+ position.y + view._height
+ );
+ drawRect.clip(Common::Rect(bitmap.getWidth(), bitmap.getHeight()));
+ view.draw(bitmap.getBuffer(), drawRect, position, view._mirrorX);
+ return s->r_acc;
+}
- curX = textX;
- curY += font->getHeight();
- txt += charCount;
- while (*txt == ' ')
- txt++; // skip over breaking spaces
- }
+reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv) {
+ // called e.g. from TextButton::createBitmap() in Torin's Passage, script 64894
- }
- break;
- case 5: // fill with color
- {
- // 6 params, called e.g. from TextView::init() and TextView::draw()
- // in Torin's Passage, script 64890
- reg_t hunkId = argv[1]; // obtained from kBitmap(0)
- uint16 x = argv[2].toUint16();
- uint16 y = argv[3].toUint16();
- uint16 fillWidth = argv[4].toUint16(); // width - 1
- uint16 fillHeight = argv[5].toUint16(); // height - 1
- uint16 back = argv[6].toUint16();
-
- byte *memoryPtr = s->_segMan->getHunkPointer(hunkId);
- // Get totalWidth, totalHeight
- uint16 totalWidth = READ_LE_UINT16(memoryPtr);
- uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2);
- uint16 width = MIN<uint16>(totalWidth - x, fillWidth);
- uint16 height = MIN<uint16>(totalHeight - y, fillHeight);
- byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE;
-
- for (uint16 curY = 0; curY < height; curY++) {
- for (uint16 curX = 0; curX < width; curX++) {
- bitmap[(curY + y) * totalWidth + (curX + x)] = back;
- }
- }
+ SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]);
+ Common::String text = s->_segMan->getString(argv[1]);
+ Common::Rect textRect(
+ argv[2].toSint16(),
+ argv[3].toSint16(),
+ argv[4].toSint16() + 1,
+ argv[5].toSint16() + 1
+ );
+ int16 foreColor = argv[6].toSint16();
+ int16 backColor = argv[7].toSint16();
+ int16 skipColor = argv[8].toSint16();
+ GuiResourceId fontId = (GuiResourceId)argv[9].toUint16();
+ TextAlign alignment = (TextAlign)argv[10].toSint16();
+ int16 borderColor = argv[11].toSint16();
+ bool dimmed = argv[12].toUint16();
+
+ // NOTE: Technically the engine checks these things:
+ // textRect.bottom > 0
+ // textRect.right > 0
+ // textRect.left < bitmap.width
+ // textRect.top < bitmap.height
+ // Then clips. But this seems stupid.
+ textRect.clip(Common::Rect(bitmap.getWidth(), bitmap.getHeight()));
+
+ reg_t textBitmapObject = g_sci->_gfxText32->createFontBitmap(textRect.width(), textRect.height(), Common::Rect(textRect.width(), textRect.height()), text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, false, false);
+ CelObjMem textCel(textBitmapObject);
+ textCel.draw(bitmap.getBuffer(), textRect, Common::Point(textRect.left, textRect.top), false);
+ s->_segMan->freeBitmap(textBitmapObject);
+
+ return s->r_acc;
+}
+
+reg_t kBitmapDrawColor(EngineState *s, int argc, reg_t *argv) {
+ // called e.g. from TextView::init() and TextView::draw() in Torin's Passage, script 64890
+ SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]);
+ Common::Rect fillRect(
+ argv[1].toSint16(),
+ argv[2].toSint16(),
+ argv[3].toSint16() + 1,
+ argv[4].toSint16() + 1
+ );
+
+ bitmap.getBuffer().fillRect(fillRect, argv[5].toSint16());
+ return s->r_acc;
+}
+
+reg_t kBitmapDrawBitmap(EngineState *s, int argc, reg_t *argv) {
+ // target bitmap, source bitmap, x, y, unknown boolean
+
+ return kStubNull(s, argc + 1, argv - 1);
+}
+
+reg_t kBitmapInvert(EngineState *s, int argc, reg_t *argv) {
+ // bitmap, left, top, right, bottom, foreColor, backColor
+
+ return kStubNull(s, argc + 1, argv - 1);
+}
+
+reg_t kBitmapSetOrigin(EngineState *s, int argc, reg_t *argv) {
+ SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]);
+ bitmap.setOrigin(Common::Point(argv[1].toSint16(), argv[2].toSint16()));
+ return s->r_acc;
+}
+
+reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv) {
+ CelObjView view(argv[0].toUint16(), argv[1].toSint16(), argv[2].toSint16());
+ const uint8 skipColor = argc > 3 && argv[3].toSint16() != -1 ? argv[3].toSint16() : view._skipColor;
+ const uint8 backColor = argc > 4 && argv[4].toSint16() != -1 ? argv[4].toSint16() : view._skipColor;
+ const bool useRemap = argc > 5 ? (bool)argv[5].toSint16() : false;
+
+ reg_t bitmapId;
+ SciBitmap &bitmap = *s->_segMan->allocateBitmap(&bitmapId, view._width, view._height, skipColor, 0, 0, view._xResolution, view._yResolution, 0, useRemap, true);
+ Buffer &buffer = bitmap.getBuffer();
+
+ const Common::Rect viewRect(view._width, view._height);
+ buffer.fillRect(viewRect, backColor);
+ view.draw(buffer, viewRect, Common::Point(0, 0), view._mirrorX);
+
+ if (argc > 6 && !argv[6].isNull()) {
+ reg_t clutHandle = argv[6];
+ if (s->_segMan->isObject(clutHandle)) {
+ clutHandle = readSelector(s->_segMan, clutHandle, SELECTOR(data));
}
- break;
- default:
- kStub(s, argc, argv);
- break;
+
+ SciArray &clut = *s->_segMan->lookupArray(clutHandle);
+ bitmap.applyRemap(clut);
}
- return s->r_acc;
+ return bitmapId;
}
-// Used for edit boxes in save/load dialogs. It's a rewritten version of kEditControl,
-// but it handles events on its own, using an internal loop, instead of using SCI
-// scripts for event management like kEditControl does. Called by script 64914,
-// DEdit::hilite().
-reg_t kEditText(EngineState *s, int argc, reg_t *argv) {
- reg_t controlObject = argv[0];
+reg_t kBitmapCopyPixels(EngineState *s, int argc, reg_t *argv) {
+ // target bitmap, source bitmap
- if (!controlObject.isNull()) {
- g_sci->_gfxControls32->kernelTexteditChange(controlObject);
+ return kStubNull(s, argc + 1, argv - 1);
+}
+
+reg_t kBitmapClone(EngineState *s, int argc, reg_t *argv) {
+ // bitmap
+
+ return kStub(s, argc + 1, argv - 1);
+}
+
+reg_t kBitmapGetInfo(EngineState *s, int argc, reg_t *argv) {
+ SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]);
+
+ if (argc == 1) {
+ return make_reg(0, bitmap.getWidth());
}
- return s->r_acc;
+ int32 offset;
+ if (argc == 2) {
+ offset = argv[1].toUint16();
+ } else {
+ const int16 x = argv[1].toSint16();
+ const int16 y = argv[2].toSint16();
+ offset = y * bitmap.getWidth() + x;
+ }
+
+ assert(offset >= 0 && offset < bitmap.getWidth() * bitmap.getHeight());
+ const uint8 color = bitmap.getPixels()[offset];
+ return make_reg(0, color);
+}
+
+reg_t kBitmapScale(EngineState *s, int argc, reg_t *argv) {
+ // TODO: SCI3
+ return kStubNull(s, argc + 1, argv - 1);
+}
+
+reg_t kBitmapCreateFromUnknown(EngineState *s, int argc, reg_t *argv) {
+ // TODO: SCI3
+ return kStub(s, argc + 1, argv - 1);
+}
+
+reg_t kEditText(EngineState *s, int argc, reg_t *argv) {
+ return g_sci->_gfxControls32->kernelEditText(argv[0]);
}
reg_t kAddLine(EngineState *s, int argc, reg_t *argv) {
- return kStubNull(s, argc, argv); // return 0:0 for now, so that follow up calls won't create signature mismatches
-#if 0
- reg_t plane = argv[0];
- Common::Point startPoint(argv[1].toUint16(), argv[2].toUint16());
- Common::Point endPoint(argv[3].toUint16(), argv[4].toUint16());
- // argv[5] is unknown (a number, usually 200)
- byte color = (byte)argv[6].toUint16();
- byte priority = (byte)argv[7].toUint16();
- byte control = (byte)argv[8].toUint16();
- // argv[9] is unknown (usually a small number, 1 or 2). Thickness, perhaps?
-// return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control);
- return s->r_acc;
-#endif
+ const reg_t plane = argv[0];
+ const Common::Point startPoint(argv[1].toSint16(), argv[2].toSint16());
+ const Common::Point endPoint(argv[3].toSint16(), argv[4].toSint16());
+
+ int16 priority;
+ uint8 color;
+ LineStyle style;
+ uint16 pattern;
+ uint8 thickness;
+
+ if (argc == 10) {
+ priority = argv[5].toSint16();
+ color = (uint8)argv[6].toUint16();
+ style = (LineStyle)argv[7].toSint16();
+ pattern = argv[8].toUint16();
+ thickness = (uint8)argv[9].toUint16();
+ } else {
+ priority = 1000;
+ color = 255;
+ style = kLineStyleSolid;
+ pattern = 0;
+ thickness = 1;
+ }
+
+ return g_sci->_gfxPaint32->kernelAddLine(plane, startPoint, endPoint, priority, color, style, pattern, thickness);
}
reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) {
- return kStub(s, argc, argv);
-
-#if 0
- reg_t hunkId = argv[0];
- reg_t plane = argv[1];
- Common::Point startPoint(argv[2].toUint16(), argv[3].toUint16());
- Common::Point endPoint(argv[4].toUint16(), argv[5].toUint16());
- // argv[6] is unknown (a number, usually 200)
- byte color = (byte)argv[7].toUint16();
- byte priority = (byte)argv[8].toUint16();
- byte control = (byte)argv[9].toUint16();
- // argv[10] is unknown (usually a small number, 1 or 2). Thickness, perhaps?
-// g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control);
+ const reg_t screenItemObject = argv[0];
+ const reg_t planeObject = argv[1];
+ const Common::Point startPoint(argv[2].toSint16(), argv[3].toSint16());
+ const Common::Point endPoint(argv[4].toSint16(), argv[5].toSint16());
+
+ int16 priority;
+ uint8 color;
+ LineStyle style;
+ uint16 pattern;
+ uint8 thickness;
+
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kUpdateLine: Plane %04x:%04x not found", PRINT_REG(planeObject));
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject);
+ if (screenItem == nullptr) {
+ error("kUpdateLine: Screen item %04x:%04x not found", PRINT_REG(screenItemObject));
+ }
+
+ if (argc == 11) {
+ priority = argv[6].toSint16();
+ color = (uint8)argv[7].toUint16();
+ style = (LineStyle)argv[8].toSint16();
+ pattern = argv[9].toUint16();
+ thickness = (uint8)argv[10].toUint16();
+ } else {
+ priority = screenItem->_priority;
+ color = screenItem->_celInfo.color;
+ style = kLineStyleSolid;
+ pattern = 0;
+ thickness = 1;
+ }
+
+ g_sci->_gfxPaint32->kernelUpdateLine(screenItem, plane, startPoint, endPoint, priority, color, style, pattern, thickness);
+
return s->r_acc;
-#endif
}
+
reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) {
- return kStub(s, argc, argv);
-#if 0
- reg_t hunkId = argv[0];
- reg_t plane = argv[1];
-// g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId);
+ g_sci->_gfxPaint32->kernelDeleteLine(argv[0], argv[1]);
return s->r_acc;
-#endif
}
+// Used by LSL6hires intro (room 110)
reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) {
- // Called in the intro of LSL6 hires (room 110)
- // The end effect of this is the same as the old screen scroll transition
-
- // 7 parameters
- reg_t planeObject = argv[0];
- //int16 x = argv[1].toSint16();
- //int16 y = argv[2].toSint16();
- uint16 pictureId = argv[3].toUint16();
- // param 4: int (0 in LSL6, probably scroll direction? The picture in LSL6 scrolls down)
- // param 5: int (first call is 1, then the subsequent one is 0 in LSL6)
- // param 6: optional int (0 in LSL6)
-
- // Set the new picture directly for now
- //writeSelectorValue(s->_segMan, planeObject, SELECTOR(left), x);
- //writeSelectorValue(s->_segMan, planeObject, SELECTOR(top), y);
- writeSelectorValue(s->_segMan, planeObject, SELECTOR(picture), pictureId);
- // and update our draw list
- g_sci->_gfxFrameout->kernelUpdatePlane(planeObject);
-
- // TODO
- return kStub(s, argc, argv);
+ const reg_t plane = argv[0];
+ const int16 deltaX = argv[1].toSint16();
+ const int16 deltaY = argv[2].toSint16();
+ const GuiResourceId pictureId = argv[3].toUint16();
+ const bool animate = argv[4].toUint16();
+ // NOTE: speed was accepted as an argument, but then never actually used
+ // const int16 speed = argc > 5 ? (bool)argv[5].toSint16() : -1;
+ const bool mirrorX = argc > 6 ? (bool)argv[6].toUint16() : false;
+
+ g_sci->_gfxTransitions32->kernelSetScroll(plane, deltaX, deltaY, pictureId, animate, mirrorX);
+ return s->r_acc;
}
// Used by SQ6, script 900, the datacorder reprogramming puzzle (from room 270)
reg_t kMorphOn(EngineState *s, int argc, reg_t *argv) {
g_sci->_gfxFrameout->_palMorphIsOn = true;
- return NULL_REG;
+ return s->r_acc;
+}
+
+reg_t kPaletteSetFromResource32(EngineState *s, int argc, reg_t *argv) {
+ const GuiResourceId paletteId = argv[0].toUint16();
+ g_sci->_gfxPalette32->loadPalette(paletteId);
+ return s->r_acc;
+}
+
+reg_t kPaletteFindColor32(EngineState *s, int argc, reg_t *argv) {
+ const uint8 r = argv[0].toUint16();
+ const uint8 g = argv[1].toUint16();
+ const uint8 b = argv[2].toUint16();
+ return make_reg(0, g_sci->_gfxPalette32->matchColor(r, g, b));
+}
+
+/*
+ * Used in SCI3. SCI3 contains 6 gamma look-up tables, with the first
+ * table (gamma = 0) being the default one.
+ */
+reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv) {
+ const uint8 gamma = argv[0].toUint16();
+ assert(gamma <= 6);
+
+ warning("TODO: kPaletteSetGamma(%d)", gamma);
+
+ return s->r_acc;
}
reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv) {
- uint16 fromColor = argv[0].toUint16();
- uint16 toColor = argv[1].toUint16();
- uint16 percent = argv[2].toUint16();
+ const uint16 fromColor = argv[0].toUint16();
+ const uint16 toColor = argv[1].toUint16();
+ const uint16 percent = argv[2].toUint16();
g_sci->_gfxPalette32->setFade(percent, fromColor, toColor);
- return NULL_REG;
+ return s->r_acc;
}
reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) {
- GuiResourceId paletteId = argv[0].toUint16();
- int time = argc > 1 ? argv[1].toSint16() * 60 : 0;
- int16 percent = argc > 2 ? argv[2].toSint16() : 100;
+ const GuiResourceId paletteId = argv[0].toUint16();
+ const int32 time = argc > 1 ? argv[1].toSint16() * 60 : 0;
+ const int16 percent = argc > 2 ? argv[2].toSint16() : 100;
int16 fromColor;
int16 toColor;
- if (argc > 4) {
+ if (g_sci->_features->hasNewPaletteCode() && argc > 4) {
fromColor = argv[3].toSint16();
toColor = argv[4].toSint16();
} else {
@@ -758,14 +988,14 @@ reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) {
}
g_sci->_gfxPalette32->kernelPalVarySet(paletteId, percent, time, fromColor, toColor);
- return NULL_REG;
+ return s->r_acc;
}
reg_t kPalVarySetPercent(EngineState *s, int argc, reg_t *argv) {
- int time = argc > 0 ? argv[0].toSint16() * 60 : 0;
- int16 percent = argc > 1 ? argv[1].toSint16() : 0;
- g_sci->_gfxPalette32->setVaryPercent(percent, time, -1, -1);
- return NULL_REG;
+ const int32 time = argc > 0 ? argv[0].toSint16() * 60 : 0;
+ const int16 percent = argc > 1 ? argv[1].toSint16() : 0;
+ g_sci->_gfxPalette32->setVaryPercent(percent, time);
+ return s->r_acc;
}
reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv) {
@@ -774,173 +1004,154 @@ reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv) {
reg_t kPalVaryOff(EngineState *s, int argc, reg_t *argv) {
g_sci->_gfxPalette32->varyOff();
- return NULL_REG;
+ return s->r_acc;
}
reg_t kPalVaryMergeTarget(EngineState *s, int argc, reg_t *argv) {
- GuiResourceId paletteId = argv[0].toUint16();
+ const GuiResourceId paletteId = argv[0].toUint16();
g_sci->_gfxPalette32->kernelPalVaryMergeTarget(paletteId);
return make_reg(0, g_sci->_gfxPalette32->getVaryPercent());
}
reg_t kPalVarySetTime(EngineState *s, int argc, reg_t *argv) {
- int time = argv[0].toSint16() * 60;
+ const int32 time = argv[0].toSint16() * 60;
g_sci->_gfxPalette32->setVaryTime(time);
- return NULL_REG;
+ return s->r_acc;
}
reg_t kPalVarySetTarget(EngineState *s, int argc, reg_t *argv) {
- GuiResourceId paletteId = argv[0].toUint16();
+ const GuiResourceId paletteId = argv[0].toUint16();
g_sci->_gfxPalette32->kernelPalVarySetTarget(paletteId);
return make_reg(0, g_sci->_gfxPalette32->getVaryPercent());
}
reg_t kPalVarySetStart(EngineState *s, int argc, reg_t *argv) {
- GuiResourceId paletteId = argv[0].toUint16();
+ const GuiResourceId paletteId = argv[0].toUint16();
g_sci->_gfxPalette32->kernelPalVarySetStart(paletteId);
return make_reg(0, g_sci->_gfxPalette32->getVaryPercent());
}
reg_t kPalVaryMergeStart(EngineState *s, int argc, reg_t *argv) {
- GuiResourceId paletteId = argv[0].toUint16();
+ const GuiResourceId paletteId = argv[0].toUint16();
g_sci->_gfxPalette32->kernelPalVaryMergeStart(paletteId);
return make_reg(0, g_sci->_gfxPalette32->getVaryPercent());
}
-enum {
- kSetCycle = 0,
- kDoCycle = 1,
- kCyclePause = 2,
- kCycleOn = 3,
- kCycleOff = 4
-};
-
reg_t kPalCycle(EngineState *s, int argc, reg_t *argv) {
- // Examples: GK1 room 480 (Bayou ritual), LSL6 room 100 (title screen)
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
- switch (argv[0].toUint16()) {
- case kSetCycle: {
- uint16 fromColor = argv[1].toUint16();
- uint16 toColor = argv[2].toUint16();
- int16 direction = argv[3].toSint16();
- uint16 delay = (argc == 4 ? 0 : argv[4].toUint16());
+reg_t kPalCycleSetCycle(EngineState *s, int argc, reg_t *argv) {
+ const uint16 fromColor = argv[0].toUint16();
+ const uint16 toColor = argv[1].toUint16();
+ const int16 direction = argv[2].toSint16();
+ const uint16 delay = argc > 3 ? argv[3].toUint16() : 0;
+ g_sci->_gfxPalette32->setCycle(fromColor, toColor, direction, delay);
+ return s->r_acc;
+}
- g_sci->_gfxPalette32->setCycle(fromColor, toColor, direction, delay);
- }
- break;
- case kDoCycle: {
- uint16 fromColor = argv[1].toUint16();
- int16 speed = (argc == 2) ? 1 : argv[2].toSint16();
- g_sci->_gfxPalette32->doCycle(fromColor, speed);
- }
- break;
- case kCyclePause: {
- if (argc == 1) {
- g_sci->_gfxPalette32->cycleAllPause();
- } else {
- uint16 fromColor = argv[1].toUint16();
- g_sci->_gfxPalette32->cyclePause(fromColor);
- }
- }
- break;
- case kCycleOn: {
- if (argc == 1) {
- g_sci->_gfxPalette32->cycleAllOn();
- } else {
- uint16 fromColor = argv[1].toUint16();
- g_sci->_gfxPalette32->cycleOn(fromColor);
- }
- }
- break;
- case kCycleOff: {
- if (argc == 1) {
- g_sci->_gfxPalette32->cycleAllOff();
- } else {
- uint16 fromColor = argv[1].toUint16();
- g_sci->_gfxPalette32->cycleOff(fromColor);
- }
- break;
- }
- default:
- // In SCI2.1 there are no values above 4, so should never get here;
- // SCI just returns early if this ever happens.
- assert(false);
- break;
+reg_t kPalCycleDoCycle(EngineState *s, int argc, reg_t *argv) {
+ const uint16 fromColor = argv[0].toUint16();
+ const int16 speed = argc > 1 ? argv[1].toSint16() : 1;
+ g_sci->_gfxPalette32->doCycle(fromColor, speed);
+ return s->r_acc;
+}
+
+reg_t kPalCyclePause(EngineState *s, int argc, reg_t *argv) {
+ if (argc == 0) {
+ g_sci->_gfxPalette32->cycleAllPause();
+ } else {
+ const uint16 fromColor = argv[0].toUint16();
+ g_sci->_gfxPalette32->cyclePause(fromColor);
+ }
+ return s->r_acc;
+}
+
+reg_t kPalCycleOn(EngineState *s, int argc, reg_t *argv) {
+ if (argc == 0) {
+ g_sci->_gfxPalette32->cycleAllOn();
+ } else {
+ const uint16 fromColor = argv[0].toUint16();
+ g_sci->_gfxPalette32->cycleOn(fromColor);
}
+ return s->r_acc;
+}
+reg_t kPalCycleOff(EngineState *s, int argc, reg_t *argv) {
+ if (argc == 0) {
+ g_sci->_gfxPalette32->cycleAllOff();
+ } else {
+ const uint16 fromColor = argv[0].toUint16();
+ g_sci->_gfxPalette32->cycleOff(fromColor);
+ }
return s->r_acc;
}
reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv) {
- uint16 operation = argv[0].toUint16();
-
- switch (operation) {
- case 0: { // turn remapping off
- // WORKAROUND: Game scripts in QFG4 erroneously turn remapping off in room
- // 140 (the character point allocation screen) and never turn it back on,
- // even if it's clearly used in that screen.
- if (g_sci->getGameId() == GID_QFG4 && s->currentRoomNumber() == 140)
- return s->r_acc;
-
- int16 base = (argc >= 2) ? argv[1].toSint16() : 0;
- if (base > 0)
- warning("kRemapColors(0) called with base %d", base);
- g_sci->_gfxPalette32->resetRemapping();
- }
- break;
- case 1: { // remap by range
- uint16 color = argv[1].toUint16();
- uint16 from = argv[2].toUint16();
- uint16 to = argv[3].toUint16();
- uint16 base = argv[4].toUint16();
- uint16 unk5 = (argc >= 6) ? argv[5].toUint16() : 0;
- if (unk5 > 0)
- warning("kRemapColors(1) called with 6 parameters, unknown parameter is %d", unk5);
- g_sci->_gfxPalette32->setRemappingRange(color, from, to, base);
- }
- break;
- case 2: { // remap by percent
- uint16 color = argv[1].toUint16();
- uint16 percent = argv[2].toUint16(); // 0 - 100
- if (argc >= 4)
- warning("RemapByPercent called with 4 parameters, unknown parameter is %d", argv[3].toUint16());
- g_sci->_gfxPalette32->setRemappingPercent(color, percent);
- }
- break;
- case 3: { // remap to gray
- // Example call: QFG4 room 490 (Baba Yaga's hut) - params are color 253, 75% and 0.
- // In this room, it's used for the cloud before Baba Yaga appears.
- int16 color = argv[1].toSint16();
- int16 percent = argv[2].toSint16(); // 0 - 100
- if (argc >= 4)
- warning("RemapToGray called with 4 parameters, unknown parameter is %d", argv[3].toUint16());
- g_sci->_gfxPalette32->setRemappingPercentGray(color, percent);
- }
- break;
- case 4: { // remap to percent gray
- // Example call: QFG4 rooms 530/535 (swamp) - params are 253, 100%, 200
- int16 color = argv[1].toSint16();
- int16 percent = argv[2].toSint16(); // 0 - 100
- // argv[3] is unknown (a number, e.g. 200) - start color, perhaps?
- if (argc >= 5)
- warning("RemapToGrayPercent called with 5 parameters, unknown parameter is %d", argv[4].toUint16());
- g_sci->_gfxPalette32->setRemappingPercentGray(color, percent);
- }
- break;
- case 5: { // don't map to range
- //int16 mapping = argv[1].toSint16();
- uint16 intensity = argv[2].toUint16();
- // HACK for PQ4
- if (g_sci->getGameId() == GID_PQ4)
- g_sci->_gfxPalette32->kernelSetIntensity(0, 255, intensity, true);
-
- kStub(s, argc, argv);
- }
- break;
- default:
- break;
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv) {
+ if (argc == 0) {
+ g_sci->_gfxRemap32->remapAllOff();
+ } else {
+ const uint8 color = argv[0].toUint16();
+ g_sci->_gfxRemap32->remapOff(color);
}
+ return s->r_acc;
+}
+
+reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv) {
+ const uint8 color = argv[0].toUint16();
+ const int16 from = argv[1].toSint16();
+ const int16 to = argv[2].toSint16();
+ const int16 base = argv[3].toSint16();
+ // NOTE: There is an optional last parameter after `base`
+ // which was only used by the priority map debugger, which
+ // does not exist in release versions of SSCI
+ g_sci->_gfxRemap32->remapByRange(color, from, to, base);
+ return s->r_acc;
+}
+
+reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv) {
+ const uint8 color = argv[0].toUint16();
+ const int16 percent = argv[1].toSint16();
+ // NOTE: There is an optional last parameter after `percent`
+ // which was only used by the priority map debugger, which
+ // does not exist in release versions of SSCI
+ g_sci->_gfxRemap32->remapByPercent(color, percent);
+ return s->r_acc;
+}
+
+reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv) {
+ const uint8 color = argv[0].toUint16();
+ const int16 gray = argv[1].toSint16();
+ // NOTE: There is an optional last parameter after `gray`
+ // which was only used by the priority map debugger, which
+ // does not exist in release versions of SSCI
+ g_sci->_gfxRemap32->remapToGray(color, gray);
+ return s->r_acc;
+}
+
+reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv) {
+ const uint8 color = argv[0].toUint16();
+ const int16 gray = argv[1].toSint16();
+ const int16 percent = argv[2].toSint16();
+ // NOTE: There is an optional last parameter after `percent`
+ // which was only used by the priority map debugger, which
+ // does not exist in release versions of SSCI
+ g_sci->_gfxRemap32->remapToPercentGray(color, gray, percent);
+ return s->r_acc;
+}
+reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv) {
+ const uint8 from = argv[0].toUint16();
+ const uint8 count = argv[1].toUint16();
+ g_sci->_gfxRemap32->blockRange(from, count);
return s->r_acc;
}
diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp
index c0da2daaeb..de5d7f1536 100644
--- a/engines/sci/engine/klists.cpp
+++ b/engines/sci/engine/klists.cpp
@@ -302,14 +302,14 @@ reg_t kAddToEnd(EngineState *s, int argc, reg_t *argv) {
reg_t kAddAfter(EngineState *s, int argc, reg_t *argv) {
List *list = s->_segMan->lookupList(argv[0]);
- Node *firstnode = argv[1].isNull() ? NULL : s->_segMan->lookupNode(argv[1]);
- Node *newnode = s->_segMan->lookupNode(argv[2]);
+ Node *firstNode = s->_segMan->lookupNode(argv[1]);
+ Node *newNode = s->_segMan->lookupNode(argv[2]);
#ifdef CHECK_LISTS
checkListPointer(s->_segMan, argv[0]);
#endif
- if (!newnode) {
+ if (!newNode) {
error("New 'node' %04x:%04x is not a node", PRINT_REG(argv[2]));
return NULL_REG;
}
@@ -320,22 +320,64 @@ reg_t kAddAfter(EngineState *s, int argc, reg_t *argv) {
}
if (argc == 4)
- newnode->key = argv[3];
+ newNode->key = argv[3];
- if (firstnode) { // We're really appending after
- reg_t oldnext = firstnode->succ;
+ if (firstNode) { // We're really appending after
+ const reg_t oldNext = firstNode->succ;
- newnode->pred = argv[1];
- firstnode->succ = argv[2];
- newnode->succ = oldnext;
+ newNode->pred = argv[1];
+ firstNode->succ = argv[2];
+ newNode->succ = oldNext;
- if (oldnext.isNull()) // Appended after last node?
+ if (oldNext.isNull()) // Appended after last node?
// Set new node as last list node
list->last = argv[2];
else
- s->_segMan->lookupNode(oldnext)->pred = argv[2];
+ s->_segMan->lookupNode(oldNext)->pred = argv[2];
- } else { // !firstnode
+ } else {
+ addToFront(s, argv[0], argv[2]); // Set as initial list node
+ }
+
+ return s->r_acc;
+}
+
+reg_t kAddBefore(EngineState *s, int argc, reg_t *argv) {
+ List *list = s->_segMan->lookupList(argv[0]);
+ Node *firstNode = s->_segMan->lookupNode(argv[1]);
+ Node *newNode = s->_segMan->lookupNode(argv[2]);
+
+#ifdef CHECK_LISTS
+ checkListPointer(s->_segMan, argv[0]);
+#endif
+
+ if (!newNode) {
+ error("New 'node' %04x:%04x is not a node", PRINT_REG(argv[2]));
+ return NULL_REG;
+ }
+
+ if (argc != 3 && argc != 4) {
+ error("kAddBefore: Haven't got 3 or 4 arguments, aborting");
+ return NULL_REG;
+ }
+
+ if (argc == 4)
+ newNode->key = argv[3];
+
+ if (firstNode) { // We're really appending before
+ const reg_t oldPred = firstNode->pred;
+
+ newNode->succ = argv[1];
+ firstNode->pred = argv[2];
+ newNode->pred = oldPred;
+
+ if (oldPred.isNull()) // Appended before first node?
+ // Set new node as first list node
+ list->first = argv[2];
+ else
+ s->_segMan->lookupNode(oldPred)->succ = argv[2];
+
+ } else {
addToFront(s, argv[0], argv[2]); // Set as initial list node
}
@@ -374,13 +416,21 @@ reg_t kFindKey(EngineState *s, int argc, reg_t *argv) {
reg_t kDeleteKey(EngineState *s, int argc, reg_t *argv) {
reg_t node_pos = kFindKey(s, 2, argv);
- Node *n;
List *list = s->_segMan->lookupList(argv[0]);
if (node_pos.isNull())
return NULL_REG; // Signal failure
- n = s->_segMan->lookupNode(node_pos);
+ Node *n = s->_segMan->lookupNode(node_pos);
+
+#ifdef ENABLE_SCI32
+ for (int i = 1; i <= list->numRecursions; ++i) {
+ if (list->nextNodes[i] == node_pos) {
+ list->nextNodes[i] = n->succ;
+ }
+ }
+#endif
+
if (list->first == node_pos)
list->first = n->succ;
if (list->last == node_pos)
@@ -486,7 +536,7 @@ reg_t kListAt(EngineState *s, int argc, reg_t *argv) {
List *list = s->_segMan->lookupList(argv[0]);
reg_t curAddress = list->first;
if (list->first.isNull()) {
- error("kListAt tried to reference empty list (%04x:%04x)", PRINT_REG(argv[0]));
+ // Happens in Torin when examining Di's locket in chapter 3
return NULL_REG;
}
Node *curNode = s->_segMan->lookupNode(curAddress);
@@ -539,15 +589,23 @@ reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) {
List *list = s->_segMan->lookupList(argv[0]);
Node *curNode = s->_segMan->lookupNode(list->first);
- reg_t curObject;
Selector slc = argv[1].toUint16();
ObjVarRef address;
+ ++list->numRecursions;
+
+ if (list->numRecursions >= ARRAYSIZE(list->nextNodes)) {
+ error("Too much recursion in kListEachElementDo");
+ }
+
while (curNode) {
- // We get the next node here as the current node might be gone after the invoke
- reg_t nextNode = curNode->succ;
- curObject = curNode->value;
+ // We get the next node here as the current node might be deleted by the
+ // invoke. In the case that the next node is also deleted, kDeleteKey
+ // needs to be able to adjust the location of the next node, which is
+ // why it is stored on the list instead of on the stack
+ list->nextNodes[list->numRecursions] = curNode->succ;
+ reg_t curObject = curNode->value;
// First, check if the target selector is a variable
if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) {
@@ -559,11 +617,18 @@ reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) {
}
} else {
invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2);
+ // Check if the call above leads to a game restore, in which case
+ // the segment manager will be reset, and the original list will
+ // be invalidated
+ if (s->abortScriptProcessing == kAbortLoadGame)
+ return s->r_acc;
}
- curNode = s->_segMan->lookupNode(nextNode);
+ curNode = s->_segMan->lookupNode(list->nextNodes[list->numRecursions]);
}
+ --list->numRecursions;
+
return s->r_acc;
}
@@ -571,37 +636,51 @@ reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv) {
List *list = s->_segMan->lookupList(argv[0]);
Node *curNode = s->_segMan->lookupNode(list->first);
- reg_t curObject;
Selector slc = argv[1].toUint16();
ObjVarRef address;
- s->r_acc = NULL_REG; // reset the accumulator
+ s->r_acc = NULL_REG;
+
+ ++list->numRecursions;
+
+ if (list->numRecursions >= ARRAYSIZE(list->nextNodes)) {
+ error("Too much recursion in kListFirstTrue");
+ }
while (curNode) {
- reg_t nextNode = curNode->succ;
- curObject = curNode->value;
+ // We get the next node here as the current node might be deleted by the
+ // invoke. In the case that the next node is also deleted, kDeleteKey
+ // needs to be able to adjust the location of the next node, which is
+ // why it is stored on the list instead of on the stack
+ list->nextNodes[list->numRecursions] = curNode->succ;
+ reg_t curObject = curNode->value;
// First, check if the target selector is a variable
if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) {
// If it's a variable selector, check its value.
// Example: script 64893 in Torin, MenuHandler::isHilited checks
// all children for variable selector 0x03ba (bHilited).
- if (!readSelector(s->_segMan, curObject, slc).isNull())
- return curObject;
+ if (!readSelector(s->_segMan, curObject, slc).isNull()) {
+ s->r_acc = curObject;
+ break;
+ }
} else {
invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2);
// Check if the result is true
- if (!s->r_acc.isNull())
- return curObject;
+ if (!s->r_acc.isNull()) {
+ s->r_acc = curObject;
+ break;
+ }
}
- curNode = s->_segMan->lookupNode(nextNode);
+ curNode = s->_segMan->lookupNode(list->nextNodes[list->numRecursions]);
}
- // No selector returned true
- return NULL_REG;
+ --list->numRecursions;
+
+ return s->r_acc;
}
reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv) {
@@ -613,10 +692,20 @@ reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv) {
ObjVarRef address;
- s->r_acc = make_reg(0, 1); // reset the accumulator
+ s->r_acc = TRUE_REG;
+
+ ++list->numRecursions;
+
+ if (list->numRecursions >= ARRAYSIZE(list->nextNodes)) {
+ error("Too much recursion in kListAllTrue");
+ }
while (curNode) {
- reg_t nextNode = curNode->succ;
+ // We get the next node here as the current node might be deleted by the
+ // invoke. In the case that the next node is also deleted, kDeleteKey
+ // needs to be able to adjust the location of the next node, which is
+ // why it is stored on the list instead of on the stack
+ list->nextNodes[list->numRecursions] = curNode->succ;
curObject = curNode->value;
// First, check if the target selector is a variable
@@ -631,7 +720,53 @@ reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv) {
if (s->r_acc.isNull())
break;
- curNode = s->_segMan->lookupNode(nextNode);
+ curNode = s->_segMan->lookupNode(list->nextNodes[list->numRecursions]);
+ }
+
+ --list->numRecursions;
+
+ return s->r_acc;
+}
+
+reg_t kListSort(EngineState *s, int argc, reg_t *argv) {
+ List *list = s->_segMan->lookupList(argv[0]);
+ const int16 selector = argv[1].toSint16();
+ const bool isDescending = argc > 2 ? (bool)argv[2].toUint16() : false;
+
+ reg_t firstNode = list->first;
+ for (reg_t node = firstNode; node != NULL_REG; node = s->_segMan->lookupNode(firstNode)->succ) {
+
+ reg_t a;
+ if (selector == -1) {
+ a = s->_segMan->lookupNode(node)->value;
+ } else {
+ a = readSelector(s->_segMan, s->_segMan->lookupNode(node)->value, selector);
+ }
+
+ firstNode = node;
+ for (reg_t newNode = s->_segMan->lookupNode(node)->succ; newNode != NULL_REG; newNode = s->_segMan->lookupNode(newNode)->succ) {
+ reg_t b;
+ if (selector == -1) {
+ b = s->_segMan->lookupNode(newNode)->value;
+ } else {
+ b = readSelector(s->_segMan, s->_segMan->lookupNode(newNode)->value, selector);
+ }
+
+ if ((!isDescending && b < a) || (isDescending && a < b)) {
+ firstNode = newNode;
+ a = b;
+ }
+ }
+
+ if (firstNode != node) {
+ reg_t buf[4] = { argv[0], s->_segMan->lookupNode(firstNode)->key };
+ kDeleteKey(s, 2, buf);
+
+ buf[1] = node;
+ buf[2] = firstNode;
+ buf[3] = s->_segMan->lookupNode(firstNode)->value;
+ kAddBefore(s, 4, buf);
+ }
}
return s->r_acc;
@@ -643,11 +778,6 @@ reg_t kList(EngineState *s, int argc, reg_t *argv) {
error("not supposed to call this");
}
-reg_t kAddBefore(EngineState *s, int argc, reg_t *argv) {
- error("Unimplemented function kAddBefore called");
- return s->r_acc;
-}
-
reg_t kMoveToFront(EngineState *s, int argc, reg_t *argv) {
error("Unimplemented function kMoveToFront called");
return s->r_acc;
@@ -659,197 +789,105 @@ reg_t kMoveToEnd(EngineState *s, int argc, reg_t *argv) {
}
reg_t kArray(EngineState *s, int argc, reg_t *argv) {
- uint16 op = argv[0].toUint16();
-
- // Use kString when accessing strings
- // This is possible, as strings inherit from arrays
- // and in this case (type 3) arrays are of type char *.
- // kString is almost exactly the same as kArray, so
- // this call is possible
- // TODO: we need to either merge SCI2 strings and
- // arrays together, and in the future merge them with
- // the SCI1 strings and arrays in the segment manager
- bool callStringFunc = false;
- if (op == 0) {
- // New, check if the target type is 3 (string)
- if (argv[2].toUint16() == 3)
- callStringFunc = true;
- } else {
- if (s->_segMan->getSegmentType(argv[1].getSegment()) == SEG_TYPE_STRING ||
- s->_segMan->getSegmentType(argv[1].getSegment()) == SEG_TYPE_SCRIPT) {
- callStringFunc = true;
- }
-
-#if 0
- if (op == 6) {
- if (s->_segMan->getSegmentType(argv[3].getSegment()) == SEG_TYPE_STRING ||
- s->_segMan->getSegmentType(argv[3].getSegment()) == SEG_TYPE_SCRIPT) {
- callStringFunc = true;
- }
- }
-#endif
- }
-
- if (callStringFunc) {
- Kernel *kernel = g_sci->getKernel();
- uint16 kernelStringFuncId = kernel->_kernelFunc_StringId;
- if (kernelStringFuncId) {
- const KernelFunction *kernelStringFunc = &kernel->_kernelFuncs[kernelStringFuncId];
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
- if (op < kernelStringFunc->subFunctionCount) {
- // subfunction-id is valid
- const KernelSubFunction *kernelStringSubCall = &kernelStringFunc->subFunctions[op];
- argc--;
- argv++; // remove subfunction-id from arguments
- // and call the kString subfunction
- return kernelStringSubCall->function(s, argc, argv);
- }
- }
- }
+reg_t kArrayNew(EngineState *s, int argc, reg_t *argv) {
+ uint16 size = argv[0].toUint16();
+ const SciArrayType type = (SciArrayType)argv[1].toUint16();
- switch (op) {
- case 0: { // New
- reg_t arrayHandle;
- SciArray<reg_t> *array = s->_segMan->allocateArray(&arrayHandle);
- array->setType(argv[2].toUint16());
- array->setSize(argv[1].toUint16());
- return arrayHandle;
- }
- case 1: { // Size
- SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]);
- return make_reg(0, array->getSize());
- }
- case 2: { // At (return value at an index)
- SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]);
- if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
- // HACK: Phantasmagoria 2 keeps trying to access past the end of an
- // array when it starts. I'm assuming it's trying to see where the
- // array ends, or tries to resize it. Adjust the array size
- // accordingly, and return NULL for now.
- if (array->getSize() == argv[2].toUint16()) {
- array->setSize(argv[2].toUint16());
- return NULL_REG;
- }
- }
- return array->getValue(argv[2].toUint16());
+ if (type == kArrayTypeString) {
+ ++size;
}
- case 3: { // Atput (put value at an index)
- SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]);
-
- uint32 index = argv[2].toUint16();
- uint32 count = argc - 3;
- if (index + count > 65535)
- break;
+ reg_t arrayHandle;
+ s->_segMan->allocateArray(type, size, &arrayHandle);
+ return arrayHandle;
+}
- if (array->getSize() < index + count)
- array->setSize(index + count);
+reg_t kArrayGetSize(EngineState *s, int argc, reg_t *argv) {
+ const SciArray &array = *s->_segMan->lookupArray(argv[0]);
+ return make_reg(0, array.size());
+}
- for (uint16 i = 0; i < count; i++)
- array->setValue(i + index, argv[i + 3]);
+reg_t kArrayGetElement(EngineState *s, int argc, reg_t *argv) {
+ SciArray &array = *s->_segMan->lookupArray(argv[0]);
+ return array.getAsID(argv[1].toUint16());
+}
- return argv[1]; // We also have to return the handle
- }
- case 4: // Free
- // Freeing of arrays is handled by the garbage collector
- return s->r_acc;
- case 5: { // Fill
- SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]);
- uint16 index = argv[2].toUint16();
+reg_t kArraySetElements(EngineState *s, int argc, reg_t *argv) {
+ SciArray &array = *s->_segMan->lookupArray(argv[0]);
+ array.setElements(argv[1].toUint16(), argc - 2, argv + 2);
+ return argv[0];
+}
- // A count of -1 means fill the rest of the array
- uint16 count = argv[3].toSint16() == -1 ? array->getSize() - index : argv[3].toUint16();
- uint16 arraySize = array->getSize();
+reg_t kArrayFree(EngineState *s, int argc, reg_t *argv) {
+ s->_segMan->freeArray(argv[0]);
+ return s->r_acc;
+}
- if (arraySize < index + count)
- array->setSize(index + count);
+reg_t kArrayFill(EngineState *s, int argc, reg_t *argv) {
+ SciArray &array = *s->_segMan->lookupArray(argv[0]);
+ array.fill(argv[1].toUint16(), argv[2].toUint16(), argv[3]);
+ return argv[0];
+}
- for (uint16 i = 0; i < count; i++)
- array->setValue(i + index, argv[4]);
+reg_t kArrayCopy(EngineState *s, int argc, reg_t *argv) {
+ SciArray &target = *s->_segMan->lookupArray(argv[0]);
+ const uint16 targetIndex = argv[1].toUint16();
- return argv[1];
+ SciArray source;
+ // String copies may be made from static script data
+ if (!s->_segMan->isArray(argv[2])) {
+ source.setType(kArrayTypeString);
+ source.fromString(s->_segMan->getString(argv[2]));
+ } else {
+ source = *s->_segMan->lookupArray(argv[2]);
}
- case 6: { // Cpy
- if (argv[1].isNull() || argv[3].isNull()) {
- if (getSciVersion() == SCI_VERSION_3) {
- // FIXME: Happens in SCI3, probably because of a missing kernel function.
- warning("kArray(Cpy): Request to copy from or to a null pointer");
- return NULL_REG;
- } else {
- // SCI2-2.1: error out
- error("kArray(Cpy): Request to copy from or to a null pointer");
- }
- }
-
- reg_t arrayHandle = argv[1];
- SciArray<reg_t> *array1 = s->_segMan->lookupArray(argv[1]);
- SciArray<reg_t> *array2 = s->_segMan->lookupArray(argv[3]);
- uint32 index1 = argv[2].toUint16();
- uint32 index2 = argv[4].toUint16();
-
- // The original engine ignores bad copies too
- if (index2 > array2->getSize())
- break;
-
- // A count of -1 means fill the rest of the array
- uint32 count = argv[5].toSint16() == -1 ? array2->getSize() - index2 : argv[5].toUint16();
+ const uint16 sourceIndex = argv[3].toUint16();
+ const uint16 count = argv[4].toUint16();
- if (array1->getSize() < index1 + count)
- array1->setSize(index1 + count);
+ target.copy(source, sourceIndex, targetIndex, count);
+ return argv[0];
+}
- for (uint16 i = 0; i < count; i++)
- array1->setValue(i + index1, array2->getValue(i + index2));
+reg_t kArrayDuplicate(EngineState *s, int argc, reg_t *argv) {
+ reg_t targetHandle;
- return arrayHandle;
+ // String duplicates may be made from static script data
+ if (!s->_segMan->isArray(argv[0])) {
+ const Common::String source = s->_segMan->getString(argv[0]);
+ SciArray &target = *s->_segMan->allocateArray(kArrayTypeString, source.size(), &targetHandle);
+ target.fromString(source);
+ } else {
+ SciArray &source = *s->_segMan->lookupArray(argv[0]);
+ SciArray &target = *s->_segMan->allocateArray(source.getType(), source.size(), &targetHandle);
+ target = source;
}
- case 7: // Cmp
- // Not implemented in SSCI
- warning("kArray(Cmp) called");
- return s->r_acc;
- case 8: { // Dup
- if (argv[1].isNull()) {
- warning("kArray(Dup): Request to duplicate a null pointer");
-#if 0
- // Allocate an array anyway
- reg_t arrayHandle;
- SciArray<reg_t> *dupArray = s->_segMan->allocateArray(&arrayHandle);
- dupArray->setType(3);
- dupArray->setSize(0);
- return arrayHandle;
-#endif
- return NULL_REG;
- }
- SegmentObj *sobj = s->_segMan->getSegmentObj(argv[1].getSegment());
- if (!sobj || sobj->getType() != SEG_TYPE_ARRAY)
- error("kArray(Dup): Request to duplicate a segment which isn't an array");
- reg_t arrayHandle;
- SciArray<reg_t> *dupArray = s->_segMan->allocateArray(&arrayHandle);
- // This must occur after allocateArray, as inserting a new object
- // in the heap object list might invalidate this pointer. Also refer
- // to the same issue in kClone()
- SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]);
-
- dupArray->setType(array->getType());
- dupArray->setSize(array->getSize());
-
- for (uint32 i = 0; i < array->getSize(); i++)
- dupArray->setValue(i, array->getValue(i));
-
- return arrayHandle;
- }
- case 9: // Getdata
- if (!s->_segMan->isHeapObject(argv[1]))
- return argv[1];
+ return targetHandle;
+}
- return readSelector(s->_segMan, argv[1], SELECTOR(data));
- default:
- error("Unknown kArray subop %d", op);
+reg_t kArrayGetData(EngineState *s, int argc, reg_t *argv) {
+ if (s->_segMan->isObject(argv[0])) {
+ return readSelector(s->_segMan, argv[0], SELECTOR(data));
}
- return NULL_REG;
+ return argv[0];
}
+reg_t kArrayByteCopy(EngineState *s, int argc, reg_t *argv) {
+ SciArray &target = *s->_segMan->lookupArray(argv[0]);
+ const uint16 targetOffset = argv[1].toUint16();
+ const SciArray &source = *s->_segMan->lookupArray(argv[2]);
+ const uint16 sourceOffset = argv[3].toUint16();
+ const uint16 count = argv[4].toUint16();
+
+ target.byteCopy(source, sourceOffset, targetOffset, count);
+ return argv[0];
+}
#endif
} // End of namespace Sci
diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp
index f4bb4ff85b..9aa03a4760 100644
--- a/engines/sci/engine/kmisc.cpp
+++ b/engines/sci/engine/kmisc.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "common/config-manager.h"
#include "common/system.h"
#include "sci/sci.h"
@@ -29,6 +30,9 @@
#include "sci/engine/kernel.h"
#include "sci/engine/gc.h"
#include "sci/graphics/cursor.h"
+#ifdef ENABLE_SCI32
+#include "sci/graphics/cursor32.h"
+#endif
#include "sci/graphics/maciconbar.h"
#include "sci/console.h"
@@ -243,10 +247,18 @@ reg_t kGetTime(EngineState *s, int argc, reg_t *argv) {
debugC(kDebugLevelTime, "GetTime(24h) returns %d", retval);
break;
case KGETTIME_DATE :
- // Year since 1980 (0 = 1980, 1 = 1981, etc.)
- retval = loc_time.tm_mday | ((loc_time.tm_mon + 1) << 5) | (((loc_time.tm_year - 80) & 0x7f) << 9);
+ {
+ // SCI0 late: Year since 1920 (0 = 1920, 1 = 1921, etc)
+ // SCI01 and newer: Year since 1980 (0 = 1980, 1 = 1981, etc)
+ // Atari ST SCI0 late versions use the newer base year.
+ int baseYear = 80;
+ if (getSciVersion() == SCI_VERSION_0_LATE && g_sci->getPlatform() == Common::kPlatformDOS) {
+ baseYear = 20;
+ }
+ retval = loc_time.tm_mday | ((loc_time.tm_mon + 1) << 5) | (((loc_time.tm_year - baseYear) & 0x7f) << 9);
debugC(kDebugLevelTime, "GetTime(date) returns %d", retval);
break;
+ }
default:
error("Attempt to use unknown GetTime mode %d", mode);
break;
@@ -401,6 +413,15 @@ reg_t kGetConfig(EngineState *s, int argc, reg_t *argv) {
} else if (setting == "startroom") {
// Debug setting in LSL7, specifies the room to start from.
s->_segMan->strcpy(data, "");
+ } else if (setting == "game") {
+ // Hoyle 5 startup, specifies the number of the game to start.
+ s->_segMan->strcpy(data, "");
+ } else if (setting == "laptop") {
+ // Hoyle 5 startup.
+ s->_segMan->strcpy(data, "");
+ } else if (setting == "jumpto") {
+ // Hoyle 5 startup.
+ s->_segMan->strcpy(data, "");
} else {
error("GetConfig: Unknown configuration setting %s", setting.c_str());
}
@@ -438,6 +459,14 @@ reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv) {
}
}
+extern Common::String format(const Common::String &source, int argc, const reg_t *argv);
+
+reg_t kPrintDebug(EngineState *s, int argc, reg_t *argv) {
+ const Common::String debugString = s->_segMan->getString(argv[0]);
+ debugC(kDebugLevelGame, "%s", format(debugString, argc - 1, argv + 1).c_str());
+ return s->r_acc;
+}
+
#endif
// kIconBar is really a subop of kMacPlatform for SCI1.1 Mac
@@ -493,9 +522,12 @@ reg_t kMacPlatform(EngineState *s, int argc, reg_t *argv) {
// In SCI1, its usage is still unknown
// In SCI1.1, it's NOP
// In SCI32, it's used for remapping cursor ID's
+#ifdef ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2_1_EARLY) // Set Mac cursor remap
- g_sci->_gfxCursor->setMacCursorRemapList(argc - 1, argv + 1);
- else if (getSciVersion() != SCI_VERSION_1_1)
+ g_sci->_gfxCursor32->setMacCursorRemapList(argc - 1, argv + 1);
+ else
+#endif
+ if (getSciVersion() != SCI_VERSION_1_1)
warning("Unknown SCI1 kMacPlatform(0) call");
break;
case 4: // Handle icon bar code
@@ -518,31 +550,28 @@ reg_t kMacPlatform(EngineState *s, int argc, reg_t *argv) {
}
enum kSciPlatforms {
+ kSciPlatformMacintosh = 0,
kSciPlatformDOS = 1,
kSciPlatformWindows = 2
};
-enum kPlatformOps {
- kPlatformUnk0 = 0,
- kPlatformCDSpeed = 1,
- kPlatformUnk2 = 2,
- kPlatformCDCheck = 3,
- kPlatformGetPlatform = 4,
- kPlatformUnk5 = 5,
- kPlatformIsHiRes = 6,
- kPlatformIsItWindows = 7
-};
-
reg_t kPlatform(EngineState *s, int argc, reg_t *argv) {
+ enum Operation {
+ kPlatformUnknown = 0,
+ kPlatformGetPlatform = 4,
+ kPlatformUnknown5 = 5,
+ kPlatformIsHiRes = 6,
+ kPlatformWin311OrHigher = 7
+ };
+
bool isWindows = g_sci->getPlatform() == Common::kPlatformWindows;
- if (argc == 0 && getSciVersion() < SCI_VERSION_2) {
+ if (argc == 0) {
// This is called in KQ5CD with no parameters, where it seems to do some
// graphics driver check. This kernel function didn't have subfunctions
// then. If 0 is returned, the game functions normally, otherwise all
// the animations show up like a slideshow (e.g. in the intro). So we
- // return 0. However, the behavior changed for kPlatform with no
- // parameters in SCI32.
+ // return 0.
return NULL_REG;
}
@@ -554,30 +583,23 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) {
uint16 operation = (argc == 0) ? 0 : argv[0].toUint16();
switch (operation) {
- case kPlatformCDSpeed:
- // TODO: Returns CD Speed?
- warning("STUB: kPlatform(CDSpeed)");
- break;
- case kPlatformUnk2:
- // Always returns 2
- return make_reg(0, 2);
- case kPlatformCDCheck:
- // TODO: Some sort of CD check?
- warning("STUB: kPlatform(CDCheck)");
- break;
- case kPlatformUnk0:
+ case kPlatformUnknown:
// For Mac versions, kPlatform(0) with other args has more functionality
if (g_sci->getPlatform() == Common::kPlatformMacintosh && argc > 1)
return kMacPlatform(s, argc - 1, argv + 1);
// Otherwise, fall through
case kPlatformGetPlatform:
- return make_reg(0, (isWindows) ? kSciPlatformWindows : kSciPlatformDOS);
- case kPlatformUnk5:
+ if (isWindows)
+ return make_reg(0, kSciPlatformWindows);
+ else if (g_sci->getPlatform() == Common::kPlatformMacintosh)
+ return make_reg(0, kSciPlatformMacintosh);
+ else
+ return make_reg(0, kSciPlatformDOS);
+ case kPlatformUnknown5:
// This case needs to return the opposite of case 6 to get hires graphics
return make_reg(0, !isWindows);
case kPlatformIsHiRes:
- return make_reg(0, isWindows);
- case kPlatformIsItWindows:
+ case kPlatformWin311OrHigher:
return make_reg(0, isWindows);
default:
error("Unsupported kPlatform operation %d", operation);
@@ -586,6 +608,43 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
+#ifdef ENABLE_SCI32
+reg_t kPlatform32(EngineState *s, int argc, reg_t *argv) {
+ enum Operation {
+ kGetPlatform = 0,
+ kGetCDSpeed = 1,
+ kGetColorDepth = 2,
+ kGetCDDrive = 3
+ };
+
+ const Operation operation = argc > 0 ? (Operation)argv[0].toSint16() : kGetPlatform;
+
+ switch (operation) {
+ case kGetPlatform:
+ switch (g_sci->getPlatform()) {
+ case Common::kPlatformDOS:
+ return make_reg(0, kSciPlatformDOS);
+ case Common::kPlatformWindows:
+ return make_reg(0, kSciPlatformWindows);
+ case Common::kPlatformMacintosh:
+ // For Mac versions, kPlatform(0) with other args has more functionality
+ if (argc > 1)
+ return kMacPlatform(s, argc - 1, argv + 1);
+ else
+ return make_reg(0, kSciPlatformMacintosh);
+ default:
+ error("Unknown platform %d", g_sci->getPlatform());
+ }
+ case kGetColorDepth:
+ return make_reg(0, /* 256 color */ 2);
+ case kGetCDSpeed:
+ case kGetCDDrive:
+ default:
+ return make_reg(0, 0);
+ }
+}
+#endif
+
reg_t kEmpty(EngineState *s, int argc, reg_t *argv) {
// Placeholder for empty kernel functions which are still called from the
// engine scripts (like the empty kSetSynonyms function in SCI1.1). This
@@ -597,18 +656,28 @@ reg_t kEmpty(EngineState *s, int argc, reg_t *argv) {
reg_t kStub(EngineState *s, int argc, reg_t *argv) {
Kernel *kernel = g_sci->getKernel();
int kernelCallNr = -1;
+ int kernelSubCallNr = -1;
Common::List<ExecStack>::const_iterator callIterator = s->_executionStack.end();
if (callIterator != s->_executionStack.begin()) {
callIterator--;
ExecStack lastCall = *callIterator;
- kernelCallNr = lastCall.debugSelector;
+ kernelCallNr = lastCall.debugKernelFunction;
+ kernelSubCallNr = lastCall.debugKernelSubFunction;
+ }
+
+ Common::String warningMsg;
+ if (kernelSubCallNr == -1) {
+ warningMsg = "Dummy function k" + kernel->getKernelName(kernelCallNr) +
+ Common::String::format("[%x]", kernelCallNr);
+ } else {
+ warningMsg = "Dummy function k" + kernel->getKernelName(kernelCallNr, kernelSubCallNr) +
+ Common::String::format("[%x:%x]", kernelCallNr, kernelSubCallNr);
+
}
- Common::String warningMsg = "Dummy function k" + kernel->getKernelName(kernelCallNr) +
- Common::String::format("[%x]", kernelCallNr) +
- " invoked. Params: " +
- Common::String::format("%d", argc) + " (";
+ warningMsg += " invoked. Params: " +
+ Common::String::format("%d", argc) + " (";
for (int i = 0; i < argc; i++) {
warningMsg += Common::String::format("%04x:%04x", PRINT_REG(argv[i]));
diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp
index 5b2245e84d..937b1cfc2f 100644
--- a/engines/sci/engine/kpathing.cpp
+++ b/engines/sci/engine/kpathing.cpp
@@ -326,7 +326,7 @@ static void draw_line(EngineState *s, Common::Point p1, Common::Point p2, int ty
p2.y = CLIP<int16>(p2.y, 0, height - 1);
assert(type >= 0 && type <= 3);
- g_sci->_gfxPaint->kernelGraphDrawLine(p1, p2, poly_colors[type], 255, 255);
+ g_sci->_gfxPaint16->kernelGraphDrawLine(p1, p2, poly_colors[type], 255, 255);
}
static void draw_point(EngineState *s, Common::Point p, int start, int width, int height) {
@@ -1397,10 +1397,8 @@ static reg_t allocateOutputArray(SegManager *segMan, int size) {
#ifdef ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2) {
- SciArray<reg_t> *array = segMan->allocateArray(&addr);
+ SciArray *array = segMan->allocateArray(kArrayTypeInt16, size * 2, &addr);
assert(array);
- array->setType(0);
- array->setSize(size * 2);
return addr;
}
#endif
@@ -1943,14 +1941,14 @@ static int liesBefore(const Vertex *v, const Common::Point &p1, const Common::Po
// indexp1/vertexp1 on the polygon being merged.
// It ends with the point intersection2, being the analogous intersection.
struct Patch {
- unsigned int indexw1;
- unsigned int indexp1;
+ uint32 indexw1;
+ uint32 indexp1;
const Vertex *vertexw1;
const Vertex *vertexp1;
Common::Point intersection1;
- unsigned int indexw2;
- unsigned int indexp2;
+ uint32 indexw2;
+ uint32 indexp2;
const Vertex *vertexw2;
const Vertex *vertexp2;
Common::Point intersection2;
@@ -1960,7 +1958,7 @@ struct Patch {
// Check if the given vertex on the work polygon is bypassed by this patch.
-static bool isVertexCovered(const Patch &p, unsigned int wi) {
+static bool isVertexCovered(const Patch &p, uint32 wi) {
// / v (outside)
// ---w1--1----p----w2--2----
@@ -2402,7 +2400,7 @@ reg_t kMergePoly(EngineState *s, int argc, reg_t *argv) {
// Copy work.vertices into arrayRef
Vertex *vertex;
- unsigned int n = 0;
+ uint32 n = 0;
CLIST_FOREACH(vertex, &work.vertices) {
if (vertex == work.vertices._head || vertex->v != vertex->_prev->v)
writePoint(arrayRef, n++, vertex->v);
diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp
index 303de079aa..0e29ccf783 100644
--- a/engines/sci/engine/kscripts.cpp
+++ b/engines/sci/engine/kscripts.cpp
@@ -238,8 +238,8 @@ reg_t kScriptID(EngineState *s, int argc, reg_t *argv) {
// initialized to 0, whereas it's 6 in other versions. Thus, we assign it
// to 6 here, fixing the speed of the introduction. Refer to bug #3102071.
if (g_sci->getGameId() == GID_PQ2 && script == 200 &&
- s->variables[VAR_GLOBAL][3].isNull()) {
- s->variables[VAR_GLOBAL][3] = make_reg(0, 6);
+ s->variables[VAR_GLOBAL][kGlobalVarSpeed].isNull()) {
+ s->variables[VAR_GLOBAL][kGlobalVarSpeed] = make_reg(0, 6);
}
return make_reg(scriptSeg, address);
@@ -260,9 +260,6 @@ reg_t kDisposeScript(EngineState *s, int argc, reg_t *argv) {
if (argc != 2) {
return s->r_acc;
} else {
- // This exists in the KQ5CD and GK1 interpreter. We know it is used
- // when GK1 starts up, before the Sierra logo.
- warning("kDisposeScript called with 2 parameters, still untested");
return argv[1];
}
}
diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp
index 398a623286..ed53b8d52f 100644
--- a/engines/sci/engine/ksound.cpp
+++ b/engines/sci/engine/ksound.cpp
@@ -26,7 +26,11 @@
#include "sci/engine/kernel.h"
#include "sci/engine/vm.h" // for Object
#include "sci/sound/audio.h"
+#ifdef ENABLE_SCI32
+#include "sci/sound/audio32.h"
+#endif
#include "sci/sound/soundcmd.h"
+#include "sci/sound/sync.h"
#include "audio/mixer.h"
#include "common/system.h"
@@ -46,7 +50,6 @@ reg_t kDoSound(EngineState *s, int argc, reg_t *argv) {
CREATE_DOSOUND_FORWARD(DoSoundInit)
CREATE_DOSOUND_FORWARD(DoSoundPlay)
-CREATE_DOSOUND_FORWARD(DoSoundRestore)
CREATE_DOSOUND_FORWARD(DoSoundDispose)
CREATE_DOSOUND_FORWARD(DoSoundMute)
CREATE_DOSOUND_FORWARD(DoSoundStop)
@@ -61,13 +64,41 @@ CREATE_DOSOUND_FORWARD(DoSoundUpdateCues)
CREATE_DOSOUND_FORWARD(DoSoundSendMidi)
CREATE_DOSOUND_FORWARD(DoSoundGlobalReverb)
CREATE_DOSOUND_FORWARD(DoSoundSetHold)
-CREATE_DOSOUND_FORWARD(DoSoundDummy)
CREATE_DOSOUND_FORWARD(DoSoundGetAudioCapability)
CREATE_DOSOUND_FORWARD(DoSoundSuspend)
CREATE_DOSOUND_FORWARD(DoSoundSetVolume)
CREATE_DOSOUND_FORWARD(DoSoundSetPriority)
CREATE_DOSOUND_FORWARD(DoSoundSetLoop)
+#ifdef ENABLE_SCI32
+reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv) {
+ // Phantasmagoria Mac (and seemingly no other game (!)) uses this
+ // cutdown version of kDoSound.
+
+ switch (argv[0].toUint16()) {
+ case 0:
+ return g_sci->_soundCmd->kDoSoundMasterVolume(argc - 1, argv + 1, s->r_acc);
+ case 2:
+ return g_sci->_soundCmd->kDoSoundInit(argc - 1, argv + 1, s->r_acc);
+ case 3:
+ return g_sci->_soundCmd->kDoSoundDispose(argc - 1, argv + 1, s->r_acc);
+ case 4:
+ return g_sci->_soundCmd->kDoSoundPlay(argc - 1, argv + 1, s->r_acc);
+ case 5:
+ return g_sci->_soundCmd->kDoSoundStop(argc - 1, argv + 1, s->r_acc);
+ case 8:
+ return g_sci->_soundCmd->kDoSoundSetVolume(argc - 1, argv + 1, s->r_acc);
+ case 9:
+ return g_sci->_soundCmd->kDoSoundSetLoop(argc - 1, argv + 1, s->r_acc);
+ case 10:
+ return g_sci->_soundCmd->kDoSoundUpdateCues(argc - 1, argv + 1, s->r_acc);
+ }
+
+ error("Unknown kDoSound Phantasmagoria Mac subop %d", argv[0].toUint16());
+ return s->r_acc;
+}
+#endif
+
reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) {
switch (argv[0].toUint16()) {
case kSciAudioPlay: {
@@ -113,7 +144,8 @@ reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) {
}
/**
- * Used for speech playback and digital soundtracks in CD games
+ * Used for speech playback and digital soundtracks in CD games.
+ * This is the SCI16 version; SCI32 is handled separately.
*/
reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
// JonesCD uses different functions based on the cdaudio.map file
@@ -184,14 +216,6 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
int16 volume = argv[1].toUint16();
volume = CLIP<int16>(volume, 0, AUDIO_VOLUME_MAX);
debugC(kDebugLevelSound, "kDoAudio: set volume to %d", volume);
-#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
- int16 volumePrev = mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2;
- volumePrev = CLIP<int16>(volumePrev, 0, AUDIO_VOLUME_MAX);
- mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2);
- return make_reg(0, volumePrev);
- } else
-#endif
mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2);
break;
}
@@ -232,12 +256,6 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
if (getSciVersion() <= SCI_VERSION_1_1) {
debugC(kDebugLevelSound, "kDoAudio: CD audio subop");
return kDoCdAudio(s, argc - 1, argv + 1);
-#ifdef ENABLE_SCI32
- } else {
- // TODO: This isn't CD Audio in SCI32 anymore
- warning("kDoAudio: Unhandled case 10, %d extra arguments passed", argc - 1);
- break;
-#endif
}
// 3 new subops in Pharkas CD (including CD demo). kDoAudio in Pharkas sits at seg026:038C
@@ -286,14 +304,12 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
}
reg_t kDoSync(EngineState *s, int argc, reg_t *argv) {
- SegManager *segMan = s->_segMan;
switch (argv[0].toUint16()) {
case kSciAudioSyncStart: {
ResourceId id;
- g_sci->_audio->stopSoundSync();
+ g_sci->_sync->stop();
- // Load sound sync resource and lock it
if (argc == 3) {
id = ResourceId(kResourceTypeSync, argv[2].toUint16());
} else if (argc == 7) {
@@ -304,14 +320,14 @@ reg_t kDoSync(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
- g_sci->_audio->setSoundSync(id, argv[1], segMan);
+ g_sci->_sync->start(id, argv[1]);
break;
}
case kSciAudioSyncNext:
- g_sci->_audio->doSoundSync(argv[1], segMan);
+ g_sci->_sync->next(argv[1]);
break;
case kSciAudioSyncStop:
- g_sci->_audio->stopSoundSync();
+ g_sci->_sync->stop();
break;
default:
error("DoSync: Unhandled subfunction %d", argv[0].toUint16());
@@ -321,6 +337,155 @@ reg_t kDoSync(EngineState *s, int argc, reg_t *argv) {
}
#ifdef ENABLE_SCI32
+reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, 0);
+}
+
+reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv) {
+ return g_sci->_audio32->kernelPlay(false, argc, argv);
+}
+
+reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv) {
+ return g_sci->_audio32->kernelPlay(true, argc, argv);
+}
+
+reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->stop(channelIndex));
+}
+
+reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->pause(channelIndex));
+}
+
+reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->resume(channelIndex));
+}
+
+reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->getPosition(channelIndex));
+}
+
+reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would set the hardware
+ // DSP sampling rate; ScummVM mixer does not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ const uint16 sampleRate = argv[0].toUint16();
+ if (sampleRate != 0) {
+ g_sci->_audio32->setSampleRate(sampleRate);
+ }
+ }
+
+ return make_reg(0, g_sci->_audio32->getSampleRate());
+}
+
+reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv) {
+ const int16 volume = argc > 0 ? argv[0].toSint16() : -1;
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 1, argc > 2 ? argv[2] : NULL_REG);
+
+ if (volume != -1) {
+ g_sci->_audio32->setVolume(channelIndex, volume);
+ }
+
+ return make_reg(0, g_sci->_audio32->getVolume(channelIndex));
+}
+
+reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, 1);
+}
+
+reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would set the hardware
+ // DSP bit depth; ScummVM mixer does not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ const uint16 bitDepth = argv[0].toUint16();
+ if (bitDepth != 0) {
+ g_sci->_audio32->setBitDepth(bitDepth);
+ }
+ }
+
+ return make_reg(0, g_sci->_audio32->getBitDepth());
+}
+
+reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv) {
+ if (argc > 0) {
+ g_sci->_audio32->setAttenuatedMixing(argv[0].toUint16());
+ }
+
+ return make_reg(0, g_sci->_audio32->getAttenuatedMixing());
+}
+
+reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would set the hardware
+ // DSP stereo output; ScummVM mixer does not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ const int16 numChannels = argv[0].toSint16();
+ if (numChannels != 0) {
+ g_sci->_audio32->setNumOutputChannels(numChannels);
+ }
+ }
+
+ return make_reg(0, g_sci->_audio32->getNumOutputChannels());
+}
+
+reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would cause audio
+ // data for new channels to be preloaded to memory when
+ // the channel was initialized; we do not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ g_sci->_audio32->setPreload(argv[0].toUint16());
+ }
+
+ return make_reg(0, g_sci->_audio32->getPreload());
+}
+
+reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv) {
+ if (argc < 4) {
+ return make_reg(0, 0);
+ }
+
+ // NOTE: Sierra did a nightmarish hack here, temporarily replacing
+ // the argc of the kernel arguments with 2 and then restoring it
+ // after findChannelByArgs was called.
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(2, argv, 0, argc > 5 ? argv[5] : NULL_REG);
+
+ const int16 volume = argv[1].toSint16();
+ const int16 speed = argv[2].toSint16();
+ const int16 steps = argv[3].toSint16();
+ const bool stopAfterFade = argc > 4 ? (bool)argv[4].toUint16() : false;
+
+ return make_reg(0, g_sci->_audio32->fadeChannel(channelIndex, volume, speed, steps, stopAfterFade));
+}
+
+reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_audio32->hasSignal());
+}
+
+reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc == 3 ? argv[2] : NULL_REG);
+
+ const bool loop = argv[0].toSint16() != 0 && argv[0].toSint16() != 1;
+
+ g_sci->_audio32->setLoop(channelIndex, loop);
+ return s->r_acc;
+}
reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv) {
// This is used by script 90 of MUMG Deluxe from the main menu to toggle
@@ -335,33 +500,6 @@ reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
-reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv) {
- // Phantasmagoria Mac (and seemingly no other game (!)) uses this
- // cutdown version of kDoSound.
-
- switch (argv[0].toUint16()) {
- case 0:
- return g_sci->_soundCmd->kDoSoundMasterVolume(argc - 1, argv + 1, s->r_acc);
- case 2:
- return g_sci->_soundCmd->kDoSoundInit(argc - 1, argv + 1, s->r_acc);
- case 3:
- return g_sci->_soundCmd->kDoSoundDispose(argc - 1, argv + 1, s->r_acc);
- case 4:
- return g_sci->_soundCmd->kDoSoundPlay(argc - 1, argv + 1, s->r_acc);
- case 5:
- return g_sci->_soundCmd->kDoSoundStop(argc - 1, argv + 1, s->r_acc);
- case 8:
- return g_sci->_soundCmd->kDoSoundSetVolume(argc - 1, argv + 1, s->r_acc);
- case 9:
- return g_sci->_soundCmd->kDoSoundSetLoop(argc - 1, argv + 1, s->r_acc);
- case 10:
- return g_sci->_soundCmd->kDoSoundUpdateCues(argc - 1, argv + 1, s->r_acc);
- }
-
- error("Unknown kDoSound Phantasmagoria Mac subop %d", argv[0].toUint16());
- return s->r_acc;
-}
-
#endif
} // End of namespace Sci
diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp
index 310e38dbd1..ab1f0210e7 100644
--- a/engines/sci/engine/kstring.cpp
+++ b/engines/sci/engine/kstring.cpp
@@ -202,11 +202,6 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) {
}
-#define ALIGN_NONE 0
-#define ALIGN_RIGHT 1
-#define ALIGN_LEFT -1
-#define ALIGN_CENTER 2
-
/* Format(targ_address, textresnr, index_inside_res, ...)
** or
** Format(targ_address, heap_text_addr, ...)
@@ -214,6 +209,13 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) {
** the supplied parameters and writes it to the targ_address.
*/
reg_t kFormat(EngineState *s, int argc, reg_t *argv) {
+ enum {
+ ALIGN_NONE = 0,
+ ALIGN_RIGHT = 1,
+ ALIGN_LEFT = -1,
+ ALIGN_CENTER = 2
+ };
+
uint16 *arguments;
reg_t dest = argv[0];
int maxsize = 4096; /* Arbitrary... */
@@ -301,12 +303,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) {
case 's': { /* Copy string */
reg_t reg = argv[startarg + paramindex];
-#ifdef ENABLE_SCI32
- // If the string is a string object, get to the actual string in the data selector
- if (s->_segMan->isObject(reg))
- reg = readSelector(s->_segMan, reg, SELECTOR(data));
-#endif
-
Common::String tempsource = g_sci->getKernel()->lookupText(reg,
arguments[paramindex + 1]);
int slen = strlen(tempsource.c_str());
@@ -379,12 +375,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) {
case 'u':
unsignedVar = true;
case 'd': { /* Copy decimal */
- // In the new SCI2 kString function, %d is used for unsigned
- // integers. An example is script 962 in Shivers - it uses %d
- // to create file names.
- if (getSciVersion() >= SCI_VERSION_2)
- unsignedVar = true;
-
/* int templen; -- unused atm */
const char *format_string = "%d";
@@ -437,14 +427,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) {
*target = 0; /* Terminate string */
-#ifdef ENABLE_SCI32
- // Resize SCI32 strings if necessary
- if (getSciVersion() >= SCI_VERSION_2) {
- SciString *string = s->_segMan->lookupString(dest);
- string->setSize(strlen(targetbuf) + 1);
- }
-#endif
-
s->_segMan->strcpy(dest, targetbuf);
return dest; /* Return target addr */
@@ -456,31 +438,15 @@ reg_t kStrLen(EngineState *s, int argc, reg_t *argv) {
reg_t kGetFarText(EngineState *s, int argc, reg_t *argv) {
- Resource *textres = g_sci->getResMan()->findResource(ResourceId(kResourceTypeText, argv[0].toUint16()), 0);
- char *seeker;
- int counter = argv[1].toUint16();
-
- if (!textres) {
- error("text.%d does not exist", argv[0].toUint16());
- return NULL_REG;
- }
-
- seeker = (char *)textres->data;
-
- // The second parameter (counter) determines the number of the string
- // inside the text resource.
- while (counter--) {
- while (*seeker++)
- ;
- }
+ const Common::String text = g_sci->getKernel()->lookupText(make_reg(0, argv[0].toUint16()), argv[1].toUint16());
// If the third argument is NULL, allocate memory for the destination. This
// occurs in SCI1 Mac games. The memory will later be freed by the game's
// scripts.
if (argv[2] == NULL_REG)
- s->_segMan->allocDynmem(strlen(seeker) + 1, "Mac FarText", &argv[2]);
+ s->_segMan->allocDynmem(text.size() + 1, "Mac FarText", &argv[2]);
- s->_segMan->strcpy(argv[2], seeker); // Copy the string and get return value
+ s->_segMan->strcpy(argv[2], text.c_str()); // Copy the string and get return value
return argv[2];
}
@@ -661,248 +627,240 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) {
#ifdef ENABLE_SCI32
-reg_t kText(EngineState *s, int argc, reg_t *argv) {
- switch (argv[0].toUint16()) {
- case 0:
- return kTextSize(s, argc - 1, argv + 1);
- default:
- // TODO: Other subops here too, perhaps kTextColors and kTextFonts
- warning("kText(%d)", argv[0].toUint16());
- break;
- }
-
- return s->r_acc;
+reg_t kString(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
}
-// TODO: there is an unused second argument, happens at least in LSL6 right during the intro
reg_t kStringNew(EngineState *s, int argc, reg_t *argv) {
reg_t stringHandle;
- SciString *string = s->_segMan->allocateString(&stringHandle);
- string->setSize(argv[0].toUint16());
-
- // Make sure the first character is a null character
- if (string->getSize() > 0)
- string->setValue(0, 0);
-
+ const uint16 size = argv[0].toUint16();
+ s->_segMan->allocateArray(kArrayTypeString, size, &stringHandle);
return stringHandle;
}
-reg_t kStringSize(EngineState *s, int argc, reg_t *argv) {
- return make_reg(0, s->_segMan->getString(argv[0]).size());
-}
+reg_t kStringGetChar(EngineState *s, int argc, reg_t *argv) {
+ const uint16 index = argv[1].toUint16();
-// At (return value at an index)
-reg_t kStringAt(EngineState *s, int argc, reg_t *argv) {
- // Note that values are put in bytes to avoid sign extension
- if (argv[0].getSegment() == s->_segMan->getStringSegmentId()) {
- SciString *string = s->_segMan->lookupString(argv[0]);
- byte val = string->getRawData()[argv[1].toUint16()];
- return make_reg(0, val);
- } else {
- Common::String string = s->_segMan->getString(argv[0]);
- byte val = string[argv[1].toUint16()];
- return make_reg(0, val);
- }
-}
-
-// Atput (put value at an index)
-reg_t kStringPutAt(EngineState *s, int argc, reg_t *argv) {
- SciString *string = s->_segMan->lookupString(argv[0]);
-
- uint32 index = argv[1].toUint16();
- uint32 count = argc - 2;
+ // Game scripts may contain static raw string data
+ if (!s->_segMan->isArray(argv[0])) {
+ const Common::String string = s->_segMan->getString(argv[0]);
+ if (index >= string.size()) {
+ return make_reg(0, 0);
+ }
- if (index + count > 65535)
- return NULL_REG;
+ return make_reg(0, (byte)string[index]);
+ }
- if (string->getSize() < index + count)
- string->setSize(index + count);
+ SciArray &array = *s->_segMan->lookupArray(argv[0]);
- for (uint16 i = 0; i < count; i++)
- string->setValue(i + index, argv[i + 2].toUint16());
+ if (index >= array.size()) {
+ return make_reg(0, 0);
+ }
- return argv[0]; // We also have to return the handle
+ return array.getAsID(index);
}
reg_t kStringFree(EngineState *s, int argc, reg_t *argv) {
- // Freeing of strings is handled by the garbage collector
+ if (!argv[0].isNull()) {
+ s->_segMan->freeArray(argv[0]);
+ }
return s->r_acc;
}
-reg_t kStringFill(EngineState *s, int argc, reg_t *argv) {
- SciString *string = s->_segMan->lookupString(argv[0]);
- uint16 index = argv[1].toUint16();
+reg_t kStringCompare(EngineState *s, int argc, reg_t *argv) {
+ const Common::String string1 = s->_segMan->getString(argv[0]);
+ const Common::String string2 = s->_segMan->getString(argv[1]);
- // A count of -1 means fill the rest of the array
- uint16 count = argv[2].toSint16() == -1 ? string->getSize() - index : argv[2].toUint16();
- uint16 stringSize = string->getSize();
+ int result;
+ if (argc == 3) {
+ result = strncmp(string1.c_str(), string2.c_str(), argv[2].toUint16());
+ } else {
+ result = strcmp(string1.c_str(), string2.c_str());
+ }
- if (stringSize < index + count)
- string->setSize(index + count);
+ return make_reg(0, (result > 0) - (result < 0));
+}
- for (uint16 i = 0; i < count; i++)
- string->setValue(i + index, argv[3].toUint16());
+reg_t kStringGetData(EngineState *s, int argc, reg_t *argv) {
+ if (s->_segMan->isObject(argv[0])) {
+ return readSelector(s->_segMan, argv[0], SELECTOR(data));
+ }
return argv[0];
}
-reg_t kStringCopy(EngineState *s, int argc, reg_t *argv) {
- const char *string2 = 0;
- uint32 string2Size = 0;
- Common::String string;
+reg_t kStringLength(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, s->_segMan->getString(argv[0]).size());
+}
- if (argv[2].getSegment() == s->_segMan->getStringSegmentId()) {
- SciString *sstr;
- sstr = s->_segMan->lookupString(argv[2]);
- string2 = sstr->getRawData();
- string2Size = sstr->getSize();
- } else {
- string = s->_segMan->getString(argv[2]);
- string2 = string.c_str();
- string2Size = string.size() + 1;
+namespace {
+ bool isFlag(const char c) {
+ return strchr("-+ 0#", c);
}
- uint32 index1 = argv[1].toUint16();
- uint32 index2 = argv[3].toUint16();
-
- if (argv[0] == argv[2]) {
- // source and destination string are one and the same
- if (index1 == index2) {
- // even same index? ignore this call
- // Happens in KQ7, when starting a chapter
- return argv[0];
- }
- // TODO: this will crash, when setSize() is triggered later
- // we need to exactly replicate original interpreter behavior
- warning("kString(Copy): source is the same as destination string");
+ bool isPrecision(const char c) {
+ return strchr(".0123456789*", c);
}
- // The original engine ignores bad copies too
- if (index2 > string2Size)
- return NULL_REG;
-
- // A count of -1 means fill the rest of the array
- uint32 count = argv[4].toSint16() == -1 ? string2Size - index2 + 1 : argv[4].toUint16();
-// reg_t strAddress = argv[0];
+ bool isWidth(const char c) {
+ return strchr("0123456789*", c);
+ }
- SciString *string1 = s->_segMan->lookupString(argv[0]);
- //SciString *string1 = !argv[1].isNull() ? s->_segMan->lookupString(argv[1]) : s->_segMan->allocateString(&strAddress);
+ bool isLength(const char c) {
+ return strchr("hjlLtz", c);
+ }
- if (string1->getSize() < index1 + count)
- string1->setSize(index1 + count);
+ bool isType(const char c) {
+ return strchr("dsuxXaAceEfFgGinop", c);
+ }
- // Note: We're accessing from c_str() here because the
- // string's size ignores the trailing 0 and therefore
- // triggers an assert when doing string2[i + index2].
- for (uint16 i = 0; i < count; i++)
- string1->setValue(i + index1, string2[i + index2]);
+ bool isSignedType(const char type) {
+ // For whatever reason, %d ends up being treated as unsigned in SSCI
+ return type == 'i';
+ }
- return argv[0];
-}
+ bool isUnsignedType(const char type) {
+ return strchr("duxXoc", type);
+ }
-reg_t kStringCompare(EngineState *s, int argc, reg_t *argv) {
- Common::String string1 = argv[0].isNull() ? "" : s->_segMan->getString(argv[0]);
- Common::String string2 = argv[1].isNull() ? "" : s->_segMan->getString(argv[1]);
+ bool isStringType(const char type) {
+ return type == 's';
+ }
- if (argc == 3) // Strncmp
- return make_reg(0, strncmp(string1.c_str(), string2.c_str(), argv[2].toUint16()));
- else // Strcmp
- return make_reg(0, strcmp(string1.c_str(), string2.c_str()));
-}
+ Common::String readPlaceholder(const char *&in, reg_t arg) {
+ const char *const start = in;
-// was removed for SCI2.1 Late+
-reg_t kStringDup(EngineState *s, int argc, reg_t *argv) {
- reg_t stringHandle;
+ assert(*in == '%');
+ ++in;
- SciString *dupString = s->_segMan->allocateString(&stringHandle);
+ while (isFlag(*in)) {
+ ++in;
+ }
+ while (isWidth(*in)) {
+ ++in;
+ }
+ while (isPrecision(*in)) {
+ ++in;
+ }
+ while (isLength(*in)) {
+ ++in;
+ }
- if (argv[0].getSegment() == s->_segMan->getStringSegmentId()) {
- *dupString = *s->_segMan->lookupString(argv[0]);
- } else {
- dupString->fromString(s->_segMan->getString(argv[0]));
+ char format[64];
+ format[0] = '\0';
+ const char type = *in++;
+ Common::strlcpy(format, start, MIN<size_t>(64, in - start + 1));
+
+ if (isType(type)) {
+ if (isSignedType(type)) {
+ const int value = arg.toSint16();
+ return Common::String::format(format, value);
+ } else if (isUnsignedType(type)) {
+ const uint value = arg.toUint16();
+ return Common::String::format(format, value);
+ } else if (isStringType(type)) {
+ Common::String value;
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+ if (segMan->isObject(arg)) {
+ value = segMan->getString(readSelector(segMan, arg, SELECTOR(data)));
+ } else {
+ value = segMan->getString(arg);
+ }
+ return Common::String::format(format, value.c_str());
+ } else {
+ error("Unsupported format type %c", type);
+ }
+ } else {
+ return Common::String::format("%s", format);
+ }
}
-
- return stringHandle;
}
-// was removed for SCI2.1 Late+
-reg_t kStringGetData(EngineState *s, int argc, reg_t *argv) {
- if (!s->_segMan->isHeapObject(argv[0]))
- return argv[0];
+Common::String format(const Common::String &source, int argc, const reg_t *argv) {
+ Common::String out;
+ const char *in = source.c_str();
+ int argIndex = 0;
+ while (*in != '\0') {
+ if (*in == '%') {
+ if (in[1] == '%') {
+ in += 2;
+ out += "%";
+ continue;
+ }
- return readSelector(s->_segMan, argv[0], SELECTOR(data));
-}
+ if (argIndex < argc) {
+ out += readPlaceholder(in, argv[argIndex++]);
+ } else {
+ out += readPlaceholder(in, NULL_REG);
+ }
+ } else {
+ out += *in++;
+ }
+ }
-reg_t kStringLen(EngineState *s, int argc, reg_t *argv) {
- return make_reg(0, s->_segMan->strlen(argv[0]));
+ return out;
}
-reg_t kStringPrintf(EngineState *s, int argc, reg_t *argv) {
+reg_t kStringFormat(EngineState *s, int argc, reg_t *argv) {
reg_t stringHandle;
- s->_segMan->allocateString(&stringHandle);
-
- reg_t *adjustedArgs = new reg_t[argc + 1];
- adjustedArgs[0] = stringHandle;
- memcpy(&adjustedArgs[1], argv, argc * sizeof(reg_t));
-
- kFormat(s, argc + 1, adjustedArgs);
- delete[] adjustedArgs;
+ SciArray &target = *s->_segMan->allocateArray(kArrayTypeString, 0, &stringHandle);
+ reg_t source = argv[0];
+ // Str objects may be passed in place of direct references to string data
+ if (s->_segMan->isObject(argv[0])) {
+ source = readSelector(s->_segMan, argv[0], SELECTOR(data));
+ }
+ target.fromString(format(s->_segMan->getString(source), argc - 1, argv + 1));
return stringHandle;
}
-reg_t kStringPrintfBuf(EngineState *s, int argc, reg_t *argv) {
- return kFormat(s, argc, argv);
+reg_t kStringFormatAt(EngineState *s, int argc, reg_t *argv) {
+ SciArray &target = *s->_segMan->lookupArray(argv[0]);
+ reg_t source = argv[1];
+ // Str objects may be passed in place of direct references to string data
+ if (s->_segMan->isObject(argv[1])) {
+ source = readSelector(s->_segMan, argv[1], SELECTOR(data));
+ }
+ target.fromString(format(s->_segMan->getString(source), argc - 2, argv + 2));
+ return argv[0];
}
-reg_t kStringAtoi(EngineState *s, int argc, reg_t *argv) {
- Common::String string = s->_segMan->getString(argv[0]);
- return make_reg(0, (uint16)atoi(string.c_str()));
+reg_t kStringToInteger(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, (uint16)s->_segMan->getString(argv[0]).asUint64());
}
reg_t kStringTrim(EngineState *s, int argc, reg_t *argv) {
- Common::String string = s->_segMan->getString(argv[0]);
-
- string.trim();
- // TODO: Second parameter (bitfield, trim from left, right, center)
- warning("kStringTrim (%d)", argv[1].getOffset());
- s->_segMan->strcpy(argv[0], string.c_str());
- return NULL_REG;
+ SciArray &array = *s->_segMan->lookupArray(argv[0]);
+ const int8 flags = argv[1].toSint16();
+ const char showChar = argc > 2 ? argv[2].toSint16() : '\0';
+ array.trim(flags, showChar);
+ return s->r_acc;
}
-reg_t kStringUpper(EngineState *s, int argc, reg_t *argv) {
+reg_t kStringToUpperCase(EngineState *s, int argc, reg_t *argv) {
Common::String string = s->_segMan->getString(argv[0]);
-
string.toUppercase();
s->_segMan->strcpy(argv[0], string.c_str());
- return NULL_REG;
+ return argv[0];
}
-reg_t kStringLower(EngineState *s, int argc, reg_t *argv) {
+reg_t kStringToLowerCase(EngineState *s, int argc, reg_t *argv) {
Common::String string = s->_segMan->getString(argv[0]);
-
string.toLowercase();
s->_segMan->strcpy(argv[0], string.c_str());
- return NULL_REG;
-}
-
-// Possibly kStringTranslate?
-reg_t kStringTrn(EngineState *s, int argc, reg_t *argv) {
- warning("kStringTrn (argc = %d)", argc);
- return NULL_REG;
+ return argv[0];
}
-// Possibly kStringTranslateExclude?
-reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv) {
- warning("kStringTrnExclude (argc = %d)", argc);
- return NULL_REG;
+reg_t kStringReplaceSubstring(EngineState *s, int argc, reg_t *argv) {
+ error("TODO: kStringReplaceSubstring not implemented");
+ return argv[3];
}
-reg_t kString(EngineState *s, int argc, reg_t *argv) {
- if (!s)
- return make_reg(0, getSciVersion());
- error("not supposed to call this");
+reg_t kStringReplaceSubstringEx(EngineState *s, int argc, reg_t *argv) {
+ error("TODO: kStringReplaceSubstringEx not implemented");
+ return argv[3];
}
-
#endif
} // End of namespace Sci
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index 8db0c542eb..11378d7647 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -40,7 +40,8 @@
#include "video/qt_decoder.h"
#include "sci/video/seq_decoder.h"
#ifdef ENABLE_SCI32
-#include "video/coktel_decoder.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/video32.h"
#include "sci/video/robot_decoder.h"
#endif
@@ -60,40 +61,21 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) {
uint16 screenWidth = g_sci->_gfxScreen->getDisplayWidth();
uint16 screenHeight = g_sci->_gfxScreen->getDisplayHeight();
- videoState.fileName.toLowercase();
- bool isVMD = videoState.fileName.hasSuffix(".vmd");
-
- if (screenWidth == 640 && width <= 320 && height <= 240 && ((videoState.flags & kDoubled) || !isVMD)) {
+ if (screenWidth == 640 && width <= 320 && height <= 240) {
width *= 2;
height *= 2;
pitch *= 2;
scaleBuffer = new byte[width * height * bytesPerPixel];
}
- uint16 x, y;
-
- // Sanity check...
- if (videoState.x > 0 && videoState.y > 0 && isVMD) {
- x = videoState.x;
- y = videoState.y;
-
- if (x + width > screenWidth || y + height > screenHeight) {
- // Happens in the Lighthouse demo
- warning("VMD video won't fit on screen, centering it instead");
- x = (screenWidth - width) / 2;
- y = (screenHeight - height) / 2;
- }
- } else {
- x = (screenWidth - width) / 2;
- y = (screenHeight - height) / 2;
- }
+ uint16 x = (screenWidth - width) / 2;
+ uint16 y = (screenHeight - height) / 2;
bool skipVideo = false;
- EngineState *s = g_sci->getEngineState();
if (videoDecoder->hasDirtyPalette()) {
- const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3;
- g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart);
+ const byte *palette = videoDecoder->getPalette();
+ g_system->getPaletteManager()->setPalette(palette, 0, 255);
}
while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) {
@@ -102,7 +84,7 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) {
if (frame) {
if (scaleBuffer) {
- // TODO: Probably should do aspect ratio correction in e.g. GK1 Windows
+ // TODO: Probably should do aspect ratio correction in KQ6
g_sci->_gfxScreen->scale2x((const byte *)frame->getPixels(), scaleBuffer, videoDecoder->getWidth(), videoDecoder->getHeight(), bytesPerPixel);
g_system->copyRectToScreen(scaleBuffer, pitch, x, y, width, height);
} else {
@@ -110,8 +92,8 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) {
}
if (videoDecoder->hasDirtyPalette()) {
- const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3;
- g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart);
+ const byte *palette = videoDecoder->getPalette();
+ g_system->getPaletteManager()->setPalette(palette, 0, 255);
}
g_system->updateScreen();
@@ -180,20 +162,9 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
// TODO: This appears to be some sort of subop. case 0 contains the string
// for the video, so we'll just play it from there for now.
-#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
- // SCI2.1 always has argv[0] as 1, the rest of the arguments seem to
- // follow SCI1.1/2.
- if (argv[0].toUint16() != 1)
- error("SCI2.1 kShowMovie argv[0] not 1");
- argv++;
- argc--;
- }
-#endif
switch (argv[0].toUint16()) {
case 0: {
Common::String filename = s->_segMan->getString(argv[1]);
- videoDecoder = new Video::AVIDecoder();
if (filename.equalsIgnoreCase("gk2a.avi")) {
// HACK: Switch to 16bpp graphics for Indeo3.
@@ -208,6 +179,8 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
}
}
+ videoDecoder = new Video::AVIDecoder();
+
if (!videoDecoder->loadFile(filename.c_str())) {
warning("Failed to open movie file %s", filename.c_str());
delete videoDecoder;
@@ -229,7 +202,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
// We also won't be copying the screen to the SCI screen...
if (g_system->getScreenFormat().bytesPerPixel != 1)
initGraphics(screenWidth, screenHeight, screenWidth > 320);
- else {
+ else if (getSciVersion() < SCI_VERSION_2) {
g_sci->_gfxScreen->kernelSyncWithFramebuffer();
g_sci->_gfxPalette16->kernelSyncScreenPalette();
}
@@ -242,160 +215,255 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
}
#ifdef ENABLE_SCI32
+reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv) {
+ Common::String fileName = s->_segMan->getString(argv[0]);
+ const int16 numTicks = argv[1].toSint16();
+ const int16 x = argc > 3 ? argv[2].toSint16() : 0;
+ const int16 y = argc > 3 ? argv[3].toSint16() : 0;
+
+ g_sci->_video32->getSEQPlayer().play(fileName, numTicks, x, y);
+
+ return s->r_acc;
+}
reg_t kRobot(EngineState *s, int argc, reg_t *argv) {
- int16 subop = argv[0].toUint16();
-
- switch (subop) {
- case 0: { // init
- int id = argv[1].toUint16();
- reg_t obj = argv[2];
- int16 flag = argv[3].toSint16();
- int16 x = argv[4].toUint16();
- int16 y = argv[5].toUint16();
- warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y);
- g_sci->_robotDecoder->load(id);
- g_sci->_robotDecoder->start();
- g_sci->_robotDecoder->setPos(x, y);
- }
- break;
- case 1: // LSL6 hires (startup)
- // TODO
- return NULL_REG; // an integer is expected
- case 4: { // start - we don't really have a use for this one
- //int id = argv[1].toUint16();
- //warning("kRobot(start), id %d", id);
- }
- break;
- case 7: // unknown, called e.g. by Phantasmagoria
- warning("kRobot(%d)", subop);
- break;
- case 8: // sync
- //if (true) { // debug: automatically skip all robot videos
- if (g_sci->_robotDecoder->endOfVideo()) {
- g_sci->_robotDecoder->close();
- // Signal the engine scripts that the video is done
- writeSelector(s->_segMan, argv[1], SELECTOR(signal), SIGNAL_REG);
- } else {
- writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG);
- }
- break;
- default:
- warning("kRobot(%d)", subop);
- break;
- }
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv) {
+ const GuiResourceId robotId = argv[0].toUint16();
+ const reg_t plane = argv[1];
+ const int16 priority = argv[2].toSint16();
+ const int16 x = argv[3].toSint16();
+ const int16 y = argv[4].toSint16();
+ const int16 scale = argc > 5 ? argv[5].toSint16() : 128;
+ g_sci->_video32->getRobotPlayer().open(robotId, plane, priority, x, y, scale);
+ return make_reg(0, 0);
+}
+reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv) {
+ const uint16 frameNo = argv[0].toUint16();
+ const uint16 newX = argc > 1 ? argv[1].toUint16() : (uint16)RobotDecoder::kUnspecified;
+ const uint16 newY = argc > 1 ? argv[2].toUint16() : (uint16)RobotDecoder::kUnspecified;
+ g_sci->_video32->getRobotPlayer().showFrame(frameNo, newX, newY, RobotDecoder::kUnspecified);
return s->r_acc;
}
-reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) {
- uint16 operation = argv[0].toUint16();
- Video::VideoDecoder *videoDecoder = 0;
- bool reshowCursor = g_sci->_gfxCursor->isVisible();
- Common::String warningMsg;
+reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv) {
+ Common::Rect frameRect;
+ const uint16 numFramesTotal = g_sci->_video32->getRobotPlayer().getFrameSize(frameRect);
- switch (operation) {
- case 0: // init
- s->_videoState.reset();
- s->_videoState.fileName = s->_segMan->derefString(argv[1]);
+ SciArray *outRect = s->_segMan->lookupArray(argv[0]);
+ reg_t values[4] = {
+ make_reg(0, frameRect.left),
+ make_reg(0, frameRect.top),
+ make_reg(0, frameRect.right - 1),
+ make_reg(0, frameRect.bottom - 1) };
+ outRect->setElements(0, 4, values);
- if (argc > 2 && argv[2] != NULL_REG)
- warning("kPlayVMD: third parameter isn't 0 (it's %04x:%04x - %s)", PRINT_REG(argv[2]), s->_segMan->getObjectName(argv[2]));
- break;
- case 1:
- {
- // Set VMD parameters. Called with a maximum of 6 parameters:
- //
- // x, y, flags, gammaBoost, gammaFirst, gammaLast
- //
- // gammaBoost boosts palette colors in the range gammaFirst to
- // gammaLast, but only if bit 4 in flags is set. Percent value such that
- // 0% = no amplification These three parameters are optional if bit 4 is
- // clear. Also note that the x, y parameters play subtle games if used
- // with subfx 21. The subtleness has to do with creation of temporary
- // planes and positioning relative to such planes.
-
- uint16 flags = argv[3].getOffset();
- Common::String flagspec;
-
- if (argc > 3) {
- if (flags & kDoubled)
- flagspec += "doubled ";
- if (flags & kDropFrames)
- flagspec += "dropframes ";
- if (flags & kBlackLines)
- flagspec += "blacklines ";
- if (flags & kUnkBit3)
- flagspec += "bit3 ";
- if (flags & kGammaBoost)
- flagspec += "gammaboost ";
- if (flags & kHoldBlackFrame)
- flagspec += "holdblack ";
- if (flags & kHoldLastFrame)
- flagspec += "holdlast ";
- if (flags & kUnkBit7)
- flagspec += "bit7 ";
- if (flags & kStretch)
- flagspec += "stretch";
-
- warning("VMDFlags: %s", flagspec.c_str());
-
- s->_videoState.flags = flags;
- }
+ return make_reg(0, numFramesTotal);
+}
- warning("x, y: %d, %d", argv[1].getOffset(), argv[2].getOffset());
- s->_videoState.x = argv[1].getOffset();
- s->_videoState.y = argv[2].getOffset();
+reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getRobotPlayer().resume();
+ return s->r_acc;
+}
- if (argc > 4 && flags & 16)
- warning("gammaBoost: %d%% between palette entries %d and %d", argv[4].getOffset(), argv[5].getOffset(), argv[6].getOffset());
- break;
+reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() == RobotDecoder::kRobotStatusEnd);
+}
+
+reg_t kRobotGetIsPlaying(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() == RobotDecoder::kRobotStatusPlaying);
+}
+
+reg_t kRobotClose(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getRobotPlayer().close();
+ return s->r_acc;
+}
+
+reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv) {
+ writeSelectorValue(s->_segMan, argv[0], SELECTOR(signal), g_sci->_video32->getRobotPlayer().getCue());
+ return s->r_acc;
+}
+
+reg_t kRobotPause(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getRobotPlayer().pause();
+ return s->r_acc;
+}
+
+reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getRobotPlayer().getFrameNo());
+}
+
+reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getRobotPlayer().setPriority(argv[0].toSint16());
+ return s->r_acc;
+}
+
+reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv) {
+ // SCI2.1 adds a movie ID to the call, but the movie ID is broken,
+ // so just ignore it
+ if (getSciVersion() > SCI_VERSION_2) {
+ ++argv;
+ --argc;
}
- case 6: // Play
- videoDecoder = new Video::AdvancedVMDDecoder();
- if (s->_videoState.fileName.empty()) {
- // Happens in Lighthouse
- warning("kPlayVMD: Empty filename passed");
- return s->r_acc;
- }
+ const Common::String fileName = s->_segMan->getString(argv[0]);
+ return make_reg(0, g_sci->_video32->getAVIPlayer().open(fileName));
+}
- if (!videoDecoder->loadFile(s->_videoState.fileName)) {
- warning("Could not open VMD %s", s->_videoState.fileName.c_str());
- break;
- }
+reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv) {
+ // SCI2.1 adds a movie ID to the call, but the movie ID is broken,
+ // so just ignore it
+ if (getSciVersion() > SCI_VERSION_2) {
+ ++argv;
+ --argc;
+ }
- if (reshowCursor)
- g_sci->_gfxCursor->kernelHide();
+ const int16 x = argv[0].toSint16();
+ const int16 y = argv[1].toSint16();
+ const int16 width = argc > 3 ? argv[2].toSint16() : 0;
+ const int16 height = argc > 3 ? argv[3].toSint16() : 0;
+ return make_reg(0, g_sci->_video32->getAVIPlayer().init1x(x, y, width, height));
+}
- playVideo(videoDecoder, s->_videoState);
+reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv) {
+ if (getSciVersion() == SCI_VERSION_2) {
+ AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)argv[0].toUint16();
+ return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags));
+ } else {
+ // argv[0] is a broken movie ID
+ const int16 from = argc > 2 ? argv[1].toSint16() : 0;
+ const int16 to = argc > 2 ? argv[2].toSint16() : 0;
+ const int16 showStyle = argc > 3 ? argv[3].toSint16() : 0;
+ const bool cue = argc > 4 ? (bool)argv[4].toSint16() : false;
+ return make_reg(0, g_sci->_video32->getAVIPlayer().play(from, to, showStyle, cue));
+ }
+}
- if (reshowCursor)
- g_sci->_gfxCursor->kernelShow();
- break;
- case 23: // set video palette range
- s->_vmdPalStart = argv[1].toUint16();
- s->_vmdPalEnd = argv[2].toUint16();
- break;
- case 14:
- // Takes an additional integer parameter (e.g. 3)
- case 16:
- // Takes an additional parameter, usually 0
- case 21:
- // Looks to be setting the video size and position. Called with 4 extra integer
- // parameters (e.g. 86, 41, 235, 106)
- default:
- warningMsg = Common::String::format("PlayVMD - unsupported subop %d. Params: %d (", operation, argc);
+reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getAVIPlayer().close());
+}
- for (int i = 0; i < argc; i++) {
- warningMsg += Common::String::format("%04x:%04x", PRINT_REG(argv[i]));
- warningMsg += (i == argc - 1 ? ")" : ", ");
- }
+reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getAVIPlayer().getDuration());
+}
- warning("%s", warningMsg.c_str());
- break;
+reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv) {
+ // SCI2.1 adds a movie ID to the call, but the movie ID is broken,
+ // so just ignore it
+ if (getSciVersion() > SCI_VERSION_2) {
+ ++argv;
+ --argc;
}
+ const uint16 frameNo = argv[0].toUint16();
+ return make_reg(0, g_sci->_video32->getAVIPlayer().cue(frameNo));
+}
+
+reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv) {
+ const int defaultFlags =
+ AVIPlayer::kEventFlagEnd |
+ AVIPlayer::kEventFlagEscapeKey;
+
+ // argv[0] is the movie number, which is not used by this method
+ const AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)(argc > 1 ? argv[1].toUint16() : defaultFlags);
+
+ return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags));
+}
+
+reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv) {
+ // argv[0] is a broken movie ID
+ const int16 x = argv[1].toSint16();
+ const int16 y = argv[2].toSint16();
+ return make_reg(0, g_sci->_video32->getAVIPlayer().init2x(x, y));
+}
+
+reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kPlayVMDOpen(EngineState *s, int argc, reg_t *argv) {
+ const Common::String fileName = s->_segMan->getString(argv[0]);
+ // argv[1] is an optional cache size argument which we do not use
+ // const uint16 cacheSize = argc > 1 ? CLIP<int16>(argv[1].toSint16(), 16, 1024) : 0;
+ const VMDPlayer::OpenFlags flags = argc > 2 ? (VMDPlayer::OpenFlags)argv[2].toUint16() : VMDPlayer::kOpenFlagNone;
+
+ return make_reg(0, g_sci->_video32->getVMDPlayer().open(fileName, flags));
+}
+
+reg_t kPlayVMDInit(EngineState *s, int argc, reg_t *argv) {
+ const int16 x = argv[0].toSint16();
+ const int16 y = argv[1].toSint16();
+ const VMDPlayer::PlayFlags flags = argc > 2 ? (VMDPlayer::PlayFlags)argv[2].toUint16() : VMDPlayer::kPlayFlagNone;
+ int16 boostPercent;
+ int16 boostStartColor;
+ int16 boostEndColor;
+ if (argc > 5 && (flags & VMDPlayer::kPlayFlagBoost)) {
+ boostPercent = argv[3].toSint16();
+ boostStartColor = argv[4].toSint16();
+ boostEndColor = argv[5].toSint16();
+ } else {
+ boostPercent = 0;
+ boostStartColor = -1;
+ boostEndColor = -1;
+ }
+
+ g_sci->_video32->getVMDPlayer().init(x, y, flags, boostPercent, boostStartColor, boostEndColor);
+
+ return make_reg(0, 0);
+}
+
+reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getVMDPlayer().close());
+}
+
+reg_t kPlayVMDIgnorePalettes(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getVMDPlayer().ignorePalettes();
+ return s->r_acc;
+}
+
+reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getVMDPlayer().getStatus());
+}
+
+reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv) {
+ const VMDPlayer::EventFlags flags = (VMDPlayer::EventFlags)argv[0].toUint16();
+ const int16 lastFrameNo = argc > 1 ? argv[1].toSint16() : -1;
+ const int16 yieldInterval = argc > 2 ? argv[2].toSint16() : -1;
+ return make_reg(0, g_sci->_video32->getVMDPlayer().kernelPlayUntilEvent(flags, lastFrameNo, yieldInterval));
+}
+
+reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getVMDPlayer().setShowCursor((bool)argv[0].toUint16());
+ return s->r_acc;
+}
+
+reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv) {
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ Common::Rect blackoutArea;
+ blackoutArea.left = MAX<int16>(0, argv[0].toSint16());
+ blackoutArea.top = MAX<int16>(0, argv[1].toSint16());
+ blackoutArea.right = MIN<int16>(scriptWidth, argv[2].toSint16() + 1);
+ blackoutArea.bottom = MIN<int16>(scriptHeight, argv[3].toSint16() + 1);
+ g_sci->_video32->getVMDPlayer().setBlackoutArea(blackoutArea);
+ return s->r_acc;
+}
+
+reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getVMDPlayer().restrictPalette(argv[0].toUint16(), argv[1].toUint16());
return s->r_acc;
}
diff --git a/engines/sci/engine/message.cpp b/engines/sci/engine/message.cpp
index 5300b72b71..26ab9b47a5 100644
--- a/engines/sci/engine/message.cpp
+++ b/engines/sci/engine/message.cpp
@@ -333,12 +333,14 @@ void MessageState::popCursorStack() {
error("Message: attempt to pop from empty stack");
}
-int MessageState::hexDigitToInt(char h) {
+int MessageState::hexDigitToWrongInt(char h) {
+ // Hex digits above 9 are incorrectly interpreted by SSCI as 11-16 instead
+ // of 10-15 because of a never-fixed typo
if ((h >= 'A') && (h <= 'F'))
- return h - 'A' + 10;
+ return h - 'A' + 11;
if ((h >= 'a') && (h <= 'f'))
- return h - 'a' + 10;
+ return h - 'a' + 11;
if ((h >= '0') && (h <= '9'))
return h - '0';
@@ -355,8 +357,8 @@ bool MessageState::stringHex(Common::String &outStr, const Common::String &inStr
if (index + 2 >= inStr.size())
return false;
- int digit1 = hexDigitToInt(inStr[index + 1]);
- int digit2 = hexDigitToInt(inStr[index + 2]);
+ int digit1 = hexDigitToWrongInt(inStr[index + 1]);
+ int digit2 = hexDigitToWrongInt(inStr[index + 2]);
// Check for hex
if ((digit1 == -1) || (digit2 == -1))
@@ -439,21 +441,8 @@ Common::String MessageState::processString(const char *s) {
void MessageState::outputString(reg_t buf, const Common::String &str) {
#ifdef ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2) {
- if (_segMan->getSegmentType(buf.getSegment()) == SEG_TYPE_STRING) {
- SciString *sciString = _segMan->lookupString(buf);
- sciString->setSize(str.size() + 1);
- for (uint32 i = 0; i < str.size(); i++)
- sciString->setValue(i, str.c_str()[i]);
- sciString->setValue(str.size(), 0);
- } else if (_segMan->getSegmentType(buf.getSegment()) == SEG_TYPE_ARRAY) {
- // Happens in the intro of LSL6, we are asked to write the string
- // into an array
- SciArray<reg_t> *sciString = _segMan->lookupArray(buf);
- sciString->setSize(str.size() + 1);
- for (uint32 i = 0; i < str.size(); i++)
- sciString->setValue(i, make_reg(0, str.c_str()[i]));
- sciString->setValue(str.size(), NULL_REG);
- }
+ SciArray *sciString = _segMan->lookupArray(buf);
+ sciString->fromString(str);
} else {
#endif
SegmentRef buffer_r = _segMan->dereference(buf);
diff --git a/engines/sci/engine/message.h b/engines/sci/engine/message.h
index ff76534d2d..5847e4767e 100644
--- a/engines/sci/engine/message.h
+++ b/engines/sci/engine/message.h
@@ -73,7 +73,7 @@ private:
bool getRecord(CursorStack &stack, bool recurse, MessageRecord &record);
void outputString(reg_t buf, const Common::String &str);
Common::String processString(const char *s);
- int hexDigitToInt(char h);
+ int hexDigitToWrongInt(char h);
bool stringHex(Common::String &outStr, const Common::String &inStr, uint &index);
bool stringLit(Common::String &outStr, const Common::String &inStr, uint &index);
bool stringStage(Common::String &outStr, const Common::String &inStr, uint &index);
diff --git a/engines/sci/engine/object.cpp b/engines/sci/engine/object.cpp
index 0626c084c1..0566d6955f 100644
--- a/engines/sci/engine/object.cpp
+++ b/engines/sci/engine/object.cpp
@@ -255,6 +255,8 @@ void Object::initSelectorsSci3(const byte *buf) {
if (g_sci->getKernel()->getSelectorNamesSize() % 32)
++groups;
+ _mustSetViewVisible.resize(groups);
+
methods = properties = 0;
// Selectors are divided into groups of 32, of which the first
@@ -270,7 +272,9 @@ void Object::initSelectorsSci3(const byte *buf) {
// This object actually has selectors belonging to this group
int typeMask = READ_SCI11ENDIAN_UINT32(seeker);
- for (int bit = 2; bit < 32; ++bit) {
+ _mustSetViewVisible[groupNr] = (typeMask & 1);
+
+ for (int bit = 2; bit < 32; ++bit) {
int value = READ_SCI11ENDIAN_UINT16(seeker + bit * 2);
if (typeMask & (1 << bit)) { // Property
++properties;
@@ -281,7 +285,8 @@ void Object::initSelectorsSci3(const byte *buf) {
}
}
- }
+ } else
+ _mustSetViewVisible[groupNr] = false;
}
_variables.resize(properties);
diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h
index cc9f5ebb52..74a908a810 100644
--- a/engines/sci/engine/object.h
+++ b/engines/sci/engine/object.h
@@ -47,10 +47,10 @@ enum infoSelectorFlags {
* When set, indicates to game scripts that a screen
* item can be updated.
*/
- kInfoFlagViewVisible = 0x0008, // TODO: "dirty" ?
+ kInfoFlagViewVisible = 0x0008,
/**
- * When set, the object has an associated screen item in
+ * When set, the VM object has an associated ScreenItem in
* the rendering tree.
*/
kInfoFlagViewInserted = 0x0010,
@@ -262,6 +262,8 @@ public:
bool initBaseObject(SegManager *segMan, reg_t addr, bool doInitSuperClass = true);
void syncBaseObject(const byte *ptr) { _baseObj = ptr; }
+ bool mustSetViewVisibleSci3(int selector) const { return _mustSetViewVisible[selector/32]; }
+
private:
void initSelectorsSci3(const byte *buf);
@@ -278,6 +280,7 @@ private:
reg_t _superClassPosSci3; /**< reg_t pointing to superclass for SCI3 */
reg_t _speciesSelectorSci3; /**< reg_t containing species "selector" for SCI3 */
reg_t _infoSelectorSci3; /**< reg_t containing info "selector" for SCI3 */
+ Common::Array<bool> _mustSetViewVisible; /** cached bit of info to make lookup fast, SCI3 only */
};
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 18cee3321f..720f6783ee 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -48,8 +48,10 @@
#include "sci/sound/music.h"
#ifdef ENABLE_SCI32
-#include "sci/graphics/palette32.h"
+#include "sci/graphics/cursor32.h"
#include "sci/graphics/frameout.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
#endif
namespace Sci {
@@ -60,24 +62,87 @@ namespace Sci {
#pragma mark -
-// Experimental hack: Use syncWithSerializer to sync. By default, this assume
-// the object to be synced is a subclass of Serializable and thus tries to invoke
-// the saveLoadWithSerializer() method. But it is possible to specialize this
-// template function to handle stuff that is not implementing that interface.
-template<typename T>
-void syncWithSerializer(Common::Serializer &s, T &obj) {
+// These are serialization functions for various objects.
+
+void syncWithSerializer(Common::Serializer &s, Common::Serializable &obj) {
+ obj.saveLoadWithSerializer(s);
+}
+
+// FIXME: Object could implement Serializable to make use of the function
+// above.
+void syncWithSerializer(Common::Serializer &s, Object &obj) {
obj.saveLoadWithSerializer(s);
}
+void syncWithSerializer(Common::Serializer &s, reg_t &obj) {
+ // Segment and offset are accessed directly here
+ s.syncAsUint16LE(obj._segment);
+ s.syncAsUint16LE(obj._offset);
+}
+
+void syncWithSerializer(Common::Serializer &s, synonym_t &obj) {
+ s.syncAsUint16LE(obj.replaceant);
+ s.syncAsUint16LE(obj.replacement);
+}
+
+void syncWithSerializer(Common::Serializer &s, Class &obj) {
+ s.syncAsSint32LE(obj.script);
+ syncWithSerializer(s, obj.reg);
+}
+
+void syncWithSerializer(Common::Serializer &s, List &obj) {
+ syncWithSerializer(s, obj.first);
+ syncWithSerializer(s, obj.last);
+}
+
+void syncWithSerializer(Common::Serializer &s, Node &obj) {
+ syncWithSerializer(s, obj.pred);
+ syncWithSerializer(s, obj.succ);
+ syncWithSerializer(s, obj.key);
+ syncWithSerializer(s, obj.value);
+}
+
+#pragma mark -
+
// By default, sync using syncWithSerializer, which in turn can easily be overloaded.
template<typename T>
struct DefaultSyncer : Common::BinaryFunction<Common::Serializer, T, void> {
- void operator()(Common::Serializer &s, T &obj) const {
- //obj.saveLoadWithSerializer(s);
+ void operator()(Common::Serializer &s, T &obj, int) const {
syncWithSerializer(s, obj);
}
};
+// Syncer for entries in a segment obj table
+template<typename T>
+struct SegmentObjTableEntrySyncer : Common::BinaryFunction<Common::Serializer, typename T::Entry &, void> {
+ void operator()(Common::Serializer &s, typename T::Entry &entry, int index) const {
+ s.syncAsSint32LE(entry.next_free);
+
+ bool hasData;
+ if (s.getVersion() >= 37) {
+ if (s.isSaving()) {
+ hasData = entry.data != nullptr;
+ }
+ s.syncAsByte(hasData);
+ } else {
+ hasData = (entry.next_free == index);
+ }
+
+ if (hasData) {
+ if (s.isLoading()) {
+ entry.data = new typename T::value_type;
+ }
+ syncWithSerializer(s, *entry.data);
+ } else if (s.isLoading()) {
+ if (s.getVersion() < 37) {
+ typename T::value_type dummy;
+ syncWithSerializer(s, dummy);
+ }
+ entry.data = nullptr;
+ }
+ }
+};
+
/**
* Sync a Common::Array using a Common::Serializer.
* When saving, this writes the length of the array, then syncs (writes) all entries.
@@ -102,9 +167,8 @@ struct ArraySyncer : Common::BinaryFunction<Common::Serializer, T, void> {
if (s.isLoading())
arr.resize(len);
- typename Common::Array<T>::iterator i;
- for (i = arr.begin(); i != arr.end(); ++i) {
- sync(s, *i);
+ for (uint i = 0; i < len; ++i) {
+ sync(s, arr[i], i);
}
}
};
@@ -116,18 +180,10 @@ void syncArray(Common::Serializer &s, Common::Array<T> &arr) {
sync(s, arr);
}
-
-template<>
-void syncWithSerializer(Common::Serializer &s, reg_t &obj) {
- // Segment and offset are accessed directly here
- s.syncAsUint16LE(obj._segment);
- s.syncAsUint16LE(obj._offset);
-}
-
-template<>
-void syncWithSerializer(Common::Serializer &s, synonym_t &obj) {
- s.syncAsUint16LE(obj.replaceant);
- s.syncAsUint16LE(obj.replacement);
+template<typename T, class Syncer>
+void syncArray(Common::Serializer &s, Common::Array<T> &arr) {
+ ArraySyncer<T, Syncer> sync;
+ sync(s, arr);
}
void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
@@ -176,9 +232,8 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
} else if (type == SEG_TYPE_ARRAY) {
// Set the correct segment for SCI32 arrays
_arraysSegId = i;
- } else if (type == SEG_TYPE_STRING) {
- // Set the correct segment for SCI32 strings
- _stringSegId = i;
+ } else if (s.getVersion() >= 36 && type == SEG_TYPE_BITMAP) {
+ _bitmapSegId = i;
#endif
}
@@ -247,12 +302,6 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
}
-template<>
-void syncWithSerializer(Common::Serializer &s, Class &obj) {
- s.syncAsSint32LE(obj.script);
- syncWithSerializer(s, obj.reg);
-}
-
static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj) {
s.syncString(obj.name);
s.syncVersion(CURRENT_SAVEGAME_VERSION);
@@ -281,6 +330,28 @@ static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj)
}
s.syncAsUint32LE(obj.playTime);
}
+
+ // Some games require additional metadata to display their restore screens
+ // correctly
+ if (s.getVersion() >= 39) {
+ if (s.isSaving()) {
+ const reg_t *globals = g_sci->getEngineState()->variables[VAR_GLOBAL];
+ if (g_sci->getGameId() == GID_SHIVERS) {
+ obj.lowScore = globals[kGlobalVarScore].toUint16();
+ obj.highScore = globals[kGlobalVarShivers1Score].toUint16();
+ obj.avatarId = 0;
+ } else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
+ obj.lowScore = obj.highScore = 0;
+ obj.avatarId = readSelectorValue(g_sci->getEngineState()->_segMan, globals[kGlobalVarEgo], SELECTOR(view));
+ } else {
+ obj.lowScore = obj.highScore = obj.avatarId = 0;
+ }
+ }
+
+ s.syncAsUint16LE(obj.lowScore);
+ s.syncAsUint16LE(obj.highScore);
+ s.syncAsByte(obj.avatarId);
+ }
}
void EngineState::saveLoadWithSerializer(Common::Serializer &s) {
@@ -310,8 +381,15 @@ void EngineState::saveLoadWithSerializer(Common::Serializer &s) {
_segMan->saveLoadWithSerializer(s);
g_sci->_soundCmd->syncPlayList(s);
- // NOTE: This will be GfxPalette32 for SCI32 engine games
- g_sci->_gfxPalette16->saveLoadWithSerializer(s);
+
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ g_sci->_gfxPalette32->saveLoadWithSerializer(s);
+ g_sci->_gfxRemap32->saveLoadWithSerializer(s);
+ g_sci->_gfxCursor32->saveLoadWithSerializer(s);
+ } else
+#endif
+ g_sci->_gfxPalette16->saveLoadWithSerializer(s);
}
void Vocabulary::saveLoadWithSerializer(Common::Serializer &s) {
@@ -331,102 +409,13 @@ void Object::saveLoadWithSerializer(Common::Serializer &s) {
syncArray<reg_t>(s, _variables);
}
-template<>
-void syncWithSerializer(Common::Serializer &s, SegmentObjTable<Clone>::Entry &obj) {
- s.syncAsSint32LE(obj.next_free);
-
- syncWithSerializer<Object>(s, obj);
-}
-
-template<>
-void syncWithSerializer(Common::Serializer &s, SegmentObjTable<List>::Entry &obj) {
- s.syncAsSint32LE(obj.next_free);
-
- syncWithSerializer(s, obj.first);
- syncWithSerializer(s, obj.last);
-}
-
-template<>
-void syncWithSerializer(Common::Serializer &s, SegmentObjTable<Node>::Entry &obj) {
- s.syncAsSint32LE(obj.next_free);
-
- syncWithSerializer(s, obj.pred);
- syncWithSerializer(s, obj.succ);
- syncWithSerializer(s, obj.key);
- syncWithSerializer(s, obj.value);
-}
-
-#ifdef ENABLE_SCI32
-template<>
-void syncWithSerializer(Common::Serializer &s, SegmentObjTable<SciArray<reg_t> >::Entry &obj) {
- s.syncAsSint32LE(obj.next_free);
-
- byte type = 0;
- uint32 size = 0;
-
- if (s.isSaving()) {
- type = (byte)obj.getType();
- size = obj.getSize();
- }
- s.syncAsByte(type);
- s.syncAsUint32LE(size);
- if (s.isLoading()) {
- obj.setType((int8)type);
-
- // HACK: Skip arrays that have a negative type
- if ((int8)type < 0)
- return;
-
- obj.setSize(size);
- }
-
- for (uint32 i = 0; i < size; i++) {
- reg_t value;
-
- if (s.isSaving())
- value = obj.getValue(i);
-
- syncWithSerializer(s, value);
-
- if (s.isLoading())
- obj.setValue(i, value);
- }
-}
-
-template<>
-void syncWithSerializer(Common::Serializer &s, SegmentObjTable<SciString>::Entry &obj) {
- s.syncAsSint32LE(obj.next_free);
-
- uint32 size = 0;
-
- if (s.isSaving()) {
- size = obj.getSize();
- s.syncAsUint32LE(size);
- } else {
- s.syncAsUint32LE(size);
- obj.setSize(size);
- }
-
- for (uint32 i = 0; i < size; i++) {
- char value = 0;
-
- if (s.isSaving())
- value = obj.getValue(i);
-
- s.syncAsByte(value);
-
- if (s.isLoading())
- obj.setValue(i, value);
- }
-}
-#endif
template<typename T>
void sync_Table(Common::Serializer &s, T &obj) {
s.syncAsSint32LE(obj.first_free);
s.syncAsSint32LE(obj.entries_used);
- syncArray<typename T::Entry>(s, obj._table);
+ syncArray<typename T::Entry, SegmentObjTableEntrySyncer<T> >(s, obj._table);
}
void CloneTable::saveLoadWithSerializer(Common::Serializer &s) {
@@ -643,28 +632,44 @@ void SoundCommandParser::syncPlayList(Common::Serializer &s) {
}
void SoundCommandParser::reconstructPlayList() {
- Common::StackLock lock(_music->_mutex);
+ _music->_mutex.lock();
// 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);
+ // Done with main playlist, so release lock
+ _music->_mutex.unlock();
+
for (MusicList::iterator i = songs.begin(); i != songs.end(); ++i) {
- initSoundResource(*i);
+ MusicEntry *entry = *i;
+ initSoundResource(entry);
+
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && entry->isSample) {
+ const reg_t &soundObj = entry->soundObj;
+
+ if ((int)readSelectorValue(_segMan, soundObj, SELECTOR(loop)) != -1 &&
+ readSelector(_segMan, soundObj, SELECTOR(handle)) != NULL_REG) {
- if ((*i)->status == kSoundPlaying) {
+ writeSelector(_segMan, soundObj, SELECTOR(handle), NULL_REG);
+ processPlaySound(soundObj, entry->playBed);
+ }
+ } else
+#endif
+ if (entry->status == kSoundPlaying) {
// 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);
+ writeSelectorValue(_segMan, entry->soundObj, SELECTOR(loop), entry->loop);
+ writeSelectorValue(_segMan, entry->soundObj, SELECTOR(priority), entry->priority);
if (_soundVersion >= SCI_VERSION_1_EARLY)
- writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(vol), (*i)->volume);
+ writeSelectorValue(_segMan, entry->soundObj, SELECTOR(vol), entry->volume);
- processPlaySound((*i)->soundObj, (*i)->playBed);
+ processPlaySound(entry->soundObj, entry->playBed);
}
}
}
@@ -677,11 +682,60 @@ void ArrayTable::saveLoadWithSerializer(Common::Serializer &ser) {
sync_Table<ArrayTable>(ser, *this);
}
-void StringTable::saveLoadWithSerializer(Common::Serializer &ser) {
- if (ser.getVersion() < 18)
+void SciArray::saveLoadWithSerializer(Common::Serializer &s) {
+ uint16 savedSize;
+
+ if (s.isSaving()) {
+ savedSize = _size;
+ }
+
+ s.syncAsByte(_type);
+ s.syncAsByte(_elementSize);
+ s.syncAsUint16LE(savedSize);
+
+ if (s.isLoading()) {
+ resize(savedSize);
+ }
+
+ switch (_type) {
+ case kArrayTypeInt16:
+ case kArrayTypeID:
+ for (int i = 0; i < savedSize; ++i) {
+ syncWithSerializer(s, ((reg_t *)_data)[i]);
+ }
+ break;
+ case kArrayTypeByte:
+ case kArrayTypeString:
+ s.syncBytes((byte *)_data, savedSize);
+ break;
+ default:
+ error("Attempt to sync invalid SciArray type %d", _type);
+ }
+}
+
+void BitmapTable::saveLoadWithSerializer(Common::Serializer &ser) {
+ if (ser.getVersion() < 36) {
+ return;
+ }
+
+ sync_Table(ser, *this);
+}
+
+void SciBitmap::saveLoadWithSerializer(Common::Serializer &s) {
+ if (s.getVersion() < 36) {
return;
+ }
+
+ s.syncAsByte(_gc);
+ s.syncAsUint32LE(_dataSize);
+ if (s.isLoading()) {
+ _data = (byte *)malloc(_dataSize);
+ }
+ s.syncBytes(_data, _dataSize);
- sync_Table<StringTable>(ser, *this);
+ if (s.isLoading()) {
+ _buffer = Buffer(getWidth(), getHeight(), getPixels());
+ }
}
#endif
@@ -729,7 +783,7 @@ void GfxPalette::saveLoadWithSerializer(Common::Serializer &s) {
}
#ifdef ENABLE_SCI32
-void saveLoadPalette32(Common::Serializer &s, Palette *const palette) {
+static void saveLoadPalette32(Common::Serializer &s, Palette *const palette) {
s.syncAsUint32LE(palette->timestamp);
for (int i = 0; i < ARRAYSIZE(palette->colors); ++i) {
s.syncAsByte(palette->colors[i].used);
@@ -739,7 +793,7 @@ void saveLoadPalette32(Common::Serializer &s, Palette *const palette) {
}
}
-void saveLoadOptionalPalette32(Common::Serializer &s, Palette **const palette) {
+static void saveLoadOptionalPalette32(Common::Serializer &s, Palette **const palette) {
bool hasPalette;
if (s.isSaving()) {
hasPalette = (*palette != nullptr);
@@ -760,6 +814,16 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) {
if (s.isLoading()) {
++_version;
+
+ for (int i = 0; i < kNumCyclers; ++i) {
+ delete _cyclers[i];
+ _cyclers[i] = nullptr;
+ }
+
+ delete _varyTargetPalette;
+ _varyTargetPalette = nullptr;
+ delete _varyStartPalette;
+ _varyStartPalette = nullptr;
}
s.syncAsSint16LE(_varyDirection);
@@ -768,7 +832,7 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) {
s.syncAsSint16LE(_varyFromColor);
s.syncAsSint16LE(_varyToColor);
s.syncAsUint16LE(_varyNumTimesPaused);
- s.syncAsByte(_versionUpdated);
+ s.syncAsByte(_needsUpdate);
s.syncAsSint32LE(_varyTime);
s.syncAsUint32LE(_varyLastTick);
@@ -808,8 +872,62 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) {
s.syncAsUint16LE(cycler->numTimesPaused);
}
}
+}
+
+void GfxRemap32::saveLoadWithSerializer(Common::Serializer &s) {
+ if (s.getVersion() < 35) {
+ return;
+ }
+
+ s.syncAsByte(_numActiveRemaps);
+ s.syncAsByte(_blockedRangeStart);
+ s.syncAsSint16LE(_blockedRangeCount);
+
+ for (uint i = 0; i < _remaps.size(); ++i) {
+ SingleRemap &singleRemap = _remaps[i];
+ s.syncAsByte(singleRemap._type);
+ if (s.isLoading() && singleRemap._type != kRemapNone) {
+ singleRemap.reset();
+ }
+ s.syncAsByte(singleRemap._from);
+ s.syncAsByte(singleRemap._to);
+ s.syncAsByte(singleRemap._delta);
+ s.syncAsByte(singleRemap._percent);
+ s.syncAsByte(singleRemap._gray);
+ }
- // TODO: _clutTable
+ if (s.isLoading()) {
+ _needsUpdate = true;
+ }
+}
+
+void GfxCursor32::saveLoadWithSerializer(Common::Serializer &s) {
+ if (s.getVersion() < 38) {
+ return;
+ }
+
+ int32 hideCount;
+ if (s.isSaving()) {
+ hideCount = _hideCount;
+ }
+ s.syncAsSint32LE(hideCount);
+ s.syncAsSint16LE(_restrictedArea.left);
+ s.syncAsSint16LE(_restrictedArea.top);
+ s.syncAsSint16LE(_restrictedArea.right);
+ s.syncAsSint16LE(_restrictedArea.bottom);
+ s.syncAsUint16LE(_cursorInfo.resourceId);
+ s.syncAsUint16LE(_cursorInfo.loopNo);
+ s.syncAsUint16LE(_cursorInfo.celNo);
+
+ if (s.isLoading()) {
+ hide();
+ setView(_cursorInfo.resourceId, _cursorInfo.loopNo, _cursorInfo.celNo);
+ if (!hideCount) {
+ show();
+ } else {
+ _hideCount = hideCount;
+ }
+ }
}
#endif
@@ -903,7 +1021,7 @@ void SegManager::reconstructClones() {
if (!isUsed)
continue;
- CloneTable::Entry &seeker = ct->_table[j];
+ CloneTable::value_type &seeker = ct->at(j);
const Object *baseObj = getObject(seeker.getSpeciesSelector());
seeker.cloneFromObject(baseObj);
if (!baseObj) {
@@ -935,10 +1053,11 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin
meta.gameObjectOffset = g_sci->getGameObject().getOffset();
// Checking here again
- if (s->executionStackBase) {
- warning("Cannot save from below kernel function");
- return false;
- }
+// TODO: This breaks Torin autosave, is there actually any reason for it?
+// if (s->executionStackBase) {
+// warning("Cannot save from below kernel function");
+// return false;
+// }
Common::Serializer ser(0, fh);
sync_SavegameMetadata(ser, meta);
@@ -1012,6 +1131,26 @@ void gamestate_afterRestoreFixUp(EngineState *s, int savegameId) {
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;
+ case GID_KQ6:
+ if (g_sci->isCD()) {
+ // WORKAROUND:
+ // For the CD version of King's Quest 6, set global depending on current hires/lowres state
+ // The game sets a global at the start depending on it and some things check that global
+ // instead of checking platform like for example the game action menu.
+ // This never happened in the original interpreter, because the original DOS interpreter
+ // was only capable of lowres graphics and the original Windows 3.11 interpreter was only capable
+ // of hires graphics. Saved games were not compatible between those two.
+ // Which means saving during lowres mode, then going into hires mode and restoring that saved game,
+ // will result in some graphics being incorrect (lowres).
+ // That's why we are setting the global after restoring a saved game depending on hires/lowres state.
+ // The CD demo of KQ6 does the same and uses the exact same global.
+ if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) {
+ s->variables[VAR_GLOBAL][0xA9].setOffset(1);
+ } else {
+ s->variables[VAR_GLOBAL][0xA9].setOffset(0);
+ }
+ }
+ break;
case GID_PQ2:
// HACK: Same as above - enable the save game menu option when loading in PQ2 (bug #6875).
// It gets disabled in the game's death screen.
@@ -1081,8 +1220,13 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
// We MUST NOT delete all planes/screen items. At least Space Quest 6 has a few in memory like for example
// the options plane, which are not re-added and are in memory all the time right from the start of the
// game. Sierra SCI32 did not clear planes, only scripts cleared the ones inside planes::elements.
- if (getSciVersion() >= SCI_VERSION_2)
- g_sci->_gfxFrameout->syncWithScripts(false);
+ if (getSciVersion() >= SCI_VERSION_2) {
+ if (!s->_delayedRestoreFromLauncher) {
+ // Only do it, when we are restoring regulary and not from launcher
+ // As it could result in option planes etc. on the screen (happens in gk1)
+ g_sci->_gfxFrameout->syncWithScripts(false);
+ }
+ }
#endif
s->reset(true);
@@ -1131,6 +1275,8 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
// signal restored game to game scripts
s->gameIsRestarting = GAMEISRESTARTING_RESTORE;
+
+ s->_delayedRestoreFromLauncher = false;
}
bool get_savegame_metadata(Common::SeekableReadStream *stream, SavegameMetadata *meta) {
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index 459e992e24..873394aebb 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -37,6 +37,11 @@ struct EngineState;
*
* Version - new/changed feature
* =============================
+ * 39 - Accurate SCI32 arrays/strings, score metadata, avatar metadata
+ * 38 - SCI32 cursor
+ * 37 - Segment entry data changed to pointers
+ * 36 - SCI32 bitmap segment
+ * 35 - SCI32 remap
* 34 - SCI32 palettes, and store play time in ticks
* 33 - new overridePriority flag in MusicEntry
* 32 - new playBed flag in MusicEntry
@@ -59,7 +64,7 @@ struct EngineState;
*/
enum {
- CURRENT_SAVEGAME_VERSION = 34,
+ CURRENT_SAVEGAME_VERSION = 39,
MINIMUM_SAVEGAME_VERSION = 14
};
@@ -73,6 +78,13 @@ struct SavegameMetadata {
uint32 playTime;
uint16 gameObjectOffset;
uint16 script0Size;
+
+ // Used by Shivers 1
+ uint16 lowScore;
+ uint16 highScore;
+
+ // Used by MGDX
+ uint8 avatarId;
};
/**
diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp
index 26a7ff5718..8a973bd217 100644
--- a/engines/sci/engine/script.cpp
+++ b/engines/sci/engine/script.cpp
@@ -72,6 +72,11 @@ void Script::freeScript() {
_offsetLookupSaidCount = 0;
}
+enum {
+ kSci11NumExportsOffset = 6,
+ kSci11ExportTableOffset = 8
+};
+
void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptPatcher) {
freeScript();
@@ -172,10 +177,11 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP
_localsCount = (READ_LE_UINT16(_buf + _localsOffset - 2) - 4) >> 1; // half block size
}
} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
- if (READ_LE_UINT16(_buf + 1 + 5) > 0) { // does the script have an export table?
- _exportTable = (const uint16 *)(_buf + 1 + 5 + 2);
- _numExports = READ_SCI11ENDIAN_UINT16(_exportTable - 1);
+ _numExports = READ_SCI11ENDIAN_UINT16(_buf + kSci11NumExportsOffset);
+ if (_numExports) {
+ _exportTable = (const uint16 *)(_buf + kSci11ExportTableOffset);
}
+
_localsOffset = _scriptSize + 4;
_localsCount = READ_SCI11ENDIAN_UINT16(_buf + _localsOffset - 2);
} else if (getSciVersion() == SCI_VERSION_3) {
@@ -234,6 +240,7 @@ void Script::identifyOffsets() {
_offsetLookupObjectCount = 0;
_offsetLookupStringCount = 0;
_offsetLookupSaidCount = 0;
+ _codeOffset = 0;
if (getSciVersion() < SCI_VERSION_1_1) {
// SCI0 + SCI1
@@ -392,6 +399,16 @@ void Script::identifyOffsets() {
scriptDataPtr = _heapStart;
scriptDataLeft = _heapSize;
+ enum {
+ kExportSize = 2,
+ kPropertySize = 2,
+ kNumMethodsSize = 2,
+ kPropDictEntrySize = 2,
+ kMethDictEntrySize = 4
+ };
+
+ const byte *hunkPtr = _buf + kSci11ExportTableOffset + _numExports * kExportSize;
+
if (scriptDataLeft < 4)
error("Script::identifyOffsets(): unexpected end of script in script %d", _nr);
@@ -431,11 +448,22 @@ void Script::identifyOffsets() {
if (scriptDataLeft < 2)
error("Script::identifyOffsets(): unexpected end of script in script %d", _nr);
- blockSize = READ_SCI11ENDIAN_UINT16(scriptDataPtr) * 2;
+ const uint16 numProperties = READ_SCI11ENDIAN_UINT16(scriptDataPtr);
+ blockSize = numProperties * kPropertySize;
if (blockSize < 4)
error("Script::identifyOffsets(): invalid block size in script %d", _nr);
scriptDataPtr += 2;
scriptDataLeft -= 2;
+
+ const uint16 scriptNum = READ_SCI11ENDIAN_UINT16(scriptDataPtr + 6);
+
+ if (scriptNum != 0xFFFF) {
+ hunkPtr += numProperties * kPropDictEntrySize;
+ }
+
+ const uint16 numMethods = READ_SCI11ENDIAN_UINT16(hunkPtr);
+ hunkPtr += kNumMethodsSize + numMethods * kMethDictEntrySize;
+
blockSize -= 4; // blocksize contains UINT16 type and UINT16 size
if (scriptDataLeft < blockSize)
error("Script::identifyOffsets(): invalid block size in script %d", _nr);
@@ -444,6 +472,8 @@ void Script::identifyOffsets() {
scriptDataLeft -= blockSize;
} while (1);
+ _codeOffset = hunkPtr - _buf;
+
// 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);
@@ -674,7 +704,7 @@ static bool relocateBlock(Common::Array<reg_t> &block, int block_location, Segme
return true;
}
-int Script::relocateOffsetSci3(uint32 offset) {
+int Script::relocateOffsetSci3(uint32 offset) const {
int relocStart = READ_LE_UINT32(_buf + 8);
int relocCount = READ_LE_UINT16(_buf + 18);
const byte *seeker = _buf + relocStart;
@@ -820,9 +850,10 @@ uint32 Script::validateExportFunc(int pubfunct, bool relocSci3) {
}
}
- // Note that it's perfectly normal to return a zero offset, especially in
- // SCI1.1 and newer games. Examples include script 64036 in Torin's Passage,
- // script 64908 in the demo of RAMA and script 1013 in KQ6 floppy.
+ // TODO: Check if this should be done for SCI1.1 games as well
+ if (getSciVersion() >= SCI_VERSION_2 && offset == 0) {
+ offset = _codeOffset;
+ }
if (offset >= _bufSize)
error("Invalid export function pointer");
@@ -1058,11 +1089,17 @@ void Script::initializeObjectsSci11(SegManager *segMan, SegmentId segmentId) {
obj->setSuperClassSelector(
segMan->getClassAddress(obj->getSuperClassSelector().getOffset(), SCRIPT_GET_LOCK, 0));
- // If object is instance, get -propDict- from class and set it for this
- // object. This is needed for ::isMemberOf() to work.
+ // -propDict- is used by Obj::isMemberOf to determine if an object
+ // is an instance of a class. For classes, we therefore relocate
+ // -propDict- to the script's segment. For instances, we copy
+ // -propDict- from its class.
// Example test case - room 381 of sq4cd - if isMemberOf() doesn't work,
- // talk-clicks on the robot will act like clicking on ego
- if (!obj->isClass()) {
+ // talk-clicks on the robot will act like clicking on ego.
+ if (obj->isClass()) {
+ reg_t propDict = obj->getPropDictSelector();
+ propDict.setSegment(segmentId);
+ obj->setPropDictSelector(propDict);
+ } else {
reg_t classObject = obj->getSuperClassSelector();
const Object *classObj = segMan->getObject(classObject);
obj->setPropDictSelector(classObj->getPropDictSelector());
diff --git a/engines/sci/engine/script.h b/engines/sci/engine/script.h
index 755e2f3698..677b367051 100644
--- a/engines/sci/engine/script.h
+++ b/engines/sci/engine/script.h
@@ -81,6 +81,8 @@ private:
const byte *_synonyms; /**< Synonyms block or 0 if not present */
uint16 _numSynonyms; /**< Number of entries in the synonyms block */
+ int _codeOffset; /**< The absolute offset of the VM code block */
+
int _localsOffset;
uint16 _localsCount;
@@ -264,7 +266,7 @@ public:
* Resolve a relocation in an SCI3 script
* @param offset The offset to relocate from
*/
- int relocateOffsetSci3(uint32 offset);
+ int relocateOffsetSci3(uint32 offset) const;
/**
* Gets an offset to the beginning of the code block in a SCI3 script
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index fc0dca5123..0c4f0da959 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -103,6 +103,9 @@ static const char *const selectorNameTable[] = {
"modNum", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support
"cycler", // Space Quest 4 / system selector
"setLoop", // Laura Bow 1 Colonel's Bequest
+#ifdef ENABLE_SCI32
+ "newWith", // SCI2 array script
+#endif
NULL
};
@@ -132,8 +135,86 @@ enum ScriptPatcherSelectors {
SELECTOR_modNum,
SELECTOR_cycler,
SELECTOR_setLoop
+#ifdef ENABLE_SCI32
+ ,
+ SELECTOR_newWith
+#endif
+};
+
+#ifdef ENABLE_SCI32
+// It is not possible to change the directory for ScummVM save games, so disable
+// the "change directory" button in the standard save dialogue
+static const uint16 sci2ChangeDirSignature[] = {
+ 0x72, SIG_ADDTOOFFSET(+2), // lofsa changeDirI
+ 0x4a, SIG_UINT16(0x04), // send 4
+ SIG_MAGICDWORD,
+ 0x36, // push
+ 0x35, 0xF7, // ldi $f7
+ 0x12, // and
+ 0x36, // push
+ SIG_END
+};
+
+static const uint16 sci2ChangeDirPatch[] = {
+ PATCH_ADDTOOFFSET(+3), // lofsa changeDirI
+ PATCH_ADDTOOFFSET(+3), // send 4
+ PATCH_ADDTOOFFSET(+1), // push
+ 0x35, 0x00, // ldi 0
+ PATCH_END
+};
+
+// Save game script hardcodes the maximum number of save games to 20, but
+// this is an artificial constraint that does not apply to ScummVM
+static const uint16 sci2NumSavesSignature1[] = {
+ SIG_MAGICDWORD,
+ 0x8b, 0x02, // lsl local[2]
+ 0x35, 0x14, // ldi 20
+ 0x22, // lt?
+ SIG_END
+};
+
+static const uint16 sci2NumSavesPatch1[] = {
+ PATCH_ADDTOOFFSET(+2), // lsl local[2]
+ 0x35, 0x63, // ldi 99
+ PATCH_END
+};
+
+static const uint16 sci2NumSavesSignature2[] = {
+ SIG_MAGICDWORD,
+ 0x8b, 0x02, // lsl local[2]
+ 0x35, 0x14, // ldi 20
+ 0x1a, // eq?
+ SIG_END
+};
+
+static const uint16 sci2NumSavesPatch2[] = {
+ PATCH_ADDTOOFFSET(+2), // lsl local[2]
+ 0x35, 0x63, // ldi 99
+ PATCH_END
};
+// Phantasmagoria & SQ6 try to initialize the first entry of an int16 array
+// using an empty string, which is not valid (it should be a number)
+static const uint16 sci21IntArraySignature[] = {
+ 0x38, SIG_SELECTOR16(newWith), // pushi newWith
+ 0x7a, // push2
+ 0x39, 0x04, // pushi $4
+ 0x72, SIG_ADDTOOFFSET(+2), // lofsa string ""
+ SIG_MAGICDWORD,
+ 0x36, // push
+ 0x51, 0x0b, // class IntArray
+ 0x4a, 0x8, // send $8
+ SIG_END
+};
+
+static const uint16 sci21IntArrayPatch[] = {
+ PATCH_ADDTOOFFSET(+6), // push $b9; push2; pushi $4
+ 0x76, // push0
+ 0x34, PATCH_UINT16(0x0001), // ldi 0001 (waste bytes)
+ PATCH_END
+};
+#endif
+
// ===========================================================================
// Conquests of Camelot
// At the bazaar in Jerusalem, it's possible to see a girl taking a shower.
@@ -379,12 +460,40 @@ static const SciScriptPatcherEntry ecoquest2Signatures[] = {
};
// ===========================================================================
+// Fan-made games
+// Attention: Try to make script patches as specific as possible
+
+// CascadeQuest::autosave in script 994 is called various times to auto-save the game.
+// The script use a fixed slot "999" for this purpose. This doesn't work in ScummVM, because we do not let
+// scripts save directly into specific slots, but instead use virtual slots / detect scripts wanting to
+// create a new slot.
+//
+// For this game we patch the code to use slot 99 instead. kSaveGame also checks for Cascade Quest,
+// will then check, if slot 99 is asked for and will then use the actual slot 0, which is the official
+// ScummVM auto-save slot.
+//
+// Responsible method: CascadeQuest::autosave
+// Fixes bug: #7007
+static const uint16 fanmadeSignatureCascadeQuestFixAutoSaving[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x03e7), // pushi 3E7 (999d) -> save game slot 999
+ 0x74, SIG_UINT16(0x06f8), // lofss "AutoSave"
+ 0x89, 0x1e, // lsg global[1E]
+ 0x43, 0x2d, 0x08, // callk SaveGame
+ SIG_END
+};
+
+static const uint16 fanmadePatchCascadeQuestFixAutoSaving[] = {
+ 0x38, PATCH_UINT16((SAVEGAMEID_OFFICIALRANGE_START - 1)), // fix slot
+ PATCH_END
+};
+
// EventHandler::handleEvent in Demo Quest has a bug, and it jumps to the
// wrong address when an incorrect word is typed, therefore leading to an
// infinite loop. This script bug was not apparent in SSCI, probably because
// event handling was slightly different there, so it was never discovered.
// Fixes bug: #5120
-static const uint16 fanmadeSignatureInfiniteLoop[] = {
+static const uint16 fanmadeSignatureDemoQuestInfiniteLoop[] = {
0x38, SIG_UINT16(0x004c), // pushi 004c
0x39, 0x00, // pushi 00
0x87, 0x01, // lap 01
@@ -395,15 +504,16 @@ static const uint16 fanmadeSignatureInfiniteLoop[] = {
SIG_END
};
-static const uint16 fanmadePatchInfiniteLoop[] = {
+static const uint16 fanmadePatchDemoQuestInfiniteLoop[] = {
PATCH_ADDTOOFFSET(+10),
- 0x30, SIG_UINT16(0x0032), // bnt 0032 [06a8] --> pushi 004c
+ 0x30, PATCH_UINT16(0x0032), // bnt 0032 [06a8] --> pushi 004c
PATCH_END
};
-// script, description, signature patch
+// script, description, signature patch
static const SciScriptPatcherEntry fanmadeSignatures[] = {
- { true, 999, "infinite loop on typo", 1, fanmadeSignatureInfiniteLoop, fanmadePatchInfiniteLoop },
+ { true, 994, "Cascade Quest: fix auto-saving", 1, fanmadeSignatureCascadeQuestFixAutoSaving, fanmadePatchCascadeQuestFixAutoSaving },
+ { true, 999, "Demo Quest: infinite loop on typo", 1, fanmadeSignatureDemoQuestInfiniteLoop, fanmadePatchDemoQuestInfiniteLoop },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -585,6 +695,10 @@ static const SciScriptPatcherEntry freddypharkasSignatures[] = {
SCI_SIGNATUREENTRY_TERMINATOR
};
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Gabriel Knight 1
+
// ===========================================================================
// daySixBeignet::changeState (4) is called when the cop goes out and sets cycles to 220.
// this is not enough time to get to the door, so we patch that to 23 seconds
@@ -634,6 +748,68 @@ static const uint16 gk1PatchDay6PoliceSleep[] = {
PATCH_END
};
+// At the start of day 5, there is like always some dialogue with Grace.
+//
+// The dialogue script code about the drum book + veve newspaper clip is a bit broken.
+//
+// In case the player already has the veve, but is supposed to get the drum book, then the drum book
+// dialogue is repeated twice and the veve newspaper dialogue is also repeated (although it was played on day 4
+// in such case already).
+//
+// Drum book dialogue is called twice.
+// Once via GetTheVeve::changeState(0) and a second time via GetTheVeve::changeState(11).
+//
+// GetTheVeve::changeState(0) would also play the first line of the veve pattern newspaper and that's skipped,
+// when the player is supposed to get the drum book.
+// GetTheVeve::changeState(1) up to state 10 will do the dialogue about the veve newspaper.
+// At the start of state 1 though, the player will get the drum book in case he ask for research.
+// Right after that the scripts check, if the player has the drum book and then go the veve newspaper route.
+//
+// We fix this by skipping the drum book check in case the player just got the drum book.
+// The scripts will then skip to state 12, skipping over the second drum book dialogue call.
+//
+// More notes: The veve newspaper item is inventory 9. The drum book is inventory 14.
+// The flag for veve research is 36, the flag for drum research is 73.
+//
+// This bug of course also occurs, when using the original interpreter.
+//
+// Special thanks, credits and kudos to sluicebox on IRC, who did a ton of research on this and even found this game bug originally.
+//
+// Applies to at least: English PC-CD, German PC-CD
+// Responsible method: getTheVeve::changeState(1) - script 212
+static const uint16 gk1SignatureDay5DrumBookDialogue[] = {
+ 0x31, 0x0b, // bnt [skip giving player drum book code]
+ 0x38, SIG_UINT16(0x0200), // pushi 0200h
+ 0x78, // push1
+ SIG_MAGICDWORD,
+ 0x39, 0x0e, // pushi 0Eh
+ 0x81, 0x00, // lag global[0]
+ 0x4a, 0x06, 0x00, // send 06 - GKEgo::get(0Eh)
+ // end of giving player drum book code
+ 0x38, SIG_UINT16(0x0202), // pushi 0202h
+ 0x78, // push1
+ 0x39, 0x0e, // pushi 0Eh
+ 0x81, 0x00, // lag global[0]
+ 0x4a, 0x06, 0x00, // send 06 - GKEgo::has(0Eh)
+ 0x18, // not
+ 0x30, SIG_UINT16(0x0025), // bnt [veve newspaper code]
+ SIG_END
+};
+
+static const uint16 gk1PatchDay5DrumBookDialogue[] = {
+ 0x31, 0x0d, // bnt [skip giving player drum book code] adjusted
+ PATCH_ADDTOOFFSET(+11), // skip give player drum book original code
+ 0x33, 0x0D, // jmp [over the check inventory for drum book code]
+ // check inventory for drum book
+ 0x38, SIG_UINT16(0x0202), // pushi 0202h
+ 0x78, // push1
+ 0x39, 0x0e, // pushi 0Eh
+ 0x81, 0x00, // lag global[0]
+ 0x4a, 0x06, 0x00, // send 06 - GKEgo::has(0Eh)
+ 0x2f, 0x23, // bt [veve newspaper code] (adjusted, saves 2 bytes)
+ PATCH_END
+};
+
// startOfDay5::changeState (20h) - when gabriel goes to the phone the script will hang
// Applies to at least: English PC-CD, German PC-CD, English Mac
// Responsible method: startOfDay5::changeState
@@ -700,7 +876,7 @@ static const uint16 gk1PatchInterrogationBug[] = {
0x76, // push0
0x4a, 0x04, 0x00, // send 0004
0xa5, 0x00, // sat 00
- 0x38, SIG_SELECTOR16(dispose), // pushi dispose
+ 0x38, PATCH_SELECTOR16(dispose), // pushi dispose
0x76, // push0
0x63, 0x50, // pToa 50
0x4a, 0x04, 0x00, // send 0004
@@ -717,15 +893,135 @@ static const uint16 gk1PatchInterrogationBug[] = {
PATCH_END
};
-// script, description, signature patch
+// On day 10 nearly at the end of the game, Gabriel Knight dresses up and right after that
+// someone will be at the door. Gabriel turns around to see what's going on.
+//
+// In ScummVM Gabriel turning around plays endlessly. This is caused by the loop of Gabriel
+// being kept at 1, but view + cel were changed accordingly. The view used - which is view 859 -
+// does not have a loop 1. kNumCels is called on that, BUT kNumCels in SSCI is broken in that
+// regard. It checks for loop > count and not loop >= count and will return basically random data
+// in case loop == count.
+//
+// In SSCI this simply worked by accident. kNumCels returned 0x53 in this case, but later script code
+// fixed that up somehow, so it worked out in the end.
+//
+// The setup for this is done in SDJEnters::changeState(0). The cycler will never reach the goal
+// because the goal will be cel -1, so it loops endlessly.
+//
+// We fix this by adding a setLoop(0).
+//
+// Applies to at least: English PC-CD, German PC-CD
+// Responsible method: sDJEnters::changeState
+static const uint16 gk1SignatureDay10GabrielDressUp[] = {
+ 0x87, 0x01, // lap param[1]
+ 0x65, 0x14, // aTop state
+ 0x36, // push
+ 0x3c, // dup
+ 0x35, 0x00, // ldi 0
+ 0x1a, // eq?
+ 0x30, SIG_UINT16(0x006f), // bnt [next state 1]
+ SIG_ADDTOOFFSET(+84),
+ 0x39, 0x0e, // pushi 0Eh (view)
+ 0x78, // push1
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x035B), // pushi 035Bh (859d)
+ 0x38, SIG_UINT16(0x0141), // pushi 0141h (setCel)
+ 0x78, // push1
+ 0x76, // push0
+ 0x38, SIG_UINT16(0x00E9), // pushi 00E9h (setCycle)
+ 0x7a, // push2
+ 0x51, 0x18, // class End
+ 0x36, // push
+ 0x7c, // pushSelf
+ 0x81, 0x00, // lag global[0]
+ 0x4a, 0x14, 0x00, // send 14h
+ // GKEgo::view(859)
+ // GKEgo::setCel(0)
+ // GKEgo::setCycle(End, sDJEnters)
+ 0x32, SIG_UINT16(0x0233), // jmp [ret]
+ // next state
+ 0x3c, // dup
+ 0x35, 0x01, // ldi 01
+ 0x1a, // eq?
+ 0x31, 0x07, // bnt [next state 2]
+ 0x35, 0x02, // ldi 02
+ 0x65, 0x1a, // aTop cycles
+ 0x32, SIG_UINT16(0x0226), // jmp [ret]
+ // next state
+ 0x3c, // dup
+ 0x35, 0x02, // ldi 02
+ 0x1a, // eq?
+ 0x31, 0x2a, // bnt [next state 3]
+ 0x78, // push1
+ SIG_ADDTOOFFSET(+34),
+ // part of state 2 code, delays for 1 cycle
+ 0x35, 0x01, // ldi 1
+ 0x65, 0x1a, // aTop cycles
+ SIG_END
+};
+
+static const uint16 gk1PatchDay10GabrielDressUp[] = {
+ PATCH_ADDTOOFFSET(+9),
+ 0x30, SIG_UINT16(0x0073), // bnt [next state 1] - offset adjusted
+ SIG_ADDTOOFFSET(+84 + 11),
+ // added by us: setting loop to 0 (5 bytes needed)
+ 0x38, SIG_UINT16(0x00FB), // pushi 00FBh (setLoop)
+ 0x78, // push1
+ 0x76, // push0
+ // original code, but offset changed
+ 0x38, SIG_UINT16(0x00E9), // pushi 00E9h (setCycle)
+ 0x7a, // push2
+ 0x51, 0x18, // class End
+ 0x36, // push
+ 0x7c, // pushSelf
+ 0x81, 0x00, // lag global[0]
+ 0x4a, 0x1a, 0x00, // send 1Ah - adjusted
+ // GKEgo::view(859)
+ // GKEgo::setCel(0)
+ // GKEgo::setLoop(0) <-- new, by us
+ // GKEgo::setCycle(End, sDJEnters)
+ // end of original code
+ 0x3a, // toss
+ 0x48, // ret (saves 1 byte)
+ // state 1 code
+ 0x3c, // dup
+ 0x34, SIG_UINT16(0x0001), // ldi 0001 (waste 1 byte)
+ 0x1a, // eq?
+ 0x31, 2, // bnt [next state 2]
+ 0x33, 41, // jmp to state 2 delay code
+ SIG_ADDTOOFFSET(+41),
+ // wait 2 cycles instead of only 1
+ 0x35, 0x02, // ldi 2
+ PATCH_END
+};
+
+// script, description, signature patch
static const SciScriptPatcherEntry gk1Signatures[] = {
- { true, 51, "interrogation bug", 1, gk1SignatureInterrogationBug, gk1PatchInterrogationBug },
- { true, 212, "day 5 phone freeze", 1, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze },
- { true, 230, "day 6 police beignet timer issue", 1, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet },
- { true, 230, "day 6 police sleep timer issue", 1, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep },
+ { true, 51, "interrogation bug", 1, gk1SignatureInterrogationBug, gk1PatchInterrogationBug },
+ { true, 212, "day 5 drum book dialogue error", 1, gk1SignatureDay5DrumBookDialogue, gk1PatchDay5DrumBookDialogue },
+ { true, 212, "day 5 phone freeze", 1, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze },
+ { true, 230, "day 6 police beignet timer issue", 1, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet },
+ { true, 230, "day 6 police sleep timer issue", 1, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep },
+ { true, 808, "day 10 gabriel dress up infinite turning", 1, gk1SignatureDay10GabrielDressUp, gk1PatchDay10GabrielDressUp },
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
+ { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
SCI_SIGNATUREENTRY_TERMINATOR
};
+#pragma mark -
+#pragma mark Gabriel Knight 2
+
+// script, description, signature patch
+static const SciScriptPatcherEntry gk2Signatures[] = {
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
+ { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#endif
+
// ===========================================================================
// at least during harpy scene export 29 of script 0 is called in kq5cd and
// has an issue for those calls, where temp 3 won't get inititialized, but
@@ -813,6 +1109,32 @@ static const uint16 kq5PatchWitchCageInit[] = {
PATCH_END
};
+// The multilingual releases of KQ5 hang right at the end during the magic battle with Mordack.
+// It seems additional code was added to wait for signals, but the signals are never set and thus
+// the game hangs. We disable that code, so that the battle works again.
+// This also happened in the original interpreter.
+// We must not change similar code, that happens before.
+
+// Applies to at least: French PC floppy, German PC floppy, Spanish PC floppy
+// Responsible method: stingScript::changeState, dragonScript::changeState, snakeScript::changeState
+static const uint16 kq5SignatureMultilingualEndingGlitch[] = {
+ SIG_MAGICDWORD,
+ 0x89, 0x57, // lsg global[57h]
+ 0x35, 0x00, // ldi 0
+ 0x1a, // eq?
+ 0x18, // not
+ 0x30, SIG_UINT16(0x0011), // bnt [skip signal check]
+ SIG_ADDTOOFFSET(+8), // skip globalSound::prevSignal get code
+ 0x36, // push
+ 0x35, 0x0a, // ldi 0Ah
+ SIG_END
+};
+
+static const uint16 kq5PatchMultilingualEndingGlitch[] = {
+ PATCH_ADDTOOFFSET(+6),
+ 0x32, // change BNT into JMP
+ PATCH_END
+};
// In the final battle, the DOS version uses signals in the music to handle
// timing, while in the Windows version another method is used and the GM
@@ -843,9 +1165,10 @@ static const uint16 kq5PatchWinGMSignals[] = {
// script, description, signature patch
static const SciScriptPatcherEntry kq5Signatures[] = {
- { true, 0, "CD: harpy volume change", 1, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume },
- { true, 200, "CD: witch cage init", 1, kq5SignatureWitchCageInit, kq5PatchWitchCageInit },
- { false, 124, "Win: GM Music signal checks", 4, kq5SignatureWinGMSignals, kq5PatchWinGMSignals },
+ { true, 0, "CD: harpy volume change", 1, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume },
+ { true, 200, "CD: witch cage init", 1, kq5SignatureWitchCageInit, kq5PatchWitchCageInit },
+ { true, 124, "Multilingual: Ending glitching out", 3, kq5SignatureMultilingualEndingGlitch, kq5PatchMultilingualEndingGlitch },
+ { false, 124, "Win: GM Music signal checks", 4, kq5SignatureWinGMSignals, kq5PatchWinGMSignals },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -1165,6 +1488,7 @@ static const uint16 kq6CDPatchAudioTextSupport2[] = {
// Additional patch specifically for King's Quest 6
// Fixes special windows, used for example in the Pawn shop (room 280),
// when the man in a robe complains about no more mints.
+// Or also in room 300 at the cliffs (aka copy protection), when Alexander falls down the cliffs.
// We have to change even more code, because the game uses PODialog class for
// text windows and myDialog class for audio. Both are saved to KQ6Print::dialog
// Sadly PODialog is created during KQ6Print::addText, myDialog is set during
@@ -1191,13 +1515,34 @@ static const uint16 kq6CDSignatureAudioTextSupport3[] = {
};
static const uint16 kq6CDPatchAudioTextSupport3[] = {
- 0x31, 0x5c, // adjust jump to reuse audio mode addText-calling code
- PATCH_ADDTOOFFSET(102),
- 0x48, // ret
- 0x48, // ret (waste byte)
+ 0x31, 0x68, // adjust jump to reuse audio mode addText-calling code
+ PATCH_ADDTOOFFSET(+85), // right at the MAGIC_DWORD
+ // check, if text is supposed to be shown. If yes, skip the follow-up check (param[1])
+ 0x89, 0x5a, // lsg global[5Ah]
+ 0x35, 0x01, // ldi 01
+ 0x12, // and
+ 0x2f, 0x07, // bt [skip over param check]
+ // original code, checks param[1]
+ 0x8f, 0x01, // lsp param[1]
+ 0x35, 0x01, // ldi 01
+ 0x1a, // eq?
+ 0x31, 0x10, // bnt [code to set property repressText to 1], adjusted
+ // use myDialog class, so that text box automatically disappears (this is not done for text only mode, like in the original)
0x72, 0x0e, 0x00, // lofsa myDialog
0x65, 0x12, // aTop dialog
- 0x33, 0xed, // jump back to audio mode addText-calling code
+ // followed by original addText-calling code
+ 0x38,
+ PATCH_GETORIGINALBYTE(+95),
+ PATCH_GETORIGINALBYTE(+96), // pushi addText
+ 0x78, // push1
+ 0x8f, 0x02, // lsp param[2]
+ 0x59, 0x03, // &rest 03
+ 0x54, 0x06, // self 06
+ 0x48, // ret
+
+ 0x35, 0x01, // ldi 01
+ 0x65, 0x2e, // aTop repressText
+ 0x48, // ret
PATCH_END
};
@@ -1396,6 +1741,173 @@ static const SciScriptPatcherEntry kq6Signatures[] = {
SCI_SIGNATUREENTRY_TERMINATOR
};
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Kings Quest 7
+
+// ===========================================================================
+
+// King's Quest 7 has really weird subtitles. It seems as if the subtitles were
+// not fully finished.
+//
+// Method kqMessager::findTalker in script 0 tries to figure out, which class to use for
+// displaying subtitles. It uses the "talker" data of the given message to do that.
+// Strangely this "talker" data seems to be quite broken.
+// For example chapter 2 starts with a cutscene.
+// Troll king: "Welcome, most beautiful of princesses!" - talker 6
+// Which is followed by the princess going
+// "Hmm?" - which is set to talker 99, normally the princess is talker 7.
+//
+// Talker 99 is seen as unknown and thus treated as "narrator", which makes
+// the scripts put the text at the top of the game screen and even use a
+// different font.
+//
+// In other cases, when the player character thinks to himself talker 99
+// is also used. In such situations it may make somewhat sense to do so,
+// but putting the text at the top of the screen is also irritating to the player.
+// It's really weird.
+//
+// The scripts also put the regular text in the middle of the screen, blocking
+// animations.
+//
+// And for certain rooms, the subtitle box may use another color
+// like for example pink/purple at the start of chapter 5.
+//
+// We fix all of that (hopefully - lots of testing is required).
+// We put the text at the bottom of the play screen.
+// We also make the scripts use the regular KQTalker instead of KQNarrator.
+// And we also make the subtitle box use color 255, which is fixed white.
+//
+// Applies to at least: PC CD 1.4 English, 1.51 English, 1.51 German, 2.00 English
+// Patched method: KQNarrator::init (script 31)
+static const uint16 kq7SignatureSubtitleFix1[] = {
+ SIG_MAGICDWORD,
+ 0x39, 0x25, // pushi 25h (fore)
+ 0x78, // push1
+ 0x39, 0x06, // pushi 06 - sets back to 6
+ 0x39, 0x26, // pushi 26 (back)
+ 0x78, // push1
+ 0x78, // push1 - sets back to 1
+ 0x39, 0x2a, // pushi 2Ah (font)
+ 0x78, // push1
+ 0x89, 0x16, // lsg global[16h] - sets font to global[16h]
+ 0x7a, // push2 (y)
+ 0x78, // push1
+ 0x76, // push0 - sets y to 0
+ 0x54, SIG_UINT16(0x0018), // self 18h
+ SIG_END
+};
+
+static const uint16 kq7PatchSubtitleFix1[] = {
+ 0x33, 0x12, // jmp [skip special init code]
+ PATCH_END
+};
+
+// Applies to at least: PC CD 1.51 English, 1.51 German, 2.00 English
+// Patched method: Narrator::init (script 64928)
+static const uint16 kq7SignatureSubtitleFix2[] = {
+ SIG_MAGICDWORD,
+ 0x89, 0x5a, // lsg global[5a]
+ 0x35, 0x02, // ldi 02
+ 0x12, // and
+ 0x31, 0x1e, // bnt [skip audio volume code]
+ 0x38, SIG_ADDTOOFFSET(+2), // pushi masterVolume (0212h for 2.00, 0219h for 1.51)
+ 0x76, // push0
+ 0x81, 0x01, // lag global[1]
+ 0x4a, 0x04, 0x00, // send 04
+ 0x65, 0x32, // aTop curVolume
+ 0x38, SIG_ADDTOOFFSET(+2), // pushi masterVolume (0212h for 2.00, 0219h for 1.51)
+ 0x78, // push1
+ 0x67, 0x32, // pTos curVolume
+ 0x35, 0x02, // ldi 02
+ 0x06, // mul
+ 0x36, // push
+ 0x35, 0x03, // ldi 03
+ 0x08, // div
+ 0x36, // push
+ 0x81, 0x01, // lag global[1]
+ 0x4a, 0x06, 0x00, // send 06
+ // end of volume code
+ 0x35, 0x01, // ldi 01
+ 0x65, 0x28, // aTop initialized
+ SIG_END
+};
+
+static const uint16 kq7PatchSubtitleFix2[] = {
+ PATCH_ADDTOOFFSET(+5), // skip to bnt
+ 0x31, 0x1b, // bnt [skip audio volume code]
+ PATCH_ADDTOOFFSET(+15), // right after "aTop curVolume / pushi masterVolume / push1"
+ 0x7a, // push2
+ 0x06, // mul (saves 3 bytes in total)
+ 0x36, // push
+ 0x35, 0x03, // ldi 03
+ 0x08, // div
+ 0x36, // push
+ 0x81, 0x01, // lag global[1]
+ 0x4a, 0x06, 0x00, // send 06
+ // end of volume code
+ 0x35, 118, // ldi 118d
+ 0x65, 0x16, // aTop y
+ 0x78, // push1 (saves 1 byte)
+ 0x69, 0x28, // sTop initialized
+ PATCH_END
+};
+
+// Applies to at least: PC CD 1.51 English, 1.51 German, 2.00 English
+// Patched method: Narrator::say (script 64928)
+static const uint16 kq7SignatureSubtitleFix3[] = {
+ SIG_MAGICDWORD,
+ 0x63, 0x28, // pToa initialized
+ 0x18, // not
+ 0x31, 0x07, // bnt [skip init code]
+ 0x38, SIG_ADDTOOFFSET(+2), // pushi init (008Eh for 2.00, 0093h for 1.51)
+ 0x76, // push0
+ 0x54, SIG_UINT16(0x0004), // self 04
+ // end of init code
+ 0x8f, 0x00, // lsp param[0]
+ 0x35, 0x01, // ldi 01
+ 0x1e, // gt?
+ 0x31, 0x08, // bnt [set acc to 0]
+ 0x87, 0x02, // lap param[2]
+ 0x31, 0x04, // bnt [set acc to 0]
+ 0x87, 0x02, // lap param[2]
+ 0x33, 0x02, // jmp [over set acc to 0 code]
+ 0x35, 0x00, // ldi 00
+ 0x65, 0x18, // aTop caller
+ SIG_END
+};
+
+static const uint16 kq7PatchSubtitleFix3[] = {
+ PATCH_ADDTOOFFSET(+2), // skip over "pToa initialized code"
+ 0x2f, 0x0c, // bt [skip init code] - saved 1 byte
+ 0x38,
+ PATCH_GETORIGINALBYTE(+6),
+ PATCH_GETORIGINALBYTE(+7), // pushi (init)
+ 0x76, // push0
+ 0x54, PATCH_UINT16(0x0004), // self 04
+ // additionally set background color here (5 bytes)
+ 0x34, PATCH_UINT16(255), // pushi 255d
+ 0x65, 0x2e, // aTop back
+ // end of init code
+ 0x8f, 0x00, // lsp param[0]
+ 0x35, 0x01, // ldi 01 - this may get optimized to get another byte
+ 0x1e, // gt?
+ 0x31, 0x04, // bnt [set acc to 0]
+ 0x87, 0x02, // lap param[2]
+ 0x2f, 0x02, // bt [over set acc to 0 code]
+ PATCH_END
+};
+
+// script, description, signature patch
+static const SciScriptPatcherEntry kq7Signatures[] = {
+ { true, 31, "subtitle fix 1/3", 1, kq7SignatureSubtitleFix1, kq7PatchSubtitleFix1 },
+ { true, 64928, "subtitle fix 2/3", 1, kq7SignatureSubtitleFix2, kq7PatchSubtitleFix2 },
+ { true, 64928, "subtitle fix 3/3", 1, kq7SignatureSubtitleFix3, kq7PatchSubtitleFix3 },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#endif
+
// ===========================================================================
// Script 210 in the German version of Longbow handles the case where Robin
// hands out the scroll to Marion and then types his name using the hand code.
@@ -1436,9 +1948,109 @@ static const uint16 longbowPatchShowHandCode[] = {
PATCH_END
};
+// When walking through the forest, arithmetic errors may occur at "random".
+// The scripts try to add a value and a pointer to the object "berryBush".
+//
+// This is caused by a local variable overflow.
+//
+// The scripts create berry bush objects dynamically. The array storage for
+// those bushes may hold a total of 8 bushes. But sometimes 10 bushes
+// are created. This overwrites 2 additional locals in script 225 and
+// those locals are used normally for value lookups.
+//
+// Changing the total of bushes could cause all sorts of other issues,
+// that's why I rather patched the code, that uses the locals for a lookup.
+// Which means it doesn't matter anymore when those locals are overwritten.
+//
+// Applies to at least: English PC floppy, German PC floppy, English Amiga floppy
+// Responsible method: export 2 of script 225
+// Fixes bug: #6751
+static const uint16 longbowSignatureBerryBushFix[] = {
+ 0x89, 0x70, // lsg global[70h]
+ 0x35, 0x03, // ldi 03h
+ 0x1a, // eq?
+ 0x2e, SIG_UINT16(0x002d), // bt [process code]
+ 0x89, 0x70, // lsg global[70h]
+ 0x35, 0x04, // ldi 04h
+ 0x1a, // eq?
+ 0x2e, SIG_UINT16(0x0025), // bt [process code]
+ 0x89, 0x70, // lsg global[70h]
+ 0x35, 0x05, // ldi 05h
+ 0x1a, // eq?
+ 0x2e, SIG_UINT16(0x001d), // bt [process code]
+ 0x89, 0x70, // lsg global[70h]
+ 0x35, 0x06, // ldi 06h
+ 0x1a, // eq?
+ 0x2e, SIG_UINT16(0x0015), // bt [process code]
+ 0x89, 0x70, // lsg global[70h]
+ 0x35, 0x18, // ldi 18h
+ 0x1a, // eq?
+ 0x2e, SIG_UINT16(0x000d), // bt [process code]
+ 0x89, 0x70, // lsg global[70h]
+ 0x35, 0x19, // ldi 19h
+ 0x1a, // eq?
+ 0x2e, SIG_UINT16(0x0005), // bt [process code]
+ 0x89, 0x70, // lsg global[70h]
+ 0x35, 0x1a, // ldi 1Ah
+ 0x1a, // eq?
+ // jump location for the "bt" instructions
+ 0x30, SIG_UINT16(0x0011), // bnt [skip over follow up code, to offset 0c35]
+ // 55 bytes until here
+ 0x85, 00, // lat temp[0]
+ SIG_MAGICDWORD,
+ 0x9a, SIG_UINT16(0x0110), // lsli local[110h] -> 110h points normally to 110h / 2Bh
+ // 5 bytes
+ 0x7a, // push2
+ SIG_END
+};
+
+static const uint16 longbowPatchBerryBushFix[] = {
+ PATCH_ADDTOOFFSET(+4), // keep: lsg global[70h], ldi 03h
+ 0x22, // lt? (global < 03h)
+ 0x2f, 0x42, // bt [skip over all the code directly]
+ 0x89, 0x70, // lsg global[70h]
+ 0x35, 0x06, // ldi 06h
+ 0x24, // le? (global <= 06h)
+ 0x2f, 0x0e, // bt [to kRandom code]
+ 0x89, 0x70, // lsg global[70h]
+ 0x35, 0x18, // ldi 18h
+ 0x22, // lt? (global < 18h)
+ 0x2f, 0x34, // bt [skip over all the code directly]
+ 0x89, 0x70, // lsg global[70h]
+ 0x35, 0x1a, // ldi 1Ah
+ 0x24, // le? (global <= 1Ah)
+ 0x31, 0x2d, // bnt [skip over all the code directly]
+ // 28 bytes, 27 bytes saved
+ // kRandom code
+ 0x85, 0x00, // lat temp[0]
+ 0x2f, 0x05, // bt [skip over case 0]
+ // temp[0] == 0
+ 0x38, SIG_UINT16(0x0110), // pushi 0110h - that's what's normally at local[110h]
+ 0x33, 0x18, // jmp [kRandom call]
+ // check temp[0] further
+ 0x78, // push1
+ 0x1a, // eq?
+ 0x31, 0x05, // bt [skip over case 1]
+ // temp[0] == 1
+ 0x38, SIG_UINT16(0x002b), // pushi 002Bh - that's what's normally at local[111h]
+ 0x33, 0x0F, // jmp [kRandom call]
+ // temp[0] >= 2
+ 0x8d, 00, // lst temp[0]
+ 0x35, 0x02, // ldi 02
+ 0x04, // sub
+ 0x9a, SIG_UINT16(0x0112), // lsli local[112h] -> look up value in 2nd table
+ // this may not be needed at all and was just added for safety reasons
+ // waste 9 spare bytes
+ 0x35, 0x00, // ldi 00
+ 0x35, 0x00, // ldi 00
+ 0x34, PATCH_UINT16(0x0000), // ldi 0000
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry longbowSignatures[] = {
{ true, 210, "hand code crash", 5, longbowSignatureShowHandCode, longbowPatchShowHandCode },
+ { true, 225, "arithmetic berry bush fix", 1, longbowSignatureBerryBushFix, longbowPatchBerryBushFix },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -1596,6 +2208,39 @@ static const SciScriptPatcherEntry larry6Signatures[] = {
SCI_SIGNATUREENTRY_TERMINATOR
};
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Leisure Suit Larry 6 Hires
+
+// When entering room 270 (diving board) from room 230, a typo in the game
+// script means that `setScale` is called accidentally instead of `setScaler`.
+// In SSCI this did not do much because the first argument happened to be
+// smaller than the y-position of `ego`, but in ScummVM the first argument is
+// larger and so a debug message "y value less than vanishingY" is displayed.
+static const uint16 larry6HiresSignatureSetScale[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x14b), // pushi 014b (setScale)
+ 0x38, SIG_UINT16(0x05), // pushi 0005
+ 0x51, 0x2c, // class 2c (Scaler)
+ SIG_END
+};
+
+static const uint16 larry6HiresPatchSetScale[] = {
+ 0x38, SIG_UINT16(0x14f), // pushi 014f (setScaler)
+ PATCH_END
+};
+
+// script, description, signature patch
+static const SciScriptPatcherEntry larry6HiresSignatures[] = {
+ { true, 270, "fix incorrect setScale call", 1, larry6HiresSignatureSetScale, larry6HiresPatchSetScale },
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
+ { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#endif
+
// ===========================================================================
// Laura Bow 1 - Colonel's Bequest
//
@@ -1627,9 +2272,160 @@ static const uint16 laurabow1PatchEasterEggViewFix[] = {
PATCH_END
};
+// When oiling the armor or opening the visor of the armor, the scripts
+// first check if Laura/ego is near the armor and if she is not, they will move her
+// to the armor. After that further code is executed.
+//
+// The current location is checked by a ego::inRect() call.
+//
+// The given rect for the inRect call inside openVisor::changeState was made larger for Atari ST/Amiga versions.
+// We change the PC version to use the same rect.
+//
+// Additionally the coordinate, that Laura is moved to, is 152, 107 and may not be reachable depending on where
+// Laura/ego was, when "use oil on helmet of armor" / "open visor of armor" got entered.
+// Bad coordinates are for example 82, 110, which then cause collisions and effectively an endless loop.
+// Game will effectively "freeze" and the user is only able to restore a previous game.
+// This also happened, when using the original interpreter.
+// We change the destination coordinate to 152, 110, which seems to be reachable all the time.
+//
+// The following patch fixes the rect for the PC version of the game.
+//
+// Applies to at least: English PC Floppy
+// Responsible method: openVisor::changeState (script 37)
+// Fixes bug: #7119
+static const uint16 laurabow1SignatureArmorOpenVisorFix[] = {
+ 0x39, 0x04, // pushi 04
+ SIG_MAGICDWORD,
+ 0x39, 0x6a, // pushi 6a (106d)
+ 0x38, SIG_UINT16(0x96), // pushi 0096 (150d)
+ 0x39, 0x6c, // pushi 6c (108d)
+ 0x38, SIG_UINT16(0x98), // pushi 0098 (152d)
+ SIG_END
+};
+
+static const uint16 laurabow1PatchArmorOpenVisorFix[] = {
+ PATCH_ADDTOOFFSET(+2),
+ 0x39, 0x68, // pushi 68 (104d) (-2)
+ 0x38, SIG_UINT16(0x94), // pushi 0094 (148d) (-2)
+ 0x39, 0x6f, // pushi 6f (111d) (+3)
+ 0x38, SIG_UINT16(0x9a), // pushi 009a (154d) (+2)
+ PATCH_END
+};
+
+// This here fixes the destination coordinate (exact details are above).
+//
+// Applies to at least: English PC Floppy, English Atari ST Floppy, English Amiga Floppy
+// Responsible method: openVisor::changeState, oiling::changeState (script 37)
+// Fixes bug: #7119
+static const uint16 laurabow1SignatureArmorMoveToFix[] = {
+ SIG_MAGICDWORD,
+ 0x36, // push
+ 0x39, 0x6b, // pushi 6B (107d)
+ 0x38, SIG_UINT16(0x0098), // pushi 98 (152d)
+ 0x7c, // pushSelf
+ 0x81, 0x00, // lag global[0]
+ SIG_END
+};
+
+static const uint16 laurabow1PatchArmorMoveToFix[] = {
+ PATCH_ADDTOOFFSET(+1),
+ 0x39, 0x6e, // pushi 6E (110d) - adjust x, so that no collision can occur anymore
+ PATCH_END
+};
+
+// In some cases like for example when the player oils the arm of the armor, command input stays
+// disabled, even when the player exits fast enough, so that Laura doesn't die.
+//
+// This is caused by the scripts only enabling control (directional movement), but do not enable command input as well.
+//
+// This bug also happens, when using the original interpreter.
+// And it was fixed for the Atari ST + Amiga versions of the game.
+//
+// Applies to at least: English PC Floppy
+// Responsible method: 2nd subroutine in script 37, called by oiling::changeState(7)
+// Fixes bug: #7154
+static const uint16 laurabow1SignatureArmorOilingArmFix[] = {
+ 0x38, SIG_UINT16(0x0089), // pushi 89h
+ 0x76, // push0
+ SIG_MAGICDWORD,
+ 0x72, SIG_UINT16(0x1a5c), // lofsa "Can" - offsets are not skipped to make sure only the PC version gets patched
+ 0x4a, 0x04, // send 04
+ 0x38, SIG_UINT16(0x0089), // pushi 89h
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x19a1), // lofsa "Visor"
+ 0x4a, 0x04, // send 04
+ 0x38, SIG_UINT16(0x0089), // pushi 89h
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x194a), // lofsa "note"
+ 0x4a, 0x04, // send 04
+ 0x38, SIG_UINT16(0x0089), // pushi 89h
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x18f3), // lofsa "valve"
+ 0x4a, 0x04, // send 04
+ 0x8b, 0x34, // lsl local[34h]
+ 0x35, 0x02, // ldi 02
+ 0x1c, // ne?
+ 0x30, SIG_UINT16(0x0014), // bnt [to ret]
+ 0x8b, 0x34, // lsl local[34h]
+ 0x35, 0x05, // ldi 05
+ 0x1c, // ne?
+ 0x30, SIG_UINT16(0x000c), // bnt [to ret]
+ 0x8b, 0x34, // lsl local[34h]
+ 0x35, 0x06, // ldi 06
+ 0x1c, // ne?
+ 0x30, SIG_UINT16(0x0004), // bnt [to ret]
+ // followed by code to call script 0 export to re-enable controls and call setMotion
+ SIG_END
+};
+
+static const uint16 laurabow1PatchArmorOilingArmFix[] = {
+ PATCH_ADDTOOFFSET(+3), // skip over pushi 89h
+ 0x3c, // dup
+ 0x3c, // dup
+ 0x3c, // dup
+ // saves a total of 6 bytes
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x1a59), // lofsa "Can"
+ 0x4a, 0x04, // send 04
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x19a1), // lofsa "Visor"
+ 0x4a, 0x04, // send 04
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x194d), // lofsa "note"
+ 0x4a, 0x04, // send 04
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x18f9), // lofsa "valve" 18f3
+ 0x4a, 0x04, // send 04
+ // new code to enable input as well, needs 9 spare bytes
+ 0x38, SIG_UINT16(0x00e2), // canInput
+ 0x78, // push1
+ 0x78, // push1
+ 0x51, 0x2b, // class User
+ 0x4a, 0x06, // send 06 -> call User::canInput(1)
+ // original code, but changed a bit to save some more bytes
+ 0x8b, 0x34, // lsl local[34h]
+ 0x35, 0x02, // ldi 02
+ 0x04, // sub
+ 0x31, 0x12, // bnt [to ret]
+ 0x36, // push
+ 0x35, 0x03, // ldi 03
+ 0x04, // sub
+ 0x31, 0x0c, // bnt [to ret]
+ 0x78, // push1
+ 0x1a, // eq?
+ 0x2f, 0x08, // bt [to ret]
+ // saves 7 bytes, we only need 3, so waste 4 bytes
+ 0x35, 0x00, // ldi 0
+ 0x35, 0x00, // ldi 0
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry laurabow1Signatures[] = {
- { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix },
+ { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix },
+ { true, 37, "armor open visor fix", 1, laurabow1SignatureArmorOpenVisorFix, laurabow1PatchArmorOpenVisorFix },
+ { true, 37, "armor move to fix", 2, laurabow1SignatureArmorMoveToFix, laurabow1PatchArmorMoveToFix },
+ { true, 37, "allowing input, after oiling arm", 1, laurabow1SignatureArmorOilingArmFix, laurabow1PatchArmorOilingArmFix },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -2066,6 +2862,18 @@ static const SciScriptPatcherEntry mothergoose256Signatures[] = {
SCI_SIGNATUREENTRY_TERMINATOR
};
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Phantasmagoria
+
+// script, description, signature patch
+static const SciScriptPatcherEntry phantasmagoriaSignatures[] = {
+ { true, 901, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#endif
+
// ===========================================================================
// Police Quest 1 VGA
@@ -2207,6 +3015,20 @@ static const SciScriptPatcherEntry pq1vgaSignatures[] = {
SCI_SIGNATUREENTRY_TERMINATOR
};
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Police Quest 4
+
+// script, description, signature patch
+static const SciScriptPatcherEntry pq4Signatures[] = {
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
+ { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#endif
+
// ===========================================================================
// 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.
@@ -2547,10 +3369,41 @@ static const uint16 qfg1vgaPatchWhiteStagDagger[] = {
PATCH_END
};
+// The dagger range has a script bug that can freeze the game or cause Brutus to kill you even after you've killed him.
+// This is a bug in the original game.
+//
+// When Bruno leaves, a 300 tick countdown starts. If you kill Brutus or leave room 73 within those 300 ticks then
+// the game is left in a broken state. For the rest of the game, if you ever return to the dagger range from the
+// east or west during the first half of the day then the game will freeze or Brutus will come back to life
+// and kill you, even if you already killed him.
+//
+// Special thanks, credits and kudos to sluicebox, who did a ton of research on this and even found this game bug originally.
+//
+// Applies to at least: English floppy, Mac floppy
+// Responsible method: brutusWaits::changeState
+// Fixes bug #9558
+static const uint16 qfg1vgaSignatureBrutusScriptFreeze[] = {
+ 0x78, // push1
+ 0x38, SIG_UINT16(0x144), // pushi 144h (324d)
+ 0x45, 0x05, 0x02, // call export 5 of script 0
+ SIG_MAGICDWORD,
+ 0x34, SIG_UINT16(0x12c), // ldi 12Ch (300d)
+ 0x65, 0x20, // aTop ticks
+ SIG_END
+};
+
+static const uint16 qfg1vgaPatchBrutusScriptFreeze[] = {
+ 0x34, PATCH_UINT16(0), // ldi 0 (waste 7 bytes)
+ 0x35, 0x00, // ldi 0
+ 0x35, 0x00, // ldi 0
+ 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, 73, "brutus script freeze glitch", 1, qfg1vgaSignatureBrutusScriptFreeze, qfg1vgaPatchBrutusScriptFreeze },
{ 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 },
@@ -2734,7 +3587,7 @@ static const uint16 qfg3PatchImportDialog[] = {
// Teller::doChild. We jump to this call of hero::solvePuzzle to get that same
// behaviour.
// Applies to at least: English, German, Italian, French, Spanish Floppy
-// Responsible method: unknown
+// Responsible method: uhuraTell::doChild
// Fixes bug: #5172
static const uint16 qfg3SignatureWooDialog[] = {
SIG_MAGICDWORD,
@@ -2907,6 +3760,84 @@ static const uint16 qfg3PatchChiefPriority[] = {
PATCH_END
};
+// There are 3 points that can't be achieved in the game. They should've been
+// awarded for telling Rakeesh and Kreesha (room 285) about the Simabni
+// initiation.
+// However the array of posibble messages the hero can tell in that room
+// (local 156) is missing the "Tell about Initiation" message (#31) which
+// awards these points.
+// This patch adds the message to that array, thus allowing the hero to tell
+// that message (after completing the initiation) and gain the 3 points.
+// A side effect of increasing the local156 array is that the next local
+// array is shifted and shrinks in size from 4 words to 3. The patch changes
+// the 2 locations in the script that reference that array, to point to the new
+// location ($aa --> $ab). It is safe to shrink the 2nd array to 3 words
+// because only the first element in it is ever used.
+//
+// Note: You have to re-enter the room in case a saved game was loaded from a
+// previous version of ScummVM and that saved game was made inside that room.
+//
+// Applies to: English, French, German, Italian, Spanish and the GOG release.
+// Responsible method: heap in script 285
+// Fixes bug #7086
+static const uint16 qfg3SignatureMissingPoints1[] = {
+ // local[$9c] = [0 -41 -76 1 -30 -77 -33 -34 -35 -36 -37 -42 -80 999]
+ // local[$aa] = [0 0 0 0]
+ SIG_UINT16(0x0000), // 0 START MARKER
+ SIG_MAGICDWORD,
+ SIG_UINT16(0xFFD7), // -41 "Greet"
+ SIG_UINT16(0xFFB4), // -76 "Say Good-bye"
+ SIG_UINT16(0x0001), // 1 "Tell about Tarna"
+ SIG_UINT16(0xFFE2), // -30 "Tell about Simani"
+ SIG_UINT16(0xFFB3), // -77 "Tell about Prisoner"
+ SIG_UINT16(0xFFDF), // -33 "Dispelled Leopard Lady"
+ SIG_UINT16(0xFFDE), // -34 "Tell about Leopard Lady"
+ SIG_UINT16(0xFFDD), // -35 "Tell about Leopard Lady"
+ SIG_UINT16(0xFFDC), // -36 "Tell about Leopard Lady"
+ SIG_UINT16(0xFFDB), // -37 "Tell about Village"
+ SIG_UINT16(0xFFD6), // -42 "Greet"
+ SIG_UINT16(0xFFB0), // -80 "Say Good-bye"
+ SIG_UINT16(0x03E7), // 999 END MARKER
+ SIG_ADDTOOFFSET(+2), // local[$aa][0]
+ SIG_END
+};
+
+static const uint16 qfg3PatchMissingPoints1[] = {
+ PATCH_ADDTOOFFSET(+14),
+ PATCH_UINT16(0xFFE1), // -31 "Tell about Initiation"
+ PATCH_UINT16(0xFFDE), // -34 "Tell about Leopard Lady"
+ PATCH_UINT16(0xFFDD), // -35 "Tell about Leopard Lady"
+ PATCH_UINT16(0xFFDC), // -36 "Tell about Leopard Lady"
+ PATCH_UINT16(0xFFDB), // -37 "Tell about Village"
+ PATCH_UINT16(0xFFD6), // -42 "Greet"
+ PATCH_UINT16(0xFFB0), // -80 "Say Good-bye"
+ PATCH_UINT16(0x03E7), // 999 END MARKER
+ PATCH_GETORIGINALBYTE(+28), // local[$aa][0].low
+ PATCH_GETORIGINALBYTE(+29), // local[$aa][0].high
+ PATCH_END
+};
+
+static const uint16 qfg3SignatureMissingPoints2a[] = {
+ SIG_MAGICDWORD,
+ 0x35, 0x00, // ldi 0
+ 0xb3, 0xaa, // sali local[$aa]
+ SIG_END
+};
+
+static const uint16 qfg3SignatureMissingPoints2b[] = {
+ SIG_MAGICDWORD,
+ 0x36, // push
+ 0x5b, 0x02, 0xaa, // lea local[$aa]
+ SIG_END
+};
+
+static const uint16 qfg3PatchMissingPoints2[] = {
+ PATCH_ADDTOOFFSET(+3),
+ 0xab, // local[$aa] ==> local[$ab]
+ PATCH_END
+};
+
+
// Partly WORKAROUND:
// During combat, the game is not properly throttled. That's because the game uses
// an inner loop for combat and does not iterate through the main loop.
@@ -2966,19 +3897,119 @@ static const uint16 qfg3PatchCombatSpeedThrottling2[] = {
PATCH_END
};
+// In room #750, when the hero enters from the top east path (room #755), it
+// could go out of the contained-access polygon bounds, and be able to travel
+// freely in the room.
+// The reason is that the cutoff y value (42) that determines whether the hero
+// enters from the top or bottom path is inaccurate: it's possible to enter the
+// top path from as low as y=45.
+// This patch changes the cutoff to be 50 which should be low enough.
+// It also changes the position in which the hero enters from the top east path
+// as the current location is hidden behind the tree.
+//
+// Applies to: English, French, German, Italian, Spanish and the GOG release.
+// Responsible method: enterEast::changeState (script 750)
+// Fixes bug #6693
+static const uint16 qfg3SignatureRoom750Bounds1[] = {
+ // (if (< (ego y?) 42)
+ 0x76, // push0 ("y")
+ 0x76, // push0
+ 0x81, 0x00, // lag global[0] (ego)
+ 0x4a, 0x04, // send 4
+ SIG_MAGICDWORD,
+ 0x36, // push
+ 0x35, 42, // ldi 42 <-- comparing ego.y with 42
+ 0x22, // lt?
+ SIG_END
+};
+
+static const uint16 qfg3PatchRoom750Bounds1[] = {
+ // (if (< (ego y?) 50)
+ PATCH_ADDTOOFFSET(+8),
+ 50, // 42 --> 50
+ PATCH_END
+};
+
+static const uint16 qfg3SignatureRoom750Bounds2[] = {
+ // (ego x: 294 y: 39)
+ 0x78, // push1 ("x")
+ 0x78, // push1
+ 0x38, SIG_UINT16(294), // pushi 294
+ 0x76, // push0 ("y")
+ 0x78, // push1
+ SIG_MAGICDWORD,
+ 0x39, 29, // pushi 29
+ 0x81, 0x00, // lag global[0] (ego)
+ 0x4a, 0x0c, // send 12
+ SIG_END
+};
+
+static const uint16 qfg3PatchRoom750Bounds2[] = {
+ // (ego x: 320 y: 39)
+ PATCH_ADDTOOFFSET(+3),
+ PATCH_UINT16(320), // 294 --> 320
+ PATCH_ADDTOOFFSET(+3),
+ 39, // 29 --> 39
+ PATCH_END
+};
+
+static const uint16 qfg3SignatureRoom750Bounds3[] = {
+ // (ego setMotion: MoveTo 282 29 self)
+ 0x38, SIG_SELECTOR16(setMotion), // pushi "setMotion" 0x133 in QfG3
+ 0x39, 0x04, // pushi 4
+ 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo
+ 0x36, // push
+ 0x38, SIG_UINT16(282), // pushi 282
+ SIG_MAGICDWORD,
+ 0x39, 29, // pushi 29
+ 0x7c, // pushSelf
+ 0x81, 0x00, // lag global[0] (ego)
+ 0x4a, 0x0c, // send 12
+ SIG_END
+};
+
+static const uint16 qfg3PatchRoom750Bounds3[] = {
+ // (ego setMotion: MoveTo 309 35 self)
+ PATCH_ADDTOOFFSET(+9),
+ PATCH_UINT16(309), // 282 --> 309
+ PATCH_ADDTOOFFSET(+1),
+ 35, // 29 --> 35
+ 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, 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 },
- { true, 550, "combat speed throttling script", 1, qfg3SignatureCombatSpeedThrottling1, qfg3PatchCombatSpeedThrottling1 },
- { true, 550, "combat speed throttling heap", 1, qfg3SignatureCombatSpeedThrottling2, qfg3PatchCombatSpeedThrottling2 },
+ { 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 },
+ { true, 285, "missing points for telling about initiation heap", 1, qfg3SignatureMissingPoints1, qfg3PatchMissingPoints1 },
+ { true, 285, "missing points for telling about initiation script", 1, qfg3SignatureMissingPoints2a, qfg3PatchMissingPoints2 },
+ { true, 285, "missing points for telling about initiation script", 1, qfg3SignatureMissingPoints2b, qfg3PatchMissingPoints2 },
+ { true, 550, "combat speed throttling script", 1, qfg3SignatureCombatSpeedThrottling1, qfg3PatchCombatSpeedThrottling1 },
+ { true, 550, "combat speed throttling heap", 1, qfg3SignatureCombatSpeedThrottling2, qfg3PatchCombatSpeedThrottling2 },
+ { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds1, qfg3PatchRoom750Bounds1 },
+ { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds2, qfg3PatchRoom750Bounds2 },
+ { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds3, qfg3PatchRoom750Bounds3 },
SCI_SIGNATUREENTRY_TERMINATOR
};
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Quest for Glory 4
+
+// script, description, signature patch
+static const SciScriptPatcherEntry qfg4Signatures[] = {
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
+ { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#endif
+
// ===========================================================================
// script 298 of sq4/floppy has an issue. object "nest" uses another property
// which isn't included in property count. We return 0 in that case, the game
@@ -3406,24 +4437,35 @@ static const uint16 sq1vgaSignatureSpiderDroidTiming[] = {
0x30, SIG_UINT16(0x0005), // bnt [further method code]
0x35, 0x00, // ldi 00
0x32, SIG_UINT16(0x0052), // jmp [super-call]
- 0x89, 0xa6, // lsg global[a6]
+ 0x89, 0xa6, // lsg global[a6] <-- flag gets set to 1 when ego went up the skeleton tail, when going down it's set to 2
0x35, 0x01, // ldi 01
0x1a, // eq?
- 0x30, SIG_UINT16(0x0012), // bnt [2nd code], in case global A6 <> 1
+ 0x30, SIG_UINT16(0x0012), // bnt [PChase set code], in case global A6 <> 1
0x81, 0xb5, // lag global[b5]
- 0x30, SIG_UINT16(0x000d), // bnt [2nd code], in case global B5 == 0
+ 0x30, SIG_UINT16(0x000d), // bnt [PChase set code], in case global B5 == 0
0x38, SIG_UINT16(0x008c), // pushi 008c
0x78, // push1
0x72, SIG_UINT16(0x1cb6), // lofsa 1CB6 (moveToPath)
0x36, // push
0x54, 0x06, // self 06
0x32, SIG_UINT16(0x0038), // jmp [super-call]
+ // PChase set call
0x81, 0xb5, // lag global[B5]
0x18, // not
0x30, SIG_UINT16(0x0032), // bnt [super-call], in case global B5 <> 0
+ // followed by:
+ // is spider in current room
+ // is global A6h == 2? -> set PChase
SIG_END
}; // 58 bytes)
+// Global A6h <> 1 (did NOT went up the skeleton)
+// Global B5h = 0 -> set PChase
+// Global B5h <> 0 -> do not do anything
+// Global A6h = 1 (did went up the skeleton)
+// Global B5h = 0 -> set PChase
+// Global B5h <> 0 -> set moveToPath
+
static const uint16 sq1vgaPatchSpiderDroidTiming[] = {
0x63, 0x4e, // pToa script
0x2f, 0x68, // bt [super-call]
@@ -3448,8 +4490,8 @@ static const uint16 sq1vgaPatchSpiderDroidTiming[] = {
0x65, 0x4c, // aTop cycleSpeed
0x65, 0x5e, // aTop moveSpeed
// new code end
- 0x89, 0xb5, // lsg global[B5]
- 0x31, 0x13, // bnt [2nd code chunk]
+ 0x81, 0xb5, // lag global[B5]
+ 0x31, 0x13, // bnt [PChase code chunk]
0x89, 0xa6, // lsg global[A6]
0x35, 0x01, // ldi 01
0x1a, // eq?
@@ -3531,6 +4573,103 @@ static const SciScriptPatcherEntry sq5Signatures[] = {
SCI_SIGNATUREENTRY_TERMINATOR
};
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Shivers
+
+// In room 35170, there is a CCTV control station with a joystick that must be
+// clicked and dragged to pan the camera. In order to enable dragging, on
+// mousedown, the vJoystick::handleEvent method calls vJoystick::doVerb(1),
+// which enables the drag functionality of the joystick. However,
+// vJoystick::handleEvent then makes a super call to ShiversProp::handleEvent,
+// which calls vJoystick::doVerb(). This second call, which fails to pass an
+// argument, causes an uninitialized read off the stack for the first parameter.
+// In SSCI, this happens to work because the uninitialized value on the stack
+// happens to be 1. Disabling the super call avoids the bad doVerb call without
+// any apparent ill effect.
+static const uint16 shiversSignatureJoystickFix[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0xa5), // pushi handleEvent
+ 0x78, // push1
+ 0x8f, 0x01, // lsp 1
+ 0x59, 0x02, // &rest 2
+ 0x57, 0x7f, SIG_UINT16(6), // super ShiversProp[7f], 6
+ SIG_END
+};
+
+static const uint16 shiversPatchJoystickFix[] = {
+ 0x48, // ret
+ PATCH_END
+};
+
+// script, description, signature patch
+static const SciScriptPatcherEntry shiversSignatures[] = {
+ { true, 35170, "fix CCTV joystick interaction", 1, shiversSignatureJoystickFix, shiversPatchJoystickFix },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#pragma mark -
+#pragma mark Space Quest 6
+
+// After the explosion in the Quarters of Deepship 86, the game tries to perform
+// a dramatic long fade, but does this with an unreasonably large number of
+// divisions which takes tens of seconds to finish (because transitions are not
+// CPU-dependent in ScummVM).
+static const uint16 sq6SlowTransitionSignature1[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x578), // pushi $0578
+ 0x51, 0x33, // class Styler
+ SIG_END
+};
+
+static const uint16 sq6SlowTransitionPatch1[] = {
+ 0x38, SIG_UINT16(500), // pushi 500
+ PATCH_END
+};
+
+// For whatever reason, SQ6 sets the default number of transition divisions to
+// be a much larger value at startup (200 vs 30) if it thinks it is running in
+// Windows. Room 410 (eulogy room) also unconditionally resets divisions to the
+// larger value.
+static const uint16 sq6SlowTransitionSignature2[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0xc8), // pushi $c8
+ 0x51, 0x33, // class Styler
+ SIG_END
+};
+
+static const uint16 sq6SlowTransitionPatch2[] = {
+ 0x38, SIG_UINT16(30), // pushi 30
+ PATCH_END
+};
+
+// script, description, signature patch
+static const SciScriptPatcherEntry sq6Signatures[] = {
+ { true, 0, "fix slow transitions", 1, sq6SlowTransitionSignature2, sq6SlowTransitionPatch2 },
+ { true, 15, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch },
+ { true, 22, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch },
+ { true, 410, "fix slow transitions", 1, sq6SlowTransitionSignature2, sq6SlowTransitionPatch2 },
+ { true, 460, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch },
+ { true, 500, "fix slow transitions", 1, sq6SlowTransitionSignature1, sq6SlowTransitionPatch1 },
+ { true, 510, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch },
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
+ { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#pragma mark -
+#pragma mark Torins Passage
+
+// script, description, signature patch
+static const SciScriptPatcherEntry torinSignatures[] = {
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
+ { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#endif
+
// =================================================================================
ScriptPatcher::ScriptPatcher() {
@@ -3756,7 +4895,7 @@ int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, cons
return findSignature(runtimeEntry->magicDWord, runtimeEntry->magicOffset, patchEntry->signatureData, patchEntry->description, scriptData, scriptSize);
}
-// Attention: Magic DWord is returns using platform specific byte order. This is done on purpose for performance.
+// Attention: Magic DWord is returned using platform specific byte order. This is done on purpose for performance.
void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescription, const uint16 *signatureData, bool magicDWordIncluded, uint32 &calculatedMagicDWord, int &calculatedMagicDWordOffset) {
Selector curSelector = -1;
int magicOffset;
@@ -3837,7 +4976,7 @@ void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescriptio
}
if (!magicDWordLeft) {
// Magic DWORD is now known, convert to platform specific byte order
- calculatedMagicDWord = READ_LE_UINT32(magicDWord);
+ calculatedMagicDWord = READ_UINT32(magicDWord);
}
}
break;
@@ -3862,7 +5001,8 @@ void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescriptio
magicDWord[4 - magicDWordLeft] = (byte)curValue;
magicDWordLeft--;
if (!magicDWordLeft) {
- calculatedMagicDWord = READ_LE_UINT32(magicDWord);
+ // Magic DWORD is now known, convert to platform specific byte order
+ calculatedMagicDWord = READ_UINT32(magicDWord);
}
}
break;
@@ -3961,15 +5101,25 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
case GID_FREDDYPHARKAS:
signatureTable = freddypharkasSignatures;
break;
+#ifdef ENABLE_SCI32
case GID_GK1:
signatureTable = gk1Signatures;
break;
+ case GID_GK2:
+ signatureTable = gk2Signatures;
+ break;
+#endif
case GID_KQ5:
signatureTable = kq5Signatures;
break;
case GID_KQ6:
signatureTable = kq6Signatures;
break;
+#ifdef ENABLE_SCI32
+ case GID_KQ7:
+ signatureTable = kq7Signatures;
+ break;
+#endif
case GID_LAURABOW:
signatureTable = laurabow1Signatures;
break;
@@ -3988,12 +5138,27 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
case GID_LSL6:
signatureTable = larry6Signatures;
break;
+#ifdef ENABLE_SCI32
+ case GID_LSL6HIRES:
+ signatureTable = larry6HiresSignatures;
+ break;
+#endif
case GID_MOTHERGOOSE256:
signatureTable = mothergoose256Signatures;
break;
+#ifdef ENABLE_SCI32
+ case GID_PHANTASMAGORIA:
+ signatureTable = phantasmagoriaSignatures;
+ break;
+#endif
case GID_PQ1:
signatureTable = pq1vgaSignatures;
break;
+#ifdef ENABLE_SCI32
+ case GID_PQ4:
+ signatureTable = pq4Signatures;
+ break;
+#endif
case GID_QFG1:
signatureTable = qfg1egaSignatures;
break;
@@ -4006,6 +5171,14 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
case GID_QFG3:
signatureTable = qfg3Signatures;
break;
+#ifdef ENABLE_SCI32
+ case GID_QFG4:
+ signatureTable = qfg4Signatures;
+ break;
+ case GID_SHIVERS:
+ signatureTable = shiversSignatures;
+ break;
+#endif
case GID_SQ1:
signatureTable = sq1vgaSignatures;
break;
@@ -4015,6 +5188,14 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
case GID_SQ5:
signatureTable = sq5Signatures;
break;
+#ifdef ENABLE_SCI32
+ case GID_SQ6:
+ signatureTable = sq6Signatures;
+ break;
+ case GID_TORIN:
+ signatureTable = torinSignatures;
+ break;
+#endif
default:
break;
}
diff --git a/engines/sci/engine/script_patches.h b/engines/sci/engine/script_patches.h
index 645e0946b3..f95806a3f3 100644
--- a/engines/sci/engine/script_patches.h
+++ b/engines/sci/engine/script_patches.h
@@ -35,13 +35,13 @@ namespace Sci {
#define SIG_BYTEMASK 0x00FF
#define SIG_MAGICDWORD 0xF000
#define SIG_CODE_ADDTOOFFSET 0xE000
-#define SIG_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | _offset_
+#define SIG_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | (_offset_)
#define SIG_CODE_SELECTOR16 0x9000
#define SIG_SELECTOR16(_selectorID_) SIG_CODE_SELECTOR16 | SELECTOR_##_selectorID_
#define SIG_CODE_SELECTOR8 0x8000
#define SIG_SELECTOR8(_selectorID_) SIG_CODE_SELECTOR8 | SELECTOR_##_selectorID_
#define SIG_CODE_UINT16 0x1000
-#define SIG_UINT16(_value_) SIG_CODE_UINT16 | (_value_ & 0xFF), (_value_ >> 8)
+#define SIG_UINT16(_value_) SIG_CODE_UINT16 | ((_value_) & 0xFF), ((_value_) >> 8)
#define SIG_CODE_BYTE 0x0000
#define PATCH_END SIG_END
@@ -49,17 +49,17 @@ namespace Sci {
#define PATCH_VALUEMASK SIG_VALUEMASK
#define PATCH_BYTEMASK SIG_BYTEMASK
#define PATCH_CODE_ADDTOOFFSET SIG_CODE_ADDTOOFFSET
-#define PATCH_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | _offset_
+#define PATCH_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | (_offset_)
#define PATCH_CODE_GETORIGINALBYTE 0xD000
-#define PATCH_GETORIGINALBYTE(_offset_) PATCH_CODE_GETORIGINALBYTE | _offset_
+#define PATCH_GETORIGINALBYTE(_offset_) PATCH_CODE_GETORIGINALBYTE | (_offset_)
#define PATCH_CODE_GETORIGINALBYTEADJUST 0xC000
-#define PATCH_GETORIGINALBYTEADJUST(_offset_, _adjustValue_) PATCH_CODE_GETORIGINALBYTEADJUST | _offset_, (uint16)(_adjustValue_)
+#define PATCH_GETORIGINALBYTEADJUST(_offset_, _adjustValue_) PATCH_CODE_GETORIGINALBYTEADJUST | (_offset_), (uint16)(_adjustValue_)
#define PATCH_CODE_SELECTOR16 SIG_CODE_SELECTOR16
#define PATCH_SELECTOR16(_selectorID_) SIG_CODE_SELECTOR16 | SELECTOR_##_selectorID_
#define PATCH_CODE_SELECTOR8 SIG_CODE_SELECTOR8
#define PATCH_SELECTOR8(_selectorID_) SIG_CODE_SELECTOR8 | SELECTOR_##_selectorID_
#define PATCH_CODE_UINT16 SIG_CODE_UINT16
-#define PATCH_UINT16(_value_) SIG_CODE_UINT16 | (_value_ & 0xFF), (_value_ >> 8)
+#define PATCH_UINT16(_value_) SIG_CODE_UINT16 | ((_value_) & 0xFF), ((_value_) >> 8)
#define PATCH_CODE_BYTE SIG_CODE_BYTE
// defines maximum scratch area for getting original bytes from unpatched script data
diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp
index f0157a6569..54b925a1b6 100644
--- a/engines/sci/engine/scriptdebug.cpp
+++ b/engines/sci/engine/scriptdebug.cpp
@@ -68,7 +68,7 @@ const char *opcodeNames[] = {
#endif // REDUCE_MEMORY_USAGE
// Disassembles one command from the heap, returns address of next command or 0 if a ret was encountered.
-reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printBytecode) {
+reg_t disassemble(EngineState *s, reg32_t pos, reg_t objAddr, bool printBWTag, bool printBytecode) {
SegmentObj *mobj = s->_segMan->getSegment(pos.getSegment(), SEG_TYPE_SCRIPT);
Script *script_entity = NULL;
const byte *scr;
@@ -99,17 +99,6 @@ reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printByteco
debugN("%04x:%04x: ", PRINT_REG(pos));
- if (opcode == op_pushSelf) { // 0x3e (62)
- if ((opsize & 1) && g_sci->getGameId() != GID_FANMADE) {
- // Debug opcode op_file
- debugN("file \"%s\"\n", scr + pos.getOffset() + 1); // +1: op_pushSelf size
- retval.incOffset(bytecount - 1);
- return retval;
- }
- }
-
- opsize &= 1; // byte if true, word if false
-
if (printBytecode) {
if (pos.getOffset() + bytecount > scr_size) {
warning("Operation arguments extend beyond end of script");
@@ -123,13 +112,24 @@ reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printByteco
debugN(" ");
}
+ opsize &= 1; // byte if true, word if false
+
if (printBWTag)
debugN("[%c] ", opsize ? 'B' : 'W');
+ if (opcode == op_pushSelf && opsize && g_sci->getGameId() != GID_FANMADE) { // 0x3e (62)
+ // Debug opcode op_file
+ debugN("file \"%s\"\n", scr + pos.getOffset() + 1); // +1: op_pushSelf size
+ retval.incOffset(bytecount - 1);
+ return retval;
+ }
+
#ifndef REDUCE_MEMORY_USAGE
- debugN("%s", opcodeNames[opcode]);
+ debugN("%-5s", opcodeNames[opcode]);
#endif
+ static const char *defaultSeparator = "\t\t; ";
+
i = 0;
while (g_sci->_opcode_formats[opcode][i]) {
switch (g_sci->_opcode_formats[opcode][i++]) {
@@ -140,14 +140,21 @@ reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printByteco
case Script_SByte:
case Script_Byte:
param_value = scr[retval.getOffset()];
- debugN(" %02x", scr[retval.getOffset()]);
+ debugN("\t%02x", param_value);
+ if (param_value > 9) {
+ debugN("%s%u", defaultSeparator, param_value);
+ }
retval.incOffset(1);
break;
case Script_Word:
case Script_SWord:
param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
- debugN(" %04x", READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]));
+ debugN("\t%04x", param_value);
+ if (param_value > 9) {
+ debugN("%s%u", defaultSeparator, param_value);
+ }
+
retval.incOffset(2);
break;
@@ -166,12 +173,38 @@ reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printByteco
retval.incOffset(2);
}
- if (opcode == op_callk)
- debugN(" %s[%x]", (param_value < kernel->_kernelFuncs.size()) ?
+ if (opcode == op_callk) {
+ debugN("\t%s[%x],", (param_value < kernel->_kernelFuncs.size()) ?
((param_value < kernel->getKernelNamesSize()) ? kernel->getKernelName(param_value).c_str() : "[Unknown(postulated)]")
: "<invalid>", param_value);
- else
- debugN(opsize ? " %02x" : " %04x", param_value);
+ } else if (opcode == op_super) {
+ Object *obj;
+ if (objAddr != NULL_REG && (obj = s->_segMan->getObject(objAddr)) != nullptr) {
+ debugN("\t%s", s->_segMan->getObjectName(obj->getSuperClassSelector()));
+ debugN(opsize ? "[%02x]" : "[%04x]", param_value);
+ } else {
+ debugN(opsize ? "\t%02x" : "\t%04x", param_value);
+ }
+
+ debugN(",");
+ } else {
+ const char *separator = defaultSeparator;
+
+ debugN(opsize ? "\t%02x" : "\t%04x", param_value);
+ if (param_value > 9) {
+ debugN("%s%u", separator, param_value);
+ separator = ", ";
+ }
+
+ if (param_value >= 0x20 && param_value <= 0x7e) {
+ debugN("%s'%c'", separator, param_value);
+ separator = ", ";
+ }
+
+ if (opcode == op_pushi && param_value < kernel->getSelectorNamesSize()) {
+ debugN("%s%s", separator, kernel->getSelectorName(param_value).c_str());
+ }
+ }
break;
@@ -183,19 +216,27 @@ reg_t disassemble(EngineState *s, reg32_t pos, bool printBWTag, bool printByteco
param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
retval.incOffset(2);
}
- debugN(opsize ? " %02x" : " %04x", param_value);
+
+ if (opcode == op_lofsa || opcode == op_lofss) {
+ reg_t addr = make_reg(pos.getSegment(), findOffset(param_value, script_entity, pos.getOffset()));
+ debugN("\t%s", s->_segMan->getObjectName(addr));
+ debugN(opsize ? "[%02x]" : "[%04x]", param_value);
+ } else {
+ debugN(opsize ? "\t%02x" : "\t%04x", param_value);
+ }
+
break;
case Script_SRelative:
if (opsize) {
int8 offset = (int8)scr[retval.getOffset()];
retval.incOffset(1);
- debugN(" %02x [%04x]", 0xff & offset, 0xffff & (retval.getOffset() + offset));
+ debugN("\t%02x [%04x]", 0xff & offset, 0xffff & (retval.getOffset() + offset));
}
else {
int16 offset = (int16)READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
retval.incOffset(2);
- debugN(" %04x [%04x]", 0xffff & offset, 0xffff & (retval.getOffset() + offset));
+ debugN("\t%04x [%04x]", 0xffff & offset, 0xffff & (retval.getOffset() + offset));
}
break;
@@ -396,7 +437,7 @@ void SciEngine::scriptDebug() {
}
debugN("Step #%d\n", s->scriptStepCounter);
- disassemble(s, s->xs->addr.pc, false, true);
+ disassemble(s, s->xs->addr.pc, s->xs->objp, false, true);
if (_debugState.runningStep) {
_debugState.runningStep--;
@@ -499,7 +540,7 @@ void Kernel::dumpScriptClass(char *data, int seeker, int objsize) {
void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) {
int objectctr[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- unsigned int _seeker = 0;
+ uint32 _seeker = 0;
Resource *script = _resMan->findResource(ResourceId(kResourceTypeScript, scriptNumber), 0);
if (!script) {
@@ -510,7 +551,7 @@ void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) {
while (_seeker < script->size) {
int objType = (int16)READ_SCI11ENDIAN_UINT16(script->data + _seeker);
int objsize;
- unsigned int seeker = _seeker + 4;
+ uint32 seeker = _seeker + 4;
if (!objType) {
debugN("End of script object (#0) encountered.\n");
@@ -631,6 +672,22 @@ bool SciEngine::checkExportBreakpoint(uint16 script, uint16 pubfunct) {
return false;
}
+bool SciEngine::checkAddressBreakpoint(const reg32_t &address) {
+ if (_debugState._activeBreakpointTypes & BREAK_ADDRESS) {
+ Common::List<Breakpoint>::const_iterator bp;
+ for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) {
+ if (bp->type == BREAK_ADDRESS && bp->regAddress == address) {
+ _console->debugPrintf("Break at %04x:%04x\n", PRINT_REG(address));
+ _debugState.debugging = true;
+ _debugState.breakpointWasHit = true;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
void debugSelectorCall(reg_t send_obj, Selector selector, int argc, StackPtr argp, ObjVarRef &varp, reg_t funcp, SegManager *segMan, SelectorType selectorType) {
int activeBreakpointTypes = g_sci->_debugState._activeBreakpointTypes;
const char *objectName = segMan->getObjectName(send_obj);
@@ -741,13 +798,13 @@ void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunction *ke
switch (mobj->getType()) {
case SEG_TYPE_HUNK:
{
- HunkTable *ht = (HunkTable *)mobj;
+ HunkTable &ht = *(HunkTable *)mobj;
int index = argv[parmNr].getOffset();
- if (ht->isValidEntry(index)) {
+ if (ht.isValidEntry(index)) {
// NOTE: This ", deleted" isn't as useful as it could
// be because it prints the status _after_ the kernel
// call.
- debugN(" ('%s' hunk%s)", ht->_table[index].type, ht->_table[index].mem ? "" : ", deleted");
+ debugN(" ('%s' hunk%s)", ht[index].type, ht[index].mem ? "" : ", deleted");
} else
debugN(" (INVALID hunk ref)");
break;
diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp
index 8090b1861d..159d3170f8 100644
--- a/engines/sci/engine/seg_manager.cpp
+++ b/engines/sci/engine/seg_manager.cpp
@@ -42,7 +42,7 @@ SegManager::SegManager(ResourceManager *resMan, ScriptPatcher *scriptPatcher)
#ifdef ENABLE_SCI32
_arraysSegId = 0;
- _stringSegId = 0;
+ _bitmapSegId = 0;
#endif
createClassTable();
@@ -71,7 +71,7 @@ void SegManager::resetSegMan() {
#ifdef ENABLE_SCI32
_arraysSegId = 0;
- _stringSegId = 0;
+ _bitmapSegId = 0;
#endif
// Reinitialize class table
@@ -86,9 +86,8 @@ void SegManager::initSysStrings() {
_parserPtr = make_reg(_saveDirPtr.getSegment(), _saveDirPtr.getOffset() + 256);
#ifdef ENABLE_SCI32
} else {
- SciString *saveDirString = allocateString(&_saveDirPtr);
- saveDirString->setSize(256);
- saveDirString->setValue(0, 0);
+ SciArray *saveDirString = allocateArray(kArrayTypeString, 256, &_saveDirPtr);
+ saveDirString->byteAt(0) = '\0';
_parserPtr = NULL_REG; // no SCI2 game had a parser
#endif
@@ -247,9 +246,9 @@ Object *SegManager::getObject(reg_t pos) const {
if (mobj != NULL) {
if (mobj->getType() == SEG_TYPE_CLONES) {
- CloneTable *ct = (CloneTable *)mobj;
- if (ct->isValidEntry(pos.getOffset()))
- obj = &(ct->_table[pos.getOffset()]);
+ CloneTable &ct = *(CloneTable *)mobj;
+ if (ct.isValidEntry(pos.getOffset()))
+ obj = &(ct[pos.getOffset()]);
else
warning("getObject(): Trying to get an invalid object");
} else if (mobj->getType() == SEG_TYPE_SCRIPT) {
@@ -313,7 +312,7 @@ reg_t SegManager::findObjectByName(const Common::String &name, int index) {
} else if (mobj->getType() == SEG_TYPE_CLONES) {
// It's clone table, scan all objects in it
const CloneTable *ct = (const CloneTable *)mobj;
- for (uint idx = 0; idx < ct->_table.size(); ++idx) {
+ for (uint idx = 0; idx < ct->size(); ++idx) {
if (!ct->isValidEntry(idx))
continue;
@@ -404,7 +403,7 @@ reg_t SegManager::allocateHunkEntry(const char *hunk_type, int size) {
offset = table->allocEntry();
reg_t addr = make_reg(_hunksSegId, offset);
- Hunk *h = &(table->_table[offset]);
+ Hunk *h = &table->at(offset);
if (!h)
return NULL_REG;
@@ -424,7 +423,7 @@ byte *SegManager::getHunkPointer(reg_t addr) {
return NULL;
}
- return (byte *)ht->_table[addr.getOffset()].mem;
+ return (byte *)ht->at(addr.getOffset()).mem;
}
Clone *SegManager::allocateClone(reg_t *addr) {
@@ -439,7 +438,7 @@ Clone *SegManager::allocateClone(reg_t *addr) {
offset = table->allocEntry();
*addr = make_reg(_clonesSegId, offset);
- return &(table->_table[offset]);
+ return &table->at(offset);
}
List *SegManager::allocateList(reg_t *addr) {
@@ -453,7 +452,7 @@ List *SegManager::allocateList(reg_t *addr) {
offset = table->allocEntry();
*addr = make_reg(_listsSegId, offset);
- return &(table->_table[offset]);
+ return &table->at(offset);
}
Node *SegManager::allocateNode(reg_t *addr) {
@@ -467,7 +466,7 @@ Node *SegManager::allocateNode(reg_t *addr) {
offset = table->allocEntry();
*addr = make_reg(_nodesSegId, offset);
- return &(table->_table[offset]);
+ return &table->at(offset);
}
reg_t SegManager::newNode(reg_t value, reg_t key) {
@@ -486,14 +485,14 @@ List *SegManager::lookupList(reg_t addr) {
return NULL;
}
- ListTable *lt = (ListTable *)_heap[addr.getSegment()];
+ ListTable &lt = *(ListTable *)_heap[addr.getSegment()];
- if (!lt->isValidEntry(addr.getOffset())) {
+ if (!lt.isValidEntry(addr.getOffset())) {
error("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr));
return NULL;
}
- return &(lt->_table[addr.getOffset()]);
+ return &(lt[addr.getOffset()]);
}
Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) {
@@ -507,9 +506,9 @@ Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) {
return NULL;
}
- NodeTable *nt = (NodeTable *)_heap[addr.getSegment()];
+ NodeTable &nt = *(NodeTable *)_heap[addr.getSegment()];
- if (!nt->isValidEntry(addr.getOffset())) {
+ if (!nt.isValidEntry(addr.getOffset())) {
if (!stopOnDiscarded)
return NULL;
@@ -517,7 +516,7 @@ Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) {
return NULL;
}
- return &(nt->_table[addr.getOffset()]);
+ return &(nt[addr.getOffset()]);
}
SegmentRef SegManager::dereference(reg_t pointer) {
@@ -797,7 +796,7 @@ size_t SegManager::strlen(reg_t str) {
}
-Common::String SegManager::getString(reg_t pointer, int entries) {
+Common::String SegManager::getString(reg_t pointer) {
Common::String ret;
if (pointer.isNull())
return ret; // empty text
@@ -807,10 +806,7 @@ Common::String SegManager::getString(reg_t pointer, int entries) {
warning("SegManager::getString(): Attempt to dereference invalid pointer %04x:%04x", PRINT_REG(pointer));
return ret;
}
- if (entries > src_r.maxSize) {
- warning("Trying to dereference pointer %04x:%04x beyond end of segment", PRINT_REG(pointer));
- return ret;
- }
+
if (src_r.isRaw)
ret = (char *)src_r.raw;
else {
@@ -861,7 +857,10 @@ bool SegManager::freeDynmem(reg_t addr) {
}
#ifdef ENABLE_SCI32
-SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) {
+#pragma mark -
+#pragma mark Arrays
+
+SciArray *SegManager::allocateArray(SciArrayType type, uint16 size, reg_t *addr) {
ArrayTable *table;
int offset;
@@ -873,78 +872,94 @@ SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) {
offset = table->allocEntry();
*addr = make_reg(_arraysSegId, offset);
- return &(table->_table[offset]);
+
+ SciArray *array = &table->at(offset);
+ array->setType(type);
+ array->resize(size);
+ return array;
}
-SciArray<reg_t> *SegManager::lookupArray(reg_t addr) {
+SciArray *SegManager::lookupArray(reg_t addr) {
if (_heap[addr.getSegment()]->getType() != SEG_TYPE_ARRAY)
error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr));
- ArrayTable *arrayTable = (ArrayTable *)_heap[addr.getSegment()];
+ ArrayTable &arrayTable = *(ArrayTable *)_heap[addr.getSegment()];
- if (!arrayTable->isValidEntry(addr.getOffset()))
+ if (!arrayTable.isValidEntry(addr.getOffset()))
error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr));
- return &(arrayTable->_table[addr.getOffset()]);
+ return &(arrayTable[addr.getOffset()]);
}
void SegManager::freeArray(reg_t addr) {
if (_heap[addr.getSegment()]->getType() != SEG_TYPE_ARRAY)
error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr));
- ArrayTable *arrayTable = (ArrayTable *)_heap[addr.getSegment()];
+ ArrayTable &arrayTable = *(ArrayTable *)_heap[addr.getSegment()];
- if (!arrayTable->isValidEntry(addr.getOffset()))
+ if (!arrayTable.isValidEntry(addr.getOffset()))
error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr));
- arrayTable->_table[addr.getOffset()].destroy();
- arrayTable->freeEntry(addr.getOffset());
+ arrayTable.freeEntry(addr.getOffset());
+}
+
+bool SegManager::isArray(reg_t addr) const {
+ return addr.getSegment() == _arraysSegId;
}
-SciString *SegManager::allocateString(reg_t *addr) {
- StringTable *table;
+#pragma mark -
+#pragma mark Bitmaps
+
+SciBitmap *SegManager::allocateBitmap(reg_t *addr, const int16 width, const int16 height, const uint8 skipColor, const int16 originX, const int16 originY, const int16 xResolution, const int16 yResolution, const uint32 paletteSize, const bool remap, const bool gc) {
+ BitmapTable *table;
int offset;
- if (!_stringSegId) {
- table = (StringTable *)allocSegment(new StringTable(), &(_stringSegId));
- } else
- table = (StringTable *)_heap[_stringSegId];
+ if (!_bitmapSegId) {
+ table = (BitmapTable *)allocSegment(new BitmapTable(), &(_bitmapSegId));
+ } else {
+ table = (BitmapTable *)_heap[_bitmapSegId];
+ }
offset = table->allocEntry();
- *addr = make_reg(_stringSegId, offset);
- return &(table->_table[offset]);
+ *addr = make_reg(_bitmapSegId, offset);
+ SciBitmap &bitmap = table->at(offset);
+
+ bitmap.create(width, height, skipColor, originX, originY, xResolution, yResolution, paletteSize, remap, gc);
+
+ return &bitmap;
}
-SciString *SegManager::lookupString(reg_t addr) {
- if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING)
- error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
+SciBitmap *SegManager::lookupBitmap(const reg_t addr) {
+ if (_heap[addr.getSegment()]->getType() != SEG_TYPE_BITMAP)
+ error("Attempt to use non-bitmap %04x:%04x as bitmap", PRINT_REG(addr));
- StringTable *stringTable = (StringTable *)_heap[addr.getSegment()];
+ BitmapTable &bitmapTable = *(BitmapTable *)_heap[addr.getSegment()];
- if (!stringTable->isValidEntry(addr.getOffset()))
- error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
+ if (!bitmapTable.isValidEntry(addr.getOffset()))
+ error("Attempt to use invalid entry %04x:%04x as bitmap", PRINT_REG(addr));
- return &(stringTable->_table[addr.getOffset()]);
+ return &(bitmapTable.at(addr.getOffset()));
}
-void SegManager::freeString(reg_t addr) {
- if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING)
- error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
+void SegManager::freeBitmap(const reg_t addr) {
+ if (_heap[addr.getSegment()]->getType() != SEG_TYPE_BITMAP)
+ error("Attempt to free non-bitmap %04x:%04x as bitmap", PRINT_REG(addr));
- StringTable *stringTable = (StringTable *)_heap[addr.getSegment()];
+ BitmapTable &bitmapTable = *(BitmapTable *)_heap[addr.getSegment()];
- if (!stringTable->isValidEntry(addr.getOffset()))
- error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
+ if (!bitmapTable.isValidEntry(addr.getOffset()))
+ error("Attempt to free invalid entry %04x:%04x as bitmap", PRINT_REG(addr));
- stringTable->_table[addr.getOffset()].destroy();
- stringTable->freeEntry(addr.getOffset());
+ bitmapTable.freeEntry(addr.getOffset());
}
+#pragma mark -
+
#endif
void SegManager::createClassTable() {
- Resource *vocab996 = _resMan->findResource(ResourceId(kResourceTypeVocab, 996), 1);
+ Resource *vocab996 = _resMan->findResource(ResourceId(kResourceTypeVocab, 996), false);
if (!vocab996)
error("SegManager: failed to open vocab 996");
@@ -958,8 +973,6 @@ void SegManager::createClassTable() {
_classTable[classNr].reg = NULL_REG;
_classTable[classNr].script = scriptNr;
}
-
- _resMan->unlockResource(vocab996);
}
reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, uint16 callerSegment) {
diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h
index 9da7e58770..e3ccb9ae5f 100644
--- a/engines/sci/engine/seg_manager.h
+++ b/engines/sci/engine/seg_manager.h
@@ -29,6 +29,9 @@
#include "sci/engine/vm.h"
#include "sci/engine/vm_types.h"
#include "sci/engine/segment.h"
+#ifdef ENABLE_SCI32
+#include "sci/graphics/celobj32.h" // kLowResX, kLowResY
+#endif
namespace Sci {
@@ -301,11 +304,10 @@ public:
* Return the string referenced by pointer.
* pointer can point to either a raw or non-raw segment.
* @param pointer The pointer to dereference
- * @parm entries The number of values expected (for checking)
* @return The string referenced, or an empty string if not enough
* entries were available.
*/
- Common::String getString(reg_t pointer, int entries = 0);
+ Common::String getString(reg_t pointer);
/**
@@ -430,13 +432,14 @@ public:
reg_t getParserPtr() const { return _parserPtr; }
#ifdef ENABLE_SCI32
- SciArray<reg_t> *allocateArray(reg_t *addr);
- SciArray<reg_t> *lookupArray(reg_t addr);
+ SciArray *allocateArray(SciArrayType type, uint16 size, reg_t *addr);
+ SciArray *lookupArray(reg_t addr);
void freeArray(reg_t addr);
- SciString *allocateString(reg_t *addr);
- SciString *lookupString(reg_t addr);
- void freeString(reg_t addr);
- SegmentId getStringSegmentId() { return _stringSegId; }
+ bool isArray(reg_t addr) const;
+
+ SciBitmap *allocateBitmap(reg_t *addr, const int16 width, const int16 height, const uint8 skipColor = kDefaultSkipColor, const int16 originX = 0, const int16 originY = 0, const int16 xResolution = kLowResX, const int16 yResolution = kLowResY, const uint32 paletteSize = 0, const bool remap = false, const bool gc = true);
+ SciBitmap *lookupBitmap(reg_t addr);
+ void freeBitmap(reg_t addr);
#endif
const Common::Array<SegmentObj *> &getSegments() const { return _heap; }
@@ -461,7 +464,7 @@ private:
#ifdef ENABLE_SCI32
SegmentId _arraysSegId;
- SegmentId _stringSegId;
+ SegmentId _bitmapSegId;
#endif
public:
diff --git a/engines/sci/engine/segment.cpp b/engines/sci/engine/segment.cpp
index bb90698e6a..fffa7f4d7e 100644
--- a/engines/sci/engine/segment.cpp
+++ b/engines/sci/engine/segment.cpp
@@ -67,8 +67,8 @@ SegmentObj *SegmentObj::createSegmentObj(SegmentType type) {
case SEG_TYPE_ARRAY:
mem = new ArrayTable();
break;
- case SEG_TYPE_STRING:
- mem = new StringTable();
+ case SEG_TYPE_BITMAP:
+ mem = new BitmapTable();
break;
#endif
default:
@@ -97,7 +97,7 @@ Common::Array<reg_t> CloneTable::listAllOutgoingReferences(reg_t addr) const {
error("Unexpected request for outgoing references from clone at %04x:%04x", PRINT_REG(addr));
}
- const Clone *clone = &(_table[addr.getOffset()]);
+ const Clone *clone = &at(addr.getOffset());
// Emit all member variables (including references to the 'super' delegate)
for (uint i = 0; i < clone->getVarCount(); i++)
@@ -112,7 +112,7 @@ Common::Array<reg_t> CloneTable::listAllOutgoingReferences(reg_t addr) const {
void CloneTable::freeAtAddress(SegManager *segMan, reg_t addr) {
#ifdef GC_DEBUG
- Object *victim_obj = &(_table[addr.getOffset()]);
+ Object *victim_obj = &at(addr.getOffset());
if (!(victim_obj->_flags & OBJECT_FLAG_FREED))
warning("[GC] Clone %04x:%04x not reachable and not freed (freeing now)", PRINT_REG(addr));
@@ -208,7 +208,7 @@ Common::Array<reg_t> ListTable::listAllOutgoingReferences(reg_t addr) const {
error("Invalid list referenced for outgoing references: %04x:%04x", PRINT_REG(addr));
}
- const List *list = &(_table[addr.getOffset()]);
+ const List *list = &at(addr.getOffset());
tmp.push_back(list->first);
tmp.push_back(list->last);
@@ -225,7 +225,7 @@ Common::Array<reg_t> NodeTable::listAllOutgoingReferences(reg_t addr) const {
if (!isValidEntry(addr.getOffset())) {
error("Invalid node referenced for outgoing references: %04x:%04x", PRINT_REG(addr));
}
- const Node *node = &(_table[addr.getOffset()]);
+ const Node *node = &at(addr.getOffset());
// We need all four here. Can't just stick with 'pred' OR 'succ' because node operations allow us
// to walk around from any given node
@@ -251,63 +251,39 @@ SegmentRef DynMem::dereference(reg_t pointer) {
SegmentRef ArrayTable::dereference(reg_t pointer) {
SegmentRef ret;
- ret.isRaw = false;
- ret.maxSize = _table[pointer.getOffset()].getSize() * 2;
- ret.reg = _table[pointer.getOffset()].getRawData();
- return ret;
-}
-void ArrayTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) {
- _table[sub_addr.getOffset()].destroy();
- freeEntry(sub_addr.getOffset());
+ SciArray &array = at(pointer.getOffset());
+ const bool isRaw = array.getType() == kArrayTypeByte || array.getType() == kArrayTypeString;
+
+ ret.isRaw = isRaw;
+ ret.maxSize = array.byteSize();
+ if (isRaw) {
+ ret.raw = (byte *)array.getRawData();
+ } else {
+ ret.reg = (reg_t *)array.getRawData();
+ }
+ return ret;
}
Common::Array<reg_t> ArrayTable::listAllOutgoingReferences(reg_t addr) const {
- Common::Array<reg_t> tmp;
+ Common::Array<reg_t> refs;
if (!isValidEntry(addr.getOffset())) {
- error("Invalid array referenced for outgoing references: %04x:%04x", PRINT_REG(addr));
+ // Scripts may still hold references to array memory that has been
+ // explicitly freed; ignore these references
+ return refs;
}
- const SciArray<reg_t> *array = &(_table[addr.getOffset()]);
-
- for (uint32 i = 0; i < array->getSize(); i++) {
- reg_t value = array->getValue(i);
- if (value.getSegment() != 0)
- tmp.push_back(value);
+ SciArray &array = const_cast<SciArray &>(at(addr.getOffset()));
+ if (array.getType() == kArrayTypeID || array.getType() == kArrayTypeInt16) {
+ for (uint16 i = 0; i < array.size(); ++i) {
+ const reg_t value = array.getAsID(i);
+ if (value.isPointer()) {
+ refs.push_back(value);
+ }
+ }
}
- return tmp;
-}
-
-Common::String SciString::toString() const {
- if (_type != 3)
- error("SciString::toString(): Array is not a string");
-
- Common::String string;
- for (uint32 i = 0; i < _size && _data[i] != 0; i++)
- string += _data[i];
-
- return string;
-}
-
-void SciString::fromString(const Common::String &string) {
- if (_type != 3)
- error("SciString::fromString(): Array is not a string");
-
- setSize(string.size() + 1);
-
- for (uint32 i = 0; i < string.size(); i++)
- _data[i] = string[i];
-
- _data[string.size()] = 0;
-}
-
-SegmentRef StringTable::dereference(reg_t pointer) {
- SegmentRef ret;
- ret.isRaw = true;
- ret.maxSize = _table[pointer.getOffset()].getSize();
- ret.raw = (byte *)_table[pointer.getOffset()].getRawData();
- return ret;
+ return refs;
}
#endif
diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h
index de7f60ac16..c5295c5523 100644
--- a/engines/sci/engine/segment.h
+++ b/engines/sci/engine/segment.h
@@ -24,7 +24,7 @@
#define SCI_ENGINE_SEGMENT_H
#include "common/serializer.h"
-
+#include "common/str.h"
#include "sci/engine/object.h"
#include "sci/engine/vm.h"
#include "sci/engine/vm_types.h" // for reg_t
@@ -70,7 +70,8 @@ enum SegmentType {
#ifdef ENABLE_SCI32
SEG_TYPE_ARRAY = 11,
- SEG_TYPE_STRING = 12,
+ // 12 used to be string, now obsolete
+ SEG_TYPE_BITMAP = 13,
#endif
SEG_TYPE_MAX // For sanity checking
@@ -199,33 +200,57 @@ struct Node {
struct List {
reg_t first;
reg_t last;
+
+#ifdef ENABLE_SCI32
+ /**
+ * The next node for each level of recursion during iteration over this list
+ * by kListEachElementDo.
+ */
+ reg_t nextNodes[10];
+
+ /**
+ * The current level of recursion of kListEachElementDo for this list.
+ */
+ int numRecursions;
+
+ List() : numRecursions(0) {}
+#endif
};
struct Hunk {
void *mem;
- unsigned int size;
+ uint32 size;
const char *type;
};
template<typename T>
struct SegmentObjTable : public SegmentObj {
typedef T value_type;
- struct Entry : public T {
+ struct Entry {
+ T *data;
int next_free; /* Only used for free entries */
};
enum { HEAPENTRY_INVALID = -1 };
-
int first_free; /**< Beginning of a singly linked list for entries */
int entries_used; /**< Statistical information */
- Common::Array<Entry> _table;
+ typedef Common::Array<Entry> ArrayType;
+ ArrayType _table;
public:
SegmentObjTable(SegmentType type) : SegmentObj(type) {
initTable();
}
+ ~SegmentObjTable() {
+ for (uint i = 0; i < _table.size(); i++) {
+ if (isValidEntry(i)) {
+ freeEntry(i);
+ }
+ }
+ }
+
void initTable() {
entries_used = 0;
first_free = HEAPENTRY_INVALID;
@@ -239,10 +264,13 @@ public:
first_free = _table[oldff].next_free;
_table[oldff].next_free = oldff;
+ assert(_table[oldff].data == nullptr);
+ _table[oldff].data = new T;
return oldff;
} else {
uint newIdx = _table.size();
_table.push_back(Entry());
+ _table.back().data = new T;
_table[newIdx].next_free = newIdx; // Tag as 'valid'
return newIdx;
}
@@ -261,6 +289,8 @@ public:
::error("Table::freeEntry: Attempt to release invalid table index %d", idx);
_table[idx].next_free = first_free;
+ delete _table[idx].data;
+ _table[idx].data = nullptr;
first_free = idx;
entries_used--;
}
@@ -272,6 +302,14 @@ public:
tmp.push_back(make_reg(segId, i));
return tmp;
}
+
+ uint size() const { return _table.size(); }
+
+ T &at(uint index) { return *_table[index].data; }
+ const T &at(uint index) const { return *_table[index].data; }
+
+ T &operator[](uint index) { return at(index); }
+ const T &operator[](uint index) const { return at(index); }
};
@@ -323,13 +361,13 @@ struct HunkTable : public SegmentObjTable<Hunk> {
}
void freeEntryContents(int idx) {
- free(_table[idx].mem);
- _table[idx].mem = 0;
+ free(at(idx).mem);
+ at(idx).mem = 0;
}
virtual void freeEntry(int idx) {
- SegmentObjTable<Hunk>::freeEntry(idx);
freeEntryContents(idx);
+ SegmentObjTable<Hunk>::freeEntry(idx);
}
virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) {
@@ -370,144 +408,783 @@ public:
#ifdef ENABLE_SCI32
-template<typename T>
-class SciArray {
+#pragma mark -
+#pragma mark Arrays
+
+enum SciArrayType {
+ kArrayTypeInt16 = 0,
+ kArrayTypeID = 1,
+ kArrayTypeByte = 2,
+ kArrayTypeString = 3,
+ // Type 4 was for 32-bit integers; never used
+ kArrayTypeInvalid = 5
+};
+
+enum SciArrayTrim {
+ kArrayTrimRight = 1, ///< Trim whitespace after the last non-whitespace character
+ kArrayTrimCenter = 2, ///< Trim whitespace between non-whitespace characters
+ kArrayTrimLeft = 4 ///< Trim whitespace before the first non-whitespace character
+};
+
+class SciArray : public Common::Serializable {
public:
- SciArray() : _type(-1), _data(NULL), _size(0), _actualSize(0) { }
+ SciArray() :
+ _type(kArrayTypeInvalid),
+ _size(0),
+ _data(nullptr) {}
- SciArray(const SciArray<T> &array) {
+ SciArray(const SciArray &array) {
_type = array._type;
_size = array._size;
- _actualSize = array._actualSize;
- _data = new T[_actualSize];
+ _elementSize = array._elementSize;
+ _data = malloc(_elementSize * _size);
assert(_data);
- memcpy(_data, array._data, _size * sizeof(T));
+ memcpy(_data, array._data, _elementSize * _size);
}
- SciArray<T>& operator=(const SciArray<T> &array) {
+ SciArray &operator=(const SciArray &array) {
if (this == &array)
return *this;
- delete[] _data;
+ free(_data);
_type = array._type;
_size = array._size;
- _actualSize = array._actualSize;
- _data = new T[_actualSize];
+ _elementSize = array._elementSize;
+ _data = malloc(_elementSize * _size);
assert(_data);
- memcpy(_data, array._data, _size * sizeof(T));
+ memcpy(_data, array._data, _elementSize * _size);
return *this;
}
virtual ~SciArray() {
- destroy();
+ free(_data);
+ _size = 0;
+ _type = kArrayTypeInvalid;
}
- virtual void destroy() {
- delete[] _data;
- _data = NULL;
- _type = -1;
- _size = _actualSize = 0;
- }
+ void saveLoadWithSerializer(Common::Serializer &s);
- void setType(byte type) {
- if (_type >= 0)
- error("SciArray::setType(): Type already set");
+ /**
+ * Returns the type of this array.
+ */
+ SciArrayType getType() const {
+ return _type;
+ }
+ /**
+ * Sets the type of this array. The type of the array may only be set once.
+ */
+ void setType(const SciArrayType type) {
+ assert(_type == kArrayTypeInvalid);
+ switch(type) {
+ case kArrayTypeInt16:
+ case kArrayTypeID:
+ _elementSize = sizeof(reg_t);
+ break;
+ case kArrayTypeString:
+ _elementSize = sizeof(char);
+ break;
+ case kArrayTypeByte:
+ _elementSize = sizeof(byte);
+ break;
+ default:
+ error("Invalid array type %d", type);
+ }
_type = type;
}
- void setSize(uint32 size) {
- if (_type < 0)
- error("SciArray::setSize(): No type set");
+ /**
+ * Returns the size of the array, in elements.
+ */
+ uint16 size() const {
+ return _size;
+ }
- // Check if we don't have to do anything
- if (_size == size)
- return;
+ /**
+ * Returns the size of the array, in bytes.
+ */
+ uint16 byteSize() const {
+ return _size * _elementSize;
+ }
- // Check if we don't have to expand the array
- if (size <= _actualSize) {
- _size = size;
- return;
+ /**
+ * Ensures the array is large enough to store at least the given number of
+ * values given in `newSize`. If `force` is true, the array will be resized
+ * to store exactly `newSize` values. New values are initialized to zero.
+ */
+ void resize(uint16 newSize, const bool force = false) {
+ if (force || newSize > _size) {
+ _data = realloc(_data, _elementSize * newSize);
+ if (newSize > _size) {
+ memset((byte *)_data + _elementSize * _size, 0, (newSize - _size) * _elementSize);
+ }
+ _size = newSize;
+ }
+ }
+
+ /**
+ * Shrinks a string array to its optimal size.
+ */
+ void snug() {
+ assert(_type == kArrayTypeString || _type == kArrayTypeByte);
+ resize(strlen((char *)_data) + 1, true);
+ }
+
+ /**
+ * Returns a pointer to the array's raw data storage.
+ */
+ void *getRawData() { return _data; }
+ const void *getRawData() const { return _data; }
+
+ /**
+ * Gets the value at the given index as a reg_t.
+ */
+ reg_t getAsID(const uint16 index) {
+ if (getSciVersion() >= SCI_VERSION_3) {
+ resize(index);
+ } else {
+ assert(index < _size);
+ }
+
+ switch(_type) {
+ case kArrayTypeInt16:
+ case kArrayTypeID:
+ return ((reg_t *)_data)[index];
+ case kArrayTypeByte:
+ case kArrayTypeString: {
+ int16 value;
+
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ value = ((int8 *)_data)[index];
+ } else {
+ value = ((uint8 *)_data)[index];
+ }
+
+ return make_reg(0, value);
+ }
+ default:
+ error("Invalid array type %d", _type);
+ }
+ }
+
+ /**
+ * Sets the value at the given index from a reg_t.
+ */
+ void setFromID(const uint16 index, const reg_t value) {
+ if (getSciVersion() >= SCI_VERSION_3) {
+ resize(index);
+ } else {
+ assert(index < _size);
+ }
+
+ switch(_type) {
+ case kArrayTypeInt16:
+ case kArrayTypeID:
+ ((reg_t *)_data)[index] = value;
+ break;
+ case kArrayTypeByte:
+ case kArrayTypeString:
+ ((byte *)_data)[index] = value.toSint16();
+ break;
+ default:
+ error("Invalid array type %d", _type);
+ }
+ }
+
+ /**
+ * Gets the value at the given index as an int16.
+ */
+ int16 getAsInt16(const uint16 index) {
+ assert(_type == kArrayTypeInt16);
+
+ if (getSciVersion() >= SCI_VERSION_3) {
+ resize(index);
+ } else {
+ assert(index < _size);
+ }
+
+ const reg_t value = ((reg_t *)_data)[index];
+ assert(value.isNumber());
+ return value.toSint16();
+ }
+
+ /**
+ * Sets the value at the given index from an int16.
+ */
+ void setFromInt16(const uint16 index, const int16 value) {
+ assert(_type == kArrayTypeInt16);
+
+ if (getSciVersion() >= SCI_VERSION_3) {
+ resize(index + 1);
+ } else {
+ assert(index < _size);
+ }
+
+ ((reg_t *)_data)[index] = make_reg(0, value);
+ }
+
+ /**
+ * Returns a reference to the byte at the given index. Only valid for
+ * string and byte arrays.
+ */
+ byte &byteAt(const uint16 index) {
+ assert(_type == kArrayTypeString || _type == kArrayTypeByte);
+
+ if (getSciVersion() >= SCI_VERSION_3) {
+ resize(index);
+ } else {
+ assert(index < _size);
+ }
+
+ return ((byte *)_data)[index];
+ }
+
+ /**
+ * Returns a reference to the char at the given index. Only valid for
+ * string and byte arrays.
+ */
+ char &charAt(const uint16 index) {
+ assert(_type == kArrayTypeString || _type == kArrayTypeByte);
+
+ if (getSciVersion() >= SCI_VERSION_3) {
+ resize(index);
+ } else {
+ assert(index < _size);
+ }
+
+ return ((char *)_data)[index];
+ }
+
+ /**
+ * Returns a reference to the reg_t at the given index. Only valid for ID
+ * and int16 arrays.
+ */
+ reg_t &IDAt(const uint16 index) {
+ assert(_type == kArrayTypeID || _type == kArrayTypeInt16);
+
+ if (getSciVersion() >= SCI_VERSION_3) {
+ resize(index);
+ } else {
+ assert(index < _size);
+ }
+
+ return ((reg_t *)_data)[index];
+ }
+
+ /**
+ * Reads values from the given reg_t pointer and sets them in the array,
+ * growing the array if needed to store all values.
+ */
+ void setElements(const uint16 index, uint16 count, const reg_t *values) {
+ resize(index + count);
+
+ switch (_type) {
+ case kArrayTypeInt16:
+ case kArrayTypeID: {
+ const reg_t *source = values;
+ reg_t *target = (reg_t *)_data + index;
+ while (count--) {
+ *target++ = *source++;
+ }
+ break;
}
+ case kArrayTypeByte:
+ case kArrayTypeString: {
+ const reg_t *source = values;
+ byte *target = (byte *)_data + index;
+ while (count--) {
+ if (!source->isNumber()) {
+ error("Non-number %04x:%04x sent to byte or string array", PRINT_REG(*source));
+ }
+ *target++ = source->getOffset();
+ ++source;
+ }
+ break;
+ }
+ default:
+ error("Attempted write to SciArray with invalid type %d", _type);
+ }
+ }
- // So, we're going to have to create an array of some sort
- T *newArray = new T[size];
- memset(newArray, 0, size * sizeof(T));
+ /**
+ * Fills the array with the given value. Existing values will be
+ * overwritten. The array will be grown if needed to store all values.
+ */
+ void fill(const uint16 index, uint16 count, const reg_t value) {
+ if (count == 65535 /* -1 */) {
+ count = size() - index;
+ }
- // Check if we never created an array before
- if (!_data) {
- _size = _actualSize = size;
- _data = newArray;
+ if (!count) {
return;
}
- // Copy data from the old array to the new
- memcpy(newArray, _data, _size * sizeof(T));
+ resize(index + count);
- // Now set the new array to the old and set the sizes
- delete[] _data;
- _data = newArray;
- _size = _actualSize = size;
+ switch (_type) {
+ case kArrayTypeInt16:
+ case kArrayTypeID: {
+ reg_t *target = (reg_t *)_data + index;
+ while (count--) {
+ *target = value;
+ }
+ break;
+ }
+ case kArrayTypeByte:
+ case kArrayTypeString: {
+ byte *target = (byte *)_data + index;
+ const byte fillValue = value.getOffset();
+ while (count--) {
+ *target = fillValue;
+ }
+ break;
+ }
+ case kArrayTypeInvalid:
+ error("Attempted write to uninitialized SciArray");
+ }
}
- T getValue(uint16 index) const {
- if (index >= _size)
- error("SciArray::getValue(): %d is out of bounds (%d)", index, _size);
+ /**
+ * Copies values from the source array. Both arrays will be grown if needed
+ * to prevent out-of-bounds reads/writes.
+ */
+ void copy(SciArray &source, const uint16 sourceIndex, const uint16 targetIndex, uint16 count) {
+ if (count == 65535 /* -1 */) {
+ count = source.size() - sourceIndex;
+ }
+
+ if (!count) {
+ return;
+ }
+
+ resize(targetIndex + count);
+ source.resize(sourceIndex + count);
- return _data[index];
+ assert(source._elementSize == _elementSize);
+
+ const byte *sourceData = (byte *)source._data + sourceIndex * source._elementSize;
+ byte *targetData = (byte *)_data + targetIndex * _elementSize;
+ memmove(targetData, sourceData, count * _elementSize);
+ }
+
+ void byteCopy(const SciArray &source, const uint16 sourceOffset, const uint16 targetOffset, const uint16 count) {
+ error("SciArray::byteCopy not implemented");
}
- void setValue(uint16 index, T value) {
- if (index >= _size)
- error("SciArray::setValue(): %d is out of bounds (%d)", index, _size);
+ /**
+ * Removes whitespace from string data held in this array.
+ */
+ void trim(const int8 flags, const char showChar) {
+ enum {
+ kWhitespaceBoundary = 32,
+ kAsciiBoundary = 128
+ };
+
+ byte *data = (byte *)_data;
+ byte *source;
+ byte *target;
+
+ if (flags & kArrayTrimLeft) {
+ target = data;
+ source = data;
+ while (*source != '\0' && *source != showChar && *source <= kWhitespaceBoundary) {
+ ++source;
+ }
+ strcpy((char *)target, (char *)source);
+ }
+
+ if (flags & kArrayTrimRight) {
+ source = data + strlen((char *)data) - 1;
+ while (source > data && *source != showChar && *source <= kWhitespaceBoundary) {
+ --source;
+ }
+ *source = '\0';
+ }
- _data[index] = value;
+ if (flags & kArrayTrimCenter) {
+ target = data;
+ while (*target && *target <= kWhitespaceBoundary && *target != showChar) {
+ ++target;
+ }
+
+ if (*target) {
+ while (*target && (*target > kWhitespaceBoundary || *target == showChar)) {
+ ++target;
+ }
+
+ if (*target) {
+ source = target;
+ while (*source) {
+ while (*source && *source <= kWhitespaceBoundary && *source != showChar) {
+ ++source;
+ }
+
+ while (*source && (*source > kWhitespaceBoundary || *source == showChar)) {
+ *target++ = *source++;
+ }
+ }
+
+ --source;
+ while (source > target && (*source <= kWhitespaceBoundary || *source >= kAsciiBoundary) && *source != showChar) {
+ --source;
+ }
+ ++source;
+
+ memmove(target, source, strlen((char *)source) + 1);
+ }
+ }
+ }
}
- byte getType() const { return _type; }
- uint32 getSize() const { return _size; }
- T *getRawData() { return _data; }
- const T *getRawData() const { return _data; }
+ /**
+ * Copies the string data held by this array into a new Common::String.
+ */
+ Common::String toString() const {
+ assert(_type == kArrayTypeString);
+ return Common::String((char *)_data);
+ }
-protected:
- int8 _type;
- T *_data;
- uint32 _size; // _size holds the number of entries that the scripts have requested
- uint32 _actualSize; // _actualSize is the actual numbers of entries allocated
-};
+ /**
+ * Copies the string from the given Common::String into this array.
+ */
+ void fromString(const Common::String &string) {
+ // At least LSL6hires uses a byte-type array to hold string data
+ assert(_type == kArrayTypeString || _type == kArrayTypeByte);
+ resize(string.size() + 1, true);
+ Common::strlcpy((char *)_data, string.c_str(), string.size() + 1);
+ }
-class SciString : public SciArray<char> {
-public:
- SciString() : SciArray<char>() { setType(3); }
+ Common::String toDebugString() const {
+ const char *type;
+ switch(_type) {
+ case kArrayTypeID:
+ type = "reg_t";
+ break;
+ case kArrayTypeByte:
+ type = "byte";
+ break;
+ case kArrayTypeInt16:
+ type = "int16";
+ break;
+ case kArrayTypeString:
+ type = "string";
+ break;
+ case kArrayTypeInvalid:
+ type = "invalid";
+ break;
+ }
- // We overload destroy to ensure the string type is 3 after destroying
- void destroy() { SciArray<char>::destroy(); _type = 3; }
+ return Common::String::format("type %s; %u entries; %u bytes", type, size(), byteSize());
+ }
- Common::String toString() const;
- void fromString(const Common::String &string);
+protected:
+ void *_data;
+ SciArrayType _type;
+ uint16 _size;
+ uint8 _elementSize;
};
-struct ArrayTable : public SegmentObjTable<SciArray<reg_t> > {
- ArrayTable() : SegmentObjTable<SciArray<reg_t> >(SEG_TYPE_ARRAY) {}
+struct ArrayTable : public SegmentObjTable<SciArray> {
+ ArrayTable() : SegmentObjTable<SciArray>(SEG_TYPE_ARRAY) {}
- virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr);
virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const;
void saveLoadWithSerializer(Common::Serializer &ser);
SegmentRef dereference(reg_t pointer);
};
-struct StringTable : public SegmentObjTable<SciString> {
- StringTable() : SegmentObjTable<SciString>(SEG_TYPE_STRING) {}
+#pragma mark -
+#pragma mark Bitmaps
- virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) {
- _table[sub_addr.getOffset()].destroy();
- freeEntry(sub_addr.getOffset());
+enum {
+ kDefaultSkipColor = 250
+};
+
+#define BITMAP_PROPERTY(size, property, offset)\
+inline uint##size get##property() const {\
+ return READ_SCI11ENDIAN_UINT##size(_data + (offset));\
+}\
+inline void set##property(uint##size value) {\
+ WRITE_SCI11ENDIAN_UINT##size(_data + (offset), (value));\
+}
+
+struct BitmapTable;
+
+/**
+ * A convenience class for creating and modifying in-memory
+ * bitmaps.
+ */
+class SciBitmap : public Common::Serializable {
+ byte *_data;
+ int _dataSize;
+ Buffer _buffer;
+ bool _gc;
+
+public:
+ enum BitmapFlags {
+ kBitmapRemap = 2
+ };
+
+ /**
+ * Gets the size of the bitmap header for the current
+ * engine version.
+ */
+ static inline uint16 getBitmapHeaderSize() {
+ // TODO: These values are accurate for each engine, but there may be no reason
+ // to not simply just always use size 40, since SCI2.1mid does not seem to
+ // actually store any data above byte 40, and SCI2 did not allow bitmaps with
+ // scaling resolutions other than the default (320x200). Perhaps SCI3 used
+ // the extra bytes, or there is some reason why they tried to align the header
+ // size with other headers like pic headers?
+// uint32 bitmapHeaderSize;
+// if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+// bitmapHeaderSize = 46;
+// } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) {
+// bitmapHeaderSize = 40;
+// } else {
+// bitmapHeaderSize = 36;
+// }
+// return bitmapHeaderSize;
+ return 46;
+ }
+
+ /**
+ * Gets the byte size of a bitmap with the given width
+ * and height.
+ */
+ static inline uint32 getBitmapSize(const uint16 width, const uint16 height) {
+ return width * height + getBitmapHeaderSize();
+ }
+
+ inline SciBitmap() : _data(nullptr), _dataSize(0), _gc(true) {}
+
+ inline SciBitmap(const SciBitmap &other) {
+ _dataSize = other._dataSize;
+ _data = (byte *)malloc(other._dataSize);
+ memcpy(_data, other._data, other._dataSize);
+ if (_dataSize) {
+ _buffer = Buffer(getWidth(), getHeight(), getPixels());
+ }
+ _gc = other._gc;
+ }
+
+ inline ~SciBitmap() {
+ free(_data);
+ _data = nullptr;
+ _dataSize = 0;
+ }
+
+ inline SciBitmap &operator=(const SciBitmap &other) {
+ if (this == &other) {
+ return *this;
+ }
+
+ free(_data);
+ _dataSize = other._dataSize;
+ _data = (byte *)malloc(other._dataSize);
+ memcpy(_data, other._data, _dataSize);
+ if (_dataSize) {
+ _buffer = Buffer(getWidth(), getHeight(), getPixels());
+ }
+ _gc = other._gc;
+
+ return *this;
+ }
+
+ /**
+ * Allocates and initialises a new bitmap.
+ */
+ inline void create(const int16 width, const int16 height, const uint8 skipColor, const int16 originX, const int16 originY, const int16 xResolution, const int16 yResolution, const uint32 paletteSize, const bool remap, const bool gc) {
+
+ _dataSize = getBitmapSize(width, height) + paletteSize;
+ _data = (byte *)realloc(_data, _dataSize);
+ _gc = gc;
+
+ const uint16 bitmapHeaderSize = getBitmapHeaderSize();
+
+ setWidth(width);
+ setHeight(height);
+ setOrigin(Common::Point(originX, originY));
+ setSkipColor(skipColor);
+ _data[9] = 0;
+ WRITE_SCI11ENDIAN_UINT16(_data + 10, 0);
+ setRemap(remap);
+ setDataSize(width * height);
+ WRITE_SCI11ENDIAN_UINT32(_data + 16, 0);
+ setHunkPaletteOffset(paletteSize > 0 ? (width * height) : 0);
+ setDataOffset(bitmapHeaderSize);
+ setUncompressedDataOffset(bitmapHeaderSize);
+ setControlOffset(0);
+ setXResolution(xResolution);
+ setYResolution(yResolution);
+
+ _buffer = Buffer(getWidth(), getHeight(), getPixels());
+ }
+
+ inline int getRawSize() const {
+ return _dataSize;
+ }
+
+ inline byte *getRawData() const {
+ return _data;
+ }
+
+ inline Buffer &getBuffer() {
+ return _buffer;
+ }
+
+ inline bool getShouldGC() const {
+ return _gc;
+ }
+
+ inline void enableGC() {
+ _gc = true;
+ }
+
+ inline void disableGC() {
+ _gc = false;
+ }
+
+ BITMAP_PROPERTY(16, Width, 0);
+ BITMAP_PROPERTY(16, Height, 2);
+
+ inline Common::Point getOrigin() const {
+ return Common::Point(
+ (int16)READ_SCI11ENDIAN_UINT16(_data + 4),
+ (int16)READ_SCI11ENDIAN_UINT16(_data + 6)
+ );
+ }
+
+ inline void setOrigin(const Common::Point &origin) {
+ WRITE_SCI11ENDIAN_UINT16(_data + 4, (uint16)origin.x);
+ WRITE_SCI11ENDIAN_UINT16(_data + 6, (uint16)origin.y);
+ }
+
+ inline uint8 getSkipColor() const {
+ return _data[8];
+ }
+
+ inline void setSkipColor(const uint8 skipColor) {
+ _data[8] = skipColor;
+ }
+
+ inline bool getRemap() const {
+ return READ_SCI11ENDIAN_UINT16(_data + 10) & kBitmapRemap;
+ }
+
+ inline void setRemap(const bool remap) {
+ uint16 flags = READ_SCI11ENDIAN_UINT16(_data + 10);
+ if (remap) {
+ flags |= kBitmapRemap;
+ } else {
+ flags &= ~kBitmapRemap;
+ }
+ WRITE_SCI11ENDIAN_UINT16(_data + 10, flags);
+ }
+
+ BITMAP_PROPERTY(32, DataSize, 12);
+
+ inline uint32 getHunkPaletteOffset() const {
+ return READ_SCI11ENDIAN_UINT32(_data + 20);
+ }
+
+ inline void setHunkPaletteOffset(uint32 hunkPaletteOffset) {
+ if (hunkPaletteOffset) {
+ hunkPaletteOffset += getBitmapHeaderSize();
+ }
+
+ WRITE_SCI11ENDIAN_UINT32(_data + 20, hunkPaletteOffset);
+ }
+
+ BITMAP_PROPERTY(32, DataOffset, 24);
+
+ // NOTE: This property is used as a "magic number" for
+ // validating that a block of memory is a valid bitmap,
+ // and so is always set to the size of the header.
+ BITMAP_PROPERTY(32, UncompressedDataOffset, 28);
+
+ // NOTE: This property always seems to be zero
+ BITMAP_PROPERTY(32, ControlOffset, 32);
+
+ inline uint16 getXResolution() const {
+ if (getDataOffset() >= 40) {
+ return READ_SCI11ENDIAN_UINT16(_data + 36);
+ }
+
+ // SCI2 bitmaps did not have scaling ability
+ return 320;
+ }
+
+ inline void setXResolution(uint16 xResolution) {
+ if (getDataOffset() >= 40) {
+ WRITE_SCI11ENDIAN_UINT16(_data + 36, xResolution);
+ }
+ }
+
+ inline uint16 getYResolution() const {
+ if (getDataOffset() >= 40) {
+ return READ_SCI11ENDIAN_UINT16(_data + 38);
+ }
+
+ // SCI2 bitmaps did not have scaling ability
+ return 200;
+ }
+
+ inline void setYResolution(uint16 yResolution) {
+ if (getDataOffset() >= 40) {
+ WRITE_SCI11ENDIAN_UINT16(_data + 38, yResolution);
+ }
+ }
+
+ inline byte *getPixels() {
+ return _data + getUncompressedDataOffset();
+ }
+
+ inline byte *getHunkPalette() {
+ if (getHunkPaletteOffset() == 0) {
+ return nullptr;
+ }
+ return _data + getHunkPaletteOffset();
+ }
+
+ virtual void saveLoadWithSerializer(Common::Serializer &ser);
+
+ void applyRemap(SciArray &clut) {
+ const int length = getWidth() * getHeight();
+ uint8 *pixel = getPixels();
+ for (int i = 0; i < length; ++i) {
+ const int16 color = clut.getAsInt16(*pixel);
+ assert(color >= 0 && color <= 255);
+ *pixel++ = (uint8)color;
+ }
+ }
+
+ Common::String toString() const {
+ return Common::String::format("%dx%d; res %dx%d; origin %dx%d; skip color %u; %s; %s)",
+ getWidth(), getHeight(),
+ getXResolution(), getYResolution(),
+ getOrigin().x, getOrigin().y,
+ getSkipColor(),
+ getRemap() ? "remap" : "no remap",
+ getShouldGC() ? "GC" : "no GC");
+ }
+};
+
+#undef BITMAP_PROPERTY
+
+struct BitmapTable : public SegmentObjTable<SciBitmap> {
+ BitmapTable() : SegmentObjTable<SciBitmap>(SEG_TYPE_BITMAP) {}
+
+ SegmentRef dereference(reg_t pointer) {
+ SegmentRef ret;
+ ret.isRaw = true;
+ ret.maxSize = at(pointer.getOffset()).getRawSize();
+ ret.raw = at(pointer.getOffset()).getRawData();
+ return ret;
}
void saveLoadWithSerializer(Common::Serializer &ser);
- SegmentRef dereference(reg_t pointer);
};
#endif
diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp
index 320f2c0664..2bc4051a79 100644
--- a/engines/sci/engine/selector.cpp
+++ b/engines/sci/engine/selector.cpp
@@ -21,6 +21,7 @@
*/
#include "sci/sci.h"
+#include "sci/engine/features.h"
#include "sci/engine/kernel.h"
#include "sci/engine/state.h"
#include "sci/engine/selector.h"
@@ -57,11 +58,11 @@ void Kernel::mapSelectors() {
FIND_SELECTOR(nsTop);
FIND_SELECTOR(nsLeft);
FIND_SELECTOR(nsBottom);
+ FIND_SELECTOR(nsRight);
FIND_SELECTOR(lsTop);
FIND_SELECTOR(lsLeft);
FIND_SELECTOR(lsBottom);
FIND_SELECTOR(lsRight);
- FIND_SELECTOR(nsRight);
FIND_SELECTOR(signal);
FIND_SELECTOR(illegalBits);
FIND_SELECTOR(brTop);
@@ -173,6 +174,7 @@ void Kernel::mapSelectors() {
FIND_SELECTOR(left);
FIND_SELECTOR(bottom);
FIND_SELECTOR(right);
+ FIND_SELECTOR(seenRect);
FIND_SELECTOR(resY);
FIND_SELECTOR(resX);
FIND_SELECTOR(dimmed);
@@ -180,6 +182,7 @@ void Kernel::mapSelectors() {
FIND_SELECTOR(back);
FIND_SELECTOR(skip);
FIND_SELECTOR(borderColor);
+ FIND_SELECTOR(width);
FIND_SELECTOR(fixPriority);
FIND_SELECTOR(mirrored);
FIND_SELECTOR(visible);
@@ -192,7 +195,12 @@ void Kernel::mapSelectors() {
FIND_SELECTOR(textLeft);
FIND_SELECTOR(textBottom);
FIND_SELECTOR(textRight);
+ FIND_SELECTOR(title);
+ FIND_SELECTOR(titleFont);
+ FIND_SELECTOR(titleFore);
+ FIND_SELECTOR(titleBack);
FIND_SELECTOR(magnifier);
+ FIND_SELECTOR(frameOut);
FIND_SELECTOR(casts);
#endif
}
@@ -207,10 +215,17 @@ reg_t readSelector(SegManager *segMan, reg_t object, Selector selectorId) {
}
#ifdef ENABLE_SCI32
-void updateInfoFlagViewVisible(Object *obj, int offset) {
- // TODO: Make this correct for all SCI versions
- // Selectors 26 through 44 are selectors for View script objects in SQ6
- if (offset >= 26 && offset <= 44 && getSciVersion() >= SCI_VERSION_2) {
+void updateInfoFlagViewVisible(Object *obj, int index) {
+ int minIndex, maxIndex;
+ if (g_sci->_features->usesAlternateSelectors()) {
+ minIndex = 24;
+ maxIndex = 43;
+ } else {
+ minIndex = 26;
+ maxIndex = 44;
+ }
+
+ if (index >= minIndex && index <= maxIndex && getSciVersion() >= SCI_VERSION_2) {
obj->setInfoSelectorFlag(kInfoFlagViewVisible);
}
}
@@ -220,20 +235,19 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t
ObjVarRef address;
if ((selectorId < 0) || (selectorId > (int)g_sci->getKernel()->getSelectorNamesSize())) {
- error("Attempt to write to invalid selector %d of"
- " object at %04x:%04x.", selectorId, PRINT_REG(object));
- return;
+ const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+ error("Attempt to write to invalid selector %d. Address %04x:%04x, %s", selectorId, PRINT_REG(object), origin.toString().c_str());
}
- if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable)
- error("Selector '%s' of object at %04x:%04x could not be"
- " written to", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
- else {
- *address.getPointer(segMan) = value;
+ if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable) {
+ const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+ error("Selector '%s' of object could not be written to. Address %04x:%04x, %s", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.toString().c_str());
+ }
+
+ *address.getPointer(segMan) = value;
#ifdef ENABLE_SCI32
- updateInfoFlagViewVisible(segMan->getObject(object), selectorId);
+ updateInfoFlagViewVisible(segMan->getObject(object), address.varindex);
#endif
- }
}
void invokeSelector(EngineState *s, reg_t object, int selectorId,
@@ -249,12 +263,12 @@ void invokeSelector(EngineState *s, reg_t object, int selectorId,
slc_type = lookupSelector(s->_segMan, object, selectorId, NULL, NULL);
if (slc_type == kSelectorNone) {
- error("Selector '%s' of object at %04x:%04x could not be invoked",
- g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
+ const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+ error("invokeSelector: Selector '%s' could not be invoked. Address %04x:%04x, %s", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.toString().c_str());
}
if (slc_type == kSelectorVariable) {
- error("Attempting to invoke variable selector %s of object %04x:%04x",
- g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
+ const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+ error("invokeSelector: Attempting to invoke variable selector %s. Address %04x:%04x, %s", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.toString().c_str());
}
for (i = 0; i < argc; i++)
@@ -282,8 +296,8 @@ SelectorType lookupSelector(SegManager *segMan, reg_t obj_location, Selector sel
selectorId &= ~1;
if (!obj) {
- error("lookupSelector(): Attempt to send to non-object or invalid script. Address was %04x:%04x",
- PRINT_REG(obj_location));
+ const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+ error("lookupSelector: Attempt to send to non-object or invalid script. Address %04x:%04x, %s", PRINT_REG(obj_location), origin.toString().c_str());
}
index = obj->locateVarSelector(segMan, selectorId);
diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h
index 1952ca0599..8d1edeb489 100644
--- a/engines/sci/engine/selector.h
+++ b/engines/sci/engine/selector.h
@@ -135,29 +135,28 @@ struct SelectorCache {
Selector bitmap; // Used to hold the text bitmap for SCI32 texts
Selector plane;
- Selector top;
- Selector left;
- Selector bottom;
- Selector right;
- Selector resX;
- Selector resY;
+ Selector top, left, bottom, right;
+ Selector resX, resY;
Selector fore;
Selector back;
Selector skip;
Selector dimmed;
Selector borderColor;
+ Selector width;
Selector fixPriority;
Selector mirrored;
Selector visible;
+ Selector seenRect;
Selector useInsetRect;
Selector inTop, inLeft, inBottom, inRight;
Selector textTop, textLeft, textBottom, textRight;
+ Selector title, titleFont, titleFore, titleBack;
Selector magnifier;
-
+ Selector frameOut;
Selector casts; // needed for sync'ing screen items/planes with scripts, when our save/restore code is patched in (see GfxFrameout::syncWithScripts)
#endif
};
@@ -201,10 +200,10 @@ void invokeSelector(EngineState *s, reg_t object, int selectorId,
/**
* SCI32 set kInfoFlagViewVisible in the -info- selector if a certain
* range of properties was written to.
- * This function checks if offset is in the right range, and sets the flag
+ * This function checks if index is in the right range, and sets the flag
* on obj.-info- if it is.
*/
-void updateInfoFlagViewVisible(Object *obj, int offset);
+void updateInfoFlagViewVisible(Object *obj, int index);
#endif
} // End of namespace Sci
diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index d53e6b48c8..c23add1211 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -70,9 +70,6 @@ static const uint16 s_halfWidthSJISMap[256] = {
EngineState::EngineState(SegManager *segMan)
: _segMan(segMan),
-#ifdef ENABLE_SCI32
- _virtualIndexFile(0),
-#endif
_dirseeker() {
reset(false);
@@ -80,9 +77,6 @@ EngineState::EngineState(SegManager *segMan)
EngineState::~EngineState() {
delete _msgState;
-#ifdef ENABLE_SCI32
- delete _virtualIndexFile;
-#endif
}
void EngineState::reset(bool isRestoring) {
@@ -95,6 +89,7 @@ void EngineState::reset(bool isRestoring) {
// reset delayed restore game functionality
_delayedRestoreGame = false;
_delayedRestoreGameId = 0;
+ _delayedRestoreFromLauncher = false;
executionStackBase = 0;
_executionStackPosChanged = false;
@@ -126,9 +121,6 @@ void EngineState::reset(bool isRestoring) {
_videoState.reset();
_syncedAudioOptions = false;
-
- _vmdPalStart = 0;
- _vmdPalEnd = 256;
}
void EngineState::speedThrottler(uint32 neededSleep) {
@@ -167,11 +159,11 @@ void EngineState::initGlobals() {
}
uint16 EngineState::currentRoomNumber() const {
- return variables[VAR_GLOBAL][13].toUint16();
+ return variables[VAR_GLOBAL][kGlobalVarNewRoomNo].toUint16();
}
void EngineState::setRoomNumber(uint16 roomNumber) {
- variables[VAR_GLOBAL][13] = make_reg(0, roomNumber);
+ variables[VAR_GLOBAL][kGlobalVarNewRoomNo] = make_reg(0, roomNumber);
}
void EngineState::shrinkStackToBase() {
@@ -210,12 +202,12 @@ Common::String SciEngine::getSciLanguageString(const Common::String &str, kLangu
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);
@@ -244,7 +236,7 @@ Common::String SciEngine::getSciLanguageString(const Common::String &str, kLangu
while (1) {
curChar = *textPtr;
-
+
switch (curChar) {
case 0: // Terminator NUL
return fullWidth;
@@ -265,7 +257,7 @@ Common::String SciEngine::getSciLanguageString(const Common::String &str, kLangu
continue;
}
}
-
+
textPtr++;
mappedChar = s_halfWidthSJISMap[curChar];
@@ -388,4 +380,46 @@ void SciEngine::checkVocabularySwitch() {
}
}
+SciCallOrigin EngineState::getCurrentCallOrigin() const {
+ // IMPORTANT: This method must always return values that match *exactly* the
+ // values in the workaround tables in workarounds.cpp, or workarounds will
+ // be broken
+
+ Common::String curObjectName = _segMan->getObjectName(xs->sendp);
+ Common::String curMethodName;
+ const Script *localScript = _segMan->getScriptIfLoaded(xs->local_segment);
+ int curScriptNr = localScript->getScriptNumber();
+
+ if (xs->debugLocalCallOffset != -1) {
+ // if lastcall was actually a local call search back for a real call
+ Common::List<ExecStack>::const_iterator callIterator = _executionStack.end();
+ while (callIterator != _executionStack.begin()) {
+ callIterator--;
+ const ExecStack &loopCall = *callIterator;
+ if ((loopCall.debugSelector != -1) || (loopCall.debugExportId != -1)) {
+ xs->debugSelector = loopCall.debugSelector;
+ xs->debugExportId = loopCall.debugExportId;
+ break;
+ }
+ }
+ }
+
+ if (xs->type == EXEC_STACK_TYPE_CALL) {
+ if (xs->debugSelector != -1) {
+ curMethodName = g_sci->getKernel()->getSelectorName(xs->debugSelector);
+ } else if (xs->debugExportId != -1) {
+ curObjectName = "";
+ curMethodName = Common::String::format("export %d", xs->debugExportId);
+ }
+ }
+
+ SciCallOrigin reply;
+ reply.objectName = curObjectName;
+ reply.methodName = curMethodName;
+ reply.scriptNr = curScriptNr;
+ reply.localCallOffset = xs->debugLocalCallOffset;
+ reply.roomNr = currentRoomNumber();
+ return reply;
+}
+
} // End of namespace Sci
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index 0f04e32fe5..5297a176d3 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -57,10 +57,6 @@ enum AbortGameState {
kAbortQuitGame = 3
};
-// slot 0 is the ScummVM auto-save slot, which is not used by us, but is still reserved
-#define SAVEGAMESLOT_FIRST 1
-#define SAVEGAMESLOT_LAST 99
-
// We assume that scripts give us savegameId 0->99 for creating a new save slot
// and savegameId 100->199 for existing save slots. Refer to kfile.cpp
enum {
@@ -99,6 +95,21 @@ struct VideoState {
}
};
+/**
+ * Trace information about a VM function call.
+ */
+struct SciCallOrigin {
+ int scriptNr; //< The source script of the function
+ Common::String objectName; //< The name of the object being called
+ Common::String methodName; //< The name of the method being called
+ int localCallOffset; //< The byte offset of a local script subroutine called by the origin method. -1 if not in a local subroutine.
+ int roomNr; //< The room that was loaded at the time of the call
+
+ Common::String toString() const {
+ return Common::String::format("method %s::%s (room %d, script %d, localCall %x)", objectName.c_str(), methodName.c_str(), roomNr, scriptNr, localCallOffset);
+ }
+};
+
struct EngineState : public Common::Serializable {
public:
EngineState(SegManager *segMan);
@@ -131,17 +142,15 @@ public:
int16 _lastSaveVirtualId; // last virtual id fed to kSaveGame, if no kGetSaveFiles was called inbetween
int16 _lastSaveNewId; // last newly created filename-id by kSaveGame
-#ifdef ENABLE_SCI32
- 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)
+ bool _delayedRestoreFromLauncher; // is set, when the the delayed restore game was triggered from launcher
uint _chosenQfGImportItem; // Remembers the item selected in QfG import rooms
bool _cursorWorkaroundActive; // Refer to GfxCursor::setPosition()
+ int16 _cursorWorkaroundPosCount; // When the cursor is reported to be at the previously set coordinate, we won't disable the workaround unless it happened for this many times
Common::Point _cursorWorkaroundPoint;
Common::Rect _cursorWorkaroundRect;
@@ -203,14 +212,19 @@ public:
uint16 _memorySegmentSize;
byte _memorySegment[kMemorySegmentMax];
+ // TODO: Excise video code from the state manager
VideoState _videoState;
- uint16 _vmdPalStart, _vmdPalEnd;
bool _syncedAudioOptions;
/**
* Resets the engine state.
*/
void reset(bool isRestoring);
+
+ /**
+ * Finds and returns the origin of the current call.
+ */
+ SciCallOrigin getCurrentCallOrigin() const;
};
} // End of namespace Sci
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 66d9fee5fd..8e407a6ab9 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "common/config-manager.h"
#include "common/debug.h"
#include "common/debug-channels.h"
@@ -124,32 +125,30 @@ static reg_t read_var(EngineState *s, int type, int index) {
case VAR_TEMP: {
// Uninitialized read on a temp
// We need to find correct replacements for each situation manually
- SciTrackOriginReply originReply;
+ SciCallOrigin originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(index, uninitializedReadWorkarounds, &originReply);
if (solution.type == WORKAROUND_NONE) {
#ifdef RELEASE_BUILD
// If we are running an official ScummVM release -> fake 0 in unknown cases
- warning("Uninitialized read for temp %d from method %s::%s (room %d, script %d, localCall %x)",
- index, originReply.objectName.c_str(), originReply.methodName.c_str(), s->currentRoomNumber(),
- originReply.scriptNr, originReply.localCallOffset);
+ warning("Uninitialized read for temp %d from %s", index, originReply.toString().c_str());
s->variables[type][index] = NULL_REG;
break;
#else
- error("Uninitialized read for temp %d from method %s::%s (room %d, script %d, localCall %x)",
- index, originReply.objectName.c_str(), originReply.methodName.c_str(), s->currentRoomNumber(),
- originReply.scriptNr, originReply.localCallOffset);
+ error("Uninitialized read for temp %d from %s", index, originReply.toString().c_str());
#endif
}
assert(solution.type == WORKAROUND_FAKE);
s->variables[type][index] = make_reg(0, solution.value);
break;
}
- case VAR_PARAM:
+ case VAR_PARAM: {
// Out-of-bounds read for a parameter that goes onto stack and hits an uninitialized temp
// We return 0 currently in that case
- debugC(kDebugLevelVM, "[VM] Read for a parameter goes out-of-bounds, onto the stack and gets uninitialized temp");
+ const SciCallOrigin origin = s->getCurrentCallOrigin();
+ warning("Uninitialized read for parameter %d from %s", index, origin.toString().c_str());
return NULL_REG;
+ }
default:
break;
}
@@ -179,7 +178,7 @@ static void write_var(EngineState *s, int type, int index, reg_t value) {
// stopGroop object, which points to ego, to the new ego object. If this is not
// done, ego's movement will not be updated properly, so the result is
// unpredictable (for example in LSL5, Patti spins around instead of walking).
- if (index == 0 && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) { // global 0 is ego
+ if (index == kGlobalVarEgo && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) {
reg_t stopGroopPos = s->_segMan->findObjectByName("stopGroop");
if (!stopGroopPos.isNull()) { // does the game have a stopGroop object?
// Find the "client" member variable of the stopGroop object, and update it
@@ -200,9 +199,26 @@ static void write_var(EngineState *s, int type, int index, reg_t value) {
s->variables[type][index] = value;
- if (type == VAR_GLOBAL && index == 90) {
+#ifdef ENABLE_SCI32
+ if (type == VAR_GLOBAL && getSciVersion() >= SCI_VERSION_2 && g_sci->getEngineState()->_syncedAudioOptions) {
+
+ switch (g_sci->getGameId()) {
+ case GID_LSL6HIRES:
+ if (index == kGlobalVarLSL6HiresTextSpeed) {
+ ConfMan.setInt("talkspeed", (14 - value.toSint16()) * 255 / 13);
+ }
+ break;
+ default:
+ if (index == kGlobalVarTextSpeed) {
+ ConfMan.setInt("talkspeed", (8 - value.toSint16()) * 255 / 8);
+ }
+ }
+ }
+#endif
+
+ if (type == VAR_GLOBAL && index == kGlobalVarMessageType) {
// The game is trying to change its speech/subtitle settings
- if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][4] == TRUE_REG) {
+ if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) {
// ScummVM audio options haven't been applied yet, so apply them.
// We also force the ScummVM audio options when loading a game from
// the launcher.
@@ -232,15 +248,16 @@ ExecStack *execute_method(EngineState *s, uint16 script, uint16 pubfunct, StackP
scr = s->_segMan->getScript(seg);
}
+ // Check if a breakpoint is set on this method
+ g_sci->checkExportBreakpoint(script, pubfunct);
+
uint32 exportAddr = scr->validateExportFunc(pubfunct, false);
if (!exportAddr)
return NULL;
- // Check if a breakpoint is set on this method
- g_sci->checkExportBreakpoint(script, pubfunct);
-
+ assert(argp[0].toUint16() == argc); // The first argument is argc
ExecStack xstack(calling_obj, calling_obj, sp, argc, argp,
- seg, make_reg32(seg, exportAddr), -1, pubfunct, -1,
+ seg, make_reg32(seg, exportAddr), -1, -1, -1, pubfunct, -1,
s->_executionStack.size() - 1, EXEC_STACK_TYPE_CALL);
s->_executionStack.push_back(xstack);
return &(s->_executionStack.back());
@@ -312,8 +329,9 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt
if (activeBreakpointTypes || DebugMan.isDebugChannelEnabled(kDebugLevelScripts))
debugSelectorCall(send_obj, selector, argc, argp, varp, funcp, s->_segMan, selectorType);
+ assert(argp[0].toUint16() == argc); // The first argument is argc
ExecStack xstack(work_obj, send_obj, curSP, argc, argp,
- 0xFFFF, curFP, selector, -1, -1,
+ 0xFFFF, curFP, selector, -1, -1, -1, -1,
origin, stackType);
if (selectorType == kSelectorVariable)
@@ -330,17 +348,20 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt
argp += argc + 1;
} // while (framesize > 0)
+ // Perform all varselector actions at the top of the stack immediately.
+ // Note that there may be some behind method selector calls as well;
+ // those will get executed by op_ret later.
_exec_varselectors(s);
return s->_executionStack.empty() ? NULL : &(s->_executionStack.back());
}
-static void addKernelCallToExecStack(EngineState *s, int kernelCallNr, int argc, reg_t *argv) {
+static void addKernelCallToExecStack(EngineState *s, int kernelCallNr, int kernelSubCallNr, int argc, reg_t *argv) {
// Add stack frame to indicate we're executing a callk.
// This is useful in debugger backtraces if this
// kernel function calls a script itself.
ExecStack xstack(NULL_REG, NULL_REG, NULL, argc, argv - 1, 0xFFFF, make_reg32(0, 0),
- kernelCallNr, -1, -1, s->_executionStack.size() - 1, EXEC_STACK_TYPE_KERNEL);
+ -1, kernelCallNr, kernelSubCallNr, -1, -1, s->_executionStack.size() - 1, EXEC_STACK_TYPE_KERNEL);
s->_executionStack.push_back(xstack);
}
@@ -359,16 +380,13 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
if (kernelCall.signature
&& !kernel->signatureMatch(kernelCall.signature, argc, argv)) {
// signature mismatch, check if a workaround is available
- SciTrackOriginReply originReply;
+ SciCallOrigin originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelCall.workarounds, &originReply);
switch (solution.type) {
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);
+ error("\n%s[VM] k%s[%x]: signature mismatch in %s", signatureDetailsStr.c_str(), kernelCall.name, kernelCallNr, originReply.toString().c_str());
break;
}
case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone
@@ -386,7 +404,8 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
// Call kernel function
if (!kernelCall.subFunctionCount) {
- addKernelCallToExecStack(s, kernelCallNr, argc, argv);
+ argv[-1] = make_reg(0, argc); // The first argument is argc
+ addKernelCallToExecStack(s, kernelCallNr, -1, argc, argv);
s->r_acc = kernelCall.function(s, argc, argv);
if (kernelCall.debugLogging)
@@ -402,6 +421,21 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
error("[VM] k%s[%x]: no subfunction ID parameter given", kernelCall.name, kernelCallNr);
if (argv[0].isPointer())
error("[VM] k%s[%x]: given subfunction ID is actually a pointer", kernelCall.name, kernelCallNr);
+
+#ifdef ENABLE_SCI32
+ // The Windows version of kShowMovie has subops, but the subop number
+ // is put in the second parameter in SCI2.1+, even though every other
+ // kcall with subops puts the subop in the first parameter. To allow use
+ // of the normal subops system, we swap the arguments so the subop
+ // number is in the usual place.
+ if (getSciVersion() > SCI_VERSION_2 &&
+ g_sci->getPlatform() == Common::kPlatformWindows &&
+ strcmp(kernelCall.name, "ShowMovie") == 0) {
+ assert(argc > 1);
+ SWAP(argv[0], argv[1]);
+ }
+#endif
+
const uint16 subId = argv[0].toUint16();
// Skip over subfunction-id
argc--;
@@ -411,7 +445,7 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
const KernelSubFunction &kernelSubCall = kernelCall.subFunctions[subId];
if (kernelSubCall.signature && !kernel->signatureMatch(kernelSubCall.signature, argc, argv)) {
// Signature mismatch
- SciTrackOriginReply originReply;
+ SciCallOrigin originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelSubCall.workarounds, &originReply);
switch (solution.type) {
case WORKAROUND_NONE: {
@@ -420,15 +454,13 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
int callNameLen = strlen(kernelCall.name);
if (strncmp(kernelCall.name, kernelSubCall.name, callNameLen) == 0) {
const char *subCallName = kernelSubCall.name + callNameLen;
- 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("\n%s[VM] k%s(%s): signature mismatch in %s",
+ signatureDetailsStr.c_str(), kernelCall.name, subCallName,
+ originReply.toString().c_str());
}
- 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);
+ error("\n%s[VM] k%s: signature mismatch in %s",
+ signatureDetailsStr.c_str(), kernelSubCall.name,
+ originReply.toString().c_str());
break;
}
case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone
@@ -444,7 +476,8 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
}
if (!kernelSubCall.function)
error("[VM] k%s: subfunction ID %d requested, but not available", kernelCall.name, subId);
- addKernelCallToExecStack(s, kernelCallNr, argc, argv);
+ argv[-1] = make_reg(0, argc); // The first argument is argc
+ addKernelCallToExecStack(s, kernelCallNr, subId, argc, argv);
s->r_acc = kernelSubCall.function(s, argc, argv);
if (kernelSubCall.debugLogging)
@@ -545,6 +578,31 @@ int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4])
return offset;
}
+uint32 findOffset(const int16 relOffset, const Script *scr, const uint32 pcOffset) {
+ uint32 offset;
+
+ switch (g_sci->_features->detectLofsType()) {
+ case SCI_VERSION_0_EARLY:
+ offset = (uint16)pcOffset + relOffset;
+ break;
+ case SCI_VERSION_1_MIDDLE:
+ offset = relOffset;
+ break;
+ case SCI_VERSION_1_1:
+ offset = relOffset + scr->getScriptSize();
+ break;
+ case SCI_VERSION_3:
+ // In theory this can break if the variant with a one-byte argument is
+ // used. For now, assume it doesn't happen.
+ offset = scr->relocateOffsetSci3(pcOffset - 2);
+ break;
+ default:
+ error("Unknown lofs type");
+ }
+
+ return offset;
+}
+
void run_vm(EngineState *s) {
assert(s);
@@ -612,6 +670,8 @@ void run_vm(EngineState *s) {
if (s->abortScriptProcessing != kAbortNone)
return; // Stop processing
+ g_sci->checkAddressBreakpoint(s->xs->addr.pc);
+
// Debug if this has been requested:
// TODO: re-implement sci_debug_flags
if (g_sci->_debugState.debugging /* sci_debug_flags*/) {
@@ -834,14 +894,15 @@ void run_vm(EngineState *s) {
int argc = (opparams[1] >> 1) // Given as offset, but we need count
+ 1 + s->r_rest;
StackPtr call_base = s->xs->sp - argc;
- s->xs->sp[1].incOffset(s->r_rest);
uint32 localCallOffset = s->xs->addr.pc.getOffset() + opparams[0];
+ int final_argc = (call_base->requireUint16()) + s->r_rest;
+ call_base[0] = make_reg(0, final_argc); // The first argument is argc
ExecStack xstack(s->xs->objp, s->xs->objp, s->xs->sp,
- (call_base->requireUint16()) + s->r_rest, call_base,
+ final_argc, call_base,
s->xs->local_segment, make_reg32(s->xs->addr.pc.getSegment(), localCallOffset),
- NULL_SELECTOR, -1, localCallOffset, s->_executionStack.size() - 1,
+ NULL_SELECTOR, -1, -1, -1, localCallOffset, s->_executionStack.size() - 1,
EXEC_STACK_TYPE_CALL);
s->_executionStack.push_back(xstack);
@@ -938,9 +999,13 @@ void run_vm(EngineState *s) {
if (old_xs->type == EXEC_STACK_TYPE_VARSELECTOR) {
// varselector access?
reg_t *var = old_xs->getVarPointer(s->_segMan);
- if (old_xs->argc) // write?
+ if (old_xs->argc) { // write?
*var = old_xs->variables_argp[1];
- else // No, read
+
+#ifdef ENABLE_SCI32
+ updateInfoFlagViewVisible(s->_segMan->getObject(old_xs->addr.varp.obj), old_xs->addr.varp.varindex);
+#endif
+ } else // No, read
s->r_acc = *var;
}
@@ -1100,7 +1165,7 @@ void run_vm(EngineState *s) {
// Accumulator To Property
validate_property(s, obj, opparams[0]) = s->r_acc;
#ifdef ENABLE_SCI32
- updateInfoFlagViewVisible(obj, opparams[0]);
+ updateInfoFlagViewVisible(obj, opparams[0]>>1);
#endif
break;
@@ -1113,7 +1178,7 @@ void run_vm(EngineState *s) {
// Stack To Property
validate_property(s, obj, opparams[0]) = POP32();
#ifdef ENABLE_SCI32
- updateInfoFlagViewVisible(obj, opparams[0]);
+ updateInfoFlagViewVisible(obj, opparams[0]>>1);
#endif
break;
@@ -1130,7 +1195,7 @@ void run_vm(EngineState *s) {
else
opProperty -= 1;
#ifdef ENABLE_SCI32
- updateInfoFlagViewVisible(obj, opparams[0]);
+ updateInfoFlagViewVisible(obj, opparams[0]>>1);
#endif
if (opcode == op_ipToa || opcode == op_dpToa)
s->r_acc = opProperty;
@@ -1140,38 +1205,21 @@ void run_vm(EngineState *s) {
}
case op_lofsa: // 0x39 (57)
- case op_lofss: // 0x3a (58)
+ case op_lofss: { // 0x3a (58)
// Load offset to accumulator or push to stack
- r_temp.setSegment(s->xs->addr.pc.getSegment());
-
- switch (g_sci->_features->detectLofsType()) {
- case SCI_VERSION_0_EARLY:
- r_temp.setOffset((uint16)s->xs->addr.pc.getOffset() + opparams[0]);
- break;
- case SCI_VERSION_1_MIDDLE:
- r_temp.setOffset(opparams[0]);
- break;
- case SCI_VERSION_1_1:
- r_temp.setOffset(opparams[0] + local_script->getScriptSize());
- break;
- case SCI_VERSION_3:
- // In theory this can break if the variant with a one-byte argument is
- // used. For now, assume it doesn't happen.
- r_temp.setOffset(local_script->relocateOffsetSci3(s->xs->addr.pc.getOffset() - 2));
- break;
- default:
- error("Unknown lofs type");
- }
+ r_temp.setSegment(s->xs->addr.pc.getSegment());
+ r_temp.setOffset(findOffset(opparams[0], local_script, s->xs->addr.pc.getOffset()));
if (r_temp.getOffset() >= scr->getBufSize())
error("VM: lofsa/lofss operation overflowed: %04x:%04x beyond end"
- " of script (at %04x)", PRINT_REG(r_temp), scr->getBufSize());
+ " of script (at %04x)", PRINT_REG(r_temp), scr->getBufSize());
if (opcode == op_lofsa)
s->r_acc = r_temp;
else
PUSH32(r_temp);
break;
+ }
case op_push0: // 0x3b (59)
PUSH(0);
diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h
index 514bf58b64..13f60fd49c 100644
--- a/engines/sci/engine/vm.h
+++ b/engines/sci/engine/vm.h
@@ -36,6 +36,7 @@ class SegManager;
struct EngineState;
class Object;
class ResourceManager;
+class Script;
/** Number of bytes to be allocated for the stack */
#define VM_STACK_SIZE 0x1000
@@ -93,16 +94,19 @@ struct ExecStack {
SegmentId local_segment; // local variables etc
- Selector debugSelector; // The selector which was used to call or -1 if not applicable
- int debugExportId; // The exportId which was called or -1 if not applicable
- int debugLocalCallOffset; // Local call offset or -1 if not applicable
- int debugOrigin; // The stack frame position the call was made from, or -1 if it was the initial call
+ Selector debugSelector; // The selector which was used to call or -1 if not applicable
+ int debugExportId; // The exportId which was called or -1 if not applicable
+ int debugLocalCallOffset; // Local call offset or -1 if not applicable
+ int debugOrigin; // The stack frame position the call was made from, or -1 if it was the initial call
+ int debugKernelFunction; // The kernel function called, or -1 if not applicable
+ int debugKernelSubFunction; // The kernel subfunction called, or -1 if not applicable
ExecStackType type;
reg_t* getVarPointer(SegManager *segMan) const;
ExecStack(reg_t objp_, reg_t sendp_, StackPtr sp_, int argc_, StackPtr argp_,
SegmentId localsSegment_, reg32_t pc_, Selector debugSelector_,
+ int debugKernelFunction_, int debugKernelSubFunction_,
int debugExportId_, int debugLocalCallOffset_, int debugOrigin_,
ExecStackType type_) {
objp = objp_;
@@ -112,12 +116,13 @@ struct ExecStack {
fp = sp = sp_;
argc = argc_;
variables_argp = argp_;
- *variables_argp = make_reg(0, argc); // The first argument is argc
if (localsSegment_ != 0xFFFF)
local_segment = localsSegment_;
else
local_segment = pc_.getSegment();
debugSelector = debugSelector_;
+ debugKernelFunction = debugKernelFunction_;
+ debugKernelSubFunction = debugKernelSubFunction_;
debugExportId = debugExportId_;
debugLocalCallOffset = debugLocalCallOffset_;
debugOrigin = debugOrigin_;
@@ -132,6 +137,23 @@ enum {
VAR_PARAM = 3
};
+enum GlobalVar {
+ kGlobalVarEgo = 0,
+ kGlobalVarCurrentRoom = 2,
+ kGlobalVarSpeed = 3, // SCI16
+ kGlobalVarQuit = 4,
+ kGlobalVarPlanes = 10, // SCI32
+ kGlobalVarCurrentRoomNo = 11,
+ kGlobalVarPreviousRoomNo = 12,
+ kGlobalVarNewRoomNo = 13,
+ kGlobalVarScore = 15,
+ kGlobalVarFastCast = 84, // SCI16
+ kGlobalVarMessageType = 90,
+ kGlobalVarTextSpeed = 94, // SCI32; 0 is fastest, 8 is slowest
+ kGlobalVarLSL6HiresTextSpeed = 167, // 1 is fastest, 14 is slowest
+ kGlobalVarShivers1Score = 349
+};
+
/** Number of kernel calls in between gcs; should be < 50000 */
enum {
GC_INTERVAL = 0x8000
@@ -366,6 +388,16 @@ SelectorType lookupSelector(SegManager *segMan, reg_t obj, Selector selectorid,
*/
int readPMachineInstruction(const byte *src, byte &extOpcode, int16 opparams[4]);
+/**
+ * Finds the script-absolute offset of a relative object offset.
+ *
+ * @param[in] relOffset the relative object offset
+ * @param[in] scr the owner script object, used by SCI1.1+
+ * @param[in] pcOffset the offset of the program counter, used by SCI0early and
+ * SCI3
+ */
+uint32 findOffset(const int16 relOffset, const Script *scr, const uint32 pcOffset);
+
} // End of namespace Sci
#endif // SCI_ENGINE_VM_H
diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp
index 53a5a5c507..b2e250ab8b 100644
--- a/engines/sci/engine/vm_types.cpp
+++ b/engines/sci/engine/vm_types.cpp
@@ -66,13 +66,10 @@ void reg_t::setOffset(uint32 offset) {
}
reg_t reg_t::lookForWorkaround(const reg_t right, const char *operation) const {
- SciTrackOriginReply originReply;
+ SciCallOrigin originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, arithmeticWorkarounds, &originReply);
if (solution.type == WORKAROUND_NONE)
- error("Invalid arithmetic operation (%s - params: %04x:%04x and %04x:%04x) from method %s::%s (room %d, script %d, localCall %x)",
- operation, PRINT_REG(*this), PRINT_REG(right), originReply.objectName.c_str(),
- originReply.methodName.c_str(), g_sci->getEngineState()->currentRoomNumber(), originReply.scriptNr,
- originReply.localCallOffset);
+ error("Invalid arithmetic operation (%s - params: %04x:%04x and %04x:%04x) from %s", operation, PRINT_REG(*this), PRINT_REG(right), originReply.toString().c_str());
assert(solution.type == WORKAROUND_FAKE);
return make_reg(0, solution.value);
}
@@ -230,6 +227,10 @@ int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const {
return toUint16() - right.toUint16();
else
return toSint16() - right.toSint16();
+#ifdef ENABLE_SCI32
+ } else if (getSciVersion() >= SCI_VERSION_2) {
+ return sci32Comparison(right);
+#endif
} else if (pointerComparisonWithInteger(right)) {
return 1;
} else if (right.pointerComparisonWithInteger(*this)) {
@@ -238,6 +239,26 @@ int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const {
return lookForWorkaround(right, "comparison").toSint16();
}
+#ifdef ENABLE_SCI32
+int reg_t::sci32Comparison(const reg_t right) const {
+ // In SCI32, MemIDs are normally indexes into the memory manager's handle
+ // list, but the engine reserves indexes at and above 20000 for objects
+ // that were created inside the engine (as opposed to inside the VM). The
+ // engine compares these as a tiebreaker for graphics objects that are at
+ // the same priority, and it is necessary to at least minimally handle
+ // this situation.
+ // This is obviously a bogus comparision, but then, this entire thing is
+ // bogus. For the moment, it just needs to be deterministic.
+ if (isNumber() && !right.isNumber()) {
+ return 1;
+ } else if (right.isNumber() && !isNumber()) {
+ return -1;
+ }
+
+ return getOffset() - right.getOffset();
+}
+#endif
+
bool reg_t::pointerComparisonWithInteger(const reg_t right) const {
// This function handles the case where a script tries to compare a pointer
// to a number. Normally, we would not want to allow that. However, SCI0 -
diff --git a/engines/sci/engine/vm_types.h b/engines/sci/engine/vm_types.h
index a646478a8e..e60f52e85c 100644
--- a/engines/sci/engine/vm_types.h
+++ b/engines/sci/engine/vm_types.h
@@ -160,6 +160,10 @@ private:
int cmp(const reg_t right, bool treatAsUnsigned) const;
reg_t lookForWorkaround(const reg_t right, const char *operation) const;
bool pointerComparisonWithInteger(const reg_t right) const;
+
+#ifdef ENABLE_SCI32
+ int sci32Comparison(const reg_t right) const;
+#endif
};
static inline reg_t make_reg(SegmentId segment, uint16 offset) {
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index aab32032f7..ed3c604f38 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -88,6 +88,7 @@ const SciWorkaroundEntry arithmeticWorkarounds[] = {
{ 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
+ { GID_TORIN, 51400,64928, 0, "Blink", "init", NULL, 0, { WORKAROUND_FAKE, 1 } }, // op_div: when Lycentia knocks Torin out after he removes her collar
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -124,6 +125,23 @@ static const uint16 sig_uninitread_hoyle4_1[] = {
SIG_END
};
+// Game: Hoyle 5
+// Calling method: export 2
+// Subroutine offset: 0x2fb2 (script 300)
+// Applies to at least: English PC demo
+static const uint16 sig_uninitread_hoyle5_1[] = {
+
+ 0x7e, SIG_ADDTOOFFSET(2), // line N
+ 0x7d, 0x68, 0x65, 0x61, 0x72,
+ 0x74, 0x73, 0x2e, 0x73,
+ 0x63, 0x00, // file "hearts.sc"
+ 0x3f, 0x01, // link 01
+ 0x7e, SIG_ADDTOOFFSET(2), // line N
+ 0x39, 0x4b, // pushi 4bh
+ 0x78, // push1
+ SIG_END
+};
+
// Game: Jones in the fast lane
// Calling method: weekendText::draw
// Subroutine offset: 0x03d3 (script 232)
@@ -279,6 +297,10 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ 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_HOYLE5, -1, 14, -1, NULL, "select", NULL, 1, { WORKAROUND_FAKE, 0 } }, // dragging the sliders in game settings
+ { GID_HOYLE5, -1, 64937, -1, NULL, "select", NULL, 7, { WORKAROUND_FAKE, 0 } }, // clicking the "control" and "options" buttons in the icon bar
+ { GID_HOYLE5, -1, 64937, -1, "IconBar", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // clicking on any button in the icon bar
+ { GID_HOYLE5, 300, 300, 0, "", "export 2", sig_uninitread_hoyle5_1, 0, { WORKAROUND_FAKE, 0 } }, // after passing around cards in hearts
{ 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
@@ -298,6 +320,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ 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_KQ7, 2450, 2450, 0, "exBridge", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // called when walking up to the throne in the cave in chapter 2
{ 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
@@ -316,19 +339,19 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ 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, 85, 0, "LL6Inv", "init", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when creating a new game
{ 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_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_PQSWAT, -1, 64950, 0, NULL, "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Using any menus in-game
{ 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
@@ -345,6 +368,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ 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, 470, 470, -1, "<invalid name>", "notify", NULL, 0, { WORKAROUND_FAKE, 0 } }, // same as previous, with rm470::name used for temp storage by fan patches added by GOG
{ 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
@@ -377,7 +401,10 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ 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_SQ6, 210, 210, 0, "buttonSecret", "doVerb", NULL, 0, { WORKAROUND_FAKE, 0 } }, // after winning the first round of stooge fighter 3
{ GID_TORIN, -1, 64017, 0, "oFlags", "clear", NULL, 0, { WORKAROUND_FAKE, 0 } }, // entering Torin's home in the French version
+ { GID_TORIN, 10000, 64029, 0, "oMessager", "nextMsg", NULL, 3, { WORKAROUND_FAKE, 0 } }, // start of chapter one
+ { GID_TORIN, 20100, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // going down the cliff at the first screen of chapter 2 (washing area)
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -392,6 +419,13 @@ const SciWorkaroundEntry kAbs_workarounds[] = {
};
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kArraySetElements_workarounds[] = {
+ { GID_GK1, 302, 64918, 0, "Str", "callKernel", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when erasing a letter on the wall in St Louis Cemetery
+ { GID_PHANTASMAGORIA, -1, 64918, 0, "Str", "callKernel", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when starting a new game and selecting a chapter above 1, or when quitting the chase (in every chase room), or when completing chase successfully
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kCelHigh_workarounds[] = {
{ 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
@@ -406,6 +440,7 @@ const SciWorkaroundEntry kCelWide_workarounds[] = {
{ 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
+ { GID_LSL6HIRES, -1, 94, 0, "ll6ControlPanel", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when opening the "controls" panel from the main menu, the third argument is missing
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -489,7 +524,7 @@ const SciWorkaroundEntry kDisplay_workarounds[] = {
{ 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, "doCatalog", "changeState", 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
};
@@ -512,6 +547,16 @@ const SciWorkaroundEntry kDisposeScript_workarounds[] = {
};
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kDoSoundPlay_workarounds[] = {
+ { GID_LSL6HIRES, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
+ { GID_QFG4, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
+ { GID_PQ4, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
+ { GID_KQ7, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
+ { GID_GK1, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // Mac version always passes an extra null argument
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kDoSoundFade_workarounds[] = {
{ 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
@@ -530,6 +575,12 @@ const SciWorkaroundEntry kGetAngle_workarounds[] = {
};
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kFileIOOpen_workarounds[] = {
+ { GID_TORIN, 61000, 61000, 0, "roSierraLogo", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // Missing second argument when the game checks for autosave.cat after the Sierra logo
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kFindKey_workarounds[] = {
{ 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
@@ -630,12 +681,18 @@ const SciWorkaroundEntry kGraphUpdateBox_workarounds[] = {
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kIsObject_workarounds[] = {
- { 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_GK1DEMO, 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, local-call-signature, index, workaround
+const SciWorkaroundEntry kListAt_workarounds[] = {
+ { GID_HOYLE5, 100, 64999, 0, "theHands", "at", NULL, 0, { WORKAROUND_FAKE, 0 } }, // After the first hand is dealt in Crazy Eights game in demo, an object is passed instead of a number
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kMemory_workarounds[] = {
{ 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
@@ -656,21 +713,60 @@ const SciWorkaroundEntry kNewWindow_workarounds[] = {
};
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kPalVarySetPercent_workarounds[] = {
+ { GID_GK1, 370, 370, 0, "graceComeOut", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // there's an extra parameter in GK1, when changing chapters. This extra parameter seems to be a bug or just unimplemented functionality, as there's no visible change from the original in the chapter change room
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kPlatform32_workarounds[] = {
+ { GID_HOYLE5, -1, 0, 0, "hoyle4", "newRoom", NULL, 0, { WORKAROUND_FAKE, 1 } }, // at the start of the game, incorrectly uses SCI16 calling convention for kPlatform
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kRandom_workarounds[] = {
+ { GID_TORIN, 51400,64928, 0, "Blink", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when Lycentia knocks Torin out after he removes her collar
+ { GID_TORIN, 51400,64928, 0, "Blink", "cycleDone", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when Lycentia knocks Torin out after he removes her collar
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+
+// 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", 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
};
+// Game: Leisure Suit Larry 6 hires
+// Calling method: myCreditText::changeState
+// Subroutine offset: 0x8c (script 740)
+// Applies to at least: English PC CD
+static const uint16 sig_kResCheck_lsl6hires_1[] = {
+ 0x3f, 0x01, // link 01
+ 0x81, 0x13, // lag global[$13]
+ 0xa5, 0x00, // sat 00
+ 0x7a, // push2
+ SIG_END
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kResCheck_workarounds[] = {
+ { GID_LSL6HIRES, 740, 740, -1, "myCreditText", "handleEvent", sig_kResCheck_lsl6hires_1, -1, { WORKAROUND_IGNORE, 0 } }, // when clicking quit during the final credits
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[] = {
- { 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
+ { GID_QFG4DEMO, 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, local-call-signature, index, workaround
const SciWorkaroundEntry kSetCursor_workarounds[] = {
{ GID_KQ5, -1, 768, 0, "KQCursor", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // CD: gets called with 4 additional "900d" parameters
+ { GID_MOTHERGOOSEHIRES,-1, 0, -1, "MG", "setCursor", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // At the start of the game, an object is passed as the cel number
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -731,13 +827,20 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = {
{ 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, 740, 740, 0, "showCartoon", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // during ending, multiple 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
};
-SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin) {
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kScrollWindowAdd_workarounds[] = {
+ { GID_PHANTASMAGORIA, 45, 64907, 0, "ScrollableWindow", "addString", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // ScrollWindow interface passes the last two parameters twice
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciCallOrigin *trackOrigin) {
// HACK for SCI3: Temporarily ignore this
if (getSciVersion() == SCI_VERSION_3) {
warning("SCI3 HACK: trackOriginAndFindWorkaround() called, ignoring");
@@ -749,37 +852,14 @@ SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroun
const EngineState *state = g_sci->getEngineState();
ExecStack *lastCall = state->xs;
- const Script *localScript = state->_segMan->getScriptIfLoaded(lastCall->local_segment);
- int curScriptNr = localScript->getScriptNumber();
- int curLocalCallOffset = lastCall->debugLocalCallOffset;
-
- 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()) {
- callIterator--;
- ExecStack loopCall = *callIterator;
- if ((loopCall.debugSelector != -1) || (loopCall.debugExportId != -1)) {
- lastCall->debugSelector = loopCall.debugSelector;
- lastCall->debugExportId = loopCall.debugExportId;
- break;
- }
- }
- }
-
- Common::String curObjectName = state->_segMan->getObjectName(lastCall->sendp);
- Common::String curMethodName;
const SciGameId gameId = g_sci->getGameId();
- const int curRoomNumber = state->currentRoomNumber();
-
- if (lastCall->type == EXEC_STACK_TYPE_CALL) {
- if (lastCall->debugSelector != -1) {
- curMethodName = g_sci->getKernel()->getSelectorName(lastCall->debugSelector);
- } else if (lastCall->debugExportId != -1) {
- curObjectName = "";
- curMethodName = Common::String::format("export %d", lastCall->debugExportId);
- }
- }
+
+ *trackOrigin = state->getCurrentCallOrigin();
+ const Common::String &curObjectName = trackOrigin->objectName;
+ const Common::String &curMethodName = trackOrigin->methodName;
+ const int &curRoomNumber = trackOrigin->roomNr;
+ const int &curScriptNr = trackOrigin->scriptNr;
+ const int &curLocalCallOffset = trackOrigin->localCallOffset;
if (workaroundList) {
// Search if there is a workaround for this one
@@ -855,12 +935,6 @@ SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroun
} while (!searchObject.isNull()); // no parent left?
}
- // give caller origin data
- trackOrigin->objectName = curObjectName;
- trackOrigin->methodName = curMethodName;
- trackOrigin->scriptNr = curScriptNr;
- trackOrigin->localCallOffset = lastCall->debugLocalCallOffset;
-
SciWorkaroundSolution noneFound;
noneFound.type = WORKAROUND_NONE;
noneFound.value = 0;
diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h
index 46059a175c..86b4ee2902 100644
--- a/engines/sci/engine/workarounds.h
+++ b/engines/sci/engine/workarounds.h
@@ -24,6 +24,7 @@
#define SCI_ENGINE_WORKAROUNDS_H
#include "sci/engine/vm_types.h"
+#include "sci/engine/state.h"
#include "sci/sci.h"
namespace Sci {
@@ -35,13 +36,6 @@ enum SciWorkaroundType {
WORKAROUND_FAKE // fake kernel call / replace temp value / fake opcode
};
-struct SciTrackOriginReply {
- int scriptNr;
- Common::String objectName;
- Common::String methodName;
- int localCallOffset;
-};
-
struct SciWorkaroundSolution {
SciWorkaroundType type;
uint16 value;
@@ -74,7 +68,9 @@ extern const SciWorkaroundEntry kDeviceInfo_workarounds[];
extern const SciWorkaroundEntry kDisplay_workarounds[];
extern const SciWorkaroundEntry kDirLoop_workarounds[];
extern const SciWorkaroundEntry kDisposeScript_workarounds[];
+extern const SciWorkaroundEntry kDoSoundPlay_workarounds[];
extern const SciWorkaroundEntry kDoSoundFade_workarounds[];
+extern const SciWorkaroundEntry kFileIOOpen_workarounds[];
extern const SciWorkaroundEntry kFindKey_workarounds[];
extern const SciWorkaroundEntry kDeleteKey_workarounds[];
extern const SciWorkaroundEntry kGetAngle_workarounds[];
@@ -86,19 +82,27 @@ extern const SciWorkaroundEntry kGraphFillBoxForeground_workarounds[];
extern const SciWorkaroundEntry kGraphFillBoxAny_workarounds[];
extern const SciWorkaroundEntry kGraphRedrawBox_workarounds[];
extern const SciWorkaroundEntry kIsObject_workarounds[];
+extern const SciWorkaroundEntry kListAt_workarounds[];
extern const SciWorkaroundEntry kMemory_workarounds[];
extern const SciWorkaroundEntry kMoveCursor_workarounds[];
extern const SciWorkaroundEntry kNewWindow_workarounds[];
+extern const SciWorkaroundEntry kPalVarySetPercent_workarounds[];
+extern const SciWorkaroundEntry kPlatform32_workarounds[];
+extern const SciWorkaroundEntry kRandom_workarounds[];
extern const SciWorkaroundEntry kReadNumber_workarounds[];
+extern const SciWorkaroundEntry kResCheck_workarounds[];
extern const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[];
extern const SciWorkaroundEntry kSetCursor_workarounds[];
+extern const SciWorkaroundEntry kArraySetElements_workarounds[];
extern const SciWorkaroundEntry kSetPort_workarounds[];
extern const SciWorkaroundEntry kStrAt_workarounds[];
extern const SciWorkaroundEntry kStrCpy_workarounds[];
extern const SciWorkaroundEntry kStrLen_workarounds[];
extern const SciWorkaroundEntry kUnLoad_workarounds[];
+extern const SciWorkaroundEntry kStringNew_workarounds[];
+extern const SciWorkaroundEntry kScrollWindowAdd_workarounds[];
-extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin);
+extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciCallOrigin *trackOrigin);
} // End of namespace Sci
diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp
index 9a87c6de0b..e365296914 100644
--- a/engines/sci/event.cpp
+++ b/engines/sci/event.cpp
@@ -29,6 +29,10 @@
#include "sci/console.h"
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
+#ifdef ENABLE_SCI32
+#include "sci/graphics/cursor32.h"
+#include "sci/graphics/frameout.h"
+#endif
#include "sci/graphics/screen.h"
namespace Sci {
@@ -105,8 +109,12 @@ static const MouseEventConversion mouseEventMappings[] = {
{ Common::EVENT_MBUTTONUP, SCI_EVENT_MOUSE_RELEASE }
};
-EventManager::EventManager(bool fontIsExtended) : _fontIsExtended(fontIsExtended) {
-}
+EventManager::EventManager(bool fontIsExtended) :
+ _fontIsExtended(fontIsExtended)
+#ifdef ENABLE_SCI32
+ , _hotRectanglesActive(false)
+#endif
+ {}
EventManager::~EventManager() {
}
@@ -133,8 +141,13 @@ static int altify(int ch) {
}
SciEvent EventManager::getScummVMEvent() {
- SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) };
- SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) };
+#ifdef ENABLE_SCI32
+ SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point(), -1 };
+ SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point(), -1 };
+#else
+ SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point() };
+ SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point() };
+#endif
Common::EventManager *em = g_system->getEventManager();
Common::Event ev;
@@ -155,7 +168,29 @@ SciEvent EventManager::getScummVMEvent() {
// via pollEvent.
// We also adjust the position based on the scaling of the screen.
Common::Point mousePos = em->getMousePos();
- g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x);
+
+#if ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ const Buffer &screen = g_sci->_gfxFrameout->getCurrentBuffer();
+
+ // This will clamp `mousePos` according to the restricted zone,
+ // so any cursor or screen item associated with the mouse position
+ // does not bounce when it hits the edge (or ignore the edge)
+ g_sci->_gfxCursor32->deviceMoved(mousePos);
+
+ Common::Point mousePosSci = mousePos;
+ mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight));
+ noEvent.mousePosSci = input.mousePosSci = mousePosSci;
+
+ if (_hotRectanglesActive) {
+ checkHotRectangles(mousePosSci);
+ }
+ } else {
+#endif
+ g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x);
+#if ENABLE_SCI32
+ }
+#endif
noEvent.mousePos = input.mousePos = mousePos;
@@ -168,7 +203,7 @@ SciEvent EventManager::getScummVMEvent() {
return noEvent;
}
- if (ev.type == Common::EVENT_QUIT) {
+ if (ev.type == Common::EVENT_QUIT || ev.type == Common::EVENT_RTL) {
input.type = SCI_EVENT_QUIT;
return input;
}
@@ -302,6 +337,11 @@ SciEvent EventManager::getScummVMEvent() {
input.character = altify(input.character);
if (getSciVersion() <= SCI_VERSION_1_MIDDLE && (scummVMKeyFlags & Common::KBD_CTRL) && input.character > 0 && input.character < 27)
input.character += 96; // 0x01 -> 'a'
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2 && (scummVMKeyFlags & Common::KBD_CTRL) && input.character == 'c') {
+ input.character = SCI_KEY_ETX;
+ }
+#endif
// If no actual key was pressed (e.g. if only a modifier key was pressed),
// ignore the event
@@ -328,10 +368,16 @@ void EventManager::updateScreen() {
}
}
-SciEvent EventManager::getSciEvent(unsigned int mask) {
- SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) };
+SciEvent EventManager::getSciEvent(uint32 mask) {
+#ifdef ENABLE_SCI32
+ SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point(), -1 };
+#else
+ SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point() };
+#endif
- EventManager::updateScreen();
+ if (getSciVersion() < SCI_VERSION_2) {
+ updateScreen();
+ }
// Get all queued events from graphics driver
do {
@@ -362,24 +408,43 @@ SciEvent EventManager::getSciEvent(unsigned int mask) {
return event;
}
-void SciEngine::sleep(uint32 msecs) {
- uint32 time;
- const uint32 wakeUpTime = g_system->getMillis() + msecs;
-
- while (true) {
- // let backend process events and update the screen
- _eventMan->getSciEvent(SCI_EVENT_PEEK);
- time = g_system->getMillis();
- if (time + 10 < wakeUpTime) {
- g_system->delayMillis(10);
- } else {
- if (time < wakeUpTime)
- g_system->delayMillis(wakeUpTime - time);
- break;
+#ifdef ENABLE_SCI32
+void EventManager::setHotRectanglesActive(const bool active) {
+ _hotRectanglesActive = active;
+}
+
+void EventManager::setHotRectangles(const Common::Array<Common::Rect> &rects) {
+ _hotRects = rects;
+ _activeRectIndex = -1;
+}
+
+void EventManager::checkHotRectangles(const Common::Point &mousePosition) {
+ int lastActiveRectIndex = _activeRectIndex;
+ _activeRectIndex = -1;
+
+ for (int16 i = 0; i < (int16)_hotRects.size(); ++i) {
+ if (_hotRects[i].contains(mousePosition)) {
+ _activeRectIndex = i;
+ if (i != lastActiveRectIndex) {
+ SciEvent hotRectEvent;
+ hotRectEvent.type = SCI_EVENT_HOT_RECTANGLE;
+ hotRectEvent.hotRectangleIndex = i;
+ _events.push_front(hotRectEvent);
+ break;
+ }
+
+ lastActiveRectIndex = _activeRectIndex;
}
+ }
+ if (lastActiveRectIndex != _activeRectIndex && lastActiveRectIndex != -1) {
+ _activeRectIndex = -1;
+ SciEvent hotRectEvent;
+ hotRectEvent.type = SCI_EVENT_HOT_RECTANGLE;
+ hotRectEvent.hotRectangleIndex = -1;
+ _events.push_front(hotRectEvent);
}
}
-
+#endif
} // End of namespace Sci
diff --git a/engines/sci/event.h b/engines/sci/event.h
index 76c884aba4..23b59f964a 100644
--- a/engines/sci/event.h
+++ b/engines/sci/event.h
@@ -39,11 +39,20 @@ struct SciEvent {
uint16 character;
/**
- * The mouse position at the time the event was created.
- *
- * These are display coordinates!
+ * The mouse position at the time the event was created,
+ * in display coordinates.
*/
Common::Point mousePos;
+
+#ifdef ENABLE_SCI32
+ /**
+ * The mouse position at the time the event was created,
+ * in script coordinates.
+ */
+ Common::Point mousePosSci;
+
+ int16 hotRectangleIndex;
+#endif
};
/*Values for type*/
@@ -53,12 +62,18 @@ struct SciEvent {
#define SCI_EVENT_KEYBOARD (1 << 2)
#define SCI_EVENT_DIRECTION (1 << 6)
#define SCI_EVENT_SAID (1 << 7)
+#ifdef ENABLE_SCI32
+#define SCI_EVENT_HOT_RECTANGLE (1 << 8)
+#endif
/*Fake values for other events*/
#define SCI_EVENT_QUIT (1 << 11)
#define SCI_EVENT_PEEK (1 << 15)
#define SCI_EVENT_ANY 0x7fff
/* Keycodes of special keys: */
+#ifdef ENABLE_SCI32
+#define SCI_KEY_ETX 3
+#endif
#define SCI_KEY_ESC 27
#define SCI_KEY_BACKSPACE 8
#define SCI_KEY_ENTER 13
@@ -121,13 +136,24 @@ public:
~EventManager();
void updateScreen();
- SciEvent getSciEvent(unsigned int mask);
+ SciEvent getSciEvent(uint32 mask);
private:
SciEvent getScummVMEvent();
const bool _fontIsExtended;
Common::List<SciEvent> _events;
+#ifdef ENABLE_SCI32
+public:
+ void setHotRectanglesActive(const bool active);
+ void setHotRectangles(const Common::Array<Common::Rect> &rects);
+ void checkHotRectangles(const Common::Point &mousePosition);
+
+private:
+ bool _hotRectanglesActive;
+ Common::Array<Common::Rect> _hotRects;
+ int16 _activeRectIndex;
+#endif
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/animate.cpp b/engines/sci/graphics/animate.cpp
index 98278397b7..8d92cb905f 100644
--- a/engines/sci/graphics/animate.cpp
+++ b/engines/sci/graphics/animate.cpp
@@ -145,7 +145,7 @@ bool GfxAnimate::invoke(List *list, int argc, reg_t *argv) {
if (_fastCastEnabled) {
// Check if the game has a fastCast object set
// if we don't abort kAnimate processing, at least in kq5 there will be animation cels drawn into speech boxes.
- if (!_s->variables[VAR_GLOBAL][84].isNull()) {
+ if (!_s->variables[VAR_GLOBAL][kGlobalVarFastCast].isNull()) {
// This normally points to an object called "fastCast",
// but for example in Eco Quest 1 it may also point to an object called "EventHandler" (see bug #5170)
// Original SCI only checked, if this global was not 0.
@@ -338,7 +338,7 @@ void GfxAnimate::applyGlobalScaling(AnimateList::iterator entry, GfxView *view)
int16 maxScale = readSelectorValue(_s->_segMan, entry->object, SELECTOR(maxScale));
int16 celHeight = view->getHeight(entry->loopNo, entry->celNo);
int16 maxCelHeight = (maxScale * celHeight) >> 7;
- reg_t globalVar2 = _s->variables[VAR_GLOBAL][2]; // current room object
+ reg_t globalVar2 = _s->variables[VAR_GLOBAL][kGlobalVarCurrentRoom]; // current room object
int16 vanishingY = readSelectorValue(_s->_segMan, globalVar2, SELECTOR(vanishingY));
int16 fixedPortY = _ports->getPort()->rect.bottom - vanishingY;
diff --git a/engines/sci/graphics/cache.cpp b/engines/sci/graphics/cache.cpp
index 59af8334eb..9c77f31a14 100644
--- a/engines/sci/graphics/cache.cpp
+++ b/engines/sci/graphics/cache.cpp
@@ -95,15 +95,21 @@ int16 GfxCache::kernelViewGetCelHeight(GuiResourceId viewId, int16 loopNo, int16
}
int16 GfxCache::kernelViewGetLoopCount(GuiResourceId viewId) {
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ return CelObjView::getNumLoops(viewId);
+ }
+#endif
return getView(viewId)->getLoopCount();
}
int16 GfxCache::kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo) {
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ return CelObjView::getNumCels(viewId, loopNo);
+ }
+#endif
return getView(viewId)->getCelCount(loopNo);
}
-byte GfxCache::kernelViewGetColorAtCoordinate(GuiResourceId viewId, int16 loopNo, int16 celNo, int16 x, int16 y) {
- return getView(viewId)->getColorAtCoordinate(loopNo, celNo, x, y);
-}
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/cache.h b/engines/sci/graphics/cache.h
index 33fa4fe399..61952718a9 100644
--- a/engines/sci/graphics/cache.h
+++ b/engines/sci/graphics/cache.h
@@ -49,8 +49,6 @@ public:
int16 kernelViewGetLoopCount(GuiResourceId viewId);
int16 kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo);
- byte kernelViewGetColorAtCoordinate(GuiResourceId viewId, int16 loopNo, int16 celNo, int16 x, int16 y);
-
private:
void purgeFontCache();
void purgeViewCache();
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index f8bce26a2c..fe0fbf7cc4 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -21,22 +21,22 @@
*/
#include "sci/resource.h"
+#include "sci/engine/features.h"
#include "sci/engine/seg_manager.h"
#include "sci/engine/state.h"
#include "sci/graphics/celobj32.h"
#include "sci/graphics/frameout.h"
#include "sci/graphics/palette32.h"
-#include "sci/graphics/picture.h"
-#include "sci/graphics/view.h"
+#include "sci/graphics/remap32.h"
+#include "sci/graphics/text32.h"
+#include "sci/engine/workarounds.h"
namespace Sci {
#pragma mark CelScaler
+
CelScaler *CelObj::_scaler = nullptr;
void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
- const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
- const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
-
for (int i = 0; i < ARRAYSIZE(_scaleTables); ++i) {
if (_scaleTables[i].scaleX == scaleX && _scaleTables[i].scaleY == scaleY) {
_activeIndex = i;
@@ -44,19 +44,17 @@ void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
}
}
- int i = 1 - _activeIndex;
+ const int i = 1 - _activeIndex;
_activeIndex = i;
CelScalerTable &table = _scaleTables[i];
if (table.scaleX != scaleX) {
- assert(screenWidth <= ARRAYSIZE(table.valuesX));
- buildLookupTable(table.valuesX, scaleX, screenWidth);
+ buildLookupTable(table.valuesX, scaleX, kCelScalerTableSize);
table.scaleX = scaleX;
}
if (table.scaleY != scaleY) {
- assert(screenHeight <= ARRAYSIZE(table.valuesY));
- buildLookupTable(table.valuesY, scaleY, screenHeight);
+ buildLookupTable(table.valuesY, scaleY, kCelScalerTableSize);
table.scaleY = scaleY;
}
}
@@ -64,7 +62,7 @@ void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
void CelScaler::buildLookupTable(int *table, const Ratio &ratio, const int size) {
int value = 0;
int remainder = 0;
- int num = ratio.getNumerator();
+ const int num = ratio.getNumerator();
for (int i = 0; i < size; ++i) {
*table++ = value;
remainder += ratio.getDenominator();
@@ -82,12 +80,13 @@ const CelScalerTable *CelScaler::getScalerTable(const Ratio &scaleX, const Ratio
#pragma mark -
#pragma mark CelObj
+bool CelObj::_drawBlackLines = false;
void CelObj::init() {
+ CelObj::deinit();
+ _drawBlackLines = false;
_nextCacheId = 1;
- delete _scaler;
_scaler = new CelScaler();
- delete _cache;
_cache = new CelCache;
_cache->resize(100);
}
@@ -95,6 +94,11 @@ void CelObj::init() {
void CelObj::deinit() {
delete _scaler;
_scaler = nullptr;
+ if (_cache != nullptr) {
+ for (CelCache::iterator it = _cache->begin(); it != _cache->end(); ++it) {
+ delete it->celObj;
+ }
+ }
delete _cache;
_cache = nullptr;
}
@@ -102,27 +106,45 @@ void CelObj::deinit() {
#pragma mark -
#pragma mark CelObj - Scalers
-template <bool FLIP, typename READER>
+template<bool FLIP, typename READER>
struct SCALER_NoScale {
+#ifndef NDEBUG
+ const byte *_rowEdge;
+#endif
const byte *_row;
READER _reader;
const int16 _lastIndex;
+ const int16 _sourceX;
+ const int16 _sourceY;
- SCALER_NoScale(const CelObj &celObj, const int16 maxWidth) :
- _reader(celObj, maxWidth),
- _lastIndex(maxWidth - 1) {}
+ SCALER_NoScale(const CelObj &celObj, const int16 maxWidth, const Common::Point &scaledPosition) :
+ _row(nullptr),
+ _reader(celObj, FLIP ? celObj._width : maxWidth),
+ _lastIndex(celObj._width - 1),
+ _sourceX(scaledPosition.x),
+ _sourceY(scaledPosition.y) {}
- inline void setSource(const int16 x, const int16 y) {
- _row = _reader.getRow(y);
+ inline void setTarget(const int16 x, const int16 y) {
+ _row = _reader.getRow(y - _sourceY);
if (FLIP) {
- _row += _lastIndex - x;
+#ifndef NDEBUG
+ _rowEdge = _row - 1;
+#endif
+ _row += _lastIndex - (x - _sourceX);
+ assert(_row > _rowEdge);
} else {
- _row += x;
+#ifndef NDEBUG
+ _rowEdge = _row + _lastIndex + 1;
+#endif
+ _row += x - _sourceX;
+ assert(_row < _rowEdge);
}
}
inline byte read() {
+ assert(_row != _rowEdge);
+
if (FLIP) {
return *_row--;
} else {
@@ -133,69 +155,140 @@ struct SCALER_NoScale {
template<bool FLIP, typename READER>
struct SCALER_Scale {
+#ifndef NDEBUG
+ int16 _minX;
+ int16 _maxX;
+#endif
const byte *_row;
READER _reader;
- const CelScalerTable *_table;
int16 _x;
- const uint16 _lastIndex;
-
- SCALER_Scale(const CelObj &celObj, const int16 maxWidth, const Ratio scaleX, const Ratio scaleY) :
+ static int16 _valuesX[kCelScalerTableSize];
+ static int16 _valuesY[kCelScalerTableSize];
+
+ SCALER_Scale(const CelObj &celObj, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio scaleX, const Ratio scaleY) :
+ _row(nullptr),
+#ifndef NDEBUG
+ _minX(targetRect.left),
+ _maxX(targetRect.right - 1),
+#endif
// The maximum width of the scaled object may not be as
// wide as the source data it requires if downscaling,
// so just always make the reader decompress an entire
// line of source data when scaling
- _reader(celObj, celObj._width),
- _table(CelObj::_scaler->getScalerTable(scaleX, scaleY)),
- _lastIndex(maxWidth - 1) {}
+ _reader(celObj, celObj._width) {
+ // In order for scaling ratios to apply equally across objects that
+ // start at different positions on the screen (like the cels of a
+ // picture), the pixels that are read from the source bitmap must all
+ // use the same pattern of division. In other words, cels must follow
+ // a global scaling pattern as if they were always drawn starting at an
+ // even multiple of the scaling ratio, even if they are not.
+ //
+ // To get the correct source pixel when reading out through the scaler,
+ // the engine creates a lookup table for each axis that translates
+ // directly from target positions to the indexes of source pixels using
+ // the global cadence for the given scaling ratio.
+ //
+ // Note, however, that not all games use the global scaling mode.
+ //
+ // SQ6 definitely uses the global scaling mode (an easy visual
+ // comparison is to leave Implants N' Stuff and then look at Roger);
+ // Torin definitely does not (scaling subtitle backgrounds will cause it
+ // to attempt a read out of bounds and crash). They are both SCI
+ // "2.1mid" games, so currently the common denominator looks to be that
+ // games which use global scaling are the ones that use low-resolution
+ // script coordinates too.
+
+ const CelScalerTable *table = CelObj::_scaler->getScalerTable(scaleX, scaleY);
+
+ if (g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth == kLowResX) {
+ const int16 unscaledX = (scaledPosition.x / scaleX).toInt();
+ if (FLIP) {
+ const int lastIndex = celObj._width - 1;
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = lastIndex - (table->valuesX[x] - unscaledX);
+ }
+ } else {
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = table->valuesX[x] - unscaledX;
+ }
+ }
- inline void setSource(const int16 x, const int16 y) {
- _row = _reader.getRow(_table->valuesY[y]);
- if (FLIP) {
- _x = _lastIndex - x;
+ const int16 unscaledY = (scaledPosition.y / scaleY).toInt();
+ for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
+ _valuesY[y] = table->valuesY[y] - unscaledY;
+ }
} else {
- _x = x;
+ if (FLIP) {
+ const int lastIndex = celObj._width - 1;
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = lastIndex - table->valuesX[x - scaledPosition.x];
+ }
+ } else {
+ for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+ _valuesX[x] = table->valuesX[x - scaledPosition.x];
+ }
+ }
+
+ for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
+ _valuesY[y] = table->valuesY[y - scaledPosition.y];
+ }
}
}
+ inline void setTarget(const int16 x, const int16 y) {
+ _row = _reader.getRow(_valuesY[y]);
+ _x = x;
+ assert(_x >= _minX && _x <= _maxX);
+ }
+
inline byte read() {
- if (FLIP) {
- return _row[_table->valuesX[_x--]];
- } else {
- return _row[_table->valuesX[_x++]];
- }
+ assert(_x >= _minX && _x <= _maxX);
+ return _row[_valuesX[_x++]];
}
};
+template<bool FLIP, typename READER>
+int16 SCALER_Scale<FLIP, READER>::_valuesX[kCelScalerTableSize];
+template<bool FLIP, typename READER>
+int16 SCALER_Scale<FLIP, READER>::_valuesY[kCelScalerTableSize];
+
#pragma mark -
#pragma mark CelObj - Resource readers
struct READER_Uncompressed {
private:
- byte *_pixels;
+#ifndef NDEBUG
+ const int16 _sourceHeight;
+#endif
+ const byte *_pixels;
const int16 _sourceWidth;
public:
READER_Uncompressed(const CelObj &celObj, const int16) :
+#ifndef NDEBUG
+ _sourceHeight(celObj._height),
+#endif
_sourceWidth(celObj._width) {
- byte *resource = celObj.getResPointer();
+ const byte *resource = celObj.getResPointer();
_pixels = resource + READ_SCI11ENDIAN_UINT32(resource + celObj._celHeaderOffset + 24);
}
inline const byte *getRow(const int16 y) const {
+ assert(y >= 0 && y < _sourceHeight);
return _pixels + y * _sourceWidth;
}
};
struct READER_Compressed {
private:
- byte *_resource;
- byte _buffer[1024];
+ const byte *const _resource;
+ byte _buffer[kCelScalerTableSize];
uint32 _controlOffset;
uint32 _dataOffset;
uint32 _uncompressedDataOffset;
int16 _y;
const int16 _sourceHeight;
- const uint8 _transparentColor;
+ const uint8 _skipColor;
const int16 _maxWidth;
public:
@@ -203,27 +296,28 @@ public:
_resource(celObj.getResPointer()),
_y(-1),
_sourceHeight(celObj._height),
- _transparentColor(celObj._transparentColor),
+ _skipColor(celObj._skipColor),
_maxWidth(maxWidth) {
- assert(_maxWidth <= celObj._width);
+ assert(maxWidth <= celObj._width);
- byte *celHeader = _resource + celObj._celHeaderOffset;
+ const byte *const celHeader = _resource + celObj._celHeaderOffset;
_dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24);
_uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28);
_controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32);
}
inline const byte *getRow(const int16 y) {
+ assert(y >= 0 && y < _sourceHeight);
if (y != _y) {
// compressed data segment for row
- byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4);
+ const byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4);
// uncompressed data segment for row
- byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4);
+ const byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4);
uint8 length;
- for (int i = 0; i < _maxWidth; i += length) {
- byte controlByte = *row++;
+ for (int16 i = 0; i < _maxWidth; i += length) {
+ const byte controlByte = *row++;
length = controlByte;
// Run-length encoded
@@ -233,7 +327,7 @@ public:
// Fill with skip color
if (controlByte & 0x40) {
- memset(_buffer + i, _transparentColor, length);
+ memset(_buffer + i, _skipColor, length);
// Next value is fill color
} else {
memset(_buffer + i, *literal, length);
@@ -256,6 +350,10 @@ public:
#pragma mark -
#pragma mark CelObj - Remappers
+/**
+ * Pixel mapper for a CelObj with transparent pixels and no
+ * remapping data.
+ */
struct MAPPER_NoMD {
inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
if (pixel != skipColor) {
@@ -263,162 +361,137 @@ struct MAPPER_NoMD {
}
}
};
+
+/**
+ * Pixel mapper for a CelObj with no transparent pixels and
+ * no remapping data.
+ */
struct MAPPER_NoMDNoSkip {
inline void draw(byte *target, const byte pixel, const uint8) const {
*target = pixel;
}
};
+/**
+ * Pixel mapper for a CelObj with transparent pixels,
+ * remapping data, and remapping enabled.
+ */
+struct MAPPER_Map {
+ inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
+ if (pixel != skipColor) {
+ // NOTE: For some reason, SSCI never checks if the source
+ // pixel is *above* the range of remaps.
+ if (pixel < g_sci->_gfxRemap32->getStartColor()) {
+ *target = pixel;
+ } else if (g_sci->_gfxRemap32->remapEnabled(pixel)) {
+ *target = g_sci->_gfxRemap32->remapColor(pixel, *target);
+ }
+ }
+ }
+};
+
+/**
+ * Pixel mapper for a CelObj with transparent pixels,
+ * remapping data, and remapping disabled.
+ */
+struct MAPPER_NoMap {
+ inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
+ // NOTE: For some reason, SSCI never checks if the source
+ // pixel is *above* the range of remaps.
+ if (pixel != skipColor && pixel < g_sci->_gfxRemap32->getStartColor()) {
+ *target = pixel;
+ }
+ }
+};
+
void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const {
- const Buffer &priorityMap = g_sci->_gfxFrameout->getPriorityMap();
const Common::Point &scaledPosition = screenItem._scaledPosition;
const Ratio &scaleX = screenItem._ratioX;
const Ratio &scaleY = screenItem._ratioY;
+ _drawBlackLines = screenItem._drawBlackLines;
if (_remap) {
- if (g_sci->_gfxFrameout->_hasRemappedScreenItem) {
- const uint8 priority = MAX((int16)0, MIN((int16)255, screenItem._priority));
-
- // NOTE: In the original engine code, there was a second branch for
- // _remap here that would then call the following functions if _remap was false:
- //
- // drawHzFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
- // drawNoFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
- // drawUncompHzFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
- // drawUncompNoFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
- // scaleDraw(Buffer &, Buffer &, Ratio &, Ratio &, Common::Rect &, Common::Point &, uint8)
- // scaleDrawUncomp(Buffer &, Buffer &, Ratio &, Ratio &, Common::Rect &, Common::Point &, uint8)
- //
- // However, obviously, _remap cannot be false here. This dead code branch existed in
- // at least SCI2/GK1 and SCI2.1/SQ6.
-
+ // NOTE: In the original code this check was `g_Remap_numActiveRemaps && _remap`,
+ // but since we are already in a `_remap` branch, there is no reason to check it
+ // again
+ if (g_sci->_gfxRemap32->getRemapCount()) {
if (scaleX.isOne() && scaleY.isOne()) {
if (_compressionType == kCelCompressionNone) {
if (_drawMirrored) {
- drawUncompHzFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+ drawUncompHzFlipMap(target, targetRect, scaledPosition);
} else {
- drawUncompNoFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+ drawUncompNoFlipMap(target, targetRect, scaledPosition);
}
} else {
if (_drawMirrored) {
- drawHzFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+ drawHzFlipMap(target, targetRect, scaledPosition);
} else {
- drawNoFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+ drawNoFlipMap(target, targetRect, scaledPosition);
}
}
} else {
if (_compressionType == kCelCompressionNone) {
- scaleDrawUncompMap(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+ scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
} else {
- scaleDrawMap(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+ scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
}
}
} else {
- // NOTE: In the original code this check was `g_Remap_numActiveRemaps && _remap`,
- // but since we are already in a `_remap` branch, there is no reason to check it
- // again
- if (/* TODO: g_Remap_numActiveRemaps */ false) {
- if (scaleX.isOne() && scaleY.isOne()) {
- if (_compressionType == kCelCompressionNone) {
- if (_drawMirrored) {
- drawUncompHzFlipMap(target, targetRect, scaledPosition);
- } else {
- drawUncompNoFlipMap(target, targetRect, scaledPosition);
- }
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlip(target, targetRect, scaledPosition);
} else {
- if (_drawMirrored) {
- drawHzFlipMap(target, targetRect, scaledPosition);
- } else {
- drawNoFlipMap(target, targetRect, scaledPosition);
- }
+ drawUncompNoFlip(target, targetRect, scaledPosition);
}
} else {
- if (_compressionType == kCelCompressionNone) {
- scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ if (_drawMirrored) {
+ drawHzFlip(target, targetRect, scaledPosition);
} else {
- scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ drawNoFlip(target, targetRect, scaledPosition);
}
}
} else {
- if (scaleX.isOne() && scaleY.isOne()) {
- if (_compressionType == kCelCompressionNone) {
- if (_drawMirrored) {
- drawUncompHzFlip(target, targetRect, scaledPosition);
- } else {
- drawUncompNoFlip(target, targetRect, scaledPosition);
- }
- } else {
- if (_drawMirrored) {
- drawHzFlip(target, targetRect, scaledPosition);
- } else {
- drawNoFlip(target, targetRect, scaledPosition);
- }
- }
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncomp(target, scaleX, scaleY, targetRect, scaledPosition);
} else {
- if (_compressionType == kCelCompressionNone) {
- scaleDrawUncomp(target, scaleX, scaleY, targetRect, scaledPosition);
- } else {
- scaleDraw(target, scaleX, scaleY, targetRect, scaledPosition);
- }
+ scaleDraw(target, scaleX, scaleY, targetRect, scaledPosition);
}
}
}
} else {
- if (g_sci->_gfxFrameout->_hasRemappedScreenItem) {
- const uint8 priority = MAX((int16)0, MIN((int16)255, screenItem._priority));
- if (scaleX.isOne() && scaleY.isOne()) {
- if (_compressionType == kCelCompressionNone) {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_transparent) {
if (_drawMirrored) {
- drawUncompHzFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+ drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
} else {
- drawUncompNoFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+ drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
}
} else {
if (_drawMirrored) {
- drawHzFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+ drawUncompHzFlipNoMDNoSkip(target, targetRect, scaledPosition);
} else {
- drawNoFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+ drawUncompNoFlipNoMDNoSkip(target, targetRect, scaledPosition);
}
}
} else {
- if (_compressionType == kCelCompressionNone) {
- scaleDrawUncompNoMD(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+ if (_drawMirrored) {
+ drawHzFlipNoMD(target, targetRect, scaledPosition);
} else {
- scaleDrawNoMD(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+ drawNoFlipNoMD(target, targetRect, scaledPosition);
}
}
} else {
- if (scaleX.isOne() && scaleY.isOne()) {
- if (_compressionType == kCelCompressionNone) {
- if (_transparent) {
- if (_drawMirrored) {
- drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
- } else {
- drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
- }
- } else {
- if (_drawMirrored) {
- drawUncompHzFlipNoMDNoSkip(target, targetRect, scaledPosition);
- } else {
- drawUncompNoFlipNoMDNoSkip(target, targetRect, scaledPosition);
- }
- }
- } else {
- if (_drawMirrored) {
- drawHzFlipNoMD(target, targetRect, scaledPosition);
- } else {
- drawNoFlipNoMD(target, targetRect, scaledPosition);
- }
- }
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
} else {
- if (_compressionType == kCelCompressionNone) {
- scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
- } else {
- scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
- }
+ scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
}
}
}
+
+ _drawBlackLines = false;
}
void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) {
@@ -496,46 +569,42 @@ uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const {
void CelObj::submitPalette() const {
if (_hunkPaletteOffset) {
- Palette palette;
-
- byte *res = getResPointer();
- // NOTE: In SCI engine this uses HunkPalette::Init.
- // TODO: Use a better size value
- g_sci->_gfxPalette32->createFromData(res + _hunkPaletteOffset, 999999, &palette);
+ const HunkPalette palette(getResPointer() + _hunkPaletteOffset);
g_sci->_gfxPalette32->submit(palette);
}
}
#pragma mark -
#pragma mark CelObj - Caching
+
int CelObj::_nextCacheId = 1;
CelCache *CelObj::_cache = nullptr;
-int CelObj::searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const {
+int CelObj::searchCache(const CelInfo32 &celInfo, int *const nextInsertIndex) const {
+ *nextInsertIndex = -1;
int oldestId = _nextCacheId + 1;
- int oldestIndex = -1;
+ int oldestIndex = 0;
for (int i = 0, len = _cache->size(); i < len; ++i) {
CelCacheEntry &entry = (*_cache)[i];
- if (entry.celObj != nullptr) {
- if (entry.celObj->_info == celInfo) {
- entry.id = ++_nextCacheId;
- return i;
+ if (entry.celObj == nullptr) {
+ if (*nextInsertIndex == -1) {
+ *nextInsertIndex = i;
}
-
- if (oldestId > entry.id) {
- oldestId = entry.id;
- oldestIndex = i;
- }
- } else if (oldestIndex == -1) {
+ } else if (entry.celObj->_info == celInfo) {
+ entry.id = ++_nextCacheId;
+ return i;
+ } else if (oldestId > entry.id) {
+ oldestId = entry.id;
oldestIndex = i;
}
}
- // NOTE: Unlike the original SCI engine code, the out-param
- // here is only updated if there was not a cache hit.
- *nextInsertIndex = oldestIndex;
+ if (*nextInsertIndex == -1) {
+ *nextInsertIndex = oldestIndex;
+ }
+
return -1;
}
@@ -557,7 +626,7 @@ void CelObj::putCopyInCache(const int cacheIndex) const {
#pragma mark -
#pragma mark CelObj - Drawing
-template <typename MAPPER, typename SCALER>
+template<typename MAPPER, typename SCALER, bool DRAW_BLACK_LINES>
struct RENDERER {
MAPPER &_mapper;
SCALER &_scaler;
@@ -569,18 +638,21 @@ struct RENDERER {
_skipColor(skipColor) {}
inline void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- const int16 sourceX = targetRect.left - scaledPosition.x;
- const int16 sourceY = targetRect.top - scaledPosition.y;
-
byte *targetPixel = (byte *)target.getPixels() + target.screenWidth * targetRect.top + targetRect.left;
const int16 skipStride = target.screenWidth - targetRect.width();
const int16 targetWidth = targetRect.width();
const int16 targetHeight = targetRect.height();
- for (int y = 0; y < targetHeight; ++y) {
- _scaler.setSource(sourceX, sourceY + y);
+ for (int16 y = 0; y < targetHeight; ++y) {
+ if (DRAW_BLACK_LINES && (y % 2) == 0) {
+ memset(targetPixel, 0, targetWidth);
+ targetPixel += targetWidth + skipStride;
+ continue;
+ }
+
+ _scaler.setTarget(targetRect.left, targetRect.top + y);
- for (int x = 0; x < targetWidth; ++x) {
+ for (int16 x = 0; x < targetWidth; ++x) {
_mapper.draw(targetPixel++, _scaler.read(), _skipColor);
}
@@ -589,75 +661,91 @@ struct RENDERER {
}
};
-template <typename MAPPER, typename SCALER>
+template<typename MAPPER, typename SCALER>
void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
MAPPER mapper;
- SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width());
- RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor);
+ SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width(), scaledPosition);
+ RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _skipColor);
renderer.draw(target, targetRect, scaledPosition);
}
-template <typename MAPPER, typename SCALER>
+template<typename MAPPER, typename SCALER>
void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const {
MAPPER mapper;
- SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width(), scaleX, scaleY);
- RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor);
- renderer.draw(target, targetRect, scaledPosition);
-}
-
-void dummyFill(Buffer &target, const Common::Rect &targetRect) {
- target.fillRect(targetRect, 250);
+ SCALER scaler(*this, targetRect, scaledPosition, scaleX, scaleY);
+ if (_drawBlackLines) {
+ RENDERER<MAPPER, SCALER, true> renderer(mapper, scaler, _skipColor);
+ renderer.draw(target, targetRect, scaledPosition);
+ } else {
+ RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _skipColor);
+ renderer.draw(target, targetRect, scaledPosition);
+ }
}
void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawHzFlip");
- dummyFill(target, targetRect);
+ render<MAPPER_NoMap, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
}
+
void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawNoFlip");
- dummyFill(target, targetRect);
+ render<MAPPER_NoMap, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
}
+
void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawUncompNoFlip");
- dummyFill(target, targetRect);
+ render<MAPPER_NoMap, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
}
+
void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawUncompHzFlip");
- dummyFill(target, targetRect);
+ render<MAPPER_NoMap, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
}
+
void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("scaleDraw");
- dummyFill(target, targetRect);
+ if (_drawMirrored) {
+ render<MAPPER_NoMap, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ } else {
+ render<MAPPER_NoMap, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
}
+
void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("scaleDrawUncomp");
- dummyFill(target, targetRect);
+ if (_drawMirrored) {
+ render<MAPPER_NoMap, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ } else {
+ render<MAPPER_NoMap, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
}
+
void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawHzFlipMap");
- dummyFill(target, targetRect);
+ render<MAPPER_Map, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
}
+
void CelObj::drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawNoFlipMap");
- dummyFill(target, targetRect);
+ render<MAPPER_Map, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
}
+
void CelObj::drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawUncompNoFlipMap");
- dummyFill(target, targetRect);
+ render<MAPPER_Map, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
}
+
void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawUncompHzFlipMap");
- dummyFill(target, targetRect);
+ render<MAPPER_Map, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
}
+
void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("scaleDrawMap");
- dummyFill(target, targetRect);
+ if (_drawMirrored) {
+ render<MAPPER_Map, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ } else {
+ render<MAPPER_Map, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
}
+
void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("scaleDrawUncompMap");
- dummyFill(target, targetRect);
+ if (_drawMirrored) {
+ render<MAPPER_Map, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ } else {
+ render<MAPPER_Map, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
}
void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
@@ -675,19 +763,20 @@ void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect
void CelObj::drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
render<MAPPER_NoMDNoSkip, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
}
+
void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
render<MAPPER_NoMD, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
}
+
void CelObj::drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
render<MAPPER_NoMDNoSkip, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
}
void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- if (_drawMirrored) {
+ if (_drawMirrored)
render<MAPPER_NoMD, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
- } else {
+ else
render<MAPPER_NoMD, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
- }
}
void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
@@ -698,22 +787,66 @@ void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Rati
}
}
-// TODO: These functions may all be vestigial.
-void CelObj::drawHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-void CelObj::drawNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-void CelObj::drawUncompNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-void CelObj::drawUncompHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-void CelObj::scaleDrawMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-void CelObj::scaleDrawUncompMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-void CelObj::drawHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-void CelObj::drawNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-void CelObj::scaleDrawNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-void CelObj::scaleDrawUncompNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
-
#pragma mark -
#pragma mark CelObjView
+
+int16 CelObjView::getNumLoops(const GuiResourceId viewId) {
+ const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
+
+ if (!resource) {
+ return 0;
+ }
+
+ assert(resource->size >= 3);
+ return resource->data[2];
+}
+
+int16 CelObjView::getNumCels(const GuiResourceId viewId, int16 loopNo) {
+ const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
+
+ if (!resource) {
+ return 0;
+ }
+
+ const byte *const data = resource->data;
+
+ const uint16 loopCount = data[2];
+
+ // Every version of SCI32 has a logic error in this function that causes
+ // random memory to be read if a script requests the cel count for one
+ // past the maximum loop index. For example, GK1 room 808 does this, and
+ // gets stuck in an infinite loop because the game script expects this
+ // method to return a non-zero value.
+ // This bug is triggered in basically every SCI32 game and appears to be
+ // universally fixable simply by always using the next lowest loop instead.
+ if (loopNo == loopCount) {
+ const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+ debugC(kDebugLevelWorkarounds, "Workaround: kNumCels loop %d -> loop %d in view %u, %s", loopNo, loopNo - 1, viewId, origin.toString().c_str());
+ --loopNo;
+ }
+
+ if (loopNo > loopCount || loopNo < 0) {
+ return 0;
+ }
+
+ const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data);
+ const uint8 loopHeaderSize = data[12];
+ const uint8 viewHeaderFieldSize = 2;
+
+#ifndef NDEBUG
+ const byte *const dataMax = data + resource->size;
+#endif
+ const byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * loopNo);
+ assert(loopHeader + 3 <= dataMax);
+
+ if ((int8)loopHeader[0] != -1) {
+ loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * (int8)loopHeader[0]);
+ assert(loopHeader >= data && loopHeader + 3 <= dataMax);
+ }
+
+ return loopHeader[2];
+}
+
CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {
_info.type = kCelTypeView;
_info.resourceId = viewId;
@@ -724,10 +857,14 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
_transparent = true;
int cacheInsertIndex;
- int cacheIndex = searchCache(_info, &cacheInsertIndex);
+ const int cacheIndex = searchCache(_info, &cacheInsertIndex);
if (cacheIndex != -1) {
CelCacheEntry &entry = (*_cache)[cacheIndex];
- *this = *dynamic_cast<CelObjView *>(entry.celObj);
+ const CelObjView *const cachedCelObj = dynamic_cast<CelObjView *>(entry.celObj);
+ if (cachedCelObj == nullptr) {
+ error("Expected a CelObjView in cache slot %d", cacheIndex);
+ }
+ *this = *cachedCelObj;
entry.id = ++_nextCacheId;
return;
}
@@ -736,34 +873,33 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
// generates view resource metadata for both SCI16 and SCI32
// implementations
- Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
+ const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
// NOTE: SCI2.1/SQ6 just silently returns here.
if (!resource) {
- warning("View resource %d not loaded", viewId);
- return;
+ error("View resource %d not found", viewId);
}
- byte *data = resource->data;
+ const byte *const data = resource->data;
- _scaledWidth = READ_SCI11ENDIAN_UINT16(data + 14);
- _scaledHeight = READ_SCI11ENDIAN_UINT16(data + 16);
+ _xResolution = READ_SCI11ENDIAN_UINT16(data + 14);
+ _yResolution = READ_SCI11ENDIAN_UINT16(data + 16);
- if (_scaledWidth == 0 || _scaledHeight == 0) {
+ if (_xResolution == 0 && _yResolution == 0) {
byte sizeFlag = data[5];
if (sizeFlag == 0) {
- _scaledWidth = 320;
- _scaledHeight = 200;
+ _xResolution = kLowResX;
+ _yResolution = kLowResY;
} else if (sizeFlag == 1) {
- _scaledWidth = 640;
- _scaledHeight = 480;
+ _xResolution = 640;
+ _yResolution = 480;
} else if (sizeFlag == 2) {
- _scaledWidth = 640;
- _scaledHeight = 400;
+ _xResolution = 640;
+ _yResolution = 400;
}
}
- uint16 loopCount = data[2];
+ const uint16 loopCount = data[2];
if (_info.loopNo >= loopCount) {
_info.loopNo = loopCount - 1;
}
@@ -771,14 +907,14 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
// NOTE: This is the actual check, in the actual location,
// from SCI engine.
if (loopNo < 0) {
- error("Loop is less than 0!");
+ error("Loop is less than 0");
}
const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data);
const uint8 loopHeaderSize = data[12];
const uint8 viewHeaderFieldSize = 2;
- byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo);
+ const byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo);
if ((int8)loopHeader[0] != -1) {
if (loopHeader[1] == 1) {
@@ -793,16 +929,32 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
_info.celNo = celCount - 1;
}
+ // A celNo can be negative and still valid. At least PQ4CD uses this strange
+ // arrangement to load its high-resolution main menu resource. In PQ4CD, the
+ // low-resolution menu is at view 23, loop 9, cel 0, and the high-resolution
+ // menu is at view 2300, loop 0, cel 0. View 2300 is specially crafted to
+ // have 2 loops, with the second loop having 0 cels. When in high-resolution
+ // mode, the game scripts only change the view resource ID from 23 to 2300,
+ // leaving loop 9 and cel 0 the same. The code in CelObjView constructor
+ // auto-corrects loop 9 to loop 1, and then auto-corrects the cel number
+ // from 0 to -1, which effectively causes loop 0, cel 0 to be read.
+ if (_info.celNo < 0 && _info.loopNo == 0) {
+ error("Cel is less than 0 on loop 0");
+ }
+
_hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 8);
_celHeaderOffset = READ_SCI11ENDIAN_UINT32(loopHeader + 12) + (data[13] * _info.celNo);
- byte *celHeader = data + _celHeaderOffset;
+ const byte *const celHeader = data + _celHeaderOffset;
_width = READ_SCI11ENDIAN_UINT16(celHeader);
_height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
- _displace.x = _width / 2 - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
- _displace.y = _height - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6) - 1;
- _transparentColor = celHeader[8];
+ _origin.x = _width / 2 - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
+ if (g_sci->_features->usesAlternateSelectors() && _mirrorX) {
+ _origin.x = _width - _origin.x - 1;
+ }
+ _origin.y = _height - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6) - 1;
+ _skipColor = celHeader[8];
_compressionType = (CelCompressionType)celHeader[9];
if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) {
@@ -825,10 +977,14 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
}
bool CelObjView::analyzeUncompressedForRemap() const {
- byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+ const byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
for (int i = 0; i < _width * _height; ++i) {
- uint8 pixel = pixels[i];
- if (/* TODO: pixel >= Remap::minRemapColor && pixel <= Remap::maxRemapColor */ false && pixel != _transparentColor) {
+ const byte pixel = pixels[i];
+ if (
+ pixel >= g_sci->_gfxRemap32->getStartColor() &&
+ pixel <= g_sci->_gfxRemap32->getEndColor() &&
+ pixel != _skipColor
+ ) {
return true;
}
}
@@ -836,7 +992,20 @@ bool CelObjView::analyzeUncompressedForRemap() const {
}
bool CelObjView::analyzeForRemap() const {
- // TODO: Implement decompression and analysis
+ READER_Compressed reader(*this, _width);
+ for (int y = 0; y < _height; y++) {
+ const byte *const curRow = reader.getRow(y);
+ for (int x = 0; x < _width; x++) {
+ const byte pixel = curRow[x];
+ if (
+ pixel >= g_sci->_gfxRemap32->getStartColor() &&
+ pixel <= g_sci->_gfxRemap32->getEndColor() &&
+ pixel != _skipColor
+ ) {
+ return true;
+ }
+ }
+ }
return false;
}
@@ -850,11 +1019,16 @@ CelObjView *CelObjView::duplicate() const {
}
byte *CelObjView::getResPointer() const {
- return g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false)->data;
+ Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false);
+ if (resource == nullptr) {
+ error("Failed to load view %d from resource manager", _info.resourceId);
+ }
+ return resource->data;
}
#pragma mark -
#pragma mark CelObjPic
+
CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
_info.type = kCelTypePic;
_info.resourceId = picId;
@@ -866,23 +1040,26 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
_remap = false;
int cacheInsertIndex;
- int cacheIndex = searchCache(_info, &cacheInsertIndex);
+ const int cacheIndex = searchCache(_info, &cacheInsertIndex);
if (cacheIndex != -1) {
CelCacheEntry &entry = (*_cache)[cacheIndex];
- *this = *dynamic_cast<CelObjPic *>(entry.celObj);
+ const CelObjPic *const cachedCelObj = dynamic_cast<CelObjPic *>(entry.celObj);
+ if (cachedCelObj == nullptr) {
+ error("Expected a CelObjPic in cache slot %d", cacheIndex);
+ }
+ *this = *cachedCelObj;
entry.id = ++_nextCacheId;
return;
}
- Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false);
+ const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false);
// NOTE: SCI2.1/SQ6 just silently returns here.
if (!resource) {
- warning("Pic resource %d not loaded", picId);
- return;
+ error("Pic resource %d not found", picId);
}
- byte *data = resource->data;
+ const byte *const data = resource->data;
_celCount = data[2];
@@ -893,39 +1070,39 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
_celHeaderOffset = READ_SCI11ENDIAN_UINT16(data) + (READ_SCI11ENDIAN_UINT16(data + 4) * _info.celNo);
_hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 6);
- byte *celHeader = data + _celHeaderOffset;
+ const byte *const celHeader = data + _celHeaderOffset;
_width = READ_SCI11ENDIAN_UINT16(celHeader);
_height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
- _displace.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
- _displace.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6);
- _transparentColor = celHeader[8];
+ _origin.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
+ _origin.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6);
+ _skipColor = celHeader[8];
_compressionType = (CelCompressionType)celHeader[9];
_priority = READ_SCI11ENDIAN_UINT16(celHeader + 36);
_relativePosition.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 38);
_relativePosition.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 40);
- uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10);
- uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12);
+ const uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10);
+ const uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12);
if (sizeFlag2) {
- _scaledWidth = sizeFlag1;
- _scaledHeight = sizeFlag2;
+ _xResolution = sizeFlag1;
+ _yResolution = sizeFlag2;
} else if (sizeFlag1 == 0) {
- _scaledWidth = 320;
- _scaledHeight = 200;
+ _xResolution = kLowResX;
+ _yResolution = kLowResY;
} else if (sizeFlag1 == 1) {
- _scaledWidth = 640;
- _scaledHeight = 480;
+ _xResolution = 640;
+ _yResolution = 480;
} else if (sizeFlag1 == 2) {
- _scaledWidth = 640;
- _scaledHeight = 400;
+ _xResolution = 640;
+ _yResolution = 400;
}
if (celHeader[10] & 128) {
// NOTE: This is correct according to SCI2.1/SQ6/DOS;
// the engine re-reads the byte value as a word value
- uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
+ const uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
_transparent = flags & 1 ? true : false;
_remap = flags & 2 ? true : false;
} else {
@@ -940,11 +1117,11 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
}
bool CelObjPic::analyzeUncompressedForSkip() const {
- byte *resource = getResPointer();
- byte *pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24);
+ const byte *const resource = getResPointer();
+ const byte *const pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24);
for (int i = 0; i < _width * _height; ++i) {
uint8 pixel = pixels[i];
- if (pixel == _transparentColor) {
+ if (pixel == _skipColor) {
return true;
}
}
@@ -953,7 +1130,7 @@ bool CelObjPic::analyzeUncompressedForSkip() const {
}
void CelObjPic::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
- Ratio square;
+ const Ratio square;
_drawMirrored = mirrorX;
drawTo(target, targetRect, scaledPosition, square, square);
}
@@ -963,33 +1140,39 @@ CelObjPic *CelObjPic::duplicate() const {
}
byte *CelObjPic::getResPointer() const {
- return g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false)->data;
+ const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false);
+ if (resource == nullptr) {
+ error("Failed to load pic %d from resource manager", _info.resourceId);
+ }
+ return resource->data;
}
#pragma mark -
#pragma mark CelObjMem
-CelObjMem::CelObjMem(const reg_t bitmap) {
+
+CelObjMem::CelObjMem(const reg_t bitmapObject) {
_info.type = kCelTypeMem;
- _info.bitmap = bitmap;
+ _info.bitmap = bitmapObject;
_mirrorX = false;
_compressionType = kCelCompressionNone;
_celHeaderOffset = 0;
_transparent = true;
- byte *bitmapData = g_sci->getEngineState()->_segMan->getHunkPointer(bitmap);
- if (bitmapData == nullptr || READ_SCI11ENDIAN_UINT32(bitmapData + 28) != 46) {
- error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap));
+ SciBitmap *bitmap = g_sci->getEngineState()->_segMan->lookupBitmap(bitmapObject);
+
+ // NOTE: SSCI did no error checking here at all.
+ if (!bitmap) {
+ error("Bitmap %04x:%04x not found", PRINT_REG(bitmapObject));
}
- _width = READ_SCI11ENDIAN_UINT16(bitmapData);
- _height = READ_SCI11ENDIAN_UINT16(bitmapData + 2);
- _displace.x = READ_SCI11ENDIAN_UINT16(bitmapData + 4);
- _displace.y = READ_SCI11ENDIAN_UINT16(bitmapData + 6);
- _transparentColor = bitmapData[8];
- _scaledWidth = READ_SCI11ENDIAN_UINT16(bitmapData + 36);
- _scaledHeight = READ_SCI11ENDIAN_UINT16(bitmapData + 38);
- _hunkPaletteOffset = READ_SCI11ENDIAN_UINT16(bitmapData + 20);
- _remap = (READ_SCI11ENDIAN_UINT16(bitmapData + 10) & 2) ? true : false;
+ _width = bitmap->getWidth();
+ _height = bitmap->getHeight();
+ _origin = bitmap->getOrigin();
+ _skipColor = bitmap->getSkipColor();
+ _xResolution = bitmap->getXResolution();
+ _yResolution = bitmap->getYResolution();
+ _hunkPaletteOffset = bitmap->getHunkPaletteOffset();
+ _remap = bitmap->getRemap();
}
CelObjMem *CelObjMem::duplicate() const {
@@ -997,18 +1180,19 @@ CelObjMem *CelObjMem::duplicate() const {
}
byte *CelObjMem::getResPointer() const {
- return g_sci->getEngineState()->_segMan->getHunkPointer(_info.bitmap);
+ return g_sci->getEngineState()->_segMan->lookupBitmap(_info.bitmap)->getRawData();
}
#pragma mark -
#pragma mark CelObjColor
+
CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) {
_info.type = kCelTypeColor;
_info.color = color;
- _displace.x = 0;
- _displace.y = 0;
- _scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
- _scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ _origin.x = 0;
+ _origin.y = 0;
+ _xResolution = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ _yResolution = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
_hunkPaletteOffset = 0;
_mirrorX = false;
_remap = false;
@@ -1036,4 +1220,4 @@ CelObjColor *CelObjColor::duplicate() const {
byte *CelObjColor::getResPointer() const {
error("Unsupported method");
}
-}
+} // End of namespace Sci
diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h
index 1422b76a57..70cbd09e7d 100644
--- a/engines/sci/graphics/celobj32.h
+++ b/engines/sci/graphics/celobj32.h
@@ -31,6 +31,20 @@
namespace Sci {
typedef Common::Rational Ratio;
+// SCI32 has four different coordinate systems:
+// 1. low resolution, 2. game/script resolution,
+// 3. text/bitmap resolution, 4. screen resolution
+//
+// In CelObj, these values are used when there is
+// no baked in resolution of cels.
+//
+// In ScreenItem, it is used when deciding which
+// path to take to calculate dimensions.
+enum {
+ kLowResX = 320,
+ kLowResY = 200
+};
+
enum CelType {
kCelTypeView = 0,
kCelTypePic = 1,
@@ -104,6 +118,22 @@ struct CelInfo32 {
bitmap == other.bitmap
);
}
+
+ inline bool operator!=(const CelInfo32 &other) {
+ return !(*this == other);
+ }
+
+ inline Common::String toString() const {
+ if (type == kCelTypeView) {
+ return Common::String::format("view %u, loop %d, cel %d", resourceId, loopNo, celNo);
+ } else if (type == kCelTypePic) {
+ return Common::String::format("pic %u", resourceId);
+ } else if (kCelTypeColor) {
+ return Common::String::format("color %d", color);
+ } else if (type == kCelTypeMem) {
+ return Common::String::format("mem %04x:%04x", PRINT_REG(bitmap));
+ }
+ }
};
class CelObj;
@@ -123,13 +153,20 @@ typedef Common::Array<CelCacheEntry> CelCache;
#pragma mark -
#pragma mark CelScaler
+enum {
+ /**
+ * The maximum size of a row/column of scaled pixel data.
+ */
+ kCelScalerTableSize = 4096
+};
+
struct CelScalerTable {
/**
* A lookup table of indexes that should be used to find
* the correct column to read from the source bitmap
* when drawing a scaled version of the source bitmap.
*/
- int valuesX[1024];
+ int valuesX[kCelScalerTableSize];
/**
* The ratio used to generate the x-values.
@@ -141,7 +178,7 @@ struct CelScalerTable {
* the correct row to read from a source bitmap when
* drawing a scaled version of the source bitmap.
*/
- int valuesY[1024];
+ int valuesY[kCelScalerTableSize];
/**
* The ratio used to generate the y-values.
@@ -180,13 +217,16 @@ public:
CelScaler() :
_scaleTables(),
_activeIndex(0) {
- CelScalerTable &table = _scaleTables[_activeIndex];
+ CelScalerTable &table = _scaleTables[0];
table.scaleX = Ratio();
table.scaleY = Ratio();
for (int i = 0; i < ARRAYSIZE(table.valuesX); ++i) {
table.valuesX[i] = i;
table.valuesY[i] = i;
}
+ for (int i = 1; i < ARRAYSIZE(_scaleTables); ++i) {
+ _scaleTables[i] = _scaleTables[0];
+ }
}
/**
@@ -207,6 +247,18 @@ class ScreenItem;
class CelObj {
protected:
/**
+ * When true, every second line of the cel will be
+ * rendered as a black line.
+ *
+ * @see ScreenItem::_drawBlackLines
+ * @note Using a static member because otherwise this
+ * would otherwise need to be copied down through
+ * several calls. (SSCI did similar, using a global
+ * variable.)
+ */
+ static bool _drawBlackLines;
+
+ /**
* When true, this cel will be horizontally mirrored
* when it is drawn. This is an internal flag that is
* set by draw methods based on the combination of the
@@ -246,7 +298,7 @@ public:
/**
* TODO: Documentation
*/
- Common::Point _displace;
+ Common::Point _origin;
/**
* The dimensions of the original coordinate system for
@@ -261,21 +313,21 @@ public:
* scriptWidth/Height but seems to typically be changed
* to more closely match the native screen resolution.
*/
- uint16 _scaledWidth, _scaledHeight;
+ uint16 _xResolution, _yResolution;
/**
* The skip (transparent) color for the cel. When
* compositing, any pixels matching this color will not
* be copied to the buffer.
*/
- uint8 _transparentColor;
+ uint8 _skipColor;
/**
* Whether or not this cel has any transparent regions.
* This is used for optimised drawing of non-transparent
* cels.
*/
- bool _transparent; // TODO: probably "skip"?
+ bool _transparent;
/**
* The compression type for the pixel data for this cel.
@@ -367,7 +419,7 @@ public:
* Reads the pixel at the given coordinates. This method
* is valid only for CelObjView and CelObjPic.
*/
- virtual uint8 readPixel(uint16 x, uint16 y, bool mirrorX) const;
+ virtual uint8 readPixel(const uint16 x, const uint16 y, const bool mirrorX) const;
/**
* Submits the palette from this cel to the palette
@@ -379,10 +431,10 @@ public:
#pragma mark -
#pragma mark CelObj - Drawing
private:
- template <typename MAPPER, typename SCALER>
+ template<typename MAPPER, typename SCALER>
void render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
- template <typename MAPPER, typename SCALER>
+ template<typename MAPPER, typename SCALER>
void render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const;
void drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
@@ -391,18 +443,15 @@ private:
void drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
void scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
void scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+
void drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
void drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
void drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
void drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
void scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
void scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
- void drawHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
- void drawNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
- void drawUncompNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
- void drawUncompHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
- void scaleDrawMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
- void scaleDrawUncompMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ // NOTE: The original includes versions of the above functions with priority parameters, which were not actually used in SCI32
+
void drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
void drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
void drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
@@ -411,12 +460,7 @@ private:
void drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
void scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
void scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
- void drawHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
- void drawNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
- void drawUncompNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
- void drawUncompHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
- void scaleDrawNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
- void scaleDrawUncompNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ // NOTE: The original includes versions of the above functions with priority parameters, which were not actually used in SCI32
#pragma mark -
#pragma mark CelObj - Caching
@@ -475,11 +519,14 @@ private:
bool analyzeForRemap() const;
public:
- CelObjView(GuiResourceId viewId, int16 loopNo, int16 celNo);
+ CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo);
virtual ~CelObjView() override {};
using CelObj::draw;
+ static int16 getNumLoops(const GuiResourceId viewId);
+ static int16 getNumCels(const GuiResourceId viewId, const int16 loopNo);
+
/**
* Draws the cel to the target buffer using the
* positioning, mirroring, and scaling information from
@@ -526,7 +573,7 @@ public:
*/
int16 _priority;
- CelObjPic(GuiResourceId pictureId, int16 celNo);
+ CelObjPic(const GuiResourceId pictureId, const int16 celNo);
virtual ~CelObjPic() override {};
using CelObj::draw;
@@ -547,7 +594,7 @@ public:
*/
class CelObjMem : public CelObj {
public:
- CelObjMem(reg_t bitmap);
+ CelObjMem(const reg_t bitmap);
virtual ~CelObjMem() override {};
virtual CelObjMem *duplicate() const override;
@@ -563,7 +610,7 @@ public:
*/
class CelObjColor : public CelObj {
public:
- CelObjColor(uint8 color, int16 width, int16 height);
+ CelObjColor(const uint8 color, const int16 width, const int16 height);
virtual ~CelObjColor() override {};
using CelObj::draw;
@@ -577,6 +624,6 @@ public:
virtual CelObjColor *duplicate() const override;
virtual byte *getResPointer() const override;
};
-}
+} // End of namespace Sci
#endif
diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp
index 8333459b64..36026a8134 100644
--- a/engines/sci/graphics/compare.cpp
+++ b/engines/sci/graphics/compare.cpp
@@ -37,7 +37,7 @@
namespace Sci {
-GfxCompare::GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster *coordAdjuster)
+GfxCompare::GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster16 *coordAdjuster)
: _segMan(segMan), _cache(cache), _screen(screen), _coordAdjuster(coordAdjuster) {
}
@@ -67,7 +67,7 @@ uint16 GfxCompare::isOnControl(uint16 screenMask, const Common::Rect &rect) {
return result;
}
-reg_t GfxCompare::canBeHereCheckRectList(reg_t checkObject, const Common::Rect &checkRect, List *list) {
+reg_t GfxCompare::canBeHereCheckRectList(const reg_t checkObject, const Common::Rect &checkRect, const List *list, const uint16 signalFlags) const {
reg_t curAddress = list->first;
Node *curNode = _segMan->lookupNode(curAddress);
reg_t curObject;
@@ -78,7 +78,7 @@ reg_t GfxCompare::canBeHereCheckRectList(reg_t checkObject, const Common::Rect &
curObject = curNode->value;
if (curObject != checkObject) {
signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
- if (!(signal & (kSignalIgnoreActor | kSignalRemoveView | kSignalNoUpdate))) {
+ if (!(signal & signalFlags)) {
curRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft));
curRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop));
curRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight));
@@ -111,9 +111,6 @@ uint16 GfxCompare::kernelOnControl(byte screenMask, const Common::Rect &rect) {
void GfxCompare::kernelSetNowSeen(reg_t objectReference) {
GfxView *view = NULL;
Common::Rect celRect(0, 0);
- // TODO/FIXME: Torin's menu code tries to draw special views with an ID of 0xFFFF, which
- // are not currently handled properly and cause a crash. These might be text views that
- // are not properly implemented.
GuiResourceId viewId = (GuiResourceId)readSelectorValue(_segMan, objectReference, SELECTOR(view));
int16 loopNo = readSelectorValue(_segMan, objectReference, SELECTOR(loop));
int16 celNo = readSelectorValue(_segMan, objectReference, SELECTOR(cel));
@@ -124,26 +121,8 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) {
z = (int16)readSelectorValue(_segMan, objectReference, SELECTOR(z));
view = _cache->getView(viewId);
-
-#ifdef ENABLE_SCI32
- if (view->isSci2Hires())
- view->adjustToUpscaledCoordinates(y, x);
- else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE))
- _coordAdjuster->fromScriptToDisplay(y, x);
-#endif
-
view->getCelRect(loopNo, celNo, x, y, z, celRect);
-#ifdef ENABLE_SCI32
- if (view->isSci2Hires()) {
- view->adjustBackUpscaledCoordinates(celRect.top, celRect.left);
- view->adjustBackUpscaledCoordinates(celRect.bottom, celRect.right);
- } else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) {
- _coordAdjuster->fromDisplayToScript(celRect.top, celRect.left);
- _coordAdjuster->fromDisplayToScript(celRect.bottom, celRect.right);
- }
-#endif
-
if (lookupSelector(_segMan, objectReference, SELECTOR(nsTop), NULL, NULL) == kSelectorVariable) {
setNSRect(objectReference, celRect);
}
@@ -151,32 +130,65 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) {
reg_t GfxCompare::kernelCanBeHere(reg_t curObject, reg_t listReference) {
Common::Rect checkRect;
- Common::Rect adjustedRect;
- uint16 signal, controlMask;
uint16 result;
checkRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft));
checkRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop));
checkRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight));
checkRect.bottom = readSelectorValue(_segMan, curObject, SELECTOR(brBottom));
+ uint16 signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
if (!checkRect.isValidRect()) { // can occur in Iceman and Mother Goose - HACK? TODO: is this really occuring in sierra sci? check this
warning("kCan(t)BeHere - invalid rect %d, %d -> %d, %d", checkRect.left, checkRect.top, checkRect.right, checkRect.bottom);
return NULL_REG; // this means "can be here"
}
- adjustedRect = _coordAdjuster->onControl(checkRect);
-
- signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
- controlMask = readSelectorValue(_segMan, curObject, SELECTOR(illegalBits));
+ Common::Rect adjustedRect = _coordAdjuster->onControl(checkRect);
+ uint16 controlMask = readSelectorValue(_segMan, curObject, SELECTOR(illegalBits));
result = isOnControl(GFX_SCREEN_MASK_CONTROL, adjustedRect) & controlMask;
if ((!result) && (signal & (kSignalIgnoreActor | kSignalRemoveView)) == 0) {
List *list = _segMan->lookupList(listReference);
if (!list)
error("kCanBeHere called with non-list as parameter");
- return canBeHereCheckRectList(curObject, checkRect, list);
+ return canBeHereCheckRectList(curObject, checkRect, list, kSignalIgnoreActor | kSignalRemoveView | kSignalNoUpdate);
}
+
+ return make_reg(0, result);
+}
+
+reg_t GfxCompare::kernelCantBeHere32(const reg_t curObject, const reg_t listReference) const {
+ // Most of SCI32 graphics code converts rects from the VM to exclusive
+ // rects before operating on them, but this call leverages SCI16 engine
+ // code that operates on inclusive rects, so the rect's bottom-right
+ // point is not modified like in other SCI32 kernel calls
+ Common::Rect checkRect;
+
+ // At least LSL6 hires passes invalid rectangles which trigger the
+ // isValidRect assertion in the Rect constructor; this is avoided by
+ // assigning the properties after construction and then testing the
+ // rect for validity ourselves here. SSCI does not care about whether
+ // or not the rects are valid
+ checkRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft));
+ checkRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop));
+ checkRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight));
+ checkRect.bottom = readSelectorValue(_segMan, curObject, SELECTOR(brBottom));
+ if (!checkRect.isValidRect()) {
+ return make_reg(0, 0);
+ }
+
+ uint16 result = 0;
+ uint16 signal = readSelectorValue(_segMan, curObject, SELECTOR(signal));
+ const uint16 signalFlags = kSignalIgnoreActor | kSignalHidden;
+
+ if ((signal & signalFlags) == 0) {
+ List *list = _segMan->lookupList(listReference);
+ if (!list) {
+ error("kCantBeHere called with non-list as parameter");
+ }
+ result = !canBeHereCheckRectList(curObject, checkRect, list, signalFlags).isNull();
+ }
+
return make_reg(0, result);
}
diff --git a/engines/sci/graphics/compare.h b/engines/sci/graphics/compare.h
index 88b44aeeb1..dd65b90bea 100644
--- a/engines/sci/graphics/compare.h
+++ b/engines/sci/graphics/compare.h
@@ -34,12 +34,13 @@ class Screen;
*/
class GfxCompare {
public:
- GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster *coordAdjuster);
+ GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster16 *coordAdjuster);
~GfxCompare();
uint16 kernelOnControl(byte screenMask, const Common::Rect &rect);
void kernelSetNowSeen(reg_t objectReference);
reg_t kernelCanBeHere(reg_t curObject, reg_t listReference);
+ reg_t kernelCantBeHere32(const reg_t curObject, const reg_t listReference) const;
bool kernelIsItSkip(GuiResourceId viewId, int16 loopNo, int16 celNo, Common::Point position);
void kernelBaseSetter(reg_t object);
Common::Rect getNSRect(reg_t object);
@@ -49,7 +50,7 @@ private:
SegManager *_segMan;
GfxCache *_cache;
GfxScreen *_screen;
- GfxCoordAdjuster *_coordAdjuster;
+ GfxCoordAdjuster16 *_coordAdjuster;
uint16 isOnControl(uint16 screenMask, const Common::Rect &rect);
@@ -58,7 +59,7 @@ private:
* *different* from checkObject, has a brRect which is contained inside
* checkRect.
*/
- reg_t canBeHereCheckRectList(reg_t checkObject, const Common::Rect &checkRect, List *list);
+ reg_t canBeHereCheckRectList(const reg_t checkObject, const Common::Rect &checkRect, const List *list, const uint16 signalFlags) const;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp
index 04a70d35f3..0cd924b38d 100644
--- a/engines/sci/graphics/controls32.cpp
+++ b/engines/sci/graphics/controls32.cpp
@@ -21,11 +21,14 @@
*/
#include "common/system.h"
-
+#include "common/translation.h"
+#include "gui/message.h"
#include "sci/sci.h"
+#include "sci/console.h"
#include "sci/event.h"
#include "sci/engine/kernel.h"
#include "sci/engine/seg_manager.h"
+#include "sci/engine/state.h"
#include "sci/graphics/cache.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/controls32.h"
@@ -34,171 +37,804 @@
#include "sci/graphics/text32.h"
namespace Sci {
-
-GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text)
- : _segMan(segMan), _cache(cache), _text(text) {
-}
+GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text) :
+ _segMan(segMan),
+ _gfxCache(cache),
+ _gfxText32(text),
+ _overwriteMode(false),
+ _nextCursorFlashTick(0),
+ // SSCI used a memory handle for a ScrollWindow object
+ // as ID. We use a simple numeric handle instead.
+ _nextScrollWindowId(10000) {}
GfxControls32::~GfxControls32() {
+ ScrollWindowMap::iterator it;
+ for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it)
+ delete it->_value;
}
-void GfxControls32::kernelTexteditChange(reg_t controlObject) {
- SciEvent curEvent;
- uint16 maxChars = 40; //readSelectorValue(_segMan, controlObject, SELECTOR(max)); // TODO
- reg_t textReference = readSelector(_segMan, controlObject, SELECTOR(text));
- GfxFont *font = _cache->getFont(readSelectorValue(_segMan, controlObject, SELECTOR(font)));
- Common::String text;
- uint16 textSize;
- bool textChanged = false;
- bool textAddChar = false;
- Common::Rect rect;
+#pragma mark -
+#pragma mark Text input control
- if (textReference.isNull())
- error("kEditControl called on object that doesn't have a text reference");
- text = _segMan->getString(textReference);
+reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
+ SegManager *segMan = _segMan;
- // TODO: Finish this
- warning("kEditText ('%s')", text.c_str());
- return;
+ TextEditor editor;
+ reg_t textObject = readSelector(_segMan, controlObject, SELECTOR(text));
+ editor.text = _segMan->getString(textObject);
+ editor.foreColor = readSelectorValue(_segMan, controlObject, SELECTOR(fore));
+ editor.backColor = readSelectorValue(_segMan, controlObject, SELECTOR(back));
+ editor.skipColor = readSelectorValue(_segMan, controlObject, SELECTOR(skip));
+ editor.fontId = readSelectorValue(_segMan, controlObject, SELECTOR(font));
+ editor.maxLength = readSelectorValue(_segMan, controlObject, SELECTOR(width));
+ editor.bitmap = readSelector(_segMan, controlObject, SELECTOR(bitmap));
+ editor.cursorCharPosition = 0;
+ editor.cursorIsDrawn = false;
+ editor.borderColor = readSelectorValue(_segMan, controlObject, SELECTOR(borderColor));
+
+ reg_t titleObject = readSelector(_segMan, controlObject, SELECTOR(title));
+
+ int16 titleHeight = 0;
+ GuiResourceId titleFontId = readSelectorValue(_segMan, controlObject, SELECTOR(titleFont));
+ if (!titleObject.isNull()) {
+ GfxFont *titleFont = _gfxCache->getFont(titleFontId);
+ titleHeight += _gfxText32->scaleUpHeight(titleFont->getHeight()) + 1;
+ if (editor.borderColor != -1) {
+ titleHeight += 2;
+ }
+ }
- uint16 cursorPos = 0;
- //uint16 oldCursorPos = cursorPos;
- bool captureEvents = true;
- EventManager* eventMan = g_sci->getEventManager();
+ int16 width = 0;
+ int16 height = titleHeight;
- while (captureEvents) {
- curEvent = g_sci->getEventManager()->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK);
+ GfxFont *editorFont = _gfxCache->getFont(editor.fontId);
+ height += _gfxText32->scaleUpHeight(editorFont->getHeight()) + 1;
+ _gfxText32->setFont(editor.fontId);
+ int16 emSize = _gfxText32->getCharWidth('M', true);
+ width += editor.maxLength * emSize + 1;
+ if (editor.borderColor != -1) {
+ width += 4;
+ height += 2;
+ }
- if (curEvent.type == SCI_EVENT_NONE) {
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
+ Common::Rect editorPlaneRect(width, height);
+ editorPlaneRect.translate(readSelectorValue(_segMan, controlObject, SELECTOR(x)), readSelectorValue(_segMan, controlObject, SELECTOR(y)));
+
+ reg_t planeObj = readSelector(_segMan, controlObject, SELECTOR(plane));
+ Plane *sourcePlane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
+ if (sourcePlane == nullptr) {
+ sourcePlane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
+ if (sourcePlane == nullptr) {
+ error("Could not find plane %04x:%04x", PRINT_REG(planeObj));
+ }
+ }
+ editorPlaneRect.translate(sourcePlane->_gameRect.left, sourcePlane->_gameRect.top);
+
+ editor.textRect = Common::Rect(2, titleHeight + 2, width - 1, height - 1);
+ editor.width = width;
+
+ if (editor.bitmap.isNull()) {
+ TextAlign alignment = (TextAlign)readSelectorValue(_segMan, controlObject, SELECTOR(mode));
+
+ if (titleObject.isNull()) {
+ bool dimmed = readSelectorValue(_segMan, controlObject, SELECTOR(dimmed));
+ editor.bitmap = _gfxText32->createFontBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, dimmed, true, false);
} else {
- textSize = text.size();
+ error("Titled bitmaps are not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+ }
+ }
- switch (curEvent.type) {
- case SCI_EVENT_MOUSE_PRESS:
- // TODO: Implement mouse support for cursor change
+ drawCursor(editor);
+
+ Plane *plane = new Plane(editorPlaneRect, kPlanePicTransparent);
+ plane->changePic();
+ g_sci->_gfxFrameout->addPlane(*plane);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = editor.bitmap;
+
+ ScreenItem *screenItem = new ScreenItem(plane->_object, celInfo, Common::Point(), ScaleInfo());
+ plane->_screenItemList.add(screenItem);
+
+ // frameOut must be called after the screen item is
+ // created, and before it is updated at the end of the
+ // event loop, otherwise it has both created and updated
+ // flags set which crashes the engine (it runs updates
+ // before creations)
+ g_sci->_gfxFrameout->frameOut(true);
+
+ EventManager *eventManager = g_sci->getEventManager();
+ bool clearTextOnInput = true;
+ bool textChanged = false;
+ for (;;) {
+ // We peek here because the last event needs to be allowed to
+ // dispatch a second time to the normal event handling system.
+ // In the actual engine, the event is always consumed and then
+ // the last event just gets posted back to the event manager for
+ // reprocessing, but instead, we only remove the event from the
+ // queue *after* we have determined it is not a defocusing event
+ const SciEvent event = eventManager->getSciEvent(SCI_EVENT_ANY | SCI_EVENT_PEEK);
+
+ bool focused = true;
+ // Original engine did not have a QUIT event but we have to handle it
+ if (event.type == SCI_EVENT_QUIT) {
+ focused = false;
+ } else if (event.type == SCI_EVENT_MOUSE_PRESS && !editorPlaneRect.contains(event.mousePosSci)) {
+ focused = false;
+ } else if (event.type == SCI_EVENT_KEYBOARD) {
+ switch (event.character) {
+ case SCI_KEY_ESC:
+ case SCI_KEY_UP:
+ case SCI_KEY_DOWN:
+ case SCI_KEY_TAB:
+ case SCI_KEY_SHIFT_TAB:
+ case SCI_KEY_ENTER:
+ focused = false;
break;
- case SCI_EVENT_KEYBOARD:
- switch (curEvent.character) {
- case SCI_KEY_BACKSPACE:
- if (cursorPos > 0) {
- cursorPos--; text.deleteChar(cursorPos);
- textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_DELETE:
- if (cursorPos < textSize) {
- text.deleteChar(cursorPos);
- textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_HOME: // HOME
- cursorPos = 0; textChanged = true;
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_END: // END
- cursorPos = textSize; textChanged = true;
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_LEFT: // LEFT
- if (cursorPos > 0) {
- cursorPos--; textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_RIGHT: // RIGHT
- if (cursorPos + 1 <= textSize) {
- cursorPos++; textChanged = true;
- }
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case 3: // returned in SCI1 late and newer when Control - C is pressed
- if (curEvent.modifiers & SCI_KEYMOD_CTRL) {
- // Control-C erases the whole line
- cursorPos = 0; text.clear();
- textChanged = true;
+ }
+ }
+
+ if (!focused) {
+ break;
+ }
+
+ // Consume the event now that we know it is not one of the
+ // defocusing events above
+ if (event.type != SCI_EVENT_NONE)
+ eventManager->getSciEvent(SCI_EVENT_ANY);
+
+ // NOTE: In the original engine, the font and bitmap were
+ // reset here on each iteration through the loop, but it
+ // doesn't seem like this should be necessary since
+ // control is not yielded back to the VM until input is
+ // received, which means there is nothing that could modify
+ // the GfxText32's state with a different font in the
+ // meantime
+
+ bool shouldDeleteChar = false;
+ bool shouldRedrawText = false;
+ uint16 lastCursorPosition = editor.cursorCharPosition;
+ if (event.type == SCI_EVENT_KEYBOARD) {
+ switch (event.character) {
+ case SCI_KEY_LEFT:
+ clearTextOnInput = false;
+ if (editor.cursorCharPosition > 0) {
+ --editor.cursorCharPosition;
+ }
+ break;
+
+ case SCI_KEY_RIGHT:
+ clearTextOnInput = false;
+ if (editor.cursorCharPosition < editor.text.size()) {
+ ++editor.cursorCharPosition;
+ }
+ break;
+
+ case SCI_KEY_HOME:
+ clearTextOnInput = false;
+ editor.cursorCharPosition = 0;
+ break;
+
+ case SCI_KEY_END:
+ clearTextOnInput = false;
+ editor.cursorCharPosition = editor.text.size();
+ break;
+
+ case SCI_KEY_INSERT:
+ clearTextOnInput = false;
+ // Redrawing also changes the cursor rect to
+ // reflect the new insertion mode
+ shouldRedrawText = true;
+ _overwriteMode = !_overwriteMode;
+ break;
+
+ case SCI_KEY_DELETE:
+ clearTextOnInput = false;
+ if (editor.cursorCharPosition < editor.text.size()) {
+ shouldDeleteChar = true;
+ }
+ break;
+
+ case SCI_KEY_BACKSPACE:
+ clearTextOnInput = false;
+ shouldDeleteChar = true;
+ if (editor.cursorCharPosition > 0) {
+ --editor.cursorCharPosition;
+ }
+ break;
+
+ case SCI_KEY_ETX:
+ editor.text.clear();
+ editor.cursorCharPosition = 0;
+ shouldRedrawText = true;
+ break;
+
+ default: {
+ if (event.character >= 20 && event.character < 257) {
+ if (clearTextOnInput) {
+ clearTextOnInput = false;
+ editor.text.clear();
}
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
- case SCI_KEY_UP:
- case SCI_KEY_DOWN:
- case SCI_KEY_ENTER:
- case SCI_KEY_ESC:
- case SCI_KEY_TAB:
- case SCI_KEY_SHIFT_TAB:
- captureEvents = false;
- break;
- default:
- if ((curEvent.modifiers & SCI_KEYMOD_CTRL) && curEvent.character == 'c') {
- // Control-C in earlier SCI games (SCI0 - SCI1 middle)
- // Control-C erases the whole line
- cursorPos = 0; text.clear();
- textChanged = true;
- } else if (curEvent.character > 31 && curEvent.character < 256 && textSize < maxChars) {
- // insert pressed character
- textAddChar = true;
- textChanged = true;
+
+ if (
+ (_overwriteMode && editor.cursorCharPosition < editor.maxLength) ||
+ (editor.text.size() < editor.maxLength && _gfxText32->getCharWidth(event.character, true) + _gfxText32->getStringWidth(editor.text) < editor.textRect.width())
+ ) {
+ if (_overwriteMode && editor.cursorCharPosition < editor.text.size()) {
+ editor.text.setChar(event.character, editor.cursorCharPosition);
+ } else {
+ editor.text.insertChar(event.character, editor.cursorCharPosition);
+ }
+
+ ++editor.cursorCharPosition;
+ shouldRedrawText = true;
}
- eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event
- break;
}
- break;
+ }
}
}
- if (textChanged) {
- rect = g_sci->_gfxCompare->getNSRect(controlObject);
+ if (shouldDeleteChar) {
+ shouldRedrawText = true;
+ if (editor.cursorCharPosition < editor.text.size()) {
+ editor.text.deleteChar(editor.cursorCharPosition);
+ }
+ }
- if (textAddChar) {
- const char *textPtr = text.c_str();
+ if (shouldRedrawText) {
+ eraseCursor(editor);
+ _gfxText32->erase(editor.textRect, true);
+ _gfxText32->drawTextBox(editor.text);
+ drawCursor(editor);
+ textChanged = true;
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ } else if (editor.cursorCharPosition != lastCursorPosition) {
+ eraseCursor(editor);
+ drawCursor(editor);
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ } else {
+ flashCursor(editor);
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ }
- // We check if we are really able to add the new char
- uint16 textWidth = 0;
- while (*textPtr)
- textWidth += font->getCharWidth((byte)*textPtr++);
- textWidth += font->getCharWidth(curEvent.character);
+ g_sci->_gfxFrameout->frameOut(true);
+ g_sci->getSciDebugger()->onFrame();
+ g_sci->_gfxFrameout->throttle();
+ }
- // Does it fit?
- if (textWidth >= rect.width()) {
- return;
- }
+ g_sci->_gfxFrameout->deletePlane(*plane);
+ if (readSelectorValue(segMan, controlObject, SELECTOR(frameOut))) {
+ g_sci->_gfxFrameout->frameOut(true);
+ }
- text.insertChar(curEvent.character, cursorPos++);
+ _segMan->freeBitmap(editor.bitmap);
- // Note: the following checkAltInput call might make the text
- // too wide to fit, but SSCI fails to check that too.
- }
+ if (textChanged) {
+ editor.text.trim();
+ SciArray &string = *_segMan->lookupArray(textObject);
+ string.fromString(editor.text);
+ }
+
+ return make_reg(0, textChanged);
+}
- reg_t hunkId = readSelector(_segMan, controlObject, SELECTOR(bitmap));
- Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(controlObject);
- //texteditCursorErase(); // TODO: Cursor
+void GfxControls32::drawCursor(TextEditor &editor) {
+ if (!editor.cursorIsDrawn) {
+ editor.cursorRect.left = editor.textRect.left + _gfxText32->getTextWidth(editor.text, 0, editor.cursorCharPosition);
- // Write back string
- _segMan->strcpy(textReference, text.c_str());
- // Modify the buffer and show it
- _text->createTextBitmap(controlObject, 0, 0, hunkId);
+ const int16 scaledFontHeight = _gfxText32->scaleUpHeight(_gfxText32->_font->getHeight());
- _text->drawTextBitmap(0, 0, nsRect, controlObject);
- //texteditCursorDraw(rect, text.c_str(), cursorPos); // TODO: Cursor
- g_system->updateScreen();
+ // NOTE: The original code branched on borderColor here but
+ // the two branches appeared to be identical, differing only
+ // because the compiler decided to be differently clever
+ // when optimising multiplication in each branch
+ if (_overwriteMode) {
+ editor.cursorRect.top = editor.textRect.top;
+ editor.cursorRect.setHeight(scaledFontHeight);
} else {
- // TODO: Cursor
- /*
- if (g_system->getMillis() >= _texteditBlinkTime) {
- _paint16->invertRect(_texteditCursorRect);
- _paint16->bitsShow(_texteditCursorRect);
- _texteditCursorVisible = !_texteditCursorVisible;
- texteditSetBlinkTime();
- }
- */
+ editor.cursorRect.top = editor.textRect.top + scaledFontHeight - 1;
+ editor.cursorRect.setHeight(1);
+ }
+
+ const char currentChar = editor.cursorCharPosition < editor.text.size() ? editor.text[editor.cursorCharPosition] : ' ';
+ editor.cursorRect.setWidth(_gfxText32->getCharWidth(currentChar, true));
+
+ _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
+
+ editor.cursorIsDrawn = true;
+ }
+
+ _nextCursorFlashTick = g_sci->getTickCount() + 30;
+}
+
+void GfxControls32::eraseCursor(TextEditor &editor) {
+ if (editor.cursorIsDrawn) {
+ _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
+ editor.cursorIsDrawn = false;
+ }
+
+ _nextCursorFlashTick = g_sci->getTickCount() + 30;
+}
+
+void GfxControls32::flashCursor(TextEditor &editor) {
+ if (g_sci->getTickCount() > _nextCursorFlashTick) {
+ _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
+
+ editor.cursorIsDrawn = !editor.cursorIsDrawn;
+ _nextCursorFlashTick = g_sci->getTickCount() + 30;
+ }
+}
+
+#pragma mark -
+#pragma mark Scrollable window control
+
+ScrollWindow::ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) :
+ _segMan(segMan),
+ _gfxText32(segMan, g_sci->_gfxCache),
+ _maxNumEntries(maxNumEntries),
+ _firstVisibleChar(0),
+ _topVisibleLine(0),
+ _lastVisibleChar(0),
+ _bottomVisibleLine(0),
+ _numLines(0),
+ _numVisibleLines(0),
+ _plane(plane),
+ _foreColor(defaultForeColor),
+ _backColor(defaultBackColor),
+ _borderColor(defaultBorderColor),
+ _fontId(defaultFontId),
+ _alignment(defaultAlignment),
+ _visible(false),
+ _position(position),
+ _screenItem(nullptr),
+ _nextEntryId(1) {
+
+ _entries.reserve(maxNumEntries);
+
+ _gfxText32.setFont(_fontId);
+ _pointSize = _gfxText32._font->getHeight();
+
+ const uint16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ Common::Rect bitmapRect(gameRect);
+ mulinc(bitmapRect, Ratio(_gfxText32._xResolution, scriptWidth), Ratio(_gfxText32._yResolution, scriptHeight));
+
+ _textRect.left = 2;
+ _textRect.top = 2;
+ _textRect.right = bitmapRect.width() - 2;
+ _textRect.bottom = bitmapRect.height() - 2;
+
+ uint8 skipColor = 0;
+ while (skipColor == _foreColor || skipColor == _backColor) {
+ skipColor++;
+ }
+
+ assert(bitmapRect.width() > 0 && bitmapRect.height() > 0);
+ _bitmap = _gfxText32.createFontBitmap(bitmapRect.width(), bitmapRect.height(), _textRect, "", _foreColor, _backColor, skipColor, _fontId, _alignment, _borderColor, false, false, false);
+
+ debugC(1, kDebugLevelGraphics, "New ScrollWindow: textRect size: %d x %d, bitmap: %04x:%04x", _textRect.width(), _textRect.height(), PRINT_REG(_bitmap));
+}
+
+ScrollWindow::~ScrollWindow() {
+ _segMan->freeBitmap(_bitmap);
+ // _screenItem will be deleted by GfxFrameout
+}
+
+Ratio ScrollWindow::where() const {
+ return Ratio(_topVisibleLine, MAX(_numLines, 1));
+}
+
+void ScrollWindow::show() {
+ if (_visible) {
+ return;
+ }
+
+ if (_screenItem == nullptr) {
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = _bitmap;
+
+ _screenItem = new ScreenItem(_plane, celInfo, _position, ScaleInfo());
+ }
+
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane);
+
+ if (plane == nullptr) {
+ error("[ScrollWindow::show]: Plane %04x:%04x not found", PRINT_REG(_plane));
+ }
+
+ plane->_screenItemList.add(_screenItem);
+
+ _visible = true;
+}
+
+void ScrollWindow::hide() {
+ if (!_visible) {
+ return;
+ }
+
+ g_sci->_gfxFrameout->deleteScreenItem(*_screenItem, _plane);
+ _screenItem = nullptr;
+ g_sci->_gfxFrameout->frameOut(true);
+
+ _visible = false;
+}
+
+reg_t ScrollWindow::add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) {
+ if (_entries.size() == _maxNumEntries) {
+ ScrollWindowEntry removedEntry = _entries.remove_at(0);
+ _text.erase(0, removedEntry.text.size());
+ // `_firstVisibleChar` will be reset shortly if
+ // `scrollTo` is true, so there is no reason to
+ // update it
+ if (!scrollTo) {
+ _firstVisibleChar -= removedEntry.text.size();
+ }
+ }
+
+ _entries.push_back(ScrollWindowEntry());
+ ScrollWindowEntry &entry = _entries.back();
+
+ // NOTE: In SSCI the line ID was a memory handle for the
+ // string of this line. We use a numeric ID instead.
+ entry.id = make_reg(0, _nextEntryId++);
+
+ if (_nextEntryId > _maxNumEntries) {
+ _nextEntryId = 1;
+ }
+
+ // NOTE: In SSCI this was updated after _text was
+ // updated, which meant there was an extra unnecessary
+ // subtraction operation (subtracting `entry.text` size)
+ if (scrollTo) {
+ _firstVisibleChar = _text.size();
+ }
+
+ fillEntry(entry, text, fontId, foreColor, alignment);
+ _text += entry.text;
+
+ computeLineIndices();
+ update(true);
+
+ return entry.id;
+}
+
+void ScrollWindow::fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment) {
+ entry.alignment = alignment;
+ entry.foreColor = foreColor;
+ entry.fontId = fontId;
+
+ Common::String formattedText;
+
+ // NB: There are inconsistencies here.
+ // If there is a multi-line entry with non-default properties, and it
+ // is only partially displayed, it may not be displayed right, since the
+ // property directives are only added to the first line.
+ // (Verified by trying this in SSCI SQ6 with a custom ScrollWindowAdd call.)
+ //
+ // The converse is also a potential issue (but unverified), where lines
+ // with properties -1 can inherit properties from the previously rendered
+ // line instead of the defaults.
+
+ // NOTE: SSCI added "|s<lineIndex>|" here, but |s| is
+ // not a valid control code, so it just always ended up
+ // getting skipped
+ if (entry.fontId != -1) {
+ formattedText += Common::String::format("|f%d|", entry.fontId);
+ }
+ if (entry.foreColor != -1) {
+ formattedText += Common::String::format("|c%d|", entry.foreColor);
+ }
+ if (entry.alignment != -1) {
+ formattedText += Common::String::format("|a%d|", entry.alignment);
+ }
+ formattedText += text;
+ entry.text = formattedText;
+}
+
+reg_t ScrollWindow::modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) {
+
+ EntriesList::iterator it = _entries.begin();
+ uint firstCharLocation = 0;
+ for ( ; it != _entries.end(); ++it) {
+ if (it->id == id) {
+ break;
}
+ firstCharLocation += it->text.size();
+ }
+
+ if (it == _entries.end()) {
+ return make_reg(0, 0);
+ }
+
+ ScrollWindowEntry &entry = *it;
+
+ uint oldTextLength = entry.text.size();
+
+ fillEntry(entry, text, fontId, foreColor, alignment);
+ _text.replace(firstCharLocation, oldTextLength, entry.text);
+
+ if (scrollTo) {
+ _firstVisibleChar = firstCharLocation;
+ }
+
+ computeLineIndices();
+ update(true);
+
+ return entry.id;
+}
+
+void ScrollWindow::upArrow() {
+ if (_topVisibleLine == 0) {
+ return;
+ }
+
+ _topVisibleLine--;
+ _bottomVisibleLine--;
+
+ if (_bottomVisibleLine - _topVisibleLine + 1 < _numVisibleLines) {
+ _bottomVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
+
+ _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
+
+ Common::String lineText(_text.c_str() + _startsOfLines[_topVisibleLine], _text.c_str() + _startsOfLines[_topVisibleLine + 1] - 1);
+
+ debugC(3, kDebugLevelGraphics, "ScrollWindow::upArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str());
+
+ _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollUp);
+
+ if (_visible) {
+ assert(_screenItem);
+
+ _screenItem->update();
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+}
+
+void ScrollWindow::downArrow() {
+ if (_topVisibleLine + 1 >= _numLines) {
+ return;
+ }
+
+ _topVisibleLine++;
+ _bottomVisibleLine++;
+
+ if (_bottomVisibleLine + 1 >= _numLines) {
+ _bottomVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
+
+ _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
+
+ Common::String lineText;
+ if (_bottomVisibleLine - _topVisibleLine + 1 == _numVisibleLines) {
+ lineText = Common::String(_text.c_str() + _startsOfLines[_bottomVisibleLine], _text.c_str() + _startsOfLines[_bottomVisibleLine + 1] - 1);
+ } else {
+ // scroll in empty string
+ }
+
+ debugC(3, kDebugLevelGraphics, "ScrollWindow::downArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str());
+
+
+ _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollDown);
+
+ if (_visible) {
+ assert(_screenItem);
+
+ _screenItem->update();
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+}
+
+void ScrollWindow::go(const Ratio location) {
+ const int line = (location * _numLines).toInt();
+ if (line < 0 || line > _numLines) {
+ error("Index is Out of Range in ScrollWindow");
+ }
+
+ _firstVisibleChar = _startsOfLines[line];
+ update(true);
+
+ // HACK:
+ // It usually isn't possible to set _topVisibleLine >= _numLines, and so
+ // update() doesn't. However, in this case we should set _topVisibleLine
+ // past the end. This is clearly visible in Phantasmagoria when dragging
+ // the slider in the About dialog to the very end. The slider ends up lower
+ // than where it can be moved by scrolling down with the arrows.
+ if (location.isOne()) {
+ _topVisibleLine = _numLines;
+ }
+}
+
+void ScrollWindow::home() {
+ if (_firstVisibleChar == 0) {
+ return;
+ }
+
+ _firstVisibleChar = 0;
+ update(true);
+}
+
+void ScrollWindow::end() {
+ if (_bottomVisibleLine + 1 >= _numLines) {
+ return;
+ }
+
+ int line = _numLines - _numVisibleLines;
+ if (line < 0) {
+ line = 0;
+ }
+ _firstVisibleChar = _startsOfLines[line];
+ update(true);
+}
+
+void ScrollWindow::pageUp() {
+ if (_topVisibleLine == 0) {
+ return;
+ }
+
+ _topVisibleLine -= _numVisibleLines;
+ if (_topVisibleLine < 0) {
+ _topVisibleLine = 0;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ update(true);
+}
+
+void ScrollWindow::pageDown() {
+ if (_topVisibleLine + 1 >= _numLines) {
+ return;
+ }
+
+ _topVisibleLine += _numVisibleLines;
+ if (_topVisibleLine + 1 >= _numLines) {
+ _topVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ update(true);
+}
+
+void ScrollWindow::computeLineIndices() {
+ _gfxText32.setFont(_fontId);
+ // NOTE: Unlike SSCI, foreColor and alignment are not
+ // set since these properties do not affect the width of
+ // lines
+
+ if (_gfxText32._font->getHeight() != _pointSize) {
+ error("Illegal font size font = %d pointSize = %d, should be %d.", _fontId, _gfxText32._font->getHeight(), _pointSize);
+ }
+
+ Common::Rect lineRect(0, 0, _textRect.width(), _pointSize + 3);
+
+ _startsOfLines.clear();
+
+ // NOTE: The original engine had a 1000-line limit; we
+ // do not enforce any limit
+ for (uint charIndex = 0; charIndex < _text.size(); ) {
+ _startsOfLines.push_back(charIndex);
+ charIndex += _gfxText32.getTextCount(_text, charIndex, lineRect, false);
+ }
+
+ _numLines = _startsOfLines.size();
+
+ _startsOfLines.push_back(_text.size());
+
+ _lastVisibleChar = _gfxText32.getTextCount(_text, 0, _fontId, _textRect, false) - 1;
+
+ _bottomVisibleLine = 0;
+ while (
+ _bottomVisibleLine < _numLines - 1 &&
+ _startsOfLines[_bottomVisibleLine + 1] < _lastVisibleChar
+ ) {
+ ++_bottomVisibleLine;
+ }
+
+ _numVisibleLines = _bottomVisibleLine + 1;
+}
+
+void ScrollWindow::update(const bool doFrameOut) {
+ _topVisibleLine = 0;
+ while (
+ _topVisibleLine < _numLines - 1 &&
+ _firstVisibleChar >= _startsOfLines[_topVisibleLine + 1]
+ ) {
+ ++_topVisibleLine;
+ }
+
+ _bottomVisibleLine = _topVisibleLine + _numVisibleLines - 1;
+ if (_bottomVisibleLine >= _numLines) {
+ _bottomVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+
+ if (_bottomVisibleLine >= 0) {
+ _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
+ } else {
+ _lastVisibleChar = -1;
+ }
+
+ _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
+
+ _gfxText32.erase(_textRect, false);
+ _gfxText32.drawTextBox(_visibleText);
+
+ if (_visible) {
+ assert(_screenItem);
+
+ _screenItem->update();
+ if (doFrameOut) {
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+ }
+}
+
+reg_t GfxControls32::makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) {
+
+ ScrollWindow *scrollWindow = new ScrollWindow(_segMan, gameRect, position, planeObj, defaultForeColor, defaultBackColor, defaultFontId, defaultAlignment, defaultBorderColor, maxNumEntries);
+
+ const uint16 id = _nextScrollWindowId++;
+ _scrollWindows[id] = scrollWindow;
+ return make_reg(0, id);
+}
+
+ScrollWindow *GfxControls32::getScrollWindow(const reg_t id) {
+ ScrollWindowMap::iterator it;
+ it = _scrollWindows.find(id.toUint16());
+ if (it == _scrollWindows.end())
+ error("Invalid ScrollWindow ID");
+
+ return it->_value;
+}
+
+void GfxControls32::destroyScrollWindow(const reg_t id) {
+ ScrollWindow *scrollWindow = getScrollWindow(id);
+ scrollWindow->hide();
+ _scrollWindows.erase(id.getOffset());
+ delete scrollWindow;
+}
+
+#pragma mark -
+#pragma mark Message box
+
+int16 GfxControls32::showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue) {
+ GUI::MessageDialog dialog(message, okLabel, altLabel);
+ return (dialog.runModal() == GUI::kMessageOK) ? okValue : altValue;
+}
+
+reg_t GfxControls32::kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style) {
+ if (g_engine) {
+ g_engine->pauseEngine(true);
+ }
+
+ int16 result;
+
+ switch (style & 0xF) {
+ case kMessageBoxOK:
+ result = showMessageBox(message, _("OK"), NULL, 1, 1);
+ break;
+ case kMessageBoxYesNo:
+ result = showMessageBox(message, _("Yes"), _("No"), 6, 7);
+ break;
+ default:
+ error("Unsupported MessageBox style 0x%x", style & 0xF);
+ }
+
+ if (g_engine) {
+ g_engine->pauseEngine(false);
+ }
- textAddChar = false;
- textChanged = false;
- g_sci->sleep(10);
- } // while
+ return make_reg(0, result);
}
} // End of namespace Sci
diff --git a/engines/sci/graphics/controls32.h b/engines/sci/graphics/controls32.h
index 5af7c20f16..680c70d2d6 100644
--- a/engines/sci/graphics/controls32.h
+++ b/engines/sci/graphics/controls32.h
@@ -23,12 +23,389 @@
#ifndef SCI_GRAPHICS_CONTROLS32_H
#define SCI_GRAPHICS_CONTROLS32_H
+#include "sci/graphics/text32.h"
+
namespace Sci {
class GfxCache;
class GfxScreen;
class GfxText32;
+enum MessageBoxStyle {
+ kMessageBoxOK = 0x0,
+ kMessageBoxYesNo = 0x4
+};
+
+struct TextEditor {
+ /**
+ * The bitmap where the editor is rendered.
+ */
+ reg_t bitmap;
+
+ /**
+ * The width of the editor, in bitmap pixels.
+ */
+ int16 width;
+
+ /**
+ * The text in the editor.
+ */
+ Common::String text;
+
+ /**
+ * The rect where text should be drawn into the editor,
+ * in bitmap pixels.
+ */
+ Common::Rect textRect;
+
+ /**
+ * The color of the border. -1 indicates no border.
+ */
+ int16 borderColor;
+
+ /**
+ * The text color.
+ */
+ uint8 foreColor;
+
+ /**
+ * The background color.
+ */
+ uint8 backColor;
+
+ /**
+ * The transparent color.
+ */
+ uint8 skipColor;
+
+ /**
+ * The font used to render the text in the editor.
+ */
+ GuiResourceId fontId;
+
+ /**
+ * The current position of the cursor within the editor.
+ */
+ uint16 cursorCharPosition;
+
+ /**
+ * Whether or not the cursor is currently drawn to the
+ * screen.
+ */
+ bool cursorIsDrawn;
+
+ /**
+ * The rectangle for drawing the input cursor, in bitmap
+ * pixels.
+ */
+ Common::Rect cursorRect;
+
+ /**
+ * The maximum allowed text length, in characters.
+ */
+ uint16 maxLength;
+};
+
+/**
+ * A single block of text written to a ScrollWindow.
+ */
+struct ScrollWindowEntry {
+ /**
+ * ID of the line. In SSCI this was actually a memory
+ * handle for the string of this line. We use a simple
+ * numeric ID instead.
+ */
+ reg_t id;
+
+ /**
+ * The alignment to use when rendering this line of
+ * text. If -1, the default alignment from the
+ * corresponding ScrollWindow will be used.
+ */
+ TextAlign alignment;
+
+ /**
+ * The color to use to render this line of text. If -1,
+ * the default foreground color from the corresponding
+ * ScrollWindow will be used.
+ */
+ int16 foreColor;
+
+ /**
+ * The font to use to render this line of text. If -1,
+ * the default font from the corresponding ScrollWindow
+ * will be used.
+ */
+ GuiResourceId fontId;
+
+ /**
+ * The text.
+ */
+ Common::String text;
+};
+
+class ScreenItem;
+
+/**
+ * A scrollable text window.
+ */
+class ScrollWindow {
+public:
+ ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries);
+ ~ScrollWindow();
+
+ /**
+ * Adds a new text entry to the window. If `fontId`,
+ * `foreColor`, or `alignment` are `-1`, the
+ * ScrollWindow's default values will be used.
+ */
+ reg_t add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo);
+
+ /**
+ * Modifies an existing text entry with the given ID. If
+ * `fontId`, `foreColor`, or `alignment` are `-1`, the
+ * ScrollWindow's default values will be used.
+ */
+ reg_t modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo);
+
+ /**
+ * Shows the ScrollWindow if it is not already visible.
+ */
+ void show();
+
+ /**
+ * Hides the ScrollWindow if it is currently visible.
+ */
+ void hide();
+
+ /**
+ * Gets the number of lines that the content of a
+ * ScrollWindow is scrolled upward, as a ratio of the
+ * total number of lines of content.
+ */
+ Ratio where() const;
+
+ /**
+ * Scrolls the window to a specific location.
+ */
+ void go(const Ratio location);
+
+ /**
+ * Scrolls the window to the top.
+ */
+ void home();
+
+ /**
+ * Scrolls the window to the bottom.
+ */
+ void end();
+
+ /**
+ * Scrolls the window up one line.
+ */
+ void upArrow();
+
+ /**
+ * Scrolls the window down one line.
+ */
+ void downArrow();
+
+ /**
+ * Scrolls the window up by one page.
+ */
+ void pageUp();
+
+ /**
+ * Scrolls the window down by one page.
+ */
+ void pageDown();
+
+ /**
+ * Gets a reference to the in-memory bitmap that
+ * is used to render the text in the ScrollWindow.
+ */
+ const reg_t getBitmap() const { return _bitmap; }
+
+private:
+ SegManager *_segMan;
+
+ typedef Common::Array<ScrollWindowEntry> EntriesList;
+
+ /**
+ * A convenience function that fills a
+ * ScrollWindowEntry's properties.
+ */
+ void fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment);
+
+ /**
+ * Rescans the entire text of the ScrollWindow when an
+ * entry is added or modified, calculating the character
+ * offsets of all line endings, the total number of
+ * lines of text, the height of the viewport (in lines
+ * of text), the last character visible in the viewport
+ * (assuming the viewport is scrolled to the top), and
+ * the line index of the bottommost visible line
+ * (assuming the viewport is scrolled to the top).
+ */
+ void computeLineIndices();
+
+ /**
+ * Calculates which text is visible within the
+ * ScrollWindow's viewport and renders the text to the
+ * internal bitmap.
+ *
+ * If `doFrameOut` is true, the screen will be refreshed
+ * immediately instead of waiting for the next call to
+ * `kFrameOut`.
+ */
+ void update(const bool doFrameOut);
+
+ /**
+ * The text renderer.
+ */
+ GfxText32 _gfxText32;
+
+ /**
+ * The individual text entries added to the
+ * ScrollWindow.
+ */
+ EntriesList _entries;
+
+ /**
+ * The maximum number of entries allowed. Once this
+ * limit is reached, the oldest entry will be removed
+ * when a new entry is added.
+ */
+ uint _maxNumEntries;
+
+ /**
+ * A mapping from a line index to the line's character
+ * offset in `_text`.
+ */
+ Common::Array<int> _startsOfLines;
+
+ /**
+ * All text added to the window.
+ */
+ Common::String _text;
+
+ /**
+ * Text that is within the viewport of the ScrollWindow.
+ */
+ Common::String _visibleText;
+
+ /**
+ * The offset of the first visible character in `_text`.
+ */
+ int _firstVisibleChar;
+
+ /**
+ * The index of the line that is at the top of the
+ * viewport.
+ */
+ int _topVisibleLine;
+
+ /**
+ * The index of the last visible character in `_text`,
+ * or -1 if there is no text.
+ */
+ int _lastVisibleChar;
+
+ /**
+ * The index of the line that is at the bottom of the
+ * viewport, or -1 if there is no text.
+ */
+ int _bottomVisibleLine;
+
+ /**
+ * The total number of lines in the backbuffer. This
+ * number may be higher than the total number of entries
+ * if an entry contains newlines.
+ */
+ int _numLines;
+
+ /**
+ * The number of lines that are currently visible in the
+ * text area of the window.
+ */
+ int _numVisibleLines;
+
+ /**
+ * The plane in which the ScrollWindow should be
+ * rendered.
+ */
+ reg_t _plane;
+
+ /**
+ * The default text color.
+ */
+ uint8 _foreColor;
+
+ /**
+ * The default background color of the text bitmap.
+ */
+ uint8 _backColor;
+
+ /**
+ * The default border color of the text bitmap. If -1,
+ * the viewport will have no border.
+ */
+ int16 _borderColor;
+
+ /**
+ * The default font used for rendering text into the
+ * ScrollWindow.
+ */
+ GuiResourceId _fontId;
+
+ /**
+ * The default text alignment used for rendering text
+ * into the ScrollWindow.
+ */
+ TextAlign _alignment;
+
+ /**
+ * The visibility of the ScrollWindow.
+ */
+ bool _visible;
+
+ /**
+ * The dimensions of the text box inside the font
+ * bitmap, in text-system coordinates.
+ */
+ Common::Rect _textRect;
+
+ /**
+ * The top-left corner of the ScrollWindow's screen
+ * item, in game script coordinates, relative to the
+ * parent plane.
+ */
+ Common::Point _position;
+
+ /**
+ * The height of the default font in screen pixels. All
+ * fonts rendered into the ScrollWindow must have this
+ * same height.
+ */
+ uint8 _pointSize;
+
+ /**
+ * The bitmap used to render text.
+ */
+ reg_t _bitmap;
+
+ /**
+ * A monotonically increasing ID used to identify
+ * text entries added to the ScrollWindow.
+ */
+ uint16 _nextEntryId;
+
+ /**
+ * The ScrollWindow's screen item.
+ */
+ ScreenItem *_screenItem;
+};
+
/**
* Controls class, handles drawing of controls in SCI32 (SCI2, SCI2.1, SCI3) games
*/
@@ -37,12 +414,93 @@ public:
GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text);
~GfxControls32();
- void kernelTexteditChange(reg_t controlObject);
-
private:
SegManager *_segMan;
- GfxCache *_cache;
- GfxText32 *_text;
+ GfxCache *_gfxCache;
+ GfxText32 *_gfxText32;
+
+#pragma mark -
+#pragma mark Text input control
+public:
+ reg_t kernelEditText(const reg_t controlObject);
+
+private:
+ /**
+ * If true, typing will overwrite text that already
+ * exists at the text cursor's current position.
+ */
+ bool _overwriteMode;
+
+ /**
+ * The tick at which the text cursor should be toggled
+ * by `flashCursor`.
+ */
+ uint32 _nextCursorFlashTick;
+
+ /**
+ * Draws the text cursor for the given editor.
+ */
+ void drawCursor(TextEditor &editor);
+
+ /**
+ * Erases the text cursor for the given editor.
+ */
+ void eraseCursor(TextEditor &editor);
+
+ /**
+ * Toggles the text cursor for the given editor to be
+ * either drawn or erased.
+ */
+ void flashCursor(TextEditor &editor);
+
+#pragma mark -
+#pragma mark Scrollable window control
+public:
+ /**
+ * Creates a new scrollable window and returns the ID
+ * for the new window, which is used by game scripts to
+ * interact with scrollable windows.
+ */
+ reg_t makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries);
+
+ /**
+ * Gets a registered ScrollWindow instance by ID.
+ */
+ ScrollWindow *getScrollWindow(const reg_t id);
+
+ /**
+ * Destroys the scroll window with the given ID.
+ */
+ void destroyScrollWindow(const reg_t id);
+
+private:
+ typedef Common::HashMap<uint16, ScrollWindow *> ScrollWindowMap;
+
+ /**
+ * Monotonically increasing ID used to identify
+ * ScrollWindow instances.
+ */
+ uint16 _nextScrollWindowId;
+
+ /**
+ * A lookup table for registered ScrollWindow instances.
+ */
+ ScrollWindowMap _scrollWindows;
+
+#pragma mark -
+#pragma mark Message box
+public:
+ /**
+ * Displays an OS-level message dialog.
+ */
+ reg_t kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style);
+
+private:
+ /**
+ * Convenience function for creating and showing a
+ * message box.
+ */
+ int16 showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/coordadjuster.cpp b/engines/sci/graphics/coordadjuster.cpp
index 93dff10382..2f22d191d0 100644
--- a/engines/sci/graphics/coordadjuster.cpp
+++ b/engines/sci/graphics/coordadjuster.cpp
@@ -32,9 +32,6 @@
namespace Sci {
-GfxCoordAdjuster::GfxCoordAdjuster() {
-}
-
GfxCoordAdjuster16::GfxCoordAdjuster16(GfxPorts *ports)
: _ports(ports) {
}
@@ -83,53 +80,4 @@ Common::Rect GfxCoordAdjuster16::pictureGetDisplayArea() {
return displayArea;
}
-#ifdef ENABLE_SCI32
-GfxCoordAdjuster32::GfxCoordAdjuster32(SegManager *segMan)
- : _segMan(segMan) {
- _scriptsRunningWidth = 0;
- _scriptsRunningHeight = 0;
-}
-
-GfxCoordAdjuster32::~GfxCoordAdjuster32() {
-}
-
-void GfxCoordAdjuster32::kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject) {
- uint16 planeTop = readSelectorValue(_segMan, planeObject, SELECTOR(top));
- uint16 planeLeft = readSelectorValue(_segMan, planeObject, SELECTOR(left));
-
- y -= planeTop;
- x -= planeLeft;
-}
-void GfxCoordAdjuster32::kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject) {
- uint16 planeTop = readSelectorValue(_segMan, planeObject, SELECTOR(top));
- uint16 planeLeft = readSelectorValue(_segMan, planeObject, SELECTOR(left));
-
- x += planeLeft;
- y += planeTop;
-}
-
-void GfxCoordAdjuster32::setScriptsResolution(uint16 width, uint16 height) {
- _scriptsRunningWidth = width;
- _scriptsRunningHeight = height;
-}
-
-void GfxCoordAdjuster32::fromDisplayToScript(int16 &y, int16 &x) {
- y = ((y * _scriptsRunningHeight) / g_sci->_gfxScreen->getHeight());
- x = ((x * _scriptsRunningWidth) / g_sci->_gfxScreen->getWidth());
-}
-
-void GfxCoordAdjuster32::fromScriptToDisplay(int16 &y, int16 &x) {
- y = ((y * g_sci->_gfxScreen->getHeight()) / _scriptsRunningHeight);
- x = ((x * g_sci->_gfxScreen->getWidth()) / _scriptsRunningWidth);
-}
-
-void GfxCoordAdjuster32::pictureSetDisplayArea(Common::Rect displayArea) {
- _pictureDisplayArea = displayArea;
-}
-
-Common::Rect GfxCoordAdjuster32::pictureGetDisplayArea() {
- return _pictureDisplayArea;
-}
-#endif
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/coordadjuster.h b/engines/sci/graphics/coordadjuster.h
index cb0227fbe4..f7ebd3ec75 100644
--- a/engines/sci/graphics/coordadjuster.h
+++ b/engines/sci/graphics/coordadjuster.h
@@ -35,27 +35,7 @@ class GfxPorts;
* most of the time sci32 doesn't do any coordinate adjustment at all
* sci16 does a lot of port adjustment on given coordinates
*/
-class GfxCoordAdjuster {
-public:
- GfxCoordAdjuster();
- virtual ~GfxCoordAdjuster() { }
-
- virtual void kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject = NULL_REG) { }
- virtual void kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject = NULL_REG) { }
-
- virtual Common::Rect onControl(Common::Rect rect) { return rect; }
- virtual void setCursorPos(Common::Point &pos) { }
- virtual void moveCursor(Common::Point &pos) { }
-
- virtual void setScriptsResolution(uint16 width, uint16 height) { }
- virtual void fromScriptToDisplay(int16 &y, int16 &x) { }
- virtual void fromDisplayToScript(int16 &y, int16 &x) { }
-
- virtual Common::Rect pictureGetDisplayArea() { return Common::Rect(0, 0); }
-private:
-};
-
-class GfxCoordAdjuster16 : public GfxCoordAdjuster {
+class GfxCoordAdjuster16 {
public:
GfxCoordAdjuster16(GfxPorts *ports);
~GfxCoordAdjuster16();
@@ -73,32 +53,6 @@ private:
GfxPorts *_ports;
};
-#ifdef ENABLE_SCI32
-class GfxCoordAdjuster32 : public GfxCoordAdjuster {
-public:
- GfxCoordAdjuster32(SegManager *segMan);
- ~GfxCoordAdjuster32();
-
- void kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject = NULL_REG);
- void kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject = NULL_REG);
-
- void setScriptsResolution(uint16 width, uint16 height);
- void fromScriptToDisplay(int16 &y, int16 &x);
- void fromDisplayToScript(int16 &y, int16 &x);
-
- void pictureSetDisplayArea(Common::Rect displayArea);
- Common::Rect pictureGetDisplayArea();
-
-private:
- SegManager *_segMan;
-
- Common::Rect _pictureDisplayArea;
-
- uint16 _scriptsRunningWidth;
- uint16 _scriptsRunningHeight;
-};
-#endif
-
} // End of namespace Sci
#endif
diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp
index e8496b96e5..7cf9a574ef 100644
--- a/engines/sci/graphics/cursor.cpp
+++ b/engines/sci/graphics/cursor.cpp
@@ -80,7 +80,7 @@ GfxCursor::~GfxCursor() {
kernelClearZoomZone();
}
-void GfxCursor::init(GfxCoordAdjuster *coordAdjuster, EventManager *event) {
+void GfxCursor::init(GfxCoordAdjuster16 *coordAdjuster, EventManager *event) {
_coordAdjuster = coordAdjuster;
_event = event;
}
@@ -336,6 +336,9 @@ void GfxCursor::setPosition(Common::Point pos) {
&& ((workaround->newPositionX == pos.x) && (workaround->newPositionY == pos.y))) {
EngineState *s = g_sci->getEngineState();
s->_cursorWorkaroundActive = true;
+ // At least on OpenPandora it seems that the cursor is actually set, but a bit afterwards
+ // touch screen controls will overwrite the position. More information see kGetEvent in kevent.cpp.
+ s->_cursorWorkaroundPosCount = 5; // should be enough for OpenPandora
s->_cursorWorkaroundPoint = pos;
s->_cursorWorkaroundRect = Common::Rect(workaround->rectLeft, workaround->rectTop, workaround->rectRight, workaround->rectBottom);
return;
@@ -509,32 +512,18 @@ void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNu
// automatically. The view resources may exist, but none of the games actually
// use them.
- if (_macCursorRemap.empty()) {
- // QFG1/Freddy/Hoyle4 use a straight viewNum->cursor ID mapping
- // KQ6 uses this mapping for its cursors
- if (g_sci->getGameId() == GID_KQ6) {
- if (viewNum == 990) // Inventory Cursors
- viewNum = loopNum * 16 + celNum + 2000;
- else if (viewNum == 998) // Regular Cursors
- viewNum = celNum + 1000;
- else // Unknown cursor, ignored
- return;
- }
- if (g_sci->hasMacIconBar())
- g_sci->_gfxMacIconBar->setInventoryIcon(viewNum);
- } else {
- // If we do have the list, we'll be using a remap based on what the
- // scripts have given us.
- for (uint32 i = 0; i < _macCursorRemap.size(); i++) {
- if (viewNum == _macCursorRemap[i]) {
- viewNum = (i + 1) * 0x100 + loopNum * 0x10 + celNum;
- break;
- }
-
- if (i == _macCursorRemap.size())
- error("Unmatched Mac cursor %d", viewNum);
- }
+ // QFG1/Freddy/Hoyle4 use a straight viewNum->cursor ID mapping
+ // KQ6 uses this mapping for its cursors
+ if (g_sci->getGameId() == GID_KQ6) {
+ if (viewNum == 990) // Inventory Cursors
+ viewNum = loopNum * 16 + celNum + 2000;
+ else if (viewNum == 998) // Regular Cursors
+ viewNum = celNum + 1000;
+ else // Unknown cursor, ignored
+ return;
}
+ if (g_sci->hasMacIconBar())
+ g_sci->_gfxMacIconBar->setInventoryIcon(viewNum);
Resource *resource = _resMan->findResource(ResourceId(kResourceTypeCursor, viewNum), false);
@@ -565,9 +554,4 @@ void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNu
kernelShow();
}
-void GfxCursor::setMacCursorRemapList(int cursorCount, reg_t *cursors) {
- for (int i = 0; i < cursorCount; i++)
- _macCursorRemap.push_back(cursors[i].toUint16());
-}
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/cursor.h b/engines/sci/graphics/cursor.h
index 8d125c45b3..36518ea5db 100644
--- a/engines/sci/graphics/cursor.h
+++ b/engines/sci/graphics/cursor.h
@@ -25,6 +25,8 @@
#include "common/array.h"
#include "common/hashmap.h"
+#include "sci/sci.h"
+#include "sci/graphics/helpers.h"
namespace Sci {
@@ -53,7 +55,7 @@ public:
GfxCursor(ResourceManager *resMan, GfxPalette *palette, GfxScreen *screen);
~GfxCursor();
- void init(GfxCoordAdjuster *coordAdjuster, EventManager *event);
+ void init(GfxCoordAdjuster16 *coordAdjuster, EventManager *event);
void kernelShow();
void kernelHide();
@@ -93,15 +95,13 @@ public:
void kernelSetPos(Common::Point pos);
void kernelMoveCursor(Common::Point pos);
- void setMacCursorRemapList(int cursorCount, reg_t *cursors);
-
private:
void purgeCache();
ResourceManager *_resMan;
GfxScreen *_screen;
GfxPalette *_palette;
- GfxCoordAdjuster *_coordAdjuster;
+ GfxCoordAdjuster16 *_coordAdjuster;
EventManager *_event;
int _upscaledHires;
@@ -134,9 +134,6 @@ private:
// these instead and replace the game's gold cursors with their silver
// equivalents.
bool _useSilverSQ4CDCursors;
-
- // Mac versions of games use a remap list to remap their cursors
- Common::Array<uint16> _macCursorRemap;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/cursor32.cpp b/engines/sci/graphics/cursor32.cpp
new file mode 100644
index 0000000000..2f2611c769
--- /dev/null
+++ b/engines/sci/graphics/cursor32.cpp
@@ -0,0 +1,449 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/rational.h" // for Rational, operator*
+#include "common/system.h" // for OSystem, g_system
+#include "common/memstream.h"
+#include "graphics/cursorman.h" // for CursorMan
+#include "graphics/maccursor.h"
+#include "sci/graphics/celobj32.h" // for CelObjView, CelInfo32, Ratio
+#include "sci/graphics/cursor32.h"
+#include "sci/graphics/frameout.h" // for GfxFrameout
+
+namespace Sci {
+
+GfxCursor32::GfxCursor32() :
+ _hideCount(0),
+ _position(0, 0),
+ _writeToVMAP(false) {
+ CursorMan.showMouse(false);
+}
+
+void GfxCursor32::init(const Buffer &vmap) {
+ _vmap = vmap;
+ _vmapRegion.rect = Common::Rect(_vmap.screenWidth, _vmap.screenHeight);
+ _vmapRegion.data = (byte *)_vmap.getPixels();
+ _restrictedArea = _vmapRegion.rect;
+}
+
+GfxCursor32::~GfxCursor32() {
+ CursorMan.showMouse(true);
+ free(_cursor.data);
+ free(_cursorBack.data);
+ free(_drawBuff1.data);
+ free(_drawBuff2.data);
+ free(_savedVmapRegion.data);
+}
+
+void GfxCursor32::hide() {
+ if (_hideCount++) {
+ return;
+ }
+
+ if (!_cursorBack.rect.isEmpty()) {
+ drawToHardware(_cursorBack);
+ }
+}
+
+void GfxCursor32::revealCursor() {
+ _cursorBack.rect = _cursor.rect;
+ _cursorBack.rect.clip(_vmapRegion.rect);
+ if (_cursorBack.rect.isEmpty()) {
+ return;
+ }
+
+ readVideo(_cursorBack);
+ _drawBuff1.rect = _cursor.rect;
+ copy(_drawBuff1, _cursorBack);
+ paint(_drawBuff1, _cursor);
+ drawToHardware(_drawBuff1);
+}
+
+void GfxCursor32::paint(DrawRegion &target, const DrawRegion &source) {
+ if (source.rect.isEmpty()) {
+ return;
+ }
+
+ Common::Rect drawRect(source.rect);
+ drawRect.clip(target.rect);
+ if (drawRect.isEmpty()) {
+ return;
+ }
+
+ const int16 sourceXOffset = drawRect.left - source.rect.left;
+ const int16 sourceYOffset = drawRect.top - source.rect.top;
+ const int16 drawRectWidth = drawRect.width();
+ const int16 drawRectHeight = drawRect.height();
+
+ byte *targetPixel = target.data + ((drawRect.top - target.rect.top) * target.rect.width()) + (drawRect.left - target.rect.left);
+ const byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset;
+ const uint8 skipColor = source.skipColor;
+
+ const int16 sourceStride = source.rect.width() - drawRectWidth;
+ const int16 targetStride = target.rect.width() - drawRectWidth;
+
+ for (int16 y = 0; y < drawRectHeight; ++y) {
+ for (int16 x = 0; x < drawRectWidth; ++x) {
+ if (*sourcePixel != skipColor) {
+ *targetPixel = *sourcePixel;
+ }
+ ++targetPixel;
+ ++sourcePixel;
+ }
+ sourcePixel += sourceStride;
+ targetPixel += targetStride;
+ }
+}
+
+void GfxCursor32::drawToHardware(const DrawRegion &source) {
+ Common::Rect drawRect(source.rect);
+ drawRect.clip(_vmapRegion.rect);
+ const int16 sourceXOffset = drawRect.left - source.rect.left;
+ const int16 sourceYOffset = drawRect.top - source.rect.top;
+ byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset;
+
+ g_system->copyRectToScreen(sourcePixel, source.rect.width(), drawRect.left, drawRect.top, drawRect.width(), drawRect.height());
+ g_system->updateScreen();
+}
+
+void GfxCursor32::unhide() {
+ if (_hideCount == 0 || --_hideCount) {
+ return;
+ }
+
+ _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y);
+ revealCursor();
+}
+
+void GfxCursor32::show() {
+ if (_hideCount) {
+ _hideCount = 0;
+ _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y);
+ revealCursor();
+ }
+}
+
+void GfxCursor32::setRestrictedArea(const Common::Rect &rect) {
+ _restrictedArea = rect;
+
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ mulru(_restrictedArea, Ratio(screenWidth, scriptWidth), Ratio(screenHeight, scriptHeight), 0);
+
+ if (_position.x < rect.left) {
+ _position.x = rect.left;
+ }
+ if (_position.x >= rect.right) {
+ _position.x = rect.right - 1;
+ }
+ if (_position.y < rect.top) {
+ _position.y = rect.top;
+ }
+ if (_position.y >= rect.bottom) {
+ _position.y = rect.bottom - 1;
+ }
+
+ g_system->warpMouse(_position.x, _position.y);
+}
+
+void GfxCursor32::clearRestrictedArea() {
+ _restrictedArea = _vmapRegion.rect;
+}
+
+void GfxCursor32::setView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {
+ hide();
+
+ _cursorInfo.resourceId = viewId;
+ _cursorInfo.loopNo = loopNo;
+ _cursorInfo.celNo = celNo;
+
+ if (_macCursorRemap.empty() && viewId != -1) {
+ CelObjView view(viewId, loopNo, celNo);
+
+ _hotSpot = view._origin;
+ _width = view._width;
+ _height = view._height;
+
+ // SSCI never increased the size of cursors, but some of the cursors
+ // in early SCI32 games were designed for low-resolution display mode
+ // and so are kind of hard to pick out when running in high-resolution
+ // mode.
+ // To address this, we make some slight adjustments to cursor display
+ // in these early games:
+ // GK1: All the cursors are increased in size since they all appear to
+ // be designed for low-res display.
+ // PQ4: We only make the cursors bigger if they are above a set
+ // threshold size because inventory items usually have a
+ // high-resolution cursor representation.
+ bool pixelDouble = false;
+ if (g_sci->_gfxFrameout->_isHiRes &&
+ (g_sci->getGameId() == GID_GK1 ||
+ (g_sci->getGameId() == GID_PQ4 && _width <= 22 && _height <= 22))) {
+
+ _width *= 2;
+ _height *= 2;
+ _hotSpot.x *= 2;
+ _hotSpot.y *= 2;
+ pixelDouble = true;
+ }
+
+ _cursor.data = (byte *)realloc(_cursor.data, _width * _height);
+ _cursor.rect = Common::Rect(_width, _height);
+ memset(_cursor.data, 255, _width * _height);
+ _cursor.skipColor = 255;
+
+ Buffer target(_width, _height, _cursor.data);
+ if (pixelDouble) {
+ view.draw(target, _cursor.rect, Common::Point(0, 0), false, 2, 2);
+ } else {
+ view.draw(target, _cursor.rect, Common::Point(0, 0), false);
+ }
+ } else if (!_macCursorRemap.empty() && viewId != -1) {
+ // Mac cursor handling
+ GuiResourceId viewNum = viewId;
+
+ // Remap cursor view based on what the scripts have given us.
+ for (uint32 i = 0; i < _macCursorRemap.size(); i++) {
+ if (viewNum == _macCursorRemap[i]) {
+ viewNum = (i + 1) * 0x100 + loopNo * 0x10 + celNo;
+ break;
+ }
+
+ if (i == _macCursorRemap.size())
+ error("Unmatched Mac cursor %d", viewNum);
+ }
+
+ _cursorInfo.resourceId = viewNum;
+
+ Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeCursor, viewNum), false);
+
+ if (!resource) {
+ // The cursor resources often don't exist, this is normal behavior
+ debug(0, "Mac cursor %d not found", viewNum);
+ return;
+ }
+ Common::MemoryReadStream resStream(resource->data, resource->size);
+ Graphics::MacCursor *macCursor = new Graphics::MacCursor();
+
+ if (!macCursor->readFromStream(resStream)) {
+ warning("Failed to load Mac cursor %d", viewNum);
+ delete macCursor;
+ return;
+ }
+
+ _hotSpot = Common::Point(macCursor->getHotspotX(), macCursor->getHotspotY());
+ _width = macCursor->getWidth();
+ _height = macCursor->getHeight();
+
+ _cursor.data = (byte *)realloc(_cursor.data, _width * _height);
+ memcpy(_cursor.data, macCursor->getSurface(), _width * _height);
+ _cursor.rect = Common::Rect(_width, _height);
+ _cursor.skipColor = macCursor->getKeyColor();
+
+ // The cursor will be drawn on next refresh
+ delete macCursor;
+ } else {
+ _hotSpot = Common::Point(0, 0);
+ _width = _height = 1;
+ _cursor.data = (byte *)realloc(_cursor.data, _width * _height);
+ _cursor.rect = Common::Rect(_width, _height);
+ *_cursor.data = _cursor.skipColor;
+ _cursorBack.rect = _cursor.rect;
+ _cursorBack.rect.clip(_vmapRegion.rect);
+ if (!_cursorBack.rect.isEmpty()) {
+ readVideo(_cursorBack);
+ }
+ }
+
+ _cursorBack.data = (byte *)realloc(_cursorBack.data, _width * _height);
+ _drawBuff1.data = (byte *)realloc(_drawBuff1.data, _width * _height);
+ _drawBuff2.data = (byte *)realloc(_drawBuff2.data, _width * _height * 4);
+ _savedVmapRegion.data = (byte *)realloc(_savedVmapRegion.data, _width * _height);
+
+ unhide();
+}
+
+void GfxCursor32::readVideo(DrawRegion &target) {
+ if (g_sci->_gfxFrameout->_frameNowVisible) {
+ copy(target, _vmapRegion);
+ } else {
+ // NOTE: SSCI would read the background for the cursor directly out of
+ // video memory here, but as far as can be determined, this does not
+ // seem to actually be necessary for proper cursor rendering
+ }
+}
+
+void GfxCursor32::copy(DrawRegion &target, const DrawRegion &source) {
+ if (source.rect.isEmpty()) {
+ return;
+ }
+
+ Common::Rect drawRect(source.rect);
+ drawRect.clip(target.rect);
+ if (drawRect.isEmpty()) {
+ return;
+ }
+
+ const int16 sourceXOffset = drawRect.left - source.rect.left;
+ const int16 sourceYOffset = drawRect.top - source.rect.top;
+ const int16 drawWidth = drawRect.width();
+ const int16 drawHeight = drawRect.height();
+
+ byte *targetPixel = target.data + ((drawRect.top - target.rect.top) * target.rect.width()) + (drawRect.left - target.rect.left);
+ const byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset;
+
+ const int16 sourceStride = source.rect.width();
+ const int16 targetStride = target.rect.width();
+
+ for (int y = 0; y < drawHeight; ++y) {
+ memcpy(targetPixel, sourcePixel, drawWidth);
+ targetPixel += targetStride;
+ sourcePixel += sourceStride;
+ }
+}
+
+void GfxCursor32::setPosition(const Common::Point &position) {
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ _position.x = (position.x * Ratio(screenWidth, scriptWidth)).toInt();
+ _position.y = (position.y * Ratio(screenHeight, scriptHeight)).toInt();
+
+ g_system->warpMouse(_position.x, _position.y);
+}
+
+void GfxCursor32::gonnaPaint(Common::Rect paintRect) {
+ if (!_hideCount && !_writeToVMAP && !_cursorBack.rect.isEmpty()) {
+ paintRect.left &= ~3;
+ paintRect.right |= 3;
+ if (_cursorBack.rect.intersects(paintRect)) {
+ _writeToVMAP = true;
+ }
+ }
+}
+
+void GfxCursor32::paintStarting() {
+ if (_writeToVMAP) {
+ _savedVmapRegion.rect = _cursor.rect;
+ copy(_savedVmapRegion, _vmapRegion);
+ paint(_vmapRegion, _cursor);
+ }
+}
+
+void GfxCursor32::donePainting() {
+ if (_writeToVMAP) {
+ copy(_vmapRegion, _savedVmapRegion);
+ _savedVmapRegion.rect = Common::Rect();
+ _writeToVMAP = false;
+ }
+
+ if (!_hideCount && !_cursorBack.rect.isEmpty()) {
+ copy(_cursorBack, _vmapRegion);
+ }
+}
+
+void GfxCursor32::deviceMoved(Common::Point &position) {
+ if (position.x < _restrictedArea.left) {
+ position.x = _restrictedArea.left;
+ }
+ if (position.x >= _restrictedArea.right) {
+ position.x = _restrictedArea.right - 1;
+ }
+ if (position.y < _restrictedArea.top) {
+ position.y = _restrictedArea.top;
+ }
+ if (position.y >= _restrictedArea.bottom) {
+ position.y = _restrictedArea.bottom - 1;
+ }
+
+ _position = position;
+
+ g_system->warpMouse(position.x, position.y);
+ move();
+}
+
+void GfxCursor32::move() {
+ if (_hideCount) {
+ return;
+ }
+
+ // Cursor moved onto the screen after being offscreen
+ _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y);
+ if (_cursorBack.rect.isEmpty()) {
+ revealCursor();
+ return;
+ }
+
+ // Cursor moved offscreen
+ if (!_cursor.rect.intersects(_vmapRegion.rect)) {
+ drawToHardware(_cursorBack);
+ return;
+ }
+
+ if (!_cursor.rect.intersects(_cursorBack.rect)) {
+ // Cursor moved to a completely different part of the screen
+ _drawBuff1.rect = _cursor.rect;
+ _drawBuff1.rect.clip(_vmapRegion.rect);
+ readVideo(_drawBuff1);
+
+ _drawBuff2.rect = _drawBuff1.rect;
+ copy(_drawBuff2, _drawBuff1);
+
+ paint(_drawBuff1, _cursor);
+ drawToHardware(_drawBuff1);
+
+ drawToHardware(_cursorBack);
+
+ _cursorBack.rect = _cursor.rect;
+ _cursorBack.rect.clip(_vmapRegion.rect);
+ copy(_cursorBack, _drawBuff2);
+ } else {
+ // Cursor moved, but still overlaps the previous cursor location
+ Common::Rect mergedRect(_cursorBack.rect);
+ mergedRect.extend(_cursor.rect);
+ mergedRect.clip(_vmapRegion.rect);
+
+ _drawBuff2.rect = mergedRect;
+ readVideo(_drawBuff2);
+
+ copy(_drawBuff2, _cursorBack);
+
+ _cursorBack.rect = _cursor.rect;
+ _cursorBack.rect.clip(_vmapRegion.rect);
+ copy(_cursorBack, _drawBuff2);
+
+ paint(_drawBuff2, _cursor);
+ drawToHardware(_drawBuff2);
+ }
+}
+
+void GfxCursor32::setMacCursorRemapList(int cursorCount, reg_t *cursors) {
+ for (int i = 0; i < cursorCount; i++)
+ _macCursorRemap.push_back(cursors[i].toUint16());
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/cursor32.h b/engines/sci/graphics/cursor32.h
new file mode 100644
index 0000000000..00a8b9baa4
--- /dev/null
+++ b/engines/sci/graphics/cursor32.h
@@ -0,0 +1,255 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_CURSOR32_H
+#define SCI_GRAPHICS_CURSOR32_H
+
+#include "common/rect.h" // for Point, Rect
+#include "common/scummsys.h" // for int16, byte, uint8
+#include "common/serializer.h" // for Serializable, Serializer (ptr only)
+#include "sci/graphics/celobj32.h" // for CelInfo32
+#include "sci/graphics/helpers.h" // for GuiResourceId
+
+namespace Sci {
+
+class GfxCursor32 : public Common::Serializable {
+public:
+ GfxCursor32();
+ ~GfxCursor32();
+
+ /**
+ * Initialises the cursor system with the given
+ * buffer to use as the output buffer for
+ * rendering the cursor.
+ */
+ void init(const Buffer &vmap);
+
+ /**
+ * Called when the hardware mouse moves.
+ */
+ void deviceMoved(Common::Point &position);
+
+ /**
+ * Called by GfxFrameout once for each show
+ * rectangle that is going to be drawn to
+ * hardware.
+ */
+ void gonnaPaint(Common::Rect paintRect);
+
+ /**
+ * Called by GfxFrameout when the rendering to
+ * hardware begins.
+ */
+ void paintStarting();
+
+ /**
+ * Called by GfxFrameout when the output buffer
+ * has finished rendering to hardware.
+ */
+ void donePainting();
+
+ /**
+ * Hides the cursor. Each call to `hide` will
+ * increment a hide counter, which must be
+ * returned to 0 before the cursor will be
+ * shown again.
+ */
+ void hide();
+
+ /**
+ * Shows the cursor, if the hide counter is
+ * returned to 0.
+ */
+ void unhide();
+
+ /**
+ * Shows the cursor regardless of the state of
+ * the hide counter.
+ */
+ void show();
+
+ /**
+ * Sets the view used to render the cursor.
+ */
+ void setView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo);
+
+ /**
+ * Explicitly sets the position of the cursor,
+ * in game script coordinates.
+ */
+ void setPosition(const Common::Point &position);
+
+ /**
+ * Sets the region that the mouse is allowed
+ * to move within.
+ */
+ void setRestrictedArea(const Common::Rect &rect);
+
+ /**
+ * Removes restrictions on mouse movement.
+ */
+ void clearRestrictedArea();
+
+ void setMacCursorRemapList(int cursorCount, reg_t *cursors);
+
+ virtual void saveLoadWithSerializer(Common::Serializer &ser);
+
+private:
+ struct DrawRegion {
+ Common::Rect rect;
+ byte *data;
+ uint8 skipColor;
+
+ DrawRegion() : rect(), data(nullptr) {}
+ };
+
+ /**
+ * Information about the current cursor.
+ * Used to restore cursor when loading a
+ * savegame.
+ */
+ CelInfo32 _cursorInfo;
+
+ /**
+ * Content behind the cursor? TODO
+ */
+ DrawRegion _cursorBack;
+
+ /**
+ * Scratch buffer.
+ */
+ DrawRegion _drawBuff1;
+
+ /**
+ * Scratch buffer 2.
+ */
+ DrawRegion _drawBuff2;
+
+ /**
+ * A draw region representing the current
+ * output buffer.
+ */
+ DrawRegion _vmapRegion;
+
+ /**
+ * The content behind the cursor in the
+ * output buffer.
+ */
+ DrawRegion _savedVmapRegion;
+
+ /**
+ * The cursor bitmap.
+ */
+ DrawRegion _cursor;
+
+ /**
+ * The width and height of the cursor,
+ * in screen coordinates.
+ */
+ int16 _width, _height;
+
+ /**
+ * The output buffer where the cursor is
+ * rendered.
+ */
+ Buffer _vmap;
+
+ /**
+ * The number of times the cursor has been
+ * hidden.
+ */
+ int _hideCount;
+
+ /**
+ * The rendered position of the cursor, in
+ * screen coordinates.
+ */
+ Common::Point _position;
+
+ /**
+ * The position of the cursor hot spot, relative
+ * to the cursor origin, in screen pixels.
+ */
+ Common::Point _hotSpot;
+
+ /**
+ * The area within which the cursor is allowed
+ * to move, in screen pixels.
+ */
+ Common::Rect _restrictedArea;
+
+ /**
+ * Indicates whether or not the cursor needs to
+ * be repainted on the output buffer due to a
+ * change of graphics in the area underneath the
+ * cursor.
+ */
+ bool _writeToVMAP;
+
+ // Mac versions of games use a remap list to remap their cursors
+ Common::Array<uint16> _macCursorRemap;
+
+ /**
+ * Reads data from the output buffer or hardware
+ * to the given draw region.
+ */
+ void readVideo(DrawRegion &target);
+
+ /**
+ * Reads data from the output buffer to the
+ * given draw region.
+ */
+ void readVideoFromVmap(DrawRegion &target);
+
+ /**
+ * Copies pixel data from the given source to
+ * the given target.
+ */
+ void copy(DrawRegion &target, const DrawRegion &source);
+
+ /**
+ * Draws from the given source onto the given
+ * target, skipping pixels in the source that
+ * match the `skipColor` property.
+ */
+ void paint(DrawRegion &target, const DrawRegion &source);
+
+ /**
+ * Draws the cursor to the position it was
+ * drawn to prior to moving offscreen or being
+ * hidden by a call to `hide`.
+ */
+ void revealCursor();
+
+ /**
+ * Draws the given source to the output buffer.
+ */
+ void drawToHardware(const DrawRegion &source);
+
+ /**
+ * Renders the cursor at its new location.
+ */
+ void move();
+};
+
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/graphics/font.h b/engines/sci/graphics/font.h
index 451261f315..b79fb2f0ba 100644
--- a/engines/sci/graphics/font.h
+++ b/engines/sci/graphics/font.h
@@ -75,7 +75,7 @@ private:
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 ccce8ef046..fe43c75e5a 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -29,112 +29,82 @@
#include "common/system.h"
#include "common/textconsole.h"
#include "engines/engine.h"
+#include "engines/util.h"
#include "graphics/palette.h"
#include "graphics/surface.h"
#include "sci/sci.h"
#include "sci/console.h"
+#include "sci/engine/features.h"
#include "sci/engine/kernel.h"
#include "sci/engine/state.h"
#include "sci/engine/selector.h"
#include "sci/engine/vm.h"
#include "sci/graphics/cache.h"
-#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/compare.h"
+#include "sci/graphics/cursor32.h"
#include "sci/graphics/font.h"
-#include "sci/graphics/view.h"
-#include "sci/graphics/screen.h"
+#include "sci/graphics/frameout.h"
#include "sci/graphics/paint32.h"
#include "sci/graphics/palette32.h"
-#include "sci/graphics/picture.h"
-#include "sci/graphics/text32.h"
#include "sci/graphics/plane32.h"
+#include "sci/graphics/remap32.h"
+#include "sci/graphics/screen.h"
#include "sci/graphics/screen_item32.h"
+#include "sci/graphics/text32.h"
#include "sci/graphics/frameout.h"
-#include "sci/video/robot_decoder.h"
+#include "sci/graphics/transitions32.h"
+#include "sci/graphics/video32.h"
namespace Sci {
-// TODO/FIXME: This is partially guesswork
-
-static int dissolveSequences[2][20] = {
- /* SCI2.1early- */ { 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080 },
- /* SCI2.1mid+ */ { 0, 0, 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080, 73728, 132096, 466944 }
-};
-static int16 divisionsDefaults[2][16] = {
- /* SCI2.1early- */ { 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 40, 40, 101, 101 },
- /* SCI2.1mid+ */ { 1, 20, 20, 20, 20, 10, 10, 10, 10, 20, 20, 6, 10, 101, 101, 2 }
-};
-static int16 unknownCDefaults[2][16] = {
- /* SCI2.1early- */ { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 0, 0, 0, 0 },
- /* SCI2.1mid+ */ { 0, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 0, 0, 7, 7, 0 }
-};
-
-GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) :
- _isHiRes(false),
- _cache(cache),
+GfxFrameout::GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor) :
+ _isHiRes(gameIsHiRes()),
_palette(palette),
- _resMan(resMan),
- _screen(screen),
+ _cursor(cursor),
_segMan(segMan),
- _paint32(paint32),
- _showStyles(nullptr),
- // TODO: Stop using _gfxScreen
- _currentBuffer(screen->getDisplayWidth(), screen->getDisplayHeight(), nullptr),
- _priorityMap(screen->getDisplayWidth(), screen->getDisplayHeight(), nullptr),
+ _transitions(transitions),
+ _benchmarkingFinished(false),
+ _throttleFrameOut(true),
+ _throttleState(0),
_remapOccurred(false),
_frameNowVisible(false),
- _screenRect(screen->getDisplayWidth(), screen->getDisplayHeight()),
_overdrawThreshold(0),
_palMorphIsOn(false) {
- _currentBuffer.setPixels(calloc(1, screen->getDisplayWidth() * screen->getDisplayHeight()));
-
- for (int i = 0; i < 236; i += 2) {
- _styleRanges[i] = 0;
- _styleRanges[i + 1] = -1;
- }
- for (int i = 236; i < ARRAYSIZE(_styleRanges); ++i) {
- _styleRanges[i] = 0;
- }
-
- // TODO: Make hires detection work uniformly across all SCI engine
- // versions (this flag is normally passed by SCI::MakeGraphicsMgr
- // to the GraphicsMgr constructor depending upon video configuration,
- // so should be handled upstream based on game configuration instead
- // of here)
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _resMan->detectHires()) {
- _isHiRes = true;
- }
-
- if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
- _dissolveSequenceSeeds = dissolveSequences[0];
- _defaultDivisions = divisionsDefaults[0];
- _defaultUnknownC = unknownCDefaults[0];
+ if (g_sci->getGameId() == GID_PHANTASMAGORIA) {
+ _currentBuffer = Buffer(630, 450, nullptr);
+ } else if (_isHiRes) {
+ _currentBuffer = Buffer(640, 480, nullptr);
} else {
- _dissolveSequenceSeeds = dissolveSequences[1];
- _defaultDivisions = divisionsDefaults[1];
- _defaultUnknownC = unknownCDefaults[1];
- }
-
- // TODO: Nothing in the renderer really uses this. Currently,
- // the cursor renderer does, and kLocalToGlobal/kGlobalToLocal
- // do, but in the real engine (1) the cursor is handled in
- // frameOut, and (2) functions do a very simple lookup of the
- // plane and arithmetic with the plane's gameRect. In
- // principle, CoordAdjuster could be reused for
- // convertGameRectToPlaneRect, but it is not super clear yet
- // what the benefit would be to do that.
- _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster;
-
- // TODO: Script resolution is hard-coded per game;
- // also this must be set or else the engine will crash
- _coordAdjuster->setScriptsResolution(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight);
+ _currentBuffer = Buffer(320, 200, nullptr);
+ }
+ _currentBuffer.setPixels(calloc(1, _currentBuffer.screenWidth * _currentBuffer.screenHeight));
+ _screenRect = Common::Rect(_currentBuffer.screenWidth, _currentBuffer.screenHeight);
+ initGraphics(_currentBuffer.screenWidth, _currentBuffer.screenHeight, _isHiRes);
+
+ switch (g_sci->getGameId()) {
+ case GID_HOYLE5:
+ case GID_GK2:
+ case GID_LIGHTHOUSE:
+ case GID_LSL7:
+ case GID_PHANTASMAGORIA2:
+ case GID_PQSWAT:
+ case GID_TORIN:
+ case GID_RAMA:
+ _currentBuffer.scriptWidth = 640;
+ _currentBuffer.scriptHeight = 480;
+ break;
+ default:
+ // default script width for other games is 320x200
+ break;
+ }
}
GfxFrameout::~GfxFrameout() {
- CelObj::deinit();
clear();
+ CelObj::deinit();
+ free(_currentBuffer.getPixels());
}
void GfxFrameout::run() {
@@ -172,7 +142,7 @@ void GfxFrameout::syncWithScripts(bool addElements) {
return;
// Get planes list object
- reg_t planesListObject = engineState->variables[VAR_GLOBAL][10];
+ reg_t planesListObject = engineState->variables[VAR_GLOBAL][kGlobalVarPlanes];
reg_t planesListElements = readSelector(segMan, planesListObject, SELECTOR(elements));
List *planesList = segMan->lookupList(planesListElements);
@@ -239,22 +209,115 @@ void GfxFrameout::syncWithScripts(bool addElements) {
}
}
+bool GfxFrameout::gameIsHiRes() const {
+ // QFG4 is always low resolution
+ if (g_sci->getGameId() == GID_QFG4) {
+ return false;
+ }
+
+ // GK1 DOS floppy is low resolution only, but GK1 Mac floppy is high
+ // resolution only
+ if (g_sci->getGameId() == GID_GK1 &&
+ !g_sci->isCD() &&
+ g_sci->getPlatform() != Common::kPlatformMacintosh) {
+
+ return false;
+ }
+
+ // All other games are either high resolution by default, or have a
+ // user-defined toggle
+ return ConfMan.getBool("enable_high_resolution_graphics");
+}
+
+#pragma mark -
+#pragma mark Benchmarking
+
+bool GfxFrameout::checkForFred(const reg_t object) {
+ const int16 viewId = readSelectorValue(_segMan, object, SELECTOR(view));
+ const SciGameId gameId = g_sci->getGameId();
+
+ if (gameId == GID_QFG4 && viewId == 9999) {
+ return true;
+ }
+
+ if (gameId != GID_QFG4 && viewId == -556) {
+ return true;
+ }
+
+ if (Common::String(_segMan->getObjectName(object)) == "fred") {
+ return true;
+ }
+
+ return false;
+}
+
#pragma mark -
#pragma mark Screen items
+void GfxFrameout::addScreenItem(ScreenItem &screenItem) const {
+ Plane *plane = _planes.findByObject(screenItem._plane);
+ if (plane == nullptr) {
+ error("GfxFrameout::addScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
+ }
+ plane->_screenItemList.add(&screenItem);
+}
+
+void GfxFrameout::updateScreenItem(ScreenItem &screenItem) const {
+ // TODO: In SCI3+ this will need to go through Plane
+// Plane *plane = _planes.findByObject(screenItem._plane);
+// if (plane == nullptr) {
+// error("GfxFrameout::updateScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
+// }
+
+ screenItem.update();
+}
+
+void GfxFrameout::deleteScreenItem(ScreenItem &screenItem) {
+ Plane *plane = _planes.findByObject(screenItem._plane);
+ if (plane == nullptr) {
+ error("GfxFrameout::deleteScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
+ }
+ if (plane->_screenItemList.findByObject(screenItem._object) == nullptr) {
+ error("GfxFrameout::deleteScreenItem: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(screenItem._object), PRINT_REG(screenItem._plane));
+ }
+ deleteScreenItem(screenItem, *plane);
+}
+
+void GfxFrameout::deleteScreenItem(ScreenItem &screenItem, Plane &plane) {
+ if (screenItem._created == 0) {
+ screenItem._created = 0;
+ screenItem._updated = 0;
+ screenItem._deleted = getScreenCount();
+ } else {
+ plane._screenItemList.erase(&screenItem);
+ plane._screenItemList.pack();
+ }
+}
+
+void GfxFrameout::deleteScreenItem(ScreenItem &screenItem, const reg_t planeObject) {
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("GfxFrameout::deleteScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(screenItem._object));
+ }
+ deleteScreenItem(screenItem, *plane);
+}
+
void GfxFrameout::kernelAddScreenItem(const reg_t object) {
- const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+ // The "fred" object is used to test graphics performance;
+ // it is impacted by framerate throttling, so disable the
+ // throttling when this item is on the screen for the
+ // performance check to pass.
+ if (!_benchmarkingFinished && _throttleFrameOut && checkForFred(object)) {
+ _throttleFrameOut = false;
+ }
-// TODO: Remove
-// debug("Adding screen item %04x:%04x to plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject));
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
_segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewInserted);
Plane *plane = _planes.findByObject(planeObject);
if (plane == nullptr) {
- warning("screen item %x:%x (%s)", object.getSegment(), object.getOffset(), g_sci->getEngineState()->_segMan->getObjectName(object));
- warning("plane %x:%x (%s)", planeObject.getSegment(), planeObject.getOffset(), g_sci->getEngineState()->_segMan->getObjectName(planeObject));
- error("Invalid plane selector passed to kAddScreenItem");
+ error("kAddScreenItem: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(object));
}
ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
@@ -272,53 +335,44 @@ void GfxFrameout::kernelUpdateScreenItem(const reg_t object) {
const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
Plane *plane = _planes.findByObject(planeObject);
if (plane == nullptr) {
- warning("screen item %x:%x (%s)", object.getSegment(), object.getOffset(), g_sci->getEngineState()->_segMan->getObjectName(object));
- warning("plane %x:%x (%s)", planeObject.getSegment(), planeObject.getOffset(), g_sci->getEngineState()->_segMan->getObjectName(planeObject));
- error("Invalid plane selector passed to kUpdateScreenItem");
+ error("kUpdateScreenItem: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(object));
}
ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
if (screenItem == nullptr) {
- warning("screen item %x:%x (%s)", object.getSegment(), object.getOffset(), g_sci->getEngineState()->_segMan->getObjectName(object));
- warning("plane %x:%x (%s)", planeObject.getSegment(), planeObject.getOffset(), g_sci->getEngineState()->_segMan->getObjectName(planeObject));
- error("Invalid screen item passed to kUpdateScreenItem");
+ error("kUpdateScreenItem: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject));
}
screenItem->update(object);
} else {
- warning("TODO: Magnifier view not implemented yet!");
+ error("Magnifier view is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
}
}
void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {
+ // The "fred" object is used to test graphics performance;
+ // it is impacted by framerate throttling, so disable the
+ // throttling when this item is on the screen for the
+ // performance check to pass.
+ if (!_benchmarkingFinished && checkForFred(object)) {
+ _benchmarkingFinished = true;
+ _throttleFrameOut = true;
+ }
+
_segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewInserted);
const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
Plane *plane = _planes.findByObject(planeObject);
if (plane == nullptr) {
- // TODO: Remove
-// warning("Invalid plane selector %04x:%04x passed to kDeleteScreenItem (real engine ignores this)", PRINT_REG(object));
return;
}
-// TODO: Remove
-// debug("Deleting screen item %04x:%04x from plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject));
-
ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
if (screenItem == nullptr) {
-// TODO: Remove
-// warning("Invalid screen item %04x:%04x passed to kDeleteScreenItem (real engine ignores this)", PRINT_REG(object));
return;
}
- if (screenItem->_created == 0) {
- screenItem->_created = 0;
- screenItem->_updated = 0;
- screenItem->_deleted = getScreenCount();
- } else {
- plane->_screenItemList.erase(screenItem);
- plane->_screenItemList.pack();
- }
+ deleteScreenItem(*screenItem, *plane);
}
#pragma mark -
@@ -338,8 +392,7 @@ void GfxFrameout::kernelAddPlane(const reg_t object) {
void GfxFrameout::kernelUpdatePlane(const reg_t object) {
Plane *plane = _planes.findByObject(object);
if (plane == nullptr) {
- warning("plane %x:%x (%s)", object.getSegment(), object.getOffset(), g_sci->getEngineState()->_segMan->getObjectName(object));
- error("Invalid plane selector passed to kUpdatePlane");
+ error("kUpdatePlane: Plane %04x:%04x not found", PRINT_REG(object));
}
plane->update(object);
@@ -349,8 +402,7 @@ void GfxFrameout::kernelUpdatePlane(const reg_t object) {
void GfxFrameout::kernelDeletePlane(const reg_t object) {
Plane *plane = _planes.findByObject(object);
if (plane == nullptr) {
- warning("plane %x:%x (%s)", object.getSegment(), object.getOffset(), g_sci->getEngineState()->_segMan->getObjectName(object));
- error("Invalid plane selector passed to kDeletePlane");
+ error("kDeletePlane: Plane %04x:%04x not found", PRINT_REG(object));
}
if (plane->_created) {
@@ -358,13 +410,53 @@ void GfxFrameout::kernelDeletePlane(const reg_t object) {
// just ends up doing this anyway so we skip the extra indirection
_planes.erase(plane);
} else {
- // TODO: Remove
-// debug("Deleting plane %04x:%04x", PRINT_REG(object));
plane->_created = 0;
plane->_deleted = g_sci->_gfxFrameout->getScreenCount();
}
}
+void GfxFrameout::deletePlane(Plane &planeToFind) {
+ Plane *plane = _planes.findByObject(planeToFind._object);
+ if (plane == nullptr) {
+ error("deletePlane: Plane %04x:%04x not found", PRINT_REG(planeToFind._object));
+ }
+
+ if (plane->_created) {
+ _planes.erase(plane);
+ } else {
+ plane->_created = 0;
+ plane->_moved = 0;
+ plane->_deleted = getScreenCount();
+ }
+}
+
+void GfxFrameout::kernelMovePlaneItems(const reg_t object, const int16 deltaX, const int16 deltaY, const bool scrollPics) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane == nullptr) {
+ error("kMovePlaneItems: Plane %04x:%04x not found", PRINT_REG(object));
+ }
+
+ plane->scrollScreenItems(deltaX, deltaY, scrollPics);
+
+ for (ScreenItemList::iterator it = plane->_screenItemList.begin(); it != plane->_screenItemList.end(); ++it) {
+ ScreenItem &screenItem = **it;
+
+ // If object is a number, the screen item from the
+ // engine, not a script, and should be ignored
+ if (screenItem._object.isNumber()) {
+ continue;
+ }
+
+ if (deltaX != 0) {
+ writeSelectorValue(_segMan, screenItem._object, SELECTOR(x), readSelectorValue(_segMan, screenItem._object, SELECTOR(x)) + deltaX);
+ }
+
+ if (deltaY != 0) {
+ writeSelectorValue(_segMan, screenItem._object, SELECTOR(y), readSelectorValue(_segMan, screenItem._object, SELECTOR(y)) + deltaY);
+ }
+ }
+}
+
int16 GfxFrameout::kernelGetHighPlanePri() {
return _planes.getTopSciPlanePriority();
}
@@ -398,23 +490,24 @@ void GfxFrameout::updatePlane(Plane &plane) {
#pragma mark -
#pragma mark Pics
-void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 x, const int16 y, const bool mirrorX) {
+void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 x, const int16 y, const bool mirrorX, const bool deleteDuplicate) {
Plane *plane = _planes.findByObject(planeObject);
if (plane == nullptr) {
- warning("plane %x:%x (%s)", planeObject.getSegment(), planeObject.getOffset(), g_sci->getEngineState()->_segMan->getObjectName(planeObject));
- error("Invalid plane selector passed to kAddPicAt");
+ error("kAddPicAt: Plane %04x:%04x not found", PRINT_REG(planeObject));
}
- plane->addPic(pictureId, Common::Point(x, y), mirrorX);
+ plane->addPic(pictureId, Common::Point(x, y), mirrorX, deleteDuplicate);
}
#pragma mark -
#pragma mark Rendering
-void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) {
-// TODO: Robot
-// if (_robot != nullptr) {
-// _robot.doRobot();
-// }
+void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseRect) {
+ RobotDecoder &robotPlayer = g_sci->_video32->getRobotPlayer();
+ const bool robotIsActive = robotPlayer.getStatus() != RobotDecoder::kRobotStatusUninitialized;
+
+ if (robotIsActive) {
+ robotPlayer.doRobot();
+ }
// NOTE: The original engine allocated these as static arrays of 100
// pointers to ScreenItemList / RectList
@@ -424,12 +517,11 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect)
screenItemLists.resize(_planes.size());
eraseLists.resize(_planes.size());
- // _numActiveRemaps was a global in SCI engine
- if (/* TODO Remap::_numActiveRemaps > 0 */ false && _remapOccurred) {
- // remapMarkRedraw();
+ if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
+ remapMarkRedraw();
}
- calcLists(screenItemLists, eraseLists, rect);
+ calcLists(screenItemLists, eraseLists, eraseRect);
for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
list->sort();
@@ -453,12 +545,11 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect)
drawScreenItemList(screenItemLists[i]);
}
-// TODO: Robot
-// if (_robot != nullptr) {
-// _robot->frameAlmostVisible();
-// }
+ if (robotIsActive) {
+ robotPlayer.frameAlmostVisible();
+ }
- _palette->updateHardware();
+ _palette->updateHardware(!shouldShowBits);
if (shouldShowBits) {
showBits();
@@ -466,136 +557,335 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect)
_frameNowVisible = true;
-// TODO: Robot
-// if (_robot != nullptr) {
-// robot->frameNowVisible();
-// }
+ if (robotIsActive) {
+ robotPlayer.frameNowVisible();
+ }
+}
+
+void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *showStyle) {
+ Palette sourcePalette(_palette->getNextPalette());
+ alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
+
+ int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][kGlobalVarPreviousRoomNo].toSint16();
+
+ Common::Rect rect(_currentBuffer.screenWidth, _currentBuffer.screenHeight);
+ _showList.add(rect);
+ showBits();
+
+ // NOTE: The original engine allocated these as static arrays of 100
+ // pointers to ScreenItemList / RectList
+ ScreenItemListList screenItemLists;
+ EraseListList eraseLists;
+
+ screenItemLists.resize(_planes.size());
+ eraseLists.resize(_planes.size());
+
+ if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
+ remapMarkRedraw();
+ }
+
+ calcLists(screenItemLists, eraseLists);
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ list->sort();
+ }
+
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+ (*drawItem)->screenItem->getCelObj().submitPalette();
+ }
+ }
+
+ _remapOccurred = _palette->updateForFrame();
+ _frameNowVisible = false;
+
+ for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+ drawEraseList(eraseLists[i], *_planes[i]);
+ drawScreenItemList(screenItemLists[i]);
+ }
+
+ Palette nextPalette(_palette->getNextPalette());
+
+ if (prevRoom < 1000) {
+ for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
+ if (styleRanges[i] == -1 || styleRanges[i] == 0) {
+ sourcePalette.colors[i] = nextPalette.colors[i];
+ sourcePalette.colors[i].used = true;
+ }
+ }
+ } else {
+ for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
+ if (styleRanges[i] == -1 || validZeroStyle(styleRanges[i], i)) {
+ sourcePalette.colors[i] = nextPalette.colors[i];
+ sourcePalette.colors[i].used = true;
+ }
+ }
+ }
+
+ _palette->submit(sourcePalette);
+ _palette->updateFFrame();
+ _palette->updateHardware();
+ alterVmap(nextPalette, sourcePalette, 1, _transitions->_styleRanges);
+
+ if (showStyle && showStyle->type != kShowStyleMorph) {
+ _transitions->processEffects(*showStyle);
+ } else {
+ showBits();
+ }
+
+ _frameNowVisible = true;
+
+ for (PlaneList::iterator plane = _planes.begin(); plane != _planes.end(); ++plane) {
+ (*plane)->_redrawAllCount = getScreenCount();
+ }
+
+ if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
+ remapMarkRedraw();
+ }
+
+ calcLists(screenItemLists, eraseLists);
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ list->sort();
+ }
+
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+ (*drawItem)->screenItem->getCelObj().submitPalette();
+ }
+ }
+
+ _remapOccurred = _palette->updateForFrame();
+ // NOTE: During this second loop, `_frameNowVisible = false` is
+ // inside the next loop in SCI2.1mid
+ _frameNowVisible = false;
+
+ for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+ drawEraseList(eraseLists[i], *_planes[i]);
+ drawScreenItemList(screenItemLists[i]);
+ }
+
+ _palette->submit(nextPalette);
+ _palette->updateFFrame();
+ _palette->updateHardware(false);
+ showBits();
+
+ _frameNowVisible = true;
}
-// Determine the parts of 'r' that aren't overlapped by 'other'.
-// Returns -1 if r and other have no intersection.
-// Returns number of returned parts (in outRects) otherwise.
-// (In particular, this returns 0 if r is contained in other.)
+/**
+ * Determines the parts of `r` that aren't overlapped by `other`.
+ * Returns -1 if `r` and `other` have no intersection.
+ * Returns number of returned parts (in `outRects`) otherwise.
+ * (In particular, this returns 0 if `r` is contained in `other`.)
+ */
int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]) {
if (!r.intersects(other)) {
return -1;
}
- int count = 0;
+ int splitCount = 0;
if (r.top < other.top) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.bottom = other.top;
r.top = other.top;
}
if (r.bottom > other.bottom) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.top = other.bottom;
r.bottom = other.bottom;
}
if (r.left < other.left) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.right = other.left;
r.left = other.left;
}
if (r.right > other.right) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.left = other.right;
}
- return count;
+ return splitCount;
}
-void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect) {
- RectList rectlist;
- Common::Rect outRects[4];
+/**
+ * Determines the parts of `middleRect` that aren't overlapped
+ * by `showRect`, optimised for contiguous memory writes.
+ * Returns -1 if `middleRect` and `showRect` have no intersection.
+ * Returns number of returned parts (in `outRects`) otherwise.
+ * (In particular, this returns 0 if `middleRect` is contained
+ * in `other`.)
+ *
+ * `middleRect` is modified directly to extend into the upper
+ * and lower rects.
+ */
+int splitRectsForRender(Common::Rect &middleRect, const Common::Rect &showRect, Common::Rect(&outRects)[2]) {
+ if (!middleRect.intersects(showRect)) {
+ return -1;
+ }
+
+ const int16 minLeft = MIN(middleRect.left, showRect.left);
+ const int16 maxRight = MAX(middleRect.right, showRect.right);
+
+ int16 upperLeft, upperTop, upperRight, upperMaxTop;
+ if (middleRect.top < showRect.top) {
+ upperLeft = middleRect.left;
+ upperTop = middleRect.top;
+ upperRight = middleRect.right;
+ upperMaxTop = showRect.top;
+ }
+ else {
+ upperLeft = showRect.left;
+ upperTop = showRect.top;
+ upperRight = showRect.right;
+ upperMaxTop = middleRect.top;
+ }
+
+ int16 lowerLeft, lowerRight, lowerBottom, lowerMinBottom;
+ if (middleRect.bottom > showRect.bottom) {
+ lowerLeft = middleRect.left;
+ lowerRight = middleRect.right;
+ lowerBottom = middleRect.bottom;
+ lowerMinBottom = showRect.bottom;
+ } else {
+ lowerLeft = showRect.left;
+ lowerRight = showRect.right;
+ lowerBottom = showRect.bottom;
+ lowerMinBottom = middleRect.bottom;
+ }
+
+ int splitCount = 0;
+ middleRect.left = minLeft;
+ middleRect.top = upperMaxTop;
+ middleRect.right = maxRight;
+ middleRect.bottom = lowerMinBottom;
+
+ if (upperTop != upperMaxTop) {
+ Common::Rect &upperRect = outRects[0];
+ upperRect.left = upperLeft;
+ upperRect.top = upperTop;
+ upperRect.right = upperRight;
+ upperRect.bottom = upperMaxTop;
+
+ // Merge upper rect into middle rect if possible
+ if (upperRect.left == middleRect.left && upperRect.right == middleRect.right) {
+ middleRect.top = upperRect.top;
+ } else {
+ ++splitCount;
+ }
+ }
+
+ if (lowerBottom != lowerMinBottom) {
+ Common::Rect &lowerRect = outRects[splitCount];
+ lowerRect.left = lowerLeft;
+ lowerRect.top = lowerMinBottom;
+ lowerRect.right = lowerRight;
+ lowerRect.bottom = lowerBottom;
+
+ // Merge lower rect into middle rect if possible
+ if (lowerRect.left == middleRect.left && lowerRect.right == middleRect.right) {
+ middleRect.bottom = lowerRect.bottom;
+ } else {
+ ++splitCount;
+ }
+ }
+
+ assert(splitCount <= 2);
+ return splitCount;
+}
+// NOTE: The third rectangle parameter is only ever given a non-empty rect
+// by VMD code, via `frameOut`
+void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect) {
+ RectList eraseList;
+ Common::Rect outRects[4];
int deletedPlaneCount = 0;
- bool addedToRectList = false;
- int planeCount = _planes.size();
+ bool addedToEraseList = false;
bool foundTransparentPlane = false;
- if (!calcRect.isEmpty()) {
- addedToRectList = true;
- rectlist.add(calcRect);
+ if (!eraseRect.isEmpty()) {
+ addedToEraseList = true;
+ eraseList.add(eraseRect);
}
- for (int outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) {
- Plane *outerPlane = _planes[outerPlaneIndex];
+ PlaneList::size_type planeCount = _planes.size();
+ for (PlaneList::size_type outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) {
+ const Plane *outerPlane = _planes[outerPlaneIndex];
+ const Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object);
+ // NOTE: SSCI only ever checks for kPlaneTypeTransparent here, even
+ // though kPlaneTypeTransparentPicture is also a transparent plane
if (outerPlane->_type == kPlaneTypeTransparent) {
foundTransparentPlane = true;
}
- Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object);
-
if (outerPlane->_deleted) {
- if (visiblePlane != nullptr) {
- if (!visiblePlane->_screenRect.isEmpty()) {
- addedToRectList = true;
- rectlist.add(visiblePlane->_screenRect);
- }
+ if (visiblePlane != nullptr && !visiblePlane->_screenRect.isEmpty()) {
+ eraseList.add(visiblePlane->_screenRect);
+ addedToEraseList = true;
}
++deletedPlaneCount;
- } else if (visiblePlane != nullptr) {
- if (outerPlane->_updated) {
- --outerPlane->_updated;
-
- int splitcount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects);
- if (splitcount) {
- if (splitcount == -1) {
- if (!visiblePlane->_screenRect.isEmpty()) {
- rectlist.add(visiblePlane->_screenRect);
- }
+ } else if (visiblePlane != nullptr && outerPlane->_moved) {
+ // _moved will be decremented in the final loop through the planes,
+ // at the end of this function
+
+ {
+ const int splitCount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects);
+ if (splitCount) {
+ if (splitCount == -1 && !visiblePlane->_screenRect.isEmpty()) {
+ eraseList.add(visiblePlane->_screenRect);
} else {
- for (int i = 0; i < splitcount; ++i) {
- rectlist.add(outRects[i]);
+ for (int i = 0; i < splitCount; ++i) {
+ eraseList.add(outRects[i]);
}
}
-
- addedToRectList = true;
+ addedToEraseList = true;
}
+ }
- if (!outerPlane->_redrawAllCount) {
- int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects);
- if (splitCount) {
- for (int i = 0; i < splitCount; ++i) {
- rectlist.add(outRects[i]);
- }
- addedToRectList = true;
+ if (!outerPlane->_redrawAllCount) {
+ const int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects);
+ if (splitCount) {
+ for (int i = 0; i < splitCount; ++i) {
+ eraseList.add(outRects[i]);
}
+ addedToEraseList = true;
}
}
}
- if (addedToRectList) {
- for (RectList::iterator rect = rectlist.begin(); rect != rectlist.end(); ++rect) {
- for (int innerPlaneIndex = _planes.size() - 1; innerPlaneIndex >= 0; --innerPlaneIndex) {
- Plane *innerPlane = _planes[innerPlaneIndex];
-
- if (!innerPlane->_deleted && innerPlane->_type != kPlaneTypeTransparent && innerPlane->_screenRect.intersects(**rect)) {
- if (innerPlane->_redrawAllCount == 0) {
- eraseLists[innerPlaneIndex].add(innerPlane->_screenRect.findIntersectingRect(**rect));
+ if (addedToEraseList) {
+ for (RectList::size_type rectIndex = 0; rectIndex < eraseList.size(); ++rectIndex) {
+ const Common::Rect &rect = *eraseList[rectIndex];
+ for (int innerPlaneIndex = planeCount - 1; innerPlaneIndex >= 0; --innerPlaneIndex) {
+ const Plane &innerPlane = *_planes[innerPlaneIndex];
+
+ if (
+ !innerPlane._deleted &&
+ innerPlane._type != kPlaneTypeTransparent &&
+ innerPlane._screenRect.intersects(rect)
+ ) {
+ if (!innerPlane._redrawAllCount) {
+ eraseLists[innerPlaneIndex].add(innerPlane._screenRect.findIntersectingRect(rect));
}
- int splitCount = splitRects(**rect, innerPlane->_screenRect, outRects);
+ const int splitCount = splitRects(rect, innerPlane._screenRect, outRects);
for (int i = 0; i < splitCount; ++i) {
- rectlist.add(outRects[i]);
+ eraseList.add(outRects[i]);
}
- rectlist.erase(rect);
+ eraseList.erase_at(rectIndex);
break;
}
}
}
- rectlist.pack();
+ eraseList.pack();
}
}
@@ -607,9 +897,9 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL
if (plane->_deleted) {
--plane->_deleted;
if (plane->_deleted <= 0) {
- PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
- if (visiblePlaneIt != _visiblePlanes.end()) {
- _visiblePlanes.erase(visiblePlaneIt);
+ const int visiblePlaneIndex = _visiblePlanes.findIndexByObject(plane->_object);
+ if (visiblePlaneIndex != -1) {
+ _visiblePlanes.remove_at(visiblePlaneIndex);
}
_planes.remove_at(planeIndex);
@@ -624,103 +914,118 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL
}
}
+ // Some planes may have been deleted, so re-retrieve count
planeCount = _planes.size();
- for (int outerIndex = 0; outerIndex < planeCount; ++outerIndex) {
- // "outer" just refers to the outer loop
- Plane *outerPlane = _planes[outerIndex];
- if (outerPlane->_priorityChanged) {
- --outerPlane->_priorityChanged;
- Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane->_object);
+ for (PlaneList::size_type outerIndex = 0; outerIndex < planeCount; ++outerIndex) {
+ // "outer" just refers to the outer loop
+ Plane &outerPlane = *_planes[outerIndex];
+ if (outerPlane._priorityChanged) {
+ --outerPlane._priorityChanged;
+
+ const Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane._object);
+ if (visibleOuterPlane == nullptr) {
+ warning("calcLists could not find visible plane for %04x:%04x", PRINT_REG(outerPlane._object));
+ continue;
+ }
- rectlist.add(outerPlane->_screenRect.findIntersectingRect(visibleOuterPlane->_screenRect));
+ eraseList.add(outerPlane._screenRect.findIntersectingRect(visibleOuterPlane->_screenRect));
- for (int innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) {
+ for (int innerIndex = (int)planeCount - 1; innerIndex >= 0; --innerIndex) {
// "inner" just refers to the inner loop
- Plane *innerPlane = _planes[innerIndex];
- Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane->_object);
-
- int rectCount = rectlist.size();
- for (int rectIndex = 0; rectIndex < rectCount; ++rectIndex) {
- int splitCount = splitRects(*rectlist[rectIndex], _planes[innerIndex]->_screenRect, outRects);
+ const Plane &innerPlane = *_planes[innerIndex];
+ const Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane._object);
+ const RectList::size_type rectCount = eraseList.size();
+ for (RectList::size_type rectIndex = 0; rectIndex < rectCount; ++rectIndex) {
+ const int splitCount = splitRects(*eraseList[rectIndex], innerPlane._screenRect, outRects);
if (splitCount == 0) {
if (visibleInnerPlane != nullptr) {
// same priority, or relative priority between inner/outer changed
- if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) {
- if (outerPlane->_priority <= innerPlane->_priority) {
- eraseLists[innerIndex].add(*rectlist[rectIndex]);
+ if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) {
+ if (outerPlane._priority <= innerPlane._priority) {
+ eraseLists[innerIndex].add(*eraseList[rectIndex]);
} else {
- eraseLists[outerIndex].add(*rectlist[rectIndex]);
+ eraseLists[outerIndex].add(*eraseList[rectIndex]);
}
}
}
- rectlist.erase_at(rectIndex);
+ eraseList.erase_at(rectIndex);
} else if (splitCount != -1) {
for (int i = 0; i < splitCount; ++i) {
- rectlist.add(outRects[i]);
+ eraseList.add(outRects[i]);
}
if (visibleInnerPlane != nullptr) {
// same priority, or relative priority between inner/outer changed
- if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) {
- *rectlist[rectIndex] = outerPlane->_screenRect.findIntersectingRect(innerPlane->_screenRect);
- if (outerPlane->_priority <= innerPlane->_priority) {
- eraseLists[innerIndex].add(*rectlist[rectIndex]);
- }
- else {
- eraseLists[outerIndex].add(*rectlist[rectIndex]);
+ if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) {
+ *eraseList[rectIndex] = outerPlane._screenRect.findIntersectingRect(innerPlane._screenRect);
+
+ if (outerPlane._priority <= innerPlane._priority) {
+ eraseLists[innerIndex].add(*eraseList[rectIndex]);
+ } else {
+ eraseLists[outerIndex].add(*eraseList[rectIndex]);
}
}
}
- rectlist.erase_at(rectIndex);
+ eraseList.erase_at(rectIndex);
}
}
- rectlist.pack();
+ eraseList.pack();
}
}
}
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- Plane *plane = _planes[planeIndex];
- Plane *visiblePlane = nullptr;
+ for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+ Plane &plane = *_planes[planeIndex];
+ Plane *visiblePlane = _visiblePlanes.findByObject(plane._object);
- PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
- if (visiblePlaneIt != _visiblePlanes.end()) {
- visiblePlane = *visiblePlaneIt;
- }
+ if (!plane._screenRect.isEmpty()) {
+ if (plane._redrawAllCount) {
+ plane.redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+ } else {
+ if (visiblePlane == nullptr) {
+ error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane._object));
+ }
- if (plane->_redrawAllCount) {
- plane->redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
- } else {
- if (visiblePlane == nullptr) {
- error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane->_object));
+ plane.calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
}
+ } else {
+ plane.decrementScreenItemArrayCounts(visiblePlane, false);
+ }
- plane->calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+ if (plane._moved) {
+ // the work for handling moved/resized planes was already done
+ // earlier in the function, we are just cleaning up now
+ --plane._moved;
}
- if (plane->_created) {
- _visiblePlanes.add(new Plane(*plane));
- --plane->_created;
- } else if (plane->_moved) {
- assert(visiblePlaneIt != _visiblePlanes.end());
- **visiblePlaneIt = *plane;
- --plane->_moved;
+ if (plane._created) {
+ _visiblePlanes.add(new Plane(plane));
+ --plane._created;
+ } else if (plane._updated) {
+ if (visiblePlane == nullptr) {
+ error("[GfxFrameout::calcLists]: Attempt to update nonexistent visible plane");
+ }
+
+ *visiblePlane = plane;
+ --plane._updated;
}
}
+ // NOTE: SSCI only looks for kPlaneTypeTransparent, not
+ // kPlaneTypeTransparentPicture
if (foundTransparentPlane) {
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- for (int i = planeIndex + 1; i < planeCount; ++i) {
+ for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+ for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) {
if (_planes[i]->_type == kPlaneTypeTransparent) {
_planes[i]->filterUpEraseRects(drawLists[i], eraseLists[planeIndex]);
}
}
if (_planes[planeIndex]->_type == kPlaneTypeTransparent) {
- for (int i = planeIndex - 1; i >= 0; --i) {
+ for (int i = (int)planeIndex - 1; i >= 0; --i) {
_planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]);
}
@@ -729,7 +1034,7 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL
}
}
- for (int i = planeIndex + 1; i < planeCount; ++i) {
+ for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) {
if (_planes[i]->_type == kPlaneTypeTransparent) {
_planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]);
}
@@ -743,188 +1048,90 @@ void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) {
return;
}
- for (RectList::const_iterator it = eraseList.begin(); it != eraseList.end(); ++it) {
- mergeToShowList(**it, _showList, _overdrawThreshold);
- _currentBuffer.fillRect(**it, plane._back);
+ const RectList::size_type eraseListSize = eraseList.size();
+ for (RectList::size_type i = 0; i < eraseListSize; ++i) {
+ mergeToShowList(*eraseList[i], _showList, _overdrawThreshold);
+ _currentBuffer.fillRect(*eraseList[i], plane._back);
}
}
void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) {
- _hasRemappedScreenItem = false;
- if (/* TODO: g_Remap_UnknownCounter2 */ false && !_priorityMap.isNull()) {
- for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) {
- if ((*it)->screenItem->getCelObj()._remap) {
- _hasRemappedScreenItem = true;
- break;
- }
- }
- }
-
- for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) {
- DrawItem &drawItem = **it;
+ const DrawList::size_type drawListSize = screenItemList.size();
+ for (DrawList::size_type i = 0; i < drawListSize; ++i) {
+ const DrawItem &drawItem = *screenItemList[i];
mergeToShowList(drawItem.rect, _showList, _overdrawThreshold);
- ScreenItem &screenItem = *drawItem.screenItem;
+ const ScreenItem &screenItem = *drawItem.screenItem;
// TODO: Remove
-// debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), drawItem.rect.left, drawItem.rect.top, drawItem.rect.right, drawItem.rect.bottom);
+// debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), PRINT_RECT(drawItem.rect));
CelObj &celObj = *screenItem._celObj;
celObj.draw(_currentBuffer, screenItem, drawItem.rect, screenItem._mirrorX ^ celObj._mirrorX);
}
}
void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) {
- Common::Rect merged(drawRect);
-
- bool didDelete = true;
- RectList::size_type count = showList.size();
- while (didDelete && count) {
- didDelete = false;
-
- for (RectList::size_type i = 0; i < count; ++i) {
- Common::Rect existing = *showList[i];
- Common::Rect candidate;
- candidate.left = MIN(merged.left, existing.left);
- candidate.top = MIN(merged.top, existing.top);
- candidate.right = MAX(merged.right, existing.right);
- candidate.bottom = MAX(merged.bottom, existing.bottom);
-
- if (candidate.height() * candidate.width() - merged.width() * merged.height() - existing.width() * existing.height() <= overdrawThreshold) {
- merged = candidate;
- showList.erase_at(i);
- didDelete = true;
- }
- }
-
- count = showList.pack();
- }
-
- showList.add(merged);
-}
-
-void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) {
- Palette sourcePalette(*_palette->getNextPalette());
- alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
-
- // TODO: unsure if this is what this variable actually
- // represents, but it is the correct variable number
- int16 lastRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16();
-
- Common::Rect rect(_screen->getDisplayWidth(), _screen->getDisplayHeight());
- _showList.add(rect);
- showBits();
-
- Common::Rect calcRect(0, 0);
-
- // NOTE: The original engine allocated these as static arrays of 100
- // pointers to ScreenItemList / RectList
- ScreenItemListList screenItemLists;
- EraseListList eraseLists;
-
- screenItemLists.resize(_planes.size());
- eraseLists.resize(_planes.size());
-
- // TODO: Remap
- // _numActiveRemaps was a global in SCI engine
- // if (Remap::_numActiveRemaps > 0 && _remapOccurred) {
- // _screen->remapMarkRedraw();
- // }
-
- calcLists(screenItemLists, eraseLists, calcRect);
- for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
- list->sort();
- }
-
- for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
- for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
- (*drawItem)->screenItem->getCelObj().submitPalette();
- }
- }
-
- _remapOccurred = _palette->updateForFrame();
- _frameNowVisible = false;
-
- for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
- drawEraseList(eraseLists[i], *_planes[i]);
- drawScreenItemList(screenItemLists[i]);
- }
-
- Palette nextPalette(*_palette->getNextPalette());
+ RectList mergeList;
+ Common::Rect merged;
+ mergeList.add(drawRect);
+
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ bool didMerge = false;
+ const Common::Rect &r1 = *mergeList[i];
+ if (!r1.isEmpty()) {
+ for (RectList::size_type j = 0; j < showList.size(); ++j) {
+ const Common::Rect &r2 = *showList[j];
+ if (!r2.isEmpty()) {
+ merged = r1;
+ merged.extend(r2);
+
+ int difference = merged.width() * merged.height();
+ difference -= r1.width() * r1.height();
+ difference -= r2.width() * r2.height();
+ if (r1.intersects(r2)) {
+ const Common::Rect overlap = r1.findIntersectingRect(r2);
+ difference += overlap.width() * overlap.height();
+ }
- if (lastRoom < 1000) {
- for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
- if (styleRanges[i] == -1 || styleRanges[i] == 0) {
- sourcePalette.colors[i] = nextPalette.colors[i];
- sourcePalette.colors[i].used = true;
- }
- }
- } else {
- for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
- if (styleRanges[i] == -1 || (styleRanges[i] == 0 && i > 71 && i < 104)) {
- sourcePalette.colors[i] = nextPalette.colors[i];
- sourcePalette.colors[i].used = true;
+ if (difference <= overdrawThreshold) {
+ mergeList.erase_at(i);
+ showList.erase_at(j);
+ mergeList.add(merged);
+ didMerge = true;
+ break;
+ } else {
+ Common::Rect outRects[2];
+ int splitCount = splitRectsForRender(*mergeList[i], *showList[j], outRects);
+ if (splitCount != -1) {
+ mergeList.add(*mergeList[i]);
+ mergeList.erase_at(i);
+ showList.erase_at(j);
+ didMerge = true;
+ while (splitCount--) {
+ mergeList.add(outRects[splitCount]);
+ }
+ break;
+ }
+ }
+ }
}
- }
- }
- _palette->submit(sourcePalette);
- _palette->updateFFrame();
- _palette->updateHardware();
- alterVmap(nextPalette, sourcePalette, 1, _styleRanges);
-
- if (showStyle && showStyle->type != kShowStyleUnknown) {
-// TODO: SCI2.1mid transition effects
-// processEffects();
- warning("Transition not implemented!");
- } else {
- showBits();
- }
-
- _frameNowVisible = true;
-
- for (PlaneList::iterator plane = _planes.begin(); plane != _planes.end(); ++plane) {
-// TODO:
-// plane->updateRedrawAllCount();
- }
-
- // TODO: Remap
- // _numActiveRemaps was a global in SCI engine
- // if (Remap::_numActiveRemaps > 0 && _remapOccurred) {
- // _screen->remapMarkRedraw();
- // }
-
- calcLists(screenItemLists, eraseLists, calcRect);
- for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
- list->sort();
- }
-
- for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
- for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
- (*drawItem)->screenItem->getCelObj().submitPalette();
+ if (didMerge) {
+ showList.pack();
+ }
}
}
- _remapOccurred = _palette->updateForFrame();
- // NOTE: During this second loop, `_frameNowVisible = false` is
- // inside the next loop in SCI2.1mid
- _frameNowVisible = false;
-
- for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
- drawEraseList(eraseLists[i], *_planes[i]);
- drawScreenItemList(screenItemLists[i]);
+ mergeList.pack();
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ showList.add(*mergeList[i]);
}
-
- _palette->submit(nextPalette);
- _palette->updateFFrame();
- _palette->updateHardware();
- showBits();
-
- _frameNowVisible = true;
}
-// TODO: What does the bit masking for the show rects do,
-// and does it cause an off-by-one error in rect calculations
-// since SOL_Rect is BR inclusive and Common::Rect is BR
-// exclusive?
void GfxFrameout::showBits() {
+ if (!_showList.size()) {
+ g_system->updateScreen();
+ return;
+ }
+
for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
Common::Rect rounded(**rect);
// NOTE: SCI engine used BR-inclusive rects so used slightly
@@ -932,13 +1139,10 @@ void GfxFrameout::showBits() {
// was always even.
rounded.left &= ~1;
rounded.right = (rounded.right + 1) & ~1;
-
- // TODO:
- // _cursor->GonnaPaint(rounded);
+ _cursor->gonnaPaint(rounded);
}
- // TODO:
- // _cursor->PaintStarting();
+ _cursor->paintStarting();
for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
Common::Rect rounded(**rect);
@@ -950,13 +1154,20 @@ void GfxFrameout::showBits() {
byte *sourceBuffer = (byte *)_currentBuffer.getPixels() + rounded.top * _currentBuffer.screenWidth + rounded.left;
+ // TODO: Sometimes transition screen items generate zero-dimension
+ // show rectangles. Is this a bug?
+ if (rounded.width() == 0 || rounded.height() == 0) {
+ warning("Zero-dimension show rectangle ignored");
+ continue;
+ }
+
g_system->copyRectToScreen(sourceBuffer, _currentBuffer.screenWidth, rounded.left, rounded.top, rounded.width(), rounded.height());
}
- // TODO:
- // _cursor->DonePainting();
+ _cursor->donePainting();
_showList.clear();
+ g_system->updateScreen();
}
void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges) {
@@ -969,7 +1180,7 @@ void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, co
if (styleRanges[paletteIndex] == style) {
int minDiff = 262140;
- int minDiffIndex;
+ int minDiffIndex = paletteIndex;
for (int i = 0; i < 236; ++i) {
if (styleRanges[i] != style) {
@@ -989,7 +1200,7 @@ void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, co
if (style == 1 && styleRanges[paletteIndex] == 0) {
int minDiff = 262140;
- int minDiffIndex;
+ int minDiffIndex = paletteIndex;
for (int i = 0; i < 236; ++i) {
int r = palette2.colors[i].r;
@@ -1007,17 +1218,17 @@ void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, co
}
}
- // NOTE: This is currBuffer->ptr in SCI engine
byte *pixels = (byte *)_currentBuffer.getPixels();
- // TODO: Guessing that display width/height is the correct
- // equivalent to screen width/height in SCI engine
for (int pixelIndex = 0, numPixels = _currentBuffer.screenWidth * _currentBuffer.screenHeight; pixelIndex < numPixels; ++pixelIndex) {
byte currentValue = pixels[pixelIndex];
int8 styleRangeValue = styleRanges[currentValue];
if (styleRangeValue == -1 && styleRangeValue == style) {
currentValue = pixels[pixelIndex] = clut[currentValue];
- styleRangeValue = styleRanges[clut[currentValue]];
+ // NOTE: In original engine this assignment happens outside of the
+ // condition, but if the branch is not followed the value is just
+ // going to be the same as it was before
+ styleRangeValue = styleRanges[currentValue];
}
if (
@@ -1029,1009 +1240,155 @@ void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, co
}
}
-void GfxFrameout::kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor) {
- if (toColor > fromColor) {
- return;
- }
-
- for (int i = fromColor; i < toColor; ++i) {
- _styleRanges[i] = 0;
- }
-}
-
-inline ShowStyleEntry * GfxFrameout::findShowStyleForPlane(const reg_t planeObj) const {
- ShowStyleEntry *entry = _showStyles;
- while (entry != nullptr) {
- if (entry->plane == planeObj) {
- break;
- }
- entry = entry->next;
- }
-
- return entry;
-}
-
-inline ShowStyleEntry *GfxFrameout::deleteShowStyleInternal(ShowStyleEntry *const showStyle) {
- ShowStyleEntry *lastEntry = nullptr;
-
- for (ShowStyleEntry *testEntry = _showStyles; testEntry != nullptr; testEntry = testEntry->next) {
- if (testEntry == showStyle) {
- break;
- }
- lastEntry = testEntry;
- }
-
- if (lastEntry == nullptr) {
- _showStyles = showStyle->next;
- lastEntry = _showStyles;
+void GfxFrameout::kernelFrameOut(const bool shouldShowBits) {
+ if (_transitions->hasShowStyles()) {
+ _transitions->processShowStyles();
+ } else if (_palMorphIsOn) {
+ palMorphFrameOut(_transitions->_styleRanges, nullptr);
+ _palMorphIsOn = false;
} else {
- lastEntry->next = showStyle->next;
- }
-
- // NOTE: Differences from SCI2/2.1early engine:
- // 1. Memory of ShowStyle-owned objects was freed before ShowStyle was
- // removed from the linked list, but since this operation is position
- // independent, it has been moved after removal from the list for
- // consistency with SCI2.1mid+
- // 2. In SCI2, `screenItems` was a pointer to an array of pointers, so
- // extra deletes were performed here; we use an owned container object
- // instead, which is automatically freed when ShowStyle is freed
-#if 0
- if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
- uint8 type = showStyle->type;
-
- if (type >= 1 && type <= 10) {
- ScreenItemList &styleItems = showStyle->screenItems;
- for (ScreenItemList::iterator it = styleItems.begin(); it != styleItems.end(); ++it) {
- if (active) {
- // TODO: _screen->deleteScreenItem(showStyle->plane, *it->id);
- _screenItems.remove(*it);
- }
- delete *it;
- }
- } else if (type == 11 || type == 12) {
- if (!showStyle->bitmapMemId.isNull()) {
- _segMan->freeHunkEntry(showStyle->bitmapMemId);
- }
- if (showStyle->bitmapScreenItem != nullptr) {
- // TODO: _screen->deleteScreenItem(showStyle->plane, showStyle->bitmapScreenItem->id);
- _screenItems.remove(showStyle->bitmapScreenItem);
- delete showStyle->bitmapScreenItem;
- }
+ if (_transitions->hasScrolls()) {
+ _transitions->processScrolls();
}
- } else {
-#endif
- delete[] showStyle->fadeColorRanges;
-#if 0
- }
-#endif
- delete showStyle;
-
- // TODO: Verify that this is the correct entry to return
- // for the loop in processShowStyles to work correctly
- return lastEntry;
-}
-
-// TODO: 10-argument version is only in SCI3; argc checks are currently wrong for this version
-// and need to be fixed in future
-// TODO: SQ6 does not use 'priority' (exists since SCI2) or 'blackScreen' (exists since SCI3);
-// check to see if other versions use or if they are just always ignored
-void GfxFrameout::kernelSetShowStyle(const uint16 argc, const reg_t &planeObj, const ShowStyleType type, const int16 seconds, const int16 back, const int16 priority, const int16 animate, const int16 frameOutNow, const reg_t &pFadeArray, const int16 divisions, const int16 blackScreen) {
-
- bool hasDivisions = false;
- bool hasFadeArray = false;
- if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
- hasDivisions = argc > 7;
- hasFadeArray = false;
- } else if (getSciVersion() < SCI_VERSION_3) {
- hasDivisions = argc > 8;
- hasFadeArray = argc > 7;
- } else {
- hasDivisions = argc > 9;
- hasFadeArray = argc > 8;
+ frameOut(shouldShowBits);
}
- bool isFadeUp;
- int16 color;
- if (back != -1) {
- isFadeUp = false;
- color = back;
- } else {
- isFadeUp = true;
- color = 0;
- }
-
- if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && type == 15) || type > 15) {
- error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj));
- }
-
- Plane *plane = _planes.findByObject(planeObj);
- if (plane == nullptr) {
- error("Plane %04x:%04x is not present in active planes list", PRINT_REG(planeObj));
- }
-
- // TODO: This is Plane.gameRect in SCI engine, not planeRect. Engine uses
- // Plane::ConvGameRectToPlaneRect to convert from gameRect to planeRect.
- // Also this never gets used by SQ6 so it is not clear what it does yet
- // Common::Rect gameRect = plane.planeRect;
-
- bool createNewEntry = true;
- ShowStyleEntry *entry = findShowStyleForPlane(planeObj);
- if (entry != nullptr) {
- bool useExisting = true;
-
- if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
- useExisting = plane->_planeRect.width() == entry->width && plane->_planeRect.height() == entry->height;
- }
-
- if (useExisting) {
- useExisting = entry->divisions == (hasDivisions ? divisions : _defaultDivisions[type]) && entry->unknownC == _defaultUnknownC[type];
- }
-
- if (useExisting) {
- createNewEntry = false;
- isFadeUp = true;
- entry->currentStep = 0;
- } else {
- isFadeUp = true;
- color = entry->color;
- deleteShowStyleInternal(entry/*, true*/);
- entry = nullptr;
- }
- }
-
- if (type > 0) {
- if (createNewEntry) {
- entry = new ShowStyleEntry;
- // NOTE: SCI2.1 engine tests if allocation returned a null pointer
- // but then only avoids setting currentStep if this is so. Since
- // this is a nonsensical approach, we do not do that here
- entry->currentStep = 0;
- entry->unknownC = _defaultUnknownC[type];
- entry->processed = false;
- entry->divisions = hasDivisions ? divisions : _defaultDivisions[type];
- entry->plane = planeObj;
-
-#if 0
- if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
- entry->bitmapMemId = NULL_REG;
- entry->screenItems.empty();
- entry->width = plane->_planeRect.width();
- entry->height = plane->_planeRect.height();
- } else {
-#endif
- entry->fadeColorRanges = nullptr;
- if (hasFadeArray) {
- // NOTE: SCI2.1mid engine does no check to verify that an array is
- // successfully retrieved, and SegMan will cause a fatal error
- // if we try to use a memory segment that is not an array
- SciArray<reg_t> *table = _segMan->lookupArray(pFadeArray);
-
- uint32 rangeCount = table->getSize();
- entry->fadeColorRangesCount = rangeCount;
-
- // NOTE: SCI engine code always allocates memory even if the range
- // table has no entries, but this does not really make sense, so
- // we avoid the allocation call in this case
- if (rangeCount > 0) {
- entry->fadeColorRanges = new uint16[rangeCount];
- for (size_t i = 0; i < rangeCount; ++i) {
- entry->fadeColorRanges[i] = table->getValue(i).toUint16();
- }
- }
- } else {
- entry->fadeColorRangesCount = 0;
- }
-#if 0
- }
-#endif
- }
-
- // NOTE: The original engine had no nullptr check and would just crash
- // if it got to here
- if (entry == nullptr) {
- error("Cannot edit non-existing ShowStyle entry");
- }
-
- entry->fadeUp = isFadeUp;
- entry->color = color;
- entry->nextTick = g_sci->getTickCount();
- entry->type = type;
- entry->animate = animate;
- entry->delay = (seconds * 60 + entry->divisions - 1) / entry->divisions;
-
- if (entry->delay == 0) {
-#if 0
- if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE && entry->fadeColorRanges != nullptr) {
-#endif
- if (entry->fadeColorRanges != nullptr) {
- delete[] entry->fadeColorRanges;
- }
- delete entry;
- error("ShowStyle has no duration");
- }
-
- if (frameOutNow) {
- Common::Rect frameOutRect(0, 0);
- frameOut(false, frameOutRect);
- }
-
- if (createNewEntry) {
- // TODO: Implement SCI3, which may or may not actually have
- // the same transitions as SCI2/SCI2.1early, but implemented
- // differently
-#if 0
- if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
- switch (entry->type) {
- case kShowStyleHShutterIn:
- case kShowStyleHShutterOut:
- prepareShowStyleWipe(entry, priority, 2, true);
- break;
-
- case kShowStyleVShutterIn:
- case kShowStyleVShutterOut:
- prepareShowStyleWipe(entry, priority, 2, false);
- break;
-
- case kShowStyleHWipe1:
- case kShowStyleHWipe2:
- prepareShowStyleWipe(entry, priority, 1, true);
- break;
-
- case kShowStyleVWipe1:
- case kShowStyleVWipe2:
- prepareShowStyleWipe(entry, priority, 1, false);
- break;
-
- case kShowStyleIrisIn:
- case kShowStyleIrisOut:
- prepareShowStyleIris(entry, priority);
- break;
-
- case kShowStyle11:
- case kShowStyle12:
- prepareShowStylePixels(entry, priority, plane->planeRect);
- break;
-
- default:
- break;
- }
- }
-#endif
-
- entry->next = _showStyles;
- _showStyles = entry;
- }
- }
+ throttle();
}
-#if 0
-void addFrameoutEntryInternal(ShowStyleEntry *const showStyle, const int16 priority, const CelInfo32 &celInfo, const Common::Rect &rect) {
- ScreenItem *screenItem = new ScreenItem;
- screenItem->plane = showStyle->plane;
- screenItem->celInfo = celInfo;
- screenItem->celRect = rect;
- screenItem->isInList = true;
- screenItem->priority = priority;
- screenItem->visible = true;
- showStyle->screenItems.push_back(screenItem);
-}
-
-void GfxFrameout::prepareShowStyleWipe(ShowStyleEntry *const showStyle, const int16 priority, const int16 edgeCount, const bool horizontal) {
- assert(edgeCount == 1 || edgeCount == 2);
-
- const int numScreenItems = showStyle->divisions * edgeCount;
- const int extra = edgeCount > 1 ? 1 : 0;
-
- showStyle->edgeCount = edgeCount;
- showStyle->screenItems.reserve(numScreenItems);
-
- CelInfo32 celInfo;
- celInfo.bitmap = NULL_REG;
- celInfo.type = kCelObjTypeView;
- celInfo.color = showStyle->color;
-
- for (int i = 0; i < numScreenItems; ++i) {
- Common::Rect rect;
-
- if (horizontal) {
- rect.top = 0;
- rect.bottom = showStyle->height - 1;
- rect.left = (showStyle->width * i) / (showStyle->divisions * edgeCount);
- rect.right = ((i + 1) * (showStyle->width + extra)) / (showStyle->divisions * edgeCount) - 1;
+void GfxFrameout::throttle() {
+ if (_throttleFrameOut) {
+ uint8 throttleTime;
+ if (_throttleState == 2) {
+ throttleTime = 16;
+ _throttleState = 0;
} else {
- rect.left = 0;
- rect.right = showStyle->width - 1;
- rect.top = (showStyle->height * i) / (showStyle->divisions * edgeCount);
- rect.bottom = ((i + 1) * (showStyle->height + extra)) / (showStyle->divisions * edgeCount) - 1;
- }
-
- addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
-
- if (edgeCount == 2) {
- if (horizontal) {
- int temp = rect.left;
- rect.left = showStyle->width - rect.right - 1;
- rect.right = showStyle->width - temp - 1;
- } else {
- int temp = rect.top;
- rect.top = showStyle->height - rect.bottom - 1;
- rect.bottom = showStyle->height - temp - 1;
- }
-
- addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
- }
- }
-}
-
-void GfxFrameout::prepareShowStyleIris(ShowStyleEntry *const showStyle, const int16 priority) {
- const int edgeCount = 4;
- const int numScreenItems = showStyle->divisions * edgeCount;
-
- showStyle->edgeCount = edgeCount;
- showStyle->screenItems.reserve(numScreenItems);
-
- CelInfo32 celInfo;
- celInfo.bitmap = NULL_REG;
- celInfo.type = kCelObjTypeView;
- celInfo.color = showStyle->color;
-
- for (int i = 0; i < numScreenItems; ++i) {
- Common::Rect rect;
-
- rect.right = showStyle->width - ((showStyle->width * i) / (showStyle->divisions * 2)) - 1;
- rect.left = (showStyle->width * i) / (showStyle->divisions * 2);
- rect.top = (showStyle->height * i) / (showStyle->divisions * 2);
- rect.bottom = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2) - 1;
-
- addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
-
- {
- int temp = rect.top;
- rect.top = showStyle->height - rect.bottom - 1;
- rect.bottom = showStyle->height - temp - 1;
+ throttleTime = 17;
+ ++_throttleState;
}
- addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
-
- rect.top = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2);
- rect.right = ((i + 1) * (showStyle->width + 1)) / (showStyle->divisions * 2) - 1;
- rect.bottom = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2) - 1;
-
- addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
-
- {
- int temp = rect.left;
- rect.left = showStyle->width - rect.right - 1;
- rect.right = showStyle->width - temp - 1;
- }
-
- addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
+ g_sci->getEngineState()->speedThrottler(throttleTime);
+ g_sci->getEngineState()->_throttleTrigger = true;
}
}
-void GfxFrameout::prepareShowStylePixels(ShowStyleEntry *const showStyle, const int16 priority, const Common::Rect planeGameRect) {
- const int bitmapSize = showStyle->width * showStyle->height;
-
- // TODO: Verify that memory type 0x200 (what GK1 engine uses)
- // is Hunk type
- reg_t bitmapMemId = _segMan->allocateHunkEntry("ShowStylePixels()", bitmapSize + sizeof(GfxBitmapHeader));
- showStyle->bitmapMemId = bitmapMemId;
-
- // TODO: SCI2 GK1 uses a Bitmap constructor function to
- // do this work
- byte *bitmap = _segMan->getHunkPointer(bitmapMemId);
- GfxBitmapHeader *header = (GfxBitmapHeader *)bitmap;
- byte *bitmapData = bitmap + sizeof(GfxBitmapHeader);
-
- // TODO: These are defaults from the Bitmap constructor in
- // GK1, not specific values set by this function.
- // TODO: This probably should not even be using a struct at
- // all since this information is machine endian dependent
- // and will be reversed for Mac versions or when running
- // ScummVM on big-endian systems. GK1 used packed structs
- // everywhere so this probably worked better there too.
- header->field_18 = 36;
- header->field_1c = 36;
- memset(header, 0, sizeof(GfxBitmapHeader));
-
- header->width = showStyle->width;
- header->height = showStyle->height;
- header->field_8 = 250;
- header->size = bitmapSize;
-
- // TODO: Scaled dimensions in bitmap headers was not added
- // until SCI2.1mid. It is not clear what the right thing to
- // do here is.
- if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
- header->scaledWidth = _currentBuffer.scriptWidth;
- header->scaledHeight = _currentBuffer.scriptHeight;
- }
-
- Common::Rect copyRect;
- // TODO: planeGameRect is supposedly in script coordinates,
- // which are usually 320x200. If bitsSaveDisplayScreen is
- // in native resolution then seemingly this function will
- // not work properly since we will be not copy enough bits,
- // or from the correct location.
- copyRect.left = planeGameRect.left;
- copyRect.top = planeGameRect.top;
- copyRect.right = planeGameRect.left + showStyle->width;
- copyRect.bottom = planeGameRect.top + showStyle->height;
- _screen->bitsSaveDisplayScreen(copyRect, bitmapData);
-
- CelInfo32 celInfo;
- celInfo.bitmap = bitmapMemId;
- celInfo.type = kCelObjTypeMem;
-
- ScreenItem *screenItem = new ScreenItem;
-
- screenItem->position.x = 0;
- screenItem->position.y = 0;
-
- showStyle->bitmapScreenItem = screenItem;
- screenItem->priority = priority;
-
- // TODO: Have not seen/identified this particular flag yet in
- // SCI2.1mid (SQ6) engine; maybe (1) a duplicate of `created`,
- // or (2) does not exist any more, or (3) one of the other
- // still-unidentified fields. Probably need to look at the
- // GK1 source for its use in drawing algorithms to determine
- // if/how this correlates to ScreenItem members in the
- // SCI2.1mid engine.
-// screenItem->isInList = true;
-
- Plane *plane = _planes.findByObject(showStyle.plane);
- plane->_screenItemList.add(screenItem);
-}
-#endif
-
-// NOTE: Different version of SCI engine support different show styles
-// SCI2 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12, 13, 14
-// SCI2.1 implements 0, 1/2/3/4/5/6/7/8/9/10/11/12/15, 13, 14
-// SCI3 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12/15, 13, 14
-// TODO: Sierra code needs to be replaced with code that uses the
-// computed entry->delay property instead of just counting divisors,
-// as the latter is machine-speed-dependent and leads to wrong
-// transition speeds
-void GfxFrameout::processShowStyles() {
- uint32 now = g_sci->getTickCount();
-
- bool continueProcessing;
-
- // TODO: Change to bool? Engine uses inc to set the value to true,
- // but there does not seem to be any reason to actually count how
- // many times it was set
- int doFrameOut;
- do {
- continueProcessing = false;
- doFrameOut = 0;
- ShowStyleEntry *showStyle = _showStyles;
- while (showStyle != nullptr) {
- bool retval = false;
-
- if (!showStyle->animate) {
- ++doFrameOut;
- }
-
- if (showStyle->nextTick < now || !showStyle->animate) {
- // TODO: Different versions of SCI use different processors!
- // This is the SQ6/KQ7/SCI2.1mid table.
- switch (showStyle->type) {
- case kShowStyleNone: {
- retval = processShowStyleNone(showStyle);
- break;
- }
- case kShowStyleHShutterOut:
- case kShowStyleVShutterOut:
- case kShowStyleWipeLeft:
- case kShowStyleWipeUp:
- case kShowStyleIrisOut:
-#if 0
- if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
-#endif
- retval = processShowStyleMorph(showStyle);
-#if 0
- } else {
- retval = processShowStyleWipe(-1, showStyle);
- }
-#endif
- break;
- case kShowStyleHShutterIn:
- case kShowStyleVShutterIn:
- case kShowStyleWipeRight:
- case kShowStyleWipeDown:
- case kShowStyleIrisIn:
-#if 0
- if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
-#endif
- retval = processShowStyleMorph(showStyle);
-#if 0
- } else {
- retval = processShowStyleWipe(1, showStyle);
- }
-#endif
- break;
- case kShowStyle11:
-#if 0
- if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
-#endif
- retval = processShowStyleMorph(showStyle);
-#if 0
- } else {
- retval = processShowStyle11(showStyle);
- }
-#endif
- break;
- case kShowStyle12:
- case kShowStyleUnknown: {
-#if 0
- if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
-#endif
- retval = processShowStyleMorph(showStyle);
-#if 0
- } else {
- retval = processShowStyle12(showStyle);
- }
-#endif
- break;
- }
- case kShowStyleFadeOut: {
- retval = processShowStyleFade(-1, showStyle);
- break;
- }
- case kShowStyleFadeIn: {
- retval = processShowStyleFade(1, showStyle);
- break;
- }
- }
- }
-
- if (!retval) {
- continueProcessing = true;
- }
-
- if (retval && showStyle->processed) {
- showStyle = deleteShowStyleInternal(showStyle);
- } else {
- showStyle = showStyle->next;
- }
- }
-
- if (doFrameOut) {
- Common::Rect frameOutRect(0, 0);
- frameOut(true, frameOutRect);
-
- // TODO: It seems like transitions without the “animate”
- // flag are too fast in in SCI2–2.1early, but the throttle
- // value is arbitrary. Someone on real hardware probably
- // needs to test what the actual speed of transitions
- // should be
- //state->speedThrottler(30);
- //state->_throttleTrigger = true;
- }
- } while(continueProcessing && doFrameOut);
-}
-
-bool GfxFrameout::processShowStyleNone(ShowStyleEntry *const showStyle) {
- if (showStyle->fadeUp) {
- _palette->setFade(100, 0, 255);
- } else {
- _palette->setFade(0, 0, 255);
- }
-
- showStyle->processed = true;
- return true;
-}
-
-bool GfxFrameout::processShowStyleMorph(ShowStyleEntry *const showStyle) {
- palMorphFrameOut(_styleRanges, showStyle);
- showStyle->processed = true;
- return true;
-}
-
-// TODO: Normalise use of 'entry' vs 'showStyle'
-bool GfxFrameout::processShowStyleFade(const int direction, ShowStyleEntry *const showStyle) {
- bool unchanged = true;
- if (showStyle->currentStep < showStyle->divisions) {
- int percent;
- if (direction <= 0) {
- percent = showStyle->divisions - showStyle->currentStep - 1;
- } else {
- percent = showStyle->currentStep;
- }
-
- percent *= 100;
- percent /= showStyle->divisions - 1;
-
- if (showStyle->fadeColorRangesCount > 0) {
- for (int i = 0, len = showStyle->fadeColorRangesCount; i < len; i += 2) {
- _palette->setFade(percent, showStyle->fadeColorRanges[i], showStyle->fadeColorRanges[i + 1]);
- }
- } else {
- _palette->setFade(percent, 0, 255);
- }
-
- ++showStyle->currentStep;
- showStyle->nextTick += showStyle->delay;
- unchanged = false;
+void GfxFrameout::shakeScreen(int16 numShakes, const ShakeDirection direction) {
+ if (direction & kShakeHorizontal) {
+ // Used by QFG4 room 750
+ warning("TODO: Horizontal shake not implemented");
+ return;
}
- if (showStyle->currentStep >= showStyle->divisions && unchanged) {
- if (direction > 0) {
- showStyle->processed = true;
+ while (numShakes--) {
+ if (direction & kShakeVertical) {
+ g_system->setShakePos(_isHiRes ? 8 : 4);
}
- return true;
- }
-
- return false;
-}
-
-// TODO: Rect sizes are wrong, rects in SCI are inclusive of bottom/right but
-// in ScummVM are exclusive so extra ±1 operations here are wrong
-#if 0
-bool GfxFrameout::processShowStyleWipe(const ShowStyleEntry *const style) {
- const int16 divisions = style->divisions;
- Common::Rect rect(divisions, divisions);
-
- const Plane *const plane = _visibleScreen->planeList->findByObject(style->plane);
-
- const int16 planeLeft = plane->field_4C.left;
- const int16 planeTop = plane->field_4C.top;
- const int16 planeRight = plane->field_4C.right;
- const int16 planeBottom = plane->field_4C.bottom;
- const int16 planeWidth = planeRight - planeLeft + 1;
- const int16 planeHeight = planeBottom - planeTop + 1;
-
- const int16 divisionWidth = planeWidth / divisions - 1;
- int16 shutterDivisionWidth = planeWidth / (2 * divisions);
- if (shutterDivisionWidth >= 0) {
- const int16 heightPerDivision = planeHeight / divisions;
- int16 shutterMiddleX = divisions * shutterDivisionWidth;
- for (int16 x = divisionWidth - shutterDivisionWidth; x <= divisionWidth; ++x) {
- int16 divisionTop = 0;
- for (int16 y = 0; y < heightPerDivision; ++y, divisionTop += divisions) {
- rect.top = planeTop + divisionTop;
- rect.bottom = rect.top + divisions - 1;
- rect.left = planeLeft + shutterMiddleX;
- rect.right = rect.left + divisions - 1;
- // _screen->rectList.clear();
- // _screen->rectList.add(rect);
- // showBits();
- }
- // number of divisions does not divide evenly into plane height,
- // draw the remainder
- if (planeHeight % divisions) {
- rect.top = planeTop + divisionTop;
- rect.bottom = rect.top + planeHeight % divisions - 1;
- rect.left = planeLeft + shutterMiddleX;
- rect.right = rect.left + divisions - 1;
- // _screen->rectList.clear();
- // _screen->rectList.add(rect);
- // showBits();
- }
+ g_system->updateScreen();
+ g_sci->getEngineState()->wait(3);
- divisionTop = 0;
- for (int16 y = 0; y < heightPerDivision; ++y, divisionTop += divisions) {
- rect.top = planeTop + divisionTop;
- rect.bottom = rect.top + divisions - 1;
- rect.left = planeLeft + divisions * x;
- rect.right = rect.left + divisions - 1;
- // _screen->rectList.clear();
- // _screen->rectList.add(rect);
- // showBits();
- }
- if (planeHeight % divisions) {
- rect.top = planeTop + divisionTop;
- rect.bottom = rect.top + planeHeight % divisions - 1;
- rect.left = planeLeft + divisions * x;
- rect.right = rect.left + divisions - 1;
- // _screen->rectList.clear();
- // _screen->rectList.add(rect);
- // showBits();
- }
-
- shutterMiddleX -= divisions;
- --shutterDivisionWidth;
+ if (direction & kShakeVertical) {
+ g_system->setShakePos(0);
}
- }
- if (planeWidth % divisions) {
- const int16 roundedPlaneWidth = divisions * divisionWidth;
- int16 divisionTop = 0;
- for (int16 y = 0; y < planeHeight / divisions; ++y, divisionTop += divisions) {
- rect.top = planeTop + divisionTop;
- rect.bottom = rect.top + divisions - 1;
- rect.left = planeLeft + roundedPlaneWidth;
- rect.right = rect.left + planeWidth % divisions + divisions - 1;
- // _screen->rectList.clear();
- // _screen->rectList.add(rect);
- // showBits();
- }
- if (planeHeight % divisions) {
- rect.top = planeTop + divisionTop;
- rect.bottom = rect.top + planeHeight % divisions - 1;
- rect.left = planeLeft + roundedPlaneWidth;
- rect.right = rect.left + planeWidth % divisions + divisions - 1;
- // _screen->rectList.clear();
- // _screen->rectList.add(rect);
- // showBits();
- }
+ g_system->updateScreen();
+ g_sci->getEngineState()->wait(3);
}
-
- rect.right = planeRight;
- rect.left = planeLeft;
- rect.top = planeTop;
- rect.bottom = planeBottom;
- // _screen->rectList.clear();
- // _screen->rectList.add(rect);
- // showBits();
}
-#endif
-#if 0
-bool GfxFrameout::processShowStyleWipe(const int direction, ShowStyleEntry *const showStyle) {
- if (showStyle->currentStep < showStyle->divisions) {
- int index;
- if (direction <= 0) {
- index = showStyle->divisions - showStyle->currentStep - 1;
- } else {
- index = showStyle->currentStep;
- }
- index *= showStyle->edgeCount;
-
- if (showStyle->edgeCount > 0) {
- for (int i = 0; i < showStyle->edgeCount; ++i) {
- if (showStyle->fadeUp) {
- ScreenItem *screenItem = showStyle->screenItems[index + i];
- if (screenItem != nullptr) {
- // TODO: _screen->deleteScreenItem(screenItem);
- _screenItems.remove(screenItem);
-
- delete screenItem;
- showStyle->screenItems[index + i] = nullptr;
- }
- } else {
- ScreenItem *screenItem = showStyle->screenItems[index + i];
- // TODO: _screen->addScreenItem(screenItem);
- _screenItems.push_back(screenItem);
- }
- }
+#pragma mark -
+#pragma mark Mouse cursor
- ++showStyle->currentStep;
- showStyle->nextTick += showStyle->delay;
- }
+reg_t GfxFrameout::kernelIsOnMe(const reg_t object, const Common::Point &position, bool checkPixel) const {
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+ Plane *plane = _visiblePlanes.findByObject(planeObject);
+ if (plane == nullptr) {
+ return make_reg(0, 0);
}
- if (showStyle->currentStep >= showStyle->divisions) {
- if (showStyle->fadeUp) {
- showStyle->processed = true;
- }
-
- return true;
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem == nullptr) {
+ return make_reg(0, 0);
}
- return false;
+ // NOTE: The original engine passed a copy of the ScreenItem into isOnMe
+ // as a hack around the fact that the screen items in `_visiblePlanes`
+ // did not have their `_celObj` pointers cleared when their CelInfo was
+ // updated by `Plane::decrementScreenItemArrayCounts`. We handle this
+ // this more intelligently by clearing `_celObj` in the copy assignment
+ // operator, which is only ever called by `decrementScreenItemArrayCounts`
+ // anyway.
+ return make_reg(0, isOnMe(*screenItem, *plane, position, checkPixel));
}
-void fillRect(byte *data, const Common::Rect &rect, const int16 color, const int16 stride) {
-
-}
-
-bool GfxFrameout::processShowStyle11(ShowStyleEntry *const showStyle) {
- int divisions = showStyle->divisions * showStyle->divisions;
-
- byte *bitmapData = _segMan->getHunkPointer(showStyle->bitmapMemId) + sizeof(GfxBitmapHeader);
+bool GfxFrameout::isOnMe(const ScreenItem &screenItem, const Plane &plane, const Common::Point &position, const bool checkPixel) const {
- int ebx;
+ Common::Point scaledPosition(position);
+ mulru(scaledPosition, Ratio(_currentBuffer.screenWidth, _currentBuffer.scriptWidth), Ratio(_currentBuffer.screenHeight, _currentBuffer.scriptHeight));
+ scaledPosition.x += plane._planeRect.left;
+ scaledPosition.y += plane._planeRect.top;
- if (showStyle->currentStep == 0) {
- int ctr = 0;
- int bloot = divisions;
- do {
- bloot >>= 1;
- ++ctr;
- } while (bloot != 1);
-
- showStyle->dissolveSeed = _dissolveSequenceSeeds[ctr];
- ebx = 800;
- showStyle->unknown3A = 800;
- showStyle->dissolveInitial = 800;
- } else {
- int ebx = showStyle->unknown3A;
- do {
- int eax = ebx >> 1;
- if (ebx & 1) {
- ebx = showStyle->dissolveSeed ^ eax;
- } else {
- ebx = eax;
- }
- } while (ebx >= divisions);
-
- if (ebx == showStyle->dissolveInitial) {
- ebx = 0;
- }
+ if (!screenItem._screenRect.contains(scaledPosition)) {
+ return false;
}
- Common::Rect rect;
-
- rect.left = (showStyle->width + showStyle->divisions - 1) / showStyle->divisions;
- rect.top = (showStyle->height + showStyle->divisions - 1) / showStyle->divisions;
-
- if (showStyle->currentStep <= showStyle->divisions) {
- int ebp = 0;
- do {
- int ecx = ebx % showStyle->divisions;
-
- Common::Rect drawRect;
- drawRect.left = rect.left * ecx;
- drawRect.right = drawRect.left + rect.left - 1;
- drawRect.top = rect.top * ebx;
- drawRect.bottom = rect.top * ebx + rect.top - 1;
-
- bool doit;
- if (drawRect.right >= 0 && drawRect.bottom >= 0 && drawRect.left <= rect.right && drawRect.top <= rect.bottom) {
- doit = true;
- } else {
- doit = false;
- }
-
- if (doit) {
- if (drawRect.left < 0) {
- drawRect.left = 0;
- }
-
- if (drawRect.top < 0) {
- drawRect.top = 0;
- }
-
- if (drawRect.right > rect.right) {
- drawRect.right = rect.right;
- }
-
- if (drawRect.bottom > rect.bottom) {
- drawRect.bottom = rect.bottom;
- }
- } else {
- drawRect.right = 0;
- drawRect.bottom = 0;
- drawRect.left = 0;
- drawRect.top = 0;
- }
-
- fillRect(bitmapData, drawRect, showStyle->color, showStyle->width);
-
- int eax = ebx;
- do {
- eax >>= 1;
- if (ebx & 1) {
- ebx = showStyle->dissolveSeed;
- ebx ^= eax;
- } else {
- ebx = eax;
- }
- } while (ebx >= divisions);
+ if (checkPixel) {
+ CelObj &celObj = screenItem.getCelObj();
- if (showStyle->currentStep != showStyle->divisions) {
- ebp++;
- } else {
- drawRect.left = 0;
- drawRect.top = 0;
- drawRect.right = showStyle->width - 1;
- drawRect.bottom = showStyle->height - 1;
- fillRect(bitmapData, drawRect, showStyle->color, showStyle->width);
- }
+ bool mirrorX = screenItem._mirrorX ^ celObj._mirrorX;
- } while (ebp <= showStyle->divisions);
+ scaledPosition.x -= screenItem._scaledPosition.x;
+ scaledPosition.y -= screenItem._scaledPosition.y;
- showStyle->unknown3A = ebx;
- ++showStyle->currentStep;
- showStyle->nextTick += showStyle->delay;
- // _screen->updateScreenItem(showStyle->bitmapScreenItem);
- }
+ mulru(scaledPosition, Ratio(celObj._xResolution, _currentBuffer.screenWidth), Ratio(celObj._yResolution, _currentBuffer.screenHeight));
- if (showStyle->currentStep >= showStyle->divisions) {
- if (showStyle->fadeUp) {
- showStyle->processed = true;
+ if (screenItem._scale.signal != kScaleSignalNone && screenItem._scale.x && screenItem._scale.y) {
+ scaledPosition.x = scaledPosition.x * 128 / screenItem._scale.x;
+ scaledPosition.y = scaledPosition.y * 128 / screenItem._scale.y;
}
- return true;
+ uint8 pixel = celObj.readPixel(scaledPosition.x, scaledPosition.y, mirrorX);
+ return pixel != celObj._skipColor;
}
- return false;
-}
-
-bool GfxFrameout::processShowStyle12(ShowStyleEntry *const showStyle) {
return true;
}
-#endif
-
-void GfxFrameout::kernelFrameout(const bool shouldShowBits) {
- if (_showStyles != nullptr) {
- processShowStyles();
- } else if (_palMorphIsOn) {
- palMorphFrameOut(_styleRanges, nullptr);
- _palMorphIsOn = false;
- } else {
-// TODO: Window scroll
-// if (g_ScrollWindow) {
-// doScroll();
-// }
-
- Common::Rect frameOutRect(0, 0);
- frameOut(shouldShowBits, frameOutRect);
- }
-}
-uint16 GfxFrameout::kernelIsOnMe(int16 x, int16 y, uint16 checkPixels, reg_t screenObject) {
- reg_t planeObject = readSelector(_segMan, screenObject, SELECTOR(plane));
- Plane *screenItemPlane = _visiblePlanes.findByObject(planeObject); // Search for plane in visible planes
- ScreenItem *screenItem = nullptr;
+bool GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const {
+ const reg_t planeObject = readSelector(_segMan, screenItemObject, SELECTOR(plane));
- if (!screenItemPlane) {
- // Specified plane not found
- return 0;
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kSetNowSeen: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(screenItemObject));
}
- screenItem = screenItemPlane->_screenItemList.findByObject(screenObject);
- if (!screenItem) {
- // Specified screen object not in item list
- return 0;
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject);
+ if (screenItem == nullptr) {
+ return false;
}
- // Original SCI32 seems to have made a copy (?) of the screenitem?
- // there is also a "or [ebp+arg_56], 1 - not sure what that did
- return isOnMe(screenItemPlane, screenItem, x, y, checkPixels);
-}
-
-uint16 GfxFrameout::isOnMe(Plane *screenItemPlane, ScreenItem *screenItem, int16 x, int16 y, uint16 checkPixels) {
- // adjust coordinate according to resolution
- int32 adjustedX = x * getCurrentBuffer().screenWidth / getCurrentBuffer().scriptWidth;
- int32 adjustedY = y * getCurrentBuffer().screenHeight / getCurrentBuffer().scriptHeight;
-
- adjustedX += screenItemPlane->_planeRect.left;
- adjustedY += screenItemPlane->_planeRect.top;
-
- //warning("kIsOnMe %s %d (%d, %d -> %d, %d) mouse %d, %d", _segMan->getObjectName(screenObject), checkPixels, screenItem->_screenRect.left, screenItem->_screenRect.top, screenItem->_screenRect.right, screenItem->_screenRect.bottom, adjustedX, adjustedY);
+ Common::Rect result = screenItem->getNowSeenRect(*plane);
- if (!screenItem->_screenRect.contains(adjustedX, adjustedY)) {
- // Specified coordinates are not within screen item
- return 0;
+ if (g_sci->_features->usesAlternateSelectors()) {
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(left), result.left);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(top), result.top);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(right), result.right - 1);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(bottom), result.bottom - 1);
+ } else {
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsLeft), result.left);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsTop), result.top);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsRight), result.right - 1);
+ writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsBottom), result.bottom - 1);
}
+ return true;
+}
- //warning("HIT!");
- if (checkPixels) {
- //warning("Check Pixels");
- CelObj &screenItemCelObject = screenItem->getCelObj();
-
- Common::Point celAdjustedPoint(adjustedX, adjustedY);
- bool celMirrored = screenItem->_mirrorX ^ screenItemCelObject._mirrorX;
-
- celAdjustedPoint.x -= screenItem->_scaledPosition.x;
- celAdjustedPoint.y -= screenItem->_scaledPosition.y;
-
- Ratio celAdjustXRatio(screenItemCelObject._scaledWidth, getCurrentBuffer().screenWidth);
- Ratio celAdjustYRatio(screenItemCelObject._scaledHeight, getCurrentBuffer().screenHeight);
- mulru(celAdjustedPoint, celAdjustXRatio, celAdjustYRatio);
-
- if ((screenItem->_scale.signal) && (screenItem->_scale.x) && (screenItem->_scale.y)) {
- // Apply scaling
- celAdjustedPoint.x = celAdjustedPoint.x * 128 / screenItem->_scale.x;
- celAdjustedPoint.y = celAdjustedPoint.y * 128 / screenItem->_scale.y;
- }
-
- byte coordinateColor = screenItemCelObject.readPixel(celAdjustedPoint.x, celAdjustedPoint.y, celMirrored);
- byte transparentColor = screenItemCelObject._transparentColor;
-
- if (coordinateColor == transparentColor) {
- // Coordinate is transparent
- //warning("TRANSPARENT!");
- return 0;
- }
+void GfxFrameout::remapMarkRedraw() {
+ for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) {
+ Plane *p = *it;
+ p->remapMarkRedraw();
}
- return 1;
}
#pragma mark -
@@ -2052,6 +1409,15 @@ void GfxFrameout::printVisiblePlaneList(Console *con) const {
printPlaneListInternal(con, _visiblePlanes);
}
+void GfxFrameout::printPlaneItemListInternal(Console *con, const ScreenItemList &screenItemList) const {
+ ScreenItemList::size_type i = 0;
+ for (ScreenItemList::const_iterator sit = screenItemList.begin(); sit != screenItemList.end(); sit++) {
+ ScreenItem *screenItem = *sit;
+ con->debugPrintf("%2d: ", i++);
+ screenItem->printDebugInfo(con);
+ }
+}
+
void GfxFrameout::printPlaneItemList(Console *con, const reg_t planeObject) const {
Plane *p = _planes.findByObject(planeObject);
@@ -2060,12 +1426,18 @@ void GfxFrameout::printPlaneItemList(Console *con, const reg_t planeObject) cons
return;
}
- ScreenItemList::size_type i = 0;
- for (ScreenItemList::iterator sit = p->_screenItemList.begin(); sit != p->_screenItemList.end(); sit++) {
- ScreenItem *screenItem = *sit;
- con->debugPrintf("%2d: ", i++);
- screenItem->printDebugInfo(con);
+ printPlaneItemListInternal(con, p->_screenItemList);
+}
+
+void GfxFrameout::printVisiblePlaneItemList(Console *con, const reg_t planeObject) const {
+ Plane *p = _visiblePlanes.findByObject(planeObject);
+
+ if (p == nullptr) {
+ con->debugPrintf("Plane does not exist");
+ return;
}
+
+ printPlaneItemListInternal(con, p->_screenItemList);
}
} // End of namespace Sci
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index 0da6866f7c..9738203b74 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -27,224 +27,97 @@
#include "sci/graphics/screen_item32.h"
namespace Sci {
-// TODO: Don't do this this way
-int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]);
-
-// TODO: Verify display styles and adjust names appropriately for
-// types 1 through 12 & 15 (others are correct)
-// Names should be:
-// * VShutterIn, VShutterOut
-// * HShutterIn, HShutterOut
-// * WipeLeft, WipeRight, WipeDown, WipeUp
-// * PixelDissolve
-// * ShutDown and Kill? (and Plain and Fade?)
-enum ShowStyleType /* : uint8 */ {
- kShowStyleNone = 0,
- kShowStyleHShutterOut = 1,
- kShowStyleHShutterIn = 2,
- kShowStyleVShutterOut = 3,
- kShowStyleVShutterIn = 4,
- kShowStyleWipeLeft = 5,
- kShowStyleWipeRight = 6,
- kShowStyleWipeUp = 7,
- kShowStyleWipeDown = 8,
- kShowStyleIrisOut = 9,
- kShowStyleIrisIn = 10,
- kShowStyle11 = 11,
- kShowStyle12 = 12,
- kShowStyleFadeOut = 13,
- kShowStyleFadeIn = 14,
- // TODO: Only in SCI3
- kShowStyleUnknown = 15
-};
+typedef Common::Array<DrawList> ScreenItemListList;
+typedef Common::Array<RectList> EraseListList;
+
+class GfxCursor32;
+class GfxTransitions32;
+struct PlaneShowStyle;
/**
- * Show styles represent transitions applied to draw planes.
- * One show style per plane can be active at a time.
+ * Frameout class, kFrameout and relevant functions for SCI32 games.
+ * Roughly equivalent to GraphicsMgr in the actual SCI engine.
*/
-struct ShowStyleEntry {
- /**
- * The ID of the plane this show style belongs to.
- * In SCI2.1mid (at least SQ6), per-plane transitions
- * were removed and a single plane ID is used.
- */
- reg_t plane;
-
- /**
- * The type of the transition.
- */
- ShowStyleType type;
-
- // TODO: This name is probably incorrect
- bool fadeUp;
-
- /**
- * The number of steps for the show style.
- */
- int16 divisions;
-
- // NOTE: This property exists from SCI2 through at least
- // SCI2.1mid but is never used in the actual processing
- // of the styles?
- int unknownC;
-
- /**
- * The color used by transitions that draw CelObjColor
- * screen items. -1 for transitions that do not draw
- * screen items.
- */
- int16 color;
-
- // TODO: Probably uint32
- // TODO: This field probably should be used in order to
- // provide time-accurate processing of show styles. In the
- // actual SCI engine (at least 2–2.1mid) it appears that
- // style transitions are drawn “as fast as possible”, one
- // step per loop, even though this delay field exists
- int delay;
-
- // TODO: Probably bool, but never seems to be true?
- int animate;
-
- /**
- * The wall time at which the next step of the animation
- * should execute.
- */
- uint32 nextTick;
-
- /**
- * During playback of the show style, the current step
- * (out of divisions).
- */
- int currentStep;
+class GfxFrameout {
+ friend class GfxTransitions32;
+private:
+ GfxCursor32 *_cursor;
+ GfxPalette32 *_palette;
+ SegManager *_segMan;
/**
- * The next show style.
+ * Determines whether the current game should be rendered in
+ * high resolution.
*/
- ShowStyleEntry *next;
+ bool gameIsHiRes() const;
- /**
- * Whether or not this style has finished running and
- * is ready for disposal.
- */
- bool processed;
+public:
+ GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor);
+ ~GfxFrameout();
- //
- // Engine-specific properties for SCI2 through 2.1early
- //
+ bool _isHiRes;
- // TODO: Could union this stuff to save literally
- // several bytes of memory.
+ void clear();
+ void syncWithScripts(bool addElements); // this is what Game::restore does, only needed when our ScummVM dialogs are patched in
+ void run();
+#pragma mark -
+#pragma mark Benchmarking
+private:
/**
- * The width of the plane. Used to determine the correct
- * size of screen items for wipes.
+ * Optimization to avoid the more expensive object name
+ * comparision on every call to kAddScreenItem and
+ * kRemoveScreenItem.
*/
- int width;
+ bool _benchmarkingFinished;
/**
- * The height of the plane. Used to determine the correct
- * size of screen items for wipes.
+ * Whether or not calls to kFrameOut should be framerate
+ * limited to 60fps.
*/
- int height;
+ bool _throttleFrameOut;
/**
- * The number of edges that a transition operates on.
- * Slide wipe: 1 edge
- * Reveal wipe: 2 edges
- * Iris wipe: 4 edges
+ * Determines whether or not a screen item is the "Fred"
+ * object.
*/
- // TODO: I have no idea why SCI engine stores this instead
- // of a screenItems count
- int edgeCount;
+ bool checkForFred(const reg_t object);
- /**
- * Used by transition types 1 through 10.
- * One screen item per division per edge.
- */
- ScreenItemList screenItems;
+#pragma mark -
+#pragma mark Screen items
+private:
+ void remapMarkRedraw();
+public:
/**
- * Used by transition types 11 and 12. A copy of the
- * visible frame buffer.
+ * Adds a screen item.
*/
- // TODO: This is a reg_t in SCI engine; not sure if
- // we can avoid allocation through SegMan or not.
- reg_t bitmapMemId;
+ void addScreenItem(ScreenItem &screenItem) const;
/**
- * Used by transition types 11 and 12. A screen item
- * used to display the associated bitmap data.
+ * Updates a screen item.
*/
- ScreenItem *bitmapScreenItem;
+ void updateScreenItem(ScreenItem &screenItem) const;
/**
- * A number used to pick pixels to dissolve by types
- * 11 and 12.
+ * Deletes a screen item.
*/
- int dissolveSeed;
- int unknown3A;
- // max?
- int dissolveInitial;
-
- //
- // Engine specific properties for SCI2.1mid through SCI3
- //
+ void deleteScreenItem(ScreenItem &screenItem);
/**
- * The number of entries in the fadeColorRanges array.
+ * Deletes a screen item from the given plane.
*/
- uint8 fadeColorRangesCount;
+ void deleteScreenItem(ScreenItem &screenItem, Plane &plane);
/**
- * A pointer to an dynamically sized array of palette
- * indexes, in the order [ fromColor, toColor, ... ].
- * Only colors within this range are transitioned.
+ * Deletes a screen item from the given plane.
*/
- uint16 *fadeColorRanges;
-};
-
-typedef Common::Array<DrawList> ScreenItemListList;
-typedef Common::Array<RectList> EraseListList;
-
-class GfxCache;
-class GfxCoordAdjuster32;
-class GfxPaint32;
-class GfxPalette;
-class GfxScreen;
+ void deleteScreenItem(ScreenItem &screenItem, const reg_t plane);
-/**
- * Frameout class, kFrameout and relevant functions for SCI32 games.
- * Roughly equivalent to GraphicsMgr in the actual SCI engine.
- */
-class GfxFrameout {
-private:
- bool _isHiRes;
- GfxCache *_cache;
- GfxCoordAdjuster32 *_coordAdjuster;
- GfxPalette32 *_palette;
- ResourceManager *_resMan;
- GfxScreen *_screen;
- SegManager *_segMan;
- GfxPaint32 *_paint32;
-
-public:
- GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32);
- ~GfxFrameout();
-
- void clear();
- void syncWithScripts(bool addElements); // this is what Game::restore does, only needed when our ScummVM dialogs are patched in
- void run();
-
-#pragma mark -
-#pragma mark Screen items
-private:
- void deleteScreenItem(ScreenItem *screenItem, const reg_t plane);
-
-public:
void kernelAddScreenItem(const reg_t object);
void kernelUpdateScreenItem(const reg_t object);
void kernelDeleteScreenItem(const reg_t object);
+ bool kernelSetNowSeen(const reg_t screenItemObject) const;
#pragma mark -
#pragma mark Planes
@@ -259,6 +132,13 @@ private:
PlaneList _planes;
/**
+ * Updates an existing plane with properties from the
+ * given VM object.
+ */
+ void updatePlane(Plane &plane);
+
+public:
+ /**
* Creates and adds a new plane to the plane list, or
* cancels deletion and updates an already-existing
* plane if a plane matching the given plane VM object
@@ -270,64 +150,40 @@ private:
void addPlane(Plane &plane);
/**
- * Updates an existing plane with properties from the
- * given VM object.
+ * Deletes a plane within the current plane list.
+ *
+ * @note This method is on Screen in SCI engine, but it
+ * is only ever called on `GraphicsMgr.screen`.
*/
- void updatePlane(Plane &plane);
+ void deletePlane(Plane &plane);
-public:
const PlaneList &getPlanes() const {
return _planes;
}
+ const PlaneList &getVisiblePlanes() const {
+ return _visiblePlanes;
+ }
void kernelAddPlane(const reg_t object);
void kernelUpdatePlane(const reg_t object);
void kernelDeletePlane(const reg_t object);
+ void kernelMovePlaneItems(const reg_t object, const int16 deltaX, const int16 deltaY, const bool scrollPics);
int16 kernelGetHighPlanePri();
#pragma mark -
#pragma mark Pics
public:
- void kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 pictureX, const int16 pictureY, const bool mirrorX);
-
-#pragma mark -
-
- // TODO: Remap-related?
- void kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor);
-
-#pragma mark -
-#pragma mark Transitions
-private:
- int *_dissolveSequenceSeeds;
- int16 *_defaultDivisions;
- int16 *_defaultUnknownC;
-
- /**
- * TODO: Documentation
- */
- ShowStyleEntry *_showStyles;
-
- inline ShowStyleEntry *findShowStyleForPlane(const reg_t planeObj) const;
- inline ShowStyleEntry *deleteShowStyleInternal(ShowStyleEntry *const showStyle);
- void processShowStyles();
- bool processShowStyleNone(ShowStyleEntry *showStyle);
- bool processShowStyleMorph(ShowStyleEntry *showStyle);
- bool processShowStyleFade(const int direction, ShowStyleEntry *showStyle);
-#if 0
- bool processShowStyleWipe(const int direction, ShowStyleEntry *const showStyle);
-#endif
-
-public:
- // NOTE: This signature is taken from SCI3 Phantasmagoria 2
- // and is valid for all implementations of SCI32
- void kernelSetShowStyle(const uint16 argc, const reg_t &planeObj, const ShowStyleType type, const int16 seconds, const int16 direction, const int16 priority, const int16 animate, const int16 frameOutNow, const reg_t &pFadeArray, const int16 divisions, const int16 blackScreen);
+ void kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 pictureX, const int16 pictureY, const bool mirrorX, const bool deleteDuplicate);
#pragma mark -
#pragma mark Rendering
private:
+ GfxTransitions32 *_transitions;
+
/**
- * TODO: Documentation
+ * State tracker to provide more accurate 60fps
+ * video throttling.
*/
- int8 _styleRanges[256];
+ uint8 _throttleState;
/**
* The internal display pixel buffer. During frameOut,
@@ -338,26 +194,14 @@ private:
*/
Buffer _currentBuffer;
- // TODO: In SCI2.1/SQ6, priority map pixels are not allocated
- // by default. In SCI2/GK1, pixels are allocated, but not used
- // anywhere except within CelObj::Draw in seemingly the same
- // way they are used in SCI2.1/SQ6: that is, never read, only
- // written.
- Buffer _priorityMap;
-
/**
- * TODO: Documentation
+ * When true, a change to the remap zone in the palette
+ * has occurred and screen items with remap data need to
+ * be redrawn.
*/
bool _remapOccurred;
/**
- * Whether or not the data in the current buffer is what
- * is visible to the user. During rendering updates,
- * this flag is set to false.
- */
- bool _frameNowVisible;
-
- /**
* TODO: Document
* TODO: Depending upon if the engine ever modifies this
* rect, it may be stupid to store it separately instead
@@ -404,8 +248,10 @@ private:
* over the entire screen for rendering the next frame.
* The draw and erase lists in `drawLists` and
* `eraseLists` each represent one plane on the screen.
+ * The optional `eraseRect` argument allows a specific
+ * area of the screen to be erased.
*/
- void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect);
+ void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect = Common::Rect());
/**
* Erases the areas in the given erase list from the
@@ -423,13 +269,6 @@ private:
void drawScreenItemList(const DrawList &screenItemList);
/**
- * Updates the internal screen buffer for the next
- * frame. If `shouldShowBits` is true, also sends the
- * buffer to hardware.
- */
- void frameOut(const bool shouldShowBits, const Common::Rect &rect);
-
- /**
* Adds a new rectangle to the list of regions to write
* out to the hardware. The provided rect may be merged
* into an existing rectangle to reduce the number of
@@ -438,22 +277,42 @@ private:
void mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold);
/**
- * TODO: Documentation
- */
- void palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle);
-
- /**
* Writes the internal frame buffer out to hardware and
* clears the show list.
*/
void showBits();
+ /**
+ * Validates whether the given palette index in the
+ * style range should copy a color from the next
+ * palette to the source palette during a palette
+ * morph operation.
+ */
+ inline bool validZeroStyle(const uint8 style, const int i) const {
+ if (style != 0) {
+ return false;
+ }
+
+ // TODO: Cannot check Shivers or MGDX until those executables can be
+ // unwrapped
+ switch (g_sci->getGameId()) {
+ case GID_KQ7:
+ case GID_PHANTASMAGORIA:
+ case GID_SQ6:
+ return (i > 71 && i < 104);
+ break;
+ default:
+ return true;
+ }
+ }
+
public:
/**
- * TODO: Document
- * This is used by CelObj::Draw.
+ * Whether or not the data in the current buffer is what
+ * is visible to the user. During rendering updates,
+ * this flag is set to false.
*/
- bool _hasRemappedScreenItem;
+ bool _frameNowVisible;
/**
* Whether palMorphFrameOut should be used instead of
@@ -462,11 +321,30 @@ public:
*/
bool _palMorphIsOn;
- inline Buffer &getCurrentBuffer() {
+ inline const Buffer &getCurrentBuffer() const {
return _currentBuffer;
}
- void kernelFrameout(const bool showBits);
+ void kernelFrameOut(const bool showBits);
+
+ /**
+ * Throttles the engine as necessary to maintain
+ * 60fps output.
+ */
+ void throttle();
+
+ /**
+ * Updates the internal screen buffer for the next
+ * frame. If `shouldShowBits` is true, also sends the
+ * buffer to hardware. If `eraseRect` is non-empty,
+ * it is added to the erase list for this frame.
+ */
+ void frameOut(const bool shouldShowBits, const Common::Rect &eraseRect = Common::Rect());
+
+ /**
+ * TODO: Documentation
+ */
+ void palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *showStyle);
/**
* Modifies the raw pixel data for the next frame with
@@ -474,11 +352,6 @@ public:
*/
void alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges);
- // TODO: SCI2 engine never uses priority map?
- inline Buffer &getPriorityMap() {
- return _priorityMap;
- }
-
// NOTE: This function is used within ScreenItem subsystem and assigned
// to various booleanish fields that seem to represent the state of the
// screen item (created, updated, deleted). In GK1/DOS, Phant1/m68k,
@@ -491,8 +364,28 @@ public:
return 1;
};
- uint16 kernelIsOnMe(int16 x, int16 y, uint16 checkPixels, reg_t screenObject);
- uint16 isOnMe(Plane *screenItemPlane, ScreenItem *screenItem, int16 x, int16 y, uint16 checkPixels);
+ /**
+ * Draws a portion of the current screen buffer to
+ * hardware. Used to display show styles in SCI2.1mid+.
+ */
+ void showRect(const Common::Rect &rect);
+
+ /**
+ * Shakes the screen.
+ */
+ void shakeScreen(const int16 numShakes, const ShakeDirection direction);
+
+#pragma mark -
+#pragma mark Mouse cursor
+private:
+ /**
+ * Determines whether or not the point given by
+ * `position` is inside of the given screen item.
+ */
+ bool isOnMe(const ScreenItem &screenItem, const Plane &plane, const Common::Point &position, const bool checkPixel) const;
+
+public:
+ reg_t kernelIsOnMe(const reg_t object, const Common::Point &position, const bool checkPixel) const;
#pragma mark -
#pragma mark Debugging
@@ -501,6 +394,8 @@ public:
void printVisiblePlaneList(Console *con) const;
void printPlaneListInternal(Console *con, const PlaneList &planeList) const;
void printPlaneItemList(Console *con, const reg_t planeObject) const;
+ void printVisiblePlaneItemList(Console *con, const reg_t planeObject) const;
+ void printPlaneItemListInternal(Console *con, const ScreenItemList &screenItemList) const;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index fbad120374..1da3749c90 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -40,8 +40,10 @@ namespace Sci {
#define MAX_CACHED_FONTS 20
#define MAX_CACHED_VIEWS 50
-#define SCI_SHAKE_DIRECTION_VERTICAL 1
-#define SCI_SHAKE_DIRECTION_HORIZONTAL 2
+enum ShakeDirection {
+ kShakeVertical = 1,
+ kShakeHorizontal = 2
+};
typedef int GuiResourceId; // is a resource-number and -1 means no parameter given
@@ -139,16 +141,28 @@ inline void mul(Common::Rect &rect, const Common::Rational &ratioX, const Common
}
/**
+ * Multiplies a rectangle by two ratios with default
+ * rounding. Modifies the rect directly. Uses inclusive
+ * rectangle rounding.
+ */
+inline void mulinc(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY) {
+ rect.left = (rect.left * ratioX).toInt();
+ rect.top = (rect.top * ratioY).toInt();
+ rect.right = ((rect.right - 1) * ratioX).toInt() + 1;
+ rect.bottom = ((rect.bottom - 1) * ratioY).toInt() + 1;
+}
+
+/**
* Multiplies a number by a rational number, rounding up to
* the nearest whole number.
*/
-inline int mulru(const int value, const Common::Rational &ratio) {
- int num = value * ratio.getNumerator();
+inline int mulru(const int value, const Common::Rational &ratio, const int extra = 0) {
+ int num = (value + extra) * ratio.getNumerator();
int result = num / ratio.getDenominator();
if (num > ratio.getDenominator() && num % ratio.getDenominator()) {
++result;
}
- return result;
+ return result - extra;
}
/**
@@ -165,19 +179,12 @@ inline void mulru(Common::Point &point, const Common::Rational &ratioX, const Co
* Multiplies a point by two rational numbers for X and Y,
* rounding up to the nearest whole number. Modifies the
* rect directly.
- *
- * @note In SCI engine, the bottom-right corner of rects
- * received an additional one pixel during the
- * multiplication in order to round up to include the
- * bottom-right corner. Since ScummVM rects do not include
- * the bottom-right corner, doing this ends up making rects
- * a pixel too wide/tall depending upon the remainder.
*/
-inline void mulru(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY) {
+inline void mulru(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY, const int extra) {
rect.left = mulru(rect.left, ratioX);
rect.top = mulru(rect.top, ratioY);
- rect.right = mulru(rect.right, ratioX);
- rect.bottom = mulru(rect.bottom, ratioY);
+ rect.right = mulru(rect.right - 1, ratioX, extra) + 1;
+ rect.bottom = mulru(rect.bottom - 1, ratioY, extra) + 1;
}
struct Buffer : public Graphics::Surface {
@@ -186,6 +193,12 @@ struct Buffer : public Graphics::Surface {
uint16 scriptWidth;
uint16 scriptHeight;
+ Buffer() :
+ screenWidth(0),
+ screenHeight(0),
+ scriptWidth(320),
+ scriptHeight(200) {}
+
Buffer(const uint16 width, const uint16 height, uint8 *const pix) :
screenWidth(width),
screenHeight(height),
@@ -226,7 +239,7 @@ struct Color {
return used == other.used && r == other.r && g == other.g && b == other.b;
}
inline bool operator!=(const Color &other) const {
- return !(*this == other);
+ return !operator==(other);
}
#endif
};
diff --git a/engines/sci/graphics/lists32.h b/engines/sci/graphics/lists32.h
index bb990e17ca..ff0bc6ceae 100644
--- a/engines/sci/graphics/lists32.h
+++ b/engines/sci/graphics/lists32.h
@@ -36,7 +36,7 @@ namespace Sci {
* calling `erase` or when destroying the
* StablePointerArray.
*/
-template <class T, uint N>
+template<class T, uint N>
class StablePointerArray {
uint _size;
T *_items[N];
@@ -170,6 +170,13 @@ public:
}
/**
+ * The maximum number of elements the container is able to hold.
+ */
+ size_type max_size() const {
+ return N;
+ }
+
+ /**
* The number of populated slots in the array. The size
* of the array will only go down once `pack` is called.
*/
@@ -178,7 +185,7 @@ public:
}
};
-template <typename T>
+template<typename T>
class FindByObject {
const reg_t &_object;
public:
@@ -188,5 +195,5 @@ public:
}
};
-}
+} // End of namespace Sci
#endif
diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp
index 6004e9ce7a..aac922d278 100644
--- a/engines/sci/graphics/paint16.cpp
+++ b/engines/sci/graphics/paint16.cpp
@@ -41,7 +41,7 @@
namespace Sci {
-GfxPaint16::GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio)
+GfxPaint16::GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster16 *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio)
: _resMan(resMan), _segMan(segMan), _cache(cache), _ports(ports),
_coordAdjuster(coordAdjuster), _screen(screen), _palette(palette),
_transitions(transitions), _audio(audio), _EGAdrawingVisualize(false) {
@@ -541,12 +541,10 @@ reg_t GfxPaint16::kernelDisplay(const char *text, uint16 languageSplitter, int a
break;
default:
- SciTrackOriginReply originReply;
+ SciCallOrigin originReply;
SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kDisplay_workarounds, &originReply);
if (solution.type == WORKAROUND_NONE)
- error("Unknown kDisplay argument (%04x:%04x) from method %s::%s (script %d, localCall %x)",
- PRINT_REG(displayArg), originReply.objectName.c_str(), originReply.methodName.c_str(),
- originReply.scriptNr, originReply.localCallOffset);
+ error("Unknown kDisplay argument (%04x:%04x) from %s", PRINT_REG(displayArg), originReply.toString().c_str());
assert(solution.type == WORKAROUND_IGNORE);
break;
}
diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h
index 955cfdec8f..6fc9cbbdfc 100644
--- a/engines/sci/graphics/paint16.h
+++ b/engines/sci/graphics/paint16.h
@@ -23,8 +23,6 @@
#ifndef SCI_GRAPHICS_PAINT16_H
#define SCI_GRAPHICS_PAINT16_H
-#include "sci/graphics/paint.h"
-
namespace Sci {
class GfxPorts;
@@ -36,9 +34,9 @@ class GfxView;
/**
* Paint16 class, handles painting/drawing for SCI16 (SCI0-SCI1.1) games
*/
-class GfxPaint16 : public GfxPaint {
+class GfxPaint16 {
public:
- GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio);
+ GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster16 *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio);
~GfxPaint16();
void init(GfxAnimate *animate, GfxText16 *text16);
@@ -93,7 +91,7 @@ private:
GfxAnimate *_animate;
GfxCache *_cache;
GfxPorts *_ports;
- GfxCoordAdjuster *_coordAdjuster;
+ GfxCoordAdjuster16 *_coordAdjuster;
GfxScreen *_screen;
GfxPalette *_palette;
GfxText16 *_text16;
diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp
index a210a469f1..bf7c73623e 100644
--- a/engines/sci/graphics/paint32.cpp
+++ b/engines/sci/graphics/paint32.cpp
@@ -20,49 +20,170 @@
*
*/
-#include "sci/sci.h"
-#include "sci/engine/state.h"
-#include "sci/engine/selector.h"
-#include "sci/graphics/coordadjuster.h"
-#include "sci/graphics/cache.h"
+#include "graphics/primitives.h"
+#include "sci/engine/seg_manager.h"
#include "sci/graphics/paint32.h"
-#include "sci/graphics/font.h"
-#include "sci/graphics/picture.h"
-#include "sci/graphics/view.h"
-#include "sci/graphics/screen.h"
-#include "sci/graphics/palette.h"
+#include "sci/graphics/text32.h"
namespace Sci {
-GfxPaint32::GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette)
- : _resMan(resMan), _coordAdjuster(coordAdjuster), _screen(screen), _palette(palette) {
+GfxPaint32::GfxPaint32(SegManager *segMan) :
+ _segMan(segMan) {}
+
+reg_t GfxPaint32::kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) {
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kAddLine: Plane %04x:%04x not found", PRINT_REG(planeObject));
+ }
+
+ Common::Rect gameRect;
+ reg_t bitmapId = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = bitmapId;
+ // SSCI stores the line color on `celInfo`, even though
+ // this is not a `kCelTypeColor`, as a hack so that
+ // `kUpdateLine` can get the originally used color
+ celInfo.color = color;
+
+ ScreenItem *screenItem = new ScreenItem(planeObject, celInfo, gameRect);
+ screenItem->_priority = priority;
+ screenItem->_fixedPriority = true;
+
+ plane->_screenItemList.add(screenItem);
+
+ return screenItem->_object;
}
-GfxPaint32::~GfxPaint32() {
+void GfxPaint32::kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) {
+
+ Common::Rect gameRect;
+ reg_t bitmapId = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect);
+
+ _segMan->freeBitmap(screenItem->_celInfo.bitmap);
+ screenItem->_celInfo.bitmap = bitmapId;
+ screenItem->_celInfo.color = color;
+ screenItem->_position = startPoint;
+ screenItem->_priority = priority;
+ screenItem->update();
}
-void GfxPaint32::fillRect(Common::Rect rect, byte color) {
- int16 y, x;
- Common::Rect clipRect = rect;
+void GfxPaint32::kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject) {
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject);
+ if (plane == nullptr) {
+ return;
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject);
+ if (screenItem == nullptr) {
+ return;
+ }
+
+ _segMan->freeBitmap(screenItem->_celInfo.bitmap);
+ g_sci->_gfxFrameout->deleteScreenItem(*screenItem, *plane);
+}
+
+void GfxPaint32::plotter(int x, int y, int color, void *data) {
+ LineProperties &properties = *static_cast<LineProperties *>(data);
+ byte *pixels = properties.bitmap->getPixels();
+
+ const uint16 bitmapWidth = properties.bitmap->getWidth();
+ const uint16 bitmapHeight = properties.bitmap->getHeight();
+ const uint32 index = bitmapWidth * y + x;
+
+ // Only draw the points in the bitmap, and ignore the rest. SSCI scripts
+ // can draw lines ending outside the visible area (e.g. negative coordinates)
+ if (x >= 0 && x < bitmapWidth && y >= 0 && y < bitmapHeight) {
+ if (properties.solid) {
+ pixels[index] = (uint8)color;
+ return;
+ }
+
+ if (properties.horizontal && x != properties.lastAddress) {
+ properties.lastAddress = x;
+ ++properties.patternIndex;
+ } else if (!properties.horizontal && y != properties.lastAddress) {
+ properties.lastAddress = y;
+ ++properties.patternIndex;
+ }
- clipRect.clip(_screen->getWidth(), _screen->getHeight());
+ if (properties.pattern[properties.patternIndex]) {
+ pixels[index] = (uint8)color;
+ }
- 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);
+ if (properties.patternIndex == ARRAYSIZE(properties.pattern)) {
+ properties.patternIndex = 0;
}
}
}
-void GfxPaint32::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) {
- GfxPicture *picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false);
+reg_t GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, uint16 pattern, uint8 thickness, Common::Rect &outRect) {
+ const uint8 skipColor = color != kDefaultSkipColor ? kDefaultSkipColor : 0;
- picture->draw(animationNr, mirroredFlag, addToFlag, EGApaletteNo);
- delete picture;
-}
+ // Line thickness is expected to be 2 * thickness + 1
+ thickness = (MAX<uint8>(1, thickness) - 1) | 1;
+ const uint8 halfThickness = thickness >> 1;
+
+ const uint16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ outRect.left = MIN<int16>(startPoint.x, endPoint.x);
+ outRect.top = MIN<int16>(startPoint.y, endPoint.y);
+ outRect.right = MAX<int16>(startPoint.x, endPoint.x) + 1 + 1; // rect lower edge + thickness offset
+ outRect.bottom = MAX<int16>(startPoint.y, endPoint.y) + 1 + 1; // rect lower edge + thickness offset
-void GfxPaint32::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) {
- _screen->drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, color, priority, control);
+ outRect.grow(halfThickness);
+ outRect.clip(Common::Rect(scriptWidth, scriptHeight));
+
+ reg_t bitmapId;
+ SciBitmap &bitmap = *_segMan->allocateBitmap(&bitmapId, outRect.width(), outRect.height(), skipColor, 0, 0, scriptWidth, scriptHeight, 0, false, true);
+
+ byte *pixels = bitmap.getPixels();
+ memset(pixels, skipColor, bitmap.getWidth() * bitmap.getHeight());
+
+ LineProperties properties;
+ properties.bitmap = &bitmap;
+
+ switch (style) {
+ case kLineStyleSolid:
+ pattern = 0xFFFF;
+ properties.solid = true;
+ break;
+ case kLineStyleDashed:
+ pattern = 0xFF00;
+ properties.solid = false;
+ break;
+ case kLineStylePattern:
+ properties.solid = pattern == 0xFFFF;
+ break;
+ }
+
+ // Change coordinates to be relative to the bitmap
+ const int16 x1 = startPoint.x - outRect.left;
+ const int16 y1 = startPoint.y - outRect.top;
+ const int16 x2 = endPoint.x - outRect.left;
+ const int16 y2 = endPoint.y - outRect.top;
+
+ if (!properties.solid) {
+ for (int i = 0; i < ARRAYSIZE(properties.pattern); ++i) {
+ properties.pattern[i] = (pattern & 0x8000);
+ pattern <<= 1;
+ }
+
+ properties.patternIndex = 0;
+ properties.horizontal = ABS(x2 - x1) > ABS(y2 - y1);
+ properties.lastAddress = properties.horizontal ? x1 : y1;
+ }
+
+ if (thickness <= 1) {
+ Graphics::drawLine(x1, y1, x2, y2, color, plotter, &properties);
+ } else {
+ Graphics::drawThickLine2(x1, y1, x2, y2, thickness, color, plotter, &properties);
+ }
+
+ return bitmapId;
}
+
} // End of namespace Sci
diff --git a/engines/sci/graphics/paint32.h b/engines/sci/graphics/paint32.h
index e7a3ec256d..3c3b7b4343 100644
--- a/engines/sci/graphics/paint32.h
+++ b/engines/sci/graphics/paint32.h
@@ -23,30 +23,48 @@
#ifndef SCI_GRAPHICS_PAINT32_H
#define SCI_GRAPHICS_PAINT32_H
-#include "sci/graphics/paint.h"
-
namespace Sci {
+class Plane;
+class SciBitmap;
+class ScreenItem;
+class SegManager;
-class GfxPorts;
+enum LineStyle {
+ kLineStyleSolid,
+ kLineStyleDashed,
+ kLineStylePattern
+};
/**
* Paint32 class, handles painting/drawing for SCI32 (SCI2+) games
*/
-class GfxPaint32 : public GfxPaint {
+class GfxPaint32 {
public:
- GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette);
- ~GfxPaint32();
+ GfxPaint32(SegManager *segMan);
- void fillRect(Common::Rect rect, byte color);
+private:
+ SegManager *_segMan;
- void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo);
- void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control);
+#pragma mark -
+#pragma mark Line drawing
+public:
+ reg_t kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness);
+ void kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness);
+ void kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject);
private:
- ResourceManager *_resMan;
- GfxCoordAdjuster *_coordAdjuster;
- GfxScreen *_screen;
- GfxPalette *_palette;
+ typedef struct {
+ SciBitmap *bitmap;
+ bool pattern[16];
+ uint8 patternIndex;
+ bool solid;
+ bool horizontal;
+ int lastAddress;
+ } LineProperties;
+
+ static void plotter(int x, int y, int color, void *data);
+
+ reg_t makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness, Common::Rect &outRect);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp
index 6f6e0b672e..1514ad838f 100644
--- a/engines/sci/graphics/palette.cpp
+++ b/engines/sci/graphics/palette.cpp
@@ -32,6 +32,7 @@
#include "sci/graphics/cache.h"
#include "sci/graphics/maciconbar.h"
#include "sci/graphics/palette.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/view.h"
@@ -103,9 +104,6 @@ GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen)
default:
error("GfxPalette: Unknown view type");
}
-
- _remapOn = false;
- resetRemapping();
}
GfxPalette::~GfxPalette() {
@@ -336,79 +334,6 @@ void GfxPalette::set(Palette *newPalette, bool force, bool forceRealMerge) {
}
}
-byte GfxPalette::remapColor(byte remappedColor, byte screenColor) {
- assert(_remapOn);
- if (_remappingType[remappedColor] == kRemappingByRange)
- return _remappingByRange[screenColor];
- else if (_remappingType[remappedColor] == kRemappingByPercent)
- return _remappingByPercent[screenColor];
- else
- error("remapColor(): Color %d isn't remapped", remappedColor);
-
- return 0; // should never reach here
-}
-
-void GfxPalette::resetRemapping() {
- _remapOn = false;
- _remappingPercentToSet = 0;
-
- for (int i = 0; i < 256; i++) {
- _remappingType[i] = kRemappingNone;
- _remappingByPercent[i] = i;
- _remappingByRange[i] = i;
- }
-}
-
-void GfxPalette::setRemappingPercent(byte color, byte percent) {
- _remapOn = true;
-
- // We need to defer the setup of the remapping table every time the screen
- // palette is changed, so that kernelFindColor() can find the correct
- // colors. Set it once here, in case the palette stays the same and update
- // it on each palette change by copySysPaletteToScreen().
- _remappingPercentToSet = percent;
-
- for (int i = 0; i < 256; i++) {
- byte r = _sysPalette.colors[i].r * _remappingPercentToSet / 100;
- byte g = _sysPalette.colors[i].g * _remappingPercentToSet / 100;
- byte b = _sysPalette.colors[i].b * _remappingPercentToSet / 100;
- _remappingByPercent[i] = kernelFindColor(r, g, b);
- }
-
- _remappingType[color] = kRemappingByPercent;
-}
-
-void GfxPalette::setRemappingPercentGray(byte color, byte percent) {
- _remapOn = true;
-
- // We need to defer the setup of the remapping table every time the screen
- // palette is changed, so that kernelFindColor() can find the correct
- // colors. Set it once here, in case the palette stays the same and update
- // it on each palette change by copySysPaletteToScreen().
- _remappingPercentToSet = percent;
-
- // Note: This is not what the original does, but the results are the same visually
- for (int i = 0; i < 256; i++) {
- byte rComponent = (byte)(_sysPalette.colors[i].r * _remappingPercentToSet * 0.30 / 100);
- byte gComponent = (byte)(_sysPalette.colors[i].g * _remappingPercentToSet * 0.59 / 100);
- byte bComponent = (byte)(_sysPalette.colors[i].b * _remappingPercentToSet * 0.11 / 100);
- byte luminosity = rComponent + gComponent + bComponent;
- _remappingByPercent[i] = kernelFindColor(luminosity, luminosity, luminosity);
- }
-
- _remappingType[color] = kRemappingByPercent;
-}
-
-void GfxPalette::setRemappingRange(byte color, byte from, byte to, byte base) {
- _remapOn = true;
-
- for (int i = from; i <= to; i++) {
- _remappingByRange[i] = i + base;
- }
-
- _remappingType[color] = kRemappingByRange;
-}
-
bool GfxPalette::insert(Palette *newPalette, Palette *destPalette) {
bool paletteChanged = false;
@@ -589,15 +514,8 @@ void GfxPalette::copySysPaletteToScreen() {
}
}
- // Check if we need to reset remapping by percent with the new colors.
- if (_remappingPercentToSet) {
- for (int i = 0; i < 256; i++) {
- byte r = _sysPalette.colors[i].r * _remappingPercentToSet / 100;
- byte g = _sysPalette.colors[i].g * _remappingPercentToSet / 100;
- byte b = _sysPalette.colors[i].b * _remappingPercentToSet / 100;
- _remappingByPercent[i] = kernelFindColor(r, g, b);
- }
- }
+ if (g_sci->_gfxRemap16)
+ g_sci->_gfxRemap16->updateRemapping();
g_system->getPaletteManager()->setPalette(bpal, 0, 256);
}
diff --git a/engines/sci/graphics/palette.h b/engines/sci/graphics/palette.h
index 61a8d36cb9..2178de8a91 100644
--- a/engines/sci/graphics/palette.h
+++ b/engines/sci/graphics/palette.h
@@ -35,12 +35,6 @@ class GfxScreen;
#define SCI_PALETTE_MATCH_PERFECT 0x8000
#define SCI_PALETTE_MATCH_COLORMASK 0xFF
-enum ColorRemappingType {
- kRemappingNone = 0,
- kRemappingByRange = 1,
- kRemappingByPercent = 2
-};
-
/**
* Palette class, handles palette operations like changing intensity, setting up the palette, merging different palettes
*/
@@ -57,32 +51,23 @@ public:
bool setAmiga();
void modifyAmigaPalette(byte *data);
void setEGA();
- virtual void set(Palette *sciPal, bool force, bool forceRealMerge = false);
+ void set(Palette *sciPal, bool force, bool forceRealMerge = false);
bool insert(Palette *newPalette, Palette *destPalette);
bool merge(Palette *pFrom, bool force, bool forceRealMerge);
uint16 matchColor(byte r, byte g, byte b);
void getSys(Palette *pal);
uint16 getTotalColorCount() const { return _totalScreenColors; }
- void resetRemapping();
- void setRemappingPercent(byte color, byte percent);
- void setRemappingPercentGray(byte color, byte percent);
- void setRemappingRange(byte color, byte from, byte to, byte base);
- bool isRemapped(byte color) const {
- return _remapOn && (_remappingType[color] != kRemappingNone);
- }
- byte remapColor(byte remappedColor, byte screenColor);
-
void setOnScreen();
void copySysPaletteToScreen();
void drewPicture(GuiResourceId pictureId);
- virtual bool kernelSetFromResource(GuiResourceId resourceId, bool force);
+ bool kernelSetFromResource(GuiResourceId resourceId, bool force);
void kernelSetFlag(uint16 fromColor, uint16 toColor, uint16 flag);
void kernelUnsetFlag(uint16 fromColor, uint16 toColor, uint16 flag);
void kernelSetIntensity(uint16 fromColor, uint16 toColor, uint16 intensity, bool setPalette);
- virtual int16 kernelFindColor(uint16 r, uint16 g, uint16 b);
+ int16 kernelFindColor(uint16 r, uint16 g, uint16 b);
bool kernelAnimate(byte fromColor, byte toColor, int speed);
void kernelAnimateSet();
reg_t kernelSave();
@@ -96,7 +81,7 @@ public:
int16 kernelPalVaryGetCurrentStep();
int16 kernelPalVaryChangeTarget(GuiResourceId resourceId);
void kernelPalVaryChangeTicks(uint16 ticks);
- virtual void kernelPalVaryPause(bool pause);
+ void kernelPalVaryPause(bool pause);
void kernelPalVaryDeinit();
void palVaryUpdate();
void palVaryPrepareForTransition();
@@ -104,7 +89,7 @@ public:
Palette _sysPalette;
- virtual void saveLoadWithSerializer(Common::Serializer &s);
+ void saveLoadWithSerializer(Common::Serializer &s);
void palVarySaveLoadPalette(Common::Serializer &s, Palette *palette);
byte findMacIconBarColor(byte r, byte g, byte b);
@@ -138,12 +123,6 @@ protected:
int _palVarySignal;
uint16 _totalScreenColors;
- bool _remapOn;
- ColorRemappingType _remappingType[256];
- byte _remappingByPercent[256];
- byte _remappingByRange[256];
- uint16 _remappingPercentToSet;
-
void loadMacIconBarPalette();
byte *_macClut;
};
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index 9204e4bf96..56e1940224 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -27,20 +27,111 @@
#include "sci/sci.h"
#include "sci/event.h"
#include "sci/resource.h"
+#include "sci/util.h"
+#include "sci/engine/features.h"
#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/screen.h"
namespace Sci {
-GfxPalette32::GfxPalette32(ResourceManager *resMan, GfxScreen *screen)
- : GfxPalette(resMan, screen),
+#pragma mark HunkPalette
+
+HunkPalette::HunkPalette(byte *rawPalette) :
+ _version(0),
+ // NOTE: The header size in palettes is garbage. In at least KQ7 2.00b and
+ // Phant1, the 999.pal sets this value to 0. In most other palettes it is
+ // set to 14, but the *actual* size of the header structure used in SSCI is
+ // 13, which is reflected by `kHunkPaletteHeaderSize`.
+ // _headerSize(rawPalette[0]),
+ _numPalettes(rawPalette[10]),
+ _data(nullptr) {
+ assert(_numPalettes == 0 || _numPalettes == 1);
+ if (_numPalettes) {
+ _data = rawPalette;
+ _version = getEntryHeader().version;
+ }
+}
+
+void HunkPalette::setVersion(const uint32 version) const {
+ if (_numPalettes != _data[10]) {
+ error("Invalid HunkPalette");
+ }
+
+ if (_numPalettes) {
+ const EntryHeader header = getEntryHeader();
+ if (header.version != _version) {
+ error("Invalid HunkPalette");
+ }
+
+ WRITE_SCI11ENDIAN_UINT32(getPalPointer() + kEntryVersionOffset, version);
+ _version = version;
+ }
+}
+
+const HunkPalette::EntryHeader HunkPalette::getEntryHeader() const {
+ const byte *const data = getPalPointer();
+
+ EntryHeader header;
+ header.startColor = data[10];
+ header.numColors = READ_SCI11ENDIAN_UINT16(data + 14);
+ header.used = data[16];
+ header.sharedUsed = data[17];
+ header.version = READ_SCI11ENDIAN_UINT32(data + kEntryVersionOffset);
+
+ return header;
+}
+
+const Palette HunkPalette::toPalette() const {
+ Palette outPalette;
+
+ for (int16 i = 0; i < ARRAYSIZE(outPalette.colors); ++i) {
+ outPalette.colors[i].used = false;
+ outPalette.colors[i].r = 0;
+ outPalette.colors[i].g = 0;
+ outPalette.colors[i].b = 0;
+ }
+
+ if (_numPalettes) {
+ const EntryHeader header = getEntryHeader();
+ const byte *data = getPalPointer() + kEntryHeaderSize;
+
+ const int16 end = header.startColor + header.numColors;
+ assert(end <= 256);
+
+ if (header.sharedUsed) {
+ for (int16 i = header.startColor; i < end; ++i) {
+ outPalette.colors[i].used = header.used;
+ outPalette.colors[i].r = *data++;
+ outPalette.colors[i].g = *data++;
+ outPalette.colors[i].b = *data++;
+ }
+ } else {
+ for (int16 i = header.startColor; i < end; ++i) {
+ outPalette.colors[i].used = *data++;
+ outPalette.colors[i].r = *data++;
+ outPalette.colors[i].g = *data++;
+ outPalette.colors[i].b = *data++;
+ }
+ }
+ }
+
+ return outPalette;
+}
+
+#pragma mark -
+#pragma mark GfxPalette32
+
+GfxPalette32::GfxPalette32(ResourceManager *resMan)
+ : _resMan(resMan),
+
// Palette versioning
_version(1),
- _versionUpdated(false),
- _sourcePalette(_sysPalette),
- _nextPalette(_sysPalette),
- // Clut
- _clutTable(nullptr),
+ _needsUpdate(false),
+ _currentPalette(),
+ _sourcePalette(),
+ _nextPalette(),
+
// Palette varying
_varyStartPalette(nullptr),
_varyTargetPalette(nullptr),
@@ -51,107 +142,57 @@ GfxPalette32::GfxPalette32(ResourceManager *resMan, GfxScreen *screen)
_varyDirection(0),
_varyTargetPercent(0),
_varyNumTimesPaused(0),
+
// Palette cycling
_cyclers(),
_cycleMap() {
- _varyPercent = _varyTargetPercent;
- memset(_fadeTable, 100, sizeof(_fadeTable));
- // NOTE: In SCI engine, the palette manager constructor loads
- // the default palette, but in ScummVM this initialisation
- // is performed by SciEngine::run; see r49523 for details
+ _varyPercent = _varyTargetPercent;
+ for (int i = 0, len = ARRAYSIZE(_fadeTable); i < len; ++i) {
+ _fadeTable[i] = 100;
+ }
+
+ loadPalette(999);
}
GfxPalette32::~GfxPalette32() {
+#ifdef ENABLE_SCI3_GAMES
unloadClut();
+#endif
varyOff();
cycleAllOff();
}
-inline void mergePaletteInternal(Palette *const to, const Palette *const from) {
- for (int i = 0; i < ARRAYSIZE(to->colors); ++i) {
- if (from->colors[i].used) {
- to->colors[i] = from->colors[i];
- }
- }
-}
-
-const Palette *GfxPalette32::getNextPalette() const {
- return &_nextPalette;
-}
-
-void GfxPalette32::submit(Palette &palette) {
- // TODO: The resource manager in SCI32 retains raw data of palettes from
- // the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and
- // the version number for submitted palettes is set in the raw palette
- // data in memory as an int at an offset
- // `rawData + *rawData[0x0a] * 2 + 31`. However, ScummVM does not retain
- // resource data like this, so this versioning code, while accurate to
- // the original engine, does not do much.
- // (Hopefully this was an optimisation mechanism in SCI engine and not a
- // clever thing to keep the same palette submitted many times from
- // overwriting other palette entries.)
- if (palette.timestamp == _version) {
- return;
- }
-
- Palette oldSourcePalette(_sourcePalette);
- mergePaletteInternal(&_sourcePalette, &palette);
-
- if (!_versionUpdated && _sourcePalette != oldSourcePalette) {
- ++_version;
- _versionUpdated = true;
- }
-
- // Technically this information is supposed to be persisted through a
- // HunkPalette object; right now it would just be lost once the temporary
- // palette was destroyed.
- palette.timestamp = _version;
-}
-
-bool GfxPalette32::kernelSetFromResource(GuiResourceId resourceId, bool force) {
- // TODO: In SCI32, palettes that come from resources come in as
- // HunkPalette objects, not SOLPalette objects. The HunkPalettes
- // have some extra persistence stuff associated with them, such that
- // when they are passed to GfxPalette32::submit, they would get the
- // version number of GfxPalette32 assigned to them.
- Palette palette;
+bool GfxPalette32::loadPalette(const GuiResourceId resourceId) {
+ Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
- if (createPaletteFromResourceInternal(resourceId, &palette)) {
- submit(palette);
- return true;
+ if (!palResource) {
+ return false;
}
- return false;
+ const HunkPalette palette(palResource->data);
+ submit(palette);
+ return true;
}
-// In SCI32 engine this method is SOLPalette::Match(Rgb24 *)
-// and is called as PaletteMgr.Current().Match(color)
-int16 GfxPalette32::kernelFindColor(uint16 r, uint16 g, uint16 b) {
- // SQ6 SCI32 engine takes the 16-bit r, g, b arguments from the
- // VM and puts them into al, ah, dl. For compatibility, make sure
- // to discard any high bits here too
- r = r & 0xFF;
- g = g & 0xFF;
- b = b & 0xFF;
+int16 GfxPalette32::matchColor(const uint8 r, const uint8 g, const uint8 b) {
int16 bestIndex = 0;
int bestDifference = 0xFFFFF;
int difference;
- // SQ6 DOS really does check only the first 236 entries
- for (int i = 0, channelDifference; i < 236; ++i) {
- difference = _sysPalette.colors[i].r - r;
+ for (int i = 0, channelDifference; i < g_sci->_gfxRemap32->getStartColor(); ++i) {
+ difference = _currentPalette.colors[i].r - r;
difference *= difference;
if (bestDifference <= difference) {
continue;
}
- channelDifference = _sysPalette.colors[i].g - g;
+ channelDifference = _currentPalette.colors[i].g - g;
difference += channelDifference * channelDifference;
if (bestDifference <= difference) {
continue;
}
- channelDifference = _sysPalette.colors[i].b - b;
+ channelDifference = _currentPalette.colors[i].b - b;
difference += channelDifference * channelDifference;
if (bestDifference <= difference) {
continue;
@@ -163,192 +204,158 @@ int16 GfxPalette32::kernelFindColor(uint16 r, uint16 g, uint16 b) {
return bestIndex;
}
-// TODO: set is overridden for the time being to send palettes coming from
-// various draw methods like GfxPicture::drawSci32Vga and GfxView::draw to
-// _nextPalette instead of _sysPalette. In the SCI32 engine, CelObj palettes
-// (which are stored as Hunk palettes) are submitted by GraphicsMgr::FrameOut
-// to PaletteMgr::Submit by way of calls to CelObj::SubmitPalette.
-// GfxPalette::set is very similar to GfxPalette32::submit, except that SCI32
-// does not do any fancy best-fit merging and so does not accept arguments
-// like `force` and `forceRealMerge`.
-void GfxPalette32::set(Palette *newPalette, bool force, bool forceRealMerge) {
- submit(*newPalette);
-}
-
-// In SCI32 engine this method is SOLPalette::Match(Rgb24 *, int, int *, int *)
-// and is used by Remap
-// TODO: Anything that calls GfxPalette::matchColor(int, int, int) is going to
-// match using an algorithm from SCI16 engine right now. This needs to be
-// corrected in the future so either nothing calls
-// GfxPalette::matchColor(int, int, int), or it is fixed to match the other
-// SCI32 algorithms.
-int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable) {
- int16 bestIndex = -1;
- int bestDifference = 0xFFFFF;
- int difference = defaultDifference;
+void GfxPalette32::submit(const Palette &palette) {
+ const Palette oldSourcePalette(_sourcePalette);
+ mergePalette(_sourcePalette, palette);
- // SQ6 DOS really does check only the first 236 entries
- for (int i = 0, channelDifference; i < 236; ++i) {
- if (matchTable[i] == 0) {
- continue;
- }
+ if (!_needsUpdate && _sourcePalette != oldSourcePalette) {
+ ++_version;
+ _needsUpdate = true;
+ }
+}
- difference = _sysPalette.colors[i].r - r;
- difference *= difference;
- if (bestDifference <= difference) {
- continue;
- }
- channelDifference = _sysPalette.colors[i].g - g;
- difference += channelDifference * channelDifference;
- if (bestDifference <= difference) {
- continue;
- }
- channelDifference = _sysPalette.colors[i].b - b;
- difference += channelDifference * channelDifference;
- if (bestDifference <= difference) {
- continue;
- }
- bestDifference = difference;
- bestIndex = i;
+void GfxPalette32::submit(const HunkPalette &hunkPalette) {
+ if (hunkPalette.getVersion() == _version) {
+ return;
}
- // NOTE: This value is only valid if the last index to
- // perform a difference calculation was the best index
- lastCalculatedDifference = difference;
- return bestIndex;
+ const Palette oldSourcePalette(_sourcePalette);
+ const Palette palette = hunkPalette.toPalette();
+ mergePalette(_sourcePalette, palette);
+
+ if (!_needsUpdate && oldSourcePalette != _sourcePalette) {
+ ++_version;
+ _needsUpdate = true;
+ }
+
+ hunkPalette.setVersion(_version);
}
bool GfxPalette32::updateForFrame() {
applyAll();
- _versionUpdated = false;
- // TODO: Implement remapping
- // return g_sci->_gfxFrameout->remapAllTables(_nextPalette != _sysPalette);
- return false;
+ _needsUpdate = false;
+ return g_sci->_gfxRemap32->remapAllTables(_nextPalette != _currentPalette);
}
void GfxPalette32::updateFFrame() {
for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
_nextPalette.colors[i] = _sourcePalette.colors[i];
}
- _versionUpdated = false;
- // TODO: Implement remapping
- // g_sci->_gfxFrameout->remapAllTables(_nextPalette != _sysPalette);
+ _needsUpdate = false;
+ g_sci->_gfxRemap32->remapAllTables(_nextPalette != _currentPalette);
}
-void GfxPalette32::updateHardware() {
- if (_sysPalette == _nextPalette) {
+void GfxPalette32::updateHardware(const bool updateScreen) {
+ if (_currentPalette == _nextPalette) {
return;
}
byte bpal[3 * 256];
- for (int i = 0; i < ARRAYSIZE(_sysPalette.colors); ++i) {
- _sysPalette.colors[i] = _nextPalette.colors[i];
+ for (int i = 0; i < ARRAYSIZE(_currentPalette.colors) - 1; ++i) {
+ _currentPalette.colors[i] = _nextPalette.colors[i];
// NOTE: If the brightness option in the user configuration file is set,
- // SCI engine adjusts palette brightnesses here by mapping RGB values to values
- // in some hard-coded brightness tables. There is no reason on modern hardware
- // to implement this, unless it is discovered that some game uses a non-standard
- // brightness setting by default
- if (_sysPalette.colors[i].used) {
- bpal[i * 3 ] = _sysPalette.colors[i].r;
- bpal[i * 3 + 1] = _sysPalette.colors[i].g;
- bpal[i * 3 + 2] = _sysPalette.colors[i].b;
- }
+ // SCI engine adjusts palette brightnesses here by mapping RGB values to
+ // values in some hard-coded brightness tables. There is no reason on
+ // modern hardware to implement this, unless it is discovered that some
+ // game uses a non-standard brightness setting by default
+
+ // All color entries MUST be copied, not just "used" entries, otherwise
+ // uninitialised memory from bpal makes its way into the system palette.
+ // This would not normally be a problem, except that games sometimes use
+ // unused palette entries. e.g. Phant1 title screen references palette
+ // entries outside its own palette, so will render garbage colors where
+ // the game expects them to be black
+ bpal[i * 3 ] = _currentPalette.colors[i].r;
+ bpal[i * 3 + 1] = _currentPalette.colors[i].g;
+ bpal[i * 3 + 2] = _currentPalette.colors[i].b;
+ }
+
+ if (g_sci->getPlatform() != Common::kPlatformMacintosh) {
+ // The last color must always be white
+ bpal[255 * 3 ] = 255;
+ bpal[255 * 3 + 1] = 255;
+ bpal[255 * 3 + 2] = 255;
+ } else {
+ bpal[255 * 3 ] = 0;
+ bpal[255 * 3 + 1] = 0;
+ bpal[255 * 3 + 2] = 0;
}
g_system->getPaletteManager()->setPalette(bpal, 0, 256);
- g_sci->getEventManager()->updateScreen();
-}
-
-void GfxPalette32::applyAll() {
- applyVary();
- applyCycles();
- applyFade();
+ if (updateScreen) {
+ g_system->updateScreen();
+ }
}
-#pragma mark -
-#pragma mark Colour look-up
-
-bool GfxPalette32::loadClut(uint16 clutId) {
- // loadClut() will load a color lookup table from a clu file and set
- // the palette found in the file. This is to be used with Phantasmagoria 2.
-
- unloadClut();
-
- Common::String filename = Common::String::format("%d.clu", clutId);
- Common::File clut;
-
- if (!clut.open(filename) || clut.size() != 0x10000 + 236 * 3)
- return false;
-
- // Read in the lookup table
- // It maps each RGB565 color to a palette index
- _clutTable = new byte[0x10000];
- clut.read(_clutTable, 0x10000);
-
- Palette pal;
- memset(&pal, 0, sizeof(Palette));
+Palette GfxPalette32::getPaletteFromResource(const GuiResourceId resourceId) const {
+ Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
- // Setup 1:1 mapping
- for (int i = 0; i < 256; i++) {
- pal.mapping[i] = i;
- }
-
- // Now load in the palette
- for (int i = 1; i <= 236; i++) {
- pal.colors[i].used = 1;
- pal.colors[i].r = clut.readByte();
- pal.colors[i].g = clut.readByte();
- pal.colors[i].b = clut.readByte();
+ if (!palResource) {
+ error("Could not load vary palette %d", resourceId);
}
- set(&pal, true);
- setOnScreen();
- return true;
+ const HunkPalette rawPalette(palResource->data);
+ return rawPalette.toPalette();
}
-byte GfxPalette32::matchClutColor(uint16 color) {
- // Match a color in RGB565 format to a palette index based on the loaded CLUT
- assert(_clutTable);
- return _clutTable[color];
+void GfxPalette32::mergePalette(Palette &to, const Palette &from) {
+ // The last color is always white in SCI, so it is not copied. (Some
+ // palettes, particularly in KQ7, try to set the last color, which causes
+ // unnecessary palette updates since the last color is forced by SSCI to a
+ // specific value)
+ for (int i = 0; i < ARRAYSIZE(to.colors) - 1; ++i) {
+ if (from.colors[i].used) {
+ to.colors[i] = from.colors[i];
+ }
+ }
}
-void GfxPalette32::unloadClut() {
- // This will only unload the actual table, but not reset any palette
- delete[] _clutTable;
- _clutTable = nullptr;
+void GfxPalette32::applyAll() {
+ applyVary();
+ applyCycles();
+ applyFade();
}
#pragma mark -
#pragma mark Varying
-inline bool GfxPalette32::createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const {
- Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, paletteId), false);
+void GfxPalette32::setVary(const Palette &target, const int16 percent, const int32 ticks, const int16 fromColor, const int16 toColor) {
+ setTarget(target);
+ setVaryTime(percent, ticks);
- if (!palResource) {
- return false;
+ if (fromColor > -1) {
+ _varyFromColor = fromColor;
+ }
+ if (toColor > -1) {
+ assert(toColor < 256);
+ _varyToColor = toColor;
+ }
+}
+
+void GfxPalette32::setVaryPercent(const int16 percent, const int32 ticks) {
+ if (_varyTargetPalette != nullptr) {
+ setVaryTime(percent, ticks);
}
- createFromData(palResource->data, palResource->size, out);
- return true;
+ // NOTE: SSCI had two additional parameters for this function to change the
+ // `_varyFromColor`, but they were always hardcoded to be ignored
}
-inline Palette GfxPalette32::getPaletteFromResourceInternal(const GuiResourceId paletteId) const {
- Palette palette;
- if (!createPaletteFromResourceInternal(paletteId, &palette)) {
- error("Could not load vary target %d", paletteId);
+void GfxPalette32::setVaryTime(const int32 time) {
+ if (_varyTargetPalette != nullptr) {
+ setVaryTime(_varyTargetPercent, time);
}
- return palette;
}
-inline void GfxPalette32::setVaryTimeInternal(const int16 percent, const int time) {
+void GfxPalette32::setVaryTime(const int16 percent, const int32 ticks) {
_varyLastTick = g_sci->getTickCount();
- if (!time || _varyPercent == percent) {
+ if (!ticks || _varyPercent == percent) {
_varyDirection = 0;
_varyTargetPercent = _varyPercent = percent;
} else {
- _varyTime = time / (percent - _varyPercent);
+ _varyTime = ticks / (percent - _varyPercent);
_varyTargetPercent = percent;
if (_varyTime > 0) {
@@ -363,74 +370,6 @@ inline void GfxPalette32::setVaryTimeInternal(const int16 percent, const int tim
}
}
-// TODO: This gets called *a lot* in at least the first scene
-// of SQ6. Optimisation would not be the worst idea in the world.
-void GfxPalette32::kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor) {
- Palette palette = getPaletteFromResourceInternal(paletteId);
- setVary(&palette, percent, time, fromColor, toColor);
-}
-
-void GfxPalette32::kernelPalVaryMergeTarget(GuiResourceId paletteId) {
- Palette palette = getPaletteFromResourceInternal(paletteId);
- mergeTarget(&palette);
-}
-
-void GfxPalette32::kernelPalVarySetTarget(GuiResourceId paletteId) {
- Palette palette = getPaletteFromResourceInternal(paletteId);
- setTarget(&palette);
-}
-
-void GfxPalette32::kernelPalVarySetStart(GuiResourceId paletteId) {
- Palette palette = getPaletteFromResourceInternal(paletteId);
- setStart(&palette);
-}
-
-void GfxPalette32::kernelPalVaryMergeStart(GuiResourceId paletteId) {
- Palette palette = getPaletteFromResourceInternal(paletteId);
- mergeStart(&palette);
-}
-
-void GfxPalette32::kernelPalVaryPause(bool pause) {
- if (pause) {
- varyPause();
- } else {
- varyOn();
- }
-}
-
-void GfxPalette32::setVary(const Palette *const target, const int16 percent, const int time, const int16 fromColor, const int16 toColor) {
- setTarget(target);
- setVaryTimeInternal(percent, time);
-
- if (fromColor > -1) {
- _varyFromColor = fromColor;
- }
- if (toColor > -1) {
- assert(toColor < 256);
- _varyToColor = toColor;
- }
-}
-
-void GfxPalette32::setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate) {
- if (_varyTargetPalette != nullptr) {
- setVaryTimeInternal(percent, time);
- }
-
- // This looks like a mistake in the actual SCI engine (both SQ6 and Lighthouse);
- // the values are always hardcoded to -1 in kPalVary, so this code can never
- // actually be executed
- if (fromColor > -1) {
- _varyFromColor = fromColor;
- }
- if (fromColorAlternate > -1) {
- _varyFromColor = fromColorAlternate;
- }
-}
-
-int16 GfxPalette32::getVaryPercent() const {
- return ABS(_varyPercent);
-}
-
void GfxPalette32::varyOff() {
_varyNumTimesPaused = 0;
_varyPercent = _varyTargetPercent = 0;
@@ -449,14 +388,6 @@ void GfxPalette32::varyOff() {
}
}
-void GfxPalette32::mergeTarget(const Palette *const palette) {
- if (_varyTargetPalette != nullptr) {
- mergePaletteInternal(_varyTargetPalette, palette);
- } else {
- _varyTargetPalette = new Palette(*palette);
- }
-}
-
void GfxPalette32::varyPause() {
_varyDirection = 0;
++_varyNumTimesPaused;
@@ -467,51 +398,44 @@ void GfxPalette32::varyOn() {
--_varyNumTimesPaused;
}
- if (_varyTargetPalette != nullptr && _varyNumTimesPaused == 0 && _varyPercent != _varyTargetPercent) {
- if (_varyTime == 0) {
- _varyPercent = _varyTargetPercent;
- } else if (_varyTargetPercent < _varyPercent) {
- _varyDirection = -1;
+ if (_varyTargetPalette != nullptr && _varyNumTimesPaused == 0) {
+ if (_varyPercent != _varyTargetPercent && _varyTime != 0) {
+ _varyDirection = (_varyTargetPercent - _varyPercent > 0) ? 1 : -1;
} else {
- _varyDirection = 1;
+ _varyPercent = _varyTargetPercent;
}
}
}
-void GfxPalette32::setVaryTime(const int time) {
- if (_varyTargetPalette == nullptr) {
- return;
- }
-
- setVaryTimeInternal(_varyTargetPercent, time);
+void GfxPalette32::setTarget(const Palette &palette) {
+ delete _varyTargetPalette;
+ _varyTargetPalette = new Palette(palette);
}
-void GfxPalette32::setTarget(const Palette *const palette) {
- if (_varyTargetPalette != nullptr) {
- delete _varyTargetPalette;
- }
-
- _varyTargetPalette = new Palette(*palette);
+void GfxPalette32::setStart(const Palette &palette) {
+ delete _varyStartPalette;
+ _varyStartPalette = new Palette(palette);
}
-void GfxPalette32::setStart(const Palette *const palette) {
+void GfxPalette32::mergeStart(const Palette &palette) {
if (_varyStartPalette != nullptr) {
- delete _varyStartPalette;
+ mergePalette(*_varyStartPalette, palette);
+ } else {
+ _varyStartPalette = new Palette(palette);
}
-
- _varyStartPalette = new Palette(*palette);
}
-void GfxPalette32::mergeStart(const Palette *const palette) {
- if (_varyStartPalette != nullptr) {
- mergePaletteInternal(_varyStartPalette, palette);
+void GfxPalette32::mergeTarget(const Palette &palette) {
+ if (_varyTargetPalette != nullptr) {
+ mergePalette(*_varyTargetPalette, palette);
} else {
- _varyStartPalette = new Palette(*palette);
+ _varyTargetPalette = new Palette(palette);
}
}
void GfxPalette32::applyVary() {
- while (g_sci->getTickCount() - _varyLastTick > (uint32)_varyTime && _varyDirection != 0) {
+ const uint32 now = g_sci->getTickCount();
+ while ((int32)(now - _varyLastTick) > _varyTime && _varyDirection != 0) {
_varyLastTick += _varyTime;
if (_varyPercent == _varyTargetPercent) {
@@ -522,7 +446,7 @@ void GfxPalette32::applyVary() {
}
if (_varyPercent == 0 || _varyTargetPalette == nullptr) {
- for (int i = 0, len = ARRAYSIZE(_nextPalette.colors); i < len; ++i) {
+ for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
if (_varyStartPalette != nullptr && i >= _varyFromColor && i <= _varyToColor) {
_nextPalette.colors[i] = _varyStartPalette->colors[i];
} else {
@@ -530,7 +454,7 @@ void GfxPalette32::applyVary() {
}
}
} else {
- for (int i = 0, len = ARRAYSIZE(_nextPalette.colors); i < len; ++i) {
+ for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
if (i >= _varyFromColor && i <= _varyToColor) {
Color targetColor = _varyTargetPalette->colors[i];
Color sourceColor;
@@ -561,89 +485,71 @@ void GfxPalette32::applyVary() {
}
}
-#pragma mark -
-#pragma mark Cycling
-
-inline void GfxPalette32::clearCycleMap(const uint16 fromColor, const uint16 numColorsToClear) {
- bool *mapEntry = _cycleMap + fromColor;
- const bool *lastEntry = _cycleMap + numColorsToClear;
- while (mapEntry < lastEntry) {
- *mapEntry++ = false;
- }
+void GfxPalette32::kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int32 ticks, const int16 fromColor, const int16 toColor) {
+ const Palette palette = getPaletteFromResource(paletteId);
+ setVary(palette, percent, ticks, fromColor, toColor);
}
-inline void GfxPalette32::setCycleMap(const uint16 fromColor, const uint16 numColorsToSet) {
- bool *mapEntry = _cycleMap + fromColor;
- const bool *lastEntry = _cycleMap + numColorsToSet;
- while (mapEntry < lastEntry) {
- if (*mapEntry != false) {
- error("Cycles intersect");
- }
- *mapEntry++ = true;
- }
+void GfxPalette32::kernelPalVaryMergeTarget(const GuiResourceId paletteId) {
+ const Palette palette = getPaletteFromResource(paletteId);
+ mergeTarget(palette);
}
-inline PalCycler *GfxPalette32::getCycler(const uint16 fromColor) {
- const int numCyclers = ARRAYSIZE(_cyclers);
-
- for (int cyclerIndex = 0; cyclerIndex < numCyclers; ++cyclerIndex) {
- PalCycler *cycler = _cyclers[cyclerIndex];
- if (cycler != nullptr && cycler->fromColor == fromColor) {
- return cycler;
- }
- }
+void GfxPalette32::kernelPalVarySetTarget(const GuiResourceId paletteId) {
+ const Palette palette = getPaletteFromResource(paletteId);
+ setTarget(palette);
+}
- return nullptr;
+void GfxPalette32::kernelPalVarySetStart(const GuiResourceId paletteId) {
+ const Palette palette = getPaletteFromResource(paletteId);
+ setStart(palette);
}
-inline void doCycleInternal(PalCycler *cycler, const int16 speed) {
- int16 currentCycle = cycler->currentCycle;
- const uint16 numColorsToCycle = cycler->numColorsToCycle;
+void GfxPalette32::kernelPalVaryMergeStart(const GuiResourceId paletteId) {
+ const Palette palette = getPaletteFromResource(paletteId);
+ mergeStart(palette);
+}
- if (cycler->direction == 0) {
- currentCycle = (currentCycle - (speed % numColorsToCycle)) + numColorsToCycle;
+void GfxPalette32::kernelPalVaryPause(const bool pause) {
+ if (pause) {
+ varyPause();
} else {
- currentCycle = currentCycle + speed;
+ varyOn();
}
-
- cycler->currentCycle = (uint8) (currentCycle % numColorsToCycle);
}
+#pragma mark -
+#pragma mark Cycling
+
void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay) {
assert(fromColor < toColor);
- int cyclerIndex;
- const int numCyclers = ARRAYSIZE(_cyclers);
-
PalCycler *cycler = getCycler(fromColor);
if (cycler != nullptr) {
clearCycleMap(fromColor, cycler->numColorsToCycle);
} else {
- for (cyclerIndex = 0; cyclerIndex < numCyclers; ++cyclerIndex) {
- if (_cyclers[cyclerIndex] == nullptr) {
- cycler = new PalCycler;
- _cyclers[cyclerIndex] = cycler;
+ for (int i = 0; i < kNumCyclers; ++i) {
+ if (_cyclers[i] == nullptr) {
+ _cyclers[i] = cycler = new PalCycler;
break;
}
}
}
- // SCI engine overrides the first oldest cycler that it finds where
- // “oldest” is determined by the difference between the tick and now
+ // If there are no free cycler slots, SCI engine overrides the first oldest
+ // cycler that it finds, where "oldest" is determined by the difference
+ // between the tick and now
if (cycler == nullptr) {
- int maxUpdateDelta = -1;
- // Optimization: Unlike actual SCI (SQ6) engine, we call
- // getTickCount only once and store it, instead of calling it
- // twice on each iteration through the loop
const uint32 now = g_sci->getTickCount();
+ uint32 minUpdateDelta = 0xFFFFFFFF;
- for (cyclerIndex = 0; cyclerIndex < numCyclers; ++cyclerIndex) {
- PalCycler *candidate = _cyclers[cyclerIndex];
+ for (int i = 0; i < kNumCyclers; ++i) {
+ PalCycler *const candidate = _cyclers[i];
- const int32 updateDelta = now - candidate->lastUpdateTick;
- if (updateDelta >= maxUpdateDelta) {
- maxUpdateDelta = updateDelta;
+ const uint32 updateDelta = now - candidate->lastUpdateTick;
+ if (updateDelta < minUpdateDelta) {
+ minUpdateDelta = updateDelta;
cycler = candidate;
}
}
@@ -651,11 +557,14 @@ void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const in
clearCycleMap(cycler->fromColor, cycler->numColorsToCycle);
}
- const uint16 numColorsToCycle = 1 + ((uint8) toColor) - fromColor;
- cycler->fromColor = (uint8) fromColor;
- cycler->numColorsToCycle = (uint8) numColorsToCycle;
- cycler->currentCycle = (uint8) fromColor;
- cycler->direction = direction < 0 ? PalCycleBackward : PalCycleForward;
+ uint16 numColorsToCycle = toColor - fromColor;
+ if (g_sci->_features->hasNewPaletteCode()) {
+ numColorsToCycle += 1;
+ }
+ cycler->fromColor = fromColor;
+ cycler->numColorsToCycle = numColorsToCycle;
+ cycler->currentCycle = fromColor;
+ cycler->direction = direction < 0 ? kPalCycleBackward : kPalCycleForward;
cycler->delay = delay;
cycler->lastUpdateTick = g_sci->getTickCount();
cycler->numTimesPaused = 0;
@@ -664,30 +573,30 @@ void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const in
}
void GfxPalette32::doCycle(const uint8 fromColor, const int16 speed) {
- PalCycler *cycler = getCycler(fromColor);
+ PalCycler *const cycler = getCycler(fromColor);
if (cycler != nullptr) {
cycler->lastUpdateTick = g_sci->getTickCount();
- doCycleInternal(cycler, speed);
+ updateCycler(*cycler, speed);
}
}
void GfxPalette32::cycleOn(const uint8 fromColor) {
- PalCycler *cycler = getCycler(fromColor);
+ PalCycler *const cycler = getCycler(fromColor);
if (cycler != nullptr && cycler->numTimesPaused > 0) {
--cycler->numTimesPaused;
}
}
void GfxPalette32::cyclePause(const uint8 fromColor) {
- PalCycler *cycler = getCycler(fromColor);
+ PalCycler *const cycler = getCycler(fromColor);
if (cycler != nullptr) {
++cycler->numTimesPaused;
}
}
void GfxPalette32::cycleAllOn() {
- for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
- PalCycler *cycler = _cyclers[i];
+ for (int i = 0; i < kNumCyclers; ++i) {
+ PalCycler *const cycler = _cyclers[i];
if (cycler != nullptr && cycler->numTimesPaused > 0) {
--cycler->numTimesPaused;
}
@@ -695,16 +604,10 @@ void GfxPalette32::cycleAllOn() {
}
void GfxPalette32::cycleAllPause() {
- // TODO: The SCI SQ6 cycleAllPause function does not seem to perform
- // nullptr checking?? This would definitely cause null pointer
- // dereference in SCI code. I have not seen anything actually call
- // this function yet, so it is possible it is just unused and broken
- // in SCI SQ6. This assert exists so that if this function is called,
- // it is noticed and can be rechecked in the actual engine.
- // Obviously this code *does* do nullptr checks instead of crashing. :)
- assert(0);
- for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
- PalCycler *cycler = _cyclers[i];
+ // NOTE: The original engine did not check for null pointers in the
+ // palette cyclers pointer array.
+ for (int i = 0; i < kNumCyclers; ++i) {
+ PalCycler *const cycler = _cyclers[i];
if (cycler != nullptr) {
// This seems odd, because currentCycle is 0..numColorsPerCycle,
// but fromColor is 0..255. When applyAllCycles runs, the values
@@ -715,8 +618,8 @@ void GfxPalette32::cycleAllPause() {
applyAllCycles();
- for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
- PalCycler *cycler = _cyclers[i];
+ for (int i = 0; i < kNumCyclers; ++i) {
+ PalCycler *const cycler = _cyclers[i];
if (cycler != nullptr) {
++cycler->numTimesPaused;
}
@@ -724,8 +627,8 @@ void GfxPalette32::cycleAllPause() {
}
void GfxPalette32::cycleOff(const uint8 fromColor) {
- for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
- PalCycler *cycler = _cyclers[i];
+ for (int i = 0; i < kNumCyclers; ++i) {
+ PalCycler *const cycler = _cyclers[i];
if (cycler != nullptr && cycler->fromColor == fromColor) {
clearCycleMap(fromColor, cycler->numColorsToCycle);
delete cycler;
@@ -736,8 +639,8 @@ void GfxPalette32::cycleOff(const uint8 fromColor) {
}
void GfxPalette32::cycleAllOff() {
- for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
- PalCycler *cycler = _cyclers[i];
+ for (int i = 0; i < kNumCyclers; ++i) {
+ PalCycler *const cycler = _cyclers[i];
if (cycler != nullptr) {
clearCycleMap(cycler->fromColor, cycler->numColorsToCycle);
delete cycler;
@@ -746,16 +649,57 @@ void GfxPalette32::cycleAllOff() {
}
}
+void GfxPalette32::updateCycler(PalCycler &cycler, const int16 speed) {
+ int16 currentCycle = cycler.currentCycle;
+ const uint16 numColorsToCycle = cycler.numColorsToCycle;
+
+ if (cycler.direction == kPalCycleBackward) {
+ currentCycle = (currentCycle - (speed % numColorsToCycle)) + numColorsToCycle;
+ } else {
+ currentCycle = currentCycle + speed;
+ }
+
+ cycler.currentCycle = currentCycle % numColorsToCycle;
+}
+
+void GfxPalette32::clearCycleMap(const uint16 fromColor, const uint16 numColorsToClear) {
+ bool *mapEntry = _cycleMap + fromColor;
+ const bool *const lastEntry = _cycleMap + numColorsToClear;
+ while (mapEntry < lastEntry) {
+ *mapEntry++ = false;
+ }
+}
+
+void GfxPalette32::setCycleMap(const uint16 fromColor, const uint16 numColorsToSet) {
+ bool *mapEntry = _cycleMap + fromColor;
+ const bool *const lastEntry = _cycleMap + numColorsToSet;
+ while (mapEntry < lastEntry) {
+ if (*mapEntry != false) {
+ error("Cycles intersect");
+ }
+ *mapEntry++ = true;
+ }
+}
+
+PalCycler *GfxPalette32::getCycler(const uint16 fromColor) {
+ for (int cyclerIndex = 0; cyclerIndex < kNumCyclers; ++cyclerIndex) {
+ PalCycler *cycler = _cyclers[cyclerIndex];
+ if (cycler != nullptr && cycler->fromColor == fromColor) {
+ return cycler;
+ }
+ }
+
+ return nullptr;
+}
+
void GfxPalette32::applyAllCycles() {
Color paletteCopy[256];
memcpy(paletteCopy, _nextPalette.colors, sizeof(Color) * 256);
- for (int cyclerIndex = 0, numCyclers = ARRAYSIZE(_cyclers); cyclerIndex < numCyclers; ++cyclerIndex) {
- PalCycler *cycler = _cyclers[cyclerIndex];
+ for (int i = 0; i < kNumCyclers; ++i) {
+ PalCycler *const cycler = _cyclers[i];
if (cycler != nullptr) {
- cycler->currentCycle = (uint8) ((((int) cycler->currentCycle) + 1) % cycler->numColorsToCycle);
- // Disassembly was not fully evaluated to verify this is exactly the same
- // as the code from applyCycles, but it appeared to be at a glance
+ cycler->currentCycle = (((int) cycler->currentCycle) + 1) % cycler->numColorsToCycle;
for (int j = 0; j < cycler->numColorsToCycle; j++) {
_nextPalette.colors[cycler->fromColor + j] = paletteCopy[cycler->fromColor + (cycler->currentCycle + j) % cycler->numColorsToCycle];
}
@@ -767,15 +711,16 @@ void GfxPalette32::applyCycles() {
Color paletteCopy[256];
memcpy(paletteCopy, _nextPalette.colors, sizeof(Color) * 256);
- for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
- PalCycler *cycler = _cyclers[i];
+ const uint32 now = g_sci->getTickCount();
+ for (int i = 0; i < kNumCyclers; ++i) {
+ PalCycler *const cycler = _cyclers[i];
if (cycler == nullptr) {
continue;
}
if (cycler->delay != 0 && cycler->numTimesPaused == 0) {
- while ((cycler->delay + cycler->lastUpdateTick) < g_sci->getTickCount()) {
- doCycleInternal(cycler, 1);
+ while ((cycler->delay + cycler->lastUpdateTick) < now) {
+ updateCycler(*cycler, 1);
cycler->lastUpdateTick += cycler->delay;
}
}
@@ -789,37 +734,38 @@ void GfxPalette32::applyCycles() {
#pragma mark -
#pragma mark Fading
-// NOTE: There are some game scripts (like SQ6 Sierra logo and main menu) that call
-// setFade with numColorsToFade set to 256, but other parts of the engine like
-// processShowStyleNone use 255 instead of 256. It is not clear if this is because
-// the last palette entry is intentionally left unmodified, or if this is a bug
-// in the engine. It certainly seems confused because all other places that accept
-// color ranges typically receive values in the range of 0–255.
-void GfxPalette32::setFade(uint8 percent, uint8 fromColor, uint16 numColorsToFade) {
- if (fromColor > numColorsToFade) {
+void GfxPalette32::setFade(const uint16 percent, const uint8 fromColor, uint16 toColor) {
+ if (fromColor > toColor) {
return;
}
- assert(numColorsToFade <= ARRAYSIZE(_fadeTable));
+ // Some game scripts (like SQ6 Sierra logo and main menu) incorrectly call
+ // setFade with toColor set to 256
+ if (toColor > 255) {
+ toColor = 255;
+ }
- for (int i = fromColor; i < numColorsToFade; i++)
+ for (int i = fromColor; i <= toColor; i++) {
_fadeTable[i] = percent;
+ }
}
void GfxPalette32::fadeOff() {
- setFade(100, 0, 256);
+ setFade(100, 0, 255);
}
void GfxPalette32::applyFade() {
for (int i = 0; i < ARRAYSIZE(_fadeTable); ++i) {
- if (_fadeTable[i] == 100)
+ if (_fadeTable[i] == 100) {
continue;
+ }
Color &color = _nextPalette.colors[i];
- color.r = (int16)color.r * _fadeTable[i] / 100;
- color.g = (int16)color.g * _fadeTable[i] / 100;
- color.b = (int16)color.b * _fadeTable[i] / 100;
+ color.r = MIN(255, (uint16)color.r * _fadeTable[i] / 100);
+ color.g = MIN(255, (uint16)color.g * _fadeTable[i] / 100);
+ color.b = MIN(255, (uint16)color.b * _fadeTable[i] / 100);
}
}
-}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h
index 9da217bf31..c4cfb35096 100644
--- a/engines/sci/graphics/palette32.h
+++ b/engines/sci/graphics/palette32.h
@@ -23,28 +23,142 @@
#ifndef SCI_GRAPHICS_PALETTE32_H
#define SCI_GRAPHICS_PALETTE32_H
-#include "sci/graphics/palette.h"
-
namespace Sci {
+
+#pragma mark HunkPalette
+
+/**
+ * HunkPalette represents a raw palette resource read from disk. The data
+ * structure of a HunkPalette allows palettes to be smaller than 256 colors. It
+ * also allows multiple palettes to be stored in one HunkPalette, though in
+ * SCI32 games there seems to only ever be one palette per HunkPalette.
+ */
+class HunkPalette {
+public:
+ HunkPalette(byte *rawPalette);
+
+ /**
+ * Gets the version of the palette. Used to avoid resubmitting a HunkPalette
+ * which has already been submitted for the next frame.
+ */
+ uint32 getVersion() const { return _version; }
+
+ /**
+ * Sets the version of the palette.
+ */
+ void setVersion(const uint32 version) const;
+
+ /**
+ * Converts the hunk palette to a standard Palette.
+ */
+ const Palette toPalette() const;
+
+private:
+ enum {
+ /**
+ * The size of the HunkPalette header.
+ */
+ kHunkPaletteHeaderSize = 13,
+
+ /**
+ * The size of a palette entry header.
+ */
+ kEntryHeaderSize = 22,
+
+ /**
+ * The offset of the hunk palette version within the palette entry
+ * header.
+ */
+ kEntryVersionOffset = 18
+ };
+
+ /**
+ * The header for a palette inside the HunkPalette.
+ */
+ struct EntryHeader {
+ /**
+ * The start color.
+ */
+ uint8 startColor;
+
+ /**
+ * The number of palette colors in this entry.
+ */
+ uint16 numColors;
+
+ /**
+ * The default `used` flag.
+ */
+ bool used;
+
+ /**
+ * Whether or not all palette entries share the same `used` value in
+ * `defaultFlag`.
+ */
+ bool sharedUsed;
+
+ /**
+ * The palette version.
+ */
+ uint32 version;
+ };
+
+ /**
+ * The version number from the last time this palette was submitted to
+ * GfxPalette32.
+ */
+ mutable uint32 _version;
+
+ /**
+ * The number of palettes stored in the hunk palette. In SCI32 games this is
+ * always 1.
+ */
+ uint8 _numPalettes;
+
+ /**
+ * The raw palette data for this hunk palette.
+ */
+ byte *_data;
+
+ /**
+ * Returns a struct that describes the palette held by this HunkPalette. The
+ * entry header is reconstructed on every call from the raw palette data.
+ */
+ const EntryHeader getEntryHeader() const;
+
+ /**
+ * Returns a pointer to the palette data within the hunk palette.
+ */
+ byte *getPalPointer() const {
+ return _data + kHunkPaletteHeaderSize + (2 * _numPalettes);
+ }
+};
+
+#pragma mark -
+#pragma mark PalCycler
+
enum PalCyclerDirection {
- PalCycleBackward = 0,
- PalCycleForward = 1
+ kPalCycleBackward = 0,
+ kPalCycleForward = 1
};
+/**
+ * PalCycler represents a range of palette entries that are rotated on a timer.
+ */
struct PalCycler {
/**
- * The color index of the palette cycler. This value is effectively used as the ID for the
- * cycler.
+ * The color index of this palette cycler. This value is used as the unique
+ * key for this PalCycler object.
*/
uint8 fromColor;
/**
- * The number of palette slots which are cycled by the palette cycler.
+ * The number of palette slots which are to be cycled by this cycler.
*/
uint16 numColorsToCycle;
/**
- * The position of the cursor in its cycle.
+ * The current position of the first palette entry.
*/
uint8 currentCycle;
@@ -54,15 +168,15 @@ struct PalCycler {
PalCyclerDirection direction;
/**
- * The cycle tick at the last time the cycler’s currentCycle was updated.
- * 795 days of game time ought to be enough for everyone? :)
+ * The last tick the cycler cycled.
*/
uint32 lastUpdateTick;
/**
- * The amount of time in ticks each cycle should take to complete. In other words,
- * the higher the delay, the slower the cycle animation. If delay is 0, the cycler
- * does not automatically cycle and needs to be pumped manually with DoCycle.
+ * The amount of time in ticks each cycle should take to complete. In other
+ * words, the higher the delay, the slower the cycle animation. If delay is
+ * 0, the cycler does not automatically cycle and needs to be cycled
+ * manually by calling `doCycle`.
*/
int16 delay;
@@ -72,105 +186,220 @@ struct PalCycler {
uint16 numTimesPaused;
};
-class GfxPalette32 : public GfxPalette {
+#pragma mark -
+#pragma mark GfxPalette32
+
+class GfxPalette32 {
public:
- GfxPalette32(ResourceManager *resMan, GfxScreen *screen);
+ GfxPalette32(ResourceManager *resMan);
~GfxPalette32();
+ void saveLoadWithSerializer(Common::Serializer &s);
+
+ /**
+ * Gets the palette that will be use for the next frame.
+ */
+ inline const Palette &getNextPalette() const { return _nextPalette; };
+
+ /**
+ * Gets the palette that is used for the current frame.
+ */
+ inline const Palette &getCurrentPalette() const { return _currentPalette; };
+
+ /**
+ * Loads a palette into GfxPalette32 with the given resource ID.
+ */
+ bool loadPalette(const GuiResourceId resourceId);
+
+ /**
+ * Finds the nearest color in the current palette matching the given RGB
+ * value.
+ */
+ int16 matchColor(const uint8 r, const uint8 g, const uint8 b);
+
+ /**
+ * Submits a palette to display. Entries marked as "used" in the submitted
+ * palette are merged into `_sourcePalette`.
+ */
+ void submit(const Palette &palette);
+ void submit(const HunkPalette &palette);
+
+ /**
+ * Applies all fades, cycles, remaps, and varies for the current frame to
+ * `nextPalette`.
+ */
+ bool updateForFrame();
+
+ /**
+ * Copies all palette entries from `sourcePalette` to `nextPalette` and
+ * applies remaps. Unlike `updateForFrame`, this call does not apply fades,
+ * cycles, or varies.
+ */
+ void updateFFrame();
+
+ /**
+ * Copies all entries from `nextPalette` to `currentPalette` and updates the
+ * backend's raw palette.
+ *
+ * @param updateScreen If true, this call will also tell the backend to draw
+ * to the screen.
+ */
+ void updateHardware(const bool updateScreen = true);
+
private:
- // NOTE: currentPalette in SCI engine is called _sysPalette
- // here.
+ ResourceManager *_resMan;
/**
- * The palette revision version. Increments once per game
- * loop that changes the source palette. TODO: Possibly
- * other areas also change _version, double-check once it
- * is all implemented.
+ * The palette revision version. Increments once per game loop that changes
+ * the source palette.
*/
uint32 _version;
/**
- * Whether or not the palette manager version was updated
- * during this loop.
+ * Whether or not the hardware palette needs updating.
+ */
+ bool _needsUpdate;
+
+ /**
+ * The currently displayed palette.
*/
- bool _versionUpdated;
+ Palette _currentPalette;
/**
- * The unmodified source palette loaded by kPalette. Additional
- * palette entries may be mixed into the source palette by
- * CelObj objects, which contain their own palettes.
+ * The unmodified source palette loaded by kPalette. Additional palette
+ * entries may be mixed into the source palette by CelObj objects, which
+ * contain their own palettes.
*/
Palette _sourcePalette;
/**
* The palette to be used when the hardware is next updated.
- * On update, _nextPalette is transferred to _sysPalette.
+ * On update, `_nextPalette` is transferred to `_currentPalette`.
*/
Palette _nextPalette;
- bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const;
- Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const;
+ /**
+ * Creates and returns a new Palette object with data from the given
+ * resource ID.
+ */
+ Palette getPaletteFromResource(const GuiResourceId paletteId) const;
+
+ /**
+ * Merges used colors in the `from` palette into the `to` palette.
+ */
+ void mergePalette(Palette &to, const Palette &from);
+ /**
+ * Applies all varies, cycles, and fades to `_nextPalette`.
+ */
+ void applyAll();
+
+#pragma mark -
+#pragma mark Varying
public:
- virtual void saveLoadWithSerializer(Common::Serializer &s) override;
- const Palette *getNextPalette() const;
+ /**
+ * Blends the `target` palette into the current palette over `time` ticks.
+ *
+ * @param target The target palette.
+ * @param percent The amount that the target palette should be blended into
+ * the source palette by the end of the vary.
+ * @param ticks The number of ticks that it should take for the blend to be
+ * completed.
+ * @param fromColor The first palette entry that should be blended.
+ * @param toColor The last palette entry that should be blended.
+ */
+ void setVary(const Palette &target, const int16 percent, const int32 ticks, const int16 fromColor, const int16 toColor);
- bool kernelSetFromResource(GuiResourceId resourceId, bool force) override;
- int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override;
- void set(Palette *newPalette, bool force, bool forceRealMerge = false) override;
- int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable);
+ /**
+ * Gets the current vary blend amount.
+ */
+ inline int16 getVaryPercent() const { return ABS(_varyPercent); }
/**
- * Submits a palette to display. Entries marked as “used” in the
- * submitted palette are merged into the existing entries of
- * _sourcePalette.
+ * Changes the percentage of the current vary to `percent`, to be completed
+ * over `time` ticks, if there is a currently active vary target palette.
*/
- void submit(Palette &palette);
+ void setVaryPercent(const int16 percent, const int32 time);
- bool updateForFrame();
- void updateFFrame();
- void updateHardware();
- void applyAll();
+ /**
+ * Changes the amount of time, in ticks, an in-progress palette vary should
+ * take to finish.
+ */
+ void setVaryTime(const int32 ticks);
-#pragma mark -
-#pragma mark color look-up
-private:
/**
- * An optional lookup table used to remap RGB565 colors to a palette
- * index. Used by Phantasmagoria 2 in 8-bit color environments.
+ * Changes the vary percent and time to perform the vary.
*/
- byte *_clutTable;
+ void setVaryTime(const int16 percent, const int32 ticks);
-public:
- bool loadClut(uint16 clutId);
- byte matchClutColor(uint16 color);
- void unloadClut();
+ /**
+ * Removes the active palette vary.
+ */
+ void varyOff();
+
+ /**
+ * Pauses any active palette vary.
+ */
+ void varyPause();
+
+ /**
+ * Unpauses any paused palette vary.
+ */
+ void varyOn();
+
+ /**
+ * Sets the target palette for the blend.
+ */
+ void setTarget(const Palette &palette);
+
+ /**
+ * Sets the start palette for the blend.
+ */
+ void setStart(const Palette &palette);
+
+ /**
+ * Merges a new start palette into the existing start palette.
+ */
+ void mergeStart(const Palette &palette);
+
+ /**
+ * Merges a new target palette into the existing target palette.
+ */
+ void mergeTarget(const Palette &palette);
+
+ /**
+ * Applies any active palette vary to `_nextPalette`.
+ */
+ void applyVary();
+
+ void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int32 ticks, const int16 fromColor, const int16 toColor);
+ void kernelPalVaryMergeTarget(const GuiResourceId paletteId);
+ void kernelPalVarySetTarget(const GuiResourceId paletteId);
+ void kernelPalVarySetStart(const GuiResourceId paletteId);
+ void kernelPalVaryMergeStart(const GuiResourceId paletteId);
+ void kernelPalVaryPause(const bool pause);
-#pragma mark -
-#pragma mark Varying
private:
/**
- * An optional palette used to describe the source colors used
- * in a palette vary operation. If this palette is not specified,
- * sourcePalette is used instead.
+ * An optional palette used to provide source colors for a palette vary
+ * operation. If this palette is not specified, `_sourcePalette` is used
+ * instead.
*/
Palette *_varyStartPalette;
/**
- * An optional palette used to describe the target colors used
- * in a palette vary operation.
+ * An optional palette used to provide target colors for a palette vary
+ * operation.
*/
Palette *_varyTargetPalette;
/**
- * The minimum palette index that has been varied from the
- * source palette. 0–255
+ * The minimum palette index that has been varied from the source palette.
*/
uint8 _varyFromColor;
/**
- * The maximum palette index that is has been varied from the
- * source palette. 0-255
+ * The maximum palette index that has been varied from the source palette.
*/
uint8 _varyToColor;
@@ -180,10 +409,10 @@ private:
uint32 _varyLastTick;
/**
- * The amount of time to elapse, in ticks, between each cycle
- * of a palette vary animation.
+ * The amount of time that should elapse, in ticks, between each cycle of a
+ * palette vary animation.
*/
- int _varyTime;
+ int32 _varyTime;
/**
* The direction of change: -1, 0, or 1.
@@ -191,87 +420,160 @@ private:
int16 _varyDirection;
/**
- * The amount, in percent, that the vary color is currently
- * blended into the source color.
+ * The amount, in percent, that the vary color is currently blended into the
+ * source color.
*/
int16 _varyPercent;
/**
- * The target amount that a vary color will be blended into
- * the source color.
+ * The target amount that a vary color will be blended into the source
+ * color.
*/
int16 _varyTargetPercent;
/**
- * The number of time palette varying has been paused.
+ * The number of times palette varying has been paused.
*/
uint16 _varyNumTimesPaused;
-public:
- void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
- void kernelPalVaryMergeTarget(const GuiResourceId paletteId);
- void kernelPalVarySetTarget(const GuiResourceId paletteId);
- void kernelPalVarySetStart(const GuiResourceId paletteId);
- void kernelPalVaryMergeStart(const GuiResourceId paletteId);
- virtual void kernelPalVaryPause(bool pause) override;
-
- void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
- void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate);
- int16 getVaryPercent() const;
- void varyOff();
- void mergeTarget(const Palette *const palette);
- void varyPause();
- void varyOn();
- void setVaryTime(const int time);
- void setTarget(const Palette *const palette);
- void setStart(const Palette *const palette);
- void mergeStart(const Palette *const palette);
- void setVaryTimeInternal(const int16 percent, const int time);
- void applyVary();
-
#pragma mark -
#pragma mark Cycling
-private:
- // SQ6 defines 10 cyclers
- PalCycler *_cyclers[10];
+public:
+ inline const bool *getCycleMap() const { return _cycleMap; }
/**
- * The cycle map is used to detect overlapping cyclers.
- * According to SCI engine code, when two cyclers overlap,
- * a fatal error has occurred and the engine will display
- * an error and then exit.
+ * Cycle palette entries between `fromColor` and `toColor`, inclusive.
+ * Palette cyclers may not overlap. `fromColor` is used in other methods as
+ * the key for looking up a cycler.
+ *
+ * @param fromColor The first color in the cycle.
+ * @param toColor The last color in the cycle.
+ * @param delay The number of ticks that should elapse between cycles.
+ * @param direction A negative `direction` will cycle backwards instead of
+ * forwards. The numeric value of this argument is ignored;
+ * only its sign is used to determine direction.
*/
- bool _cycleMap[256];
- inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear);
- inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear);
- inline PalCycler *getCycler(uint16 fromColor);
-
-public:
void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay);
+
+ /**
+ * Performs a round of palette cycling.
+ *
+ * @param fromColor The color key for the cycler.
+ * @param speed The number of entries that should be cycled this round.
+ */
void doCycle(const uint8 fromColor, const int16 speed);
+
+ /**
+ * Unpauses the cycler starting at `fromColor`.
+ */
void cycleOn(const uint8 fromColor);
+
+ /**
+ * Pauses the cycler starting at `fromColor`.
+ */
void cyclePause(const uint8 fromColor);
+
+ /**
+ * Unpauses all cyclers.
+ */
void cycleAllOn();
+
+ /**
+ * Pauses all cyclers.
+ */
void cycleAllPause();
+
+ /**
+ * Removes the cycler starting at `fromColor`.
+ */
void cycleOff(const uint8 fromColor);
+
+ /**
+ * Removes all cyclers.
+ */
void cycleAllOff();
+
+private:
+ enum {
+ kNumCyclers = 10
+ };
+
+ PalCycler *_cyclers[kNumCyclers];
+
+ /**
+ * Updates the `currentCycle` of the given `cycler` by `speed` entries.
+ */
+ void updateCycler(PalCycler &cycler, const int16 speed);
+
+ /**
+ * The cycle map is used to detect overlapping cyclers, and to avoid
+ * remapping to palette entries that are being cycled.
+ *
+ * According to SCI engine code, when two cyclers overlap, a fatal error has
+ * occurred and the engine will display an error and then exit.
+ *
+ * The color remapping system avoids attempts to remap to palette entries
+ * that are cycling because they won't be the expected color once the cycler
+ * updates the palette entries.
+ */
+ bool _cycleMap[256];
+
+ /**
+ * Marks `numColorsToClear` colors starting at `fromColor` in the cycle
+ * map as inactive.
+ */
+ void clearCycleMap(const uint16 fromColor, const uint16 numColorsToClear);
+
+ /**
+ * Marks `numColorsToClear` colors starting at `fromColor` in the cycle
+ * map as active.
+ */
+ void setCycleMap(const uint16 fromColor, const uint16 numColorsToClear);
+
+ /**
+ * Gets the cycler object that starts at the given `fromColor`, or NULL if
+ * there is no cycler for that color.
+ */
+ PalCycler *getCycler(const uint16 fromColor);
+
+ /**
+ * Advances all cyclers by one step, regardless of whether or not it is time
+ * to perform another cycle.
+ */
void applyAllCycles();
+
+ /**
+ * Advances, by one step, only the cyclers whose time has come to cycle.
+ */
void applyCycles();
#pragma mark -
#pragma mark Fading
-private:
+public:
/**
- * The fade table records the expected intensity level of each pixel
- * in the palette that will be displayed on the next frame.
+ * Sets the intensity level for a range of palette entries. An intensity of
+ * zero indicates total darkness. Intensity may also be set above 100
+ * percent to increase the intensity of a palette entry.
*/
- byte _fadeTable[256];
+ void setFade(const uint16 percent, const uint8 fromColor, const uint16 toColor);
-public:
- void setFade(const uint8 percent, const uint8 fromColor, const uint16 toColor);
+ /**
+ * Resets the intensity of all palette entries to 100%.
+ */
void fadeOff();
+
+ /**
+ * Applies intensity values to the palette entries in `_nextPalette`.
+ */
void applyFade();
+
+private:
+ /**
+ * The intensity levels of each palette entry, in percent. Defaults to 100.
+ */
+ uint16 _fadeTable[256];
};
-}
+
+} // End of namespace Sci
#endif
diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp
index 2eab391afd..864327feaa 100644
--- a/engines/sci/graphics/picture.cpp
+++ b/engines/sci/graphics/picture.cpp
@@ -35,7 +35,7 @@ namespace Sci {
//#define DEBUG_PICTURE_DRAW
-GfxPicture::GfxPicture(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize)
+GfxPicture::GfxPicture(ResourceManager *resMan, GfxCoordAdjuster16 *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize)
: _resMan(resMan), _coordAdjuster(coordAdjuster), _ports(ports), _screen(screen), _palette(palette), _resourceId(resourceId), _EGAdrawingVisualize(EGAdrawingVisualize) {
assert(resourceId != -1);
initData(resourceId);
@@ -1004,14 +1004,14 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by
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->vectorIsFillMatch(p.x, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA)) == 0) // already filled
@@ -1212,6 +1212,7 @@ void GfxPicture::vectorPatternTexturedBox(Common::Rect box, byte color, byte pri
void GfxPicture::vectorPatternCircle(Common::Rect box, byte size, byte color, byte prio, byte control) {
byte flag = _screen->getDrawingMask(color, prio, control);
+ assert(size < ARRAYSIZE(vectorPatternCircles));
const byte *circleData = vectorPatternCircles[size];
byte bitmap = *circleData;
byte bitNo = 0;
@@ -1219,29 +1220,36 @@ 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 (bitNo == 8) {
+ circleData++;
+ bitmap = *circleData;
+ bitNo = 0;
+ }
if (bitmap & 1) {
_screen->vectorPutPixel(x, y, flag, color, prio, control);
}
bitNo++;
- if (bitNo == 8) {
- circleData++; bitmap = *circleData; bitNo = 0;
- } else {
- bitmap = bitmap >> 1;
- }
+ bitmap >>= 1;
}
}
}
void GfxPicture::vectorPatternTexturedCircle(Common::Rect box, byte size, byte color, byte prio, byte control, byte texture) {
byte flag = _screen->getDrawingMask(color, prio, control);
+ assert(size < ARRAYSIZE(vectorPatternCircles));
const byte *circleData = vectorPatternCircles[size];
byte bitmap = *circleData;
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 (bitNo == 8) {
+ circleData++;
+ bitmap = *circleData;
+ bitNo = 0;
+ }
if (bitmap & 1) {
if (*textureData) {
_screen->vectorPutPixel(x, y, flag, color, prio, control);
@@ -1249,11 +1257,7 @@ void GfxPicture::vectorPatternTexturedCircle(Common::Rect box, byte size, byte c
textureData++;
}
bitNo++;
- if (bitNo == 8) {
- circleData++; bitmap = *circleData; bitNo = 0;
- } else {
- bitmap = bitmap >> 1;
- }
+ bitmap >>= 1;
}
}
}
diff --git a/engines/sci/graphics/picture.h b/engines/sci/graphics/picture.h
index 942fa0f107..1be1ae3004 100644
--- a/engines/sci/graphics/picture.h
+++ b/engines/sci/graphics/picture.h
@@ -38,7 +38,7 @@ enum {
class GfxPorts;
class GfxScreen;
class GfxPalette;
-class GfxCoordAdjuster;
+class GfxCoordAdjuster16;
class ResourceManager;
class Resource;
@@ -48,7 +48,7 @@ class Resource;
*/
class GfxPicture {
public:
- GfxPicture(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize = false);
+ GfxPicture(ResourceManager *resMan, GfxCoordAdjuster16 *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize = false);
~GfxPicture();
GuiResourceId getResourceId();
@@ -84,7 +84,7 @@ private:
void vectorPatternTexturedCircle(Common::Rect box, byte size, byte color, byte prio, byte control, byte texture);
ResourceManager *_resMan;
- GfxCoordAdjuster *_coordAdjuster;
+ GfxCoordAdjuster16 *_coordAdjuster;
GfxPorts *_ports;
GfxScreen *_screen;
GfxPalette *_palette;
diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
index d0de5b5917..aa8cd52d42 100644
--- a/engines/sci/graphics/plane32.cpp
+++ b/engines/sci/graphics/plane32.cpp
@@ -21,12 +21,14 @@
*/
#include "sci/console.h"
+#include "sci/engine/features.h"
#include "sci/engine/kernel.h"
#include "sci/engine/selector.h"
#include "sci/engine/state.h"
#include "sci/graphics/frameout.h"
#include "sci/graphics/lists32.h"
#include "sci/graphics/plane32.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/screen_item32.h"
@@ -42,19 +44,21 @@ void DrawList::add(ScreenItem *screenItem, const Common::Rect &rect) {
#pragma mark -
#pragma mark Plane
uint16 Plane::_nextObjectId = 20000;
+uint32 Plane::_nextCreationId = 0;
-Plane::Plane(const Common::Rect &gameRect) :
-_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
-_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
-_pictureId(kPlanePicColored),
+Plane::Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId) :
+_creationId(_nextCreationId++),
+_pictureId(pictureId),
_mirrored(false),
+_type(kPlaneTypeColored),
_back(0),
-_priorityChanged(0),
+_priorityChanged(false),
_object(make_reg(0, _nextObjectId++)),
_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()),
_created(g_sci->_gfxFrameout->getScreenCount()),
_updated(0),
_deleted(0),
+_moved(0),
_gameRect(gameRect) {
convertGameRectToPlaneRect();
_priority = MAX(10000, g_sci->_gfxFrameout->getPlanes().getTopPlanePriority() + 1);
@@ -63,8 +67,8 @@ _gameRect(gameRect) {
}
Plane::Plane(reg_t object) :
-_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
-_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
+_creationId(_nextCreationId++),
+_type(kPlaneTypeColored),
_priorityChanged(false),
_object(object),
_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()),
@@ -76,10 +80,17 @@ _moved(0) {
_vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX));
_vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY));
- _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
- _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
- _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
- _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+ if (g_sci->_features->usesAlternateSelectors()) {
+ _gameRect.left = readSelectorValue(segMan, object, SELECTOR(left));
+ _gameRect.top = readSelectorValue(segMan, object, SELECTOR(top));
+ _gameRect.right = readSelectorValue(segMan, object, SELECTOR(right)) + 1;
+ _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(bottom)) + 1;
+ } else {
+ _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+ _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+ }
convertGameRectToPlaneRect();
_back = readSelectorValue(segMan, object, SELECTOR(back));
@@ -93,10 +104,10 @@ _moved(0) {
}
Plane::Plane(const Plane &other) :
+_creationId(other._creationId),
_pictureId(other._pictureId),
_mirrored(other._mirrored),
-_field_34(other._field_34), _field_38(other._field_38),
-_field_3C(other._field_3C), _field_40(other._field_40),
+_type(other._type),
_back(other._back),
_object(other._object),
_priority(other._priority),
@@ -106,6 +117,7 @@ _screenRect(other._screenRect),
_screenItemList(other._screenItemList) {}
void Plane::operator=(const Plane &other) {
+ _creationId = other._creationId;
_gameRect = other._gameRect;
_planeRect = other._planeRect;
_vanishingPoint = other._vanishingPoint;
@@ -114,16 +126,13 @@ void Plane::operator=(const Plane &other) {
_mirrored = other._mirrored;
_priority = other._priority;
_back = other._back;
- _width = other._width;
- _field_34 = other._field_34;
- _height = other._height;
_screenRect = other._screenRect;
- _field_3C = other._field_3C;
_priorityChanged = other._priorityChanged;
}
void Plane::init() {
_nextObjectId = 20000;
+ _nextCreationId = 0;
}
void Plane::convertGameRectToPlaneRect() {
@@ -136,7 +145,7 @@ void Plane::convertGameRectToPlaneRect() {
const Ratio ratioY = Ratio(screenHeight, scriptHeight);
_planeRect = _gameRect;
- mulru(_planeRect, ratioX, ratioY);
+ mulru(_planeRect, ratioX, ratioY, 1);
}
void Plane::printDebugInfo(Console *con) const {
@@ -148,11 +157,12 @@ void Plane::printDebugInfo(Console *con) const {
name = g_sci->getEngineState()->_segMan->getObjectName(_object);
}
- con->debugPrintf("%04x:%04x (%s): type %d, prio %d, pic %d, mirror %d, back %d\n",
+ con->debugPrintf("%04x:%04x (%s): type %d, prio %d, ins %u, pic %d, mirror %d, back %d\n",
PRINT_REG(_object),
name.c_str(),
_type,
_priority,
+ _creationId,
_pictureId,
_mirrored,
_back
@@ -171,17 +181,21 @@ void Plane::printDebugInfo(Console *con) const {
void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX) {
uint16 celCount = 1000;
+ bool transparent = true;
for (uint16 celNo = 0; celNo < celCount; ++celNo) {
CelObjPic *celObj = new CelObjPic(pictureId, celNo);
if (celCount == 1000) {
celCount = celObj->_celCount;
}
+ if (!celObj->_transparent) {
+ transparent = false;
+ }
ScreenItem *screenItem = new ScreenItem(_object, celObj->_info);
screenItem->_pictureId = pictureId;
screenItem->_mirrorX = mirrorX;
screenItem->_priority = celObj->_priority;
- screenItem->_fixPriority = true;
+ screenItem->_fixedPriority = true;
if (position != nullptr) {
screenItem->_position = *position + celObj->_relativePosition;
} else {
@@ -192,19 +206,21 @@ void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *p
delete screenItem->_celObj;
screenItem->_celObj = celObj;
}
+ _type = (g_sci->_features->hasTransparentPicturePlanes() && transparent) ? kPlaneTypeTransparentPicture : kPlaneTypePicture;
}
-void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) {
- deletePic(pictureId);
+GuiResourceId Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX, const bool deleteDuplicate) {
+ if (deleteDuplicate) {
+ deletePic(pictureId);
+ }
addPicInternal(pictureId, &position, mirrorX);
- // NOTE: In SCI engine this method returned the pictureId of the
- // plane, but this return value was never used
+ return _pictureId;
}
void Plane::changePic() {
_pictureChanged = false;
- if (_type != kPlaneTypePicture) {
+ if (_type != kPlaneTypePicture && _type != kPlaneTypeTransparentPicture) {
return;
}
@@ -247,17 +263,23 @@ void Plane::deleteAllPics() {
#pragma mark -
#pragma mark Plane - Rendering
+extern int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]);
+
void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const {
- int index = planeList.findIndexByObject(_object);
+ const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1;
+ const PlaneList::size_type planeCount = planeList.size();
for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
- for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) {
- if (planeList[j]->_type != kPlaneTypeTransparent) {
- Common::Rect ptr[4];
- int count = splitRects(drawList[i]->rect, planeList[j]->_screenRect, ptr);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- drawList.add(drawList[i]->screenItem, ptr[k]);
+ for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) {
+ if (
+ planeList[j]->_type != kPlaneTypeTransparent &&
+ planeList[j]->_type != kPlaneTypeTransparentPicture
+ ) {
+ Common::Rect outRects[4];
+ int splitCount = splitRects(drawList[i]->rect, planeList[j]->_screenRect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ drawList.add(drawList[i]->screenItem, outRects[splitCount]);
}
drawList.erase_at(i);
@@ -270,17 +292,20 @@ void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList
}
void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const {
- int index = planeList.findIndexByObject(_object);
+ const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1;
+ const PlaneList::size_type planeCount = planeList.size();
for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
- for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) {
- if (planeList[j]->_type != kPlaneTypeTransparent) {
- Common::Rect ptr[4];
-
- int count = splitRects(*eraseList[i], planeList[j]->_screenRect, ptr);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- eraseList.add(ptr[k]);
+ for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) {
+ if (
+ planeList[j]->_type != kPlaneTypeTransparent &&
+ planeList[j]->_type != kPlaneTypeTransparentPicture
+ ) {
+ Common::Rect outRects[4];
+ int splitCount = splitRects(*eraseList[i], planeList[j]->_screenRect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ eraseList.add(outRects[splitCount]);
}
eraseList.erase_at(i);
@@ -293,85 +318,109 @@ void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeLi
}
void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
- ScreenItemList::size_type planeItemCount = _screenItemList.size();
- ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size();
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ const ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size();
+
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ // Items can be added to ScreenItemList and we don't want to process
+ // those new items, but the list also can grow smaller, so we need
+ // to check that we are still within the upper bound of the list and
+ // quit if we aren't any more
+ if (i >= _screenItemList.size()) {
+ break;
+ }
+
+ ScreenItem *item = _screenItemList[i];
+ if (item == nullptr) {
+ continue;
+ }
- for (PlaneList::size_type i = 0; i < planeItemCount; ++i) {
- ScreenItem *vitem = nullptr;
// NOTE: The original engine used an array without bounds checking
// so could just get the visible screen item directly; we need to
// verify that the index is actually within the valid range for
// the visible plane before accessing the item to avoid a range
// error.
+ const ScreenItem *visibleItem = nullptr;
if (i < visiblePlaneItemCount) {
- vitem = visiblePlane._screenItemList[i];
+ visibleItem = visiblePlane._screenItemList[i];
}
- ScreenItem *item = _screenItemList[i];
- if (i < _screenItemList.size() && item != nullptr) {
- if (item->_deleted) {
- // add item's rect to erase list
- if (i < visiblePlane._screenItemList.size() && vitem != nullptr) {
- if (!vitem->_screenRect.isEmpty()) {
- if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps?
- mergeToRectList(vitem->_screenRect, eraseList);
- } else {
- eraseList.add(vitem->_screenRect);
- }
- }
- }
- } else if (item->_created) {
- // add item to draw list
- item->getCelObj();
- item->calcRects(*this);
-
- if(!item->_screenRect.isEmpty()) {
- if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps?
- drawList.add(item, item->_screenRect);
- mergeToRectList(item->_screenRect, eraseList);
- } else {
- drawList.add(item, item->_screenRect);
- }
+ // Keep erase rects for this screen item from drawing outside
+ // of its owner plane
+ Common::Rect visibleItemScreenRect;
+ if (visibleItem != nullptr) {
+ visibleItemScreenRect = visibleItem->_screenRect;
+ visibleItemScreenRect.clip(_screenRect);
+ }
+
+ if (item->_deleted) {
+ // Add item's rect to erase list
+ if (
+ visibleItem != nullptr &&
+ !visibleItemScreenRect.isEmpty()
+ ) {
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ mergeToRectList(visibleItemScreenRect, eraseList);
+ } else {
+ eraseList.add(visibleItemScreenRect);
}
- } else if (item->_updated) {
- // add old rect to erase list, new item to draw list
- item->getCelObj();
- item->calcRects(*this);
- if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps
- // if item and vitem don't overlap, ...
- if (item->_screenRect.isEmpty() ||
- i >= visiblePlaneItemCount ||
- vitem == nullptr ||
- vitem->_screenRect.isEmpty() ||
- !vitem->_screenRect.intersects(item->_screenRect)
- ) {
- // add item to draw list, and old rect to erase list
- if (!item->_screenRect.isEmpty()) {
- drawList.add(item, item->_screenRect);
- mergeToRectList(item->_screenRect, eraseList);
- }
- if (i < visiblePlaneItemCount && vitem != nullptr && !vitem->_screenRect.isEmpty()) {
- mergeToRectList(vitem->_screenRect, eraseList);
- }
- } else {
- // otherwise, add bounding box of old+new to erase list,
- // and item to draw list
-
- // TODO: This was changed from disasm, verify please!
- Common::Rect extendedScreenItem = vitem->_screenRect;
- extendedScreenItem.extend(item->_screenRect);
- drawList.add(item, item->_screenRect);
- mergeToRectList(extendedScreenItem, eraseList);
- }
+ }
+ }
+
+ if (!item->_created && !item->_updated) {
+ continue;
+ }
+
+ item->calcRects(*this);
+ const Common::Rect itemScreenRect(item->_screenRect);
+
+ if (item->_created) {
+ // Add item to draw list
+ if(!itemScreenRect.isEmpty()) {
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ drawList.add(item, itemScreenRect);
+ mergeToRectList(itemScreenRect, eraseList);
} else {
- // if no active remaps, just add item to draw list and old rect
- // to erase list
- if (!item->_screenRect.isEmpty()) {
- drawList.add(item, item->_screenRect);
+ drawList.add(item, itemScreenRect);
+ }
+ }
+ } else {
+ // Add old rect to erase list, new item to draw list
+
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ // If item and visibleItem don't overlap...
+ if (itemScreenRect.isEmpty() ||
+ visibleItem == nullptr ||
+ visibleItemScreenRect.isEmpty() ||
+ !visibleItemScreenRect.intersects(itemScreenRect)
+ ) {
+ // ...add item to draw list, and old rect to erase list...
+ if (!itemScreenRect.isEmpty()) {
+ drawList.add(item, itemScreenRect);
+ mergeToRectList(itemScreenRect, eraseList);
}
- if (i < visiblePlaneItemCount && vitem != nullptr && !vitem->_screenRect.isEmpty()) {
- eraseList.add(vitem->_screenRect);
+ if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) {
+ mergeToRectList(visibleItemScreenRect, eraseList);
}
+ } else {
+ // ...otherwise, add bounding box of old+new to erase list,
+ // and item to draw list
+ Common::Rect extendedScreenRect = visibleItemScreenRect;
+ extendedScreenRect.extend(itemScreenRect);
+
+ drawList.add(item, itemScreenRect);
+ mergeToRectList(extendedScreenRect, eraseList);
+ }
+ } else {
+ // If no active remaps, just add item to draw list and old rect
+ // to erase list
+
+ // TODO: SCI3 update rects for VMD?
+ if (!itemScreenRect.isEmpty()) {
+ drawList.add(item, itemScreenRect);
+ }
+ if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) {
+ eraseList.add(visibleItemScreenRect);
}
}
}
@@ -381,39 +430,47 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
breakEraseListByPlanes(eraseList, planeList);
breakDrawListByPlanes(drawList, planeList);
- if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"????
+ // We store the current size of the drawlist, as we want to loop
+ // over the currently inserted entries later.
+ DrawList::size_type drawListSizePrimary = drawList.size();
+ const RectList::size_type eraseListCount = eraseList.size();
+
+ // TODO: Figure out which games need which rendering method
+ if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"
_screenItemList.sort();
- bool encounteredPic = false;
- bool v81 = false;
+ bool pictureDrawn = false;
+ bool screenItemDrawn = false;
- for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
- Common::Rect *rect = eraseList[i];
+ for (RectList::size_type i = 0; i < eraseListCount; ++i) {
+ const Common::Rect &rect = *eraseList[i];
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
ScreenItem *item = _screenItemList[j];
- if (j < _screenItemList.size() && item != nullptr) {
- if (rect->intersects(item->_screenRect)) {
- Common::Rect intersection = rect->findIntersectingRect(item->_screenRect);
- if (!item->_deleted) {
- if (encounteredPic) {
- if (item->_celInfo.type == kCelTypePic) {
- if (v81 || item->_celInfo.celNo == 0) {
- drawList.add(item, intersection);
- }
- } else {
- if (!item->_updated && !item->_created) {
- drawList.add(item, intersection);
- }
- v81 = true;
+ if (item == nullptr) {
+ continue;
+ }
+
+ if (rect.intersects(item->_screenRect)) {
+ const Common::Rect intersection = rect.findIntersectingRect(item->_screenRect);
+ if (!item->_deleted) {
+ if (pictureDrawn) {
+ if (item->_celInfo.type == kCelTypePic) {
+ if (screenItemDrawn || item->_celInfo.celNo == 0) {
+ mergeToDrawList(j, intersection, drawList);
}
} else {
if (!item->_updated && !item->_created) {
- drawList.add(item, intersection);
- }
- if (item->_celInfo.type == kCelTypePic) {
- encounteredPic = true;
+ mergeToDrawList(j, intersection, drawList);
}
+ screenItemDrawn = true;
+ }
+ } else {
+ if (!item->_updated && !item->_created) {
+ mergeToDrawList(j, intersection, drawList);
+ }
+ if (item->_celInfo.type == kCelTypePic) {
+ pictureDrawn = true;
}
}
}
@@ -423,35 +480,52 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
_screenItemList.unsort();
} else {
- // add all items overlapping the erase list to the draw list
- for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ // Add all items overlapping the erase list to the draw list
+ for (RectList::size_type i = 0; i < eraseListCount; ++i) {
+ const Common::Rect &rect = *eraseList[i];
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
ScreenItem *item = _screenItemList[j];
- if (j < _screenItemList.size() && item != nullptr && !item->_updated && !item->_deleted && !item->_created && eraseList[i]->intersects(item->_screenRect)) {
- drawList.add(item, eraseList[i]->findIntersectingRect(item->_screenRect));
+ if (
+ item != nullptr &&
+ !item->_created && !item->_updated && !item->_deleted &&
+ rect.intersects(item->_screenRect)
+ ) {
+ drawList.add(item, rect.findIntersectingRect(item->_screenRect));
}
}
}
}
- if (/* TODO: g_Remap_numActiveRemaps */ false) { // no remaps active?
+
+ if (g_sci->_gfxRemap32->getRemapCount() == 0) {
// Add all items that overlap with items in the drawlist and have higher
- // priority
- for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
- DrawItem *dli = drawList[i];
-
- for (PlaneList::size_type j = 0; j < planeItemCount; ++j) {
- ScreenItem *sli = _screenItemList[j];
-
- if (i < drawList.size() && dli) {
- if (j < _screenItemList.size() && sli) {
- if (!sli->_updated && !sli->_deleted && !sli->_created) {
- ScreenItem *item = dli->screenItem;
- if (sli->_priority > item->_priority || (sli->_priority == item->_priority && sli->_object > item->_object)) {
- if (dli->rect.intersects(sli->_screenRect)) {
- drawList.add(sli, dli->rect.findIntersectingRect(sli->_screenRect));
- }
- }
- }
+ // priority.
+
+ // We only loop over "primary" items in the draw list, skipping
+ // those that were added because of the erase list in the previous loop,
+ // or those to be added in this loop.
+ for (DrawList::size_type i = 0; i < drawListSizePrimary; ++i) {
+ const DrawItem *drawListEntry = nullptr;
+ if (i < drawList.size()) {
+ drawListEntry = drawList[i];
+ }
+
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ ScreenItem *newItem = nullptr;
+ if (j < _screenItemList.size()) {
+ newItem = _screenItemList[j];
+ }
+
+ if (
+ drawListEntry != nullptr && newItem != nullptr &&
+ !newItem->_created && !newItem->_updated && !newItem->_deleted
+ ) {
+ const ScreenItem *drawnItem = drawListEntry->screenItem;
+
+ if (
+ (newItem->_priority > drawnItem->_priority || (newItem->_priority == drawnItem->_priority && newItem->_creationId > drawnItem->_creationId)) &&
+ drawListEntry->rect.intersects(newItem->_screenRect)
+ ) {
+ mergeToDrawList(j, drawListEntry->rect.findIntersectingRect(newItem->_screenRect), drawList);
}
}
}
@@ -459,27 +533,18 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
}
decrementScreenItemArrayCounts(&visiblePlane, false);
- _screenItemList.pack();
- visiblePlane._screenItemList.pack();
}
void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate) {
- // The size of the screenItemList may change, so it is
- // critical to re-check the size on each iteration
- for (ScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
ScreenItem *item = _screenItemList[i];
if (item != nullptr) {
// update item in visiblePlane if item is updated
- if (
- item->_updated ||
- (
- forceUpdate &&
- visiblePlane != nullptr &&
- visiblePlane->_screenItemList.findByObject(item->_object) != nullptr
- )
- ) {
- *visiblePlane->_screenItemList[i] = *_screenItemList[i];
+ if (visiblePlane != nullptr && (
+ item->_updated || (forceUpdate && visiblePlane->_screenItemList.findByObject(item->_object) != nullptr))) {
+ *visiblePlane->_screenItemList[i] = *item;
}
if (item->_updated) {
@@ -490,8 +555,7 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force
if (item->_created) {
item->_created--;
if (visiblePlane != nullptr) {
- ScreenItem *n = new ScreenItem(*item);
- visiblePlane->_screenItemList.add(n);
+ visiblePlane->_screenItemList.add(new ScreenItem(*item));
}
}
@@ -499,177 +563,182 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force
if (item->_deleted) {
item->_deleted--;
if (!item->_deleted) {
- visiblePlane->_screenItemList.erase_at(i);
+ if (visiblePlane != nullptr && visiblePlane->_screenItemList.findByObject(item->_object) != nullptr) {
+ visiblePlane->_screenItemList.erase_at(i);
+ }
_screenItemList.erase_at(i);
}
}
}
}
+
+ _screenItemList.pack();
+ if (visiblePlane != nullptr) {
+ visiblePlane->_screenItemList.pack();
+ }
}
-void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const {
- if (_type == kPlaneTypeTransparent) {
- for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) {
- Common::Rect *r = transparentEraseList[i];
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
- if (item != nullptr) {
- if (r->intersects(item->_screenRect)) {
- mergeToDrawList(j, *r, drawList);
- }
+void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const {
+ const RectList::size_type higherEraseCount = higherEraseList.size();
+
+ if (_type == kPlaneTypeTransparent || _type == kPlaneTypeTransparentPicture) {
+ for (RectList::size_type i = 0; i < higherEraseCount; ++i) {
+ const Common::Rect &r = *higherEraseList[i];
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
}
}
}
} else {
- for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) {
- Common::Rect *r = transparentEraseList[i];
- if (r->intersects(_screenRect)) {
- r->clip(_screenRect);
- mergeToRectList(*r, eraseList);
-
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
-
- if (item != nullptr) {
- if (r->intersects(item->_screenRect)) {
- mergeToDrawList(j, *r, drawList);
- }
+ for (RectList::size_type i = 0; i < higherEraseCount; ++i) {
+ Common::Rect r = *higherEraseList[i];
+ if (r.intersects(_screenRect)) {
+ r.clip(_screenRect);
+ mergeToRectList(r, eraseList);
+
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
}
}
- Common::Rect ptr[4];
- Common::Rect *r2 = transparentEraseList[i];
- int count = splitRects(*r2, *r, ptr);
- for (int k = count - 1; k >= 0; --k) {
- transparentEraseList.add(ptr[k]);
+ Common::Rect outRects[4];
+ const Common::Rect &r2 = *higherEraseList[i];
+ int splitCount = splitRects(r2, r, outRects);
+ if (splitCount > 0) {
+ while (splitCount--) {
+ higherEraseList.add(outRects[splitCount]);
+ }
}
- transparentEraseList.erase_at(i);
+ higherEraseList.erase_at(i);
}
}
- transparentEraseList.pack();
+ higherEraseList.pack();
}
}
-void Plane::filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const {
- for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
- Common::Rect &r = drawList[i]->rect;
-
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
- if (item != nullptr) {
- if (r.intersects(item->_screenRect)) {
- mergeToDrawList(j, r, transparentDrawList);
- }
+void Plane::filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const {
+ const DrawList::size_type lowerDrawCount = lowerDrawList.size();
+ for (DrawList::size_type i = 0; i < lowerDrawCount; ++i) {
+ const Common::Rect &r = lowerDrawList[i]->rect;
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
}
}
}
}
-void Plane::filterUpEraseRects(DrawList &drawList, RectList &eraseList) const {
- for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
- Common::Rect &r = *eraseList[i];
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
-
- if (item != nullptr) {
- if (r.intersects(item->_screenRect)) {
- mergeToDrawList(j, r, drawList);
- }
+void Plane::filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const {
+ const RectList::size_type lowerEraseCount = lowerEraseList.size();
+ for (RectList::size_type i = 0; i < lowerEraseCount; ++i) {
+ const Common::Rect &r = *lowerEraseList[i];
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
}
}
}
}
-void Plane::mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const {
- RectList rects;
-
- Common::Rect r = _screenItemList[index]->_screenRect;
+void Plane::mergeToDrawList(const ScreenItemList::size_type index, const Common::Rect &rect, DrawList &drawList) const {
+ RectList mergeList;
+ ScreenItem &item = *_screenItemList[index];
+ Common::Rect r = item._screenRect;
r.clip(rect);
+ mergeList.add(r);
- rects.add(r);
- ScreenItem *item = _screenItemList[index];
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ r = *mergeList[i];
- for (RectList::size_type i = 0; i < rects.size(); ++i) {
- r = *rects[i];
-
- for (DrawList::size_type j = 0; j < drawList.size(); ++j) {
- DrawItem *drawitem = drawList[j];
- if (item->_object == drawitem->screenItem->_object) {
- if (drawitem->rect.contains(r)) {
- rects.erase_at(i);
+ const DrawList::size_type drawCount = drawList.size();
+ for (DrawList::size_type j = 0; j < drawCount; ++j) {
+ const DrawItem &drawItem = *drawList[j];
+ if (item._object == drawItem.screenItem->_object) {
+ if (drawItem.rect.contains(r)) {
+ mergeList.erase_at(i);
break;
}
Common::Rect outRects[4];
- int count = splitRects(r, drawitem->rect, outRects);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- rects.add(outRects[k]);
+ int splitCount = splitRects(r, drawItem.rect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ mergeList.add(outRects[splitCount]);
}
- rects.erase_at(i);
+ mergeList.erase_at(i);
// proceed to the next rect
- r = *rects[++i];
+ r = *mergeList[++i];
}
}
}
}
- rects.pack();
+ mergeList.pack();
- for (RectList::size_type i = 0; i < rects.size(); ++i) {
- drawList.add(item, *rects[i]);
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ drawList.add(&item, *mergeList[i]);
}
}
-void Plane::mergeToRectList(const Common::Rect &rect, RectList &rectList) const {
- RectList temp;
- temp.add(rect);
+void Plane::mergeToRectList(const Common::Rect &rect, RectList &eraseList) const {
+ RectList mergeList;
+ Common::Rect r;
+ mergeList.add(rect);
- for (RectList::size_type i = 0; i < temp.size(); ++i) {
- Common::Rect r = *temp[i];
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ r = *mergeList[i];
- for (RectList::size_type j = 0; j < rectList.size(); ++j) {
- Common::Rect *innerRect = rectList[j];
- if (innerRect->contains(r)) {
- temp.erase_at(i);
+ const RectList::size_type eraseCount = eraseList.size();
+ for (RectList::size_type j = 0; j < eraseCount; ++j) {
+ const Common::Rect &eraseRect = *eraseList[j];
+ if (eraseRect.contains(r)) {
+ mergeList.erase_at(i);
break;
}
- Common::Rect out[4];
- int count = splitRects(r, *innerRect, out);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- temp.add(out[k]);
+ Common::Rect outRects[4];
+ int splitCount = splitRects(r, eraseRect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ mergeList.add(outRects[splitCount]);
}
- temp.erase_at(i);
+ mergeList.erase_at(i);
// proceed to the next rect
- r = *temp[++i];
+ r = *mergeList[++i];
}
}
}
- temp.pack();
+ mergeList.pack();
- for (RectList::size_type i = 0; i < temp.size(); ++i) {
- rectList.add(*temp[i]);
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ eraseList.add(*mergeList[i]);
}
}
void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
- for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) {
- if (*screenItemPtr != nullptr) {
- ScreenItem &screenItem = **screenItemPtr;
- if (!screenItem._deleted) {
- screenItem.getCelObj();
- screenItem.calcRects(*this);
- if (!screenItem._screenRect.isEmpty()) {
- drawList.add(&screenItem, screenItem._screenRect);
- }
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ ScreenItem *screenItem = _screenItemList[i];
+ if (screenItem != nullptr && !screenItem->_deleted) {
+ screenItem->calcRects(*this);
+ if (!screenItem->_screenRect.isEmpty()) {
+ mergeToDrawList(i, screenItem->_screenRect, drawList);
}
}
}
@@ -683,21 +752,30 @@ void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList
breakDrawListByPlanes(drawList, planeList);
--_redrawAllCount;
decrementScreenItemArrayCounts(visiblePlane, true);
- _screenItemList.pack();
- if (visiblePlane != nullptr) {
- visiblePlane->_screenItemList.pack();
- }
}
void Plane::setType() {
- if (_pictureId == kPlanePicOpaque) {
- _type = kPlaneTypeOpaque;
- } else if (_pictureId == kPlanePicTransparent) {
- _type = kPlaneTypeTransparent;
- } else if (_pictureId == kPlanePicColored) {
+ switch (_pictureId) {
+ case kPlanePicColored:
_type = kPlaneTypeColored;
- } else {
- _type = kPlaneTypePicture;
+ break;
+ case kPlanePicTransparent:
+ _type = kPlaneTypeTransparent;
+ break;
+ case kPlanePicOpaque:
+ _type = kPlaneTypeOpaque;
+ break;
+ case kPlanePicTransparentPicture:
+ if (g_sci->_features->hasTransparentPicturePlanes()) {
+ _type = kPlaneTypeTransparentPicture;
+ break;
+ }
+ // fall through for games without transparent picture planes
+ default:
+ if (!g_sci->_features->hasTransparentPicturePlanes() || _type != kPlaneTypeTransparentPicture) {
+ _type = kPlaneTypePicture;
+ }
+ break;
}
}
@@ -718,10 +796,12 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) {
_planeRect.right > other->_planeRect.right ||
_planeRect.bottom > other->_planeRect.bottom
) {
+ // the plane moved or got larger
_redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
- _updated = g_sci->_gfxFrameout->getScreenCount();
+ _moved = g_sci->_gfxFrameout->getScreenCount();
} else if (_planeRect != other->_planeRect) {
- _updated = g_sci->_gfxFrameout->getScreenCount();
+ // the plane got smaller
+ _moved = g_sci->_gfxFrameout->getScreenCount();
}
if (_priority != other->_priority) {
@@ -742,12 +822,10 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) {
_deleted = 0;
if (_created == 0) {
- _moved = g_sci->_gfxFrameout->getScreenCount();
+ _updated = g_sci->_gfxFrameout->getScreenCount();
}
convertGameRectToPlaneRect();
- _width = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
- _height = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
_screenRect = _planeRect;
// NOTE: screenRect originally was retrieved through globals
// instead of being passed into the function
@@ -758,10 +836,18 @@ void Plane::update(const reg_t object) {
SegManager *segMan = g_sci->getEngineState()->_segMan;
_vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX));
_vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY));
- _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
- _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
- _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
- _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+
+ if (g_sci->_features->usesAlternateSelectors()) {
+ _gameRect.left = readSelectorValue(segMan, object, SELECTOR(left));
+ _gameRect.top = readSelectorValue(segMan, object, SELECTOR(top));
+ _gameRect.right = readSelectorValue(segMan, object, SELECTOR(right)) + 1;
+ _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(bottom)) + 1;
+ } else {
+ _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+ _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+ }
convertGameRectToPlaneRect();
_priority = readSelectorValue(segMan, object, SELECTOR(priority));
@@ -775,8 +861,48 @@ void Plane::update(const reg_t object) {
_back = readSelectorValue(segMan, object, SELECTOR(back));
}
+void Plane::scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool scrollPics) {
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+
+ for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) {
+ if (*it != nullptr) {
+ ScreenItem &screenItem = **it;
+ if (!screenItem._deleted && (screenItem._celInfo.type != kCelTypePic || scrollPics)) {
+ screenItem._position.x += deltaX;
+ screenItem._position.y += deltaY;
+ }
+ }
+ }
+}
+
+void Plane::remapMarkRedraw() {
+ ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ ScreenItem *screenItem = _screenItemList[i];
+ if (
+ screenItem != nullptr &&
+ !screenItem->_deleted && !screenItem->_created &&
+ screenItem->getCelObj()._remap
+ ) {
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+ }
+}
+
#pragma mark -
#pragma mark PlaneList
+
+void PlaneList::add(Plane *plane) {
+ for (iterator it = begin(); it != end(); ++it) {
+ if ((*it)->_priority > plane->_priority) {
+ insert(it, plane);
+ return;
+ }
+ }
+
+ push_back(plane);
+}
+
void PlaneList::clear() {
for (iterator it = begin(); it != end(); ++it) {
delete *it;
@@ -794,6 +920,11 @@ void PlaneList::erase(Plane *plane) {
}
}
+PlaneList::iterator PlaneList::erase(iterator it) {
+ delete *it;
+ return PlaneListBase::erase(it);
+}
+
int PlaneList::findIndexByObject(const reg_t object) const {
for (size_type i = 0; i < size(); ++i) {
if ((*this)[i] != nullptr && (*this)[i]->_object == object) {
@@ -836,15 +967,8 @@ int16 PlaneList::getTopSciPlanePriority() const {
return priority;
}
-void PlaneList::add(Plane *plane) {
- for (iterator it = begin(); it != end(); ++it) {
- if ((*it)->_priority < plane->_priority) {
- insert(it, plane);
- return;
- }
- }
-
- push_back(plane);
+void PlaneList::remove_at(size_type index) {
+ delete PlaneListBase::remove_at(index);
}
-}
+} // End of namespace Sci
diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h
index be6f71464a..a34df1cc1a 100644
--- a/engines/sci/graphics/plane32.h
+++ b/engines/sci/graphics/plane32.h
@@ -32,19 +32,21 @@
namespace Sci {
enum PlaneType {
- kPlaneTypeColored = 0,
- kPlaneTypePicture = 1,
- kPlaneTypeTransparent = 2,
- kPlaneTypeOpaque = 3
+ kPlaneTypeColored = 0,
+ kPlaneTypePicture = 1,
+ kPlaneTypeTransparent = 2,
+ kPlaneTypeOpaque = 3,
+ kPlaneTypeTransparentPicture = 4
};
enum PlanePictureCodes {
- // NOTE: Any value at or below 65532 means the plane
+ // NOTE: Any value at or below 65531 means the plane
// is a kPlaneTypePicture.
- kPlanePic = 65532,
- kPlanePicOpaque = 65533,
- kPlanePicTransparent = 65534,
- kPlanePicColored = 65535
+ kPlanePic = 65531,
+ kPlanePicTransparentPicture = 65532,
+ kPlanePicOpaque = 65533,
+ kPlanePicTransparent = 65534,
+ kPlanePicColored = 65535
};
#pragma mark -
@@ -62,7 +64,14 @@ public:
#pragma mark DrawList
struct DrawItem {
+ /**
+ * The screen item to draw.
+ */
ScreenItem *screenItem;
+
+ /**
+ * The target rectangle of the draw operation.
+ */
Common::Rect rect;
inline bool operator<(const DrawItem &other) const {
@@ -101,14 +110,18 @@ private:
static uint16 _nextObjectId;
/**
- * The dimensions of the plane, in game script
- * coordinates.
- * TODO: These are never used and are always
- * scriptWidth x scriptHeight in SCI engine? The actual
- * dimensions of the plane are always in
- * gameRect/planeRect.
+ * A serial used to identify the creation order of
+ * planes, to ensure a stable sort order for planes
+ * with identical priorities.
*/
- int16 _width, _height;
+ static uint32 _nextCreationId;
+
+ /**
+ * The creation order number, which ensures a stable
+ * sort when planes with identical priorities are added
+ * to the plane list.
+ */
+ uint32 _creationId;
/**
* For planes that are used to render picture data, the
@@ -133,11 +146,7 @@ private:
* synchronised to another plane (which calls
* changePic).
*/
- bool _pictureChanged; // ?
-
- // TODO: Are these ever actually used?
- int _field_34, _field_38; // probably a point or ratio
- int _field_3C, _field_40; // probably a point or ratio
+ bool _pictureChanged;
/**
* Converts the dimensions of the game rect used by
@@ -155,6 +164,11 @@ private:
public:
/**
+ * The type of the plane.
+ */
+ PlaneType _type;
+
+ /**
* The color to use when erasing the plane. Only
* applies to planes of type kPlaneTypeColored.
*/
@@ -166,7 +180,7 @@ public:
* another plane and cleared when draw list calculation
* occurs.
*/
- int _priorityChanged; // ?
+ int _priorityChanged;
/**
* A handle to the VM object corresponding to this
@@ -182,12 +196,15 @@ public:
int16 _priority;
/**
- * TODO: Document
+ * Whether or not all screen items in this plane should
+ * be redrawn on the next frameout, instead of just
+ * the screen items marked as updated. This is set when
+ * visual changes to the plane itself are made that
+ * affect the rendering of the entire plane, and cleared
+ * once those changes are rendered by `redrawAll`.
*/
int _redrawAllCount;
- PlaneType _type;
-
/**
* Flags indicating the state of the plane.
* - `created` is set when the plane is first created,
@@ -198,16 +215,15 @@ public:
* not match
* - `deleted` is set when the plane is deleted by a
* kernel call
- * - `moved` is set when the plane is synchronised from
- * another plane and is not already in the "created"
- * state
+ * - `moved` is set when the plane has been moved or
+ * resized
*/
int _created, _updated, _deleted, _moved;
/**
* The vanishing point for the plane. Used when
- * calculating the correct scaling of the plane's screen
- * items according to their position.
+ * automatically calculating the correct scaling of the
+ * plane's screen items according to their position.
*/
Common::Point _vanishingPoint;
@@ -241,30 +257,30 @@ public:
*/
static void init();
- Plane(const Common::Rect &gameRect);
+ // NOTE: This constructor signature originally did not accept a
+ // picture ID, but some calls to construct planes with this signature
+ // immediately set the picture ID and then called setType again, so
+ // it made more sense to just make the picture ID a parameter instead.
+ Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId = kPlanePicColored);
+
Plane(const reg_t object);
+
Plane(const Plane &other);
+
void operator=(const Plane &other);
+
inline bool operator<(const Plane &other) const {
- // TODO: In SCI engine, _object is actually a uint16 and can either
- // contain a MemID (a handle to MemoryMgr, similar to reg_t) or
- // a serial (Plane::_nextObjectId). These numbers can be compared
- // directly in the real engine and the lowest MemID wins, but in
- // ScummVM reg_t pointers are not comparable so we have to use a
- // different strategy when two planes generated by scripts conflict.
- // For now we just don't check if the priority is below 0, since
- // that priority is used to represent hidden planes and is guaranteed
- // to generate conflicts with script-generated planes. If there are
- // other future conflicts with script-generated planes then we need
- // to come up with a solution that works, similar to
- // reg_t::pointerComparisonWithInteger used by SCI16.
- //
- // For now, we check the object offsets, as this will likely work
- // like in the original SCI engine, without comparing objects.
- // However, this whole comparison is quite ugly, and if it still
- // fails, we should try to change it to something equivalent, to avoid
- // adding loads of workarounds just for this
- return _priority < other._priority || (_priority == other._priority && _priority > -1 && _object.getOffset() < other._object.getOffset());
+ if (_priority < other._priority) {
+ return true;
+ }
+
+ if (_priority == other._priority) {
+ // This is different than SSCI; see ScreenItem::operator< for an
+ // explanation
+ return _creationId < other._creationId;
+ }
+
+ return false;
}
/**
@@ -272,7 +288,11 @@ public:
* given screen rect.
*/
inline void clipScreenRect(const Common::Rect &screenRect) {
- if (_screenRect.intersects(screenRect)) {
+ // LSL6 hires creates planes with invalid rects; SSCI does not
+ // care about this, but `Common::Rect::clip` does, so we need to
+ // check whether or not the rect is actually valid before clipping
+ // and only clip valid rects
+ if (_screenRect.isValidRect() && _screenRect.intersects(screenRect)) {
_screenRect.clip(screenRect);
} else {
_screenRect.left = 0;
@@ -305,6 +325,13 @@ public:
*/
void update(const reg_t object);
+ /**
+ * Modifies the position of all non-pic screen items
+ * by the given delta. If `scrollPics` is true, pic
+ * items are also repositioned.
+ */
+ void scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool scrollPics);
+
#pragma mark -
#pragma mark Plane - Pic
private:
@@ -318,12 +345,6 @@ private:
inline void addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX);
/**
- * If the plane is a picture plane, re-adds all cels
- * from its picture resource to the plane.
- */
- void changePic();
-
- /**
* Marks all screen items to be deleted that are within
* this plane and match the given picture ID.
*/
@@ -331,14 +352,6 @@ private:
/**
* Marks all screen items to be deleted that are within
- * this plane and match the given picture ID, then sets
- * the picture ID of the plane to the new picture ID
- * without adding any screen items.
- */
- void deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId);
-
- /**
- * Marks all screen items to be deleted that are within
* this plane and are picture cels.
*/
void deleteAllPics();
@@ -350,49 +363,55 @@ public:
* new picture resource to the plane at the given
* position.
*/
- void addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX);
+ GuiResourceId addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX, const bool deleteDuplicate = true);
+
+ /**
+ * If the plane is a picture plane, re-adds all cels
+ * from its picture resource to the plane. Otherwise,
+ * just clears the _pictureChanged flag.
+ */
+ void changePic();
+
+ /**
+ * Marks all screen items to be deleted that are within
+ * this plane and match the given picture ID, then sets
+ * the picture ID of the plane to the new picture ID
+ * without adding any screen items.
+ */
+ void deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId);
#pragma mark -
#pragma mark Plane - Rendering
private:
/**
* Splits all rects in the given draw list at the edges
- * of all non-transparent planes above the current
- * plane.
+ * of all higher-priority, non-transparent, intersecting
+ * planes.
*/
void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const;
/**
- * Splits all rects in the given erase list rects at the
- * edges of all non-transparent planes above the current
- * plane.
+ * Splits all rects in the given erase list at the
+ * edges of higher-priority, non-transparent,
+ * intersecting planes.
*/
void breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const;
/**
- * Synchronises changes to screen items from the current
- * plane to the visible plane and deletes screen items
- * from the current plane that have been marked as
- * deleted. If `forceUpdate` is true, all screen items
- * on the visible plane will be updated, even if they
- * are not marked as having changed.
- */
- void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate);
-
- /**
- * Merges the screen item from this plane at the given
- * index into the given draw list, clipped to the given
- * rect. TODO: Finish documenting
+ * Adds the screen item at `index` into `drawList`,
+ * ensuring it is only drawn within the bounds of
+ * `rect`. If an existing draw list entry exists
+ * for this screen item, it will be modified.
+ * Otherwise, a new entry will be added.
*/
void mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const;
/**
- * Adds the given rect into the given rect list,
- * merging it with other rects already inside the list,
- * if possible, to avoid overdraw. TODO: Finish
- * documenting
+ * Merges `rect` with an existing rect in `eraseList`,
+ * if possible. Otherwise, adds the rect as a new entry
+ * to `eraseList`.
*/
- void mergeToRectList(const Common::Rect &rect, RectList &rectList) const;
+ void mergeToRectList(const Common::Rect &rect, RectList &eraseList) const;
public:
/**
@@ -405,25 +424,81 @@ public:
void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
/**
- * TODO: Documentation
+ * Synchronises changes to screen items from the current
+ * plane to the visible plane and deletes screen items
+ * from the current plane that have been marked as
+ * deleted. If `forceUpdate` is true, all screen items
+ * on the visible plane will be updated, even if they
+ * are not marked as having changed.
*/
- void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const;
+ void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate);
/**
- * TODO: Documentation
+ * This method is called from the highest priority plane
+ * to the lowest priority plane.
+ *
+ * Adds screen items from this plane to the draw list
+ * that must be redrawn because they intersect entries
+ * in the `higherEraseList`.
+ *
+ * If this plane is opaque, all intersecting erase rects
+ * in `lowerEraseList` are removed, as they would be
+ * completely overwritten by the contents of this plane.
+ *
+ * If this plane is transparent, erase rects from the
+ * `lowerEraseList` are added to the erase list for this
+ * plane, so that lower planes.
+ *
+ * @param drawList The draw list for this plane.
+ * @param eraseList The erase list for this plane.
+ * @param higherEraseList The erase list for a plane
+ * above this plane.
*/
- void filterUpEraseRects(DrawList &drawList, RectList &eraseList) const;
+ void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const;
/**
- * TODO: Documentation
+ * This method is called from the lowest priority plane
+ * to the highest priority plane.
+ *
+ * Adds screen items from this plane to the draw list
+ * that must be drawn because the lower plane is being
+ * redrawn and potentially transparent screen items
+ * from this plane would draw over the lower priority
+ * plane's screen items.
+ *
+ * This method applies only to transparent planes.
+ *
+ * @param drawList The draw list for this plane.
+ * @param eraseList The erase list for a plane below
+ * this plane.
+ */
+ void filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const;
+
+ /**
+ * This method is called from the lowest priority plane
+ * to the highest priority plane.
+ *
+ * Adds screen items from this plane to the draw list
+ * that must be drawn because the lower plane is being
+ * redrawn and potentially transparent screen items
+ * from this plane would draw over the lower priority
+ * plane's screen items.
+ *
+ * This method applies only to transparent planes.
+ *
+ * @param drawList The draw list for this plane.
+ * @param lowerDrawList The draw list for a plane below
+ * this plane.
*/
- void filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const;
+ void filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const;
/**
* Updates all of the plane's non-deleted screen items
* and adds them to the given draw and erase lists.
*/
void redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
+
+ void remapMarkRedraw();
};
#pragma mark -
@@ -459,13 +534,14 @@ public:
void add(Plane *plane);
void clear();
- using PlaneListBase::erase;
+ iterator erase(iterator it);
void erase(Plane *plane);
inline void sort() {
Common::sort(begin(), end(), sortHelper);
}
+ void remove_at(size_type index);
};
-}
+} // End of namespace Sci
#endif
diff --git a/engines/sci/graphics/remap.cpp b/engines/sci/graphics/remap.cpp
new file mode 100644
index 0000000000..2abf03ea29
--- /dev/null
+++ b/engines/sci/graphics/remap.cpp
@@ -0,0 +1,99 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/sci.h"
+#include "sci/graphics/palette.h"
+#include "sci/graphics/remap.h"
+#include "sci/graphics/screen.h"
+
+namespace Sci {
+
+GfxRemap::GfxRemap(GfxPalette *palette)
+ : _palette(palette) {
+ _remapOn = false;
+ resetRemapping();
+}
+
+byte GfxRemap::remapColor(byte remappedColor, byte screenColor) {
+ assert(_remapOn);
+ if (_remappingType[remappedColor] == kRemapByRange)
+ return _remappingByRange[screenColor];
+ else if (_remappingType[remappedColor] == kRemapByPercent)
+ return _remappingByPercent[screenColor];
+ else
+ error("remapColor(): Color %d isn't remapped", remappedColor);
+
+ return 0; // should never reach here
+}
+
+void GfxRemap::resetRemapping() {
+ _remapOn = false;
+ _remappingPercentToSet = 0;
+
+ for (int i = 0; i < 256; i++) {
+ _remappingType[i] = kRemapNone;
+ _remappingByPercent[i] = i;
+ _remappingByRange[i] = i;
+ }
+}
+
+void GfxRemap::setRemappingPercent(byte color, byte percent) {
+ _remapOn = true;
+
+ // We need to defer the setup of the remapping table every time the screen
+ // palette is changed, so that kernelFindColor() can find the correct
+ // colors. Set it once here, in case the palette stays the same and update
+ // it on each palette change by copySysPaletteToScreen().
+ _remappingPercentToSet = percent;
+
+ for (int i = 0; i < 256; i++) {
+ byte r = _palette->_sysPalette.colors[i].r * _remappingPercentToSet / 100;
+ byte g = _palette->_sysPalette.colors[i].g * _remappingPercentToSet / 100;
+ byte b = _palette->_sysPalette.colors[i].b * _remappingPercentToSet / 100;
+ _remappingByPercent[i] = _palette->kernelFindColor(r, g, b);
+ }
+
+ _remappingType[color] = kRemapByPercent;
+}
+
+void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) {
+ _remapOn = true;
+
+ for (int i = from; i <= to; i++) {
+ _remappingByRange[i] = i + base;
+ }
+
+ _remappingType[color] = kRemapByRange;
+}
+
+void GfxRemap::updateRemapping() {
+ // Check if we need to reset remapping by percent with the new colors.
+ if (_remappingPercentToSet) {
+ for (int i = 0; i < 256; i++) {
+ byte r = _palette->_sysPalette.colors[i].r * _remappingPercentToSet / 100;
+ byte g = _palette->_sysPalette.colors[i].g * _remappingPercentToSet / 100;
+ byte b = _palette->_sysPalette.colors[i].b * _remappingPercentToSet / 100;
+ _remappingByPercent[i] = _palette->kernelFindColor(r, g, b);
+ }
+ }
+}
+} // End of namespace Sci
diff --git a/engines/sci/graphics/paint.h b/engines/sci/graphics/remap.h
index b2277131d5..98177f6d19 100644
--- a/engines/sci/graphics/paint.h
+++ b/engines/sci/graphics/remap.h
@@ -20,20 +20,48 @@
*
*/
-#ifndef SCI_GRAPHICS_PAINT_H
-#define SCI_GRAPHICS_PAINT_H
+#ifndef SCI_GRAPHICS_REMAP_H
+#define SCI_GRAPHICS_REMAP_H
+
+#include "common/array.h"
+#include "common/serializer.h"
namespace Sci {
-class GfxPaint {
+class GfxScreen;
+
+/**
+ * This class handles color remapping for the QFG4 demo.
+ */
+class GfxRemap {
+private:
+ enum ColorRemappingType {
+ kRemapNone = 0,
+ kRemapByRange = 1,
+ kRemapByPercent = 2
+ };
+
public:
- GfxPaint();
- virtual ~GfxPaint();
+ GfxRemap(GfxPalette *_palette);
- virtual void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo);
- virtual void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control);
-};
+ void resetRemapping();
+ void setRemappingPercent(byte color, byte percent);
+ void setRemappingRange(byte color, byte from, byte to, byte base);
+ bool isRemapped(byte color) const {
+ return _remapOn && (_remappingType[color] != kRemapNone);
+ }
+ byte remapColor(byte remappedColor, byte screenColor);
+ void updateRemapping();
+
+private:
+ GfxPalette *_palette;
+ bool _remapOn;
+ ColorRemappingType _remappingType[256];
+ byte _remappingByPercent[256];
+ byte _remappingByRange[256];
+ uint16 _remappingPercentToSet;
+};
} // End of namespace Sci
#endif
diff --git a/engines/sci/graphics/remap32.cpp b/engines/sci/graphics/remap32.cpp
new file mode 100644
index 0000000000..768594f974
--- /dev/null
+++ b/engines/sci/graphics/remap32.cpp
@@ -0,0 +1,469 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/sci.h"
+#include "sci/engine/features.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
+
+namespace Sci {
+
+#pragma mark SingleRemap
+
+void SingleRemap::reset() {
+ _lastPercent = 100;
+ _lastGray = 0;
+
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+ for (uint i = 0; i < remapStartColor; ++i) {
+ const Color &color = currentPalette.colors[i];
+ _remapColors[i] = i;
+ _originalColors[i] = color;
+ _originalColorsChanged[i] = true;
+ _idealColors[i] = color;
+ _idealColorsChanged[i] = false;
+ _matchDistances[i] = 0;
+ }
+}
+
+bool SingleRemap::update() {
+ switch (_type) {
+ case kRemapNone:
+ break;
+ case kRemapByRange:
+ return updateRange();
+ case kRemapByPercent:
+ return updateBrightness();
+ case kRemapToGray:
+ return updateSaturation();
+ case kRemapToPercentGray:
+ return updateSaturationAndBrightness();
+ default:
+ error("Illegal remap type %d", _type);
+ }
+
+ return false;
+}
+
+bool SingleRemap::updateRange() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ bool updated = false;
+
+ for (uint i = 0; i < remapStartColor; ++i) {
+ uint8 targetColor;
+ if (_from <= i && i <= _to) {
+ targetColor = i + _delta;
+ } else {
+ targetColor = i;
+ }
+
+ if (_remapColors[i] != targetColor) {
+ updated = true;
+ _remapColors[i] = targetColor;
+ }
+
+ _originalColorsChanged[i] = true;
+ }
+
+ return updated;
+}
+
+bool SingleRemap::updateBrightness() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette();
+ for (uint i = 1; i < remapStartColor; ++i) {
+ Color color(nextPalette.colors[i]);
+
+ if (_originalColors[i] != color) {
+ _originalColorsChanged[i] = true;
+ _originalColors[i] = color;
+ }
+
+ if (_percent != _lastPercent || _originalColorsChanged[i]) {
+ // NOTE: SSCI checked if percent was over 100 and only
+ // then clipped values, but we always unconditionally
+ // ensure the result is in the correct range
+ color.r = MIN(255, (uint16)color.r * _percent / 100);
+ color.g = MIN(255, (uint16)color.g * _percent / 100);
+ color.b = MIN(255, (uint16)color.b * _percent / 100);
+
+ if (_idealColors[i] != color) {
+ _idealColorsChanged[i] = true;
+ _idealColors[i] = color;
+ }
+ }
+ }
+
+ const bool updated = apply();
+ Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+ Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+ _lastPercent = _percent;
+ return updated;
+}
+
+bool SingleRemap::updateSaturation() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+ for (uint i = 1; i < remapStartColor; ++i) {
+ Color color(currentPalette.colors[i]);
+ if (_originalColors[i] != color) {
+ _originalColorsChanged[i] = true;
+ _originalColors[i] = color;
+ }
+
+ if (_gray != _lastGray || _originalColorsChanged[i]) {
+ const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100;
+
+ color.r = MIN(255, color.r - ((color.r - luminosity) * _gray / 100));
+ color.g = MIN(255, color.g - ((color.g - luminosity) * _gray / 100));
+ color.b = MIN(255, color.b - ((color.b - luminosity) * _gray / 100));
+
+ if (_idealColors[i] != color) {
+ _idealColorsChanged[i] = true;
+ _idealColors[i] = color;
+ }
+ }
+ }
+
+ const bool updated = apply();
+ Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+ Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+ _lastGray = _gray;
+ return updated;
+}
+
+bool SingleRemap::updateSaturationAndBrightness() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+ for (uint i = 1; i < remapStartColor; i++) {
+ Color color(currentPalette.colors[i]);
+ if (_originalColors[i] != color) {
+ _originalColorsChanged[i] = true;
+ _originalColors[i] = color;
+ }
+
+ if (_percent != _lastPercent || _gray != _lastGray || _originalColorsChanged[i]) {
+ const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100;
+
+ color.r = MIN(255, color.r - ((color.r - luminosity) * _gray) / 100);
+ color.g = MIN(255, color.g - ((color.g - luminosity) * _gray) / 100);
+ color.b = MIN(255, color.b - ((color.b - luminosity) * _gray) / 100);
+
+ if (_idealColors[i] != color) {
+ _idealColorsChanged[i] = true;
+ _idealColors[i] = color;
+ }
+ }
+ }
+
+ const bool updated = apply();
+ Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+ Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+ _lastPercent = _percent;
+ _lastGray = _gray;
+ return updated;
+}
+
+bool SingleRemap::apply() {
+ const GfxRemap32 *const gfxRemap32 = g_sci->_gfxRemap32;
+ const uint8 remapStartColor = gfxRemap32->getStartColor();
+
+ // Blocked colors are not allowed to be used as target
+ // colors for the remap
+ bool blockedColors[236];
+ Common::fill(blockedColors, blockedColors + remapStartColor, false);
+
+ const bool *const paletteCycleMap = g_sci->_gfxPalette32->getCycleMap();
+
+ const int16 blockedRangeCount = gfxRemap32->getBlockedRangeCount();
+ if (blockedRangeCount) {
+ const uint8 blockedRangeStart = gfxRemap32->getBlockedRangeStart();
+ Common::fill(blockedColors + blockedRangeStart, blockedColors + blockedRangeStart + blockedRangeCount, true);
+ }
+
+ for (uint i = 0; i < remapStartColor; ++i) {
+ if (paletteCycleMap[i]) {
+ blockedColors[i] = true;
+ }
+ }
+
+ // NOTE: SSCI did a loop over colors here to create a
+ // new array of updated, unblocked colors, but then
+ // never used it
+
+ bool updated = false;
+ for (uint i = 1; i < remapStartColor; ++i) {
+ int distance;
+
+ if (!_idealColorsChanged[i] && !_originalColorsChanged[_remapColors[i]]) {
+ continue;
+ }
+
+ if (
+ _idealColorsChanged[i] &&
+ _originalColorsChanged[_remapColors[i]] &&
+ _matchDistances[i] < 100 &&
+ colorDistance(_idealColors[i], _originalColors[_remapColors[i]]) <= _matchDistances[i]
+ ) {
+ continue;
+ }
+
+ const int16 bestColor = matchColor(_idealColors[i], _matchDistances[i], distance, blockedColors);
+
+ if (bestColor != -1 && _remapColors[i] != bestColor) {
+ updated = true;
+ _remapColors[i] = bestColor;
+ _matchDistances[i] = distance;
+ }
+ }
+
+ return updated;
+}
+
+int SingleRemap::colorDistance(const Color &a, const Color &b) const {
+ int channelDistance = a.r - b.r;
+ int distance = channelDistance * channelDistance;
+ channelDistance = a.g - b.g;
+ distance += channelDistance * channelDistance;
+ channelDistance = a.b - b.b;
+ distance += channelDistance * channelDistance;
+ return distance;
+}
+
+int16 SingleRemap::matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const {
+ int16 bestIndex = -1;
+ int bestDistance = 0xFFFFF;
+ int distance = minimumDistance;
+ const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette();
+
+ for (uint i = 0, channelDistance; i < g_sci->_gfxRemap32->getStartColor(); ++i) {
+ if (blockedIndexes[i]) {
+ continue;
+ }
+
+ distance = nextPalette.colors[i].r - color.r;
+ distance *= distance;
+ if (bestDistance <= distance) {
+ continue;
+ }
+ channelDistance = nextPalette.colors[i].g - color.g;
+ distance += channelDistance * channelDistance;
+ if (bestDistance <= distance) {
+ continue;
+ }
+ channelDistance = nextPalette.colors[i].b - color.b;
+ distance += channelDistance * channelDistance;
+ if (bestDistance <= distance) {
+ continue;
+ }
+ bestDistance = distance;
+ bestIndex = i;
+ }
+
+ // This value is only valid if the last index to
+ // perform a distance calculation was the best index
+ outDistance = distance;
+ return bestIndex;
+}
+
+#pragma mark -
+#pragma mark GfxRemap32
+
+GfxRemap32::GfxRemap32() :
+ _needsUpdate(false),
+ _blockedRangeStart(0),
+ _blockedRangeCount(0),
+ _remapStartColor(236),
+ _numActiveRemaps(0) {
+ // The `_remapStartColor` seems to always be 236 in SSCI,
+ // but if it is ever changed then the various C-style
+ // member arrays hard-coded to 236 need to be changed to
+ // match the highest possible value of `_remapStartColor`
+ assert(_remapStartColor == 236);
+
+ if (g_sci->_features->hasNewPaletteCode()) {
+ _remaps.resize(9);
+ } else {
+ _remaps.resize(19);
+ }
+
+ _remapEndColor = _remapStartColor + _remaps.size() - 1;
+}
+
+void GfxRemap32::remapOff(const uint8 color) {
+ if (color == 0) {
+ remapAllOff();
+ return;
+ }
+
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapOff: %d out of remap range", color);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+ singleRemap._type = kRemapNone;
+ --_numActiveRemaps;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapAllOff() {
+ for (uint i = 0, len = _remaps.size(); i < len; ++i) {
+ _remaps[i]._type = kRemapNone;
+ }
+
+ _numActiveRemaps = 0;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapByRange: %d out of remap range", color);
+ return;
+ }
+
+ if (from < 0) {
+ warning("GfxRemap32::remapByRange: attempt to remap negative color %d", from);
+ return;
+ }
+
+ if (to >= _remapStartColor) {
+ warning("GfxRemap32::remapByRange: attempt to remap into the remap zone at %d", to);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._from = from;
+ singleRemap._to = to;
+ singleRemap._delta = delta;
+ singleRemap._type = kRemapByRange;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapByPercent(const uint8 color, const int16 percent) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapByPercent: %d out of remap range", color);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._percent = percent;
+ singleRemap._type = kRemapByPercent;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapToGray(const uint8 color, const int8 gray) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapToGray: %d out of remap range", color);
+ return;
+ }
+
+ if (gray < 0 || gray > 100) {
+ error("RemapToGray percent out of range; gray = %d", gray);
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._gray = gray;
+ singleRemap._type = kRemapToGray;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapToPercentGray(const uint8 color, const int16 gray, const int16 percent) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapToPercentGray: %d out of remap range", color);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._percent = percent;
+ singleRemap._gray = gray;
+ singleRemap._type = kRemapToPercentGray;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::blockRange(const uint8 from, const int16 count) {
+ _blockedRangeStart = from;
+ _blockedRangeCount = count;
+}
+
+bool GfxRemap32::remapAllTables(const bool paletteUpdated) {
+ if (!_needsUpdate && !paletteUpdated) {
+ return false;
+ }
+
+ bool updated = false;
+
+ for (SingleRemapsList::iterator it = _remaps.begin(); it != _remaps.end(); ++it) {
+ if (it->_type != kRemapNone) {
+ updated |= it->update();
+ }
+ }
+
+ _needsUpdate = false;
+ return updated;
+}
+} // End of namespace Sci
diff --git a/engines/sci/graphics/remap32.h b/engines/sci/graphics/remap32.h
new file mode 100644
index 0000000000..1b9628c7be
--- /dev/null
+++ b/engines/sci/graphics/remap32.h
@@ -0,0 +1,405 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_REMAP32_H
+#define SCI_GRAPHICS_REMAP32_H
+
+#include "common/algorithm.h"
+#include "common/array.h"
+#include "common/scummsys.h"
+#include "sci/graphics/helpers.h"
+
+namespace Sci {
+class GfxPalette32;
+
+enum RemapType {
+ kRemapNone = 0,
+ kRemapByRange = 1,
+ kRemapByPercent = 2,
+ kRemapToGray = 3,
+ kRemapToPercentGray = 4
+};
+
+#pragma mark -
+#pragma mark SingleRemap
+
+/**
+ * SingleRemap objects each manage one remapping operation.
+ */
+class SingleRemap {
+public:
+ SingleRemap() : _type(kRemapNone) {}
+
+ /**
+ * The type of remap.
+ */
+ RemapType _type;
+
+ /**
+ * The first color that should be shifted by a range
+ * remap.
+ */
+ uint8 _from;
+
+ /**
+ * The last color that should be shifted a range remap.
+ */
+ uint8 _to;
+
+ /**
+ * The direction and amount that the colors should be
+ * shifted in a range remap.
+ */
+ int16 _delta;
+
+ /**
+ * The difference in brightness that should be
+ * applied by a brightness (percent) remap.
+ *
+ * This value may be be greater than 100, in
+ * which case the color will be oversaturated.
+ */
+ int16 _percent;
+
+ /**
+ * The amount of desaturation that should be
+ * applied by a saturation (gray) remap, where
+ * 0 is full saturation and 100 is full
+ * desaturation.
+ */
+ uint8 _gray;
+
+ /**
+ * The final array used by CelObj renderers to composite
+ * remapped pixels to the screen buffer.
+ *
+ * Here is how it works:
+ *
+ * The source bitmap being rendered will have pixels
+ * within the remap range (236-245 or 236-254), and the
+ * target buffer will have colors in the non-remapped
+ * range (0-235).
+ *
+ * To arrive at the correct color, first the source
+ * pixel is used to look up the correct SingleRemap for
+ * that pixel. Then, the final composited color is
+ * looked up in this array using the target's pixel
+ * color. In other words,
+ * `target = _remaps[remapEndColor - source].remapColors[target]`.
+ */
+ uint8 _remapColors[236];
+
+ /**
+ * Resets this SingleRemap's color information to
+ * default values.
+ */
+ void reset();
+
+ /**
+ * Recalculates and reapplies remap colors to the
+ * `_remapColors` array.
+ */
+ bool update();
+
+private:
+ /**
+ * The previous brightness value. Used to
+ * determine whether or not targetColors needs
+ * to be updated.
+ */
+ int16 _lastPercent;
+
+ /**
+ * The previous saturation value. Used to
+ * determine whether or not targetColors needs
+ * to be updated.
+ */
+ uint8 _lastGray;
+
+ /**
+ * The colors from the current GfxPalette32 palette
+ * before this SingleRemap is applied.
+ */
+ Color _originalColors[236];
+
+ /**
+ * Map of colors that changed in `_originalColors`
+ * when this SingleRemap was updated. This map is
+ * transient and gets reset to `false` after the
+ * SingleRemap finishes updating.
+ */
+ bool _originalColorsChanged[236];
+
+ /**
+ * The ideal target RGB color values for each generated
+ * remap color.
+ */
+ Color _idealColors[236];
+
+ /**
+ * Map of colors that changed in `_idealColors` when
+ * this SingleRemap was updated. This map is transient
+ * and gets reset to `false` after the SingleRemap
+ * finishes applying.
+ */
+ bool _idealColorsChanged[236];
+
+ /**
+ * When applying a SingleRemap, finding an appropriate
+ * color in the palette is the responsibility of a
+ * distance function. Once a match is found, the
+ * distance of that match is stored here so that the
+ * next time the SingleRemap is applied, it can check
+ * the distance from the previous application and avoid
+ * triggering an expensive redraw of the entire screen
+ * if the new palette value only changed slightly.
+ */
+ int _matchDistances[236];
+
+ /**
+ * Computes the final target values for a range remap
+ * and applies them directly to the `_remaps` map.
+ *
+ * @note Was ByRange in SSCI.
+ */
+ bool updateRange();
+
+ /**
+ * Computes the intermediate target values for a
+ * brightness remap and applies them indirectly via
+ * the `apply` method.
+ *
+ * @note Was ByPercent in SSCI.
+ */
+ bool updateBrightness();
+
+ /**
+ * Computes the intermediate target values for a
+ * saturation remap and applies them indirectly via
+ * the `apply` method.
+ *
+ * @note Was ToGray in SSCI.
+ */
+ bool updateSaturation();
+
+ /**
+ * Computes the intermediate target values for a
+ * saturation + brightness bitmap and applies them
+ * indirectly via the `apply` method.
+ *
+ * @note Was ToPercentGray in SSCI.
+ */
+ bool updateSaturationAndBrightness();
+
+ /**
+ * Computes and applies the final values to the
+ * `_remaps` map.
+ *
+ * @note In SSCI, a boolean array of changed values
+ * was passed into this method, but this was done by
+ * creating arrays on the stack in the caller. Instead
+ * of doing this, we simply add another member property
+ * `_idealColorsChanged` and use that instead.
+ */
+ bool apply();
+
+ /**
+ * Calculates the square distance of two colors.
+ *
+ * @note In SSCI this method is Rgb24::Dist, but it is
+ * only used by SingleRemap.
+ */
+ int colorDistance(const Color &a, const Color &b) const;
+
+ /**
+ * Finds the closest index in the next palette matching
+ * the given RGB color. Returns -1 if no match can be
+ * found that is closer than `minimumDistance`.
+ *
+ * @note In SSCI, this method is SOLPalette::Match, but
+ * this particular signature is only used by
+ * SingleRemap.
+ */
+ int16 matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const;
+};
+
+#pragma mark -
+#pragma mark GfxRemap32
+
+/**
+ * This class provides color remapping support for SCI32
+ * games.
+ */
+class GfxRemap32 : public Common::Serializable {
+public:
+ GfxRemap32();
+
+ void saveLoadWithSerializer(Common::Serializer &s);
+
+ inline uint8 getRemapCount() const { return _numActiveRemaps; }
+ inline uint8 getStartColor() const { return _remapStartColor; }
+ inline uint8 getEndColor() const { return _remapEndColor; }
+ inline uint8 getBlockedRangeStart() const { return _blockedRangeStart; }
+ inline int16 getBlockedRangeCount() const { return _blockedRangeCount; }
+
+ /**
+ * Turns off remapping of the given color. If `color` is
+ * 0, all remaps are turned off.
+ */
+ void remapOff(const uint8 color);
+
+ /**
+ * Turns off all color remaps.
+ */
+ void remapAllOff();
+
+ /**
+ * Configures a SingleRemap for the remap color `color`.
+ * The SingleRemap will shift palette colors between
+ * `from` and `to` (inclusive) by `delta` palette
+ * entries when the remap is applied.
+ */
+ void remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta);
+
+ /**
+ * Configures a SingleRemap for the remap color `color`
+ * to modify the brightness of remapped colors by
+ * `percent`.
+ */
+ void remapByPercent(const uint8 color, const int16 percent);
+
+ /**
+ * Configures a SingleRemap for the remap color `color`
+ * to modify the saturation of remapped colors by
+ * `gray`.
+ */
+ void remapToGray(const uint8 color, const int8 gray);
+
+ /**
+ * Configures a SingleRemap for the remap color `color`
+ * to modify the brightness of remapped colors by
+ * `percent`, and saturation of remapped colors by
+ * `gray`.
+ */
+ void remapToPercentGray(const uint8 color, const int16 gray, const int16 percent);
+
+ /**
+ * Prevents GfxRemap32 from using the given range of
+ * palette entries as potential remap targets.
+ *
+ * @NOTE Was DontMapToRange in SSCI.
+ */
+ void blockRange(const uint8 from, const int16 count);
+
+ /**
+ * Determines whether or not the given color has an
+ * active remapper. If it does not, it is treated as a
+ * skip color and the pixel is not drawn.
+ *
+ * @note SSCI uses a boolean array to decide whether a
+ * a pixel is remapped, but it is possible to get the
+ * same information from `_remaps`, as this function
+ * does.
+ * Presumably, the separate array was created for
+ * performance reasons, since this is called a lot in
+ * the most critical section of the renderer.
+ */
+ inline bool remapEnabled(uint8 color) const {
+ const uint8 index = _remapEndColor - color;
+ // At least KQ7 DOS uses remap colors that are outside the valid remap
+ // range; in these cases, just treat those pixels as skip pixels (which
+ // is how they would be treated in SSCI)
+ if (index >= _remaps.size()) {
+ return false;
+ }
+ return (_remaps[index]._type != kRemapNone);
+ }
+
+ /**
+ * Calculates the correct color for a target by looking
+ * up the target color in the SingleRemap that controls
+ * the given sourceColor. If there is no remap for the
+ * given color, it will be treated as a skip color.
+ */
+ inline uint8 remapColor(const uint8 sourceColor, const uint8 targetColor) const {
+ const uint8 index = _remapEndColor - sourceColor;
+ assert(index < _remaps.size());
+ const SingleRemap &singleRemap = _remaps[index];
+ assert(singleRemap._type != kRemapNone);
+ return singleRemap._remapColors[targetColor];
+ }
+
+ /**
+ * Updates all active remaps in response to a palette
+ * change or a remap settings change.
+ *
+ * `paletteChanged` is true if the next palette in
+ * GfxPalette32 has been previously modified by other
+ * palette operations.
+ */
+ bool remapAllTables(const bool paletteUpdated);
+
+private:
+ typedef Common::Array<SingleRemap> SingleRemapsList;
+
+ /**
+ * The first index of the remap area in the system
+ * palette.
+ */
+ const uint8 _remapStartColor;
+
+ /**
+ * The last index of the remap area in the system
+ * palette.
+ */
+ uint8 _remapEndColor;
+
+ /**
+ * The number of currently active remaps.
+ */
+ uint8 _numActiveRemaps;
+
+ /**
+ * The list of SingleRemaps.
+ */
+ SingleRemapsList _remaps;
+
+ /**
+ * If true, indicates that one or more SingleRemaps were
+ * reconfigured and all remaps need to be recalculated.
+ */
+ bool _needsUpdate;
+
+ /**
+ * The first color that is blocked from being used as a
+ * remap target color.
+ */
+ uint8 _blockedRangeStart;
+
+ /**
+ * The size of the range of blocked colors. If zero,
+ * all colors are potential targets for remapping.
+ */
+ int16 _blockedRangeCount;
+};
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp
index c977a93817..de6df39bb9 100644
--- a/engines/sci/graphics/screen.cpp
+++ b/engines/sci/graphics/screen.cpp
@@ -37,7 +37,7 @@ 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;
@@ -45,7 +45,7 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
_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
@@ -53,18 +53,12 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) {
if (g_sci->getGameId() == GID_KQ6)
_upscaledHires = GFX_SCREEN_UPSCALED_640x440;
-#ifdef ENABLE_SCI32
- if (g_sci->getGameId() == GID_GK1)
- _upscaledHires = GFX_SCREEN_UPSCALED_640x480;
- if (g_sci->getGameId() == GID_PQ4)
- _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) {
@@ -72,7 +66,7 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
_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
@@ -90,28 +84,6 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
}
}
-#ifdef ENABLE_SCI32
- // GK1 Mac uses a 640x480 resolution too
- if (g_sci->getPlatform() == Common::kPlatformMacintosh) {
- if (g_sci->getGameId() == GID_GK1)
- _upscaledHires = GFX_SCREEN_UPSCALED_640x480;
- }
-#endif
-
- if (_resMan->detectHires()) {
- _scriptWidth = 640;
- _scriptHeight = 480;
- }
-
-#ifdef ENABLE_SCI32
- // 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
-
// if not yet set, set those to script-width/height
if (!_width)
_width = _scriptWidth;
@@ -168,7 +140,7 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
}
_displayPixels = _displayWidth * _displayHeight;
-
+
// Allocate visual, priority, control and display screen
_visualScreen = (byte *)calloc(_pixels, 1);
_priorityScreen = (byte *)calloc(_pixels, 1);
@@ -337,7 +309,7 @@ void GfxScreen::vectorAdjustLineCoordinates(int16 *left, int16 *top, int16 *righ
void GfxScreen::vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) {
if (_upscaledHires == GFX_SCREEN_UPSCALED_480x300) {
vectorPutLinePixel480x300(x, y, drawMask, color, priority, control);
- return;
+ return;
}
// For anything else forward to the regular putPixel
@@ -632,13 +604,13 @@ void GfxScreen::setVerticalShakePos(uint16 shakePos) {
void GfxScreen::kernelShakeScreen(uint16 shakeCount, uint16 directions) {
while (shakeCount--) {
- if (directions & SCI_SHAKE_DIRECTION_VERTICAL)
+ if (directions & kShakeVertical)
setVerticalShakePos(10);
// TODO: horizontal shakes
g_system->updateScreen();
g_sci->getEngineState()->wait(3);
- if (directions & SCI_SHAKE_DIRECTION_VERTICAL)
+ if (directions & kShakeVertical)
setVerticalShakePos(0);
g_system->updateScreen();
@@ -651,7 +623,7 @@ 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++) {
diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h
index 65416252f6..63ee4ed09e 100644
--- a/engines/sci/graphics/screen.h
+++ b/engines/sci/graphics/screen.h
@@ -289,7 +289,7 @@ public:
default:
break;
}
-
+
// For non-upscaled mode and 480x300 Mac put pixels directly
int offset = y * _width + x;
diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp
index 0bbb056071..f7239c33bb 100644
--- a/engines/sci/graphics/screen_item32.cpp
+++ b/engines/sci/graphics/screen_item32.cpp
@@ -22,6 +22,7 @@
#include "sci/console.h"
#include "sci/resource.h"
+#include "sci/engine/features.h"
#include "sci/engine/kernel.h"
#include "sci/engine/selector.h"
#include "sci/engine/state.h"
@@ -34,15 +35,18 @@ namespace Sci {
#pragma mark ScreenItem
uint16 ScreenItem::_nextObjectId = 20000;
+uint32 ScreenItem::_nextCreationId = 0;
ScreenItem::ScreenItem(const reg_t object) :
+_creationId(_nextCreationId++),
_celObj(nullptr),
_object(object),
_pictureId(-1),
_created(g_sci->_gfxFrameout->getScreenCount()),
_updated(0),
_deleted(0),
-_mirrorX(false) {
+_mirrorX(false),
+_drawBlackLines(false) {
SegManager *segMan = g_sci->getEngineState()->_segMan;
setFromObject(segMan, object, true, true);
@@ -50,56 +54,63 @@ _mirrorX(false) {
}
ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo) :
+_creationId(_nextCreationId++),
_plane(plane),
_useInsetRect(false),
_z(0),
_celInfo(celInfo),
_celObj(nullptr),
-_fixPriority(false),
+_fixedPriority(false),
_position(0, 0),
_object(make_reg(0, _nextObjectId++)),
_pictureId(-1),
_created(g_sci->_gfxFrameout->getScreenCount()),
_updated(0),
_deleted(0),
-_mirrorX(false) {}
+_mirrorX(false),
+_drawBlackLines(false) {}
ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect) :
+_creationId(_nextCreationId++),
_plane(plane),
_useInsetRect(false),
_z(0),
_celInfo(celInfo),
_celObj(nullptr),
-_fixPriority(false),
+_fixedPriority(false),
_position(rect.left, rect.top),
_object(make_reg(0, _nextObjectId++)),
_pictureId(-1),
_created(g_sci->_gfxFrameout->getScreenCount()),
_updated(0),
_deleted(0),
-_mirrorX(false) {
+_mirrorX(false),
+_drawBlackLines(false) {
if (celInfo.type == kCelTypeColor) {
_insetRect = rect;
}
}
-ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect, const ScaleInfo &scaleInfo) :
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Point &position, const ScaleInfo &scaleInfo) :
+_creationId(_nextCreationId++),
_plane(plane),
_scale(scaleInfo),
_useInsetRect(false),
_z(0),
_celInfo(celInfo),
_celObj(nullptr),
-_fixPriority(false),
-_position(rect.left, rect.top),
+_fixedPriority(false),
+_position(position),
_object(make_reg(0, _nextObjectId++)),
_pictureId(-1),
_created(g_sci->_gfxFrameout->getScreenCount()),
_updated(0),
_deleted(0),
-_mirrorX(false) {}
+_mirrorX(false),
+_drawBlackLines(false) {}
ScreenItem::ScreenItem(const ScreenItem &other) :
+_creationId(other._creationId),
_plane(other._plane),
_scale(other._scale),
_useInsetRect(other._useInsetRect),
@@ -108,14 +119,26 @@ _celObj(nullptr),
_object(other._object),
_mirrorX(other._mirrorX),
_scaledPosition(other._scaledPosition),
-_screenRect(other._screenRect) {
+_screenRect(other._screenRect),
+_drawBlackLines(other._drawBlackLines) {
if (other._useInsetRect) {
_insetRect = other._insetRect;
}
}
void ScreenItem::operator=(const ScreenItem &other) {
- _celInfo = other._celInfo;
+ // NOTE: The original engine did not check for differences in `_celInfo`
+ // to clear `_celObj` here; instead, it unconditionally set `_celInfo`,
+ // didn't clear `_celObj`, and did hacky stuff in `kIsOnMe` to avoid
+ // testing a mismatched `_celObj`. See `GfxFrameout::kernelIsOnMe` for
+ // more detail.
+ if (_celInfo != other._celInfo) {
+ _celInfo = other._celInfo;
+ delete _celObj;
+ _celObj = nullptr;
+ }
+
+ _creationId = other._creationId;
_screenRect = other._screenRect;
_mirrorX = other._mirrorX;
_useInsetRect = other._useInsetRect;
@@ -124,10 +147,16 @@ void ScreenItem::operator=(const ScreenItem &other) {
}
_scale = other._scale;
_scaledPosition = other._scaledPosition;
+ _drawBlackLines = other._drawBlackLines;
+}
+
+ScreenItem::~ScreenItem() {
+ delete _celObj;
}
void ScreenItem::init() {
_nextObjectId = 20000;
+ _nextCreationId = 0;
}
void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap) {
@@ -149,7 +178,7 @@ void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const boo
// single location
Resource *view = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _celInfo.resourceId), false);
if (!view) {
- error("Failed to load resource %d", _celInfo.resourceId);
+ error("Failed to load %s", _celInfo.toString().c_str());
}
// NOTE: +2 because the header size field itself is excluded from
@@ -158,7 +187,9 @@ void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const boo
const uint8 loopCount = view->data[2];
const uint8 loopSize = view->data[12];
- if (_celInfo.loopNo >= loopCount) {
+ // loopNo is set to be an unsigned integer in SSCI, so if it's a
+ // negative value, it'll be fixed accordingly
+ if ((uint16)_celInfo.loopNo >= loopCount) {
const int maxLoopNo = loopCount - 1;
_celInfo.loopNo = maxLoopNo;
writeSelectorValue(segMan, object, SELECTOR(loop), maxLoopNo);
@@ -169,8 +200,11 @@ void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const boo
if (seekEntry != -1) {
loopData = view->data + headerSize + (seekEntry * loopSize);
}
+
+ // celNo is set to be an unsigned integer in SSCI, so if it's a
+ // negative value, it'll be fixed accordingly
const uint8 celCount = loopData[2];
- if (_celInfo.celNo >= celCount) {
+ if ((uint16)_celInfo.celNo >= celCount) {
const int maxCelNo = celCount - 1;
_celInfo.celNo = maxCelNo;
writeSelectorValue(segMan, object, SELECTOR(cel), maxCelNo);
@@ -195,24 +229,36 @@ void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const boo
}
if (readSelectorValue(segMan, object, SELECTOR(fixPriority))) {
- _fixPriority = true;
+ _fixedPriority = true;
_priority = readSelectorValue(segMan, object, SELECTOR(priority));
} else {
- _fixPriority = false;
+ _fixedPriority = false;
writeSelectorValue(segMan, object, SELECTOR(priority), _position.y);
}
_z = readSelectorValue(segMan, object, SELECTOR(z));
_position.y -= _z;
- if (readSelectorValue(segMan, object, SELECTOR(useInsetRect))) {
- _useInsetRect = true;
- _insetRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
- _insetRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
- _insetRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
- _insetRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+ if (g_sci->_features->usesAlternateSelectors()) {
+ if (readSelectorValue(segMan, object, SELECTOR(seenRect))) {
+ _useInsetRect = true;
+ _insetRect.left = readSelectorValue(segMan, object, SELECTOR(left));
+ _insetRect.top = readSelectorValue(segMan, object, SELECTOR(top));
+ _insetRect.right = readSelectorValue(segMan, object, SELECTOR(right)) + 1;
+ _insetRect.bottom = readSelectorValue(segMan, object, SELECTOR(bottom)) + 1;
+ } else {
+ _useInsetRect = false;
+ }
} else {
- _useInsetRect = false;
+ if (readSelectorValue(segMan, object, SELECTOR(useInsetRect))) {
+ _useInsetRect = true;
+ _insetRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _insetRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _insetRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+ _insetRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
+ } else {
+ _useInsetRect = false;
+ }
}
segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewVisible);
@@ -224,7 +270,9 @@ void ScreenItem::calcRects(const Plane &plane) {
const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
- Common::Rect celRect(_celObj->_width, _celObj->_height);
+ const CelObj &celObj = getCelObj();
+
+ Common::Rect celRect(celObj._width, celObj._height);
if (_useInsetRect) {
if (_insetRect.intersects(celRect)) {
_insetRect.clip(celRect);
@@ -235,28 +283,32 @@ void ScreenItem::calcRects(const Plane &plane) {
_insetRect = celRect;
}
- Ratio newRatioX;
- Ratio newRatioY;
-
- if (_scale.signal & kScaleSignalDoScaling32) {
- if (_scale.signal & kScaleSignalUseVanishingPoint) {
- int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
- newRatioX = Ratio(num, 128);
- newRatioY = Ratio(num, 128);
- } else {
- newRatioX = Ratio(_scale.x, 128);
- newRatioY = Ratio(_scale.y, 128);
- }
+ Ratio scaleX, scaleY;
+ if (_scale.signal == kScaleSignalManual) {
+ scaleX = Ratio(_scale.x, 128);
+ scaleY = Ratio(_scale.y, 128);
+ } else if (_scale.signal == kScaleSignalVanishingPoint) {
+ int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
+ scaleX = Ratio(num, 128);
+ scaleY = Ratio(num, 128);
}
- if (newRatioX.getNumerator() && newRatioY.getNumerator()) {
+ if (scaleX.getNumerator() && scaleY.getNumerator()) {
_screenItemRect = _insetRect;
- if (_celObj->_scaledWidth != scriptWidth || _celObj->_scaledHeight != scriptHeight) {
+ const Ratio celToScreenX(screenWidth, celObj._xResolution);
+ const Ratio celToScreenY(screenHeight, celObj._yResolution);
+
+ // Cel may use a coordinate system that is not the same size as the
+ // script coordinate system (usually this means high-resolution
+ // pictures with low-resolution scripts)
+ if (celObj._xResolution != kLowResX || celObj._yResolution != kLowResY) {
+ // high resolution coordinates
+
if (_useInsetRect) {
- Ratio celScriptXRatio(_celObj->_scaledWidth, scriptWidth);
- Ratio celScriptYRatio(_celObj->_scaledHeight, scriptHeight);
- mulru(_screenItemRect, celScriptXRatio, celScriptYRatio);
+ const Ratio scriptToCelX(celObj._xResolution, scriptWidth);
+ const Ratio scriptToCelY(celObj._yResolution, scriptHeight);
+ mulru(_screenItemRect, scriptToCelX, scriptToCelY, 0);
if (_screenItemRect.intersects(celRect)) {
_screenItemRect.clip(celRect);
@@ -265,53 +317,76 @@ void ScreenItem::calcRects(const Plane &plane) {
}
}
- int displaceX = _celObj->_displace.x;
- int displaceY = _celObj->_displace.y;
+ int originX = celObj._origin.x;
+ int originY = celObj._origin.y;
- if (_mirrorX != _celObj->_mirrorX && _celInfo.type != kCelTypePic) {
- displaceX = _celObj->_width - _celObj->_displace.x - 1;
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ originX = celObj._width - celObj._origin.x - 1;
}
- if (!newRatioX.isOne() || !newRatioY.isOne()) {
- mulru(_screenItemRect, newRatioX, newRatioY);
- displaceX = (displaceX * newRatioX).toInt();
- displaceY = (displaceY * newRatioY).toInt();
- }
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ // Different games use a different cel scaling mode, but the
+ // difference isn't consistent across SCI versions; instead,
+ // it seems to be related to an update that happened during
+ // SCI2.1mid where games started using hi-resolution game
+ // scripts
+ if (scriptWidth == kLowResX) {
+ mulinc(_screenItemRect, scaleX, scaleY);
+ } else {
+ _screenItemRect.left = (_screenItemRect.left * scaleX).toInt();
+ _screenItemRect.top = (_screenItemRect.top * scaleY).toInt();
+
+ if (scaleX.getNumerator() > scaleX.getDenominator()) {
+ _screenItemRect.right = (_screenItemRect.right * scaleX).toInt();
+ } else {
+ _screenItemRect.right = ((_screenItemRect.right - 1) * scaleX).toInt() + 1;
+ }
+
+ if (scaleY.getNumerator() > scaleY.getDenominator()) {
+ _screenItemRect.bottom = (_screenItemRect.bottom * scaleY).toInt();
+ } else {
+ _screenItemRect.bottom = ((_screenItemRect.bottom - 1) * scaleY).toInt() + 1;
+ }
+ }
- Ratio celXRatio(screenWidth, _celObj->_scaledWidth);
- Ratio celYRatio(screenHeight, _celObj->_scaledHeight);
+ originX = (originX * scaleX).toInt();
+ originY = (originY * scaleY).toInt();
+ }
- displaceX = (displaceX * celXRatio).toInt();
- displaceY = (displaceY * celYRatio).toInt();
+ mulinc(_screenItemRect, celToScreenX, celToScreenY);
+ originX = (originX * celToScreenX).toInt();
+ originY = (originY * celToScreenY).toInt();
- mulru(_screenItemRect, celXRatio, celYRatio);
+ const Ratio scriptToScreenX = Ratio(screenWidth, scriptWidth);
+ const Ratio scriptToScreenY = Ratio(screenHeight, scriptHeight);
if (/* TODO: dword_C6288 */ false && _celInfo.type == kCelTypePic) {
_scaledPosition.x = _position.x;
_scaledPosition.y = _position.y;
} else {
- _scaledPosition.x = (_position.x * screenWidth / scriptWidth) - displaceX;
- _scaledPosition.y = (_position.y * screenHeight / scriptHeight) - displaceY;
+ _scaledPosition.x = (_position.x * scriptToScreenX).toInt() - originX;
+ _scaledPosition.y = (_position.y * scriptToScreenY).toInt() - originY;
}
_screenItemRect.translate(_scaledPosition.x, _scaledPosition.y);
- if (_mirrorX != _celObj->_mirrorX && _celInfo.type == kCelTypePic) {
+ if (_mirrorX != celObj._mirrorX && _celInfo.type == kCelTypePic) {
Common::Rect temp(_insetRect);
- if (!newRatioX.isOne()) {
- mulru(temp, newRatioX, Ratio());
+ if (!scaleX.isOne()) {
+ mulinc(temp, scaleX, Ratio());
}
- mulru(temp, celXRatio, Ratio());
+ mulinc(temp, celToScreenX, Ratio());
CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj);
+ if (celObjPic == nullptr) {
+ error("Expected a CelObjPic");
+ }
+ temp.translate((celObjPic->_relativePosition.x * scriptToScreenX).toInt() - originX, 0);
- temp.translate(celObjPic->_relativePosition.x * screenWidth / scriptWidth - displaceX, 0);
-
- // TODO: This is weird, and probably wrong calculation of widths
- // due to BR-inclusion
- int deltaX = plane._planeRect.right - plane._planeRect.left + 1 - temp.right - 1 - temp.left;
+ // TODO: This is weird.
+ int deltaX = plane._planeRect.width() - temp.right - 1 - temp.left;
_scaledPosition.x += deltaX;
_screenItemRect.translate(deltaX, 0);
@@ -321,41 +396,45 @@ void ScreenItem::calcRects(const Plane &plane) {
_scaledPosition.y += plane._planeRect.top;
_screenItemRect.translate(plane._planeRect.left, plane._planeRect.top);
- _ratioX = newRatioX * Ratio(screenWidth, _celObj->_scaledWidth);
- _ratioY = newRatioY * Ratio(screenHeight, _celObj->_scaledHeight);
+ _ratioX = scaleX * celToScreenX;
+ _ratioY = scaleY * celToScreenY;
} else {
- int displaceX = _celObj->_displace.x;
- if (_mirrorX != _celObj->_mirrorX && _celInfo.type != kCelTypePic) {
- displaceX = _celObj->_width - _celObj->_displace.x - 1;
+ // low resolution coordinates
+
+ int originX = celObj._origin.x;
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ originX = celObj._width - celObj._origin.x - 1;
}
- if (!newRatioX.isOne() || !newRatioY.isOne()) {
- mulru(_screenItemRect, newRatioX, newRatioY);
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ mulinc(_screenItemRect, scaleX, scaleY);
// TODO: This was in the original code, baked into the
- // multiplication though it is not immediately clear
+ // multiplication though it is not immediately clear
// why this is the only one that reduces the BR corner
_screenItemRect.right -= 1;
_screenItemRect.bottom -= 1;
}
- _scaledPosition.x = _position.x - (displaceX * newRatioX).toInt();
- _scaledPosition.y = _position.y - (_celObj->_displace.y * newRatioY).toInt();
+ _scaledPosition.x = _position.x - (originX * scaleX).toInt();
+ _scaledPosition.y = _position.y - (celObj._origin.y * scaleY).toInt();
_screenItemRect.translate(_scaledPosition.x, _scaledPosition.y);
- if (_mirrorX != _celObj->_mirrorX && _celInfo.type == kCelTypePic) {
+ if (_mirrorX != celObj._mirrorX && _celInfo.type == kCelTypePic) {
Common::Rect temp(_insetRect);
- if (!newRatioX.isOne()) {
- mulru(temp, newRatioX, Ratio());
+ if (!scaleX.isOne()) {
+ mulinc(temp, scaleX, Ratio());
temp.right -= 1;
}
CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj);
- temp.translate(celObjPic->_relativePosition.x - (displaceX * newRatioX).toInt(), celObjPic->_relativePosition.y - (_celObj->_displace.y * newRatioY).toInt());
+ if (celObjPic == nullptr) {
+ error("Expected a CelObjPic");
+ }
+ temp.translate(celObjPic->_relativePosition.x - (originX * scaleX).toInt(), celObjPic->_relativePosition.y - (celObj._origin.y * scaleY).toInt());
- // TODO: This is weird, and probably wrong calculation of widths
- // due to BR-inclusion
- int deltaX = plane._gameRect.right - plane._gameRect.left + 1 - temp.right - 1 - temp.left;
+ // TODO: This is weird.
+ int deltaX = plane._gameRect.width() - temp.right - 1 - temp.left;
_scaledPosition.x += deltaX;
_screenItemRect.translate(deltaX, 0);
@@ -365,15 +444,13 @@ void ScreenItem::calcRects(const Plane &plane) {
_scaledPosition.y += plane._gameRect.top;
_screenItemRect.translate(plane._gameRect.left, plane._gameRect.top);
- if (screenWidth != _celObj->_scaledWidth || _celObj->_scaledHeight != screenHeight) {
- Ratio celXRatio(screenWidth, _celObj->_scaledWidth);
- Ratio celYRatio(screenHeight, _celObj->_scaledHeight);
- mulru(_scaledPosition, celXRatio, celYRatio);
- mulru(_screenItemRect, celXRatio, celYRatio);
+ if (celObj._xResolution != screenWidth || celObj._yResolution != screenHeight) {
+ mulru(_scaledPosition, celToScreenX, celToScreenY);
+ mulru(_screenItemRect, celToScreenX, celToScreenY, 1);
}
- _ratioX = newRatioX * Ratio(screenWidth, _celObj->_scaledWidth);
- _ratioY = newRatioY * Ratio(screenHeight, _celObj->_scaledHeight);
+ _ratioX = scaleX * celToScreenX;
+ _ratioY = scaleY * celToScreenY;
}
_screenRect = _screenItemRect;
@@ -387,7 +464,7 @@ void ScreenItem::calcRects(const Plane &plane) {
_screenRect.top = 0;
}
- if (!_fixPriority) {
+ if (!_fixedPriority) {
_priority = _z + _position.y;
}
} else {
@@ -398,7 +475,7 @@ void ScreenItem::calcRects(const Plane &plane) {
}
}
-CelObj &ScreenItem::getCelObj() {
+CelObj &ScreenItem::getCelObj() const {
if (_celObj == nullptr) {
switch (_celInfo.type) {
case kCelTypeView:
@@ -420,10 +497,11 @@ CelObj &ScreenItem::getCelObj() {
}
void ScreenItem::printDebugInfo(Console *con) const {
- con->debugPrintf("%x:%x (%s), prio %d, x %d, y %d, z: %d, scaledX: %d, scaledY: %d flags: %d\n",
+ con->debugPrintf("%04x:%04x (%s), prio %d, ins %u, x %d, y %d, z: %d, scaledX: %d, scaledY: %d flags: %d\n",
_object.getSegment(), _object.getOffset(),
g_sci->getEngineState()->_segMan->getObjectName(_object),
_priority,
+ _creationId,
_position.x,
_position.y,
_z,
@@ -436,36 +514,14 @@ void ScreenItem::printDebugInfo(Console *con) const {
con->debugPrintf(" inset rect: (%d, %d, %d, %d)\n", PRINT_RECT(_insetRect));
}
- Common::String celType;
- switch (_celInfo.type) {
- case kCelTypePic:
- celType = "pic";
- break;
- case kCelTypeView:
- celType = "view";
- break;
- case kCelTypeColor:
- celType = "color";
- break;
- case kCelTypeMem:
- celType = "mem";
- break;
- }
+ con->debugPrintf(" %s\n", _celInfo.toString().c_str());
- con->debugPrintf(" type: %s, res %d, loop %d, cel %d, bitmap %04x:%04x, color: %d\n",
- celType.c_str(),
- _celInfo.resourceId,
- _celInfo.loopNo,
- _celInfo.celNo,
- PRINT_REG(_celInfo.bitmap),
- _celInfo.color
- );
if (_celObj != nullptr) {
- con->debugPrintf(" width %d, height %d, scaledWidth %d, scaledHeight %d\n",
+ con->debugPrintf(" width %d, height %d, x-resolution %d, y-resolution %d\n",
_celObj->_width,
_celObj->_height,
- _celObj->_scaledWidth,
- _celObj->_scaledHeight
+ _celObj->_xResolution,
+ _celObj->_yResolution
);
}
}
@@ -494,6 +550,144 @@ void ScreenItem::update(const reg_t object) {
_deleted = 0;
}
+void ScreenItem::update() {
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane);
+ if (plane == nullptr) {
+ error("ScreenItem::update: Invalid plane %04x:%04x", PRINT_REG(_plane));
+ }
+
+ if (plane->_screenItemList.findByObject(_object) == nullptr) {
+ error("ScreenItem::update: %04x:%04x not in plane %04x:%04x", PRINT_REG(_object), PRINT_REG(_plane));
+ }
+
+ if (!_created) {
+ _updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+ _deleted = 0;
+
+ delete _celObj;
+ _celObj = nullptr;
+}
+
+Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {
+ CelObj &celObj = getCelObj();
+
+ Common::Rect celObjRect(celObj._width, celObj._height);
+ Common::Rect nsRect;
+
+ if (_useInsetRect) {
+ if (_insetRect.intersects(celObjRect)) {
+ nsRect = _insetRect;
+ nsRect.clip(celObjRect);
+ } else {
+ nsRect = Common::Rect();
+ }
+ } else {
+ nsRect = celObjRect;
+ }
+
+ const uint16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ Ratio scaleX, scaleY;
+ if (_scale.signal == kScaleSignalManual) {
+ scaleX = Ratio(_scale.x, 128);
+ scaleY = Ratio(_scale.y, 128);
+ } else if (_scale.signal == kScaleSignalVanishingPoint) {
+ int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
+ scaleX = Ratio(num, 128);
+ scaleY = Ratio(num, 128);
+ }
+
+ if (scaleX.getNumerator() == 0 || scaleY.getNumerator() == 0) {
+ return Common::Rect();
+ }
+
+ int16 originX = celObj._origin.x;
+ int16 originY = celObj._origin.y;
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ originX = celObj._width - originX - 1;
+ }
+
+ if (celObj._xResolution != kLowResX || celObj._yResolution != kLowResY) {
+ // high resolution coordinates
+
+ if (_useInsetRect) {
+ Ratio scriptToCelX(celObj._xResolution, scriptWidth);
+ Ratio scriptToCelY(celObj._yResolution, scriptHeight);
+ mulru(nsRect, scriptToCelX, scriptToCelY, 0);
+
+ if (nsRect.intersects(celObjRect)) {
+ nsRect.clip(celObjRect);
+ } else {
+ nsRect = Common::Rect();
+ }
+ }
+
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ // Different games use a different cel scaling mode, but the
+ // difference isn't consistent across SCI versions; instead,
+ // it seems to be related to an update that happened during
+ // SCI2.1mid where games started using hi-resolution game
+ // scripts
+ if (scriptWidth == kLowResX) {
+ mulinc(nsRect, scaleX, scaleY);
+ // TODO: This was in the original code, baked into the
+ // multiplication though it is not immediately clear
+ // why this is the only one that reduces the BR corner
+ nsRect.right -= 1;
+ nsRect.bottom -= 1;
+ } else {
+ nsRect.left = (nsRect.left * scaleX).toInt();
+ nsRect.top = (nsRect.top * scaleY).toInt();
+
+ if (scaleX.getNumerator() > scaleX.getDenominator()) {
+ nsRect.right = (nsRect.right * scaleX).toInt();
+ } else {
+ nsRect.right = ((nsRect.right - 1) * scaleX).toInt() + 1;
+ }
+
+ if (scaleY.getNumerator() > scaleY.getDenominator()) {
+ nsRect.bottom = (nsRect.bottom * scaleY).toInt();
+ } else {
+ nsRect.bottom = ((nsRect.bottom - 1) * scaleY).toInt() + 1;
+ }
+ }
+ }
+
+ Ratio celToScriptX(scriptWidth, celObj._xResolution);
+ Ratio celToScriptY(scriptHeight, celObj._yResolution);
+
+ originX = (originX * scaleX * celToScriptX).toInt();
+ originY = (originY * scaleY * celToScriptY).toInt();
+
+ mulinc(nsRect, celToScriptX, celToScriptY);
+ nsRect.translate(_position.x - originX, _position.y - originY);
+ } else {
+ // low resolution coordinates
+
+ if (!scaleX.isOne() || !scaleY.isOne()) {
+ mulinc(nsRect, scaleX, scaleY);
+ // TODO: This was in the original code, baked into the
+ // multiplication though it is not immediately clear
+ // why this is the only one that reduces the BR corner
+ nsRect.right -= 1;
+ nsRect.bottom -= 1;
+ }
+
+ originX = (originX * scaleX).toInt();
+ originY = (originY * scaleY).toInt();
+ nsRect.translate(_position.x - originX, _position.y - originY);
+
+ if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
+ nsRect.translate(plane._gameRect.width() - nsRect.width(), 0);
+ }
+ }
+
+ return nsRect;
+}
+
#pragma mark -
#pragma mark ScreenItemList
ScreenItem *ScreenItemList::findByObject(const reg_t object) const {
@@ -506,24 +700,44 @@ ScreenItem *ScreenItemList::findByObject(const reg_t object) const {
return *screenItemIt;
}
void ScreenItemList::sort() {
- // TODO: SCI engine used _unsorted as an array of indexes into the
- // list itself and then performed the same swap operations on the
- // _unsorted array as the _storage array during sorting, but the
- // only reason to do this would be if some of the pointers in the
- // list were replaced so the pointer values themselves couldn’t
- // simply be recorded and then restored later. It is not yet
- // verified whether this simplification of the sort/unsort is
- // safe.
+ if (size() < 2) {
+ return;
+ }
+
for (size_type i = 0; i < size(); ++i) {
- _unsorted[i] = (*this)[i];
+ _unsorted[i] = i;
}
- Common::sort(begin(), end(), sortHelper);
+ for (size_type i = size() - 1; i > 0; --i) {
+ bool swap = false;
+
+ for (size_type j = 0; j < i; ++j) {
+ value_type &a = operator[](j);
+ value_type &b = operator[](j + 1);
+
+ if (a == nullptr || *a > *b) {
+ SWAP(a, b);
+ SWAP(_unsorted[j], _unsorted[j + 1]);
+ swap = true;
+ }
+ }
+
+ if (!swap) {
+ break;
+ }
+ }
}
void ScreenItemList::unsort() {
+ if (size() < 2) {
+ return;
+ }
+
for (size_type i = 0; i < size(); ++i) {
- (*this)[i] = _unsorted[i];
+ while (_unsorted[i] != i) {
+ SWAP(operator[](_unsorted[i]), operator[](i));
+ SWAP(_unsorted[_unsorted[i]], _unsorted[i]);
+ }
}
}
-}
+} // End of namespace Sci
diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h
index 0ca840a13a..c2c4e43358 100644
--- a/engines/sci/graphics/screen_item32.h
+++ b/engines/sci/graphics/screen_item32.h
@@ -30,12 +30,9 @@
namespace Sci {
enum ScaleSignals32 {
- kScaleSignalNone = 0,
- kScaleSignalDoScaling32 = 1, // enables scaling when drawing that cel (involves scaleX and scaleY)
- kScaleSignalUseVanishingPoint = 2,
- // TODO: Is this actually a thing? I have not seen it and
- // the original engine masks &3 where it uses scale signals.
- kScaleSignalDisableGlobalScaling32 = 4
+ kScaleSignalNone = 0,
+ kScaleSignalManual = 1,
+ kScaleSignalVanishingPoint = 2
};
struct ScaleInfo {
@@ -65,11 +62,18 @@ private:
static uint16 _nextObjectId;
/**
+ * A serial used to identify the creation order of
+ * screen items, to ensure a stable sort order for
+ * screen items with identical priorities and z-indexes.
+ */
+ static uint32 _nextCreationId;
+
+public:
+ /**
* The parent plane of this screen item.
*/
reg_t _plane;
-public:
/**
* Scaling data used to calculate the final screen
* dimensions of the screen item as well as the scaling
@@ -88,15 +92,23 @@ private:
Common::Rect _screenItemRect;
/**
- * TODO: Document
+ * If true, the `_insetRect` rectangle will be used
+ * when calculating the dimensions of the screen item
+ * instead of the cel's intrinsic width and height.
+ *
+ * In other words, using an inset rect means that
+ * the cel is cropped to the dimensions given in
+ * `_insetRect`.
*/
bool _useInsetRect;
/**
- * TODO: Documentation
- * The insetRect is also used to describe the fill
- * rectangle of a screen item that is drawn using
- * CelObjColor.
+ * The cropping rectangle used when `_useInsetRect`
+ * is true.
+ *
+ * `_insetRect` is also used to describe the fill
+ * rectangle of a screen item with a CelObjColor
+ * cel.
*/
Common::Rect _insetRect;
@@ -115,6 +127,13 @@ private:
public:
/**
+ * The creation order number, which ensures a stable
+ * sort when screen items with identical priorities and
+ * z-indexes are added to the screen item list.
+ */
+ uint32 _creationId;
+
+ /**
* A descriptor for the cel object represented by the
* screen item.
*/
@@ -125,14 +144,14 @@ public:
* item. This member is populated by calling
* `getCelObj`.
*/
- CelObj *_celObj;
+ mutable CelObj *_celObj;
/**
* If set, the priority for this screen item is fixed
* in place. Otherwise, the priority of the screen item
* is calculated from its y-position + z-index.
*/
- bool _fixPriority;
+ bool _fixedPriority;
/**
* The rendering priority of the screen item, relative
@@ -172,7 +191,7 @@ public:
* plane is a pic type and its picture resource ID has
* changed
*/
- int _created, _updated, _deleted; // ?
+ int _created, _updated, _deleted;
/**
* For screen items that represent picture cels, this
@@ -206,6 +225,14 @@ public:
Common::Rect _screenRect;
/**
+ * Whether or not the screen item should be drawn
+ * with black lines drawn every second line. This is
+ * used when pixel doubling videos to improve apparent
+ * sharpness at the cost of your eyesight.
+ */
+ bool _drawBlackLines;
+
+ /**
* Initialises static Plane members.
*/
static void init();
@@ -213,8 +240,9 @@ public:
ScreenItem(const reg_t screenItem);
ScreenItem(const reg_t plane, const CelInfo32 &celInfo);
ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect);
- ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect, const ScaleInfo &scaleInfo);
+ ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Point &position, const ScaleInfo &scaleInfo);
ScreenItem(const ScreenItem &other);
+ ~ScreenItem();
void operator=(const ScreenItem &);
inline bool operator<(const ScreenItem &other) const {
@@ -228,9 +256,46 @@ public:
}
if (_position.y + _z == other._position.y + other._z) {
- return false;
- // TODO: Failure in SQ6 room 220
-// return _object < other._object;
+ // SSCI's last resort comparison here is to compare the _object
+ // IDs, but this is wrong and randomly breaks (at least):
+ //
+ // (1) the death dialog at the end of Phant1, where the ID of
+ // the text is often higher than the ID of the border;
+ // (2) text-based buttons and dialogues in Hoyle5, where the ID
+ // of the text is often lower than the ID of the
+ // button/dialogue background.
+ //
+ // This occurs because object IDs (in both ScummVM and SSCI) are
+ // reused, so objects created later may receive a lower ID,
+ // which makes them sort lower when the programmer intended them
+ // to sort higher.
+ //
+ // To fix this problem, we give each ScreenItem a monotonically
+ // increasing insertion ID at construction time, and compare
+ // these insertion IDs instead. They are more stable and cause
+ // objects with identical priority and z-index to be rendered in
+ // the order that they were created.
+ return _creationId < other._creationId;
+ }
+ }
+
+ return false;
+ }
+
+ inline bool operator>(const ScreenItem &other) const {
+ if (_priority > other._priority) {
+ return true;
+ }
+
+ if (_priority == other._priority) {
+ if (_position.y + _z > other._position.y + other._z) {
+ return true;
+ }
+
+ if (_position.y + _z == other._position.y + other._z) {
+ // This is different than SSCI; see ScreenItem::operator< for an
+ // explanation
+ return _creationId > other._creationId;
}
}
@@ -251,7 +316,7 @@ public:
* screen item. If a cel object does not already exist,
* one will be created and assigned.
*/
- CelObj &getCelObj();
+ CelObj &getCelObj() const;
void printDebugInfo(Console *con) const;
@@ -260,6 +325,19 @@ public:
* VM object.
*/
void update(const reg_t object);
+
+ /**
+ * Updates the properties of the screen item for one not belonging
+ * to a VM object. Originally GraphicsMgr::UpdateScreenItem.
+ */
+ void update();
+
+ /**
+ * Gets the "now seen" rect for the screen item, which
+ * represents the current size and position of the
+ * screen item on the screen in script coordinates.
+ */
+ Common::Rect getNowSeenRect(const Plane &plane) const;
};
#pragma mark -
@@ -267,16 +345,14 @@ public:
typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase;
class ScreenItemList : public ScreenItemListBase {
- static bool inline sortHelper(const ScreenItem *a, const ScreenItem *b) {
- return *a < *b;
- }
-public:
- ScreenItem *_unsorted[250];
+private:
+ size_type _unsorted[250];
+public:
ScreenItem *findByObject(const reg_t object) const;
void sort();
void unsort();
};
-}
+} // End of namespace Sci
#endif
diff --git a/engines/sci/graphics/text16.cpp b/engines/sci/graphics/text16.cpp
index b0f2c52791..b5dd9aee0b 100644
--- a/engines/sci/graphics/text16.cpp
+++ b/engines/sci/graphics/text16.cpp
@@ -83,7 +83,7 @@ void GfxText16::ClearChar(int16 chr) {
}
// This internal function gets called as soon as a '|' is found in a text. It
-// will process the encountered code and set new font/set color.
+// will process the encountered code and set new font/set color.
// Returns textcode character count.
int16 GfxText16::CodeProcessing(const char *&text, GuiResourceId orgFontId, int16 orgPenColor, bool doingDrawing) {
const char *textCode = text;
@@ -179,7 +179,7 @@ static const uint16 text16_shiftJIS_punctuation_SCI01[] = {
// return max # of chars to fit maxwidth with full words, does not include
// breaking space
// 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
@@ -245,7 +245,7 @@ int16 GfxText16::GetLongest(const char *&textPtr, int16 maxWidth, GuiResourceId
break;
}
tempWidth += _font->getCharWidth(curChar);
-
+
// Width is too large? -> break out
if (tempWidth > maxWidth)
break;
@@ -633,7 +633,7 @@ reg_t GfxText16::allocAndFillReferenceRectArray() {
if (rectCount) {
reg_t rectArray;
byte *rectArrayPtr = g_sci->getEngineState()->_segMan->allocDynmem(4 * 2 * (rectCount + 1), "text code reference rects", &rectArray);
- GfxCoordAdjuster *coordAdjuster = g_sci->_gfxCoordAdjuster;
+ GfxCoordAdjuster16 *coordAdjuster = g_sci->_gfxCoordAdjuster;
for (uint curRect = 0; curRect < rectCount; curRect++) {
coordAdjuster->kernelLocalToGlobal(_codeRefRects[curRect].left, _codeRefRects[curRect].top);
coordAdjuster->kernelLocalToGlobal(_codeRefRects[curRect].right, _codeRefRects[curRect].bottom);
diff --git a/engines/sci/graphics/text16.h b/engines/sci/graphics/text16.h
index 2724d97347..eb39fb2513 100644
--- a/engines/sci/graphics/text16.h
+++ b/engines/sci/graphics/text16.h
@@ -64,7 +64,7 @@ public:
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);
diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp
index eabc329ee0..181dabc9a8 100644
--- a/engines/sci/graphics/text32.cpp
+++ b/engines/sci/graphics/text32.cpp
@@ -29,6 +29,7 @@
#include "sci/engine/selector.h"
#include "sci/engine/state.h"
#include "sci/graphics/cache.h"
+#include "sci/graphics/celobj32.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/font.h"
#include "sci/graphics/frameout.h"
@@ -37,51 +38,29 @@
namespace Sci {
-#define BITMAP_HEADER_SIZE 46
+int16 GfxText32::_defaultFontId = 0;
+int16 GfxText32::_xResolution = 0;
+int16 GfxText32::_yResolution = 0;
-GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen) :
+GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) :
_segMan(segMan),
_cache(fonts),
- _screen(screen),
- _scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
- _scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
- _bitmap(NULL_REG) {}
-
-void GfxText32::buildBitmapHeader(byte *bitmap, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool useRemap) const {
-
- WRITE_SCI11ENDIAN_UINT16(bitmap + 0, width);
- WRITE_SCI11ENDIAN_UINT16(bitmap + 2, height);
- WRITE_SCI11ENDIAN_UINT16(bitmap + 4, (uint16)displaceX);
- WRITE_SCI11ENDIAN_UINT16(bitmap + 6, (uint16)displaceY);
- bitmap[8] = skipColor;
- bitmap[9] = 0;
- WRITE_SCI11ENDIAN_UINT16(bitmap + 10, 0);
-
- if (useRemap) {
- bitmap[10] |= 2;
+ // Not a typo, the original engine did not initialise height, only width
+ _width(0),
+ _text(""),
+ _bitmap(NULL_REG) {
+ _fontId = _defaultFontId;
+ _font = _cache->getFont(_defaultFontId);
+
+ if (_xResolution == 0) {
+ // initialize the statics
+ _xResolution = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ _yResolution = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ }
}
- WRITE_SCI11ENDIAN_UINT32(bitmap + 12, width * height);
- WRITE_SCI11ENDIAN_UINT32(bitmap + 16, 0);
-
- if (hunkPaletteOffset) {
- WRITE_SCI11ENDIAN_UINT32(bitmap + 20, hunkPaletteOffset + BITMAP_HEADER_SIZE);
- } else {
- WRITE_SCI11ENDIAN_UINT32(bitmap + 20, 0);
- }
+reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling, const bool gc) {
- WRITE_SCI11ENDIAN_UINT32(bitmap + 24, BITMAP_HEADER_SIZE);
- WRITE_SCI11ENDIAN_UINT32(bitmap + 28, BITMAP_HEADER_SIZE);
- WRITE_SCI11ENDIAN_UINT32(bitmap + 32, 0);
- WRITE_SCI11ENDIAN_UINT16(bitmap + 36, scaledWidth);
- WRITE_SCI11ENDIAN_UINT16(bitmap + 38, scaledHeight);
-}
-
-int16 GfxText32::_defaultFontId = 0;
-
-reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling, reg_t *outBitmapObject) {
-
- _field_22 = 0;
_borderColor = borderColor;
_text = text;
_textRect = rect;
@@ -93,21 +72,18 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect
_alignment = alignment;
_dimmed = dimmed;
- if (fontId != _fontId) {
- _fontId = fontId == -1 ? _defaultFontId : fontId;
- _font = _cache->getFont(_fontId);
- }
+ setFont(fontId);
if (doScaling) {
int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
- Ratio scaleX(_scaledWidth, scriptWidth);
- Ratio scaleY(_scaledHeight, scriptHeight);
+ Ratio scaleX(_xResolution, scriptWidth);
+ Ratio scaleY(_yResolution, scriptHeight);
_width = (_width * scaleX).toInt();
_height = (_height * scaleY).toInt();
- mul(_textRect, scaleX, scaleY);
+ mulinc(_textRect, scaleX, scaleY);
}
// _textRect represents where text is drawn inside the
@@ -120,10 +96,7 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect
_textRect = Common::Rect();
}
- _bitmap = _segMan->allocateHunkEntry("FontBitmap()", _width * _height + BITMAP_HEADER_SIZE);
-
- byte *bitmap = _segMan->getHunkPointer(_bitmap);
- buildBitmapHeader(bitmap, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false);
+ _segMan->allocateBitmap(&_bitmap, _width, _height, _skipColor, 0, 0, _xResolution, _yResolution, 0, false, gc);
erase(bitmapRect, false);
@@ -132,461 +105,616 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect
}
drawTextBox();
+ return _bitmap;
+}
+
+reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed, const bool gc) {
+ _borderColor = borderColor;
+ _text = text;
+ _textRect = rect;
+ _foreColor = foreColor;
+ _dimmed = dimmed;
+
+ setFont(fontId);
+
+ int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
- debug("Drawing a bitmap %dx%d, scaled %dx%d, border %d, font %d", width, height, _width, _height, _borderColor, _fontId);
+ mulinc(_textRect, Ratio(_xResolution, scriptWidth), Ratio(_yResolution, scriptHeight));
+
+ CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo);
+ _skipColor = view._skipColor;
+ _width = view._width * _xResolution / view._xResolution;
+ _height = view._height * _yResolution / view._yResolution;
+
+ Common::Rect bitmapRect(_width, _height);
+ if (_textRect.intersects(bitmapRect)) {
+ _textRect.clip(bitmapRect);
+ } else {
+ _textRect = Common::Rect();
+ }
+
+ SciBitmap &bitmap = *_segMan->allocateBitmap(&_bitmap, _width, _height, _skipColor, 0, 0, _xResolution, _yResolution, 0, false, gc);
+
+ // NOTE: The engine filled the bitmap pixels with 11 here, which is silly
+ // because then it just erased the bitmap using the skip color. So we don't
+ // fill the bitmap redundantly here.
+
+ _backColor = _skipColor;
+ erase(bitmapRect, false);
+ _backColor = backColor;
+
+ view.draw(bitmap.getBuffer(), bitmapRect, Common::Point(0, 0), false, Ratio(_xResolution, view._xResolution), Ratio(_yResolution, view._yResolution));
+
+ if (_backColor != skipColor && _foreColor != skipColor) {
+ erase(_textRect, false);
+ }
+
+ if (text.size() > 0) {
+ if (_foreColor == skipColor) {
+ error("TODO: Implement transparent text");
+ } else {
+ if (borderColor != -1) {
+ drawFrame(bitmapRect, 1, _borderColor, false);
+ }
+
+ drawTextBox();
+ }
+ }
- *outBitmapObject = _bitmap;
return _bitmap;
}
-reg_t GfxText32::createTitledFontBitmap(CelInfo32 &celInfo, Common::Rect &rect, Common::String &text, int16 foreColor, int16 backColor, int font, int16 skipColor, int16 borderColor, bool dimmed, void *unknown1) {
- warning("TODO: createTitledFontBitmap");
- return NULL_REG;
+void GfxText32::setFont(const GuiResourceId fontId) {
+ // NOTE: In SCI engine this calls FontMgr::BuildFontTable and then a font
+ // table is built on the FontMgr directly; instead, because we already have
+ // font resources, this code just grabs a font out of GfxCache.
+ if (fontId != _fontId) {
+ _fontId = fontId == -1 ? _defaultFontId : fontId;
+ _font = _cache->getFont(_fontId);
+ }
}
-void GfxText32::drawFrame(const Common::Rect &rect, const int size, const uint8 color, const bool doScaling) {
+void GfxText32::drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling) {
Common::Rect targetRect = doScaling ? scaleRect(rect) : rect;
- byte *bitmap = _segMan->getHunkPointer(_bitmap);
- byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28);
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
+ byte *pixels = bitmap.getPixels() + rect.top * _width + rect.left;
// NOTE: Not fully disassembled, but this should be right
- // TODO: Implement variable frame size
- assert(size == 1);
- Buffer buffer(_width, _height, pixels);
- buffer.frameRect(targetRect, color);
+ int16 rectWidth = targetRect.width();
+ int16 heightRemaining = targetRect.height();
+ int16 sidesHeight = heightRemaining - size * 2;
+ int16 centerWidth = rectWidth - size * 2;
+ int16 stride = _width - rectWidth;
+
+ for (int16 y = 0; y < size && y < heightRemaining; ++y) {
+ memset(pixels, color, rectWidth);
+ pixels += _width;
+ --heightRemaining;
+ }
+ for (int16 y = 0; y < sidesHeight; ++y) {
+ for (int16 x = 0; x < size; ++x) {
+ *pixels++ = color;
+ }
+ pixels += centerWidth;
+ for (int16 x = 0; x < size; ++x) {
+ *pixels++ = color;
+ }
+ pixels += stride;
+ }
+ for (int16 y = 0; y < size && y < heightRemaining; ++y) {
+ memset(pixels, color, rectWidth);
+ pixels += _width;
+ --heightRemaining;
+ }
+}
+
+void GfxText32::drawChar(const char charIndex) {
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
+ byte *pixels = bitmap.getPixels();
+
+ _font->drawToBuffer((unsigned char)charIndex, _drawPosition.y, _drawPosition.x, _foreColor, _dimmed, pixels, _width, _height);
+ _drawPosition.x += _font->getCharWidth((unsigned char)charIndex);
+}
+
+uint16 GfxText32::getCharWidth(const char charIndex, const bool doScaling) const {
+ uint16 width = _font->getCharWidth((unsigned char)charIndex);
+ if (doScaling) {
+ width = scaleUpWidth(width);
+ }
+ return width;
}
-// TODO: This is not disassembled
void GfxText32::drawTextBox() {
- int16 charCount = 0;
- uint16 curX = 0, curY = 0;
- const char *txt = _text.c_str();
- int16 textWidth, textHeight, totalHeight = 0, offsetX = 0, offsetY = 0;
- uint16 start = 0;
-
- // Calculate total text height
- while (*txt) {
- charCount = GetLongest(txt, _textRect.width(), _font);
- if (charCount == 0)
- break;
-
- Width(txt, 0, (int16)strlen(txt), _fontId, textWidth, textHeight, true);
-
- totalHeight += textHeight;
- txt += charCount;
- while (*txt == ' ') {
- txt++; // skip over breaking spaces
- }
+ if (_text.size() == 0) {
+ return;
}
- txt = _text.c_str();
-
- byte *pixels = _segMan->getHunkPointer(_bitmap);
- pixels = pixels + READ_SCI11ENDIAN_UINT32(pixels + 28) + _width * _textRect.top + _textRect.left;
-
- // Draw text in buffer
- while (*txt) {
- charCount = GetLongest(txt, _textRect.width(), _font);
- if (charCount == 0)
- break;
- Width(txt, start, charCount, _fontId, textWidth, textHeight, true);
-
- switch (_alignment) {
- case kTextAlignRight:
- offsetX = _textRect.width() - textWidth;
- break;
- case kTextAlignCenter:
- // Center text both horizontally and vertically
- offsetX = (_textRect.width() - textWidth) / 2;
- offsetY = (_textRect.height() - totalHeight) / 2;
- break;
- case kTextAlignLeft:
- offsetX = 0;
- break;
-
- default:
- warning("Invalid alignment %d used in TextBox()", _alignment);
- }
+ const char *text = _text.c_str();
+ const char *sourceText = text;
+ int16 textRectWidth = _textRect.width();
+ _drawPosition.y = _textRect.top;
+ uint charIndex = 0;
- byte curChar;
-
- for (int i = 0; i < charCount; i++) {
- curChar = txt[i];
-
- switch (curChar) {
- case 0x0A:
- case 0x0D:
- case 0:
- break;
- case 0x7C:
- warning("Code processing isn't implemented in SCI32");
- break;
- default:
- _font->drawToBuffer(curChar, curY + offsetY, curX + offsetX, _foreColor, _dimmed, pixels, _width, _height);
- curX += _font->getCharWidth(curChar);
- break;
- }
+ if (g_sci->getGameId() == GID_SQ6 || g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
+ if (getLongest(&charIndex, textRectWidth) == 0) {
+ error("DrawTextBox GetLongest=0");
}
+ }
+
+ charIndex = 0;
+ uint nextCharIndex = 0;
+ while (*text != '\0') {
+ _drawPosition.x = _textRect.left;
+
+ uint length = getLongest(&nextCharIndex, textRectWidth);
+ int16 textWidth = getTextWidth(charIndex, length);
- curX = 0;
- curY += _font->getHeight();
- txt += charCount;
- while (*txt == ' ') {
- txt++; // skip over breaking spaces
+ if (_alignment == kTextAlignCenter) {
+ _drawPosition.x += (textRectWidth - textWidth) / 2;
+ } else if (_alignment == kTextAlignRight) {
+ _drawPosition.x += textRectWidth - textWidth;
}
+
+ drawText(charIndex, length);
+ charIndex = nextCharIndex;
+ text = sourceText + charIndex;
+ _drawPosition.y += _font->getHeight();
}
}
-void GfxText32::erase(const Common::Rect &rect, const bool doScaling) {
- Common::Rect targetRect = doScaling ? rect : scaleRect(rect);
+void GfxText32::drawTextBox(const Common::String &text) {
+ _text = text;
+ drawTextBox();
+}
- byte *bitmap = _segMan->getHunkPointer(_bitmap);
- byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28);
+void GfxText32::drawText(const uint index, uint length) {
+ assert(index + length <= _text.size());
- // NOTE: There is an extra optimisation within the SCI code to
- // do a single memset if the scaledRect is the same size as
- // the bitmap, not implemented here.
- Buffer buffer(_width, _height, pixels);
- buffer.fillRect(targetRect, _backColor);
-}
+ // NOTE: This draw loop implementation is somewhat different than the
+ // implementation in the actual engine, but should be accurate. Primarily
+ // the changes revolve around eliminating some extra temporaries and
+ // fixing the logic to match.
+ const char *text = _text.c_str() + index;
+ while (length-- > 0) {
+ char currentChar = *text++;
-reg_t GfxText32::createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) {
- return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk);
-}
-reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) {
- reg_t stringObject = readSelector(_segMan, textObject, SELECTOR(text));
- // The object in the text selector of the item can be either a raw string
- // or a Str object. In the latter case, we need to access the object's data
- // selector to get the raw string.
- if (_segMan->isHeapObject(stringObject))
- stringObject = readSelector(_segMan, stringObject, SELECTOR(data));
+ if (currentChar == '|') {
+ const char controlChar = *text++;
+ --length;
- Common::String text = _segMan->getString(stringObject);
+ if (length == 0) {
+ return;
+ }
+
+ if (controlChar == 'a' || controlChar == 'c' || controlChar == 'f') {
+ uint16 value = 0;
+
+ while (length > 0) {
+ const char valueChar = *text;
+ if (valueChar < '0' || valueChar > '9') {
+ break;
+ }
+
+ ++text;
+ --length;
+ value = 10 * value + (valueChar - '0');
+ }
+
+ if (length == 0) {
+ return;
+ }
+
+ if (controlChar == 'a') {
+ _alignment = (TextAlign)value;
+ } else if (controlChar == 'c') {
+ _foreColor = value;
+ } else if (controlChar == 'f') {
+ setFont(value);
+ }
+ }
- return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk);
+ while (length > 0 && *text != '|') {
+ ++text;
+ --length;
+ }
+ if (length > 0) {
+ ++text;
+ --length;
+ }
+ } else {
+ drawChar(currentChar);
+ }
+ }
}
-reg_t GfxText32::createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) {
- GuiResourceId fontId = readSelectorValue(_segMan, textObject, SELECTOR(font));
- GfxFont *font = _cache->getFont(fontId);
- bool dimmed = readSelectorValue(_segMan, textObject, SELECTOR(dimmed));
- int16 alignment = readSelectorValue(_segMan, textObject, SELECTOR(mode));
- uint16 foreColor = readSelectorValue(_segMan, textObject, SELECTOR(fore));
- uint16 backColor = readSelectorValue(_segMan, textObject, SELECTOR(back));
-
- Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(textObject);
- uint16 width = nsRect.width() + 1;
- uint16 height = nsRect.height() + 1;
-
- // Limit rectangle dimensions, if requested
- if (maxWidth > 0)
- width = maxWidth;
- if (maxHeight > 0)
- height = maxHeight;
-
- // Upscale the coordinates/width if the fonts are already upscaled
- if (_screen->fontIsUpscaled()) {
- width = width * _screen->getDisplayWidth() / _screen->getWidth();
- height = height * _screen->getDisplayHeight() / _screen->getHeight();
+void GfxText32::invertRect(const reg_t bitmapId, int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling) {
+ Common::Rect targetRect = rect;
+ if (doScaling) {
+ bitmapStride = bitmapStride * _xResolution / g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ targetRect = scaleRect(rect);
}
- int entrySize = width * height + BITMAP_HEADER_SIZE;
- reg_t memoryId = NULL_REG;
- if (prevHunk.isNull()) {
- memoryId = _segMan->allocateHunkEntry("TextBitmap()", entrySize);
+ SciBitmap &bitmap = *_segMan->lookupBitmap(bitmapId);
- // Scroll text objects have no bitmap selector!
- ObjVarRef varp;
- if (lookupSelector(_segMan, textObject, SELECTOR(bitmap), &varp, NULL) == kSelectorVariable)
- writeSelector(_segMan, textObject, SELECTOR(bitmap), memoryId);
- } else {
- memoryId = prevHunk;
- }
- byte *memoryPtr = _segMan->getHunkPointer(memoryId);
-
- if (prevHunk.isNull())
- memset(memoryPtr, 0, BITMAP_HEADER_SIZE);
-
- byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE;
- memset(bitmap, backColor, width * height);
-
- // Save totalWidth, totalHeight
- WRITE_SCI11ENDIAN_UINT16(memoryPtr, width);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 2, height);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 4, 0);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 6, 0);
- memoryPtr[8] = 0;
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 10, 0);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 20, BITMAP_HEADER_SIZE);
- WRITE_SCI11ENDIAN_UINT32(memoryPtr + 28, 46);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 36, width);
- WRITE_SCI11ENDIAN_UINT16(memoryPtr + 38, height);
-
- int16 charCount = 0;
- uint16 curX = 0, curY = 0;
- const char *txt = text.c_str();
- int16 textWidth, textHeight, totalHeight = 0, offsetX = 0, offsetY = 0;
- uint16 start = 0;
-
- // Calculate total text height
- while (*txt) {
- charCount = GetLongest(txt, width, font);
- if (charCount == 0)
- break;
-
- Width(txt, 0, (int16)strlen(txt), fontId, textWidth, textHeight, true);
-
- totalHeight += textHeight;
- txt += charCount;
- while (*txt == ' ')
- txt++; // skip over breaking spaces
+ // NOTE: SCI code is super weird here; it seems to be trying to look at the
+ // entire size of the bitmap including the header, instead of just the pixel
+ // data size. We just look at the pixel size. This function generally is an
+ // odd duck since the stride dimension for a bitmap is built in to the bitmap
+ // header, so perhaps it was once an unheadered bitmap format and this
+ // function was never updated to match? Or maybe they exploit the
+ // configurable stride length somewhere else to do stair stepping inverts...
+ uint32 invertSize = targetRect.height() * bitmapStride + targetRect.width();
+ uint32 bitmapSize = bitmap.getDataSize();
+
+ if (invertSize >= bitmapSize) {
+ error("InvertRect too big: %u >= %u", invertSize, bitmapSize);
}
- txt = text.c_str();
-
- // Draw text in buffer
- while (*txt) {
- charCount = GetLongest(txt, width, font);
- if (charCount == 0)
- break;
- Width(txt, start, charCount, fontId, textWidth, textHeight, true);
-
- switch (alignment) {
- case kTextAlignRight:
- offsetX = width - textWidth;
- break;
- case kTextAlignCenter:
- // Center text both horizontally and vertically
- offsetX = (width - textWidth) / 2;
- offsetY = (height - totalHeight) / 2;
- break;
- case kTextAlignLeft:
- offsetX = 0;
- break;
-
- default:
- warning("Invalid alignment %d used in TextBox()", alignment);
- }
+ // NOTE: Actual engine just added the bitmap header size hardcoded here
+ byte *pixel = bitmap.getPixels() + bitmapStride * targetRect.top + targetRect.left;
+
+ int16 stride = bitmapStride - targetRect.width();
+ int16 targetHeight = targetRect.height();
+ int16 targetWidth = targetRect.width();
- byte curChar;
-
- for (int i = 0; i < charCount; i++) {
- curChar = txt[i];
-
- switch (curChar) {
- case 0x0A:
- case 0x0D:
- case 0:
- break;
- case 0x7C:
- warning("Code processing isn't implemented in SCI32");
- break;
- default:
- font->drawToBuffer(curChar, curY + offsetY, curX + offsetX, foreColor, dimmed, bitmap, width, height);
- curX += font->getCharWidth(curChar);
- break;
+ for (int16 y = 0; y < targetHeight; ++y) {
+ for (int16 x = 0; x < targetWidth; ++x) {
+ if (*pixel == foreColor) {
+ *pixel = backColor;
+ } else if (*pixel == backColor) {
+ *pixel = foreColor;
}
+
+ ++pixel;
}
- curX = 0;
- curY += font->getHeight();
- txt += charCount;
- while (*txt == ' ')
- txt++; // skip over breaking spaces
+ pixel += stride;
}
-
- return memoryId;
}
-void GfxText32::disposeTextBitmap(reg_t hunkId) {
- _segMan->freeHunkEntry(hunkId);
-}
+uint GfxText32::getLongest(uint *charIndex, const int16 width) {
+ assert(width > 0);
-void GfxText32::drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject) {
- reg_t hunkId = readSelector(_segMan, textObject, SELECTOR(bitmap));
- drawTextBitmapInternal(x, y, planeRect, textObject, hunkId);
-}
+ uint testLength = 0;
+ uint length = 0;
-void GfxText32::drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y) {
- /*reg_t plane = readSelector(_segMan, textObject, SELECTOR(plane));
- Common::Rect planeRect;
- planeRect.top = readSelectorValue(_segMan, plane, SELECTOR(top));
- planeRect.left = readSelectorValue(_segMan, plane, SELECTOR(left));
- planeRect.bottom = readSelectorValue(_segMan, plane, SELECTOR(bottom));
- planeRect.right = readSelectorValue(_segMan, plane, SELECTOR(right));
+ const uint initialCharIndex = *charIndex;
- drawTextBitmapInternal(x, y, planeRect, textObject, hunkId);*/
+ // The index of the next word after the last word break
+ uint lastWordBreakIndex = *charIndex;
- // HACK: we pretty much ignore the plane rect and x, y...
- drawTextBitmapInternal(0, 0, Common::Rect(20, 390, 600, 460), textObject, hunkId);
-}
+ const char *text = _text.c_str() + *charIndex;
-void GfxText32::drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId) {
- int16 backColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(back));
- // Sanity check: Check if the hunk is set. If not, either the game scripts
- // didn't set it, or an old saved game has been loaded, where it wasn't set.
- if (hunkId.isNull())
- return;
+ char currentChar;
+ while ((currentChar = *text++) != '\0') {
+ // NOTE: In the original engine, the font, color, and alignment were
+ // reset here to their initial values
- // Negative coordinates indicate that text shouldn't be displayed
- if (x < 0 || y < 0)
- return;
+ // The text to render contains a line break; stop at the line break
+ if (currentChar == '\r' || currentChar == '\n') {
+ // Skip the rest of the line break if it is a Windows-style
+ // \r\n or non-standard \n\r
+ // NOTE: In the original engine, the `text` pointer had not been
+ // advanced yet so the indexes used to access characters were
+ // one higher
+ if (
+ (currentChar == '\r' && text[0] == '\n') ||
+ (currentChar == '\n' && text[0] == '\r' && text[1] != '\n')
+ ) {
+ ++*charIndex;
+ }
- byte *memoryPtr = _segMan->getHunkPointer(hunkId);
+ // We are at the end of a line but the last word in the line made
+ // it too wide to fit in the text area; return up to the previous
+ // word
+ if (length && getTextWidth(initialCharIndex, testLength) > width) {
+ *charIndex = lastWordBreakIndex;
+ return length;
+ }
- if (!memoryPtr) {
- // Happens when restoring in some SCI32 games (e.g. SQ6).
- // Commented out to reduce console spam
- //warning("Attempt to draw an invalid text bitmap");
- return;
- }
+ // Skip the line break and return all text seen up to now
+ // NOTE: In original engine, the font, color, and alignment were
+ // reset, then getTextWidth was called to use its side-effects to
+ // set font, color, and alignment according to the text from
+ // `initialCharIndex` to `testLength`
+ ++*charIndex;
+ return testLength;
+ } else if (currentChar == ' ') {
+ // The last word in the line made it too wide to fit in the text area;
+ // return up to the previous word, then collapse the whitespace
+ // between that word and its next sibling word into the line break
+ if (getTextWidth(initialCharIndex, testLength) > width) {
+ *charIndex = lastWordBreakIndex;
+ const char *nextChar = _text.c_str() + lastWordBreakIndex;
+ while (*nextChar++ == ' ') {
+ ++*charIndex;
+ }
+
+ // NOTE: In original engine, the font, color, and alignment were
+ // set here to the values that were seen at the last space character
+ return length;
+ }
- byte *surface = memoryPtr + BITMAP_HEADER_SIZE;
+ // NOTE: In the original engine, the values of _fontId, _foreColor,
+ // and _alignment were stored for use in the return path mentioned
+ // just above here
- int curByte = 0;
- int16 skipColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(skip));
- uint16 textX = planeRect.left + x;
- uint16 textY = planeRect.top + y;
- // Get totalWidth, totalHeight
- uint16 width = READ_LE_UINT16(memoryPtr);
- uint16 height = READ_LE_UINT16(memoryPtr + 2);
+ // We found a word break that was within the text area, memorise it
+ // and continue processing. +1 on the character index because it has
+ // not been incremented yet so currently points to the word break
+ // and not the word after the break
+ length = testLength;
+ lastWordBreakIndex = *charIndex + 1;
+ }
- // Upscale the coordinates/width if the fonts are already upscaled
- if (_screen->fontIsUpscaled()) {
- textX = textX * _screen->getDisplayWidth() / _screen->getWidth();
- textY = textY * _screen->getDisplayHeight() / _screen->getHeight();
- }
+ // In the middle of a line, keep processing
+ ++*charIndex;
+ ++testLength;
- bool translucent = (skipColor == -1 && backColor == -1);
+ // NOTE: In the original engine, the font, color, and alignment were
+ // reset here to their initial values
- for (int curY = 0; curY < height; curY++) {
- for (int curX = 0; curX < width; curX++) {
- byte pixel = surface[curByte++];
- if ((!translucent && pixel != skipColor && pixel != backColor) ||
- (translucent && pixel != 0xFF))
- _screen->putFontPixel(textY, curX + textX, curY, pixel);
+ // The text to render contained no word breaks yet but is already too
+ // wide for the text area; just split the word in half at the point
+ // where it overflows
+ if (length == 0 && getTextWidth(initialCharIndex, testLength) > width) {
+ *charIndex = --testLength + lastWordBreakIndex;
+ return testLength;
}
}
+
+ // The complete text to render was a single word, or was narrower than
+ // the text area, so return the entire line
+ if (length == 0 || getTextWidth(initialCharIndex, testLength) <= width) {
+ // NOTE: In original engine, the font, color, and alignment were
+ // reset, then getTextWidth was called to use its side-effects to
+ // set font, color, and alignment according to the text from
+ // `initialCharIndex` to `testLength`
+ return testLength;
+ }
+
+ // The last word in the line made it wider than the text area, so return
+ // up to the penultimate word
+ *charIndex = lastWordBreakIndex;
+ return length;
}
-int16 GfxText32::GetLongest(const char *text, int16 maxWidth, GfxFont *font) {
- uint16 curChar = 0;
- int16 maxChars = 0, curCharCount = 0;
- uint16 width = 0;
-
- while (width <= maxWidth) {
- curChar = (*(const byte *)text++);
-
- switch (curChar) {
- // 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)
- // 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++;
- // it's meant to pass through here
- case 0xA:
- curCharCount++;
- // and it's also meant to pass through here
- case 0:
- return curCharCount;
- case ' ':
- maxChars = curCharCount; // return count up to (but not including) breaking space
- break;
+int16 GfxText32::getTextWidth(const uint index, uint length) const {
+ int16 width = 0;
+
+ const char *text = _text.c_str() + index;
+
+ GfxFont *font = _font;
+
+ char currentChar = *text++;
+ while (length > 0 && currentChar != '\0') {
+ // Control codes are in the format `|<code><value>|`
+ if (currentChar == '|') {
+ // NOTE: Original engine code changed the global state of the
+ // FontMgr here upon encountering any color, alignment, or
+ // font control code.
+ // To avoid requiring all callers to manually restore these
+ // values on every call, we ignore control codes other than
+ // font change (since alignment and color do not change the
+ // width of characters), and simply update the font pointer
+ // on stack instead of the member property font.
+ currentChar = *text++;
+ --length;
+
+ if (length > 0 && currentChar == 'f') {
+ GuiResourceId fontId = 0;
+ while (length > 0 && *text >= '0' && *text <= '9') {
+ currentChar = *text++;
+ --length;
+
+ fontId = fontId * 10 + currentChar - '0';
+ }
+
+ if (length > 0) {
+ font = _cache->getFont(fontId);
+ }
+ }
+
+ // Forward through any more unknown control character data
+ while (length > 0 && *text != '|') {
+ ++text;
+ --length;
+ }
+ if (length > 0) {
+ ++text;
+ --length;
+ }
+ } else {
+ width += font->getCharWidth((unsigned char)currentChar);
+ }
+
+ if (length > 0) {
+ currentChar = *text++;
+ --length;
}
- if (width + font->getCharWidth(curChar) > maxWidth)
- break;
- width += font->getCharWidth(curChar);
- curCharCount++;
}
- return maxChars;
+ return width;
}
-void GfxText32::kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) {
- Common::Rect rect(0, 0, 0, 0);
- Size(rect, text, font, maxWidth);
- *textWidth = rect.width();
- *textHeight = rect.height();
+int16 GfxText32::getTextWidth(const Common::String &text, const uint index, const uint length) {
+ _text = text;
+ return scaleUpWidth(getTextWidth(index, length));
}
-void GfxText32::StringWidth(const char *str, GuiResourceId fontId, int16 &textWidth, int16 &textHeight) {
- Width(str, 0, (int16)strlen(str), fontId, textWidth, textHeight, true);
-}
+Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, bool doScaling) {
+ // NOTE: Like most of the text rendering code, this function was pretty
+ // weird in the original engine. The initial result rectangle was actually
+ // a 1x1 rectangle (0, 0, 0, 0), which was then "fixed" after the main
+ // text size loop finished running by subtracting 1 from the right and
+ // bottom edges. Like other functions in SCI32, this has been converted
+ // to use exclusive rects with inclusive rounding.
+
+ Common::Rect result;
+
+ int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ maxWidth = maxWidth * _xResolution / scriptWidth;
+
+ _text = text;
+
+ if (maxWidth >= 0) {
+ if (maxWidth == 0) {
+ maxWidth = _xResolution * 3 / 5;
+ }
-void GfxText32::Width(const char *text, int16 from, int16 len, GuiResourceId fontId, int16 &textWidth, int16 &textHeight, bool restoreFont) {
- byte curChar;
- textWidth = 0; textHeight = 0;
-
- GfxFont *font = _cache->getFont(fontId);
-
- if (font) {
- text += from;
- while (len--) {
- curChar = (*(const byte *)text++);
- switch (curChar) {
- case 0x0A:
- case 0x0D:
- textHeight = MAX<int16> (textHeight, font->getHeight());
- break;
- case 0x7C:
- warning("Code processing isn't implemented in SCI32");
- break;
- default:
- textHeight = MAX<int16> (textHeight, font->getHeight());
- textWidth += font->getCharWidth(curChar);
- break;
+ result.right = maxWidth;
+
+ int16 textWidth = 0;
+ if (_text.size() > 0) {
+ const char *rawText = _text.c_str();
+ const char *sourceText = rawText;
+ uint charIndex = 0;
+ uint nextCharIndex = 0;
+ while (*rawText != '\0') {
+ uint length = getLongest(&nextCharIndex, result.width());
+ textWidth = MAX(textWidth, getTextWidth(charIndex, length));
+ charIndex = nextCharIndex;
+ rawText = sourceText + charIndex;
+ // TODO: Due to getLongest and getTextWidth not having side
+ // effects, it is possible that the currently loaded font's
+ // height is wrong for this line if it was changed inline
+ result.bottom += _font->getHeight();
}
}
+
+ if (textWidth < maxWidth) {
+ result.right = textWidth;
+ }
+ } else {
+ result.right = getTextWidth(0, 10000);
+
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ result.bottom = 0;
+ } else {
+ // NOTE: In the original engine code, the bottom was not decremented
+ // by 1, which means that the rect was actually a pixel taller than
+ // the height of the font. This was not the case in the other branch,
+ // which decremented the bottom by 1 at the end of the loop.
+ result.bottom = _font->getHeight() + 1;
+ }
}
+
+ if (doScaling) {
+ // NOTE: The original engine code also scaled top/left but these are
+ // always zero so there is no reason to do that.
+ result.right = ((result.right - 1) * scriptWidth + _xResolution - 1) / _xResolution + 1;
+ result.bottom = ((result.bottom - 1) * scriptHeight + _yResolution - 1) / _yResolution + 1;
+ }
+
+ return result;
}
-int16 GfxText32::Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth) {
- int16 charCount;
- int16 maxTextWidth = 0, textWidth;
- int16 totalHeight = 0, textHeight;
+void GfxText32::erase(const Common::Rect &rect, const bool doScaling) {
+ Common::Rect targetRect = doScaling ? scaleRect(rect) : rect;
- int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
- int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
+ bitmap.getBuffer().fillRect(targetRect, _backColor);
+}
+
+int16 GfxText32::getStringWidth(const Common::String &text) {
+ return getTextWidth(text, 0, 10000);
+}
+
+int16 GfxText32::getTextCount(const Common::String &text, const uint index, const Common::Rect &textRect, const bool doScaling) {
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
- maxWidth = maxWidth * _scaledWidth / scriptWidth;
+ Common::Rect scaledRect(textRect);
+ if (doScaling) {
+ mulinc(scaledRect, Ratio(_xResolution, scriptWidth), Ratio(_yResolution, scriptHeight));
+ }
- rect.top = rect.left = 0;
- GfxFont *font = _cache->getFont(fontId);
+ Common::String oldText = _text;
+ _text = text;
+
+ uint charIndex = index;
+ int16 maxWidth = scaledRect.width();
+ int16 lineCount = (scaledRect.height() - 2) / _font->getHeight();
+ while (lineCount--) {
+ getLongest(&charIndex, maxWidth);
+ }
- if (maxWidth < 0) { // force output as single line
- StringWidth(text, fontId, textWidth, textHeight);
- rect.bottom = textHeight;
- rect.right = textWidth;
+ _text = oldText;
+ return charIndex - index;
+}
+
+int16 GfxText32::getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling) {
+ setFont(fontId);
+ return getTextCount(text, index, textRect, doScaling);
+}
+
+void GfxText32::scrollLine(const Common::String &lineText, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir) {
+ SciBitmap &bmr = *_segMan->lookupBitmap(_bitmap);
+ byte *pixels = bmr.getPixels();
+
+ int h = _font->getHeight();
+
+ if (dir == kScrollUp) {
+ // Scroll existing text down
+ for (int i = 0; i < (numLines - 1) * h; ++i) {
+ int y = _textRect.top + numLines * h - i - 1;
+ memcpy(pixels + y * _width + _textRect.left,
+ pixels + (y - h) * _width + _textRect.left,
+ _textRect.width());
+ }
} else {
- // 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, font);
- if (charCount == 0)
- break;
- Width(curPos, 0, charCount, fontId, textWidth, textHeight, false);
- maxTextWidth = MAX(textWidth, maxTextWidth);
- totalHeight += textHeight;
- curPos += charCount;
- while (*curPos == ' ')
- curPos++; // skip over breaking spaces
+ // Scroll existing text up
+ for (int i = 0; i < (numLines - 1) * h; ++i) {
+ int y = _textRect.top + i;
+ memcpy(pixels + y * _width + _textRect.left,
+ pixels + (y + h) * _width + _textRect.left,
+ _textRect.width());
}
- rect.bottom = totalHeight;
- rect.right = maxWidth ? maxWidth : MIN(rect.right, maxTextWidth);
}
- rect.right = rect.right * scriptWidth / _scaledWidth;
- rect.bottom = rect.bottom * scriptHeight / _scaledHeight;
+ Common::Rect lineRect = _textRect;
+
+ if (dir == kScrollUp) {
+ lineRect.bottom = lineRect.top + h;
+ } else {
+ // It is unclear to me what the purpose of this bottom++ is.
+ // It does not seem to be the usual inc/exc issue.
+ lineRect.top += (numLines - 1) * h;
+ lineRect.bottom++;
+ }
+
+ erase(lineRect, false);
+
+ _drawPosition.x = _textRect.left;
+ _drawPosition.y = _textRect.top;
+ if (dir == kScrollDown) {
+ _drawPosition.y += (numLines - 1) * h;
+ }
+
+ _foreColor = color;
+ _alignment = align;
+ //int fc = _foreColor;
+
+ setFont(fontId);
+
+ _text = lineText;
+ int16 textWidth = getTextWidth(0, lineText.size());
+
+ if (_alignment == kTextAlignCenter) {
+ _drawPosition.x += (_textRect.width() - textWidth) / 2;
+ } else if (_alignment == kTextAlignRight) {
+ _drawPosition.x += _textRect.width() - textWidth;
+ }
+
+ //_foreColor = fc;
+ //setFont(fontId);
- return rect.right;
+ drawText(0, lineText.size());
}
+
} // End of namespace Sci
diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h
index 08568e4958..c4521a4f56 100644
--- a/engines/sci/graphics/text32.h
+++ b/engines/sci/graphics/text32.h
@@ -23,17 +23,27 @@
#ifndef SCI_GRAPHICS_TEXT32_H
#define SCI_GRAPHICS_TEXT32_H
+#include "sci/engine/state.h"
#include "sci/graphics/celobj32.h"
#include "sci/graphics/frameout.h"
+#include "sci/graphics/helpers.h"
namespace Sci {
enum TextAlign {
- kTextAlignLeft = 0,
- kTextAlignCenter = 1,
- kTextAlignRight = -1
+ kTextAlignDefault = -1,
+ kTextAlignLeft = 0,
+ kTextAlignCenter = 1,
+ kTextAlignRight = 2
};
+enum ScrollDirection {
+ kScrollUp,
+ kScrollDown
+};
+
+class GfxFont;
+
/**
* This class handles text calculation and rendering for
* SCI32 games. The text calculation system in SCI32 is
@@ -43,6 +53,9 @@ enum TextAlign {
*/
class GfxText32 {
private:
+ SegManager *_segMan;
+ GfxCache *_cache;
+
/**
* The resource ID of the default font used by the game.
*
@@ -54,6 +67,8 @@ private:
/**
* The width and height of the currently active text
* bitmap, in text-system coordinates.
+ *
+ * @note These are unsigned in the actual engine.
*/
int16 _width, _height;
@@ -106,14 +121,61 @@ private:
TextAlign _alignment;
/**
+ * The position of the text draw cursor.
+ */
+ Common::Point _drawPosition;
+
+ void drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling);
+
+ void drawChar(const char charIndex);
+ void drawText(const uint index, uint length);
+
+ /**
+ * Gets the length of the longest run of text available
+ * within the currently loaded text, starting from the
+ * given `charIndex` and running for up to `maxWidth`
+ * pixels. Returns the number of characters that can be
+ * written, and mutates the value pointed to by
+ * `charIndex` to point to the index of the next
+ * character to render.
+ */
+ uint getLongest(uint *charIndex, const int16 maxWidth);
+
+ /**
+ * Gets the pixel width of a substring of the currently
+ * loaded text, without scaling.
+ */
+ int16 getTextWidth(const uint index, uint length) const;
+
+ inline Common::Rect scaleRect(const Common::Rect &rect) {
+ Common::Rect scaledRect(rect);
+ int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ Ratio scaleX(_xResolution, scriptWidth);
+ Ratio scaleY(_yResolution, scriptHeight);
+ mulinc(scaledRect, scaleX, scaleY);
+ return scaledRect;
+ }
+
+public:
+ GfxText32(SegManager *segMan, GfxCache *fonts);
+
+ /**
* The memory handle of the currently active bitmap.
*/
reg_t _bitmap;
/**
- * TODO: Document
+ * The size of the x-dimension of the coordinate system
+ * used by the text renderer. Static since it was global in SSCI.
*/
- int _field_22;
+ static int16 _xResolution;
+
+ /**
+ * The size of the y-dimension of the coordinate system
+ * used by the text renderer. Static since it was global in SSCI.
+ */
+ static int16 _yResolution;
/**
* The currently active font resource used to write text
@@ -124,66 +186,99 @@ private:
*/
GfxFont *_font;
- // TODO: This is general for all CelObjMem and should be
- // put in a single location, like maybe as a static
- // method of CelObjMem?!
- void buildBitmapHeader(byte *bitmap, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool useRemap) const;
+ /**
+ * Creates a plain font bitmap with a flat color
+ * background.
+ */
+ reg_t createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, TextAlign alignment, const int16 borderColor, bool dimmed, const bool doScaling, const bool gc);
- void drawFrame(const Common::Rect &rect, const int size, const uint8 color, const bool doScaling);
- void drawTextBox();
- void erase(const Common::Rect &rect, const bool doScaling);
+ /**
+ * Creates a font bitmap with a view background.
+ */
+ reg_t createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed, const bool gc);
- inline Common::Rect scaleRect(const Common::Rect &rect) {
- Common::Rect scaledRect(rect);
- int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
- int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
- Ratio scaleX(_scaledWidth, scriptWidth);
- Ratio scaleY(_scaledHeight, scriptHeight);
- mul(scaledRect, scaleX, scaleY);
- return scaledRect;
+ inline int scaleUpWidth(int value) const {
+ const int scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ return (value * scriptWidth + _xResolution - 1) / _xResolution;
}
-public:
- GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen);
+ inline int scaleUpHeight(int value) const {
+ const int scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ return (value * scriptHeight + _yResolution - 1) / _yResolution;
+ }
/**
- * The size of the x-dimension of the coordinate system
- * used by the text renderer.
+ * Draws the text to the bitmap.
*/
- int16 _scaledWidth;
+ void drawTextBox();
/**
- * The size of the y-dimension of the coordinate system
- * used by the text renderer.
+ * Draws the given text to the bitmap.
+ *
+ * @note The original engine holds a reference to a
+ * shared string which lets the text be updated from
+ * outside of the font manager. Instead, we give this
+ * extra signature to send the text to draw.
+ *
+ * TODO: Use shared string instead?
+ */
+ void drawTextBox(const Common::String &text);
+
+ /**
+ * Erases the given rect by filling with the background
+ * color.
+ */
+ void erase(const Common::Rect &rect, const bool doScaling);
+
+ void invertRect(const reg_t bitmap, const int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling);
+
+ /**
+ * Sets the font to be used for rendering and
+ * calculation of text dimensions.
*/
- int16 _scaledHeight;
+ void setFont(const GuiResourceId fontId);
- reg_t createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, TextAlign alignment, const int16 borderColor, bool dimmed, const bool doScaling, reg_t *outBitmapObject);
+ /**
+ * Gets the width of a character.
+ */
+ uint16 getCharWidth(const char charIndex, const bool doScaling) const;
- reg_t createTitledFontBitmap(CelInfo32 &celInfo, Common::Rect &rect, Common::String &text, int16 foreColor, int16 backColor, int font, int16 skipColor, int16 borderColor, bool dimmed, void *unknown1);
+ /**
+ * Retrieves the width and height of a block of text.
+ */
+ Common::Rect getTextSize(const Common::String &text, const int16 maxWidth, bool doScaling);
-#pragma mark -
-#pragma mark Old stuff
+ /**
+ * Gets the pixel width of a substring of the currently
+ * loaded text, with scaling.
+ */
+ int16 getTextWidth(const Common::String &text, const uint index, const uint length);
- reg_t createTextBitmap(reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG);
- reg_t createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG);
- void drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject);
- void drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y);
- void disposeTextBitmap(reg_t hunkId);
- int16 GetLongest(const char *text, int16 maxWidth, GfxFont *font);
+ /**
+ * Retrieves the width of a line of text.
+ */
+ int16 getStringWidth(const Common::String &text);
- void kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight);
+ /**
+ * Gets the number of characters of `text`, starting
+ * from `index`, that can be safely rendered into
+ * `textRect`.
+ */
+ int16 getTextCount(const Common::String &text, const uint index, const Common::Rect &textRect, const bool doScaling);
-private:
- reg_t createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t hunkId);
- void drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId);
- int16 Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth);
- 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);
+ /**
+ * Gets the number of characters of `text`, starting
+ * from `index`, that can be safely rendered into
+ * `textRect` using the given font.
+ */
+ int16 getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling);
- SegManager *_segMan;
- GfxCache *_cache;
- GfxScreen *_screen;
+ /**
+ * Scroll up/down one line. `numLines` is the number of the lines in the
+ * textarea, and `textLine` contains the text to draw as the newly
+ * visible line. Originally FontMgr::DrawOneLine and FontMgr::UpOneLine.
+ */
+ void scrollLine(const Common::String &textLine, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp
new file mode 100644
index 0000000000..a1ae352b6c
--- /dev/null
+++ b/engines/sci/graphics/transitions32.cpp
@@ -0,0 +1,1040 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/console.h"
+#include "sci/engine/segment.h"
+#include "sci/engine/seg_manager.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/text32.h"
+#include "sci/graphics/transitions32.h"
+#include "sci/sci.h"
+
+namespace Sci {
+static int dissolveSequences[2][20] = {
+ /* SCI2.1early- */ { 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080 },
+ /* SCI2.1mid+ */ { 0, 0, 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080, 73728, 132096, 466944 }
+};
+static int16 divisionsDefaults[2][16] = {
+ /* SCI2.1early- */ { 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 40, 40, 101, 101 },
+ /* SCI2.1mid+ */ { 1, 20, 20, 20, 20, 10, 10, 10, 10, 20, 20, 6, 10, 101, 101, 2 }
+};
+
+GfxTransitions32::GfxTransitions32(SegManager *segMan) :
+ _segMan(segMan) {
+ for (int i = 0; i < 236; i += 2) {
+ _styleRanges[i] = 0;
+ _styleRanges[i + 1] = -1;
+ }
+ for (int i = 236; i < ARRAYSIZE(_styleRanges); ++i) {
+ _styleRanges[i] = 0;
+ }
+
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ _dissolveSequenceSeeds = dissolveSequences[0];
+ _defaultDivisions = divisionsDefaults[0];
+ } else {
+ _dissolveSequenceSeeds = dissolveSequences[1];
+ _defaultDivisions = divisionsDefaults[1];
+ }
+}
+
+GfxTransitions32::~GfxTransitions32() {
+ for (ShowStyleList::iterator it = _showStyles.begin();
+ it != _showStyles.end();
+ it = deleteShowStyle(it));
+ _scrolls.clear();
+}
+
+void GfxTransitions32::throttle(const uint32 ms) {
+ g_sci->getEngineState()->speedThrottler(ms);
+ g_sci->getEngineState()->_throttleTrigger = true;
+}
+
+void GfxTransitions32::clearShowRects() {
+ g_sci->_gfxFrameout->_showList.clear();
+}
+
+void GfxTransitions32::addShowRect(const Common::Rect &rect) {
+ if (!rect.isEmpty()) {
+ g_sci->_gfxFrameout->_showList.add(rect);
+ }
+}
+
+void GfxTransitions32::sendShowRects() {
+ g_sci->_gfxFrameout->showBits();
+ g_sci->getSciDebugger()->onFrame();
+ clearShowRects();
+ throttle();
+}
+
+#pragma mark -
+#pragma mark Show styles
+
+void GfxTransitions32::processShowStyles() {
+ uint32 now = g_sci->getTickCount();
+
+ bool continueProcessing;
+ bool doFrameOut;
+ do {
+ continueProcessing = false;
+ doFrameOut = false;
+ ShowStyleList::iterator showStyle = _showStyles.begin();
+ while (showStyle != _showStyles.end()) {
+ bool finished = false;
+
+ if (!showStyle->animate) {
+ doFrameOut = true;
+ }
+
+ finished = processShowStyle(*showStyle, now);
+
+ if (!finished) {
+ continueProcessing = true;
+ }
+
+ if (finished && showStyle->processed) {
+ showStyle = deleteShowStyle(showStyle);
+ } else {
+ showStyle = ++showStyle;
+ }
+ }
+
+ if (g_engine->shouldQuit()) {
+ return;
+ }
+
+ if (doFrameOut) {
+ g_sci->_gfxFrameout->frameOut(true);
+ g_sci->getSciDebugger()->onFrame();
+ throttle();
+ }
+ } while(continueProcessing && doFrameOut);
+}
+
+void GfxTransitions32::processEffects(PlaneShowStyle &showStyle) {
+ switch(showStyle.type) {
+ case kShowStyleHShutterOut:
+ processHShutterOut(showStyle);
+ break;
+ case kShowStyleHShutterIn:
+ processHShutterIn(showStyle);
+ break;
+ case kShowStyleVShutterOut:
+ processVShutterOut(showStyle);
+ break;
+ case kShowStyleVShutterIn:
+ processVShutterIn(showStyle);
+ break;
+ case kShowStyleWipeLeft:
+ processWipeLeft(showStyle);
+ break;
+ case kShowStyleWipeRight:
+ processWipeRight(showStyle);
+ break;
+ case kShowStyleWipeUp:
+ processWipeUp(showStyle);
+ break;
+ case kShowStyleWipeDown:
+ processWipeDown(showStyle);
+ break;
+ case kShowStyleIrisOut:
+ processIrisOut(showStyle);
+ break;
+ case kShowStyleIrisIn:
+ processIrisIn(showStyle);
+ break;
+ case kShowStyleDissolveNoMorph:
+ case kShowStyleDissolve:
+ processPixelDissolve(showStyle);
+ break;
+ case kShowStyleNone:
+ case kShowStyleFadeOut:
+ case kShowStyleFadeIn:
+ case kShowStyleMorph:
+ break;
+ }
+}
+
+// TODO: 10-argument version is only in SCI3; argc checks are currently wrong for this version
+// and need to be fixed in future
+void GfxTransitions32::kernelSetShowStyle(const uint16 argc, const reg_t planeObj, const ShowStyleType type, const int16 seconds, const int16 back, const int16 priority, const int16 animate, const int16 frameOutNow, reg_t pFadeArray, int16 divisions, const int16 blackScreen) {
+
+ bool hasDivisions = false;
+ bool hasFadeArray = false;
+
+ // KQ7 2.0b uses a mismatched version of the Styler script (SCI2.1early script
+ // for SCI2.1mid engine), so the calls it makes to kSetShowStyle are wrong and
+ // put `divisions` where `pFadeArray` is supposed to be
+ if (getSciVersion() == SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() == GID_KQ7) {
+ hasDivisions = argc > 7;
+ hasFadeArray = false;
+ divisions = argc > 7 ? pFadeArray.toSint16() : -1;
+ pFadeArray = NULL_REG;
+ } else if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ hasDivisions = argc > 7;
+ hasFadeArray = false;
+ } else if (getSciVersion() < SCI_VERSION_3) {
+ hasDivisions = argc > 8;
+ hasFadeArray = argc > 7;
+ } else {
+ hasDivisions = argc > 9;
+ hasFadeArray = argc > 8;
+ }
+
+ bool isFadeUp;
+ int16 color;
+ if (back != -1) {
+ isFadeUp = false;
+ color = back;
+ } else {
+ isFadeUp = true;
+ color = 0;
+ }
+
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
+ if (plane == nullptr) {
+ error("Plane %04x:%04x is not present in active planes list", PRINT_REG(planeObj));
+ }
+
+ bool createNewEntry = true;
+ PlaneShowStyle *entry = findShowStyleForPlane(planeObj);
+ if (entry != nullptr) {
+ bool useExisting = true;
+
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ useExisting = plane->_gameRect.width() == entry->width && plane->_gameRect.height() == entry->height;
+ }
+
+ if (useExisting) {
+ useExisting = entry->divisions == (hasDivisions ? divisions : _defaultDivisions[type]);
+ }
+
+ if (useExisting) {
+ createNewEntry = false;
+ isFadeUp = true;
+ entry->currentStep = 0;
+ } else {
+ isFadeUp = true;
+ color = entry->color;
+ deleteShowStyle(findIteratorForPlane(planeObj));
+ entry = nullptr;
+ }
+ }
+
+ if (type == kShowStyleNone) {
+ if (createNewEntry == false) {
+ deleteShowStyle(findIteratorForPlane(planeObj));
+ }
+
+ return;
+ }
+
+ if (createNewEntry) {
+ entry = new PlaneShowStyle;
+ // NOTE: SCI2.1 engine tests if allocation returned a null pointer
+ // but then only avoids setting currentStep if this is so. Since
+ // this is a nonsensical approach, we do not do that here
+ entry->currentStep = 0;
+ entry->processed = false;
+ entry->divisions = hasDivisions ? divisions : _defaultDivisions[type];
+ entry->plane = planeObj;
+ entry->fadeColorRangesCount = 0;
+
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ // for pixel dissolve
+ entry->bitmap = NULL_REG;
+ entry->bitmapScreenItem = nullptr;
+
+ // for wipe
+ entry->screenItems.clear();
+ entry->width = plane->_gameRect.width();
+ entry->height = plane->_gameRect.height();
+ } else {
+ entry->fadeColorRanges = nullptr;
+ if (hasFadeArray) {
+ // NOTE: SCI2.1mid engine does no check to verify that an array is
+ // successfully retrieved, and SegMan will cause a fatal error
+ // if we try to use a memory segment that is not an array
+ SciArray &table = *_segMan->lookupArray(pFadeArray);
+
+ uint32 rangeCount = table.size();
+ entry->fadeColorRangesCount = rangeCount;
+
+ // NOTE: SCI engine code always allocates memory even if the range
+ // table has no entries, but this does not really make sense, so
+ // we avoid the allocation call in this case
+ if (rangeCount > 0) {
+ entry->fadeColorRanges = new uint16[rangeCount];
+ for (size_t i = 0; i < rangeCount; ++i) {
+ entry->fadeColorRanges[i] = table.getAsInt16(i);
+ }
+ }
+ }
+ }
+ }
+
+ // NOTE: The original engine had no nullptr check and would just crash
+ // if it got to here
+ if (entry == nullptr) {
+ error("Cannot edit non-existing ShowStyle entry");
+ }
+
+ entry->fadeUp = isFadeUp;
+ entry->color = color;
+ entry->nextTick = g_sci->getTickCount();
+ entry->type = type;
+ entry->animate = animate;
+ entry->delay = (seconds * 60 + entry->divisions - 1) / entry->divisions;
+
+ if (entry->delay == 0) {
+ error("ShowStyle has no duration");
+ }
+
+ if (frameOutNow) {
+ // Creates a reference frame for the pixel dissolves to use
+ g_sci->_gfxFrameout->frameOut(false);
+ }
+
+ if (createNewEntry) {
+ if (getSciVersion() <= SCI_VERSION_2_1_EARLY) {
+ switch (entry->type) {
+ case kShowStyleIrisOut:
+ case kShowStyleIrisIn:
+ configure21EarlyIris(*entry, priority);
+ break;
+ case kShowStyleDissolve:
+ configure21EarlyDissolve(*entry, priority, plane->_gameRect);
+ break;
+ default:
+ // do nothing
+ break;
+ }
+ }
+
+ _showStyles.push_back(*entry);
+ delete entry;
+ }
+}
+
+void GfxTransitions32::kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor) {
+ if (toColor > fromColor) {
+ return;
+ }
+
+ for (int i = fromColor; i <= toColor; ++i) {
+ _styleRanges[i] = 0;
+ }
+}
+
+PlaneShowStyle *GfxTransitions32::findShowStyleForPlane(const reg_t planeObj) {
+ for (ShowStyleList::iterator it = _showStyles.begin(); it != _showStyles.end(); ++it) {
+ if (it->plane == planeObj) {
+ return &*it;
+ }
+ }
+
+ return nullptr;
+}
+
+ShowStyleList::iterator GfxTransitions32::findIteratorForPlane(const reg_t planeObj) {
+ ShowStyleList::iterator it;
+ for (it = _showStyles.begin(); it != _showStyles.end(); ++it) {
+ if (it->plane == planeObj) {
+ break;
+ }
+ }
+
+ return it;
+}
+
+ShowStyleList::iterator GfxTransitions32::deleteShowStyle(const ShowStyleList::iterator &showStyle) {
+ switch (showStyle->type) {
+ case kShowStyleDissolveNoMorph:
+ case kShowStyleDissolve:
+ if (getSciVersion() <= SCI_VERSION_2_1_EARLY) {
+ _segMan->freeBitmap(showStyle->bitmap);
+ g_sci->_gfxFrameout->deleteScreenItem(*showStyle->bitmapScreenItem);
+ }
+ break;
+ case kShowStyleIrisOut:
+ case kShowStyleIrisIn:
+ if (getSciVersion() <= SCI_VERSION_2_1_EARLY) {
+ for (uint i = 0; i < showStyle->screenItems.size(); ++i) {
+ ScreenItem *screenItem = showStyle->screenItems[i];
+ if (screenItem != nullptr) {
+ g_sci->_gfxFrameout->deleteScreenItem(*screenItem);
+ }
+ }
+ }
+ break;
+ case kShowStyleFadeIn:
+ case kShowStyleFadeOut:
+ if (getSciVersion() > SCI_VERSION_2_1_EARLY && showStyle->fadeColorRangesCount > 0) {
+ delete[] showStyle->fadeColorRanges;
+ }
+ break;
+ case kShowStyleNone:
+ case kShowStyleMorph:
+ case kShowStyleHShutterIn:
+ // do nothing
+ break;
+ default:
+ error("Unknown delete transition type %d", showStyle->type);
+ }
+
+ return _showStyles.erase(showStyle);
+}
+
+void GfxTransitions32::configure21EarlyIris(PlaneShowStyle &showStyle, const int16 priority) {
+ showStyle.numEdges = 4;
+ const int numScreenItems = showStyle.numEdges * showStyle.divisions;
+ showStyle.screenItems.reserve(numScreenItems);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeColor;
+ celInfo.color = showStyle.color;
+
+ const int width = showStyle.width;
+ const int height = showStyle.height;
+ const int divisions = showStyle.divisions;
+
+ for (int i = 0; i < divisions; ++i) {
+ Common::Rect rect;
+
+ // Top
+ rect.left = (width * i) / (2 * divisions);
+ rect.top = (height * i) / (2 * divisions);
+ rect.right = width - rect.left;
+ rect.bottom = (height + 1) * (i + 1) / (2 * divisions);
+ const int16 topTop = rect.top;
+ const int16 topBottom = rect.bottom;
+
+ showStyle.screenItems.push_back(new ScreenItem(showStyle.plane, celInfo, rect));
+ showStyle.screenItems.back()->_priority = priority;
+ showStyle.screenItems.back()->_fixedPriority = true;
+
+ // Bottom
+ rect.top = height - rect.bottom;
+ rect.bottom = height - topTop;
+ const int16 bottomTop = rect.top;
+
+ showStyle.screenItems.push_back(new ScreenItem(showStyle.plane, celInfo, rect));
+ showStyle.screenItems.back()->_priority = priority;
+ showStyle.screenItems.back()->_fixedPriority = true;
+
+ // Left
+ rect.top = topBottom;
+ rect.right = (width + 1) * (i + 1) / (2 * divisions);
+ rect.bottom = bottomTop;
+ const int16 leftLeft = rect.left;
+
+ showStyle.screenItems.push_back(new ScreenItem(showStyle.plane, celInfo, rect));
+ showStyle.screenItems.back()->_priority = priority;
+ showStyle.screenItems.back()->_fixedPriority = true;
+
+ // Right
+ rect.left = width - rect.right;
+ rect.right = width - leftLeft;
+
+ showStyle.screenItems.push_back(new ScreenItem(showStyle.plane, celInfo, rect));
+ showStyle.screenItems.back()->_priority = priority;
+ showStyle.screenItems.back()->_fixedPriority = true;
+ }
+
+ if (showStyle.fadeUp) {
+ for (int i = 0; i < numScreenItems; ++i) {
+ g_sci->_gfxFrameout->addScreenItem(*showStyle.screenItems[i]);
+ }
+ }
+}
+
+void GfxTransitions32::configure21EarlyDissolve(PlaneShowStyle &showStyle, const int16 priority, const Common::Rect &gameRect) {
+
+ reg_t bitmapId;
+ SciBitmap &bitmap = *_segMan->allocateBitmap(&bitmapId, showStyle.width, showStyle.height, kDefaultSkipColor, 0, 0, kLowResX, kLowResY, 0, false, false);
+
+ showStyle.bitmap = bitmapId;
+
+ const Buffer &source = g_sci->_gfxFrameout->getCurrentBuffer();
+ Buffer target(showStyle.width, showStyle.height, bitmap.getPixels());
+
+ target.fillRect(Common::Rect(bitmap.getWidth(), bitmap.getHeight()), kDefaultSkipColor);
+ target.copyRectToSurface(source, 0, 0, gameRect);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = bitmapId;
+
+ showStyle.bitmapScreenItem = new ScreenItem(showStyle.plane, celInfo, Common::Point(0, 0), ScaleInfo());
+ showStyle.bitmapScreenItem->_priority = priority;
+ showStyle.bitmapScreenItem->_fixedPriority = true;
+
+ g_sci->_gfxFrameout->addScreenItem(*showStyle.bitmapScreenItem);
+}
+
+bool GfxTransitions32::processShowStyle(PlaneShowStyle &showStyle, uint32 now) {
+ if (showStyle.nextTick >= now && showStyle.animate) {
+ return false;
+ }
+
+ switch (showStyle.type) {
+ default:
+ case kShowStyleNone:
+ return processNone(showStyle);
+ case kShowStyleHShutterOut:
+ case kShowStyleHShutterIn:
+ case kShowStyleVShutterOut:
+ case kShowStyleVShutterIn:
+ case kShowStyleWipeLeft:
+ case kShowStyleWipeRight:
+ case kShowStyleWipeUp:
+ case kShowStyleWipeDown:
+ case kShowStyleDissolveNoMorph:
+ case kShowStyleMorph:
+ return processMorph(showStyle);
+ case kShowStyleDissolve:
+ if (getSciVersion() > SCI_VERSION_2_1_EARLY) {
+ return processMorph(showStyle);
+ } else {
+ return processPixelDissolve(showStyle);
+ }
+ case kShowStyleIrisOut:
+ if (getSciVersion() > SCI_VERSION_2_1_EARLY) {
+ return processMorph(showStyle);
+ } else {
+ return processIrisOut(showStyle);
+ }
+ case kShowStyleIrisIn:
+ if (getSciVersion() > SCI_VERSION_2_1_EARLY) {
+ return processMorph(showStyle);
+ } else {
+ return processIrisIn(showStyle);
+ }
+ case kShowStyleFadeOut:
+ return processFade(-1, showStyle);
+ case kShowStyleFadeIn:
+ return processFade(1, showStyle);
+ }
+}
+
+bool GfxTransitions32::processNone(PlaneShowStyle &showStyle) {
+ if (showStyle.fadeUp) {
+ g_sci->_gfxPalette32->setFade(100, 0, 255);
+ } else {
+ g_sci->_gfxPalette32->setFade(0, 0, 255);
+ }
+
+ showStyle.processed = true;
+ return true;
+}
+
+void GfxTransitions32::processHShutterOut(PlaneShowStyle &showStyle) {
+ error("HShutterOut is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+}
+
+void GfxTransitions32::processHShutterIn(const PlaneShowStyle &showStyle) {
+ if (getSciVersion() <= SCI_VERSION_2_1_EARLY) {
+ error("HShutterIn is not known to be used by any SCI2.1early- game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+ }
+
+ Plane* plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(showStyle.plane);
+ const Common::Rect &screenRect = plane->_screenRect;
+ Common::Rect rect;
+
+ const int divisions = showStyle.divisions;
+ const int width = screenRect.width();
+ const int divisionWidth = width / divisions - 1;
+
+ clearShowRects();
+
+ if (width % divisions) {
+ rect.left = (divisionWidth + 1) * divisions + screenRect.left;
+ rect.top = screenRect.top;
+ rect.right = (divisionWidth + 1) * divisions + (width % divisions) + screenRect.left;
+ rect.bottom = screenRect.bottom;
+ addShowRect(rect);
+ sendShowRects();
+ }
+
+ for (int i = 0; i < width / (2 * divisions); ++i) {
+ // Left side
+ rect.left = i * divisions + screenRect.left;
+ rect.top = screenRect.top;
+ rect.right = i * divisions + divisions + screenRect.left;
+ rect.bottom = screenRect.bottom;
+ addShowRect(rect);
+
+ // Right side
+ rect.left = (divisionWidth - i) * divisions + screenRect.left;
+ rect.top = screenRect.top;
+ rect.right = (divisionWidth - i) * divisions + divisions + screenRect.left;
+ rect.bottom = screenRect.bottom;
+ addShowRect(rect);
+
+ sendShowRects();
+ }
+
+ addShowRect(screenRect);
+ sendShowRects();
+}
+
+void GfxTransitions32::processVShutterOut(PlaneShowStyle &showStyle) {
+ error("VShutterOut is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+}
+
+void GfxTransitions32::processVShutterIn(PlaneShowStyle &showStyle) {
+ error("VShutterIn is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+}
+
+void GfxTransitions32::processWipeLeft(PlaneShowStyle &showStyle) {
+ error("WipeLeft is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+}
+
+void GfxTransitions32::processWipeRight(PlaneShowStyle &showStyle) {
+ error("WipeRight is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+}
+
+void GfxTransitions32::processWipeUp(PlaneShowStyle &showStyle) {
+ error("WipeUp is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+}
+
+void GfxTransitions32::processWipeDown(PlaneShowStyle &showStyle) {
+ error("WipeDown is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+}
+
+bool GfxTransitions32::processIrisOut(PlaneShowStyle &showStyle) {
+ if (getSciVersion() > SCI_VERSION_2_1_EARLY) {
+ error("IrisOut is not known to be used by any SCI2.1mid+ game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+ }
+
+ return processWipe(-1, showStyle);
+}
+
+bool GfxTransitions32::processIrisIn(PlaneShowStyle &showStyle) {
+ if (getSciVersion() > SCI_VERSION_2_1_EARLY) {
+ error("IrisIn is not known to be used by any SCI2.1mid+ game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+ }
+
+ return processWipe(1, showStyle);
+}
+
+void GfxTransitions32::processDissolveNoMorph(PlaneShowStyle &showStyle) {
+ error("DissolveNoMorph is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
+}
+
+inline int bitWidth(int number) {
+ int width = 0;
+ while (number != 0) {
+ number >>= 1;
+ width += 1;
+ }
+ return width;
+}
+
+bool GfxTransitions32::processPixelDissolve(PlaneShowStyle &showStyle) {
+ if (getSciVersion() > SCI_VERSION_2_1_EARLY) {
+ return processPixelDissolve21Mid(showStyle);
+ } else {
+ return processPixelDissolve21Early(showStyle);
+ }
+}
+
+bool GfxTransitions32::processPixelDissolve21Early(PlaneShowStyle &showStyle) {
+ bool unchanged = true;
+
+ SciBitmap &bitmap = *_segMan->lookupBitmap(showStyle.bitmap);
+ Buffer buffer(showStyle.width, showStyle.height, bitmap.getPixels());
+
+ uint32 numPixels = showStyle.width * showStyle.height;
+ uint32 numPixelsPerDivision = (numPixels + showStyle.divisions) / showStyle.divisions;
+
+ uint32 index;
+ if (showStyle.currentStep == 0) {
+ int i = 0;
+ index = numPixels;
+ if (index != 1) {
+ for (;;) {
+ index >>= 1;
+ if (index == 1) {
+ break;
+ }
+ i++;
+ }
+ }
+
+ showStyle.dissolveMask = _dissolveSequenceSeeds[i];
+ index = 53427;
+
+ showStyle.firstPixel = index;
+ showStyle.pixel = index;
+ } else {
+ index = showStyle.pixel;
+ for (;;) {
+ if (index & 1) {
+ index >>= 1;
+ index ^= showStyle.dissolveMask;
+ } else {
+ index >>= 1;
+ }
+
+ if (index < numPixels) {
+ break;
+ }
+ }
+
+ if (index == showStyle.firstPixel) {
+ index = 0;
+ }
+ }
+
+ if (showStyle.currentStep < showStyle.divisions) {
+ for (uint32 i = 0; i < numPixelsPerDivision; ++i) {
+ *(byte *)buffer.getBasePtr(index % showStyle.width, index / showStyle.width) = showStyle.color;
+
+ for (;;) {
+ if (index & 1) {
+ index >>= 1;
+ index ^= showStyle.dissolveMask;
+ } else {
+ index >>= 1;
+ }
+
+ if (index < numPixels) {
+ break;
+ }
+ }
+
+ if (index == showStyle.firstPixel) {
+ buffer.fillRect(Common::Rect(0, 0, showStyle.width, showStyle.height), showStyle.color);
+ break;
+ }
+ }
+
+ showStyle.pixel = index;
+ showStyle.nextTick += showStyle.delay;
+ ++showStyle.currentStep;
+ unchanged = false;
+ if (showStyle.bitmapScreenItem->_created == 0) {
+ showStyle.bitmapScreenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+ }
+
+ if ((showStyle.currentStep >= showStyle.divisions) && unchanged) {
+ if (showStyle.fadeUp) {
+ showStyle.processed = true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool GfxTransitions32::processPixelDissolve21Mid(const PlaneShowStyle &showStyle) {
+ // SQ6 room 530
+
+ Plane* plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(showStyle.plane);
+ const Common::Rect &screenRect = plane->_screenRect;
+ Common::Rect rect;
+
+ const int planeWidth = screenRect.width();
+ const int planeHeight = screenRect.height();
+ const int divisions = showStyle.divisions;
+ const int width = planeWidth / divisions + ((planeWidth % divisions) ? 1 : 0);
+ const int height = planeHeight / divisions + ((planeHeight % divisions) ? 1 : 0);
+
+ const uint32 mask = _dissolveSequenceSeeds[bitWidth(width * height - 1)];
+ int seq = 1;
+
+ uint iteration = 0;
+ const uint numIterationsPerTick = g_sci->_gfxFrameout->_showList.max_size();
+
+ clearShowRects();
+
+ do {
+ int row = seq / width;
+ int col = seq % width;
+
+ if (row < height) {
+ if (row == height - 1 && (planeHeight % divisions)) {
+ if (col == width - 1 && (planeWidth % divisions)) {
+ rect.left = col * divisions;
+ rect.top = row * divisions;
+ rect.right = col * divisions + (planeWidth % divisions);
+ rect.bottom = row * divisions + (planeHeight % divisions);
+ addShowRect(rect);
+ } else {
+ rect.left = col * divisions;
+ rect.top = row * divisions;
+ rect.right = col * divisions + divisions;
+ rect.bottom = row * divisions + (planeHeight % divisions);
+ addShowRect(rect);
+ }
+ } else {
+ if (col == width - 1 && (planeWidth % divisions)) {
+ rect.left = col * divisions;
+ rect.top = row * divisions;
+ rect.right = col * divisions + (planeWidth % divisions);
+ rect.bottom = row * divisions + divisions;
+ addShowRect(rect);
+ } else {
+ rect.left = col * divisions;
+ rect.top = row * divisions;
+ rect.right = col * divisions + divisions;
+ rect.bottom = row * divisions + divisions;
+ addShowRect(rect);
+ }
+ }
+ }
+
+ if (seq & 1) {
+ seq = (seq >> 1) ^ mask;
+ } else {
+ seq >>= 1;
+ }
+
+ if (++iteration == numIterationsPerTick) {
+ sendShowRects();
+ iteration = 0;
+ }
+ } while (seq != 1 && !g_engine->shouldQuit());
+
+ rect.left = screenRect.left;
+ rect.top = screenRect.top;
+ rect.right = divisions + screenRect.left;
+ rect.bottom = divisions + screenRect.bottom;
+ addShowRect(rect);
+ sendShowRects();
+
+ addShowRect(screenRect);
+ sendShowRects();
+
+ return true;
+}
+
+bool GfxTransitions32::processFade(const int8 direction, PlaneShowStyle &showStyle) {
+ bool unchanged = true;
+ if (showStyle.currentStep < showStyle.divisions) {
+ int percent;
+ if (direction <= 0) {
+ percent = showStyle.divisions - showStyle.currentStep - 1;
+ } else {
+ percent = showStyle.currentStep;
+ }
+
+ percent *= 100;
+ percent /= showStyle.divisions - 1;
+
+ if (showStyle.fadeColorRangesCount > 0) {
+ for (int i = 0, len = showStyle.fadeColorRangesCount; i < len; i += 2) {
+ g_sci->_gfxPalette32->setFade(percent, showStyle.fadeColorRanges[i], showStyle.fadeColorRanges[i + 1]);
+ }
+ } else {
+ g_sci->_gfxPalette32->setFade(percent, 0, 255);
+ }
+
+ ++showStyle.currentStep;
+ showStyle.nextTick += showStyle.delay;
+ unchanged = false;
+ }
+
+ if (showStyle.currentStep >= showStyle.divisions && unchanged) {
+ if (direction > 0) {
+ showStyle.processed = true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool GfxTransitions32::processMorph(PlaneShowStyle &showStyle) {
+ g_sci->_gfxFrameout->palMorphFrameOut(_styleRanges, &showStyle);
+ showStyle.processed = true;
+ return true;
+}
+
+bool GfxTransitions32::processWipe(const int8 direction, PlaneShowStyle &showStyle) {
+ bool unchanged = true;
+ if (showStyle.currentStep < showStyle.divisions) {
+ int index;
+ if (direction > 0) {
+ index = showStyle.currentStep;
+ } else {
+ index = showStyle.divisions - showStyle.currentStep - 1;
+ }
+
+ index *= showStyle.numEdges;
+ for (int i = 0; i < showStyle.numEdges; ++i) {
+ ScreenItem *screenItem = showStyle.screenItems[index + i];
+ if (showStyle.fadeUp) {
+ g_sci->_gfxFrameout->deleteScreenItem(*screenItem);
+ showStyle.screenItems[index + i] = nullptr;
+ } else {
+ g_sci->_gfxFrameout->addScreenItem(*screenItem);
+ }
+ }
+
+ ++showStyle.currentStep;
+ showStyle.nextTick += showStyle.delay;
+ unchanged = false;
+ }
+
+ if (showStyle.currentStep >= showStyle.divisions && unchanged) {
+ if (showStyle.fadeUp) {
+ showStyle.processed = true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+#pragma mark -
+#pragma mark Scrolls
+
+void GfxTransitions32::processScrolls() {
+ for (ScrollList::iterator it = _scrolls.begin(); it != _scrolls.end(); ) {
+ bool finished = processScroll(*it);
+ if (finished) {
+ it = _scrolls.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ throttle(33);
+}
+
+void GfxTransitions32::kernelSetScroll(const reg_t planeId, const int16 deltaX, const int16 deltaY, const GuiResourceId pictureId, const bool animate, const bool mirrorX) {
+
+ for (ScrollList::const_iterator it = _scrolls.begin(); it != _scrolls.end(); ++it) {
+ if (it->plane == planeId) {
+ error("Scroll already exists on plane %04x:%04x", PRINT_REG(planeId));
+ }
+ }
+
+ if (!deltaX && !deltaY) {
+ error("kSetScroll: Scroll has no movement");
+ }
+
+ if (deltaX && deltaY) {
+ error("kSetScroll: Cannot scroll in two dimensions");
+ }
+
+ PlaneScroll *scroll = new PlaneScroll;
+ scroll->plane = planeId;
+ scroll->x = 0;
+ scroll->y = 0;
+ scroll->deltaX = deltaX;
+ scroll->deltaY = deltaY;
+ scroll->newPictureId = pictureId;
+ scroll->animate = animate;
+ scroll->startTick = g_sci->getTickCount();
+
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeId);
+ if (plane == nullptr) {
+ error("kSetScroll: Plane %04x:%04x not found", PRINT_REG(planeId));
+ }
+
+ Plane *visiblePlane = g_sci->_gfxFrameout->getPlanes().findByObject(planeId);
+ if (visiblePlane == nullptr) {
+ error("kSetScroll: Visible plane %04x:%04x not found", PRINT_REG(planeId));
+ }
+
+ const Common::Rect &gameRect = visiblePlane->_gameRect;
+ Common::Point picOrigin;
+
+ if (deltaX) {
+ picOrigin.y = 0;
+
+ if (deltaX > 0) {
+ scroll->x = picOrigin.x = -gameRect.width();
+ } else {
+ scroll->x = picOrigin.x = gameRect.width();
+ }
+ } else {
+ picOrigin.x = 0;
+
+ if (deltaY > 0) {
+ scroll->y = picOrigin.y = -gameRect.height();
+ } else {
+ scroll->y = picOrigin.y = gameRect.height();
+ }
+ }
+
+ scroll->oldPictureId = plane->addPic(pictureId, picOrigin, mirrorX);
+
+ if (animate) {
+ _scrolls.push_front(*scroll);
+ } else {
+ bool finished = false;
+ while (!finished && !g_engine->shouldQuit()) {
+ finished = processScroll(*scroll);
+ g_sci->_gfxFrameout->frameOut(true);
+ throttle(33);
+ }
+ }
+
+ delete scroll;
+}
+
+bool GfxTransitions32::processScroll(PlaneScroll &scroll) {
+ bool finished = false;
+ uint32 now = g_sci->getTickCount();
+ if (scroll.startTick >= now) {
+ return false;
+ }
+
+ int deltaX = scroll.deltaX;
+ int deltaY = scroll.deltaY;
+ if (((scroll.x + deltaX) * scroll.x) <= 0) {
+ deltaX = -scroll.x;
+ }
+ if (((scroll.y + deltaY) * scroll.y) <= 0) {
+ deltaY = -scroll.y;
+ }
+
+ scroll.x += deltaX;
+ scroll.y += deltaY;
+
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(scroll.plane);
+
+ if (plane == nullptr) {
+ error("[GfxTransitions32::processScroll]: Plane %04x:%04x not found", PRINT_REG(scroll.plane));
+ }
+
+ if ((scroll.x == 0) && (scroll.y == 0)) {
+ plane->deletePic(scroll.oldPictureId, scroll.newPictureId);
+ finished = true;
+ }
+
+ plane->scrollScreenItems(deltaX, deltaY, true);
+
+ return finished;
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/transitions32.h b/engines/sci/graphics/transitions32.h
new file mode 100644
index 0000000000..0c828a20f7
--- /dev/null
+++ b/engines/sci/graphics/transitions32.h
@@ -0,0 +1,488 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_TRANSITIONS32_H
+#define SCI_GRAPHICS_TRANSITIONS32_H
+
+#include "common/list.h"
+#include "common/scummsys.h"
+#include "sci/engine/vm_types.h"
+
+namespace Sci {
+enum ShowStyleType /* : uint8 */ {
+ kShowStyleNone = 0,
+ kShowStyleHShutterOut = 1,
+ kShowStyleHShutterIn = 2,
+ kShowStyleVShutterOut = 3,
+ kShowStyleVShutterIn = 4,
+ kShowStyleWipeLeft = 5,
+ kShowStyleWipeRight = 6,
+ kShowStyleWipeUp = 7,
+ kShowStyleWipeDown = 8,
+ kShowStyleIrisOut = 9,
+ kShowStyleIrisIn = 10,
+ kShowStyleDissolveNoMorph = 11,
+ kShowStyleDissolve = 12,
+ kShowStyleFadeOut = 13,
+ kShowStyleFadeIn = 14,
+ kShowStyleMorph = 15
+};
+
+/**
+ * Show styles represent transitions applied to draw planes.
+ * One show style per plane can be active at a time.
+ */
+struct PlaneShowStyle {
+ /**
+ * The ID of the plane this show style belongs to.
+ * In SCI2.1mid (at least SQ6), per-plane transitions
+ * were removed and a single plane ID is used.
+ */
+ reg_t plane;
+
+ /**
+ * The type of the transition.
+ */
+ ShowStyleType type;
+
+ /**
+ * When true, the show style is an entry transition
+ * to a new room. When false, it is an exit
+ * transition away from an old room.
+ */
+ bool fadeUp;
+
+ /**
+ * The number of steps for the show style.
+ */
+ int16 divisions;
+
+ /**
+ * The color used by transitions that draw CelObjColor
+ * screen items. -1 for transitions that do not draw
+ * screen items.
+ */
+ int16 color;
+
+ // TODO: Probably uint32
+ // TODO: This field probably should be used in order to
+ // provide time-accurate processing of show styles. In the
+ // actual SCI engine (at least 2–2.1mid) it appears that
+ // style transitions are drawn “as fast as possible”, one
+ // step per loop, even though this delay field exists
+ int delay;
+
+ // TODO: Probably bool, but never seems to be true?
+ bool animate;
+
+ /**
+ * The wall time at which the next step of the animation
+ * should execute.
+ */
+ uint32 nextTick;
+
+ /**
+ * During playback of the show style, the current step
+ * (out of divisions).
+ */
+ int currentStep;
+
+ /**
+ * Whether or not this style has finished running and
+ * is ready for disposal.
+ */
+ bool processed;
+
+ //
+ // Engine specific properties for SCI2.1early
+ //
+
+ /**
+ * A list of screen items, each representing one
+ * block of a wipe transition.
+ */
+ Common::Array<ScreenItem *> screenItems;
+
+ /**
+ * For wipe transitions, the number of edges with a
+ * moving wipe (1, 2, or 4).
+ */
+ uint8 numEdges;
+
+ /**
+ * The dimensions of the plane, in game script
+ * coordinates.
+ */
+ int16 width, height;
+
+ /**
+ * For pixel dissolve transitions, the screen item
+ * used to render the transition.
+ */
+ ScreenItem *bitmapScreenItem;
+
+ /**
+ * For pixel dissolve transitions, the bitmap used
+ * to render the transition.
+ */
+ reg_t bitmap;
+
+ /**
+ * The bit mask used by pixel dissolve transitions.
+ */
+ uint32 dissolveMask;
+
+ /**
+ * The first pixel that was dissolved in a pixel
+ * dissolve transition.
+ */
+ uint32 firstPixel;
+
+ /**
+ * The last pixel that was dissolved. Once all
+ * pixels have been dissolved, `pixel` will once
+ * again equal `firstPixel`.
+ */
+ uint32 pixel;
+
+ //
+ // Engine specific properties for SCI2.1mid through SCI3
+ //
+
+ /**
+ * The number of entries in the fadeColorRanges array.
+ */
+ uint8 fadeColorRangesCount;
+
+ /**
+ * A pointer to an dynamically sized array of palette
+ * indexes, in the order [ fromColor, toColor, ... ].
+ * Only colors within this range are transitioned.
+ */
+ uint16 *fadeColorRanges;
+};
+
+/**
+ * PlaneScroll describes a transition between two different
+ * pictures within a single plane.
+ */
+struct PlaneScroll {
+ /**
+ * The ID of the plane to be scrolled.
+ */
+ reg_t plane;
+
+ /**
+ * The current position of the scroll.
+ */
+ int16 x, y;
+
+ /**
+ * The distance that should be scrolled. Only one of
+ * `deltaX` or `deltaY` may be set.
+ */
+ int16 deltaX, deltaY;
+
+ /**
+ * The pic that should be created and scrolled into
+ * view inside the plane.
+ */
+ GuiResourceId newPictureId;
+
+ /**
+ * The picture that should be scrolled out of view
+ * and deleted from the plane.
+ */
+ GuiResourceId oldPictureId;
+
+ /**
+ * If true, the scroll animation is interleaved
+ * with other updates to the graphics. If false,
+ * the scroll will be exclusively animated until
+ * it is finished.
+ */
+ bool animate;
+
+ /**
+ * The tick after which the animation will start.
+ */
+ uint32 startTick;
+};
+
+typedef Common::List<PlaneShowStyle> ShowStyleList;
+typedef Common::List<PlaneScroll> ScrollList;
+
+class GfxTransitions32 {
+public:
+ GfxTransitions32(SegManager *_segMan);
+ ~GfxTransitions32();
+private:
+ SegManager *_segMan;
+
+ /**
+ * Throttles transition playback to prevent transitions from being
+ * instantaneous on modern computers.
+ *
+ * kSetShowStyle transitions are throttled at 10ms intervals, under the
+ * assumption that the default fade transition of 101 divisions was designed
+ * to finish in one second. Empirically, this seems to roughly match the
+ * speed of DOSBox, and feels reasonable.
+ *
+ * Transitions using kSetScroll (used in the LSL6hires intro) need to be
+ * slower, so they get throttled at 33ms instead of 10ms. This value was
+ * chosen by gut feel, as these scrolling transitions are instantaneous in
+ * DOSBox.
+ */
+ void throttle(const uint32 ms = 10);
+
+ void clearShowRects();
+ void addShowRect(const Common::Rect &rect);
+ void sendShowRects();
+
+#pragma mark -
+#pragma mark Show styles
+public:
+ inline bool hasShowStyles() const { return !_showStyles.empty(); }
+
+ /**
+ * Processes all active show styles in a loop
+ * until they are finished.
+ */
+ void processShowStyles();
+
+ /**
+ * Processes show styles that are applied
+ * through `GfxFrameout::palMorphFrameOut`.
+ */
+ void processEffects(PlaneShowStyle &showStyle);
+
+ // NOTE: This signature is taken from SCI3 Phantasmagoria 2
+ // and is valid for all implementations of SCI32
+ void kernelSetShowStyle(const uint16 argc, const reg_t planeObj, const ShowStyleType type, const int16 seconds, const int16 direction, const int16 priority, const int16 animate, const int16 frameOutNow, reg_t pFadeArray, int16 divisions, const int16 blackScreen);
+
+ /**
+ * Sets the range that will be used by
+ * `GfxFrameout::palMorphFrameOut` to alter
+ * palette entries.
+ */
+ void kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor);
+
+ /**
+ * A map of palette entries that can be morphed
+ * by the Morph show style.
+ */
+ int8 _styleRanges[256];
+
+private:
+ /**
+ * Default sequence values for pixel dissolve
+ * transition bit masks.
+ */
+ int *_dissolveSequenceSeeds;
+
+ /**
+ * Default values for `PlaneShowStyle::divisions`
+ * for the current SCI version.
+ */
+ int16 *_defaultDivisions;
+
+ /**
+ * The list of PlaneShowStyles that are
+ * currently active.
+ */
+ ShowStyleList _showStyles;
+
+ /**
+ * Finds a show style that applies to the given
+ * plane.
+ */
+ PlaneShowStyle *findShowStyleForPlane(const reg_t planeObj);
+
+ /**
+ * Finds the iterator for a show style that
+ * applies to the given plane.
+ */
+ ShowStyleList::iterator findIteratorForPlane(const reg_t planeObj);
+
+ /**
+ * Deletes the given PlaneShowStyle and returns
+ * the next PlaneShowStyle from the list of
+ * styles.
+ */
+ ShowStyleList::iterator deleteShowStyle(const ShowStyleList::iterator &showStyle);
+
+ /**
+ * Initializes the given PlaneShowStyle for an
+ * iris effect for SCI2 to 2.1early.
+ */
+ void configure21EarlyIris(PlaneShowStyle &showStyle, const int16 priority);
+
+ /**
+ * Initializes the given PlaneShowStyle for a
+ * pixel dissolve effect for SCI2 to 2.1early.
+ */
+ void configure21EarlyDissolve(PlaneShowStyle &showStyle, const int16 priority, const Common::Rect &gameRect);
+
+ /**
+ * Processes one tick of the given
+ * PlaneShowStyle.
+ */
+ bool processShowStyle(PlaneShowStyle &showStyle, uint32 now);
+
+ /**
+ * Performs an instant transition between two
+ * rooms.
+ */
+ bool processNone(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders into a room
+ * with a horizontal shutter effect.
+ */
+ void processHShutterOut(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders to black
+ * with a horizontal shutter effect.
+ */
+ void processHShutterIn(const PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders into a room
+ * with a vertical shutter effect.
+ */
+ void processVShutterOut(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders to black
+ * with a vertical shutter effect.
+ */
+ void processVShutterIn(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders into a room
+ * with a wipe to the left.
+ */
+ void processWipeLeft(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders to black
+ * with a wipe to the right.
+ */
+ void processWipeRight(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders into a room
+ * with a wipe upwards.
+ */
+ void processWipeUp(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders to black
+ * with a wipe downwards.
+ */
+ void processWipeDown(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders into a room
+ * with an iris effect.
+ */
+ bool processIrisOut(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders to black
+ * with an iris effect.
+ */
+ bool processIrisIn(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders between
+ * rooms using a block dissolve effect.
+ */
+ void processDissolveNoMorph(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that renders between
+ * rooms with a pixel dissolve effect.
+ */
+ bool processPixelDissolve(PlaneShowStyle &showStyle);
+
+ /**
+ * SCI2 to 2.1early implementation of pixel
+ * dissolve.
+ */
+ bool processPixelDissolve21Early(PlaneShowStyle &showStyle);
+
+ /**
+ * SCI2.1mid and later implementation of
+ * pixel dissolve.
+ */
+ bool processPixelDissolve21Mid(const PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a transition that fades to black
+ * between rooms.
+ */
+ bool processFade(const int8 direction, PlaneShowStyle &showStyle);
+
+ /**
+ * Morph transition calls back into the
+ * transition system's `processEffects`
+ * method, which then applies transitions
+ * other than None, Fade, or Morph.
+ */
+ bool processMorph(PlaneShowStyle &showStyle);
+
+ /**
+ * Performs a generic transition for any of
+ * the wipe/shutter/iris effects.
+ */
+ bool processWipe(const int8 direction, PlaneShowStyle &showStyle);
+
+#pragma mark -
+#pragma mark Scrolls
+public:
+ inline bool hasScrolls() const { return !_scrolls.empty(); }
+
+ /**
+ * Processes all active plane scrolls
+ * in a loop until they are finished.
+ */
+ void processScrolls();
+
+ void kernelSetScroll(const reg_t plane, const int16 deltaX, const int16 deltaY, const GuiResourceId pictureId, const bool animate, const bool mirrorX);
+
+private:
+ /**
+ * A list of active plane scrolls.
+ */
+ ScrollList _scrolls;
+
+ /**
+ * Performs a scroll of the content of
+ * a plane.
+ */
+ bool processScroll(PlaneScroll &scroll);
+};
+
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
new file mode 100644
index 0000000000..c3aafb62bf
--- /dev/null
+++ b/engines/sci/graphics/video32.cpp
@@ -0,0 +1,893 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/mixer.h" // for Audio::Mixer::kSFXSoundType
+#include "common/config-manager.h" // for ConfMan
+#include "common/textconsole.h" // for warning, error
+#include "common/util.h" // for ARRAYSIZE
+#include "common/system.h" // for g_system
+#include "engine.h" // for Engine, g_engine
+#include "engines/util.h" // for initGraphics
+#include "sci/console.h" // for Console
+#include "sci/engine/features.h" // for GameFeatures
+#include "sci/engine/state.h" // for EngineState
+#include "sci/engine/vm_types.h" // for reg_t
+#include "sci/event.h" // for SciEvent, EventManager, SCI_...
+#include "sci/graphics/celobj32.h" // for CelInfo32, ::kLowResX, ::kLo...
+#include "sci/graphics/cursor32.h" // for GfxCursor32
+#include "sci/graphics/frameout.h" // for GfxFrameout
+#include "sci/graphics/helpers.h" // for Color, Palette
+#include "sci/graphics/palette32.h" // for GfxPalette32
+#include "sci/graphics/plane32.h" // for Plane, PlanePictureCodes::kP...
+#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem, Scale...
+#include "sci/sci.h" // for SciEngine, g_sci, getSciVersion
+#include "sci/sound/audio32.h" // for Audio32
+#include "sci/video/seq_decoder.h" // for SEQDecoder
+#include "video/avi_decoder.h" // for AVIDecoder
+#include "video/coktel_decoder.h" // for AdvancedVMDDecoder
+#include "sci/graphics/video32.h"
+
+namespace Graphics { struct Surface; }
+
+namespace Sci {
+
+#pragma mark SEQPlayer
+
+SEQPlayer::SEQPlayer(SegManager *segMan) :
+ _segMan(segMan),
+ _decoder(nullptr),
+ _plane(nullptr),
+ _screenItem(nullptr) {}
+
+void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y) {
+ delete _decoder;
+ _decoder = new SEQDecoder(numTicks);
+ if (!_decoder->loadFile(fileName)) {
+ warning("[SEQPlayer::play]: Failed to load %s", fileName.c_str());
+ return;
+ }
+
+ // NOTE: In the original engine, video was output directly to the hardware,
+ // bypassing the game's rendering engine. Instead of doing this, we use a
+ // mechanism that is very similar to that used by the VMD player, which
+ // allows the SEQ to be drawn into a bitmap ScreenItem and displayed using
+ // the normal graphics system.
+ reg_t bitmapId;
+ SciBitmap &bitmap = *_segMan->allocateBitmap(&bitmapId, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, kLowResX, kLowResY, 0, false, false);
+ bitmap.getBuffer().fillRect(Common::Rect(_decoder->getWidth(), _decoder->getHeight()), 0);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = bitmapId;
+
+ _plane = new Plane(Common::Rect(kLowResX, kLowResY), kPlanePicColored);
+ g_sci->_gfxFrameout->addPlane(*_plane);
+
+ // Normally we would use the x, y coordinates passed into the play function
+ // to position the screen item, but because the video frame bitmap is
+ // drawn in low-resolution coordinates, it gets automatically scaled up by
+ // the engine (pixel doubling with aspect ratio correction). As a result,
+ // the animation does not need the extra offsets from the game in order to
+ // be correctly positioned in the middle of the window, so we ignore them.
+ _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(0, 0), ScaleInfo());
+ g_sci->_gfxFrameout->addScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+ _decoder->start();
+
+ while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) {
+ g_sci->sleep(_decoder->getTimeToNextFrame());
+ renderFrame(bitmap);
+ }
+
+ _segMan->freeBitmap(bitmapId);
+ g_sci->_gfxFrameout->deletePlane(*_plane);
+ g_sci->_gfxFrameout->frameOut(true);
+ _screenItem = nullptr;
+ _plane = nullptr;
+}
+
+void SEQPlayer::renderFrame(SciBitmap &bitmap) const {
+ const Graphics::Surface *surface = _decoder->decodeNextFrame();
+
+ bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h));
+
+ const bool dirtyPalette = _decoder->hasDirtyPalette();
+ if (dirtyPalette) {
+ Palette palette;
+ const byte *rawPalette = _decoder->getPalette();
+ for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) {
+ palette.colors[i].r = *rawPalette++;
+ palette.colors[i].g = *rawPalette++;
+ palette.colors[i].b = *rawPalette++;
+ palette.colors[i].used = true;
+ }
+
+ g_sci->_gfxPalette32->submit(palette);
+ }
+
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+ g_sci->getSciDebugger()->onFrame();
+}
+
+#pragma mark -
+#pragma mark AVIPlayer
+
+AVIPlayer::AVIPlayer(SegManager *segMan, EventManager *eventMan) :
+ _segMan(segMan),
+ _eventMan(eventMan),
+ _decoder(new Video::AVIDecoder(Audio::Mixer::kSFXSoundType)),
+ _scaleBuffer(nullptr),
+ _plane(nullptr),
+ _screenItem(nullptr),
+ _status(kAVINotOpen) {}
+
+AVIPlayer::~AVIPlayer() {
+ close();
+ delete _decoder;
+}
+
+AVIPlayer::IOStatus AVIPlayer::open(const Common::String &fileName) {
+ if (_status != kAVINotOpen) {
+ close();
+ }
+
+ if (!_decoder->loadFile(fileName)) {
+ return kIOFileNotFound;
+ }
+
+ _status = kAVIOpen;
+ return kIOSuccess;
+}
+
+AVIPlayer::IOStatus AVIPlayer::init1x(const int16 x, const int16 y, int16 width, int16 height) {
+ if (_status == kAVINotOpen) {
+ return kIOFileNotFound;
+ }
+
+ _pixelDouble = false;
+
+ if (!width || !height) {
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ const Ratio screenToScriptX(scriptWidth, screenWidth);
+ const Ratio screenToScriptY(scriptHeight, screenHeight);
+ width = (_decoder->getWidth() * screenToScriptX).toInt();
+ height = (_decoder->getHeight() * screenToScriptY).toInt();
+ }
+
+ // QFG4CD gives non-multiple-of-2 values for width and height of the intro
+ // video, which would normally be OK except the source video is a pixel
+ // bigger in each dimension so it just causes part of the video to get cut
+ // off
+ width = (width + 1) & ~1;
+ height = (height + 1) & ~1;
+
+ // GK1 CREDITS.AVI is not rendered correctly in SSCI because it is a 640x480
+ // video and the game script gives the wrong dimensions.
+ // Since this is the only high-resolution AVI ever used by any SCI game,
+ // just set the draw rectangle to draw across the entire screen
+ if (g_sci->getGameId() == GID_GK1 && _decoder->getWidth() > 320) {
+ _drawRect.left = 0;
+ _drawRect.top = 0;
+ _drawRect.right = 320;
+ _drawRect.bottom = 200;
+ } else {
+ _drawRect.left = x;
+ _drawRect.top = y;
+ _drawRect.right = x + width;
+ _drawRect.bottom = y + height;
+ }
+
+ init();
+
+ return kIOSuccess;
+}
+
+AVIPlayer::IOStatus AVIPlayer::init2x(const int16 x, const int16 y) {
+ if (_status == kAVINotOpen) {
+ return kIOFileNotFound;
+ }
+
+ _drawRect.left = x;
+ _drawRect.top = y;
+ _drawRect.right = x + _decoder->getWidth() * 2;
+ _drawRect.bottom = y + _decoder->getHeight() * 2;
+ _pixelDouble = true;
+
+ init();
+
+ return kIOSuccess;
+}
+
+void AVIPlayer::init() {
+ int16 xRes;
+ int16 yRes;
+
+ // GK1 CREDITS.AVI or KQ7 1.51 half-size videos
+ if ((g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() > 320) ||
+ (g_sci->getGameId() == GID_KQ7 && getSciVersion() == SCI_VERSION_2_1_EARLY && _drawRect.width() <= 160)) {
+ xRes = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ yRes = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ } else {
+ xRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+
+ const Ratio videoRatio(_decoder->getWidth(), _decoder->getHeight());
+ const Ratio screenRatio(4, 3);
+
+ // Videos that already have a 4:3 aspect ratio should not receive any
+ // aspect ratio correction
+ if (videoRatio == screenRatio) {
+ yRes = 240;
+ } else {
+ yRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ }
+ }
+
+ _plane = new Plane(_drawRect);
+ g_sci->_gfxFrameout->addPlane(*_plane);
+
+ if (_decoder->getPixelFormat().bytesPerPixel == 1) {
+ SciBitmap &bitmap = *_segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, xRes, yRes, 0, false, false);
+ bitmap.getBuffer().fillRect(Common::Rect(_decoder->getWidth(), _decoder->getHeight()), 0);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = _bitmap;
+
+ _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(), ScaleInfo());
+ g_sci->_gfxFrameout->addScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+ } else {
+ // Attempting to draw a palettized cursor into a 24bpp surface will
+ // cause memory corruption, so hide the cursor in this mode (SCI did not
+ // have a 24bpp mode but just directed VFW to display videos instead)
+ g_sci->_gfxCursor32->hide();
+
+ const Buffer &currentBuffer = g_sci->_gfxFrameout->getCurrentBuffer();
+ const Graphics::PixelFormat format = _decoder->getPixelFormat();
+ initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, g_sci->_gfxFrameout->_isHiRes, &format);
+
+ if (_pixelDouble) {
+ const int16 width = _drawRect.width();
+ const int16 height = _drawRect.height();
+ _scaleBuffer = calloc(1, width * height * format.bytesPerPixel);
+ }
+ }
+}
+
+AVIPlayer::IOStatus AVIPlayer::play(const int16 from, const int16 to, const int16, const bool async) {
+ if (_status == kAVINotOpen) {
+ return kIOFileNotFound;
+ }
+
+ if (from >= 0 && to > 0 && from <= to) {
+ _decoder->seekToFrame(from);
+ _decoder->setEndFrame(to);
+ }
+
+ if (!async) {
+ renderVideo();
+ } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) {
+ playUntilEvent((EventFlags)(kEventFlagEnd | kEventFlagEscapeKey));
+ } else {
+ _status = kAVIPlaying;
+ }
+
+ return kIOSuccess;
+}
+
+void AVIPlayer::renderVideo() const {
+ _decoder->start();
+ while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) {
+ g_sci->sleep(_decoder->getTimeToNextFrame());
+ while (_decoder->needsUpdate()) {
+ renderFrame();
+ }
+ }
+}
+
+AVIPlayer::IOStatus AVIPlayer::close() {
+ if (_status == kAVINotOpen) {
+ return kIOSuccess;
+ }
+
+ free(_scaleBuffer);
+ _scaleBuffer = nullptr;
+
+ if (_decoder->getPixelFormat().bytesPerPixel != 1) {
+ const bool isHiRes = g_sci->_gfxFrameout->_isHiRes;
+ const Buffer &currentBuffer = g_sci->_gfxFrameout->getCurrentBuffer();
+ const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8();
+ initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, isHiRes, &format);
+ g_sci->_gfxCursor32->unhide();
+ }
+
+ _decoder->close();
+ _status = kAVINotOpen;
+ _segMan->freeBitmap(_bitmap);
+ _bitmap = NULL_REG;
+ g_sci->_gfxFrameout->deletePlane(*_plane);
+ _plane = nullptr;
+ _screenItem = nullptr;
+ return kIOSuccess;
+}
+
+AVIPlayer::IOStatus AVIPlayer::cue(const uint16 frameNo) {
+ if (!_decoder->seekToFrame(frameNo)) {
+ return kIOSeekFailed;
+ }
+
+ _status = kAVIPaused;
+ return kIOSuccess;
+}
+
+uint16 AVIPlayer::getDuration() const {
+ if (_status == kAVINotOpen) {
+ return 0;
+ }
+
+ return _decoder->getFrameCount();
+}
+
+void AVIPlayer::renderFrame() const {
+ const Graphics::Surface *surface = _decoder->decodeNextFrame();
+
+ if (surface->format.bytesPerPixel == 1) {
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
+ if (surface->w > bitmap.getWidth() || surface->h > bitmap.getHeight()) {
+ warning("Attempted to draw a video frame larger than the destination bitmap");
+ return;
+ }
+
+ // KQ7 1.51 encodes videos with palette entry 0 as white, which makes
+ // the area around the video turn white too, since it is coded to use
+ // palette entry 0. This happens to work in the original game because
+ // the video is rendered by VfW, not in the engine itself. To fix this,
+ // we just modify the incoming pixel data from the video so if a pixel
+ // is using entry 0, we change it to use entry 255, which is guaranteed
+ // to always be white
+ if (getSciVersion() == SCI_VERSION_2_1_EARLY && g_sci->getGameId() == GID_KQ7) {
+ uint8 *target = bitmap.getPixels();
+ const uint8 *source = (const uint8 *)surface->getPixels();
+ const uint8 *end = (const uint8 *)surface->getPixels() + surface->w * surface->h;
+
+ while (source != end) {
+ const uint8 value = *source++;
+ *target++ = value == 0 ? 255 : value;
+ }
+ } else {
+ bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h));
+ }
+
+ const bool dirtyPalette = _decoder->hasDirtyPalette();
+ if (dirtyPalette) {
+ Palette palette;
+ const byte *rawPalette = _decoder->getPalette();
+ for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) {
+ palette.colors[i].r = *rawPalette++;
+ palette.colors[i].g = *rawPalette++;
+ palette.colors[i].b = *rawPalette++;
+ palette.colors[i].used = true;
+ }
+
+ // Prevent KQ7 1.51 from setting entry 0 to white
+ palette.colors[0].used = false;
+
+ g_sci->_gfxPalette32->submit(palette);
+ }
+
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+ g_sci->getSciDebugger()->onFrame();
+ } else {
+ assert(surface->format.bytesPerPixel == 4);
+
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ Common::Rect drawRect(_drawRect);
+ mulru(drawRect, Ratio(screenWidth, scriptWidth), Ratio(screenHeight, scriptHeight), 1);
+
+ if (_pixelDouble) {
+ const uint32 *source = (const uint32 *)surface->getPixels();
+ uint32 *target = (uint32 *)_scaleBuffer;
+ // target pitch here is in uint32s, not bytes, because the surface
+ // bpp is 4
+ const uint16 pitch = surface->pitch / 2;
+ for (int y = 0; y < surface->h; ++y) {
+ for (int x = 0; x < surface->w; ++x) {
+ const uint32 value = *source++;
+
+ target[0] = value;
+ target[1] = value;
+ target[pitch] = value;
+ target[pitch + 1] = value;
+ target += 2;
+ }
+ target += pitch;
+ }
+
+ g_system->copyRectToScreen(_scaleBuffer, surface->pitch * 2, drawRect.left, drawRect.top, _drawRect.width(), _drawRect.height());
+ } else {
+ g_system->copyRectToScreen(surface->getPixels(), surface->pitch, drawRect.left, drawRect.top, surface->w, surface->h);
+ }
+
+ g_system->updateScreen();
+ }
+}
+
+AVIPlayer::EventFlags AVIPlayer::playUntilEvent(EventFlags flags) {
+ _decoder->start();
+
+ EventFlags stopFlag = kEventFlagNone;
+ while (!g_engine->shouldQuit()) {
+ if (_decoder->endOfVideo()) {
+ stopFlag = kEventFlagEnd;
+ break;
+ }
+
+ g_sci->sleep(_decoder->getTimeToNextFrame());
+ while (_decoder->needsUpdate()) {
+ renderFrame();
+ }
+
+ SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK);
+ if ((flags & kEventFlagMouseDown) && event.type == SCI_EVENT_MOUSE_PRESS) {
+ stopFlag = kEventFlagMouseDown;
+ break;
+ }
+
+ event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK);
+ if ((flags & kEventFlagEscapeKey) && event.type == SCI_EVENT_KEYBOARD) {
+ bool stop = false;
+ while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)),
+ event.type != SCI_EVENT_NONE) {
+ if (event.character == SCI_KEY_ESC) {
+ stop = true;
+ break;
+ }
+ }
+
+ if (stop) {
+ stopFlag = kEventFlagEscapeKey;
+ break;
+ }
+ }
+ }
+
+ return stopFlag;
+}
+
+#pragma mark -
+#pragma mark VMDPlayer
+
+VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) :
+ _segMan(segMan),
+ _eventMan(eventMan),
+ _decoder(new Video::AdvancedVMDDecoder(Audio::Mixer::kSFXSoundType)),
+
+ _isOpen(false),
+ _isInitialized(false),
+ _yieldFrame(0),
+ _yieldInterval(0),
+ _lastYieldedFrameNo(0),
+
+ _plane(nullptr),
+ _screenItem(nullptr),
+ _planeIsOwned(true),
+ _priority(0),
+ _doublePixels(false),
+ _stretchVertical(false),
+ _blackLines(false),
+ _leaveScreenBlack(false),
+ _leaveLastFrame(false),
+ _ignorePalettes(false),
+
+ _blackoutPlane(nullptr),
+
+ _startColor(0),
+ _endColor(255),
+ _blackPalette(false),
+
+ _boostPercent(100),
+ _boostStartColor(0),
+ _boostEndColor(255),
+
+ _showCursor(false) {}
+
+VMDPlayer::~VMDPlayer() {
+ close();
+ delete _decoder;
+}
+
+#pragma mark -
+#pragma mark VMDPlayer - Playback
+
+VMDPlayer::IOStatus VMDPlayer::open(const Common::String &fileName, const OpenFlags flags) {
+ if (_isOpen) {
+ error("Attempted to play %s, but another VMD was loaded", fileName.c_str());
+ }
+
+ if (g_sci->_features->VMDOpenStopsAudio()) {
+ g_sci->_audio32->stop(kAllChannels);
+ }
+
+ if (_decoder->loadFile(fileName)) {
+ if (flags & kOpenFlagMute) {
+ _decoder->setVolume(0);
+ }
+ _isOpen = true;
+ return kIOSuccess;
+ } else {
+ return kIOError;
+ }
+}
+
+void VMDPlayer::init(const int16 x, const int16 y, const PlayFlags flags, const int16 boostPercent, const int16 boostStartColor, const int16 boostEndColor) {
+ _x = getSciVersion() >= SCI_VERSION_3 ? x : (x & ~1);
+ _y = y;
+ _doublePixels = flags & kPlayFlagDoublePixels;
+ _blackLines = ConfMan.getBool("enable_black_lined_video") && (flags & kPlayFlagBlackLines);
+ _boostPercent = 100 + (flags & kPlayFlagBoost ? boostPercent : 0);
+ _boostStartColor = CLIP<int16>(boostStartColor, 0, 255);
+ _boostEndColor = CLIP<int16>(boostEndColor, 0, 255);
+ _leaveScreenBlack = flags & kPlayFlagLeaveScreenBlack;
+ _leaveLastFrame = flags & kPlayFlagLeaveLastFrame;
+ _blackPalette = flags & kPlayFlagBlackPalette;
+ _stretchVertical = flags & kPlayFlagStretchVertical;
+}
+
+VMDPlayer::IOStatus VMDPlayer::close() {
+ if (!_isOpen) {
+ return kIOSuccess;
+ }
+
+ _decoder->close();
+ _isOpen = false;
+ _isInitialized = false;
+ _ignorePalettes = false;
+
+ _segMan->freeBitmap(_screenItem->_celInfo.bitmap);
+
+ if (!_planeIsOwned && _screenItem != nullptr) {
+ g_sci->_gfxFrameout->deleteScreenItem(*_screenItem);
+ _screenItem = nullptr;
+ } else if (_plane != nullptr) {
+ g_sci->_gfxFrameout->deletePlane(*_plane);
+ _plane = nullptr;
+ }
+
+ if (!_leaveLastFrame && _leaveScreenBlack) {
+ // This call *actually* deletes the plane/screen item
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+
+ if (_blackoutPlane != nullptr) {
+ g_sci->_gfxFrameout->deletePlane(*_blackoutPlane);
+ _blackoutPlane = nullptr;
+ }
+
+ if (!_leaveLastFrame && !_leaveScreenBlack) {
+ // This call *actually* deletes the blackout plane
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+
+ if (!_showCursor) {
+ g_sci->_gfxCursor32->unhide();
+ }
+
+ _lastYieldedFrameNo = 0;
+ _planeIsOwned = true;
+ _priority = 0;
+ return kIOSuccess;
+}
+
+VMDPlayer::VMDStatus VMDPlayer::getStatus() const {
+ if (!_isOpen) {
+ return kVMDNotOpen;
+ }
+ if (_decoder->isPaused()) {
+ return kVMDPaused;
+ }
+ if (_decoder->isPlaying()) {
+ return kVMDPlaying;
+ }
+ if (_decoder->endOfVideo()) {
+ return kVMDFinished;
+ }
+ return kVMDOpen;
+}
+
+VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval) {
+ assert(lastFrameNo >= -1);
+
+ const int32 maxFrameNo = _decoder->getFrameCount() - 1;
+
+ if (flags & kEventFlagToFrame) {
+ _yieldFrame = MIN<int32>(lastFrameNo, maxFrameNo);
+ } else {
+ _yieldFrame = maxFrameNo;
+ }
+
+ if (flags & kEventFlagYieldToVM) {
+ _yieldInterval = 3;
+ if (yieldInterval == -1 && !(flags & kEventFlagToFrame)) {
+ _yieldInterval = lastFrameNo;
+ } else if (yieldInterval != -1) {
+ _yieldInterval = MIN<int32>(yieldInterval, maxFrameNo);
+ }
+ } else {
+ _yieldInterval = maxFrameNo;
+ }
+
+ return playUntilEvent(flags);
+}
+
+VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
+ // Flushing all the keyboard and mouse events out of the event manager
+ // keeps events queued from before the start of playback from accidentally
+ // activating a video stop flag
+ for (;;) {
+ const SciEvent event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_MOUSE_PRESS | SCI_EVENT_MOUSE_RELEASE | SCI_EVENT_HOT_RECTANGLE | SCI_EVENT_QUIT);
+ if (event.type == SCI_EVENT_NONE) {
+ break;
+ } else if (event.type == SCI_EVENT_QUIT) {
+ return kEventFlagEnd;
+ }
+ }
+
+ if (flags & kEventFlagReverse) {
+ // NOTE: This flag may not work properly since SSCI does not care
+ // if a video has audio, but the VMD decoder does.
+ const bool success = _decoder->setReverse(true);
+ assert(success);
+ _decoder->setVolume(0);
+ }
+
+ if (!_isInitialized) {
+ _isInitialized = true;
+
+ if (!_showCursor) {
+ g_sci->_gfxCursor32->hide();
+ }
+
+ Common::Rect vmdRect(_x,
+ _y,
+ _x + _decoder->getWidth(),
+ _y + _decoder->getHeight());
+ ScaleInfo vmdScaleInfo;
+
+ if (!_blackoutRect.isEmpty() && _planeIsOwned) {
+ _blackoutPlane = new Plane(_blackoutRect);
+ g_sci->_gfxFrameout->addPlane(*_blackoutPlane);
+ }
+
+ if (_doublePixels) {
+ vmdScaleInfo.x = 256;
+ vmdScaleInfo.y = 256;
+ vmdScaleInfo.signal = kScaleSignalManual;
+ vmdRect.right += vmdRect.width();
+ vmdRect.bottom += vmdRect.height();
+ } else if (_stretchVertical) {
+ vmdScaleInfo.y = 256;
+ vmdScaleInfo.signal = kScaleSignalManual;
+ vmdRect.bottom += vmdRect.height();
+ }
+
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ reg_t bitmapId;
+ SciBitmap &vmdBitmap = *_segMan->allocateBitmap(&bitmapId, vmdRect.width(), vmdRect.height(), 255, 0, 0, screenWidth, screenHeight, 0, false, false);
+ vmdBitmap.getBuffer().fillRect(Common::Rect(vmdRect.width(), vmdRect.height()), 0);
+
+ if (screenWidth != scriptWidth || screenHeight != scriptHeight) {
+ mulru(vmdRect, Ratio(scriptWidth, screenWidth), Ratio(scriptHeight, screenHeight), 1);
+ }
+
+ CelInfo32 vmdCelInfo;
+ vmdCelInfo.bitmap = bitmapId;
+ _decoder->setSurfaceMemory(vmdBitmap.getPixels(), vmdBitmap.getWidth(), vmdBitmap.getHeight(), 1);
+
+ if (_planeIsOwned) {
+ _x = 0;
+ _y = 0;
+ _plane = new Plane(vmdRect, kPlanePicColored);
+ if (_priority) {
+ _plane->_priority = _priority;
+ }
+ g_sci->_gfxFrameout->addPlane(*_plane);
+ _screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(), vmdScaleInfo);
+ } else {
+ _screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(_x, _y), vmdScaleInfo);
+ if (_priority) {
+ _screenItem->_priority = _priority;
+ }
+ }
+
+ if (_blackLines) {
+ _screenItem->_drawBlackLines = true;
+ }
+
+ // NOTE: There was code for positioning the screen item using insetRect
+ // here, but none of the game scripts seem to use this functionality.
+
+ g_sci->_gfxFrameout->addScreenItem(*_screenItem);
+
+ _decoder->start();
+ }
+
+ EventFlags stopFlag = kEventFlagNone;
+ while (!g_engine->shouldQuit()) {
+ if (_decoder->endOfVideo()) {
+ stopFlag = kEventFlagEnd;
+ break;
+ }
+
+ // Sleeping any more than 1/60th of a second will make the mouse feel
+ // very sluggish during VMD action sequences because the frame rate of
+ // VMDs is usually only 15fps
+ g_sci->sleep(MIN<uint32>(10, _decoder->getTimeToNextFrame()));
+ while (_decoder->needsUpdate()) {
+ renderFrame();
+ }
+
+ const int currentFrameNo = _decoder->getCurFrame();
+
+ if (currentFrameNo == _yieldFrame) {
+ stopFlag = kEventFlagEnd;
+ break;
+ }
+
+ if (_yieldInterval > 0 &&
+ currentFrameNo != _lastYieldedFrameNo &&
+ (currentFrameNo % _yieldInterval) == 0
+ ) {
+ _lastYieldedFrameNo = currentFrameNo;
+ stopFlag = kEventFlagYieldToVM;
+ break;
+ }
+
+ SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK);
+ if ((flags & kEventFlagMouseDown) && event.type == SCI_EVENT_MOUSE_PRESS) {
+ stopFlag = kEventFlagMouseDown;
+ break;
+ }
+
+ event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK);
+ if ((flags & kEventFlagEscapeKey) && event.type == SCI_EVENT_KEYBOARD) {
+ bool stop = false;
+ if (getSciVersion() < SCI_VERSION_3) {
+ while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)),
+ event.type != SCI_EVENT_NONE) {
+ if (event.character == SCI_KEY_ESC) {
+ stop = true;
+ break;
+ }
+ }
+ } else {
+ stop = (event.character == SCI_KEY_ESC);
+ }
+
+ if (stop) {
+ stopFlag = kEventFlagEscapeKey;
+ break;
+ }
+ }
+
+ event = _eventMan->getSciEvent(SCI_EVENT_HOT_RECTANGLE | SCI_EVENT_PEEK);
+ if ((flags & kEventFlagHotRectangle) && event.type == SCI_EVENT_HOT_RECTANGLE) {
+ stopFlag = kEventFlagHotRectangle;
+ break;
+ }
+ }
+
+ return stopFlag;
+}
+
+#pragma mark -
+#pragma mark VMDPlayer - Rendering
+
+void VMDPlayer::renderFrame() const {
+ // This writes directly to the CelObjMem we already created,
+ // so no need to take its return value
+ _decoder->decodeNextFrame();
+
+ // NOTE: Normally this would write a hunk palette at the end of the
+ // video bitmap that CelObjMem would read out and submit, but instead
+ // we are just submitting it directly here because the decoder exposes
+ // this information a little bit differently than the one in SSCI
+ const bool dirtyPalette = _decoder->hasDirtyPalette();
+ if (dirtyPalette && !_ignorePalettes) {
+ Palette palette;
+ palette.timestamp = g_sci->getTickCount();
+ for (uint16 i = 0; i < _startColor; ++i) {
+ palette.colors[i].used = false;
+ }
+ for (uint16 i = _endColor; i < 256; ++i) {
+ palette.colors[i].used = false;
+ }
+ if (_blackPalette) {
+ for (uint16 i = _startColor; i <= _endColor; ++i) {
+ palette.colors[i].r = palette.colors[i].g = palette.colors[i].b = 0;
+ palette.colors[i].used = true;
+ }
+ } else {
+ fillPalette(palette);
+ }
+
+ g_sci->_gfxPalette32->submit(palette);
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+
+ if (_blackPalette) {
+ fillPalette(palette);
+ g_sci->_gfxPalette32->submit(palette);
+ g_sci->_gfxPalette32->updateForFrame();
+ g_sci->_gfxPalette32->updateHardware();
+ }
+ } else {
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+ g_sci->getSciDebugger()->onFrame();
+ }
+}
+
+void VMDPlayer::fillPalette(Palette &palette) const {
+ const byte *vmdPalette = _decoder->getPalette() + _startColor * 3;
+ for (uint16 i = _startColor; i <= _endColor; ++i) {
+ int16 r = *vmdPalette++;
+ int16 g = *vmdPalette++;
+ int16 b = *vmdPalette++;
+
+ if (_boostPercent != 100 && i >= _boostStartColor && i <= _boostEndColor) {
+ r = CLIP<int16>(r * _boostPercent / 100, 0, 255);
+ g = CLIP<int16>(g * _boostPercent / 100, 0, 255);
+ b = CLIP<int16>(b * _boostPercent / 100, 0, 255);
+ }
+
+ palette.colors[i].r = r;
+ palette.colors[i].g = g;
+ palette.colors[i].b = b;
+ palette.colors[i].used = true;
+ }
+}
+
+#pragma mark -
+#pragma mark VMDPlayer - Palette
+
+void VMDPlayer::restrictPalette(const uint8 startColor, const int16 endColor) {
+ _startColor = startColor;
+ // At least GK2 sends 256 as the end color, which is wrong,
+ // but works in the original engine as the storage size is 4 bytes
+ // and used values are clamped to 0-255
+ _endColor = MIN<int16>(255, endColor);
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h
new file mode 100644
index 0000000000..69acdf2fce
--- /dev/null
+++ b/engines/sci/graphics/video32.h
@@ -0,0 +1,552 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_VIDEO32_H
+#define SCI_GRAPHICS_VIDEO32_H
+
+#include "common/rect.h" // for Rect
+#include "common/scummsys.h" // for int16, uint8, uint16, int32
+#include "common/str.h" // for String
+#include "sci/engine/vm_types.h" // for reg_t
+#include "sci/video/robot_decoder.h" // for RobotDecoder
+
+namespace Video {
+class AdvancedVMDDecoder;
+class AVIDecoder;
+}
+namespace Sci {
+class EventManager;
+class Plane;
+class ScreenItem;
+class SegManager;
+class SEQDecoder;
+struct Palette;
+
+#pragma mark SEQPlayer
+
+/**
+ * SEQPlayer is used to play SEQ animations.
+ * Used by DOS versions of GK1 and QFG4CD.
+ */
+class SEQPlayer {
+public:
+ SEQPlayer(SegManager *segMan);
+
+ /**
+ * Plays a SEQ animation with the given
+ * file name, with each frame being displayed
+ * for `numTicks` ticks.
+ */
+ void play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y);
+
+private:
+ SegManager *_segMan;
+ SEQDecoder *_decoder;
+
+ /**
+ * The plane where the SEQ will be drawn.
+ */
+ Plane *_plane;
+
+ /**
+ * The screen item representing the SEQ surface.
+ */
+ ScreenItem *_screenItem;
+
+ /**
+ * Renders a single frame of video.
+ */
+ void renderFrame(SciBitmap &bitmap) const;
+};
+
+#pragma mark -
+#pragma mark AVIPlayer
+
+/**
+ * AVIPlayer is used to play AVI videos. Used by
+ * Windows versions of GK1CD, KQ7, and QFG4CD.
+ */
+class AVIPlayer {
+public:
+ enum IOStatus {
+ kIOSuccess = 0,
+ kIOFileNotFound = 2,
+ kIOSeekFailed = 12
+ };
+
+ enum AVIStatus {
+ kAVINotOpen = 0,
+ kAVIOpen = 1,
+ kAVIPlaying = 2,
+ kAVIPaused = 3
+ };
+
+ enum EventFlags {
+ kEventFlagNone = 0,
+ kEventFlagEnd = 1,
+ kEventFlagEscapeKey = 2,
+ kEventFlagMouseDown = 4,
+ kEventFlagHotRectangle = 8
+ };
+
+ AVIPlayer(SegManager *segMan, EventManager *eventMan);
+ ~AVIPlayer();
+
+ /**
+ * Opens a stream to an AVI resource.
+ */
+ IOStatus open(const Common::String &fileName);
+
+ /**
+ * Initializes the AVI rendering parameters for the
+ * current AVI. This must be called after `open`.
+ */
+ IOStatus init1x(const int16 x, const int16 y, const int16 width, const int16 height);
+
+ /**
+ * Initializes the AVI rendering parameters for the
+ * current AVI, in pixel-doubling mode. This must
+ * be called after `open`.
+ */
+ IOStatus init2x(const int16 x, const int16 y);
+
+ /**
+ * Begins playback of the current AVI.
+ */
+ IOStatus play(const int16 from, const int16 to, const int16 showStyle, const bool cue);
+
+ /**
+ * Stops playback and closes the currently open AVI stream.
+ */
+ IOStatus close();
+
+ /**
+ * Seeks the currently open AVI stream to the given frame.
+ */
+ IOStatus cue(const uint16 frameNo);
+
+ /**
+ * Returns the duration of the current video.
+ */
+ uint16 getDuration() const;
+
+ /**
+ * Plays the AVI until an event occurs (e.g. user
+ * presses escape, clicks, etc.).
+ */
+ EventFlags playUntilEvent(const EventFlags flags);
+
+private:
+ typedef Common::HashMap<uint16, AVIStatus> StatusMap;
+
+ SegManager *_segMan;
+ EventManager *_eventMan;
+ Video::AVIDecoder *_decoder;
+
+ /**
+ * Playback status of the player.
+ */
+ AVIStatus _status;
+
+ /**
+ * The plane where the AVI will be drawn.
+ */
+ Plane *_plane;
+
+ /**
+ * The screen item representing the AVI surface,
+ * in 8bpp mode. In 24bpp mode, video is drawn
+ * directly to the screen.
+ */
+ ScreenItem *_screenItem;
+
+ /**
+ * The bitmap used to render video output in
+ * 8bpp mode.
+ */
+ reg_t _bitmap;
+
+ /**
+ * The rectangle where the video will be drawn,
+ * in game script coordinates.
+ */
+ Common::Rect _drawRect;
+
+ /**
+ * The scale buffer for pixel-doubled videos
+ * drawn in 24bpp mode.
+ */
+ void *_scaleBuffer;
+
+ /**
+ * In SCI2.1, whether or not the video should
+ * be pixel doubled for playback.
+ */
+ bool _pixelDouble;
+
+ /**
+ * Performs common initialisation for both
+ * scaled and unscaled videos.
+ */
+ void init();
+
+ /**
+ * Renders video without event input until the
+ * video is complete.
+ */
+ void renderVideo() const;
+
+ /**
+ * Renders a single frame of video.
+ */
+ void renderFrame() const;
+};
+
+#pragma mark -
+#pragma mark VMDPlayer
+
+/**
+ * VMDPlayer is used to play VMD videos.
+ * Used by Phant1, GK2, PQ:SWAT, Shivers, SQ6,
+ * Torin, and Lighthouse.
+ */
+class VMDPlayer {
+public:
+ enum OpenFlags {
+ kOpenFlagNone = 0,
+ kOpenFlagMute = 1
+ };
+
+ enum IOStatus {
+ kIOSuccess = 0,
+ kIOError = 0xFFFF
+ };
+
+ enum PlayFlags {
+ kPlayFlagNone = 0,
+ kPlayFlagDoublePixels = 1,
+ kPlayFlagBlackLines = 4,
+ kPlayFlagBoost = 0x10,
+ kPlayFlagLeaveScreenBlack = 0x20,
+ kPlayFlagLeaveLastFrame = 0x40,
+ kPlayFlagBlackPalette = 0x80,
+ kPlayFlagStretchVertical = 0x100
+ };
+
+ enum EventFlags {
+ kEventFlagNone = 0,
+ kEventFlagEnd = 1,
+ kEventFlagEscapeKey = 2,
+ kEventFlagMouseDown = 4,
+ kEventFlagHotRectangle = 8,
+ kEventFlagToFrame = 0x10,
+ kEventFlagYieldToVM = 0x20,
+ kEventFlagReverse = 0x80
+ };
+
+ enum VMDStatus {
+ kVMDNotOpen = 0,
+ kVMDOpen = 1,
+ kVMDPlaying = 2,
+ kVMDPaused = 3,
+ kVMDStopped = 4,
+ kVMDFinished = 5
+ };
+
+ VMDPlayer(SegManager *segMan, EventManager *eventMan);
+ ~VMDPlayer();
+
+private:
+ SegManager *_segMan;
+ EventManager *_eventMan;
+ Video::AdvancedVMDDecoder *_decoder;
+
+#pragma mark -
+#pragma mark VMDPlayer - Playback
+public:
+ /**
+ * Opens a stream to a VMD resource.
+ */
+ IOStatus open(const Common::String &fileName, const OpenFlags flags);
+
+ /**
+ * Initializes the VMD rendering parameters for the
+ * current VMD. This must be called after `open`.
+ */
+ void init(const int16 x, const int16 y, const PlayFlags flags, const int16 boostPercent, const int16 boostStartColor, const int16 boostEndColor);
+
+ /**
+ * Stops playback and closes the currently open VMD stream.
+ */
+ IOStatus close();
+
+ /**
+ * Gets the playback status of the VMD player.
+ */
+ VMDStatus getStatus() const;
+
+ // NOTE: Was WaitForEvent in SSCI
+ EventFlags kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval);
+
+private:
+ /**
+ * Whether or not a VMD stream has been opened with
+ * `open`.
+ */
+ bool _isOpen;
+
+ /**
+ * Whether or not a VMD player has been initialised
+ * with `init`.
+ */
+ bool _isInitialized;
+
+ /**
+ * For VMDs played with the `kEventFlagToFrame` flag,
+ * the target frame for yielding back to the SCI VM.
+ */
+ int32 _yieldFrame;
+
+ /**
+ * For VMDs played with the `kEventFlagYieldToVM` flag,
+ * the number of frames that should be rendered until
+ * yielding back to the SCI VM.
+ */
+ int32 _yieldInterval;
+
+ /**
+ * For VMDs played with the `kEventFlagYieldToVM` flag,
+ * the last frame when control of the main thread was
+ * yielded back to the SCI VM.
+ */
+ int _lastYieldedFrameNo;
+
+ /**
+ * Plays the VMD until an event occurs (e.g. user
+ * presses escape, clicks, etc.).
+ */
+ EventFlags playUntilEvent(const EventFlags flags);
+
+#pragma mark -
+#pragma mark VMDPlayer - Rendering
+public:
+ /**
+ * Causes the VMD player to ignore all palettes in
+ * the currently playing video.
+ */
+ void ignorePalettes() { _ignorePalettes = true; }
+
+private:
+ /**
+ * The location of the VMD plane, in game script
+ * coordinates.
+ */
+ int16 _x, _y;
+
+ /**
+ * The plane where the VMD will be drawn.
+ */
+ Plane *_plane;
+
+ /**
+ * The screen item representing the VMD surface.
+ */
+ ScreenItem *_screenItem;
+
+ // TODO: planeIsOwned and priority are used in SCI3+ only
+
+ /**
+ * If true, the plane for this VMD was set
+ * externally and is not owned by this VMDPlayer.
+ */
+ bool _planeIsOwned;
+
+ /**
+ * The screen priority of the video.
+ * @see ScreenItem::_priority
+ */
+ int _priority;
+
+ /**
+ * Whether or not the video should be pixel doubled.
+ */
+ bool _doublePixels;
+
+ /**
+ * Whether or not the video should be pixel doubled
+ * vertically only.
+ */
+ bool _stretchVertical;
+
+ /**
+ * Whether or not black lines should be rendered
+ * across the video.
+ */
+ bool _blackLines;
+
+ /**
+ * Whether or not the playback area of the VMD
+ * should be left black at the end of playback.
+ */
+ bool _leaveScreenBlack;
+
+ /**
+ * Whether or not the area of the VMD should be left
+ * displaying the final frame of the video.
+ */
+ bool _leaveLastFrame;
+
+ /**
+ * Whether or not palettes from the VMD should be ignored.
+ */
+ bool _ignorePalettes;
+
+ /**
+ * Renders a frame of video to the output bitmap.
+ */
+ void renderFrame() const;
+
+ /**
+ * Fills the given palette with RGB values from
+ * the VMD palette, applying brightness boost if
+ * it is enabled.
+ */
+ void fillPalette(Palette &palette) const;
+
+#pragma mark -
+#pragma mark VMDPlayer - Blackout
+public:
+ /**
+ * Sets the area of the screen that should be blacked out
+ * during VMD playback.
+ */
+ void setBlackoutArea(const Common::Rect &rect) { _blackoutRect = rect; }
+
+private:
+ /**
+ * The dimensions of the blackout plane.
+ */
+ Common::Rect _blackoutRect;
+
+ /**
+ * An optional plane that will be used to black out
+ * areas of the screen outside of the VMD surface.
+ */
+ Plane *_blackoutPlane;
+
+#pragma mark -
+#pragma mark VMDPlayer - Palette
+public:
+ /**
+ * Restricts use of the system palette by VMD playback to
+ * the given range of palette indexes.
+ */
+ void restrictPalette(const uint8 startColor, const int16 endColor);
+
+private:
+ /**
+ * The first color in the system palette that the VMD
+ * can write to.
+ */
+ uint8 _startColor;
+
+ /**
+ * The last color in the system palette that the VMD
+ * can write to.
+ */
+ uint8 _endColor;
+
+ /**
+ * If true, video frames are rendered after a blank
+ * palette is submitted to the palette manager,
+ * which is then restored after the video pixels
+ * have already been rendered.
+ */
+ bool _blackPalette;
+
+#pragma mark -
+#pragma mark VMDPlayer - Brightness boost
+private:
+ /**
+ * The amount of brightness boost for the video.
+ * Values above 100 increase brightness; values below
+ * 100 reduce it.
+ */
+ int16 _boostPercent;
+
+ /**
+ * The first color in the palette that should be
+ * brightness boosted.
+ */
+ uint8 _boostStartColor;
+
+ /**
+ * The last color in the palette that should be
+ * brightness boosted.
+ */
+ uint8 _boostEndColor;
+
+#pragma mark -
+#pragma mark VMDPlayer - Mouse cursor
+public:
+ /**
+ * Sets whether or not the mouse cursor should be drawn.
+ * This does not have any effect during playback, but can
+ * be used to prevent the mouse cursor from being shown
+ * again after the video has finished.
+ */
+ void setShowCursor(const bool shouldShow) { _showCursor = shouldShow; }
+
+private:
+ /**
+ * Whether or not the mouse cursor should be shown
+ * during playback.
+ */
+ bool _showCursor;
+};
+
+/**
+ * Video32 provides facilities for playing back
+ * video in SCI engine.
+ */
+class Video32 {
+public:
+ Video32(SegManager *segMan, EventManager *eventMan) :
+ _SEQPlayer(segMan),
+ _AVIPlayer(segMan, eventMan),
+ _VMDPlayer(segMan, eventMan),
+ _robotPlayer(segMan) {}
+
+ SEQPlayer &getSEQPlayer() { return _SEQPlayer; }
+ AVIPlayer &getAVIPlayer() { return _AVIPlayer; }
+ VMDPlayer &getVMDPlayer() { return _VMDPlayer; }
+ RobotDecoder &getRobotPlayer() { return _robotPlayer; }
+
+private:
+ SEQPlayer _SEQPlayer;
+ AVIPlayer _AVIPlayer;
+ VMDPlayer _VMDPlayer;
+ RobotDecoder _robotPlayer;
+};
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp
index 4116eda876..0c09fcbb30 100644
--- a/engines/sci/graphics/view.cpp
+++ b/engines/sci/graphics/view.cpp
@@ -25,6 +25,7 @@
#include "sci/engine/state.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/palette.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/view.h"
@@ -350,18 +351,6 @@ void GfxView::initData(GuiResourceId resourceId) {
celData += celSize;
}
}
-#ifdef ENABLE_SCI32
- // adjust width/height returned to scripts
- if (_sci2ScaleRes != SCI_VIEW_NATIVERES_NONE) {
- for (loopNo = 0; loopNo < _loopCount; loopNo++)
- for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++)
- _screen->adjustBackUpscaledCoordinates(_loop[loopNo].cel[celNo].scriptWidth, _loop[loopNo].cel[celNo].scriptHeight, _sci2ScaleRes);
- } else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) {
- for (loopNo = 0; loopNo < _loopCount; loopNo++)
- for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++)
- _coordAdjuster->fromDisplayToScript(_loop[loopNo].cel[celNo].scriptHeight, _loop[loopNo].cel[celNo].scriptWidth);
- }
-#endif
break;
default:
@@ -842,12 +831,11 @@ void GfxView::draw(const Common::Rect &rect, const Common::Rect &clipRect, const
const int y2 = clipRectTranslated.top + y;
if (!upscaledHires) {
if (priority >= _screen->getPriority(x2, y2)) {
- if (!_palette->isRemapped(palette->mapping[color])) {
- _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0);
- } else {
- byte remappedColor = _palette->remapColor(palette->mapping[color], _screen->getVisual(x2, y2));
- _screen->putPixel(x2, y2, drawMask, remappedColor, priority, 0);
- }
+ byte outputColor = palette->mapping[color];
+ // SCI16 remapping (QFG4 demo)
+ if (g_sci->_gfxRemap16 && g_sci->_gfxRemap16->isRemapped(outputColor))
+ outputColor = g_sci->_gfxRemap16->remapColor(outputColor, _screen->getVisual(x2, y2));
+ _screen->putPixel(x2, y2, drawMask, outputColor, priority, 0);
}
} else {
// UpscaledHires means view is hires and is supposed to
@@ -957,12 +945,11 @@ void GfxView::drawScaled(const Common::Rect &rect, const Common::Rect &clipRect,
const int x2 = clipRectTranslated.left + x;
const int y2 = clipRectTranslated.top + y;
if (color != clearKey && priority >= _screen->getPriority(x2, y2)) {
- if (!_palette->isRemapped(palette->mapping[color])) {
- _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0);
- } else {
- byte remappedColor = _palette->remapColor(palette->mapping[color], _screen->getVisual(x2, y2));
- _screen->putPixel(x2, y2, drawMask, remappedColor, priority, 0);
- }
+ byte outputColor = palette->mapping[color];
+ // SCI16 remapping (QFG4 demo)
+ if (g_sci->_gfxRemap16 && g_sci->_gfxRemap16->isRemapped(outputColor))
+ outputColor = g_sci->_gfxRemap16->remapColor(outputColor, _screen->getVisual(x2, y2));
+ _screen->putPixel(x2, y2, drawMask, outputColor, priority, 0);
}
}
}
@@ -976,13 +963,4 @@ void GfxView::adjustBackUpscaledCoordinates(int16 &y, int16 &x) {
_screen->adjustBackUpscaledCoordinates(y, x, _sci2ScaleRes);
}
-byte GfxView::getColorAtCoordinate(int16 loopNo, int16 celNo, int16 x, int16 y) {
- const CelInfo *celInfo = getCelInfo(loopNo, celNo);
- const byte *bitmap = getBitmap(loopNo, celNo);
- const int16 celWidth = celInfo->width;
-
- bitmap += (celWidth * y);
- return bitmap[x];
-}
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h
index d8803db208..5e422468b5 100644
--- a/engines/sci/graphics/view.h
+++ b/engines/sci/graphics/view.h
@@ -55,6 +55,7 @@ struct LoopInfo {
class GfxScreen;
class GfxPalette;
+class Resource;
/**
* View class, handles loading of view resources and drawing contained cels to screen
@@ -85,15 +86,13 @@ public:
void adjustToUpscaledCoordinates(int16 &y, int16 &x);
void adjustBackUpscaledCoordinates(int16 &y, int16 &x);
- byte getColorAtCoordinate(int16 loopNo, int16 celNo, int16 x, int16 y);
-
private:
void initData(GuiResourceId resourceId);
void unpackCel(int16 loopNo, int16 celNo, byte *outPtr, uint32 pixelCount);
void unditherBitmap(byte *bitmap, int16 width, int16 height, byte clearKey);
ResourceManager *_resMan;
- GfxCoordAdjuster *_coordAdjuster;
+ GfxCoordAdjuster16 *_coordAdjuster;
GfxScreen *_screen;
GfxPalette *_palette;
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index 963b65742d..eb2c6a148b 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -51,12 +51,12 @@ MODULE_OBJS := \
graphics/fontsjis.o \
graphics/maciconbar.o \
graphics/menu.o \
- graphics/paint.o \
graphics/paint16.o \
graphics/palette.o \
graphics/picture.o \
graphics/portrait.o \
graphics/ports.o \
+ graphics/remap.o \
graphics/screen.o \
graphics/text16.o \
graphics/transitions.o \
@@ -68,6 +68,7 @@ MODULE_OBJS := \
sound/midiparser_sci.o \
sound/music.o \
sound/soundcmd.o \
+ sound/sync.o \
sound/drivers/adlib.o \
sound/drivers/amigamac.o \
sound/drivers/cms.o \
@@ -87,8 +88,14 @@ MODULE_OBJS += \
graphics/paint32.o \
graphics/plane32.o \
graphics/palette32.o \
+ graphics/remap32.o \
graphics/screen_item32.o \
graphics/text32.o \
+ graphics/transitions32.o \
+ graphics/video32.o \
+ graphics/cursor32.o \
+ sound/audio32.o \
+ sound/decoders/sol.o \
video/robot_decoder.o
endif
diff --git a/engines/sci/parser/vocabulary.cpp b/engines/sci/parser/vocabulary.cpp
index 828a57abeb..a0f958167d 100644
--- a/engines/sci/parser/vocabulary.cpp
+++ b/engines/sci/parser/vocabulary.cpp
@@ -121,7 +121,7 @@ bool Vocabulary::loadParserWords() {
}
}
- unsigned int seeker;
+ uint32 seeker;
if (resourceType == kVocabularySCI1)
seeker = 255 * 2; // vocab.900 starts with 255 16-bit pointers which we don't use
else
@@ -202,7 +202,7 @@ bool Vocabulary::loadSuffixes() {
if (!resource)
return false; // No vocabulary found
- unsigned int seeker = 1;
+ uint32 seeker = 1;
while ((seeker < resource->size - 1) && (resource->data[seeker + 1] != 0xff)) {
suffix_t suffix;
@@ -288,7 +288,7 @@ bool Vocabulary::loadAltInputs() {
AltInput t;
t._input = data;
- unsigned int l = strlen(data);
+ uint32 l = strlen(data);
t._inputLength = l;
data += l + 1;
@@ -325,15 +325,15 @@ bool Vocabulary::checkAltInput(Common::String& text, uint16& cursorPos) {
return false;
bool ret = false;
- unsigned int loopCount = 0;
+ uint32 loopCount = 0;
bool changed;
do {
changed = false;
const char* t = text.c_str();
- unsigned int tlen = text.size();
+ uint32 tlen = text.size();
- for (unsigned int p = 0; p < tlen && !changed; ++p) {
+ for (uint32 p = 0; p < tlen && !changed; ++p) {
unsigned char s = t[p];
if (s >= _altInputs.size() || _altInputs[s].empty())
continue;
@@ -351,7 +351,7 @@ bool Vocabulary::checkAltInput(Common::String& text, uint16& cursorPos) {
cursorPos = p + strlen(i->_replacement);
}
- for (unsigned int j = 0; j < i->_inputLength; ++j)
+ for (uint32 j = 0; j < i->_inputLength; ++j)
text.deleteChar(p);
const char *r = i->_replacement;
while (*r)
@@ -365,7 +365,7 @@ bool Vocabulary::checkAltInput(Common::String& text, uint16& cursorPos) {
}
}
}
- } while (changed && loopCount < 10);
+ } while (changed && loopCount++ < 10);
return ret;
}
diff --git a/engines/sci/parser/vocabulary.h b/engines/sci/parser/vocabulary.h
index f4adee6e55..59558ce18a 100644
--- a/engines/sci/parser/vocabulary.h
+++ b/engines/sci/parser/vocabulary.h
@@ -156,7 +156,7 @@ typedef Common::Array<synonym_t> SynonymList;
struct AltInput {
const char *_input;
const char *_replacement;
- unsigned int _inputLength;
+ uint32 _inputLength;
bool _prefix;
};
diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index 2a4cd95b91..364ea2a0ac 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -26,6 +26,9 @@
#include "common/fs.h"
#include "common/macresman.h"
#include "common/textconsole.h"
+#ifdef ENABLE_SCI32
+#include "common/memstream.h"
+#endif
#include "sci/resource.h"
#include "sci/resource_intern.h"
@@ -156,7 +159,7 @@ static const ResourceType s_resTypeMapSci21[] = {
kResourceTypeView, kResourceTypePic, kResourceTypeScript, kResourceTypeAnimation, // 0x00-0x03
kResourceTypeSound, kResourceTypeEtc, kResourceTypeVocab, kResourceTypeFont, // 0x04-0x07
kResourceTypeCursor, kResourceTypePatch, kResourceTypeBitmap, kResourceTypePalette, // 0x08-0x0B
- kResourceTypeInvalid, kResourceTypeAudio, kResourceTypeSync, kResourceTypeMessage, // 0x0C-0x0F
+ kResourceTypeAudio, kResourceTypeAudio, kResourceTypeSync, kResourceTypeMessage, // 0x0C-0x0F
kResourceTypeMap, kResourceTypeHeap, kResourceTypeChunk, kResourceTypeAudio36, // 0x10-0x13
kResourceTypeSync36, kResourceTypeTranslation, kResourceTypeRobot, kResourceTypeVMD, // 0x14-0x17
kResourceTypeDuck, kResourceTypeClut, kResourceTypeTGA, kResourceTypeZZZ // 0x18-0x1B
@@ -221,6 +224,12 @@ void Resource::writeToStream(Common::WriteStream *stream) const {
stream->write(data, size);
}
+#ifdef ENABLE_SCI32
+Common::SeekableReadStream *Resource::makeStream() const {
+ return new Common::MemoryReadStream(data, size, DisposeAfterUse::NO);
+}
+#endif
+
uint32 Resource::getAudioCompressionType() const {
return _source->getAudioCompressionType();
}
@@ -229,7 +238,6 @@ uint32 AudioVolumeResourceSource::getAudioCompressionType() const {
return _audioCompressionType;
}
-
ResourceSource::ResourceSource(ResSourceType type, const Common::String &name, int volNum, const Common::FSNode *resFile)
: _sourceType(type), _name(name), _volumeNumber(volNum), _resourceFile(resFile) {
_scanned = false;
@@ -293,32 +301,30 @@ ResourceSource *ResourceManager::findVolume(ResourceSource *map, int volume_nr)
// Resource manager constructors and operations
bool Resource::loadPatch(Common::SeekableReadStream *file) {
- Resource *res = this;
-
- // We assume that the resource type matches res->type
+ // We assume that the resource type matches `type`
// We also assume that the current file position is right at the actual data (behind resourceid/headersize byte)
- res->data = new byte[res->size];
+ data = new byte[size];
- if (res->_headerSize > 0)
- res->_header = new byte[res->_headerSize];
+ if (_headerSize > 0)
+ _header = new byte[_headerSize];
- if ((res->data == NULL) || ((res->_headerSize > 0) && (res->_header == NULL))) {
- error("Can't allocate %d bytes needed for loading %s", res->size + res->_headerSize, res->_id.toString().c_str());
+ if (data == nullptr || (_headerSize > 0 && _header == nullptr)) {
+ error("Can't allocate %d bytes needed for loading %s", size + _headerSize, _id.toString().c_str());
}
- unsigned int really_read;
- if (res->_headerSize > 0) {
- really_read = file->read(res->_header, res->_headerSize);
- if (really_read != res->_headerSize)
- error("Read %d bytes from %s but expected %d", really_read, res->_id.toString().c_str(), res->_headerSize);
+ uint32 really_read;
+ if (_headerSize > 0) {
+ really_read = file->read(_header, _headerSize);
+ if (really_read != _headerSize)
+ error("Read %d bytes from %s but expected %d", really_read, _id.toString().c_str(), _headerSize);
}
- really_read = file->read(res->data, res->size);
- if (really_read != res->size)
- error("Read %d bytes from %s but expected %d", really_read, res->_id.toString().c_str(), res->size);
+ really_read = file->read(data, size);
+ if (really_read != size)
+ error("Read %d bytes from %s but expected %d", really_read, _id.toString().c_str(), size);
- res->_status = kResStatusAllocated;
+ _status = kResStatusAllocated;
return true;
}
@@ -565,12 +571,14 @@ Resource *ResourceManager::testResource(ResourceId id) {
}
int ResourceManager::addAppropriateSources() {
- Common::ArchiveMemberList files;
-
+#ifdef ENABLE_SCI32
+ _multiDiscAudio = false;
+#endif
if (Common::File::exists("resource.map")) {
// SCI0-SCI2 file naming scheme
ResourceSource *map = addExternalMap("resource.map");
+ Common::ArchiveMemberList files;
SearchMan.listMatchingMembers(files, "resource.0??");
for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
@@ -587,20 +595,20 @@ int ResourceManager::addAppropriateSources() {
#endif
} else if (Common::MacResManager::exists("Data1")) {
// Mac SCI1.1+ file naming scheme
- SearchMan.listMatchingMembers(files, "Data?*");
+ Common::StringArray files;
+ Common::MacResManager::listFiles(files, "Data?");
- for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
- Common::String filename = (*x)->getName();
- addSource(new MacResourceForkResourceSource(filename, atoi(filename.c_str() + 4)));
+ for (Common::StringArray::const_iterator x = files.begin(); x != files.end(); ++x) {
+ addSource(new MacResourceForkResourceSource(*x, atoi(x->c_str() + 4)));
}
#ifdef ENABLE_SCI32
// There can also be a "Patches" resource fork with patches
- if (Common::File::exists("Patches"))
+ if (Common::MacResManager::exists("Patches"))
addSource(new MacResourceForkResourceSource("Patches", 100));
} else {
// SCI2.1-SCI3 file naming scheme
- Common::ArchiveMemberList mapFiles;
+ Common::ArchiveMemberList mapFiles, files;
SearchMan.listMatchingMembers(mapFiles, "resmap.0??");
SearchMan.listMatchingMembers(files, "ressci.0??");
@@ -608,6 +616,10 @@ int ResourceManager::addAppropriateSources() {
if (mapFiles.empty() || files.empty() || mapFiles.size() != files.size())
return 0;
+ if (Common::File::exists("resaud.001")) {
+ _multiDiscAudio = true;
+ }
+
for (Common::ArchiveMemberList::const_iterator mapIterator = mapFiles.begin(); mapIterator != mapFiles.end(); ++mapIterator) {
Common::String mapName = (*mapIterator)->getName();
int mapNumber = atoi(strrchr(mapName.c_str(), '.') + 1);
@@ -652,6 +664,7 @@ int ResourceManager::addAppropriateSourcesForDetection(const Common::FSList &fsl
#ifdef ENABLE_SCI32
ResourceSource *sci21PatchMap = 0;
const Common::FSNode *sci21PatchRes = 0;
+ _multiDiscAudio = false;
#endif
// First, find resource.map
@@ -852,6 +865,13 @@ void ResourceManager::addResourcesFromChunk(uint16 id) {
scanNewSources();
}
+void ResourceManager::findDisc(const int16 discNo) {
+ // Since all resources are expected to be copied from the original discs
+ // into a single game directory, this call just records the number of the CD
+ // that the game has requested
+ _currentDiscNo = discNo;
+}
+
#endif
void ResourceManager::freeResourceSources() {
@@ -871,7 +891,9 @@ void ResourceManager::init() {
_LRU.clear();
_resMap.clear();
_audioMapSCI1 = NULL;
-
+#ifdef ENABLE_SCI32
+ _currentDiscNo = 1;
+#endif
// FIXME: put this in an Init() function, so that we can error out if detection fails completely
_mapVersion = detectMapVersion();
@@ -924,7 +946,7 @@ void ResourceManager::init() {
// cache, leading to constant decompression of picture resources
// and making the renderer very slow.
if (getSciVersion() >= SCI_VERSION_2) {
- _maxMemoryLRU = 2048 * 1024; // 2MiB
+ _maxMemoryLRU = 4096 * 1024; // 4MiB
}
switch (_viewType) {
@@ -1044,7 +1066,13 @@ Resource *ResourceManager::findResource(ResourceId id, bool lock) {
if (retval->_status == kResStatusNoMalloc)
loadResource(retval);
else if (retval->_status == kResStatusEnqueued)
+ // The resource is removed from its current position
+ // in the LRU list because it has been requested
+ // again. Below, it will either be locked, or it
+ // will be added back to the LRU list at the 'most
+ // recent' position.
removeFromLRU(retval);
+
// Unless an error occurred, the resource is now either
// locked or allocated, but never queued or freed.
@@ -1353,6 +1381,8 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource
Common::File *file = new Common::File();
if (!file->open(source->getLocationName())) {
warning("ResourceManager::processPatch(): failed to open %s", source->getLocationName().c_str());
+ delete source;
+ delete file;
return;
}
fileStream = file;
@@ -1361,16 +1391,32 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource
int fsize = fileStream->size();
if (fsize < 3) {
debug("Patching %s failed - file too small", source->getLocationName().c_str());
+ delete source;
+ delete fileStream;
return;
}
byte patchType = convertResType(fileStream->readByte());
- byte patchDataOffset = fileStream->readByte();
+ int32 patchDataOffset;
+ if (_volVersion < kResVersionSci2) {
+ patchDataOffset = fileStream->readByte();
+ } else if (patchType == kResourceTypeView) {
+ fileStream->seek(3, SEEK_SET);
+ patchDataOffset = fileStream->readByte() + 22 + 2;
+ } else if (patchType == kResourceTypePic) {
+ patchDataOffset = 2;
+ } else if (patchType == kResourceTypePalette) {
+ fileStream->seek(3, SEEK_SET);
+ patchDataOffset = fileStream->readByte() + 2;
+ } else {
+ patchDataOffset = 0;
+ }
delete fileStream;
if (patchType != checkForType) {
debug("Patching %s failed - resource type mismatch", source->getLocationName().c_str());
+ delete source;
return;
}
@@ -1395,6 +1441,7 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource
if (patchDataOffset + 2 >= fsize) {
debug("Patching %s failed - patch starting at offset %d can't be in file of size %d",
source->getLocationName().c_str(), patchDataOffset + 2, fsize);
+ delete source;
return;
}
@@ -1442,17 +1489,28 @@ void ResourceManager::readResourcePatchesBase36() {
files.clear();
// audio36 resources start with a @, A, or B
- // sync36 resources start with a #
+ // sync36 resources start with a #, S, or T
if (i == kResourceTypeAudio36) {
SearchMan.listMatchingMembers(files, "@???????.???");
SearchMan.listMatchingMembers(files, "A???????.???");
SearchMan.listMatchingMembers(files, "B???????.???");
- } else
+ } else {
SearchMan.listMatchingMembers(files, "#???????.???");
+#ifdef ENABLE_SCI32
+ SearchMan.listMatchingMembers(files, "S???????.???");
+ SearchMan.listMatchingMembers(files, "T???????.???");
+#endif
+ }
for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
name = (*x)->getName();
+ // The S/T prefixes often conflict with non-patch files and generate
+ // spurious warnings about invalid patches
+ if (name.hasSuffix(".DLL") || name.hasSuffix(".EXE") || name.hasSuffix(".TXT")) {
+ continue;
+ }
+
ResourceId resource36 = convertPatchNameBase36((ResourceType)i, name);
/*
@@ -1714,11 +1772,42 @@ int ResourceManager::readResourceMapSCI1(ResourceSource *map) {
// if we use the first entries in the resource file, half of the
// game will be English and umlauts will also be missing :P
if (resource->_source->getSourceType() == kSourceVolume) {
+ // Maps are read during the scanning process (below), so
+ // need to be treated as unallocated in order for the new
+ // data from this volume to be picked up and used
+ if (resId.getType() == kResourceTypeMap) {
+ resource->_status = kResStatusNoMalloc;
+ }
resource->_source = source;
resource->_fileOffset = fileOffset;
resource->size = 0;
}
}
+
+#ifdef ENABLE_SCI32
+ // Different CDs may have different audio maps on each disc. The
+ // ResourceManager does not know how to deal with this; it expects
+ // each resource ID to be unique across an entire game. To work
+ // around this problem, all audio maps from this disc must be
+ // processed immediately, since they will be replaced by the audio
+ // map from the next disc on the next call to readResourceMapSCI1
+ if (_multiDiscAudio && resId.getType() == kResourceTypeMap) {
+ IntMapResourceSource *audioMap = static_cast<IntMapResourceSource *>(addSource(new IntMapResourceSource("MAP", mapVolumeNr, resId.getNumber())));
+ Common::String volumeName;
+ if (resId.getNumber() == 65535) {
+ volumeName = Common::String::format("RESSFX.%03d", mapVolumeNr);
+ } else {
+ volumeName = Common::String::format("RESAUD.%03d", mapVolumeNr);
+ }
+
+ ResourceSource *audioVolume = addSource(new AudioVolumeResourceSource(this, volumeName, audioMap, mapVolumeNr));
+ if (!audioMap->_scanned) {
+ audioVolume->_scanned = true;
+ audioMap->_scanned = true;
+ audioMap->scanSource(this);
+ }
+ }
+#endif
}
}
@@ -2381,38 +2470,6 @@ void ResourceManager::detectSciVersion() {
}
}
-bool ResourceManager::detectHires() {
- // SCI 1.1 and prior is never hires
- if (getSciVersion() <= SCI_VERSION_1_1)
- return false;
-
-#ifdef ENABLE_SCI32
- for (int i = 0; i < 32768; i++) {
- Resource *res = findResource(ResourceId(kResourceTypePic, i), 0);
-
- if (res) {
- if (READ_SCI11ENDIAN_UINT16(res->data) == 0x0e) {
- // SCI32 picture
- uint16 width = READ_SCI11ENDIAN_UINT16(res->data + 10);
- uint16 height = READ_SCI11ENDIAN_UINT16(res->data + 12);
- // Surely lowres (e.g. QFG4CD)
- if ((width == 320) && ((height == 190) || (height == 200)))
- return false;
- // Surely hires
- if ((width >= 600) || (height >= 400))
- return true;
- }
- }
- }
-
- // We haven't been able to find hires content
-
- return false;
-#else
- error("no sci32 support");
-#endif
-}
-
bool ResourceManager::detectFontExtended() {
Resource *res = findResource(ResourceId(kResourceTypeFont, 0), 0);
diff --git a/engines/sci/resource.h b/engines/sci/resource.h
index ef474d97c2..e601a1c434 100644
--- a/engines/sci/resource.h
+++ b/engines/sci/resource.h
@@ -84,7 +84,10 @@ enum ResourceType {
kResourceTypePatch,
kResourceTypeBitmap,
kResourceTypePalette,
- kResourceTypeCdAudio,
+ kResourceTypeCdAudio = 12,
+#ifdef ENABLE_SCI32
+ kResourceTypeWave = 12,
+#endif
kResourceTypeAudio,
kResourceTypeSync,
kResourceTypeMessage,
@@ -171,14 +174,10 @@ public:
}
Common::String toString() const {
- char buf[32];
-
- snprintf(buf, 32, "%s.%d", getResourceTypeName(_type), _number);
- Common::String retStr = buf;
+ Common::String retStr = Common::String::format("%s.%d", getResourceTypeName(_type), _number);
if (_tuple != 0) {
- snprintf(buf, 32, "(%d, %d, %d, %d)", _tuple >> 24, (_tuple >> 16) & 0xff, (_tuple >> 8) & 0xff, _tuple & 0xff);
- retStr += buf;
+ retStr += Common::String::format("(%d, %d, %d, %d)", _tuple >> 24, (_tuple >> 16) & 0xff, (_tuple >> 8) & 0xff, _tuple & 0xff);
}
return retStr;
@@ -212,6 +211,10 @@ public:
return (_type == other._type) && (_number == other._number) && (_tuple == other._tuple);
}
+ bool operator!=(const ResourceId &other) const {
+ return !operator==(other);
+ }
+
bool operator<(const ResourceId &other) const {
return (_type < other._type) || ((_type == other._type) && (_number < other._number))
|| ((_type == other._type) && (_number == other._number) && (_tuple < other._tuple));
@@ -259,6 +262,10 @@ public:
*/
void writeToStream(Common::WriteStream *stream) const;
+#ifdef ENABLE_SCI32
+ Common::SeekableReadStream *makeStream() const;
+#endif
+
const Common::String &getResourceLocation() const;
// FIXME: This audio specific method is a hack. After all, why should a
@@ -285,6 +292,7 @@ protected:
typedef Common::HashMap<ResourceId, Resource *, ResourceIdHash> ResourceMap;
+class IntMapResourceSource;
class ResourceManager {
// FIXME: These 'friend' declarations are meant to be a temporary hack to
// ease transition to the ResourceSource class system.
@@ -386,9 +394,32 @@ public:
* resource manager.
*/
void addResourcesFromChunk(uint16 id);
+
+ /**
+ * Updates the currently active disc number.
+ */
+ void findDisc(const int16 discNo);
+
+ /**
+ * Gets the currently active disc number.
+ */
+ int16 getCurrentDiscNo() const { return _currentDiscNo; }
+
+private:
+ /**
+ * The currently active disc number.
+ */
+ int16 _currentDiscNo;
+
+ /**
+ * If true, the game has multiple audio volumes that contain different
+ * audio files for each disc.
+ */
+ bool _multiDiscAudio;
+
+public:
#endif
- bool detectHires();
// Detects, if standard font of current game includes extended characters (>0x80)
bool detectFontExtended();
// Detects, if SCI1.1 game uses palette merging
@@ -509,7 +540,7 @@ protected:
* @param map The map
* @return 0 on success, an SCI_ERROR_* code otherwise
*/
- int readAudioMapSCI11(ResourceSource *map);
+ int readAudioMapSCI11(IntMapResourceSource *map);
/**
* Reads SCI1 audio map files.
diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp
index 6869e6379e..7757445bc1 100644
--- a/engines/sci/resource_audio.cpp
+++ b/engines/sci/resource_audio.cpp
@@ -25,7 +25,7 @@
#include "common/archive.h"
#include "common/file.h"
#include "common/textconsole.h"
-
+#include "common/memstream.h"
#include "sci/resource.h"
#include "sci/resource_intern.h"
#include "sci/util.h"
@@ -139,7 +139,7 @@ bool Resource::loadFromAudioVolumeSCI1(Common::SeekableReadStream *file) {
error("Can't allocate %d bytes needed for loading %s", size, _id.toString().c_str());
}
- unsigned int really_read = file->read(data, size);
+ uint32 really_read = file->read(data, size);
if (really_read != size)
warning("Read %d bytes from %s but expected %d", really_read, _id.toString().c_str(), size);
@@ -277,7 +277,7 @@ void ResourceManager::removeAudioResource(ResourceId resId) {
// w syncSize (iff seq has bit 7 set)
// w syncAscSize (iff seq has bit 6 set)
-int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
+int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) {
#ifndef ENABLE_SCI32
// SCI32 support is not built in. Check if this is a SCI32 game
// and if it is abort here.
@@ -286,17 +286,19 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
#endif
uint32 offset = 0;
- Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->_volumeNumber), false);
+ Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->_mapNumber), false);
if (!mapRes) {
- warning("Failed to open %i.MAP", map->_volumeNumber);
+ warning("Failed to open %i.MAP", map->_mapNumber);
return SCI_ERROR_RESMAP_NOT_FOUND;
}
- ResourceSource *src = findVolume(map, 0);
+ ResourceSource *src = findVolume(map, map->_volumeNumber);
- if (!src)
+ if (!src) {
+ warning("Failed to find volume for %i.MAP", map->_mapNumber);
return SCI_ERROR_NO_RESOURCE_FILES_FOUND;
+ }
byte *ptr = mapRes->data;
@@ -309,7 +311,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
break;
}
- if (map->_volumeNumber == 65535) {
+ if (map->_mapNumber == 65535) {
while (ptr < mapRes->data + mapRes->size) {
uint16 n = READ_LE_UINT16(ptr);
ptr += 2;
@@ -327,7 +329,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
addResource(ResourceId(kResourceTypeAudio, n), src, offset);
}
- } else if (map->_volumeNumber == 0 && entrySize == 10 && ptr[3] == 0) {
+ } else if (map->_mapNumber == 0 && entrySize == 10 && ptr[3] == 0) {
// QFG3 demo format
// ptr[3] would be 'seq' in the normal format and cannot possibly be 0
while (ptr < mapRes->data + mapRes->size) {
@@ -344,7 +346,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
addResource(ResourceId(kResourceTypeAudio, n), src, offset, size);
}
- } else if (map->_volumeNumber == 0 && entrySize == 8 && READ_LE_UINT16(ptr + 2) == 0xffff) {
+ } else if (map->_mapNumber == 0 && entrySize == 8 && READ_LE_UINT16(ptr + 2) == 0xffff) {
// LB2 Floppy/Mother Goose SCI1.1 format
Common::SeekableReadStream *stream = getVolumeFile(src);
@@ -400,22 +402,24 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
// FIXME: The sync36 resource seems to be two bytes too big in KQ6CD
// (bytes taken from the RAVE resource right after it)
if (syncSize > 0)
- addResource(ResourceId(kResourceTypeSync36, map->_volumeNumber, n & 0xffffff3f), src, offset, syncSize);
+ addResource(ResourceId(kResourceTypeSync36, map->_mapNumber, n & 0xffffff3f), src, offset, syncSize);
}
- if (n & 0x40) {
+ // Checking for this 0x40 flag breaks at least Laura Bow 2 CD 1.1
+ // map 448
+ if (g_sci->getGameId() == GID_KQ6 && (n & 0x40)) {
// This seems to define the size of raw lipsync data (at least
// in KQ6 CD Windows).
int kq6HiresSyncSize = READ_LE_UINT16(ptr);
ptr += 2;
if (kq6HiresSyncSize > 0) {
- addResource(ResourceId(kResourceTypeRave, map->_volumeNumber, n & 0xffffff3f), src, offset + syncSize, kq6HiresSyncSize);
+ addResource(ResourceId(kResourceTypeRave, map->_mapNumber, n & 0xffffff3f), src, offset + syncSize, kq6HiresSyncSize);
syncSize += kq6HiresSyncSize;
}
}
- addResource(ResourceId(kResourceTypeAudio36, map->_volumeNumber, n & 0xffffff3f), src, offset + syncSize);
+ addResource(ResourceId(kResourceTypeAudio36, map->_mapNumber, n & 0xffffff3f), src, offset + syncSize);
}
}
@@ -509,10 +513,8 @@ void ResourceManager::setAudioLanguage(int language) {
_audioMapSCI1 = NULL;
}
- char filename[9];
- snprintf(filename, 9, "AUDIO%03d", language);
-
- Common::String fullname = Common::String(filename) + ".MAP";
+ Common::String filename = Common::String::format("AUDIO%03d", language);
+ Common::String fullname = filename + ".MAP";
if (!Common::File::exists(fullname)) {
warning("No audio map found for language %i", language);
return;
@@ -522,7 +524,7 @@ void ResourceManager::setAudioLanguage(int language) {
// Search for audio volumes for this language and add them to the source list
Common::ArchiveMemberList files;
- SearchMan.listMatchingMembers(files, Common::String(filename) + ".0??");
+ SearchMan.listMatchingMembers(files, filename + ".0??");
for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
const Common::String name = (*x)->getName();
const char *dot = strrchr(name.c_str(), '.');
@@ -688,6 +690,12 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers
channel->data = resource->data + dataOffset;
channel->size = READ_LE_UINT16(data + 4);
+
+ if (dataOffset + channel->size > resource->size) {
+ warning("Invalid size inside sound resource %d: track %d, channel %d", resourceNr, trackNr, channelNr);
+ channel->size = resource->size - dataOffset;
+ }
+
channel->curPos = 0;
channel->number = *channel->data;
@@ -863,6 +871,7 @@ void WaveResourceSource::loadResource(ResourceManager *resMan, Resource *res) {
if (!fileStream)
return;
+ assert(fileStream->size() == -1 || res->_fileOffset < fileStream->size());
fileStream->seek(res->_fileOffset, SEEK_SET);
res->loadFromWaveFile(fileStream);
if (_resourceFile)
@@ -916,6 +925,7 @@ void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource *
break;
}
} else {
+ assert(fileStream->size() == -1 || res->_fileOffset < fileStream->size());
// original file, directly seek to given offset and get SCI1/SCI1.1 audio resource
fileStream->seek(res->_fileOffset, SEEK_SET);
}
@@ -929,13 +939,21 @@ void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource *
}
bool ResourceManager::addAudioSources() {
+#ifdef ENABLE_SCI32
+ // Multi-disc audio is added during addAppropriateSources for those titles
+ // that require it
+ if (_multiDiscAudio) {
+ return true;
+ }
+#endif
+
Common::List<ResourceId> resources = listResources(kResourceTypeMap);
Common::List<ResourceId>::iterator itr;
for (itr = resources.begin(); itr != resources.end(); ++itr) {
- ResourceSource *src = addSource(new IntMapResourceSource("MAP", itr->getNumber()));
+ ResourceSource *src = addSource(new IntMapResourceSource("MAP", 0, itr->getNumber()));
- if ((itr->getNumber() == 65535) && Common::File::exists("RESOURCE.SFX"))
+ if (itr->getNumber() == 65535 && Common::File::exists("RESOURCE.SFX"))
addSource(new AudioVolumeResourceSource(this, "RESOURCE.SFX", src, 0));
else if (Common::File::exists("RESOURCE.AUD"))
addSource(new AudioVolumeResourceSource(this, "RESOURCE.AUD", src, 0));
@@ -947,6 +965,10 @@ bool ResourceManager::addAudioSources() {
}
void ResourceManager::changeAudioDirectory(Common::String path) {
+ // TODO: This implementation is broken.
+ return;
+
+#if 0
// Remove all of the audio map resource sources, as well as the audio resource sources
for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end();) {
ResourceSource *source = *it;
@@ -983,12 +1005,13 @@ void ResourceManager::changeAudioDirectory(Common::String path) {
if ((it->getNumber() == 65535))
continue;
- ResourceSource *src = addSource(new IntMapResourceSource(mapName, it->getNumber()));
+ ResourceSource *src = addSource(new IntMapResourceSource(mapName, 0, it->getNumber()));
addSource(new AudioVolumeResourceSource(this, audioResourceName, src, 0));
}
// Rescan the newly added resources
scanNewSources();
+#endif
}
} // End of namespace Sci
diff --git a/engines/sci/resource_intern.h b/engines/sci/resource_intern.h
index 461d684005..fe4b0a97f4 100644
--- a/engines/sci/resource_intern.h
+++ b/engines/sci/resource_intern.h
@@ -134,8 +134,9 @@ public:
class IntMapResourceSource : public ResourceSource {
public:
- IntMapResourceSource(const Common::String &name, int volNum)
- : ResourceSource(kSourceIntMap, name, volNum) {
+ uint16 _mapNumber;
+ IntMapResourceSource(const Common::String &name, int volNum, int mapNum)
+ : ResourceSource(kSourceIntMap, name, volNum), _mapNumber(mapNum) {
}
virtual void scanSource(ResourceManager *resMan);
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 6d36fabde9..d725e36a5a 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -43,12 +43,12 @@
#include "sci/sound/audio.h"
#include "sci/sound/music.h"
+#include "sci/sound/sync.h"
#include "sci/sound/soundcmd.h"
#include "sci/graphics/animate.h"
#include "sci/graphics/cache.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/controls16.h"
-#include "sci/graphics/controls32.h"
#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/cursor.h"
#include "sci/graphics/maciconbar.h"
@@ -58,24 +58,27 @@
#include "sci/graphics/picture.h"
#include "sci/graphics/ports.h"
#include "sci/graphics/palette.h"
+#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/text16.h"
#include "sci/graphics/transitions.h"
#ifdef ENABLE_SCI32
+#include "sci/graphics/controls32.h"
+#include "sci/graphics/cursor32.h"
+#include "sci/graphics/frameout.h"
#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/text32.h"
-#include "sci/graphics/frameout.h"
-#include "sci/video/robot_decoder.h"
+#include "sci/graphics/transitions32.h"
+#include "sci/graphics/video32.h"
+#include "sci/sound/audio32.h"
#endif
namespace Sci {
SciEngine *g_sci = 0;
-
-class GfxDriver;
-
SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gameId)
: Engine(syst), _gameDescription(desc), _gameId(gameId), _rng("sci") {
@@ -85,6 +88,12 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam
_gfxMacIconBar = 0;
_audio = 0;
+ _sync = nullptr;
+#ifdef ENABLE_SCI32
+ _audio32 = nullptr;
+ _video32 = nullptr;
+ _gfxCursor32 = nullptr;
+#endif
_features = 0;
_resMan = 0;
_gamestate = 0;
@@ -118,6 +127,8 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam
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(kDebugLevelVideo, "Video", "Video (SEQ, VMD, RBT) debugging");
+ DebugMan.addDebugChannel(kDebugLevelGame, "Game", "Debug calls from game scripts");
DebugMan.addDebugChannel(kDebugLevelGC, "GC", "Garbage Collector debugging");
DebugMan.addDebugChannel(kDebugLevelResMan, "ResMan", "Resource manager debugging");
DebugMan.addDebugChannel(kDebugLevelOnStartup, "OnStartup", "Enter debugger at start of game");
@@ -156,29 +167,37 @@ SciEngine::~SciEngine() {
DebugMan.clearAllDebugChannels();
#ifdef ENABLE_SCI32
- // _gfxPalette32 is the same as _gfxPalette16
- // and will be destroyed when _gfxPalette16 is
- // destroyed
delete _gfxControls32;
+ delete _gfxPaint32;
delete _gfxText32;
- delete _robotDecoder;
+ // GfxFrameout and GfxPalette32 must be deleted after Video32 since
+ // destruction of screen items in the Video32 destructor relies on these
+ // components
+ delete _video32;
+ delete _gfxCursor32;
+ delete _gfxPalette32;
+ delete _gfxTransitions32;
delete _gfxFrameout;
+ delete _gfxRemap32;
+ delete _audio32;
#endif
delete _gfxMenu;
delete _gfxControls16;
delete _gfxText16;
delete _gfxAnimate;
- delete _gfxPaint;
+ delete _gfxPaint16;
delete _gfxTransitions;
delete _gfxCompare;
delete _gfxCoordAdjuster;
delete _gfxPorts;
delete _gfxCache;
delete _gfxPalette16;
+ delete _gfxRemap16;
delete _gfxCursor;
delete _gfxScreen;
delete _audio;
+ delete _sync;
delete _soundCmd;
delete _kernel;
delete _vocabulary;
@@ -224,41 +243,31 @@ Common::Error SciEngine::run() {
_scriptPatcher = new ScriptPatcher();
SegManager *segMan = new SegManager(_resMan, _scriptPatcher);
- // Read user option for hires graphics
+ // Read user option for forcing hires graphics
// Only show/selectable for:
// - King's Quest 6 CD
// - King's Quest 6 CD demo
// - Gabriel Knight 1 CD
// - Police Quest 4 CD
// TODO: Check, if Gabriel Knight 1 floppy supports high resolution
- // TODO: Check, if Gabriel Knight 1 on Mac supports high resolution
- switch (getPlatform()) {
- case Common::kPlatformDOS:
- case Common::kPlatformWindows:
- // Only DOS+Windows
- switch (_gameId) {
- case GID_KQ6:
- if (isCD())
- _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics");
- break;
- case GID_GK1:
- if ((isCD()) && (!isDemo()))
- _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics");
- break;
- case GID_PQ4:
- if (isCD())
- _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics");
- break;
- default:
- break;
- }
- default:
- break;
- };
+ //
+ // Gabriel Knight 1 on Mac is hi-res only, so it should NOT get this option.
+ // Confirmed by [md5] and originally by clone2727.
+ if (Common::checkGameGUIOption(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, ConfMan.get("guioptions"))) {
+ // GAMEOPTION_HIGH_RESOLUTION_GRAPHICS is available for the currently detected game,
+ // so read the user option now.
+ // We need to do this, because the option's default is "true", but we don't want "true"
+ // for any game that does not have this option.
+ _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics");
+ }
- // Initialize the game screen
- _gfxScreen = new GfxScreen(_resMan);
- _gfxScreen->enableUndithering(ConfMan.getBool("disable_dithering"));
+ if (getSciVersion() < SCI_VERSION_2) {
+ // Initialize the game screen
+ _gfxScreen = new GfxScreen(_resMan);
+ _gfxScreen->enableUndithering(ConfMan.getBool("disable_dithering"));
+ } else {
+ _gfxScreen = nullptr;
+ }
_kernel = new Kernel(_resMan, segMan);
_kernel->init();
@@ -269,9 +278,21 @@ Common::Error SciEngine::run() {
// Also, XMAS1990 apparently had a parser too. Refer to http://forums.scummvm.org/viewtopic.php?t=9135
if (getGameId() == GID_CHRISTMAS1990)
_vocabulary = new Vocabulary(_resMan, false);
- _audio = new AudioPlayer(_resMan);
+
_gamestate = new EngineState(segMan);
_eventMan = new EventManager(_resMan->detectFontExtended());
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
+ _audio32 = new Audio32(_resMan);
+ } else
+#endif
+ _audio = new AudioPlayer(_resMan);
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ _video32 = new Video32(segMan, _eventMan);
+ }
+#endif
+ _sync = new Sync(_resMan, segMan);
// Create debugger console. It requires GFX and _gamestate to be initialized
_console = new Console(this);
@@ -296,10 +317,6 @@ Common::Error SciEngine::run() {
// Must be called after game_init(), as they use _features
_kernel->loadKernelNames(_features);
- _soundCmd = new SoundCommandParser(_resMan, segMan, _kernel, _audio, _features->detectDoSoundType());
-
- syncSoundSettings();
- syncIngameAudioOptions();
// Load our Mac executable here for icon bar palettes and high-res fonts
loadMacExecutable();
@@ -307,6 +324,15 @@ Common::Error SciEngine::run() {
// Initialize all graphics related subsystems
initGraphics();
+ // Sound must be initialized after graphics because SysEx transfers at the
+ // start of the game must pump the event loop to avoid making the OS think
+ // that ScummVM is hanged, and pumping the event loop requires GfxCursor to
+ // be initialized
+ _soundCmd = new SoundCommandParser(_resMan, segMan, _kernel, _audio, _features->detectDoSoundType());
+
+ syncSoundSettings();
+ syncIngameAudioOptions();
+
// Patch in our save/restore code, so that dialogs are replaced
patchGameSaveRestore();
setLauncherLanguage();
@@ -316,6 +342,7 @@ Common::Error SciEngine::run() {
if (directSaveSlotLoading >= 0) {
_gamestate->_delayedRestoreGame = true;
_gamestate->_delayedRestoreGameId = directSaveSlotLoading;
+ _gamestate->_delayedRestoreFromLauncher = true;
// Jones only initializes its menus when restarting/restoring, thus set
// the gameIsRestarting flag here before initializing. Fixes bug #6536.
@@ -343,6 +370,17 @@ Common::Error SciEngine::run() {
}
}
+ if (getGameId() == GID_KQ7 && ConfMan.getBool("subtitles")) {
+ showScummVMDialog("Subtitles are enabled, but subtitling in King's"
+ " Quest 7 was unfinished and disabled in the release"
+ " version of the game. ScummVM allows the subtitles"
+ " to be re-enabled, but because they were removed from"
+ " the original game, they do not always render"
+ " properly or reflect the actual game speech."
+ " This is not a ScummVM bug -- it is a problem with"
+ " the game's assets.");
+ }
+
// Show a warning if the user has selected a General MIDI device, no GM patch exists
// (i.e. patch 4) and the game is one of the known 8 SCI1 games that Sierra has provided
// after market patches for in their "General MIDI Utility".
@@ -462,62 +500,89 @@ static byte patchGameRestoreSave[] = {
0x48, // ret
};
+#ifdef ENABLE_SCI32
// SCI2 version: Same as above, but the second parameter to callk is a word
-static byte patchGameRestoreSaveSci2[] = {
- 0x39, 0x03, // pushi 03
- 0x76, // push0
- 0x38, 0xff, 0xff, // pushi -1
- 0x76, // push0
- 0x43, 0xff, 0x06, 0x00, // callk kRestoreGame/kSaveGame (will get changed afterwards)
- 0x48, // ret
+// and third parameter is a string reference
+static byte patchGameRestoreSci2[] = {
+ 0x39, 0x03, // pushi 03
+ 0x76, // push0 (game name)
+ 0x38, 0xff, 0xff, // pushi -1 (save number)
+ 0x89, 0x1b, // lsg global[27] (game version)
+ 0x43, 0xff, 0x06, 0x00, // callk kRestoreGame (0xFF will be overwritten by patcher)
+ 0x48, // ret
};
-// SCI21 version: Same as above, but the second parameter to callk is a word
-static byte patchGameRestoreSaveSci21[] = {
- 0x39, 0x04, // pushi 04
- 0x76, // push0 // 0: save, 1: restore (will get changed afterwards)
- 0x76, // push0
- 0x38, 0xff, 0xff, // pushi -1
- 0x76, // push0
- 0x43, 0xff, 0x08, 0x00, // callk kSave (will get changed afterwards)
- 0x48, // ret
+static byte patchGameSaveSci2[] = {
+ 0x39, 0x04, // pushi 04
+ 0x76, // push0 (game name)
+ 0x38, 0xff, 0xff, // pushi -1 (save number)
+ 0x76, // push0 (save description)
+ 0x89, 0x1b, // lsg global[27] (game version)
+ 0x43, 0xff, 0x08, 0x00, // callk kSaveGame (0xFF will be overwritten by patcher)
+ 0x48, // ret
};
+// SCI2.1mid version: Same as above, but with an extra subop parameter
+static byte patchGameRestoreSci21[] = {
+ 0x39, 0x04, // pushi 04
+ 0x78, // push1 (subop)
+ 0x76, // push0 (game name)
+ 0x38, 0xff, 0xff, // pushi -1 (save number)
+ 0x89, 0x1b, // lsg global[27] (game version)
+ 0x43, 0xff, 0x08, 0x00, // callk kSave (0xFF will be overwritten by patcher)
+ 0x48, // ret
+};
+
+static byte patchGameSaveSci21[] = {
+ 0x39, 0x05, // pushi 05
+ 0x76, // push0 (subop)
+ 0x76, // push0 (game name)
+ 0x38, 0xff, 0xff, // pushi -1 (save number)
+ 0x76, // push0 (save description)
+ 0x89, 0x1b, // lsg global[27] (game version)
+ 0x43, 0xff, 0x0a, 0x00, // callk kSave (0xFF will be overwritten by patcher)
+ 0x48, // ret
+};
+#endif
+
static void patchGameSaveRestoreCode(SegManager *segMan, reg_t methodAddress, byte id) {
Script *script = segMan->getScript(methodAddress.getSegment());
byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.getOffset()));
- if (getSciVersion() <= SCI_VERSION_1_1) {
- memcpy(patchPtr, patchGameRestoreSave, sizeof(patchGameRestoreSave));
- } else { // SCI2+
- memcpy(patchPtr, patchGameRestoreSaveSci2, sizeof(patchGameRestoreSaveSci2));
-
- if (g_sci->isBE()) {
- // LE -> BE
- patchPtr[9] = 0x00;
- patchPtr[10] = 0x06;
- }
- }
-
+ memcpy(patchPtr, patchGameRestoreSave, sizeof(patchGameRestoreSave));
patchPtr[8] = id;
}
-static void patchGameSaveRestoreCodeSci21(SegManager *segMan, reg_t methodAddress, byte id, bool doRestore) {
+#ifdef ENABLE_SCI32
+static void patchGameSaveRestoreCodeSci2(SegManager *segMan, reg_t methodAddress, byte id, bool doRestore) {
Script *script = segMan->getScript(methodAddress.getSegment());
byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.getOffset()));
- memcpy(patchPtr, patchGameRestoreSaveSci21, sizeof(patchGameRestoreSaveSci21));
+ int kcallOffset;
- if (doRestore)
- patchPtr[2] = 0x78; // push1
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ if (doRestore) {
+ memcpy(patchPtr, patchGameRestoreSci2, sizeof(patchGameRestoreSci2));
+ kcallOffset = 9;
+ } else {
+ memcpy(patchPtr, patchGameSaveSci2, sizeof(patchGameSaveSci2));
+ kcallOffset = 10;
+ }
+ } else {
+ if (doRestore) {
+ memcpy(patchPtr, patchGameRestoreSci21, sizeof(patchGameRestoreSci21));
+ kcallOffset = 10;
+ } else {
+ memcpy(patchPtr, patchGameSaveSci21, sizeof(patchGameSaveSci21));
+ kcallOffset = 11;
+ }
+ }
+ patchPtr[kcallOffset] = id;
if (g_sci->isBE()) {
- // LE -> BE
- patchPtr[10] = 0x00;
- patchPtr[11] = 0x08;
+ SWAP(patchPtr[kcallOffset + 1], patchPtr[kcallOffset + 2]);
}
-
- patchPtr[9] = id;
}
+#endif
void SciEngine::patchGameSaveRestore() {
SegManager *segMan = _gamestate->_segMan;
@@ -532,9 +597,12 @@ void SciEngine::patchGameSaveRestore() {
case GID_HOYLE1: // gets confused, although the game doesn't support saving/restoring at all
case GID_HOYLE2: // gets confused, see hoyle1
case GID_JONES: // gets confused, when we patch us in, the game is only able to save to 1 slot, so hooking is not required
+ case GID_KQ7: // has custom save/load code
case GID_MOTHERGOOSE: // mother goose EGA saves/restores directly and has no save/restore dialogs
case GID_MOTHERGOOSE256: // mother goose saves/restores directly and has no save/restore dialogs
+ case GID_MOTHERGOOSEHIRES: // has custom save/load code
case GID_PHANTASMAGORIA: // has custom save/load code
+ case GID_PQSWAT: // has custom save/load code
case GID_SHIVERS: // has custom save/load code
return;
default:
@@ -561,17 +629,21 @@ void SciEngine::patchGameSaveRestore() {
uint16 selectorId = gameSuperObject->getFuncSelector(methodNr);
Common::String methodName = _kernel->getSelectorName(selectorId);
if (methodName == "restore") {
- if (kernelIdSave != kernelIdRestore)
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ patchGameSaveRestoreCodeSci2(segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore, true);
+ } else
+#endif
patchGameSaveRestoreCode(segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore);
- else
- patchGameSaveRestoreCodeSci21(segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore, true);
}
else if (methodName == "save") {
if (_gameId != GID_FAIRYTALES) { // Fairy Tales saves automatically without a dialog
- if (kernelIdSave != kernelIdRestore)
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ patchGameSaveRestoreCodeSci2(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave, false);
+ } else
+#endif
patchGameSaveRestoreCode(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave);
- else
- patchGameSaveRestoreCodeSci21(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave, false);
}
}
}
@@ -595,10 +667,12 @@ void SciEngine::patchGameSaveRestore() {
Common::String methodName = _kernel->getSelectorName(selectorId);
if (methodName == "save") {
if (_gameId != GID_FAIRYTALES) { // Fairy Tales saves automatically without a dialog
- if (kernelIdSave != kernelIdRestore)
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ patchGameSaveRestoreCodeSci2(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave, false);
+ } else
+#endif
patchGameSaveRestoreCode(segMan, patchObjectSave->getFunction(methodNr), kernelIdSave);
- else
- patchGameSaveRestoreCodeSci21(segMan, patchObjectSave->getFunction(methodNr), kernelIdSave, false);
}
break;
}
@@ -662,19 +736,21 @@ void SciEngine::initGraphics() {
_gfxCursor = 0;
_gfxMacIconBar = 0;
_gfxMenu = 0;
- _gfxPaint = 0;
_gfxPaint16 = 0;
_gfxPalette16 = 0;
+ _gfxRemap16 = 0;
_gfxPorts = 0;
_gfxText16 = 0;
_gfxTransitions = 0;
#ifdef ENABLE_SCI32
_gfxControls32 = 0;
_gfxText32 = 0;
- _robotDecoder = 0;
_gfxFrameout = 0;
_gfxPaint32 = 0;
_gfxPalette32 = 0;
+ _gfxRemap32 = 0;
+ _gfxTransitions32 = 0;
+ _gfxCursor32 = 0;
#endif
if (hasMacIconBar())
@@ -682,41 +758,41 @@ void SciEngine::initGraphics() {
#ifdef ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2) {
- _gfxPalette32 = new GfxPalette32(_resMan, _gfxScreen);
- _gfxPalette16 = _gfxPalette32;
+ _gfxPalette32 = new GfxPalette32(_resMan);
+ _gfxRemap32 = new GfxRemap32();
} else {
#endif
_gfxPalette16 = new GfxPalette(_resMan, _gfxScreen);
+ if (getGameId() == GID_QFG4DEMO)
+ _gfxRemap16 = new GfxRemap(_gfxPalette16);
#ifdef ENABLE_SCI32
}
#endif
_gfxCache = new GfxCache(_resMan, _gfxScreen, _gfxPalette16);
- _gfxCursor = new GfxCursor(_resMan, _gfxPalette16, _gfxScreen);
#ifdef ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2) {
// SCI32 graphic objects creation
- _gfxCoordAdjuster = new GfxCoordAdjuster32(_gamestate->_segMan);
- _gfxCursor->init(_gfxCoordAdjuster, _eventMan);
- _gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster);
- _gfxPaint32 = new GfxPaint32(_resMan, _gfxCoordAdjuster, _gfxScreen, _gfxPalette32);
- _gfxPaint = _gfxPaint32;
- _robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh);
- _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette32, _gfxPaint32);
- _gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache, _gfxScreen);
+ _gfxCursor32 = new GfxCursor32();
+ _gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, nullptr, _gfxCoordAdjuster);
+ _gfxPaint32 = new GfxPaint32(_gamestate->_segMan);
+ _gfxTransitions32 = new GfxTransitions32(_gamestate->_segMan);
+ _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _gfxPalette32, _gfxTransitions32, _gfxCursor32);
+ _gfxCursor32->init(_gfxFrameout->getCurrentBuffer());
+ _gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache);
_gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32);
_gfxFrameout->run();
} else {
#endif
// SCI0-SCI1.1 graphic objects creation
+ _gfxCursor = new GfxCursor(_resMan, _gfxPalette16, _gfxScreen);
_gfxPorts = new GfxPorts(_gamestate->_segMan, _gfxScreen);
_gfxCoordAdjuster = new GfxCoordAdjuster16(_gfxPorts);
_gfxCursor->init(_gfxCoordAdjuster, _eventMan);
_gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster);
_gfxTransitions = new GfxTransitions(_gfxScreen, _gfxPalette16);
_gfxPaint16 = new GfxPaint16(_resMan, _gamestate->_segMan, _gfxCache, _gfxPorts, _gfxCoordAdjuster, _gfxScreen, _gfxPalette16, _gfxTransitions, _audio);
- _gfxPaint = _gfxPaint16;
_gfxAnimate = new GfxAnimate(_gamestate, _scriptPatcher, _gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen, _gfxPalette16, _gfxCursor, _gfxTransitions);
_gfxText16 = new GfxText16(_gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen);
_gfxControls16 = new GfxControls16(_gamestate->_segMan, _gfxPorts, _gfxPaint16, _gfxText16, _gfxScreen);
@@ -731,8 +807,10 @@ void SciEngine::initGraphics() {
}
#endif
- // Set default (EGA, amiga or resource 999) palette
- _gfxPalette16->setDefault();
+ if (getSciVersion() < SCI_VERSION_2) {
+ // Set default (EGA, amiga or resource 999) palette
+ _gfxPalette16->setDefault();
+ }
}
void SciEngine::initStackBaseWithSelector(Selector selector) {
@@ -798,7 +876,10 @@ void SciEngine::runGame() {
void SciEngine::exitGame() {
if (_gamestate->abortScriptProcessing != kAbortLoadGame) {
_gamestate->_executionStack.clear();
- _audio->stopAllAudio();
+ if (_audio) {
+ _audio->stopAllAudio();
+ }
+ _sync->stop();
_soundCmd->clearPlayList();
}
@@ -833,7 +914,7 @@ Console *SciEngine::getSciDebugger() {
}
const char *SciEngine::getGameIdStr() const {
- return _gameDescription->gameid;
+ return _gameDescription->gameId;
}
Common::Language SciEngine::getLanguage() const {
@@ -884,14 +965,23 @@ Common::String SciEngine::getFilePrefix() const {
}
Common::String SciEngine::wrapFilename(const Common::String &name) const {
- return getFilePrefix() + "-" + name;
+ Common::String prefix = getFilePrefix() + "-";
+ if (name.hasPrefix(prefix.c_str()))
+ return name;
+ else
+ return prefix + name;
}
Common::String SciEngine::unwrapFilename(const Common::String &name) const {
Common::String prefix = getFilePrefix() + "-";
if (name.hasPrefix(prefix.c_str()))
return Common::String(name.c_str() + prefix.size());
- return name;
+ else
+ return name;
+}
+
+const char *SciEngine::getGameObjectName() {
+ return _gamestate->_segMan->getObjectName(_gameObjectAddress);
}
int SciEngine::inQfGImportRoom() const {
@@ -907,6 +997,25 @@ int SciEngine::inQfGImportRoom() const {
return 0;
}
+void SciEngine::sleep(uint32 msecs) {
+ uint32 time;
+ const uint32 wakeUpTime = g_system->getMillis() + msecs;
+
+ for (;;) {
+ // let backend process events and update the screen
+ _eventMan->getSciEvent(SCI_EVENT_PEEK);
+ time = g_system->getMillis();
+ if (time + 10 < wakeUpTime) {
+ g_system->delayMillis(10);
+ } else {
+ if (time < wakeUpTime)
+ g_system->delayMillis(wakeUpTime - time);
+ break;
+ }
+
+ }
+}
+
void SciEngine::setLauncherLanguage() {
if (_gameDescription->flags & ADGF_ADDENGLISH) {
// If game is multilingual
@@ -992,17 +1101,19 @@ void SciEngine::syncIngameAudioOptions() {
case GID_SQ6: // SCI2.1
case GID_TORIN: // SCI2.1
case GID_QFG4: // SCI2.1
+ case GID_PQ4: // SCI2
+ case GID_PHANTASMAGORIA: // SCI2.1
+ case GID_MOTHERGOOSEHIRES: // SCI2.1
useGlobal90 = true;
break;
case GID_LSL6: // SCI2.1
// TODO: Uses gameFlags array
break;
+ // Shivers does not use global 90
+ // Police Quest: SWAT does not use global 90
+ //
// TODO: Unknown at the moment:
- // Shivers - seems not to use global 90
- // Police Quest: SWAT - unable to check
- // Police Quest 4 - unable to check
- // Mixed Up Mother Goose - unable to check
- // Phantasmagoria - seems to use global 90, unable to check for subtitles atm
+ // LSL7, Lighthouse, RAMA, Phantasmagoria 2
default:
return;
}
@@ -1015,11 +1126,30 @@ void SciEngine::syncIngameAudioOptions() {
bool subtitlesOn = ConfMan.getBool("subtitles");
bool speechOn = !ConfMan.getBool("speech_mute");
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ GlobalVar index;
+ uint16 textSpeed;
+
+ switch (g_sci->getGameId()) {
+ case GID_LSL6HIRES:
+ index = kGlobalVarLSL6HiresTextSpeed;
+ textSpeed = 14 - ConfMan.getInt("talkspeed") * 14 / 255 + 1;
+ break;
+ default:
+ index = kGlobalVarTextSpeed;
+ textSpeed = 8 - ConfMan.getInt("talkspeed") * 8 / 255;
+ }
+
+ _gamestate->variables[VAR_GLOBAL][index] = make_reg(0, textSpeed);
+ }
+#endif
+
if (useGlobal90) {
if (subtitlesOn && !speechOn) {
- _gamestate->variables[VAR_GLOBAL][90] = make_reg(0, 1); // subtitles
+ _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 1); // subtitles
} else if (!subtitlesOn && speechOn) {
- _gamestate->variables[VAR_GLOBAL][90] = make_reg(0, 2); // speech
+ _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 2); // speech
} else if (subtitlesOn && speechOn) {
// Is it a game that supports simultaneous speech and subtitles?
switch (_gameId) {
@@ -1036,12 +1166,15 @@ void SciEngine::syncIngameAudioOptions() {
case GID_SQ6: // SCI2.1, SQ6 seems to always use subtitles anyway
case GID_TORIN: // SCI2.1
case GID_QFG4: // SCI2.1
+ case GID_PQ4: // SCI2
+ // Phantasmagoria does not support simultaneous speech + subtitles
+ // Mixed Up Mother Goose Deluxe does not support simultaneous speech + subtitles
#endif // ENABLE_SCI32
- _gamestate->variables[VAR_GLOBAL][90] = make_reg(0, 3); // speech + subtitles
+ _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 3); // speech + subtitles
break;
default:
// Game does not support speech and subtitles, set it to speech
- _gamestate->variables[VAR_GLOBAL][90] = make_reg(0, 2); // speech
+ _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 2); // speech
}
}
}
@@ -1051,8 +1184,10 @@ void SciEngine::syncIngameAudioOptions() {
void SciEngine::updateScummVMAudioOptions() {
// Update ScummVM's speech/subtitles settings for SCI1.1 CD games,
// depending on the in-game settings
- if (isCD() && getSciVersion() == SCI_VERSION_1_1) {
- uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][90].getOffset();
+ if ((isCD() && getSciVersion() == SCI_VERSION_1_1) ||
+ getSciVersion() >= SCI_VERSION_2) {
+
+ uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType].getOffset();
switch (ingameSetting) {
case 1:
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index 3945e68e33..a36ae001f4 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -20,8 +20,8 @@
*
*/
-#ifndef SCI_H
-#define SCI_H
+#ifndef SCI_SCI_H
+#define SCI_SCI_H
#include "engines/engine.h"
#include "common/macresman.h"
@@ -35,7 +35,7 @@ struct ADGameDescription;
/**
* This is the namespace of the SCI engine.
*
- * Status of this engine: ???
+ * Status of this engine: Awesome
*
* Games using this engine:
* - Newer Sierra adventure games (based on FreeSCI)
@@ -45,6 +45,18 @@ struct ADGameDescription;
*/
namespace Sci {
+// GUI-options, primarily used by detection_tables.h
+#define GAMEOPTION_PREFER_DIGITAL_SFX GUIO_GAMEOPTIONS1
+#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS2
+#define GAMEOPTION_FB01_MIDI GUIO_GAMEOPTIONS3
+#define GAMEOPTION_JONES_CDAUDIO GUIO_GAMEOPTIONS4
+#define GAMEOPTION_KQ6_WINDOWS_CURSORS GUIO_GAMEOPTIONS5
+#define GAMEOPTION_SQ4_SILVER_CURSORS GUIO_GAMEOPTIONS6
+#define GAMEOPTION_EGA_UNDITHER GUIO_GAMEOPTIONS7
+// HIGH_RESOLUTION_GRAPHICS availability is checked for in SciEngine::run()
+#define GAMEOPTION_HIGH_RESOLUTION_GRAPHICS GUIO_GAMEOPTIONS8
+#define GAMEOPTION_ENABLE_BLACK_LINED_VIDEO GUIO_GAMEOPTIONS9
+
struct EngineState;
class Vocabulary;
class ResourceManager;
@@ -56,21 +68,23 @@ class SoundCommandParser;
class EventManager;
class SegManager;
class ScriptPatcher;
+class Sync;
class GfxAnimate;
class GfxCache;
class GfxCompare;
class GfxControls16;
class GfxControls32;
-class GfxCoordAdjuster;
+class GfxCoordAdjuster16;
class GfxCursor;
class GfxMacIconBar;
class GfxMenu;
-class GfxPaint;
class GfxPaint16;
class GfxPaint32;
class GfxPalette;
class GfxPalette32;
+class GfxRemap;
+class GfxRemap32;
class GfxPorts;
class GfxScreen;
class GfxText16;
@@ -78,8 +92,11 @@ class GfxText32;
class GfxTransitions;
#ifdef ENABLE_SCI32
-class RobotDecoder;
class GfxFrameout;
+class Audio32;
+class Video32;
+class GfxTransitions32;
+class GfxCursor32;
#endif
// our engine debug levels
@@ -107,7 +124,9 @@ enum kDebugLevels {
kDebugLevelOnStartup = 1 << 20,
kDebugLevelDebugMode = 1 << 21,
kDebugLevelScriptPatcher = 1 << 22,
- kDebugLevelWorkarounds = 1 << 23
+ kDebugLevelWorkarounds = 1 << 23,
+ kDebugLevelVideo = 1 << 24,
+ kDebugLevelGame = 1 << 25
};
enum SciGameId {
@@ -128,13 +147,16 @@ enum SciGameId {
GID_FAIRYTALES,
GID_FREDDYPHARKAS,
GID_FUNSEEKER,
+ GID_GK1DEMO, // We have a separate ID for GK1 demo, because it's actually a completely different game (SCI1.1 vs SCI2/SCI2.1)
GID_GK1,
GID_GK2,
GID_HOYLE1,
GID_HOYLE2,
GID_HOYLE3,
GID_HOYLE4,
+ GID_HOYLE5,
GID_ICEMAN,
+ GID_INNDEMO,
GID_ISLANDBRAIN,
GID_JONES,
GID_KQ1,
@@ -165,12 +187,14 @@ enum SciGameId {
GID_PQ2,
GID_PQ3,
GID_PQ4,
+ GID_PQ4DEMO, // We have a separate ID for PQ4 demo, because it's actually a completely different game (SCI1.1 vs SCI2/SCI2.1)
GID_PQSWAT,
GID_QFG1,
GID_QFG1VGA,
GID_QFG2,
GID_QFG3,
GID_QFG4,
+ GID_QFG4DEMO, // We have a separate ID for QFG4 demo, because it's actually a completely different game (SCI1.1 vs SCI2/SCI2.1)
GID_RAMA,
GID_SHIVERS,
//GID_SHIVERS2, // Not SCI
@@ -201,8 +225,8 @@ enum SciVersion {
SCI_VERSION_1_LATE, // Dr. Brain 1, EcoQuest 1, Longbow, PQ3, SQ1, LSL5, KQ5 CD
SCI_VERSION_1_1, // Dr. Brain 2, EcoQuest 1 CD, EcoQuest 2, KQ6, QFG3, SQ4CD, XMAS 1992 and many more
SCI_VERSION_2, // GK1, PQ4 floppy, QFG4 floppy
- SCI_VERSION_2_1_EARLY, // GK2 demo, KQ7, LSL6 hires, PQ4, QFG4 floppy
- SCI_VERSION_2_1_MIDDLE, // GK2, KQ7, MUMG Deluxe, Phantasmagoria 1, PQ4CD, PQ:SWAT, QFG4CD, Shivers 1, SQ6, Torin
+ SCI_VERSION_2_1_EARLY, // GK2 demo, KQ7 1.4/1.51, LSL6 hires, PQ4CD, QFG4CD
+ SCI_VERSION_2_1_MIDDLE, // GK2, KQ7 2.00b, MUMG Deluxe, Phantasmagoria 1, PQ:SWAT, Shivers 1, SQ6, Torin
SCI_VERSION_2_1_LATE, // demos of LSL7, Lighthouse, RAMA
SCI_VERSION_3 // LSL7, Lighthouse, RAMA, Phantasmagoria 2
};
@@ -297,6 +321,8 @@ public:
/** Remove the 'TARGET-' prefix of the given filename, if present. */
Common::String unwrapFilename(const Common::String &name) const;
+ const char *getGameObjectName(); // Gets the name of the game object (should only be used for identifying fanmade games)
+
/**
* Checks if we are in a QfG import screen, where special handling
* of file-listings is performed.
@@ -308,6 +334,7 @@ public:
void scriptDebug();
bool checkExportBreakpoint(uint16 script, uint16 pubfunct);
bool checkSelectorBreakpoint(BreakpointType breakpointType, reg_t send_obj, int selector);
+ bool checkAddressBreakpoint(const reg32_t &address);
void patchGameSaveRestore();
@@ -344,12 +371,13 @@ public:
GfxCompare *_gfxCompare;
GfxControls16 *_gfxControls16; // Controls for 16-bit gfx
GfxControls32 *_gfxControls32; // Controls for 32-bit gfx
- GfxCoordAdjuster *_gfxCoordAdjuster;
+ GfxCoordAdjuster16 *_gfxCoordAdjuster;
GfxCursor *_gfxCursor;
GfxMenu *_gfxMenu; // Menu for 16-bit gfx
GfxPalette *_gfxPalette16;
GfxPalette32 *_gfxPalette32; // Palette for 32-bit gfx
- GfxPaint *_gfxPaint;
+ GfxRemap *_gfxRemap16; // Remapping for the QFG4 demo
+ GfxRemap32 *_gfxRemap32; // Remapping for 32-bit gfx
GfxPaint16 *_gfxPaint16; // Painting in 16-bit gfx
GfxPaint32 *_gfxPaint32; // Painting in 32-bit gfx
GfxPorts *_gfxPorts; // Port managment for 16-bit gfx
@@ -360,11 +388,15 @@ public:
GfxMacIconBar *_gfxMacIconBar; // Mac Icon Bar manager
#ifdef ENABLE_SCI32
- RobotDecoder *_robotDecoder;
+ Audio32 *_audio32;
+ Video32 *_video32;
GfxFrameout *_gfxFrameout; // kFrameout and the like for 32-bit gfx
+ GfxTransitions32 *_gfxTransitions32;
+ GfxCursor32 *_gfxCursor32;
#endif
AudioPlayer *_audio;
+ Sync *_sync;
SoundCommandParser *_soundCmd;
GameFeatures *_features;
@@ -453,4 +485,4 @@ const char *getSciVersionDesc(SciVersion version);
} // End of namespace Sci
-#endif // SCI_H
+#endif // SCI_SCI_H
diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp
index 57f0415285..4fb9a58003 100644
--- a/engines/sci/sound/audio.cpp
+++ b/engines/sci/sound/audio.cpp
@@ -22,12 +22,12 @@
#include "sci/resource.h"
#include "sci/engine/kernel.h"
-#include "sci/engine/selector.h"
#include "sci/engine/seg_manager.h"
#include "sci/sound/audio.h"
#include "backends/audiocd/audiocd.h"
+#include "common/config-manager.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/system.h"
@@ -44,7 +44,7 @@
namespace Sci {
AudioPlayer::AudioPlayer(ResourceManager *resMan) : _resMan(resMan), _audioRate(11025),
- _syncResource(NULL), _syncOffset(0), _audioCdStart(0) {
+ _audioCdStart(0), _initCD(false) {
_mixer = g_system->getMixer();
_wPlayFlag = false;
@@ -55,7 +55,6 @@ AudioPlayer::~AudioPlayer() {
}
void AudioPlayer::stopAllAudio() {
- stopSoundSync();
stopAudio();
if (_audioCdStart > 0)
audioCdStop();
@@ -254,13 +253,7 @@ static void deDPCM16(byte *soundBuf, Common::SeekableReadStream &audioStream, ui
static void deDPCM8Nibble(byte *soundBuf, int32 &s, byte b) {
if (b & 8) {
-#ifdef ENABLE_SCI32
- // SCI2.1 reverses the order of the table values here
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY)
- s -= tableDPCM8[b & 7];
- else
-#endif
- s -= tableDPCM8[7 - (b & 7)];
+ s -= tableDPCM8[7 - (b & 7)];
} else
s += tableDPCM8[b & 7];
s = CLIP<int32>(s, 0, 255);
@@ -473,44 +466,13 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32
return NULL;
}
-void AudioPlayer::setSoundSync(ResourceId id, reg_t syncObjAddr, SegManager *segMan) {
- _syncResource = _resMan->findResource(id, 1);
- _syncOffset = 0;
-
- if (_syncResource) {
- writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncCue), 0);
- } else {
- warning("setSoundSync: failed to find resource %s", id.toString().c_str());
- // Notify the scripts to stop sound sync
- writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncCue), SIGNAL_OFFSET);
- }
-}
-
-void AudioPlayer::doSoundSync(reg_t syncObjAddr, SegManager *segMan) {
- if (_syncResource && (_syncOffset < _syncResource->size - 1)) {
- int16 syncCue = -1;
- int16 syncTime = (int16)READ_SCI11ENDIAN_UINT16(_syncResource->data + _syncOffset);
-
- _syncOffset += 2;
-
- if ((syncTime != -1) && (_syncOffset < _syncResource->size - 1)) {
- syncCue = (int16)READ_SCI11ENDIAN_UINT16(_syncResource->data + _syncOffset);
- _syncOffset += 2;
- }
-
- writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncTime), syncTime);
- writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncCue), syncCue);
- }
-}
-
-void AudioPlayer::stopSoundSync() {
- if (_syncResource) {
- _resMan->unlockResource(_syncResource);
- _syncResource = NULL;
+int AudioPlayer::audioCdPlay(int track, int start, int duration) {
+ if (!_initCD) {
+ // Initialize CD mode if we haven't already
+ g_system->getAudioCDManager()->open();
+ _initCD = true;
}
-}
-int AudioPlayer::audioCdPlay(int track, int start, int duration) {
if (getSciVersion() == SCI_VERSION_1_1) {
// King's Quest VI CD Audio format
_audioCdStart = g_system->getMillis();
diff --git a/engines/sci/sound/audio.h b/engines/sci/sound/audio.h
index 9e65d6e0c8..3d25dcaeef 100644
--- a/engines/sci/sound/audio.h
+++ b/engines/sci/sound/audio.h
@@ -46,12 +46,6 @@ enum AudioCommands {
kSciAudioCD = 10 /* Plays SCI1.1 CD audio */
};
-enum AudioSyncCommands {
- kSciAudioSyncStart = 0,
- kSciAudioSyncNext = 1,
- kSciAudioSyncStop = 2
-};
-
#define AUDIO_VOLUME_MAX 127
class Resource;
@@ -77,10 +71,6 @@ public:
void handleFanmadeSciAudio(reg_t sciAudioObject, SegManager *segMan);
- void setSoundSync(ResourceId id, reg_t syncObjAddr, SegManager *segMan);
- void doSoundSync(reg_t syncObjAddr, SegManager *segMan);
- void stopSoundSync();
-
int audioCdPlay(int track, int start, int duration);
void audioCdStop();
void audioCdUpdate();
@@ -93,10 +83,9 @@ private:
uint16 _audioRate;
Audio::SoundHandle _audioHandle;
Audio::Mixer *_mixer;
- Resource *_syncResource; /**< Used by kDoSync for speech syncing in CD talkie games */
- uint _syncOffset;
uint32 _audioCdStart;
bool _wPlayFlag;
+ bool _initCD;
};
} // End of namespace Sci
diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp
new file mode 100644
index 0000000000..d5a7ae14b8
--- /dev/null
+++ b/engines/sci/sound/audio32.cpp
@@ -0,0 +1,1107 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/sound/audio32.h"
+#include "audio/audiostream.h" // for SeekableAudioStream
+#include "audio/decoders/raw.h" // for makeRawStream, RawFlags::FLAG_16BITS
+#include "audio/decoders/wave.h" // for makeWAVStream
+#include "audio/rate.h" // for RateConverter, makeRateConverter
+#include "audio/timestamp.h" // for Timestamp
+#include "common/config-manager.h" // for ConfMan
+#include "common/endian.h" // for MKTAG
+#include "common/memstream.h" // for MemoryReadStream
+#include "common/str.h" // for String
+#include "common/stream.h" // for SeekableReadStream
+#include "common/system.h" // for OSystem, g_system
+#include "common/textconsole.h" // for warning
+#include "common/types.h" // for Flag::NO
+#include "engine.h" // for Engine, g_engine
+#include "sci/engine/features.h" // for GameFeatures
+#include "sci/engine/vm_types.h" // for reg_t, make_reg, NULL_REG
+#include "sci/resource.h" // for ResourceId, ResourceType::kResour...
+#include "sci/sci.h" // for SciEngine, g_sci, getSciVersion
+#include "sci/sound/decoders/sol.h" // for makeSOLStream
+
+namespace Sci {
+
+bool detectSolAudio(Common::SeekableReadStream &stream) {
+ const size_t initialPosition = stream.pos();
+
+// TODO: Resource manager for audio resources reads past the
+// header so even though this is the detection algorithm
+// in SSCI, ScummVM can't use it
+#if 0
+ byte header[6];
+ if (stream.read(header, sizeof(header)) != sizeof(header)) {
+ stream.seek(initialPosition);
+ return false;
+ }
+
+ stream.seek(initialPosition);
+
+ if (header[0] != 0x8d || READ_BE_UINT32(header + 2) != MKTAG('S', 'O', 'L', 0)) {
+ return false;
+ }
+
+ return true;
+#else
+ byte header[4];
+ if (stream.read(header, sizeof(header)) != sizeof(header)) {
+ stream.seek(initialPosition);
+ return false;
+ }
+
+ stream.seek(initialPosition);
+
+ if (READ_BE_UINT32(header) != MKTAG('S', 'O', 'L', 0)) {
+ return false;
+ }
+
+ return true;
+#endif
+}
+
+bool detectWaveAudio(Common::SeekableReadStream &stream) {
+ const size_t initialPosition = stream.pos();
+
+ byte blockHeader[8];
+ if (stream.read(blockHeader, sizeof(blockHeader)) != sizeof(blockHeader)) {
+ stream.seek(initialPosition);
+ return false;
+ }
+
+ stream.seek(initialPosition);
+ const uint32 headerType = READ_BE_UINT32(blockHeader);
+
+ if (headerType != MKTAG('R', 'I', 'F', 'F')) {
+ return false;
+ }
+
+ return true;
+}
+
+#pragma mark -
+
+Audio32::Audio32(ResourceManager *resMan) :
+ _resMan(resMan),
+ _mixer(g_system->getMixer()),
+ _handle(),
+ _mutex(),
+
+ _numActiveChannels(0),
+ _inAudioThread(false),
+
+ _globalSampleRate(44100),
+ _maxAllowedSampleRate(44100),
+ _globalBitDepth(16),
+ _maxAllowedBitDepth(16),
+ _globalNumOutputChannels(2),
+ _maxAllowedOutputChannels(2),
+ _preload(0),
+
+ _robotAudioPaused(false),
+
+ _pausedAtTick(0),
+ _startedAtTick(0),
+
+ _attenuatedMixing(true),
+
+ _monitoredChannelIndex(-1),
+ _monitoredBuffer(nullptr),
+ _monitoredBufferSize(0),
+ _numMonitoredSamples(0) {
+
+ if (getSciVersion() < SCI_VERSION_3) {
+ _channels.resize(5);
+ } else {
+ _channels.resize(8);
+ }
+
+ _useModifiedAttenuation = g_sci->_features->usesModifiedAudioAttenuation();
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+Audio32::~Audio32() {
+ stop(kAllChannels);
+ _mixer->stopHandle(_handle);
+ free(_monitoredBuffer);
+}
+
+#pragma mark -
+#pragma mark AudioStream implementation
+
+int Audio32::writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) {
+ int samplesToRead = numSamples;
+
+ // The parent rate converter will request N * 2
+ // samples from this `readBuffer` call, because
+ // we tell it that we send stereo output, but
+ // the source stream we're mixing in may be
+ // mono, in which case we need to request half
+ // as many samples from the mono stream and let
+ // the converter double them for stereo output
+ if (!sourceStream->isStereo()) {
+ samplesToRead >>= 1;
+ }
+
+ int samplesWritten = 0;
+
+ do {
+ if (loop && sourceStream->endOfStream()) {
+ Audio::RewindableAudioStream *rewindableStream = dynamic_cast<Audio::RewindableAudioStream *>(sourceStream);
+ if (rewindableStream == nullptr) {
+ error("[Audio32::writeAudioInternal]: Unable to cast stream");
+ }
+ rewindableStream->rewind();
+ }
+
+ const int loopSamplesWritten = converter->flow(*sourceStream, targetBuffer, samplesToRead, leftVolume, rightVolume);
+
+ if (loopSamplesWritten == 0) {
+ break;
+ }
+
+ samplesToRead -= loopSamplesWritten;
+ samplesWritten += loopSamplesWritten;
+ targetBuffer += loopSamplesWritten << 1;
+ } while (loop && samplesToRead > 0);
+
+ if (!sourceStream->isStereo()) {
+ samplesWritten <<= 1;
+ }
+
+ return samplesWritten;
+}
+
+// In earlier versions of SCI32 engine, audio mixing is
+// split into three different functions.
+//
+// The first function is called from the main game thread in
+// AsyncEventCheck; later versions of SSCI also call it when
+// getting the playback position. This function is
+// responsible for cleaning up finished channels and
+// filling active channel buffers with decompressed audio
+// matching the hardware output audio format so they can
+// just be copied into the main DAC buffer directly later.
+//
+// The second function is called by the audio hardware when
+// the DAC buffer needs to be filled, and by `play` when
+// there is only one active sample (so it can just blow away
+// whatever was already in the DAC buffer). It merges all
+// active channels into the DAC buffer and then updates the
+// offset into the DAC buffer.
+//
+// Finally, a third function is called by the second
+// function, and it actually puts data into the DAC buffer,
+// performing volume, distortion, and balance adjustments.
+//
+// Since we only have one callback from the audio thread,
+// and should be able to do all audio processing in
+// real time, and we have streams, and we do not need to
+// completely fill the audio buffer, the functionality of
+// all these original functions is combined here and
+// simplified.
+int Audio32::readBuffer(Audio::st_sample_t *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ if (_pausedAtTick != 0 || _numActiveChannels == 0) {
+ return 0;
+ }
+
+ // ResourceManager is not thread-safe so we need to
+ // avoid calling into it from the audio thread, but at
+ // the same time we need to be able to clear out any
+ // finished channels on a regular basis
+ _inAudioThread = true;
+
+ freeUnusedChannels();
+
+ // The caller of `readBuffer` is a rate converter,
+ // which reuses (without clearing) an intermediate
+ // buffer, so we need to zero the intermediate buffer
+ // to prevent mixing into audio data from the last
+ // callback.
+ memset(buffer, 0, numSamples * sizeof(Audio::st_sample_t));
+
+ // This emulates the attenuated mixing mode of SSCI
+ // engine, which reduces the volume of the target
+ // buffer when each new channel is mixed in.
+ // Instead of manipulating the content of the target
+ // buffer when mixing (which would either require
+ // modification of RateConverter or an expensive second
+ // pass against the entire target buffer), we just
+ // scale the volume for each channel in advance, with
+ // the earliest (lowest) channel having the highest
+ // amount of attenuation (lowest volume).
+ uint8 attenuationAmount;
+ uint8 attenuationStepAmount;
+ if (_useModifiedAttenuation) {
+ // channel | divisor
+ // 0 | 0 (>> 0)
+ // 1 | 4 (>> 2)
+ // 2 | 8...
+ attenuationAmount = _numActiveChannels * 2;
+ attenuationStepAmount = 2;
+ } else {
+ // channel | divisor
+ // 0 | 2 (>> 1)
+ // 1 | 4 (>> 2)
+ // 2 | 6...
+ if (_monitoredChannelIndex == -1 && _numActiveChannels > 1) {
+ attenuationAmount = _numActiveChannels + 1;
+ attenuationStepAmount = 1;
+ } else {
+ attenuationAmount = 0;
+ attenuationStepAmount = 0;
+ }
+ }
+
+ int maxSamplesWritten = 0;
+
+ for (int16 channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) {
+ attenuationAmount -= attenuationStepAmount;
+
+ const AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.pausedAtTick || (channel.robot && _robotAudioPaused)) {
+ continue;
+ }
+
+ // Channel finished fading and had the
+ // stopChannelOnFade flag set, so no longer exists
+ if (channel.fadeStartTick && processFade(channelIndex)) {
+ --channelIndex;
+ continue;
+ }
+
+ if (channel.robot) {
+ if (channel.stream->endOfStream()) {
+ stop(channelIndex--);
+ } else {
+ const int channelSamplesWritten = writeAudioInternal(channel.stream, channel.converter, buffer, numSamples, kMaxVolume, kMaxVolume, channel.loop);
+ if (channelSamplesWritten > maxSamplesWritten) {
+ maxSamplesWritten = channelSamplesWritten;
+ }
+ }
+ continue;
+ }
+
+ Audio::st_volume_t leftVolume, rightVolume;
+
+ if (channel.pan == -1 || !isStereo()) {
+ leftVolume = rightVolume = channel.volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume;
+ } else {
+ // TODO: This should match the SCI3 algorithm,
+ // which seems to halve the volume of each
+ // channel when centered; is this intended?
+ leftVolume = channel.volume * (100 - channel.pan) / 100 * Audio::Mixer::kMaxChannelVolume / kMaxVolume;
+ rightVolume = channel.volume * channel.pan / 100 * Audio::Mixer::kMaxChannelVolume / kMaxVolume;
+ }
+
+ if (_monitoredChannelIndex == -1 && _attenuatedMixing) {
+ leftVolume >>= attenuationAmount;
+ rightVolume >>= attenuationAmount;
+ }
+
+ if (channelIndex == _monitoredChannelIndex) {
+ const size_t bufferSize = numSamples * sizeof(Audio::st_sample_t);
+ if (_monitoredBufferSize < bufferSize) {
+ _monitoredBuffer = (Audio::st_sample_t *)realloc(_monitoredBuffer, bufferSize);
+ _monitoredBufferSize = bufferSize;
+ }
+
+ memset(_monitoredBuffer, 0, _monitoredBufferSize);
+
+ _numMonitoredSamples = writeAudioInternal(channel.stream, channel.converter, _monitoredBuffer, numSamples, leftVolume, rightVolume, channel.loop);
+
+ Audio::st_sample_t *sourceBuffer = _monitoredBuffer;
+ Audio::st_sample_t *targetBuffer = buffer;
+ const Audio::st_sample_t *const end = _monitoredBuffer + _numMonitoredSamples;
+ while (sourceBuffer != end) {
+ Audio::clampedAdd(*targetBuffer++, *sourceBuffer++);
+ }
+
+ if (_numMonitoredSamples > maxSamplesWritten) {
+ maxSamplesWritten = _numMonitoredSamples;
+ }
+ } else if (!channel.stream->endOfStream() || channel.loop) {
+ if (_monitoredChannelIndex != -1) {
+ // Audio that is not on the monitored channel is silent
+ // when the monitored channel is active, but the stream still
+ // needs to be read in order to ensure that sound effects sync
+ // up once the monitored channel is turned off. The easiest
+ // way to guarantee this is to just do the normal channel read,
+ // but set the channel volume to zero so nothing is mixed in
+ leftVolume = rightVolume = 0;
+ }
+
+ const int channelSamplesWritten = writeAudioInternal(channel.stream, channel.converter, buffer, numSamples, leftVolume, rightVolume, channel.loop);
+ if (channelSamplesWritten > maxSamplesWritten) {
+ maxSamplesWritten = channelSamplesWritten;
+ }
+ }
+ }
+
+ _inAudioThread = false;
+
+ return maxSamplesWritten;
+}
+
+#pragma mark -
+#pragma mark Channel management
+
+int16 Audio32::findChannelByArgs(int argc, const reg_t *argv, const int startIndex, const reg_t soundNode) const {
+ // NOTE: argc/argv are already reduced by one in our engine because
+ // this call is always made from a subop, so no reduction for the
+ // subop is made in this function. SSCI takes extra steps to skip
+ // the subop argument.
+
+ argc -= startIndex;
+ if (argc <= 0) {
+ return kAllChannels;
+ }
+
+ Common::StackLock lock(_mutex);
+
+ if (_numActiveChannels == 0) {
+ return kNoExistingChannel;
+ }
+
+ ResourceId searchId;
+
+ if (argc < 5) {
+ searchId = ResourceId(kResourceTypeAudio, argv[startIndex].toUint16());
+ } else {
+ searchId = ResourceId(
+ kResourceTypeAudio36,
+ argv[startIndex].toUint16(),
+ argv[startIndex + 1].toUint16(),
+ argv[startIndex + 2].toUint16(),
+ argv[startIndex + 3].toUint16(),
+ argv[startIndex + 4].toUint16()
+ );
+ }
+
+ return findChannelById(searchId, soundNode);
+}
+
+int16 Audio32::findChannelById(const ResourceId resourceId, const reg_t soundNode) const {
+ Common::StackLock lock(_mutex);
+
+ if (_numActiveChannels == 0) {
+ return kNoExistingChannel;
+ }
+
+ if (resourceId.getType() == kResourceTypeAudio) {
+ for (int16 i = 0; i < _numActiveChannels; ++i) {
+ const AudioChannel channel = _channels[i];
+ if (
+ channel.id == resourceId &&
+ (soundNode.isNull() || soundNode == channel.soundNode)
+ ) {
+ return i;
+ }
+ }
+ } else if (resourceId.getType() == kResourceTypeAudio36) {
+ for (int16 i = 0; i < _numActiveChannels; ++i) {
+ const AudioChannel &candidate = getChannel(i);
+ if (!candidate.robot && candidate.id == resourceId) {
+ return i;
+ }
+ }
+ } else {
+ error("Audio32::findChannelById: Unknown resource type %d", resourceId.getType());
+ }
+
+ return kNoExistingChannel;
+}
+
+void Audio32::freeUnusedChannels() {
+ Common::StackLock lock(_mutex);
+ for (int channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) {
+ const AudioChannel &channel = getChannel(channelIndex);
+ if (!channel.robot && channel.stream->endOfStream()) {
+ if (channel.loop) {
+ Audio::SeekableAudioStream *stream = dynamic_cast<Audio::SeekableAudioStream *>(channel.stream);
+ if (stream == nullptr) {
+ error("[Audio32::freeUnusedChannels]: Unable to cast stream for resource %s", channel.id.toString().c_str());
+ }
+ stream->rewind();
+ } else {
+ stop(channelIndex--);
+ }
+ }
+ }
+
+ if (!_inAudioThread) {
+ unlockResources();
+ }
+}
+
+void Audio32::freeChannel(const int16 channelIndex) {
+ // The original engine did this:
+ // 1. Unlock memory-cached resource, if one existed
+ // 2. Close patched audio file descriptor, if one existed
+ // 3. Free decompression memory buffer, if one existed
+ // 4. Clear monitored memory buffer, if one existed
+ Common::StackLock lock(_mutex);
+ AudioChannel &channel = getChannel(channelIndex);
+
+ // Robots have no corresponding resource to free
+ if (channel.robot) {
+ delete channel.stream;
+ channel.stream = nullptr;
+ channel.robot = false;
+ } else {
+ // We cannot unlock resources from the audio thread
+ // because ResourceManager is not thread-safe; instead,
+ // we just record that the resource needs unlocking and
+ // unlock it whenever we are on the main thread again
+ if (_inAudioThread) {
+ _resourcesToUnlock.push_back(channel.resource);
+ } else {
+ _resMan->unlockResource(channel.resource);
+ }
+
+ channel.resource = nullptr;
+ delete channel.stream;
+ channel.stream = nullptr;
+ delete channel.resourceStream;
+ channel.resourceStream = nullptr;
+ }
+
+ delete channel.converter;
+ channel.converter = nullptr;
+
+ if (_monitoredChannelIndex == channelIndex) {
+ _monitoredChannelIndex = -1;
+ }
+}
+
+void Audio32::unlockResources() {
+ Common::StackLock lock(_mutex);
+ assert(!_inAudioThread);
+
+ for (UnlockList::const_iterator it = _resourcesToUnlock.begin(); it != _resourcesToUnlock.end(); ++it) {
+ _resMan->unlockResource(*it);
+ }
+ _resourcesToUnlock.clear();
+}
+
+#pragma mark -
+#pragma mark Script compatibility
+
+void Audio32::setSampleRate(uint16 rate) {
+ if (rate > _maxAllowedSampleRate) {
+ rate = _maxAllowedSampleRate;
+ }
+
+ _globalSampleRate = rate;
+}
+
+void Audio32::setBitDepth(uint8 depth) {
+ if (depth > _maxAllowedBitDepth) {
+ depth = _maxAllowedBitDepth;
+ }
+
+ _globalBitDepth = depth;
+}
+
+void Audio32::setNumOutputChannels(int16 numChannels) {
+ if (numChannels > _maxAllowedOutputChannels) {
+ numChannels = _maxAllowedOutputChannels;
+ }
+
+ _globalNumOutputChannels = numChannels;
+}
+
+#pragma mark -
+#pragma mark Robot
+
+int16 Audio32::findRobotChannel() const {
+ Common::StackLock lock(_mutex);
+ for (int16 i = 0; i < _numActiveChannels; ++i) {
+ if (_channels[i].robot) {
+ return i;
+ }
+ }
+
+ return kNoExistingChannel;
+}
+
+bool Audio32::playRobotAudio(const RobotAudioStream::RobotAudioPacket &packet) {
+ // Stop immediately
+ if (packet.dataSize == 0) {
+ warning("Stopping robot stream by zero-length packet");
+ return stopRobotAudio();
+ }
+
+ // Flush and then stop
+ if (packet.dataSize == -1) {
+ warning("Stopping robot stream by negative-length packet");
+ return finishRobotAudio();
+ }
+
+ Common::StackLock lock(_mutex);
+ int16 channelIndex = findRobotChannel();
+
+ bool isNewChannel = false;
+ if (channelIndex == kNoExistingChannel) {
+ if (_numActiveChannels == _channels.size()) {
+ return false;
+ }
+
+ channelIndex = _numActiveChannels++;
+ isNewChannel = true;
+ }
+
+ AudioChannel &channel = getChannel(channelIndex);
+
+ if (isNewChannel) {
+ channel.id = ResourceId();
+ channel.resource = nullptr;
+ channel.loop = false;
+ channel.robot = true;
+ channel.fadeStartTick = 0;
+ channel.pausedAtTick = 0;
+ channel.soundNode = NULL_REG;
+ channel.volume = kMaxVolume;
+ // TODO: SCI3 introduces stereo audio
+ channel.pan = -1;
+ channel.converter = Audio::makeRateConverter(RobotAudioStream::kRobotSampleRate, getRate(), false);
+ // The RobotAudioStream buffer size is
+ // ((bytesPerSample * channels * sampleRate * 2000ms) / 1000ms) & ~3
+ // where bytesPerSample = 2, channels = 1, and sampleRate = 22050
+ channel.stream = new RobotAudioStream(88200);
+ _robotAudioPaused = false;
+
+ if (_numActiveChannels == 1) {
+ _startedAtTick = g_sci->getTickCount();
+ }
+ }
+
+ return static_cast<RobotAudioStream *>(channel.stream)->addPacket(packet);
+}
+
+bool Audio32::queryRobotAudio(RobotAudioStream::StreamState &status) const {
+ Common::StackLock lock(_mutex);
+
+ const int16 channelIndex = findRobotChannel();
+ if (channelIndex == kNoExistingChannel) {
+ status.bytesPlaying = 0;
+ return false;
+ }
+
+ status = static_cast<RobotAudioStream *>(getChannel(channelIndex).stream)->getStatus();
+ return true;
+}
+
+bool Audio32::finishRobotAudio() {
+ Common::StackLock lock(_mutex);
+
+ const int16 channelIndex = findRobotChannel();
+ if (channelIndex == kNoExistingChannel) {
+ return false;
+ }
+
+ static_cast<RobotAudioStream *>(getChannel(channelIndex).stream)->finish();
+ return true;
+}
+
+bool Audio32::stopRobotAudio() {
+ Common::StackLock lock(_mutex);
+
+ const int16 channelIndex = findRobotChannel();
+ if (channelIndex == kNoExistingChannel) {
+ return false;
+ }
+
+ stop(channelIndex);
+ return true;
+}
+
+#pragma mark -
+#pragma mark Playback
+
+uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor) {
+ Common::StackLock lock(_mutex);
+
+ freeUnusedChannels();
+
+ if (channelIndex != kNoExistingChannel) {
+ AudioChannel &channel = getChannel(channelIndex);
+ Audio::SeekableAudioStream *stream = dynamic_cast<Audio::SeekableAudioStream *>(channel.stream);
+ if (stream == nullptr) {
+ error("[Audio32::play]: Unable to cast stream for resource %s", resourceId.toString().c_str());
+ }
+
+ if (channel.pausedAtTick) {
+ resume(channelIndex);
+ return MIN(65534, 1 + stream->getLength().msecs() * 60 / 1000);
+ }
+
+ warning("Tried to resume channel %s that was not paused", channel.id.toString().c_str());
+ return MIN(65534, 1 + stream->getLength().msecs() * 60 / 1000);
+ }
+
+ if (_numActiveChannels == _channels.size()) {
+ warning("Audio mixer is full when trying to play %s", resourceId.toString().c_str());
+ return 0;
+ }
+
+ // NOTE: SCI engine itself normally searches in this order:
+ //
+ // For Audio36:
+ //
+ // 1. First, request a FD using Audio36 name and use it as the
+ // source FD for reading the audio resource data.
+ // 2a. If the returned FD is -1, or equals the audio map, or
+ // equals the audio bundle, try to get the offset of the
+ // data from the audio map, using the Audio36 name.
+ //
+ // If the returned offset is -1, this is not a valid resource;
+ // return 0. Otherwise, set the read offset for the FD to the
+ // returned offset.
+ // 2b. Otherwise, use the FD as-is (it is a patch file), with zero
+ // offset, and record it separately so it can be closed later.
+ //
+ // For plain audio:
+ //
+ // 1. First, request an Audio resource from the resource cache. If
+ // one does not exist, make the same request for a Wave resource.
+ // 2a. If an audio resource was discovered, record its memory ID
+ // and clear the streaming FD
+ // 2b. Otherwise, request an Audio FD. If one does not exist, make
+ // the same request for a Wave FD. If neither exist, this is not
+ // a valid resource; return 0. Otherwise, use the returned FD as
+ // the streaming ID and set the memory ID to null.
+ //
+ // Once these steps are complete, the audio engine either has a file
+ // descriptor + offset that it can use to read streamed audio, or it
+ // has a memory ID that it can use to read cached audio.
+ //
+ // Here in ScummVM we just ask the resource manager to give us the
+ // resource and we get a seekable stream.
+
+ // TODO: This should be fixed to use streaming, which means
+ // fixing the resource manager to allow streaming, which means
+ // probably rewriting a bunch of the resource manager.
+ Resource *resource = _resMan->findResource(resourceId, true);
+ if (resource == nullptr) {
+ return 0;
+ }
+
+ channelIndex = _numActiveChannels++;
+
+ AudioChannel &channel = getChannel(channelIndex);
+ channel.id = resourceId;
+ channel.resource = resource;
+ channel.loop = loop;
+ channel.robot = false;
+ channel.fadeStartTick = 0;
+ channel.soundNode = soundNode;
+ channel.volume = volume < 0 || volume > kMaxVolume ? (int)kMaxVolume : volume;
+ // TODO: SCI3 introduces stereo audio
+ channel.pan = -1;
+
+ if (monitor) {
+ _monitoredChannelIndex = channelIndex;
+ }
+
+ Common::MemoryReadStream headerStream(resource->_header, resource->_headerSize, DisposeAfterUse::NO);
+ Common::SeekableReadStream *dataStream = channel.resourceStream = resource->makeStream();
+
+ if (detectSolAudio(headerStream)) {
+ channel.stream = makeSOLStream(&headerStream, dataStream, DisposeAfterUse::NO);
+ } else if (detectWaveAudio(*dataStream)) {
+ channel.stream = Audio::makeWAVStream(dataStream, DisposeAfterUse::NO);
+ } else {
+ byte flags = Audio::FLAG_LITTLE_ENDIAN;
+ if (_globalBitDepth == 16) {
+ flags |= Audio::FLAG_16BITS;
+ } else {
+ flags |= Audio::FLAG_UNSIGNED;
+ }
+
+ if (_globalNumOutputChannels == 2) {
+ flags |= Audio::FLAG_STEREO;
+ }
+
+ channel.stream = Audio::makeRawStream(dataStream, _globalSampleRate, flags, DisposeAfterUse::NO);
+ }
+
+ channel.converter = Audio::makeRateConverter(channel.stream->getRate(), getRate(), channel.stream->isStereo(), false);
+
+ // NOTE: SCI engine sets up a decompression buffer here for the audio
+ // stream, plus writes information about the sample to the channel to
+ // convert to the correct hardware output format, and allocates the
+ // monitoring buffer to match the bitrate/samplerate/channels of the
+ // original stream. We do not need to do any of these things since we
+ // use audio streams, and allocate and fill the monitoring buffer
+ // when reading audio data from the stream.
+
+ Audio::SeekableAudioStream *stream = dynamic_cast<Audio::SeekableAudioStream *>(channel.stream);
+ if (stream == nullptr) {
+ error("[Audio32::play]: Unable to cast stream for resource %s", resourceId.toString().c_str());
+ }
+
+ channel.duration = /* round up */ 1 + (stream->getLength().msecs() * 60 / 1000);
+
+ const uint32 now = g_sci->getTickCount();
+ channel.pausedAtTick = autoPlay ? 0 : now;
+ channel.startedAtTick = now;
+
+ if (_numActiveChannels == 1) {
+ _startedAtTick = now;
+ }
+
+ return channel.duration;
+}
+
+bool Audio32::resume(const int16 channelIndex) {
+ if (channelIndex == kNoExistingChannel) {
+ return false;
+ }
+
+ Common::StackLock lock(_mutex);
+ const uint32 now = g_sci->getTickCount();
+
+ if (channelIndex == kAllChannels) {
+ // Global pause in SSCI is an extra layer over
+ // individual channel pauses, so only unpause channels
+ // if there was not a global pause in place
+ if (_pausedAtTick == 0) {
+ return false;
+ }
+
+ for (int i = 0; i < _numActiveChannels; ++i) {
+ AudioChannel &channel = getChannel(i);
+ if (!channel.pausedAtTick) {
+ channel.startedAtTick += now - _pausedAtTick;
+ }
+ }
+
+ _startedAtTick += now - _pausedAtTick;
+ _pausedAtTick = 0;
+ return true;
+ } else if (channelIndex == kRobotChannel) {
+ for (int i = 0; i < _numActiveChannels; ++i) {
+ AudioChannel &channel = getChannel(i);
+ if (channel.robot) {
+ channel.startedAtTick += now - channel.pausedAtTick;
+ channel.pausedAtTick = 0;
+ return true;
+ }
+ }
+ } else {
+ AudioChannel &channel = getChannel(channelIndex);
+ if (channel.pausedAtTick) {
+ channel.startedAtTick += now - channel.pausedAtTick;
+ channel.pausedAtTick = 0;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool Audio32::pause(const int16 channelIndex) {
+ if (channelIndex == kNoExistingChannel) {
+ return false;
+ }
+
+ Common::StackLock lock(_mutex);
+ const uint32 now = g_sci->getTickCount();
+ bool didPause = false;
+
+ if (channelIndex == kAllChannels) {
+ if (_pausedAtTick == 0) {
+ _pausedAtTick = now;
+ didPause = true;
+ }
+ } else if (channelIndex == kRobotChannel) {
+ _robotAudioPaused = true;
+ for (int16 i = 0; i < _numActiveChannels; ++i) {
+ AudioChannel &channel = getChannel(i);
+ if (channel.robot) {
+ channel.pausedAtTick = now;
+ }
+ }
+
+ // NOTE: The actual engine returns false here regardless of whether
+ // or not channels were paused
+ } else {
+ AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.pausedAtTick == 0) {
+ channel.pausedAtTick = now;
+ didPause = true;
+ }
+ }
+
+ return didPause;
+}
+
+int16 Audio32::stop(const int16 channelIndex) {
+ Common::StackLock lock(_mutex);
+ const int16 oldNumChannels = _numActiveChannels;
+
+ if (channelIndex == kNoExistingChannel || oldNumChannels == 0) {
+ return 0;
+ }
+
+ if (channelIndex == kAllChannels) {
+ for (int i = 0; i < oldNumChannels; ++i) {
+ freeChannel(i);
+ }
+ _numActiveChannels = 0;
+ } else {
+ freeChannel(channelIndex);
+ --_numActiveChannels;
+ for (int i = channelIndex; i < oldNumChannels - 1; ++i) {
+ _channels[i] = _channels[i + 1];
+ if (i + 1 == _monitoredChannelIndex) {
+ _monitoredChannelIndex = i;
+ }
+ }
+ }
+
+ // NOTE: SSCI stops the DSP interrupt and frees the
+ // global decompression buffer here if there are no
+ // more active channels
+
+ return oldNumChannels;
+}
+
+int16 Audio32::getPosition(const int16 channelIndex) const {
+ Common::StackLock lock(_mutex);
+ if (channelIndex == kNoExistingChannel || _numActiveChannels == 0) {
+ return -1;
+ }
+
+ // NOTE: SSCI treats this as an unsigned short except for
+ // when the value is 65535, then it treats it as signed
+ int position = -1;
+ const uint32 now = g_sci->getTickCount();
+
+ // NOTE: The original engine also queried the audio driver to see whether
+ // it thought that there was audio playback occurring via driver opcode 9
+ if (channelIndex == kAllChannels) {
+ if (_pausedAtTick) {
+ position = _pausedAtTick - _startedAtTick;
+ } else {
+ position = now - _startedAtTick;
+ }
+ } else {
+ const AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.pausedAtTick) {
+ position = channel.pausedAtTick - channel.startedAtTick;
+ } else if (_pausedAtTick) {
+ position = _pausedAtTick - channel.startedAtTick;
+ } else {
+ position = now - channel.startedAtTick;
+ }
+ }
+
+ return MIN(position, 65534);
+}
+
+void Audio32::setLoop(const int16 channelIndex, const bool loop) {
+ Common::StackLock lock(_mutex);
+
+ if (channelIndex < 0 || channelIndex >= _numActiveChannels) {
+ return;
+ }
+
+ AudioChannel &channel = getChannel(channelIndex);
+ channel.loop = loop;
+}
+
+reg_t Audio32::kernelPlay(const bool autoPlay, const int argc, const reg_t *const argv) {
+ if (argc == 0) {
+ return make_reg(0, _numActiveChannels);
+ }
+
+ const int16 channelIndex = findChannelByArgs(argc, argv, 0, NULL_REG);
+ ResourceId resourceId;
+ bool loop;
+ int16 volume;
+ bool monitor = false;
+ reg_t soundNode = NULL_REG;
+
+ if (argc >= 5) {
+ resourceId = ResourceId(kResourceTypeAudio36, argv[0].toUint16(), argv[1].toUint16(), argv[2].toUint16(), argv[3].toUint16(), argv[4].toUint16());
+
+ if (argc < 6 || argv[5].toSint16() == 1) {
+ loop = false;
+ } else {
+ // NOTE: Uses -1 for infinite loop. Presumably the
+ // engine was supposed to allow counter loops at one
+ // point, but ended up only using loop as a boolean.
+ loop = (bool)argv[5].toSint16();
+ }
+
+ if (argc < 7 || argv[6].toSint16() < 0 || argv[6].toSint16() > Audio32::kMaxVolume) {
+ volume = Audio32::kMaxVolume;
+
+ if (argc >= 7) {
+ monitor = true;
+ }
+ } else {
+ volume = argv[6].toSint16();
+ }
+ } else {
+ resourceId = ResourceId(kResourceTypeAudio, argv[0].toUint16());
+
+ if (argc < 2 || argv[1].toSint16() == 1) {
+ loop = false;
+ } else {
+ loop = (bool)argv[1].toSint16();
+ }
+
+ // TODO: SCI3 uses the 0x80 bit as a flag to
+ // indicate "priority channel", but the volume is clamped
+ // in this call to 0x7F so that flag never makes it into
+ // the audio subsystem
+ if (argc < 3 || argv[2].toSint16() < 0 || argv[2].toSint16() > Audio32::kMaxVolume) {
+ volume = Audio32::kMaxVolume;
+
+ if (argc >= 3) {
+ monitor = true;
+ }
+ } else {
+ volume = argv[2].toSint16();
+ }
+
+ soundNode = argc == 4 ? argv[3] : NULL_REG;
+ }
+
+ return make_reg(0, play(channelIndex, resourceId, autoPlay, loop, volume, soundNode, monitor));
+}
+
+#pragma mark -
+#pragma mark Effects
+
+int16 Audio32::getVolume(const int16 channelIndex) const {
+ if (channelIndex < 0 || channelIndex >= _numActiveChannels) {
+ return _mixer->getChannelVolume(_handle) * kMaxVolume / Audio::Mixer::kMaxChannelVolume;
+ }
+
+ Common::StackLock lock(_mutex);
+ return getChannel(channelIndex).volume;
+}
+
+void Audio32::setVolume(const int16 channelIndex, int16 volume) {
+ volume = MIN<int16>(kMaxVolume, volume);
+ if (channelIndex == kAllChannels) {
+ ConfMan.setInt("sfx_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
+ ConfMan.setInt("speech_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
+ _mixer->setChannelVolume(_handle, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
+ g_engine->syncSoundSettings();
+ } else if (channelIndex != kNoExistingChannel) {
+ Common::StackLock lock(_mutex);
+ getChannel(channelIndex).volume = volume;
+ }
+}
+
+bool Audio32::fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade) {
+ Common::StackLock lock(_mutex);
+
+ if (channelIndex < 0 || channelIndex >= _numActiveChannels) {
+ return false;
+ }
+
+ AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.id.getType() != kResourceTypeAudio || channel.volume == targetVolume) {
+ return false;
+ }
+
+ if (steps && speed) {
+ channel.fadeStartTick = g_sci->getTickCount();
+ channel.fadeStartVolume = channel.volume;
+ channel.fadeTargetVolume = targetVolume;
+ channel.fadeDuration = speed * steps;
+ channel.stopChannelOnFade = stopAfterFade;
+ } else {
+ setVolume(channelIndex, targetVolume);
+ }
+
+ return true;
+}
+
+bool Audio32::processFade(const int16 channelIndex) {
+ Common::StackLock lock(_mutex);
+ AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.fadeStartTick) {
+ const uint32 fadeElapsed = g_sci->getTickCount() - channel.fadeStartTick;
+ if (fadeElapsed > channel.fadeDuration) {
+ channel.fadeStartTick = 0;
+ if (channel.stopChannelOnFade) {
+ stop(channelIndex);
+ return true;
+ } else {
+ setVolume(channelIndex, channel.fadeTargetVolume);
+ }
+ return false;
+ }
+
+ int volume;
+ if (channel.fadeStartVolume > channel.fadeTargetVolume) {
+ volume = channel.fadeStartVolume - fadeElapsed * (channel.fadeStartVolume - channel.fadeTargetVolume) / channel.fadeDuration;
+ } else {
+ volume = channel.fadeStartVolume + fadeElapsed * (channel.fadeTargetVolume - channel.fadeStartVolume) / channel.fadeDuration;
+ }
+
+ setVolume(channelIndex, volume);
+ return false;
+ }
+
+ return false;
+}
+
+#pragma mark -
+#pragma mark Signal monitoring
+
+bool Audio32::hasSignal() const {
+ Common::StackLock lock(_mutex);
+
+ if (_monitoredChannelIndex == -1) {
+ return false;
+ }
+
+ const Audio::st_sample_t *buffer = _monitoredBuffer;
+ const Audio::st_sample_t *const end = _monitoredBuffer + _numMonitoredSamples;
+
+ while (buffer != end) {
+ const Audio::st_sample_t sample = *buffer++;
+ if (sample > 1280 || sample < -1280) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h
new file mode 100644
index 0000000000..a9905ab6bf
--- /dev/null
+++ b/engines/sci/sound/audio32.h
@@ -0,0 +1,591 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_AUDIO32_H
+#define SCI_AUDIO32_H
+#include "audio/audiostream.h" // for AudioStream, SeekableAudioStream (...
+#include "audio/mixer.h" // for Mixer, SoundHandle
+#include "audio/rate.h" // for Audio::st_volume_t, RateConverter
+#include "common/array.h" // for Array
+#include "common/mutex.h" // for StackLock, Mutex
+#include "common/scummsys.h" // for int16, uint8, uint32, uint16
+#include "engines/sci/resource.h" // for ResourceId
+#include "sci/engine/vm_types.h" // for reg_t, NULL_REG
+#include "sci/video/robot_decoder.h" // for RobotAudioStream
+
+namespace Sci {
+#pragma mark AudioChannel
+
+/**
+ * An audio channel used by the software SCI mixer.
+ */
+struct AudioChannel {
+ /**
+ * The ID of the resource loaded into this channel.
+ */
+ ResourceId id;
+
+ /**
+ * The resource loaded into this channel.
+ */
+ Resource *resource;
+
+ /**
+ * Data stream containing the raw audio for the channel.
+ */
+ Common::SeekableReadStream *resourceStream;
+
+ /**
+ * The audio stream loaded into this channel. Can cast
+ * to `SeekableAudioStream` for normal channels and
+ * `RobotAudioStream` for robot channels.
+ */
+ Audio::AudioStream *stream;
+
+ /**
+ * The converter used to transform and merge the input
+ * stream into the mixer's output buffer.
+ */
+ Audio::RateConverter *converter;
+
+ /**
+ * Duration of the channel, in ticks.
+ */
+ uint32 duration;
+
+ /**
+ * The tick when the channel was started.
+ */
+ uint32 startedAtTick;
+
+ /**
+ * The tick when the channel was paused.
+ */
+ uint32 pausedAtTick;
+
+ /**
+ * Whether or not the audio in this channel should loop
+ * infinitely.
+ */
+ bool loop;
+
+ /**
+ * The time, in ticks, that the channel fade began.
+ * If 0, the channel is not being faded.
+ */
+ uint32 fadeStartTick;
+
+ /**
+ * The start volume of a fade.
+ */
+ int fadeStartVolume;
+
+ /**
+ * The total length of the fade, in ticks.
+ */
+ uint32 fadeDuration;
+
+ /**
+ * The end volume of a fade.
+ */
+ int fadeTargetVolume;
+
+ /**
+ * Whether or not the channel should be stopped and
+ * freed when the fade is complete.
+ */
+ bool stopChannelOnFade;
+
+ /**
+ * Whether or not this channel contains a Robot
+ * audio block.
+ */
+ bool robot;
+
+ /**
+ * For digital sound effects, the related VM
+ * Sound::nodePtr object for the sound.
+ */
+ reg_t soundNode;
+
+ /**
+ * The playback volume, from 1 to 127 inclusive.
+ */
+ int volume;
+
+ /**
+ * The amount to pan to the right, from 0 to 100.
+ * 50 is centered, -1 is not panned.
+ */
+ int pan;
+};
+
+/**
+ * Special audio channel indexes used to select a channel
+ * for digital audio playback.
+ */
+enum AudioChannelIndex {
+ kRobotChannel = -3,
+ kNoExistingChannel = -2,
+ kAllChannels = -1
+};
+
+/**
+ * Audio32 acts as a permanent audio stream into the system
+ * mixer and provides digital audio services for the SCI32
+ * engine, since the system mixer does not support all the
+ * features of SCI.
+ */
+class Audio32 : public Audio::AudioStream {
+public:
+ Audio32(ResourceManager *resMan);
+ ~Audio32();
+
+private:
+ ResourceManager *_resMan;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _handle;
+ Common::Mutex _mutex;
+
+ enum {
+ /**
+ * The maximum channel volume.
+ */
+ kMaxVolume = 127
+ };
+
+#pragma mark -
+#pragma mark AudioStream implementation
+public:
+ int readBuffer(Audio::st_sample_t *buffer, const int numSamples);
+ bool isStereo() const { return true; }
+ int getRate() const { return _mixer->getOutputRate(); }
+ bool endOfData() const { return _numActiveChannels == 0; }
+ bool endOfStream() const { return false; }
+
+private:
+ /**
+ * Mixes audio from the given source stream into the
+ * target buffer using the given rate converter.
+ */
+ int writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop);
+
+#pragma mark -
+#pragma mark Channel management
+public:
+ /**
+ * Gets the number of currently active channels.
+ */
+ inline uint8 getNumActiveChannels() const {
+ Common::StackLock lock(_mutex);
+ return _numActiveChannels;
+ }
+
+ /**
+ * Finds a channel that is already configured for the
+ * given audio sample.
+ *
+ * @param startIndex The location of the audio resource
+ * information in the arguments list.
+ */
+ int16 findChannelByArgs(int argc, const reg_t *argv, const int startIndex, const reg_t soundNode) const;
+
+ /**
+ * Finds a channel that is already configured for the
+ * given audio sample.
+ */
+ int16 findChannelById(const ResourceId resourceId, const reg_t soundNode = NULL_REG) const;
+
+private:
+ typedef Common::Array<Resource *> UnlockList;
+
+ /**
+ * The audio channels.
+ */
+ Common::Array<AudioChannel> _channels;
+
+ /**
+ * The number of active audio channels in the mixer.
+ * Being active is not the same as playing; active
+ * channels may be paused.
+ */
+ uint8 _numActiveChannels;
+
+ /**
+ * Whether or not we are in the audio thread.
+ *
+ * This flag is used instead of passing a parameter to
+ * `freeUnusedChannels` because a parameter would
+ * require forwarding through the public method `stop`,
+ * and there is not currently any reason for this
+ * implementation detail to be exposed.
+ */
+ bool _inAudioThread;
+
+ /**
+ * The list of resources from freed channels that need
+ * to be unlocked from the main thread.
+ */
+ UnlockList _resourcesToUnlock;
+
+ /**
+ * Gets the audio channel at the given index.
+ */
+ inline AudioChannel &getChannel(const int16 channelIndex) {
+ Common::StackLock lock(_mutex);
+ assert(channelIndex >= 0 && channelIndex < _numActiveChannels);
+ return _channels[channelIndex];
+ }
+
+ /**
+ * Gets the audio channel at the given index.
+ */
+ inline const AudioChannel &getChannel(const int16 channelIndex) const {
+ Common::StackLock lock(_mutex);
+ assert(channelIndex >= 0 && channelIndex < _numActiveChannels);
+ return _channels[channelIndex];
+ }
+
+ /**
+ * Frees all non-looping channels that have reached the
+ * end of their stream.
+ */
+ void freeUnusedChannels();
+
+ /**
+ * Frees resources allocated to the given channel.
+ */
+ void freeChannel(const int16 channelIndex);
+
+ /**
+ * Unlocks all resources that were freed by the audio
+ * thread.
+ */
+ void unlockResources();
+
+#pragma mark -
+#pragma mark Script compatibility
+public:
+ /**
+ * Gets the (fake) sample rate of the hardware DAC.
+ * For script compatibility only.
+ */
+ inline uint16 getSampleRate() const {
+ return _globalSampleRate;
+ }
+
+ /**
+ * Sets the (fake) sample rate of the hardware DAC.
+ * For script compatibility only.
+ */
+ void setSampleRate(uint16 rate);
+
+ /**
+ * Gets the (fake) bit depth of the hardware DAC.
+ * For script compatibility only.
+ */
+ inline uint8 getBitDepth() const {
+ return _globalBitDepth;
+ }
+
+ /**
+ * Sets the (fake) sample rate of the hardware DAC.
+ * For script compatibility only.
+ */
+ void setBitDepth(uint8 depth);
+
+ /**
+ * Gets the (fake) number of output (speaker) channels
+ * of the hardware DAC. For script compatibility only.
+ */
+ inline uint8 getNumOutputChannels() const {
+ return _globalNumOutputChannels;
+ }
+
+ /**
+ * Sets the (fake) number of output (speaker) channels
+ * of the hardware DAC. For script compatibility only.
+ */
+ void setNumOutputChannels(int16 numChannels);
+
+ /**
+ * Gets the (fake) number of preloaded channels.
+ * For script compatibility only.
+ */
+ inline uint8 getPreload() const {
+ return _preload;
+ }
+
+ /**
+ * Sets the (fake) number of preloaded channels.
+ * For script compatibility only.
+ */
+ inline void setPreload(uint8 preload) {
+ _preload = preload;
+ }
+
+private:
+ /**
+ * The hardware DAC sample rate. Stored only for script
+ * compatibility.
+ */
+ uint16 _globalSampleRate;
+
+ /**
+ * The maximum allowed sample rate of the system mixer.
+ * Stored only for script compatibility.
+ */
+ uint16 _maxAllowedSampleRate;
+
+ /**
+ * The hardware DAC bit depth. Stored only for script
+ * compatibility.
+ */
+ uint8 _globalBitDepth;
+
+ /**
+ * The maximum allowed bit depth of the system mixer.
+ * Stored only for script compatibility.
+ */
+ uint8 _maxAllowedBitDepth;
+
+ /**
+ * The hardware DAC output (speaker) channel
+ * configuration. Stored only for script compatibility.
+ */
+ uint8 _globalNumOutputChannels;
+
+ /**
+ * The maximum allowed number of output (speaker)
+ * channels of the system mixer. Stored only for script
+ * compatibility.
+ */
+ uint8 _maxAllowedOutputChannels;
+
+ /**
+ * The number of audio channels that should have their
+ * data preloaded into memory instead of streaming from
+ * disk.
+ * 1 = all channels, 2 = 2nd active channel and above,
+ * etc.
+ * Stored only for script compatibility.
+ */
+ uint8 _preload;
+
+#pragma mark -
+#pragma mark Robot
+public:
+ bool playRobotAudio(const RobotAudioStream::RobotAudioPacket &packet);
+ bool queryRobotAudio(RobotAudioStream::StreamState &outStatus) const;
+ bool finishRobotAudio();
+ bool stopRobotAudio();
+
+private:
+ /**
+ * Finds a channel that is configured for robot playback.
+ */
+ int16 findRobotChannel() const;
+
+ /**
+ * When true, channels marked as robot audio will not be
+ * played.
+ */
+ bool _robotAudioPaused;
+
+#pragma mark -
+#pragma mark Playback
+public:
+ /**
+ * Starts or resumes playback of an audio channel.
+ */
+ uint16 play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor);
+
+ /**
+ * Resumes playback of a paused audio channel, or of
+ * the entire audio player.
+ */
+ bool resume(const int16 channelIndex);
+ bool resume(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return resume(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Pauses an audio channel, or the entire audio player.
+ */
+ bool pause(const int16 channelIndex);
+ bool pause(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return pause(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Stops and unloads an audio channel, or the entire
+ * audio player.
+ */
+ int16 stop(const int16 channelIndex);
+ int16 stop(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return stop(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Returns the playback position for the given channel
+ * number, in ticks.
+ */
+ int16 getPosition(const int16 channelIndex) const;
+ int16 getPosition(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return getPosition(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Sets whether or not the given channel should loop.
+ */
+ void setLoop(const int16 channelIndex, const bool loop);
+ void setLoop(const ResourceId resourceId, const reg_t soundNode, const bool loop) {
+ Common::StackLock lock(_mutex);
+ setLoop(findChannelById(resourceId, soundNode), loop);
+ }
+
+ reg_t kernelPlay(const bool autoPlay, const int argc, const reg_t *const argv);
+
+private:
+ /**
+ * The tick when audio was globally paused.
+ */
+ uint32 _pausedAtTick;
+
+ /**
+ * The tick when audio was globally started.
+ */
+ uint32 _startedAtTick;
+
+#pragma mark -
+#pragma mark Effects
+public:
+ /**
+ * Gets the volume for a given channel. Passing
+ * `kAllChannels` will get the global volume.
+ */
+ int16 getVolume(const int16 channelIndex) const;
+ int16 getVolume(const ResourceId resourceId, const reg_t soundNode) const {
+ Common::StackLock lock(_mutex);
+ return getVolume(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Sets the volume of an audio channel. Passing
+ * `kAllChannels` will set the global volume.
+ */
+ void setVolume(const int16 channelIndex, int16 volume);
+ void setVolume(const ResourceId resourceId, const reg_t soundNode, const int16 volume) {
+ Common::StackLock lock(_mutex);
+ setVolume(findChannelById(resourceId, soundNode), volume);
+ }
+
+ /**
+ * Initiate an immediate fade of the given channel.
+ */
+ bool fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade);
+ bool fadeChannel(const ResourceId resourceId, const reg_t soundNode, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade) {
+ Common::StackLock lock(_mutex);
+ return fadeChannel(findChannelById(resourceId, soundNode), targetVolume, speed, steps, stopAfterFade);
+ }
+
+ /**
+ * Gets whether attenuated mixing mode is active.
+ */
+ inline bool getAttenuatedMixing() const {
+ return _attenuatedMixing;
+ }
+
+ /**
+ * Sets the attenuated mixing mode.
+ */
+ void setAttenuatedMixing(bool attenuated) {
+ Common::StackLock lock(_mutex);
+ _attenuatedMixing = attenuated;
+ }
+
+private:
+ /**
+ * If true, audio will be mixed by reducing the target
+ * buffer by half every time a new channel is mixed in.
+ * The final channel is not attenuated.
+ */
+ bool _attenuatedMixing;
+
+ /**
+ * When true, a modified attenuation algorithm is used
+ * (`A/4 + B`) instead of standard linear attenuation
+ * (`A/2 + B/2`).
+ */
+ bool _useModifiedAttenuation;
+
+ /**
+ * Processes an audio fade for the given channel.
+ *
+ * @returns true if the fade was completed and the
+ * channel was stopped.
+ */
+ bool processFade(const int16 channelIndex);
+
+#pragma mark -
+#pragma mark Signal monitoring
+public:
+ /**
+ * Returns whether the currently monitored audio channel
+ * contains any signal within the next audio frame.
+ */
+ bool hasSignal() const;
+
+private:
+ /**
+ * The index of the channel being monitored for signal,
+ * or -1 if no channel is monitored. When a channel is
+ * monitored, it also causes the engine to play only the
+ * monitored channel.
+ */
+ int16 _monitoredChannelIndex;
+
+ /**
+ * The data buffer holding decompressed audio data for
+ * the channel that will be monitored for an audio
+ * signal.
+ */
+ Audio::st_sample_t *_monitoredBuffer;
+
+ /**
+ * The size of the buffer, in bytes.
+ */
+ size_t _monitoredBufferSize;
+
+ /**
+ * The number of valid audio samples in the signal
+ * monitoring buffer.
+ */
+ int _numMonitoredSamples;
+};
+
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/sound/decoders/sol.cpp b/engines/sci/sound/decoders/sol.cpp
new file mode 100644
index 0000000000..ee1ba35406
--- /dev/null
+++ b/engines/sci/sound/decoders/sol.cpp
@@ -0,0 +1,286 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/audiostream.h"
+#include "audio/rate.h"
+#include "audio/decoders/raw.h"
+#include "common/substream.h"
+#include "common/util.h"
+#include "engines/sci/sci.h"
+#include "engines/sci/sound/decoders/sol.h"
+
+namespace Sci {
+
+// Note that the 16-bit version is also used in coktelvideo.cpp
+static const uint16 tableDPCM16[128] = {
+ 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080,
+ 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120,
+ 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0,
+ 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230,
+ 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280,
+ 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0,
+ 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320,
+ 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370,
+ 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0,
+ 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480,
+ 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700,
+ 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00,
+ 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
+};
+
+static const byte tableDPCM8[8] = { 0, 1, 2, 3, 6, 10, 15, 21 };
+
+/**
+ * Decompresses 16-bit DPCM compressed audio. Each byte read
+ * outputs one sample into the decompression buffer.
+ */
+static void deDPCM16(int16 *out, Common::ReadStream &audioStream, const uint32 numBytes, int16 &sample) {
+ for (uint32 i = 0; i < numBytes; ++i) {
+ const uint8 delta = audioStream.readByte();
+ if (delta & 0x80) {
+ sample -= tableDPCM16[delta & 0x7f];
+ } else {
+ sample += tableDPCM16[delta];
+ }
+ sample = CLIP<int16>(sample, -32768, 32767);
+ *out++ = TO_LE_16(sample);
+ }
+}
+
+void deDPCM16(int16 *out, const byte *in, const uint32 numBytes, int16 &sample) {
+ for (uint32 i = 0; i < numBytes; ++i) {
+ const uint8 delta = *in++;
+ if (delta & 0x80) {
+ sample -= tableDPCM16[delta & 0x7f];
+ } else {
+ sample += tableDPCM16[delta];
+ }
+ sample = CLIP<int16>(sample, -32768, 32767);
+ *out++ = TO_LE_16(sample);
+ }
+}
+
+/**
+ * Decompresses one half of an 8-bit DPCM compressed audio
+ * byte.
+ */
+static void deDPCM8Nibble(int16 *out, uint8 &sample, uint8 delta) {
+ const uint8 lastSample = sample;
+ if (delta & 8) {
+ sample -= tableDPCM8[delta & 7];
+ } else {
+ sample += tableDPCM8[delta & 7];
+ }
+ sample = CLIP<byte>(sample, 0, 255);
+ *out = ((lastSample + sample) << 7) ^ 0x8000;
+}
+
+/**
+ * Decompresses 8-bit DPCM compressed audio. Each byte read
+ * outputs two samples into the decompression buffer.
+ */
+static void deDPCM8(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, uint8 &sample) {
+ for (uint32 i = 0; i < numBytes; ++i) {
+ const uint8 delta = audioStream.readByte();
+ deDPCM8Nibble(out++, sample, delta >> 4);
+ deDPCM8Nibble(out++, sample, delta & 0xf);
+ }
+}
+
+# pragma mark -
+
+template<bool STEREO, bool S16BIT>
+SOLStream<STEREO, S16BIT>::SOLStream(Common::SeekableReadStream *stream, const DisposeAfterUse::Flag disposeAfterUse, const int32 dataOffset, const uint16 sampleRate, const int32 rawDataSize) :
+ _stream(stream, disposeAfterUse),
+ _dataOffset(dataOffset),
+ _sampleRate(sampleRate),
+ // SSCI aligns the size of SOL data to 32 bits
+ _rawDataSize(rawDataSize & ~3) {
+ // TODO: This is not valid for stereo SOL files, which
+ // have interleaved L/R compression so need to store the
+ // carried values for each channel separately. See
+ // 60900.aud from Lighthouse for an example stereo file
+ if (S16BIT) {
+ _dpcmCarry16 = 0;
+ } else {
+ _dpcmCarry8 = 0x80;
+ }
+
+ const uint8 compressionRatio = 2;
+ const uint8 numChannels = STEREO ? 2 : 1;
+ const uint8 bytesPerSample = S16BIT ? 2 : 1;
+ _length = Audio::Timestamp((_rawDataSize * compressionRatio * 1000) / (_sampleRate * numChannels * bytesPerSample), 60);
+ }
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::seek(const Audio::Timestamp &where) {
+ if (where != 0) {
+ // In order to seek in compressed SOL files, all
+ // previous bytes must be known since it uses
+ // differential compression. Therefore, only seeking
+ // to the beginning is supported now (SSCI does not
+ // offer seeking anyway)
+ return false;
+ }
+
+ if (S16BIT) {
+ _dpcmCarry16 = 0;
+ } else {
+ _dpcmCarry8 = 0x80;
+ }
+
+ return _stream->seek(_dataOffset, SEEK_SET);
+}
+
+template <bool STEREO, bool S16BIT>
+Audio::Timestamp SOLStream<STEREO, S16BIT>::getLength() const {
+ return _length;
+}
+
+template <bool STEREO, bool S16BIT>
+int SOLStream<STEREO, S16BIT>::readBuffer(int16 *buffer, const int numSamples) {
+ // Reading an odd number of 8-bit samples will result in a loss of samples
+ // since one byte represents two samples and we do not store the second
+ // nibble in this case; it should never happen in reality
+ assert(S16BIT || (numSamples % 2) == 0);
+
+ const int samplesPerByte = S16BIT ? 1 : 2;
+
+ int32 bytesToRead = numSamples / samplesPerByte;
+ if (_stream->pos() + bytesToRead > _rawDataSize) {
+ bytesToRead = _rawDataSize - _stream->pos();
+ }
+
+ if (S16BIT) {
+ deDPCM16(buffer, *_stream, bytesToRead, _dpcmCarry16);
+ } else {
+ deDPCM8(buffer, *_stream, bytesToRead, _dpcmCarry8);
+ }
+
+ const int samplesRead = bytesToRead * samplesPerByte;
+ return samplesRead;
+}
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::isStereo() const {
+ return STEREO;
+}
+
+template <bool STEREO, bool S16BIT>
+int SOLStream<STEREO, S16BIT>::getRate() const {
+ return _sampleRate;
+}
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::endOfData() const {
+ return _stream->eos() || _stream->pos() >= _rawDataSize;
+}
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::rewind() {
+ return seek(0);
+}
+
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+
+ // TODO: Might not be necessary? Makes seeking work, but
+ // not sure if audio is ever actually seeked in SSCI.
+ const int32 initialPosition = stream->pos();
+
+ byte header[6];
+ if (stream->read(header, sizeof(header)) != sizeof(header)) {
+ return nullptr;
+ }
+
+ if (header[0] != 0x8d || READ_BE_UINT32(header + 2) != MKTAG('S', 'O', 'L', 0)) {
+ return nullptr;
+ }
+
+ const uint8 headerSize = header[1];
+ const uint16 sampleRate = stream->readUint16LE();
+ const byte flags = stream->readByte();
+ const uint32 dataSize = stream->readUint32LE();
+
+ if (flags & kCompressed) {
+ if (flags & kStereo && flags & k16Bit) {
+ return new SOLStream<true, true>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ } else if (flags & kStereo) {
+ return new SOLStream<true, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ } else if (flags & k16Bit) {
+ return new SOLStream<false, true>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ } else {
+ return new SOLStream<false, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ }
+ }
+
+ byte rawFlags = Audio::FLAG_LITTLE_ENDIAN;
+ if (flags & k16Bit) {
+ rawFlags |= Audio::FLAG_16BITS;
+ } else {
+ rawFlags |= Audio::FLAG_UNSIGNED;
+ }
+
+ if (flags & kStereo) {
+ rawFlags |= Audio::FLAG_STEREO;
+ }
+
+ return Audio::makeRawStream(new Common::SeekableSubReadStream(stream, initialPosition + headerSize, initialPosition + headerSize + dataSize, disposeAfterUse), sampleRate, rawFlags, disposeAfterUse);
+}
+
+// TODO: This needs to be removed when resource manager is fixed
+// to not split audio into two parts
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStream, Common::SeekableReadStream *dataStream, DisposeAfterUse::Flag disposeAfterUse) {
+
+ if (headerStream->readUint32BE() != MKTAG('S', 'O', 'L', 0)) {
+ return nullptr;
+ }
+
+ const uint16 sampleRate = headerStream->readUint16LE();
+ const byte flags = headerStream->readByte();
+ const int32 dataSize = headerStream->readSint32LE();
+
+ if (flags & kCompressed) {
+ if (flags & kStereo && flags & k16Bit) {
+ return new SOLStream<true, true>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ } else if (flags & kStereo) {
+ return new SOLStream<true, false>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ } else if (flags & k16Bit) {
+ return new SOLStream<false, true>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ } else {
+ return new SOLStream<false, false>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ }
+ }
+
+ byte rawFlags = Audio::FLAG_LITTLE_ENDIAN;
+ if (flags & k16Bit) {
+ rawFlags |= Audio::FLAG_16BITS;
+ } else {
+ rawFlags |= Audio::FLAG_UNSIGNED;
+ }
+
+ if (flags & kStereo) {
+ rawFlags |= Audio::FLAG_STEREO;
+ }
+
+ return Audio::makeRawStream(dataStream, sampleRate, rawFlags, disposeAfterUse);
+}
+}
diff --git a/engines/sci/sound/decoders/sol.h b/engines/sci/sound/decoders/sol.h
new file mode 100644
index 0000000000..1046d0b213
--- /dev/null
+++ b/engines/sci/sound/decoders/sol.h
@@ -0,0 +1,89 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_SOUND_DECODERS_SOL_H
+#define SCI_SOUND_DECODERS_SOL_H
+#include "audio/audiostream.h"
+#include "common/stream.h"
+
+namespace Sci {
+
+enum SOLFlags {
+ kCompressed = 1,
+ k16Bit = 4,
+ kStereo = 16
+};
+
+template <bool STEREO, bool S16BIT>
+class SOLStream : public Audio::SeekableAudioStream {
+private:
+ /**
+ * Read stream containing possibly-compressed SOL audio.
+ */
+ Common::DisposablePtr<Common::SeekableReadStream> _stream;
+
+ /**
+ * Start offset of the audio data in the read stream.
+ */
+ int32 _dataOffset;
+
+ /**
+ * Sample rate of audio data.
+ */
+ uint16 _sampleRate;
+
+ /**
+ * The raw (possibly-compressed) size of audio data in
+ * the stream.
+ */
+ int32 _rawDataSize;
+
+ /**
+ * The last sample from the previous DPCM decode.
+ */
+ union {
+ int16 _dpcmCarry16;
+ uint8 _dpcmCarry8;
+ };
+
+ /**
+ * The calculated length of the stream.
+ */
+ Audio::Timestamp _length;
+
+ virtual bool seek(const Audio::Timestamp &where) override;
+ virtual Audio::Timestamp getLength() const override;
+ virtual int readBuffer(int16 *buffer, const int numSamples) override;
+ virtual bool isStereo() const override;
+ virtual int getRate() const override;
+ virtual bool endOfData() const override;
+ virtual bool rewind() override;
+
+public:
+ SOLStream(Common::SeekableReadStream *stream, const DisposeAfterUse::Flag disposeAfterUse, const int32 dataOffset, const uint16 sampleRate, const int32 rawDataSize);
+};
+
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
+
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStream, Common::SeekableReadStream *dataStream, DisposeAfterUse::Flag disposeAfterUse);
+}
+#endif
diff --git a/engines/sci/sound/drivers/amigamac.cpp b/engines/sci/sound/drivers/amigamac.cpp
index 5ce49086ca..f5daab726e 100644
--- a/engines/sci/sound/drivers/amigamac.cpp
+++ b/engines/sci/sound/drivers/amigamac.cpp
@@ -144,7 +144,7 @@ private:
void setEnvelope(Voice *channel, Envelope *envelope, int phase);
void setOutputFrac(int voice);
- int interpolate(int8 *samples, frac_t offset, bool isUnsigned);
+ int interpolate(int8 *samples, frac_t offset, uint32 maxOffset, bool isUnsigned);
void playInstrument(int16 *dest, Voice *channel, int count);
void changeInstrument(int channel, int instrument);
void stopChannel(int ch);
@@ -169,17 +169,18 @@ void MidiDriver_AmigaMac::setEnvelope(Voice *channel, Envelope *envelope, int ph
channel->velocity = envelope[phase - 1].target;
}
-int MidiDriver_AmigaMac::interpolate(int8 *samples, frac_t offset, bool isUnsigned) {
- int x = fracToInt(offset);
+int MidiDriver_AmigaMac::interpolate(int8 *samples, frac_t offset, uint32 maxOffset, bool isUnsigned) {
+ uint x = fracToInt(offset);
+ uint x2 = x == maxOffset ? 0 : x + 1;
if (isUnsigned) {
int s1 = (byte)samples[x] - 0x80;
- int s2 = (byte)samples[x + 1] - 0x80;
+ int s2 = (byte)samples[x2] - 0x80;
int diff = (s2 - s1) << 8;
return (s1 << 8) + fracToInt(diff * (offset & FRAC_LO_MASK));
}
- int diff = (samples[x + 1] - samples[x]) << 8;
+ int diff = (samples[x2] - samples[x]) << 8;
return (samples[x] << 8) + fracToInt(diff * (offset & FRAC_LO_MASK));
}
@@ -220,7 +221,7 @@ void MidiDriver_AmigaMac::playInstrument(int16 *dest, Voice *channel, int count)
amount = channel->envelope_samples;
for (i = 0; i < amount; i++) {
- dest[index++] = interpolate(samples, channel->offset, instrument->isUnsigned) * channel->velocity / 64 * channel->note_velocity * vol / (127 * 127);
+ dest[index++] = interpolate(samples, channel->offset, seg_end, instrument->isUnsigned) * channel->velocity / 64 * channel->note_velocity * vol / (127 * 127);
channel->offset += channel->rate;
}
@@ -497,7 +498,7 @@ MidiDriver_AmigaMac::InstrumentSample *MidiDriver_AmigaMac::readInstrumentSCI0(C
}
instrument->samples = (int8 *) malloc(size + 1);
- if (file.read(instrument->samples, size) < (unsigned int)size) {
+ if (file.read(instrument->samples, size) < (uint32)size) {
warning("Amiga/Mac driver: failed to read instrument samples");
free(instrument->samples);
delete instrument;
@@ -773,6 +774,7 @@ bool MidiDriver_AmigaMac::loadInstrumentsSCI0(Common::File &file) {
if (id < 0 || id > 255) {
warning("Amiga/Mac driver: Error: instrument ID out of bounds");
+ delete instrument;
return false;
}
diff --git a/engines/sci/sound/drivers/midi.cpp b/engines/sci/sound/drivers/midi.cpp
index aa464cdc19..badbe663ca 100644
--- a/engines/sci/sound/drivers/midi.cpp
+++ b/engines/sci/sound/drivers/midi.cpp
@@ -27,7 +27,7 @@
#include "common/memstream.h"
#include "common/system.h"
-#include "audio/fmopl.h"
+#include "audio/mididrv.h"
#include "sci/resource.h"
#include "sci/engine/features.h"
@@ -1053,7 +1053,7 @@ int MidiPlayer_Midi::open(ResourceManager *resMan) {
void MidiPlayer_Midi::close() {
if (_isMt32) {
// Send goodbye message
- sendMt32SysEx(0x200000, _goodbyeMsg, 20);
+ sendMt32SysEx(0x200000, _goodbyeMsg, 20, true);
}
_driver->close();
@@ -1069,8 +1069,8 @@ void MidiPlayer_Midi::sysEx(const byte *msg, uint16 length) {
if (_isMt32)
delay += 40;
- g_system->delayMillis(delay);
g_system->updateScreen();
+ g_sci->sleep(delay);
}
byte MidiPlayer_Midi::getPlayId() const {
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
index 5a11ac386a..487d30e840 100644
--- a/engines/sci/sound/music.cpp
+++ b/engines/sci/sound/music.cpp
@@ -212,6 +212,13 @@ void SciMusic::clearPlayList() {
void SciMusic::pauseAll(bool pause) {
const MusicList::iterator end = _playList.end();
for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
+#ifdef ENABLE_SCI32
+ // The entire DAC will have been paused by the caller;
+ // do not pause the individual samples too
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && (*i)->isSample) {
+ continue;
+ }
+#endif
soundToggle(*i, pause);
}
}
@@ -347,12 +354,14 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) {
pSnd->pStreamAud = Audio::makeRawStream(channelData + track->digitalSampleStart,
track->digitalSampleSize - track->digitalSampleStart - endPart,
track->digitalSampleRate, flags, DisposeAfterUse::NO);
+ assert(pSnd->pStreamAud);
delete pSnd->pLoopStream;
pSnd->pLoopStream = 0;
pSnd->soundType = Audio::Mixer::kSFXSoundType;
pSnd->hCurrentAud = Audio::SoundHandle();
pSnd->playBed = false;
pSnd->overridePriority = false;
+ pSnd->isSample = true;
} else {
// play MIDI track
Common::StackLock lock(_mutex);
@@ -472,7 +481,16 @@ void SciMusic::soundPlay(MusicEntry *pSnd) {
}
}
- if (pSnd->pStreamAud) {
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+
+ g_sci->_audio32->play(kNoExistingChannel, ResourceId(kResourceTypeAudio, pSnd->resourceId), true, pSnd->loop != 0 && pSnd->loop != 1, pSnd->volume, pSnd->soundObj, false);
+
+ return;
+ } else
+#endif
if (!_pMixer->isSoundHandleActive(pSnd->hCurrentAud)) {
if ((_currentlyPlayingSample) && (_pMixer->isSoundHandleActive(_currentlyPlayingSample->hCurrentAud))) {
// Another sample is already playing, we have to stop that one
@@ -550,10 +568,18 @@ void SciMusic::soundStop(MusicEntry *pSnd) {
pSnd->status = kSoundStopped;
if (_soundVersion <= SCI_VERSION_0_LATE)
pSnd->isQueued = false;
- if (pSnd->pStreamAud) {
- if (_currentlyPlayingSample == pSnd)
- _currentlyPlayingSample = NULL;
- _pMixer->stopHandle(pSnd->hCurrentAud);
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ } else {
+#endif
+ if (_currentlyPlayingSample == pSnd)
+ _currentlyPlayingSample = NULL;
+ _pMixer->stopHandle(pSnd->hCurrentAud);
+#ifdef ENABLE_SCI32
+ }
+#endif
}
if (pSnd->pMidiParser) {
@@ -572,9 +598,12 @@ void SciMusic::soundStop(MusicEntry *pSnd) {
void SciMusic::soundSetVolume(MusicEntry *pSnd, byte volume) {
assert(volume <= MUSIC_VOLUME_MAX);
- if (pSnd->pStreamAud) {
- // we simply ignore volume changes for samples, because sierra sci also
- // doesn't support volume for samples via kDoSound
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->setVolume(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj, volume);
+ }
+#endif
} else if (pSnd->pMidiParser) {
Common::StackLock lock(_mutex);
pSnd->pMidiParser->mainThreadBegin();
@@ -614,16 +643,25 @@ void SciMusic::soundKill(MusicEntry *pSnd) {
_mutex.unlock();
- if (pSnd->pStreamAud) {
- if (_currentlyPlayingSample == pSnd) {
- // Forget about this sound, in case it was currently playing
- _currentlyPlayingSample = NULL;
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ } else {
+#endif
+ if (_currentlyPlayingSample == pSnd) {
+ // Forget about this sound, in case it was currently playing
+ _currentlyPlayingSample = NULL;
+ }
+ _pMixer->stopHandle(pSnd->hCurrentAud);
+#ifdef ENABLE_SCI32
}
- _pMixer->stopHandle(pSnd->hCurrentAud);
+#endif
delete pSnd->pStreamAud;
pSnd->pStreamAud = NULL;
delete pSnd->pLoopStream;
pSnd->pLoopStream = 0;
+ pSnd->isSample = false;
}
_mutex.lock();
@@ -685,6 +723,18 @@ void SciMusic::soundResume(MusicEntry *pSnd) {
}
void SciMusic::soundToggle(MusicEntry *pSnd, bool pause) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && pSnd->isSample) {
+ if (pause) {
+ g_sci->_audio32->pause(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ } else {
+ g_sci->_audio32->resume(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ }
+
+ return;
+ }
+#endif
+
if (pause)
soundPause(pSnd);
else
@@ -813,6 +863,7 @@ MusicEntry::MusicEntry() {
pStreamAud = 0;
pLoopStream = 0;
pMidiParser = 0;
+ isSample = false;
for (int i = 0; i < 16; ++i) {
_usedChannels[i] = 0xFF;
diff --git a/engines/sci/sound/music.h b/engines/sci/sound/music.h
index a610f32d89..3a6de81c49 100644
--- a/engines/sci/sound/music.h
+++ b/engines/sci/sound/music.h
@@ -27,11 +27,18 @@
#include "common/mutex.h"
#include "audio/mixer.h"
-#include "audio/audiostream.h"
#include "sci/sci.h"
#include "sci/resource.h"
#include "sci/sound/drivers/mididriver.h"
+#ifdef ENABLE_SCI32
+#include "sci/sound/audio32.h"
+#endif
+
+namespace Audio {
+class LoopingAudioStream;
+class RewindableAudioStream;
+}
namespace Sci {
@@ -119,6 +126,7 @@ public:
Audio::RewindableAudioStream *pStreamAud;
Audio::LoopingAudioStream *pLoopStream;
Audio::SoundHandle hCurrentAud;
+ bool isSample;
public:
MusicEntry();
diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp
index ee5903fda2..b9a764c93a 100644
--- a/engines/sci/sound/soundcmd.cpp
+++ b/engines/sci/sound/soundcmd.cpp
@@ -21,6 +21,9 @@
*/
#include "common/config-manager.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "sci/resource.h"
#include "sci/sound/audio.h"
#include "sci/sound/music.h"
#include "sci/sound/soundcmd.h"
@@ -44,7 +47,7 @@ SoundCommandParser::SoundCommandParser(ResourceManager *resMan, SegManager *segM
// resource number, but it's totally unrelated to the menu music).
// The GK1 demo (very late SCI1.1) does the same thing
// TODO: Check the QFG4 demo
- _useDigitalSFX = (getSciVersion() >= SCI_VERSION_2 || g_sci->getGameId() == GID_GK1 || ConfMan.getBool("prefer_digitalsfx"));
+ _useDigitalSFX = (getSciVersion() >= SCI_VERSION_2 || g_sci->getGameId() == GID_GK1DEMO || ConfMan.getBool("prefer_digitalsfx"));
_music = new SciMusic(_soundVersion, _useDigitalSFX);
_music->init();
@@ -95,12 +98,21 @@ void SoundCommandParser::initSoundResource(MusicEntry *newSound) {
// user wants the digital version.
if (_useDigitalSFX || !newSound->soundRes) {
int sampleLen;
- newSound->pStreamAud = _audio->getAudioStream(newSound->resourceId, 65535, &sampleLen);
- newSound->soundType = Audio::Mixer::kSFXSoundType;
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ newSound->isSample = g_sci->getResMan()->testResource(ResourceId(kResourceTypeAudio, newSound->resourceId));
+ } else {
+#endif
+ newSound->pStreamAud = _audio->getAudioStream(newSound->resourceId, 65535, &sampleLen);
+ newSound->soundType = Audio::Mixer::kSFXSoundType;
+ newSound->isSample = newSound->pStreamAud != nullptr;
+#ifdef ENABLE_SCI32
+ }
+#endif
}
}
- if (!newSound->pStreamAud && newSound->soundRes)
+ if (!newSound->isSample && newSound->soundRes)
_music->soundInitSnd(newSound);
}
@@ -116,6 +128,7 @@ void SoundCommandParser::processInitSound(reg_t obj) {
newSound->resourceId = resourceId;
newSound->soundObj = obj;
newSound->loop = readSelectorValue(_segMan, obj, SELECTOR(loop));
+ newSound->overridePriority = false;
if (_soundVersion <= SCI_VERSION_0_LATE)
newSound->priority = readSelectorValue(_segMan, obj, SELECTOR(priority));
else
@@ -131,7 +144,7 @@ void SoundCommandParser::processInitSound(reg_t obj) {
_music->pushBackSlot(newSound);
- if (newSound->soundRes || newSound->pStreamAud) {
+ if (newSound->soundRes || newSound->isSample) {
// Notify the engine
if (_soundVersion <= SCI_VERSION_0_LATE)
writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundInitialized);
@@ -212,17 +225,6 @@ void SoundCommandParser::processPlaySound(reg_t obj, bool playBed) {
musicSlot->fadeStep = 0;
}
-reg_t SoundCommandParser::kDoSoundRestore(int argc, reg_t *argv, reg_t acc) {
- // Called after loading, to restore the playlist
- // We don't really use or need this
- return acc;
-}
-
-reg_t SoundCommandParser::kDoSoundDummy(int argc, reg_t *argv, reg_t acc) {
- warning("cmdDummy invoked"); // not supposed to occur
- return acc;
-}
-
reg_t SoundCommandParser::kDoSoundDispose(int argc, reg_t *argv, reg_t acc) {
debugC(kDebugLevelSound, "kDoSound(dispose): %04x:%04x", PRINT_REG(argv[0]));
processDisposeSound(argv[0]);
@@ -311,10 +313,22 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {
}
reg_t obj = argv[0];
- uint16 value = argc > 1 ? argv[1].toUint16() : 0;
- if (!obj.getSegment()) { // pause the whole playlist
- _music->pauseAll(value);
- } else { // pause a playlist slot
+ const bool shouldPause = argc > 1 ? argv[1].toUint16() : false;
+ if (
+ (_soundVersion < SCI_VERSION_2_1_EARLY && !obj.getSegment()) ||
+ (_soundVersion >= SCI_VERSION_2_1_EARLY && obj.isNull())
+ ) {
+ _music->pauseAll(shouldPause);
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ if (shouldPause) {
+ g_sci->_audio32->pause(kAllChannels);
+ } else {
+ g_sci->_audio32->resume(kAllChannels);
+ }
+ }
+#endif
+ } else {
MusicEntry *musicSlot = _music->getSlot(obj);
if (!musicSlot) {
// This happens quite frequently
@@ -322,7 +336,23 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {
return acc;
}
- _music->soundToggle(musicSlot, value);
+#ifdef ENABLE_SCI32
+ // NOTE: The original engine also expected a global
+ // "kernel call" flag to be true in order to perform
+ // this action, but the architecture of the ScummVM
+ // implementation is so different that it doesn't
+ // matter here
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ const int16 channelIndex = g_sci->_audio32->findChannelById(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj);
+
+ if (shouldPause) {
+ g_sci->_audio32->pause(channelIndex);
+ } else {
+ g_sci->_audio32->resume(channelIndex);
+ }
+ } else
+#endif
+ _music->soundToggle(musicSlot, shouldPause);
}
return acc;
}
@@ -352,7 +382,11 @@ reg_t SoundCommandParser::kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc)
int vol = CLIP<int16>(argv[0].toSint16(), 0, MUSIC_MASTERVOLUME_MAX);
vol = vol * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX;
ConfMan.setInt("music_volume", vol);
- ConfMan.setInt("sfx_volume", vol);
+ // In SCI32, digital audio volume is controlled separately by
+ // kDoAudioVolume
+ if (_soundVersion < SCI_VERSION_2_1_EARLY) {
+ ConfMan.setInt("sfx_volume", vol);
+ }
g_engine->syncSoundSettings();
}
return acc;
@@ -375,6 +409,13 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {
int volume = musicSlot->volume;
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ g_sci->_audio32->fadeChannel(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj, argv[1].toSint16(), argv[2].toSint16(), argv[3].toSint16(), argc > 4 ? (bool)argv[4].toSint16() : false);
+ return acc;
+ }
+#endif
+
// If sound is not playing currently, set signal directly
if (musicSlot->status != kSoundPlaying) {
debugC(kDebugLevelSound, "kDoSound(fade): %04x:%04x fading requested, but sound is currently not playing", PRINT_REG(obj));
@@ -463,7 +504,18 @@ void SoundCommandParser::processUpdateCues(reg_t obj) {
return;
}
- if (musicSlot->pStreamAud) {
+ if (musicSlot->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ const int position = g_sci->_audio32->getPosition(g_sci->_audio32->findChannelById(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj));
+
+ if (position == -1) {
+ processStopSound(musicSlot->soundObj, true);
+ }
+
+ return;
+ }
+#endif
// Update digital sound effect slots
uint currentLoopCounter = 0;
@@ -666,6 +718,12 @@ reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) {
value = CLIP<int>(value, 0, MUSIC_VOLUME_MAX);
+#ifdef ENABLE_SCI32
+ // SSCI unconditionally sets volume if it is digital audio
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ _music->soundSetVolume(musicSlot, value);
+ }
+#endif
if (musicSlot->volume != value) {
musicSlot->volume = value;
_music->soundSetVolume(musicSlot, value);
@@ -724,6 +782,15 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {
}
return acc;
}
+
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ if (value != -1) {
+ value = 1;
+ }
+ }
+#endif
+
if (value == -1) {
musicSlot->loop = 0xFFFF;
} else {
@@ -731,6 +798,13 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {
}
writeSelectorValue(_segMan, obj, SELECTOR(loop), musicSlot->loop);
+
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ g_sci->_audio32->setLoop(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj, value == -1);
+ }
+#endif
+
return acc;
}
diff --git a/engines/sci/sound/sync.cpp b/engines/sci/sound/sync.cpp
new file mode 100644
index 0000000000..4e75dab725
--- /dev/null
+++ b/engines/sci/sound/sync.cpp
@@ -0,0 +1,76 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/engine/kernel.h"
+#include "sci/util.h"
+#include "sync.h"
+
+namespace Sci {
+
+Sync::Sync(ResourceManager *resMan, SegManager *segMan) :
+ _resMan(resMan),
+ _segMan(segMan),
+ _resource(nullptr),
+ _offset(0) {}
+
+Sync::~Sync() {
+ stop();
+}
+
+void Sync::start(const ResourceId id, const reg_t syncObjAddr) {
+ _resource = _resMan->findResource(id, true);
+ _offset = 0;
+
+ if (_resource) {
+ writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), 0);
+ } else {
+ warning("Sync::start: failed to find resource %s", id.toString().c_str());
+ // Notify the scripts to stop sound sync
+ writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), SIGNAL_OFFSET);
+ }
+}
+
+void Sync::next(const reg_t syncObjAddr) {
+ if (_resource && (_offset < _resource->size - 1)) {
+ int16 syncCue = -1;
+ int16 syncTime = (int16)READ_SCI11ENDIAN_UINT16(_resource->data + _offset);
+
+ _offset += 2;
+
+ if ((syncTime != -1) && (_offset < _resource->size - 1)) {
+ syncCue = (int16)READ_SCI11ENDIAN_UINT16(_resource->data + _offset);
+ _offset += 2;
+ }
+
+ writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncTime), syncTime);
+ writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), syncCue);
+ }
+}
+
+void Sync::stop() {
+ if (_resource) {
+ _resMan->unlockResource(_resource);
+ _resource = nullptr;
+ }
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/paint.cpp b/engines/sci/sound/sync.h
index 482b81aff1..4b9e2d1b3c 100644
--- a/engines/sci/graphics/paint.cpp
+++ b/engines/sci/sound/sync.h
@@ -20,25 +20,42 @@
*
*/
-#include "graphics/primitives.h"
+#ifndef SCI_SOUND_SYNC_H
+#define SCI_SOUND_SYNC_H
-#include "sci/sci.h"
-#include "sci/engine/state.h"
#include "sci/engine/selector.h"
-#include "sci/graphics/paint.h"
+#include "sci/engine/vm_types.h"
namespace Sci {
-GfxPaint::GfxPaint() {
-}
+enum AudioSyncCommands {
+ kSciAudioSyncStart = 0,
+ kSciAudioSyncNext = 1,
+ kSciAudioSyncStop = 2
+};
-GfxPaint::~GfxPaint() {
-}
+class Resource;
+class ResourceManager;
+class SegManager;
-void GfxPaint::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) {
-}
+/**
+ * Sync class, kDoSync and relevant functions for SCI games.
+ * Provides AV synchronization for animations.
+ */
+class Sync {
+ SegManager *_segMan;
+ ResourceManager *_resMan;
+ Resource *_resource;
+ uint _offset;
+
+public:
+ Sync(ResourceManager *resMan, SegManager *segMan);
+ ~Sync();
-void GfxPaint::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) {
-}
+ void start(const ResourceId id, const reg_t syncObjAddr);
+ void next(const reg_t syncObjAddr);
+ void stop();
+};
} // End of namespace Sci
+#endif
diff --git a/engines/sci/video/robot_decoder.cpp b/engines/sci/video/robot_decoder.cpp
index a2795d21f9..446b986581 100644
--- a/engines/sci/video/robot_decoder.cpp
+++ b/engines/sci/video/robot_decoder.cpp
@@ -20,391 +20,1597 @@
*
*/
-#include "common/archive.h"
-#include "common/stream.h"
-#include "common/substream.h"
-#include "common/system.h"
-#include "common/textconsole.h"
-#include "common/util.h"
-
-#include "graphics/surface.h"
-#include "audio/audiostream.h"
-#include "audio/decoders/raw.h"
-
-#include "sci/resource.h"
-#include "sci/util.h"
-#include "sci/sound/audio.h"
#include "sci/video/robot_decoder.h"
+#include "common/archive.h" // for SearchMan
+#include "common/debug.h" // for debugC
+#include "common/endian.h" // for MKTAG
+#include "common/memstream.h" // for MemoryReadStream
+#include "common/platform.h" // for Platform::kPlatformMacintosh
+#include "common/rational.h" // for operator*, Rational
+#include "common/str.h" // for String
+#include "common/stream.h" // for SeekableReadStream
+#include "common/substream.h" // for SeekableSubReadStreamEndian
+#include "common/textconsole.h" // for error, warning
+#include "common/types.h" // for Flag::NO, Flag::YES
+#include "sci/engine/seg_manager.h" // for SegManager
+#include "sci/graphics/celobj32.h" // for Ratio, ::kLowResX, ::kLowResY
+#include "sci/graphics/text32.h" // for BitmapResource
+#include "sci/sound/audio32.h" // for Audio32
+#include "sci/sci.h" // for kDebugLevels::kDebugLevelVideo
+#include "sci/util.h" // for READ_SCI11ENDIAN_UINT16, READ_SC...
namespace Sci {
-// TODO:
-// - Positioning
-// - Proper handling of frame scaling - scaled frames look squashed
-// (probably because both dimensions should be scaled)
-// - Transparency support
-// - Timing - the arbitrary 100ms delay between each frame is not quite right
-// - Proper handling of sound chunks in some cases, so that the frame size
-// table can be ignored (it's only used to determine the correct sound chunk
-// size at the moment, cause it can be wrong in some cases)
-// - Fix audio "hiccups" - probably data that shouldn't be in the audio frames
-
-
-// Some non technical information on robot files, from an interview with
-// Greg Tomko-Pavia of Sierra On-Line
-// Taken from http://anthonylarme.tripod.com/phantas/phintgtp.html
-//
-// (...) What we needed was a way of playing video, but have it blend into
-// normal room art instead of occupying its own rectangular area. Room art
-// consists of a background pic overlaid with various animating cels
-// (traditional lingo: sprites). The cels each have a priority that determines
-// who is on top and who is behind in the drawing order. Cels are read from
-// *.v56 files (another proprietary format). A Robot is video frames with
-// transparent background including priority and x,y information. Thus, it is
-// like a cel, except it comes from an RBT - not a v56. Because it blends into
-// our graphics engine, it looks just like a part of the room. A RBT can move
-// around the screen and go behind other objects. (...)
-
-enum RobotPalTypes {
- kRobotPalVariable = 0,
- kRobotPalConstant = 1
-};
-
-RobotDecoder::RobotDecoder(bool isBigEndian) {
- _fileStream = 0;
- _pos = Common::Point(0, 0);
- _isBigEndian = isBigEndian;
- _frameTotalSize = 0;
+#pragma mark RobotAudioStream
+
+extern void deDPCM16(int16 *out, const byte *in, const uint32 numBytes, int16 &sample);
+
+RobotAudioStream::RobotAudioStream(const int32 bufferSize) :
+ _loopBuffer((byte *)malloc(bufferSize)),
+ _loopBufferSize(bufferSize),
+ _decompressionBuffer(nullptr),
+ _decompressionBufferSize(0),
+ _decompressionBufferPosition(-1),
+ _waiting(true),
+ _finished(false),
+ _firstPacketPosition(-1) {}
+
+RobotAudioStream::~RobotAudioStream() {
+ free(_loopBuffer);
+ free(_decompressionBuffer);
}
-RobotDecoder::~RobotDecoder() {
- close();
+static void interpolateChannel(int16 *buffer, int32 numSamples, const int8 bufferIndex) {
+ if (numSamples <= 0) {
+ return;
+ }
+
+ if (bufferIndex) {
+ int16 lastSample = *buffer;
+ int sample = lastSample;
+ int16 *target = buffer + 1;
+ const int16 *source = buffer + 2;
+ --numSamples;
+
+ while (numSamples--) {
+ sample = *source + lastSample;
+ lastSample = *source;
+ sample /= 2;
+ *target = sample;
+ source += 2;
+ target += 2;
+ }
+
+ *target = sample;
+ } else {
+ int16 *target = buffer;
+ const int16 *source = buffer + 1;
+ int16 lastSample = *source;
+
+ while (numSamples--) {
+ int sample = *source + lastSample;
+ lastSample = *source;
+ sample /= 2;
+ *target = sample;
+ source += 2;
+ target += 2;
+ }
+ }
}
-bool RobotDecoder::loadStream(Common::SeekableReadStream *stream) {
- close();
+static void copyEveryOtherSample(int16 *out, const int16 *in, int numSamples) {
+ while (numSamples--) {
+ *out = *in++;
+ out += 2;
+ }
+}
+
+bool RobotAudioStream::addPacket(const RobotAudioPacket &packet) {
+ Common::StackLock lock(_mutex);
+
+ if (_finished) {
+ warning("Packet %d sent to finished robot audio stream", packet.position);
+ return false;
+ }
+
+ // `packet.position` is the decompressed (doubled) position of the packet,
+ // so values of `position` will always be divisible either by 2 (even) or by
+ // 4 (odd).
+ const int8 bufferIndex = packet.position % 4 ? 1 : 0;
+
+ // Packet 0 is the first primer, packet 2 is the second primer,
+ // packet 4+ are regular audio data
+ if (packet.position <= 2 && _firstPacketPosition == -1) {
+ _readHead = 0;
+ _readHeadAbs = 0;
+ _maxWriteAbs = _loopBufferSize;
+ _writeHeadAbs = 2;
+ _jointMin[0] = 0;
+ _jointMin[1] = 2;
+ _waiting = true;
+ _finished = false;
+ _firstPacketPosition = packet.position;
+ fillRobotBuffer(packet, bufferIndex);
+ return true;
+ }
- _fileStream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), _isBigEndian, DisposeAfterUse::YES);
+ const int32 packetEndByte = packet.position + (packet.dataSize * sizeof(int16) * kEOSExpansion);
- readHeaderChunk();
+ // Already read all the way past this packet (or already wrote valid samples
+ // to this channel all the way past this packet), so discard it
+ if (packetEndByte <= MAX(_readHeadAbs, _jointMin[bufferIndex])) {
+ debugC(kDebugLevelVideo, "Rejecting packet %d, read past %d / %d", packet.position, _readHeadAbs, _jointMin[bufferIndex]);
+ return true;
+ }
- // There are several versions of robot files, ranging from 3 to 6.
- // v3: no known examples
- // v4: PQ:SWAT demo
- // v5: SCI2.1 and SCI3 games
- // v6: SCI3 games
- if (_header.version < 4 || _header.version > 6)
- error("Unknown robot version: %d", _header.version);
+ // The loop buffer is full, so tell the caller to send the packet again
+ // later
+ if (_maxWriteAbs <= _jointMin[bufferIndex]) {
+ debugC(kDebugLevelVideo, "Rejecting packet %d, full buffer", packet.position);
+ return false;
+ }
- RobotVideoTrack *videoTrack = new RobotVideoTrack(_header.frameCount);
- addTrack(videoTrack);
+ fillRobotBuffer(packet, bufferIndex);
- if (_header.hasSound)
- addTrack(new RobotAudioTrack());
+ // This packet is the second primer, so allow playback to begin
+ if (_firstPacketPosition != -1 && _firstPacketPosition != packet.position) {
+ debugC(kDebugLevelVideo, "Done waiting. Robot audio begins");
+ _waiting = false;
+ _firstPacketPosition = -1;
+ }
- videoTrack->readPaletteChunk(_fileStream, _header.paletteDataSize);
- readFrameSizesChunk();
- videoTrack->calculateVideoDimensions(_fileStream, _frameTotalSize);
+ // Only part of the packet could be read into the loop buffer before it was
+ // full, so tell the caller to send the packet again later
+ if (packetEndByte > _maxWriteAbs) {
+ debugC(kDebugLevelVideo, "Partial read of packet %d (%d / %d)", packet.position, packetEndByte - _maxWriteAbs, packetEndByte - packet.position);
+ return false;
+ }
+
+ // The entire packet was successfully read into the loop buffer
return true;
}
-bool RobotDecoder::load(GuiResourceId id) {
- // TODO: RAMA's robot 1003 cannot be played (shown at the menu screen) -
- // its drawn at odd coordinates. SV can't play it either (along with some
- // others), so it must be some new functionality added in RAMA's robot
- // videos. Skip it for now.
- if (g_sci->getGameId() == GID_RAMA && id == 1003)
- return false;
+void RobotAudioStream::fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex) {
+ int32 sourceByte = 0;
- // Robots for the options in the RAMA menu
- if (g_sci->getGameId() == GID_RAMA && (id >= 1004 && id <= 1009))
- return false;
+ const int32 decompressedSize = packet.dataSize * sizeof(int16);
+ if (_decompressionBufferPosition != packet.position) {
+ if (decompressedSize != _decompressionBufferSize) {
+ _decompressionBuffer = (byte *)realloc(_decompressionBuffer, decompressedSize);
+ _decompressionBufferSize = decompressedSize;
+ }
- // TODO: The robot video in the Lighthouse demo gets stuck
- if (g_sci->getGameId() == GID_LIGHTHOUSE && id == 16)
- return false;
+ int16 carry = 0;
+ deDPCM16((int16 *)_decompressionBuffer, packet.data, packet.dataSize, carry);
+ _decompressionBufferPosition = packet.position;
+ }
+
+ int32 numBytes = decompressedSize;
+ int32 packetPosition = packet.position;
+ int32 endByte = packet.position + decompressedSize * kEOSExpansion;
+ int32 startByte = MAX(_readHeadAbs + bufferIndex * 2, _jointMin[bufferIndex]);
+ int32 maxWriteByte = _maxWriteAbs + bufferIndex * 2;
+ if (packetPosition < startByte) {
+ sourceByte = (startByte - packetPosition) / kEOSExpansion;
+ numBytes -= sourceByte;
+ packetPosition = startByte;
+ }
+ if (packetPosition > maxWriteByte) {
+ numBytes += (packetPosition - maxWriteByte) / kEOSExpansion;
+ packetPosition = maxWriteByte;
+ }
+ if (endByte > maxWriteByte) {
+ numBytes -= (endByte - maxWriteByte) / kEOSExpansion;
+ endByte = maxWriteByte;
+ }
- Common::String fileName = Common::String::format("%d.rbt", id);
+ const int32 maxJointMin = MAX(_jointMin[0], _jointMin[1]);
+ if (endByte > maxJointMin) {
+ _writeHeadAbs += endByte - maxJointMin;
+ }
+
+ if (packetPosition > _jointMin[bufferIndex]) {
+ int32 packetEndByte = packetPosition % _loopBufferSize;
+ int32 targetBytePosition;
+ int32 numBytesToEnd;
+ if ((packetPosition & ~3) > (_jointMin[1 - bufferIndex] & ~3)) {
+ targetBytePosition = _jointMin[1 - bufferIndex] % _loopBufferSize;
+ if (targetBytePosition >= packetEndByte) {
+ numBytesToEnd = _loopBufferSize - targetBytePosition;
+ memset(_loopBuffer + targetBytePosition, 0, numBytesToEnd);
+ targetBytePosition = (1 - bufferIndex) ? 2 : 0;
+ }
+ numBytesToEnd = packetEndByte - targetBytePosition;
+ if (numBytesToEnd > 0) {
+ memset(_loopBuffer + targetBytePosition, 0, numBytesToEnd);
+ }
+ }
+ targetBytePosition = _jointMin[bufferIndex] % _loopBufferSize;
+ if (targetBytePosition >= packetEndByte) {
+ numBytesToEnd = _loopBufferSize - targetBytePosition;
+ interpolateChannel((int16 *)(_loopBuffer + targetBytePosition), numBytesToEnd / sizeof(int16) / kEOSExpansion, 0);
+ targetBytePosition = bufferIndex ? 2 : 0;
+ }
+ numBytesToEnd = packetEndByte - targetBytePosition;
+ if (numBytesToEnd > 0) {
+ interpolateChannel((int16 *)(_loopBuffer + targetBytePosition), numBytesToEnd / sizeof(int16) / kEOSExpansion, 0);
+ }
+ }
+
+ if (numBytes > 0) {
+ int32 targetBytePosition = packetPosition % _loopBufferSize;
+ int32 packetEndByte = endByte % _loopBufferSize;
+ int32 numBytesToEnd = 0;
+ if (targetBytePosition >= packetEndByte) {
+ numBytesToEnd = (_loopBufferSize - (targetBytePosition & ~3)) / kEOSExpansion;
+ copyEveryOtherSample((int16 *)(_loopBuffer + targetBytePosition), (int16 *)(_decompressionBuffer + sourceByte), numBytesToEnd / kEOSExpansion);
+ targetBytePosition = bufferIndex ? 2 : 0;
+ }
+ copyEveryOtherSample((int16 *)(_loopBuffer + targetBytePosition), (int16 *)(_decompressionBuffer + sourceByte + numBytesToEnd), (packetEndByte - targetBytePosition) / sizeof(int16) / kEOSExpansion);
+ }
+ _jointMin[bufferIndex] = endByte;
+}
+
+void RobotAudioStream::interpolateMissingSamples(int32 numSamples) {
+ int32 numBytes = numSamples * sizeof(int16) * kEOSExpansion;
+ int32 targetPosition = _readHead;
+
+ if (_readHeadAbs > _jointMin[1]) {
+ if (_readHeadAbs > _jointMin[0]) {
+ if (targetPosition + numBytes >= _loopBufferSize) {
+ const int32 numBytesToEdge = (_loopBufferSize - targetPosition);
+ memset(_loopBuffer + targetPosition, 0, numBytesToEdge);
+ numBytes -= numBytesToEdge;
+ targetPosition = 0;
+ }
+ memset(_loopBuffer + targetPosition, 0, numBytes);
+ _jointMin[0] += numBytes;
+ _jointMin[1] += numBytes;
+ } else {
+ if (targetPosition + numBytes >= _loopBufferSize) {
+ const int32 numSamplesToEdge = (_loopBufferSize - targetPosition) / sizeof(int16) / kEOSExpansion;
+ interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamplesToEdge, 1);
+ numSamples -= numSamplesToEdge;
+ targetPosition = 0;
+ }
+ interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamples, 1);
+ _jointMin[1] += numBytes;
+ }
+ } else if (_readHeadAbs > _jointMin[0]) {
+ if (targetPosition + numBytes >= _loopBufferSize) {
+ const int32 numSamplesToEdge = (_loopBufferSize - targetPosition) / sizeof(int16) / kEOSExpansion;
+ interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamplesToEdge, 0);
+ numSamples -= numSamplesToEdge;
+ targetPosition = 2;
+ }
+ interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamples, 0);
+ _jointMin[0] += numBytes;
+ }
+}
+
+void RobotAudioStream::finish() {
+ Common::StackLock lock(_mutex);
+ _finished = true;
+}
+
+RobotAudioStream::StreamState RobotAudioStream::getStatus() const {
+ Common::StackLock lock(_mutex);
+ StreamState status;
+ status.bytesPlaying = _readHeadAbs;
+ status.rate = getRate();
+ status.bits = 8 * sizeof(int16);
+ return status;
+}
+
+int RobotAudioStream::readBuffer(Audio::st_sample_t *outBuffer, int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ if (_waiting) {
+ return 0;
+ }
+
+ assert(!((_writeHeadAbs - _readHeadAbs) & 1));
+ const int maxNumSamples = (_writeHeadAbs - _readHeadAbs) / sizeof(Audio::st_sample_t);
+ numSamples = MIN(numSamples, maxNumSamples);
+
+ if (!numSamples) {
+ return 0;
+ }
+
+ interpolateMissingSamples(numSamples);
+
+ Audio::st_sample_t *inBuffer = (Audio::st_sample_t *)(_loopBuffer + _readHead);
+
+ assert(!((_loopBufferSize - _readHead) & 1));
+ const int numSamplesToEnd = (_loopBufferSize - _readHead) / sizeof(Audio::st_sample_t);
+
+ int numSamplesToRead = MIN(numSamples, numSamplesToEnd);
+ Common::copy(inBuffer, inBuffer + numSamplesToRead, outBuffer);
+
+ if (numSamplesToRead < numSamples) {
+ inBuffer = (Audio::st_sample_t *)_loopBuffer;
+ outBuffer += numSamplesToRead;
+ numSamplesToRead = numSamples - numSamplesToRead;
+ Common::copy(inBuffer, inBuffer + numSamplesToRead, outBuffer);
+ }
+
+ const int32 numBytes = numSamples * sizeof(Audio::st_sample_t);
+
+ _readHead += numBytes;
+ if (_readHead > _loopBufferSize) {
+ _readHead -= _loopBufferSize;
+ }
+ _readHeadAbs += numBytes;
+ _maxWriteAbs += numBytes;
+ assert(!(_readHead & 1));
+ assert(!(_readHeadAbs & 1));
+
+ return numSamples;
+}
+
+#pragma mark -
+#pragma mark RobotDecoder
+
+RobotDecoder::RobotDecoder(SegManager *segMan) :
+ _delayTime(this),
+ _segMan(segMan),
+ _status(kRobotStatusUninitialized),
+ _audioBuffer(nullptr),
+ _rawPalette((uint8 *)malloc(kRawPaletteSize)) {}
+
+RobotDecoder::~RobotDecoder() {
+ close();
+ free(_rawPalette);
+ free(_audioBuffer);
+}
+
+#pragma mark -
+#pragma mark RobotDecoder - Initialization
+
+void RobotDecoder::initStream(const GuiResourceId robotId) {
+ const Common::String fileName = Common::String::format("%d.rbt", robotId);
Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName);
+ _fileOffset = 0;
- if (!stream) {
- warning("Unable to open robot file %s", fileName.c_str());
- return false;
+ if (stream == nullptr) {
+ error("Unable to open robot file %s", fileName.c_str());
+ }
+
+ const uint16 id = stream->readUint16LE();
+ if (id != 0x16) {
+ error("Invalid robot file %s", fileName.c_str());
+ }
+
+ // TODO: Mac version not tested, so this could be totally wrong
+ _stream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), g_sci->getPlatform() == Common::kPlatformMacintosh, DisposeAfterUse::YES);
+ _stream->seek(2, SEEK_SET);
+ if (_stream->readUint32BE() != MKTAG('S', 'O', 'L', 0)) {
+ error("Resource %s is not Robot type!", fileName.c_str());
+ }
+}
+
+void RobotDecoder::initPlayback() {
+ _startFrameNo = 0;
+ _startTime = -1;
+ _startingFrameNo = -1;
+ _cueForceShowFrame = -1;
+ _previousFrameNo = -1;
+ _currentFrameNo = 0;
+ _status = kRobotStatusPaused;
+}
+
+void RobotDecoder::initAudio() {
+ _syncFrame = true;
+
+ _audioRecordInterval = RobotAudioStream::kRobotSampleRate / _frameRate;
+
+ // TODO: Might actually be for all games newer than Lighthouse; check to
+ // see which games have this condition.
+ if (g_sci->getGameId() != GID_LIGHTHOUSE && !(_audioRecordInterval & 1)) {
+ ++_audioRecordInterval;
+ }
+
+ _expectedAudioBlockSize = _audioBlockSize - kAudioBlockHeaderSize;
+ _audioBuffer = (byte *)realloc(_audioBuffer, kRobotZeroCompressSize + _expectedAudioBlockSize);
+
+ if (_primerReservedSize != 0) {
+ const int32 primerHeaderPosition = _stream->pos();
+ _totalPrimerSize = _stream->readSint32();
+ const int16 compressionType = _stream->readSint16();
+ _evenPrimerSize = _stream->readSint32();
+ _oddPrimerSize = _stream->readSint32();
+ _primerPosition = _stream->pos();
+
+ if (compressionType) {
+ error("Unknown audio header compression type %d", compressionType);
+ }
+
+ if (_evenPrimerSize + _oddPrimerSize != _primerReservedSize) {
+ _stream->seek(primerHeaderPosition + _primerReservedSize, SEEK_SET);
+ }
+ } else if (_primerZeroCompressFlag) {
+ _evenPrimerSize = 19922;
+ _oddPrimerSize = 21024;
+ }
+
+ _firstAudioRecordPosition = _evenPrimerSize * 2;
+
+ const int usedEachFrame = (RobotAudioStream::kRobotSampleRate / 2) / _frameRate;
+ _maxSkippablePackets = MAX(0, _audioBlockSize / usedEachFrame - 1);
+}
+
+void RobotDecoder::initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize) {
+ _position = Common::Point(x, y);
+
+ if (scale != 128) {
+ _scaleInfo.x = scale;
+ _scaleInfo.y = scale;
+ _scaleInfo.signal = kScaleSignalManual;
+ }
+
+ _plane = g_sci->_gfxFrameout->getPlanes().findByObject(plane);
+ if (_plane == nullptr) {
+ error("Invalid plane %04x:%04x passed to RobotDecoder::open", PRINT_REG(plane));
+ }
+
+ _minFrameRate = _frameRate - kMaxFrameRateDrift;
+ _maxFrameRate = _frameRate + kMaxFrameRateDrift;
+
+ if (_xResolution == 0 || _yResolution == 0) {
+ // TODO: Default values were taken from RESOURCE.CFG hires property
+ // if it exists, so need to check games' configuration files for those
+ _xResolution = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ _yResolution = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
}
- return loadStream(stream);
+ if (hasPalette) {
+ _stream->read(_rawPalette, paletteSize);
+ } else {
+ _stream->seek(paletteSize, SEEK_CUR);
+ }
+
+ _screenItemList.reserve(kScreenItemListSize);
+ _maxCelArea.reserve(kFixedCelListSize);
+
+ // Fixed cel buffers are for version 5 and newer
+ _fixedCels.reserve(MIN(_maxCelsPerFrame, (int16)kFixedCelListSize));
+ _celDecompressionBuffer.reserve(_maxCelArea[0] + SciBitmap::getBitmapHeaderSize() + kRawPaletteSize);
+ _celDecompressionArea = _maxCelArea[0];
+}
+
+void RobotDecoder::initRecordAndCuePositions() {
+ PositionList recordSizes;
+ _videoSizes.reserve(_numFramesTotal);
+ _recordPositions.reserve(_numFramesTotal);
+ recordSizes.reserve(_numFramesTotal);
+
+ switch(_version) {
+ case 5: // 16-bit sizes and positions
+ for (int i = 0; i < _numFramesTotal; ++i) {
+ _videoSizes.push_back(_stream->readUint16());
+ }
+ for (int i = 0; i < _numFramesTotal; ++i) {
+ recordSizes.push_back(_stream->readUint16());
+ }
+ break;
+ case 6: // 32-bit sizes and positions
+ for (int i = 0; i < _numFramesTotal; ++i) {
+ _videoSizes.push_back(_stream->readSint32());
+ }
+ for (int i = 0; i < _numFramesTotal; ++i) {
+ recordSizes.push_back(_stream->readSint32());
+ }
+ break;
+ default:
+ error("Unknown Robot version %d", _version);
+ }
+
+ for (int i = 0; i < kCueListSize; ++i) {
+ _cueTimes[i] = _stream->readSint32();
+ }
+
+ for (int i = 0; i < kCueListSize; ++i) {
+ _cueValues[i] = _stream->readUint16();
+ }
+
+ Common::copy(_cueTimes, _cueTimes + kCueListSize, _masterCueTimes);
+
+ int bytesRemaining = (_stream->pos() - _fileOffset) % kRobotFrameSize;
+ if (bytesRemaining != 0) {
+ _stream->seek(kRobotFrameSize - bytesRemaining, SEEK_CUR);
+ }
+
+ int position = _stream->pos();
+ _recordPositions.push_back(position);
+ for (int i = 0; i < _numFramesTotal - 1; ++i) {
+ position += recordSizes[i];
+ _recordPositions.push_back(position);
+ }
+}
+
+#pragma mark -
+#pragma mark RobotDecoder - Playback
+
+void RobotDecoder::open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale) {
+ if (_status != kRobotStatusUninitialized) {
+ close();
+ }
+
+ initStream(robotId);
+
+ _version = _stream->readUint16();
+
+ // TODO: Version 4 for PQ:SWAT demo?
+ if (_version < 5 || _version > 6) {
+ error("Unsupported version %d of Robot resource", _version);
+ }
+
+ debugC(kDebugLevelVideo, "Opening version %d robot %d", _version, robotId);
+
+ initPlayback();
+
+ _audioBlockSize = _stream->readUint16();
+ _primerZeroCompressFlag = _stream->readSint16();
+ _stream->seek(2, SEEK_CUR); // unused
+ _numFramesTotal = _stream->readUint16();
+ const uint16 paletteSize = _stream->readUint16();
+ _primerReservedSize = _stream->readUint16();
+ _xResolution = _stream->readSint16();
+ _yResolution = _stream->readSint16();
+ const bool hasPalette = (bool)_stream->readByte();
+ _hasAudio = (bool)_stream->readByte();
+ _stream->seek(2, SEEK_CUR); // unused
+ _frameRate = _normalFrameRate = _stream->readSint16();
+ _isHiRes = (bool)_stream->readSint16();
+ _maxSkippablePackets = _stream->readSint16();
+ _maxCelsPerFrame = _stream->readSint16();
+
+ // used for memory preallocation of fixed cels
+ _maxCelArea.push_back(_stream->readSint32());
+ _maxCelArea.push_back(_stream->readSint32());
+ _maxCelArea.push_back(_stream->readSint32());
+ _maxCelArea.push_back(_stream->readSint32());
+ _stream->seek(8, SEEK_CUR); // reserved
+
+ if (_hasAudio) {
+ initAudio();
+ } else {
+ _stream->seek(_primerReservedSize, SEEK_CUR);
+ }
+
+ _priority = priority;
+ initVideo(x, y, scale, plane, hasPalette, paletteSize);
+ initRecordAndCuePositions();
}
void RobotDecoder::close() {
- VideoDecoder::close();
+ if (_status == kRobotStatusUninitialized) {
+ return;
+ }
+
+ debugC(kDebugLevelVideo, "Closing robot");
- delete _fileStream;
- _fileStream = 0;
+ _status = kRobotStatusUninitialized;
+ _videoSizes.clear();
+ _recordPositions.clear();
+ _celDecompressionBuffer.clear();
+ _doVersion5Scratch.clear();
+ delete _stream;
+ _stream = nullptr;
+
+ for (CelHandleList::size_type i = 0; i < _celHandles.size(); ++i) {
+ if (_celHandles[i].status == CelHandleInfo::kFrameLifetime) {
+ _segMan->freeBitmap(_celHandles[i].bitmapId);
+ }
+ }
+ _celHandles.clear();
+
+ for (FixedCelsList::size_type i = 0; i < _fixedCels.size(); ++i) {
+ _segMan->freeBitmap(_fixedCels[i]);
+ }
+ _fixedCels.clear();
+
+ if (g_sci->_gfxFrameout->getPlanes().findByObject(_plane->_object) != nullptr) {
+ for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+ if (_screenItemList[i] != nullptr) {
+ g_sci->_gfxFrameout->deleteScreenItem(*_screenItemList[i]);
+ }
+ }
+ }
+ _screenItemList.clear();
- delete[] _frameTotalSize;
- _frameTotalSize = 0;
+ if (_hasAudio) {
+ _audioList.reset();
+ }
}
-void RobotDecoder::readNextPacket() {
- // Get our track
- RobotVideoTrack *videoTrack = (RobotVideoTrack *)getTrack(0);
- videoTrack->increaseCurFrame();
- Graphics::Surface *surface = videoTrack->getSurface();
+void RobotDecoder::pause() {
+ if (_status != kRobotStatusPlaying) {
+ return;
+ }
+
+ if (_hasAudio) {
+ _audioList.stopAudioNow();
+ }
+
+ _status = kRobotStatusPaused;
+ _frameRate = _normalFrameRate;
+}
- if (videoTrack->endOfTrack())
+void RobotDecoder::resume() {
+ if (_status != kRobotStatusPaused) {
return;
+ }
+
+ _startingFrameNo = _currentFrameNo;
+ _status = kRobotStatusPlaying;
+ if (_hasAudio) {
+ primeAudio(_currentFrameNo * 60 / _frameRate);
+ _syncFrame = true;
+ }
+
+ setRobotTime(_currentFrameNo);
+ for (int i = 0; i < kCueListSize; ++i) {
+ if (_masterCueTimes[i] != -1 && _masterCueTimes[i] < _currentFrameNo) {
+ _cueTimes[i] = -1;
+ } else {
+ _cueTimes[i] = _masterCueTimes[i];
+ }
+ }
+}
- // Read frame image header (24 bytes)
- _fileStream->skip(3);
- byte frameScale = _fileStream->readByte();
- uint16 frameWidth = _fileStream->readUint16();
- uint16 frameHeight = _fileStream->readUint16();
- _fileStream->skip(4); // unknown, almost always 0
- uint16 frameX = _fileStream->readUint16();
- uint16 frameY = _fileStream->readUint16();
-
- // TODO: In v4 robot files, frameX and frameY have a different meaning.
- // Set them both to 0 for v4 for now, so that robots in PQ:SWAT show up
- // correctly.
- if (_header.version == 4)
- frameX = frameY = 0;
-
- uint16 compressedSize = _fileStream->readUint16();
- uint16 frameFragments = _fileStream->readUint16();
- _fileStream->skip(4); // unknown
- uint32 decompressedSize = frameWidth * frameHeight * frameScale / 100;
-
- // FIXME: A frame's height + position can go off limits... why? With the
- // following, we cut the contents to fit the frame
- uint16 scaledHeight = CLIP<uint16>(decompressedSize / frameWidth, 0, surface->h - frameY);
-
- // FIXME: Same goes for the frame's width + position. In this case, we
- // modify the position to fit the contents on screen.
- if (frameWidth + frameX > surface->w)
- frameX = surface->w - frameWidth;
-
- assert(frameWidth + frameX <= surface->w && scaledHeight + frameY <= surface->h);
-
- DecompressorLZS lzs;
- byte *decompressedFrame = new byte[decompressedSize];
- byte *outPtr = decompressedFrame;
-
- if (_header.version == 4) {
- // v4 has just the one fragment, it seems, and ignores the fragment count
- Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedSize);
- lzs.unpack(&fragmentStream, outPtr, compressedSize, decompressedSize);
+void RobotDecoder::showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority) {
+ debugC(kDebugLevelVideo, "Show frame %d (%d %d %d)", frameNo, newX, newY, newPriority);
+
+ if (newX != kUnspecified) {
+ _position.x = newX;
+ }
+
+ if (newY != kUnspecified) {
+ _position.y = newY;
+ }
+
+ if (newPriority != kUnspecified) {
+ _priority = newPriority;
+ }
+
+ _currentFrameNo = frameNo;
+ pause();
+
+ if (frameNo != _previousFrameNo) {
+ seekToFrame(frameNo);
+ doVersion5(false);
} else {
- for (uint16 i = 0; i < frameFragments; ++i) {
- uint32 compressedFragmentSize = _fileStream->readUint32();
- uint32 decompressedFragmentSize = _fileStream->readUint32();
- uint16 compressionType = _fileStream->readUint16();
-
- if (compressionType == 0) {
- Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedFragmentSize);
- lzs.unpack(&fragmentStream, outPtr, compressedFragmentSize, decompressedFragmentSize);
- } else if (compressionType == 2) { // untested
- _fileStream->read(outPtr, compressedFragmentSize);
+ for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+ if (_isHiRes) {
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_celHandles[i].bitmapId);
+
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ if (scriptWidth == kLowResX && scriptHeight == kLowResY) {
+ const Ratio lowResToScreenX(screenWidth, kLowResX);
+ const Ratio lowResToScreenY(screenHeight, kLowResY);
+ const Ratio screenToLowResX(kLowResX, screenWidth);
+ const Ratio screenToLowResY(kLowResY, screenHeight);
+
+ const int16 scaledX = _originalScreenItemX[i] + (_position.x * lowResToScreenX).toInt();
+ const int16 scaledY1 = _originalScreenItemY[i] + (_position.y * lowResToScreenY).toInt();
+ const int16 scaledY2 = scaledY1 + bitmap.getHeight() - 1;
+
+ const int16 lowResX = (scaledX * screenToLowResX).toInt();
+ const int16 lowResY = (scaledY2 * screenToLowResY).toInt();
+
+ bitmap.setOrigin(Common::Point(
+ (scaledX - (lowResX * lowResToScreenX).toInt()) * -1,
+ (lowResY * lowResToScreenY).toInt() - scaledY1
+ ));
+
+ _screenItemX[i] = lowResX;
+ _screenItemY[i] = lowResY;
+ } else {
+ const int16 scaledX = _originalScreenItemX[i] + _position.x;
+ const int16 scaledY = _originalScreenItemY[i] + _position.y + bitmap.getHeight() - 1;
+ bitmap.setOrigin(Common::Point(0, bitmap.getHeight() - 1));
+ _screenItemX[i] = scaledX;
+ _screenItemY[i] = scaledY;
+ }
+ } else {
+ _screenItemX[i] = _originalScreenItemX[i] + _position.x;
+ _screenItemY[i] = _originalScreenItemY[i] + _position.y;
+ }
+
+ if (_screenItemList[i] == nullptr) {
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = _celHandles[i].bitmapId;
+ ScreenItem *screenItem = new ScreenItem(_plane->_object, celInfo);
+ _screenItemList[i] = screenItem;
+ screenItem->_position = Common::Point(_screenItemX[i], _screenItemY[i]);
+ if (_priority == -1) {
+ screenItem->_fixedPriority = false;
+ } else {
+ screenItem->_priority = _priority;
+ screenItem->_fixedPriority = true;
+ }
+ g_sci->_gfxFrameout->addScreenItem(*screenItem);
} else {
- error("Unknown frame compression found: %d", compressionType);
+ ScreenItem *screenItem = _screenItemList[i];
+ screenItem->_celInfo.bitmap = _celHandles[i].bitmapId;
+ screenItem->_position = Common::Point(_screenItemX[i], _screenItemY[i]);
+ if (_priority == -1) {
+ screenItem->_fixedPriority = false;
+ } else {
+ screenItem->_priority = _priority;
+ screenItem->_fixedPriority = true;
+ }
+ g_sci->_gfxFrameout->updateScreenItem(*screenItem);
+ }
+ }
+ }
+
+ _previousFrameNo = frameNo;
+}
+
+int16 RobotDecoder::getCue() const {
+ if (_status == kRobotStatusUninitialized ||
+ _status == kRobotStatusPaused ||
+ _syncFrame) {
+ return 0;
+ }
+
+ if (_status == kRobotStatusEnd) {
+ return -1;
+ }
+
+ const uint16 estimatedNextFrameNo = MIN(calculateNextFrameNo(_delayTime.predictedTicks()), _numFramesTotal);
+
+ for (int i = 0; i < kCueListSize; ++i) {
+ if (_cueTimes[i] != -1 && _cueTimes[i] <= estimatedNextFrameNo) {
+ if (_cueTimes[i] >= _previousFrameNo) {
+ _cueForceShowFrame = _cueTimes[i] + 1;
}
- outPtr += decompressedFragmentSize;
+ _cueTimes[i] = -1;
+ return _cueValues[i];
}
}
- // Copy over the decompressed frame
- byte *inFrame = decompressedFrame;
- byte *outFrame = (byte *)surface->getPixels();
+ return 0;
+}
- // Black out the surface
- memset(outFrame, 0, surface->w * surface->h);
+int16 RobotDecoder::getFrameNo() const {
+ if (_status == kRobotStatusUninitialized) {
+ return 0;
+ }
- // Move to the correct y coordinate
- outFrame += surface->w * frameY;
+ return _currentFrameNo;
+}
+
+RobotDecoder::RobotStatus RobotDecoder::getStatus() const {
+ return _status;
+}
- for (uint16 y = 0; y < scaledHeight; y++) {
- memcpy(outFrame + frameX, inFrame, frameWidth);
- inFrame += frameWidth;
- outFrame += surface->w;
+bool RobotDecoder::seekToFrame(const int frameNo) {
+ return _stream->seek(_recordPositions[frameNo], SEEK_SET);
+}
+
+void RobotDecoder::setRobotTime(const int frameNo) {
+ _startTime = getTickCount();
+ _startFrameNo = frameNo;
+}
+
+#pragma mark -
+#pragma mark RobotDecoder - Timing
+
+RobotDecoder::DelayTime::DelayTime(RobotDecoder *decoder) :
+ _decoder(decoder) {
+ for (int i = 0; i < kDelayListSize; ++i) {
+ _timestamps[i] = i;
+ _delays[i] = 0;
}
- delete[] decompressedFrame;
+ _oldestTimestamp = 0;
+ _newestTimestamp = kDelayListSize - 1;
+ _startTime = 0;
+}
- uint32 audioChunkSize = _frameTotalSize[videoTrack->getCurFrame()] - (24 + compressedSize);
+void RobotDecoder::DelayTime::startTiming() {
+ _startTime = _decoder->getTickCount();
+}
-// TODO: The audio chunk size below is usually correct, but there are some
-// exceptions (e.g. robot 4902 in Phantasmagoria, towards its end)
-#if 0
- // Read frame audio header (14 bytes)
- _fileStream->skip(2); // buffer position
- _fileStream->skip(2); // unknown (usually 1)
- _fileStream->skip(2); /*uint16 audioChunkSize = _fileStream->readUint16() + 8;*/
- _fileStream->skip(2);
-#endif
+void RobotDecoder::DelayTime::endTiming() {
+ const int timeDelta = _decoder->getTickCount() - _startTime;
+ for (uint i = 0; i < kDelayListSize; ++i) {
+ if (_timestamps[i] == _oldestTimestamp) {
+ _timestamps[i] = ++_newestTimestamp;
+ _delays[i] = timeDelta;
+ break;
+ }
+ }
+ ++_newestTimestamp;
+ _startTime = 0;
+ sortList();
+}
- // Queue the next audio frame
- // FIXME: For some reason, there are audio hiccups/gaps
- if (_header.hasSound) {
- RobotAudioTrack *audioTrack = (RobotAudioTrack *)getTrack(1);
- _fileStream->skip(8); // header
- audioChunkSize -= 8;
- audioTrack->queueBuffer(g_sci->_audio->getDecodedRobotAudioFrame(_fileStream, audioChunkSize), audioChunkSize * 2);
+bool RobotDecoder::DelayTime::timingInProgress() const {
+ return _startTime != 0;
+}
+
+int RobotDecoder::DelayTime::predictedTicks() const {
+ return _delays[kDelayListSize / 2];
+}
+
+void RobotDecoder::DelayTime::sortList() {
+ for (uint i = 0; i < kDelayListSize - 1; ++i) {
+ int smallestDelay = _delays[i];
+ uint smallestIndex = i;
+
+ for (uint j = i + 1; j < kDelayListSize - 1; ++j) {
+ if (_delays[j] < smallestDelay) {
+ smallestDelay = _delays[j];
+ smallestIndex = j;
+ }
+ }
+
+ if (smallestIndex != i) {
+ SWAP(_delays[i], _delays[smallestIndex]);
+ SWAP(_timestamps[i], _timestamps[smallestIndex]);
+ }
+ }
+}
+
+uint16 RobotDecoder::calculateNextFrameNo(const uint32 extraTicks) const {
+ return ticksToFrames(getTickCount() + extraTicks - _startTime) + _startFrameNo;
+}
+
+uint32 RobotDecoder::ticksToFrames(const uint32 ticks) const {
+ return (ticks * _frameRate) / 60;
+}
+
+uint32 RobotDecoder::getTickCount() const {
+ return g_sci->getTickCount();
+}
+
+#pragma mark -
+#pragma mark RobotDecoder - Audio
+
+RobotDecoder::AudioList::AudioList() :
+ _blocks(),
+ _blocksSize(0),
+ _oldestBlockIndex(0),
+ _newestBlockIndex(0),
+ _startOffset(0),
+ _status(kRobotAudioReady) {}
+
+void RobotDecoder::AudioList::startAudioNow() {
+ submitDriverMax();
+ g_sci->_audio32->resume(kRobotChannel);
+ _status = kRobotAudioPlaying;
+}
+
+void RobotDecoder::AudioList::stopAudio() {
+ g_sci->_audio32->finishRobotAudio();
+ freeAudioBlocks();
+ _status = kRobotAudioStopping;
+}
+
+void RobotDecoder::AudioList::stopAudioNow() {
+ if (_status == kRobotAudioPlaying || _status == kRobotAudioStopping || _status == kRobotAudioPaused) {
+ g_sci->_audio32->stopRobotAudio();
+ _status = kRobotAudioStopped;
+ }
+
+ freeAudioBlocks();
+}
+
+void RobotDecoder::AudioList::submitDriverMax() {
+ while (_blocksSize != 0) {
+ if (!_blocks[_oldestBlockIndex]->submit(_startOffset)) {
+ return;
+ }
+
+ delete _blocks[_oldestBlockIndex];
+ _blocks[_oldestBlockIndex] = nullptr;
+ ++_oldestBlockIndex;
+ if (_oldestBlockIndex == kAudioListSize) {
+ _oldestBlockIndex = 0;
+ }
+
+ --_blocksSize;
+ }
+}
+
+void RobotDecoder::AudioList::addBlock(const int position, const int size, const byte *data) {
+ assert(data != nullptr);
+ assert(size >= 0);
+ assert(position >= -1);
+
+ if (_blocksSize == kAudioListSize) {
+ delete _blocks[_oldestBlockIndex];
+ _blocks[_oldestBlockIndex] = nullptr;
+ ++_oldestBlockIndex;
+ if (_oldestBlockIndex == kAudioListSize) {
+ _oldestBlockIndex = 0;
+ }
+ --_blocksSize;
+ }
+
+ if (_blocksSize == 0) {
+ _oldestBlockIndex = _newestBlockIndex = 0;
} else {
- _fileStream->skip(audioChunkSize);
- }
-}
-
-void RobotDecoder::readHeaderChunk() {
- // Header (60 bytes)
- _fileStream->skip(6);
- _header.version = _fileStream->readUint16();
- _header.audioChunkSize = _fileStream->readUint16();
- _header.audioSilenceSize = _fileStream->readUint16();
- _fileStream->skip(2);
- _header.frameCount = _fileStream->readUint16();
- _header.paletteDataSize = _fileStream->readUint16();
- _header.unkChunkDataSize = _fileStream->readUint16();
- _fileStream->skip(5);
- _header.hasSound = _fileStream->readByte();
- _fileStream->skip(34);
-
- // Some videos (e.g. robot 1305 in Phantasmagoria and
- // robot 184 in Lighthouse) have an unknown chunk before
- // the palette chunk (probably used for sound preloading).
- // Skip it here.
- if (_header.unkChunkDataSize)
- _fileStream->skip(_header.unkChunkDataSize);
-}
-
-void RobotDecoder::readFrameSizesChunk() {
- // The robot video file contains 2 tables, with one entry for each frame:
- // - A table containing the size of the image in each video frame
- // - A table containing the total size of each video frame.
- // In v5 robots, the tables contain 16-bit integers, whereas in v6 robots,
- // they contain 32-bit integers.
-
- _frameTotalSize = new uint32[_header.frameCount];
-
- // TODO: The table reading code can probably be removed once the
- // audio chunk size is figured out (check the TODO inside processNextFrame())
-#if 0
- // We don't need any of the two tables to play the video, so we ignore
- // both of them.
- uint16 wordSize = _header.version == 6 ? 4 : 2;
- _fileStream->skip(_header.frameCount * wordSize * 2);
-#else
- switch (_header.version) {
- case 4:
- case 5: // sizes are 16-bit integers
- // Skip table with frame image sizes, as we don't need it
- _fileStream->skip(_header.frameCount * 2);
- for (int i = 0; i < _header.frameCount; ++i)
- _frameTotalSize[i] = _fileStream->readUint16();
- break;
- case 6: // sizes are 32-bit integers
- // Skip table with frame image sizes, as we don't need it
- _fileStream->skip(_header.frameCount * 4);
- for (int i = 0; i < _header.frameCount; ++i)
- _frameTotalSize[i] = _fileStream->readUint32();
- break;
- default:
- error("Can't yet handle index table for robot version %d", _header.version);
+ ++_newestBlockIndex;
+ if (_newestBlockIndex == kAudioListSize) {
+ _newestBlockIndex = 0;
+ }
}
-#endif
- // 2 more unknown tables
- _fileStream->skip(1024 + 512);
+ _blocks[_newestBlockIndex] = new AudioBlock(position, size, data);
+ ++_blocksSize;
+}
- // Pad to nearest 2 kilobytes
- uint32 curPos = _fileStream->pos();
- if (curPos & 0x7ff)
- _fileStream->seek((curPos & ~0x7ff) + 2048);
+void RobotDecoder::AudioList::reset() {
+ stopAudioNow();
+ _startOffset = 0;
+ _status = kRobotAudioReady;
}
-RobotDecoder::RobotVideoTrack::RobotVideoTrack(int frameCount) : _frameCount(frameCount) {
- _surface = new Graphics::Surface();
- _curFrame = -1;
- _dirtyPalette = false;
+void RobotDecoder::AudioList::prepareForPrimer() {
+ g_sci->_audio32->pause(kRobotChannel);
+ _status = kRobotAudioPaused;
}
-RobotDecoder::RobotVideoTrack::~RobotVideoTrack() {
- _surface->free();
- delete _surface;
+void RobotDecoder::AudioList::setAudioOffset(const int offset) {
+ _startOffset = offset;
}
-uint16 RobotDecoder::RobotVideoTrack::getWidth() const {
- return _surface->w;
+RobotDecoder::AudioList::AudioBlock::AudioBlock(const int position, const int size, const byte* const data) :
+ _position(position),
+ _size(size) {
+ _data = (byte *)malloc(size);
+ memcpy(_data, data, size);
}
-uint16 RobotDecoder::RobotVideoTrack::getHeight() const {
- return _surface->h;
+RobotDecoder::AudioList::AudioBlock::~AudioBlock() {
+ free(_data);
}
-Graphics::PixelFormat RobotDecoder::RobotVideoTrack::getPixelFormat() const {
- return _surface->format;
+bool RobotDecoder::AudioList::AudioBlock::submit(const int startOffset) {
+ assert(_data != nullptr);
+ RobotAudioStream::RobotAudioPacket packet(_data, _size, (_position - startOffset) * 2);
+ return g_sci->_audio32->playRobotAudio(packet);
}
-void RobotDecoder::RobotVideoTrack::readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize) {
- byte *paletteData = new byte[chunkSize];
- stream->read(paletteData, chunkSize);
+void RobotDecoder::AudioList::freeAudioBlocks() {
+ while (_blocksSize != 0) {
+ delete _blocks[_oldestBlockIndex];
+ _blocks[_oldestBlockIndex] = nullptr;
+ ++_oldestBlockIndex;
+ if (_oldestBlockIndex == kAudioListSize) {
+ _oldestBlockIndex = 0;
+ }
+
+ --_blocksSize;
+ }
+}
- // SCI1.1 palette
- byte palFormat = paletteData[32];
- uint16 palColorStart = paletteData[25];
- uint16 palColorCount = READ_SCI11ENDIAN_UINT16(paletteData + 29);
+bool RobotDecoder::primeAudio(const uint32 startTick) {
+ bool success = true;
+ _audioList.reset();
+
+ if (startTick == 0) {
+ _audioList.prepareForPrimer();
+ byte *evenPrimerBuff = new byte[_evenPrimerSize];
+ byte *oddPrimerBuff = new byte[_oddPrimerSize];
+
+ success = readPrimerData(evenPrimerBuff, oddPrimerBuff);
+ if (success) {
+ if (_evenPrimerSize != 0) {
+ _audioList.addBlock(0, _evenPrimerSize, evenPrimerBuff);
+ }
+ if (_oddPrimerSize != 0) {
+ _audioList.addBlock(1, _oddPrimerSize, oddPrimerBuff);
+ }
+ }
+
+ delete[] evenPrimerBuff;
+ delete[] oddPrimerBuff;
+ } else {
+ assert(_evenPrimerSize * 2 >= _audioRecordInterval || _oddPrimerSize * 2 >= _audioRecordInterval);
+
+ int audioStartFrame = 0;
+ int videoStartFrame = startTick * _frameRate / 60;
+ assert(videoStartFrame < _numFramesTotal);
+
+ int audioStartPosition = (startTick * RobotAudioStream::kRobotSampleRate) / 60;
+ if (audioStartPosition & 1) {
+ audioStartPosition--;
+ }
+ _audioList.setAudioOffset(audioStartPosition);
+ _audioList.prepareForPrimer();
+
+ if (audioStartPosition < _evenPrimerSize * 2 ||
+ audioStartPosition + 1 < _oddPrimerSize * 2) {
+
+ byte *evenPrimerBuffer = new byte[_evenPrimerSize];
+ byte *oddPrimerBuffer = new byte[_oddPrimerSize];
+ success = readPrimerData(evenPrimerBuffer, oddPrimerBuffer);
+ if (success) {
+ int halfAudioStartPosition = audioStartPosition / 2;
+ if (audioStartPosition < _evenPrimerSize * 2) {
+ _audioList.addBlock(audioStartPosition, _evenPrimerSize - halfAudioStartPosition, &evenPrimerBuffer[halfAudioStartPosition]);
+ }
+
+ if (audioStartPosition + 1 < _oddPrimerSize * 2) {
+ _audioList.addBlock(audioStartPosition + 1, _oddPrimerSize - halfAudioStartPosition, &oddPrimerBuffer[halfAudioStartPosition]);
+ }
+ }
+
+ delete[] evenPrimerBuffer;
+ delete[] oddPrimerBuffer;
+ }
- int palOffset = 37;
- memset(_palette, 0, 256 * 3);
+ if (audioStartPosition >= _firstAudioRecordPosition) {
+ int audioRecordSize = _expectedAudioBlockSize;
+ assert(audioRecordSize > 0);
+ assert(_audioRecordInterval > 0);
+ assert(_firstAudioRecordPosition >= 0);
- for (uint16 colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) {
- if (palFormat == kRobotPalVariable)
- palOffset++;
- _palette[colorNo * 3 + 0] = paletteData[palOffset++];
- _palette[colorNo * 3 + 1] = paletteData[palOffset++];
- _palette[colorNo * 3 + 2] = paletteData[palOffset++];
+ audioStartFrame = (audioStartPosition - _firstAudioRecordPosition) / _audioRecordInterval;
+ assert(audioStartFrame < videoStartFrame);
+
+ if (audioStartFrame > 0) {
+ int lastAudioFrame = audioStartFrame - 1;
+ int oddRemainder = lastAudioFrame & 1;
+ int audioRecordStart = (lastAudioFrame * _audioRecordInterval) + oddRemainder + _firstAudioRecordPosition;
+ int audioRecordEnd = (audioRecordStart + ((audioRecordSize - 1) * 2)) + oddRemainder + _firstAudioRecordPosition;
+
+ if (audioStartPosition >= audioRecordStart && audioStartPosition <= audioRecordEnd) {
+ --audioStartFrame;
+ }
+ }
+
+ assert(!(audioStartPosition & 1));
+ if (audioStartFrame & 1) {
+ ++audioStartPosition;
+ }
+
+ if (!readPartialAudioRecordAndSubmit(audioStartFrame, audioStartPosition)) {
+ return false;
+ }
+
+ ++audioStartFrame;
+ assert(audioStartFrame < videoStartFrame);
+
+ int oddRemainder = audioStartFrame & 1;
+ int audioRecordStart = (audioStartFrame * _audioRecordInterval) + oddRemainder + _firstAudioRecordPosition;
+ int audioRecordEnd = (audioRecordStart + ((audioRecordSize - 1) * 2)) + oddRemainder + _firstAudioRecordPosition;
+
+ if (audioStartPosition >= audioRecordStart && audioStartPosition <= audioRecordEnd) {
+ if (!readPartialAudioRecordAndSubmit(audioStartFrame, audioStartPosition + 1)) {
+ return false;
+ }
+
+ ++audioStartFrame;
+ }
+ }
+
+ int audioPosition, audioSize;
+ for (int i = audioStartFrame; i < videoStartFrame; i++) {
+ if (!readAudioDataFromRecord(i, _audioBuffer, audioPosition, audioSize)) {
+ break;
+ }
+
+ _audioList.addBlock(audioPosition, audioSize, _audioBuffer);
+ }
}
- _dirtyPalette = true;
- delete[] paletteData;
+ return success;
}
-void RobotDecoder::RobotVideoTrack::calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes) {
- // This is an O(n) operation, as each frame has a different size.
- // We need to know the actual frame size to have a constant video size.
- uint32 pos = stream->pos();
+bool RobotDecoder::readPrimerData(byte *outEvenBuffer, byte *outOddBuffer) {
+ if (_primerReservedSize != 0) {
+ if (_totalPrimerSize != 0) {
+ _stream->seek(_primerPosition, SEEK_SET);
+ if (_evenPrimerSize > 0) {
+ _stream->read(outEvenBuffer, _evenPrimerSize);
+ }
+
+ if (_oddPrimerSize > 0) {
+ _stream->read(outOddBuffer, _oddPrimerSize);
+ }
+ }
+ } else if (_primerZeroCompressFlag) {
+ memset(outEvenBuffer, 0, _evenPrimerSize);
+ memset(outOddBuffer, 0, _oddPrimerSize);
+ } else {
+ error("ReadPrimerData - Flags corrupt");
+ }
+
+ return !_stream->err();
+}
+
+bool RobotDecoder::readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize) {
+ _stream->seek(_recordPositions[frameNo] + _videoSizes[frameNo], SEEK_SET);
+ _audioList.submitDriverMax();
+
+ // Compressed absolute position of the audio block in the audio stream
+ const int position = _stream->readSint32();
- uint16 width = 0, height = 0;
+ // Size of the block of audio, excluding the audio block header
+ int size = _stream->readSint32();
- for (int curFrame = 0; curFrame < _frameCount; curFrame++) {
- stream->skip(4);
- uint16 frameWidth = stream->readUint16();
- uint16 frameHeight = stream->readUint16();
- if (frameWidth > width)
- width = frameWidth;
- if (frameHeight > height)
- height = frameHeight;
- stream->skip(frameSizes[curFrame] - 8);
+ assert(size <= _expectedAudioBlockSize);
+
+ if (position == 0) {
+ return false;
}
- stream->seek(pos);
+ if (size != _expectedAudioBlockSize) {
+ memset(outBuffer, 0, kRobotZeroCompressSize);
+ _stream->read(outBuffer + kRobotZeroCompressSize, size);
+ size += kRobotZeroCompressSize;
+ } else {
+ _stream->read(outBuffer, size);
+ }
- _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+ outAudioPosition = position;
+ outAudioSize = size;
+ return !_stream->err();
}
-RobotDecoder::RobotAudioTrack::RobotAudioTrack() {
- _audioStream = Audio::makeQueuingAudioStream(11025, false);
+bool RobotDecoder::readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition) {
+ int audioPosition, audioSize;
+ bool success = readAudioDataFromRecord(startFrame, _audioBuffer, audioPosition, audioSize);
+ if (success) {
+ const int relativeStartOffset = (startPosition - audioPosition) / 2;
+ _audioList.addBlock(startPosition, audioSize - relativeStartOffset, _audioBuffer + relativeStartOffset);
+ }
+
+ return success;
}
-RobotDecoder::RobotAudioTrack::~RobotAudioTrack() {
- delete _audioStream;
+#pragma mark -
+#pragma mark RobotDecoder - Rendering
+
+uint16 RobotDecoder::getFrameSize(Common::Rect &outRect) const {
+ outRect.clip(0, 0);
+ for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+ ScreenItem &screenItem = *_screenItemList[i];
+ outRect.extend(screenItem.getNowSeenRect(*_plane));
+ }
+
+ return _numFramesTotal;
}
-void RobotDecoder::RobotAudioTrack::queueBuffer(byte *buffer, int size) {
- _audioStream->queueBuffer(buffer, size, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
+void RobotDecoder::doRobot() {
+ if (_status != kRobotStatusPlaying) {
+ return;
+ }
+
+ if (!_syncFrame) {
+ if (_cueForceShowFrame != -1) {
+ _currentFrameNo = _cueForceShowFrame;
+ _cueForceShowFrame = -1;
+ } else {
+ const int nextFrameNo = calculateNextFrameNo(_delayTime.predictedTicks());
+ if (nextFrameNo < _currentFrameNo) {
+ return;
+ }
+ _currentFrameNo = nextFrameNo;
+ }
+ }
+
+ if (_currentFrameNo >= _numFramesTotal) {
+ const int finalFrameNo = _numFramesTotal - 1;
+ if (_previousFrameNo == finalFrameNo) {
+ _status = kRobotStatusEnd;
+ if (_hasAudio) {
+ _audioList.stopAudio();
+ _frameRate = _normalFrameRate;
+ _hasAudio = false;
+ }
+ return;
+ } else {
+ _currentFrameNo = finalFrameNo;
+ }
+ }
+
+ if (_currentFrameNo == _previousFrameNo) {
+ _audioList.submitDriverMax();
+ return;
+ }
+
+ if (_hasAudio) {
+ for (int candidateFrameNo = _previousFrameNo + _maxSkippablePackets + 1; candidateFrameNo < _currentFrameNo; candidateFrameNo += _maxSkippablePackets + 1) {
+
+ _audioList.submitDriverMax();
+
+ int audioPosition, audioSize;
+ if (readAudioDataFromRecord(candidateFrameNo, _audioBuffer, audioPosition, audioSize)) {
+ _audioList.addBlock(audioPosition, audioSize, _audioBuffer);
+ }
+ }
+ _audioList.submitDriverMax();
+ }
+
+ _delayTime.startTiming();
+ seekToFrame(_currentFrameNo);
+ doVersion5();
+ if (_hasAudio) {
+ _audioList.submitDriverMax();
+ }
+}
+
+void RobotDecoder::frameAlmostVisible() {
+ if (_status == kRobotStatusPlaying && !_syncFrame) {
+ if (_previousFrameNo != _currentFrameNo) {
+ while (calculateNextFrameNo() < _currentFrameNo) {
+ _audioList.submitDriverMax();
+ }
+ }
+ }
+}
+
+void RobotDecoder::frameNowVisible() {
+ if (_status != kRobotStatusPlaying) {
+ return;
+ }
+
+ if (_syncFrame) {
+ _syncFrame = false;
+ if (_hasAudio) {
+ _audioList.startAudioNow();
+ _checkAudioSyncTime = _startTime + kAudioSyncCheckInterval;
+ }
+
+ setRobotTime(_currentFrameNo);
+ }
+
+ if (_delayTime.timingInProgress()) {
+ _delayTime.endTiming();
+ }
+
+ if (_hasAudio) {
+ _audioList.submitDriverMax();
+ }
+
+ if (_previousFrameNo != _currentFrameNo) {
+ _previousFrameNo = _currentFrameNo;
+ }
+
+ if (!_syncFrame && _hasAudio && getTickCount() >= _checkAudioSyncTime) {
+ RobotAudioStream::StreamState status;
+ const bool success = g_sci->_audio32->queryRobotAudio(status);
+ if (!success) {
+ return;
+ }
+
+ const int bytesPerFrame = status.rate / _normalFrameRate * (status.bits == 16 ? 2 : 1);
+ // check again in 1/3rd second
+ _checkAudioSyncTime = getTickCount() + 60 / 3;
+
+ const int currentVideoFrameNo = calculateNextFrameNo() - _startingFrameNo;
+ const int currentAudioFrameNo = status.bytesPlaying / bytesPerFrame;
+ debugC(kDebugLevelVideo, "Video frame %d %s audio frame %d", currentVideoFrameNo, currentVideoFrameNo == currentAudioFrameNo ? "=" : currentVideoFrameNo < currentAudioFrameNo ? "<" : ">", currentAudioFrameNo);
+ if (currentVideoFrameNo < _numFramesTotal &&
+ currentAudioFrameNo < _numFramesTotal) {
+
+ bool shouldResetRobotTime = false;
+
+ if (currentAudioFrameNo < currentVideoFrameNo - 1 && _frameRate != _minFrameRate) {
+ debugC(kDebugLevelVideo, "[v] Reducing frame rate");
+ _frameRate = _minFrameRate;
+ shouldResetRobotTime = true;
+ } else if (currentAudioFrameNo > currentVideoFrameNo + 1 && _frameRate != _maxFrameRate) {
+ debugC(kDebugLevelVideo, "[^] Increasing frame rate");
+ _frameRate = _maxFrameRate;
+ shouldResetRobotTime = true;
+ } else if (_frameRate != _normalFrameRate) {
+ debugC(kDebugLevelVideo, "[=] Setting to normal frame rate");
+ _frameRate = _normalFrameRate;
+ shouldResetRobotTime = true;
+ }
+
+ if (shouldResetRobotTime) {
+ if (currentAudioFrameNo < _currentFrameNo) {
+ setRobotTime(_currentFrameNo);
+ } else {
+ setRobotTime(currentAudioFrameNo);
+ }
+ }
+ }
+ }
+}
+
+void RobotDecoder::expandCel(byte* target, const byte* source, const int16 celWidth, const int16 celHeight) const {
+ assert(source != nullptr && target != nullptr);
+
+ const int sourceHeight = (celHeight * _verticalScaleFactor) / 100;
+ assert(sourceHeight > 0);
+
+ const int16 numerator = celHeight;
+ const int16 denominator = sourceHeight;
+ int remainder = 0;
+ for (int16 y = sourceHeight - 1; y >= 0; --y) {
+ remainder += numerator;
+ int16 linesToDraw = remainder / denominator;
+ remainder %= denominator;
+
+ while (linesToDraw--) {
+ memcpy(target, source, celWidth);
+ target += celWidth;
+ }
+
+ source += celWidth;
+ }
+}
+
+void RobotDecoder::setPriority(const int16 newPriority) {
+ _priority = newPriority;
+}
+
+void RobotDecoder::doVersion5(const bool shouldSubmitAudio) {
+ const RobotScreenItemList::size_type oldScreenItemCount = _screenItemList.size();
+ const int videoSize = _videoSizes[_currentFrameNo];
+ _doVersion5Scratch.resize(videoSize);
+
+ byte *videoFrameData = _doVersion5Scratch.begin();
+
+ if (!_stream->read(videoFrameData, videoSize)) {
+ error("RobotDecoder::doVersion5: Read error");
+ }
+
+ const RobotScreenItemList::size_type screenItemCount = READ_SCI11ENDIAN_UINT16(videoFrameData);
+
+ if (screenItemCount > kScreenItemListSize) {
+ return;
+ }
+
+ if (_hasAudio &&
+ (getSciVersion() < SCI_VERSION_3 || shouldSubmitAudio)) {
+ int audioPosition, audioSize;
+ if (readAudioDataFromRecord(_currentFrameNo, _audioBuffer, audioPosition, audioSize)) {
+ _audioList.addBlock(audioPosition, audioSize, _audioBuffer);
+ }
+ }
+
+ if (screenItemCount > oldScreenItemCount) {
+ _screenItemList.resize(screenItemCount);
+ _screenItemX.resize(screenItemCount);
+ _screenItemY.resize(screenItemCount);
+ _originalScreenItemX.resize(screenItemCount);
+ _originalScreenItemY.resize(screenItemCount);
+ }
+
+ createCels5(videoFrameData + 2, screenItemCount, true);
+ for (RobotScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ Common::Point position(_screenItemX[i], _screenItemY[i]);
+
+// TODO: Version 6 robot?
+// int scaleXRemainder;
+ if (_scaleInfo.signal == kScaleSignalManual) {
+ position.x = (position.x * _scaleInfo.x) / 128;
+// TODO: Version 6 robot?
+// scaleXRemainder = (position.x * _scaleInfo.x) % 128;
+ position.y = (position.y * _scaleInfo.y) / 128;
+ }
+
+ if (_screenItemList[i] == nullptr) {
+ CelInfo32 celInfo;
+ celInfo.bitmap = _celHandles[i].bitmapId;
+ ScreenItem *screenItem = new ScreenItem(_plane->_object, celInfo, position, _scaleInfo);
+ _screenItemList[i] = screenItem;
+ // TODO: Version 6 robot?
+ // screenItem->_field_30 = scaleXRemainder;
+
+ if (_priority == -1) {
+ screenItem->_fixedPriority = false;
+ } else {
+ screenItem->_fixedPriority = true;
+ screenItem->_priority = _priority;
+ }
+ g_sci->_gfxFrameout->addScreenItem(*screenItem);
+ } else {
+ ScreenItem *screenItem = _screenItemList[i];
+ screenItem->_celInfo.bitmap = _celHandles[i].bitmapId;
+ screenItem->_position = position;
+ // TODO: Version 6 robot?
+ // screenItem->_field_30 = scaleXRemainder;
+
+ if (_priority == -1) {
+ screenItem->_fixedPriority = false;
+ } else {
+ screenItem->_fixedPriority = true;
+ screenItem->_priority = _priority;
+ }
+ g_sci->_gfxFrameout->updateScreenItem(*screenItem);
+ }
+ }
+
+ for (RobotScreenItemList::size_type i = screenItemCount; i < oldScreenItemCount; ++i) {
+ if (_screenItemList[i] != nullptr) {
+ g_sci->_gfxFrameout->deleteScreenItem(*_screenItemList[i]);
+ _screenItemList[i] = nullptr;
+ }
+ }
+}
+
+void RobotDecoder::createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette) {
+ preallocateCelMemory(rawVideoData, numCels);
+ for (int16 i = 0; i < numCels; ++i) {
+ rawVideoData += createCel5(rawVideoData, i, usePalette);
+ }
+}
+
+uint32 RobotDecoder::createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette) {
+ _verticalScaleFactor = rawVideoData[1];
+ const int16 celWidth = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 2);
+ const int16 celHeight = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 4);
+ const Common::Point celPosition((int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 10),
+ (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 12));
+ const uint16 dataSize = READ_SCI11ENDIAN_UINT16(rawVideoData + 14);
+ const int16 numDataChunks = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 16);
+
+ rawVideoData += kCelHeaderSize;
+
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ Common::Point origin;
+ if (scriptWidth == kLowResX && scriptHeight == kLowResY) {
+ const Ratio lowResToScreenX(screenWidth, kLowResX);
+ const Ratio lowResToScreenY(screenHeight, kLowResY);
+ const Ratio screenToLowResX(kLowResX, screenWidth);
+ const Ratio screenToLowResY(kLowResY, screenHeight);
+
+ const int16 scaledX = celPosition.x + (_position.x * lowResToScreenX).toInt();
+ const int16 scaledY1 = celPosition.y + (_position.y * lowResToScreenY).toInt();
+ const int16 scaledY2 = scaledY1 + celHeight - 1;
+
+ const int16 lowResX = (scaledX * screenToLowResX).toInt();
+ const int16 lowResY = (scaledY2 * screenToLowResY).toInt();
+
+ origin.x = (scaledX - (lowResX * lowResToScreenX).toInt()) * -1;
+ origin.y = (lowResY * lowResToScreenY).toInt() - scaledY1;
+ _screenItemX[screenItemIndex] = lowResX;
+ _screenItemY[screenItemIndex] = lowResY;
+
+ debugC(kDebugLevelVideo, "Low resolution position c: %d %d l: %d/%d %d/%d d: %d %d s: %d/%d %d/%d x: %d y: %d", celPosition.x, celPosition.y, lowResX, scriptWidth, lowResY, scriptHeight, origin.x, origin.y, scaledX, screenWidth, scaledY2, screenHeight, scaledX - origin.x, scaledY2 - origin.y);
+ } else {
+ const int16 highResX = celPosition.x + _position.x;
+ const int16 highResY = celPosition.y + _position.y + celHeight - 1;
+
+ origin.x = 0;
+ origin.y = celHeight - 1;
+ _screenItemX[screenItemIndex] = highResX;
+ _screenItemY[screenItemIndex] = highResY;
+
+ debugC(kDebugLevelVideo, "High resolution position c: %d %d s: %d %d d: %d %d", celPosition.x, celPosition.y, highResX, highResY, origin.x, origin.y);
+ }
+
+ _originalScreenItemX[screenItemIndex] = celPosition.x;
+ _originalScreenItemY[screenItemIndex] = celPosition.y;
+
+ assert(_celHandles[screenItemIndex].area >= celWidth * celHeight);
+
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_celHandles[screenItemIndex].bitmapId);
+ assert(bitmap.getWidth() == celWidth && bitmap.getHeight() == celHeight);
+ assert(bitmap.getXResolution() == _xResolution && bitmap.getYResolution() == _yResolution);
+ assert(bitmap.getHunkPaletteOffset() == (uint32)bitmap.getWidth() * bitmap.getHeight() + SciBitmap::getBitmapHeaderSize());
+ bitmap.setOrigin(origin);
+
+ byte *targetBuffer = nullptr;
+ if (_verticalScaleFactor == 100) {
+ // direct copy to bitmap
+ targetBuffer = bitmap.getPixels();
+ } else {
+ // go through squashed cel decompressor
+ _celDecompressionBuffer.resize(_celDecompressionArea >= celWidth * (celHeight * _verticalScaleFactor / 100));
+ targetBuffer = _celDecompressionBuffer.begin();
+ }
+
+ for (int i = 0; i < numDataChunks; ++i) {
+ uint compressedSize = READ_SCI11ENDIAN_UINT32(rawVideoData);
+ uint decompressedSize = READ_SCI11ENDIAN_UINT32(rawVideoData + 4);
+ uint16 compressionType = READ_SCI11ENDIAN_UINT16(rawVideoData + 8);
+ rawVideoData += 10;
+
+ switch (compressionType) {
+ case kCompressionLZS: {
+ Common::MemoryReadStream videoDataStream(rawVideoData, compressedSize, DisposeAfterUse::NO);
+ _decompressor.unpack(&videoDataStream, targetBuffer, compressedSize, decompressedSize);
+ break;
+ }
+ case kCompressionNone:
+ Common::copy(rawVideoData, rawVideoData + decompressedSize, targetBuffer);
+ break;
+ default:
+ error("Unknown compression type %d!", compressionType);
+ }
+
+ rawVideoData += compressedSize;
+ targetBuffer += decompressedSize;
+ }
+
+ if (_verticalScaleFactor != 100) {
+ expandCel(bitmap.getPixels(), _celDecompressionBuffer.begin(), celWidth, celHeight);
+ }
+
+ if (usePalette) {
+ Common::copy(_rawPalette, _rawPalette + kRawPaletteSize, bitmap.getHunkPalette());
+ }
+
+ return kCelHeaderSize + dataSize;
}
-Audio::AudioStream *RobotDecoder::RobotAudioTrack::getAudioStream() const {
- return _audioStream;
+void RobotDecoder::preallocateCelMemory(const byte *rawVideoData, const int16 numCels) {
+ for (CelHandleList::size_type i = 0; i < _celHandles.size(); ++i) {
+ CelHandleInfo &celHandle = _celHandles[i];
+
+ if (celHandle.status == CelHandleInfo::kFrameLifetime) {
+ _segMan->freeBitmap(celHandle.bitmapId);
+ celHandle.bitmapId = NULL_REG;
+ celHandle.status = CelHandleInfo::kNoCel;
+ celHandle.area = 0;
+ }
+ }
+ _celHandles.resize(numCels);
+
+ const int numFixedCels = MIN(numCels, (int16)kFixedCelListSize);
+ for (int i = 0; i < numFixedCels; ++i) {
+ CelHandleInfo &celHandle = _celHandles[i];
+
+ // NOTE: There was a check to see if the cel handle was not allocated
+ // here, for some reason, which would mean that nothing was ever
+ // allocated from fixed cels, because the _celHandles array just got
+ // deleted and recreated...
+ if (celHandle.bitmapId == NULL_REG) {
+ break;
+ }
+
+ celHandle.bitmapId = _fixedCels[i];
+ celHandle.status = CelHandleInfo::kRobotLifetime;
+ celHandle.area = _maxCelArea[i];
+ }
+
+ uint maxFrameArea = 0;
+ for (int i = 0; i < numCels; ++i) {
+ const int16 celWidth = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 2);
+ const int16 celHeight = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 4);
+ const uint16 dataSize = READ_SCI11ENDIAN_UINT16(rawVideoData + 14);
+ const uint area = celWidth * celHeight;
+
+ if (area > maxFrameArea) {
+ maxFrameArea = area;
+ }
+
+ CelHandleInfo &celHandle = _celHandles[i];
+ if (celHandle.status == CelHandleInfo::kRobotLifetime) {
+ if (_maxCelArea[i] < area) {
+ _segMan->freeBitmap(celHandle.bitmapId);
+ _segMan->allocateBitmap(&celHandle.bitmapId, celWidth, celHeight, 255, 0, 0, _xResolution, _yResolution, kRawPaletteSize, false, false);
+ celHandle.area = area;
+ celHandle.status = CelHandleInfo::kFrameLifetime;
+ }
+ } else if (celHandle.status == CelHandleInfo::kNoCel) {
+ _segMan->allocateBitmap(&celHandle.bitmapId, celWidth, celHeight, 255, 0, 0, _xResolution, _yResolution, kRawPaletteSize, false, false);
+ celHandle.area = area;
+ celHandle.status = CelHandleInfo::kFrameLifetime;
+ } else {
+ error("Cel Handle has bad status");
+ }
+
+ rawVideoData += kCelHeaderSize + dataSize;
+ }
+
+ if (maxFrameArea > _celDecompressionBuffer.size()) {
+ _celDecompressionBuffer.resize(maxFrameArea);
+ }
}
} // End of namespace Sci
diff --git a/engines/sci/video/robot_decoder.h b/engines/sci/video/robot_decoder.h
index 4faea5008a..9d8c720968 100644
--- a/engines/sci/video/robot_decoder.h
+++ b/engines/sci/video/robot_decoder.h
@@ -20,109 +20,1412 @@
*
*/
-#ifndef SCI_VIDEO_ROBOT_DECODER_H
-#define SCI_VIDEO_ROBOT_DECODER_H
+#ifndef SCI_SOUND_DECODERS_ROBOT_H
+#define SCI_SOUND_DECODERS_ROBOT_H
-#include "common/rational.h"
-#include "common/rect.h"
-#include "video/video_decoder.h"
+#include "audio/audiostream.h" // for AudioStream
+#include "audio/rate.h" // for st_sample_t
+#include "common/array.h" // for Array
+#include "common/mutex.h" // for StackLock, Mutex
+#include "common/rect.h" // for Point, Rect (ptr only)
+#include "common/scummsys.h" // for int16, int32, byte, uint16
+#include "sci/engine/vm_types.h" // for NULL_REG, reg_t
+#include "sci/graphics/helpers.h" // for GuiResourceId
+#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem (ptr o...
-namespace Audio {
-class QueuingAudioStream;
-}
+namespace Common { class SeekableSubReadStreamEndian; }
+namespace Sci {
+class Plane;
+class SegManager;
-namespace Common {
-class SeekableSubReadStreamEndian;
-}
+// There were 3 different Robot video versions, used in the following games:
+// - v4: PQ:SWAT demo
+// - v5: KQ7 DOS, Phantasmagoria, PQ:SWAT, Lighthouse
+// - v6: RAMA
+//
+// Notes on Robot v5/v6 format:
+//
+// Robot is a packetized streaming AV format that encodes multiple bitmaps +
+// positioning data, plus synchronised audio, for rendering in the SCI graphics
+// system.
+//
+// Unlike traditional AV formats, Robot videos almost always require playback
+// within the game engine because certain information (like the resolution of
+// the Robot coordinates and the background for the video) is dependent on data
+// that does not exist within the Robot file itself.
+//
+// The Robot container consists of a file header, an optional primer audio
+// section, an optional colour palette, a frame seek index, a set of cuepoints,
+// and variable-sized packets of compressed video+audio data.
+//
+// Integers in Robot files are coded using native endianness (LSB for x86
+// versions, MSB for 68k/PPC versions).
+//
+// Robot video coding is a relatively simple variable-length compression with no
+// interframe compression. Each cel in a frame is constructed from multiple
+// contiguous data blocks, each of which can be independently compressed with
+// LZS or left uncompressed. An entire cel can also be line decimated, where
+// lines are deleted from the source bitmap at compression time and are
+// reconstructed by decompression using line doubling. Each cel also includes
+// coordinates where it should be placed within the video frame, relative to the
+// top-left corner of the frame.
+//
+// Audio coding is fixed-length, and all audio blocks except for the primer
+// audio are the same size. Audio is encoded with Sierra SOL DPCM16 compression,
+// and is split into two channels ('even' and 'odd'), each at a 11025Hz sample
+// rate. The original signal is restored by interleaving samples from the two
+// channels together. Channel packets are 'even' if they have an ''absolute
+// position of audio'' that is evenly divisible by 2; otherwise, they are 'odd'.
+// Because the channels use DPCM compression, there is an 8-byte runway at the
+// start of every audio block that is never written to the output stream, which
+// is used to move the signal to the correct location by the 9th sample.
+//
+// File header (v5/v6):
+//
+// byte | description
+// 0 | signature 0x16
+// 1 | unused
+// 2-5 | signature 'SOL\0'
+// 6-7 | version (4, 5, and 6 are the only known versions)
+// 8-9 | size of audio blocks
+// 10-11 | primer is compressed flag
+// 12-13 | unused
+// 14-15 | total number of video frames
+// 16-17 | embedded palette size, in bytes
+// 18-19 | primer reserved size
+// 20-21 | coordinate X-resolution (if 0, uses game coordinates)
+// 22-23 | coordinate Y-resolution (if 0, uses game coordinates)
+// 24 | if non-zero, Robot includes a palette
+// 25 | if non-zero, Robot includes audio
+// 26-27 | unused
+// 28-29 | the frame rate, in frames per second
+// 30-31 | coordinate conversion flag; if true, screen item coordinates
+// | from the robot should be used as-is with NO conversion when
+// | explicitly displaying a specific frame
+// 32-33 | the maximum number of packets that can be skipped without causing
+// | audio drop-out
+// 34-35 | the maximum possible number of cels that will be displayed in any
+// | frame of the robot
+// 36-39 | the maximum possible size, in bytes, of the first fixed cel
+// 40-43 | the maximum possible size, in bytes, of the second fixed cel
+// 44-47 | the maximum possible size, in bytes, of the third fixed cel
+// 48-51 | the maximum possible size, in bytes, of the fourth fixed cel
+// 52-59 | unused
+//
+// If the ''file includes audio'' flag is false, seek ''primer reserved size''
+// bytes from the end of the file header to get past a padding zone.
+//
+// If the ''file includes audio'' flag is true, and the ''primer reserved size''
+// is not zero, the data immediately after the file header consists of an audio
+// primer header plus compressed audio data:
+//
+// Audio primer header:
+//
+// byte | description
+// 0-3 | the size, in bytes, of the entire primer audio section
+// 4-5 | the compression format of the primer audio (must be zero)
+// 6-9 | the size, in bytes, of the "even" primer
+// 10-13 | the size, in bytes, of the "odd" primer
+//
+// If the combined sizes of the even and odd primers do not match the ''primer
+// reserved size'', the next header block can be found ''primer reserved size''
+// bytes from the *start* of the audio primer header.
+//
+// Otherwise, if the Robot has audio, and the ''primer reserved size'' is zero,
+// and the ''primer is compressed flag'' is set, the "even" primer size is
+// 19922, the "odd" primer size is 21024, and the "even" and "odd" buffers
+// should be zero-filled.
+//
+// Any other combination of these flags is an error.
+//
+// If the Robot has a palette, the next ''palette size'' bytes should be read
+// as a SCI HunkPalette. Otherwise, seek ''palette size'' bytes from the current
+// position to get to the frame index.
+//
+// The next section of the Robot is the video frame size index. In version 5
+// robots, read ''total number of frames'' 16-bit integers to get the size of
+// the compressed video for each frame. For version 6 robots, use 32-bit
+// integers.
+//
+// The next section of the Robot is the packet size index (combined compressed
+// size of video + audio for each frame). In version 5 Robots, read ''total
+// number of frames'' 16-bit integers. In version 6 robots, use 32-bit integers.
+//
+// The next section of the Robot is the cue times index. Read 256 32-bit
+// integers, which represent the number of ticks from the start of playback that
+// the given cue point falls on.
+//
+// The next section of the Robot is the cue values index. Read 256 16-bit
+// integers, which represent the actual cue values that will be passed back to
+// the game engine when a cue is requested.
+//
+// Finally, to get to the first frame packet, seek from the current position to
+// the start of the next 2048-byte-aligned sector.
+//
+// Frame packet:
+//
+// byte | description
+// 0..n | video data (size is in the ''video frame size index'')
+// n+1.. | optional audio data (size is ''size of audio blocks'')
+//
+// Video data:
+//
+// byte | description
+// 0-2 | number of cels in the frame (max 10)
+// 3..n | cels
+//
+// Cel:
+//
+// 0-17 | cel header
+// 18..n | data chunks
+//
+// Cel header:
+//
+// byte | description
+// 0 | unused
+// 1 | vertical scale factor, in percent decimation (100 = no decimation,
+// | 50 = 50% of lines were removed)
+// 2-3 | cel width
+// 4-5 | cel height
+// 6-9 | unused
+// 10-11 | cel x-position, in Robot coordinates
+// 12-13 | cel y-position, in Robot coordinates
+// 14-15 | cel total data chunk size, in bytes
+// 16-17 | number of data chunks
+//
+// Cel data chunk:
+//
+// 0-9 | cel data chunk header
+// 10..n | cel data
+//
+// Cel data chunk header:
+//
+// byte | description
+// 0-3 | compressed size
+// 4-7 | decompressed size
+// 8-9 | compression type (0 = LZS, 2 = uncompressed)
+//
+// Random frame seeking can be done by calculating the address of the frame
+// packet by adding up the ''packet size index'' entries up to the current
+// frame. This will normally disable audio playback, as audio data in a packet
+// does not correspond to the video in the same packet.
+//
+// Audio data is placed immediately after the end of the video data in a packet,
+// and consists of an audio header plus compressed audio data:
+//
+// Audio data:
+//
+// byte | description
+// 0-7 | audio data header
+// 8-15 | DPCM runway
+// 16..n | compressed audio data
+//
+// Audio data header:
+//
+// byte | description
+// 0-3 | absolute position of audio in the audio stream
+// 4-7 | the size of the audio block, excluding the header
+//
+// When a block of audio is processed, first check to ensure that the
+// decompressed audio block's `position * 2 + length * 4` runs past the end of
+// the last packet of the same evenness/oddness. Discard the audio block
+// entirely if data has already been written past the end of this block for this
+// channel, or if the read head has already read past the end of this audio
+// block.
+//
+// If the block is not discarded, apply DPCM decompression to the entire block,
+// starting from beginning of the DPCM runway, using an initial sample value of
+// 0. Then, copy every sample from the decompressed source outside of the DPCM
+// runway into every *other* sample of the final audio buffer (1 -> 2, 2 -> 4,
+// 3 -> 6, etc.).
+//
+// Finally, for any skipped samples where the opposing (even/odd) channel did
+// not yet write, interpolate the skipped areas by adding together the
+// neighbouring samples from this audio block and dividing by two. (This allows
+// the audio quality to degrade to 11kHz in case it takes too long to decode all
+// the frames in the stream). Interpolated samples must not be written on top of
+// true data from the opposing channel. Audio from later packets must also not
+// be written on top of data in the same channel that was already written by an
+// earlier packet, in particular because the first 8 bytes of the next packet
+// are garbage data used to move the waveform to the correct position (due to
+// the use of DPCM compression).
-namespace Sci {
+#pragma mark -
+#pragma mark RobotAudioStream
+
+/**
+ * A Robot audio stream is a simple loop buffer
+ * that accepts audio blocks from the Robot engine.
+ */
+class RobotAudioStream : public Audio::AudioStream {
+public:
+ enum {
+ /**
+ * The sample rate used for all robot audio.
+ */
+ kRobotSampleRate = 22050,
+
+ /**
+ * Multiplier for the size of a packet that
+ * is being expanded by writing to every other
+ * byte of the target buffer.
+ */
+ kEOSExpansion = 2
+ };
+
+ /**
+ * Playback state information. Used for framerate
+ * calculation.
+ */
+ struct StreamState {
+ /**
+ * The current position of the read head of
+ * the audio stream.
+ */
+ int bytesPlaying;
+
+ /**
+ * The sample rate of the audio stream.
+ * Always 22050.
+ */
+ uint16 rate;
+
+ /**
+ * The bit depth of the audio stream.
+ * Always 16.
+ */
+ uint8 bits;
+ };
+
+ /**
+ * A single packet of compressed audio from a
+ * Robot data stream.
+ */
+ struct RobotAudioPacket {
+ /**
+ * Raw DPCM-compressed audio data.
+ */
+ byte *data;
+
+ /**
+ * The size of the compressed audio data,
+ * in bytes.
+ */
+ int dataSize;
+
+ /**
+ * The uncompressed, file-relative position
+ * of this audio packet.
+ */
+ int position;
+
+ RobotAudioPacket(byte *data_, const int dataSize_, const int position_) :
+ data(data_), dataSize(dataSize_), position(position_) {}
+ };
+
+ RobotAudioStream(const int32 bufferSize);
+ virtual ~RobotAudioStream();
+
+ /**
+ * Adds a new audio packet to the stream.
+ * @returns `true` if the audio packet was fully
+ * consumed, otherwise `false`.
+ */
+ bool addPacket(const RobotAudioPacket &packet);
+
+ /**
+ * Prevents any additional audio packets from
+ * being added to the audio stream.
+ */
+ void finish();
+
+ /**
+ * Returns the current status of the audio
+ * stream.
+ */
+ StreamState getStatus() const;
+
+private:
+ Common::Mutex _mutex;
+
+ /**
+ * Loop buffer for playback. Contains decompressed
+ * 16-bit PCM samples.
+ */
+ byte *_loopBuffer;
+
+ /**
+ * The size of the loop buffer, in bytes.
+ */
+ int32 _loopBufferSize;
+
+ /**
+ * The position of the read head within the loop
+ * buffer, in bytes.
+ */
+ int32 _readHead;
+
+ /**
+ * The lowest file position that can be buffered,
+ * in uncompressed bytes.
+ */
+ int32 _readHeadAbs;
+
+ /**
+ * The highest file position that can be buffered,
+ * in uncompressed bytes.
+ */
+ int32 _maxWriteAbs;
+
+ /**
+ * The highest file position, in uncompressed bytes,
+ * that has been written to the stream.
+ * Different from `_maxWriteAbs`, which is the highest
+ * uncompressed position which *can* be written right
+ * now.
+ */
+ int32 _writeHeadAbs;
+
+ /**
+ * The highest file position, in uncompressed bytes,
+ * that has been written to the even & odd sides of
+ * the stream.
+ *
+ * Index 0 corresponds to the 'even' side; index
+ * 1 correspond to the 'odd' side.
+ */
+ int32 _jointMin[2];
+
+ /**
+ * When `true`, the stream is waiting for all primer
+ * blocks to be received before allowing playback to
+ * begin.
+ */
+ bool _waiting;
+
+ /**
+ * When `true`, the stream will accept no more audio
+ * blocks.
+ */
+ bool _finished;
+
+ /**
+ * The uncompressed position of the first packet of
+ * robot data. Used to decide whether all primer
+ * blocks have been received and the stream should
+ * be started.
+ */
+ int32 _firstPacketPosition;
+
+ /**
+ * Decompression buffer, used to temporarily store
+ * an uncompressed block of audio data.
+ */
+ byte *_decompressionBuffer;
+
+ /**
+ * The size of the decompression buffer, in bytes.
+ */
+ int32 _decompressionBufferSize;
+
+ /**
+ * The position of the packet currently in the
+ * decompression buffer. Used to avoid
+ * re-decompressing audio data that has already
+ * been decompressed during a partial packet read.
+ */
+ int32 _decompressionBufferPosition;
+
+ /**
+ * Calculates the absolute ranges for new fills
+ * into the loop buffer.
+ */
+ void fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex);
+
+ /**
+ * Interpolates `numSamples` samples from the read
+ * head, if no true samples were written for one
+ * (or both) of the joint channels.
+ */
+ void interpolateMissingSamples(const int32 numSamples);
+
+#pragma mark -
+#pragma mark RobotAudioStream - AudioStream implementation
+public:
+ int readBuffer(Audio::st_sample_t *outBuffer, int numSamples) override;
+ virtual bool isStereo() const override { return false; };
+ virtual int getRate() const override { return 22050; };
+ virtual bool endOfData() const override {
+ Common::StackLock lock(_mutex);
+ return _readHeadAbs >= _writeHeadAbs;
+ };
+ virtual bool endOfStream() const override {
+ Common::StackLock lock(_mutex);
+ return _finished && endOfData();
+ }
+};
+
+#pragma mark -
+#pragma mark RobotDecoder
+
+/**
+ * RobotDecoder implements the logic required
+ * for Robot animations.
+ *
+ * @note A paused or finished RobotDecoder was
+ * classified as serializable in SCI3, but the
+ * save/load code would attempt to use uninitialised
+ * values, so it seems that robots were not ever
+ * actually able to be saved.
+ */
+class RobotDecoder {
+public:
+ RobotDecoder(SegManager *segMan);
+ ~RobotDecoder();
+
+private:
+ SegManager *_segMan;
+
+#pragma mark Constants
+public:
+ /**
+ * The playback status of the robot.
+ */
+ enum RobotStatus {
+ kRobotStatusUninitialized = 0,
+ kRobotStatusPlaying = 1,
+ kRobotStatusEnd = 2,
+ kRobotStatusPaused = 3
+ };
+
+ enum {
+ // Special high value used to represent
+ // parameters that should be left unchanged
+ // when calling `showFrame`
+ kUnspecified = 50000
+ };
+
+private:
+ enum {
+ /**
+ * Maximum number of on-screen screen items.
+ */
+ kScreenItemListSize = 10,
+
+ /**
+ * Maximum number of queued audio blocks.
+ */
+ kAudioListSize = 10,
+
+ /**
+ * Maximum number of samples used for frame timing.
+ */
+ kDelayListSize = 10,
+
+ /**
+ * Maximum number of cues.
+ */
+ kCueListSize = 256,
+
+ /**
+ * Maximum number of 'fixed' cels that never
+ * change for the duration of a robot.
+ */
+ kFixedCelListSize = 4,
+
+ /**
+ * The size of a hunk palette in the Robot stream.
+ */
+ kRawPaletteSize = 1200,
+
+ /**
+ * The size of a frame of Robot data. This
+ * value was used to align the first block of
+ * data after the main Robot header to the next
+ * CD sector.
+ */
+ kRobotFrameSize = 2048,
+
+ /**
+ * The size of a block of zero-compressed
+ * audio. Used to fill audio when the size of
+ * an audio packet does not match the expected
+ * packet size.
+ */
+ kRobotZeroCompressSize = 2048,
-class RobotDecoder : public Video::VideoDecoder {
+ /**
+ * The size of the audio block header, in bytes.
+ * The audio block header consists of the
+ * compressed size of the audio in the record,
+ * plus the position of the audio in the
+ * compressed data stream.
+ */
+ kAudioBlockHeaderSize = 8,
+
+ /**
+ * The size of a Robot cel header, in bytes.
+ */
+ kCelHeaderSize = 22,
+
+ /**
+ * The maximum amount that the frame rate is
+ * allowed to drift from the nominal frame rate
+ * in order to correct for AV drift or slow
+ * playback.
+ */
+ kMaxFrameRateDrift = 1
+ };
+
+ /**
+ * The version number for the currently loaded
+ * robot.
+ *
+ * There are several known versions of robot:
+ *
+ * v2: before Nov 1994; no known examples
+ * v3: before Nov 1994; no known examples
+ * v4: Jan 1995; PQ:SWAT demo
+ * v5: Mar 1995; SCI2.1 and SCI3 games
+ * v6: SCI3 games
+ */
+ uint16 _version;
+
+#pragma mark -
+#pragma mark Initialisation
+private:
+ /**
+ * Sets up the read stream for the robot.
+ */
+ void initStream(const GuiResourceId robotId);
+
+ /**
+ * Sets up the initial values for playback control.
+ */
+ void initPlayback();
+
+ /**
+ * Sets up the initial values for audio decoding.
+ */
+ void initAudio();
+
+ /**
+ * Sets up the initial values for video rendering.
+ */
+ void initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize);
+
+ /**
+ * Sets up the robot's data record and cue positions.
+ */
+ void initRecordAndCuePositions();
+
+#pragma mark -
+#pragma mark Playback
public:
- RobotDecoder(bool isBigEndian);
- virtual ~RobotDecoder();
+ /**
+ * Opens a robot file for playback.
+ * Newly opened robots are paused by default.
+ */
+ void open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale);
- bool loadStream(Common::SeekableReadStream *stream);
- bool load(GuiResourceId id);
+ /**
+ * Closes the currently open robot file.
+ */
void close();
- void setPos(uint16 x, uint16 y) { _pos = Common::Point(x, y); }
- Common::Point getPos() const { return _pos; }
+ /**
+ * Pauses the robot. Once paused, the audio for a robot
+ * is disabled until the end of playback.
+ */
+ void pause();
+
+ /**
+ * Resumes a paused robot.
+ */
+ void resume();
+
+ /**
+ * Moves robot to the specified frame and pauses playback.
+ *
+ * @note Called DisplayFrame in SSCI.
+ */
+ void showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority);
+
+ /**
+ * Retrieves the value associated with the
+ * current cue point.
+ */
+ int16 getCue() const;
+
+ /**
+ * Gets the currently displayed frame.
+ */
+ int16 getFrameNo() const;
+
+ /**
+ * Gets the playback status of the player.
+ */
+ RobotStatus getStatus() const;
+
+private:
+ /**
+ * The read stream containing raw robot data.
+ */
+ Common::SeekableSubReadStreamEndian *_stream;
+
+ /**
+ * The current status of the player.
+ */
+ RobotStatus _status;
+
+ typedef Common::Array<int> PositionList;
+
+ /**
+ * A map of frame numbers to byte offsets within `_stream`.
+ */
+ PositionList _recordPositions;
+
+ /**
+ * The offset of the Robot file within a
+ * resource bundle.
+ */
+ int32 _fileOffset;
+
+ /**
+ * A list of cue times that is updated to
+ * prevent earlier cue values from being
+ * given to the game more than once.
+ */
+ mutable int32 _cueTimes[kCueListSize];
+
+ /**
+ * The original list of cue times from the
+ * raw Robot data.
+ */
+ int32 _masterCueTimes[kCueListSize];
+
+ /**
+ * The list of values to provide to a game
+ * when a cue value is requested.
+ */
+ int32 _cueValues[kCueListSize];
+
+ /**
+ * The current playback frame rate.
+ */
+ int16 _frameRate;
+
+ /**
+ * The nominal playback frame rate.
+ */
+ int16 _normalFrameRate;
+
+ /**
+ * The minimal playback frame rate. Used to
+ * correct for AV sync drift when the video
+ * is more than one frame ahead of the audio.
+ */
+ int16 _minFrameRate;
+
+ /**
+ * The maximum playback frame rate. Used to
+ * correct for AV sync drift when the video
+ * is more than one frame behind the audio.
+ */
+ int16 _maxFrameRate;
+
+ /**
+ * The maximum number of record blocks that
+ * can be skipped without causing audio to
+ * drop out.
+ */
+ int16 _maxSkippablePackets;
+
+ /**
+ * The currently displayed frame number.
+ */
+ int _currentFrameNo;
+
+ /**
+ * The last displayed frame number.
+ */
+ int _previousFrameNo;
+
+ /**
+ * The time, in ticks, when the robot was
+ * last started or resumed.
+ */
+ int32 _startTime;
+
+ /**
+ * The first frame displayed when the
+ * robot was resumed.
+ */
+ int32 _startFrameNo;
+
+ /**
+ * The last frame displayed when the robot
+ * was resumed.
+ */
+ int32 _startingFrameNo;
+
+ /**
+ * Seeks the raw data stream to the record for
+ * the given frame number.
+ */
+ bool seekToFrame(const int frameNo);
-protected:
- void readNextPacket();
+ /**
+ * Sets the start time and frame of the robot
+ * when the robot is started or resumed.
+ */
+ void setRobotTime(const int frameNo);
+#pragma mark -
+#pragma mark Timing
private:
- class RobotVideoTrack : public FixedRateVideoTrack {
+ /**
+ * This class tracks the amount of time it takes for
+ * a frame of robot animation to be rendered. This
+ * information is used by the player to speculatively
+ * skip rendering of future frames to keep the
+ * animation in sync with the robot audio.
+ */
+ class DelayTime {
public:
- RobotVideoTrack(int frameCount);
- ~RobotVideoTrack();
-
- uint16 getWidth() const;
- uint16 getHeight() const;
- Graphics::PixelFormat getPixelFormat() const;
- int getCurFrame() const { return _curFrame; }
- int getFrameCount() const { return _frameCount; }
- const Graphics::Surface *decodeNextFrame() { return _surface; }
- const byte *getPalette() const { _dirtyPalette = false; return _palette; }
- bool hasDirtyPalette() const { return _dirtyPalette; }
-
- void readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize);
- void calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes);
- Graphics::Surface *getSurface() { return _surface; }
- void increaseCurFrame() { _curFrame++; }
-
- protected:
- Common::Rational getFrameRate() const { return Common::Rational(60, 10); }
+ DelayTime(RobotDecoder *decoder);
+
+ /**
+ * Starts performance timing.
+ */
+ void startTiming();
+
+ /**
+ * Ends performance timing.
+ */
+ void endTiming();
+
+ /**
+ * Returns whether or not timing is currently in
+ * progress.
+ */
+ bool timingInProgress() const;
+
+ /**
+ * Returns the median time, in ticks, of the
+ * currently stored timing samples.
+ */
+ int predictedTicks() const;
private:
- int _frameCount;
- int _curFrame;
- byte _palette[256 * 3];
- mutable bool _dirtyPalette;
- Graphics::Surface *_surface;
+ RobotDecoder *_decoder;
+
+ /**
+ * The start time, in ticks, of the current timing
+ * loop. If no loop is in progress, the value is 0.
+ *
+ * @note This is slightly different than SSCI where
+ * the not-timing value was -1.
+ */
+ uint32 _startTime;
+
+ /**
+ * A sorted list containing the timing data for
+ * the last `kDelayListSize` frames, in ticks.
+ */
+ int _delays[kDelayListSize];
+
+ /**
+ * A list of monotonically increasing identifiers
+ * used to identify and replace the oldest sample
+ * in the `_delays` array when finishing the
+ * next timing operation.
+ */
+ uint _timestamps[kDelayListSize];
+
+ /**
+ * The identifier of the oldest timing.
+ */
+ uint _oldestTimestamp;
+
+ /**
+ * The identifier of the newest timing.
+ */
+ uint _newestTimestamp;
+
+ /**
+ * Sorts the list of timings.
+ */
+ void sortList();
};
- class RobotAudioTrack : public AudioTrack {
+ /**
+ * Calculates the next frame number that needs
+ * to be rendered, using the timing data
+ * collected by DelayTime.
+ */
+ uint16 calculateNextFrameNo(const uint32 extraTicks = 0) const;
+
+ /**
+ * Calculates and returns the number of frames
+ * that should be rendered in `ticks` time,
+ * according to the current target frame rate
+ * of the robot.
+ */
+ uint32 ticksToFrames(const uint32 ticks) const;
+
+ /**
+ * Gets the current game time, in ticks.
+ */
+ uint32 getTickCount() const;
+
+ /**
+ * The performance timer for the robot.
+ */
+ DelayTime _delayTime;
+
+#pragma mark -
+#pragma mark Audio
+private:
+ enum {
+ /**
+ * The number of ticks that should elapse
+ * between each AV sync check.
+ */
+ kAudioSyncCheckInterval = 5 * 60 /* 5 seconds */
+ };
+
+ /**
+ * The status of the audio track of a Robot
+ * animation.
+ */
+ enum RobotAudioStatus {
+ kRobotAudioReady = 1,
+ kRobotAudioStopped = 2,
+ kRobotAudioPlaying = 3,
+ kRobotAudioPaused = 4,
+ kRobotAudioStopping = 5
+ };
+
+#pragma mark -
+#pragma mark Audio - AudioList
+private:
+ /**
+ * This class manages packetized audio playback
+ * for robots.
+ */
+ class AudioList {
public:
- RobotAudioTrack();
- ~RobotAudioTrack();
+ AudioList();
+
+ /**
+ * Starts playback of robot audio.
+ */
+ void startAudioNow();
+
+ /**
+ * Stops playback of robot audio, allowing
+ * any queued audio to finish playing back.
+ */
+ void stopAudio();
+
+ /**
+ * Stops playback of robot audio immediately.
+ */
+ void stopAudioNow();
+
+ /**
+ * Submits as many blocks of audio as possible
+ * to the audio engine.
+ */
+ void submitDriverMax();
+
+ /**
+ * Adds a new AudioBlock to the queue.
+ *
+ * @param position The absolute position of the
+ * audio for the block, in compressed bytes.
+ * @param size The size of the buffer.
+ * @param buffer A pointer to compressed audio
+ * data that will be copied into the new
+ * AudioBlock.
+ */
+ void addBlock(const int position, const int size, const byte *buffer);
- Audio::Mixer::SoundType getSoundType() const { return Audio::Mixer::kMusicSoundType; }
+ /**
+ * Immediately stops any active playback and
+ * purges all audio data in the audio list.
+ */
+ void reset();
- void queueBuffer(byte *buffer, int size);
+ /**
+ * Pauses the robot audio channel in
+ * preparation for the first block of audio
+ * data to be read.
+ */
+ void prepareForPrimer();
- protected:
- Audio::AudioStream *getAudioStream() const;
+ /**
+ * Sets the audio offset which is used to
+ * offset the position of audio packets
+ * sent to the audio stream.
+ */
+ void setAudioOffset(const int offset);
+
+#pragma mark -
+#pragma mark Audio - AudioList - AudioBlock
private:
- Audio::QueuingAudioStream *_audioStream;
+ /**
+ * AudioBlock represents a block of audio
+ * from the Robot's audio track.
+ */
+ class AudioBlock {
+ public:
+ AudioBlock(const int position, const int size, const byte *const data);
+ ~AudioBlock();
+
+ /**
+ * Submits the block of audio to the
+ * audio manager.
+ * @returns true if the block was fully
+ * read, or false if the block was not
+ * read or only partially read.
+ */
+ bool submit(const int startOffset);
+
+ private:
+ /**
+ * The absolute position, in compressed
+ * bytes, of this audio block's audio
+ * data in the audio stream.
+ */
+ int _position;
+
+ /**
+ * The compressed size, in bytes, of
+ * this audio block's audio data.
+ */
+ int _size;
+
+ /**
+ * A buffer containing raw
+ * SOL-compressed audio data.
+ */
+ byte *_data;
+ };
+
+ /**
+ * The list of compressed audio blocks
+ * submitted for playback.
+ */
+ AudioBlock *_blocks[kAudioListSize];
+
+ /**
+ * The number of blocks in `_blocks` that are
+ * ready to be submitted.
+ */
+ uint8 _blocksSize;
+
+ /**
+ * The index of the oldest submitted audio block.
+ */
+ uint8 _oldestBlockIndex;
+
+ /**
+ * The index of the newest submitted audio block.
+ */
+ uint8 _newestBlockIndex;
+
+ /**
+ * The offset used when sending packets to the
+ * audio stream.
+ */
+ int _startOffset;
+
+ /**
+ * The status of robot audio playback.
+ */
+ RobotAudioStatus _status;
+
+ /**
+ * Frees all audio blocks in the `_blocks` list.
+ */
+ void freeAudioBlocks();
};
- struct RobotHeader {
- // 6 bytes, identifier bytes
- uint16 version;
- uint16 audioChunkSize;
- uint16 audioSilenceSize;
- // 2 bytes, unknown
- uint16 frameCount;
- uint16 paletteDataSize;
- uint16 unkChunkDataSize;
- // 5 bytes, unknown
- byte hasSound;
- // 34 bytes, unknown
- } _header;
-
- void readHeaderChunk();
- void readFrameSizesChunk();
-
- Common::Point _pos;
- bool _isBigEndian;
- uint32 *_frameTotalSize;
-
- Common::SeekableSubReadStreamEndian *_fileStream;
-};
+ /**
+ * Whether or not this robot animation has
+ * an audio track.
+ */
+ bool _hasAudio;
+
+ /**
+ * The audio list for the current robot.
+ */
+ AudioList _audioList;
+
+ /**
+ * The size, in bytes, of a block of audio data,
+ * excluding the audio block header.
+ */
+ uint16 _audioBlockSize;
+
+ /**
+ * The expected size of a block of audio data,
+ * in bytes, excluding the audio block header.
+ */
+ int16 _expectedAudioBlockSize;
+
+ /**
+ * The number of compressed audio bytes that are
+ * needed per frame to fill the audio buffer
+ * without causing audio to drop out.
+ */
+ int16 _audioRecordInterval;
+
+ /**
+ * If true, primer audio buffers should be filled
+ * with silence instead of trying to read buffers
+ * from the Robot data.
+ */
+ uint16 _primerZeroCompressFlag;
+
+ /**
+ * The size, in bytes, of the primer audio in the
+ * Robot, including any extra alignment padding.
+ */
+ uint16 _primerReservedSize;
+
+ /**
+ * The combined size, in bytes, of the even and odd
+ * primer channels.
+ */
+ int32 _totalPrimerSize;
+
+ /**
+ * The absolute offset of the primer audio data in
+ * the robot data stream.
+ */
+ int32 _primerPosition;
+
+ /**
+ * The size, in bytes, of the even primer.
+ */
+ int32 _evenPrimerSize;
+
+ /**
+ * The size, in bytes, of the odd primer.
+ */
+ int32 _oddPrimerSize;
+
+ /**
+ * The absolute position in the audio stream of
+ * the first audio packet.
+ */
+ int32 _firstAudioRecordPosition;
-} // End of namespace Sci
+ /**
+ * A temporary buffer used to hold one frame of
+ * raw (DPCM-compressed) audio when reading audio
+ * records from the robot stream.
+ */
+ byte *_audioBuffer;
+ /**
+ * The next tick count when AV sync should be
+ * checked and framerate adjustments made, if
+ * necessary.
+ */
+ uint32 _checkAudioSyncTime;
+
+ /**
+ * Primes the audio buffer with the first frame
+ * of audio data.
+ *
+ * @note `primeAudio` was `InitAudio` in SSCI
+ */
+ bool primeAudio(const uint32 startTick);
+
+ /**
+ * Reads primer data from the robot data stream
+ * and puts it into the given buffers.
+ */
+ bool readPrimerData(byte *outEvenBuffer, byte *outOddBuffer);
+
+ /**
+ * Reads audio data for the given frame number
+ * into the given buffer.
+ *
+ * @param outAudioPosition The position of the
+ * audio, in compressed bytes, in the data stream.
+ * @param outAudioSize The size of the audio data,
+ * in compressed bytes.
+ */
+ bool readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize);
+
+ /**
+ * Submits part of the audio packet of the given
+ * frame to the audio list, starting `startPosition`
+ * bytes into the audio.
+ */
+ bool readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition);
+
+#pragma mark -
+#pragma mark Rendering
+public:
+ /**
+ * Puts the current dimensions of the robot, in game script
+ * coordinates, into the given rect, and returns the total
+ * number of frames in the robot animation.
+ */
+ uint16 getFrameSize(Common::Rect &outRect) const;
+
+ /**
+ * Pumps the robot player for the next frame of video.
+ * This is the main rendering function.
+ */
+ void doRobot();
+
+ /**
+ * Submits any outstanding audio blocks that should
+ * be added to the queue before the robot frame
+ * becomes visible.
+ */
+ void frameAlmostVisible();
+
+ /**
+ * Evaluates frame drift and makes modifications to
+ * the player in order to ensure that future frames
+ * will arrive on time.
+ */
+ void frameNowVisible();
+
+ /**
+ * Scales a vertically compressed cel to its original
+ * uncompressed dimensions.
+ */
+ void expandCel(byte *target, const byte* source, const int16 celWidth, const int16 celHeight) const;
+
+ /**
+ * Sets the visual priority of the robot.
+ * @see Plane::_priority
+ */
+ void setPriority(const int16 newPriority);
+
+private:
+ enum CompressionType {
+ kCompressionLZS = 0,
+ kCompressionNone = 2
+ };
+
+ /**
+ * Describes the state of a Robot video cel.
+ */
+ struct CelHandleInfo {
+ /**
+ * The persistence level of Robot cels.
+ */
+ enum CelHandleLifetime {
+ kNoCel = 0,
+ kFrameLifetime = 1,
+ kRobotLifetime = 2
+ };
+
+ /**
+ * A reg_t pointer to an in-memory
+ * bitmap containing the cel.
+ */
+ reg_t bitmapId;
+
+ /**
+ * The lifetime of the cel, either just
+ * for this frame or for the entire
+ * duration of the robot playback.
+ */
+ CelHandleLifetime status;
+
+ /**
+ * The size, in pixels, of the decompressed
+ * cel.
+ */
+ int area;
+
+ CelHandleInfo() : bitmapId(NULL_REG), status(kNoCel), area(0) {}
+ };
+
+ typedef Common::Array<ScreenItem *> RobotScreenItemList;
+ typedef Common::Array<CelHandleInfo> CelHandleList;
+ typedef Common::Array<int> VideoSizeList;
+ typedef Common::Array<uint> MaxCelAreaList;
+ typedef Common::Array<reg_t> FixedCelsList;
+ typedef Common::Array<Common::Point> CelPositionsList;
+ typedef Common::Array<byte> ScratchMemory;
+
+ /**
+ * Renders a version 5/6 robot frame.
+ */
+ void doVersion5(const bool shouldSubmitAudio = true);
+
+ /**
+ * Creates screen items for a version 5/6 robot.
+ */
+ void createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette);
+
+ /**
+ * Creates a single screen item for a cel in a
+ * version 5/6 robot.
+ *
+ * Returns the size, in bytes, of the raw cel data.
+ */
+ uint32 createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette);
+
+ /**
+ * Preallocates memory for the next `numCels` cels
+ * in the robot data stream.
+ */
+ void preallocateCelMemory(const byte *rawVideoData, const int16 numCels);
+
+ /**
+ * The decompressor for LZS-compressed cels.
+ */
+ DecompressorLZS _decompressor;
+
+ /**
+ * The origin of the robot animation, in screen
+ * coordinates.
+ */
+ Common::Point _position;
+
+ /**
+ * Global scaling applied to the robot.
+ */
+ ScaleInfo _scaleInfo;
+
+ /**
+ * The native resolution of the robot.
+ */
+ int16 _xResolution, _yResolution;
+
+ /**
+ * Whether or not the coordinates read from robot
+ * data are high resolution.
+ */
+ bool _isHiRes;
+
+ /**
+ * The maximum number of cels that will be rendered
+ * on any given frame in this robot. Used for
+ * preallocation of cel memory.
+ */
+ int16 _maxCelsPerFrame;
+
+ /**
+ * The maximum areas, in pixels, for each of
+ * the fixed cels in the robot. Used for
+ * preallocation of cel memory.
+ */
+ MaxCelAreaList _maxCelArea;
+
+ /**
+ * The hunk palette to use when rendering the
+ * current frame, if the `usePalette` flag was set
+ * in the robot header.
+ */
+ uint8 *_rawPalette;
+
+ /**
+ * A list of the raw video data sizes, in bytes,
+ * for each frame of the robot.
+ */
+ VideoSizeList _videoSizes;
+
+ /**
+ * A list of cels that will be present for the
+ * entire duration of the robot animation.
+ */
+ FixedCelsList _fixedCels;
+
+ /**
+ * A list of handles for each cel in the current
+ * frame.
+ */
+ CelHandleList _celHandles;
+
+ /**
+ * Scratch memory used to temporarily store
+ * decompressed cel data for vertically squashed
+ * cels.
+ */
+ ScratchMemory _celDecompressionBuffer;
+
+ /**
+ * The size, in bytes, of the squashed cel
+ * decompression buffer.
+ */
+ int _celDecompressionArea;
+
+ /**
+ * If true, the robot just started playing and
+ * is awaiting output for the first frame.
+ */
+ bool _syncFrame;
+
+ /**
+ * Scratch memory used to store the compressed robot
+ * video data for the current frame.
+ */
+ ScratchMemory _doVersion5Scratch;
+
+ /**
+ * When set to a non-negative value, forces the next
+ * call to doRobot to render the given frame number
+ * instead of whatever frame would have normally been
+ * rendered.
+ */
+ mutable int _cueForceShowFrame;
+
+ /**
+ * The plane where the robot animation will be drawn.
+ */
+ Plane *_plane;
+
+ /**
+ * A list of pointers to ScreenItems used by the robot.
+ */
+ RobotScreenItemList _screenItemList;
+
+ /**
+ * The positions of the various screen items in this
+ * robot, in screen coordinates.
+ */
+ Common::Array<int16> _screenItemX, _screenItemY;
+
+ /**
+ * The raw position values from the cel header for
+ * each screen item currently on-screen.
+ */
+ Common::Array<int16> _originalScreenItemX, _originalScreenItemY;
+
+ /**
+ * The duration of the current robot, in frames.
+ */
+ uint16 _numFramesTotal;
+
+ /**
+ * The screen priority of the video.
+ * @see ScreenItem::_priority
+ */
+ int16 _priority;
+
+ /**
+ * The amount of visual vertical compression applied
+ * to the current cel. A value of 100 means no
+ * compression; a value above 100 indicates how much
+ * the cel needs to be scaled along the y-axis to
+ * return to its original dimensions.
+ */
+ uint8 _verticalScaleFactor;
+};
+} // end of namespace Sci
#endif