diff options
Diffstat (limited to 'engines/sci')
28 files changed, 333 insertions, 178 deletions
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index 8737c8b954..83f1271252 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -53,6 +53,7 @@ #ifdef ENABLE_SCI32 #include "sci/graphics/frameout.h" #include "sci/graphics/paint32.h" +#include "sci/graphics/palette32.h" #include "video/coktel_decoder.h" #endif @@ -228,6 +229,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)); @@ -446,6 +449,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"); @@ -2843,6 +2847,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], ®, 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], ®_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"); @@ -4530,7 +4653,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; } diff --git a/engines/sci/console.h b/engines/sci/console.h index 0b87a4408b..d4b17ee802 100644 --- a/engines/sci/console.h +++ b/engines/sci/console.h @@ -163,6 +163,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); diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp index 83f1d30916..a00da7c5fd 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -556,7 +556,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, 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; } diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index 22d2b6f308..4a3d13cbed 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -3398,7 +3398,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}, diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h index 15c80a7277..36b0d06360 100644 --- a/engines/sci/engine/features.h +++ b/engines/sci/engine/features.h @@ -117,6 +117,17 @@ public: 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; + } #endif /** diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index cce9a223d3..03c8a673a8 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -466,6 +466,7 @@ 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); diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index 6e88112992..8093147cb4 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -466,6 +466,7 @@ 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 }, diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index 6ec61343b0..74c1f99778 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -733,11 +733,12 @@ namespace { } bool isSignedType(const char type) { - return type == 'd' || type == 'i'; + // For whatever reason, %d ends up being treated as unsigned in SSCI + return type == 'i'; } bool isUnsignedType(const char type) { - return strchr("uxXoc", type); + return strchr("duxXoc", type); } bool isStringType(const char type) { @@ -805,8 +806,11 @@ Common::String format(const Common::String &source, int argc, const reg_t *argv) continue; } - assert(argIndex < argc); - out += readPlaceholder(in, argv[argIndex++]); + if (argIndex < argc) { + out += readPlaceholder(in, argv[argIndex++]); + } else { + out += readPlaceholder(in, NULL_REG); + } } else { out += *in++; } diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp index 83a02883af..11378d7647 100644 --- a/engines/sci/engine/kvideo.cpp +++ b/engines/sci/engine/kvideo.cpp @@ -428,6 +428,11 @@ 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()); } diff --git a/engines/sci/engine/message.cpp b/engines/sci/engine/message.cpp index b0615b4213..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)) 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/savegame.cpp b/engines/sci/engine/savegame.cpp index 2e4da46b70..720f6783ee 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -643,20 +643,33 @@ void SoundCommandParser::reconstructPlayList() { _music->_mutex.unlock(); for (MusicList::iterator i = songs.begin(); i != songs.end(); ++i) { - initSoundResource(*i); + MusicEntry *entry = *i; + initSoundResource(entry); - if ((*i)->status == kSoundPlaying) { +#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) { + + 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); } } } diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index 5ac4b758ba..5f3370bad5 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -2190,8 +2190,27 @@ static const SciScriptPatcherEntry larry6Signatures[] = { #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 }, diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h index 361c1cb895..e8f0be3a79 100644 --- a/engines/sci/engine/segment.h +++ b/engines/sci/engine/segment.h @@ -552,8 +552,17 @@ public: case kArrayTypeID: return ((reg_t *)_data)[index]; case kArrayTypeByte: - case kArrayTypeString: - return make_reg(0, ((byte *)_data)[index]); + 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); } diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index dabf79a54b..4e9c59b225 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -399,6 +399,7 @@ 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,902, 64918, 0, "Str", "callKernel", NULL, 0, { WORKAROUND_FAKE, 0 } }, // tries to set an element of a string array to the ego object when starting a new game and selecting a chapter above 1 SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -684,21 +685,6 @@ const SciWorkaroundEntry kNewWindow_workarounds[] = { }; // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround -const SciWorkaroundEntry kNumCels_workarounds[] = { - { GID_GK1, 460, 64998, -1, "GKEgo", "lastCel", NULL, -1, { WORKAROUND_FAKE, 5 } }, // when Gabriel clicks "ask" on the bartender from a distance - { GID_GK1, 808, 64998, -1, "sDJEnters", "changeState", NULL, -1, { WORKAROUND_FAKE, 6 } }, // - { GID_GK2, 470, 64998, -1, "pLookieLoos", "lastCel", NULL, -1, { WORKAROUND_FAKE, 50 } }, // random background movement in the crime scene in Munich city centre - { GID_GK2, 470, 64998, -1, "pNewsCrew", "lastCel", NULL, -1, { WORKAROUND_FAKE, 18 } }, // random background movement in the crime scene in Munich city centre - { GID_GK2, 470, 64998, -1, "pLeberGroup", "lastCel", NULL, -1, { WORKAROUND_FAKE, 22 } }, // random background movement in the crime scene in Munich city centre - { GID_SQ6, 270, 64998, -1, "offWorld", "lastCel", NULL, -1, { WORKAROUND_FAKE, 3 } }, // when exiting the kidnapping room - { GID_SQ6, 320, 64998, -1, "wandererB", "lastCel", NULL, -1, { WORKAROUND_FAKE, 8 } }, // random background movement on Polysorbate LX street 3 - { GID_SQ6, 340, 64998, -1, "wandererB", "lastCel", NULL, -1, { WORKAROUND_FAKE, 8 } }, // random background movement on Polysorbate LX street 1 - { GID_SQ6, 530, 64998, -1, "monitors", "lastCel", NULL, -1, { WORKAROUND_FAKE, 5 } }, // random background movement during cutscene - { GID_SQ6, 680, 64998, -1, "ego", "lastCel", NULL, -1, { WORKAROUND_FAKE, 44 } }, // when double-clicking hand icon on blockage - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// 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 diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h index c50a7fb04e..2d72ae5811 100644 --- a/engines/sci/engine/workarounds.h +++ b/engines/sci/engine/workarounds.h @@ -85,7 +85,6 @@ extern const SciWorkaroundEntry kIsObject_workarounds[]; extern const SciWorkaroundEntry kMemory_workarounds[]; extern const SciWorkaroundEntry kMoveCursor_workarounds[]; extern const SciWorkaroundEntry kNewWindow_workarounds[]; -extern const SciWorkaroundEntry kNumCels_workarounds[]; extern const SciWorkaroundEntry kPalVarySetPercent_workarounds[]; extern const SciWorkaroundEntry kRandom_workarounds[]; extern const SciWorkaroundEntry kReadNumber_workarounds[]; diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp index 430500ce1e..09ea05bd59 100644 --- a/engines/sci/graphics/celobj32.cpp +++ b/engines/sci/graphics/celobj32.cpp @@ -800,7 +800,7 @@ int16 CelObjView::getNumLoops(const GuiResourceId viewId) { return resource->data[2]; } -int16 CelObjView::getNumCels(const GuiResourceId viewId, const int16 loopNo) { +int16 CelObjView::getNumCels(const GuiResourceId viewId, int16 loopNo) { const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false); if (!resource) { @@ -813,25 +813,15 @@ int16 CelObjView::getNumCels(const GuiResourceId viewId, const int16 loopNo) { // 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. At least GK1 room 800 does this, and gets - // stuck in an infinite loop because the game script expects this method - // to return a non-zero value. - // The scope of this bug means it is likely to pop up in other games, so we - // explicitly trap the bad condition here and report it so that any other - // game scripts relying on this broken behavior can be fixed as well + // 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) { - SciCallOrigin origin; - SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kNumCels_workarounds, &origin); - switch (solution.type) { - case WORKAROUND_NONE: - error("[CelObjView::getNumCels]: loop number %d is equal to loop count in view %u, %s", loopNo, viewId, origin.toString().c_str()); - case WORKAROUND_FAKE: - return (int16)solution.value; - case WORKAROUND_IGNORE: - return 0; - case WORKAROUND_STILLCALL: - break; - } + 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) { diff --git a/engines/sci/graphics/lists32.h b/engines/sci/graphics/lists32.h index 4f74c77325..ff0bc6ceae 100644 --- a/engines/sci/graphics/lists32.h +++ b/engines/sci/graphics/lists32.h @@ -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. */ diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h index 81e9bbbfd3..c4cfb35096 100644 --- a/engines/sci/graphics/palette32.h +++ b/engines/sci/graphics/palette32.h @@ -23,7 +23,6 @@ #ifndef SCI_GRAPHICS_PALETTE32_H #define SCI_GRAPHICS_PALETTE32_H -#include "sci/graphics/palette.h" namespace Sci { #pragma mark HunkPalette diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp index 23e92ef6a9..de6df39bb9 100644 --- a/engines/sci/graphics/screen.cpp +++ b/engines/sci/graphics/screen.cpp @@ -84,11 +84,6 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { } } - if (_resMan->detectHires()) { - _scriptWidth = 640; - _scriptHeight = 480; - } - // if not yet set, set those to script-width/height if (!_width) _width = _scriptWidth; diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp index d142ff75c3..7375fdeffd 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -527,7 +527,7 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const { --length; } } else { - width += font->getCharWidth(currentChar); + width += font->getCharWidth((unsigned char)currentChar); } if (length > 0) { diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp index ddcb50b140..bad0185179 100644 --- a/engines/sci/graphics/transitions32.cpp +++ b/engines/sci/graphics/transitions32.cpp @@ -778,7 +778,7 @@ bool GfxTransitions32::processPixelDissolve21Mid(const PlaneShowStyle &showStyle int seq = 1; uint iteration = 0; - const uint numIterationsPerTick = ARRAYSIZE(g_sci->_gfxFrameout->_showList); + const uint numIterationsPerTick = g_sci->_gfxFrameout->_showList.max_size(); clearShowRects(); diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp index 1db66644c8..bf0c990015 100644 --- a/engines/sci/graphics/video32.cpp +++ b/engines/sci/graphics/video32.cpp @@ -28,6 +28,7 @@ #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_... @@ -39,10 +40,12 @@ #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/graphics/video32.h" +#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 { @@ -68,7 +71,8 @@ void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const // 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. - _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, kLowResX, kLowResY, 0, false, false); + SciBitmap &bitmap = *_segMan->allocateBitmap(&_bitmap, _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; @@ -163,49 +167,37 @@ AVIPlayer::IOStatus AVIPlayer::init1x(const int16 x, const int16 y, int16 width, _pixelDouble = false; if (!width || !height) { - width = _decoder->getWidth(); - height = _decoder->getHeight(); - } else if (getSciVersion() == SCI_VERSION_2_1_EARLY && g_sci->getGameId() == GID_KQ7) { - // KQ7 1.51 provides an explicit width and height when it wants scaling, - // though the width and height it provides are not scaled - _pixelDouble = true; - width *= 2; - height *= 2; - } - - // QFG4CD gives non-multiple-of-2 values for width and height, - // which would normally be OK except the source video is a pixel bigger - // in each dimension + 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; - _drawRect.left = x; - _drawRect.top = y; - _drawRect.right = x + width; - _drawRect.bottom = y + height; - - // SCI2.1mid uses init2x to draw a pixel-doubled AVI, but SCI2 has only the - // one play routine which automatically pixel-doubles in hi-res mode - if (getSciVersion() == SCI_VERSION_2) { - // This is somewhat of a hack; credits.avi from GK1 is not - // rendered correctly in SSCI because it is a 640x480 video, but the - // game script gives the wrong dimensions. Since this is the only - // high-resolution AVI ever used, just set the draw rectangle to draw - // the entire screen - if (_decoder->getWidth() > 320) { - _drawRect.left = 0; - _drawRect.top = 0; - _drawRect.right = 320; - _drawRect.bottom = 200; - } - - // In hi-res mode, video will be pixel doubled, so the origin (which - // corresponds to the correct position without pixel doubling) needs to - // be corrected - if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() <= 320) { - _drawRect.left /= 2; - _drawRect.top /= 2; - } + // 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(); @@ -222,8 +214,8 @@ AVIPlayer::IOStatus AVIPlayer::init2x(const int16 x, const int16 y) { _drawRect.top = y; _drawRect.right = x + _decoder->getWidth() * 2; _drawRect.bottom = y + _decoder->getHeight() * 2; - _pixelDouble = true; + init(); return kIOSuccess; @@ -233,42 +225,38 @@ void AVIPlayer::init() { int16 xRes; int16 yRes; - bool useScreenDimensions = false; - if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() > 320) { - useScreenDimensions = true; - } - - // KQ7 1.51 gives video position in screen coordinates, not game - // coordinates, because in SSCI they are passed to Video for Windows, which - // renders as an overlay on the game video. Because we put the video into a - // ScreenItem instead of rendering directly to the hardware surface, the - // coordinates need to be converted to game script coordinates - if (g_sci->getGameId() == GID_KQ7 && getSciVersion() == SCI_VERSION_2_1_EARLY) { - useScreenDimensions = !_pixelDouble; - // This y-translation is arbitrary, based on what roughly centers the - // videos in the game window - _drawRect.translate(-_drawRect.left / 2, -_drawRect.top * 2 / 3); - } - - if (useScreenDimensions) { + // 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; - yRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + 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) { - _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, xRes, yRes, 0, false, false); + 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(_drawRect.left, _drawRect.top), ScaleInfo()); + _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(), ScaleInfo()); g_sci->_gfxFrameout->addScreenItem(*_screenItem); g_sci->_gfxFrameout->frameOut(true); } else { @@ -384,7 +372,7 @@ void AVIPlayer::renderFrame() const { const uint8 *end = (const uint8 *)surface->getPixels() + surface->w * surface->h; while (source != end) { - uint8 value = *source++; + const uint8 value = *source++; *target++ = value == 0 ? 255 : value; } } else { @@ -414,12 +402,19 @@ void AVIPlayer::renderFrame() const { } 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 + // 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) { @@ -434,17 +429,12 @@ void AVIPlayer::renderFrame() const { target += pitch; } - g_system->copyRectToScreen(_scaleBuffer, surface->pitch * 2, _drawRect.left, _drawRect.top, _drawRect.width(), _drawRect.height()); + g_system->copyRectToScreen(_scaleBuffer, surface->pitch * 2, drawRect.left, drawRect.top, _drawRect.width(), _drawRect.height()); } else { - 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; - - mulinc(drawRect, Ratio(screenWidth, scriptWidth), Ratio(screenHeight, scriptHeight)); - g_system->copyRectToScreen(surface->getPixels(), surface->pitch, drawRect.left, drawRect.top, surface->w, surface->h); } + + g_system->updateScreen(); } } @@ -500,6 +490,7 @@ VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) : _isOpen(false), _isInitialized(false), + _yieldFrame(0), _yieldInterval(0), _lastYieldedFrameNo(0), @@ -512,6 +503,7 @@ VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) : _blackLines(false), _leaveScreenBlack(false), _leaveLastFrame(false), + _ignorePalettes(false), _blackoutPlane(nullptr), @@ -538,6 +530,10 @@ VMDPlayer::IOStatus VMDPlayer::open(const Common::String &fileName, const OpenFl 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); @@ -571,6 +567,7 @@ VMDPlayer::IOStatus VMDPlayer::close() { _decoder->close(); _isOpen = false; _isInitialized = false; + _ignorePalettes = false; if (!_planeIsOwned && _screenItem != nullptr) { g_sci->_gfxFrameout->deleteScreenItem(*_screenItem); @@ -625,12 +622,12 @@ VMDPlayer::VMDStatus VMDPlayer::getStatus() const { VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval) { assert(lastFrameNo >= -1); - const int32 maxFrameNo = (int32)(_decoder->getFrameCount() - 1); + const int32 maxFrameNo = _decoder->getFrameCount() - 1; - if ((flags & kEventFlagToFrame) && lastFrameNo > 0) { - _decoder->setEndFrame(MIN<int32>(lastFrameNo, maxFrameNo)); + if (flags & kEventFlagToFrame) { + _yieldFrame = MIN<int32>(lastFrameNo, maxFrameNo); } else { - _decoder->setEndFrame(maxFrameNo); + _yieldFrame = maxFrameNo; } if (flags & kEventFlagYieldToVM) { @@ -705,6 +702,7 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { 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); @@ -767,6 +765,11 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { const int currentFrameNo = _decoder->getCurFrame(); + if (currentFrameNo == _yieldFrame) { + stopFlag = kEventFlagEnd; + break; + } + if (_yieldInterval > 0 && currentFrameNo != _lastYieldedFrameNo && (currentFrameNo % _yieldInterval) == 0 @@ -826,7 +829,7 @@ void VMDPlayer::renderFrame() const { // 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) { + if (dirtyPalette && !_ignorePalettes) { Palette palette; palette.timestamp = g_sci->getTickCount(); for (uint16 i = 0; i < _startColor; ++i) { diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h index 75b8fb2d21..4fc627e674 100644 --- a/engines/sci/graphics/video32.h +++ b/engines/sci/graphics/video32.h @@ -326,6 +326,12 @@ private: 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. @@ -347,6 +353,13 @@ private: #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 @@ -408,6 +421,11 @@ private: 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; diff --git a/engines/sci/parser/vocabulary.cpp b/engines/sci/parser/vocabulary.cpp index a09ba8f3ce..a0f958167d 100644 --- a/engines/sci/parser/vocabulary.cpp +++ b/engines/sci/parser/vocabulary.cpp @@ -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/resource.cpp b/engines/sci/resource.cpp index 8826b0625a..5b57eed123 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -2459,38 +2459,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 70db5909b7..928d571dbc 100644 --- a/engines/sci/resource.h +++ b/engines/sci/resource.h @@ -424,7 +424,6 @@ private: 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 diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 246c031c06..e89d05217c 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -1166,7 +1166,9 @@ 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) { + if ((isCD() && getSciVersion() == SCI_VERSION_1_1) || + getSciVersion() >= SCI_VERSION_2) { + uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType].getOffset(); switch (ingameSetting) { |